- Extrae helpers asLingRec/asHotspotMode/asLingRecOpt para evitar `as` en template
- Corrige select: elimina value={}, usa selected={} en cada option
- Añade for/id en todos los pares label-input y aria-label en color picker
- Añade variant="dark" al LingInput de tooltip (evita fondo blanco en editor oscuro)
- Incluye renombrados de componentes y demás cambios de la sesión
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
master
parent
006a0f0d63
commit
f956144577
@ -1,78 +1,7 @@
|
||||
<script lang="ts">
|
||||
import './app.css';
|
||||
import CatalogEditor from '@/vcen-edit/comps/CatalogEditor.svelte';
|
||||
import ZMenu from '@/vcen-edit/comps/ZMenu.svelte';
|
||||
import type { Cat } from '@/vcen/lib';
|
||||
import { logr } from '@/vcen-edit/lib/logr.ts';
|
||||
import {loadCatalogs, saveCatalog} from "@/vcen-edit/services/configurador_db.ts";
|
||||
import Editor from "@/vcen-edit/canvas/Editor.svelte";
|
||||
|
||||
const demoCat: Cat = {
|
||||
pricingDefaults: { currency: 'EUR', vat: 21, vatIncluded: true },
|
||||
id: `ct_${crypto.randomUUID()}` as Cat['id'],
|
||||
name: 'Catálogo demo',
|
||||
lang: 'es',
|
||||
langs: ['es'],
|
||||
atts: [],
|
||||
opts: [],
|
||||
optCategories: [],
|
||||
objs: []
|
||||
};
|
||||
|
||||
let catalogs = $state<Cat[]>([]);
|
||||
let loading = $state(true);
|
||||
let saving = $state(false);
|
||||
let activeCat = $state<Cat>(demoCat);
|
||||
|
||||
$effect(() => {
|
||||
loadCatalogs()
|
||||
.then(list => {
|
||||
catalogs = list;
|
||||
if (list.length > 0) activeCat = list[0];
|
||||
})
|
||||
.catch(e => logr.error('db', 'Error cargando', e))
|
||||
.finally(() => loading = false);
|
||||
});
|
||||
|
||||
function loadCatalog(cat: Cat) {
|
||||
activeCat = JSON.parse(JSON.stringify(cat));
|
||||
}
|
||||
|
||||
async function onSave(updated: Cat) {
|
||||
saving = true;
|
||||
try {
|
||||
await saveCatalog(updated);
|
||||
catalogs = await loadCatalogs();
|
||||
logr.info('editor', 'Guardado', { id: updated.id });
|
||||
} catch (e) {
|
||||
logr.error('editor', 'Error guardando', { error: String(e) });
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="h-screen flex items-center justify-center text-sm text-neutral-400">
|
||||
Cargando...
|
||||
</div>
|
||||
{:else}
|
||||
<div class="h-screen w-screen overflow-hidden flex flex-col">
|
||||
<ZMenu
|
||||
{catalogs}
|
||||
current={activeCat}
|
||||
onload={loadCatalog}
|
||||
/>
|
||||
|
||||
<div class="flex-1 overflow-hidden relative">
|
||||
{#key activeCat.id}
|
||||
<CatalogEditor cat={activeCat} onsave={onSave} />
|
||||
{/key}
|
||||
|
||||
{#if saving}
|
||||
<div class="fixed bottom-4 right-4 bg-black text-white text-sm px-3 py-1 rounded-full shadow">
|
||||
Guardando...
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<Editor />
|
||||
|
||||
@ -1,44 +1,82 @@
|
||||
<script lang="ts">
|
||||
|
||||
import {type Cat, toCatID} from "@/vcen/lib";
|
||||
|
||||
import type {Cat} from "@/vcen/lib";
|
||||
import {loadCatalogs, saveCatalog} from "@/vcen-edit/services/configurador_db.ts";
|
||||
import {logr} from "@/vcen-edit/lib/logr.ts";
|
||||
import CatalogEditor from "@/vcen-edit/comps/CatalogEditor.svelte";
|
||||
import ZMenu from "@/vcen-edit/comps/ZMenu.svelte";
|
||||
|
||||
const STORAGE_KEY = 'vcen-editor-cat'
|
||||
const demoCat: Cat = {
|
||||
pricingDefaults: { currency: 'EUR', vat: 21, vatIncluded: true },
|
||||
id: `ct_${crypto.randomUUID()}` as Cat['id'],
|
||||
name: 'Catálogo demo',
|
||||
lang: 'es',
|
||||
langs: ['es'],
|
||||
atts: [],
|
||||
opts: [],
|
||||
optCategories: [],
|
||||
objs: []
|
||||
};
|
||||
|
||||
function loadCat(): Cat {
|
||||
try {
|
||||
const stored = localStorage.getItem(STORAGE_KEY)
|
||||
if (stored) return JSON.parse(stored)
|
||||
} catch {}
|
||||
return {
|
||||
id: toCatID('test'),
|
||||
name: 'Nuevo',
|
||||
lang: 'es',
|
||||
langs: ['es'],
|
||||
optCategories: [],
|
||||
opts: [],
|
||||
atts: [],
|
||||
objs: [],
|
||||
}
|
||||
let catalogs = $state<Cat[]>([]);
|
||||
let loading = $state(true);
|
||||
let saving = $state(false);
|
||||
let activeCat = $state<Cat>(demoCat);
|
||||
|
||||
$effect(() => {
|
||||
loadCatalogs()
|
||||
.then(list => {
|
||||
catalogs = list;
|
||||
if (list.length > 0) activeCat = list[0];
|
||||
})
|
||||
.catch(e => logr.error('db', 'Error cargando', e))
|
||||
.finally(() => loading = false);
|
||||
});
|
||||
|
||||
function loadCatalog(cat: Cat) {
|
||||
activeCat = JSON.parse(JSON.stringify(cat));
|
||||
}
|
||||
|
||||
function saveCat(cat: Cat) {
|
||||
async function onsave(updated: Cat) {
|
||||
saving = true;
|
||||
try {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(cat))
|
||||
} catch {}
|
||||
await saveCatalog(updated);
|
||||
catalogs = await loadCatalogs();
|
||||
logr.info('editor', 'Guardado', { id: updated.id });
|
||||
} catch (e) {
|
||||
logr.error('editor', 'Error guardando', { error: String(e) });
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
const cat = loadCat()
|
||||
|
||||
</script>
|
||||
|
||||
const oncancel = () => void {}
|
||||
|
||||
<div class="h-full">
|
||||
<CatalogEditor {cat} onsave={saveCat} />
|
||||
</div>
|
||||
</script>
|
||||
|
||||
{#if loading}
|
||||
<div class="h-screen flex items-center justify-center text-sm text-neutral-400">
|
||||
Cargando...
|
||||
</div>
|
||||
{:else}
|
||||
<div class="h-screen w-screen overflow-hidden flex flex-col">
|
||||
<ZMenu
|
||||
{catalogs}
|
||||
current={activeCat}
|
||||
onload={loadCatalog}
|
||||
/>
|
||||
|
||||
<style>
|
||||
<div class="flex-1 overflow-hidden relative">
|
||||
{#key activeCat.id}
|
||||
<CatalogEditor cat={activeCat} {onsave} {oncancel} />
|
||||
{/key}
|
||||
|
||||
</style>
|
||||
{#if saving}
|
||||
<div class="fixed bottom-4 right-4 bg-black text-white text-sm px-3 py-1 rounded-full shadow">
|
||||
Guardando...
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import type { Pricing } from '@/vcen/lib'
|
||||
import type { EditorGlob } from '@/vcen-edit/lib/editor-glob.svelte.js'
|
||||
import { getContext } from 'svelte'
|
||||
|
||||
const glob = getContext<EditorGlob>('editor-glob')
|
||||
|
||||
let {
|
||||
pricing,
|
||||
currencies,
|
||||
defaultCurrency,
|
||||
onUpdate,
|
||||
}: {
|
||||
pricing?: Pricing
|
||||
currencies: { code: string; symbol: string }[]
|
||||
defaultCurrency: string
|
||||
onUpdate: (p: Pricing | undefined) => void
|
||||
} = $props()
|
||||
</script>
|
||||
|
||||
<section class="flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between section-title" style="margin-bottom:0">
|
||||
<span>{glob.t('vcen.editor.entity.pricing')}</span>
|
||||
{#if pricing}
|
||||
<button onclick={() => onUpdate(undefined)} class="ve-btn-link danger">{glob.t('vcen.editor.entity.pricingRm')}</button>
|
||||
{:else}
|
||||
<button onclick={() => onUpdate({ baseAmount: 0 })} class="ve-btn-link accent">{glob.t('vcen.editor.entity.pricingAdd')}</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if pricing}
|
||||
{@const p = pricing}
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ve-label">{glob.t('vcen.editor.entity.baseAmount')}</label>
|
||||
<div class="flex gap-1.5">
|
||||
{#if p.baseAmount === 'consultation'}
|
||||
<span class="field-input text-xs text-gray-500 italic flex-1">{glob.t('vcen.editor.entity.consultation')}</span>
|
||||
{:else}
|
||||
<input type="number" value={p.baseAmount} min="0" step="0.01"
|
||||
oninput={e => onUpdate({ ...p, baseAmount: parseFloat(e.currentTarget.value) || 0 })}
|
||||
class="field-input text-xs flex-1" />
|
||||
{/if}
|
||||
<button
|
||||
onclick={() => onUpdate({ ...p, baseAmount: p.baseAmount === 'consultation' ? 0 : 'consultation' })}
|
||||
class="ve-card-btn shrink-0"
|
||||
title={p.baseAmount === 'consultation' ? glob.t('vcen.editor.entity.toNumTitle') : glob.t('vcen.editor.entity.toConsultTitle')}
|
||||
>{p.baseAmount === 'consultation' ? glob.t('vcen.editor.entity.toNum') : glob.t('vcen.editor.entity.toConsult')}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ve-label">{glob.t('vcen.editor.entity.currency')} <span class="text-gray-700">{glob.t('vcen.editor.entity.currencyOverride')}</span></label>
|
||||
<select value={p.currency ?? ''}
|
||||
onchange={e => onUpdate({ ...p, currency: e.currentTarget.value || undefined })}
|
||||
class="field-input font-mono text-xs">
|
||||
<option value="">{glob.t('vcen.editor.entity.currencyInherit', { code: defaultCurrency })}</option>
|
||||
{#each currencies as c (c.code)}
|
||||
<option value={c.code}>{c.code} {c.symbol}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<p class="text-xs text-gray-700 italic">{glob.t('vcen.editor.entity.noBasePricing')}</p>
|
||||
{/if}
|
||||
</section>
|
||||
@ -0,0 +1,190 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { EditorGlob } from '@/vcen-edit/lib/editor-glob.svelte.js'
|
||||
import type { Transaction, TransactionEasing } from '@/vcen/lib'
|
||||
import { KNOWN_TRANSACTIONS } from '@/vcen-edit/lib/transactions.ts'
|
||||
|
||||
const glob = getContext<EditorGlob>('editor-glob')
|
||||
|
||||
let {
|
||||
value,
|
||||
onUpdate,
|
||||
}: {
|
||||
value?: Transaction
|
||||
onUpdate: (t: Transaction | undefined) => void
|
||||
} = $props()
|
||||
|
||||
const EASINGS: TransactionEasing[] = ['linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out']
|
||||
|
||||
const selectedDef = $derived(KNOWN_TRANSACTIONS.find(d => d.name === value?.name) ?? null)
|
||||
const hasDirection = $derived(selectedDef?.hasDirection ?? false)
|
||||
|
||||
let open = $state(false)
|
||||
|
||||
function add() {
|
||||
onUpdate({ name: '', duration: 400, easing: 'ease' })
|
||||
open = true
|
||||
}
|
||||
|
||||
function remove() {
|
||||
onUpdate(undefined)
|
||||
open = false
|
||||
}
|
||||
|
||||
function patch(p: Partial<Transaction>) {
|
||||
if (!value) return
|
||||
onUpdate({ ...value, ...p })
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- ── Cabecera colapsable ─────────────────────────────────────────────────── -->
|
||||
<div class="border border-gray-800 rounded overflow-hidden">
|
||||
|
||||
<div
|
||||
role="button" tabindex="0"
|
||||
onclick={() => { if (value) open = !open }}
|
||||
onkeydown={e => { if (e.key === 'Enter' && value) open = !open }}
|
||||
class="flex items-center gap-2 px-3 py-2 select-none {value ? 'cursor-pointer hover:bg-gray-800/40' : ''} transition-colors"
|
||||
>
|
||||
{#if value}
|
||||
<svg class="h-2.5 w-2.5 shrink-0 transition-transform text-gray-600 {open ? 'rotate-90' : ''}"
|
||||
viewBox="0 0 8 8" fill="currentColor"><polygon points="2,1 7,4 2,7"/></svg>
|
||||
{/if}
|
||||
<span class="text-[10px] font-bold uppercase tracking-widest text-gray-600 shrink-0">
|
||||
{glob.t('vcen.editor.transaction.section')}
|
||||
</span>
|
||||
{#if value}
|
||||
<span class="text-xs text-gray-500 font-mono truncate">{value.name || '—'}</span>
|
||||
<span class="text-xs text-gray-700 shrink-0">{value.duration}ms</span>
|
||||
<button
|
||||
onclick={e => { e.stopPropagation(); remove() }}
|
||||
class="ve-card-btn danger ml-auto px-1 py-0.5 shrink-0"
|
||||
title={glob.t('vcen.editor.transaction.removeBtn')}
|
||||
>
|
||||
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"
|
||||
stroke-linecap="round" stroke-linejoin="round" class="h-3 w-3">
|
||||
<path d="M2 4h12M5 4V2.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5V4M6 7v5M10 7v5M3 4l.8 9a.5.5 0 0 0 .5.45h7.4a.5.5 0 0 0 .5-.45L13 4"/>
|
||||
</svg>
|
||||
</button>
|
||||
{:else}
|
||||
<span class="text-xs text-gray-700 italic">{glob.t('vcen.editor.transaction.noTransaction')}</span>
|
||||
<button
|
||||
onclick={e => { e.stopPropagation(); add() }}
|
||||
class="ve-card-btn ml-auto shrink-0"
|
||||
>{glob.t('vcen.editor.transaction.addBtn')}</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- ── Formulario ──────────────────────────────────────────────────────── -->
|
||||
{#if value && open}
|
||||
<div class="flex flex-col gap-3 px-3 pb-3 pt-2 border-t border-gray-800/60">
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- Nombre / tipo -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ve-label">{glob.t('vcen.editor.transaction.name')}</label>
|
||||
<select
|
||||
value={value.name}
|
||||
onchange={e => patch({ name: e.currentTarget.value, direction: undefined })}
|
||||
class="field-input text-xs"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{#each KNOWN_TRANSACTIONS as def (def.name)}
|
||||
<option value={def.name} title={def.description}>{def.label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
{#if selectedDef?.description}
|
||||
<span class="text-[10px] text-gray-700 italic">{selectedDef.description}</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Duración -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ve-label">{glob.t('vcen.editor.transaction.duration')}</label>
|
||||
<input
|
||||
type="number" min="0" step="50"
|
||||
value={value.duration}
|
||||
oninput={e => patch({ duration: Number(e.currentTarget.value) || 0 })}
|
||||
placeholder="400"
|
||||
class="field-input text-xs"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- Easing -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ve-label">{glob.t('vcen.editor.transaction.easing')}</label>
|
||||
<select
|
||||
value={value.easing ?? ''}
|
||||
onchange={e => patch({ easing: (e.currentTarget.value || undefined) as TransactionEasing | undefined })}
|
||||
class="field-input text-xs"
|
||||
>
|
||||
<option value="">—</option>
|
||||
{#each EASINGS as eg (eg)}
|
||||
<option value={eg}>{glob.t(`vcen.editor.transaction.easings.${eg}`)}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Dirección — solo si la transición la soporta -->
|
||||
{#if hasDirection}
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ve-label">{glob.t('vcen.editor.transaction.direction')}</label>
|
||||
<select
|
||||
value={value.direction ?? ''}
|
||||
onchange={e => patch({ direction: e.currentTarget.value || undefined })}
|
||||
class="field-input text-xs"
|
||||
>
|
||||
<option value="">—</option>
|
||||
<option value="left">Left</option>
|
||||
<option value="right">Right</option>
|
||||
<option value="up">Up</option>
|
||||
<option value="down">Down</option>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- Delay entrada -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ve-label">{glob.t('vcen.editor.transaction.delayIn')}</label>
|
||||
<input
|
||||
type="number" min="0" step="50"
|
||||
value={value.delayIn ?? ''}
|
||||
oninput={e => patch({ delayIn: Number(e.currentTarget.value) || undefined })}
|
||||
placeholder="0"
|
||||
class="field-input text-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Delay salida -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<label class="ve-label">{glob.t('vcen.editor.transaction.delayOut')}</label>
|
||||
<input
|
||||
type="number" min="0" step="50"
|
||||
value={value.delayOut ?? ''}
|
||||
oninput={e => patch({ delayOut: Number(e.currentTarget.value) || undefined })}
|
||||
placeholder="0"
|
||||
class="field-input text-xs"
|
||||
disabled={value.persistent === true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Persistente -->
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={value.persistent ?? false}
|
||||
onchange={e => patch({ persistent: e.currentTarget.checked || undefined })}
|
||||
/>
|
||||
<span class="text-xs text-gray-400">{glob.t('vcen.editor.transaction.persistent')}</span>
|
||||
<span class="text-xs text-gray-600 italic">{glob.t('vcen.editor.transaction.persistentHint')}</span>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
@ -0,0 +1,50 @@
|
||||
import type { CatalogEditorStore } from '@/vcen-edit/stores/catalog-editor.svelte.ts'
|
||||
import type { EditorGlob } from '@/vcen-edit/lib/editor-glob.svelte.ts'
|
||||
|
||||
export type EntityTab = 'info' | 'atts' | 'visual' | 'rules' | 'issues' | 'meta'
|
||||
|
||||
export function entityTabsArray(glob: EditorGlob): { id: EntityTab; label: string }[] {
|
||||
return [
|
||||
{ id: 'info', label: glob.t('vcen.editor.entity.tabs.info') },
|
||||
{ id: 'atts', label: glob.t('vcen.editor.entity.tabs.atts') },
|
||||
{ id: 'visual', label: glob.t('vcen.editor.entity.tabs.visual') },
|
||||
{ id: 'rules', label: glob.t('vcen.editor.entity.tabs.rules') },
|
||||
{ id: 'issues', label: glob.t('vcen.editor.entity.tabs.issues') },
|
||||
{ id: 'meta', label: glob.t('vcen.editor.entity.tabs.meta') },
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Centraliza la lógica reactiva de tabs para paneles de entidad (obj, sec).
|
||||
*
|
||||
* @param prefix Prefijo del kind de selección: 'obj' | 'sec'
|
||||
* @param editor Store del editor
|
||||
* @param getSel Getter reactivo de la selección actual
|
||||
* @param getIds Getter de los IDs a pasar en editor.select (null si no hay contexto)
|
||||
*/
|
||||
export function createEntityTabs(
|
||||
prefix: string,
|
||||
editor: CatalogEditorStore,
|
||||
getSel: () => { kind: string },
|
||||
getIds: () => Record<string, unknown> | null,
|
||||
) {
|
||||
const tabFromSel = $derived(getSel().kind.split('-')[1] as EntityTab)
|
||||
let extraTab = $state<'visual' | null>(null)
|
||||
|
||||
$effect(() => { void getSel(); extraTab = null })
|
||||
|
||||
const tab = $derived((extraTab ?? tabFromSel) as EntityTab)
|
||||
|
||||
function goTab(t: string) {
|
||||
if (t === 'visual') { extraTab = 'visual'; return }
|
||||
extraTab = null
|
||||
const ids = getIds()
|
||||
if (!ids) return
|
||||
editor.select({ kind: `${prefix}-${t}`, ...ids } as any)
|
||||
}
|
||||
|
||||
return {
|
||||
get tab() { return tab },
|
||||
goTab,
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Catálogo de transiciones conocidas por el editor.
|
||||
* Cada entrada define el nombre (identificador que usará el motor de render)
|
||||
* y los campos opcionales que tienen sentido para esa animación.
|
||||
*
|
||||
* Añadir aquí una entrada la hace disponible en el selector del TransactionEditor.
|
||||
* El motor de visualización es responsable de interpretar el `name`.
|
||||
*/
|
||||
export interface TransactionDef {
|
||||
/** Identificador — coincide con Transaction.name */
|
||||
name: string;
|
||||
/** Etiqueta legible para la UI */
|
||||
label: string;
|
||||
/** Si true, el campo `direction` es relevante para esta transición */
|
||||
hasDirection?: boolean;
|
||||
/** Descripción corta para tooltip o ayuda contextual */
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export const KNOWN_TRANSACTIONS: TransactionDef[] = [
|
||||
{
|
||||
name: 'fade',
|
||||
label: 'Fade',
|
||||
description: 'Fundido de opacidad. La capa aparece y desaparece gradualmente.',
|
||||
},
|
||||
{
|
||||
name: 'crossfade',
|
||||
label: 'Crossfade',
|
||||
description: 'La imagen antigua y la nueva se solapan durante la transición.',
|
||||
},
|
||||
{
|
||||
name: 'slide',
|
||||
label: 'Slide',
|
||||
hasDirection: true,
|
||||
description: 'La capa entra deslizándose desde un lado.',
|
||||
},
|
||||
{
|
||||
name: 'zoom',
|
||||
label: 'Zoom',
|
||||
description: 'La capa aparece con un efecto de escala.',
|
||||
},
|
||||
{
|
||||
name: 'flip',
|
||||
label: 'Flip',
|
||||
hasDirection: true,
|
||||
description: 'Giro 3D sobre el eje horizontal o vertical.',
|
||||
},
|
||||
{
|
||||
name: 'blur',
|
||||
label: 'Blur',
|
||||
description: 'La imagen se desenfoca al salir y se enfoca al entrar.',
|
||||
},
|
||||
{
|
||||
name: 'wipe',
|
||||
label: 'Wipe',
|
||||
hasDirection: true,
|
||||
description: 'Barrido: la nueva imagen barre a la anterior en una dirección.',
|
||||
},
|
||||
{
|
||||
name: 'dissolve',
|
||||
label: 'Dissolve',
|
||||
description: 'Disolución píxel a píxel entre imagen antigua y nueva.',
|
||||
},
|
||||
{
|
||||
name: 'morph',
|
||||
label: 'Morph',
|
||||
description: 'Transformación fluida entre dos estados de la capa.',
|
||||
},
|
||||
]
|
||||
|
Before Width: | Height: | Size: 553 B After Width: | Height: | Size: 561 B |
Loading…
Reference in new issue