@slopmachine/react 0.8.0 → 0.9.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
@@ -1,5 +1,43 @@
1
1
  # @slopmachine/react
2
2
 
3
+ ## 0.9.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 56eaf03: Component polish (React and Svelte):
8
+
9
+ - **`alt` on `SlopImage`:** describe the image for screen readers (or pass `""` for decorative images). The default text is unchanged.
10
+ - **`objectFit` on `SlopVideo`:** matches `SlopImage`; defaults to `"cover"`.
11
+ - **`SlopText` error handling** now matches `SlopImage`/`SlopVideo`:
12
+ - a default "Failed to load text" message instead of an empty element;
13
+ - `onGenerationError(error)`;
14
+ - errors are `SlopMachineError` with the API's message and HTTP `status`;
15
+ - React's `errorFallback` also accepts `(error) => node`.
16
+ - **Stale text fix:** `SlopText` cancels in-flight requests when its props change, so a slow response can no longer replace newer text. This affected Svelte.
17
+ - **Theming:** the loader, shimmer and error states read new `--slop-muted`, `--slop-muted-foreground` and `--slop-shimmer` CSS variables. These fall back to `--muted`/`--muted-foreground`, so the default look is unchanged; set them to style the components independently, or to fix themes whose `--muted` isn't a plain colour.
18
+ - **Svelte now matches React:**
19
+ - `SlopImage`/`SlopVideo` render the media URL immediately, including when server-rendering (previously an empty `src` for 50 ms);
20
+ - later prop changes are debounced by 100 ms, as in React;
21
+ - `SlopText` bypasses the HTTP cache, as React already did;
22
+ - `SlopTextProps` is now exported.
23
+ - **Core:** new `fetchRenderedText(url, { signal })`, shared by both `SlopText` components.
24
+
25
+ ### Patch Changes
26
+
27
+ - Updated dependencies [56eaf03]
28
+ - @slopmachine/core@0.9.0
29
+
30
+ ## 0.8.1
31
+
32
+ ### Patch Changes
33
+
34
+ - 9e91dd6: - Passing bucket-only options (`model`, `version`, `original`, `attachments`, video `duration`) together with `pipelineId` now logs a one-time console warning. The pipeline endpoint doesn't support them, and they were previously dropped silently. `aspectRatio` still sizes the component and doesn't warn.
35
+ - `interpolatePrompt` now treats variable names and values literally. Keys containing regex characters (e.g. `{a.b}`) no longer match other placeholders, and values containing `$` are no longer mangled.
36
+ - Clarified that `baseUrl` is a full endpoint URL, not a base URL.
37
+ - Internal: the URL builders share one implementation. Generated URLs are byte-for-byte unchanged, so existing browser and CDN caches stay valid.
38
+ - Updated dependencies [9e91dd6]
39
+ - @slopmachine/core@0.8.1
40
+
3
41
  ## 0.8.0
4
42
 
5
43
  ### Minor Changes
