@bloomscorp/blooms-ai-embed 0.1.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,146 @@
2
2
 
3
3
  All notable changes to `@bloomscorp/blooms-ai-embed`.
4
4
 
5
+ ## 0.2.0
6
+
7
+ Twelve new components, and the bundle is now code-split so adopting one does not
8
+ cost you all of them.
9
+
10
+ Upgrading is a drop-in **unless you branch on `customElements.get()`** or stage
11
+ `dist/` by hand — see *Action if you are upgrading* below.
12
+
13
+ ### Added
14
+
15
+ - **Twelve new elements.** `<blooms-login>` is no longer the only thing you can
16
+ embed:
17
+
18
+ | | | |
19
+ |---|---|---|
20
+ | `<blooms-crawl-insights>` | `<blooms-sitemap-crawl>` | `<blooms-sitemap-crawl-detail>` |
21
+ | `<blooms-keywords>` | `<blooms-keyword-detail>` | `<blooms-reports>` |
22
+ | `<blooms-gmc-analytics>` | `<blooms-social-media-calendar>` | `<blooms-smc-day-detail>` |
23
+ | `<blooms-influencer-dashboard>` | `<blooms-influencer-detail>` | `<blooms-blog-strategy>` |
24
+
25
+ Each takes its state through attributes or properties rather than a URL, since
26
+ a custom element cannot read the host page's address bar: `project-id` on most,
27
+ plus `crawl-id`, `keyword-id`, `influencer-id`, `strategy-id`, `event-id`,
28
+ `date-iso` where the component addresses one record. Components that used to
29
+ navigate now emit a `bloomsnavigate` event describing where they would have
30
+ gone — your app decides what that means.
31
+
32
+ Declared in `HTMLElementTagNameMap`, so TypeScript knows all thirteen tags.
33
+
34
+ - **`preload(...features)`.** Fetches a component's code before it is needed:
35
+
36
+ ```js
37
+ await BloomsEmbed.preload('crawl-insights', 'keywords');
38
+ ```
39
+
40
+ Optional and never throws — a failed preload just means the chunk is fetched
41
+ later, when the component is actually mounted.
42
+
43
+ ### Changed
44
+
45
+ - **The bundle is code-split: one lazily-fetched chunk per component.** Shipping
46
+ thirteen components eagerly had grown the download to 162 kB gzipped, with the
47
+ three largest still unconverted — so every host paid for every component to use
48
+ one. Now:
49
+
50
+ | | Transfer (gzipped) |
51
+ |---|---|
52
+ | Loader + shared core | **~65 kB** |
53
+ | …plus one component | ~69 kB |
54
+ | …plus all thirteen | ~180 kB |
55
+
56
+ `dist/blooms-embed.js` is a ~3 kB loader; the components live in sibling
57
+ `dist/chunk-*.js` files that it imports by relative path on first use.
58
+
59
+ - **Elements are defined on demand, not at import.** A tag is registered the
60
+ first time an instance of it appears, and the browser upgrades the element that
61
+ was already there. Tags in your HTML behave exactly as before and there is
62
+ nothing to await. One observable difference:
63
+
64
+ ```js
65
+ import { BloomsEmbed } from '@bloomscorp/blooms-ai-embed';
66
+ customElements.get('blooms-crawl-insights'); // undefined until one is used
67
+ ```
68
+
69
+ Setting a **property** before a component's chunk arrives is safe — the value
70
+ is applied once the component is defined. Attributes were never affected.
71
+
72
+ - **Write actions are enabled** for Social Media Calendar, Influencers and Blog
73
+ Strategy — create, edit, reschedule, delete, and the AI generation steps. They
74
+ were read-only in `0.1.x`, with controls that rendered and then returned 403.
75
+
76
+ ### Action if you are upgrading
77
+
78
+ 1. **If you bundle the package** (the recommended setup, and what the Electron
79
+ guide describes) — nothing to do. Webpack, Vite, esbuild and Rollup all follow
80
+ the dynamic imports and emit their own chunks.
81
+
82
+ 2. **If you stage `dist/` by hand**, copy **all** of it. `chunk-*.js` must sit in
83
+ the same directory as `blooms-embed.js`. Copying only the entry leaves you with
84
+ a package that installs cleanly and renders every component as a blank
85
+ element, with one console error per component as the only clue.
86
+
87
+ 3. **Remove any `customElements.get()` guard** around showing a component. It was
88
+ reliable in `0.1.x` because every tag was defined at import; it is not now.
89
+ Place the tag, or call `mount()`, and let the loader do its work.
90
+
91
+ ### Known gaps, stated so they are not surprises
92
+
93
+ - `<blooms-keyword-detail>`'s Search Console section is **empty when embedded**.
94
+ The underlying route takes its project id as a query parameter and carries no
95
+ authorization of its own, and our allowlist pins project ids in the path — so
96
+ exposing it would have handed every embedded session cross-project Search
97
+ Console data. The route is being fixed rather than the allowlist widened.
98
+ - Report generation (Crawl Insights) and bulk delete (Reports) are hidden when
99
+ embedded: their output is delivered to the blooms.ai portal, which a host app
100
+ cannot reach. Starting a job whose result you cannot collect is worse than not
101
+ offering it.
102
+
103
+ ## 0.1.1
104
+
105
+ Everything here came from the first production Electron integration reporting
106
+ against `0.1.0`. No API changes — upgrading is a drop-in.
107
+
108
+ ### Fixed
109
+
110
+ - **Poppins is now self-hosted.** The stylesheet carried 18 `@font-face` rules
111
+ pointing at `fonts.gstatic.com`, inherited from a redundant `@import` in the
112
+ portal's global stylesheet. On a third-party page that is blocked by a
113
+ `font-src 'self'` CSP — so components silently fell back to a system font and
114
+ no longer matched blooms.ai — and it needed the network, contradicting the
115
+ claim that sign-in renders offline.
116
+
117
+ The package now ships the Poppins latin subset (400/500/600/700, **31 kB**) in
118
+ `dist/media/`, referenced relative to the stylesheet so it resolves through
119
+ `assetBase` in every delivery mode. The stylesheet shrank from 12.6 kB to
120
+ 7.8 kB.
121
+
122
+ **Action if you are upgrading:** copy `dist/media/` alongside
123
+ `dist/blooms-embed.css`. Copying only the CSS leaves the fonts behind and the
124
+ `@font-face` rules 404. You can now drop `https://fonts.gstatic.com` from your
125
+ `font-src`.
126
+
127
+ - **Documentation named a host that does not exist.** Examples used
128
+ `https://api.blooms.ai`, which has never resolved. The production API origin is
129
+ `https://z.bloomscorp.com`. Corrected throughout — it matters because this is
130
+ the value integrators pin in a CSP.
131
+
132
+ - **Google-provisioned accounts were described wrongly.** The docs said such
133
+ users should "set a password in the portal first". They cannot: password reset
134
+ refuses them with `409 PASSWORD_NOT_APPLICABLE` by design, so that an account
135
+ governed by Google never grows a second credential. There is no self-service
136
+ path — a blooms.ai admin must provision a separate local account. Embedded
137
+ Google sign-in is planned, not built.
138
+
139
+ ### Changed
140
+
141
+ - The `0.1.0` changelog understated what shipped. `mount()`, `projects()`,
142
+ `project()`, `setProject()` and `can(key, projectId)` were all present in that
143
+ release's type declarations; they are now listed below.
144
+
5
145
  ## 0.1.0
