diff --git a/.gitignore b/.gitignore
index 908fdba313a..bac9d688222 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,4 +32,5 @@ Temporary Items
**/node_modules
**/build
.vercel
-.env
\ No newline at end of file
+.env
+.codegpt
\ No newline at end of file
diff --git a/package.json b/package.json
index 7f3eed7825a..a5ec8b366aa 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
- "@telefonica/mistica": "^16.4.0",
+ "@telefonica/mistica": "^16.8.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
diff --git a/src/App.js b/src/App.js
index 02301292aec..b1de95eca6d 100644
--- a/src/App.js
+++ b/src/App.js
@@ -12,6 +12,9 @@ import { useState } from "react";
import { createContext } from "react";
import SkinGenerator from "./pages/skinGenerator";
import PaletteGenerator from "./pages/paletteGenerator";
+import CreateColor from "./pages/skin-tool/onboarding/create-color";
+import CreateTypo from "./pages/skin-tool/onboarding/create-typo";
+import CreateBorder from "./pages/skin-tool/onboarding/create-border";
import Wrapped2023 from "./pages/wrapped2023/index";
import AdventCalendar2024 from "./pages/advent-calendar-2024/index";
import WrappedFinale from "./pages/wrapped2023/finale";
@@ -21,6 +24,7 @@ import ComingSoon from "./pages/advent-calendar-2024/pages/coming-soon";
import ProgressView from "./pages/advent-calendar-2024/pages/progress-view";
import ProductStatus from "./pages/mistica-product-status/index";
import ClaimYourGift from "./pages/advent-calendar-2024/pages/claim-your-gift";
+import OnboardingComplete from "./pages/skin-tool/onboarding/onboarding-complete";
export const SchemeContext = createContext();
@@ -55,8 +59,20 @@ const App = () => {
element: ,
},
{
- path: `/palette-generator`,
- element: ,
+ path: `/create-skin`,
+ element:
+ },
+ {
+ path: `/create-typo`,
+ element:
+ },
+ {
+ path: `/create-border`,
+ element:
+ },
+ {
+ path: `/onboarding-complete`,
+ element:
},
{
path: `/palette-generator`,
diff --git a/src/pages/skin-tool/Instructions.md b/src/pages/skin-tool/Instructions.md
new file mode 100644
index 00000000000..ac47dea95b9
--- /dev/null
+++ b/src/pages/skin-tool/Instructions.md
@@ -0,0 +1,99 @@
+# Mística SkinTool
+
+## Project Goal
+To provide an intuitive tool for creating and previewing custom brand skins efficiently using Mística.
+
+## Project Overview
+Mística SkinTool enables users to customize colors, typography, and borders for brand skins. It features a multi-step flow, real-time previews, localStorage persistence, and JSON export capabilities.
+
+## File Descriptions and Next Steps
+
+### `Theme.js`
+- **Purpose**: Provides a theme context for Mística components, merging custom `themeColors` with defaults to define a comprehensive palette, border radii, and text presets.
+- **Specific To-Dos/Fixes**:
+ - Address doubts: palette variable names can be changed? (e.g., `movistarBlue` ), for colors that vary (e.g., `selectedColor`), what name should be used?
+- **Next Steps**:
+ - Extend to accept `typography` and `borderRadius` props for full customization.
+ - Map user colors to `darkModeColors` for dark mode support.
+ - Optimize palette generation for performance.
+
+### `storageUtils.js`
+- **Purpose**: Manages localStorage with keys, defaults, and utilities to persist and retrieve skin configurations, generating a final JSON config.
+- **Specific To-Dos/Fixes**:
+ - Decide if `DEFAULT_VALUES` should include all variables (recommended for completeness) or only user-editable ones (current approach).
+ - In `generateSkinConfig`, clarify if all color variables should be returned (full palette) or just user-selected ones (current subset).
+- **Next Steps**:
+ - Add error handling for incomplete configs in `generateSkinConfig`.
+ - Support versioning in exported JSON (e.g., `"version": "1.0"`).
+ - Optimize storage calls (e.g., batch updates).
+
+### `ThemePreviewWithTools.js`
+- **Purpose**: Allows previewing and customizing a theme’s colors, typography, and borders, persisting colors in localStorage and exporting as JSON.
+- **Specific To-Dos/Fixes**:
+ - Convert custom dialog and components (e.g., `ColorDialog`, `ColorBox`) to Mística’s `Dialog` and existing components where possible.
+- **Next Steps**:
+ - Add “Typography” and “Border” tabs for full customization preview.
+ - Integrate undo/redo buttons in the navigation bar.
+ - Add a dark mode toggle in the “Preview” tab.
+
+### `getColorScale.js`
+- **Purpose**: Generates a color scale from a base HEX color by converting to HSL, adjusting lightness, and returning HEX values for UI use.
+- **Specific To-Dos/Fixes**:
+ - Simplify shading: Adapt scale based on color brightness (e.g., more light shades for dark bases, more dark shades for light bases).
+- **Next Steps**:
+ - Add unit tests for edge cases (e.g., `#000000`, invalid HEX).
+ - Optimize algorithm for faster execution.
+ - Support custom scale steps (e.g., adjustable `darkCount`/`lightCount`).
+
+### `ColorDialog.js`
+- **Purpose**: A modal for editing color names and values within `ThemePreviewWithTools`, with save/cancel options.
+- **Specific To-Dos/Fixes**:
+ - Adapt to Mística’s `Dialog` or `Drawer` component for consistency and built-in features.
+- **Next Steps**:
+ - Add HEX validation with error feedback.
+ - Enhance accessibility with ARIA labels (e.g., “Edit color name”).
+ - Improve responsiveness for smaller screens.
+ - Improve UI when selecting color input.
+
+### `SkinTool.js`
+- **Purpose**: The entry point, offering options to start a new skin or remix an existing one.
+- **Specific To-Dos/Fixes**:
+ - Improve UI: Add a decorative or interactive mockup to illustrate platform functionality.
+- **Next Steps**:
+ - Add an intro modal with a tutorial and “Get Started” CTA.
+ - Implement “Import Skin” with file input.
+ - Create "Remix with existing skin" behaviour.
+ - Enhance mobile responsiveness.
+
+### `CreateColor.js`
+- **Purpose**: The first step, enabling color palette definition with pickers, persisting selections in localStorage, and navigation.
+- **Next Steps**:
+ - Add section headers (e.g., “Primary”, “Feedback”) and tooltips.
+ - Validate HEX codes and debounce color updates.
+
+### `CreateTypo.js`
+- **Purpose**: The second step in the flow, allowing font and weight selection with a preview, persisting choices in localStorage.
+- **Specific To-Dos/Fixes**:
+ - Convert custom components (e.g., weight buttons) to Mística equivalents (e.g., `Button` with custom styling).
+- **Next Steps**:
+ - Add a preview toggle for all weights of the selected font.
+ - Include font info and use case labels (e.g., “Bold: Headlines”).
+ - Enhance accessibility with keyboard navigation and focus styles.
+
+### `CreateBorder.js`
+- **Purpose**: The third step, allowing border radius customization and rounded button toggling with a preview grid.
+- **Specific To-Dos/Fixes**:
+ - Fix rounded borders not appearing (ensure `borderRadius` applies correctly in `CardWrapper`).
+ - Fix button roundness when checkbox is unselected (correct `button` radius logic in `customTheme`).
+ - Finalize `borderRadiusValues` for “Ultra Soft”, “Soft”, “Square” (adjust from placeholders `[32, 24, 0]`).
+- **Next Steps**:
+ - Integrate with `ThemePreviewWithTools` for consistent preview.
+
+### `OnboardingComplete.js`
+- **Purpose**: The final screen, showing a celebratory message with confetti, marking flow completion, and transitioning to advanced tools.
+
+## Next Steps for SkinTool
+1. **Beta Release**: Implement core UI/UX improvements (e.g., onboarding, navigation) and fixes (e.g., border issues, component conversions).
+2. **Iteration**: Test with users, refine based on feedback (e.g., copy, bugs).
+3. **Feature Expansion**: Add dark mode, enhanced import/export, and collaboration features.
+4. **Production Release**: Finalize accessibility, performance, and deploy with testing/docs.
\ No newline at end of file
diff --git a/src/pages/skin-tool/advanced-tools.css b/src/pages/skin-tool/advanced-tools.css
new file mode 100644
index 00000000000..6c393c310ad
--- /dev/null
+++ b/src/pages/skin-tool/advanced-tools.css
@@ -0,0 +1,29 @@
+/* Estilo del mensaje y confeti que se mueve hacia arriba */
+.animated-message {
+ transition: opacity 1s ease-out, transform 4s ease-out;
+ }
+
+ .animated-message.fade-out {
+ opacity: 0;
+ transform: translateY(-100%); /* Mueve el contenido hacia arriba */
+ }
+
+ /* Herramientas avanzadas */
+ .advanced-tools-container {
+ position: relative;
+ top: 0;
+ animation: slideUp 3s forwards;
+ }
+
+ /* Animación para que las herramientas avanzadas suban */
+ @keyframes slideUp {
+ 0% {
+ transform: translateY(100%);
+ opacity: 0;
+ }
+ 100% {
+ transform: translateY(0);
+ opacity: 1;
+ }
+ }
+
\ No newline at end of file
diff --git a/src/pages/skin-tool/advanced-tools.jsx b/src/pages/skin-tool/advanced-tools.jsx
new file mode 100644
index 00000000000..4fdd8af3526
--- /dev/null
+++ b/src/pages/skin-tool/advanced-tools.jsx
@@ -0,0 +1,373 @@
+// This file defines a React component `ThemePreviewWithTools` that allows users to preview and customize a theme's color palette, typography, and borders.
+// It integrates with localStorage to persist color selections, provides a navigation bar to switch between sections, and includes export functionality
+// for the customized theme as JSON.
+
+// Next steps:
+// 1. change some code to Mística components (dialog, drawer) (maybe is it possible to mistify the colorbox component?)
+// 2.
+
+import React, { useState } from "react";
+import {
+ ResponsiveLayout,
+ MainNavigationBar,
+ ButtonPrimary,
+ Text2,
+ skinVars,
+ Header,
+ HeaderLayout,
+ Inline,
+ Row,
+ IconChevronLeftRegular,
+ IconShareRegular,
+ IconTimeRegular,
+ IconWifiRegular,
+ IconCheckRegular,
+ ButtonLayout,
+ ButtonSecondary,
+ Title2,
+ Text5,
+ Text1,
+ IconButton,
+ IconEditPencilRegular,
+ Title1,
+} from "@telefonica/mistica";
+import getColorScale from './utils/getColorScale';
+import Theme from "./utils/theme";
+import ColorDialog from './utils/color-input';
+
+// Component definition for ThemePreviewWithTools
+const ThemePreviewWithTools = () => {
+ // State to track the currently selected section index in the navigation bar
+ const [index, setIndex] = useState(0);
+
+ //State for the skin name, defaulting to custom-skin
+ const [skinName, setSkinName] = useState('custom-skin');
+
+ //State for the currently selected color in he color picker
+ const [selectedColor, setSelectedColor] = useState('#0072F0');
+
+ //State to manage the theme's color palette, initialized from localStorage or default
+ const [colors, setColors] = useState(() => {
+ const storedColors = localStorage.getItem("skinColors");
+ return storedColors ? JSON.parse(storedColors) : {
+ primaryColor: '#0072F0',
+ secondaryColor: '#FFB600',
+ warningColor: '#FF4C4C',
+ successColor: '#00C9B0',
+ highlightColor: '#B24FFF',
+ neutralColor: '#001E64'
+ };
+ });
+
+ //State to store the generated color scale for the selected color
+ const [colorScaleOutput, setColorScaleOutput] = useState({});
+
+ //State to track the key of the currently selected color for editing
+ const [selectedColorKey, setSelectedColorKey] = useState(null);
+
+ //State to control the visibility of the color dialog
+ const [isColorDialogOpen, setIsColorDialogOpen] = useState(false);
+
+ //State to track the current color key being edited in the color dialog
+ const [currentColorKey, setCurrentColorKey] = useState(null);
+
+ // Temporary color value used in the color dialog before saving
+ const [tempColor, setTempColor] = useState('#0072F0');
+
+
+ // Array defining the sections available in the navigation bar
+ const sections = [
+ { title: "Color Palette", onPress: () => setIndex(0) },
+ { title: "Preview", onPress: () => setIndex(1) },
+ { title: "Typography", onPress: () => setIndex(2) },
+ { title: "Border", onPress: () => setIndex(3) }
+ ];
+
+ // Function to export the current theme configuration as a JSON file
+ const handleExportJSON = () => {
+ const exportData = {
+ name: skinName,
+ colors: colors
+ };
+ const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `${skinName}.json`;
+ link.click();
+ };
+
+ // Function to handle clicking a color box, updating the selected color and its scale
+ const handleColorClick = (key) => {
+ setSelectedColor(colors[key]);
+ setSelectedColorKey(key);
+ setColorScaleOutput({
+ [key]: getColorScale(colors[key])
+ });
+ };
+
+ // Function to open the color dialog for editting a specific color
+ const openColorDialog = (colorKey) => {
+ setCurrentColorKey(colorKey);
+ setTempColor(colors[colorKey] || '#0072F0');
+ setIsColorDialogOpen(true);
+ };
+
+ // Function to save the edited color from the dialog and update the theme
+ const handleSaveColor = () => {
+ if (currentColorKey) {
+ const newColors = {
+ ...colors,
+ [currentColorKey]: tempColor
+ };
+ setColors(newColors);
+ setSelectedColor(tempColor);
+ setColorScaleOutput({
+ [currentColorKey]: getColorScale(tempColor)
+ });
+ localStorage.setItem("skinColors", JSON.stringify(newColors));
+ }
+ setIsColorDialogOpen(false);
+ };
+
+ //Function to update the temporary color value in the dialog
+ const handleCancelColor = () => {
+ setIsColorDialogOpen(false);
+ setTempColor(colors[currentColorKey] || '#0072F0');
+ };
+
+ //Function to update the color value in the dialog
+ const handleColorChange = (newColor) => {
+ setTempColor(newColor);
+ };
+
+ // Component to render a single color box with optional edit icon
+ const ColorBox = ({ colorKey, label, showEditIcon = false }) => {
+ return (
+
+ Primary colors
+
+ Based on the core color of the palette, the rest of the colors are derived. You can manually adjust each derived color if you need to.
+
+ {selectedColorKey && (
+
+ )}
+ {Object.entries(colorScaleOutput).map(([key, scale]) => (
+
+ At a glance
+ }
+ description="Enjoy day of access to over 1000 gyms nationwide"
+ />
+ }
+ description="Only available for gyms with a Day Pass RRP of 15 or under"
+ />
+ {}}>Use Now}
+ secondaryButton={ {}}>Save}
+ />
+
+
+
+
+
+ )}
+
+
+
+ >
+ );
+};
+
+export default ThemePreviewWithTools;
\ No newline at end of file
diff --git a/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Bold.otf b/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Bold.otf
new file mode 100644
index 00000000000..3bde3150df8
Binary files /dev/null and b/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Bold.otf differ
diff --git a/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Light.otf b/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Light.otf
new file mode 100644
index 00000000000..6a02bc83ff4
Binary files /dev/null and b/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Light.otf differ
diff --git a/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Medium.otf b/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Medium.otf
new file mode 100644
index 00000000000..6596d787c8d
Binary files /dev/null and b/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Medium.otf differ
diff --git a/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Regular.otf b/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Regular.otf
new file mode 100644
index 00000000000..767996408ff
Binary files /dev/null and b/src/pages/skin-tool/fonts/Telefonica Sans/General use/Telefonica Sans Regular.otf differ
diff --git a/src/pages/skin-tool/fonts/Vivo/VivoTypeBold.otf b/src/pages/skin-tool/fonts/Vivo/VivoTypeBold.otf
new file mode 100644
index 00000000000..a7dff2e6944
Binary files /dev/null and b/src/pages/skin-tool/fonts/Vivo/VivoTypeBold.otf differ
diff --git a/src/pages/skin-tool/fonts/Vivo/VivoTypeLight.otf b/src/pages/skin-tool/fonts/Vivo/VivoTypeLight.otf
new file mode 100644
index 00000000000..b00dbe6fe44
Binary files /dev/null and b/src/pages/skin-tool/fonts/Vivo/VivoTypeLight.otf differ
diff --git a/src/pages/skin-tool/fonts/Vivo/VivoTypeRegular.otf b/src/pages/skin-tool/fonts/Vivo/VivoTypeRegular.otf
new file mode 100644
index 00000000000..d14c02657d6
Binary files /dev/null and b/src/pages/skin-tool/fonts/Vivo/VivoTypeRegular.otf differ
diff --git a/src/pages/skin-tool/fonts/fonts.css b/src/pages/skin-tool/fonts/fonts.css
new file mode 100644
index 00000000000..2b227d6e4dd
--- /dev/null
+++ b/src/pages/skin-tool/fonts/fonts.css
@@ -0,0 +1,50 @@
+/* VIVO */
+@font-face {
+ font-family: 'VivoType';
+ src: url('./Vivo/VivoTypeRegular.otf') format('opentype');
+ font-weight: normal;
+ font-style: normal;
+ }
+
+ @font-face {
+ font-family: 'VivoType';
+ src: url('./Vivo/VivoTypeBold.otf') format('opentype');
+ font-weight: bold;
+ font-style: normal;
+ }
+
+ @font-face {
+ font-family: 'VivoType';
+ src: url('./Vivo/VivoTypeLight.otf') format('opentype');
+ font-weight: 300;
+ font-style: normal;
+ }
+
+ /* Telefonica Sans */
+@font-face {
+ font-family: 'Telefonica Sans';
+ src: url('./Telefonica Sans/General use/Telefonica Sans Regular.otf') format('opentype');
+ font-weight: normal;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Telefonica Sans';
+ src: url('./Telefonica Sans/General use/Telefonica Sans Bold.otf') format('opentype');
+ font-weight: bold;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Telefonica Sans';
+ src: url('./Telefonica Sans/General use/Telefonica Sans Light.otf') format('opentype');
+ font-weight: 300;
+ font-style: normal;
+}
+
+@font-face {
+ font-family: 'Telefonica Sans';
+ src: url('./Telefonica Sans/General use/Telefonica Sans Medium.otf') format('opentype');
+ font-weight: 500;
+ font-style: normal;
+}
\ No newline at end of file
diff --git a/src/pages/skin-tool/index.jsx b/src/pages/skin-tool/index.jsx
index 9e3d940d3d1..fb4f89feb54 100644
--- a/src/pages/skin-tool/index.jsx
+++ b/src/pages/skin-tool/index.jsx
@@ -1,16 +1,60 @@
-import { MainNavigationBar, ResponsiveLayout } from "@telefonica/mistica";
+// This file defines the `SkinTool` component, the entry point for the skin creation tool.
+// It provides options to start creating a skin from scratch or remix an existing one.
+
+// To-do: improve UI and add a Mockup for decoration purpose and to let the user know how the platform works
+
+import {
+ ResponsiveLayout,
+ skinVars,
+ ButtonPrimary,
+ ButtonSecondary,
+} from "@telefonica/mistica";
import AppLayout from "../../components/app-layout";
+import { useNavigate } from "react-router-dom";
+
const SkinTool = () => {
- return (
-
-
-
+
+
+ This is an example of Display Text
+
+ }
+ description="Display Texts are often used in large text"
+ />
+
+
+
+
+
+
+
+
+ navigate("/create-skin")}>
+ Back to colors
+
+ navigate("/create-border")}>
+ Next step: border
+
+
+
+ );
+};
+
+export default CreateTypo;
\ No newline at end of file
diff --git a/src/pages/skin-tool/onboarding/onboarding-complete.jsx b/src/pages/skin-tool/onboarding/onboarding-complete.jsx
new file mode 100644
index 00000000000..679f8943b14
--- /dev/null
+++ b/src/pages/skin-tool/onboarding/onboarding-complete.jsx
@@ -0,0 +1,83 @@
+// This file defines the `OnboardingComplete` component, which serves as the final screen in the onboarding flow.
+// It displays a celebratory message with confetti animation, marks the flow as completed in localStorage, and transitions to an advanced tools section after a delay.
+
+import React, { useState, useEffect } from "react";
+import { ResponsiveLayout, ProgressBarStepped, Text2, Text6, Text4, skinVars, } from "@telefonica/mistica";
+import { markFlowAsCompleted } from '../utils/storageUtils.js';
+import ConfettiEffect from '../utils/ConfettiEffect.jsx'
+import AdvancedTools from "../advanced-tools.jsx";
+
+const OnboardingComplete = () => {
+ // State to control the visibility of the confetti animation
+ const [showConfetti, setShowConfetti] = useState(true);
+
+ //State to control the visibility of the advanced tools section
+ const [showAdvancedTools, setShowAdvancedTools] = useState(false);
+
+ // State to control the visibility of the initial celebratory content
+ const [showInitialContent, setShowInitialContent] = useState(true);
+
+ // State to trigger a fade-out animation for the initial content
+ const [fadeOut, setFadeOut] = useState(false);
+
+ // Effect hook to handle timers for animations and transitions
+ useEffect(() => {
+ markFlowAsCompleted();
+
+ const confettiTimer = setTimeout(() => {
+ setShowConfetti(false);
+ }, 3000);
+
+ const fadeOutTimer = setTimeout(() => {
+ setFadeOut(true);
+ }, 3500);
+
+ const initialContentTimer = setTimeout(() => {
+ setShowInitialContent(false);
+ setShowAdvancedTools(true);
+ }, 4000);
+
+ return () => {
+ clearTimeout(confettiTimer);
+ clearTimeout(fadeOutTimer);
+ clearTimeout(initialContentTimer);
+ };
+ }, []);
+
+ return (
+
+ {showInitialContent && (
+
+
loguito
+
+
+
+ Step 4 of 4
+
+ )}
+
+ {showInitialContent && (
+
+ {showConfetti && }
+ 🎉 Your skin is ready!
+
+ Now it's time to fine-tune it for perfection.
+
+
+ )}
+
+ {showAdvancedTools && }
+
+ );
+};
+
+export default OnboardingComplete;
\ No newline at end of file
diff --git a/src/pages/skin-tool/utils/ConfettiEffect.jsx b/src/pages/skin-tool/utils/ConfettiEffect.jsx
new file mode 100644
index 00000000000..0bb3b0800f0
--- /dev/null
+++ b/src/pages/skin-tool/utils/ConfettiEffect.jsx
@@ -0,0 +1,81 @@
+// Confetti animation component
+
+import { useRef, useEffect } from "react";
+
+const ConfettiEffect = () => {
+ const canvasRef = useRef(null);
+
+ useEffect(() => {
+ const canvas = canvasRef.current;
+ const ctx = canvas.getContext("2d");
+
+ const confettiPieces = [];
+ const colors = ["#EAC344", "#C466EF", "#E66C64", "#59C2C9", "#0066FF"];
+ const confettiCount = 100;
+
+ const createConfettiPiece = () => ({
+ x: canvas.width / 2, // Center the confetti to the screen horizontal
+ y: canvas.height / 2, // Center the confetti to the screen vertical
+ size: Math.random() * 8 + 5,
+ color: colors[Math.floor(Math.random() * colors.length)],
+ velocityX: (Math.random() - 0.5) * 10, // Animation expands on the sides
+ velocityY: (Math.random() - 0.8) * 10, // Animation goes up
+ rotation: Math.random() * 360,
+ opacity: 1,
+ });
+
+ for (let i = 0; i < confettiCount; i++) {
+ confettiPieces.push(createConfettiPiece());
+ }
+
+ const updateConfetti = () => {
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ confettiPieces.forEach((piece, index) => {
+ piece.x += piece.velocityX;
+ piece.y += piece.velocityY;
+ piece.velocityY += 0.2; // Gravity simulation
+ piece.rotation += piece.velocityX * 2;
+ piece.opacity -= 0.01;
+
+ if (piece.opacity <= 0) {
+ confettiPieces.splice(index, 1);
+ }
+
+ drawConfettiPiece(piece);
+ });
+
+ if (confettiPieces.length > 0) {
+ requestAnimationFrame(updateConfetti);
+ }
+ };
+
+ const drawConfettiPiece = (piece) => {
+ ctx.save();
+ ctx.translate(piece.x, piece.y);
+ ctx.rotate((piece.rotation * Math.PI) / 180);
+ ctx.globalAlpha = piece.opacity;
+ ctx.fillStyle = piece.color;
+ ctx.fillRect(-piece.size / 2, -piece.size / 2, piece.size, piece.size);
+ ctx.restore();
+ };
+
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+
+ updateConfetti();
+
+ setTimeout(() => {
+ confettiPieces.length = 0;
+ }, 3000);
+ }, []);
+
+ return (
+
+ );
+};
+
+export default ConfettiEffect;
diff --git a/src/pages/skin-tool/utils/color-input.jsx b/src/pages/skin-tool/utils/color-input.jsx
new file mode 100644
index 00000000000..16c5033150d
--- /dev/null
+++ b/src/pages/skin-tool/utils/color-input.jsx
@@ -0,0 +1,102 @@
+// This file defines the `ColorDialog` component, a modal dialog for editing a color's name and value.
+// It provides inputs for text and color selection, with save and cancel options.
+// It is used within the ThemePreviewWithTools component.
+
+// To-do: adapt this custom component to Mistica existing Dialog or Drawer component
+
+import React from "react";
+import {
+ ButtonPrimary,
+ ButtonSecondary,
+ ButtonLayout,
+ skinVars,
+} from "@telefonica/mistica";
+
+const ColorDialog = ({
+ isOpen,
+ onClose,
+ onSave,
+ colorName,
+ onColorNameChange,
+ colorValue,
+ onColorChange,
+}) => {
+ //Return null if the dialog is not open
+ if (!isOpen) return null;
+
+ return (
+ <>
+