Mudanças entre as edições de "Módulo:Droflax"
Ir para navegação
Ir para pesquisar
Etiquetas: anulando Desfazer |
|||
| Linha 1: | Linha 1: | ||
local p = {} | |||
local args = frame:getParent().args | |||
function p._main(args) | |||
-- Validaciones básicas | |||
if not args.nome or args.nome == '' then | |||
return '<div style="color:red;">Error: Nome es requerido</div>' | |||
end | |||
-- Procesamiento de habilidades | |||
local habilidades = {} | |||
for i = 1, 21 do | |||
local nomeKey = 'hab' .. i .. '-nome' | |||
if args[nomeKey] and args[nomeKey] ~= '' then | |||
table.insert(habilidades, { | |||
nome = args[nomeKey] or '', | |||
icon = args['hab' .. i .. '-icon'] or '', | |||
level = tonumber(args['hab' .. i .. '-level']) or 1, | |||
desc = args['hab' .. i .. '-desc'] or '', | |||
atr = args['hab' .. i .. '-atr'] or '', | |||
video = args['hab' .. i .. '-video'] or '' | |||
}) | |||
end | |||
end | |||
-- Procesamiento de skins | |||
local skins = {} | |||
for i = 1, 11 do | |||
local imageKey = 'skin' .. i .. '-image' | |||
if args[imageKey] and args[imageKey] ~= '' then | |||
table.insert(skins, { | |||
image = args[imageKey] or '', | |||
banner = args['skin' .. i .. '-banner'] or '', | |||
tooltip = args['skin' .. i .. '-tooltip'] or '' | |||
}) | |||
end | |||
end | |||
-- Construcción del HTML usando mw.html (más eficiente) | |||
local root = mw.html.create('div') | |||
:addClass('personaje-box') | |||
:attr('data-tier', args.tier or '') | |||
-- Header | |||
local header = root:tag('div'):addClass('personaje-header') | |||
local topbar = header:tag('div'):addClass('personaje-topbar') | |||
local nomeBox = topbar:tag('div'):addClass('personaje-nome-box') | |||
nomeBox:tag('img') | |||
:attr('src', '/images/6/63/Franky_ts_medal.png') | |||
:addClass('topbar-icon') | |||
local nomeCategory = nomeBox:tag('div'):addClass('personaje-nome-category') | |||
nomeCategory:tag('div'):addClass('nome'):wikitext(args.nome) | |||
nomeCategory:tag('div'):addClass('classes'):wikitext(args.classe or '') | |||
-- Description | |||
header:tag('div') | |||
:addClass('topbar-description') | |||
:wikitext((args.nome or '') .. ' (' .. (args.tier or '') .. ') é um personagem do tier ' .. string.lower(args.tier or '') .. '.') | |||
-- Tabs | |||
local tabs = header:tag('div'):addClass('personaje-tabs') | |||
tabs:tag('button'):addClass('tab-btn'):attr('data-tab', 'arma'):wikitext('Arma') | |||
tabs:tag('button'):addClass('tab-btn active'):attr('data-tab', 'habilidades'):wikitext('Habilidades') | |||
tabs:tag('button'):addClass('tab-btn'):attr('data-tab', 'skins'):wikitext('Skins') | |||
-- Art image | |||
root:tag('img') | |||
:addClass('art-personaje') | |||
:attr('src', args.image or '') | |||
:attr('alt', 'Arte del personaje') | |||
-- Habilidades tab content | |||
local habTab = root:tag('div') | |||
:addClass('tab-content active') | |||
:attr('id', 'habilidades') | |||
-- Cuadros container (iconos de habilidades) | |||
local cuadrosContainer = habTab:tag('div'):addClass('cuadros-container') | |||
for i, hab in ipairs(habilidades) do | |||
local cuadro = cuadrosContainer:tag('div') | |||
:addClass('cuadro') | |||
:attr('data-hab-index', i) | |||
:attr('title', hab.nome) | |||
cuadro:tag('img') | |||
:attr('src', hab.icon) | |||
:attr('alt', hab.nome) | |||
:css('width', '100%') | |||
:css('height', '100%') | |||
:css('object-fit', 'cover') | |||
end | |||
-- Habilidades details container | |||
local habContainer = habTab:tag('div'):addClass('habilidades-container') | |||
local habDetails = habContainer:tag('div'):addClass('habilidades-details') | |||
local descripcionContainer = habDetails:tag('div') | |||
:addClass('descripcion-container') | |||
:attr('id', 'descripcion-container') | |||
local videoContainer = habContainer:tag('div') | |||
:addClass('video-container') | |||
:attr('id', 'video-container') | |||
-- Skins tab content | |||
local skinsTab = root:tag('div') | |||
:addClass('tab-content') | |||
:attr('id', 'skins') | |||
local cardSkins = skinsTab:tag('div'):addClass('card-skins') | |||
cardSkins:tag('span') | |||
:addClass('card-skins-title') | |||
:wikitext('SKINS & SPOTLIGHTS') | |||
local carouselWrapper = cardSkins:tag('div'):addClass('skins-carousel-wrapper') | |||
carouselWrapper:tag('button') | |||
:addClass('skins-arrow left') | |||
:wikitext('«') | |||
local carousel = carouselWrapper:tag('div'):addClass('skins-carousel') | |||
for _, skin in ipairs(skins) do | |||
local skinCard = carousel:tag('div'):addClass('skin-card') | |||
skinCard:tag('img') | |||
:addClass('skins--imageBanner') | |||
:attr('src', skin.banner) | |||
:attr('alt', 'banner') | |||
skinCard:tag('img') | |||
:addClass('skins--imageSkin') | |||
:attr('src', skin.image) | |||
:attr('alt', 'skin') | |||
end | |||
carouselWrapper:tag('button') | |||
:addClass('skins-arrow right') | |||
:wikitext('»') | |||
-- Datos JSON para JavaScript (más eficiente que data-attributes múltiples) | |||
local dataScript = mw.html.create('script') | |||
:attr('type', 'application/json') | |||
:attr('id', 'persona-data-json') | |||
:wikitext(mw.text.jsonEncode({ | |||
nome = args.nome or '', | |||
tier = args.tier or '', | |||
classe = args.classe or '', | |||
image = args.image or '', | |||
habilidades = habilidades, | |||
skins = skins | |||
})) | |||
-- CSS (solo lo esencial, el resto en CSS común) | |||
local style = mw.html.create('style'):wikitext([[ | |||
.personaje-box { padding: 16px; color: #000; font-family: 'Segoe UI', sans-serif; width: 90%; margin: auto; position: relative; user-select: none; } | |||
.personaje-topbar { display: flex; flex-direction: column; align-items: flex-start; padding: 8px 16px; } | |||
.personaje-nome-box { display: flex; align-items: center; gap: 8px; } | |||
.topbar-icon { width: 90px; height: 90px; object-fit: none; background: #60dae2; } | |||
.nome { font-size: 60px; font-family: 'Orbitron', sans-serif; font-weight: 900; } | |||
.topbar-description { font-size: 16px; margin-top: 6px; background: #6AF3FB; width: fit-content; padding-inline: 16px; border-radius: 0 10px 10px 0; } | |||
.personaje-header { display: flex; gap: 10px; flex-direction: column; } | |||
.art-personaje { width: 665px; position: absolute; right: 1rem; top: 0.4rem; z-index: 9; } | |||
/* ... resto del CSS optimizado ... */ | |||
]]) | |||
-- JavaScript optimizado | |||
local script = mw.html.create('script'):wikitext([[ | |||
(function() { | |||
'use strict'; | |||
// Cargar datos JSON (más rápido que procesar data-attributes) | |||
const dataEl = document.getElementById('persona-data-json'); | |||
if (!dataEl) return console.error('Persona: no se encontró JSON data'); | |||
let data; | |||
try { | |||
data = JSON.parse(dataEl.textContent); | |||
} catch (e) { | |||
return console.error('Persona: error parsing JSON', e); | |||
} | |||
// Cache de elementos DOM | |||
const elements = { | |||
tabBtns: document.querySelectorAll('.tab-btn'), | |||
tabContents: document.querySelectorAll('.tab-content'), | |||
cuadros: document.querySelectorAll('.cuadro'), | |||
descripcionContainer: document.getElementById('descripcion-container'), | |||
videoContainer: document.getElementById('video-container'), | |||
personajeBox: document.querySelector('.personaje-box') | |||
}; | |||
// Aplicar tier class | |||
const tierClass = { | |||
'bronze': 'tier-bronze', 'bronce': 'tier-bronze', | |||
'silver': 'tier-silver', 'prata': 'tier-silver', | |||
'gold': 'tier-gold', 'ouro': 'tier-gold', | |||
'diamond': 'tier-diamond', 'diamante': 'tier-diamond' | |||
}[data.tier.toLowerCase()]; | |||
if (tierClass) elements.personajeBox.classList.add(tierClass); | |||
// Event listeners optimizados | |||
elements.tabBtns.forEach(btn => { | |||
btn.addEventListener('click', handleTabClick, false); | |||
}); | |||
elements.cuadros.forEach((cuadro, index) => { | |||
cuadro.addEventListener('click', () => handleHabilidadClick(index), false); | |||
}); | |||
// Funciones optimizadas | |||
function handleTabClick(e) { | |||
const target = e.target.dataset.tab; | |||
elements.tabBtns.forEach(b => b.classList.remove('active')); | |||
elements.tabContents.forEach(c => c.classList.remove('active')); | |||
e.target.classList.add('active'); | |||
document.getElementById(target).classList.add('active'); | |||
} | |||
function handleHabilidadClick(index) { | |||
const hab = data.habilidades[index]; | |||
if (!hab) return; | |||
// Generar HTML de atributos | |||
const atributos = hab.atr.split(',').map(v => v.trim()); | |||
const labels = ['Poder PVE', 'Poder PVP', 'Energía', 'Recarga']; | |||
const icons = [ | |||
'/images/7/7a/Icon-pve.png', | |||
'/images/5/5f/Icon-pvp.png', | |||
'/images/3/38/Icon-energy.png', | |||
'/images/b/b1/Icon-cooldown.png' | |||
]; | |||
const atributosHTML = atributos.map((v, i) => { | |||
let f = v === '-' ? '-' : parseInt(v); | |||
if (i === 1 && !isNaN(f)) f = (f > 0 ? '+' : '') + f; | |||
return ` | |||
<span class="simple-tooltip"> | |||
<div class="cardAttribute"> | |||
<img src="${icons[i]}" class="cardAttribute--icon"> | |||
<h2 class="cardAttribute--value">${f}${i === 0 && f !== '-' ? ' seg' : ''}</h2> | |||
</div> | |||
</span>`; | |||
}).join(''); | |||
// Actualizar descripción | |||
elements.descripcionContainer.innerHTML = ` | |||
<div class="titulo-habilidad"> | |||
<h3>${hab.nome}</h3> | |||
<div class="tooltip-container"> | |||
<button class="info-btn">i</button> | |||
<span class="tooltip-text">Información adicional sobre la habilidad.</span> | |||
</div> | |||
</div> | |||
<div class="attribute--cardsContainer">${atributosHTML}</div> | |||
<div class="desc">${hab.desc.replace(/'''(.*?)'''/g, '<b>$1</b>')}</div> | |||
`; | |||
// Actualizar video | |||
elements.videoContainer.innerHTML = hab.video ? | |||
`<video width="100%" controls playsinline><source src="${hab.video}" type="video/webm"></video>` : ''; | |||
// Actualizar clases activas | |||
elements.cuadros.forEach(c => c.classList.remove('activo')); | |||
elements.cuadros[index].classList.add('activo'); | |||
} | |||
// Inicializar primera habilidad | |||
if (data.habilidades.length > 0) { | |||
handleHabilidadClick(0); | |||
} | |||
// Inicializar carrusel de skins | |||
initSkinsCarousel(); | |||
function initSkinsCarousel() { | |||
const carousel = document.querySelector('.skins-carousel'); | |||
if (!carousel) return; | |||
const wrapper = document.querySelector('.skins-carousel-wrapper'); | |||
const leftBtn = document.querySelector('.skins-arrow.left'); | |||
const rightBtn = document.querySelector('.skins-arrow.right'); | |||
let isScrolling = false; | |||
function updateArrows() { | |||
const maxScroll = carousel.scrollWidth - carousel.clientWidth; | |||
const hasLeft = carousel.scrollLeft > 5; | |||
const hasRight = carousel.scrollLeft < maxScroll - 5; | |||
leftBtn.style.display = hasLeft ? 'inline-block' : 'none'; | |||
rightBtn.style.display = hasRight ? 'inline-block' : 'none'; | |||
wrapper.classList.toggle('has-left', hasLeft); | |||
wrapper.classList.toggle('has-right', hasRight); | |||
} | |||
function scrollCarousel(direction) { | |||
if (isScrolling) return; | |||
isScrolling = true; | |||
const scrollAmount = carousel.clientWidth * 0.6; | |||
const currentScroll = carousel.scrollLeft; | |||
const maxScroll = carousel.scrollWidth - carousel.clientWidth; | |||
let nextScroll; | |||
if (direction === 'left') { | |||
nextScroll = Math.max(0, currentScroll - scrollAmount); | |||
} else { | |||
nextScroll = Math.min(maxScroll, currentScroll + scrollAmount); | |||
} | |||
carousel.scrollTo({ left: nextScroll, behavior: 'smooth' }); | |||
setTimeout(() => { | |||
isScrolling = false; | |||
updateArrows(); | |||
}, 300); | |||
} | |||
leftBtn.addEventListener('click', () => scrollCarousel('left')); | |||
rightBtn.addEventListener('click', () => scrollCarousel('right')); | |||
carousel.addEventListener('scroll', updateArrows, { passive: true }); | |||
// Observer para cambios de tamaño | |||
new ResizeObserver(updateArrows).observe(carousel); | |||
// Inicializar | |||
updateArrows(); | |||
} | |||
// Limpiar data element | |||
dataEl.remove(); | |||
})(); | |||
]]) | |||
return tostring(root) .. tostring(dataScript) .. tostring(style) .. tostring(script) | |||
end | |||
return p | |||
Edição das 15h24min de 20 de julho de 2025
A documentação para este módulo pode ser criada em Módulo:Droflax/doc
local p = {}
local args = frame:getParent().args
function p._main(args)
-- Validaciones básicas
if not args.nome or args.nome == '' then
return '<div style="color:red;">Error: Nome es requerido</div>'
end
-- Procesamiento de habilidades
local habilidades = {}
for i = 1, 21 do
local nomeKey = 'hab' .. i .. '-nome'
if args[nomeKey] and args[nomeKey] ~= '' then
table.insert(habilidades, {
nome = args[nomeKey] or '',
icon = args['hab' .. i .. '-icon'] or '',
level = tonumber(args['hab' .. i .. '-level']) or 1,
desc = args['hab' .. i .. '-desc'] or '',
atr = args['hab' .. i .. '-atr'] or '',
video = args['hab' .. i .. '-video'] or ''
})
end
end
-- Procesamiento de skins
local skins = {}
for i = 1, 11 do
local imageKey = 'skin' .. i .. '-image'
if args[imageKey] and args[imageKey] ~= '' then
table.insert(skins, {
image = args[imageKey] or '',
banner = args['skin' .. i .. '-banner'] or '',
tooltip = args['skin' .. i .. '-tooltip'] or ''
})
end
end
-- Construcción del HTML usando mw.html (más eficiente)
local root = mw.html.create('div')
:addClass('personaje-box')
:attr('data-tier', args.tier or '')
-- Header
local header = root:tag('div'):addClass('personaje-header')
local topbar = header:tag('div'):addClass('personaje-topbar')
local nomeBox = topbar:tag('div'):addClass('personaje-nome-box')
nomeBox:tag('img')
:attr('src', '/images/6/63/Franky_ts_medal.png')
:addClass('topbar-icon')
local nomeCategory = nomeBox:tag('div'):addClass('personaje-nome-category')
nomeCategory:tag('div'):addClass('nome'):wikitext(args.nome)
nomeCategory:tag('div'):addClass('classes'):wikitext(args.classe or '')
-- Description
header:tag('div')
:addClass('topbar-description')
:wikitext((args.nome or '') .. ' (' .. (args.tier or '') .. ') é um personagem do tier ' .. string.lower(args.tier or '') .. '.')
-- Tabs
local tabs = header:tag('div'):addClass('personaje-tabs')
tabs:tag('button'):addClass('tab-btn'):attr('data-tab', 'arma'):wikitext('Arma')
tabs:tag('button'):addClass('tab-btn active'):attr('data-tab', 'habilidades'):wikitext('Habilidades')
tabs:tag('button'):addClass('tab-btn'):attr('data-tab', 'skins'):wikitext('Skins')
-- Art image
root:tag('img')
:addClass('art-personaje')
:attr('src', args.image or '')
:attr('alt', 'Arte del personaje')
-- Habilidades tab content
local habTab = root:tag('div')
:addClass('tab-content active')
:attr('id', 'habilidades')
-- Cuadros container (iconos de habilidades)
local cuadrosContainer = habTab:tag('div'):addClass('cuadros-container')
for i, hab in ipairs(habilidades) do
local cuadro = cuadrosContainer:tag('div')
:addClass('cuadro')
:attr('data-hab-index', i)
:attr('title', hab.nome)
cuadro:tag('img')
:attr('src', hab.icon)
:attr('alt', hab.nome)
:css('width', '100%')
:css('height', '100%')
:css('object-fit', 'cover')
end
-- Habilidades details container
local habContainer = habTab:tag('div'):addClass('habilidades-container')
local habDetails = habContainer:tag('div'):addClass('habilidades-details')
local descripcionContainer = habDetails:tag('div')
:addClass('descripcion-container')
:attr('id', 'descripcion-container')
local videoContainer = habContainer:tag('div')
:addClass('video-container')
:attr('id', 'video-container')
-- Skins tab content
local skinsTab = root:tag('div')
:addClass('tab-content')
:attr('id', 'skins')
local cardSkins = skinsTab:tag('div'):addClass('card-skins')
cardSkins:tag('span')
:addClass('card-skins-title')
:wikitext('SKINS & SPOTLIGHTS')
local carouselWrapper = cardSkins:tag('div'):addClass('skins-carousel-wrapper')
carouselWrapper:tag('button')
:addClass('skins-arrow left')
:wikitext('«')
local carousel = carouselWrapper:tag('div'):addClass('skins-carousel')
for _, skin in ipairs(skins) do
local skinCard = carousel:tag('div'):addClass('skin-card')
skinCard:tag('img')
:addClass('skins--imageBanner')
:attr('src', skin.banner)
:attr('alt', 'banner')
skinCard:tag('img')
:addClass('skins--imageSkin')
:attr('src', skin.image)
:attr('alt', 'skin')
end
carouselWrapper:tag('button')
:addClass('skins-arrow right')
:wikitext('»')
-- Datos JSON para JavaScript (más eficiente que data-attributes múltiples)
local dataScript = mw.html.create('script')
:attr('type', 'application/json')
:attr('id', 'persona-data-json')
:wikitext(mw.text.jsonEncode({
nome = args.nome or '',
tier = args.tier or '',
classe = args.classe or '',
image = args.image or '',
habilidades = habilidades,
skins = skins
}))
-- CSS (solo lo esencial, el resto en CSS común)
local style = mw.html.create('style'):wikitext([[
.personaje-box { padding: 16px; color: #000; font-family: 'Segoe UI', sans-serif; width: 90%; margin: auto; position: relative; user-select: none; }
.personaje-topbar { display: flex; flex-direction: column; align-items: flex-start; padding: 8px 16px; }
.personaje-nome-box { display: flex; align-items: center; gap: 8px; }
.topbar-icon { width: 90px; height: 90px; object-fit: none; background: #60dae2; }
.nome { font-size: 60px; font-family: 'Orbitron', sans-serif; font-weight: 900; }
.topbar-description { font-size: 16px; margin-top: 6px; background: #6AF3FB; width: fit-content; padding-inline: 16px; border-radius: 0 10px 10px 0; }
.personaje-header { display: flex; gap: 10px; flex-direction: column; }
.art-personaje { width: 665px; position: absolute; right: 1rem; top: 0.4rem; z-index: 9; }
/* ... resto del CSS optimizado ... */
]])
-- JavaScript optimizado
local script = mw.html.create('script'):wikitext([[
(function() {
'use strict';
// Cargar datos JSON (más rápido que procesar data-attributes)
const dataEl = document.getElementById('persona-data-json');
if (!dataEl) return console.error('Persona: no se encontró JSON data');
let data;
try {
data = JSON.parse(dataEl.textContent);
} catch (e) {
return console.error('Persona: error parsing JSON', e);
}
// Cache de elementos DOM
const elements = {
tabBtns: document.querySelectorAll('.tab-btn'),
tabContents: document.querySelectorAll('.tab-content'),
cuadros: document.querySelectorAll('.cuadro'),
descripcionContainer: document.getElementById('descripcion-container'),
videoContainer: document.getElementById('video-container'),
personajeBox: document.querySelector('.personaje-box')
};
// Aplicar tier class
const tierClass = {
'bronze': 'tier-bronze', 'bronce': 'tier-bronze',
'silver': 'tier-silver', 'prata': 'tier-silver',
'gold': 'tier-gold', 'ouro': 'tier-gold',
'diamond': 'tier-diamond', 'diamante': 'tier-diamond'
}[data.tier.toLowerCase()];
if (tierClass) elements.personajeBox.classList.add(tierClass);
// Event listeners optimizados
elements.tabBtns.forEach(btn => {
btn.addEventListener('click', handleTabClick, false);
});
elements.cuadros.forEach((cuadro, index) => {
cuadro.addEventListener('click', () => handleHabilidadClick(index), false);
});
// Funciones optimizadas
function handleTabClick(e) {
const target = e.target.dataset.tab;
elements.tabBtns.forEach(b => b.classList.remove('active'));
elements.tabContents.forEach(c => c.classList.remove('active'));
e.target.classList.add('active');
document.getElementById(target).classList.add('active');
}
function handleHabilidadClick(index) {
const hab = data.habilidades[index];
if (!hab) return;
// Generar HTML de atributos
const atributos = hab.atr.split(',').map(v => v.trim());
const labels = ['Poder PVE', 'Poder PVP', 'Energía', 'Recarga'];
const icons = [
'/images/7/7a/Icon-pve.png',
'/images/5/5f/Icon-pvp.png',
'/images/3/38/Icon-energy.png',
'/images/b/b1/Icon-cooldown.png'
];
const atributosHTML = atributos.map((v, i) => {
let f = v === '-' ? '-' : parseInt(v);
if (i === 1 && !isNaN(f)) f = (f > 0 ? '+' : '') + f;
return `
<span class="simple-tooltip">
<div class="cardAttribute">
<img src="${icons[i]}" class="cardAttribute--icon">
<h2 class="cardAttribute--value">${f}${i === 0 && f !== '-' ? ' seg' : ''}</h2>
</div>
</span>`;
}).join('');
// Actualizar descripción
elements.descripcionContainer.innerHTML = `
<div class="titulo-habilidad">
<h3>${hab.nome}</h3>
<div class="tooltip-container">
<button class="info-btn">i</button>
<span class="tooltip-text">Información adicional sobre la habilidad.</span>
</div>
</div>
<div class="attribute--cardsContainer">${atributosHTML}</div>
<div class="desc">${hab.desc.replace(/'''(.*?)'''/g, '<b>$1</b>')}</div>
`;
// Actualizar video
elements.videoContainer.innerHTML = hab.video ?
`<video width="100%" controls playsinline><source src="${hab.video}" type="video/webm"></video>` : '';
// Actualizar clases activas
elements.cuadros.forEach(c => c.classList.remove('activo'));
elements.cuadros[index].classList.add('activo');
}
// Inicializar primera habilidad
if (data.habilidades.length > 0) {
handleHabilidadClick(0);
}
// Inicializar carrusel de skins
initSkinsCarousel();
function initSkinsCarousel() {
const carousel = document.querySelector('.skins-carousel');
if (!carousel) return;
const wrapper = document.querySelector('.skins-carousel-wrapper');
const leftBtn = document.querySelector('.skins-arrow.left');
const rightBtn = document.querySelector('.skins-arrow.right');
let isScrolling = false;
function updateArrows() {
const maxScroll = carousel.scrollWidth - carousel.clientWidth;
const hasLeft = carousel.scrollLeft > 5;
const hasRight = carousel.scrollLeft < maxScroll - 5;
leftBtn.style.display = hasLeft ? 'inline-block' : 'none';
rightBtn.style.display = hasRight ? 'inline-block' : 'none';
wrapper.classList.toggle('has-left', hasLeft);
wrapper.classList.toggle('has-right', hasRight);
}
function scrollCarousel(direction) {
if (isScrolling) return;
isScrolling = true;
const scrollAmount = carousel.clientWidth * 0.6;
const currentScroll = carousel.scrollLeft;
const maxScroll = carousel.scrollWidth - carousel.clientWidth;
let nextScroll;
if (direction === 'left') {
nextScroll = Math.max(0, currentScroll - scrollAmount);
} else {
nextScroll = Math.min(maxScroll, currentScroll + scrollAmount);
}
carousel.scrollTo({ left: nextScroll, behavior: 'smooth' });
setTimeout(() => {
isScrolling = false;
updateArrows();
}, 300);
}
leftBtn.addEventListener('click', () => scrollCarousel('left'));
rightBtn.addEventListener('click', () => scrollCarousel('right'));
carousel.addEventListener('scroll', updateArrows, { passive: true });
// Observer para cambios de tamaño
new ResizeObserver(updateArrows).observe(carousel);
// Inicializar
updateArrows();
}
// Limpiar data element
dataEl.remove();
})();
]])
return tostring(root) .. tostring(dataScript) .. tostring(style) .. tostring(script)
end
return p