6
146
 
7
147
  First release.
@@ -11,7 +151,12 @@ First release.
11
151
  components locally instead of loading remote code into a renderer, and work offline.
12
152
  - Importing the package defines the custom elements (`<blooms-login>`) as a side effect and
13
153
  exports the typed `BloomsEmbed` SDK: `configure`, `mode`, `manifest`, `login`, `session`,
14
- `resume`, `can`, `logout`, `on`.
154
+ `resume`, `can`, `projects`, `project`, `setProject`, `logout`, `on`, `mount`.
155
+ - Multi-project integrations: one token can span several projects, and `projects()` /
156
+ `setProject()` / `can(key, projectId)` switch between them locally with no re-authentication.
157
+ `can()` returns `false` for a project the session cannot act on rather than falling back.
158
+ - `mount(target, { feature })` renders a component in whichever mode `configure()` resolved —
159
+ the custom element directly, or the iframe shell for an opaque origin.
15
160
  - Hand-written TypeScript declarations for the whole public surface, including
16
161
  `<blooms-login>` in `HTMLElementTagNameMap` and `BloomsEmbed` on `Window`.
17
162
  - Importing outside a browser throws an explanatory error naming the likely cause (SSR, or the
package/README.md CHANGED
@@ -44,7 +44,7 @@ in that project, narrowed further to the features enabled on your token. No sess
44
44
 
45
45
  BloomsEmbed.configure({
46
46
  token: 'blm_embed_9f2c…',
47
- apiBase: 'https://api.blooms.ai',
47
+ apiBase: 'https://z.bloomscorp.com',
48
48
  });
49
49
 
50
50
  BloomsEmbed.on('session', event => {
@@ -59,11 +59,49 @@ That is the whole integration. Importing the package defines the elements and in
59
59
  `<blooms-login>` renders the sign-in form, remembers the session across reloads, and revalidates
60
60
  it on the next load.
61
61
 
62
+ ### Elements in this bundle
63
+
64
+ | Tag | What it renders | Token ceiling it needs |
65
+ |---|---|---|
66
+ | `<blooms-login>` | Sign-in form, session persistence, precise error states | — |
67
+ | `<blooms-crawl-insights>` | Crawl results: pages crawled, broken links, page issues, missing meta, duplicate titles, health score. Starts crawls and re-scans single URLs. | `crawl-insights` (or `crawl`) |
68
+ | `<blooms-sitemap-crawl>` | Crawl from a sitemap: start a run, watch progress, browse history. | `sitemap` (or `crawl`) |
69
+ | `<blooms-sitemap-crawl-detail>` | One sitemap run in detail. Takes `crawl-id`. | `sitemap` (or `crawl`) |
70
+ | `<blooms-keywords>` | Tracked keywords: rank, volume, difficulty, trend. Adds keywords, triggers a rank sync. | `manager` |
71
+ | `<blooms-keyword-detail>` | One keyword in detail. Takes `keyword-id`. Its Search Console section is empty when embedded — see below. | `manager` |
72
+ | `<blooms-reports>` | Report list and downloads. Generation and bulk delete are hidden when embedded. | `manager` |
73
+ | `<blooms-gmc-analytics>` | Google Merchant Centre overview: feed health, disapprovals, account issues. | `manager` |
74
+ | `<blooms-social-media-calendar>` | Content calendar: month and list views, statuses, attachments, assignees. Create, edit, reschedule, delete. Takes `event-id` to deep-link a drawer. | `social-media-calendar` |
75
+ | `<blooms-smc-day-detail>` | One day of the calendar. Takes `date-iso` (`YYYY-MM-DD`), required. | `social-media-calendar` |
76
+ | `<blooms-influencer-dashboard>` | Tracked influencers with tiers, platforms, latest metrics. Create and edit. | `influencer-management` |
77
+ | `<blooms-influencer-detail>` | One influencer: profile, platforms, snapshots. Takes `influencer-id`. | `influencer-management` |
78
+ | `<blooms-blog-strategy>` | The blog authoring workflow: topic → SEO research → outline → draft → visuals → metadata → review → publish → promotion → performance. Takes `strategy-id` and `step`. | `blog-publishing` |
79
+
80
+ A `manager` or `owner` ceiling covers all of them, so one token needs no edit as
81
+ you adopt more. The keys above matter only for a deliberately narrow ceiling —
82
+ and Keywords, Reports and GMC are gated on `manager` itself, so a narrow ceiling
83
+ cannot include them without including everything. A ceiling is never a grant: the
84
+ signed-in user's own per-project permissions are evaluated on every request.
85
+
86
+ Components that would navigate emit `bloomsnavigate` instead of routing — the
87
+ detail is in the Electron guide, and the payload is typed.
88
+
89
+ **Two deliberate gaps.** `<blooms-keyword-detail>`'s Search Console section is
90
+ empty when embedded: that route takes its project id as a query parameter and has
91
+ no authorization of its own, so exposing it would leak cross-project data — being
92
+ fixed server-side. Report generation and bulk delete are hidden because their
93
+ output is delivered to the blooms.ai portal, which your app cannot reach.
94
+
95
+ Feature elements render a sign-in prompt until a session exists, so you can place them
96
+ unconditionally. Each takes an optional `project-id`; omit it and the element follows the
97
+ session's active project, so `BloomsEmbed.setProject()` moves it.
98
+
62
99
  Three things worth knowing:
63
100
 
64
- - **`configure()` once, early.** Elements placed in your HTML upgrade the moment the package
65
- evaluates, which is before your `configure()` call runs. They wait for it rather than failing,
66
- showing a skeleton meanwhile — so a slow `configure()` is a slow first paint, not an error.
101
+ - **`configure()` once, early.** An element placed in your HTML is upgraded when its chunk
102
+ arrives, which may be before or after your `configure()` call. Either order is fine: it waits
103
+ for configuration rather than failing, showing a skeleton meanwhile — so a slow `configure()`
104
+ is a slow first paint, not an error.
67
105
  - **`apiBase` is optional** when you load the bundle from blooms.ai, because it defaults to the
68
106
  bundle's own origin. When you install from npm the bundle's origin is *your* app, so **pass
69
107
  `apiBase` explicitly**.
@@ -79,6 +117,7 @@ Three things worth knowing:
79
117
  | `version` | `string` | Bundle version. |
80
118
  | `configure` | `(config: BloomsEmbedConfig) => void` | Call first. Throws on a missing token or a token that is not `blm_embed_…`. |
81
119
  | `mode` | `() => 'element' \| 'iframe' \| null` | The resolved rendering mode; `null` before `configure()`. |
120
+ | `preload` | `(...features: string[]) => Promise<void>` | Warms a component's lazy chunk before it is used. Optional, never throws. |
82
121
  | `manifest` | `() => Promise<EmbedManifest>` | Pre-login bootstrap: project name, enabled features, theme, branding flag, session TTL. |
83
122
  | `login` | `({ email, password }) => Promise<EmbedSessionState>` | Programmatic alternative to `<blooms-login>`, for hosts with their own form. |
84
123
  | `session` | `() => EmbedSessionState \| null` | The current session, restored from storage by `configure()`. Synchronous. |
@@ -167,8 +206,10 @@ The element handles its own states: loading skeleton, signed-out form, signed-in
167
206
  distinct messages for a wrong password, an account without access to the project, a deactivated
168
207
  or banned account, and rate limiting.
169
208
 
170
- Google sign-in is not offered. Accounts created with Google must set a password in the blooms.ai
171
- portal before they can use this form.
209
+ Google sign-in is not offered, and accounts created with Google **cannot use this form at all** —
210
+ they have no password and cannot set one (password reset refuses them by design, so that a
211
+ Google-governed account never grows a second credential). A blooms.ai admin must provision such
212
+ users a separate local account. Embedded Google sign-in is planned but not built.
172
213
 
173
214
  ---
174
215
 
@@ -210,7 +251,7 @@ These are read with fallbacks, so set only what you care about:
210
251
  ```js
211
252
  BloomsEmbed.configure({
212
253
  token: 'blm_embed_…',
213
- apiBase: 'https://api.blooms.ai',
254
+ apiBase: 'https://z.bloomscorp.com',
214
255
  theme: { '--brand-primary': '#0f766e', '--radius-lg': '10px' },
215
256
  });
216
257
  ```
@@ -236,7 +277,7 @@ React version):
236
277
  import { useEffect } from 'react';
237
278
  import { BloomsEmbed } from '@bloomscorp/blooms-ai-embed';
238
279
 
239
- BloomsEmbed.configure({ token: 'blm_embed_…', apiBase: 'https://api.blooms.ai' });
280
+ BloomsEmbed.configure({ token: 'blm_embed_…', apiBase: 'https://z.bloomscorp.com' });
240
281
 
241
282
  export function Login({ onSession }) {
242
283
  useEffect(() => BloomsEmbed.on('session', e => onSession(e.session)), [onSession]);
@@ -339,7 +380,7 @@ cause is an unregistered origin or a missing privileged-scheme registration, not
339
380
 
340
381
  ```
341
382
  default-src 'self' app:;
342
- connect-src 'self' https://api.blooms.ai;
383
+ connect-src 'self' https://z.bloomscorp.com;
343
384
  style-src 'self' 'unsafe-inline';
344
385
  img-src 'self' data: https:;
345
386
  ```
@@ -381,7 +422,7 @@ and add `BloomsEmbed` to `Window`.
381
422
  ```ts
382
423
  import { BloomsEmbed, type EmbedSessionState } from '@bloomscorp/blooms-ai-embed';
383
424
 
384
- BloomsEmbed.configure({ token: 'blm_embed_…', apiBase: 'https://api.blooms.ai' });
425
+ BloomsEmbed.configure({ token: 'blm_embed_…', apiBase: 'https://z.bloomscorp.com' });
385
426
 
386
427
  const session: EmbedSessionState | null = await BloomsEmbed.resume();
387
428
  if (session && BloomsEmbed.can('analytics')) { /* … */ }
@@ -407,6 +448,40 @@ In Electron, that also means: import it in the **renderer**, never in the main p
407
448
 
408
449
  ---
409
450
 
451
+ ## Lazy loading
452
+
453
+ The bundle is code-split: a small loader plus one chunk per component, fetched the first time that
454
+ component is used. A page embedding one component downloads one component (~69 kB gzipped, against
455
+ ~180 kB for the full set).
456
+
457
+ Nothing in your markup changes. A tag in your HTML is defined when its chunk arrives and the
458
+ browser upgrades the element that was already there. You never await anything.
459
+
460
+ Three things worth knowing:
461
+
462
+ - **Do not gate your UI on `customElements.get(tag)`.** It returns `undefined` until that
463
+ component has been used once. Before code splitting it was defined immediately, so a check like
464
+ this used to pass:
465
+
466
+ ```js
467
+ import { BloomsEmbed } from '@bloomscorp/blooms-ai-embed';
468
+ if (customElements.get('blooms-keywords')) { /* no longer true up front */ }
469
+ ```
470
+
471
+ - **Setting properties early is safe.** `el.projectId = '...'` on an element whose chunk has not
472
+ arrived is preserved and applied once the component is defined. Attributes were never affected.
473
+
474
+ - **Warm a chunk if you know what is next:**
475
+
476
+ ```js
477
+ await BloomsEmbed.preload('crawl-insights', 'keywords');
478
+ ```
479
+
480
+ If you stage `dist/` by hand instead of importing the package, copy **all** of it — `chunk-*.js`
481
+ must stay beside `blooms-embed.js`, and `media/*.woff2` beside the stylesheet.
482
+
483
+ ---
484
+
410
485
  ## Security notes worth reading once
411
486
 
412
487
  - The embed token is publishable, but it is not a secret to be careless with: it is what ties
@@ -1 +1 @@
1
- @charset "UTF-8";@font-face{font-family:Poppins;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLDz8Z11lFc-K.woff2) format("woff2");unicode-range:U+0900-097F,U+1CD0-1CF9,U+200C-200D,U+20A8,U+20B9,U+20F0,U+25CC,U+A830-A839,U+A8E0-A8FF,U+11B00-11B09}@font-face{font-family:Poppins;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLDz8Z1JlFc-K.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Poppins;font-style:normal;font-weight:300;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLDz8Z1xlFQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Poppins;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiEyp8kv8JHgFVrJJbecmNE.woff2) format("woff2");unicode-range:U+0900-097F,U+1CD0-1CF9,U+200C-200D,U+20A8,U+20B9,U+20F0,U+25CC,U+A830-A839,U+A8E0-A8FF,U+11B00-11B09}@font-face{font-family:Poppins;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiEyp8kv8JHgFVrJJnecmNE.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Poppins;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiEyp8kv8JHgFVrJJfecg.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Poppins;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLGT9Z11lFc-K.woff2) format("woff2");unicode-range:U+0900-097F,U+1CD0-1CF9,U+200C-200D,U+20A8,U+20B9,U+20F0,U+25CC,U+A830-A839,U+A8E0-A8FF,U+11B00-11B09}@font-face{font-family:Poppins;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLGT9Z1JlFc-K.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Poppins;font-style:normal;font-weight:500;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLGT9Z1xlFQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Poppins;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLEj6Z11lFc-K.woff2) format("woff2");unicode-range:U+0900-097F,U+1CD0-1CF9,U+200C-200D,U+20A8,U+20B9,U+20F0,U+25CC,U+A830-A839,U+A8E0-A8FF,U+11B00-11B09}@font-face{font-family:Poppins;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLEj6Z1JlFc-K.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Poppins;font-style:normal;font-weight:600;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLEj6Z1xlFQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Poppins;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLCz7Z11lFc-K.woff2) format("woff2");unicode-range:U+0900-097F,U+1CD0-1CF9,U+200C-200D,U+20A8,U+20B9,U+20F0,U+25CC,U+A830-A839,U+A8E0-A8FF,U+11B00-11B09}@font-face{font-family:Poppins;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLCz7Z1JlFc-K.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Poppins;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLCz7Z1xlFQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Poppins;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLDD4Z11lFc-K.woff2) format("woff2");unicode-range:U+0900-097F,U+1CD0-1CF9,U+200C-200D,U+20A8,U+20B9,U+20F0,U+25CC,U+A830-A839,U+A8E0-A8FF,U+11B00-11B09}@font-face{font-family:Poppins;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLDD4Z1JlFc-K.woff2) format("woff2");unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Poppins;font-style:normal;font-weight:800;font-display:swap;src:url(https://fonts.gstatic.com/s/poppins/v24/pxiByp8kv8JHgFVrLDD4Z1xlFQ.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--color-primary-soft: #E3D8F1;--color-secondary: #DABECA;--color-accent: #E84855;--color-accent-soft: #f5c5c9;--color-text: #5D5F71;--color-text-muted: #9395a5;--color-bg: #f7f5fb;--color-card-bg: #FFFFFF;--color-sidebar-bg: #FFFFFF;--color-rail-bg: #f3f0f8;--color-border: #ede9f6;--color-hover: #f5f2fb;--font-sans: "Poppins", system-ui, -apple-system, sans-serif;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 16px;--radius-full: 9999px;--shadow-sm: 0 1px 4px rgba(0, 0, 0, .06);--shadow-md: 0 4px 16px rgba(0, 0, 0, .08);--shadow-lg: 0 8px 32px rgba(0, 0, 0, .1);--z-agent-button: 9000;--z-agent-widget: 9100;--z-agent-menu: 9200;--z-agent-banner: 9300}@keyframes agent-halo-spin{to{transform:rotate(360deg)}}@keyframes agent-halo-pulse{0%,to{opacity:.8}50%{opacity:1}}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;-webkit-text-size-adjust:100%;scroll-behavior:smooth}body{font-family:var(--font-sans);font-size:14px;color:var(--color-text);background-color:var(--color-bg);line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.bui-tooltip{position:fixed;z-index:100000;max-width:260px;padding:7px 11px;border-radius:12px;background:#fff;color:#1d1d1f;border:1px solid rgba(0,0,0,.06);box-shadow:0 8px 26px #00000029,0 2px 6px #0000001a;font-family:var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);font-size:12.5px;font-weight:500;line-height:1.45;letter-spacing:.01em;text-align:left;white-space:normal;word-break:break-word;pointer-events:none;opacity:0;transform:scale(.92);transform-origin:center;transition:opacity .13s ease,transform .14s cubic-bezier(.34,1.56,.64,1)}.bui-tooltip--in{opacity:1;transform:scale(1)}.bui-tooltip__tail{position:absolute;z-index:-1;width:9px;height:9px;background:#fff;border:1px solid rgba(0,0,0,.06);border-radius:2px;transform:rotate(45deg)}.bui-tooltip__tail.is-bottom{bottom:-5px}.bui-tooltip__tail.is-top{top:-5px}.bui-tooltip__tail.is-right{right:-5px}.bui-tooltip__tail.is-left{left:-5px}@media (prefers-reduced-motion: reduce){.bui-tooltip{transition:opacity .1s ease;transform:none}.bui-tooltip--in{transform:none}}h1,h2,h3,h4,h5,h6{font-family:var(--font-sans);font-weight:700;line-height:1.3;color:var(--color-text)}a{color:var(--color-accent);text-decoration:none}a:hover{text-decoration:underline}p{line-height:1.7}.btn{display:inline-flex;align-items:center;gap:6px;padding:9px 18px;border-radius:var(--radius-md);font-family:var(--font-sans);font-size:13px;font-weight:600;cursor:pointer;border:1px solid transparent;transition:background .15s,border-color .15s,box-shadow .15s,opacity .15s;text-decoration:none;white-space:nowrap}.btn:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.btn-primary{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.btn-primary:hover{background:#d13d4a;border-color:#d13d4a;box-shadow:0 2px 8px #e848554d;text-decoration:none}.btn-outline{background:transparent;color:var(--color-text);border-color:var(--color-border)}.btn-outline:hover{background:var(--color-hover);border-color:#d5d0e4;text-decoration:none}.btn-ghost{background:transparent;color:var(--color-text-muted);border-color:transparent}.btn-ghost:hover{background:var(--color-hover);color:var(--color-text)}.btn-sm{padding:6px 12px;font-size:12px;border-radius:var(--radius-sm)}.btn-lg{padding:12px 24px;font-size:15px;border-radius:12px}input,textarea,select{font-family:var(--font-sans);font-size:14px;color:var(--color-text)}.text-muted{color:var(--color-text-muted)}.text-accent{color:var(--color-accent)}.text-sm{font-size:12px}.text-xs{font-size:11px}.font-semibold{font-weight:600}.font-bold{font-weight:700}.flex{display:flex}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-2{gap:8px}.gap-4{gap:16px}.w-full{width:100%}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--color-border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--color-secondary)}router-outlet+*{animation:fadeInUp .2s ease}@keyframes fadeInUp{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.pane-section{display:flex;flex-direction:column;gap:24px;max-width:520px}.pane-hd{display:flex;flex-direction:column;gap:4px;padding-bottom:20px;border-bottom:1px solid var(--color-border)}.pane-hd--row{flex-direction:row;align-items:flex-start;justify-content:space-between;gap:16px}.pane-title{font-size:15px;font-weight:700;color:var(--color-text);margin:0}.pane-desc{font-size:12px;color:var(--color-text-muted);margin:0;line-height:1.5}.pane-form{display:flex;flex-direction:column;gap:18px}.pane-success{font-size:12px;font-weight:500;color:#065f46;background:#d1fae5;border:1px solid #a7f3d0;border-radius:var(--radius-sm);padding:8px 12px;margin:0}.field{display:flex;flex-direction:column;gap:6px}.field__label{font-size:12px;font-weight:600;color:var(--color-text);letter-spacing:.02em;text-transform:uppercase}.field__input{padding:9px 13px;border:1px solid var(--color-border);border-radius:var(--radius-md);font-family:var(--font-sans);font-size:13px;color:var(--color-text);background:var(--color-bg);outline:none;transition:border-color .15s,box-shadow .15s;width:100%}.field__input:focus{border-color:var(--color-accent);box-shadow:0 0 0 3px #e8485514}.field__input--locked{opacity:.55;cursor:not-allowed;color:var(--color-text-muted)}.field__input:disabled{cursor:not-allowed}.field textarea.field__input{resize:vertical}.field__hint{font-size:11px;color:var(--color-text-muted)}@media (max-width: 768px){.btn{padding:8px 14px}.detail-table-wrap,.pages-table-wrap,.diff-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}.result-summary,.insight-grid,.pr-stats-grid{grid-template-columns:1fr 1fr!important}.sitemap-form{flex-direction:column}.pr-detail__cols,.diff-screen__body{grid-template-columns:1fr!important}.diff-files{display:none}.diff-files.mobile-show{display:block}.pr-filters__top{flex-direction:column;align-items:flex-start}.pr-filters__right{width:100%}.pr-search{max-width:100%!important;flex:1}.repo-card{flex-direction:column;align-items:flex-start}.repo-card__right{align-self:flex-start;flex-wrap:wrap}.history-item{flex-direction:column}.results-toolbar{flex-direction:column;align-items:flex-start}.diff-screen__title-row{flex-direction:column}.diff-screen__header-right{align-self:flex-start}}
1
+ @charset "UTF-8";:root{--color-primary-soft: #E3D8F1;--color-secondary: #DABECA;--color-accent: #E84855;--color-accent-soft: #f5c5c9;--color-text: #5D5F71;--color-text-muted: #9395a5;--color-bg: #f7f5fb;--color-card-bg: #FFFFFF;--color-sidebar-bg: #FFFFFF;--color-rail-bg: #f3f0f8;--color-border: #ede9f6;--color-hover: #f5f2fb;--font-sans: "Poppins", system-ui, -apple-system, sans-serif;--radius-sm: 6px;--radius-md: 10px;--radius-lg: 16px;--radius-full: 9999px;--shadow-sm: 0 1px 4px rgba(0, 0, 0, .06);--shadow-md: 0 4px 16px rgba(0, 0, 0, .08);--shadow-lg: 0 8px 32px rgba(0, 0, 0, .1);--z-agent-button: 9000;--z-agent-widget: 9100;--z-agent-menu: 9200;--z-agent-banner: 9300}@keyframes agent-halo-spin{to{transform:rotate(360deg)}}@keyframes agent-halo-pulse{0%,to{opacity:.8}50%{opacity:1}}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}html{font-size:16px;-webkit-text-size-adjust:100%;scroll-behavior:smooth}body{font-family:var(--font-sans);font-size:14px;color:var(--color-text);background-color:var(--color-bg);line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.bui-tooltip{position:fixed;z-index:100000;max-width:260px;padding:7px 11px;border-radius:12px;background:#fff;color:#1d1d1f;border:1px solid rgba(0,0,0,.06);box-shadow:0 8px 26px #00000029,0 2px 6px #0000001a;font-family:var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif);font-size:12.5px;font-weight:500;line-height:1.45;letter-spacing:.01em;text-align:left;white-space:normal;word-break:break-word;pointer-events:none;opacity:0;transform:scale(.92);transform-origin:center;transition:opacity .13s ease,transform .14s cubic-bezier(.34,1.56,.64,1)}.bui-tooltip--in{opacity:1;transform:scale(1)}.bui-tooltip__tail{position:absolute;z-index:-1;width:9px;height:9px;background:#fff;border:1px solid rgba(0,0,0,.06);border-radius:2px;transform:rotate(45deg)}.bui-tooltip__tail.is-bottom{bottom:-5px}.bui-tooltip__tail.is-top{top:-5px}.bui-tooltip__tail.is-right{right:-5px}.bui-tooltip__tail.is-left{left:-5px}@media (prefers-reduced-motion: reduce){.bui-tooltip{transition:opacity .1s ease;transform:none}.bui-tooltip--in{transform:none}}h1,h2,h3,h4,h5,h6{font-family:var(--font-sans);font-weight:700;line-height:1.3;color:var(--color-text)}a{color:var(--color-accent);text-decoration:none}a:hover{text-decoration:underline}p{line-height:1.7}.btn{display:inline-flex;align-items:center;gap:6px;padding:9px 18px;border-radius:var(--radius-md);font-family:var(--font-sans);font-size:13px;font-weight:600;cursor:pointer;border:1px solid transparent;transition:background .15s,border-color .15s,box-shadow .15s,opacity .15s;text-decoration:none;white-space:nowrap}.btn:disabled{opacity:.5;cursor:not-allowed;pointer-events:none}.btn-primary{background:var(--color-accent);color:#fff;border-color:var(--color-accent)}.btn-primary:hover{background:#d13d4a;border-color:#d13d4a;box-shadow:0 2px 8px #e848554d;text-decoration:none}.btn-outline{background:transparent;color:var(--color-text);border-color:var(--color-border)}.btn-outline:hover{background:var(--color-hover);border-color:#d5d0e4;text-decoration:none}.btn-ghost{background:transparent;color:var(--color-text-muted);border-color:transparent}.btn-ghost:hover{background:var(--color-hover);color:var(--color-text)}.btn-sm{padding:6px 12px;font-size:12px;border-radius:var(--radius-sm)}.btn-lg{padding:12px 24px;font-size:15px;border-radius:12px}input,textarea,select{font-family:var(--font-sans);font-size:14px;color:var(--color-text)}.text-muted{color:var(--color-text-muted)}.text-accent{color:var(--color-accent)}.text-sm{font-size:12px}.text-xs{font-size:11px}.font-semibold{font-weight:600}.font-bold{font-weight:700}.flex{display:flex}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-2{gap:8px}.gap-4{gap:16px}.w-full{width:100%}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--color-border);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--color-secondary)}router-outlet+*{animation:fadeInUp .2s ease}@keyframes fadeInUp{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.pane-section{display:flex;flex-direction:column;gap:24px;max-width:520px}.pane-hd{display:flex;flex-direction:column;gap:4px;padding-bottom:20px;border-bottom:1px solid var(--color-border)}.pane-hd--row{flex-direction:row;align-items:flex-start;justify-content:space-between;gap:16px}.pane-title{font-size:15px;font-weight:700;color:var(--color-text);margin:0}.pane-desc{font-size:12px;color:var(--color-text-muted);margin:0;line-height:1.5}.pane-form{display:flex;flex-direction:column;gap:18px}.pane-success{font-size:12px;font-weight:500;color:#065f46;background:#d1fae5;border:1px solid #a7f3d0;border-radius:var(--radius-sm);padding:8px 12px;margin:0}.field{display:flex;flex-direction:column;gap:6px}.field__label{font-size:12px;font-weight:600;color:var(--color-text);letter-spacing:.02em;text-transform:uppercase}.field__input{padding:9px 13px;border:1px solid var(--color-border);border-radius:var(--radius-md);font-family:var(--font-sans);font-size:13px;color:var(--color-text);background:var(--color-bg);outline:none;transition:border-color .15s,box-shadow .15s;width:100%}.field__input:focus{border-color:var(--color-accent);box-shadow:0 0 0 3px #e8485514}.field__input--locked{opacity:.55;cursor:not-allowed;color:var(--color-text-muted)}.field__input:disabled{cursor:not-allowed}.field textarea.field__input{resize:vertical}.field__hint{font-size:11px;color:var(--color-text-muted)}@media (max-width: 768px){.btn{padding:8px 14px}.detail-table-wrap,.pages-table-wrap,.diff-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}.result-summary,.insight-grid,.pr-stats-grid{grid-template-columns:1fr 1fr!important}.sitemap-form{flex-direction:column}.pr-detail__cols,.diff-screen__body{grid-template-columns:1fr!important}.diff-files{display:none}.diff-files.mobile-show{display:block}.pr-filters__top{flex-direction:column;align-items:flex-start}.pr-filters__right{width:100%}.pr-search{max-width:100%!important;flex:1}.repo-card{flex-direction:column;align-items:flex-start}.repo-card__right{align-self:flex-start;flex-wrap:wrap}.history-item{flex-direction:column}.results-toolbar{flex-direction:column;align-items:flex-start}.diff-screen__title-row{flex-direction:column}.diff-screen__header-right{align-self:flex-start}}@font-face{font-family:Poppins;font-style:normal;font-weight:400;font-display:swap;src:url("./media/poppins-latin-400.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Poppins;font-style:normal;font-weight:500;font-display:swap;src:url("./media/poppins-latin-500.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Poppins;font-style:normal;font-weight:600;font-display:swap;src:url("./media/poppins-latin-600.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Poppins;font-style:normal;font-weight:700;font-display:swap;src:url("./media/poppins-latin-700.woff2") format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}