@plusscommunities/pluss-feature-builder-web-d 1.0.2-beta.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.
Files changed (117) hide show
  1. package/.babelrc +4 -0
  2. package/dist/index.cjs.js +7792 -0
  3. package/package.json +54 -0
  4. package/rollup.config.js +68 -0
  5. package/src/actions/featureBuilderStringsActions.js +88 -0
  6. package/src/actions/featureDefinitionsIndex.js +258 -0
  7. package/src/actions/formActions.js +311 -0
  8. package/src/actions/index.js +12 -0
  9. package/src/actions/listingActions.js +350 -0
  10. package/src/actions/wizardActions.js +240 -0
  11. package/src/components/ActivityCardExample.jsx +86 -0
  12. package/src/components/ActivityCardExample.module.css +130 -0
  13. package/src/components/BackgroundLoader.jsx +33 -0
  14. package/src/components/BackgroundLoader.module.css +46 -0
  15. package/src/components/BaseFieldConfig.jsx +305 -0
  16. package/src/components/BaseFieldConfig.module.css +42 -0
  17. package/src/components/CenteredContainer.jsx +29 -0
  18. package/src/components/CenteredContainer.module.css +171 -0
  19. package/src/components/DeleteConfirmationPopup.jsx +95 -0
  20. package/src/components/DeleteConfirmationPopup.module.css +12 -0
  21. package/src/components/ErrorBoundary.jsx +134 -0
  22. package/src/components/ErrorBoundary.module.css +77 -0
  23. package/src/components/ErrorMessage.jsx +85 -0
  24. package/src/components/ErrorMessage.module.css +116 -0
  25. package/src/components/ExampleDisplay.jsx +26 -0
  26. package/src/components/ExampleDisplay.module.css +3 -0
  27. package/src/components/FeatureBuilderSidebar.jsx +84 -0
  28. package/src/components/FeatureBuilderSuccessPopup.jsx +55 -0
  29. package/src/components/FeatureBuilderSuccessPopup.module.css +43 -0
  30. package/src/components/FeatureBuilderWelcomePopup.jsx +51 -0
  31. package/src/components/FeatureBuilderWelcomePopup.module.css +21 -0
  32. package/src/components/FeatureListingCard.jsx +104 -0
  33. package/src/components/FeatureListingCard.module.css +62 -0
  34. package/src/components/Fields.jsx +460 -0
  35. package/src/components/Fields.module.css +159 -0
  36. package/src/components/IconLoader.jsx +153 -0
  37. package/src/components/IconLoader.module.css +92 -0
  38. package/src/components/IconSelector.jsx +112 -0
  39. package/src/components/IconSelector.module.css +197 -0
  40. package/src/components/ListingEditor.jsx +406 -0
  41. package/src/components/ListingEditor.module.css +14 -0
  42. package/src/components/ListingSuccessPopup.jsx +52 -0
  43. package/src/components/LoadingScreen.jsx +54 -0
  44. package/src/components/LoadingScreen.module.css +103 -0
  45. package/src/components/LoadingState.jsx +40 -0
  46. package/src/components/LoadingState.module.css +18 -0
  47. package/src/components/PreviewFull.js +24 -0
  48. package/src/components/PreviewFull.module.css +11 -0
  49. package/src/components/PreviewGrid.js +14 -0
  50. package/src/components/PreviewWidget.js +27 -0
  51. package/src/components/PreviewWidget.module.css +15 -0
  52. package/src/components/SidebarLayout.jsx +292 -0
  53. package/src/components/SidebarLayout.module.css +145 -0
  54. package/src/components/SkeletonLoader.jsx +128 -0
  55. package/src/components/SkeletonLoader.module.css +295 -0
  56. package/src/components/SortButtonGroup.jsx +34 -0
  57. package/src/components/SortButtonGroup.module.css +51 -0
  58. package/src/components/ToastContainer.jsx +98 -0
  59. package/src/components/ToastContainer.module.css +156 -0
  60. package/src/components/ToggleSwitch.js +40 -0
  61. package/src/components/ToggleSwitch.module.css +48 -0
  62. package/src/components/TwoColumnInput.jsx +29 -0
  63. package/src/components/TwoColumnInput.module.css +32 -0
  64. package/src/components/ViewFull.js +139 -0
  65. package/src/components/ViewFull.module.css +71 -0
  66. package/src/components/ViewWidget.js +62 -0
  67. package/src/components/ViewWidget.module.css +28 -0
  68. package/src/components/iconCategories.js +135 -0
  69. package/src/components/iconImports.js +409 -0
  70. package/src/components/index.js +61 -0
  71. package/src/components/listing/FileListItem.jsx +86 -0
  72. package/src/components/listing/GalleryDisplay.jsx +331 -0
  73. package/src/components/listing/GalleryDisplay.module.css +309 -0
  74. package/src/components/listing/ListingCTAInput.jsx +82 -0
  75. package/src/components/listing/ListingDescriptionInput.jsx +73 -0
  76. package/src/components/listing/ListingField.jsx +101 -0
  77. package/src/components/listing/ListingField.module.css +106 -0
  78. package/src/components/listing/ListingFileInput.jsx +255 -0
  79. package/src/components/listing/ListingFileInput.module.css +192 -0
  80. package/src/components/listing/ListingForm.jsx +90 -0
  81. package/src/components/listing/ListingForm.module.css +38 -0
  82. package/src/components/listing/ListingGalleryInput.jsx +236 -0
  83. package/src/components/listing/ListingGalleryInput.module.css +131 -0
  84. package/src/components/listing/ListingImageInput.jsx +153 -0
  85. package/src/components/listing/ListingTextInput.jsx +72 -0
  86. package/src/feature.config.js +130 -0
  87. package/src/helper/index.js +135 -0
  88. package/src/hooks/useFeatureDefinitionLoader.js +62 -0
  89. package/src/images/full.png +0 -0
  90. package/src/images/fullNoTitle.png +0 -0
  91. package/src/images/previewWidget.png +0 -0
  92. package/src/images/widget.png +0 -0
  93. package/src/index.js +38 -0
  94. package/src/pages/CreateListingPage.jsx +49 -0
  95. package/src/pages/EditListingPage.jsx +58 -0
  96. package/src/reducers/featureBuilderReducer.js +744 -0
  97. package/src/screens/CreateListing.module.css +45 -0
  98. package/src/screens/Form.module.css +734 -0
  99. package/src/screens/FormFieldsStep.jsx +689 -0
  100. package/src/screens/FormLayoutStep.jsx +445 -0
  101. package/src/screens/FormOverviewStep.jsx +396 -0
  102. package/src/screens/ListingScreen.jsx +478 -0
  103. package/src/screens/ListingScreen.module.css +333 -0
  104. package/src/selectors/featureBuilderSelectors.js +529 -0
  105. package/src/types/index.js +91 -0
  106. package/src/utils/textUtils.js +89 -0
  107. package/src/validators/galleryValidators.js +345 -0
  108. package/src/values.config.a.js +49 -0
  109. package/src/values.config.b.js +49 -0
  110. package/src/values.config.c.js +49 -0
  111. package/src/values.config.d.js +49 -0
  112. package/src/values.config.js +49 -0
  113. package/src/webapi/featureDefinitionActions.js +0 -0
  114. package/src/webapi/featuresActions.js +90 -0
  115. package/src/webapi/helper.js +4 -0
  116. package/src/webapi/index.js +12 -0
  117. package/src/webapi/listingActions.js +176 -0
