diff --git a/src/App.svelte b/src/App.svelte index 7a45bf1..500a6eb 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -1,18 +1,20 @@ @@ -22,17 +24,17 @@
- {#key navegationState.activePage} + {#key navigationState.activePage}
- {#if navegationState.activePage === 'cfg'} + {#if navigationState.activePage === 'cfg'} - {:else if navegationState.activePage === 'sel'} + {:else if navigationState.activePage === 'sel'} - {:else if navegationState.activePage === 'gal'} + {:else if navigationState.activePage === 'gal'} {:else} @@ -43,6 +45,9 @@ + + + diff --git a/src/lib/cmps/BtOption.svelte b/src/lib/cmps/BtOption.svelte index cdf4167..911ebae 100644 --- a/src/lib/cmps/BtOption.svelte +++ b/src/lib/cmps/BtOption.svelte @@ -3,6 +3,7 @@ import type { OptionReference } from "../data/DataConfigTypes"; import {dataConfig} from "../data/DataConfig"; import {formatPrice} from "../utils/formats"; + import {popupMessage} from "../data/PopsState.svelte"; const imgPath : string = dataConfig.sources.imgValues @@ -35,6 +36,9 @@ function handleClick() { console.log('🔵 CLICK:', { viewId, groupId, optionId: optionRef.id }); + if (consultation.message) { + popupMessage.open(consultation.message); + } appState.setOption(viewId, groupId, optionRef.id); } @@ -71,16 +75,19 @@ class:border-gray-200={!isSelected} class:cursor-not-allowed={isDisabled} + class:cursor-pointer={!isDisabled} onclick={handleClick} > + {#if consultation.message}
⚠
{/if} + {#if isSelected}
@@ -105,7 +112,7 @@ {/if} -
+
{optionValue?.name || optionRef.id}
@@ -123,11 +130,13 @@ {/if} + diff --git a/src/lib/cmps/BtViewAccordion.svelte b/src/lib/cmps/BtViewAccordion.svelte index 21914ac..6aec4d9 100644 --- a/src/lib/cmps/BtViewAccordion.svelte +++ b/src/lib/cmps/BtViewAccordion.svelte @@ -1,21 +1,24 @@
+ + diff --git a/src/lib/cmps/HotspotModal.svelte b/src/lib/cmps/HotspotModal.svelte new file mode 100644 index 0000000..e3a872a --- /dev/null +++ b/src/lib/cmps/HotspotModal.svelte @@ -0,0 +1,249 @@ + + + + +{#if isOpen} + + +{/if} + + \ No newline at end of file diff --git a/src/lib/cmps/HotspotPositioner.svelte b/src/lib/cmps/HotspotPositioner.svelte new file mode 100644 index 0000000..eaec0bb --- /dev/null +++ b/src/lib/cmps/HotspotPositioner.svelte @@ -0,0 +1,372 @@ + + +
+
+

+ 🎯 Posicionador de Hotspots +

+

+ Haz click en la imagen para añadir hotspots. Las coordenadas se copiarán automáticamente. +

+
+ + +
+
+ + +
+ + +
+ + +
+ +
+ {#each colors as color} + + {/each} +
+
+ + +
+ +
+
+ + +
+ + +
+ + {positions.length} hotspot{positions.length !== 1 ? 's' : ''} + +
+
+ + +
+
+ {viewName} { + const target = e.currentTarget as HTMLImageElement; + target.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="800" height="600"%3E%3Crect fill="%23ddd" width="800" height="600"/%3E%3Ctext x="50%25" y="50%25" text-anchor="middle" fill="%23999" font-size="24"%3EImagen no encontrada%3C/text%3E%3C/svg%3E'; + }} + /> + + + {#if showGrid} + + {#each Array(10) as _, i} + + + + + + + {(i + 1) * 10} + + + {(i + 1) * 10} + + {/each} + + {/if} + + + {#each positions as position, index} +
+ +
+ + +
+ + {index + 1} + +
+ + + + + +
+ x: {position.x}, y: {position.y} +
+
+
+ {/each} +
+
+ + + {#if positions.length > 0} +
+

+ Hotspots Creados +

+
+ {#each positions as position, index} +
+
+ + {index + 1} + + + x: {position.x}, y: {position.y} + +
+ +
+ {/each} +
+
+ {/if} + + +
+

💡 Instrucciones

+
    +
  • Haz click en la imagen donde quieras posicionar un hotspot
  • +
  • Las coordenadas se copiarán automáticamente al portapapeles
  • +
  • Ajusta el radio y color antes de hacer click
  • +
  • Activa la cuadrícula para precisión
  • +
  • Usa "Exportar Código" para obtener todos los hotspots de una vez
  • +
  • Revisa la consola del navegador para ver el código generado
  • +
+
+
+ + diff --git a/src/lib/cmps/NvMain.svelte b/src/lib/cmps/NvMain.svelte index a3e779f..757050c 100644 --- a/src/lib/cmps/NvMain.svelte +++ b/src/lib/cmps/NvMain.svelte @@ -1,5 +1,5 @@ + +{#if popupImage.isVisible } + + + + +
+ +
e.stopPropagation()} + role="document" + > +

+ Opción de {popupImage.texto} +

+ + {#if isLoading} +
+
+
+ {/if} + + Imagen ampliada + + +
+
+{/if} diff --git a/src/lib/cmps/PoMessage.svelte b/src/lib/cmps/PoMessage.svelte new file mode 100644 index 0000000..a8d6525 --- /dev/null +++ b/src/lib/cmps/PoMessage.svelte @@ -0,0 +1,79 @@ + + + + + +{#if popupMessage.isVisible } + +{/if} \ No newline at end of file diff --git a/src/lib/cmps/ViewWithHotspots.svelte b/src/lib/cmps/ViewWithHotspots.svelte new file mode 100644 index 0000000..606bd17 --- /dev/null +++ b/src/lib/cmps/ViewWithHotspots.svelte @@ -0,0 +1,130 @@ + + +
+ + {view.name { + const target = e.currentTarget as HTMLImageElement; + target.src = dataConfig.sources.images + 'placeholder.jpg'; + }} + /> + + + {#if showHotspots} +
+ {#each groupsWithHotspots as group} + {#if group.hotspot} +
+ handleHotspotClick(group)} + /> +
+ {/if} + {/each} +
+ {/if} + + + {#if showHotspots && groupsWithHotspots.length > 0} +
+
+ + + + + Pasa el ratón para ver detalles + +
+
+ {/if} +
+ + +{#if selectedGroup} + +{/if} + + diff --git a/src/lib/data/DataConfig.ts b/src/lib/data/DataConfig.ts index 241f430..0bf80ba 100644 --- a/src/lib/data/DataConfig.ts +++ b/src/lib/data/DataConfig.ts @@ -23,10 +23,10 @@ export const dataConfig : DataConfig = { imgViews64 : '', // imgOptionsHi: Directorio donde residen las imágenes de las opciones // en alta resolución - imgOptionsHi : '', + imgOptionsHi : './img/mt/', // imgOptionsLow: Directorio donde residen las imágenes de las opciones // en baja resolución - imgOptionsLow : '', + imgOptionsLow : './img/mt/', // imgGallery: Directorio donde residen las galerías de imágenes imgGallery : './img/gl/', imgGalleryLow : './img/gl/lw/', @@ -192,9 +192,9 @@ export const dataConfig : DataConfig = { }, sensow: { value : 'sensow', - name : 'D-Neo Senso Wash', - text : '', - image : '', + name : 'D-Neo SensoWash', + text : 'SensoWash® D-Neo aporta tecnología de lavado en un formato compacto. Su diseño minimalista de perfil bajo se integra con armonía en todas las series de diseño de Duravit.Una solución funcional que suma bienestar, higiene avanzada y estética moderna a presupuestos moderados.', + image : 'sensow.jpg', thumb : 'sensow_thumb.jpg', }, @@ -251,15 +251,15 @@ export const dataConfig : DataConfig = { keyla: { value : 'keyla', name : 'Dekton Keyla', - text : '', - image : 'encimeras/keyla.jpg', + text : 'Con un toque de particularidad, este tono se inspira en los mármoles oscuros con vetas claras para los ambientes más especiales.', + image : 'keyla.jpg', thumb : 'keyla_thumb.png', }, kairos: { value : 'kairos', name : 'Dekton Kairos', - text : '', - image : 'encimeras/kairos.jpg', + text : 'Con sus características únicas, Kairos se asimila a uno de los mármoles más blancos y fríos del mercado, el mármol estatuario. Entre su luminosidad resaltan las finas vetas en gris pálido.', + image : 'kairos.jpg', thumb : 'kairos_thumb.png', }, enpe: { @@ -342,6 +342,14 @@ export const dataConfig : DataConfig = { rules: { general: { suelo: { allow:['trentino','roble'] } } } }, ], + // 🎯 HOTSPOT CONFIGURADO + hotspot: { + x: 65, + y: 55, + radius: 14, + color: '#00a63e', // Rosa + icon: '✨' + } }, { id: 'inodoro', @@ -357,8 +365,17 @@ export const dataConfig : DataConfig = { warn: 'Este inodoro es suspendido' }, ], + // 🎯 HOTSPOT CONFIGURADO + hotspot: { + x: 34, + y: 85, + radius: 14, + color: '#00a63e', // Rosa + icon: '✨' + } }, ], + }, bano2: { @@ -506,8 +523,16 @@ export const dataConfig : DataConfig = { id: 'enpe', amount: 'CON COSTE', warn: 'Coste según tipología y personalización, se contratará con Studio56', - } + }, ], + // 🎯 HOTSPOT CONFIGURADO + hotspot: { + x: 40, + y: 63, + radius: 14, + color: '#00a63e', // Rosa + icon: '✨' + } }, ], }, diff --git a/src/lib/data/DataConfigTypes.ts b/src/lib/data/DataConfigTypes.ts index 80be8df..f5acc3b 100644 --- a/src/lib/data/DataConfigTypes.ts +++ b/src/lib/data/DataConfigTypes.ts @@ -9,6 +9,22 @@ export interface OptionValue { thumb?: string; } + +// 2. Definición de un Hotspot +export interface Hotspot { + /** Posición X en porcentaje (0-100) */ + x: number; + /** Posición Y en porcentaje (0-100) */ + y: number; + /** Radio del hotspot en píxeles (opcional, default: 20) */ + radius?: number; + /** Color del hotspot (opcional, default: verde) */ + color?: string; + /** Icono del hotspot (opcional, default: 'ℹ️') */ + icon?: string; +} + + // 2. Definición de la referencia dentro de un grupo (ej: { id: 'trentino' }) export interface OptionReference { id: string; @@ -27,6 +43,8 @@ export interface OptionGroup { help: string; default: string; warn?: string; + /** Hotspot para visualizar esta opción en la vista */ + hotspot?: Hotspot; values: OptionReference[]; } diff --git a/src/lib/data/NavState.svelte.ts b/src/lib/data/NavState.svelte.ts index c16f0ed..e7e9c80 100644 --- a/src/lib/data/NavState.svelte.ts +++ b/src/lib/data/NavState.svelte.ts @@ -9,14 +9,24 @@ interface PageInfo { } +console.log('NAV STORE CARGADO'); + // Rune global para la sección activa let activeSection = $state( window.location.hash?.replace('#', '') as PageId || 'ini' ); +let isOpenGeneral = $state(true); + +export const navigationState = { + get isOpenGeneral(): boolean { + return isOpenGeneral; + }, + turnOpenGeneral() { + return isOpenGeneral = !isOpenGeneral; + }, -export const navegationState = { get activePage (): PageId { return activeSection }, diff --git a/src/lib/data/PopsState.svelte.ts b/src/lib/data/PopsState.svelte.ts new file mode 100644 index 0000000..f988912 --- /dev/null +++ b/src/lib/data/PopsState.svelte.ts @@ -0,0 +1,32 @@ + +export type PopupMessageState = { + mensaje : string; +} | null ; + + +let popupMessageState = $state(null); + + + export const popupMessage = { + get isVisible () : boolean { return popupMessageState !== null; }, + get mensaje () : string { return popupMessageState?.mensaje ?? ''}, + open : (mensaje : string) => { popupMessageState = { mensaje }; }, + close : () => popupMessageState = null, + } + + + export type PopupImageState = { + texto: string; + imagen: string; + } | null; + + + let popupImageState = $state(null); + + export const popupImage = { + get isVisible () : boolean { return popupImageState !== null; }, + get texto () : string { return popupImageState?.texto ?? '' }, + get imagen() : string { return popupImageState?.imagen ?? ''; }, + open : (imagen: string, texto: string) => { popupImageState = { imagen, texto }}, + close : () => popupImageState = null, + } diff --git a/src/lib/views/VwConfig.svelte b/src/lib/views/VwConfig.svelte index 16be3e5..aae2899 100644 --- a/src/lib/views/VwConfig.svelte +++ b/src/lib/views/VwConfig.svelte @@ -5,6 +5,8 @@ import BtViewSelector from "../cmps/BtViewSelector.svelte"; import PnGroup from "../cmps/PnGroup.svelte"; import BtViewAccordion from "../cmps/BtViewAccordion.svelte"; + import {navigationState} from "../data/NavState.svelte"; + import ViewWithHotspots from "../cmps/ViewWithHotspots.svelte"; let isLoading = $state(true); @@ -49,6 +51,7 @@ + + + +
+
+ +
+
+ + @@ -94,6 +110,8 @@