@avocadostudio-ai/preview-adapter 0.19.0 → 0.21.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/dist/bridge-controller.js +3 -2
- package/dist/bridge-functions.d.ts +12 -1
- package/dist/bridge-functions.js +102 -68
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/src/styles.css +86 -39
|
@@ -258,8 +258,9 @@ export function attachBridge(config) {
|
|
|
258
258
|
const blockId = String(msg.payload.blockId ?? "");
|
|
259
259
|
const editablePath = String(msg.payload.editablePath ?? "");
|
|
260
260
|
const active = Boolean(msg.payload.active);
|
|
261
|
-
|
|
262
|
-
|
|
261
|
+
const scope = msg.payload.scope === "page" ? "page" : "block";
|
|
262
|
+
state.activeShimmer = active ? { blockId, editablePath, scope } : null;
|
|
263
|
+
applyAiFieldLoading(blockId, editablePath, active, scope);
|
|
263
264
|
}
|
|
264
265
|
if (msg.type === "scrollToBlock") {
|
|
265
266
|
const blockId = String(msg.payload.blockId ?? "");
|
|
@@ -231,7 +231,17 @@ export declare function removeOverlayControls(deleteConfirmTimer: {
|
|
|
231
231
|
current: number | null;
|
|
232
232
|
}): void;
|
|
233
233
|
export declare function clearAllHighlights(): void;
|
|
234
|
-
|
|
234
|
+
/** What the editor says is being worked on: one block, or the whole page. */
|
|
235
|
+
export type AiLoadingScope = "block" | "page";
|
|
236
|
+
/**
|
|
237
|
+
* True when everything `applyAiFieldLoading` would paint for this target is
|
|
238
|
+
* already on the page. React re-renders drop the block overlay without
|
|
239
|
+
* touching the page frame, so the two are checked separately.
|
|
240
|
+
*/
|
|
241
|
+
export declare function isAiFieldLoadingApplied(blockId: string, editablePath: string, scope?: AiLoadingScope): boolean;
|
|
242
|
+
/** Remove every shimmer this module painted and give blocks their position back. */
|
|
243
|
+
export declare function clearAiFieldLoading(): void;
|
|
244
|
+
export declare function applyAiFieldLoading(blockId: string, editablePath: string, active: boolean, scope?: AiLoadingScope): void;
|
|
235
245
|
export declare function cleanupOverlayElements(): void;
|
|
236
246
|
/** Callbacks for communicating user actions back to the editor/widget. */
|
|
237
247
|
export type BridgeCallbacks = {
|
|
@@ -359,6 +369,7 @@ export type BridgeState = {
|
|
|
359
369
|
activeShimmer: {
|
|
360
370
|
blockId: string;
|
|
361
371
|
editablePath: string;
|
|
372
|
+
scope: AiLoadingScope;
|
|
362
373
|
} | null;
|
|
363
374
|
};
|
|
364
375
|
export declare function createBridgeState(): BridgeState;
|
package/dist/bridge-functions.js
CHANGED
|
@@ -703,72 +703,109 @@ export function removeOverlayControls(deleteConfirmTimer) {
|
|
|
703
703
|
export function clearAllHighlights() {
|
|
704
704
|
document.querySelectorAll(".editor-highlight").forEach((node) => node.classList.remove("editor-highlight"));
|
|
705
705
|
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
706
|
+
const AIFX_KEY_ATTR = "data-aifx-key";
|
|
707
|
+
const AIFX_PREV_POSITION_ATTR = "data-aifx-prev-position";
|
|
708
|
+
const AIFX_SPARKLE_SVG = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/><path d="M4 17v2"/><path d="M5 18H3"/></svg>';
|
|
709
|
+
function aiLoadingKey(scope, blockId, editablePath) {
|
|
710
|
+
return `${scope}|${blockId}|${editablePath}`;
|
|
711
|
+
}
|
|
712
|
+
function createSparkle() {
|
|
713
|
+
const sparkle = document.createElement("div");
|
|
714
|
+
sparkle.className = "aifx-shimmer-sparkle";
|
|
715
|
+
sparkle.innerHTML = AIFX_SPARKLE_SVG;
|
|
716
|
+
return sparkle;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* True when everything `applyAiFieldLoading` would paint for this target is
|
|
720
|
+
* already on the page. React re-renders drop the block overlay without
|
|
721
|
+
* touching the page frame, so the two are checked separately.
|
|
722
|
+
*/
|
|
723
|
+
export function isAiFieldLoadingApplied(blockId, editablePath, scope = "block") {
|
|
724
|
+
const key = aiLoadingKey(scope, blockId, editablePath);
|
|
725
|
+
if (scope === "page") {
|
|
726
|
+
const frame = document.querySelector(".aifx-page-shimmer");
|
|
727
|
+
if (frame?.getAttribute(AIFX_KEY_ATTR) !== key)
|
|
728
|
+
return false;
|
|
715
729
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
if (!parent.style.cssText.trim())
|
|
739
|
-
parent.removeAttribute("style");
|
|
740
|
-
}
|
|
730
|
+
if (!blockId)
|
|
731
|
+
return scope === "page" || !document.querySelector(".aifx-shimmer-overlay");
|
|
732
|
+
const node = findBlockNode(blockId);
|
|
733
|
+
if (!node)
|
|
734
|
+
return false;
|
|
735
|
+
const overlay = Array.from(node.children).find((child) => child.classList.contains("aifx-shimmer-overlay"));
|
|
736
|
+
return overlay?.getAttribute(AIFX_KEY_ATTR) === key;
|
|
737
|
+
}
|
|
738
|
+
/** Remove every shimmer this module painted and give blocks their position back. */
|
|
739
|
+
export function clearAiFieldLoading() {
|
|
740
|
+
document
|
|
741
|
+
.querySelectorAll(".aifx-shimmer-overlay, .aifx-shimmer-sparkle, .aifx-page-shimmer")
|
|
742
|
+
.forEach((el) => el.remove());
|
|
743
|
+
document.querySelectorAll(`[${AIFX_PREV_POSITION_ATTR}]`).forEach((node) => {
|
|
744
|
+
const prev = node.getAttribute(AIFX_PREV_POSITION_ATTR) ?? "";
|
|
745
|
+
if (prev)
|
|
746
|
+
node.style.position = prev;
|
|
747
|
+
else
|
|
748
|
+
node.style.removeProperty("position");
|
|
749
|
+
node.removeAttribute(AIFX_PREV_POSITION_ATTR);
|
|
750
|
+
if (!node.style.cssText.trim())
|
|
751
|
+
node.removeAttribute("style");
|
|
741
752
|
});
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
753
|
+
}
|
|
754
|
+
/*
|
|
755
|
+
* Paint "AI is working on this" into the preview.
|
|
756
|
+
*
|
|
757
|
+
* `scope: "page"` frames the viewport, because the request is about the page
|
|
758
|
+
* rather than any one block — "review this page" writes no ops, so there is no
|
|
759
|
+
* block to point at, and pointing at the selected one says the wrong thing.
|
|
760
|
+
* A `blockId` alongside it adds the sweep to the block currently being
|
|
761
|
+
* written, so a page-wide rewrite shows both the scope and the progress.
|
|
762
|
+
*
|
|
763
|
+
* There is deliberately no fallback. This used to paint the selected block, or
|
|
764
|
+
* failing that the first block on the page, whenever it was not told which
|
|
765
|
+
* block — which is how a page review came to shimmer the site header. A block
|
|
766
|
+
* that is not in the DOM yet paints nothing; the editor re-sends while the
|
|
767
|
+
* turn runs, and `smoothRefresh` re-applies after a re-render.
|
|
768
|
+
*/
|
|
769
|
+
export function applyAiFieldLoading(blockId, editablePath, active, scope = "block") {
|
|
770
|
+
blockId = blockId ?? "";
|
|
771
|
+
editablePath = editablePath ?? "";
|
|
772
|
+
if (active && isAiFieldLoadingApplied(blockId, editablePath, scope))
|
|
773
|
+
return;
|
|
774
|
+
clearAiFieldLoading();
|
|
775
|
+
if (!active)
|
|
776
|
+
return;
|
|
777
|
+
const key = aiLoadingKey(scope, blockId, editablePath);
|
|
778
|
+
if (scope === "page" && document.body) {
|
|
779
|
+
const frame = document.createElement("div");
|
|
780
|
+
frame.className = "aifx-page-shimmer";
|
|
781
|
+
frame.setAttribute(AIFX_KEY_ATTR, key);
|
|
782
|
+
frame.appendChild(createSparkle());
|
|
783
|
+
document.body.appendChild(frame);
|
|
784
|
+
}
|
|
785
|
+
const block = blockId ? findBlockNode(blockId) : null;
|
|
786
|
+
if (!block)
|
|
787
|
+
return;
|
|
788
|
+
// Paint on the block, not the field: field nodes are often inline and small
|
|
789
|
+
// enough to make the sweep imperceptible.
|
|
790
|
+
const overlay = document.createElement("div");
|
|
791
|
+
overlay.className = "aifx-shimmer-overlay";
|
|
792
|
+
overlay.setAttribute(AIFX_KEY_ATTR, key);
|
|
793
|
+
// The overlay is absolutely positioned, so the block must be a containing
|
|
794
|
+
// block — but only a static one needs help. Forcing `relative` on a block
|
|
795
|
+
// whose stylesheet makes it sticky or fixed un-sticks it for the whole turn.
|
|
796
|
+
if (getComputedStyle(block).position === "static") {
|
|
797
|
+
if (!block.hasAttribute(AIFX_PREV_POSITION_ATTR))
|
|
798
|
+
block.setAttribute(AIFX_PREV_POSITION_ATTR, block.style.position);
|
|
799
|
+
block.style.position = "relative";
|
|
767
800
|
}
|
|
801
|
+
block.appendChild(overlay);
|
|
802
|
+
// One sparkle per shimmer: the page frame carries it when there is one.
|
|
803
|
+
if (scope === "block")
|
|
804
|
+
block.appendChild(createSparkle());
|
|
768
805
|
}
|
|
769
806
|
export function cleanupOverlayElements() {
|
|
770
807
|
document
|
|
771
|
-
.querySelectorAll(".aifx-shimmer-overlay, .aifx-shimmer-sparkle, .editor-image-change-btn, .editor-integration-warning")
|
|
808
|
+
.querySelectorAll(".aifx-shimmer-overlay, .aifx-shimmer-sparkle, .aifx-page-shimmer, .editor-image-change-btn, .editor-integration-warning")
|
|
772
809
|
.forEach((el) => el.remove());
|
|
773
810
|
document.documentElement.removeAttribute("data-editor-active");
|
|
774
811
|
document.documentElement.removeAttribute("data-editor-selection-mode");
|
|
@@ -1575,17 +1612,14 @@ export function createBridgeFunctions(state, callbacks, config) {
|
|
|
1575
1612
|
// Re-apply shimmer after DOM settles — poll briefly since React
|
|
1576
1613
|
// reconciliation after router.refresh() is async and rAF isn't enough.
|
|
1577
1614
|
if (state.activeShimmer) {
|
|
1578
|
-
const { blockId, editablePath } = state.activeShimmer;
|
|
1615
|
+
const { blockId, editablePath, scope } = state.activeShimmer;
|
|
1579
1616
|
let attempts = 0;
|
|
1580
1617
|
const tryReapply = () => {
|
|
1581
|
-
|
|
1618
|
+
const current = state.activeShimmer;
|
|
1619
|
+
if (!current || current.blockId !== blockId || current.scope !== scope)
|
|
1582
1620
|
return;
|
|
1583
|
-
applyAiFieldLoading(blockId, editablePath, true);
|
|
1584
|
-
|
|
1585
|
-
const hasOverlay = node
|
|
1586
|
-
? Boolean(node.querySelector(".aifx-shimmer-overlay"))
|
|
1587
|
-
: Boolean(document.querySelector(".aifx-shimmer-overlay"));
|
|
1588
|
-
if (!hasOverlay && attempts < 8) {
|
|
1621
|
+
applyAiFieldLoading(blockId, editablePath, true, scope);
|
|
1622
|
+
if (!isAiFieldLoadingApplied(blockId, editablePath, scope) && attempts < 8) {
|
|
1589
1623
|
attempts++;
|
|
1590
1624
|
setTimeout(tryReapply, 60);
|
|
1591
1625
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { PreviewBridge } from "./preview-bridge.tsx";
|
|
|
2
2
|
export { PreviewBridgeCore } from "./preview-bridge-core.tsx";
|
|
3
3
|
export type { PreviewBridgeConfig, PreviewBridgeCoreProps } from "./preview-bridge-core.tsx";
|
|
4
4
|
export { getPreviewWrapperProps } from "./selectable.ts";
|
|
5
|
-
export { createBridgeFunctions, createBridgeState, findBlockNode, findEditableNode, scrollBlockIntoView, parseListItemPath, supportsInlineEditablePath, readNodeText, placeCaretAtEnd, orderedBlockNodes, blockOrderIndex, computeMoveAfter, computeInsertBefore, groupListItemNodes, commonItemRoot, markdownToHtml, withPreviewParams, setNestedLabelsVisibility, clearChildFocus, clearListItemSelection, removeOverlayControls, clearAllHighlights, showSkeleton, removeSkeletons, ensureBlockBadges, applyAiFieldLoading, cleanupOverlayElements, isImageEditable, isInlineEditable, isMultilineEditable, missingBlockWrapperWarning, previewIntegrationWarnings, } from "./bridge-functions.ts";
|
|
6
|
-
export type { BridgeCallbacks, BridgeState, BridgeFunctions } from "./bridge-functions.ts";
|
|
5
|
+
export { createBridgeFunctions, createBridgeState, findBlockNode, findEditableNode, scrollBlockIntoView, parseListItemPath, supportsInlineEditablePath, readNodeText, placeCaretAtEnd, orderedBlockNodes, blockOrderIndex, computeMoveAfter, computeInsertBefore, groupListItemNodes, commonItemRoot, markdownToHtml, withPreviewParams, setNestedLabelsVisibility, clearChildFocus, clearListItemSelection, removeOverlayControls, clearAllHighlights, showSkeleton, removeSkeletons, ensureBlockBadges, applyAiFieldLoading, clearAiFieldLoading, isAiFieldLoadingApplied, cleanupOverlayElements, isImageEditable, isInlineEditable, isMultilineEditable, missingBlockWrapperWarning, previewIntegrationWarnings, } from "./bridge-functions.ts";
|
|
6
|
+
export type { AiLoadingScope, BridgeCallbacks, BridgeState, BridgeFunctions } from "./bridge-functions.ts";
|
|
7
7
|
export { LivePreviewProvider, useLivePreviewBlocks, useLivePreviewBridgeApi, } from "./live-preview-store.tsx";
|
|
8
8
|
export type { LivePreviewPage, LivePreviewBridgeApi } from "./live-preview-store.tsx";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { PreviewBridge } from "./preview-bridge.js";
|
|
2
2
|
export { PreviewBridgeCore } from "./preview-bridge-core.js";
|
|
3
3
|
export { getPreviewWrapperProps } from "./selectable.js";
|
|
4
|
-
export { createBridgeFunctions, createBridgeState, findBlockNode, findEditableNode, scrollBlockIntoView, parseListItemPath, supportsInlineEditablePath, readNodeText, placeCaretAtEnd, orderedBlockNodes, blockOrderIndex, computeMoveAfter, computeInsertBefore, groupListItemNodes, commonItemRoot, markdownToHtml, withPreviewParams, setNestedLabelsVisibility, clearChildFocus, clearListItemSelection, removeOverlayControls, clearAllHighlights, showSkeleton, removeSkeletons, ensureBlockBadges, applyAiFieldLoading, cleanupOverlayElements, isImageEditable, isInlineEditable, isMultilineEditable, missingBlockWrapperWarning, previewIntegrationWarnings, } from "./bridge-functions.js";
|
|
4
|
+
export { createBridgeFunctions, createBridgeState, findBlockNode, findEditableNode, scrollBlockIntoView, parseListItemPath, supportsInlineEditablePath, readNodeText, placeCaretAtEnd, orderedBlockNodes, blockOrderIndex, computeMoveAfter, computeInsertBefore, groupListItemNodes, commonItemRoot, markdownToHtml, withPreviewParams, setNestedLabelsVisibility, clearChildFocus, clearListItemSelection, removeOverlayControls, clearAllHighlights, showSkeleton, removeSkeletons, ensureBlockBadges, applyAiFieldLoading, clearAiFieldLoading, isAiFieldLoadingApplied, cleanupOverlayElements, isImageEditable, isInlineEditable, isMultilineEditable, missingBlockWrapperWarning, previewIntegrationWarnings, } from "./bridge-functions.js";
|
|
5
5
|
export { LivePreviewProvider, useLivePreviewBlocks, useLivePreviewBridgeApi, } from "./live-preview-store.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/preview-adapter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"src/styles.css"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@avocadostudio-ai/shared": "^0.
|
|
40
|
+
"@avocadostudio-ai/shared": "^0.21.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"next": ">=15.0.0",
|
package/src/styles.css
CHANGED
|
@@ -265,34 +265,50 @@
|
|
|
265
265
|
background: rgba(148, 163, 184, 0.06);
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
/*
|
|
268
|
+
/*
|
|
269
|
+
* "AI is working on this" — the sweep over one block (.aifx-shimmer-overlay)
|
|
270
|
+
* or the frame round the whole page (.aifx-page-shimmer), which the bridge
|
|
271
|
+
* paints when the request is about the page rather than a block.
|
|
272
|
+
*
|
|
273
|
+
* The sweep is tinted, not white. It used to be a white band, which on a white
|
|
274
|
+
* section paints nothing at all; the accent flanks show on light backgrounds
|
|
275
|
+
* and the white core on dark ones, so one gradient serves both. The old
|
|
276
|
+
* `prefers-color-scheme: light` variant keyed on the OS, not on the site's own
|
|
277
|
+
* theme, and was all but identical anyway.
|
|
278
|
+
*
|
|
279
|
+
* The page frame lives on <body>, outside every `.editor-selectable`, where
|
|
280
|
+
* the editor tokens are not defined — hence the fallbacks.
|
|
281
|
+
*/
|
|
282
|
+
.aifx-shimmer-overlay,
|
|
283
|
+
.aifx-page-shimmer {
|
|
284
|
+
--aifx-rgb: var(--editor-accent-rgb, 37, 99, 235);
|
|
285
|
+
--aifx-sweep: linear-gradient(
|
|
286
|
+
110deg,
|
|
287
|
+
transparent 0%,
|
|
288
|
+
transparent 34%,
|
|
289
|
+
rgba(var(--aifx-rgb), 0.10) 42%,
|
|
290
|
+
rgba(255, 255, 255, 0.42) 50%,
|
|
291
|
+
rgba(var(--aifx-rgb), 0.14) 57%,
|
|
292
|
+
transparent 66%,
|
|
293
|
+
transparent 100%
|
|
294
|
+
);
|
|
295
|
+
pointer-events: none;
|
|
296
|
+
overflow: hidden;
|
|
297
|
+
}
|
|
298
|
+
|
|
269
299
|
.aifx-shimmer-overlay {
|
|
270
300
|
position: absolute;
|
|
271
301
|
inset: 0;
|
|
272
302
|
z-index: 21;
|
|
273
|
-
pointer-events: none;
|
|
274
303
|
border-radius: inherit;
|
|
275
|
-
overflow: hidden;
|
|
276
|
-
background: transparent;
|
|
277
|
-
mix-blend-mode: normal;
|
|
278
|
-
box-shadow: none;
|
|
279
304
|
}
|
|
280
305
|
|
|
281
|
-
.aifx-shimmer-overlay::before
|
|
306
|
+
.aifx-shimmer-overlay::before,
|
|
307
|
+
.aifx-page-shimmer::before {
|
|
282
308
|
content: "";
|
|
283
309
|
position: absolute;
|
|
284
310
|
inset: 0;
|
|
285
|
-
background:
|
|
286
|
-
110deg,
|
|
287
|
-
transparent 0%,
|
|
288
|
-
transparent 38%,
|
|
289
|
-
rgba(255, 255, 255, 0.0) 42%,
|
|
290
|
-
rgba(255, 255, 255, 0.42) 50%,
|
|
291
|
-
rgba(219, 234, 254, 0.30) 54%,
|
|
292
|
-
rgba(255, 255, 255, 0.0) 60%,
|
|
293
|
-
transparent 66%,
|
|
294
|
-
transparent 100%
|
|
295
|
-
);
|
|
311
|
+
background: var(--aifx-sweep);
|
|
296
312
|
transform: translateX(120%);
|
|
297
313
|
animation: aifx-shimmer-slide 2.9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
298
314
|
}
|
|
@@ -302,6 +318,34 @@
|
|
|
302
318
|
100% { transform: translateX(-120%); }
|
|
303
319
|
}
|
|
304
320
|
|
|
321
|
+
/* Fixed, so it frames what the user is looking at wherever they scroll. */
|
|
322
|
+
.aifx-page-shimmer {
|
|
323
|
+
position: fixed;
|
|
324
|
+
inset: 0;
|
|
325
|
+
z-index: 2147483000;
|
|
326
|
+
box-shadow:
|
|
327
|
+
inset 0 0 0 2px rgba(var(--aifx-rgb), 0.5),
|
|
328
|
+
inset 0 0 32px rgba(var(--aifx-rgb), 0.16);
|
|
329
|
+
animation: aifx-page-pulse 2.4s ease-in-out infinite;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.aifx-page-shimmer::before {
|
|
333
|
+
opacity: 0.7;
|
|
334
|
+
animation-duration: 3.6s;
|
|
335
|
+
transition: opacity 240ms ease;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Once a block is being written, the frame says "page" and the block's sweep
|
|
339
|
+
says "here, now". Two sweeps at two speeds just read as noise. */
|
|
340
|
+
body:has(.aifx-shimmer-overlay) > .aifx-page-shimmer::before {
|
|
341
|
+
opacity: 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
@keyframes aifx-page-pulse {
|
|
345
|
+
0%, 100% { opacity: 1; }
|
|
346
|
+
50% { opacity: 0.6; }
|
|
347
|
+
}
|
|
348
|
+
|
|
305
349
|
/* Force chrome visible while AI shimmer is active */
|
|
306
350
|
.editor-block-badge:has(.aifx-shimmer-sparkle) {
|
|
307
351
|
opacity: 1 !important;
|
|
@@ -326,6 +370,14 @@
|
|
|
326
370
|
animation: aifx-sparkle-blink 1.6s ease-in-out infinite;
|
|
327
371
|
}
|
|
328
372
|
|
|
373
|
+
/* Bottom corner: the top-right of a page is where the site's header puts its
|
|
374
|
+
own buttons. */
|
|
375
|
+
.aifx-page-shimmer > .aifx-shimmer-sparkle {
|
|
376
|
+
top: auto;
|
|
377
|
+
right: 14px;
|
|
378
|
+
bottom: 14px;
|
|
379
|
+
}
|
|
380
|
+
|
|
329
381
|
.aifx-shimmer-sparkle svg {
|
|
330
382
|
display: block;
|
|
331
383
|
width: 13px;
|
|
@@ -338,34 +390,27 @@
|
|
|
338
390
|
50% { transform: scale(0.9); opacity: 0.65; }
|
|
339
391
|
}
|
|
340
392
|
|
|
341
|
-
@media (prefers-color-scheme: light) {
|
|
342
|
-
.aifx-shimmer-overlay {
|
|
343
|
-
background: transparent;
|
|
344
|
-
box-shadow: none;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
.aifx-shimmer-overlay::before {
|
|
348
|
-
background: linear-gradient(
|
|
349
|
-
110deg,
|
|
350
|
-
transparent 0%,
|
|
351
|
-
transparent 36%,
|
|
352
|
-
rgba(255, 255, 255, 0.0) 41%,
|
|
353
|
-
rgba(255, 255, 255, 0.46) 50%,
|
|
354
|
-
rgba(191, 219, 254, 0.34) 54%,
|
|
355
|
-
rgba(255, 255, 255, 0.0) 60%,
|
|
356
|
-
transparent 66%,
|
|
357
|
-
transparent 100%
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
393
|
@media (prefers-reduced-motion: reduce) {
|
|
363
394
|
.aifx-shimmer-overlay,
|
|
364
395
|
.aifx-shimmer-overlay::before,
|
|
396
|
+
.aifx-page-shimmer,
|
|
397
|
+
.aifx-page-shimmer::before,
|
|
365
398
|
.aifx-shimmer-sparkle,
|
|
366
399
|
.editor-skeleton-block::before {
|
|
367
400
|
animation: none !important;
|
|
368
401
|
}
|
|
402
|
+
|
|
403
|
+
/* A stopped sweep rests off-canvas at translateX(120%), which left nothing
|
|
404
|
+
on screen at all. Hold a still tint instead; the page frame's border
|
|
405
|
+
already reads without motion. */
|
|
406
|
+
.aifx-shimmer-overlay::before {
|
|
407
|
+
transform: none;
|
|
408
|
+
background: rgba(var(--aifx-rgb), 0.08);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.aifx-page-shimmer::before {
|
|
412
|
+
display: none;
|
|
413
|
+
}
|
|
369
414
|
}
|
|
370
415
|
|
|
371
416
|
[data-editor-active] .editor-highlight::before {
|
|
@@ -1234,6 +1279,8 @@
|
|
|
1234
1279
|
animation: none !important;
|
|
1235
1280
|
}
|
|
1236
1281
|
.aifx-shimmer-overlay,
|
|
1282
|
+
.aifx-page-shimmer,
|
|
1283
|
+
.aifx-page-shimmer::before,
|
|
1237
1284
|
.aifx-shimmer-sparkle {
|
|
1238
1285
|
animation: none !important;
|
|
1239
1286
|
}
|