@avocadostudio-ai/preview-adapter 0.20.0 → 0.21.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.
- 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 +103 -42
|
@@ -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.1",
|
|
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.1"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"next": ">=15.0.0",
|
package/src/styles.css
CHANGED
|
@@ -140,8 +140,11 @@
|
|
|
140
140
|
/* Scope the typing shimmer to the fields actually being edited (.editor-live-typing
|
|
141
141
|
is only added to text targets, never images), so it doesn't wash the whole block —
|
|
142
142
|
including the hero image, which looked like it was being regenerated. */
|
|
143
|
-
.editor-live-draft-active .editor-live-typing:not([class*="absolute"]):not([class*="fixed"]) {
|
|
143
|
+
:where(.editor-live-draft-active .editor-live-typing:not([class*="absolute"]):not([class*="fixed"])) {
|
|
144
144
|
position: relative;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.editor-live-draft-active .editor-live-typing:not([class*="absolute"]):not([class*="fixed"]) {
|
|
145
148
|
overflow: hidden;
|
|
146
149
|
}
|
|
147
150
|
|
|
@@ -265,34 +268,50 @@
|
|
|
265
268
|
background: rgba(148, 163, 184, 0.06);
|
|
266
269
|
}
|
|
267
270
|
|
|
268
|
-
/*
|
|
271
|
+
/*
|
|
272
|
+
* "AI is working on this" — the sweep over one block (.aifx-shimmer-overlay)
|
|
273
|
+
* or the frame round the whole page (.aifx-page-shimmer), which the bridge
|
|
274
|
+
* paints when the request is about the page rather than a block.
|
|
275
|
+
*
|
|
276
|
+
* The sweep is tinted, not white. It used to be a white band, which on a white
|
|
277
|
+
* section paints nothing at all; the accent flanks show on light backgrounds
|
|
278
|
+
* and the white core on dark ones, so one gradient serves both. The old
|
|
279
|
+
* `prefers-color-scheme: light` variant keyed on the OS, not on the site's own
|
|
280
|
+
* theme, and was all but identical anyway.
|
|
281
|
+
*
|
|
282
|
+
* The page frame lives on <body>, outside every `.editor-selectable`, where
|
|
283
|
+
* the editor tokens are not defined — hence the fallbacks.
|
|
284
|
+
*/
|
|
285
|
+
.aifx-shimmer-overlay,
|
|
286
|
+
.aifx-page-shimmer {
|
|
287
|
+
--aifx-rgb: var(--editor-accent-rgb, 37, 99, 235);
|
|
288
|
+
--aifx-sweep: linear-gradient(
|
|
289
|
+
110deg,
|
|
290
|
+
transparent 0%,
|
|
291
|
+
transparent 34%,
|
|
292
|
+
rgba(var(--aifx-rgb), 0.10) 42%,
|
|
293
|
+
rgba(255, 255, 255, 0.42) 50%,
|
|
294
|
+
rgba(var(--aifx-rgb), 0.14) 57%,
|
|
295
|
+
transparent 66%,
|
|
296
|
+
transparent 100%
|
|
297
|
+
);
|
|
298
|
+
pointer-events: none;
|
|
299
|
+
overflow: hidden;
|
|
300
|
+
}
|
|
301
|
+
|
|
269
302
|
.aifx-shimmer-overlay {
|
|
270
303
|
position: absolute;
|
|
271
304
|
inset: 0;
|
|
272
305
|
z-index: 21;
|
|
273
|
-
pointer-events: none;
|
|
274
306
|
border-radius: inherit;
|
|
275
|
-
overflow: hidden;
|
|
276
|
-
background: transparent;
|
|
277
|
-
mix-blend-mode: normal;
|
|
278
|
-
box-shadow: none;
|
|
279
307
|
}
|
|
280
308
|
|
|
281
|
-
.aifx-shimmer-overlay::before
|
|
309
|
+
.aifx-shimmer-overlay::before,
|
|
310
|
+
.aifx-page-shimmer::before {
|
|
282
311
|
content: "";
|
|
283
312
|
position: absolute;
|
|
284
313
|
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
|
-
);
|
|
314
|
+
background: var(--aifx-sweep);
|
|
296
315
|
transform: translateX(120%);
|
|
297
316
|
animation: aifx-shimmer-slide 2.9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
|
|
298
317
|
}
|
|
@@ -302,6 +321,34 @@
|
|
|
302
321
|
100% { transform: translateX(-120%); }
|
|
303
322
|
}
|
|
304
323
|
|
|
324
|
+
/* Fixed, so it frames what the user is looking at wherever they scroll. */
|
|
325
|
+
.aifx-page-shimmer {
|
|
326
|
+
position: fixed;
|
|
327
|
+
inset: 0;
|
|
328
|
+
z-index: 2147483000;
|
|
329
|
+
box-shadow:
|
|
330
|
+
inset 0 0 0 2px rgba(var(--aifx-rgb), 0.5),
|
|
331
|
+
inset 0 0 32px rgba(var(--aifx-rgb), 0.16);
|
|
332
|
+
animation: aifx-page-pulse 2.4s ease-in-out infinite;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.aifx-page-shimmer::before {
|
|
336
|
+
opacity: 0.7;
|
|
337
|
+
animation-duration: 3.6s;
|
|
338
|
+
transition: opacity 240ms ease;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* Once a block is being written, the frame says "page" and the block's sweep
|
|
342
|
+
says "here, now". Two sweeps at two speeds just read as noise. */
|
|
343
|
+
body:has(.aifx-shimmer-overlay) > .aifx-page-shimmer::before {
|
|
344
|
+
opacity: 0;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
@keyframes aifx-page-pulse {
|
|
348
|
+
0%, 100% { opacity: 1; }
|
|
349
|
+
50% { opacity: 0.6; }
|
|
350
|
+
}
|
|
351
|
+
|
|
305
352
|
/* Force chrome visible while AI shimmer is active */
|
|
306
353
|
.editor-block-badge:has(.aifx-shimmer-sparkle) {
|
|
307
354
|
opacity: 1 !important;
|
|
@@ -326,6 +373,14 @@
|
|
|
326
373
|
animation: aifx-sparkle-blink 1.6s ease-in-out infinite;
|
|
327
374
|
}
|
|
328
375
|
|
|
376
|
+
/* Bottom corner: the top-right of a page is where the site's header puts its
|
|
377
|
+
own buttons. */
|
|
378
|
+
.aifx-page-shimmer > .aifx-shimmer-sparkle {
|
|
379
|
+
top: auto;
|
|
380
|
+
right: 14px;
|
|
381
|
+
bottom: 14px;
|
|
382
|
+
}
|
|
383
|
+
|
|
329
384
|
.aifx-shimmer-sparkle svg {
|
|
330
385
|
display: block;
|
|
331
386
|
width: 13px;
|
|
@@ -338,34 +393,27 @@
|
|
|
338
393
|
50% { transform: scale(0.9); opacity: 0.65; }
|
|
339
394
|
}
|
|
340
395
|
|
|
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
396
|
@media (prefers-reduced-motion: reduce) {
|
|
363
397
|
.aifx-shimmer-overlay,
|
|
364
398
|
.aifx-shimmer-overlay::before,
|
|
399
|
+
.aifx-page-shimmer,
|
|
400
|
+
.aifx-page-shimmer::before,
|
|
365
401
|
.aifx-shimmer-sparkle,
|
|
366
402
|
.editor-skeleton-block::before {
|
|
367
403
|
animation: none !important;
|
|
368
404
|
}
|
|
405
|
+
|
|
406
|
+
/* A stopped sweep rests off-canvas at translateX(120%), which left nothing
|
|
407
|
+
on screen at all. Hold a still tint instead; the page frame's border
|
|
408
|
+
already reads without motion. */
|
|
409
|
+
.aifx-shimmer-overlay::before {
|
|
410
|
+
transform: none;
|
|
411
|
+
background: rgba(var(--aifx-rgb), 0.08);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.aifx-page-shimmer::before {
|
|
415
|
+
display: none;
|
|
416
|
+
}
|
|
369
417
|
}
|
|
370
418
|
|
|
371
419
|
[data-editor-active] .editor-highlight::before {
|
|
@@ -816,7 +864,18 @@
|
|
|
816
864
|
transform: none;
|
|
817
865
|
}
|
|
818
866
|
|
|
819
|
-
|
|
867
|
+
/*
|
|
868
|
+
* `position: relative` anchors the field pills, and it has to lose to anything
|
|
869
|
+
* the site says. Written plainly this selector was (0,5,0), which beats almost
|
|
870
|
+
* any site rule, and the only guard was a Tailwind class-name match. So the
|
|
871
|
+
* full-width hero's background image — `.hero--full-width .hero__bg-image
|
|
872
|
+
* { position: absolute }` in the blocks package, no Tailwind class on it —
|
|
873
|
+
* went back into the section's flex row. It pushed the headline off the right
|
|
874
|
+
* edge of every editor preview while the public page rendered correctly.
|
|
875
|
+
* Inside `:where()` the rule has no specificity, so it only applies to elements
|
|
876
|
+
* the site left static, which are the only ones that need it.
|
|
877
|
+
*/
|
|
878
|
+
:where([data-editor-active] .editor-selectable [data-editable-label]:not([class*="absolute"]):not([class*="fixed"])) {
|
|
820
879
|
position: relative;
|
|
821
880
|
}
|
|
822
881
|
|
|
@@ -825,7 +884,7 @@
|
|
|
825
884
|
display: none;
|
|
826
885
|
}
|
|
827
886
|
|
|
828
|
-
.editor-highlight [data-editable-target]:not([class*="absolute"]):not([class*="fixed"]) {
|
|
887
|
+
:where(.editor-highlight [data-editable-target]:not([class*="absolute"]):not([class*="fixed"])) {
|
|
829
888
|
position: relative;
|
|
830
889
|
}
|
|
831
890
|
|
|
@@ -1234,6 +1293,8 @@
|
|
|
1234
1293
|
animation: none !important;
|
|
1235
1294
|
}
|
|
1236
1295
|
.aifx-shimmer-overlay,
|
|
1296
|
+
.aifx-page-shimmer,
|
|
1297
|
+
.aifx-page-shimmer::before,
|
|
1237
1298
|
.aifx-shimmer-sparkle {
|
|
1238
1299
|
animation: none !important;
|
|
1239
1300
|
}
|