@@ -0,0 +1,240 @@
1
+ /**
2
+ * Wizard Actions for Feature Builder
3
+ * Manages wizard state including navigation, step validation, and mode switching
4
+ * Provides validation logic for overview, fields, and layout steps
5
+ * Coordinates with form state and step progression
6
+ *
7
+ * @namespace wizardActions
8
+ */
9
+
10
+ // Import selectors for form state access
11
+ import {
12
+ selectFormTitle,
13
+ selectFormIcon,
14
+ selectFormDisplayName,
15
+ selectFormLayout,
16
+ selectFormFields,
17
+ } from "../selectors/featureBuilderSelectors";
18
+
19
+ // Wizard action types
20
+ export const SET_WIZARD_MODE = "SET_WIZARD_MODE";
21
+ export const SET_NAVIGATION_STATE = "SET_NAVIGATION_STATE";
22
+ export const UPDATE_STEP_VALIDATION = "UPDATE_STEP_VALIDATION";
23
+ export const MARK_STEP_COMPLETE = "MARK_STEP_COMPLETE";
24
+ export const RESET_WIZARD_STATE = "RESET_WIZARD_STATE";
25
+ export const VALIDATE_AND_UPDATE_STEP = "VALIDATE_AND_UPDATE_STEP";
26
+
27
+ // Mode setters
28
+ export const setWizardMode = (mode) => ({
29
+ type: SET_WIZARD_MODE,
30
+ payload: mode,
31
+ });
32
+
33
+ // Navigation actions
34
+ export const setNavigationState = (navigationState) => ({
35
+ type: SET_NAVIGATION_STATE,
36
+ payload: navigationState,
37
+ });
38
+
39
+ export const setCurrentStep = (step, previousStep = null) => ({
40
+ type: SET_NAVIGATION_STATE,
41
+ payload: {
42
+ currentStep: step,
43
+ previousStep,
44
+ canGoBack: step !== "welcome",
45
+ canGoForward: true,
46
+ },
47
+ });
48
+
49
+ export const goToStep = (step) => (dispatch, getState) => {
50
+ const state = getState()[require("../values.config").reducerKey];
51
+ const currentStep =
52
+ state &&
53
+ state.wizard &&
54
+ state.wizard.navigation &&
55
+ state.wizard.navigation.currentStep;
56
+
57
+ // Clear form submission state when changing steps
58
+ const { clearFormSubmissionState } = require("./formActions");
59
+ dispatch(clearFormSubmissionState());
60
+
61
+ dispatch(setCurrentStep(step, currentStep));
62
+ };
63
+
64
+ export const updateStepValidation = (step, isValid, errors = {}) => ({
65
+ type: UPDATE_STEP_VALIDATION,
66
+ payload: {
67
+ step,
68
+ isValid,
69
+ errors,
70
+ },
71
+ });
72
+
73
+ export const validateAndUpdateStep = (step) => (dispatch, getState) => {
74
+ // Use existing selectors to get form data
75
+ const state = getState();
76
+ const title = selectFormTitle(state);
77
+ const icon = selectFormIcon(state);
78
+ const displayName = selectFormDisplayName(state);
79
+ const layout = selectFormLayout(state);
80
+ const fields = selectFormFields(state);
81
+
82
+ const form = { title, icon, displayName, layout, fields };
83
+
84
+ const { isValid, errors } = getFormValidation(form, step);
85
+
86
+ dispatch(updateStepValidation(step, isValid, errors));
87
+
88
+ // If valid, mark as complete
89
+ if (isValid) {
90
+ dispatch({
91
+ type: MARK_STEP_COMPLETE,
92
+ payload: step,
93
+ });
94
+ }
95
+
96
+ return { isValid, errors };
97
+ };
98
+
99
+ // Step completion
100
+ export const markStepComplete = (step) => ({
101
+ type: MARK_STEP_COMPLETE,
102
+ payload: step,
103
+ });
104
+
105
+ // Reset wizard state
106
+ export const resetWizardState = () => ({
107
+ type: RESET_WIZARD_STATE,
108
+ });
109
+
110
+ const getFormValidation = (form, step) => {
111
+ // Return validation results for undefined form (prevent crashes)
112
+ if (!form) {
113
+ switch (step) {
114
+ case "overview":
115
+ return {
116
+ isValid: false,
117
+ errors: {
118
+ title: "Title is required",
119
+ displayName: "Display name is required",
120
+ icon: "Icon is required",
121
+ },
122
+ };
123
+ case "fields":
124
+ return {
125
+ isValid: false,
126
+ errors: {
127
+ missingTitle: "Title field is required",
128
+ missingImage: "Feature image field is required",
129
+ fieldLabels: "Some fields are missing labels",
130
+ },
131
+ };
132
+ case "layout":
133
+ return {
134
+ isValid: false,
135
+ errors: {
136
+ layoutType: "Layout type is required",
137
+ },
138
+ };
139
+ default:
140
+ return { isValid: false, errors: {} };
141
+ }
142
+ }
143
+
144
+ switch (step) {
145
+ case "overview": {
146
+ const hasTitle = form.title && form.title.trim().length > 0;
147
+ const hasDisplayName =
148
+ form.displayName && form.displayName.trim().length > 0;
149
+ const hasIcon = form.icon && form.icon.length > 0;
150
+
151
+ return {
152
+ isValid: hasTitle && hasDisplayName && hasIcon,
153
+ errors: {
154
+ title: !hasTitle ? "Title is required" : null,
155
+ displayName: !hasDisplayName ? "Display name is required" : null,
156
+ icon: !hasIcon ? "Icon is required" : null,
157
+ },
158
+ };
159
+ }
160
+ case "fields": {
161
+ const hasTitleField =
162
+ form.fields &&
163
+ form.fields.some((field) => field.id === "mandatory-title");
164
+ const hasImageField =
165
+ form.fields &&
166
+ form.fields.some((field) => field.id === "mandatory-feature-image");
167
+
168
+ // Check each field for missing labels and create field-specific errors
169
+ const fieldErrors = {};
170
+ let allFieldsHaveLabels = true;
171
+
172
+ if (form.fields) {
173
+ form.fields.forEach((field) => {
174
+ if (
175
+ (field.type === "text" ||
176
+ field.type === "description" ||
177
+ field.type === "title" ||
178
+ field.type === "image" ||
179
+ field.type === "gallery" ||
180
+ field.type === "feature-image" ||
181
+ field.type === "file" ||
182
+ field.type === "cta") &&
183
+ field.values
184
+ ) {
185
+ if (!field.values.label || field.values.label.trim().length === 0) {
186
+ fieldErrors[field.id] = "Field label is required";
187
+ allFieldsHaveLabels = false;
188
+ }
189
+ }
190
+ });
191
+ }
192
+
193
+ return {
194
+ isValid: hasTitleField && hasImageField && allFieldsHaveLabels,
195
+ errors: {
196
+ missingTitle: !hasTitleField ? "Title field is required" : null,
197
+ missingImage: !hasImageField
198
+ ? "Feature image field is required"
199
+ : null,
200
+ ...fieldErrors,
201
+ },
202
+ };
203
+ }
204
+ case "layout": {
205
+ const hasLayoutType =
206
+ form.layout && form.layout.type && form.layout.type.length > 0;
207
+ return {
208
+ isValid: hasLayoutType,
209
+ errors: {
210
+ layoutType: !hasLayoutType ? "Layout type is required" : null,
211
+ },
212
+ };
213
+ }
214
+ default:
215
+ return { isValid: true, errors: {} };
216
+ }
217
+ };
218
+
219
+ export const setWizardModeAndSave = (mode) => (dispatch) => {
220
+ dispatch(setWizardMode(mode));
221
+ };
222
+
223
+ export const setCurrentStepAndSave =
224
+ (step, previousStep = null) =>
225
+ (dispatch) => {
226
+ dispatch(setCurrentStep(step, previousStep));
227
+ };
228
+
229
+ export const updateStepValidationAndSave =
230
+ (step, isValid, errors = {}) =>
231
+ (dispatch) => {
232
+ dispatch(updateStepValidation(step, isValid, errors));
233
+ if (isValid) {
234
+ dispatch(markStepComplete(step));
235
+ }
236
+ };
237
+
238
+ export const markStepCompleteAndSave = (step) => (dispatch) => {
239
+ dispatch(markStepComplete(step));
240
+ };
@@ -0,0 +1,86 @@
1
+ import React from "react";
2
+ import { Button } from "../components";
3
+ import { PlussCore } from "../feature.config";
4
+ const { Text } = PlussCore.Components;
5
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
6
+ import { faUserCircle, faClock } from "@fortawesome/free-solid-svg-icons";
7
+ import { capitalizeTextWithFallback } from "../utils/textUtils";
8
+ import styles from "./ActivityCardExample.module.css";
9
+
10
+ const ActivityCardExample = ({ displayName }) => {
11
+ // Handle "a/an" grammar correctly
12
+ const getArticle = (name) => {
13
+ if (!name) return "a";
14
+ const firstChar = name.toLowerCase().charAt(0);
15
+ // Handle vowel sounds and special cases
16
+ return ["a", "e", "i", "o", "u"].includes(firstChar) ? "an" : "a";
17
+ };
18
+
19
+ // Handle button click - show that it's a display-only button
20
+ const handleButtonClick = () => {
21
+ alert(
22
+ "This is just a preview of how your button will look. In the actual application, this button would create a new " +
23
+ (displayName || "item") +
24
+ ".",
25
+ );
26
+ };
27
+
28
+ const exampleTitle = displayName
29
+ ? `${capitalizeTextWithFallback(displayName, "")} Example`
30
+ : "Example Item";
31
+
32
+ return (
33
+ <div className={styles.activityCardExample}>
34
+ <Text type="formLabel" className={styles.title}>
35
+ Activity Feed Example
36
+ </Text>
37
+
38
+ {/* Additional info */}
39
+ <div className={styles.additionalInfo}>
40
+ <Text type="help" className={styles.infoText}>
41
+ This shows how your single page name will appear in real activity
42
+ feeds and on buttons throughout the application.
43
+ </Text>
44
+ </div>
45
+
46
+ <div className={styles.activityCard}>
47
+ {/* User Avatar */}
48
+ <div className={styles.profilePic}>
49
+ <FontAwesomeIcon icon={faUserCircle} className={styles.avatarIcon} />
50
+ </div>
51
+
52
+ {/* Activity Content */}
53
+ <div className={styles.activityContent}>
54
+ <div className={styles.activityText}>
55
+ <span className={styles.highlightedUser}>John Smith</span>
56
+ &nbsp;commented on {getArticle(displayName)}&nbsp;
57
+ <span className={styles.highlightedFeature}>
58
+ {capitalizeTextWithFallback(displayName, "item")}
59
+ </span>
60
+ :&nbsp;
61
+ <span className={styles.exampleTitle}>{exampleTitle}</span>
62
+ </div>
63
+
64
+ <div className={styles.activityTime}>
65
+ <FontAwesomeIcon icon={faClock} className={styles.timeIcon} /> 2
66
+ minutes ago
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ {/* Primary Add Button */}
72
+ <div className={styles.buttonContainer}>
73
+ <Button
74
+ buttonType="primary"
75
+ leftIcon="plus"
76
+ isActive
77
+ onClick={handleButtonClick}
78
+ >
79
+ New {capitalizeTextWithFallback(displayName, "Item")}
80
+ </Button>
81
+ </div>
82
+ </div>
83
+ );
84
+ };
85
+
86
+ export default ActivityCardExample;
@@ -0,0 +1,130 @@
1
+ /* Activity Card Example Component Styles */
2
+ .activityCardExample {
3
+ margin-top: 16px;
4
+ margin-bottom: 24px;
5
+ }
6
+
7
+ .title {
8
+ margin-bottom: 8px;
9
+ font-weight: 600;
10
+ color: var(--active-text);
11
+ }
12
+
13
+ .activityCard {
14
+ display: flex;
15
+ align-items: flex-start;
16
+ gap: 12px;
17
+ padding: 16px;
18
+ background: var(--bg-white);
19
+ border: 1px solid var(--linegrey);
20
+ border-radius: 8px;
21
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
22
+ transition: box-shadow 0.2s ease;
23
+ margin-top: 12px;
24
+ }
25
+
26
+ .activityCard:hover {
27
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
28
+ }
29
+
30
+ /* Profile Picture */
31
+ .profilePic {
32
+ width: 34px;
33
+ height: 34px;
34
+ display: flex;
35
+ align-items: center;
36
+ justify-content: center;
37
+ background: var(--bg-bluegrey);
38
+ border-radius: 50%;
39
+ flex-shrink: 0;
40
+ }
41
+
42
+ .avatarIcon {
43
+ font-size: 20px;
44
+ color: var(--text-light);
45
+ }
46
+
47
+ /* Activity Content */
48
+ .activityContent {
49
+ flex: 1;
50
+ min-width: 0;
51
+ }
52
+
53
+ .activityText {
54
+ font-size: 14px;
55
+ line-height: 1.4;
56
+ color: var(--active-text);
57
+ margin-bottom: 4px;
58
+ word-wrap: break-word;
59
+ }
60
+
61
+ .highlightedUser {
62
+ font-weight: 600;
63
+ color: var(--colour-blue);
64
+ }
65
+
66
+ .highlightedFeature {
67
+ font-weight: 600;
68
+ color: var(--colour-blue);
69
+ }
70
+
71
+ .exampleTitle {
72
+ color: var(--text-mid);
73
+ font-style: italic;
74
+ }
75
+
76
+ /* Activity Time */
77
+ .activityTime {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 4px;
81
+ font-size: 12px;
82
+ color: var(--text-light);
83
+ }
84
+
85
+ .timeIcon {
86
+ font-size: 10px;
87
+ color: var(--text-light);
88
+ }
89
+
90
+ /* Additional Info */
91
+ .additionalInfo {
92
+ margin-bottom: 8px;
93
+ }
94
+
95
+ .infoText {
96
+ font-size: 11px;
97
+ color: var(--text-light);
98
+ line-height: 1.3;
99
+ }
100
+
101
+ /* Primary Add Button */
102
+ .buttonContainer {
103
+ margin-top: 20px;
104
+ display: flex;
105
+ justify-content: flex-start;
106
+ }
107
+
108
+ /* Responsive design */
109
+ @media (max-width: 768px) {
110
+ .activityCard {
111
+ padding: 12px;
112
+ }
113
+
114
+ .profilePic {
115
+ width: 30px;
116
+ height: 30px;
117
+ }
118
+
119
+ .avatarIcon {
120
+ font-size: 18px;
121
+ }
122
+
123
+ .activityText {
124
+ font-size: 13px;
125
+ }
126
+
127
+ .activityTime {
128
+ font-size: 11px;
129
+ }
130
+ }
@@ -0,0 +1,33 @@
1
+ import React from "react";
2
+ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3
+ import { faSync } from "@fortawesome/free-solid-svg-icons";
4
+ import styles from "./BackgroundLoader.module.css";
5
+
6
+ /**
7
+ * Background Loader component for overlay loading states
8
+ * Displays a spinning icon with text overlay for async operations
9
+ * Conditionally renders based on show prop
10
+ *
11
+ * @param {Object} props - Component props
12
+ * @param {string} [props.text="Updating..."] - Text to display alongside spinner
13
+ * @param {boolean} [props.show=true] - Whether the loader should be visible
14
+ * @returns {React.ReactElement|null} Background loader overlay or null if hidden
15
+ *
16
+ * @example
17
+ * <BackgroundLoader text="Saving data..." show={isLoading} />
18
+ *
19
+ * @example
20
+ * <BackgroundLoader />
21
+ */
22
+ const BackgroundLoader = ({ text = "Updating...", show = true }) => {
23
+ if (!show) return null;
24
+
25
+ return (
26
+ <div className={styles.backgroundLoader}>
27
+ <FontAwesomeIcon icon={faSync} spin className={styles.icon} />
28
+ <span className={styles.text}>{text}</span>
29
+ </div>
30
+ );
31
+ };
32
+
33
+ export default BackgroundLoader;
@@ -0,0 +1,46 @@
1
+ .backgroundLoader {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 0.5rem;
5
+ padding: 0.5rem 1rem;
6
+ background: rgba(255, 255, 255, 0.95);
7
+ border: 1px solid #e9ecef;
8
+ border-radius: 20px;
9
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
10
+ font-size: 0.875rem;
11
+ color: var(--text-bluegrey, #6c7a90);
12
+ position: fixed;
13
+ top: 1rem;
14
+ right: 1rem;
15
+ z-index: 1000;
16
+ animation: slideIn 0.3s ease-out;
17
+ }
18
+
19
+ .icon {
20
+ color: var(--colour-branding-main, #4a57b7);
21
+ font-size: 0.875rem;
22
+ }
23
+
24
+ .text {
25
+ font-weight: 500;
26
+ }
27
+
28
+ @keyframes slideIn {
29
+ from {
30
+ opacity: 0;
31
+ transform: translateX(20px);
32
+ }
33
+ to {
34
+ opacity: 1;
35
+ transform: translateX(0);
36
+ }
37
+ }
38
+
39
+ @media (max-width: 768px) {
40
+ .backgroundLoader {
41
+ top: auto;
42
+ bottom: 1rem;
43
+ right: 1rem;
44
+ font-size: 0.75rem;
45
+ }
46
+ }