package/dist/index.d.mts CHANGED
@@ -8,11 +8,17 @@ export { ImageAspectRatio, RateResultOptions, RateResultResponse, ResultRating,
8
8
  */
9
9
  type SlopErrorFallback = React.ReactNode | ((error: SlopMachineError) => React.ReactNode);
10
10
 
11
- interface SlopImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">, Omit<SlopImageOptions, "aspectRatio"> {
11
+ interface SlopImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src">, Omit<SlopImageOptions, "aspectRatio"> {
12
12
  /**
13
13
  * The aspect ratio of the generated image. Defaults to "1:1".
14
14
  */
15
15
  aspectRatio?: ImageAspectRatio;
16
+ /**
17
+ * Alternative text describing the image, for screen readers and when the image
18
+ * can't be shown. Defaults to "Image produced by Slop Machine (slopmachine.dev)".
19
+ * Describe what the image shows, or pass `""` if it's purely decorative.
20
+ */
21
+ alt?: string;
16
22
  /**
17
23
  * Custom React node to display while the image is loading.
18
24
  * If not provided, a default spinner and shimmer effect will be shown.
@@ -64,7 +70,7 @@ interface SlopImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>,
64
70
  * />
65
71
  * ```
66
72
  *
67
- * @version 0.8.0
73
+ * @version 0.9.0
68
74
  */
69
75
  declare const SlopImage: React.FC<SlopImageProps>;
70
76
 
@@ -78,6 +84,10 @@ interface SlopVideoProps extends Omit<React.VideoHTMLAttributes<HTMLVideoElement
78
84
  * If not provided, a default spinner and shimmer effect will be shown.
79
85
  */
80
86
  loader?: React.ReactNode;
87
+ /**
88
+ * How the video should be resized to fit its container. Defaults to "cover".
89
+ */
90
+ objectFit?: React.CSSProperties["objectFit"];
81
91
  /**
82
92
  * Content to display if the video fails to generate or load.
83
93
  * Pass a node, or a function that receives the `SlopMachineError` and returns a node.
@@ -118,19 +128,27 @@ interface SlopVideoProps extends Omit<React.VideoHTMLAttributes<HTMLVideoElement
118
128
  * />
119
129
  * ```
120
130
  *
121
- * @version 0.8.0
131
+ * @version 0.9.0
122
132
  */
123
133
  declare const SlopVideo: React.FC<SlopVideoProps>;
124
134
 
125
135
  interface SlopTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">, SlopTextOptions {
126
136
  /**
127
- * Optional loading component to show while the text is being generated
137
+ * Optional loading component to show while the text is being generated.
138
+ * If not provided, a default spinner and shimmer effect will be shown.
128
139
  */
129
140
  fallback?: React.ReactNode;
130
141
  /**
131
- * Optional error component to show if the text generation fails
142
+ * Content to display if the text fails to generate or load.
143
+ * Pass a node, or a function that receives the `SlopMachineError` and returns a node.
144
+ * If not provided, a default error message is shown.
145
+ */
146
+ errorFallback?: SlopErrorFallback;
147
+ /**
148
+ * Called once per URL when the text fails to generate or load.
149
+ * `error.status` holds the HTTP status when the API reported the failure.
132
150
  */
133
- errorFallback?: React.ReactNode;
151
+ onGenerationError?: (error: SlopMachineError) => void;
134
152
  }
135
153
  /**
136
154
  * Renders text generated by Slop Machine.
@@ -144,7 +162,7 @@ interface SlopTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "chil
144
162
  * <SlopText
145
163
  * bucketId="my-text-bucket"
146
164
  * fallback={<div>Loading story...</div>}
147
- * errorFallback={<div>Failed to load story</div>}
165
+ * errorFallback={(error) => <div>Failed to load story: {error.message}</div>}
148
166
  * className="text-lg text-gray-800"
149
167
  * />
150
168
  *
@@ -156,7 +174,7 @@ interface SlopTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "chil
156
174
  * />
157
175
  * ```
158
176
  *
159
- * @version 0.8.0
177
+ * @version 0.9.0
160
178
  */
161
179
  declare const SlopText: React.ForwardRefExoticComponent<SlopTextProps & React.RefAttributes<HTMLDivElement>>;
162
180
 
