|
|
|
|
@@ -726,51 +726,73 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
});
|
|
|
|
|
document.addEventListener('pkt:open-consent', openConsentModal);
|
|
|
|
|
|
|
|
|
|
const readAvatarValue = (form, field) => {
|
|
|
|
|
const checked = form.querySelector(`[data-avatar-field="${field}"]:checked`);
|
|
|
|
|
const avatarStyleConfigs = {
|
|
|
|
|
lorelei: {
|
|
|
|
|
fields: ['eyesVariant', 'eyebrowsVariant', 'mouthVariant', 'glassesVariant', 'hairVariant', 'beardVariant', 'earringsVariant'],
|
|
|
|
|
paramMap: {
|
|
|
|
|
eyesVariant: 'eyesVariant',
|
|
|
|
|
eyebrowsVariant: 'eyebrowsVariant',
|
|
|
|
|
mouthVariant: 'mouthVariant',
|
|
|
|
|
glassesVariant: 'glassesVariant',
|
|
|
|
|
hairVariant: 'hairVariant',
|
|
|
|
|
beardVariant: 'beardVariant',
|
|
|
|
|
earringsVariant: 'earringsVariant',
|
|
|
|
|
},
|
|
|
|
|
optionalProbabilityMap: {
|
|
|
|
|
glassesVariant: 'glassesProbability',
|
|
|
|
|
beardVariant: 'beardProbability',
|
|
|
|
|
earringsVariant: 'earringsProbability',
|
|
|
|
|
},
|
|
|
|
|
alwaysProbabilityMap: {
|
|
|
|
|
hairVariant: 'hairProbability',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getCurrentAvatarStyle = (form) => {
|
|
|
|
|
const input = form.querySelector('[data-avatar-style-select]');
|
|
|
|
|
const value = input?.value || '';
|
|
|
|
|
if (value) return value;
|
|
|
|
|
return form.querySelector('[data-avatar-style-panel]')?.getAttribute('data-avatar-style-panel') || 'lorelei';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getAvatarSeedInput = (form) => form.querySelector('[data-avatar-seed]');
|
|
|
|
|
|
|
|
|
|
const getAvatarStylePanel = (form, style) => form.querySelector(`[data-avatar-style-panel="${style}"]`);
|
|
|
|
|
|
|
|
|
|
const readAvatarValue = (form, style, field) => {
|
|
|
|
|
const panel = getAvatarStylePanel(form, style);
|
|
|
|
|
if (!panel) return '';
|
|
|
|
|
const checked = panel.querySelector(`[data-avatar-field="${field}"]:checked`);
|
|
|
|
|
if (checked) return checked.value;
|
|
|
|
|
const input = form.querySelector(`[data-avatar-field="${field}"]`);
|
|
|
|
|
const input = panel.querySelector(`[data-avatar-field="${field}"]`);
|
|
|
|
|
return input ? input.value : '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const avatarComponentFieldMap = {
|
|
|
|
|
avatar_lorelei_eyes_variant: 'eyesVariant',
|
|
|
|
|
avatar_lorelei_eyebrows_variant: 'eyebrowsVariant',
|
|
|
|
|
avatar_lorelei_mouth_variant: 'mouthVariant',
|
|
|
|
|
avatar_lorelei_glasses_variant: 'glassesVariant',
|
|
|
|
|
avatar_lorelei_hair_variant: 'hairVariant',
|
|
|
|
|
avatar_lorelei_beard_variant: 'beardVariant',
|
|
|
|
|
avatar_lorelei_earrings_variant: 'earringsVariant',
|
|
|
|
|
const getAvatarStyleConfig = (style) => {
|
|
|
|
|
if (avatarStyleConfigs[style]) return avatarStyleConfigs[style];
|
|
|
|
|
return avatarStyleConfigs.lorelei;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const avatarComponentFields = Object.keys(avatarComponentFieldMap);
|
|
|
|
|
const avatarEndpoint = '/api/avatar.php';
|
|
|
|
|
const avatarOptionalProbabilityMap = {
|
|
|
|
|
avatar_lorelei_glasses_variant: 'glassesProbability',
|
|
|
|
|
avatar_lorelei_beard_variant: 'beardProbability',
|
|
|
|
|
avatar_lorelei_earrings_variant: 'earringsProbability',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const buildAvatarParams = (form, overrides = {}) => {
|
|
|
|
|
const seed = overrides.avatar_preset || readAvatarValue(form, 'avatar_preset');
|
|
|
|
|
const buildAvatarParams = (form, style = getCurrentAvatarStyle(form), overrides = {}) => {
|
|
|
|
|
const styleConfig = getAvatarStyleConfig(style);
|
|
|
|
|
const seed = overrides.avatar_seed || getAvatarSeedInput(form)?.value || '';
|
|
|
|
|
if (!seed) return null;
|
|
|
|
|
|
|
|
|
|
const params = new URLSearchParams({ seed });
|
|
|
|
|
const hairValue = Object.prototype.hasOwnProperty.call(overrides, 'avatar_lorelei_hair_variant')
|
|
|
|
|
? overrides.avatar_lorelei_hair_variant
|
|
|
|
|
: readAvatarValue(form, 'avatar_lorelei_hair_variant');
|
|
|
|
|
params.set('hairProbability', '100');
|
|
|
|
|
if (hairValue) {
|
|
|
|
|
params.set('hairVariant', hairValue);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
avatarComponentFields.forEach((field) => {
|
|
|
|
|
if (field === 'avatar_lorelei_hair_variant') return;
|
|
|
|
|
const params = new URLSearchParams({ style, seed });
|
|
|
|
|
styleConfig.fields.forEach((field) => {
|
|
|
|
|
const value = Object.prototype.hasOwnProperty.call(overrides, field)
|
|
|
|
|
? overrides[field]
|
|
|
|
|
: readAvatarValue(form, field);
|
|
|
|
|
const param = avatarComponentFieldMap[field];
|
|
|
|
|
const probabilityParam = avatarOptionalProbabilityMap[field];
|
|
|
|
|
: readAvatarValue(form, style, field);
|
|
|
|
|
const param = styleConfig.paramMap[field];
|
|
|
|
|
const probabilityParam = styleConfig.optionalProbabilityMap[field];
|
|
|
|
|
const alwaysProbabilityParam = styleConfig.alwaysProbabilityMap[field];
|
|
|
|
|
|
|
|
|
|
if (alwaysProbabilityParam) {
|
|
|
|
|
params.set(alwaysProbabilityParam, value ? '100' : '0');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (probabilityParam) {
|
|
|
|
|
params.set(probabilityParam, value ? '100' : '0');
|
|
|
|
|
@@ -788,7 +810,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
const image = preview.querySelector('.pkt-avatar__img');
|
|
|
|
|
if (!image) return;
|
|
|
|
|
|
|
|
|
|
const params = buildAvatarParams(form);
|
|
|
|
|
const style = getCurrentAvatarStyle(form);
|
|
|
|
|
const params = buildAvatarParams(form, style);
|
|
|
|
|
if (!params) return;
|
|
|
|
|
|
|
|
|
|
const url = `${avatarEndpoint}?${params.toString()}`;
|
|
|
|
|
@@ -796,20 +819,24 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
image.src = url;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getActiveAvatarField = (form) => {
|
|
|
|
|
const activeTab = form.querySelector('[data-avatar-component-tab].is-active');
|
|
|
|
|
return activeTab?.getAttribute('data-avatar-component-tab') || 'avatar_lorelei_eyes_variant';
|
|
|
|
|
const getActiveAvatarField = (form, style) => {
|
|
|
|
|
const panel = getAvatarStylePanel(form, style);
|
|
|
|
|
if (!panel) return '';
|
|
|
|
|
const activeTab = panel.querySelector('[data-avatar-component-tab].is-active');
|
|
|
|
|
return activeTab?.getAttribute('data-avatar-component-tab') || panel.querySelector('[data-avatar-component-tab]')?.getAttribute('data-avatar-component-tab') || '';
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const updateAvatarOptionPreviews = (form, activeField = null) => {
|
|
|
|
|
const updateAvatarOptionPreviews = (form, style, activeField = null) => {
|
|
|
|
|
const panel = getAvatarStylePanel(form, style);
|
|
|
|
|
if (!panel) return;
|
|
|
|
|
const selector = activeField
|
|
|
|
|
? `[data-avatar-component-panel="${activeField}"] [data-avatar-option-thumb]`
|
|
|
|
|
: '[data-avatar-option-thumb]';
|
|
|
|
|
|
|
|
|
|
form.querySelectorAll(selector).forEach((image) => {
|
|
|
|
|
panel.querySelectorAll(selector).forEach((image) => {
|
|
|
|
|
const field = image.getAttribute('data-avatar-option-field') || '';
|
|
|
|
|
const value = image.getAttribute('data-avatar-option-value') || '';
|
|
|
|
|
const params = buildAvatarParams(form, { [field]: value });
|
|
|
|
|
const params = buildAvatarParams(form, style, { [field]: value });
|
|
|
|
|
if (!params) return;
|
|
|
|
|
|
|
|
|
|
const src = `${avatarEndpoint}?${params.toString()}`;
|
|
|
|
|
@@ -819,52 +846,83 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const ensureAvatarBuilderLoaded = (form) => {
|
|
|
|
|
if (form.dataset.avatarInitialized === 'true') return;
|
|
|
|
|
form.dataset.avatarInitialized = 'true';
|
|
|
|
|
const ensureAvatarBuilderLoaded = (form, style = getCurrentAvatarStyle(form)) => {
|
|
|
|
|
const loadedStyles = new Set((form.dataset.avatarInitializedStyles || '').split(',').filter(Boolean));
|
|
|
|
|
if (loadedStyles.has(style)) return;
|
|
|
|
|
loadedStyles.add(style);
|
|
|
|
|
form.dataset.avatarInitializedStyles = Array.from(loadedStyles).join(',');
|
|
|
|
|
updateAvatarPreview(form);
|
|
|
|
|
updateAvatarOptionPreviews(form, getActiveAvatarField(form));
|
|
|
|
|
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll('[data-avatar-builder]').forEach((form) => {
|
|
|
|
|
form.querySelectorAll('[data-avatar-field]').forEach((field) => {
|
|
|
|
|
field.addEventListener('change', () => {
|
|
|
|
|
ensureAvatarBuilderLoaded(form);
|
|
|
|
|
const style = field.getAttribute('data-avatar-style') || getCurrentAvatarStyle(form);
|
|
|
|
|
ensureAvatarBuilderLoaded(form, style);
|
|
|
|
|
updateAvatarPreview(form);
|
|
|
|
|
updateAvatarOptionPreviews(form, getActiveAvatarField(form));
|
|
|
|
|
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
|
|
|
|
});
|
|
|
|
|
field.addEventListener('input', () => {
|
|
|
|
|
ensureAvatarBuilderLoaded(form);
|
|
|
|
|
const style = field.getAttribute('data-avatar-style') || getCurrentAvatarStyle(form);
|
|
|
|
|
ensureAvatarBuilderLoaded(form, style);
|
|
|
|
|
updateAvatarPreview(form);
|
|
|
|
|
updateAvatarOptionPreviews(form, getActiveAvatarField(form));
|
|
|
|
|
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const setActiveComponentPanel = (field) => {
|
|
|
|
|
form.querySelectorAll('[data-avatar-component-tab]').forEach((button) => {
|
|
|
|
|
const setActiveComponentPanel = (style, field) => {
|
|
|
|
|
const panel = getAvatarStylePanel(form, style);
|
|
|
|
|
if (!panel) return;
|
|
|
|
|
panel.querySelectorAll('[data-avatar-component-tab]').forEach((button) => {
|
|
|
|
|
const active = button.getAttribute('data-avatar-component-tab') === field;
|
|
|
|
|
button.classList.toggle('is-active', active);
|
|
|
|
|
button.setAttribute('aria-selected', active ? 'true' : 'false');
|
|
|
|
|
});
|
|
|
|
|
form.querySelectorAll('[data-avatar-component-panel]').forEach((panel) => {
|
|
|
|
|
panel.classList.toggle('is-active', panel.getAttribute('data-avatar-component-panel') === field);
|
|
|
|
|
panel.querySelectorAll('[data-avatar-component-panel]').forEach((componentPanel) => {
|
|
|
|
|
componentPanel.classList.toggle('is-active', componentPanel.getAttribute('data-avatar-component-panel') === field);
|
|
|
|
|
});
|
|
|
|
|
updateAvatarOptionPreviews(form, field);
|
|
|
|
|
updateAvatarOptionPreviews(form, style, field);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const setActiveStyle = (style) => {
|
|
|
|
|
form.querySelectorAll('[data-avatar-style-panel]').forEach((panel) => {
|
|
|
|
|
const active = panel.getAttribute('data-avatar-style-panel') === style;
|
|
|
|
|
panel.hidden = !active;
|
|
|
|
|
panel.classList.toggle('is-active', active);
|
|
|
|
|
panel.querySelectorAll('[data-avatar-field]').forEach((input) => {
|
|
|
|
|
input.disabled = !active;
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
ensureAvatarBuilderLoaded(form, style);
|
|
|
|
|
setActiveComponentPanel(style, getActiveAvatarField(form, style));
|
|
|
|
|
updateAvatarPreview(form);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
form.querySelector('[data-avatar-style-select]')?.addEventListener('change', (event) => {
|
|
|
|
|
const nextStyle = event.currentTarget?.value || getCurrentAvatarStyle(form);
|
|
|
|
|
setActiveStyle(nextStyle);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
form.querySelectorAll('[data-avatar-component-tab]').forEach((button) => {
|
|
|
|
|
button.addEventListener('click', () => {
|
|
|
|
|
const style = button.getAttribute('data-avatar-style') || getCurrentAvatarStyle(form);
|
|
|
|
|
const field = button.getAttribute('data-avatar-component-tab');
|
|
|
|
|
if (field) {
|
|
|
|
|
ensureAvatarBuilderLoaded(form);
|
|
|
|
|
setActiveComponentPanel(field);
|
|
|
|
|
ensureAvatarBuilderLoaded(form, style);
|
|
|
|
|
setActiveComponentPanel(style, field);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
avatarComponentFields.forEach((field) => {
|
|
|
|
|
form.querySelectorAll(`[data-avatar-field="${field}"]`).forEach((input) => {
|
|
|
|
|
input.addEventListener('change', () => setActiveComponentPanel(field));
|
|
|
|
|
Object.values(avatarStyleConfigs).forEach((styleConfig) => {
|
|
|
|
|
styleConfig.fields.forEach((fieldName) => {
|
|
|
|
|
form.querySelectorAll(`[data-avatar-field="${fieldName}"]`).forEach((input) => {
|
|
|
|
|
input.addEventListener('change', () => {
|
|
|
|
|
const style = input.getAttribute('data-avatar-style') || getCurrentAvatarStyle(form);
|
|
|
|
|
setActiveComponentPanel(style, fieldName);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
@@ -891,29 +949,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
form.querySelector('[data-avatar-random]')?.addEventListener('click', () => {
|
|
|
|
|
const seedField = form.querySelector('[data-avatar-field="avatar_preset"]');
|
|
|
|
|
const style = getCurrentAvatarStyle(form);
|
|
|
|
|
const styleConfig = getAvatarStyleConfig(style);
|
|
|
|
|
const seedField = getAvatarSeedInput(form);
|
|
|
|
|
if (!(seedField instanceof HTMLInputElement)) return;
|
|
|
|
|
seedField.value = buildSeed();
|
|
|
|
|
|
|
|
|
|
avatarComponentFields.forEach((field) => {
|
|
|
|
|
const options = Array.from(form.querySelectorAll(`[data-avatar-field="${field}"]`));
|
|
|
|
|
styleConfig.fields.forEach((field) => {
|
|
|
|
|
const panel = getAvatarStylePanel(form, style);
|
|
|
|
|
if (!panel) return;
|
|
|
|
|
const options = Array.from(panel.querySelectorAll(`[data-avatar-field="${field}"]`));
|
|
|
|
|
if (!options.length) return;
|
|
|
|
|
const randomOption = options[Math.floor(Math.random() * options.length)];
|
|
|
|
|
if (!(randomOption instanceof HTMLInputElement)) return;
|
|
|
|
|
randomOption.checked = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
ensureAvatarBuilderLoaded(form);
|
|
|
|
|
ensureAvatarBuilderLoaded(form, style);
|
|
|
|
|
updateAvatarPreview(form);
|
|
|
|
|
updateAvatarOptionPreviews(form, getActiveAvatarField(form));
|
|
|
|
|
updateAvatarOptionPreviews(form, style, getActiveAvatarField(form, style));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
setActiveStyle(getCurrentAvatarStyle(form));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.querySelectorAll('[data-modal-open="modalAvatar"]').forEach((button) => {
|
|
|
|
|
button.addEventListener('click', () => {
|
|
|
|
|
const form = document.querySelector('#modalAvatar [data-avatar-builder]');
|
|
|
|
|
if (!form) return;
|
|
|
|
|
window.setTimeout(() => ensureAvatarBuilderLoaded(form), 20);
|
|
|
|
|
const style = getCurrentAvatarStyle(form);
|
|
|
|
|
window.setTimeout(() => ensureAvatarBuilderLoaded(form, style), 20);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|