package/dist/index.d.ts CHANGED
@@ -8,11 +8,17 @@ export { ImageAspectRatio, RateResultOptions, RateResultResponse, ResultRating,
8
8
  */
9
9
  type SlopErrorFallback = React.ReactNode | ((error: SlopMachineError) => React.ReactNode);
10
10
 
11
- interface SlopImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src" | "alt">, Omit<SlopImageOptions, "aspectRatio"> {
11
+ interface SlopImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src">, Omit<SlopImageOptions, "aspectRatio"> {
12
12
  /**
13
13
  * The aspect ratio of the generated image. Defaults to "1:1".
14
14
  */
15
15
  aspectRatio?: ImageAspectRatio;
16
+ /**
17
+ * Alternative text describing the image, for screen readers and when the image
18
+ * can't be shown. Defaults to "Image produced by Slop Machine (slopmachine.dev)".
19
+ * Describe what the image shows, or pass `""` if it's purely decorative.
20
+ */
21
+ alt?: string;
16
22
  /**
17
23
  * Custom React node to display while the image is loading.
18
24
  * If not provided, a default spinner and shimmer effect will be shown.
@@ -64,7 +70,7 @@ interface SlopImageProps extends Omit<React.ImgHTMLAttributes<HTMLImageElement>,
64
70
  * />
65
71
  * ```
66
72
  *
67
- * @version 0.8.0
73
+ * @version 0.9.0
68
74
  */
69
75
  declare const SlopImage: React.FC<SlopImageProps>;
70
76
 
@@ -78,6 +84,10 @@ interface SlopVideoProps extends Omit<React.VideoHTMLAttributes<HTMLVideoElement
78
84
  * If not provided, a default spinner and shimmer effect will be shown.
79
85
  */
80
86
  loader?: React.ReactNode;
87
+ /**
88
+ * How the video should be resized to fit its container. Defaults to "cover".
89
+ */
90
+ objectFit?: React.CSSProperties["objectFit"];
81
91
  /**
82
92
  * Content to display if the video fails to generate or load.
83
93
  * Pass a node, or a function that receives the `SlopMachineError` and returns a node.
@@ -118,19 +128,27 @@ interface SlopVideoProps extends Omit<React.VideoHTMLAttributes<HTMLVideoElement
118
128
  * />
119
129
  * ```
120
130
  *
121
- * @version 0.8.0
131
+ * @version 0.9.0
122
132
  */
123
133
  declare const SlopVideo: React.FC<SlopVideoProps>;
124
134
 
125
135
  interface SlopTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children">, SlopTextOptions {
126
136
  /**
127
- * Optional loading component to show while the text is being generated
137
+ * Optional loading component to show while the text is being generated.
138
+ * If not provided, a default spinner and shimmer effect will be shown.
128
139
  */
129
140
  fallback?: React.ReactNode;
130
141
  /**
131
- * Optional error component to show if the text generation fails
142
+ * Content to display if the text fails to generate or load.
143
+ * Pass a node, or a function that receives the `SlopMachineError` and returns a node.
144
+ * If not provided, a default error message is shown.
145
+ */
146
+ errorFallback?: SlopErrorFallback;
147
+ /**
148
+ * Called once per URL when the text fails to generate or load.
149
+ * `error.status` holds the HTTP status when the API reported the failure.
132
150
  */
133
- errorFallback?: React.ReactNode;
151
+ onGenerationError?: (error: SlopMachineError) => void;
134
152
  }
135
153
  /**
136
154
  * Renders text generated by Slop Machine.
@@ -144,7 +162,7 @@ interface SlopTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "chil
144
162
  * <SlopText
145
163
  * bucketId="my-text-bucket"
146
164
  * fallback={<div>Loading story...</div>}
147
- * errorFallback={<div>Failed to load story</div>}
165
+ * errorFallback={(error) => <div>Failed to load story: {error.message}</div>}
148
166
  * className="text-lg text-gray-800"
149
167
  * />
150
168
  *
@@ -156,7 +174,7 @@ interface SlopTextProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "chil
156
174
  * />
157
175
  * ```
158
176
  *
159
- * @version 0.8.0
177
+ * @version 0.9.0
160
178
  */
161
179
  declare const SlopText: React.ForwardRefExoticComponent<SlopTextProps & React.RefAttributes<HTMLDivElement>>;
162
180
 
package/dist/index.js CHANGED
@@ -80,7 +80,10 @@ var ErrorOverlay = ({
80
80
  "div",
81
81
  {
82
82
  className: "absolute inset-0 z-10 flex items-center justify-center bg-muted",
83
- style: { ...overlayStyle, backgroundColor: "var(--muted, #f3f4f6)" },
83
+ style: {
84
+ ...overlayStyle,
85
+ backgroundColor: "var(--slop-muted, var(--muted, #f3f4f6))"
86
+ },
84
87
  title: error.message,
85
88
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
86
89
  "div",
@@ -91,7 +94,7 @@ var ErrorOverlay = ({
91
94
  flexDirection: "column",
92
95
  alignItems: "center",
93
96
  gap: "0.5rem",
94
- color: "var(--muted-foreground, #6b7280)"
97
+ color: "var(--slop-muted-foreground, var(--muted-foreground, #6b7280))"
95
98
  },
96
99
  children: [
97
100
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -123,6 +126,7 @@ var ErrorOverlay = ({
123
126
 
124
127
  // src/components/SlopImage.tsx
125
128
  var import_jsx_runtime2 = require("react/jsx-runtime");
129
+ var DEFAULT_ALT = "Image produced by Slop Machine (slopmachine.dev)";
126
130
  function cn(...inputs) {
127
131
  return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
128
132
  }
@@ -134,6 +138,7 @@ var SlopImage = ({
134
138
  metadata,
135
139
  className,
136
140
  aspectRatio = "1:1",
141
+ alt = DEFAULT_ALT,
137
142
  model,
138
143
  version,
139
144
  resultId,
@@ -190,7 +195,6 @@ var SlopImage = ({
190
195
  }, 100);
191
196
  return () => clearTimeout(timer);
192
197
  }, [rawSrc]);
193
- const alt = "Image produced by Slop Machine (slopmachine.dev)";
194
198
  const [isLoading, setIsLoading] = (0, import_react.useState)(true);
195
199
  const [error, setError] = (0, import_react.useState)(null);
196
200
  const [currentSrc, setCurrentSrc] = (0, import_react.useState)(src);
@@ -289,7 +293,7 @@ var SlopImage = ({
289
293
  display: "flex",
290
294
  alignItems: "center",
291
295
  justifyContent: "center",
292
- backgroundColor: "var(--muted, #f3f4f6)",
296
+ backgroundColor: "var(--slop-muted, var(--muted, #f3f4f6))",
293
297
  opacity: isLoading ? 1 : 0,
294
298
  pointerEvents: isLoading ? "auto" : "none",
295
299
  transition: "opacity 300ms ease-in-out"
@@ -315,7 +319,7 @@ var SlopImage = ({
315
319
  style: {
316
320
  width: "24px",
317
321
  height: "24px",
318
- color: "var(--muted-foreground, #6b7280)",
322
+ color: "var(--slop-muted-foreground, var(--muted-foreground, #6b7280))",
319
323
  animation: "slop-spin 1s linear infinite"
320
324
  },
321
325
  children: [
@@ -349,7 +353,7 @@ var SlopImage = ({
349
353
  className: "text-xs text-muted-foreground",
350
354
  style: {
351
355
  fontSize: "0.75rem",
352
- color: "var(--muted-foreground, #6b7280)"
356
+ color: "var(--slop-muted-foreground, var(--muted-foreground, #6b7280))"
353
357
  },
354
358
  children: "Loading..."
355
359
  }
@@ -372,7 +376,7 @@ var SlopImage = ({
372
376
  left: 0,
373
377
  right: 0,
374
378
  bottom: 0,
375
- background: "linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)",
379
+ background: "linear-gradient(90deg, var(--slop-muted, var(--muted, #f3f4f6)) 0%, var(--slop-shimmer, var(--muted-foreground, #e5e7eb)) 50%, var(--slop-muted, var(--muted, #f3f4f6)) 100%)",
376
380
  backgroundSize: "200% 100%",
377
381
  animation: "slop-shimmer 2s ease-in-out infinite",
378
382
  opacity: isLoading ? 1 : 0,
@@ -450,6 +454,7 @@ var SlopVideo = ({
450
454
  original,
451
455
  attachments,
452
456
  loader,
457
+ objectFit = "cover",
453
458
  errorFallback,
454
459
  onGenerationError,
455
460
  autoPlay = true,
@@ -598,7 +603,7 @@ var SlopVideo = ({
598
603
  display: "flex",
599
604
  alignItems: "center",
600
605
  justifyContent: "center",
601
- backgroundColor: "var(--muted, #f3f4f6)",
606
+ backgroundColor: "var(--slop-muted, var(--muted, #f3f4f6))",
602
607
  opacity: isLoading ? 1 : 0,
603
608
  pointerEvents: isLoading ? "auto" : "none",
604
609
  transition: "opacity 300ms ease-in-out"
@@ -624,7 +629,7 @@ var SlopVideo = ({
624
629
  style: {
625
630
  width: "24px",
626
631
  height: "24px",
627
- color: "var(--muted-foreground, #6b7280)",
632
+ color: "var(--slop-muted-foreground, var(--muted-foreground, #6b7280))",
628
633
  animation: "slop-spin 1s linear infinite"
629
634
  },
630
635
  children: [
@@ -658,7 +663,7 @@ var SlopVideo = ({
658
663
  className: "text-xs text-muted-foreground",
659
664
  style: {
660
665
  fontSize: "0.75rem",
661
- color: "var(--muted-foreground, #6b7280)"
666
+ color: "var(--slop-muted-foreground, var(--muted-foreground, #6b7280))"
662
667
  },
663
668
  children: "Loading..."
664
669
  }
@@ -681,7 +686,7 @@ var SlopVideo = ({
681
686
  left: 0,
682
687
  right: 0,
683
688
  bottom: 0,
684
- background: "linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)",
689
+ background: "linear-gradient(90deg, var(--slop-muted, var(--muted, #f3f4f6)) 0%, var(--slop-shimmer, var(--muted-foreground, #e5e7eb)) 50%, var(--slop-muted, var(--muted, #f3f4f6)) 100%)",
685
690
  backgroundSize: "200% 100%",
686
691
  animation: "slop-shimmer 2s ease-in-out infinite",
687
692
  opacity: isLoading ? 1 : 0,
@@ -718,13 +723,13 @@ var SlopVideo = ({
718
723
  },
719
724
  "aria-hidden": error ? true : props["aria-hidden"],
720
725
  className: cn2(
721
- "h-full w-full object-cover transition-opacity duration-500",
726
+ "h-full w-full transition-opacity duration-500",
722
727
  isLoading || error ? "opacity-0" : "opacity-100"
723
728
  ),
724
729
  style: {
725
730
  height: "100%",
726
731
  width: "100%",
727
- objectFit: "cover",
732
+ objectFit,
728
733
  transition: "opacity 500ms",
729
734
  opacity: isLoading || error ? 0 : 1
730
735
  }
@@ -756,6 +761,7 @@ var SlopText = import_react3.default.forwardRef(
756
761
  attachments,
757
762
  fallback,
758
763
  errorFallback,
764
+ onGenerationError,
759
765
  ...props
760
766
  }, ref) => {
761
767
  const [text, setText] = (0, import_react3.useState)(null);
@@ -789,48 +795,48 @@ var SlopText = import_react3.default.forwardRef(
789
795
  JSON.stringify(attachments)
790
796
  ]
791
797
  );
798
+ const onGenerationErrorRef = (0, import_react3.useRef)(onGenerationError);
792
799
  (0, import_react3.useEffect)(() => {
793
- let isMounted = true;
794
- const fetchText = async () => {
795
- try {
796
- setLoading(true);
797
- setError(null);
798
- const response = await fetch(rawUrl, { cache: "no-store" });
799
- if (!response.ok) {
800
- let errorMessage = response.statusText;
801
- try {
802
- const errorText = await response.text();
803
- const errorData = JSON.parse(errorText);
804
- if (errorData.error) {
805
- errorMessage = errorData.error;
806
- }
807
- } catch (e) {
808
- }
809
- throw new Error(`Failed to fetch text: ${errorMessage}`);
810
- }
811
- const content = await response.text();
812
- if (isMounted) {
813
- setText(content);
814
- }
815
- } catch (err) {
816
- if (isMounted) {
817
- const finalError = err instanceof Error ? err : new Error("Failed to generate text");
818
- console.error(finalError);
819
- setError(finalError);
820
- }
821
- } finally {
822
- if (isMounted) {
823
- setLoading(false);
824
- }
825
- }
826
- };
827
- fetchText();
828
- return () => {
829
- isMounted = false;
830
- };
800
+ onGenerationErrorRef.current = onGenerationError;
801
+ });
802
+ (0, import_react3.useEffect)(() => {
803
+ const controller = new AbortController();
804
+ setLoading(true);
805
+ setError(null);
806
+ (0, import_core3.fetchRenderedText)(rawUrl, { signal: controller.signal }).then((content) => {
807
+ setText(content);
808
+ setLoading(false);
809
+ }).catch((err) => {
810
+ if (controller.signal.aborted) return;
811
+ const finalError = err instanceof import_core3.SlopMachineError ? err : new import_core3.SlopMachineError("Failed to load text");
812
+ console.error(
813
+ "SlopText error:",
814
+ finalError.status ?? "",
815
+ finalError.message
816
+ );
817
+ setError(finalError);
818
+ setLoading(false);
819
+ onGenerationErrorRef.current?.(finalError);
820
+ });
821
+ return () => controller.abort();
831
822
  }, [rawUrl]);
832
- if (error && errorFallback) {
833
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: errorFallback });
823
+ if (error) {
824
+ if (errorFallback !== void 0) {
825
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: typeof errorFallback === "function" ? errorFallback(error) : errorFallback });
826
+ }
827
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { ref, ...props, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
828
+ "div",
829
+ {
830
+ className: "slop-wrapper relative overflow-hidden w-full",
831
+ style: {
832
+ position: "relative",
833
+ overflow: "hidden",
834
+ width: "100%",
835
+ minHeight: "100px"
836
+ },
837
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ErrorOverlay, { error, label: "Failed to load text" })
838
+ }
839
+ ) });
834
840
  }
835
841
  if (loading) {
836
842
  if (fallback) {
@@ -872,7 +878,7 @@ var SlopText = import_react3.default.forwardRef(
872
878
  display: "flex",
873
879
  alignItems: "center",
874
880
  justifyContent: "center",
875
- backgroundColor: "var(--muted, #f3f4f6)"
881
+ backgroundColor: "var(--slop-muted, var(--muted, #f3f4f6))"
876
882
  },
877
883
  children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
878
884
  "div",
@@ -895,7 +901,7 @@ var SlopText = import_react3.default.forwardRef(
895
901
  style: {
896
902
  width: "24px",
897
903
  height: "24px",
898
- color: "var(--muted-foreground, #6b7280)",
904
+ color: "var(--slop-muted-foreground, var(--muted-foreground, #6b7280))",
899
905
  animation: "slop-spin 1s linear infinite"
900
906
  },
901
907
  children: [
@@ -929,7 +935,7 @@ var SlopText = import_react3.default.forwardRef(
929
935
  className: "text-xs text-muted-foreground",
930
936
  style: {
931
937
  fontSize: "0.75rem",
932
- color: "var(--muted-foreground, #6b7280)"
938
+ color: "var(--slop-muted-foreground, var(--muted-foreground, #6b7280))"
933
939
  },
934
940
  children: "Loading..."
935
941
  }
@@ -949,7 +955,7 @@ var SlopText = import_react3.default.forwardRef(
949
955
  left: 0,
950
956
  right: 0,
951
957
  bottom: 0,
952
- background: "linear-gradient(90deg, var(--muted, #f3f4f6) 0%, var(--muted-foreground, #e5e7eb) 50%, var(--muted, #f3f4f6) 100%)",
958
+ background: "linear-gradient(90deg, var(--slop-muted, var(--muted, #f3f4f6)) 0%, var(--slop-shimmer, var(--muted-foreground, #e5e7eb)) 50%, var(--slop-muted, var(--muted, #f3f4f6)) 100%)",
953
959
  backgroundSize: "200% 100%",
954
960
  animation: "slop-shimmer 2s ease-in-out infinite"
955
961
  }