import grapesjs from 'grapesjs';
import 'grapesjs/dist/css/grapes.min.css';
import Pickr from '@simonwep/pickr';
import '@simonwep/pickr/dist/themes/nano.min.css';
import blocksIcon from 'lucide-static/icons/blocks.svg';
import fileStackIcon from 'lucide-static/icons/file-stack.svg';
import globeIcon from 'lucide-static/icons/globe.svg';
import imagesIcon from 'lucide-static/icons/images.svg';
import alignCenterHorizontalIcon from 'lucide-static/icons/align-center-horizontal.svg';
import alignCenterVerticalIcon from 'lucide-static/icons/align-center-vertical.svg';
import alignEndHorizontalIcon from 'lucide-static/icons/align-end-horizontal.svg';
import alignEndVerticalIcon from 'lucide-static/icons/align-end-vertical.svg';
import alignHorizontalDistributeCenterIcon from 'lucide-static/icons/align-horizontal-distribute-center.svg';
import alignHorizontalSpaceAroundIcon from 'lucide-static/icons/align-horizontal-space-around.svg';
import alignHorizontalSpaceBetweenIcon from 'lucide-static/icons/align-horizontal-space-between.svg';
import alignStartHorizontalIcon from 'lucide-static/icons/align-start-horizontal.svg';
import alignStartVerticalIcon from 'lucide-static/icons/align-start-vertical.svg';
import arrowDownIcon from 'lucide-static/icons/arrow-down.svg';
import arrowLeftIcon from 'lucide-static/icons/arrow-left.svg';
import arrowRightIcon from 'lucide-static/icons/arrow-right.svg';
import arrowUpIcon from 'lucide-static/icons/arrow-up.svg';
import baselineIcon from 'lucide-static/icons/baseline.svg';
import ellipsisVerticalIcon from 'lucide-static/icons/ellipsis-vertical.svg';
import expandIcon from 'lucide-static/icons/expand.svg';
import moveHorizontalIcon from 'lucide-static/icons/move-horizontal.svg';
import plusIcon from 'lucide-static/icons/plus.svg';
import shrinkIcon from 'lucide-static/icons/shrink.svg';
import squareIcon from 'lucide-static/icons/square.svg';
import squareDashedIcon from 'lucide-static/icons/square-dashed.svg';
import stretchHorizontalIcon from 'lucide-static/icons/stretch-horizontal.svg';
import tagIcon from 'lucide-static/icons/tag.svg';
import {
     builderBaseClasses,
    classNames,
    classOption,
     builderContainerMinHeights,
     builderContainerPaddings,
     builderHorizontalAlignments,
     builderSectionHorizontalPaddings,
     builderSectionMarginBottoms,
     builderSectionMaxWidths,
     builderVerticalAlignments,
} from './page-builder-tailwind-options.mjs';
import './page-builder-tailwind-classes';
import './css/page-builder.css';

const html = document.documentElement;

function escapeHtml(value) {
    return String(value ?? '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#039;');
}

function resolveThemeAssetUrl(value) {
    const url = String(value ?? '');
    const resolvedUrl = url.startsWith('/themes/') ? `/bundles/cpcreationvititheme${url}` : url;

    return resolvedUrl.startsWith('/') ? `${window.location.origin}${resolvedUrl}` : resolvedUrl;
}

function normalizeChoice(value, options, fallback) {
    const key = String(value ?? fallback);

    return Object.hasOwn(options, key) ? key : String(fallback);
}

function normalizeHorizontalAlignment(value) {
    if (value === 'start') {
        return 'left';
    }

    return normalizeChoice(value,  builderHorizontalAlignments, 'left');
}

function normalizeVerticalAlignment(value) {
    return normalizeChoice(value,  builderVerticalAlignments, 'bottom');
}

const  builderInspectableStyleDefaults = {
    display: '',
    'flex-direction': '',
    'justify-content': '',
    'align-items': '',
    gap: '',
    'flex-wrap': '',
    width: '',
    height: '',
    'min-width': '',
    'min-height': '',
    'max-width': '',
    'max-height': '',
    'inline-size': '',
    'min-inline-size': '',
    'max-inline-size': '',
    'block-size': '',
    'min-block-size': '',
    'max-block-size': '',
    padding: '',
    'padding-top': '',
    'padding-right': '',
    'padding-bottom': '',
    'padding-left': '',
    margin: '',
    'margin-top': '',
    'margin-right': '',
    'margin-left': '',
    position: '',
    top: '',
    right: '',
    bottom: '',
    left: '',
    'z-index': '',
    'background-color': '',
    'background-clip': '',
    border: '',
    'border-width': '',
    'border-top-width': '',
    'border-right-width': '',
    'border-bottom-width': '',
    'border-left-width': '',
    'border-style': '',
    'border-top-style': '',
    'border-right-style': '',
    'border-bottom-style': '',
    'border-left-style': '',
    'border-color': '',
    'border-top-color': '',
    'border-right-color': '',
    'border-bottom-color': '',
    'border-left-color': '',
    'border-radius': '',
    'border-top-left-radius': '',
    'border-top-right-radius': '',
    'border-bottom-right-radius': '',
    'border-bottom-left-radius': '',
    opacity: '',
    'mix-blend-mode': '',
    cursor: '',
    'box-shadow': '',
    'text-shadow': '',
    filter: '',
    'backdrop-filter': '',
    transition: '',
    transform: '',
    'transform-origin': '',
    overflow: '',
    'overflow-x': '',
    'overflow-y': '',
    'backface-visibility': '',
    perspective: '',
    'perspective-origin': '',
    'transform-style': '',
    'vertical-align': '',
    'font-smoothing': '',
    'font-stretch': '',
    'font-variant-numeric': '',
    'font-feature-settings': '',
    'line-clamp': '',
    'list-style-image': '',
    'list-style-position': '',
    'list-style-type': '',
    direction: '',
    'white-space': '',
    'text-decoration': '',
    'text-decoration-line': '',
    'text-decoration-color': '',
    'text-decoration-style': '',
    'text-decoration-thickness': '',
    'text-underline-offset': '',
    'text-overflow': '',
    'text-wrap': '',
    'text-indent': '',
    'tab-size': '',
    'word-break': '',
    'overflow-wrap': '',
    hyphens: '',
    content: '',
    'grid-template-columns': '',
    'grid-template-rows': '',
    'grid-auto-flow': '',
    'justify-items': '',
    'align-content': '',
};

const  builderTextStyleDefaults = {
    section: {
        ... builderInspectableStyleDefaults,
        'max-width': '',
        'padding-inline': '',
        'margin-bottom': '',
    },
    container: {
        ... builderInspectableStyleDefaults,
    },
    buttons: {
        ... builderInspectableStyleDefaults,
    },
    eyebrow: {
        ... builderInspectableStyleDefaults,
        'font-family': '',
        'font-size': '12px',
        'font-weight': '700',
        'font-style': 'normal',
        'text-align': '',
        'text-transform': 'uppercase',
        'letter-spacing': '.22em',
        'line-height': '1.2',
        color: 'rgba(255,255,255,.8)',
        'max-width': '900px',
        'margin-bottom': '16px',
    },
    heading: {
        ... builderInspectableStyleDefaults,
        'font-family': '',
        'font-size': '64px',
        'font-weight': '500',
        'font-style': 'italic',
        'text-align': '',
        'text-transform': 'none',
        'letter-spacing': '0',
        'line-height': '1.08',
        color: '#ffffff',
        'max-width': '900px',
        'margin-bottom': '24px',
    },
    text: {
        ... builderInspectableStyleDefaults,
        'font-family': '',
        'font-size': '18px',
        'font-weight': '400',
        'font-style': 'normal',
        'text-align': '',
        'text-transform': 'none',
        'letter-spacing': '0',
        'line-height': '1.6',
        color: 'rgba(255,255,255,.9)',
        'max-width': '680px',
        'margin-bottom': '40px',
    },
    primaryButton: {
        ... builderInspectableStyleDefaults,
        'font-family': '',
        'font-size': '12px',
        'font-weight': '700',
        'font-style': 'normal',
        'text-align': '',
        'text-transform': 'uppercase',
        'letter-spacing': '.1em',
        'line-height': '1',
        color: '#ffffff',
        'background-color': '#5b1f2e',
        border: '0',
        padding: '16px 28px',
        'border-radius': '0',
    },
    secondaryButton: {
        ... builderInspectableStyleDefaults,
        'font-family': '',
        'font-size': '12px',
        'font-weight': '700',
        'font-style': 'normal',
        'text-align': '',
        'text-transform': 'uppercase',
        'letter-spacing': '.1em',
        'line-height': '1',
        color: '#ffffff',
        'background-color': 'transparent',
        border: '1px solid #c8a96a',
        padding: '16px 28px',
        'border-radius': '0',
    },
};

const  builderTextLayerConfigs = {
    eyebrow: {
        title: 'Style du surtitre',
        contentLabel: 'Surtitre',
        contentAttribute: 'data-eyebrow',
        contentInput: 'text',
    },
    heading: {
        title: 'Style du titre',
        contentLabel: 'Titre',
        contentAttribute: 'data-title',
        contentInput: 'text',
    },
    text: {
        title: 'Style du texte',
        contentLabel: 'Texte',
        contentAttribute: 'data-body',
        contentInput: 'textarea',
    },
    primaryButton: {
        title: 'Style du bouton principal',
        contentLabel: 'Bouton principal',
        contentAttribute: 'data-primary-cta-label',
        contentInput: 'text',
    },
    secondaryButton: {
        title: 'Style du bouton secondaire',
        contentLabel: 'Bouton secondaire',
        contentAttribute: 'data-secondary-cta-label',
        contentInput: 'text',
    },
};

const  builderPresetDefaults = {
    section: 'site',
    container: 'site',
    eyebrow: 'site',
    heading: 'site',
    text: 'site',
    buttons: 'site',
    primaryButton: 'site',
    secondaryButton: 'site',
};

const  builderLayoutStyleDefaults = {
    container: {
        display: 'flex',
        'flex-direction': 'column',
        'justify-content': 'flex-end',
        'align-items': 'flex-start',
        gap: '',
        'flex-wrap': '',
        'grid-template-columns': '',
        'grid-template-rows': '',
        'grid-auto-flow': '',
        'justify-items': '',
        'align-content': '',
        'place-items': '',
        'place-content': '',
    },
    buttons: {
        display: 'flex',
        'flex-direction': 'row',
        'justify-content': '',
        'align-items': '',
        gap: '',
        'flex-wrap': '',
        'grid-template-columns': '',
        'grid-template-rows': '',
        'grid-auto-flow': '',
        'justify-items': '',
        'align-content': '',
        'place-items': '',
        'place-content': '',
    },
};

const  builderFlexChildDefaults = {
    container: { flex: '', 'align-self': '', 'justify-self': '', 'place-self': '', order: '' },
    eyebrow: { flex: '', 'align-self': '', 'justify-self': '', 'place-self': '', order: '' },
    heading: { flex: '', 'align-self': '', 'justify-self': '', 'place-self': '', order: '' },
    text: { flex: '', 'align-self': '', 'justify-self': '', 'place-self': '', order: '' },
    buttons: { flex: '', 'align-self': '', 'justify-self': '', 'place-self': '', order: '' },
    primaryButton: { flex: '', 'align-self': '', 'justify-self': '', 'place-self': '', order: '' },
    secondaryButton: { flex: '', 'align-self': '', 'justify-self': '', 'place-self': '', order: '' },
};

 builderTextStyleDefaults.dom = {
    ... builderInspectableStyleDefaults,
    'padding-inline': '',
    'margin-bottom': '',
};

 builderLayoutStyleDefaults.dom = {
    display: '',
    'flex-direction': '',
    'justify-content': '',
    'align-items': '',
    gap: '',
    'flex-wrap': '',
    'grid-template-columns': '',
    'grid-template-rows': '',
    'grid-auto-flow': '',
    'justify-items': '',
    'align-content': '',
    'place-items': '',
    'place-content': '',
};

 builderFlexChildDefaults.dom = { flex: '', 'align-self': '', 'justify-self': '', 'place-self': '', order: '' };

const  builderFontFamilyOptions = {
    '': { label: 'Theme' },
    'var(--font-body)': { label: 'Theme - Body' },
    'var(--font-heading)': { label: 'Theme - Heading' },
    'var(--font-subheading)': { label: 'Theme - Subheading' },
    'var(--font-accent)': { label: 'Theme - Accent' },
    'Montserrat, sans-serif': { label: 'Projet - Montserrat' },
    '"trajan-pro-3", serif': { label: 'Projet - Trajan' },
    inherit: { label: 'Heriter' },
    serif: { label: 'Theme serif' },
    'ui-sans-serif, system-ui, sans-serif': { label: 'System sans serif' },
    'ui-monospace, SFMono-Regular, monospace': { label: 'System monospace' },
    'Arial, sans-serif': { label: 'Google/system - Arial' },
    'Roboto, sans-serif': { label: 'Google - Roboto' },
    'Lato, sans-serif': { label: 'Google - Lato' },
    'Playfair Display, serif': { label: 'Google - Playfair Display' },
    'Cormorant Garamond, serif': { label: 'Google - Cormorant Garamond' },
};

const  builderFontSizeOptions = {
    '10px': { label: '10px' },
    '12px': { label: '12px' },
    '14px': { label: '14px' },
    '16px': { label: '16px' },
    '18px': { label: '18px' },
    '20px': { label: '20px' },
    '24px': { label: '24px' },
    '32px': { label: '32px' },
    '48px': { label: '48px' },
    '64px': { label: '64px' },
    '80px': { label: '80px' },
    '96px': { label: '96px' },
};

const  builderFontWeightOptions = {
    100: { label: 'Thin' },
    200: { label: 'Extra light' },
    300: { label: 'Light' },
    400: { label: 'Regular' },
    500: { label: 'Medium' },
    600: { label: 'Semi bold' },
    700: { label: 'Bold' },
    800: { label: 'Extra bold' },
    900: { label: 'Ultra bold' },
};

const  builderFontStyleOptions = {
    normal: { label: 'Normal' },
    italic: { label: 'Italique' },
};

const  builderFontSmoothingOptions = {
    antialiased: { label: 'Antialiased' },
    'subpixel-antialiased': { label: 'Subpixel' },
};

const  builderFontStretchOptions = createOrderedOptionMap({
    '': { label: 'Auto' },
    'ultra-condensed': { label: 'Ultra condensed' },
    'extra-condensed': { label: 'Extra condensed' },
    condensed: { label: 'Condensed' },
    'semi-condensed': { label: 'Semi condensed' },
    normal: { label: 'Normal' },
    'semi-expanded': { label: 'Semi expanded' },
    expanded: { label: 'Expanded' },
    'extra-expanded': { label: 'Extra expanded' },
    'ultra-expanded': { label: 'Ultra expanded' },
});

const  builderFontVariantNumericOptions = createOrderedOptionMap({
    '': { label: 'Auto' },
    normal: { label: 'Normal' },
    ordinal: { label: 'Ordinal' },
    'slashed-zero': { label: 'Slashed zero' },
    'lining-nums': { label: 'Lining nums' },
    'oldstyle-nums': { label: 'Oldstyle nums' },
    'proportional-nums': { label: 'Proportional nums' },
    'tabular-nums': { label: 'Tabular nums' },
    'diagonal-fractions': { label: 'Diagonal fractions' },
    'stacked-fractions': { label: 'Stacked fractions' },
});

const  builderTextTransformOptions = {
    none: { label: 'Normal' },
    uppercase: { label: 'Majuscules' },
    lowercase: { label: 'Minuscules' },
    capitalize: { label: 'Capitales' },
};

const  builderTextAlignOptions = {
    '': { label: 'Auto' },
    left: { label: 'Gauche' },
    center: { label: 'Centre' },
    right: { label: 'Droite' },
    justify: { label: 'Justifié' },
    start: { label: 'Début' },
    end: { label: 'Fin' },
};

const  builderLetterSpacingOptions = {
    0: { label: '0' },
    '.04em': { label: '0.04em' },
    '.08em': { label: '0.08em' },
    '.1em': { label: '0.10em' },
    '.16em': { label: '0.16em' },
    '.22em': { label: '0.22em' },
};

const  builderLineHeightOptions = {
    1: { label: '1' },
    1.08: { label: '1.08' },
    1.2: { label: '1.2' },
    1.4: { label: '1.4' },
    1.6: { label: '1.6' },
    1.8: { label: '1.8' },
};

const  builderMaxWidthOptions = {
    none: { label: 'Aucune' },
    '480px': { label: '480px' },
    '680px': { label: '680px' },
    '900px': { label: '900px' },
    '1120px': { label: '1120px' },
    '100%': { label: '100%' },
};

const  builderMarginBottomOptions = {
    0: { label: '0' },
    '8px': { label: '8px' },
    '16px': { label: '16px' },
    '24px': { label: '24px' },
    '32px': { label: '32px' },
    '40px': { label: '40px' },
    '56px': { label: '56px' },
};

const  builderTextStyleControls = [
    { label: 'Police', property: 'font-family', type: 'select', options:  builderFontFamilyOptions },
    { label: 'Taille', property: 'font-size', type: 'css-value', units: ['px', '%', 'em', 'rem', 'vh', 'vw'] },
    { label: 'Graisse', property: 'font-weight', type: 'select', options:  builderFontWeightOptions },
    { label: 'Style', property: 'font-style', type: 'segmented', options:  builderFontStyleOptions },
    { label: 'Casse', property: 'text-transform', type: 'select', options:  builderTextTransformOptions },
    { label: 'Alignement', property: 'text-align', type: 'segmented', options:  builderTextAlignOptions },
    { label: 'Interlettrage', property: 'letter-spacing', type: 'css-value', units: ['px', '%', 'em', 'rem', 'vh', 'vw'] },
    { label: 'Interligne', property: 'line-height', type: 'css-value', units: ['', 'px', '%', 'em', 'rem', 'vh', 'vw'] },
    { label: 'Couleur', property: 'color', type: 'text' },
    { label: 'Largeur max', property: 'max-width', type: 'css-value', units: ['px', '%', 'em', 'rem', 'vh', 'vw'] },
    { label: 'Marge basse', property: 'margin-bottom', type: 'css-value', units: ['px', '%', 'em', 'rem', 'vh', 'vw'] },
];

const  builderCssValueUnits = ['tw', 'px', '%', 'em', 'rem', 'vh', 'vw'];
const tailwindContainerWidthTokens = ['3xs', '2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl'];
const tailwindContainerWidthTokenValues = {
    '3xs': 'var(--container-3xs)',
    '2xs': 'var(--container-2xs)',
    xs: 'var(--container-xs)',
    sm: 'var(--container-sm)',
    md: 'var(--container-md)',
    lg: 'var(--container-lg)',
    xl: 'var(--container-xl)',
    '2xl': 'var(--container-2xl)',
    '3xl': 'var(--container-3xl)',
    '4xl': 'var(--container-4xl)',
    '5xl': 'var(--container-5xl)',
    '6xl': 'var(--container-6xl)',
    '7xl': 'var(--container-7xl)',
};
const tailwindWidthKeywordValues = {
    auto: 'auto',
    px: '1px',
    full: '100%',
    screen: '100vw',
    dvw: '100dvw',
    dvh: '100dvh',
    lvw: '100lvw',
    lvh: '100lvh',
    svw: '100svw',
    svh: '100svh',
    min: 'min-content',
    max: 'max-content',
    fit: 'fit-content',
};
const tailwindHeightKeywordValues = {
    auto: 'auto',
    px: '1px',
    full: '100%',
    screen: '100vh',
    dvh: '100dvh',
    dvw: '100dvw',
    lvh: '100lvh',
    lvw: '100lvw',
    svh: '100svh',
    svw: '100svw',
    min: 'min-content',
    max: 'max-content',
    fit: 'fit-content',
    lh: '1lh',
};
const tailwindMaxWidthKeywordValues = {
    ...tailwindWidthKeywordValues,
    none: 'none',
};
const tailwindMaxHeightKeywordValues = {
    ...tailwindHeightKeywordValues,
    none: 'none',
};

function cssValueDefaultUnit(attribute, units) {
    if (!units.includes('px')) {
        return '';
    }

    if (attribute.includes('opacity') || attribute.includes('z-index') || attribute.includes('order') || attribute.includes('line-height')) {
        return '';
    }

    return 'px';
}

const  builderButtonExtraStyleControls = [
    { label: 'Fond', property: 'background-color', type: 'text' },
    { label: 'Bordure', property: 'border', type: 'text' },
    { label: 'Padding', property: 'padding', type: 'text' },
    { label: 'Arrondi', property: 'border-radius', type: 'text' },
];

const  builderDisplayOptions = {
    '': { label: '-' },
    block: { label: 'Block' },
    inline: { label: 'Inline' },
    'inline-block': { label: 'Inline block' },
    flex: { label: 'Flex' },
    'inline-flex': { label: 'Inline flex' },
    grid: { label: 'Grid' },
    none: { label: 'None' },
};

const  builderFlexDirectionOptions = {
    row: { label: 'Ligne', icon: arrowRightIcon },
    'row-reverse': { label: 'Ligne inversée', icon: arrowLeftIcon },
    column: { label: 'Colonne', icon: arrowDownIcon },
    'column-reverse': { label: 'Colonne inversée', icon: arrowUpIcon },
};

const  builderJustifyContentOptions = {
    'flex-start': { label: 'Début', icon: alignStartHorizontalIcon },
    center: { label: 'Centré', icon: alignCenterHorizontalIcon },
    'flex-end': { label: 'Fin', icon: alignEndHorizontalIcon },
    'space-between': { label: 'Espace entre', icon: alignHorizontalSpaceBetweenIcon },
    'space-around': { label: 'Espace autour', icon: alignHorizontalSpaceAroundIcon },
    'space-evenly': { label: 'Répartition égale', icon: alignHorizontalDistributeCenterIcon },
};

const  builderAlignItemsOptions = {
    'flex-start': { label: 'Début', icon: alignStartVerticalIcon },
    center: { label: 'Centré', icon: alignCenterVerticalIcon },
    'flex-end': { label: 'Fin', icon: alignEndVerticalIcon },
    stretch: { label: 'Étiré', icon: stretchHorizontalIcon },
    baseline: { label: 'Ligne de base', icon: baselineIcon },
};

const  builderFlexWrapOptions = {
    nowrap: { label: 'No wrap' },
    wrap: { label: 'Wrap' },
    'wrap-reverse': { label: 'Wrap reverse' },
};

const  builderFlexPresetOptions = {
    '': { label: 'Auto', icon: squareDashedIcon },
    '0 0 auto': { label: 'Fixe', icon: moveHorizontalIcon },
    '1 1 0%': { label: 'Grandir', icon: expandIcon },
    '0 1 auto': { label: 'Réduire', icon: shrinkIcon },
};

const  builderAlignSelfOptions = {
    auto: { label: 'Auto', icon: squareDashedIcon },
    'flex-start': { label: 'Début', icon: alignStartVerticalIcon },
    center: { label: 'Centré', icon: alignCenterVerticalIcon },
    'flex-end': { label: 'Fin', icon: alignEndVerticalIcon },
    stretch: { label: 'Étiré', icon: stretchHorizontalIcon },
    baseline: { label: 'Ligne de base', icon: baselineIcon },
};

const  builderJustifySelfOptions = {
    auto: { label: 'Auto', icon: squareDashedIcon },
    start: { label: 'Début', icon: alignStartHorizontalIcon },
    center: { label: 'Centré', icon: alignCenterHorizontalIcon },
    end: { label: 'Fin', icon: alignEndHorizontalIcon },
    stretch: { label: 'Étiré', icon: stretchHorizontalIcon },
};

const  builderPositionOptions = {
    '': { label: '-' },
    static: { label: 'Static' },
    relative: { label: 'Relative' },
    absolute: { label: 'Absolute' },
    fixed: { label: 'Fixed' },
    sticky: { label: 'Sticky' },
};

const  builderBorderStyleOptions = {
    '': { label: 'None' },
    none: { label: 'None' },
    solid: { label: 'Solid' },
    dotted: { label: 'Dotted' },
    dashed: { label: 'Dashed' },
    double: { label: 'Double' },
    groove: { label: 'Groove' },
    ridge: { label: 'Ridge' },
    inset: { label: 'Inset' },
    outset: { label: 'Outset' },
};

const  builderBlendModeOptions = {
    '': { label: '-' },
    normal: { label: 'Normal' },
    multiply: { label: 'Multiply' },
    screen: { label: 'Screen' },
    overlay: { label: 'Overlay' },
    darken: { label: 'Darken' },
    lighten: { label: 'Lighten' },
    'color-dodge': { label: 'Color dodge' },
    'color-burn': { label: 'Color burn' },
    'hard-light': { label: 'Hard light' },
    'soft-light': { label: 'Soft light' },
    difference: { label: 'Difference' },
    exclusion: { label: 'Exclusion' },
    hue: { label: 'Hue' },
    saturation: { label: 'Saturation' },
    color: { label: 'Color' },
    luminosity: { label: 'Luminosity' },
};

const  builderCursorOptions = {
    '': { label: '-' },
    auto: { label: 'auto' },
    default: { label: 'default' },
    pointer: { label: 'pointer' },
    copy: { label: 'copy' },
    crosshair: { label: 'crosshair' },
    text: { label: 'text' },
    grab: { label: 'grab' },
    grabbing: { label: 'grabbing' },
    help: { label: 'help' },
    move: { label: 'move' },
};

const  builderOverflowOptions = {
    '': { label: '-' },
    visible: { label: 'Visible' },
    hidden: { label: 'Hidden' },
    scroll: { label: 'Scroll' },
    auto: { label: 'Auto' },
};

const  builderLineClampOptions = createOrderedOptionMap({
    '': { label: 'Auto' },
    none: { label: 'None' },
    1: { label: '1' },
    2: { label: '2' },
    3: { label: '3' },
    4: { label: '4' },
    5: { label: '5' },
    6: { label: '6' },
});

const  builderListStylePositionOptions = {
    '': { label: 'Auto' },
    outside: { label: 'Outside' },
    inside: { label: 'Inside' },
};

const  builderListStyleTypeOptions = {
    '': { label: 'Auto' },
    disc: { label: 'Disc' },
    decimal: { label: 'Decimal' },
    none: { label: 'None' },
};

const  builderTextDecorationLineOptions = {
    '': { label: 'None' },
    none: { label: 'None' },
    underline: { label: 'Underline' },
    overline: { label: 'Overline' },
    'line-through': { label: 'Line through' },
};

const  builderTextDecorationStyleOptions = {
    '': { label: 'Auto' },
    solid: { label: 'Solid' },
    double: { label: 'Double' },
    dotted: { label: 'Dotted' },
    dashed: { label: 'Dashed' },
    wavy: { label: 'Wavy' },
};

const  builderTextDecorationThicknessOptions = createOrderedOptionMap({
    '': { label: 'Auto' },
    auto: { label: 'Auto' },
    'from-font': { label: 'From font' },
    '1px': { label: '1px' },
    '2px': { label: '2px' },
    '4px': { label: '4px' },
    '8px': { label: '8px' },
});

const  builderTextUnderlineOffsetOptions = createOrderedOptionMap({
    '': { label: 'Auto' },
    auto: { label: 'Auto' },
    '0px': { label: '0px' },
    '1px': { label: '1px' },
    '2px': { label: '2px' },
    '4px': { label: '4px' },
    '8px': { label: '8px' },
});

const  builderTextOverflowOptions = {
    '': { label: 'Auto' },
    truncate: { label: 'Truncate' },
    ellipsis: { label: 'Ellipsis' },
    clip: { label: 'Clip' },
};

const  builderTextWrapOptions = {
    '': { label: 'Auto' },
    wrap: { label: 'Wrap' },
    nowrap: { label: 'No wrap' },
    balance: { label: 'Balance' },
    pretty: { label: 'Pretty' },
};

const  builderTabSizeOptions = createOrderedOptionMap({
    '': { label: 'Auto' },
    2: { label: '2' },
    4: { label: '4' },
    8: { label: '8' },
});

const  builderVerticalAlignOptions = {
    '': { label: 'Baseline' },
    baseline: { label: 'Baseline' },
    top: { label: 'Top' },
    middle: { label: 'Middle' },
    bottom: { label: 'Bottom' },
    'text-top': { label: 'Text top' },
    'text-bottom': { label: 'Text bottom' },
    sub: { label: 'Sub' },
    super: { label: 'Super' },
};

const  builderDirectionOptions = {
    '': { label: 'LTR' },
    ltr: { label: 'LTR' },
    rtl: { label: 'RTL' },
};

const  builderWhiteSpaceOptions = {
    '': { label: 'Normal' },
    normal: { label: 'Normal' },
    nowrap: { label: 'No wrap' },
    pre: { label: 'Pre' },
    'pre-wrap': { label: 'Pre wrap' },
    'pre-line': { label: 'Pre line' },
    'break-spaces': { label: 'Break spaces' },
};

const  builderWordBreakOptions = {
    '': { label: 'Auto' },
    normal: { label: 'Normal' },
    'break-all': { label: 'Break all' },
    'keep-all': { label: 'Keep all' },
};

const  builderOverflowWrapOptions = {
    '': { label: 'Auto' },
    normal: { label: 'Normal' },
    'break-word': { label: 'Break word' },
    anywhere: { label: 'Anywhere' },
};

const  builderHyphensOptions = {
    '': { label: 'Auto' },
    none: { label: 'None' },
    manual: { label: 'Manual' },
    auto: { label: 'Auto' },
};

const  builderBackgroundRepeatOptions = {
    repeat: { label: 'Repeat' },
    'no-repeat': { label: 'No repeat' },
    'repeat-x': { label: 'Repeat X' },
    'repeat-y': { label: 'Repeat Y' },
    round: { label: 'Round' },
    space: { label: 'Space' },
};

const  builderBackgroundAttachmentOptions = {
    scroll: { label: 'Scroll' },
    fixed: { label: 'Fixed' },
    local: { label: 'Local' },
};

const  builderBackgroundOriginOptions = {
    'border-box': { label: 'Border box' },
    'padding-box': { label: 'Padding box' },
    'content-box': { label: 'Content box' },
};

const  builderBackgroundClipOptions = {
    '': { label: 'Auto' },
    'border-box': { label: 'Border' },
    'padding-box': { label: 'Padding' },
    'content-box': { label: 'Content' },
    text: { label: 'Text' },
};

const  builderBackgroundSizeOptions = {
    auto: { label: 'Auto' },
    cover: { label: 'Cover' },
    contain: { label: 'Contain' },
    custom: { label: 'Custom' },
};
const  builderBackgroundPositionOptions = {
    'bg-top-left': { label: 'bg-top-left' },
    'bg-top': { label: 'bg-top' },
    'bg-top-right': { label: 'bg-top-right' },
    'bg-left': { label: 'bg-left' },
    'bg-center': { label: 'bg-center' },
    'bg-right': { label: 'bg-right' },
    'bg-bottom-left': { label: 'bg-bottom-left' },
    'bg-bottom': { label: 'bg-bottom' },
    'bg-bottom-right': { label: 'bg-bottom-right' },
};
const  builderBackgroundPositionValues = {
    'bg-top-left': 'top left',
    'bg-top': 'top',
    'bg-top-right': 'top right',
    'bg-left': 'left',
    'bg-center': 'center',
    'bg-right': 'right',
    'bg-bottom-left': 'bottom left',
    'bg-bottom': 'bottom',
    'bg-bottom-right': 'bottom right',
};

const  builderBackgroundTypeIcons = {
    image: imagesIcon,
    gradient: squareDashedIcon,
    color: squareIcon,
};
const  builderGradientDirectionOptions = {
    'to-t': { label: 'to-t' },
    'to-tr': { label: 'to-tr' },
    'to-r': { label: 'to-r' },
    'to-br': { label: 'to-br' },
    'to-b': { label: 'to-b' },
    'to-bl': { label: 'to-bl' },
    'to-l': { label: 'to-l' },
    'to-tl': { label: 'to-tl' },
};
const  builderGradientDirectionDegrees = {
    'to-t': '0deg',
    'to-tr': '45deg',
    'to-r': '90deg',
    'to-br': '135deg',
    'to-b': '180deg',
    'to-bl': '225deg',
    'to-l': '270deg',
    'to-tl': '315deg',
};
const  builderBackgroundDefaults = {
    'data-overlay': 'none',
    'data-background-asset': '',
    'data-background-image-position': '',
    'data-background-image-size': 'cover',
    'data-background-image-repeat': '',
    'data-background-image-attachment': '',
    'data-background-image-origin': '',
    'data-background-gradient-angle': 'tw:to-t',
    'data-background-gradient-repeat': 'false',
    'data-background-gradient-radial': 'false',
    'data-background-gradient-size': '100px',
    'data-background-gradient-x': '50%',
    'data-background-gradient-y': '50%',
    'data-background-gradient-stops': '',
};
const  builderBackgroundOverridesAttribute = 'data-background-overrides';
const  builderBackgroundItemsAttribute = 'data-background-items';
const  builderDomMutationsAttribute = 'data-dom-mutations';
const  builderContentMutationsAttribute = 'data-content-mutations';

function  builderLayerAttributeName(layer) {
    return String(layer || 'container').replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
}

function  builderBackgroundItemsAttributeForLayer(layer = 'container') {
    return layer === 'container'
        ?  builderBackgroundItemsAttribute
        : `data-${ builderLayerAttributeName(layer)}-background-items`;
}

function isBackgroundItemsCollectionAttribute(attribute) {
    const value = String(attribute || '');

    return value ===  builderBackgroundItemsAttribute || /^data-[a-z0-9-]+-background-items$/i.test(value);
}

function layerFromBackgroundItemsCollectionAttribute(attribute) {
    const value = String(attribute || '');
    if (value ===  builderBackgroundItemsAttribute) {
        return 'container';
    }

    const match = value.match(/^data-(.+)-background-items$/i);

    return match ? match[1].replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()) : null;
}

function createBackgroundItemId() {
    return `bg-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

const  builderGridAutoFlowOptions = {
    row: { label: 'Row', icon: arrowRightIcon },
    column: { label: 'Column', icon: arrowDownIcon },
    dense: { label: 'Dense', icon: squareDashedIcon },
    'row dense': { label: 'Row dense', icon: arrowLeftIcon },
    'column dense': { label: 'Column dense', icon: arrowUpIcon },
};

const  builderJustifyItemsOptions = {
    stretch: { label: 'Stretch', icon: stretchHorizontalIcon },
    start: { label: 'Start', icon: alignStartHorizontalIcon },
    center: { label: 'Center', icon: alignCenterHorizontalIcon },
    end: { label: 'End', icon: alignEndHorizontalIcon },
};

const  builderAlignContentOptions = {
    stretch: { label: 'Stretch', icon: stretchHorizontalIcon },
    start: { label: 'Start', icon: alignStartVerticalIcon },
    center: { label: 'Center', icon: alignCenterVerticalIcon },
    end: { label: 'End', icon: alignEndVerticalIcon },
    baseline: { label: 'Baseline', icon: baselineIcon },
    'space-between': { label: 'Space between', icon: alignHorizontalSpaceBetweenIcon },
    'space-around': { label: 'Space around', icon: alignHorizontalSpaceAroundIcon },
    'space-evenly': { label: 'Space evenly', icon: alignHorizontalDistributeCenterIcon },
};

const  builderPlaceItemsOptions = {
    stretch: { label: 'Stretch', icon: stretchHorizontalIcon },
    start: { label: 'Start', icon: alignStartHorizontalIcon },
    center: { label: 'Center', icon: alignCenterHorizontalIcon },
    end: { label: 'End', icon: alignEndHorizontalIcon },
    baseline: { label: 'Baseline', icon: baselineIcon },
};

const  builderPlaceContentOptions = {
    stretch: { label: 'Stretch', icon: stretchHorizontalIcon },
    start: { label: 'Start', icon: alignStartVerticalIcon },
    center: { label: 'Center', icon: alignCenterVerticalIcon },
    end: { label: 'End', icon: alignEndVerticalIcon },
    baseline: { label: 'Baseline', icon: baselineIcon },
    'space-between': { label: 'Space between', icon: alignHorizontalSpaceBetweenIcon },
    'space-around': { label: 'Space around', icon: alignHorizontalSpaceAroundIcon },
    'space-evenly': { label: 'Space evenly', icon: alignHorizontalDistributeCenterIcon },
};

const  builderPlaceSelfOptions = {
    auto: { label: 'Auto', icon: squareDashedIcon },
    start: { label: 'Start', icon: alignStartHorizontalIcon },
    center: { label: 'Center', icon: alignCenterHorizontalIcon },
    end: { label: 'End', icon: alignEndHorizontalIcon },
    stretch: { label: 'Stretch', icon: stretchHorizontalIcon },
};

const  builderGridTemplateColumnsPresetOptions = createOrderedOptionMap({
    '': { label: '-' },
    none: { label: 'none' },
    subgrid: { label: 'subgrid' },
    '1fr': { label: '1fr' },
    '1fr 1fr': { label: '1fr 1fr' },
    '1fr 1fr 1fr': { label: '1fr 1fr 1fr' },
    'repeat(2, minmax(0, 1fr))': { label: 'repeat(2, minmax(0, 1fr))' },
    'repeat(3, minmax(0, 1fr))': { label: 'repeat(3, minmax(0, 1fr))' },
    'repeat(4, minmax(0, 1fr))': { label: 'repeat(4, minmax(0, 1fr))' },
    '200px 1fr': { label: '200px 1fr' },
    '240px 1fr': { label: '240px 1fr' },
    'minmax(0, 1fr) 2fr': { label: 'minmax(0, 1fr) 2fr' },
    'repeat(auto-fit, minmax(200px, 1fr))': { label: 'repeat(auto-fit, minmax(200px, 1fr))' },
    'repeat(auto-fill, minmax(160px, 1fr))': { label: 'repeat(auto-fill, minmax(160px, 1fr))' },
});

const  builderGridTemplateRowsPresetOptions = createOrderedOptionMap({
    '': { label: '-' },
    none: { label: 'none' },
    subgrid: { label: 'subgrid' },
    auto: { label: 'auto' },
    'auto auto': { label: 'auto auto' },
    '1fr': { label: '1fr' },
    '1fr 1fr': { label: '1fr 1fr' },
    'auto 1fr auto': { label: 'auto 1fr auto' },
    'minmax(120px, auto) 1fr': { label: 'minmax(120px, auto) 1fr' },
    'repeat(2, minmax(0, 1fr))': { label: 'repeat(2, minmax(0, 1fr))' },
    'repeat(3, minmax(0, 1fr))': { label: 'repeat(3, minmax(0, 1fr))' },
});

const  builderBackfaceOptions = {
    visible: { label: 'Visible' },
    hidden: { label: 'Hidden' },
};

const  builderTransformStyleOptions = {
    flat: { label: '2D' },
    'preserve-3d': { label: '3D' },
};

const  builderEffectCollectionConfigs = {
    'box-shadow': { label: 'Box Shadow', separator: ', ' },
    'text-shadow': { label: 'Text Shadow', separator: ', ' },
    filter: { label: 'Filter', separator: ' ' },
    'backdrop-filter': { label: 'Backdrop Filter', separator: ' ' },
    transition: { label: 'Transition', separator: ', ' },
    transform: { label: 'Transform', separator: ' ' },
};

function  builderStyleAttribute(layer, property) {
    return `data-${layer.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-style-${property}`;
}

function  builderPresetAttribute(layer) {
    return `data-${layer.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-preset`;
}

function  builderClassAttribute(layer) {
    return `data-${layer.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-classes`;
}

function  builderLayoutAttribute(target, property) {
    return `data-${target.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-layout-${property}`;
}

function  builderFlexChildAttribute(layer, property) {
    return `data-${layer.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-flex-${property}`;
}

function  builderEffectCollectionAttribute(layer, property) {
    return `data-${layer.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-effect-${property}-items`;
}

function normalizeLegacyTailwindClass(className) {
    const parts = tailwindClassParts(String(className || ''));
    const important = String(className || '').startsWith('!') || String(className || '').endsWith('!');
    const propertyMatch = parts.base.match(/^\[(color|background-color|border-color|border-top-color|border-right-color|border-bottom-color|border-left-color|text-decoration-color):(.+)]$/);
    if (propertyMatch) {
        const normalizedClass = tailwindColorClassFromBuilderValue(propertyMatch[1], arbitraryCssValue(propertyMatch[2]));
        if (normalizedClass) {
            return `${parts.breakpoint ? `${parts.breakpoint}:` : ''}${normalizedClass}${important ? '!' : ''}`;
        }
    }

    const orderValue = orderValueFromTailwindClass(parts.base);
    if (orderValue === null) {
        return className;
    }

    return `${parts.breakpoint ? `${parts.breakpoint}:` : ''}${tailwindOrderClassFromValue(orderValue)}${important ? '!' : ''}`;
}

const obsoleteFrontBackgroundClassPatterns = [
    /^\[&_img]:\[object-position:/,
    /^\[&_img]:object-(?:contain|cover|fill|none|scale-down)$/,
    /^\[&_img]:opacity-\[/,
    /^\[&_\[data-background]]:\[background-position:/,
    /^\[&_\[data-background]]:\[background-size:/,
];

function normalizeBuilderClassList(value) {
    return String(value ?? '')
        .split(/\s+/)
        .filter(Boolean)
        .filter((className) => !obsoleteFrontBackgroundClassPatterns.some((pattern) => pattern.test(className)))
        .map((className) => normalizeLegacyTailwindClass(className))
        .join(' ');
}

function  builderStyleAttributes(attributes = {}) {
    return Object.entries( builderTextStyleDefaults).reduce((styleAttributes, [layer, defaults]) => {
        Object.entries(defaults).forEach(([property, fallback]) => {
            const attribute =  builderStyleAttribute(layer, property);
            styleAttributes[attribute] = String(attributes[attribute] ?? fallback);
        });

        return styleAttributes;
    }, {});
}

function  builderPresetAttributes(attributes = {}) {
    return Object.entries( builderPresetDefaults).reduce((presetAttributes, [layer, fallback]) => {
        presetAttributes[ builderPresetAttribute(layer)] = String(attributes[ builderPresetAttribute(layer)] ?? fallback);
        presetAttributes[ builderClassAttribute(layer)] = normalizeBuilderClassList(attributes[ builderClassAttribute(layer)] ?? '');

        return presetAttributes;
    }, {});
}

function  builderLayoutAttributes(attributes = {}) {
    return Object.entries( builderLayoutStyleDefaults).reduce((styleAttributes, [target, defaults]) => {
        Object.entries(defaults).forEach(([property, fallback]) => {
            const attribute =  builderLayoutAttribute(target, property);
            styleAttributes[attribute] = String(attributes[attribute] ?? fallback);
        });

        return styleAttributes;
    }, {});
}

function  builderFlexChildAttributes(attributes = {}) {
    return Object.entries( builderFlexChildDefaults).reduce((styleAttributes, [layer, defaults]) => {
        Object.entries(defaults).forEach(([property, fallback]) => {
            const attribute =  builderFlexChildAttribute(layer, property);
            styleAttributes[attribute] = String(attributes[attribute] ?? fallback);
        });

    return styleAttributes;
    }, {});
}

function  builderEffectCollectionAttributes(attributes = {}) {
    return Object.keys( builderTextStyleDefaults).reduce((collectionAttributes, layer) => {
        Object.keys( builderEffectCollectionConfigs).forEach((property) => {
            const attribute =  builderEffectCollectionAttribute(layer, property);
            collectionAttributes[attribute] = String(attributes[attribute] ?? '');
        });

        return collectionAttributes;
    }, {});
}

function cssPreviewValueFromTailwindToken(property, token) {
    const normalizedToken = String(token ?? '').trim();
    const negative = normalizedToken.startsWith('-');
    const unsignedToken = negative ? normalizedToken.slice(1) : normalizedToken;
    const spacingValue = () => {
        if (unsignedToken === 'px') {
            return negative ? '-1px' : '1px';
        }

        if (!tailwindScaleValues.has(unsignedToken) || !/^\d+(?:\.5)?$/.test(unsignedToken)) {
            return null;
        }

        const value = Number(unsignedToken) * 0.25;

        return `${negative ? '-' : ''}${Number.isInteger(value) ? value : Math.round(value * 1000) / 1000}rem`;
    };
    const fontSizeMap = {
        xs: '0.75rem',
        sm: '0.875rem',
        base: '1rem',
        lg: '1.125rem',
        xl: '1.25rem',
        '2xl': '1.5rem',
        '3xl': '1.875rem',
        '4xl': '2.25rem',
        '5xl': '3rem',
        '6xl': '3.75rem',
        '7xl': '4.5rem',
        '8xl': '6rem',
        '9xl': '8rem',
    };
    const lineHeightMap = {
        none: '1',
        tight: '1.25',
        snug: '1.375',
        normal: '1.5',
        relaxed: '1.625',
        loose: '2',
    };
    const letterSpacingMap = {
        tighter: '-0.05em',
        tight: '-0.025em',
        normal: '0em',
        wide: '0.025em',
        wider: '0.05em',
        widest: '0.1em',
    };

    if (property === 'font-size') {
        return negative ? null : fontSizeMap[unsignedToken] || null;
    }

    if (property === 'line-height') {
        return negative ? null : (lineHeightMap[unsignedToken] || spacingValue());
    }

    if (property === 'letter-spacing') {
        return negative ? null : (letterSpacingMap[unsignedToken] || null);
    }

    const dimensionConfig = tailwindDimensionConfig(property);
    if (dimensionConfig) {
        if (negative) {
            return null;
        }

        return dimensionConfig.values[unsignedToken] || spacingValue();
    }

    return spacingValue();
}

function cssPreviewValueFromBuilderValue(property, value) {
    const normalized = String(value ?? '').trim();

    if (property === 'font-smoothing') {
        return '';
    }

    if (property === 'text-overflow' && normalized === 'truncate') {
        return '';
    }

    if (!normalized.startsWith('tw:')) {
        return normalized;
    }

    return cssPreviewValueFromTailwindToken(property, normalized.slice(3));
}

function spacingPixelValueFromBuilderValue(value) {
    const normalized = String(value ?? '').trim();
    if (!normalized) {
        return '';
    }

    if (!normalized.startsWith('tw:')) {
        return normalized;
    }

    const token = normalized.slice(3);
    if (token === 'px') {
        return '1px';
    }

    if (/^\d+(?:\.5)?$/.test(token)) {
        const pixels = Number(token) * 4;

        return `${Number.isInteger(pixels) ? pixels : Math.round(pixels * 1000) / 1000}px`;
    }

    return cssPreviewValueFromBuilderValue('padding', normalized);
}

function spacingTokenFromAxisClass(className, prefix) {
    const escaped = prefix.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
    const tokenMatch = className.match(new RegExp(`^${escaped}-(auto|px|\\d+(?:\\.5)?)$`));
    if (tokenMatch) {
        return tokenMatch[1] === 'auto' ? 'auto' : `tw:${tokenMatch[1]}`;
    }

    const arbitraryMatch = className.match(new RegExp(`^${escaped}-\\[(.+)]$`));

    return arbitraryMatch ? arbitraryMatch[1].replace(/_/g, ' ') : null;
}

function compositeAxisSpacingValueFromTailwindClasses(classNames, config) {
    let all = null;
    let horizontal = null;
    let vertical = null;

    classNames.forEach((className) => {
        const allValue = styleValueFromTailwindClass(className, config.property);
        if (allValue !== null) {
            all = allValue;
        }

        const horizontalValue = spacingTokenFromAxisClass(className, config.horizontalPrefix);
        if (horizontalValue !== null) {
            horizontal = horizontalValue;
        }

        const verticalValue = spacingTokenFromAxisClass(className, config.verticalPrefix);
        if (verticalValue !== null) {
            vertical = verticalValue;
        }
    });

    if (all !== null) {
        return spacingPixelValueFromBuilderValue(all);
    }

    if (horizontal !== null && vertical !== null) {
        return `${spacingPixelValueFromBuilderValue(vertical)} ${spacingPixelValueFromBuilderValue(horizontal)}`;
    }

    return null;
}

function compositePaddingValueFromTailwindClasses(classNames) {
    return compositeAxisSpacingValueFromTailwindClasses(classNames, {
        property: 'padding',
        horizontalPrefix: 'px',
        verticalPrefix: 'py',
    });
}

function compositeMarginValueFromTailwindClasses(classNames) {
    return compositeAxisSpacingValueFromTailwindClasses(classNames, {
        property: 'margin',
        horizontalPrefix: 'mx',
        verticalPrefix: 'my',
    });
}

function  builderStyleFromDefaults(attributes, defaults, attributeResolver) {
    return Object.keys(defaults || {}).map((property) => {
        const attribute = attributeResolver(property);
        const value = attributes[attribute];
        if (!isBuilderStyleOverride(attribute, value)) {
            return '';
        }

        const previewValue = cssPreviewValueFromBuilderValue(property, value);
        if (!previewValue) {
            return '';
        }

        return `${property}: ${escapeHtml(previewValue)};`;
    }).filter(Boolean).join(' ');
}

function backgroundItemCssLayer(item) {
    const normalized = normalizeBackgroundItem(item);

    if (normalized.type === 'image') {
        const assetUrl = resolveThemeAssetUrl(normalized.asset);

        return normalized.asset ? {
            image: `url(${JSON.stringify(assetUrl)})`,
            position: normalized.position || 'center',
            size: backgroundImageSizeValue(normalized),
            repeat: normalized.repeat || 'no-repeat',
            origin: normalized.origin || 'padding-box',
            attachment: normalized.attachment || 'scroll',
        } : backgroundImagePlaceholderLayer();
    }

    if (normalized.type === 'gradient') {
        const gradient = gradientValueFromBackgroundItem(normalized);

        return gradient ? {
            image: gradient,
            position: 'center',
            size: 'auto',
            repeat: normalized.repeat === 'true' ? 'repeat' : 'no-repeat',
            origin: 'padding-box',
            attachment: 'scroll',
        } : null;
    }

    if (normalized.type === 'color' && normalized.color) {
        return {
            image: `linear-gradient(${normalized.color}, ${normalized.color})`,
            position: 'center',
            size: 'auto',
            repeat: 'no-repeat',
            origin: 'padding-box',
            attachment: 'scroll',
        };
    }

    return null;
}

function backgroundItemsCssStyle(items) {
    const layers = [...(items || [])]
        .map((item) => normalizeBackgroundItem(item))
        .reverse()
        .map(backgroundItemCssLayer)
        .filter(Boolean);

    if (!layers.length) {
        return '';
    }

    return [
        `background-image: ${escapeHtml(layers.map((layer) => layer.image).join(', '))};`,
        `background-position: ${escapeHtml(layers.map((layer) => layer.position).join(', '))};`,
        `background-size: ${escapeHtml(layers.map((layer) => layer.size).join(', '))};`,
        `background-repeat: ${escapeHtml(layers.map((layer) => layer.repeat).join(', '))};`,
        `background-origin: ${escapeHtml(layers.map((layer) => layer.origin).join(', '))};`,
        `background-attachment: ${escapeHtml(layers.map((layer) => layer.attachment).join(', '))};`,
    ].join(' ');
}

function backgroundItemPreviewStyle(item) {
    return backgroundItemsCssStyle([normalizeBackgroundItem(item)]);
}

function backgroundThumbMarkup(item) {
    const normalized = normalizeBackgroundItem(item, 'image');

    return `<div class="viti-builder-background-thumb${normalized.asset ? ' is-preview' : ' is-placeholder'}" style="${backgroundItemPreviewStyle(normalized)}">${normalized.asset ? '' : '<span>Image</span>'}</div>`;
}

function backgroundClipStyle(attributes, layer) {
    const clip = styleAttributeValue(attributes, layer, 'background-clip', '');
    if (clip !== 'text') {
        return '';
    }

    const hasBackgroundFill = parseBackgroundItems(attributes, layer).length > 0;

    return [
        '-webkit-background-clip: text;',
        'background-clip: text;',
        hasBackgroundFill ? 'color: transparent;' : '',
        hasBackgroundFill ? '-webkit-text-fill-color: transparent;' : '',
    ].filter(Boolean).join(' ');
}

function  builderLayerStyle(attributes, layer) {
    return [
         builderStyleFromDefaults(attributes,  builderTextStyleDefaults[layer], (property) =>  builderStyleAttribute(layer, property)),
         builderStyleFromDefaults(attributes,  builderFlexChildDefaults[layer], (property) =>  builderFlexChildAttribute(layer, property)),
        layer === 'container' ? '' : backgroundItemsCssStyle(parseBackgroundItems(attributes, layer)),
        backgroundClipStyle(attributes, layer),
    ].filter(Boolean).join(' ');
}

function  builderLayoutStyle(attributes, target) {
    return  builderStyleFromDefaults(attributes,  builderLayoutStyleDefaults[target], (property) =>  builderLayoutAttribute(target, property));
}

function  builderClassOverride(attributes, layer) {
    return attributes[ builderClassAttribute(layer)] || '';
}

function  builderSectionClasses(attributes) {
    return classNames(
         builderBaseClasses.section,
        classOption( builderSectionMaxWidths, attributes['data-section-max-width'], '1280').classes,
        classOption( builderSectionHorizontalPaddings, attributes['data-section-horizontal-padding'], '64').classes,
        classOption( builderSectionMarginBottoms, attributes['data-section-margin-bottom'], '96').classes,
         builderClassOverride(attributes, 'section'),
    );
}

function  builderContainerClasses(attributes) {
    return classNames(
         builderBaseClasses.container,
        classOption( builderContainerMinHeights, attributes['data-min-height'], '720').classes,
        classOption( builderContainerPaddings, attributes['data-container-padding'], '64').classes,
        classOption( builderHorizontalAlignments, attributes['data-horizontal-alignment'], 'left').classes,
        classOption( builderVerticalAlignments, attributes['data-vertical-alignment'], 'bottom').classes,
    );
}

function isBackgroundElement(element) {
    return element?.dataset?.background === 'true'
        || element?.dataset?.gjsBackground === 'true'
        || isLegacyBackgroundElement(element);
}

function hasClassPrefix(element, prefix) {
    return [...(element?.classList || [])].some((className) => className.startsWith(prefix));
}

function isAbsoluteInsetElement(element) {
    return !!element
        && element.classList.contains('absolute')
        && element.classList.contains('inset-0');
}

function isLegacyBackgroundElement(element) {
    if (!isAbsoluteInsetElement(element)) {
        return false;
    }

    const tagName = element.tagName?.toLowerCase();
    const style = element.getAttribute?.('style') || '';

    return tagName === 'img'
        || hasClassPrefix(element, 'bg-')
        || style.includes('background:') || style.includes('background-image:');
}

function isLegacyBackgroundImage(img) {
    return !!img
        && img.tagName?.toLowerCase() === 'img'
        && isAbsoluteInsetElement(img)
        && (
            img.classList.contains('object-cover')
            || (img.classList.contains('h-full') && img.classList.contains('w-full'))
            || img.style?.objectFit === 'cover'
        );
}

function isBackgroundImage(img) {
    return isBackgroundElement(img) || isLegacyBackgroundImage(img);
}

function  builderOverlayGradientStops(overlay) {
    return overlay === 'soft'
        ? '#4009198c 0%, rgba(64, 9, 25, 0.15) 48%, rgba(64, 9, 25, 0) 100%'
        : '#400919cc 0%, rgba(64, 9, 25, 0) 100%';
}

function createGradientStopId() {
    return `stop-${Math.random().toString(36).slice(2, 9)}`;
}

function clampNumber(value, min, max, fallback = min) {
    const number = Number.parseFloat(value);

    return Number.isNaN(number) ? fallback : Math.max(min, Math.min(max, number));
}

function rgbaToHex(value) {
    const normalized = String(value || '').trim();
    const hex = normalized.match(/^#([0-9a-f]{3,8})$/i);
    if (hex) {
        const raw = hex[1];
        const expanded = raw.length === 3 || raw.length === 4
            ? raw.split('').map((char) => char + char).join('')
            : raw;

        return `#${expanded.slice(0, 6)}`;
    }

    const rgba = normalized.match(/rgba?\(([^)]+)\)/i);
    if (!rgba) {
        return '#400919';
    }

    const parts = rgba[1].split(',').map((part) => Number.parseFloat(part.trim()));
    const toHex = (number) => Math.max(0, Math.min(255, Math.round(number || 0))).toString(16).padStart(2, '0');

    return `#${toHex(parts[0])}${toHex(parts[1])}${toHex(parts[2])}`;
}

function alphaFromColorValue(value, fallback = 1) {
    const normalized = String(value || '').trim();
    const hex = normalized.match(/^#([0-9a-f]{4}|[0-9a-f]{8})$/i);
    if (hex) {
        const raw = hex[1];
        const alpha = raw.length === 4 ? raw[3] + raw[3] : raw.slice(6, 8);

        return clampNumber(Number.parseInt(alpha, 16) / 255, 0, 1, fallback);
    }

    const rgba = normalized.match(/rgba?\(([^)]+)\)/i);
    if (!rgba) {
        return fallback;
    }

    const alpha = rgba[1].split(',')[3];

    return alpha === undefined ? 1 : clampNumber(alpha, 0, 1, fallback);
}

function cssColorComparableValue(value) {
    const normalized = String(value ?? '').trim();
    if (!normalized) {
        return null;
    }

    const lower = normalized.toLowerCase();
    if (lower === 'transparent') {
        return { type: 'color', hex: '#000000', alpha: 0 };
    }

    if (lower === 'currentcolor' || lower === 'inherit') {
        return { type: lower };
    }

    const variable = normalized.match(/^var\(\s*(--color-[^) ,]+)\s*\)$/i);
    if (variable) {
        const resolved = window.getComputedStyle(document.documentElement).getPropertyValue(variable[1]).trim();

        return resolved ? cssColorComparableValue(resolved) : null;
    }

    const named = {
        black: '#000000',
        white: '#ffffff',
    }[lower];
    if (named) {
        return { type: 'color', hex: named, alpha: 1 };
    }

    const hex = normalized.match(/^#([0-9a-f]{3,8})$/i);
    if (hex) {
        const raw = hex[1];
        const expanded = raw.length === 3 || raw.length === 4
            ? raw.split('').map((char) => char + char).join('')
            : raw;
        const alpha = expanded.length === 8
            ? clampNumber(Number.parseInt(expanded.slice(6, 8), 16) / 255, 0, 1, 1)
            : 1;

        return { type: 'color', hex: `#${expanded.slice(0, 6).toLowerCase()}`, alpha };
    }

    const rgba = normalized.match(/^rgba?\(([^)]+)\)$/i);
    if (rgba) {
        const parts = rgba[1].split(',').map((part) => part.trim());
        if (parts.length >= 3) {
            const red = Number.parseFloat(parts[0]);
            const green = Number.parseFloat(parts[1]);
            const blue = Number.parseFloat(parts[2]);
            if (![red, green, blue].some((part) => Number.isNaN(part))) {
                return {
                    type: 'color',
                    hex: hexFromRgb(red, green, blue),
                    alpha: parts[3] === undefined ? 1 : clampNumber(Number.parseFloat(parts[3]), 0, 1, 1),
                };
            }
        }
    }

    return null;
}

function equivalentColorComparableValue(left, right) {
    if (!left || !right || left.type !== right.type) {
        return false;
    }

    if (left.type !== 'color') {
        return true;
    }

    return left.hex === right.hex && Math.abs(left.alpha - right.alpha) < 0.01;
}

function themeColorTokenNameFromValue(value) {
    const comparable = cssColorComparableValue(value);
    if (!comparable || comparable.type !== 'color' || comparable.alpha < 0.99) {
        return null;
    }

    const directVariable = String(value ?? '').trim().match(/^var\(\s*--color-([^) ,]+)\s*\)$/i);
    if (directVariable) {
        return directVariable[1];
    }

    const computedStyle = window.getComputedStyle(document.documentElement);
    for (let index = 0; index < computedStyle.length; index += 1) {
        const property = computedStyle.item(index);
        if (!property.startsWith('--color-')) {
            continue;
        }

        const tokenComparable = cssColorComparableValue(computedStyle.getPropertyValue(property));
        if (equivalentColorComparableValue(comparable, tokenComparable)) {
            return property.slice('--color-'.length);
        }
    }

    return null;
}

function hasVisibleBackgroundColorValue(value) {
    const normalized = String(value || '').trim();
    if (!normalized || /^transparent$/i.test(normalized)) {
        return false;
    }

    return alphaFromColorValue(normalized, 1) > 0;
}

function colorWithAlpha(color, alpha) {
    const hex = rgbaToHex(color).replace('#', '');
    const red = Number.parseInt(hex.slice(0, 2), 16);
    const green = Number.parseInt(hex.slice(2, 4), 16);
    const blue = Number.parseInt(hex.slice(4, 6), 16);
    const normalizedAlpha = clampNumber(alpha, 0, 1, 1);

    return normalizedAlpha >= 1 ? `#${hex}` : `rgba(${red}, ${green}, ${blue}, ${Math.round(normalizedAlpha * 100) / 100})`;
}

function hexFromRgb(red, green, blue) {
    const toHex = (number) => Math.max(0, Math.min(255, Math.round(number || 0))).toString(16).padStart(2, '0');

    return `#${toHex(red)}${toHex(green)}${toHex(blue)}`;
}

function defaultGradientStops(overlay = 'dark') {
    if (overlay === 'soft') {
        return [
            { id: createGradientStopId(), color: '#400919', alpha: 0.55, position: 0 },
            { id: createGradientStopId(), color: '#400919', alpha: 0.15, position: 48 },
            { id: createGradientStopId(), color: '#400919', alpha: 0, position: 100 },
        ];
    }

    return [
        { id: createGradientStopId(), color: '#400919', alpha: 0.8, position: 0 },
        { id: createGradientStopId(), color: '#400919', alpha: 0, position: 100 },
    ];
}

function normalizeGradientStops(value, overlay = 'dark') {
    let parsed = value;
    if (typeof value === 'string' && value.trim()) {
        try {
            parsed = JSON.parse(value);
        } catch {
            parsed = null;
        }
    }

    const stops = Array.isArray(parsed) ? parsed : defaultGradientStops(overlay);
    const normalized = stops.map((stop, index) => {
        const color = rgbaToHex(stop.color || '#400919');

        return {
            id: stop.id || createGradientStopId(),
            color,
            alpha: clampNumber(stop.alpha ?? alphaFromColorValue(stop.color, index === 0 ? 1 : 0), 0, 1, index === 0 ? 1 : 0),
            position: clampNumber(stop.position ?? (index === 0 ? 0 : 100), 0, 100, index === 0 ? 0 : 100),
        };
    }).sort((left, right) => left.position - right.position);

    return normalized.length >= 2 ? normalized : defaultGradientStops(overlay);
}

function serializeGradientStops(stops) {
    return JSON.stringify(normalizeGradientStops(stops).map((stop) => ({
        id: stop.id,
        color: stop.color,
        alpha: Math.round(stop.alpha * 100) / 100,
        position: Math.round(stop.position * 10) / 10,
    })));
}

function gradientStopsCss(value, overlay = 'dark') {
    return normalizeGradientStops(value, overlay)
        .map((stop) => `${colorWithAlpha(stop.color, stop.alpha)} ${Math.round(stop.position * 10) / 10}%`)
        .join(', ');
}

function  builderGradientAngleValue(value) {
    const normalized = String(value || '').trim();
    if (normalized.startsWith('tw:')) {
        return  builderGradientDirectionDegrees[normalized.slice(3)] || '0deg';
    }

    return normalized || '0deg';
}

function  builderOverlayGradientValue(attributes) {
    const overlay = attributes['data-overlay'] || 'dark';
    if (overlay === 'none') {
        return '';
    }

    const stops = attributes['data-background-gradient-stops']
        ? gradientStopsCss(attributes['data-background-gradient-stops'], overlay)
        :  builderOverlayGradientStops(overlay);
    const repeatPrefix = attributes['data-background-gradient-repeat'] === 'true' ? 'repeating-' : '';

    if (attributes['data-background-gradient-radial'] === 'true') {
        const size = attributes['data-background-gradient-size'] || '100px';
        const x = attributes['data-background-gradient-x'] || '50%';
        const y = attributes['data-background-gradient-y'] || '50%';

        return `${repeatPrefix}radial-gradient(circle ${size} at ${x} ${y}, ${stops})`;
    }

    return `${repeatPrefix}linear-gradient(${ builderGradientAngleValue(attributes['data-background-gradient-angle'])}, ${stops})`;
}

function  builderOverlayBackgroundStyle(attributes) {
    const gradient =  builderOverlayGradientValue(attributes);

    return gradient ? `background: ${gradient};` : '';
}

function backgroundImagePlaceholderSrc() {
    const svg = `
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMid slice">
            <rect width="1600" height="900" fill="#9b9b9b"/>
            <g fill="rgba(255,255,255,.92)" font-family="Arial, Helvetica, sans-serif" font-size="76" font-weight="700" letter-spacing="2">
                <text x="90" y="470">IMAGE</text>
                <text x="540" y="470">IMAGE</text>
                <text x="990" y="470">IMAGE</text>
                <text x="1440" y="470">IMAGE</text>
            </g>
        </svg>
    `.trim();

    return `data:image/svg+xml;charset=UTF-8,${encodeURIComponent(svg)}`;
}

function backgroundImagePlaceholderLayer() {
    return {
        image: `url(${JSON.stringify(backgroundImagePlaceholderSrc())})`,
        position: 'center',
        size: 'cover',
        repeat: 'no-repeat',
        origin: 'padding-box',
        attachment: 'scroll',
    };
}

function backgroundItemDefaults(type) {
    return {
        image: {
            id: createBackgroundItemId(),
            type: 'image',
            asset: '',
            position: '',
            size: 'cover',
            opacity: '',
            repeat: '',
            attachment: '',
            origin: '',
        },
        gradient: {
            id: createBackgroundItemId(),
            type: 'gradient',
            overlay: 'dark',
            angle: 'tw:to-t',
            repeat: 'false',
            radial: 'false',
            size: '100px',
            x: '50%',
            y: '50%',
            stops: '',
        },
        color: {
            id: createBackgroundItemId(),
            type: 'color',
            color: 'rgba(0, 0, 0, 0.5)',
        },
    }[type] || null;
}

function createdBackgroundItem(type) {
    return {
        ...backgroundItemDefaults(type),
        createdInBuilder: true,
    };
}

function createEffectCollectionItemId() {
    return `fx-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
}

function effectCollectionConfig(property) {
    return  builderEffectCollectionConfigs[property] || null;
}

function serializeEffectCollectionItems(items) {
    return JSON.stringify((items || []).map((item) => ({
        id: item.id || createEffectCollectionItemId(),
        value: String(item.value || ''),
        createdInBuilder: item.createdInBuilder === true,
    })));
}

function normalizeEffectCollectionItem(item, index = 0) {
    return {
        id: item?.id || `fx-import-${index}`,
        value: String(item?.value || '').trim(),
        createdInBuilder: item?.createdInBuilder === true,
    };
}

function parseEffectCollectionItems(attributes, layer, property) {
    const config = effectCollectionConfig(property);
    if (!config) {
        return [];
    }

    const collectionAttribute =  builderEffectCollectionAttribute(layer, property);
    const serialized = String(attributes?.[collectionAttribute] || '').trim();
    if (serialized) {
        try {
            const items = JSON.parse(serialized);
            if (Array.isArray(items)) {
                const normalizedItems = items.map((item, index) => normalizeEffectCollectionItem(item, index));

                return normalizedItems.length ? [normalizedItems[normalizedItems.length - 1]] : [];
            }
        } catch {
            return [];
        }
    }

    const rawValue = inspectorStyleValue(attributes || {}, layer, property);
    if (!String(rawValue || '').trim()) {
        return [];
    }

    return [{
        id: `fx-import-${property}`,
        value: String(rawValue).trim(),
        createdInBuilder: false,
    }];
}

function effectCollectionValue(items, property) {
    const config = effectCollectionConfig(property);
    if (!config) {
        return '';
    }

    return (items || []).slice(-1)
        .map((item) => String(item?.value || '').trim())
        .filter(Boolean)
        .join(config.separator)
        .trim();
}

function isEffectCollectionAttribute(attribute) {
    return /^data-[a-z0-9-]+-effect-(box-shadow|text-shadow|filter|backdrop-filter|transition|transform)-items$/i.test(String(attribute || ''));
}

function effectCollectionAttributeParts(attribute) {
    const match = String(attribute || '').match(/^data-(.+)-effect-(box-shadow|text-shadow|filter|backdrop-filter|transition|transform)-items$/i);
    if (!match) {
        return null;
    }

    return {
        layer: match[1].replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()),
        property: match[2],
    };
}

function backgroundImagePositionParts(value) {
    const normalized = backgroundImagePositionCssValue(value);
    const parts = normalized.split(/\s+/).filter(Boolean);
    const left = parts[0] || '';
    const top = parts[1] || left;

    return { left, top };
}

function backgroundImagePositionCssValue(value) {
    const normalized = String(value || '').trim();
    if (!normalized.startsWith('tw:')) {
        return normalized;
    }

    return  builderBackgroundPositionValues[normalized.slice(3)] || '';
}

function backgroundImagePositionValue(value) {
    const cssValue = backgroundImagePositionCssValue(value);
    if (!cssValue) {
        return '';
    }
    if (Object.values( builderBackgroundPositionValues).includes(cssValue)) {
        return cssValue;
    }

    const { left, top } = backgroundImagePositionParts(value);

    return left ? `${left} ${top || left}` : '';
}

function backgroundImageSizeParts(value) {
    const normalized = String(value || '').trim();
    const parts = normalized.split(/\s+/).filter(Boolean);
    const width = parts[0] || '';
    const height = parts[1] || width;

    return { width, height };
}

function backgroundImageSizeValue(item) {
    const normalized = item || {};
    if (normalized.size !== 'custom') {
        return normalized.size || 'cover';
    }

    const width = normalized.imageSizeWidth || normalized.sizeWidth || normalized.imageSizeValue || normalized.sizeValue || 'auto';
    const height = normalized.imageSizeHeight || normalized.sizeHeight || normalized.imageSizeValue || normalized.sizeValue || width || 'auto';

    return `${width} ${height}`;
}

function normalizeOpacityValue(value) {
    const normalized = String(value ?? '').trim();
    if (!normalized) {
        return '';
    }

    const numericValue = normalized.endsWith('%')
        ? Number.parseFloat(normalized) / 100
        : Number.parseFloat(normalized);
    if (Number.isNaN(numericValue)) {
        return normalized;
    }

    const clamped = Math.max(0, Math.min(1, numericValue));

    return Number.isInteger(clamped) ? String(clamped) : String(Math.round(clamped * 100) / 100);
}

function opacityValueFromTailwindClass(className) {
    const normalized = tailwindClassParts(className).base;
    const arbitrary = normalized.match(/^opacity-\[(.+)]$/);
    if (arbitrary) {
        return normalizeOpacityValue(arbitrary[1]);
    }

    const match = normalized.match(/^opacity-(\d+)$/);
    if (!match) {
        return '';
    }

    const value = Number.parseInt(match[1], 10);

    return Number.isNaN(value) ? '' : normalizeOpacityValue(value / 100);
}

function opacityValueFromClassList(classList) {
    const classes = String(classList || '').split(/\s+/).filter(Boolean);

    for (let index = classes.length - 1; index >= 0; index -= 1) {
        const value = opacityValueFromTailwindClass(classes[index]);
        if (value !== '') {
            return value;
        }
    }

    return '';
}

function normalizeBackgroundItem(item, fallbackType = 'image') {
    const type = ['image', 'gradient', 'color'].includes(item?.type) ? item.type : fallbackType;
    const defaults = backgroundItemDefaults(type);
    const merged = {
        ...defaults,
        ...(item || {}),
        id: item?.id || defaults.id,
        type,
    };
    let overrides = Array.isArray(merged.overrides) ? [...merged.overrides] : merged.overrides;
    let itemDefaults = merged.defaults && typeof merged.defaults === 'object' ? { ...merged.defaults } : merged.defaults;

    if (type === 'image') {
        const imageSizeKeywords = ['auto', 'cover', 'contain'];
        const rawSize = merged.imageSize || merged.size || defaults.size;
        const hasCustomSize = rawSize === 'custom' || !imageSizeKeywords.includes(rawSize);
        const sizeParts = backgroundImageSizeParts(merged.imageSizeValue || merged.sizeValue || (hasCustomSize && rawSize !== 'custom' ? rawSize : ''));
        const customSize = merged.imageSizeWidth || merged.imageSizeHeight || merged.sizeWidth || merged.sizeHeight;
        let imageSizeValue = merged.imageSizeValue || merged.sizeValue || (!customSize && sizeParts.width && sizeParts.width === sizeParts.height ? sizeParts.width : '');
        let imageSizeWidth = merged.imageSizeWidth || merged.sizeWidth || (customSize ? sizeParts.width : '');
        let imageSizeHeight = merged.imageSizeHeight || merged.sizeHeight || (customSize ? sizeParts.height : '');
        const positionParts = backgroundImagePositionParts(merged.imagePosition || merged.position || defaults.position);
        const customPosition = merged.imagePositionLeft || merged.imagePositionTop;
        let globalPosition = merged.imagePosition || (positionParts.left && positionParts.left === positionParts.top ? positionParts.left : '');
        let imagePositionLeft = merged.imagePositionLeft || (customPosition ? positionParts.left : '');
        let imagePositionTop = merged.imagePositionTop || (customPosition ? positionParts.top : '');
        const hasRedundantCustomPosition = imagePositionLeft
            && imagePositionTop
            && imagePositionLeft === imagePositionTop
            && (!globalPosition || globalPosition === imagePositionLeft);

        if (hasRedundantCustomPosition) {
            globalPosition = globalPosition || imagePositionLeft;
            imagePositionLeft = '';
            imagePositionTop = '';
            if (Array.isArray(overrides)) {
                overrides = overrides.filter((field) => field !== 'imagePositionLeft' && field !== 'imagePositionTop');
            }
            if (itemDefaults && typeof itemDefaults === 'object') {
                delete itemDefaults.imagePositionLeft;
                delete itemDefaults.imagePositionTop;
            }
        }

        return {
            ...merged,
            asset: merged.asset || itemDefaults?.asset || '',
            imagePosition: globalPosition,
            imagePositionLeft,
            imagePositionTop,
            position: imagePositionLeft || imagePositionTop
                ? `${imagePositionLeft || positionParts.left} ${imagePositionTop || positionParts.top}`
                : backgroundImagePositionValue(globalPosition || merged.position || defaults.position),
            imageSizeValue,
            imageSizeWidth,
            imageSizeHeight,
            size: hasCustomSize ? 'custom' : rawSize,
            opacity: normalizeOpacityValue(merged.imageOpacity || merged.opacity || itemDefaults?.imageOpacity || defaults.opacity),
            repeat: merged.imageRepeat || merged.repeat || '',
            attachment: merged.imageAttachment || merged.attachment || '',
            origin: merged.imageOrigin || merged.origin || '',
            ...(itemDefaults ? { defaults: itemDefaults } : {}),
            ...(Array.isArray(overrides) ? { overrides } : {}),
        };
    }

    if (type === 'gradient') {
        return {
            ...merged,
            overlay: merged.gradientOverlay || merged.overlay || defaults.overlay,
            angle: merged.gradientAngle || merged.angle || defaults.angle,
            repeat: merged.gradientRepeat || merged.repeat || defaults.repeat,
            radial: merged.gradientRadial || merged.radial || defaults.radial,
            size: merged.gradientSize || merged.size || defaults.size,
            x: merged.gradientX || merged.x || defaults.x,
            y: merged.gradientY || merged.y || defaults.y,
            stops: merged.gradientStops || merged.stops || defaults.stops,
            ...(itemDefaults ? { defaults: itemDefaults } : {}),
            ...(Array.isArray(overrides) ? { overrides } : {}),
        };
    }

    return {
        ...merged,
        ...(itemDefaults ? { defaults: itemDefaults } : {}),
        ...(Array.isArray(overrides) ? { overrides } : {}),
    };
}

function backgroundItemMemory(item) {
    const raw = item || {};
    const normalized = normalizeBackgroundItem(raw);

    return {
        id: raw.id || normalized.id,
        ...(raw.createdInBuilder || normalized.createdInBuilder ? { createdInBuilder: true } : {}),
        asset: raw.asset || (normalized.type === 'image' ? normalized.asset : ''),
        imagePosition: raw.imagePosition || (normalized.type === 'image' ? normalized.position : ''),
        imagePositionLeft: raw.imagePositionLeft || (normalized.type === 'image' ? normalized.imagePositionLeft : ''),
        imagePositionTop: raw.imagePositionTop || (normalized.type === 'image' ? normalized.imagePositionTop : ''),
        imageSize: raw.imageSize || (normalized.type === 'image' ? normalized.size : ''),
        imageSizeValue: raw.imageSizeValue || (normalized.type === 'image' ? normalized.imageSizeValue : ''),
        imageSizeWidth: raw.imageSizeWidth || (normalized.type === 'image' ? normalized.imageSizeWidth : ''),
        imageSizeHeight: raw.imageSizeHeight || (normalized.type === 'image' ? normalized.imageSizeHeight : ''),
        imageOpacity: raw.imageOpacity || (normalized.type === 'image' ? normalized.opacity : ''),
        imageRepeat: raw.imageRepeat || (normalized.type === 'image' ? normalized.repeat : ''),
        imageAttachment: raw.imageAttachment || (normalized.type === 'image' ? normalized.attachment : ''),
        imageOrigin: raw.imageOrigin || (normalized.type === 'image' ? normalized.origin : ''),
        gradientOverlay: raw.gradientOverlay || (normalized.type === 'gradient' ? normalized.overlay : ''),
        gradientAngle: raw.gradientAngle || (normalized.type === 'gradient' ? normalized.angle : ''),
        gradientRepeat: raw.gradientRepeat || (normalized.type === 'gradient' ? normalized.repeat : ''),
        gradientRadial: raw.gradientRadial || (normalized.type === 'gradient' ? normalized.radial : ''),
        gradientSize: raw.gradientSize || (normalized.type === 'gradient' ? normalized.size : ''),
        gradientX: raw.gradientX || (normalized.type === 'gradient' ? normalized.x : ''),
        gradientY: raw.gradientY || (normalized.type === 'gradient' ? normalized.y : ''),
        gradientStops: raw.gradientStops || (normalized.type === 'gradient' ? normalized.stops : ''),
        color: raw.color || (normalized.type === 'color' ? normalized.color : ''),
    };
}

const backgroundItemTrackedFields = [
    'asset',
    'imagePosition',
    'imagePositionLeft',
    'imagePositionTop',
    'imageSize',
    'imageSizeValue',
    'imageSizeWidth',
    'imageSizeHeight',
    'imageOpacity',
    'imageRepeat',
    'imageAttachment',
    'imageOrigin',
    'gradientOverlay',
    'gradientAngle',
    'gradientRepeat',
    'gradientRadial',
    'gradientSize',
    'gradientX',
    'gradientY',
    'gradientStops',
    'color',
];

function backgroundItemSnapshot(item) {
    const memory = backgroundItemMemory(item || {});

    return {
        asset: memory.asset || '',
        imagePosition: memory.imagePosition || '',
        imagePositionLeft: memory.imagePositionLeft || '',
        imagePositionTop: memory.imagePositionTop || '',
        imageSize: memory.imageSize || '',
        imageSizeValue: memory.imageSizeValue || '',
        imageSizeWidth: memory.imageSizeWidth || '',
        imageSizeHeight: memory.imageSizeHeight || '',
        imageOpacity: memory.imageOpacity || '',
        imageRepeat: memory.imageRepeat || '',
        imageAttachment: memory.imageAttachment || '',
        imageOrigin: memory.imageOrigin || '',
        gradientOverlay: memory.gradientOverlay || '',
        gradientAngle: memory.gradientAngle || '',
        gradientRepeat: memory.gradientRepeat || '',
        gradientRadial: memory.gradientRadial || '',
        gradientSize: memory.gradientSize || '',
        gradientX: memory.gradientX || '',
        gradientY: memory.gradientY || '',
        gradientStops: memory.gradientStops || '',
        color: memory.color || '',
    };
}

function emptyBackgroundItemSnapshot() {
    return Object.fromEntries(backgroundItemTrackedFields.map((field) => [field, '']));
}

function inferBackgroundItemTypeFromSnapshot(snapshot) {
    if (!snapshot || typeof snapshot !== 'object') {
        return '';
    }

    if (snapshot.gradientOverlay || snapshot.gradientAngle || snapshot.gradientStops || snapshot.gradientRepeat || snapshot.gradientRadial) {
        return 'gradient';
    }

    if (snapshot.asset || snapshot.imagePosition || snapshot.imageSize || snapshot.imageOpacity) {
        return 'image';
    }

    return snapshot.color ? 'color' : '';
}

function backgroundItemBaseSnapshot(item) {
    const defaults = item?.defaults && typeof item.defaults === 'object' ? item.defaults : {};
    const type = defaults.type || inferBackgroundItemTypeFromSnapshot(defaults) || item?.type || '';

    return {
        ...emptyBackgroundItemSnapshot(),
        ...defaults,
        type,
    };
}

function backgroundItemFieldState(item, field) {
    const base = backgroundItemBaseSnapshot(item);
    const overrides = new Set(item?.overrides || []);
    const states = [
        (base[field] || overrides.has(field)) ? 'defined' : '',
        overrides.has(field) ? 'override' : '',
    ].filter(Boolean);

    return states.join(' ') || null;
}

function backgroundItemState(item) {
    const normalized = normalizeBackgroundItem(item);
    const base = backgroundItemBaseSnapshot(normalized);
    const hasOverride = (normalized.overrides || []).length > 0
        || (!!base.type && base.type !== normalized.type);
    const hasDefinedValue = !!normalized.type;
    const states = [
        hasDefinedValue ? 'defined' : '',
        hasOverride ? 'override' : '',
    ].filter(Boolean);

    return states.join(' ') || null;
}

function backgroundItemFieldPatch(field, value) {
    const normalizedValue = field === 'imageOpacity' ? normalizeOpacityValue(value) : value;
    const patch = { [field]: normalizedValue };
    const aliases = {
        imagePosition: ['position'],
        imagePositionLeft: ['position'],
        imagePositionTop: ['position'],
        imageSize: ['size'],
        imageSizeValue: ['sizeValue'],
        imageSizeWidth: ['sizeWidth'],
        imageSizeHeight: ['sizeHeight'],
        imageOpacity: ['opacity'],
        imageRepeat: ['repeat'],
        imageAttachment: ['attachment'],
        imageOrigin: ['origin'],
        gradientOverlay: ['overlay'],
        gradientAngle: ['angle'],
        gradientRepeat: ['repeat'],
        gradientRadial: ['radial'],
        gradientSize: ['size'],
        gradientX: ['x'],
        gradientY: ['y'],
        gradientStops: ['stops'],
    };

    (aliases[field] || []).forEach((alias) => {
        patch[alias] = normalizedValue;
    });

    if (field === 'imagePosition') {
        patch.position = value ? backgroundImagePositionValue(value) : '';
    }

    return patch;
}

function backgroundItemAsType(item, type) {
    const sourceType = ['image', 'gradient', 'color'].includes(item?.type) ? item.type : type;
    const defaults = item?.defaults && typeof item.defaults === 'object'
        ? { ...item.defaults, type: item.defaults.type || inferBackgroundItemTypeFromSnapshot(item.defaults) || sourceType }
        : { type: sourceType, ...backgroundItemSnapshot(item || backgroundItemDefaults(sourceType)) };

    return normalizeBackgroundItem({
        ...backgroundItemDefaults(type),
        ...backgroundItemMemory(item),
        id: item?.id || createBackgroundItemId(),
        defaults,
        overrides: item?.overrides || [],
        type,
    }, type);
}

function backgroundItemsWithLegacyAttributes(items, attributes) {
    const legacyOpacity = normalizeOpacityValue(attributes?.['data-background-image-opacity']);
    if (!legacyOpacity) {
        return items;
    }

    let opacityApplied = false;

    return items.map((item) => {
        if (opacityApplied || item.type !== 'image') {
            return item;
        }

        const base = backgroundItemBaseSnapshot(item);
        if (item.imageOpacity || item.opacity || base.imageOpacity) {
            opacityApplied = true;

            return item;
        }

        opacityApplied = true;

        return normalizeBackgroundItem({
            ...item,
            imageOpacity: legacyOpacity,
            opacity: legacyOpacity,
            defaults: {
                ...base,
                imageOpacity: legacyOpacity,
            },
        }, item.type);
    });
}

function hasExplicitBackgroundItemsCollection(attributes, layer = 'container') {
    const attribute =  builderBackgroundItemsAttributeForLayer(layer);

    return attributes
        && Object.hasOwn(attributes, attribute)
        && String(attributes[attribute] ?? '').trim() !== '';
}

function parseBackgroundItems(attributes, layer = 'container') {
    const collectionAttribute =  builderBackgroundItemsAttributeForLayer(layer);
    const hasCollection = hasExplicitBackgroundItemsCollection(attributes, layer);
    if (hasCollection) {
        try {
            const items = JSON.parse(attributes?.[collectionAttribute] || '[]');
            if (Array.isArray(items)) {
                const normalizedItems = items.map((item) => normalizeBackgroundItem(item)).filter(Boolean);
                const singleItems = normalizedItems.length ? [normalizedItems[normalizedItems.length - 1]] : [];

                return layer === 'container'
                    ? backgroundItemsWithLegacyAttributes(singleItems, attributes)
                    : singleItems;
            }
        } catch {
            // Fallback below keeps older templates editable.
        }

        return [];
    }

    const items = [];
    if (layer === 'container' && attributes?.['data-background-asset']) {
        const hasPosition = Object.hasOwn(attributes, 'data-background-image-position') && String(attributes['data-background-image-position'] || '').trim() !== '';
        const position = hasPosition ? attributes['data-background-image-position'] : '';
        const positionParts = backgroundImagePositionParts(position);
        const imagePosition = hasPosition && positionParts.left === positionParts.top ? positionParts.left : '';
        const imagePositionLeft = hasPosition && positionParts.left !== positionParts.top ? positionParts.left : '';
        const imagePositionTop = hasPosition && positionParts.left !== positionParts.top ? positionParts.top : '';
        const imageSize = attributes['data-background-image-size'] || 'cover';
        const imageSizeValue = attributes['data-background-image-size-value'] || '';
        const imageSizeWidth = attributes['data-background-image-size-width'] || '';
        const imageSizeHeight = attributes['data-background-image-size-height'] || '';
        const imageOpacity = normalizeOpacityValue(attributes['data-background-image-opacity']);
        const imageRepeat = attributes['data-background-image-repeat'] || '';
        const imageAttachment = attributes['data-background-image-attachment'] || '';
        const imageOrigin = attributes['data-background-image-origin'] || '';
        items.push(normalizeBackgroundItem({
            id: 'bg-image',
            type: 'image',
            asset: attributes['data-background-asset'] || '',
            imagePosition,
            imagePositionLeft,
            imagePositionTop,
            size: imageSize,
            imageSizeValue,
            imageSizeWidth,
            imageSizeHeight,
            opacity: imageOpacity,
            repeat: imageRepeat,
            attachment: imageAttachment,
            origin: imageOrigin,
            defaults: {
                type: 'image',
                asset: attributes['data-background-asset'] || '',
                imagePosition,
                imagePositionLeft,
                imagePositionTop,
                imageSize: Object.hasOwn(attributes, 'data-background-image-size') ? imageSize : '',
                imageSizeValue: Object.hasOwn(attributes, 'data-background-image-size-value') ? imageSizeValue : '',
                imageSizeWidth: Object.hasOwn(attributes, 'data-background-image-size-width') ? imageSizeWidth : '',
                imageSizeHeight: Object.hasOwn(attributes, 'data-background-image-size-height') ? imageSizeHeight : '',
                imageOpacity: Object.hasOwn(attributes, 'data-background-image-opacity') ? imageOpacity : '',
                imageRepeat: Object.hasOwn(attributes, 'data-background-image-repeat') ? imageRepeat : '',
                imageAttachment: Object.hasOwn(attributes, 'data-background-image-attachment') ? imageAttachment : '',
                imageOrigin: Object.hasOwn(attributes, 'data-background-image-origin') ? imageOrigin : '',
            },
        }));
    }
    if (layer === 'container' && (attributes?.['data-overlay'] || 'none') !== 'none') {
        items.push(normalizeBackgroundItem({
            id: 'bg-gradient',
            type: 'gradient',
            overlay: attributes['data-overlay'] || 'dark',
            angle: attributes['data-background-gradient-angle'] || 'tw:to-t',
            repeat: attributes['data-background-gradient-repeat'] || 'false',
            radial: attributes['data-background-gradient-radial'] || 'false',
            size: attributes['data-background-gradient-size'] || '100px',
            x: attributes['data-background-gradient-x'] || '50%',
            y: attributes['data-background-gradient-y'] || '50%',
            stops: attributes['data-background-gradient-stops'] || '',
            defaults: {
                type: 'gradient',
                gradientOverlay: attributes['data-overlay'] || 'dark',
                gradientAngle: Object.hasOwn(attributes, 'data-background-gradient-angle') ? attributes['data-background-gradient-angle'] : '',
                gradientRepeat: Object.hasOwn(attributes, 'data-background-gradient-repeat') ? attributes['data-background-gradient-repeat'] : '',
                gradientRadial: Object.hasOwn(attributes, 'data-background-gradient-radial') ? attributes['data-background-gradient-radial'] : '',
                gradientSize: Object.hasOwn(attributes, 'data-background-gradient-size') ? attributes['data-background-gradient-size'] : '',
                gradientX: Object.hasOwn(attributes, 'data-background-gradient-x') ? attributes['data-background-gradient-x'] : '',
                gradientY: Object.hasOwn(attributes, 'data-background-gradient-y') ? attributes['data-background-gradient-y'] : '',
                gradientStops: Object.hasOwn(attributes, 'data-background-gradient-stops') ? attributes['data-background-gradient-stops'] : '',
            },
        }));
    }
    const backgroundColor = inspectorStyleValue(attributes || {}, layer, 'background-color');
    if (hasVisibleBackgroundColorValue(backgroundColor)) {
        items.push(normalizeBackgroundItem({
            id: layer === 'container' ? 'bg-color' : `bg-${ builderLayerAttributeName(layer)}-color`,
            type: 'color',
            color: backgroundColor,
            defaults: {
                type: 'color',
                color: backgroundColor,
            },
        }));
    }

    return items;
}

function serializeBackgroundItems(items) {
    return JSON.stringify((items || []).map((item) => normalizeBackgroundItem(item)));
}

function  builderBackgroundLayerAttributes(attributes) {
    const layers = new Set([
        ...Object.keys( builderPresetDefaults),
        ...Object.keys(attributes || {})
            .map(layerFromBackgroundItemsCollectionAttribute)
            .filter(Boolean),
    ]);
    layers.delete('container');

    return Object.fromEntries([...layers].flatMap((layer) => {
            const items = parseBackgroundItems(attributes, layer);
            if (!items.length && !hasExplicitBackgroundItemsCollection(attributes, layer)) {
                return [];
            }

            return [[ builderBackgroundItemsAttributeForLayer(layer), serializeBackgroundItems(items)]];
        }));
}

function backgroundLegacyAttributesFromItems(items) {
    const normalizedItems = (items || []).map((item) => normalizeBackgroundItem(item));
    const image = normalizedItems.find((item) => item.type === 'image');
    const gradient = normalizedItems.find((item) => item.type === 'gradient');

    return {
        'data-overlay': gradient ? (gradient.overlay || 'dark') : 'none',
        'data-background-asset': image?.asset || '',
        'data-background-image-position': image?.imagePosition || '',
        'data-background-image-size': image?.size || '',
        'data-background-image-size-value': image?.imageSizeValue || '',
        'data-background-image-size-width': image?.imageSizeWidth || '',
        'data-background-image-size-height': image?.imageSizeHeight || '',
        'data-background-image-opacity': image?.opacity || '',
        'data-background-image-repeat': image?.repeat || '',
        'data-background-image-attachment': image?.attachment || '',
        'data-background-image-origin': image?.origin || '',
        'data-background-gradient-angle': gradient?.angle || '',
        'data-background-gradient-repeat': gradient?.repeat || '',
        'data-background-gradient-radial': gradient?.radial || '',
        'data-background-gradient-size': gradient?.size || '',
        'data-background-gradient-x': gradient?.x || '',
        'data-background-gradient-y': gradient?.y || '',
        'data-background-gradient-stops': gradient?.stops || '',
    };
}

function backgroundCollectionMutationValues(layer, items) {
    const normalizedItems = (items || []).map((item) => normalizeBackgroundItem(item)).filter(Boolean);
    const singleItems = normalizedItems.length ? [normalizedItems[normalizedItems.length - 1]] : [];
    const normalizedItem = singleItems[0] || null;
    const values = {
        [ builderBackgroundItemsAttributeForLayer(layer)]: serializeBackgroundItems(singleItems),
        [ builderStyleAttribute(layer, 'background-color')]: normalizedItem?.type === 'color' ? (normalizedItem.color || '') : '',
    };

    if (layer === 'container') {
        Object.assign(values, backgroundLegacyAttributesFromItems(singleItems));
    }

    return values;
}

function backgroundItemsUpdateValue(attributes, itemId, field, value, layer = 'container') {
    let didUpdate = false;
    const items = parseBackgroundItems(attributes, layer).map((item) => {
        if (item.id !== itemId) {
            return item;
        }

        didUpdate = true;
        const defaults = backgroundItemBaseSnapshot(item);
        const nextItem = normalizeBackgroundItem({
            ...item,
            ...backgroundItemFieldPatch(field, value),
            defaults,
        }, item.type);
        const nextSnapshot = backgroundItemSnapshot(nextItem);
        const overrides = new Set(item.overrides || []);
        [field].forEach((changedField) => {
            if (!backgroundItemTrackedFields.includes(changedField)) {
                return;
            }
            if (equivalentInspectorValue(nextSnapshot[changedField], defaults[changedField] || '')) {
                overrides.delete(changedField);
            } else {
                overrides.add(changedField);
            }
        });

        return normalizeBackgroundItem({
            ...nextItem,
            defaults,
            overrides: [...overrides],
        }, nextItem.type);
    });

    if (!didUpdate) {
        const type = field === 'color'
            ? 'color'
            : (field.startsWith('gradient') ? 'gradient' : 'image');
        const created = normalizeBackgroundItem({
            id: itemId,
            type,
            ...backgroundItemFieldPatch(field, value),
        }, type);
        const defaults = backgroundItemBaseSnapshot(created);
        const snapshot = backgroundItemSnapshot(created);
        const overrides = [field].filter((changedField) => !equivalentInspectorValue(snapshot[changedField], defaults[changedField] || ''));
        items.push(normalizeBackgroundItem({
            ...created,
            defaults,
            overrides,
        }, type));
    }

    return backgroundCollectionMutationValues(layer, items);
}

function gradientValueFromBackgroundItem(item) {
    return  builderOverlayGradientValue({
        'data-overlay': item.overlay || 'dark',
        'data-background-gradient-angle': item.angle || 'tw:to-t',
        'data-background-gradient-repeat': item.repeat || 'false',
        'data-background-gradient-radial': item.radial || 'false',
        'data-background-gradient-size': item.size || '100px',
        'data-background-gradient-x': item.x || '50%',
        'data-background-gradient-y': item.y || '50%',
        'data-background-gradient-stops': item.stops || '',
    });
}

function tailwindOpacitySuffix(alpha) {
    const normalized = clampNumber(alpha, 0, 1, 1);
    if (normalized <= 0) {
        return null;
    }
    if (normalized >= 1) {
        return '';
    }

    return `/${Math.round(normalized * 100)}`;
}

function tailwindGradientStopClass(prefix, stop) {
    const color = rgbaToHex(stop.color || '#400919');
    const opacity = tailwindOpacitySuffix(stop.alpha);

    if (opacity === null) {
        return `${prefix}-transparent`;
    }

    return `${prefix}-[${color}]${opacity}`;
}

function tailwindGradientClassesFromBackgroundItem(item) {
    const normalized = normalizeBackgroundItem(item, 'gradient');
    if (normalized.type !== 'gradient'
        || normalized.radial === 'true'
        || normalized.repeat === 'true'
        || !String(normalized.angle || '').startsWith('tw:')
    ) {
        return '';
    }

    const stops = normalizeGradientStops(normalized.stops, normalized.overlay || 'dark');
    if (stops.length < 2 || stops.length > 3) {
        return '';
    }

    const direction = normalized.angle.slice(3);
    const classes = [
        `bg-gradient-${direction}`,
        tailwindGradientStopClass('from', stops[0]),
    ];

    if (stops.length === 3) {
        classes.push(tailwindGradientStopClass('via', stops[1]));
    }

    classes.push(tailwindGradientStopClass('to', stops[stops.length - 1]));

    return classes.join(' ');
}

function withoutTailwindBackgroundClasses(className) {
    return String(className || '')
        .split(/\s+/)
        .filter(Boolean)
        .filter((item) => !/^bg-gradient-/.test(tailwindClassParts(item).base))
        .filter((item) => !/^(from|via|to)-/.test(tailwindClassParts(item).base))
        .join(' ');
}

function isTailwindBackgroundUtility(className) {
    const base = tailwindClassParts(className).base;

    return /^bg-/.test(base) || /^(from|via|to)-/.test(base);
}

function tailwindBackgroundUtilities(className) {
    return String(className || '')
        .split(/\s+/)
        .filter(Boolean)
        .filter(isTailwindBackgroundUtility);
}

function isManagedTailwindBackgroundUtility(className) {
    const base = tailwindClassParts(className).base;
    if (base.startsWith('bg-clip-')) {
        return false;
    }

    return /^bg-/.test(base) || /^(from|via|to)-/.test(base);
}

function withoutManagedTailwindBackgroundUtilities(className) {
    return String(className || '')
        .split(/\s+/)
        .filter(Boolean)
        .filter((item) => !isManagedTailwindBackgroundUtility(item))
        .join(' ');
}

function tailwindBackgroundPositionClass(value) {
    const cssValue = backgroundImagePositionValue(value);
    if (!cssValue) {
        return '';
    }

    return Object.entries( builderBackgroundPositionValues).find(([, itemValue]) => itemValue === cssValue)?.[0]
        || `bg-[position:${arbitraryValue(cssValue)}]`;
}

function tailwindBackgroundSizeClass(item) {
    const size = backgroundImageSizeValue(item);
    if (!size) {
        return '';
    }

    if (['auto', 'cover', 'contain'].includes(size)) {
        return `bg-${size}`;
    }

    return `bg-[length:${arbitraryValue(size)}]`;
}

function tailwindBackgroundRepeatClass(value) {
    return {
        repeat: 'bg-repeat',
        'no-repeat': 'bg-no-repeat',
        'repeat-x': 'bg-repeat-x',
        'repeat-y': 'bg-repeat-y',
        round: 'bg-repeat-round',
        space: 'bg-repeat-space',
    }[String(value || '')] || '';
}

function tailwindBackgroundAttachmentClass(value) {
    return {
        fixed: 'bg-fixed',
        local: 'bg-local',
        scroll: 'bg-scroll',
    }[String(value || '')] || '';
}

function tailwindBackgroundOriginClass(value) {
    return {
        'border-box': 'bg-origin-border',
        'padding-box': 'bg-origin-padding',
        'content-box': 'bg-origin-content',
    }[String(value || '')] || '';
}

function tailwindBackgroundImageClass(value) {
    const normalized = String(value || '').trim();

    return normalized ? `bg-[image:${arbitraryValue(normalized)}]` : '';
}

function tailwindBackgroundClassesFromItem(item) {
    const normalized = normalizeBackgroundItem(item);

    if (normalized.type === 'color') {
        const color = String(normalized.color || '').trim();

        if (!color) {
            return [];
        }

        return [tailwindClassFromBuilderAttribute( builderStyleAttribute('dom', 'background-color'), color) || `bg-[${arbitraryValue(color)}]`];
    }

    if (normalized.type === 'gradient') {
        const gradientClasses = tailwindGradientClassesFromBackgroundItem(normalized);
        if (gradientClasses) {
            return gradientClasses.split(/\s+/).filter(Boolean);
        }

        return [tailwindBackgroundImageClass(gradientValueFromBackgroundItem(normalized))].filter(Boolean);
    }

    if (normalized.type === 'image') {
        const asset = normalized.asset ? resolveThemeAssetUrl(normalized.asset) : backgroundImagePlaceholderSrc();
        const image = asset ? `url(${String(asset).replace(/\s+/g, '%20')})` : '';

        return [
            tailwindBackgroundImageClass(image),
            tailwindBackgroundPositionClass(normalized.position),
            tailwindBackgroundSizeClass(normalized),
            tailwindBackgroundRepeatClass(normalized.repeat || 'no-repeat'),
            tailwindBackgroundAttachmentClass(normalized.attachment),
            tailwindBackgroundOriginClass(normalized.origin),
        ].filter(Boolean);
    }

    return [];
}

function withoutDomBackgroundOwnershipClasses(className) {
    return String(className || '')
        .split(/\s+/)
        .filter(Boolean)
        .filter((item) => !['relative', 'isolate'].includes(tailwindClassParts(item).base))
        .join(' ');
}

function backgroundMutationClassName(element, item) {
    const existingClass = normalizeBuilderClassList(element?.getAttribute?.('class') || 'absolute inset-0');
    const gradientClasses = item?.type === 'gradient' ? tailwindGradientClassesFromBackgroundItem(item) : '';
    if (!gradientClasses) {
        return existingClass;
    }

    return classNames(withoutTailwindBackgroundClasses(existingClass), gradientClasses);
}

function sanitizeDomAttributesForSave(attributes) {
    return Object.fromEntries(Object.entries(attributes || {}).map(([attribute, value]) => (
        attribute === 'class' ? [attribute, normalizeBuilderClassList(value)] : [attribute, value]
    )).filter(([attribute, value]) => {
        const name = String(attribute || '');
        if (!name) {
            return false;
        }

        if (name.startsWith('data-gjs-') || name.startsWith('data-highlightable')) {
            return false;
        }

        if (['contenteditable', 'draggable', 'spellcheck'].includes(name)) {
            return false;
        }

        return String(value ?? '') !== '';
    }));
}

function serializeDomBuilderComponent(component) {
    if (component.get('type') === 'textnode') {
        return {
            type: 'textnode',
            content: component.get('content') || '',
            builderMeta: component.get('builderMeta') || undefined,
        };
    }

    return {
        type: 'default',
        tagName: component.get('tagName') || 'div',
        attributes: sanitizeDomAttributesForSave(component.getAttributes()),
        components: component.components().map((child) => serializeDomBuilderComponent(child)),
        builderMeta: component.get('builderMeta') || undefined,
    };
}

function toSaveBuilderDocument(editor) {
    return editor.getWrapper().components().map((component) => serializeDomBuilderComponent(component));
}

function genericDomBlocks() {
    return [
        {
            id: 'dom-section',
            label: 'Section',
            category: 'Structure',
            media: squareIcon,
            content: '<section class="py-16"><div class="mx-auto max-w-6xl px-6"><h2 class="text-4xl font-semibold">Titre de section</h2><p class="mt-4">Ajoutez votre contenu ici.</p></div></section>',
        },
        {
            id: 'dom-container',
            label: 'Conteneur',
            category: 'Structure',
            media: blocksIcon,
            content: '<div class="mx-auto max-w-6xl px-6"><p>Contenu</p></div>',
        },
        {
            id: 'dom-heading',
            label: 'Titre',
            category: 'Contenu',
            media: tagIcon,
            content: '<h2 class="text-4xl font-semibold">Titre</h2>',
        },
        {
            id: 'dom-text',
            label: 'Texte',
            category: 'Contenu',
            media: alignStartHorizontalIcon,
            content: '<p>Votre texte</p>',
        },
        {
            id: 'dom-image',
            label: 'Image',
            category: 'Contenu',
            media: imagesIcon,
            content: '<img src="https://placehold.co/1200x800" alt="" class="w-full">',
        },
        {
            id: 'dom-link',
            label: 'Lien',
            category: 'Contenu',
            media: arrowRightIcon,
            content: '<a href="#" class="inline-flex items-center gap-2">Call to action</a>',
        },
    ];
}

const defaultDocumentInspectorSchema = {
    tagOptions: ['section', 'div', 'article', 'header', 'footer', 'nav', 'main', 'aside', 'figure', 'figcaption', 'picture', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'span', 'strong', 'em', 'a', 'button', 'img', 'ul', 'ol', 'li'],
    attributes: [
        { name: 'class', label: 'Classes', type: 'text' },
        { name: 'id', label: 'ID', type: 'text' },
        { name: 'href', label: 'Href', type: 'text' },
        { name: 'src', label: 'Src', type: 'text' },
        { name: 'alt', label: 'Alt', type: 'text' },
        { name: 'title', label: 'Title', type: 'text' },
        { name: 'x-data', label: 'x-data', type: 'textarea', rows: 3 },
        { name: 'x-show', label: 'x-show', type: 'textarea', rows: 2 },
        { name: '@click', label: '@click', type: 'textarea', rows: 2 },
        { name: ':class', label: ':class', type: 'textarea', rows: 3 },
    ],
    advanced: {
        label: 'Un attribut par ligne (`nom=valeur`)',
        placeholder: 'x-transition.opacity.duration.300ms=true',
        rows: 8,
    },
};

const globalThemeTokenPrefixes = ['--color-', '--font-', '--text-', '--radius-', '--spacing-', '--shadow-', '--border-'];

const globalThemeSemanticSections = [
    {
        id: 'body',
        title: 'Body',
        fields: [
            { label: 'Background', token: '--color-background', type: 'color' },
            { label: 'Color', token: '--color-text', type: 'color' },
            { label: 'Font Size', token: '--text-body', type: 'css-value' },
            { label: 'Line Height', token: '--text-body--line-height', type: 'css-value', units: [''] },
            { label: 'Font Family', token: '--font-body', type: 'text' },
        ],
    },
    {
        id: 'heading',
        title: 'Heading',
        fields: [
            { label: 'Color', token: '--color-primary', type: 'color', displayReference: true },
            { label: 'Font Size', token: '--text-heading', type: 'css-value' },
            { label: 'Line Height', token: '--text-heading--line-height', type: 'css-value', units: [''] },
            { label: 'Font Family', token: '--font-heading', type: 'text' },
        ],
    },
    {
        id: 'subheading',
        title: 'Subheading',
        fields: [
            { label: 'Color', token: '--color-accent', type: 'color', displayReference: true },
            { label: 'Font Size', token: '--text-subheading', type: 'css-value' },
            { label: 'Line Height', token: '--text-subheading--line-height', type: 'css-value', units: [''] },
            { label: 'Font Family', token: '--font-subheading', type: 'text' },
        ],
    },
    {
        id: 'buttons',
        title: 'Buttons',
        fields: [
            { label: 'Background', token: '--color-primary', type: 'color', displayReference: true },
            { label: 'Color', token: '--color-surface', type: 'color', displayReference: true },
            { label: 'Border Radius', token: '--radius-button', type: 'text' },
        ],
    },
    {
        id: 'links',
        title: 'Links',
        fields: [
            { label: 'Color', token: '--color-primary', type: 'color', displayReference: true },
        ],
    },
    {
        id: 'borders',
        title: 'Borders',
        fields: [
            { label: 'Border Radius', token: '--radius-small', type: 'text' },
            { label: 'Border Color', token: '--color-border', type: 'color' },
        ],
    },
];

function globalThemeTokenType(token) {
    if (token.startsWith('--color-')) {
        return 'color';
    }

    if (token.startsWith('--text-')) {
        return 'css-value';
    }

    return 'text';
}

function globalThemeTokenGroup(token) {
    if (token.startsWith('--color-')) {
        return 'Colors';
    }

    if (token.startsWith('--font-')) {
        return 'Fonts';
    }

    if (token.startsWith('--text-')) {
        return 'Typography';
    }

    if (token.startsWith('--radius-')) {
        return 'Radius';
    }

    if (token.startsWith('--spacing-')) {
        return 'Spacing';
    }

    if (token.startsWith('--shadow-')) {
        return 'Shadows';
    }

    if (token.startsWith('--border-')) {
        return 'Borders';
    }

    return 'Tokens';
}

function globalThemeTokenLabel(token) {
    return String(token || '')
        .replace(/^--(?:color|font|text|radius|spacing|shadow|border)-/, '')
        .replace(/--/g, ' ')
        .replace(/-/g, ' ')
        .replace(/\b\w/g, (letter) => letter.toUpperCase());
}

function isGlobalThemeToken(token) {
    return globalThemeTokenPrefixes.some((prefix) => String(token || '').startsWith(prefix));
}

function normalizeGlobalThemeOverrides(value) {
    const entries = value && typeof value === 'object' && !Array.isArray(value) ? Object.entries(value) : [];

    return entries.reduce((overrides, [token, tokenValue]) => {
        const name = String(token || '').trim();
        const resolvedValue = String(tokenValue ?? '').trim();
        if (isGlobalThemeToken(name) && resolvedValue) {
            overrides[name] = resolvedValue;
        }

        return overrides;
    }, {});
}

function discoverGlobalThemeTokens(document) {
    const computedStyle = document?.defaultView?.getComputedStyle(document.documentElement);
    if (!computedStyle) {
        return [];
    }

    const names = new Set();
    for (let index = 0; index < computedStyle.length; index += 1) {
        const property = computedStyle.item(index);
        if (isGlobalThemeToken(property)) {
            names.add(property);
        }
    }

    return [...names]
        .sort((left, right) => left.localeCompare(right))
        .map((name) => ({
            name,
            label: globalThemeTokenLabel(name),
            group: globalThemeTokenGroup(name),
            type: globalThemeTokenType(name),
            value: computedStyle.getPropertyValue(name).trim(),
        }))
        .filter((token) => token.value !== '');
}

function globalThemeCssValue(label, token, value, units =  builderCssValueUnits, state = null) {
    return withGlobalThemeFieldData(
        inspectorCssValue(label, token, value, units, state).replaceAll('data-builder-layer-css', 'data-builder-global-css'),
        token,
    );
}

function withGlobalThemeFieldData(markup, token, options = {}) {
    return String(markup).replace(
        'class="viti-builder-inspector-field',
        `data-builder-global-field="${escapeHtml(token)}"${options.displayReference ? ' data-builder-global-display-reference="1"' : ''} class="viti-builder-inspector-field`,
    );
}

function globalThemeFieldMarkup(label, token, type, value, referenceValue, state = null, units =  builderCssValueUnits, options = {}) {
    if (type === 'color') {
        if (options.displayReference) {
            return `
        <label data-builder-global-field="${escapeHtml(token)}" data-builder-global-display-reference="1" class="viti-builder-inspector-field viti-builder-color-field">
            ${inspectorLabel(label, token, state)}
            <span class="viti-builder-color-control">
                <input type="text" value="${escapeHtml(`var(${token})`)}" readonly>
                <input type="color" value="${escapeHtml(hexColorValue(value))}" data-builder-global-color-for="${escapeHtml(token)}" title="Modifier ${escapeHtml(`var(${token})`)}">
            </span>
        </label>
    `;
        }

        return withGlobalThemeFieldData(
            inspectorColorField(
                label,
                token,
                value,
                referenceValue || 'currentColor',
                state,
            )
                .replaceAll('data-builder-layer-attribute', 'data-builder-global-token')
                .replaceAll('data-builder-color-for', 'data-builder-global-color-for'),
            token,
        );
    }

    if (type === 'css-value') {
        return globalThemeCssValue(label, token, value, units, state);
    }

    return withGlobalThemeFieldData(
        inspectorTextField(label, token, value, '', state)
            .replaceAll('data-builder-layer-attribute', 'data-builder-global-token'),
        token,
    );
}

function globalThemeSectionMarkup(id, title, fields) {
    if (!fields.length) {
        return '';
    }

    return inspectorSection(id, title, fields.join(''));
}

function addTailwindClassTokens(value, classes) {
    String(value || '')
        .split(/\s+/)
        .map((className) => className.trim())
        .filter(Boolean)
        .filter((className) => !className.startsWith('gjs-'))
        .map((className) => normalizeLegacyTailwindClass(className))
        .forEach((className) => classes.add(className));
}

function collectComponentTailwindClasses(component, classes) {
    if (!component) {
        return;
    }

    Object.entries(component.getAttributes?.() || {}).forEach(([attribute, value]) => {
        if (attribute === 'class' || /^data-.+-classes$/.test(attribute)) {
            addTailwindClassTokens(value, classes);
        }

        if (attribute ===  builderDomMutationsAttribute) {
            try {
                JSON.parse(value || '[]').forEach((mutation) => {
                    addTailwindClassTokens(mutation?.attributes?.class || '', classes);
                });
            } catch {
                // Ignore invalid persisted mutations while collecting the current Tailwind safelist.
            }
        }
    });

    component.components?.().forEach((child) => collectComponentTailwindClasses(child, classes));
}

function collectCurrentTailwindClasses(editor) {
    const classes = new Set();
    editor.getWrapper().components().forEach((component) => collectComponentTailwindClasses(component, classes));

    const document = editor.Canvas.getDocument();
    document?.querySelectorAll('[class]').forEach((element) => {
        addTailwindClassTokens(element.getAttribute('class'), classes);
    });

    return [...classes].sort();
}

function componentName(component) {
    return component?.get('name') || component?.getAttributes()?.['data-gjs-name'] || '';
}

function refreshDomInspectorComputedStyleValues(component) {
    clearInspectorComputedStyleValues();

    if (!component || component.get?.('type') === 'textnode') {
        return;
    }

    const properties = [...new Set([
        ...Object.keys( builderTextStyleDefaults.dom || {}),
        ...Object.keys( builderLayoutStyleDefaults.dom || {}),
        ...Object.keys( builderFlexChildDefaults.dom || {}),
        ...Object.keys(inspectorComputedStylePropertyMap),
    ])];
    const element = component?.view?.el || component?.getEl?.();
    const computedStyle = element ? window.getComputedStyle?.(element) : null;
    if (!computedStyle) {
        return;
    }

    properties.forEach((property) => {
        const computedProperty = inspectorComputedStylePropertyMap[property] || property;
        const value = computedStyle.getPropertyValue(computedProperty)?.trim() || '';
        setInspectorComputedStyleValue('dom', property, value);
    });
}

function ensureCanvasComponents(editor, components) {
    if (!components.length) {
        return;
    }

    const wrapper = editor.getWrapper?.();
    if ((wrapper?.components?.().length || 0) > 0) {
        return;
    }

    editor.setComponents(components);
}

function splitCssValue(value) {
    const normalized = String(value ?? '').trim();
    if (normalized.startsWith('tw:')) {
        return { number: normalized.slice(3), unit: 'tw' };
    }

    const match = normalized.match(/^(-?\d*\.?\d+)([a-z%]*)$/i);

    if (!match) {
        return { number: normalized, unit: '' };
    }

    return { number: match[1], unit: match[2] };
}

const builderStyleAttributePattern = /^data-(.+?)-(style|layout|flex)-(.+)$/;

function cssSelectorValue(value) {
    return window.CSS?.escape ? window.CSS.escape(value) : String(value).replace(/"/g, '\\"');
}

function builderStyleAttributeParts(attribute) {
    const match = String(attribute || '').match(builderStyleAttributePattern);
    if (!match) {
        return null;
    }

    return {
        target: match[1].replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()),
        type: match[2],
        property: match[3],
    };
}

function isBuilderStyleAttribute(attribute) {
    return builderStyleAttributePattern.test(String(attribute || ''));
}

function isBuilderPresetAttribute(attribute) {
    return /^data-.+-preset$/.test(String(attribute || ''));
}

function isBuilderClassAttribute(attribute) {
    return /^data-.+-classes$/.test(String(attribute || ''));
}

function targetFromBuilderAttribute(attribute) {
    return builderStyleAttributeParts(attribute)?.target || null;
}

function cssTokenOptionsForBuilderAttribute(attribute) {
    const property = builderStyleAttributeParts(attribute)?.property || '';
    return tailwindDimensionTokenOptionsByProperty[property] || null;
}

function defaultsFromBuilderAttribute(attribute) {
    const parts = builderStyleAttributeParts(attribute);
    const target = parts?.target;
    const property = parts?.property || '';

    if (parts?.type === 'layout') {
        return { defaults:  builderLayoutStyleDefaults[target] || {}, property };
    }

    if (parts?.type === 'flex') {
        return { defaults:  builderFlexChildDefaults[target] || {}, property };
    }

    return { defaults:  builderTextStyleDefaults[target] || {}, property };
}

function isBuilderStyleOverride(attribute, value) {
    if (!isBuilderStyleAttribute(attribute)) {
        return false;
    }

    const { defaults, property } = defaultsFromBuilderAttribute(attribute);
    const fallback = defaults[property] ?? '';

    return !equivalentInspectorValue(value, fallback);
}

function builderBreakpointForDevice(device) {
    return {
        Mobile: '',
        Tablet: 'md',
        Desktop: 'lg',
    }[device] ?? 'lg';
}

function builderViewportWidth(device) {
    return {
        Mobile: 375,
        Tablet: 768,
        Desktop: 1280,
    }[device] ?? 1280;
}

function builderViewportHeight(device) {
    return {
        Mobile: 667,
        Tablet: 1024,
        Desktop: 720,
    }[device] ?? 720;
}

function responsiveTailwindClassName(className, breakpoint) {
    return String(className)
        .split(/\s+/)
        .filter(Boolean)
        .map((item) => {
            if (!breakpoint || item.includes(':')) {
                return item;
            }

            return `${breakpoint}:${item}`;
        })
        .join(' ');
}

let activeInspectorBreakpoint = '';

function applicableBreakpointsForInspector(breakpoint = '') {
    if (breakpoint === 'lg') {
        return ['', 'md', 'lg'];
    }

    if (breakpoint === 'md') {
        return ['', 'md'];
    }

    return [''];
}

function tailwindClassParts(className) {
    const normalized = className.replace(/^!/, '').replace(/!$/, '');
    const match = normalized.match(/^(md|lg):(.+)$/);

    return match ? { breakpoint: match[1], base: match[2] } : { breakpoint: '', base: normalized };
}

function tailwindOrderClassFromValue(value) {
    const normalized = String(value ?? '').trim();
    if (!normalized) {
        return '';
    }

    if (/^-?\d+$/.test(normalized)) {
        const number = Number.parseInt(normalized, 10);

        return number < 0 ? `-order-${Math.abs(number)}` : `order-${number}`;
    }

    return `order-[${arbitraryValue(normalized)}]`;
}

function orderValueFromTailwindClass(className) {
    const base = tailwindClassParts(String(className || '')).base;
    const match = base.match(/^order-(\d+)$/)
        || base.match(/^order-\[(\d+)\]$/);
    if (match) {
        return match[1];
    }

    const negativeMatch = base.match(/^-order-(\d+)$/)
        || base.match(/^-order-\[(\d+)\]$/)
        || base.match(/^order-\[-(\d+)\]$/)
        || base.match(/^order--(\d+)$/);

    return negativeMatch ? `-${negativeMatch[1]}` : null;
}

function builderClassOverrideValue(attributes, layer, property, breakpoint = '', options = {}) {
    const includeImportant = options.includeImportant !== false;
    const classes = String(attributes[ builderClassAttribute(layer)] || '')
        .split(/\s+/)
        .filter(Boolean)
        .filter((className) => {
            if (includeImportant) {
                return true;
            }

            return !className.startsWith('!') && !className.endsWith('!');
        })
        .map((className) => tailwindClassParts(className))
        .filter((className) => className.breakpoint === breakpoint);

    for (let index = classes.length - 1; index >= 0; index -= 1) {
        const className = classes[index].base;

        if (property === 'order') {
            const orderValue = orderValueFromTailwindClass(className);
            if (orderValue !== null) {
                return orderValue;
            }
        }

        if (property === 'align-self') {
            const alignSelfMap = {
                'self-auto': 'auto',
                'self-start': 'flex-start',
                'self-center': 'center',
                'self-end': 'flex-end',
                'self-stretch': 'stretch',
                'self-baseline': 'baseline',
            };

            if (alignSelfMap[className]) {
                return alignSelfMap[className];
            }
        }

        if (property === 'justify-self') {
            const justifySelfMap = {
                'justify-self-auto': 'auto',
                'justify-self-start': 'start',
                'justify-self-center': 'center',
                'justify-self-end': 'end',
                'justify-self-stretch': 'stretch',
            };

            if (justifySelfMap[className]) {
                return justifySelfMap[className];
            }
        }

        if (property === 'flex') {
            const flexMap = {
                'flex-none': '',
                'flex-1': '1 1 0%',
                'flex-initial': '0 1 auto',
                'flex-auto': '1 1 auto',
            };

            if (Object.hasOwn(flexMap, className)) {
                return flexMap[className];
            }
        }
    }

    return null;
}

function tailwindDimensionTokenFromClass(className, property) {
    const config = tailwindDimensionConfig(property);
    const prefix = config?.prefix;
    if (!prefix || !className.startsWith(`${prefix}-`)) {
        return null;
    }

    const token = className.slice(prefix.length + 1);

    return config.tokenSet.has(token) ? `tw:${token}` : null;
}

const inspectorComputedStyleValues = new Map();
const forcedInspectorDomCustomGroupModes = new Map();
let activeInspectorDomNodeId = '';

const inspectorComputedStylePropertyMap = {
    color: 'color',
    'background-color': 'background-color',
    'text-decoration-color': 'text-decoration-color',
    'border-color': 'border-top-color',
    'border-top-color': 'border-top-color',
    'border-right-color': 'border-right-color',
    'border-bottom-color': 'border-bottom-color',
    'border-left-color': 'border-left-color',
};

function clearInspectorComputedStyleValues() {
    inspectorComputedStyleValues.clear();
}

function setInspectorComputedStyleValue(layer, property, value) {
    const normalized = String(value ?? '').trim();
    const key = `${layer}:${property}`;

    if (!normalized) {
        inspectorComputedStyleValues.delete(key);
        return;
    }

    inspectorComputedStyleValues.set(key, normalized);
}

function inspectorComputedStyleValue(layer, property) {
    return inspectorComputedStyleValues.get(`${layer}:${property}`) || null;
}

function domInspectorCustomGroupMode(group, fallback = 'all') {
    if (!activeInspectorDomNodeId) {
        return fallback;
    }

    return forcedInspectorDomCustomGroupModes.get(`${activeInspectorDomNodeId}::${group}`) || fallback;
}

function arbitraryTextValueLooksLikeFontSize(value) {
    const normalized = String(value ?? '').trim();
    if (!normalized) {
        return false;
    }

    return /^-?\d+(?:\.\d+)?(?:px|rem|em|%|vw|vh|svw|svh|lvw|lvh|dvw|dvh|ch|ex|lh|rlh)$/i.test(normalized)
        || /^(?:calc|min|max|clamp)\(/i.test(normalized)
        || /^var\(--(?:text|font|spacing|size|container)-/i.test(normalized);
}

function arbitraryColorValueMatch(prefix, className) {
    const match = String(className).match(new RegExp(`^${prefix.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}-\\[(.+)]$`));
    if (!match) {
        return null;
    }

    const value = match[1].replace(/_/g, ' ').trim();
    if (!value) {
        return null;
    }

    return arbitraryTextValueLooksLikeFontSize(value) ? null : value;
}

function isTailwindTextColorClass(className) {
    const normalized = String(className || '');
    if (!normalized.startsWith('text-')) {
        return false;
    }

    if (/^text-(left|center|right|justify|start|end|ellipsis|clip|wrap|nowrap|balance|pretty)$/.test(normalized)) {
        return false;
    }

    if (/^text-(xs|sm|base|lg|xl|[2-9]xl)$/.test(normalized)) {
        return false;
    }

    const arbitrary = arbitraryColorValueMatch('text', normalized);
    if (arbitrary !== null) {
        return true;
    }

    return !/^text-\[/.test(normalized);
}

function isTailwindBackgroundColorClass(className) {
    const normalized = String(className || '');
    if (['bg-fixed', 'bg-local', 'bg-scroll', 'bg-clip-border', 'bg-clip-padding', 'bg-clip-content', 'bg-clip-text'].includes(normalized)) {
        return false;
    }
    if (/^bg-(?:top|right|bottom|left|center)$/.test(normalized)) {
        return false;
    }
    if (/^bg-(?:repeat|no-repeat|repeat-x|repeat-y|repeat-round|repeat-space)$/.test(normalized)) {
        return false;
    }
    if (/^bg-(?:auto|cover|contain)$/.test(normalized)) {
        return false;
    }
    if (/^bg-\[/.test(normalized)) {
        const arbitrary = arbitraryColorValueMatch('bg', normalized);

        return arbitrary !== null && cssColorComparableValue(arbitrary) !== null;
    }

    return normalized.startsWith('bg-');
}

function isTailwindDecorationColorClass(className) {
    const normalized = String(className || '');
    if (['decoration-solid', 'decoration-double', 'decoration-dotted', 'decoration-dashed', 'decoration-wavy', 'decoration-auto', 'decoration-from-font'].includes(normalized)) {
        return false;
    }
    if (/^decoration-(?:0|1|2|4|8)$/.test(normalized)) {
        return false;
    }
    if (/^decoration-\[/.test(normalized)) {
        const arbitrary = arbitraryColorValueMatch('decoration', normalized);

        return arbitrary !== null && cssColorComparableValue(arbitrary) !== null;
    }

    return normalized.startsWith('decoration-');
}

function isTailwindBorderWidthToken(value) {
    return value === '' || value === 'px' || /^(?:0|2|4|8)$/.test(value);
}

function arbitraryBorderValueLooksLikeWidth(value) {
    return /^-?\d+(?:\.\d+)?(?:px|rem|em|%|vh|vw|svh|svw|lvh|lvw|dvh|dvw)?$/i.test(String(value ?? '').trim());
}

function isTailwindBorderColorClass(className, prefix = 'border') {
    const normalized = String(className || '');
    if (normalized === `${prefix}-current` || normalized === `${prefix}-white` || normalized === `${prefix}-black`) {
        return true;
    }

    if (!normalized.startsWith(`${prefix}-`)) {
        return false;
    }

    const token = normalized.slice(prefix.length + 1);
    if (!token || isTailwindBorderWidthToken(token) || ['solid', 'dashed', 'dotted', 'double', 'none'].includes(token)) {
        return false;
    }

    if (token.startsWith('[') && token.endsWith(']')) {
        return !arbitraryBorderValueLooksLikeWidth(token.slice(1, -1));
    }

    return true;
}

function hasTailwindColorClass(classes, property) {
    const items = classes.map((item) => typeof item === 'string' ? item : item?.base).filter(Boolean);

    if (property === 'color') {
        return items.some((className) => isTailwindTextColorClass(className));
    }

    if (property === 'background-color') {
        return items.some((className) => isTailwindBackgroundColorClass(className));
    }

    if (property === 'border-color') {
        return items.some((className) => isTailwindBorderColorClass(className, 'border'));
    }

    if (property === 'border-top-color') {
        return items.some((className) => isTailwindBorderColorClass(className, 'border-t'));
    }

    if (property === 'border-right-color') {
        return items.some((className) => isTailwindBorderColorClass(className, 'border-r'));
    }

    if (property === 'border-bottom-color') {
        return items.some((className) => isTailwindBorderColorClass(className, 'border-b'));
    }

    if (property === 'border-left-color') {
        return items.some((className) => isTailwindBorderColorClass(className, 'border-l'));
    }

    if (property === 'text-decoration-color') {
        return items.some((className) => isTailwindDecorationColorClass(className));
    }

    return false;
}

function computedTailwindColorClassValue(layer, property, classes) {
    if (!hasTailwindColorClass(classes, property)) {
        return null;
    }

    return inspectorComputedStyleValue(layer, property);
}

const domComputedStyleFallbackProperties = new Set([
    'color',
    'background-color',
    'background-clip',
    'font-family',
    'font-size',
    'font-smoothing',
    'font-style',
    'font-weight',
    'font-stretch',
    'font-variant-numeric',
    'font-feature-settings',
    'line-height',
    'letter-spacing',
    'list-style-image',
    'list-style-position',
    'list-style-type',
    'text-align',
    'text-decoration-line',
    'text-decoration-color',
    'text-decoration-style',
    'text-decoration-thickness',
    'text-underline-offset',
    'text-transform',
    'text-overflow',
    'text-wrap',
    'text-indent',
    'tab-size',
    'vertical-align',
    'white-space',
    'word-break',
    'overflow-wrap',
    'hyphens',
]);

function domComputedStyleFallback(property) {
    if (!domComputedStyleFallbackProperties.has(String(property || ''))) {
        return null;
    }

    const value = inspectorComputedStyleValue('dom', property);

    return String(value || '').trim() || null;
}

function styleValueFromTailwindClass(className, property) {
    const arbitraryPropertyValue = arbitraryPropertyValueFromTailwindClass(className, property);
    if (arbitraryPropertyValue !== null) {
        return arbitraryPropertyValue;
    }

    const spacingValue = (value) => {
        if (value === 'px' || value === '-px') {
            return `tw:${value}`;
        }

        if (/^-?\d+(?:\.5)?$/.test(value)) {
            return `tw:${value}`;
        }

        return null;
    };
    const arbitraryValueMatch = (prefix) => {
        const match = className.match(new RegExp(`^${prefix.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}-\\[(.+)]$`));

        return match ? arbitraryCssValue(match[1]) : null;
    };
    const borderArbitraryValueMatch = (prefix) => {
        const value = arbitraryValueMatch(prefix);
        if (value === null) {
            return null;
        }

        return /^-?\d+(?:\.\d+)?(?:px|rem|em|%|vh|vw|svh|svw|lvh|lvw|dvh|dvw)?$/i.test(value)
            ? value
            : null;
    };
    const tailwindTokenMatch = (prefix) => {
        const match = className.match(new RegExp(`^(-)?${prefix.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')}-(px|\\d+(?:\\.5)?)$`));

        return match ? spacingValue(`${match[1] || ''}${match[2]}`) : null;
    };
    const valueMaps = {
        display: {
            block: 'block',
            inline: 'inline',
            'inline-block': 'inline-block',
            flex: 'flex',
            'inline-flex': 'inline-flex',
            grid: 'grid',
            hidden: 'none',
        },
        'flex-direction': {
            'flex-row': 'row',
            'flex-row-reverse': 'row-reverse',
            'flex-col': 'column',
            'flex-col-reverse': 'column-reverse',
        },
        'justify-content': {
            'justify-start': 'flex-start',
            'justify-center': 'center',
            'justify-end': 'flex-end',
            'justify-between': 'space-between',
            'justify-around': 'space-around',
            'justify-evenly': 'space-evenly',
        },
        'align-items': {
            'items-start': 'flex-start',
            'items-center': 'center',
            'items-end': 'flex-end',
            'items-stretch': 'stretch',
            'items-baseline': 'baseline',
        },
        'justify-items': {
            'justify-items-start': 'start',
            'justify-items-center': 'center',
            'justify-items-end': 'end',
            'justify-items-stretch': 'stretch',
        },
        'align-content': {
            'content-start': 'start',
            'content-center': 'center',
            'content-end': 'end',
            'content-between': 'space-between',
            'content-around': 'space-around',
            'content-evenly': 'space-evenly',
            'content-baseline': 'baseline',
            'content-stretch': 'stretch',
        },
        'place-items': {
            'place-items-start': 'start',
            'place-items-center': 'center',
            'place-items-end': 'end',
            'place-items-stretch': 'stretch',
            'place-items-baseline': 'baseline',
        },
        'place-content': {
            'place-content-start': 'start',
            'place-content-center': 'center',
            'place-content-end': 'end',
            'place-content-between': 'space-between',
            'place-content-around': 'space-around',
            'place-content-evenly': 'space-evenly',
            'place-content-baseline': 'baseline',
            'place-content-stretch': 'stretch',
        },
        'place-self': {
            'place-self-auto': 'auto',
            'place-self-start': 'start',
            'place-self-center': 'center',
            'place-self-end': 'end',
            'place-self-stretch': 'stretch',
        },
        'flex-wrap': {
            'flex-nowrap': 'nowrap',
            'flex-wrap': 'wrap',
            'flex-wrap-reverse': 'wrap-reverse',
        },
        position: {
            static: 'static',
            relative: 'relative',
            absolute: 'absolute',
            fixed: 'fixed',
            sticky: 'sticky',
        },
        overflow: {
            'overflow-visible': 'visible',
            'overflow-hidden': 'hidden',
            'overflow-scroll': 'scroll',
            'overflow-auto': 'auto',
        },
        'background-clip': {
            'bg-clip-border': 'border-box',
            'bg-clip-padding': 'padding-box',
            'bg-clip-content': 'content-box',
            'bg-clip-text': 'text',
        },
        'font-smoothing': {
            antialiased: 'antialiased',
            'subpixel-antialiased': 'subpixel-antialiased',
        },
        'font-style': {
            italic: 'italic',
            'not-italic': 'normal',
        },
        'font-family': {
            'font-body': 'var(--font-body)',
            'font-heading': 'var(--font-heading)',
            'font-subheading': 'var(--font-subheading)',
            'font-accent': 'var(--font-accent)',
            'font-serif': 'serif',
            'font-sans': 'ui-sans-serif, system-ui, sans-serif',
            'font-mono': 'ui-monospace, SFMono-Regular, monospace',
        },
        'font-weight': {
            'font-thin': '100',
            'font-extralight': '200',
            'font-light': '300',
            'font-normal': '400',
            'font-medium': '500',
            'font-semibold': '600',
            'font-bold': '700',
            'font-extrabold': '800',
            'font-black': '900',
        },
        'font-stretch': {
            'font-stretch-ultra-condensed': 'ultra-condensed',
            'font-stretch-extra-condensed': 'extra-condensed',
            'font-stretch-condensed': 'condensed',
            'font-stretch-semi-condensed': 'semi-condensed',
            'font-stretch-normal': 'normal',
            'font-stretch-semi-expanded': 'semi-expanded',
            'font-stretch-expanded': 'expanded',
            'font-stretch-extra-expanded': 'extra-expanded',
            'font-stretch-ultra-expanded': 'ultra-expanded',
        },
        'font-variant-numeric': {
            'normal-nums': 'normal',
            ordinal: 'ordinal',
            'slashed-zero': 'slashed-zero',
            'lining-nums': 'lining-nums',
            'oldstyle-nums': 'oldstyle-nums',
            'proportional-nums': 'proportional-nums',
            'tabular-nums': 'tabular-nums',
            'diagonal-fractions': 'diagonal-fractions',
            'stacked-fractions': 'stacked-fractions',
        },
        'text-transform': {
            uppercase: 'uppercase',
            lowercase: 'lowercase',
            capitalize: 'capitalize',
            'normal-case': 'none',
        },
        'text-align': {
            'text-left': 'left',
            'text-center': 'center',
            'text-right': 'right',
            'text-justify': 'justify',
            'text-start': 'start',
            'text-end': 'end',
        },
        'list-style-position': {
            'list-inside': 'inside',
            'list-outside': 'outside',
        },
        'list-style-type': {
            'list-disc': 'disc',
            'list-decimal': 'decimal',
            'list-none': 'none',
        },
        'text-decoration-line': {
            underline: 'underline',
            overline: 'overline',
            'line-through': 'line-through',
            'no-underline': 'none',
        },
        'text-decoration-style': {
            'decoration-solid': 'solid',
            'decoration-double': 'double',
            'decoration-dotted': 'dotted',
            'decoration-dashed': 'dashed',
            'decoration-wavy': 'wavy',
        },
        'text-overflow': {
            truncate: 'truncate',
            'text-ellipsis': 'ellipsis',
            'text-clip': 'clip',
        },
        'text-wrap': {
            'text-wrap': 'wrap',
            'text-nowrap': 'nowrap',
            'text-balance': 'balance',
            'text-pretty': 'pretty',
        },
        'vertical-align': {
            'align-baseline': 'baseline',
            'align-top': 'top',
            'align-middle': 'middle',
            'align-bottom': 'bottom',
            'align-text-top': 'text-top',
            'align-text-bottom': 'text-bottom',
            'align-sub': 'sub',
            'align-super': 'super',
        },
        'white-space': {
            'whitespace-normal': 'normal',
            'whitespace-nowrap': 'nowrap',
            'whitespace-pre': 'pre',
            'whitespace-pre-line': 'pre-line',
            'whitespace-pre-wrap': 'pre-wrap',
            'whitespace-break-spaces': 'break-spaces',
        },
        'word-break': {
            'break-normal': 'normal',
            'break-all': 'break-all',
            'break-keep': 'keep-all',
        },
        'overflow-wrap': {
            'wrap-break-word': 'break-word',
            'wrap-anywhere': 'anywhere',
            'wrap-normal': 'normal',
        },
        hyphens: {
            'hyphens-none': 'none',
            'hyphens-manual': 'manual',
            'hyphens-auto': 'auto',
        },
    };

    if (valueMaps[property]?.[className]) {
        return valueMaps[property][className];
    }

    if (property === 'padding') {
        return tailwindTokenMatch('p') || arbitraryValueMatch('p');
    }

    if (property === 'padding-inline') {
        return tailwindTokenMatch('px') || arbitraryValueMatch('px');
    }

    if (property === 'padding-top') {
        return tailwindTokenMatch('pt') || tailwindTokenMatch('py') || arbitraryValueMatch('pt') || arbitraryValueMatch('py');
    }

    if (property === 'padding-right') {
        return tailwindTokenMatch('pr') || tailwindTokenMatch('px') || arbitraryValueMatch('pr') || arbitraryValueMatch('px');
    }

    if (property === 'padding-bottom') {
        return tailwindTokenMatch('pb') || tailwindTokenMatch('py') || arbitraryValueMatch('pb') || arbitraryValueMatch('py');
    }

    if (property === 'padding-left') {
        return tailwindTokenMatch('pl') || tailwindTokenMatch('px') || arbitraryValueMatch('pl') || arbitraryValueMatch('px');
    }

    if (property === 'margin') {
        return tailwindTokenMatch('m') || arbitraryValueMatch('m');
    }

    if (property === 'margin-top') {
        return tailwindTokenMatch('mt') || tailwindTokenMatch('my') || arbitraryValueMatch('mt') || arbitraryValueMatch('my');
    }

    if (property === 'margin-right') {
        if (className === 'mr-auto' || className === 'mx-auto') {
            return 'auto';
        }

        return tailwindTokenMatch('mr') || tailwindTokenMatch('mx') || arbitraryValueMatch('mr') || arbitraryValueMatch('mx');
    }

    if (property === 'margin-bottom') {
        return tailwindTokenMatch('mb') || tailwindTokenMatch('my') || arbitraryValueMatch('mb') || arbitraryValueMatch('my');
    }

    if (property === 'margin-left') {
        if (className === 'ml-auto' || className === 'mx-auto') {
            return 'auto';
        }

        return tailwindTokenMatch('ml') || tailwindTokenMatch('mx') || arbitraryValueMatch('ml') || arbitraryValueMatch('mx');
    }

    if (property === 'gap') {
        return tailwindTokenMatch('gap') || arbitraryValueMatch('gap');
    }

    if (property === 'order') {
        return orderValueFromTailwindClass(className);
    }

    if (property === 'max-width') {
        if (className === 'max-w-none') {
            return 'none';
        }

        if (className === 'max-w-full') {
            return '100%';
        }

        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('max-w');
    }

    if (property === 'font-size') {
        const fontSizeMap = {
            'text-xs': 'tw:xs',
            'text-sm': 'tw:sm',
            'text-base': 'tw:base',
            'text-lg': 'tw:lg',
            'text-xl': 'tw:xl',
            'text-2xl': 'tw:2xl',
            'text-3xl': 'tw:3xl',
            'text-4xl': 'tw:4xl',
            'text-5xl': 'tw:5xl',
            'text-6xl': 'tw:6xl',
        };
        const arbitraryFontSize = arbitraryValueMatch('text');

        return fontSizeMap[className] || (arbitraryTextValueLooksLikeFontSize(arbitraryFontSize) ? arbitraryFontSize : null);
    }

    if (property === 'line-height') {
        const lineHeightMap = {
            'leading-none': 'tw:none',
            'leading-tight': 'tw:tight',
            'leading-snug': 'tw:snug',
            'leading-normal': 'tw:normal',
            'leading-relaxed': 'tw:relaxed',
            'leading-loose': 'tw:loose',
        };
        const match = className.match(/^leading-(px|-?\d+(?:\.5)?)$/);

        return lineHeightMap[className] || (match ? `tw:${match[1]}` : arbitraryValueMatch('leading'));
    }

    if (property === 'letter-spacing') {
        const letterSpacingMap = {
            'tracking-tighter': 'tw:tighter',
            'tracking-tight': 'tw:tight',
            'tracking-normal': 'tw:normal',
            'tracking-wide': 'tw:wide',
            'tracking-wider': 'tw:wider',
            'tracking-widest': 'tw:widest',
        };

        return letterSpacingMap[className] || arbitraryValueMatch('tracking');
    }

    if (property === 'color') {
        const colorMap = {
            'text-white': '#ffffff',
            'text-black': '#000000',
            'text-current': 'currentColor',
            'text-white/80': 'rgba(255,255,255,.8)',
            'text-white/90': 'rgba(255,255,255,.9)',
        };

        return colorMap[className] || tailwindThemeColorValueFromClass('text', className) || arbitraryColorValueMatch('text', className);
    }

    if (property === 'background-color') {
        const backgroundColorMap = {
            'bg-transparent': 'transparent',
            'bg-inherit': 'inherit',
            'bg-current': 'currentColor',
            'bg-white': '#ffffff',
            'bg-black': '#000000',
        };

        return backgroundColorMap[className] || tailwindThemeColorValueFromClass('bg', className) || arbitraryColorValueMatch('bg', className);
    }

    if (property === 'border-width') {
        const borderWidthMap = {
            border: 'tw:px',
            'border-0': 'tw:0',
            'border-2': 'tw:2',
            'border-4': 'tw:4',
            'border-8': 'tw:8',
        };

        return borderWidthMap[className] || borderArbitraryValueMatch('border');
    }

    if (property === 'border-top-width') {
        const borderTopWidthMap = {
            'border-t': 'tw:px',
            'border-t-0': 'tw:0',
            'border-t-2': 'tw:2',
            'border-t-4': 'tw:4',
            'border-t-8': 'tw:8',
        };

        return borderTopWidthMap[className] || borderArbitraryValueMatch('border-t');
    }

    if (property === 'border-right-width') {
        const borderRightWidthMap = {
            'border-r': 'tw:px',
            'border-r-0': 'tw:0',
            'border-r-2': 'tw:2',
            'border-r-4': 'tw:4',
            'border-r-8': 'tw:8',
        };

        return borderRightWidthMap[className] || borderArbitraryValueMatch('border-r');
    }

    if (property === 'border-bottom-width') {
        const borderBottomWidthMap = {
            'border-b': 'tw:px',
            'border-b-0': 'tw:0',
            'border-b-2': 'tw:2',
            'border-b-4': 'tw:4',
            'border-b-8': 'tw:8',
        };

        return borderBottomWidthMap[className] || borderArbitraryValueMatch('border-b');
    }

    if (property === 'border-left-width') {
        const borderLeftWidthMap = {
            'border-l': 'tw:px',
            'border-l-0': 'tw:0',
            'border-l-2': 'tw:2',
            'border-l-4': 'tw:4',
            'border-l-8': 'tw:8',
        };

        return borderLeftWidthMap[className] || borderArbitraryValueMatch('border-l');
    }

    if (property === 'border-color') {
        const borderColorMap = {
            'border-current': 'currentColor',
            'border-white': '#ffffff',
            'border-black': '#000000',
        };

        return borderColorMap[className] || tailwindThemeColorValueFromClass('border', className) || (isTailwindBorderColorClass(className, 'border') ? arbitraryValueMatch('border') : null);
    }

    if (property === 'border-top-color') {
        const borderTopColorMap = {
            'border-t-current': 'currentColor',
            'border-t-white': '#ffffff',
            'border-t-black': '#000000',
        };

        return borderTopColorMap[className] || tailwindThemeColorValueFromClass('border-t', className) || (isTailwindBorderColorClass(className, 'border-t') ? arbitraryValueMatch('border-t') : null);
    }

    if (property === 'border-right-color') {
        const borderRightColorMap = {
            'border-r-current': 'currentColor',
            'border-r-white': '#ffffff',
            'border-r-black': '#000000',
        };

        return borderRightColorMap[className] || tailwindThemeColorValueFromClass('border-r', className) || (isTailwindBorderColorClass(className, 'border-r') ? arbitraryValueMatch('border-r') : null);
    }

    if (property === 'border-bottom-color') {
        const borderBottomColorMap = {
            'border-b-current': 'currentColor',
            'border-b-white': '#ffffff',
            'border-b-black': '#000000',
        };

        return borderBottomColorMap[className] || tailwindThemeColorValueFromClass('border-b', className) || (isTailwindBorderColorClass(className, 'border-b') ? arbitraryValueMatch('border-b') : null);
    }

    if (property === 'border-left-color') {
        const borderLeftColorMap = {
            'border-l-current': 'currentColor',
            'border-l-white': '#ffffff',
            'border-l-black': '#000000',
        };

        return borderLeftColorMap[className] || tailwindThemeColorValueFromClass('border-l', className) || (isTailwindBorderColorClass(className, 'border-l') ? arbitraryValueMatch('border-l') : null);
    }

    if (property === 'border-radius') {
        const borderRadiusMap = {
            rounded: 'tw:default',
            'rounded-none': 'tw:none',
            'rounded-xs': 'tw:xs',
            'rounded-sm': 'tw:sm',
            'rounded-md': 'tw:md',
            'rounded-lg': 'tw:lg',
            'rounded-xl': 'tw:xl',
            'rounded-2xl': 'tw:2xl',
            'rounded-3xl': 'tw:3xl',
            'rounded-4xl': 'tw:4xl',
            'rounded-full': 'tw:full',
        };
        const match = className.match(/^rounded-(\d+)$/);

        return borderRadiusMap[className] || (match ? `tw:${match[1]}` : arbitraryValueMatch('rounded'));
    }

    if (property === 'border-top-left-radius') {
        const borderTopLeftRadiusMap = {
            'rounded-tl': 'tw:default',
            'rounded-tl-none': 'tw:none',
            'rounded-tl-xs': 'tw:xs',
            'rounded-tl-sm': 'tw:sm',
            'rounded-tl-md': 'tw:md',
            'rounded-tl-lg': 'tw:lg',
            'rounded-tl-xl': 'tw:xl',
            'rounded-tl-2xl': 'tw:2xl',
            'rounded-tl-3xl': 'tw:3xl',
            'rounded-tl-4xl': 'tw:4xl',
            'rounded-tl-full': 'tw:full',
        };
        const match = className.match(/^rounded-tl-(\d+)$/);

        return borderTopLeftRadiusMap[className] || (match ? `tw:${match[1]}` : arbitraryValueMatch('rounded-tl'));
    }

    if (property === 'border-top-right-radius') {
        const borderTopRightRadiusMap = {
            'rounded-tr': 'tw:default',
            'rounded-tr-none': 'tw:none',
            'rounded-tr-xs': 'tw:xs',
            'rounded-tr-sm': 'tw:sm',
            'rounded-tr-md': 'tw:md',
            'rounded-tr-lg': 'tw:lg',
            'rounded-tr-xl': 'tw:xl',
            'rounded-tr-2xl': 'tw:2xl',
            'rounded-tr-3xl': 'tw:3xl',
            'rounded-tr-4xl': 'tw:4xl',
            'rounded-tr-full': 'tw:full',
        };
        const match = className.match(/^rounded-tr-(\d+)$/);

        return borderTopRightRadiusMap[className] || (match ? `tw:${match[1]}` : arbitraryValueMatch('rounded-tr'));
    }

    if (property === 'border-bottom-right-radius') {
        const borderBottomRightRadiusMap = {
            'rounded-br': 'tw:default',
            'rounded-br-none': 'tw:none',
            'rounded-br-xs': 'tw:xs',
            'rounded-br-sm': 'tw:sm',
            'rounded-br-md': 'tw:md',
            'rounded-br-lg': 'tw:lg',
            'rounded-br-xl': 'tw:xl',
            'rounded-br-2xl': 'tw:2xl',
            'rounded-br-3xl': 'tw:3xl',
            'rounded-br-4xl': 'tw:4xl',
            'rounded-br-full': 'tw:full',
        };
        const match = className.match(/^rounded-br-(\d+)$/);

        return borderBottomRightRadiusMap[className] || (match ? `tw:${match[1]}` : arbitraryValueMatch('rounded-br'));
    }

    if (property === 'border-bottom-left-radius') {
        const borderBottomLeftRadiusMap = {
            'rounded-bl': 'tw:default',
            'rounded-bl-none': 'tw:none',
            'rounded-bl-xs': 'tw:xs',
            'rounded-bl-sm': 'tw:sm',
            'rounded-bl-md': 'tw:md',
            'rounded-bl-lg': 'tw:lg',
            'rounded-bl-xl': 'tw:xl',
            'rounded-bl-2xl': 'tw:2xl',
            'rounded-bl-3xl': 'tw:3xl',
            'rounded-bl-4xl': 'tw:4xl',
            'rounded-bl-full': 'tw:full',
        };
        const match = className.match(/^rounded-bl-(\d+)$/);

        return borderBottomLeftRadiusMap[className] || (match ? `tw:${match[1]}` : arbitraryValueMatch('rounded-bl'));
    }

    if (property === 'font-feature-settings') {
        return arbitraryValueMatch('font-features');
    }

    if (property === 'line-clamp') {
        if (className === 'line-clamp-none') {
            return 'none';
        }

        const match = className.match(/^line-clamp-(\d+)$/);

        return match ? match[1] : arbitraryValueMatch('line-clamp');
    }

    if (property === 'list-style-image') {
        if (className === 'list-image-none') {
            return 'none';
        }

        return arbitraryValueMatch('list-image');
    }

    if (property === 'list-style-type') {
        return arbitraryValueMatch('list');
    }

    if (property === 'text-decoration-color') {
        const decorationColorMap = {
            'decoration-inherit': 'inherit',
            'decoration-current': 'currentColor',
            'decoration-transparent': 'transparent',
            'decoration-black': '#000000',
            'decoration-white': '#ffffff',
        };

        return decorationColorMap[className] || tailwindThemeColorValueFromClass('decoration', className) || arbitraryColorValueMatch('decoration', className);
    }

    if (property === 'text-decoration-thickness') {
        if (className === 'decoration-auto') {
            return 'auto';
        }

        if (className === 'decoration-from-font') {
            return 'from-font';
        }

        const match = className.match(/^decoration-(\d+)$/);

        return match ? `${match[1]}px` : null;
    }

    if (property === 'text-underline-offset') {
        if (className === 'underline-offset-auto') {
            return 'auto';
        }

        const match = className.match(/^-?underline-offset-(\d+)$/);

        if (match) {
            return className.startsWith('-') ? `-${match[1]}px` : `${match[1]}px`;
        }

        return arbitraryValueMatch('underline-offset');
    }

    if (property === 'text-indent') {
        return tailwindTokenMatch('indent') || arbitraryValueMatch('indent');
    }

    if (property === 'tab-size') {
        const match = className.match(/^tab-(\d+)$/);

        return match ? match[1] : arbitraryValueMatch('tab');
    }

    if (property === 'content') {
        if (className === 'content-none') {
            return 'none';
        }

        return arbitraryValueMatch('content');
    }

    if (property === 'width') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('w');
    }

    if (property === 'height') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('h');
    }

    if (property === 'inline-size') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('inline');
    }

    if (property === 'block-size') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('block');
    }

    if (property === 'min-height') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('min-h');
    }

    if (property === 'min-width') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('min-w');
    }

    if (property === 'min-inline-size') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('min-inline');
    }

    if (property === 'min-block-size') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('min-block');
    }

    if (property === 'max-height') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('max-h');
    }

    if (property === 'max-inline-size') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('max-inline');
    }

    if (property === 'max-block-size') {
        return tailwindDimensionTokenFromClass(className, property) || arbitraryValueMatch('max-block');
    }

    if (['top', 'right', 'bottom', 'left'].includes(property)) {
        return tailwindTokenMatch(property) || arbitraryValueMatch(property);
    }

    if (property === 'z-index') {
        const match = className.match(/^z-(?:\[)?(-?\d+)(?:\])?$/);

        return match ? match[1] : null;
    }

    if (property === 'opacity') {
        return opacityValueFromTailwindClass(className) || null;
    }

    return null;
}

function layerBaseClasses(layer, attributes = {}) {
    if (layer === 'section') {
        return  builderSectionClasses({ ...attributes, [ builderClassAttribute('section')]: '' });
    }

    if (layer === 'container') {
        return  builderContainerClasses(attributes);
    }

    if (layer === 'buttons') {
        return classNames( builderBaseClasses.buttons,  builderHorizontalAlignments[normalizeHorizontalAlignment(attributes['data-horizontal-alignment'])].buttonClasses);
    }

    return  builderBaseClasses[layer] || '';
}

function styleAttributeClassValue(attributes, layer, property, breakpoint = activeInspectorBreakpoint) {
    const applicableBreakpoints = applicableBreakpointsForInspector(breakpoint);
    const classes = classNames(layerBaseClasses(layer, attributes),  builderClassOverride(attributes, layer))
        .split(/\s+/)
        .filter(Boolean)
        .map((className) => tailwindClassParts(className))
        .filter((className) => applicableBreakpoints.includes(className.breakpoint));

    if (property === 'padding') {
        const compositePaddingValue = compositePaddingValueFromTailwindClasses(classes.map((className) => className.base));
        if (compositePaddingValue !== null) {
            return compositePaddingValue;
        }
    }

    if (property === 'margin') {
        const compositeMarginValue = compositeMarginValueFromTailwindClasses(classes.map((className) => className.base));
        if (compositeMarginValue !== null) {
            return compositeMarginValue;
        }
    }

    for (let index = classes.length - 1; index >= 0; index -= 1) {
        const value = styleValueFromTailwindClass(classes[index].base, property);
        if (value !== null) {
            return value;
        }
    }

    const computedFallback = computedTailwindColorClassValue(layer, property, classes);
    if (computedFallback !== null) {
        return computedFallback;
    }

    return null;
}

function inheritedStyleAttributeValue(attributes, layer, property, breakpoint = activeInspectorBreakpoint) {
    const applicableBreakpoints = applicableBreakpointsForInspector(breakpoint);
    const classes = classNames(layerBaseClasses(layer, attributes),  builderClassOverride(attributes, layer))
        .split(/\s+/)
        .filter(Boolean)
        .filter((className) => !className.startsWith('!') && !className.endsWith('!'))
        .map((className) => tailwindClassParts(className))
        .filter((className) => applicableBreakpoints.includes(className.breakpoint));

    if (property === 'padding') {
        const compositePaddingValue = compositePaddingValueFromTailwindClasses(classes.map((className) => className.base));
        if (compositePaddingValue !== null) {
            return compositePaddingValue;
        }
    }

    if (property === 'margin') {
        const compositeMarginValue = compositeMarginValueFromTailwindClasses(classes.map((className) => className.base));
        if (compositeMarginValue !== null) {
            return compositeMarginValue;
        }
    }

    for (let index = classes.length - 1; index >= 0; index -= 1) {
        const value = styleValueFromTailwindClass(classes[index].base, property);
        if (value !== null) {
            return value;
        }
    }

    const computedFallback = computedTailwindColorClassValue(layer, property, classes);
    if (computedFallback !== null) {
        return computedFallback;
    }

    return null;
}

function explicitClassAttributeValue(attributes, layer, property) {
    const classes = String( builderClassOverride(attributes, layer) || '')
        .split(/\s+/)
        .filter(Boolean)
        .map((className) => tailwindClassParts(className).base);

    if (property === 'padding') {
        const compositePaddingValue = compositePaddingValueFromTailwindClasses(classes);
        if (compositePaddingValue !== null) {
            return compositePaddingValue;
        }
    }

    if (property === 'margin') {
        const compositeMarginValue = compositeMarginValueFromTailwindClasses(classes);
        if (compositeMarginValue !== null) {
            return compositeMarginValue;
        }
    }

    for (let index = classes.length - 1; index >= 0; index -= 1) {
        const value = styleValueFromTailwindClass(classes[index], property);
        if (value !== null) {
            return value;
        }
    }

    const computedFallback = computedTailwindColorClassValue(layer, property, classes);
    if (computedFallback !== null) {
        return computedFallback;
    }

    return null;
}

function minimumValueForAttribute(attribute) {
    const normalizedAttribute = String(attribute || '').toLowerCase();
    if (
        attribute.endsWith('-layout-gap')
        || attribute.endsWith('-style-gap')
        || /-(?:width|height|min-width|min-height|max-width|max-height|inline-size|block-size|min-inline-size|min-block-size|max-inline-size|max-block-size)$/.test(attribute)
        || /-(?:line-clamp|tab-size|text-decoration-thickness)$/.test(attribute)
        || /-(?:padding|padding-inline|padding-top|padding-right|padding-bottom|padding-left)$/.test(attribute)
        || /-(?:border-radius|border-top-left-radius|border-top-right-radius|border-bottom-right-radius|border-bottom-left-radius)$/.test(attribute)
        || /-(?:border-width|border-top-width|border-right-width|border-bottom-width|border-left-width)$/.test(attribute)
        || attribute.endsWith('-style-perspective')
        || normalizedAttribute.includes('imagesize')
    ) {
        return 0;
    }

    if (normalizedAttribute.includes('opacity')) {
        return 0;
    }

    return null;
}

function maximumValueForAttribute(attribute) {
    if (String(attribute || '').toLowerCase().includes('opacity')) {
        return 1;
    }

    return null;
}

function clampBuilderValue(attribute, value, unit = '') {
    const minimum = minimumValueForAttribute(attribute);
    const maximum = maximumValueForAttribute(attribute);
    if (minimum === null && maximum === null) {
        return String(value ?? '');
    }

    const numericValue = Number.parseFloat(String(value ?? '').trim());
    if (Number.isNaN(numericValue)) {
        return String(value ?? '');
    }

    const clampedMinimum = minimum === null ? numericValue : Math.max(minimum, numericValue);
    const clamped = maximum === null ? clampedMinimum : Math.min(maximum, clampedMinimum);

    return `${Number.isInteger(clamped) ? String(clamped) : String(Math.round(clamped * 100) / 100)}${unit}`;
}

function tailwindClassMatchesGroup(className, groups, breakpoint) {
    const parts = tailwindClassParts(className);
    if (parts.breakpoint !== breakpoint) {
        return false;
    }

    const base = parts.base.replace(/^-/, '');

    return groups.some((group) => {
        if (group.startsWith('[') && group.endsWith(':')) {
            return base.startsWith(group);
        }

        if (group === 'text-color:') {
            return isTailwindTextColorClass(base);
        }

        if (group === 'bg-color:') {
            return isTailwindBackgroundColorClass(base);
        }

        if (group === 'border-color:') {
            return isTailwindBorderColorClass(base, 'border');
        }

        if (group === 'border-top-color:') {
            return isTailwindBorderColorClass(base, 'border-t');
        }

        if (group === 'border-right-color:') {
            return isTailwindBorderColorClass(base, 'border-r');
        }

        if (group === 'border-bottom-color:') {
            return isTailwindBorderColorClass(base, 'border-b');
        }

        if (group === 'border-left-color:') {
            return isTailwindBorderColorClass(base, 'border-l');
        }

        if (group === 'decoration-color:') {
            return isTailwindDecorationColorClass(base);
        }

        if (group === 'rounded-') {
            return /^rounded-(?![trblse]-).+/.test(base);
        }

        if (group === 'rounded-tl-') {
            return base.startsWith('rounded-tl-');
        }

        if (group === 'rounded-tr-') {
            return base.startsWith('rounded-tr-');
        }

        if (group === 'rounded-br-') {
            return base.startsWith('rounded-br-');
        }

        if (group === 'rounded-bl-') {
            return base.startsWith('rounded-bl-');
        }

        if (group.endsWith('-') || group.endsWith('[')) {
            return base.startsWith(group);
        }

        return base === group;
    });
}

function withTailwindClassOverrides(attributes) {
    const nextAttributes = { ...attributes };

    Object.entries(nextAttributes).forEach(([attribute, value]) => {
        if (!isBuilderStyleOverride(attribute, value)) {
            return;
        }

        const target = targetFromBuilderAttribute(attribute);
        const classAttribute = target ?  builderClassAttribute(target) : '';
        const groups = classGroupFromBuilderAttribute(attribute);
        const hasClassOverride = String(nextAttributes[classAttribute] || '')
            .split(/\s+/)
            .filter(Boolean)
            .some((className) => ['', 'md', 'lg'].some((breakpoint) => tailwindClassMatchesGroup(className, groups, breakpoint)));

        if (hasClassOverride) {
            return;
        }

        replaceTailwindClassOverride(nextAttributes, attribute, value, '');
    });

    return nextAttributes;
}

function withTailwindClassOverridesForBreakpoint(attributes, breakpoint = '') {
    const nextAttributes = { ...attributes };

    Object.entries(nextAttributes).forEach(([attribute, value]) => {
        if (!isBuilderStyleOverride(attribute, value)) {
            return;
        }

        const target = targetFromBuilderAttribute(attribute);
        const classAttribute = target ?  builderClassAttribute(target) : '';
        const groups = classGroupFromBuilderAttribute(attribute);
        const hasClassOverride = String(nextAttributes[classAttribute] || '')
            .split(/\s+/)
            .filter(Boolean)
            .some((className) => {
                const parsed = tailwindClassParts(className);

                return (className.startsWith('!') || className.endsWith('!'))
                    && parsed.breakpoint === breakpoint
                    && tailwindClassMatchesGroup(className, groups, breakpoint);
            });

        if (hasClassOverride) {
            return;
        }

        replaceTailwindClassOverride(nextAttributes, attribute, value, breakpoint);
    });

    return nextAttributes;
}

function classGroupFromBuilderAttribute(attribute) {
    const property = builderStyleAttributeParts(attribute)?.property || '';
    const groups = {
        display: ['block', 'inline', 'inline-block', 'flex', 'inline-flex', 'grid', 'hidden'],
        'flex-direction': ['flex-row', 'flex-row-reverse', 'flex-col', 'flex-col-reverse'],
        'justify-content': ['justify-start', 'justify-center', 'justify-end', 'justify-between', 'justify-around', 'justify-evenly'],
        'align-items': ['items-start', 'items-center', 'items-end', 'items-stretch', 'items-baseline'],
        'justify-items': ['justify-items-start', 'justify-items-center', 'justify-items-end', 'justify-items-stretch'],
        'align-content': ['content-start', 'content-center', 'content-end', 'content-between', 'content-around', 'content-evenly', 'content-baseline', 'content-stretch'],
        'place-items': ['place-items-start', 'place-items-center', 'place-items-end', 'place-items-stretch', 'place-items-baseline'],
        'place-content': ['place-content-start', 'place-content-center', 'place-content-end', 'place-content-between', 'place-content-around', 'place-content-evenly', 'place-content-baseline', 'place-content-stretch'],
        'flex-wrap': ['flex-nowrap', 'flex-wrap', 'flex-wrap-reverse'],
        gap: ['gap-'],
        flex: ['flex-none', 'flex-1', 'flex-initial', 'flex-auto'],
        'align-self': ['self-auto', 'self-start', 'self-center', 'self-end', 'self-stretch', 'self-baseline'],
        'justify-self': ['justify-self-auto', 'justify-self-start', 'justify-self-center', 'justify-self-end', 'justify-self-stretch'],
        'place-self': ['place-self-auto', 'place-self-start', 'place-self-center', 'place-self-end', 'place-self-stretch'],
        order: ['order-'],
        'font-family': ['font-body', 'font-heading', 'font-subheading', 'font-accent', 'font-serif', 'font-sans', 'font-mono', 'font-['],
        'font-size': ['text-xs', 'text-sm', 'text-base', 'text-lg', 'text-xl', 'text-2xl', 'text-3xl', 'text-4xl', 'text-5xl', 'text-6xl', 'text-7xl', 'text-8xl', 'text-9xl', 'text-['],
        'font-smoothing': ['antialiased', 'subpixel-antialiased'],
        'font-weight': ['font-thin', 'font-extralight', 'font-light', 'font-normal', 'font-medium', 'font-semibold', 'font-bold', 'font-extrabold', 'font-black'],
        'font-style': ['italic', 'not-italic'],
        'font-stretch': ['font-stretch-'],
        'font-variant-numeric': ['normal-nums', 'ordinal', 'slashed-zero', 'lining-nums', 'oldstyle-nums', 'proportional-nums', 'tabular-nums', 'diagonal-fractions', 'stacked-fractions'],
        'font-feature-settings': ['font-features-['],
        'text-transform': ['uppercase', 'lowercase', 'capitalize', 'normal-case'],
        'line-clamp': ['line-clamp-'],
        'list-style-image': ['list-image-none', 'list-image-['],
        'list-style-position': ['list-inside', 'list-outside'],
        'list-style-type': ['list-disc', 'list-decimal', 'list-none', 'list-['],
        'text-align': ['text-left', 'text-center', 'text-right', 'text-justify', 'text-start', 'text-end'],
        'letter-spacing': ['tracking-'],
        'line-height': ['leading-'],
        color: ['text-color:'],
        'text-decoration-line': ['no-underline', 'underline', 'overline', 'line-through'],
        'text-decoration-color': ['decoration-color:'],
        'text-decoration-style': ['decoration-solid', 'decoration-double', 'decoration-dotted', 'decoration-dashed', 'decoration-wavy'],
        'text-decoration-thickness': ['decoration-auto', 'decoration-from-font', 'decoration-0', 'decoration-1', 'decoration-2', 'decoration-4', 'decoration-8'],
        'text-underline-offset': ['underline-offset-auto', 'underline-offset-0', 'underline-offset-1', 'underline-offset-2', 'underline-offset-4', 'underline-offset-8'],
        'text-overflow': ['truncate', 'text-ellipsis', 'text-clip'],
        'text-wrap': ['text-wrap', 'text-nowrap', 'text-balance', 'text-pretty'],
        'text-indent': ['indent-'],
        'tab-size': ['tab-'],
        'vertical-align': ['align-baseline', 'align-top', 'align-middle', 'align-bottom', 'align-text-top', 'align-text-bottom', 'align-sub', 'align-super'],
        'white-space': ['whitespace-'],
        'word-break': ['break-normal', 'break-all', 'break-keep'],
        'overflow-wrap': ['wrap-break-word', 'wrap-anywhere', 'wrap-normal'],
        hyphens: ['hyphens-none', 'hyphens-manual', 'hyphens-auto'],
        content: ['content-none', 'content-['],
        width: ['w-'],
        height: ['h-'],
        'inline-size': ['inline-'],
        'block-size': ['block-'],
        'min-width': ['min-w-'],
        'min-height': ['min-h-'],
        'min-inline-size': ['min-inline-'],
        'min-block-size': ['min-block-'],
        'max-height': ['max-h-'],
        'max-width': ['max-w-', 'max-w-none', 'max-w-[', 'max-w-full'],
        'max-inline-size': ['max-inline-'],
        'max-block-size': ['max-block-'],
        margin: ['m-'],
        'margin-top': ['mt-', 'my-'],
        'margin-right': ['mr-', 'mx-'],
        'margin-bottom': ['mb-', 'my-'],
        'margin-left': ['ml-', 'mx-'],
        'background-color': ['bg-color:'],
        'background-clip': ['bg-clip-border', 'bg-clip-padding', 'bg-clip-content', 'bg-clip-text'],
        border: ['border', 'border-0', 'border-['],
        'border-width': ['border', 'border-0', 'border-2', 'border-4', 'border-8', 'border-['],
        'border-top-width': ['border-t', 'border-t-0', 'border-t-2', 'border-t-4', 'border-t-8', 'border-t-['],
        'border-right-width': ['border-r', 'border-r-0', 'border-r-2', 'border-r-4', 'border-r-8', 'border-r-['],
        'border-bottom-width': ['border-b', 'border-b-0', 'border-b-2', 'border-b-4', 'border-b-8', 'border-b-['],
        'border-left-width': ['border-l', 'border-l-0', 'border-l-2', 'border-l-4', 'border-l-8', 'border-l-['],
        'border-style': ['border-solid', 'border-dashed', 'border-dotted', 'border-double', 'border-none'],
        'border-color': ['border-color:'],
        'border-top-color': ['border-top-color:'],
        'border-right-color': ['border-right-color:'],
        'border-bottom-color': ['border-bottom-color:'],
        'border-left-color': ['border-left-color:'],
        padding: ['p-', 'px-', 'py-'],
        'padding-top': ['pt-', 'py-'],
        'padding-right': ['pr-', 'px-'],
        'padding-bottom': ['pb-', 'py-'],
        'padding-left': ['pl-', 'px-'],
        'padding-inline': ['px-'],
        'border-radius': ['rounded', 'rounded-', 'rounded-none', 'rounded-xs', 'rounded-sm', 'rounded-md', 'rounded-lg', 'rounded-xl', 'rounded-2xl', 'rounded-3xl', 'rounded-4xl', 'rounded-full', 'rounded-['],
        'border-top-left-radius': ['rounded-tl', 'rounded-tl-', 'rounded-tl-none', 'rounded-tl-xs', 'rounded-tl-sm', 'rounded-tl-md', 'rounded-tl-lg', 'rounded-tl-xl', 'rounded-tl-2xl', 'rounded-tl-3xl', 'rounded-tl-4xl', 'rounded-tl-full', 'rounded-tl-['],
        'border-top-right-radius': ['rounded-tr', 'rounded-tr-', 'rounded-tr-none', 'rounded-tr-xs', 'rounded-tr-sm', 'rounded-tr-md', 'rounded-tr-lg', 'rounded-tr-xl', 'rounded-tr-2xl', 'rounded-tr-3xl', 'rounded-tr-4xl', 'rounded-tr-full', 'rounded-tr-['],
        'border-bottom-right-radius': ['rounded-br', 'rounded-br-', 'rounded-br-none', 'rounded-br-xs', 'rounded-br-sm', 'rounded-br-md', 'rounded-br-lg', 'rounded-br-xl', 'rounded-br-2xl', 'rounded-br-3xl', 'rounded-br-4xl', 'rounded-br-full', 'rounded-br-['],
        'border-bottom-left-radius': ['rounded-bl', 'rounded-bl-', 'rounded-bl-none', 'rounded-bl-xs', 'rounded-bl-sm', 'rounded-bl-md', 'rounded-bl-lg', 'rounded-bl-xl', 'rounded-bl-2xl', 'rounded-bl-3xl', 'rounded-bl-4xl', 'rounded-bl-full', 'rounded-bl-['],
        position: ['static', 'relative', 'absolute', 'fixed', 'sticky'],
        top: ['top-'],
        right: ['right-'],
        bottom: ['bottom-'],
        left: ['left-'],
        'z-index': ['z-'],
        opacity: ['opacity-'],
        overflow: ['overflow-'],
        'overflow-x': ['overflow-x-'],
        'overflow-y': ['overflow-y-'],
        'text-decoration': ['no-underline', 'underline', 'overline', 'line-through'],
    };

    return property ? [...(groups[property] || []), `[${property}:`] : [];
}

function builderStyleMutation(attribute, rawValue) {
    const parts = builderStyleAttributeParts(attribute);
    if (!parts) {
        return null;
    }

    const value = String(rawValue ?? '').trim();

    return {
        attribute,
        target: parts.target,
        type: parts.type,
        property: parts.property,
        value,
        className: value ? tailwindClassFromBuilderAttribute(attribute, value) : '',
        groups: classGroupFromBuilderAttribute(attribute),
        previewValue: value ? cssPreviewValueFromBuilderValue(parts.property, value) : '',
    };
}

function knownSpacingClass(prefix, value) {
    if (String(value).startsWith('tw:')) {
        const token = String(value).slice(3);

        return tailwindScaleValues.has(token) ? `${prefix}-${token}` : null;
    }

    const map = {
        0: `${prefix}-0`,
    };

    return map[String(value)] || `${prefix}-[${arbitraryValue(value)}]`;
}

function arbitraryValue(value) {
    return String(value).trim().replace(/\s+/g, '_');
}

function arbitraryCssValue(value) {
    return String(value || '').replace(/_/g, ' ');
}

function escapeRegExp(value) {
    return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function arbitraryPropertyTailwindClass(property, value) {
    const normalizedProperty = String(property || '').trim();
    const normalizedValue = String(value || '').trim();

    return normalizedProperty && normalizedValue
        ? `[${normalizedProperty}:${arbitraryValue(normalizedValue)}]`
        : '';
}

function arbitraryPropertyValueFromTailwindClass(className, property) {
    const normalizedProperty = String(property || '').trim();
    if (!normalizedProperty) {
        return null;
    }

    const match = String(className || '').match(new RegExp(`^\\[${escapeRegExp(normalizedProperty)}:(.+)]$`));

    return match ? arbitraryCssValue(match[1]) : null;
}

function tailwindColorClassPrefixForProperty(property) {
    return {
        color: 'text',
        'background-color': 'bg',
        'border-color': 'border',
        'border-top-color': 'border-t',
        'border-right-color': 'border-r',
        'border-bottom-color': 'border-b',
        'border-left-color': 'border-l',
        'text-decoration-color': 'decoration',
    }[property] || null;
}

function tailwindColorClassFromBuilderValue(property, value) {
    const prefix = tailwindColorClassPrefixForProperty(property);
    if (!prefix) {
        return null;
    }

    const normalized = String(value ?? '').trim();
    const lower = normalized.toLowerCase();
    const keywords = {
        transparent: 'transparent',
        inherit: 'inherit',
        currentcolor: 'current',
        black: 'black',
        '#000000': 'black',
        white: 'white',
        '#ffffff': 'white',
    };
    if (Object.hasOwn(keywords, lower)) {
        return `${prefix}-${keywords[lower]}`;
    }

    const themeToken = themeColorTokenNameFromValue(normalized);
    if (themeToken) {
        return `${prefix}-${themeToken}`;
    }

    const comparable = cssColorComparableValue(normalized);
    if (!comparable || comparable.type !== 'color') {
        return null;
    }

    const color = comparable.alpha >= 0.99
        ? comparable.hex
        : colorWithAlpha(comparable.hex, comparable.alpha);

    return `${prefix}-[${arbitraryValue(color)}]`;
}

function tailwindThemeColorValueFromClass(prefix, className) {
    const normalized = String(className || '');
    if (!normalized.startsWith(`${prefix}-`)) {
        return null;
    }

    const token = normalized.slice(prefix.length + 1);
    if (!token || token.startsWith('[') || ['transparent', 'inherit', 'current', 'black', 'white'].includes(token)) {
        return null;
    }

    const variable = `--color-${token}`;
    const resolved = window.getComputedStyle(document.documentElement).getPropertyValue(variable).trim();

    return resolved ? `var(${variable})` : null;
}

const tailwindScaleTokens = [
    '0', 'px', '0.5', '1', '1.5', '2', '2.5', '3', '3.5', '4', '5', '6', '7', '8', '9', '10', '11', '12',
    '14', '16', '20', '24', '28', '32', '36', '40', '44', '48', '52', '56', '60', '64', '72', '80', '96',
];
const tailwindScaleValues = new Set(tailwindScaleTokens);
const tailwindFontSizeTokens = new Set(['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl', '8xl', '9xl']);
const tailwindLineHeightTokens = new Set(['none', 'tight', 'snug', 'normal', 'relaxed', 'loose']);
const tailwindLetterSpacingTokens = new Set(['tighter', 'tight', 'normal', 'wide', 'wider', 'widest']);
const tailwindFontSizeTokenOrder = ['xs', 'sm', 'base', 'lg', 'xl', '2xl', '3xl', '4xl', '5xl', '6xl', '7xl', '8xl', '9xl'];
const tailwindLineHeightTokenOrder = [...tailwindScaleTokens, 'none', 'tight', 'snug', 'normal', 'relaxed', 'loose'];
const tailwindLetterSpacingTokenOrder = ['tighter', 'tight', 'normal', 'wide', 'wider', 'widest'];
const tailwindDimensionConfigs = {
    width: {
        prefix: 'w',
        values: {
            ...tailwindContainerWidthTokenValues,
            ...tailwindWidthKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...tailwindContainerWidthTokens, ...Object.keys(tailwindWidthKeywordValues)],
    },
    'min-width': {
        prefix: 'min-w',
        values: {
            ...tailwindContainerWidthTokenValues,
            ...tailwindWidthKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...tailwindContainerWidthTokens, ...Object.keys(tailwindWidthKeywordValues)],
    },
    'max-width': {
        prefix: 'max-w',
        values: {
            ...tailwindContainerWidthTokenValues,
            ...tailwindMaxWidthKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...tailwindContainerWidthTokens, ...Object.keys(tailwindMaxWidthKeywordValues)],
    },
    height: {
        prefix: 'h',
        values: {
            ...tailwindHeightKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...Object.keys(tailwindHeightKeywordValues)],
    },
    'min-height': {
        prefix: 'min-h',
        values: {
            ...tailwindHeightKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...Object.keys(tailwindHeightKeywordValues)],
    },
    'max-height': {
        prefix: 'max-h',
        values: {
            ...tailwindMaxHeightKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...Object.keys(tailwindMaxHeightKeywordValues)],
    },
    'inline-size': {
        prefix: 'inline',
        values: {
            ...tailwindContainerWidthTokenValues,
            ...tailwindWidthKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...tailwindContainerWidthTokens, ...Object.keys(tailwindWidthKeywordValues)],
    },
    'min-inline-size': {
        prefix: 'min-inline',
        values: {
            ...tailwindContainerWidthTokenValues,
            ...tailwindWidthKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...tailwindContainerWidthTokens, ...Object.keys(tailwindWidthKeywordValues)],
    },
    'max-inline-size': {
        prefix: 'max-inline',
        values: {
            ...tailwindContainerWidthTokenValues,
            ...tailwindMaxWidthKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...tailwindContainerWidthTokens, ...Object.keys(tailwindMaxWidthKeywordValues)],
    },
    'block-size': {
        prefix: 'block',
        values: {
            ...tailwindHeightKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...Object.keys(tailwindHeightKeywordValues)],
    },
    'min-block-size': {
        prefix: 'min-block',
        values: {
            ...tailwindHeightKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...Object.keys(tailwindHeightKeywordValues)],
    },
    'max-block-size': {
        prefix: 'max-block',
        values: {
            ...tailwindMaxHeightKeywordValues,
        },
        tokens: [...tailwindScaleTokens, ...Object.keys(tailwindMaxHeightKeywordValues)],
    },
};

function tailwindDimensionConfig(property) {
    const config = tailwindDimensionConfigs[property];
    if (!config) {
        return null;
    }

    return {
        ...config,
        tokenSet: config.tokenSet || new Set(config.tokens),
    };
}

const tailwindWidthTokenOptions = createTailwindDimensionTokenOptions('w', tailwindDimensionConfigs.width.tokens);
const tailwindMinWidthTokenOptions = createTailwindDimensionTokenOptions('min-w', tailwindDimensionConfigs['min-width'].tokens);
const tailwindMaxWidthTokenOptions = createTailwindDimensionTokenOptions('max-w', tailwindDimensionConfigs['max-width'].tokens);
const tailwindHeightTokenOptions = createTailwindDimensionTokenOptions('h', tailwindDimensionConfigs.height.tokens);
const tailwindMinHeightTokenOptions = createTailwindDimensionTokenOptions('min-h', tailwindDimensionConfigs['min-height'].tokens);
const tailwindMaxHeightTokenOptions = createTailwindDimensionTokenOptions('max-h', tailwindDimensionConfigs['max-height'].tokens);
const tailwindInlineSizeTokenOptions = createTailwindDimensionTokenOptions('inline', tailwindDimensionConfigs['inline-size'].tokens);
const tailwindMinInlineSizeTokenOptions = createTailwindDimensionTokenOptions('min-inline', tailwindDimensionConfigs['min-inline-size'].tokens);
const tailwindMaxInlineSizeTokenOptions = createTailwindDimensionTokenOptions('max-inline', tailwindDimensionConfigs['max-inline-size'].tokens);
const tailwindBlockSizeTokenOptions = createTailwindDimensionTokenOptions('block', tailwindDimensionConfigs['block-size'].tokens);
const tailwindMinBlockSizeTokenOptions = createTailwindDimensionTokenOptions('min-block', tailwindDimensionConfigs['min-block-size'].tokens);
const tailwindMaxBlockSizeTokenOptions = createTailwindDimensionTokenOptions('max-block', tailwindDimensionConfigs['max-block-size'].tokens);
const tailwindTextIndentTokenOptions = createTailwindDimensionTokenOptions('indent', tailwindScaleTokens);
const tailwindFontSizeTokenOptions = createTailwindDimensionTokenOptions('text', tailwindFontSizeTokenOrder);
const tailwindLineHeightTokenOptions = createTailwindDimensionTokenOptions('leading', tailwindLineHeightTokenOrder);
const tailwindLetterSpacingTokenOptions = createTailwindDimensionTokenOptions('tracking', tailwindLetterSpacingTokenOrder);
const tailwindRadiusTokenOptions = createOrderedOptionMap({
    none: { label: 'rounded-none' },
    xs: { label: 'rounded-xs' },
    sm: { label: 'rounded-sm' },
    default: { label: 'rounded' },
    md: { label: 'rounded-md' },
    lg: { label: 'rounded-lg' },
    xl: { label: 'rounded-xl' },
    '2xl': { label: 'rounded-2xl' },
    '3xl': { label: 'rounded-3xl' },
    '4xl': { label: 'rounded-4xl' },
    full: { label: 'rounded-full' },
});
const tailwindRadiusTopLeftTokenOptions = createOrderedOptionMap({
    none: { label: 'rounded-tl-none' },
    xs: { label: 'rounded-tl-xs' },
    sm: { label: 'rounded-tl-sm' },
    default: { label: 'rounded-tl' },
    md: { label: 'rounded-tl-md' },
    lg: { label: 'rounded-tl-lg' },
    xl: { label: 'rounded-tl-xl' },
    '2xl': { label: 'rounded-tl-2xl' },
    '3xl': { label: 'rounded-tl-3xl' },
    '4xl': { label: 'rounded-tl-4xl' },
    full: { label: 'rounded-tl-full' },
});
const tailwindRadiusTopRightTokenOptions = createOrderedOptionMap({
    none: { label: 'rounded-tr-none' },
    xs: { label: 'rounded-tr-xs' },
    sm: { label: 'rounded-tr-sm' },
    default: { label: 'rounded-tr' },
    md: { label: 'rounded-tr-md' },
    lg: { label: 'rounded-tr-lg' },
    xl: { label: 'rounded-tr-xl' },
    '2xl': { label: 'rounded-tr-2xl' },
    '3xl': { label: 'rounded-tr-3xl' },
    '4xl': { label: 'rounded-tr-4xl' },
    full: { label: 'rounded-tr-full' },
});
const tailwindRadiusBottomRightTokenOptions = createOrderedOptionMap({
    none: { label: 'rounded-br-none' },
    xs: { label: 'rounded-br-xs' },
    sm: { label: 'rounded-br-sm' },
    default: { label: 'rounded-br' },
    md: { label: 'rounded-br-md' },
    lg: { label: 'rounded-br-lg' },
    xl: { label: 'rounded-br-xl' },
    '2xl': { label: 'rounded-br-2xl' },
    '3xl': { label: 'rounded-br-3xl' },
    '4xl': { label: 'rounded-br-4xl' },
    full: { label: 'rounded-br-full' },
});
const tailwindRadiusBottomLeftTokenOptions = createOrderedOptionMap({
    none: { label: 'rounded-bl-none' },
    xs: { label: 'rounded-bl-xs' },
    sm: { label: 'rounded-bl-sm' },
    default: { label: 'rounded-bl' },
    md: { label: 'rounded-bl-md' },
    lg: { label: 'rounded-bl-lg' },
    xl: { label: 'rounded-bl-xl' },
    '2xl': { label: 'rounded-bl-2xl' },
    '3xl': { label: 'rounded-bl-3xl' },
    '4xl': { label: 'rounded-bl-4xl' },
    full: { label: 'rounded-bl-full' },
});
const tailwindBorderWidthTokenOptions = {
    px: { label: 'border' },
    0: { label: 'border-0' },
    2: { label: 'border-2' },
    4: { label: 'border-4' },
    8: { label: 'border-8' },
};
const tailwindBorderTopWidthTokenOptions = {
    px: { label: 'border-t' },
    0: { label: 'border-t-0' },
    2: { label: 'border-t-2' },
    4: { label: 'border-t-4' },
    8: { label: 'border-t-8' },
};
const tailwindBorderRightWidthTokenOptions = {
    px: { label: 'border-r' },
    0: { label: 'border-r-0' },
    2: { label: 'border-r-2' },
    4: { label: 'border-r-4' },
    8: { label: 'border-r-8' },
};
const tailwindBorderBottomWidthTokenOptions = {
    px: { label: 'border-b' },
    0: { label: 'border-b-0' },
    2: { label: 'border-b-2' },
    4: { label: 'border-b-4' },
    8: { label: 'border-b-8' },
};
const tailwindBorderLeftWidthTokenOptions = {
    px: { label: 'border-l' },
    0: { label: 'border-l-0' },
    2: { label: 'border-l-2' },
    4: { label: 'border-l-4' },
    8: { label: 'border-l-8' },
};
const tailwindDimensionTokenOptionsByProperty = {
    width: tailwindWidthTokenOptions,
    'min-width': tailwindMinWidthTokenOptions,
    'max-width': tailwindMaxWidthTokenOptions,
    height: tailwindHeightTokenOptions,
    'min-height': tailwindMinHeightTokenOptions,
    'max-height': tailwindMaxHeightTokenOptions,
    'inline-size': tailwindInlineSizeTokenOptions,
    'min-inline-size': tailwindMinInlineSizeTokenOptions,
    'max-inline-size': tailwindMaxInlineSizeTokenOptions,
    'block-size': tailwindBlockSizeTokenOptions,
    'min-block-size': tailwindMinBlockSizeTokenOptions,
    'max-block-size': tailwindMaxBlockSizeTokenOptions,
    'text-indent': tailwindTextIndentTokenOptions,
    'font-size': tailwindFontSizeTokenOptions,
    'line-height': tailwindLineHeightTokenOptions,
    'letter-spacing': tailwindLetterSpacingTokenOptions,
    'border-radius': tailwindRadiusTokenOptions,
    'border-top-left-radius': tailwindRadiusTopLeftTokenOptions,
    'border-top-right-radius': tailwindRadiusTopRightTokenOptions,
    'border-bottom-right-radius': tailwindRadiusBottomRightTokenOptions,
    'border-bottom-left-radius': tailwindRadiusBottomLeftTokenOptions,
    'border-width': tailwindBorderWidthTokenOptions,
    'border-top-width': tailwindBorderTopWidthTokenOptions,
    'border-right-width': tailwindBorderRightWidthTokenOptions,
    'border-bottom-width': tailwindBorderBottomWidthTokenOptions,
    'border-left-width': tailwindBorderLeftWidthTokenOptions,
};

function createTailwindDimensionTokenOptions(prefix, tokens) {
    const options = tokens
        .reduce((options, token) => {
            options[token] = { label: `${prefix}-${token}` };

            return options;
        }, {});

    Object.defineProperty(options, '__order', {
        value: [...tokens],
        enumerable: false,
    });

    return options;
}

function orderedTokenOptionEntries(tokenOptions) {
    if (!tokenOptions) {
        return [];
    }

    const orderedKeys = Array.isArray(tokenOptions.__order)
        ? tokenOptions.__order
        : Object.keys(tokenOptions);

    return orderedKeys
        .filter((key) => Object.hasOwn(tokenOptions, key))
        .map((key) => [key, tokenOptions[key]]);
}

function createOrderedOptionMap(options) {
    const map = { ...options };

    Object.defineProperty(map, '__order', {
        value: Object.keys(options),
        enumerable: false,
    });

    return map;
}

function nearestTailwindScaleToken(token) {
    const normalized = String(token ?? '').trim();
    if (tailwindScaleValues.has(normalized)) {
        return normalized;
    }

    const numericValue = Number.parseFloat(normalized);
    if (Number.isNaN(numericValue)) {
        return '0';
    }

    return tailwindScaleTokens.reduce((nearest, candidate) => {
        const candidateValue = candidate === 'px' ? 0.0625 : Number.parseFloat(candidate);
        const nearestValue = nearest === 'px' ? 0.0625 : Number.parseFloat(nearest);

        return Math.abs(candidateValue - numericValue) < Math.abs(nearestValue - numericValue) ? candidate : nearest;
    }, '0');
}

function tailwindScaleTokenStep(token, direction, multiplier = 1) {
    const current = nearestTailwindScaleToken(token);
    const currentIndex = tailwindScaleTokens.indexOf(current);
    const nextIndex = Math.max(0, Math.min(tailwindScaleTokens.length - 1, currentIndex + (direction * multiplier)));

    return tailwindScaleTokens[nextIndex] || current;
}

function tailwindScaleClassFromBuilderAttribute(property, token) {
    const normalizedToken = String(token ?? '').trim();
    const negative = normalizedToken.startsWith('-');
    const unsignedToken = negative ? normalizedToken.slice(1) : normalizedToken;
    const dimensionConfig = tailwindDimensionConfig(property);
    const tokenSets = {
        'font-size': tailwindFontSizeTokens,
        'line-height': new Set([...tailwindScaleValues, ...tailwindLineHeightTokens]),
        'letter-spacing': tailwindLetterSpacingTokens,
    };
    const allowedTokens = tokenSets[property] || tailwindScaleValues;
    const negativeProperties = new Set(['margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'top', 'right', 'bottom', 'left', 'text-indent']);

    const prefixes = {
        gap: 'gap',
        padding: 'p',
        'padding-inline': 'px',
        'padding-top': 'pt',
        'padding-right': 'pr',
        'padding-bottom': 'pb',
        'padding-left': 'pl',
        margin: 'm',
        'margin-top': 'mt',
        'margin-right': 'mr',
        'margin-bottom': 'mb',
        'margin-left': 'ml',
        width: 'w',
        height: 'h',
        'min-width': 'min-w',
        'min-height': 'min-h',
        'max-width': 'max-w',
        'max-height': 'max-h',
        'inline-size': 'inline',
        'min-inline-size': 'min-inline',
        'max-inline-size': 'max-inline',
        'block-size': 'block',
        'min-block-size': 'min-block',
        'max-block-size': 'max-block',
        top: 'top',
        right: 'right',
        bottom: 'bottom',
        left: 'left',
        'line-height': 'leading',
        'font-size': 'text',
        'text-indent': 'indent',
        'letter-spacing': 'tracking',
    };
    const borderWidthPrefixes = {
        'border-width': 'border',
        'border-top-width': 'border-t',
        'border-right-width': 'border-r',
        'border-bottom-width': 'border-b',
        'border-left-width': 'border-l',
    };
    const borderWidthAllowedTokens = new Set(['px', '0', '2', '4', '8']);
    const borderRadiusPrefixes = {
        'border-radius': 'rounded',
        'border-top-left-radius': 'rounded-tl',
        'border-top-right-radius': 'rounded-tr',
        'border-bottom-right-radius': 'rounded-br',
        'border-bottom-left-radius': 'rounded-bl',
    };
    const borderRadiusAllowedTokens = new Set(['none', 'xs', 'sm', 'default', 'md', 'lg', 'xl', '2xl', '3xl', '4xl', 'full']);

    if (dimensionConfig) {
        if (negative || !dimensionConfig.tokenSet.has(unsignedToken)) {
            return null;
        }

        return `${dimensionConfig.prefix}-${unsignedToken}`;
    }

    if (Object.hasOwn(borderWidthPrefixes, property)) {
        if (negative || !borderWidthAllowedTokens.has(unsignedToken)) {
            return null;
        }

        const prefix = borderWidthPrefixes[property];
        const classMap = {
            px: prefix,
            0: `${prefix}-0`,
            2: `${prefix}-2`,
            4: `${prefix}-4`,
            8: `${prefix}-8`,
        };

        return classMap[unsignedToken] || null;
    }

    if (Object.hasOwn(borderRadiusPrefixes, property)) {
        if (negative) {
            return null;
        }

        const prefix = borderRadiusPrefixes[property];
        const classMap = {
            none: `${prefix}-none`,
            xs: `${prefix}-xs`,
            sm: `${prefix}-sm`,
            default: prefix,
            md: `${prefix}-md`,
            lg: `${prefix}-lg`,
            xl: `${prefix}-xl`,
            '2xl': `${prefix}-2xl`,
            '3xl': `${prefix}-3xl`,
            '4xl': `${prefix}-4xl`,
            full: `${prefix}-full`,
        };

        if (classMap[unsignedToken]) {
            return classMap[unsignedToken];
        }

        if (/^\d+$/.test(unsignedToken)) {
            return `${prefix}-${unsignedToken}`;
        }

        return `${prefix}-[${arbitraryValue(unsignedToken)}]`;
    }

    if (!prefixes[property] || !allowedTokens.has(unsignedToken)) {
        return null;
    }

    if (negative && !negativeProperties.has(property)) {
        return null;
    }

    return negative ? `-${prefixes[property]}-${unsignedToken}` : `${prefixes[property]}-${unsignedToken}`;
}

function tailwindClassFromBuilderAttribute(attribute, value) {
    const property = builderStyleAttributeParts(attribute)?.property || '';
    const normalized = String(value ?? '').trim();
    if (!normalized) {
        return '';
    }

    if (normalized.startsWith('tw:')) {
        return tailwindScaleClassFromBuilderAttribute(property, normalized.slice(3));
    }

    const maps = {
        display: {
            block: 'block',
            inline: 'inline',
            'inline-block': 'inline-block',
            flex: 'flex',
            'inline-flex': 'inline-flex',
            grid: 'grid',
            none: 'hidden',
        },
        'flex-direction': {
            row: 'flex-row',
            'row-reverse': 'flex-row-reverse',
            column: 'flex-col',
            'column-reverse': 'flex-col-reverse',
        },
        'justify-content': {
            'flex-start': 'justify-start',
            center: 'justify-center',
            'flex-end': 'justify-end',
            'space-between': 'justify-between',
            'space-around': 'justify-around',
            'space-evenly': 'justify-evenly',
        },
        'align-items': {
            'flex-start': 'items-start',
            center: 'items-center',
            'flex-end': 'items-end',
            stretch: 'items-stretch',
            baseline: 'items-baseline',
        },
        'justify-items': {
            start: 'justify-items-start',
            center: 'justify-items-center',
            end: 'justify-items-end',
            stretch: 'justify-items-stretch',
        },
        'align-content': {
            start: 'content-start',
            center: 'content-center',
            end: 'content-end',
            'space-between': 'content-between',
            'space-around': 'content-around',
            'space-evenly': 'content-evenly',
            baseline: 'content-baseline',
            stretch: 'content-stretch',
        },
        'place-items': {
            start: 'place-items-start',
            center: 'place-items-center',
            end: 'place-items-end',
            stretch: 'place-items-stretch',
            baseline: 'place-items-baseline',
        },
        'place-content': {
            start: 'place-content-start',
            center: 'place-content-center',
            end: 'place-content-end',
            'space-between': 'place-content-between',
            'space-around': 'place-content-around',
            'space-evenly': 'place-content-evenly',
            baseline: 'place-content-baseline',
            stretch: 'place-content-stretch',
        },
        'flex-wrap': {
            nowrap: 'flex-nowrap',
            wrap: 'flex-wrap',
            'wrap-reverse': 'flex-wrap-reverse',
        },
        flex: {
            '0 0 auto': 'flex-none',
            '1 1 0%': 'flex-1',
            '0 1 auto': 'flex-initial',
            auto: 'flex-auto',
        },
        'align-self': {
            auto: 'self-auto',
            'flex-start': 'self-start',
            center: 'self-center',
            'flex-end': 'self-end',
            stretch: 'self-stretch',
            baseline: 'self-baseline',
        },
        'justify-self': {
            auto: 'justify-self-auto',
            start: 'justify-self-start',
            center: 'justify-self-center',
            end: 'justify-self-end',
            stretch: 'justify-self-stretch',
        },
        'place-self': {
            auto: 'place-self-auto',
            start: 'place-self-start',
            center: 'place-self-center',
            end: 'place-self-end',
            stretch: 'place-self-stretch',
        },
        'font-family': {
            'var(--font-body)': 'font-body',
            'var(--font-heading)': 'font-heading',
            'var(--font-subheading)': 'font-subheading',
            'var(--font-accent)': 'font-accent',
            'Montserrat, sans-serif': 'font-[Montserrat,sans-serif]',
            '"trajan-pro-3", serif': 'font-[trajan-pro-3,serif]',
            serif: 'font-serif',
            'ui-sans-serif, system-ui, sans-serif': 'font-sans',
            'ui-monospace, SFMono-Regular, monospace': 'font-mono',
        },
        'font-size': {
            '10px': 'text-[10px]',
            '12px': 'text-xs',
            '14px': 'text-sm',
            '16px': 'text-base',
            '18px': 'text-lg',
            '20px': 'text-xl',
            '24px': 'text-2xl',
            '32px': 'text-3xl',
            '48px': 'text-5xl',
            '64px': 'text-[64px]',
            '80px': 'text-[80px]',
            '96px': 'text-[96px]',
        },
        'font-smoothing': {
            antialiased: 'antialiased',
            'subpixel-antialiased': 'subpixel-antialiased',
        },
        'font-weight': {
            100: 'font-thin',
            200: 'font-extralight',
            300: 'font-light',
            400: 'font-normal',
            500: 'font-medium',
            600: 'font-semibold',
            700: 'font-bold',
            800: 'font-extrabold',
            900: 'font-black',
        },
        'font-style': {
            normal: 'not-italic',
            italic: 'italic',
        },
        'font-stretch': {
            'ultra-condensed': 'font-stretch-ultra-condensed',
            'extra-condensed': 'font-stretch-extra-condensed',
            condensed: 'font-stretch-condensed',
            'semi-condensed': 'font-stretch-semi-condensed',
            normal: 'font-stretch-normal',
            'semi-expanded': 'font-stretch-semi-expanded',
            expanded: 'font-stretch-expanded',
            'extra-expanded': 'font-stretch-extra-expanded',
            'ultra-expanded': 'font-stretch-ultra-expanded',
        },
        'font-variant-numeric': {
            normal: 'normal-nums',
            ordinal: 'ordinal',
            'slashed-zero': 'slashed-zero',
            'lining-nums': 'lining-nums',
            'oldstyle-nums': 'oldstyle-nums',
            'proportional-nums': 'proportional-nums',
            'tabular-nums': 'tabular-nums',
            'diagonal-fractions': 'diagonal-fractions',
            'stacked-fractions': 'stacked-fractions',
        },
        'text-transform': {
            none: 'normal-case',
            uppercase: 'uppercase',
            lowercase: 'lowercase',
            capitalize: 'capitalize',
        },
        'text-align': {
            left: 'text-left',
            center: 'text-center',
            right: 'text-right',
            justify: 'text-justify',
            start: 'text-start',
            end: 'text-end',
        },
        'letter-spacing': {
            0: 'tracking-normal',
            '.04em': 'tracking-wide',
            '.08em': 'tracking-wider',
            '.1em': 'tracking-widest',
            '.16em': 'tracking-[.16em]',
            '.22em': 'tracking-[.22em]',
        },
        'line-height': {
            1: 'leading-none',
            1.08: 'leading-[1.08]',
            1.2: 'leading-tight',
            1.4: 'leading-snug',
            1.6: 'leading-[1.6]',
            1.8: 'leading-loose',
        },
        'max-width': {
            none: 'max-w-none',
            '100%': 'max-w-full',
            '480px': 'max-w-[480px]',
            '680px': 'max-w-[680px]',
            '900px': 'max-w-[900px]',
            '1120px': 'max-w-[1120px]',
        },
        color: {
            '#ffffff': 'text-white',
            white: 'text-white',
            black: 'text-black',
            currentColor: 'text-current',
            'rgba(255,255,255,.8)': 'text-white/80',
            'rgba(255,255,255,.9)': 'text-white/90',
        },
        'list-style-position': {
            inside: 'list-inside',
            outside: 'list-outside',
        },
        'list-style-type': {
            disc: 'list-disc',
            decimal: 'list-decimal',
            none: 'list-none',
        },
        'text-decoration-line': {
            none: 'no-underline',
            underline: 'underline',
            overline: 'overline',
            'line-through': 'line-through',
        },
        'text-decoration-color': {
            inherit: 'decoration-inherit',
            currentColor: 'decoration-current',
            transparent: 'decoration-transparent',
            '#ffffff': 'decoration-white',
            white: 'decoration-white',
            '#000000': 'decoration-black',
            black: 'decoration-black',
        },
        'text-decoration-style': {
            solid: 'decoration-solid',
            double: 'decoration-double',
            dotted: 'decoration-dotted',
            dashed: 'decoration-dashed',
            wavy: 'decoration-wavy',
        },
        'text-decoration-thickness': {
            auto: 'decoration-auto',
            'from-font': 'decoration-from-font',
            '1px': 'decoration-1',
            '2px': 'decoration-2',
            '4px': 'decoration-4',
            '8px': 'decoration-8',
        },
        'text-underline-offset': {
            auto: 'underline-offset-auto',
            '0px': 'underline-offset-0',
            '1px': 'underline-offset-1',
            '2px': 'underline-offset-2',
            '4px': 'underline-offset-4',
            '8px': 'underline-offset-8',
        },
        'text-overflow': {
            truncate: 'truncate',
            ellipsis: 'text-ellipsis',
            clip: 'text-clip',
        },
        'text-wrap': {
            wrap: 'text-wrap',
            nowrap: 'text-nowrap',
            balance: 'text-balance',
            pretty: 'text-pretty',
        },
        'vertical-align': {
            baseline: 'align-baseline',
            top: 'align-top',
            middle: 'align-middle',
            bottom: 'align-bottom',
            'text-top': 'align-text-top',
            'text-bottom': 'align-text-bottom',
            sub: 'align-sub',
            super: 'align-super',
        },
        'white-space': {
            normal: 'whitespace-normal',
            nowrap: 'whitespace-nowrap',
            pre: 'whitespace-pre',
            'pre-line': 'whitespace-pre-line',
            'pre-wrap': 'whitespace-pre-wrap',
            'break-spaces': 'whitespace-break-spaces',
        },
        'word-break': {
            normal: 'break-normal',
            'break-all': 'break-all',
            'keep-all': 'break-keep',
        },
        'overflow-wrap': {
            normal: 'wrap-normal',
            'break-word': 'wrap-break-word',
            anywhere: 'wrap-anywhere',
        },
        hyphens: {
            none: 'hyphens-none',
            manual: 'hyphens-manual',
            auto: 'hyphens-auto',
        },
        'background-color': {
            transparent: 'bg-transparent',
            '#ffffff': 'bg-white',
            '#000000': 'bg-black',
            '#5b1f2e': 'bg-[#5b1f2e]',
        },
        'background-clip': {
            'border-box': 'bg-clip-border',
            'padding-box': 'bg-clip-padding',
            'content-box': 'bg-clip-content',
            text: 'bg-clip-text',
        },
        'border-radius': {
            0: 'rounded-none',
            '0px': 'rounded-none',
            '4px': 'rounded',
            '8px': 'rounded-lg',
            '9999px': 'rounded-full',
        },
        position: {
            static: 'static',
            relative: 'relative',
            absolute: 'absolute',
            fixed: 'fixed',
            sticky: 'sticky',
        },
        overflow: {
            visible: 'overflow-visible',
            hidden: 'overflow-hidden',
            scroll: 'overflow-scroll',
            auto: 'overflow-auto',
        },
        'text-decoration': {
            none: 'no-underline',
            underline: 'underline',
            overline: 'overline',
            'line-through': 'line-through',
        },
        'border-style': {
            none: 'border-none',
            solid: 'border-solid',
            dashed: 'border-dashed',
            dotted: 'border-dotted',
            double: 'border-double',
        },
        border: {
            0: 'border-0',
            none: 'border-0',
            '1px solid #c8a96a': 'border border-[#c8a96a]',
        },
        padding: {
            '16px 28px': 'px-7 py-4',
        },
    };

    const colorClass = tailwindColorClassFromBuilderValue(property, normalized);
    if (colorClass !== null) {
        return colorClass;
    }

    if (normalized.startsWith('tw:') && ['border-width', 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'].includes(property)) {
        const token = normalized.slice(3);
        const prefix = {
            'border-width': 'border',
            'border-top-width': 'border-t',
            'border-right-width': 'border-r',
            'border-bottom-width': 'border-b',
            'border-left-width': 'border-l',
        }[property];
        const classMap = {
            px: prefix,
            0: `${prefix}-0`,
            2: `${prefix}-2`,
            4: `${prefix}-4`,
            8: `${prefix}-8`,
        };

        return classMap[token] ?? null;
    }

    if (normalized.startsWith('tw:') && ['border-radius', 'border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'].includes(property)) {
        const token = normalized.slice(3);
        const prefix = {
            'border-radius': 'rounded',
            'border-top-left-radius': 'rounded-tl',
            'border-top-right-radius': 'rounded-tr',
            'border-bottom-right-radius': 'rounded-br',
            'border-bottom-left-radius': 'rounded-bl',
        }[property];
        const classMap = {
            none: `${prefix}-none`,
            sm: `${prefix}-sm`,
            default: prefix,
            md: `${prefix}-md`,
            lg: `${prefix}-lg`,
            xl: `${prefix}-xl`,
            '2xl': `${prefix}-2xl`,
            '3xl': `${prefix}-3xl`,
            '4xl': `${prefix}-4xl`,
            full: `${prefix}-full`,
        };

        if (classMap[token]) {
            return classMap[token];
        }

        return `${prefix}-[${arbitraryValue(token)}]`;
    }

    if (property === 'gap') {
        return knownSpacingClass('gap', normalized);
    }

    if (property === 'padding-inline') {
        return knownSpacingClass('px', normalized);
    }

    if (['padding-top', 'padding-right', 'padding-bottom', 'padding-left'].includes(property)) {
        const prefix = {
            'padding-top': 'pt',
            'padding-right': 'pr',
            'padding-bottom': 'pb',
            'padding-left': 'pl',
        }[property];

        return knownSpacingClass(prefix, normalized);
    }

    if ([
        'width',
        'height',
        'min-width',
        'min-height',
        'max-width',
        'max-height',
        'inline-size',
        'min-inline-size',
        'max-inline-size',
        'block-size',
        'min-block-size',
        'max-block-size',
    ].includes(property)) {
        const prefix = {
            width: 'w',
            height: 'h',
            'min-width': 'min-w',
            'min-height': 'min-h',
            'max-width': 'max-w',
            'max-height': 'max-h',
            'inline-size': 'inline',
            'min-inline-size': 'min-inline',
            'max-inline-size': 'max-inline',
            'block-size': 'block',
            'min-block-size': 'min-block',
            'max-block-size': 'max-block',
        }[property];

        if (normalized === 'auto') {
            return ['width', 'height', 'inline-size', 'block-size', 'min-width', 'min-height', 'min-inline-size', 'min-block-size'].includes(property)
                ? `${prefix}-auto`
                : '';
        }

        return `${prefix}-[${normalized}]`;
    }

    if (['margin', 'margin-top', 'margin-right', 'margin-left'].includes(property)) {
        const prefix = {
            margin: 'm',
            'margin-top': 'mt',
            'margin-right': 'mr',
            'margin-left': 'ml',
        }[property];

        if (normalized === 'auto' && property !== 'margin-top') {
            return `${prefix}-auto`;
        }

        return knownSpacingClass(prefix, normalized);
    }

    if (property === 'margin-bottom') {
        return knownSpacingClass('mb', normalized);
    }

    if (['top', 'right', 'bottom', 'left'].includes(property)) {
        return knownSpacingClass(property, normalized);
    }

    if (property === 'order') {
        return tailwindOrderClassFromValue(normalized);
    }

    if (property === 'z-index') {
        return normalized === '0' ? 'z-0' : `z-[${normalized}]`;
    }

    if (property === 'opacity') {
        return `opacity-[${normalized}]`;
    }

    if (property === 'font-size') {
        return maps['font-size'][normalized] ?? `text-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'line-height') {
        return maps['line-height'][normalized] ?? `leading-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'letter-spacing') {
        return maps['letter-spacing'][normalized] ?? `tracking-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'font-feature-settings') {
        return `font-features-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'font-stretch') {
        return maps['font-stretch'][normalized] ?? `font-stretch-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'line-clamp') {
        if (normalized === 'none') {
            return 'line-clamp-none';
        }

        if (/^\d+$/.test(normalized)) {
            return `line-clamp-${normalized}`;
        }

        return `line-clamp-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'list-style-image') {
        if (normalized === 'none') {
            return 'list-image-none';
        }

        return `list-image-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'list-style-type') {
        return maps['list-style-type'][normalized] ?? `list-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'text-decoration-line') {
        return maps['text-decoration-line'][normalized] ?? null;
    }

    if (property === 'text-decoration-color') {
        return maps['text-decoration-color'][normalized] ?? maps['text-decoration-color'][normalized.toLowerCase()] ?? null;
    }

    if (property === 'text-decoration-thickness') {
        return maps['text-decoration-thickness'][normalized] ?? `decoration-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'text-underline-offset') {
        if (/^-?\d+(?:\.\d+)?px$/.test(normalized)) {
            const numericValue = Number.parseFloat(normalized);
            if (Number.isInteger(numericValue)) {
                const prefix = numericValue < 0 ? '-' : '';

                return `${prefix}underline-offset-${Math.abs(numericValue)}`;
            }
        }

        return maps['text-underline-offset'][normalized] ?? `underline-offset-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'text-indent') {
        if (normalized.startsWith('tw:')) {
            return knownSpacingClass('indent', normalized);
        }

        return `indent-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'tab-size') {
        if (/^\d+$/.test(normalized)) {
            return `tab-${normalized}`;
        }

        return `tab-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'content') {
        if (normalized === 'none') {
            return 'content-none';
        }

        return `content-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'border-width') {
        const borderWidthClasses = {
            0: 'border-0',
            '0px': 'border-0',
            '1px': 'border',
            '2px': 'border-2',
            '4px': 'border-4',
            '8px': 'border-8',
        };

        return borderWidthClasses[normalized] ?? `border-[${normalized}]`;
    }

    if (property === 'border-top-width') {
        const borderTopWidthClasses = {
            0: 'border-t-0',
            '0px': 'border-t-0',
            '1px': 'border-t',
            '2px': 'border-t-2',
            '4px': 'border-t-4',
            '8px': 'border-t-8',
        };

        return borderTopWidthClasses[normalized] ?? `border-t-[${normalized}]`;
    }

    if (property === 'border-right-width') {
        const borderRightWidthClasses = {
            0: 'border-r-0',
            '0px': 'border-r-0',
            '1px': 'border-r',
            '2px': 'border-r-2',
            '4px': 'border-r-4',
            '8px': 'border-r-8',
        };

        return borderRightWidthClasses[normalized] ?? `border-r-[${normalized}]`;
    }

    if (property === 'border-bottom-width') {
        const borderBottomWidthClasses = {
            0: 'border-b-0',
            '0px': 'border-b-0',
            '1px': 'border-b',
            '2px': 'border-b-2',
            '4px': 'border-b-4',
            '8px': 'border-b-8',
        };

        return borderBottomWidthClasses[normalized] ?? `border-b-[${normalized}]`;
    }

    if (property === 'border-left-width') {
        const borderLeftWidthClasses = {
            0: 'border-l-0',
            '0px': 'border-l-0',
            '1px': 'border-l',
            '2px': 'border-l-2',
            '4px': 'border-l-4',
            '8px': 'border-l-8',
        };

        return borderLeftWidthClasses[normalized] ?? `border-l-[${normalized}]`;
    }

    if (property === 'border-color') {
        const borderColorClasses = {
            currentColor: 'border-current',
            white: 'border-white',
            black: 'border-black',
            '#ffffff': 'border-white',
            '#000000': 'border-black',
            '#c8a96a': 'border-[#c8a96a]',
        };

        return borderColorClasses[normalized] ?? borderColorClasses[normalized.toLowerCase()] ?? null;
    }

    if (property === 'padding') {
        return maps.padding[normalized] ?? `p-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'border-radius') {
        return maps['border-radius'][normalized] ?? `rounded-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'border-top-left-radius') {
        const borderTopLeftRadiusClasses = {
            0: 'rounded-tl-none',
            '0px': 'rounded-tl-none',
            '2px': 'rounded-tl-sm',
            '4px': 'rounded-tl',
            '6px': 'rounded-tl-md',
            '8px': 'rounded-tl-lg',
            '12px': 'rounded-tl-xl',
            '16px': 'rounded-tl-2xl',
            '24px': 'rounded-tl-3xl',
            '32px': 'rounded-tl-4xl',
            '9999px': 'rounded-tl-full',
        };

        return borderTopLeftRadiusClasses[normalized] ?? `rounded-tl-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'border-top-right-radius') {
        const borderTopRightRadiusClasses = {
            0: 'rounded-tr-none',
            '0px': 'rounded-tr-none',
            '2px': 'rounded-tr-sm',
            '4px': 'rounded-tr',
            '6px': 'rounded-tr-md',
            '8px': 'rounded-tr-lg',
            '12px': 'rounded-tr-xl',
            '16px': 'rounded-tr-2xl',
            '24px': 'rounded-tr-3xl',
            '32px': 'rounded-tr-4xl',
            '9999px': 'rounded-tr-full',
        };

        return borderTopRightRadiusClasses[normalized] ?? `rounded-tr-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'border-bottom-right-radius') {
        const borderBottomRightRadiusClasses = {
            0: 'rounded-br-none',
            '0px': 'rounded-br-none',
            '2px': 'rounded-br-sm',
            '4px': 'rounded-br',
            '6px': 'rounded-br-md',
            '8px': 'rounded-br-lg',
            '12px': 'rounded-br-xl',
            '16px': 'rounded-br-2xl',
            '24px': 'rounded-br-3xl',
            '32px': 'rounded-br-4xl',
            '9999px': 'rounded-br-full',
        };

        return borderBottomRightRadiusClasses[normalized] ?? `rounded-br-[${arbitraryValue(normalized)}]`;
    }

    if (property === 'border-bottom-left-radius') {
        const borderBottomLeftRadiusClasses = {
            0: 'rounded-bl-none',
            '0px': 'rounded-bl-none',
            '2px': 'rounded-bl-sm',
            '4px': 'rounded-bl',
            '6px': 'rounded-bl-md',
            '8px': 'rounded-bl-lg',
            '12px': 'rounded-bl-xl',
            '16px': 'rounded-bl-2xl',
            '24px': 'rounded-bl-3xl',
            '32px': 'rounded-bl-4xl',
            '9999px': 'rounded-bl-full',
        };

        return borderBottomLeftRadiusClasses[normalized] ?? `rounded-bl-[${arbitraryValue(normalized)}]`;
    }

    return maps[property]?.[normalized]
        ?? maps[property]?.[normalized.toLowerCase()]
        ?? arbitraryPropertyTailwindClass(property, normalized);
}

function replaceTailwindClassOverride(attributes, attribute, value, breakpoint = '') {
    const mutation = builderStyleMutation(attribute, value);
    const compressedAxisSpec = compressedAxisOverrideSpec({ ...attributes, [attribute]: value }, attribute, value);
    const className = compressedAxisSpec?.className ?? mutation?.className;
    if (!mutation?.target) {
        return false;
    }

    attributes[attribute] = mutation.value;

    if (className === null) {
        attributes[ builderPresetAttribute(mutation.target)] = 'custom';

        return true;
    }

    const classAttribute =  builderClassAttribute(mutation.target);
    const groups = compressedAxisSpec?.groups || mutation.groups;
    const currentClasses = String(attributes[classAttribute] || '')
        .split(/\s+/)
        .filter(Boolean)
        .filter((item) => {
            const isOverrideClass = item.startsWith('!') || item.endsWith('!');

            return !(isOverrideClass && tailwindClassMatchesGroup(item, groups, breakpoint));
        });

    if (className) {
        currentClasses.push(...importantTailwindClasses(responsiveTailwindClassName(className, breakpoint)));
    }

    attributes[classAttribute] = currentClasses.join(' ');
    attributes[ builderPresetAttribute(mutation.target)] = 'custom';

    return true;
}

function importantTailwindClasses(className) {
    return String(className)
        .split(/\s+/)
        .filter(Boolean)
        .map((item) => item.endsWith('!') ? item : `${item}!`);
}

function equivalentInspectorValue(left, right) {
    const leftColor = cssColorComparableValue(left);
    const rightColor = cssColorComparableValue(right);
    if (leftColor || rightColor) {
        return equivalentColorComparableValue(leftColor, rightColor);
    }

    return String(left ?? '').trim() === String(right ?? '').trim();
}

function backgroundOverrideSet(attributes) {
    return new Set(String(attributes?.[ builderBackgroundOverridesAttribute] || '').split(/\s+/).filter(Boolean));
}

function updateBackgroundOverride(attributes, attribute, enabled) {
    if (!Object.hasOwn( builderBackgroundDefaults, attribute)) {
        return;
    }

    const overrides = backgroundOverrideSet(attributes);
    if (enabled) {
        overrides.add(attribute);
    } else {
        overrides.delete(attribute);
    }
    attributes[ builderBackgroundOverridesAttribute] = [...overrides].join(' ');
}

function inspectorBackgroundFieldState(attributes, attribute) {
    if (!Object.hasOwn( builderBackgroundDefaults, attribute)) {
        return null;
    }

    const overrides = backgroundOverrideSet(attributes);
    const value = attributes?.[attribute] ?? '';
    const isDefined = attribute === 'data-overlay'
        ? value !== 'none'
        : String(value ?? '').trim() !== '';

    return [
        isDefined ? 'defined' : '',
        overrides.has(attribute) ? 'override' : '',
    ].filter(Boolean).join(' ') || null;
}

function inheritedBuilderAttributeValue(attributes, attribute) {
    const parts = builderStyleAttributeParts(attribute);
    if (!parts) {
        return null;
    }

    const inheritedValue = inheritedStyleAttributeValue(attributes || {}, parts.target, parts.property);
    if (inheritedValue !== null) {
        return inheritedValue;
    }

    return null;
}

function builderAttributeHasTailwindClassValue(attribute, value) {
    if (value === undefined || value === null || value === '') {
        return false;
    }

    return tailwindClassFromBuilderAttribute(attribute, value) !== null;
}

function builderAttributeHasPendingTailwindOverride(attributes, attribute, value, breakpoint = activeInspectorBreakpoint) {
    if (!builderAttributeHasTailwindClassValue(attribute, value) || !isBuilderStyleOverride(attribute, value)) {
        return false;
    }
    if (!Object.hasOwn(attributes || {}, attribute)) {
        return false;
    }

    const parts = builderStyleAttributeParts(attribute);
    if (!parts) {
        return false;
    }

    const effectiveValue = styleAttributeClassValue(attributes || {}, parts.target, parts.property, breakpoint)
        ?? inheritedStyleAttributeValue(attributes || {}, parts.target, parts.property, breakpoint)
        ?? '';

    return !equivalentInspectorValue(effectiveValue, value);
}

function removeImportantClassOverrides(attributes, attribute, breakpoint = '', options = {}) {
    const target = targetFromBuilderAttribute(attribute);
    const classAttribute = target ?  builderClassAttribute(target) : '';
    const groups = classGroupFromBuilderAttribute(attribute);
    if (!classAttribute || !groups.length) {
        return false;
    }

    let removed = false;
    const classes = String(attributes[classAttribute] || '')
        .split(/\s+/)
        .filter(Boolean);
    let nextClasses = classes.filter((className) => {
            const isOverrideClass = className.startsWith('!') || className.endsWith('!');
            const isDirectMatch = isOverrideClass && tailwindClassMatchesGroup(className, groups, breakpoint);
            if (isDirectMatch) {
                removed = true;
            }

            return !isDirectMatch;
        });

    if (!removed && options.includeInherited === true) {
        const inheritedBreakpoints = applicableBreakpointsForInspector(breakpoint)
            .filter((item) => item !== breakpoint)
            .reverse();
        const inheritedBreakpoint = inheritedBreakpoints.find((item) => nextClasses.some((className) => {
            const isOverrideClass = className.startsWith('!') || className.endsWith('!');

            return isOverrideClass && tailwindClassMatchesGroup(className, groups, item);
        }));

        if (inheritedBreakpoint !== undefined) {
            nextClasses = nextClasses.filter((className) => {
                const isOverrideClass = className.startsWith('!') || className.endsWith('!');
                const isInheritedMatch = isOverrideClass && tailwindClassMatchesGroup(className, groups, inheritedBreakpoint);
                if (isInheritedMatch) {
                    removed = true;
                }

                return !isInheritedMatch;
            });
        }
    }

    attributes[classAttribute] = nextClasses.join(' ');

    return removed;
}

function applyBuilderStyleValuesToAttributes(attributes, values, breakpoint = '') {
    Object.entries(values || {}).forEach(([attribute, rawValue]) => {
        const mutation = builderStyleMutation(attribute, rawValue);
        if (!mutation) {
            attributes[attribute] = rawValue;

            return;
        }

        const inheritedValue = inheritedBuilderAttributeValue(attributes, attribute);
        if (inheritedValue !== null && equivalentInspectorValue(mutation.value, inheritedValue)) {
            removeImportantClassOverrides(attributes, attribute, breakpoint);
            const { defaults, property } = defaultsFromBuilderAttribute(attribute);
            attributes[attribute] = defaults[property] ?? '';

            return;
        }

        replaceTailwindClassOverride(attributes, attribute, mutation.value, breakpoint);
    });

    return attributes;
}

function siblingBuilderStyleAttribute(attribute, siblingProperty) {
    const parts = builderStyleAttributeParts(attribute);
    if (!parts) {
        return '';
    }

    if (parts.type === 'layout') {
        return  builderLayoutAttribute(parts.target, siblingProperty);
    }

    if (parts.type === 'flex') {
        return  builderFlexChildAttribute(parts.target, siblingProperty);
    }

    return  builderStyleAttribute(parts.target, siblingProperty);
}

function dependentBuilderAttributesForReset(attribute) {
    const parts = builderStyleAttributeParts(attribute);
    if (!parts) {
        return [];
    }

    const attributeForProperty = (property) => {
        if (parts.type === 'layout') {
            return  builderLayoutAttribute(parts.target, property);
        }

        if (parts.type === 'flex') {
            return  builderFlexChildAttribute(parts.target, property);
        }

        return  builderStyleAttribute(parts.target, property);
    };
    const propertyDependencies = {
        display: [
            'flex-direction',
            'justify-content',
            'align-items',
            'gap',
            'flex-wrap',
            'grid-template-columns',
            'grid-template-rows',
            'grid-auto-flow',
            'justify-items',
            'align-content',
            'place-items',
            'place-content',
        ],
        position: ['top', 'right', 'bottom', 'left'],
        padding: ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'],
        margin: ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'],
        'border-radius': [
            'border-top-left-radius',
            'border-top-right-radius',
            'border-bottom-right-radius',
            'border-bottom-left-radius',
        ],
        'border-width': [
            'border-top-width',
            'border-right-width',
            'border-bottom-width',
            'border-left-width',
        ],
        'border-style': [
            'border-top-style',
            'border-right-style',
            'border-bottom-style',
            'border-left-style',
        ],
        'border-color': [
            'border-top-color',
            'border-right-color',
            'border-bottom-color',
            'border-left-color',
        ],
    };

    return (propertyDependencies[parts.property] || []).map(attributeForProperty);
}

function axisTailwindClassForBuilderValue(prefix, property, value) {
    const normalized = String(value ?? '').trim();
    if (!normalized) {
        return '';
    }

    if (property.startsWith('margin') && normalized === 'auto') {
        return `${prefix}-auto`;
    }

    if (normalized.startsWith('tw:')) {
        const token = normalized.slice(3);
        if (token === 'auto' && property.startsWith('margin')) {
            return `${prefix}-auto`;
        }

        return knownSpacingClass(prefix, normalized);
    }

    return knownSpacingClass(prefix, normalized);
}

function compressedAxisOverrideSpec(attributes, attribute, value) {
    const property = builderStyleAttributeParts(attribute)?.property || '';
    const pairMap = {
        'padding-left': { sibling: 'padding-right', prefix: 'px' },
        'padding-right': { sibling: 'padding-left', prefix: 'px' },
        'padding-top': { sibling: 'padding-bottom', prefix: 'py' },
        'padding-bottom': { sibling: 'padding-top', prefix: 'py' },
        'margin-left': { sibling: 'margin-right', prefix: 'mx' },
        'margin-right': { sibling: 'margin-left', prefix: 'mx' },
        'margin-top': { sibling: 'margin-bottom', prefix: 'my' },
        'margin-bottom': { sibling: 'margin-top', prefix: 'my' },
    };
    const pair = pairMap[property];
    if (!pair || !String(value ?? '').trim()) {
        return null;
    }

    const siblingAttribute = siblingBuilderStyleAttribute(attribute, pair.sibling);
    const siblingValue = siblingAttribute ? attributes?.[siblingAttribute] : '';
    if (!siblingAttribute || !String(siblingValue ?? '').trim() || !equivalentInspectorValue(value, siblingValue)) {
        return null;
    }

    const className = axisTailwindClassForBuilderValue(pair.prefix, property, value);
    if (!className) {
        return null;
    }

    return {
        className,
        groups: [...new Set([
            ...classGroupFromBuilderAttribute(attribute),
            ...classGroupFromBuilderAttribute(siblingAttribute),
        ])],
    };
}

function inspectorControlState(attributes, attribute, value, breakpoint = activeInspectorBreakpoint) {
    if (Object.hasOwn( builderBackgroundDefaults, attribute)) {
        return inspectorBackgroundFieldState(attributes, attribute);
    }

    const parts = builderStyleAttributeParts(attribute);
    if (!parts) {
        return null;
    }

    const groups = classGroupFromBuilderAttribute(attribute);
    const classAttribute =  builderClassAttribute(parts.target);
    const hasClassOverride = String(attributes?.[classAttribute] || '')
        .split(/\s+/)
        .filter(Boolean)
        .some((className) => {
            const parsed = tailwindClassParts(className);

            return (className.startsWith('!') || className.endsWith('!'))
                && parsed.breakpoint === breakpoint
                && tailwindClassMatchesGroup(className, groups, parsed.breakpoint);
        });
    const actualValue = attributes?.[attribute];
    const hasStyleOverride = actualValue !== undefined
        && actualValue !== null
        && actualValue !== ''
        && isBuilderStyleOverride(attribute, actualValue)
        && !builderAttributeHasTailwindClassValue(attribute, actualValue);
    const hasPendingTailwindOverride = !hasClassOverride
        && builderAttributeHasPendingTailwindOverride(attributes, attribute, value, breakpoint);
    const inheritedValue = inheritedStyleAttributeValue(attributes || {}, parts.target, parts.property, breakpoint);
    const explicitValue = parts.target === 'dom' && parts.type === 'flex'
        ? builderClassOverrideValue(attributes || {}, parts.target, parts.property, breakpoint, { includeImportant: false })
        : null;
    const hasDefinedValue = parts.target === 'dom'
        ? (parts.type === 'flex' ? explicitValue !== null && explicitValue !== '' : inheritedValue !== null)
        : inheritedValue !== null;

    if ((hasStyleOverride || hasClassOverride || hasPendingTailwindOverride) && hasDefinedValue) {
        return 'defined override';
    }

    if (hasStyleOverride || hasClassOverride || hasPendingTailwindOverride) {
        return 'override';
    }

    if (hasDefinedValue) {
        return 'defined';
    }

    return null;
}

function inspectorLabel(label, attribute, state = null, actions = '') {
    const states = String(state || '').split(/\s+/).filter(Boolean);
    const hasOverride = states.includes('override');
    const resetButton = hasOverride
        ? `<button type="button" class="viti-builder-property-reset" title="Annuler la surcharge" aria-label="Annuler la surcharge"><span data-builder-reset-attribute="${escapeHtml(attribute)}">×</span></button>`
        : '';
    const dots = [
        states.includes('defined') ? '<i class="viti-builder-property-dot is-defined-dot" aria-hidden="true"></i>' : '',
        hasOverride ? '<i class="viti-builder-property-dot is-override-dot" aria-hidden="true"></i>' : '',
    ].join('');

    return `
        <span class="viti-builder-property-label${states.map((item) => ` is-${escapeHtml(item)}`).join('')}">
            <span class="viti-builder-property-label__main">
                <span>${escapeHtml(label)}</span>
                ${dots}
            </span>
            ${resetButton}
            ${actions ? `<span class="viti-builder-property-actions">${actions}</span>` : ''}
        </span>
    `;
}

function inspectorLabelDisplayOnly(label, state = null) {
    const states = String(state || '').split(/\s+/).filter(Boolean);
    const dots = [
        states.includes('defined') ? '<i class="viti-builder-property-dot is-defined-dot" aria-hidden="true"></i>' : '',
        states.includes('override') ? '<i class="viti-builder-property-dot is-override-dot" aria-hidden="true"></i>' : '',
    ].join('');

    return `
        <span class="viti-builder-property-label${states.map((item) => ` is-${escapeHtml(item)}`).join('')}">
            <span class="viti-builder-property-label__main">
                <span>${escapeHtml(label)}</span>
                ${dots}
            </span>
        </span>
    `;
}

function inspectorField(label, attribute, value, type = 'number', className = '', state = null) {
    const minimum = type === 'number' ? minimumValueForAttribute(attribute) : null;
    const inputValue = value === '-' ? '' : value;
    const placeholder = value === '-' || value === '' || value === null || value === undefined ? ' placeholder="-"' : '';

    return `
        <label class="viti-builder-inspector-field ${escapeHtml(className)}">
            ${inspectorLabel(label, attribute, state)}
            <input type="${type}" value="${escapeHtml(inputValue)}"${placeholder} data-builder-layer-attribute="${escapeHtml(attribute)}"${minimum !== null ? ` min="${escapeHtml(minimum)}"` : ''}>
        </label>
    `;
}

function inspectorTextField(label, attribute, value, className = '', state = null) {
    return inspectorField(label, attribute, value, 'text', className, state);
}

function hexColorValue(value) {
    const normalized = String(value || '').trim();
    if (!normalized) {
        return '#000000';
    }

    if (/^(?:currentcolor|transparent|inherit)$/i.test(normalized)) {
        return '#000000';
    }

    return rgbaToHex(normalized);
}

function inspectorColorField(label, attribute, value, fallback = 'currentColor', state = null, options = {}) {
    const resolvedValue = value || '';
    const placeholder = fallback || '-';
    const disabled = options.disabled ? ' disabled' : '';
    const pickerValue = resolvedValue || fallback || 'transparent';

    return `
        <label class="viti-builder-inspector-field viti-builder-color-field">
            ${inspectorLabel(label, attribute, state)}
            <span class="viti-builder-color-control">
                <input type="text" value="${escapeHtml(resolvedValue)}" placeholder="${escapeHtml(placeholder)}" data-builder-layer-attribute="${escapeHtml(attribute)}"${disabled}>
                <span class="viti-builder-gradient-color-picker">
                    <button type="button" data-builder-color-for="${escapeHtml(attribute)}" style="--builder-pickr-color: ${escapeHtml(pickerValue)};" aria-label="Modifier la couleur"${disabled}></button>
                    <span data-builder-color-label="${escapeHtml(attribute)}">${escapeHtml(pickerValue)}</span>
                </span>
            </span>
        </label>
    `;
}

function inspectorTextarea(label, attribute, value) {
    return `
        <label class="viti-builder-inspector-field">
            <span>${escapeHtml(label)}</span>
            <textarea rows="5" data-builder-layer-attribute="${escapeHtml(attribute)}">${escapeHtml(value)}</textarea>
        </label>
    `;
}

function inspectorSelect(label, attribute, value, options, state = null, fieldOptions = {}) {
    const hasValue = Object.hasOwn(options, String(value));
    const disabled = fieldOptions.disabled ? ' disabled' : '';
    const optionMarkup = [
        hasValue ? '' : `<option value="${escapeHtml(value)}" selected>${value === '' ? '-' : `Personnalisé (${escapeHtml(value)})`}</option>`,
        ...Object.entries(options).map(([id, option]) => `
        <option value="${escapeHtml(id)}"${id === value ? ' selected' : ''}>${escapeHtml(option.label)}</option>
    `),
    ].join('');

    return `
        <label class="viti-builder-inspector-field">
            ${inspectorLabel(label, attribute, state)}
            <span class="viti-builder-control">
                <select data-builder-layer-attribute="${escapeHtml(attribute)}"${disabled}>${optionMarkup}</select>
            </span>
        </label>
    `;
}

function inspectorCssValue(label, attribute, value, units =  builderCssValueUnits, state = null, options = {}) {
    const parsed = splitCssValue(value);
    const effectiveUnit = parsed.unit || (parsed.number !== '' && /^-?\d*\.?\d+$/.test(String(parsed.number)) ? cssValueDefaultUnit(attribute, units) : '');
    const normalizedUnits = units.includes(effectiveUnit) ? units : [...units, effectiveUnit];
    const hasNumericInputValue = parsed.number !== '' && /^-?\d*\.?\d+$/.test(String(parsed.number)) && effectiveUnit !== 'tw';
    const disabled = options.disabled ? ' disabled' : '';
    const tailwindPresetOnly = !!options.tailwindPresetOnly;
    const tokenOptions = options.tokenOptions || cssTokenOptionsForBuilderAttribute(attribute);
    const tokenEntries = orderedTokenOptionEntries(tokenOptions);
    const hasTokenSelect = tokenOptions && normalizedUnits.includes('tw');
    const hasTailwindTextInput = effectiveUnit === 'tw';
    const inputPlaceholder = options.placeholder ?? (parsed.number === '' ? '-' : (hasNumericInputValue || hasTailwindTextInput ? '' : parsed.number));
    const inputValue = options.value ?? (
        hasNumericInputValue
            ? clampBuilderValue(attribute, parsed.number)
            : (hasTailwindTextInput ? parsed.number : '')
    );
    const hasKnownTokenValue = Object.hasOwn(tokenOptions || {}, parsed.number);
    const fallbackTokenValue = tokenEntries[0]?.[0] || '';
    const customModeActive = !tailwindPresetOnly && hasTokenSelect && effectiveUnit === 'tw' && !hasKnownTokenValue;
    const tokenValue = hasKnownTokenValue ? parsed.number : fallbackTokenValue;
    const unitOptions = normalizedUnits.map((unit) => `
        <option value="${escapeHtml(unit)}"${unit === effectiveUnit ? ' selected' : ''}>${escapeHtml(unit || '-')}</option>
    `).join('');
    const tokenSelect = hasTokenSelect ? `
                <select class="viti-builder-css-token${effectiveUnit !== 'tw' || customModeActive ? ' is-hidden' : ''}" data-builder-layer-css-token="${escapeHtml(attribute)}"${disabled}>
                    ${tokenEntries.map(([id, option]) => `
                        <option value="${escapeHtml(id)}"${id === tokenValue ? ' selected' : ''}>${escapeHtml(option.label)}</option>
                    `).join('')}
                </select>
    ` : '';
    const modeToggle = hasTokenSelect && !tailwindPresetOnly ? `
        <span class="viti-builder-css-mode-toggle${effectiveUnit !== 'tw' ? ' is-hidden' : ''}" data-builder-layer-css-mode-group="${escapeHtml(attribute)}">
            <button
                type="button"
                class="viti-builder-css-mode-toggle__button${customModeActive ? ' is-active' : ''}"
                data-builder-layer-css-mode="${escapeHtml(attribute)}"
                data-builder-layer-css-mode-value="${customModeActive ? 'custom' : 'preset'}"
                aria-pressed="${customModeActive ? 'true' : 'false'}"
                aria-label="${customModeActive ? 'Passer en mode Preset' : 'Passer en mode Custom'}"
                ${disabled}
            >${customModeActive ? 'C' : 'P'}</button>
        </span>
    ` : '';

    return `
        <label class="viti-builder-inspector-field">
            ${inspectorLabel(label, attribute, state, modeToggle)}
            <span class="viti-builder-css-value${hasTokenSelect ? ' has-token-toggle' : ''}" data-builder-css-mode="${customModeActive ? 'custom' : 'preset'}"${tailwindPresetOnly ? ' data-builder-css-preset-only="true"' : ''}>
                <input class="${hasTokenSelect && effectiveUnit === 'tw' && (!customModeActive || tailwindPresetOnly) ? ' is-hidden' : ''}" type="text" value="${escapeHtml(tailwindPresetOnly && effectiveUnit === 'tw' ? tokenValue : inputValue)}" placeholder="${escapeHtml(inputPlaceholder)}" data-builder-layer-css-value="${escapeHtml(attribute)}"${disabled}>
                ${tokenSelect}
                <select data-builder-layer-css-unit="${escapeHtml(attribute)}"${disabled}>${unitOptions}</select>
            </span>
        </label>
    `;
}

function inspectorCssPresetValue(label, attribute, value, presetOptions, state = null, options = {}) {
    const normalizedValue = String(value ?? '').trim();
    const tokenOptions = presetOptions || {};
    const tokenEntries = orderedTokenOptionEntries(tokenOptions);
    const hasKnownTokenValue = Object.hasOwn(tokenOptions, normalizedValue);
    const customModeActive = !hasKnownTokenValue && normalizedValue !== '';
    const tokenValue = hasKnownTokenValue ? normalizedValue : (tokenEntries[0]?.[0] || '');
    const inputValue = options.value ?? (customModeActive ? normalizedValue : tokenValue);
    const inputPlaceholder = options.placeholder ?? '-';
    const disabled = options.disabled ? ' disabled' : '';
    const modeToggle = `
        <span class="viti-builder-css-mode-toggle" data-builder-layer-css-mode-group="${escapeHtml(attribute)}">
            <button
                type="button"
                class="viti-builder-css-mode-toggle__button${customModeActive ? ' is-active' : ''}"
                data-builder-layer-css-mode="${escapeHtml(attribute)}"
                data-builder-layer-css-mode-value="${customModeActive ? 'custom' : 'preset'}"
                aria-pressed="${customModeActive ? 'true' : 'false'}"
                aria-label="${customModeActive ? 'Passer en mode Preset' : 'Passer en mode Custom'}"
                ${disabled}
            >${customModeActive ? 'C' : 'P'}</button>
        </span>
    `;

    return `
        <label class="viti-builder-inspector-field">
            ${inspectorLabel(label, attribute, state, modeToggle)}
            <span class="viti-builder-css-value is-preset-text" data-builder-css-kind="preset-text" data-builder-css-mode="${customModeActive ? 'custom' : 'preset'}">
                <input class="${customModeActive ? '' : ' is-hidden'}" type="text" value="${escapeHtml(inputValue)}" placeholder="${escapeHtml(inputPlaceholder)}" data-builder-layer-css-value="${escapeHtml(attribute)}"${disabled}>
                <select class="viti-builder-css-token${customModeActive ? ' is-hidden' : ''}" data-builder-layer-css-token="${escapeHtml(attribute)}"${disabled}>
                    ${tokenEntries.map(([id, option]) => `
                        <option value="${escapeHtml(id)}"${id === tokenValue ? ' selected' : ''}>${escapeHtml(option.label)}</option>
                    `).join('')}
                </select>
            </span>
        </label>
    `;
}

function inspectorSegmented(label, attribute, value, options, state = null) {
    const buttons = Object.entries(options).map(([id, option]) => `
        <button
            type="button"
            class="${String(value) === id ? 'is-active' : ''}"
            data-builder-layer-attribute="${escapeHtml(attribute)}"
            data-builder-layer-value="${escapeHtml(id)}"
            title="${escapeHtml(option.label)}"
            aria-label="${escapeHtml(option.label)}"
        >${option.icon ? `<span class="viti-builder-segmented-icon"><img src="${escapeHtml(option.icon)}" alt="" aria-hidden="true"></span>` : escapeHtml(option.label)}</button>
    `).join('');

    return `
        <div class="viti-builder-inspector-field">
            ${inspectorLabel(label, attribute, state)}
            <div class="viti-builder-segmented">${buttons}</div>
        </div>
    `;
}

function inspectorSectionKey(id) {
    const normalizedId = String(id || '');
    const genericKeys = [
        'flex-child',
        'typography',
        'background',
        'position',
        'content',
        'borders',
        'effects',
        'layout',
        'space',
        'size',
        'link',
    ];

    return genericKeys.find((key) => normalizedId === key || normalizedId.endsWith(`-${key}`)) || normalizedId;
}

function inspectorSectionState(body) {
    const value = String(body);
    const states = [
        /viti-builder-property-label[^"]*\bis-defined\b/.test(value) ? 'defined' : '',
        /viti-builder-property-label[^"]*\bis-override\b/.test(value) ? 'override' : '',
    ].filter(Boolean);

    return states.join(' ') || null;
}

function inspectorSection(id, title, body, accent = false, explicitState = undefined) {
    const state = explicitState === undefined ? inspectorSectionState(body) : explicitState;
    const states = String(state || '').split(/\s+/).filter(Boolean);
    const dots = [
        states.includes('defined') ? '<i class="is-defined-dot" aria-hidden="true"></i>' : '',
        states.includes('override') ? '<i class="is-override-dot" aria-hidden="true"></i>' : '',
    ].join('');

    return `
        <details class="viti-builder-style-section" data-builder-inspector-section="${escapeHtml(id)}" data-builder-inspector-section-key="${escapeHtml(inspectorSectionKey(id))}">
            <summary>
                <span class="viti-builder-section-label${states.map((item) => ` is-${escapeHtml(item)}`).join('')}">
                    <span>${escapeHtml(title)}</span>
                    ${dots}
                </span>
            </summary>
            <div class="viti-builder-style-section__body">${body}</div>
        </details>
    `;
}

function inspectorGrid(...items) {
    return `<div class="viti-builder-inspector-grid">${items.join('')}</div>`;
}

function inspectorActionRow(...items) {
    return `<div class="viti-builder-inspector-actions">${items.join('')}</div>`;
}

function inspectorIconButton(label, icon, attribute = '', value = '') {
    const data = attribute ? ` data-builder-layer-attribute="${escapeHtml(attribute)}" data-builder-layer-value="${escapeHtml(value)}"` : '';

    return `<button type="button" class="viti-builder-icon-button" title="${escapeHtml(label)}" aria-label="${escapeHtml(label)}"${data}><img src="${escapeHtml(icon)}" alt="" aria-hidden="true"></button>`;
}

function styleAttributeValue(attributes, layer, property, fallback = '') {
    const attribute =  builderStyleAttribute(layer, property);
    const value = attributes[attribute];

    if (value !== undefined && value !== null && value !== '') {
        return value;
    }

    const classValue = styleAttributeClassValue(attributes, layer, property);

    return classValue === null ? fallback : classValue;
}

function inspectorStyleValue(attributes, layer, property) {
    const attribute =  builderStyleAttribute(layer, property);
    const value = attributes[attribute];

    if (value !== undefined
        && value !== null
        && value !== ''
        && isBuilderStyleOverride(attribute, value)
        && !builderAttributeHasTailwindClassValue(attribute, value)) {
        return value;
    }

    if (property === 'background-clip') {
        const explicitValue = explicitClassAttributeValue(attributes, layer, property);

        return explicitValue !== null && explicitValue !== ''
            ? explicitValue
            : (value ?? '');
    }

    const classValue = styleAttributeClassValue(attributes, layer, property, activeInspectorBreakpoint);
    if (classValue !== null && classValue !== '') {
        return classValue;
    }

    return value ?? '';
}

function inspectorLayoutValue(attributes, target, property) {
    const attribute =  builderLayoutAttribute(target, property);
    const value = attributes[attribute];

    if (value !== undefined
        && value !== null
        && value !== ''
        && isBuilderStyleOverride(attribute, value)
        && !builderAttributeHasTailwindClassValue(attribute, value)) {
        return value;
    }

    const classValue = styleAttributeClassValue(attributes, target, property, activeInspectorBreakpoint);
    if (classValue !== null && classValue !== '') {
        return classValue;
    }

    return value ?? '';
}

function inspectorStyleSelect(label, layer, property, attributes, options, fallback = '', fieldOptions = {}) {
    const attribute =  builderStyleAttribute(layer, property);
    const value = inspectorStyleValue(attributes, layer, property);

    return inspectorSelect(label, attribute, value, options, inspectorControlState(attributes, attribute, value), fieldOptions);
}

function inspectorStyleText(label, layer, property, attributes, fallback = '', className = '') {
    const attribute =  builderStyleAttribute(layer, property);
    const value = inspectorStyleValue(attributes, layer, property);

    return inspectorTextField(label, attribute, value, className, inspectorControlState(attributes, attribute, value));
}

function inspectorStyleColor(label, layer, property, attributes, fallback = 'currentColor', fieldOptions = {}) {
    const attribute =  builderStyleAttribute(layer, property);
    const value = inspectorStyleValue(attributes, layer, property);

    return inspectorColorField(label, attribute, value, fallback, inspectorControlState(attributes, attribute, value), fieldOptions);
}

function inspectorStyleCssValue(label, layer, property, attributes, units =  builderCssValueUnits, fallback = '', options = {}) {
    const attribute =  builderStyleAttribute(layer, property);
    const value = inspectorStyleValue(attributes, layer, property);

    return inspectorCssValue(label, attribute, value, units, inspectorControlState(attributes, attribute, value), options);
}

function inspectorStyleSegmented(label, layer, property, attributes, options, fallback = '') {
    const attribute =  builderStyleAttribute(layer, property);
    const value = inspectorStyleValue(attributes, layer, property);

    return inspectorSegmented(label, attribute, value, options, inspectorControlState(attributes, attribute, value));
}

function isFlexDisplay(value) {
    return value === 'flex' || value === 'inline-flex';
}

function isGridDisplay(value) {
    return value === 'grid';
}

function inspectorGridLayoutControls(attributeResolver, valueResolver) {
    return [
        ['Columns', 'grid-template-columns', '', 'preset-css',  builderGridTemplateColumnsPresetOptions],
        ['Rows', 'grid-template-rows', '', 'preset-css',  builderGridTemplateRowsPresetOptions],
        ['Auto flow', 'grid-auto-flow', 'row', 'segmented',  builderGridAutoFlowOptions],
        ['Justify items', 'justify-items', 'stretch', 'segmented',  builderJustifyItemsOptions],
        ['Align content', 'align-content', 'stretch', 'segmented',  builderAlignContentOptions],
        ['Place items', 'place-items', 'stretch', 'segmented',  builderPlaceItemsOptions],
        ['Place content', 'place-content', 'stretch', 'segmented',  builderPlaceContentOptions],
        ['Gap', 'gap', '0', 'css'],
    ].map(([label, property, fallback, type, options]) => {
        const attribute = attributeResolver(property);
        const value = valueResolver(property, fallback);
        const state = inspectorControlState(valueResolver.attributes || {}, attribute, value);

        if (type === 'select') {
            return inspectorSelect(label, attribute, value, options, state);
        }

        if (type === 'segmented') {
            return inspectorSegmented(label, attribute, value, options, state);
        }

        if (type === 'preset-css') {
            return inspectorCssPresetValue(label, attribute, value, options, state);
        }

        return inspectorCssValue(label, attribute, value,  builderCssValueUnits, state);
    });
}

function layoutValueResolver(attributes, attributeResolver) {
    const resolver = (property, fallback) => {
        const attribute = attributeResolver(property);
        const target = targetFromBuilderAttribute(attribute);

        return target ? inspectorLayoutValue(attributes, target, property) : '';
    };
    resolver.attributes = attributes;

    return resolver;
}

function styleValueResolver(attributes, layer) {
    const resolver = (property, fallback) => inspectorStyleValue(attributes, layer, property);
    resolver.attributes = attributes;

    return resolver;
}

function inspectorLayoutSelect(label, target, property, attributes, options, fallback = '') {
    const attribute =  builderLayoutAttribute(target, property);
    const value = inspectorLayoutValue(attributes, target, property);

    return inspectorSelect(label, attribute, value, options, inspectorControlState(attributes, attribute, value));
}

function inspectorLayoutCssValue(label, target, property, attributes, units =  builderCssValueUnits, fallback = '') {
    const attribute =  builderLayoutAttribute(target, property);
    const value = inspectorLayoutValue(attributes, target, property);

    return inspectorCssValue(label, attribute, value, units, inspectorControlState(attributes, attribute, value));
}

function inspectorLayoutSegmented(label, target, property, attributes, options, fallback = '') {
    const attribute =  builderLayoutAttribute(target, property);
    const value = inspectorLayoutValue(attributes, target, property);

    return inspectorSegmented(label, attribute, value, options, inspectorControlState(attributes, attribute, value));
}

function inspectorFlexSegmented(label, layer, property, attributes, value, options) {
    const attribute =  builderFlexChildAttribute(layer, property);

    return inspectorSegmented(label, attribute, value, options, inspectorControlState(attributes, attribute, value));
}

function inspectorFlexOrder(layer, attributes, value) {
    const attribute =  builderFlexChildAttribute(layer, 'order');

    return inspectorCssValue('Order', attribute, value || '', [''], inspectorControlState(attributes, attribute, value));
}

function parentDisplayForLayer(layer, attributes, parentAttributes = null) {
    if (layer === 'dom' && parentAttributes) {
        return inspectorStyleValue(parentAttributes, 'dom', 'display');
    }

    if (layer === 'container') {
        return inspectorStyleValue(attributes, 'section', 'display');
    }

    if (['primaryButton', 'secondaryButton'].includes(layer)) {
        return inspectorLayoutValue(attributes, 'buttons', 'display');
    }

    if (['eyebrow', 'heading', 'text', 'buttons'].includes(layer)) {
        return inspectorLayoutValue(attributes, 'container', 'display');
    }

    return '';
}

function canShowFlexChildPanel(layer, attributes, parentAttributes = null) {
    const parentDisplay = parentDisplayForLayer(layer, attributes, parentAttributes);

    return isFlexDisplay(parentDisplay) || isGridDisplay(parentDisplay);
}

function inspectorLayoutPanel(target, attributes, extraControls = [], breakpoint = '') {
    const display = inspectorLayoutValue(attributes, target, 'display');
    const controls = [
        ...extraControls,
        inspectorLayoutSelect('Display', target, 'display', attributes,  builderDisplayOptions),
    ];

    if (isFlexDisplay(display)) {
        controls.push(
            inspectorLayoutSegmented('Direction', target, 'flex-direction', attributes,  builderFlexDirectionOptions),
            inspectorLayoutSegmented('Justify', target, 'justify-content', attributes,  builderJustifyContentOptions),
            inspectorLayoutSegmented('Align', target, 'align-items', attributes,  builderAlignItemsOptions),
            inspectorLayoutCssValue('Gap', target, 'gap', attributes,  builderCssValueUnits, ''),
            inspectorLayoutSelect('Flex Wrap', target, 'flex-wrap', attributes,  builderFlexWrapOptions),
        );
    } else if (isGridDisplay(display)) {
        controls.push(...inspectorGridLayoutControls(
            (property) =>  builderLayoutAttribute(target, property),
            layoutValueResolver(attributes, (property) =>  builderLayoutAttribute(target, property)),
        ));
    }

    if (canShowFlexChildPanel(target, attributes)) {
        controls.push(inspectorFlexChildControls(target, attributes, breakpoint));
    }

    return inspectorSection(`${target}-layout`, 'Layout', controls.join(''), true);
}

function inspectorElementLayoutPanel(layer, attributes, breakpoint = '', parentAttributes = null) {
    const display = inspectorStyleValue(attributes, layer, 'display');
    const controls = [
        inspectorStyleSelect('Display', layer, 'display', attributes,  builderDisplayOptions, ''),
    ];

    if (isFlexDisplay(display)) {
        controls.push(
            inspectorStyleSelect('Flex Wrap', layer, 'flex-wrap', attributes,  builderFlexWrapOptions, 'nowrap'),
            inspectorStyleSegmented('Direction', layer, 'flex-direction', attributes,  builderFlexDirectionOptions, 'row'),
            inspectorStyleSegmented('Justify', layer, 'justify-content', attributes,  builderJustifyContentOptions, 'flex-start'),
            inspectorStyleSegmented('Align', layer, 'align-items', attributes,  builderAlignItemsOptions, 'stretch'),
            inspectorStyleCssValue('Gap', layer, 'gap', attributes,  builderCssValueUnits, '0'),
        );
    } else if (isGridDisplay(display)) {
        controls.push(...inspectorGridLayoutControls(
            (property) =>  builderStyleAttribute(layer, property),
            styleValueResolver(attributes, layer),
        ));
    }

    if (canShowFlexChildPanel(layer, attributes, parentAttributes)) {
        controls.push(inspectorFlexChildControls(layer, attributes, breakpoint, parentAttributes));
    }

    return inspectorSection(`${layer}-layout`, 'Layout', controls.join(''), true);
}

function inspectorFlexChildControls(layer, attributes, breakpoint = '', parentAttributes = null) {
    const parentDisplay = parentDisplayForLayer(layer, attributes, parentAttributes);
    const flexAttribute =  builderFlexChildAttribute(layer, 'flex');
    const alignAttribute =  builderFlexChildAttribute(layer, 'align-self');
    const justifyAttribute =  builderFlexChildAttribute(layer, 'justify-self');
    const placeSelfAttribute =  builderFlexChildAttribute(layer, 'place-self');
    const orderAttribute =  builderFlexChildAttribute(layer, 'order');
    const flexValue = builderClassOverrideValue(attributes, layer, 'flex', breakpoint) ?? attributes[flexAttribute];
    const alignValue = builderClassOverrideValue(attributes, layer, 'align-self', breakpoint) ?? attributes[alignAttribute];
    const justifyValue = builderClassOverrideValue(attributes, layer, 'justify-self', breakpoint) ?? attributes[justifyAttribute];
    const placeSelfValue = builderClassOverrideValue(attributes, layer, 'place-self', breakpoint) ?? attributes[placeSelfAttribute];
    const orderValue = builderClassOverrideValue(attributes, layer, 'order', breakpoint) ?? attributes[orderAttribute];
    const controls = [
        inspectorFlexSegmented('Align', layer, 'align-self', attributes, alignValue,  builderAlignSelfOptions),
        inspectorFlexOrder(layer, attributes, orderValue),
    ];

    if (isFlexDisplay(parentDisplay)) {
        controls.unshift(inspectorFlexSegmented('Flex', layer, 'flex', attributes, flexValue,  builderFlexPresetOptions));
    }

    if (isGridDisplay(parentDisplay)) {
        controls.splice(1, 0,
            inspectorFlexSegmented('Justify', layer, 'justify-self', attributes, justifyValue,  builderJustifySelfOptions),
            inspectorFlexSegmented('Place self', layer, 'place-self', attributes, placeSelfValue,  builderPlaceSelfOptions),
        );
    }

    return `
        <div class="viti-builder-subsection">
            <h4>Item</h4>
            ${controls.join('')}
        </div>
    `;
}

function inspectorSizePanel(layer, attributes, extraControls = []) {
    return inspectorSection(`${layer}-size`, 'Size', [
        extraControls.length ? inspectorGrid(...extraControls) : '',
        inspectorGrid(
        inspectorStyleCssValue('Width', layer, 'width', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Height', layer, 'height', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Min Width', layer, 'min-width', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Min Height', layer, 'min-height', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Max Width', layer, 'max-width', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Max Height', layer, 'max-height', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Inline Size', layer, 'inline-size', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Min Inline Size', layer, 'min-inline-size', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Max Inline Size', layer, 'max-inline-size', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Block Size', layer, 'block-size', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Min Block Size', layer, 'min-block-size', attributes,  builderCssValueUnits, 'auto'),
        inspectorStyleCssValue('Max Block Size', layer, 'max-block-size', attributes,  builderCssValueUnits, 'auto'),
        ),
    ].join(''));
}

function inspectorModeButton(group, mode, active, icon, label) {
    return `
        <button type="button" class="${active ? 'is-active' : ''}" data-builder-custom-mode="${escapeHtml(mode)}" data-builder-custom-group="${escapeHtml(group)}" title="${escapeHtml(label)}" aria-label="${escapeHtml(label)}">
            <img src="${escapeHtml(icon)}" alt="" aria-hidden="true">
        </button>
    `;
}

function inspectorCustomModeSwitch(group, activeMode) {
    return `
        <div class="viti-builder-custom-switch">
            ${inspectorModeButton(group, 'all', activeMode === 'all', squareIcon, 'Tous')}
            ${inspectorModeButton(group, 'custom', activeMode === 'custom', squareDashedIcon, 'Custom')}
        </div>
    `;
}

function inspectorHasCustomValues(attributes, layer, properties) {
    return properties.some((property) => inspectorStyleValue(attributes, layer, property) !== '');
}

function mergeInspectorStates(...states) {
    const tokens = new Set(
        states
            .flatMap((state) => String(state || '').split(/\s+/))
            .map((token) => token.trim())
            .filter(Boolean),
    );

    return [...tokens].join(' ');
}

function inspectorBoxCustomGroup(layer, attributes, config) {
    const group = `${layer}-${config.baseProperty}`;
    const inferredCustomActive = inspectorHasCustomValues(attributes, layer, config.customProperties);
    const resolvedMode = layer === 'dom'
        ? domInspectorCustomGroupMode(group, 'all')
        : (inferredCustomActive ? 'custom' : 'all');
    const customActive = resolvedMode === 'custom';
    const baseAttribute =  builderStyleAttribute(layer, config.baseProperty);
    const baseValue = inspectorStyleValue(attributes, layer, config.baseProperty);
    const mainState = mergeInspectorStates(
        inspectorControlState(attributes, baseAttribute, baseValue),
        ...config.customProperties.map((property) => inspectorControlState(
            attributes,
             builderStyleAttribute(layer, property),
            inspectorStyleValue(attributes, layer, property),
        )),
    );
    const units = config.units ||  builderCssValueUnits;
    const main = inspectorCssValue(
        config.label,
        baseAttribute,
        baseValue,
        units,
        mainState,
        customActive ? { placeholder: 'Custom', disabled: true } : {},
    );
    const fields = config.fields.map((field) => `
        <div class="viti-builder-custom-block__cell is-${escapeHtml(field.slot)}">
            ${inspectorStyleCssValue(field.label, layer, field.property, attributes, units, '')}
        </div>
    `).join('');

    return `
        <div class="viti-builder-custom-control" data-builder-custom-control="${escapeHtml(group)}">
            <div class="viti-builder-custom-control__head">
                ${main}
                ${inspectorCustomModeSwitch(group, customActive ? 'custom' : 'all')}
            </div>
            <div class="viti-builder-custom-block has-${escapeHtml(String(config.fields.length))}${customActive ? '' : ' is-hidden'}" data-builder-custom-block="${escapeHtml(group)}">
                ${fields}
            </div>
        </div>
    `;
}

function inspectorAttributeCustomGroup(layer, attributes, config) {
    const group = `${layer}-${config.baseProperty}`;
    const inferredCustomActive = inspectorHasCustomValues(attributes, layer, config.customProperties);
    const resolvedMode = layer === 'dom'
        ? domInspectorCustomGroupMode(group, 'all')
        : (inferredCustomActive ? 'custom' : 'all');
    const customActive = resolvedMode === 'custom';
    const main = config.renderMain(customActive);
    const fields = config.fields.map((field) => `
        <div class="viti-builder-custom-block__cell is-${escapeHtml(field.slot)}">
            ${field.render()}
        </div>
    `).join('');

    return `
        <div class="viti-builder-custom-control" data-builder-custom-control="${escapeHtml(group)}">
            <div class="viti-builder-custom-control__head">
                ${main}
                ${inspectorCustomModeSwitch(group, customActive ? 'custom' : 'all')}
            </div>
            <div class="viti-builder-custom-block has-${escapeHtml(String(config.fields.length))}${customActive ? '' : ' is-hidden'}" data-builder-custom-block="${escapeHtml(group)}">
                ${fields}
            </div>
        </div>
    `;
}

function inspectorSpacePanel(layer, attributes, extraControls = []) {
    const paddingGroup = inspectorBoxCustomGroup(layer, attributes, {
        label: 'Padding',
        baseProperty: 'padding',
        customProperties: ['padding-top', 'padding-right', 'padding-bottom', 'padding-left'],
        fields: [
            { label: 'Top', property: 'padding-top', slot: 'top' },
            { label: 'Left', property: 'padding-left', slot: 'left' },
            { label: 'Right', property: 'padding-right', slot: 'right' },
            { label: 'Bottom', property: 'padding-bottom', slot: 'bottom' },
        ],
    });
    const marginGroup = inspectorBoxCustomGroup(layer, attributes, {
        label: 'Margin',
        baseProperty: 'margin',
        customProperties: ['margin-top', 'margin-right', 'margin-bottom', 'margin-left'],
        fields: [
            { label: 'Top', property: 'margin-top', slot: 'top' },
            { label: 'Left', property: 'margin-left', slot: 'left' },
            { label: 'Right', property: 'margin-right', slot: 'right' },
            { label: 'Bottom', property: 'margin-bottom', slot: 'bottom' },
        ],
    });

    return inspectorSection(`${layer}-space`, 'Space', [
        extraControls.length ? inspectorGrid(...extraControls) : '',
        paddingGroup,
        marginGroup,
    ].join(''), true);
}

function inspectorPositionPanel(layer, attributes) {
    const position = styleAttributeValue(attributes, layer, 'position', '');
    const sideControls = position && position !== 'static'
        ? `
            <div class="viti-builder-custom-block viti-builder-position-fields has-4">
                <div class="viti-builder-custom-block__cell is-top">${inspectorStyleCssValue('Top', layer, 'top', attributes,  builderCssValueUnits, '')}</div>
                <div class="viti-builder-custom-block__cell is-left">${inspectorStyleCssValue('Left', layer, 'left', attributes,  builderCssValueUnits, '')}</div>
                <div class="viti-builder-custom-block__cell is-right">${inspectorStyleCssValue('Right', layer, 'right', attributes,  builderCssValueUnits, '')}</div>
                <div class="viti-builder-custom-block__cell is-bottom">${inspectorStyleCssValue('Bottom', layer, 'bottom', attributes,  builderCssValueUnits, '')}</div>
            </div>
        `
        : '';
    const presetControls = ['absolute', 'fixed'].includes(position)
        ? inspectorActionRow(
            ...['top-left', 'top-right', 'bottom-left', 'bottom-right', 'left', 'right', 'bottom', 'top', 'full'].map((preset) => (
                `<button type="button" class="viti-builder-mini-button" data-builder-position-preset="${escapeHtml(preset)}" data-builder-position-layer="${escapeHtml(layer)}">${escapeHtml(preset)}</button>`
            )),
        )
        : '';

    return inspectorSection(`${layer}-position`, 'Position', [
        inspectorGrid(
            inspectorStyleSelect('Position', layer, 'position', attributes,  builderPositionOptions, ''),
            inspectorStyleCssValue('Z Index', layer, 'z-index', attributes, [''], ''),
        ),
        presetControls,
        sideControls,
    ].join(''), true);
}

function positionPresetStyleValues(layer, preset) {
    const top =  builderStyleAttribute(layer, 'top');
    const right =  builderStyleAttribute(layer, 'right');
    const bottom =  builderStyleAttribute(layer, 'bottom');
    const left =  builderStyleAttribute(layer, 'left');
    const empty = { [top]: '', [right]: '', [bottom]: '', [left]: '' };
    const zero = '0px';
    const presets = {
        'top-left': { [top]: zero, [left]: zero },
        'top-right': { [top]: zero, [right]: zero },
        'bottom-left': { [bottom]: zero, [left]: zero },
        'bottom-right': { [bottom]: zero, [right]: zero },
        left: { [top]: zero, [bottom]: zero, [left]: zero },
        right: { [top]: zero, [right]: zero, [bottom]: zero },
        bottom: { [right]: zero, [bottom]: zero, [left]: zero },
        top: { [top]: zero, [right]: zero, [left]: zero },
        full: { [top]: zero, [right]: zero, [bottom]: zero, [left]: zero },
    };

    return { ...empty, ...(presets[preset] || {}) };
}

function inspectorTypographyPanel(layer, attributes, title = 'Typographie') {
    return inspectorSection(`${layer}-typography`, 'Typographie', [
        inspectorGrid(
            inspectorStyleSelect('Font', layer, 'font-family', attributes,  builderFontFamilyOptions, ''),
            inspectorStyleCssValue('Size', layer, 'font-size', attributes,  builderCssValueUnits),
            inspectorStyleSelect('Weight', layer, 'font-weight', attributes,  builderFontWeightOptions, '400'),
            inspectorStyleSegmented('Style', layer, 'font-style', attributes,  builderFontStyleOptions, 'normal'),
            inspectorStyleSelect('Smoothing', layer, 'font-smoothing', attributes,  builderFontSmoothingOptions, ''),
            inspectorStyleSelect('Stretch', layer, 'font-stretch', attributes,  builderFontStretchOptions, ''),
            inspectorStyleSelect('Numeric', layer, 'font-variant-numeric', attributes,  builderFontVariantNumericOptions, ''),
            inspectorStyleText('Features', layer, 'font-feature-settings', attributes, ''),
            inspectorStyleCssValue('Spacing', layer, 'letter-spacing', attributes,  builderCssValueUnits, 'normal', { tailwindPresetOnly: true }),
            inspectorStyleCssValue('Line Height', layer, 'line-height', attributes, ['', ... builderCssValueUnits]),
            inspectorStyleSelect('Clamp', layer, 'line-clamp', attributes,  builderLineClampOptions, ''),
            inspectorStyleText('List Image', layer, 'list-style-image', attributes, ''),
            inspectorStyleSelect('List Position', layer, 'list-style-position', attributes,  builderListStylePositionOptions, ''),
            inspectorStyleSelect('List Type', layer, 'list-style-type', attributes,  builderListStyleTypeOptions, ''),
            inspectorStyleSelect('Align', layer, 'text-align', attributes,  builderTextAlignOptions, ''),
            inspectorStyleColor('Color', layer, 'color', attributes, 'currentColor'),
            inspectorStyleSelect('Decoration', layer, 'text-decoration-line', attributes,  builderTextDecorationLineOptions, ''),
            inspectorStyleColor('Deco Color', layer, 'text-decoration-color', attributes, 'currentColor'),
            inspectorStyleSelect('Deco Style', layer, 'text-decoration-style', attributes,  builderTextDecorationStyleOptions, ''),
            inspectorStyleSelect('Deco Thick', layer, 'text-decoration-thickness', attributes,  builderTextDecorationThicknessOptions, ''),
            inspectorStyleSelect('Underline', layer, 'text-underline-offset', attributes,  builderTextUnderlineOffsetOptions, ''),
            inspectorStyleSelect('Transform', layer, 'text-transform', attributes,  builderTextTransformOptions, 'none'),
            inspectorStyleSelect('Overflow', layer, 'text-overflow', attributes,  builderTextOverflowOptions, ''),
            inspectorStyleSelect('Wrap', layer, 'text-wrap', attributes,  builderTextWrapOptions, ''),
            inspectorStyleCssValue('Indent', layer, 'text-indent', attributes,  builderCssValueUnits, ''),
            inspectorStyleSelect('Tabs', layer, 'tab-size', attributes,  builderTabSizeOptions, ''),
            inspectorStyleSelect('Vertical', layer, 'vertical-align', attributes,  builderVerticalAlignOptions, 'baseline'),
            inspectorStyleSelect('White Space', layer, 'white-space', attributes,  builderWhiteSpaceOptions, 'normal'),
            inspectorStyleSelect('Word Break', layer, 'word-break', attributes,  builderWordBreakOptions, ''),
            inspectorStyleSelect('Overflow Wrap', layer, 'overflow-wrap', attributes,  builderOverflowWrapOptions, ''),
            inspectorStyleSelect('Hyphens', layer, 'hyphens', attributes,  builderHyphensOptions, ''),
        ),
    ].join(''), true);
}

function inspectorBackgroundTypeButton(type, active) {
    const labels = { image: 'Image', gradient: 'Gradient', color: 'Couleur' };

    return `
        <button type="button" class="${active ? 'is-active' : ''}" data-builder-background-type="${escapeHtml(type)}" title="${escapeHtml(labels[type])}" aria-label="${escapeHtml(labels[type])}">
            <img src="${escapeHtml( builderBackgroundTypeIcons[type])}" alt="" aria-hidden="true">
        </button>
    `;
}

function inspectorBackgroundTypeTabs(activeType) {
    return `
        <div class="viti-builder-background-tabs">
            ${['image', 'gradient', 'color'].map((type) => inspectorBackgroundTypeButton(type, activeType === type)).join('')}
        </div>
    `;
}

function inspectorBackgroundGradientControl(attributes, item = null, stopsAttribute = '', stopsState = null) {
    const previewGradient = item
        ? gradientValueFromBackgroundItem(item)
        :  builderOverlayGradientValue(attributes);
    const stops = normalizeGradientStops(item?.stops, item?.overlay || attributes?.['data-overlay'] || 'dark');
    const serializedStops = serializeGradientStops(stops);
    const stopMarkup = stops.map((stop, index) => `
                <button type="button" class="viti-builder-gradient-stop${index === 0 ? ' is-active' : ''}" data-builder-gradient-stop="${escapeHtml(stop.id)}" style="left: ${escapeHtml(stop.position)}%; background: ${escapeHtml(colorWithAlpha(stop.color, stop.alpha))};" aria-label="Modifier le point ${index + 1}"></button>
    `).join('');
    const stopEditors = stops.map((stop, index) => `
            <div class="viti-builder-gradient-stop-editor${index === 0 ? ' is-active' : ''}" data-builder-gradient-stop-editor="${escapeHtml(stop.id)}">
                <label class="viti-builder-gradient-color-field">
                    <span>Couleur</span>
                    <span class="viti-builder-gradient-color-picker">
                        <button type="button" data-builder-gradient-stop-color="${escapeHtml(stop.id)}" style="--builder-pickr-color: ${escapeHtml(colorWithAlpha(stop.color, stop.alpha))};" aria-label="Modifier la couleur"></button>
                        <span data-builder-gradient-stop-color-label="${escapeHtml(stop.id)}">${escapeHtml(colorWithAlpha(stop.color, stop.alpha))}</span>
                    </span>
                </label>
                <label>
                    <span>Position</span>
                    <span class="viti-builder-gradient-position-control">
                        <input type="number" min="0" max="100" step="1" value="${escapeHtml(stop.position)}" data-builder-gradient-stop-position="${escapeHtml(stop.id)}" title="Glisser pour ajuster">
                        <span>%</span>
                    </span>
                </label>
                <button type="button" data-builder-gradient-stop-remove="${escapeHtml(stop.id)}"${stops.length <= 2 ? ' disabled' : ''}>×</button>
            </div>
    `).join('');

    return `
        <div class="viti-builder-gradient-control" data-builder-gradient-stops-control data-builder-gradient-stops-attribute="${escapeHtml(stopsAttribute)}">
            ${inspectorLabel('Gradient', stopsAttribute, stopsState)}
            <input type="hidden" data-builder-layer-attribute="${escapeHtml(stopsAttribute)}" value="${escapeHtml(serializedStops)}">
            <div class="viti-builder-gradient-track" data-builder-gradient-track style="background: ${escapeHtml(previewGradient || `linear-gradient(0deg, ${ builderOverlayGradientStops('dark')})`)}">
                ${stopMarkup}
            </div>
            <div class="viti-builder-gradient-stop-editors">
                ${stopEditors}
            </div>
        </div>
    `;
}

function inspectorBackgroundColorControl(label, attribute, value, state = null) {
    const color = value || 'rgba(0, 0, 0, 0.5)';

    return `
        <label class="viti-builder-inspector-field viti-builder-background-color-field">
            ${inspectorLabel(label, attribute, state)}
            <span class="viti-builder-gradient-color-picker">
                <button type="button" data-builder-background-color-picker data-builder-background-color-attribute="${escapeHtml(attribute)}" style="--builder-pickr-color: ${escapeHtml(color)};" aria-label="Modifier la couleur"></button>
                <span data-builder-background-color-label="${escapeHtml(attribute)}">${escapeHtml(color)}</span>
                <input type="hidden" data-builder-layer-attribute="${escapeHtml(attribute)}" value="${escapeHtml(value || '')}">
            </span>
        </label>
    `;
}

function inspectorBackgroundBooleanButton(label, attribute, isActive) {
    return `
        <button type="button" class="viti-builder-background-toggle${isActive ? ' is-active' : ''}" data-builder-layer-attribute="${escapeHtml(attribute)}" data-builder-layer-value="${isActive ? 'false' : 'true'}" aria-pressed="${isActive ? 'true' : 'false'}">
            <span>${escapeHtml(label)}</span>
            <i aria-hidden="true"></i>
        </button>
    `;
}

function backgroundItemAttribute(itemId, field, layer = 'container') {
    return `data-background-item:${ builderLayerAttributeName(layer)}:${itemId}:${field}`;
}

function backgroundItemAttributeParts(attribute) {
    const scopedMatch = String(attribute || '').match(/^data-background-item:([^:]+):([^:]+):(.+)$/);
    if (scopedMatch) {
        return {
            layer: scopedMatch[1].replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()),
            itemId: scopedMatch[2],
            field: scopedMatch[3],
        };
    }

    const match = String(attribute || '').match(/^data-background-item:([^:]+):(.+)$/);

    return match ? { layer: 'container', itemId: match[1], field: match[2] } : null;
}

function isBackgroundItemAttribute(attribute) {
    return !!backgroundItemAttributeParts(attribute);
}

function backgroundItemValue(item, field) {
    return normalizeBackgroundItem(item)[field] || '';
}

function backgroundResetValues(layer, type) {
    return {
        image: {
            'data-background-asset': '',
            'data-background-image-position': '',
            'data-background-image-size': '',
            'data-background-image-size-value': '',
            'data-background-image-size-width': '',
            'data-background-image-size-height': '',
            'data-background-image-opacity': '',
            'data-background-image-repeat': '',
            'data-background-image-attachment': '',
            'data-background-image-origin': '',
        },
        gradient: {
            'data-overlay': 'none',
            'data-background-gradient-angle': '',
            'data-background-gradient-repeat': '',
            'data-background-gradient-radial': '',
            'data-background-gradient-size': '',
            'data-background-gradient-x': '',
            'data-background-gradient-y': '',
            'data-background-gradient-stops': '',
        },
        color: {
            [ builderStyleAttribute(layer, 'background-color')]: '',
        },
    }[type] || {};
}

function backgroundInitValues(layer, type, attributes) {
    return {
        image: {
            'data-background-asset': attributes['data-background-asset'] || '',
            'data-background-image-position': attributes['data-background-image-position'] || '',
            'data-background-image-size': attributes['data-background-image-size'] || 'cover',
            'data-background-image-size-value': attributes['data-background-image-size-value'] || '',
            'data-background-image-size-width': attributes['data-background-image-size-width'] || '',
            'data-background-image-size-height': attributes['data-background-image-size-height'] || '',
            'data-background-image-opacity': normalizeOpacityValue(attributes['data-background-image-opacity']),
            'data-background-image-repeat': attributes['data-background-image-repeat'] || '',
            'data-background-image-attachment': attributes['data-background-image-attachment'] || '',
            'data-background-image-origin': attributes['data-background-image-origin'] || '',
        },
        gradient: {
            'data-overlay': attributes['data-overlay'] && attributes['data-overlay'] !== 'none' ? attributes['data-overlay'] : 'dark',
            'data-background-gradient-angle': attributes['data-background-gradient-angle'] || 'tw:to-t',
            'data-background-gradient-repeat': attributes['data-background-gradient-repeat'] || 'false',
            'data-background-gradient-radial': attributes['data-background-gradient-radial'] || 'false',
            'data-background-gradient-size': attributes['data-background-gradient-size'] || '100px',
            'data-background-gradient-x': attributes['data-background-gradient-x'] || '50%',
            'data-background-gradient-y': attributes['data-background-gradient-y'] || '50%',
            'data-background-gradient-stops': attributes['data-background-gradient-stops'] || '',
        },
        color: {
            [ builderStyleAttribute(layer, 'background-color')]: inspectorStyleValue(attributes, layer, 'background-color') || 'rgba(0, 0, 0, 0.5)',
        },
    }[type] || {};
}

function backgroundStoredValues(attributes, layer, type) {
    const keys = {
        image: [
            'data-background-asset',
            'data-background-image-position',
            'data-background-image-size',
            'data-background-image-size-value',
            'data-background-image-size-width',
            'data-background-image-size-height',
            'data-background-image-opacity',
            'data-background-image-repeat',
            'data-background-image-attachment',
            'data-background-image-origin',
        ],
        gradient: [
            'data-overlay',
            'data-background-gradient-angle',
            'data-background-gradient-repeat',
            'data-background-gradient-radial',
            'data-background-gradient-size',
            'data-background-gradient-x',
            'data-background-gradient-y',
            'data-background-gradient-stops',
        ],
        color: [
             builderStyleAttribute(layer, 'background-color'),
        ],
    }[type] || [];

    return Object.fromEntries(keys.map((key) => [key, attributes[key] || '']));
}

function backgroundCollectionResetAttribute(layer) {
    return `data-${String(layer).replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`)}-background-collection`;
}

function backgroundItemResetAttribute(itemId, layer) {
    return `data-background-item-reset:${ builderLayerAttributeName(layer)}:${itemId}`;
}

function backgroundItemResetParts(attribute) {
    const match = String(attribute || '').match(/^data-background-item-reset:([^:]+):(.+)$/);
    if (!match) {
        return null;
    }

    return {
        layer: match[1].replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()),
        itemId: match[2],
    };
}

function layerFromBackgroundCollectionResetAttribute(attribute) {
    const match = String(attribute || '').match(/^data-(.+)-background-collection$/);
    if (!match) {
        return null;
    }

    return match[1].replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
}

function backgroundCollectionResetValues(layer) {
    return {
        ...(layer === 'container' ? backgroundResetValues(layer, 'image') : {}),
        ...(layer === 'container' ? backgroundResetValues(layer, 'gradient') : {}),
        ...backgroundResetValues(layer, 'color'),
        ...(layer === 'container' ? { [ builderBackgroundOverridesAttribute]: '' } : {}),
        [ builderBackgroundItemsAttributeForLayer(layer)]: '[]',
    };
}

function resetBackgroundItemToDefaults(item) {
    const defaults = backgroundItemBaseSnapshot(item);
    const targetType = ['image', 'gradient', 'color'].includes(defaults.type) ? defaults.type : item.type;
    const patch = backgroundItemTrackedFields.reduce((values, field) => ({
        ...values,
        ...backgroundItemFieldPatch(field, defaults[field] || ''),
    }), {});

    return normalizeBackgroundItem({
        ...backgroundItemDefaults(targetType),
        ...item,
        ...patch,
        type: targetType,
        defaults,
        overrides: [],
    }, targetType);
}

function backgroundStoredDatasetKey(type) {
    return `builderBackgroundStored${type[0].toUpperCase()}${type.slice(1)}`;
}

function readBackgroundStoredValues(holder, type) {
    try {
        return JSON.parse(holder?.dataset?.[backgroundStoredDatasetKey(type)] || '{}');
    } catch {
        return {};
    }
}

function writeBackgroundStoredValues(holder, type, values) {
    if (!holder || !type) {
        return;
    }

    holder.dataset[backgroundStoredDatasetKey(type)] = JSON.stringify(values);
}

function backgroundSwitchValues(layer, fromType, toType, attributes, options = {}) {
    if (fromType === toType || fromType === 'new') {
        return backgroundInitValues(layer, toType, attributes);
    }

    return {
        ...(options.preserveFromType ? {} : backgroundResetValues(layer, fromType)),
        ...backgroundInitValues(layer, toType, attributes),
    };
}

function inspectorBackgroundItemFromAttributes(layer, attributes, type) {
    if (type === 'image') {
        const hasPosition = Object.hasOwn(attributes, 'data-background-image-position') && String(attributes['data-background-image-position'] || '').trim() !== '';
        const position = hasPosition ? attributes['data-background-image-position'] : '';
        const positionParts = backgroundImagePositionParts(position);
        const imagePosition = hasPosition && positionParts.left === positionParts.top ? positionParts.left : '';
        const imagePositionLeft = hasPosition && positionParts.left !== positionParts.top ? positionParts.left : '';
        const imagePositionTop = hasPosition && positionParts.left !== positionParts.top ? positionParts.top : '';
        const imageSize = attributes['data-background-image-size'] || 'cover';
        const imageSizeValue = attributes['data-background-image-size-value'] || '';
        const imageSizeWidth = attributes['data-background-image-size-width'] || '';
        const imageSizeHeight = attributes['data-background-image-size-height'] || '';
        const imageOpacity = normalizeOpacityValue(attributes['data-background-image-opacity']);
        const imageRepeat = attributes['data-background-image-repeat'] || '';
        const imageAttachment = attributes['data-background-image-attachment'] || '';
        const imageOrigin = attributes['data-background-image-origin'] || '';

        return normalizeBackgroundItem({
            type,
            asset: attributes['data-background-asset'] || '',
            imagePosition,
            imagePositionLeft,
            imagePositionTop,
            size: imageSize,
            imageSizeValue,
            imageSizeWidth,
            imageSizeHeight,
            opacity: imageOpacity,
            repeat: imageRepeat,
            attachment: imageAttachment,
            origin: imageOrigin,
            defaults: {
                type,
                asset: attributes['data-background-asset'] || '',
                imagePosition,
                imagePositionLeft,
                imagePositionTop,
                imageSize: Object.hasOwn(attributes, 'data-background-image-size') ? imageSize : '',
                imageSizeValue: Object.hasOwn(attributes, 'data-background-image-size-value') ? imageSizeValue : '',
                imageSizeWidth: Object.hasOwn(attributes, 'data-background-image-size-width') ? imageSizeWidth : '',
                imageSizeHeight: Object.hasOwn(attributes, 'data-background-image-size-height') ? imageSizeHeight : '',
                imageOpacity: Object.hasOwn(attributes, 'data-background-image-opacity') ? imageOpacity : '',
                imageRepeat: Object.hasOwn(attributes, 'data-background-image-repeat') ? imageRepeat : '',
                imageAttachment: Object.hasOwn(attributes, 'data-background-image-attachment') ? imageAttachment : '',
                imageOrigin: Object.hasOwn(attributes, 'data-background-image-origin') ? imageOrigin : '',
            },
        }, type);
    }

    if (type === 'gradient') {
        return normalizeBackgroundItem({
            type,
            overlay: attributes['data-overlay'] && attributes['data-overlay'] !== 'none' ? attributes['data-overlay'] : 'dark',
            angle: attributes['data-background-gradient-angle'] || 'tw:to-t',
            repeat: attributes['data-background-gradient-repeat'] || 'false',
            radial: attributes['data-background-gradient-radial'] || 'false',
            size: attributes['data-background-gradient-size'] || '100px',
            x: attributes['data-background-gradient-x'] || '50%',
            y: attributes['data-background-gradient-y'] || '50%',
            stops: attributes['data-background-gradient-stops'] || '',
            defaults: {
                type,
                gradientOverlay: attributes['data-overlay'] && attributes['data-overlay'] !== 'none' ? attributes['data-overlay'] : 'dark',
                gradientAngle: Object.hasOwn(attributes, 'data-background-gradient-angle') ? attributes['data-background-gradient-angle'] : '',
                gradientRepeat: Object.hasOwn(attributes, 'data-background-gradient-repeat') ? attributes['data-background-gradient-repeat'] : '',
                gradientRadial: Object.hasOwn(attributes, 'data-background-gradient-radial') ? attributes['data-background-gradient-radial'] : '',
                gradientSize: Object.hasOwn(attributes, 'data-background-gradient-size') ? attributes['data-background-gradient-size'] : '',
                gradientX: Object.hasOwn(attributes, 'data-background-gradient-x') ? attributes['data-background-gradient-x'] : '',
                gradientY: Object.hasOwn(attributes, 'data-background-gradient-y') ? attributes['data-background-gradient-y'] : '',
                gradientStops: Object.hasOwn(attributes, 'data-background-gradient-stops') ? attributes['data-background-gradient-stops'] : '',
            },
        }, type);
    }

    return normalizeBackgroundItem({
        type,
        color: inspectorStyleValue(attributes, layer, 'background-color') || 'rgba(0, 0, 0, 0.5)',
        defaults: {
            type,
            color: inspectorStyleValue(attributes, layer, 'background-color') || '',
        },
    }, type);
}

function inspectorBackgroundEditor(layer, attributes, activeType = 'image', allowTypeSwitch = true, item = null) {
    const editorItem = normalizeBackgroundItem(item || inspectorBackgroundItemFromAttributes(layer, attributes, activeType), activeType);
    const itemForType = (type) => backgroundItemAsType(editorItem, type);
    const imageItem = itemForType('image');
    const gradientItem = itemForType('gradient');
    const colorItem = itemForType('color');
    const editorState = backgroundItemState(editorItem);
    const itemResetAttribute = backgroundItemResetAttribute(editorItem.id, layer);
    const itemAttribute = (field) => backgroundItemAttribute(editorItem.id, field, layer);
    const imageBase = backgroundItemBaseSnapshot(imageItem);
    const imageOpacity = normalizeOpacityValue(imageItem.opacity || imageBase.imageOpacity);
    const imageName = imageItem.asset ? String(imageItem.asset).split('/').filter(Boolean).pop() : 'Aucune image';
    const isRadialGradient = gradientItem.radial === 'true';
    const backgroundState = (attribute) => {
        const parts = backgroundItemAttributeParts(attribute);
        if (parts) {
            return backgroundItemFieldState(editorItem, parts.field);
        }

        return inspectorBackgroundFieldState(attributes, attribute);
    };
    const shouldRenderPane = (type) => allowTypeSwitch || activeType === type;

    return `
        <div class="viti-builder-background-popover" data-builder-background-layer="${escapeHtml(layer)}" data-builder-background-current-type="${escapeHtml(activeType)}" data-builder-background-item-id="${escapeHtml(editorItem.id)}">
            <div class="viti-builder-background-popover__head">
                ${inspectorLabel('Background', itemResetAttribute, editorState)}
                <button type="button" data-builder-background-close aria-label="Fermer">×</button>
            </div>
            ${allowTypeSwitch ? inspectorBackgroundTypeTabs(activeType) : ''}
            ${shouldRenderPane('image') ? `<div class="viti-builder-background-pane${activeType === 'image' ? ' is-active' : ''}" data-builder-background-pane="image">
                <div class="viti-builder-background-image-row">
                    ${backgroundThumbMarkup(imageItem)}
                    <div>
                        <strong>${escapeHtml(imageName)}</strong>
                        ${inspectorField('Image', itemAttribute('asset'), imageItem.asset, 'text', '', backgroundState(itemAttribute('asset')))}
                    </div>
                </div>
                ${inspectorBackgroundPositionControl(imageItem, itemAttribute, backgroundState)}
                ${inspectorGrid(
        inspectorSelect('Size mode', itemAttribute('imageSize'), imageItem.size,  builderBackgroundSizeOptions, backgroundState(itemAttribute('imageSize'))),
        inspectorSelect('Repeat', itemAttribute('imageRepeat'), imageItem.repeat,  builderBackgroundRepeatOptions, backgroundState(itemAttribute('imageRepeat'))),
                )}
                ${inspectorBackgroundSizeControl(imageItem, itemAttribute, backgroundState)}
                ${inspectorGrid(
        inspectorSelect('Attachment', itemAttribute('imageAttachment'), imageItem.attachment,  builderBackgroundAttachmentOptions, backgroundState(itemAttribute('imageAttachment'))),
        inspectorSelect('Origin', itemAttribute('imageOrigin'), imageItem.origin,  builderBackgroundOriginOptions, backgroundState(itemAttribute('imageOrigin'))),
                )}
                ${inspectorCssValue('Opacity', itemAttribute('imageOpacity'), imageOpacity, [''], backgroundState(itemAttribute('imageOpacity')), { placeholder: '1' })}
            </div>` : ''}
            ${shouldRenderPane('gradient') ? `<div class="viti-builder-background-pane${activeType === 'gradient' ? ' is-active' : ''}" data-builder-background-pane="gradient">
                ${inspectorBackgroundGradientControl(attributes, gradientItem, itemAttribute('gradientStops'), backgroundState(itemAttribute('gradientStops')))}
                ${inspectorGrid(
        inspectorSelect('Overlay', itemAttribute('gradientOverlay'), gradientItem.overlay, {
            dark: { label: 'Dark' },
            soft: { label: 'Soft' },
            none: { label: 'None' },
        }, backgroundState(itemAttribute('gradientOverlay'))),
        `<span class="viti-builder-background-field-slot${isRadialGradient ? ' is-hidden' : ''}" data-builder-gradient-linear-field>${inspectorCssValue('Angle', itemAttribute('gradientAngle'), gradientItem.angle, ['tw', 'deg'], backgroundState(itemAttribute('gradientAngle')), { tokenOptions:  builderGradientDirectionOptions })}</span>
        <span class="viti-builder-background-field-slot${isRadialGradient ? '' : ' is-hidden'}" data-builder-gradient-radial-field>${inspectorCssValue('S', itemAttribute('gradientSize'), gradientItem.size, ['px', '%', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('gradientSize')))}</span>`,
                )}
                <div class="viti-builder-background-toggle-row">
                    ${inspectorBackgroundBooleanButton('Repeat', itemAttribute('gradientRepeat'), gradientItem.repeat === 'true')}
                    ${inspectorBackgroundBooleanButton('Radial', itemAttribute('gradientRadial'), isRadialGradient)}
                </div>
                <div class="viti-builder-gradient-radial-fields${isRadialGradient ? '' : ' is-hidden'}" data-builder-gradient-radial-fields>
                    ${inspectorGrid(
        inspectorCssValue('X', itemAttribute('gradientX'), gradientItem.x, ['%', 'px', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('gradientX'))),
        inspectorCssValue('Y', itemAttribute('gradientY'), gradientItem.y, ['%', 'px', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('gradientY'))),
                    )}
                </div>
            </div>` : ''}
            ${shouldRenderPane('color') ? `<div class="viti-builder-background-pane${activeType === 'color' ? ' is-active' : ''}" data-builder-background-pane="color">
                ${inspectorBackgroundColorControl('Background Color', itemAttribute('color'), colorItem.color, backgroundState(itemAttribute('color')))}
            </div>` : ''}
        </div>
    `;
}

function inspectorBackgroundPositionControl(item, itemAttribute, backgroundState) {
    const positionParts = backgroundImagePositionParts(item.position);
    const hasGlobalPosition = !!item.imagePosition;
    const isCustom = !!item.imagePositionLeft || !!item.imagePositionTop || (!hasGlobalPosition && positionParts.left !== positionParts.top);
    const globalValue = isCustom ? (item.imagePosition || '') : (item.imagePosition || positionParts.left);
    const leftValue = item.imagePositionLeft || positionParts.left;
    const topValue = item.imagePositionTop || positionParts.top;
    const group = `${item.id}-background-position`;

    return `
        <div class="viti-builder-custom-control" data-builder-custom-control="${escapeHtml(group)}">
            <div class="viti-builder-custom-control__head">
                ${inspectorCssValue('Position', itemAttribute('imagePosition'), globalValue, ['tw', 'px', '%', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('imagePosition')), { ...(isCustom ? { placeholder: globalValue ? undefined : '-' } : {}), tokenOptions:  builderBackgroundPositionOptions })}
                ${inspectorCustomModeSwitch(group, isCustom ? 'custom' : 'all')}
            </div>
            <div class="viti-builder-custom-block has-2${isCustom ? '' : ' is-hidden'}" data-builder-custom-block="${escapeHtml(group)}">
                <div class="viti-builder-custom-block__cell is-left">
                    ${inspectorCssValue('Left', itemAttribute('imagePositionLeft'), leftValue, ['px', '%', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('imagePositionLeft')))}
                </div>
                <div class="viti-builder-custom-block__cell is-right">
                    ${inspectorCssValue('Top', itemAttribute('imagePositionTop'), topValue, ['px', '%', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('imagePositionTop')))}
                </div>
            </div>
        </div>
    `;
}

function inspectorBackgroundSizeControl(item, itemAttribute, backgroundState) {
    const isCustomSize = item.size === 'custom';
    const presetSizeValues = new Set(['auto', 'cover', 'contain']);
    const explicitSizeValue = presetSizeValues.has(item.imageSizeValue) ? '' : (item.imageSizeValue || '');
    const sizeParts = backgroundImageSizeParts(explicitSizeValue);
    const hasCustomAxes = !!item.imageSizeWidth || !!item.imageSizeHeight || (!!sizeParts.width && !!sizeParts.height && sizeParts.width !== sizeParts.height);
    const globalValue = explicitSizeValue || (!hasCustomAxes && sizeParts.width === sizeParts.height ? sizeParts.width : '');
    const widthValue = item.imageSizeWidth || sizeParts.width;
    const heightValue = item.imageSizeHeight || sizeParts.height;
    const group = `${item.id}-background-size`;

    return `
        <div class="viti-builder-background-size-custom${isCustomSize ? '' : ' is-hidden'}" data-builder-background-size-custom${isCustomSize ? '' : ' hidden'}>
            <div class="viti-builder-custom-control" data-builder-custom-control="${escapeHtml(group)}">
                <div class="viti-builder-custom-control__head">
                    ${inspectorCssValue('Size', itemAttribute('imageSizeValue'), globalValue, ['px', '%', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('imageSizeValue')), hasCustomAxes ? { placeholder: globalValue ? undefined : '-' } : {})}
                    ${inspectorCustomModeSwitch(group, hasCustomAxes ? 'custom' : 'all')}
                </div>
                <div class="viti-builder-custom-block has-2${hasCustomAxes ? '' : ' is-hidden'}" data-builder-custom-block="${escapeHtml(group)}">
                    <div class="viti-builder-custom-block__cell is-left">
                        ${inspectorCssValue('Width', itemAttribute('imageSizeWidth'), widthValue, ['px', '%', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('imageSizeWidth')))}
                    </div>
                    <div class="viti-builder-custom-block__cell is-right">
                        ${inspectorCssValue('Height', itemAttribute('imageSizeHeight'), heightValue, ['px', '%', 'em', 'rem', 'vw', 'vh'], backgroundState(itemAttribute('imageSizeHeight')))}
                    </div>
                </div>
            </div>
        </div>
    `;
}

function inspectorBackgroundEntry(layer, attributes, item) {
    const normalized = normalizeBackgroundItem(item);
    const type = normalized.type;
    const labels = { image: 'Image', gradient: 'Gradient', color: 'Couleur' };
    const label = labels[type] || 'Background';
    const isBuilderAdded = normalized.createdInBuilder === true || normalized.createdInBuilder === 'true';
    const state = backgroundItemState(normalized);
    const stateClasses = String(state || '').split(/\s+/).filter(Boolean);
    const swatch = (() => {
        if (type === 'image' && normalized.asset) {
            return `<span class="viti-builder-background-swatch is-preview" style="background-image: url(${escapeHtml(JSON.stringify(resolveThemeAssetUrl(normalized.asset)))});"></span>`;
        }
        if (type === 'gradient') {
            return `<span class="viti-builder-background-swatch is-preview" style="background: ${escapeHtml(gradientValueFromBackgroundItem(normalized))};"></span>`;
        }
        if (type === 'color') {
            return `<span class="viti-builder-background-swatch is-preview" style="background: ${escapeHtml(normalized.color || 'transparent')};"></span>`;
        }

        return `<span class="viti-builder-background-swatch"><img src="${escapeHtml( builderBackgroundTypeIcons[type])}" alt="" aria-hidden="true"></span>`;
    })();

    return `
        <div class="viti-builder-background-item${isBuilderAdded ? ' is-builder-added' : ''}${stateClasses.map((itemState) => ` is-${escapeHtml(itemState)}`).join('')}" data-builder-background-item data-builder-background-layer="${escapeHtml(layer)}" data-builder-background-item-id="${escapeHtml(normalized.id)}" data-builder-background-item-type="${escapeHtml(type)}">
            <button type="button" class="viti-builder-background-row" data-builder-background-edit>
                ${swatch}
                ${inspectorLabelDisplayOnly(label, state)}
            </button>
            ${inspectorBackgroundEditor(layer, attributes, type, true, normalized)}
        </div>
    `;
}

function inspectorBackgroundEntries(layer, attributes) {
    return parseBackgroundItems(attributes, layer).map((item) => inspectorBackgroundEntry(layer, attributes, item));
}

function effectCollectionState(attributes, layer, property) {
    return inspectorControlState(
        attributes,
         builderStyleAttribute(layer, property),
        inspectorStyleValue(attributes, layer, property),
    );
}

function inspectorEffectCollection(layer, attributes, property) {
    const config = effectCollectionConfig(property);
    if (!config) {
        return '';
    }

    const attribute =  builderEffectCollectionAttribute(layer, property);
    const item = parseEffectCollectionItems(attributes, layer, property)[0] || {
        id: `fx-${property}`,
        value: '',
        createdInBuilder: true,
    };
    const state = effectCollectionState(attributes, layer, property);
    const row = `
        <div class="viti-builder-effect-collection__item" data-builder-effect-collection-item="${escapeHtml(item.id)}">
            <input
                type="text"
                value="${escapeHtml(item.value)}"
                placeholder="${escapeHtml(config.label)}"
                data-builder-effect-collection-value="${escapeHtml(attribute)}"
                data-builder-effect-collection-item-id="${escapeHtml(item.id)}"
            >
        </div>
    `;

    const stateAttribute =  builderStyleAttribute(layer, property);

    return `
        <div class="viti-builder-effect-collection" data-builder-effect-collection="${escapeHtml(attribute)}" data-builder-effect-collection-state-for="${escapeHtml(stateAttribute)}">
            <div class="viti-builder-effect-collection__head">
                ${inspectorLabel(config.label, attribute, state)}
            </div>
            <div class="viti-builder-effect-collection__body">
                ${row}
            </div>
        </div>
    `;
}

function backgroundCollectionState(attributes, layer = 'container') {
    const items = parseBackgroundItems(attributes, layer);
    const states = new Set();
    const hasBackgroundValue = items.length > 0;
    const hasBackgroundOverride = items.some((item) => String(backgroundItemState(item) || '').includes('override'))
        || (layer === 'container' && backgroundOverrideSet(attributes).size > 0);

    if (hasBackgroundValue) {
        states.add('defined');
    }
    if (hasBackgroundOverride) {
        states.add('override');
    }

    return [...states].join(' ') || null;
}

function inspectorBackgroundPanel(layer, attributes, extra = '') {
    const backgroundItems = parseBackgroundItems(attributes, layer);
    const entries = backgroundItems.map((item) => inspectorBackgroundEntry(layer, attributes, item));
    const backgroundItem = backgroundItems[0] || null;
    const hasBackground = !!backgroundItem;
    const state = backgroundCollectionState(attributes, layer);
    const hasBackgroundOverride = String(state || '').split(/\s+/).includes('override');
    const backgroundTitleLabel = hasBackground && hasBackgroundOverride
        ? inspectorLabel('Background', backgroundItemResetAttribute(backgroundItem.id, layer), state)
        : inspectorLabelDisplayOnly('Background', state);
    const backgroundDeleteButton = hasBackground && !hasBackgroundOverride
        ? `<button type="button" class="viti-builder-background-title-remove" data-builder-background-remove="${escapeHtml(backgroundItem.id)}" data-builder-background-layer="${escapeHtml(layer)}" aria-label="Supprimer le background">×</button>`
        : '';
    const clipState = inspectorControlState(
        attributes,
         builderStyleAttribute(layer, 'background-clip'),
        inspectorStyleValue(attributes, layer, 'background-clip'),
    );
    const sectionState = [...new Set([
        ...String(state || '').split(/\s+/).filter(Boolean),
        ...String(clipState || '').split(/\s+/).filter(Boolean),
    ])].join(' ') || null;
    const clipControls = inspectorGrid(
        inspectorStyleSelect('Clip', layer, 'background-clip', attributes,  builderBackgroundClipOptions, ''),
    );

    return inspectorSection(`${layer}-background`, 'Background', [
        extra,
        clipControls,
        `<div class="viti-builder-background-list">
            <div class="viti-builder-background-title" data-builder-background-title-layer="${escapeHtml(layer)}">
                <span class="viti-builder-background-title-main">
                    ${backgroundTitleLabel}
                    ${backgroundDeleteButton}
                </span>
                ${hasBackground ? '' : '<button type="button" data-builder-background-add aria-label="Ajouter un background">+</button>'}
            </div>
            ${entries.join('') || '<p class="viti-builder-inspector-empty">Aucun background</p>'}
            <div class="viti-builder-background-add-popover" data-builder-background-item-type="new">
                ${inspectorBackgroundEditor(layer, attributes, 'color', true)}
            </div>
        </div>`,
    ].join(''), false, sectionState);
}

function inspectorBordersPanel(layer, attributes) {
    const radiusGroup = inspectorBoxCustomGroup(layer, attributes, {
        label: 'Radius',
        baseProperty: 'border-radius',
        customProperties: ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
        fields: [
            { label: 'Top left', property: 'border-top-left-radius', slot: 'top' },
            { label: 'Bottom left', property: 'border-bottom-left-radius', slot: 'left' },
            { label: 'Top right', property: 'border-top-right-radius', slot: 'right' },
            { label: 'Bottom right', property: 'border-bottom-right-radius', slot: 'bottom' },
        ],
    });
    const widthGroup = inspectorBoxCustomGroup(layer, attributes, {
        label: 'Width',
        baseProperty: 'border-width',
        units: ['tw', 'px', 'em', 'rem', 'vw', 'vh'],
        customProperties: ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'],
        fields: [
            { label: 'Top', property: 'border-top-width', slot: 'top' },
            { label: 'Left', property: 'border-left-width', slot: 'left' },
            { label: 'Right', property: 'border-right-width', slot: 'right' },
            { label: 'Bottom', property: 'border-bottom-width', slot: 'bottom' },
        ],
    });
    const styleGroup = inspectorAttributeCustomGroup(layer, attributes, {
        baseProperty: 'border-style',
        customProperties: ['border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style'],
        renderMain: (customActive) => inspectorStyleSelect('Style', layer, 'border-style', attributes,  builderBorderStyleOptions, 'none', { disabled: customActive }),
        fields: [
            { slot: 'top', render: () => inspectorStyleSelect('Top', layer, 'border-top-style', attributes,  builderBorderStyleOptions, '') },
            { slot: 'left', render: () => inspectorStyleSelect('Left', layer, 'border-left-style', attributes,  builderBorderStyleOptions, '') },
            { slot: 'right', render: () => inspectorStyleSelect('Right', layer, 'border-right-style', attributes,  builderBorderStyleOptions, '') },
            { slot: 'bottom', render: () => inspectorStyleSelect('Bottom', layer, 'border-bottom-style', attributes,  builderBorderStyleOptions, '') },
        ],
    });
    const colorGroup = inspectorAttributeCustomGroup(layer, attributes, {
        baseProperty: 'border-color',
        customProperties: ['border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'],
        renderMain: (customActive) => inspectorStyleColor('Color', layer, 'border-color', attributes, 'currentColor', { disabled: customActive }),
        fields: [
            { slot: 'top', render: () => inspectorStyleColor('Top', layer, 'border-top-color', attributes, 'currentColor') },
            { slot: 'left', render: () => inspectorStyleColor('Left', layer, 'border-left-color', attributes, 'currentColor') },
            { slot: 'right', render: () => inspectorStyleColor('Right', layer, 'border-right-color', attributes, 'currentColor') },
            { slot: 'bottom', render: () => inspectorStyleColor('Bottom', layer, 'border-bottom-color', attributes, 'currentColor') },
        ],
    });

    return inspectorSection(`${layer}-borders`, 'Borders', [
        radiusGroup,
        widthGroup,
        styleGroup,
        colorGroup,
    ].join(''));
}

function inspectorEffectsPanel(layer, attributes) {
    return inspectorSection(`${layer}-effects`, 'Effects', [
        inspectorGrid(
            inspectorStyleCssValue('Opacity', layer, 'opacity', attributes, [''], '1'),
            inspectorStyleSelect('Blend Mode', layer, 'mix-blend-mode', attributes,  builderBlendModeOptions, ''),
            inspectorStyleSelect('Cursor', layer, 'cursor', attributes,  builderCursorOptions, ''),
            inspectorStyleSelect('Backface', layer, 'backface-visibility', attributes,  builderBackfaceOptions, 'visible'),
        ),
        [
            inspectorEffectCollection(layer, attributes, 'box-shadow'),
            inspectorEffectCollection(layer, attributes, 'text-shadow'),
            inspectorEffectCollection(layer, attributes, 'filter'),
            inspectorEffectCollection(layer, attributes, 'backdrop-filter'),
            inspectorEffectCollection(layer, attributes, 'transition'),
            inspectorEffectCollection(layer, attributes, 'transform'),
        ].join(''),
        inspectorGrid(
            inspectorStyleText('Transform origin', layer, 'transform-origin', attributes, '50% 50%'),
            inspectorStyleSelect('Children Transform', layer, 'transform-style', attributes,  builderTransformStyleOptions, 'flat'),
            inspectorStyleCssValue('Perspective', layer, 'perspective', attributes,  builderCssValueUnits, ''),
            inspectorStyleText('Perspective origin', layer, 'perspective-origin', attributes, '50% 50%'),
        ),
        inspectorGrid(
            inspectorStyleSelect('Overflow', layer, 'overflow', attributes,  builderOverflowOptions, ''),
            inspectorStyleSelect('Overflow X', layer, 'overflow-x', attributes,  builderOverflowOptions, ''),
            inspectorStyleSelect('Overflow Y', layer, 'overflow-y', attributes,  builderOverflowOptions, ''),
        ),
    ].join(''));
}

function inspectorTextLayerContent(layer, attributes) {
    const config =  builderTextLayerConfigs[layer];
    if (!config) {
        return '';
    }

    if (config.contentInput === 'textarea') {
        return inspectorTextarea(config.contentLabel, config.contentAttribute, attributes[config.contentAttribute]);
    }

    return inspectorField(config.contentLabel, config.contentAttribute, attributes[config.contentAttribute], 'text');
}

function inspectorTextLayerStyles(layer, attributes) {
    return  builderTextStyleControls.map((control) => {
        const attribute =  builderStyleAttribute(layer, control.property);
        const value = attributes[attribute];

        if (control.type === 'select') {
            return inspectorSelect(control.label, attribute, value, control.options);
        }

        if (control.type === 'segmented') {
            return inspectorSegmented(control.label, attribute, value, control.options);
        }

        if (control.type === 'css-value') {
            return inspectorCssValue(control.label, attribute, value, control.units);
        }

        return inspectorField(control.label, attribute, value, 'text');
    }).join('');
}

function inspectorTextLayerPanel(layer, attributes, breakpoint = '') {
    const config =  builderTextLayerConfigs[layer];

    return [
        inspectorSection(`${layer}-content`, 'Contenu', inspectorTextLayerContent(layer, attributes)),
        inspectorElementLayoutPanel(layer, attributes, breakpoint),
        inspectorSizePanel(layer, attributes),
        inspectorSpacePanel(layer, attributes),
        inspectorPositionPanel(layer, attributes),
        inspectorTypographyPanel(layer, attributes, config.title),
        inspectorBackgroundPanel(layer, attributes),
        inspectorBordersPanel(layer, attributes),
        inspectorEffectsPanel(layer, attributes),
    ].join('');
}

function inspectorPanel(title, body, type = 'Layer') {
    return `
        <div class="viti-builder-inspector-head">
            <button type="button" class="is-active">Styles</button>
            <button type="button">Properties</button>
        </div>
        <div class="viti-builder-inspector-body">
            <div class="viti-builder-selection">
                <span>Selection</span>
                <div class="viti-builder-selection-tools">
                    ${inspectorIconButton('Gérer les classes CSS', tagIcon)}
                    ${inspectorIconButton('Ajouter une classe', plusIcon)}
                    <strong>${escapeHtml(title)}</strong>
                    <em>${escapeHtml(type)}</em>
                    ${inspectorIconButton('Options de la sélection', ellipsisVerticalIcon)}
                </div>
            </div>
            ${body}
        </div>
    `;
}

function installCanvasPreviewStyles(editor) {
    const frame = editor.Canvas.getFrameEl();
    if (frame) {
        frame.style.border = '0';
    }

    const document = editor.Canvas.getDocument();
    if (!document) {
        return;
    }

    const parentHtmlComputed = window.getComputedStyle?.(window.document.documentElement);
    const parentBodyComputed = window.getComputedStyle?.(window.document.body);
    if (parentHtmlComputed && document.documentElement?.style) {
        for (let index = 0; index < parentHtmlComputed.length; index += 1) {
            const property = parentHtmlComputed.item(index);
            if (!/^--(?:color|font|text|radius|spacing|shadow|border)-/.test(property)) {
                continue;
            }

            const value = parentHtmlComputed.getPropertyValue(property)?.trim();
            if (!value) {
                continue;
            }

            document.documentElement.style.setProperty(property, value);
        }
    }

    const htmlBackground = parentHtmlComputed?.backgroundColor || 'transparent';
    const htmlColor = parentHtmlComputed?.color || 'inherit';
    const bodyBackground = parentBodyComputed?.backgroundColor || htmlBackground;
    const bodyColor = parentBodyComputed?.color || htmlColor;
    const bodyFontFamily = parentBodyComputed?.fontFamily || 'inherit';
    const htmlClasses = String(window.document.documentElement.className || '')
        .split(/\s+/)
        .filter(Boolean)
        .filter((className) => className !== 'viti-builder-active')
        .join(' ');
    const bodyClasses = String(window.document.body.className || '').trim();

    let style = document.getElementById('viti-builder-canvas-preview-style');
    if (!style) {
        style = document.createElement('style');
        style.id = 'viti-builder-canvas-preview-style';
        document.head.appendChild(style);
    }

    document.documentElement.lang = window.document.documentElement.lang || document.documentElement.lang;
    document.documentElement.className = htmlClasses;
    document.body.className = bodyClasses;
    document.documentElement.style.backgroundColor = htmlBackground;
    document.documentElement.style.color = htmlColor;
    document.body.style.backgroundColor = bodyBackground;
    document.body.style.color = bodyColor;
    document.body.style.fontFamily = bodyFontFamily;
    style.textContent = `
        html,
        body {
            min-height: 100%;
            margin: 0 !important;
        }

        html {
            background-color: ${htmlBackground};
            color: ${htmlColor};
        }

        body {
            overflow-x: hidden;
            background-color: ${bodyBackground};
            color: ${bodyColor};
            font-family: ${bodyFontFamily};
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
        }

        #wrapper {
            min-height: 100vh;
        }
    `;
}

function preloadCanvasBackgroundAssets(editor) {
    const document = editor.Canvas.getDocument();
    if (!document) {
        return;
    }

    editor.getWrapper().find('*').forEach((component) => {
        const backgroundAsset = component.getAttributes()['data-background-asset'];
        if (!backgroundAsset) {
            return;
        }

        const href = resolveThemeAssetUrl(backgroundAsset);
        if (!href || [...document.querySelectorAll('link[data-viti-builder-preload]')].some((link) => link.href === href)) {
            return;
        }

        const preload = document.createElement('link');
        preload.rel = 'preload';
        preload.as = 'image';
        preload.href = href;
        preload.dataset.vitiBuilderPreload = 'image';
        document.head.appendChild(preload);
    });
}

function initializeBuilderTooltips(root) {
    const targetSelector = [
        'button',
        'a[title]',
        'input[title]',
        '[role="button"]',
        '[data-builder-background-drag]',
        '[title]',
        '[aria-label]',
    ].join(',');
    const tooltip = document.createElement('div');
    let activeTarget = null;
    let showTimer = 0;

    tooltip.className = 'viti-builder-tooltip';
    tooltip.setAttribute('role', 'tooltip');
    tooltip.hidden = true;
    root.appendChild(tooltip);

    const tooltipText = (target) => {
        if (!target || target.disabled || target.getAttribute('aria-disabled') === 'true') {
            return '';
        }

        if (target.title) {
            target.dataset.builderTooltip = target.title;
            target.removeAttribute('title');
        }

        return String(target.dataset.builderTooltip || target.getAttribute('aria-label') || '').trim();
    };

    const tooltipTarget = (event) => {
        const target = event.target.closest(targetSelector);

        return target && root.contains(target) ? target : null;
    };

    const hideTooltip = () => {
        window.clearTimeout(showTimer);
        showTimer = 0;
        activeTarget = null;
        tooltip.hidden = true;
        tooltip.textContent = '';
    };

    const placeTooltip = () => {
        if (!activeTarget || tooltip.hidden) {
            return;
        }

        const rect = activeTarget.getBoundingClientRect();
        const tooltipRect = tooltip.getBoundingClientRect();
        const spacing = 7;
        const preferredTop = rect.top - tooltipRect.height - spacing;
        const top = preferredTop > 4 ? preferredTop : rect.bottom + spacing;
        const left = Math.min(
            Math.max(rect.left + (rect.width - tooltipRect.width) / 2, 6),
            window.innerWidth - tooltipRect.width - 6,
        );

        tooltip.style.left = `${Math.round(left)}px`;
        tooltip.style.top = `${Math.round(top)}px`;
    };

    const showTooltip = (target) => {
        const text = tooltipText(target);

        if (!text) {
            hideTooltip();

            return;
        }

        window.clearTimeout(showTimer);
        activeTarget = target;
        showTimer = window.setTimeout(() => {
            if (activeTarget !== target) {
                return;
            }

            tooltip.textContent = text;
            tooltip.hidden = false;
            placeTooltip();
        }, 120);
    };

    root.querySelectorAll('[title]').forEach((target) => {
        tooltipText(target);
    });

    root.addEventListener('pointerover', (event) => {
        const target = tooltipTarget(event);
        if (!target || target.contains(event.relatedTarget)) {
            return;
        }

        showTooltip(target);
    });
    root.addEventListener('pointerout', (event) => {
        const target = tooltipTarget(event);
        if (!target || target.contains(event.relatedTarget)) {
            return;
        }

        hideTooltip();
    });
    root.addEventListener('focusin', (event) => {
        const target = tooltipTarget(event);
        if (target) {
            showTooltip(target);
        }
    });
    root.addEventListener('focusout', hideTooltip);
    root.addEventListener('pointerdown', hideTooltip);
    root.addEventListener('scroll', hideTooltip, true);
    window.addEventListener('resize', hideTooltip);
    window.addEventListener('keydown', (event) => {
        if (event.key === 'Escape') {
            hideTooltip();
        }
    });
}

function initBuilder(root) {
    html.classList.add('viti-builder-active');

    const publicBuilderRequested = new URLSearchParams(window.location.search).get('builder') === '1';
    const publicBuilderUrl = (url) => {
        if (!publicBuilderRequested) {
            return url;
        }

        const resolvedUrl = new URL(url, window.location.href);
        resolvedUrl.searchParams.set('builder', '1');

        return resolvedUrl.toString();
    };
    const loadUrl = publicBuilderUrl(root.dataset.loadUrl);
    const saveUrl = publicBuilderUrl(root.dataset.saveUrl);
    const tailwindUrl = root.dataset.tailwindUrl ? publicBuilderUrl(root.dataset.tailwindUrl) : '';
    const exitUrl = root.dataset.exitUrl || window.location.pathname;
    const csrfToken = root.dataset.csrfToken || '';
    const pageName = root.dataset.pageName || 'Page';
    let activeBuilderDevice = 'Desktop';
    let lastSelectedLayerName = '';
    let restoringBackgroundHistory = false;
    const backgroundHistory = [];
    const backgroundRedoHistory = [];

    root.innerHTML = `
        <div class="viti-builder-shell">
            <header class="viti-builder-topbar">
                <div class="viti-builder-topbar__left">
                    <span class="viti-builder-page-title">${escapeHtml(pageName)}</span>
                </div>
                <div class="viti-builder-topbar__center">
                    <div>
                        <select class="viti-builder-select" data-builder-device>
                            <option value="Desktop">Desktop</option>
                            <option value="Tablet">Tablet</option>
                            <option value="Mobile">Mobile</option>
                        </select>
                    </div>
                    <div class="viti-builder-breakpoint" data-builder-breakpoint>Édition desktop</div>
                    <div class="viti-builder-status" data-builder-status>Chargement du builder...</div>
                    <div>
                        <button type="button" class="viti-builder-button" data-builder-command="sw-visibility" title="Contours">□</button>
                        <button type="button" class="viti-builder-button" data-builder-command="preview" title="Prévisualiser">◉</button>
                        <button type="button" class="viti-builder-button" data-builder-command="fullscreen" title="Plein écran">⛶</button>
                        <button type="button" class="viti-builder-button" data-builder-command="export-template" title="Afficher le HTML">⌘</button>
                        <button type="button" class="viti-builder-button" data-builder-undo title="Retour">↶</button>
                        <button type="button" class="viti-builder-button" data-builder-redo title="Avancer">↷</button>
                    </div>
                </div>
                <div class="viti-builder-topbar__right">
                    <button type="button" class="viti-builder-button viti-builder-button--primary" data-builder-save title="Enregistrer">Sauver</button>
                    <a class="viti-builder-button" href="${escapeHtml(exitUrl)}" title="Quitter">Quitter</a>
                </div>
            </header>
            <div class="viti-builder-body">
                <aside class="viti-builder-sidebar">
                    <nav class="viti-builder-rail" aria-label="Builder">
                        <button type="button" class="viti-builder-rail-button" data-builder-sidebar="blocks" title="Blocks" aria-label="Blocks"><img src="${blocksIcon}" alt="" aria-hidden="true"></button>
                        <button type="button" class="viti-builder-rail-button is-active" data-builder-sidebar="pages" title="Pages" aria-label="Pages"><img src="${fileStackIcon}" alt="" aria-hidden="true"></button>
                        <button type="button" class="viti-builder-rail-button" data-builder-sidebar="globals" title="Globals" aria-label="Globals"><img src="${globeIcon}" alt="" aria-hidden="true"></button>
                        <button type="button" class="viti-builder-rail-button" data-builder-sidebar="assets" title="Assets" aria-label="Assets"><img src="${imagesIcon}" alt="" aria-hidden="true"></button>
                    </nav>
                    <div class="viti-builder-sidebar-panels">
                        <section class="viti-builder-sidebar-panel" data-builder-sidebar-panel="blocks">
                            <div class="viti-builder-sidebar-panel__head">
                                <span>Blocks</span>
                                <span>+</span>
                            </div>
                            <div id="viti-builder-blocks" class="viti-builder-sidebar-panel__body"></div>
                        </section>
                        <section class="viti-builder-sidebar-panel is-active" data-builder-sidebar-panel="pages">
                            <div class="viti-builder-sidebar-panel__head">
                                <span>Pages</span>
                                <span>+</span>
                            </div>
                            <div class="viti-builder-sidebar-panel__body viti-builder-sidebar-panel__body--pages" data-builder-pages>
                                <div class="viti-builder-page-row">
                                    <span>${escapeHtml(pageName)}</span>
                                    <span>⋮</span>
                                </div>
                            </div>
                            <div class="viti-builder-sidebar-panel__subhead">
                                <span>Layers</span>
                            </div>
                            <div id="viti-builder-layers" class="viti-builder-sidebar-panel__body viti-builder-sidebar-panel__body--layers"></div>
                        </section>
                        <section class="viti-builder-sidebar-panel" data-builder-sidebar-panel="globals">
                            <div class="viti-builder-sidebar-panel__head">
                                <span>Globals</span>
                                <button type="button" class="viti-builder-icon-button" data-builder-global-reset-all title="Réinitialiser les globals" aria-label="Réinitialiser les globals">×</button>
                            </div>
                            <div class="viti-builder-sidebar-panel__body viti-builder-sidebar-panel__body--globals" data-builder-globals>
                                <div class="viti-builder-empty-row">Aucun global</div>
                            </div>
                        </section>
                        <section class="viti-builder-sidebar-panel" data-builder-sidebar-panel="assets">
                            <div class="viti-builder-sidebar-panel__head">
                                <span>Assets</span>
                                <span>+</span>
                            </div>
                            <div id="viti-builder-assets" class="viti-builder-sidebar-panel__body"></div>
                        </section>
                    </div>
                </aside>
                <section class="viti-builder-canvas-wrap">
                    <button type="button" class="viti-builder-sidebar-reopen" data-builder-sidebar-reopen title="Ouvrir le menu"><img src="${fileStackIcon}" alt="" aria-hidden="true"></button>
                    <div id="viti-builder-canvas" class="viti-builder-canvas"></div>
                </section>
                <aside class="viti-builder-inspector" data-builder-inspector></aside>
            </div>
        </div>
    `;

    const status = root.querySelector('[data-builder-status]');
    const saveButton = root.querySelector('[data-builder-save]');
    const inspector = root.querySelector('[data-builder-inspector]');
    const breakpointLabel = root.querySelector('[data-builder-breakpoint]');
    const shell = root.querySelector('.viti-builder-shell');
    const canvasWrap = root.querySelector('.viti-builder-canvas-wrap');
    const sidebarReopenButton = root.querySelector('[data-builder-sidebar-reopen]');
    const globalsPanel = root.querySelector('[data-builder-globals]');
    const pagesPanel = root.querySelector('[data-builder-pages]');
    initializeBuilderTooltips(root);
    let pageSettings = {
        headerMode: 'global',
        footerMode: 'global',
    };
    let documentInspectorSchema = defaultDocumentInspectorSchema;
    const forcedDomCssModes = new Map();
    let themeSourceFiles = {};
    let layoutBuilderUrls = {
        header: '',
        footer: '',
    };
    const normalizeLayoutMode = (value) => ['global', 'custom', 'none'].includes(String(value ?? '')) ? String(value) : 'global';
    const renderPagePanel = () => {
        if (!pagesPanel) {
            return;
        }

        const modes = {
            global: 'Global',
            custom: 'Personnalisé',
            none: 'Aucun',
        };
        const headerUrl = layoutBuilderUrls.header || '#';
        const footerUrl = layoutBuilderUrls.footer || '#';

        pagesPanel.innerHTML = `
            <div class="viti-builder-page-row">
                <span>${escapeHtml(pageName)}</span>
                <span>⋮</span>
            </div>
            <label class="viti-builder-inspector-field">
                <span class="viti-builder-property-label">
                    <span class="viti-builder-property-label__main">Header</span>
                </span>
                <span class="viti-builder-control">
                    <select data-builder-page-setting="headerMode">
                        ${Object.entries(modes).map(([value, label]) => `<option value="${escapeHtml(value)}"${pageSettings.headerMode === value ? ' selected' : ''}>${escapeHtml(label)}</option>`).join('')}
                    </select>
                </span>
            </label>
            <a class="viti-builder-page-link${headerUrl === '#' ? ' is-disabled' : ''}" href="${escapeHtml(headerUrl)}">Éditer le header global</a>
            <label class="viti-builder-inspector-field">
                <span class="viti-builder-property-label">
                    <span class="viti-builder-property-label__main">Footer</span>
                </span>
                <span class="viti-builder-control">
                    <select data-builder-page-setting="footerMode">
                        ${Object.entries(modes).map(([value, label]) => `<option value="${escapeHtml(value)}"${pageSettings.footerMode === value ? ' selected' : ''}>${escapeHtml(label)}</option>`).join('')}
                    </select>
                </span>
            </label>
            <a class="viti-builder-page-link${footerUrl === '#' ? ' is-disabled' : ''}" href="${escapeHtml(footerUrl)}">Éditer le footer global</a>
        `;
    };
    renderPagePanel();
    let canvasOverlayRefreshFrame = 0;
    const setStatus = (message, state = '') => {
        status.textContent = message;
        status.dataset.state = state;
    };
    const refreshCanvasOverlays = () => {
        if (canvasOverlayRefreshFrame) {
            cancelAnimationFrame(canvasOverlayRefreshFrame);
        }

        canvasOverlayRefreshFrame = requestAnimationFrame(() => {
            canvasOverlayRefreshFrame = requestAnimationFrame(() => {
                if (!editor?.Canvas) {
                    return;
                }

                editor.Canvas.refresh?.({ all: true });
                editor.refresh?.({ tools: true });
            });
        });
    };
    const syncCanvasViewport = () => {
        const frameModel = editor.Canvas.getFrames?.()[0];
        if (!frameModel || !canvasWrap) {
            return;
        }

        const viewportWidth = builderViewportWidth(activeBuilderDevice);
        const viewportHeight = builderViewportHeight(activeBuilderDevice);
        const availableWidth = Math.max(1, canvasWrap.clientWidth || viewportWidth);
        const availableHeight = Math.max(1, canvasWrap.clientHeight || viewportHeight);
        const zoom = Math.min(
            100,
            Math.max(1, Math.floor(Math.min(availableWidth / viewportWidth, availableHeight / viewportHeight) * 100)),
        );
        const frameWidth = viewportWidth;
        const contentHeight = editor.Canvas.getDocument()?.body?.scrollHeight || viewportHeight;
        const frameHeight = Math.max(viewportHeight, contentHeight);

        frameModel.set({
            x: 0,
            y: 0,
            width: `${frameWidth}px`,
            height: `${frameHeight}px`,
            minHeight: `${frameHeight}px`,
        }, { noUndo: 1 });

        editor.Canvas.setZoom?.(zoom);
        editor.Canvas.setCoords?.(0, 0);

        refreshCanvasOverlays();
    };
    const setActiveBuilderDevice = (device) => {
        activeBuilderDevice = device;
        activeInspectorBreakpoint = builderBreakpointForDevice(device);
        const labels = {
            Desktop: 'Édition desktop',
            Tablet: 'Édition tablette',
            Mobile: 'Édition mobile',
        };
        if (breakpointLabel) {
            breakpointLabel.textContent = labels[device] || labels.Desktop;
        }

        requestAnimationFrame(syncCanvasViewport);
    };
    activeInspectorBreakpoint = builderBreakpointForDevice(activeBuilderDevice);
    let rememberedOpenInspectorSectionKeys = new Set();
    let rememberInspectorSectionsFrame = 0;
    const inspectorSectionKeyFromElement = (section) => (
        section?.dataset?.builderInspectorSectionKey || section?.dataset?.builderInspectorSection || ''
    );
    const closeInspectorSections = () => {
        inspector.querySelectorAll('.viti-builder-style-section').forEach((section) => {
            section.open = false;
        });
    };
    const openInspectorSectionKeys = () => new Set(
        [...inspector.querySelectorAll('.viti-builder-style-section[open]')]
            .map(inspectorSectionKeyFromElement)
            .filter(Boolean),
    );
    const rememberOpenInspectorSections = () => {
        rememberedOpenInspectorSectionKeys = openInspectorSectionKeys();
    };
    const scheduleOpenInspectorSectionsRemember = () => {
        if (rememberInspectorSectionsFrame) {
            cancelAnimationFrame(rememberInspectorSectionsFrame);
        }

        rememberInspectorSectionsFrame = requestAnimationFrame(() => {
            rememberInspectorSectionsFrame = 0;
            rememberOpenInspectorSections();
        });
    };
    const restoreInspectorSectionKeys = (sectionKeys) => {
        const sections = [...inspector.querySelectorAll('.viti-builder-style-section')];
        const keys = new Set(sectionKeys);
        const availableKeys = new Set(sections.map(inspectorSectionKeyFromElement).filter(Boolean));
        const canRestore = keys.size > 0 && [...keys].every((key) => availableKeys.has(key));

        sections.forEach((section) => {
            section.open = canRestore && keys.has(inspectorSectionKeyFromElement(section));
        });

        rememberedOpenInspectorSectionKeys = canRestore ? new Set(keys) : new Set();
    };

    const editor = grapesjs.init({
        container: '#viti-builder-canvas',
        height: '100%',
        fromElement: false,
        storageManager: false,
        blockManager: { appendTo: '#viti-builder-blocks' },
        assetManager: { appendTo: '#viti-builder-assets' },
        layerManager: { appendTo: '#viti-builder-layers' },
        panels: { defaults: [] },
        deviceManager: {
            devices: [
                { id: 'Desktop', name: 'Desktop', width: '1280px', widthMedia: '1280px' },
                { id: 'Tablet', name: 'Tablet', width: '768px', widthMedia: '992px' },
                { id: 'Mobile', name: 'Mobile', width: '375px', widthMedia: '575px' },
            ],
        },
        canvas: {
            styles: Array.from(document.querySelectorAll('link[rel="stylesheet"]')).map((link) => link.href),
        },
    });
    if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
        window.__vitiBuilderDebug = {
            get editor() {
                return editor;
            },
        };
    }
    editor.Commands.add('export-template', {
        run() {
            openBuilderSourceViewer();
        },
    });

    editor.on('load canvas:frame:load', () => installCanvasPreviewStyles(editor));
    editor.on('load canvas:frame:load', () => preloadCanvasBackgroundAssets(editor));
    editor.on('load canvas:frame:load', () => requestAnimationFrame(syncCanvasViewport));
    editor.on('load canvas:frame:load', () => requestAnimationFrame(renderGlobalThemePanel));
    editor.on('component:hover component:hovered component:unhovered', () => {
        requestAnimationFrame(refreshCanvasOverlays);
    });

    let applyingInspectorChange = false;
    let tailwindSyncSignature = '';
    let tailwindSyncTimeout = null;
    let tailwindSyncRunning = false;
    let tailwindSyncQueued = false;
    let tailwindClassesDirty = false;
    let tailwindSyncPromise = Promise.resolve({ skipped: true });
    let inspectorApplyTimeout = null;
    let pendingInspectorApply = null;
    let globalThemeReferenceTokens = [];
    let globalThemeOverrides = {};

    const globalThemeReferenceByName = () => Object.fromEntries(globalThemeReferenceTokens.map((token) => [token.name, token]));

    const applyGlobalThemeOverridesToDocument = (document) => {
        const targetDocument = document || editor.Canvas.getDocument();
        if (!targetDocument?.documentElement) {
            return;
        }

        Object.keys(globalThemeReferenceByName()).forEach((token) => {
            targetDocument.documentElement.style.removeProperty(token);
        });
        Object.entries(globalThemeOverrides).forEach(([token, value]) => {
            targetDocument.documentElement.style.setProperty(token, value);
        });
    };

    const applyGlobalThemeOverrides = () => {
        applyGlobalThemeOverridesToDocument(editor.Canvas.getDocument());
    };

    const refreshGlobalThemeTokens = () => {
        const document = editor.Canvas.getDocument();
        if (!document) {
            return;
        }

        const previousReferences = globalThemeReferenceByName();
        const discovered = discoverGlobalThemeTokens(document);
        globalThemeReferenceTokens = discovered.map((token) => previousReferences[token.name] || token);
        applyGlobalThemeOverrides();
    };

    const globalThemeTokenValue = (tokenName) => globalThemeOverrides[tokenName] ?? globalThemeReferenceByName()[tokenName]?.value ?? '';

    const globalThemeControlState = (tokenName) => {
        const referenceValue = globalThemeReferenceByName()[tokenName]?.value ?? '';
        const currentValue = globalThemeTokenValue(tokenName);

        return currentValue !== referenceValue ? 'defined override' : 'defined';
    };

    const globalThemeField = (field) => {
        const token = globalThemeReferenceByName()[field.token];
        if (!token) {
            return '';
        }

        return globalThemeFieldMarkup(
            field.label,
            field.token,
            field.type || token.type,
            globalThemeTokenValue(field.token),
            token.value,
            globalThemeControlState(field.token),
            field.units ||  builderCssValueUnits,
            { displayReference: field.displayReference === true },
        );
    };

    const renderGlobalThemePanel = () => {
        if (!globalsPanel) {
            return;
        }

        refreshGlobalThemeTokens();
        if (!globalThemeReferenceTokens.length) {
            globalsPanel.innerHTML = '<div class="viti-builder-empty-row">Aucun global</div>';

            return;
        }

        const semanticTokenNames = new Set(globalThemeSemanticSections.flatMap((section) => section.fields.map((field) => field.token)));
        const sections = [];
        const colors = globalThemeReferenceTokens
            .filter((token) => token.group === 'Colors')
            .map((token) => globalThemeFieldMarkup(
                token.label,
                token.name,
                'color',
                globalThemeTokenValue(token.name),
                token.value,
                globalThemeControlState(token.name),
            ));

        sections.push(globalThemeSectionMarkup('globals-colors', 'Colors', colors));
        globalThemeSemanticSections.forEach((section) => {
            sections.push(globalThemeSectionMarkup(
                `globals-${section.id}`,
                section.title,
                section.fields.map(globalThemeField).filter(Boolean),
            ));
        });

        ['Fonts', 'Typography', 'Radius', 'Spacing', 'Shadows', 'Borders'].forEach((group) => {
            const fields = globalThemeReferenceTokens
                .filter((token) => token.group === group)
                .filter((token) => !semanticTokenNames.has(token.name))
                .map((token) => globalThemeFieldMarkup(
                    token.label,
                    token.name,
                    token.type,
                    globalThemeTokenValue(token.name),
                    token.value,
                    globalThemeControlState(token.name),
                ));

            sections.push(globalThemeSectionMarkup(`globals-${group.toLowerCase()}`, group, fields));
        });

        globalsPanel.innerHTML = sections.filter(Boolean).join('') || '<div class="viti-builder-empty-row">Aucun global</div>';
        globalsPanel.querySelector('[data-builder-inspector-section="globals-colors"]')?.setAttribute('open', '');
    };

    const syncGlobalThemeTokenState = (tokenName) => {
        if (!globalsPanel) {
            return;
        }

        const selector = cssSelectorValue(tokenName);
        const state = globalThemeControlState(tokenName);
        const hasOverride = state.split(/\s+/).includes('override');
        const currentValue = globalThemeTokenValue(tokenName);

        globalsPanel.querySelectorAll(`[data-builder-global-field="${selector}"]`).forEach((field) => {
            field.classList.toggle('is-override', hasOverride);
            const label = field.querySelector('.viti-builder-property-label');
            label?.classList.toggle('is-override', hasOverride);

            const labelMain = label?.querySelector('.viti-builder-property-label__main') || label;
            let labelActions = label?.querySelector('.viti-builder-property-actions');
            if (!labelActions && label) {
                labelActions = document.createElement('span');
                labelActions.className = 'viti-builder-property-actions';
                label.append(labelActions);
            }

            if (hasOverride && labelMain && !labelMain.querySelector('.is-override-dot')) {
                labelMain.insertAdjacentHTML('beforeend', '<i class="viti-builder-property-dot is-override-dot" aria-hidden="true"></i>');
            }
            if (!hasOverride) {
                label?.querySelector('.is-override-dot')?.remove();
            }

            if (hasOverride && label && !label.querySelector('.viti-builder-property-reset')) {
                const reset = createPropertyResetButton(tokenName);
                label.insertBefore(reset, labelActions || null);
            }
            if (!hasOverride) {
                label?.querySelector('.viti-builder-property-reset')?.remove();
            }

            const colorPicker = field.querySelector(`[data-builder-global-color-for="${selector}"]`);
            if (colorPicker) {
                if (colorPicker.tagName === 'INPUT') {
                    colorPicker.value = hexColorValue(currentValue);
                } else {
                    colorPicker.style.setProperty('--builder-pickr-color', currentValue || 'transparent');
                    field.querySelector(`[data-builder-color-label="${selector}"]`)?.replaceChildren(document.createTextNode(currentValue || 'transparent'));
                }
            }

            if (field.dataset.builderGlobalDisplayReference !== '1') {
                const tokenInput = field.querySelector(`[data-builder-global-token="${selector}"]`);
                if (tokenInput && !field.contains(document.activeElement)) {
                    tokenInput.value = currentValue;
                }
            }

            const cssInput = field.querySelector(`[data-builder-global-css-value="${selector}"]`);
            const cssUnit = field.querySelector(`[data-builder-global-css-unit="${selector}"]`);
            if (cssInput && cssUnit && !field.contains(document.activeElement)) {
                const parsed = splitCssValue(currentValue);
                cssInput.value = parsed.number;
                if ([...cssUnit.options].some((option) => option.value === parsed.unit)) {
                    cssUnit.value = parsed.unit;
                }
            }
        });
    };

    const setGlobalThemeToken = (tokenName, value, options = {}) => {
        const token = globalThemeReferenceByName()[tokenName];
        if (!token) {
            return;
        }

        const normalizedValue = String(value ?? '').trim();
        if (!normalizedValue || normalizedValue === token.value) {
            delete globalThemeOverrides[tokenName];
        } else {
            globalThemeOverrides[tokenName] = normalizedValue;
        }

        applyGlobalThemeOverrides();
        if (options.render !== false) {
            renderGlobalThemePanel();
        } else {
            syncGlobalThemeTokenState(tokenName);
        }
        refreshCanvasOverlays();
    };

    const readGlobalCssControlValue = (target, tokenName) => {
        const field = target.closest('.viti-builder-css-value');
        const selector = cssSelectorValue(tokenName);
        const rawNumber = field?.querySelector(`[data-builder-global-css-value="${selector}"]`)?.value ?? '';
        const unitField = field?.querySelector(`[data-builder-global-css-unit="${selector}"]`);
        const selectedUnit = unitField?.value ?? '';
        const availableUnits = [...(unitField?.options || [])].map((option) => option.value);
        const unit = selectedUnit || (/^-?\d*\.?\d+$/.test(String(rawNumber).trim()) ? cssValueDefaultUnit(tokenName, availableUnits) : '');
        const number = clampBuilderValue(tokenName, rawNumber);

        return `${number}${unit}`;
    };

    const refreshCanvasStylesheets = () => {
        const document = editor.Canvas.getDocument();
        const cacheKey = String(Date.now());

        document?.querySelectorAll('link[rel="stylesheet"]').forEach((link) => {
            const href = link.getAttribute('href');
            if (!href || !href.includes('/build/')) {
                return;
            }

            const url = new URL(href, window.location.href);
            url.searchParams.set('builder_css', cacheKey);
            link.setAttribute('href', url.toString());
        });
    };

    const syncTailwindClasses = (force = false, forceCompile = false, options = {}) => {
        const refreshCanvas = options.refreshCanvas !== false;
        if (!tailwindUrl) {
            return Promise.resolve({ skipped: true });
        }

        const classes = collectCurrentTailwindClasses(editor);
        const signature = classes.join(' ');
        if (!signature) {
            return Promise.resolve({ skipped: true });
        }

        if (!tailwindClassesDirty && signature === tailwindSyncSignature && !forceCompile) {
            return Promise.resolve({ skipped: true });
        }

        if (!force && (!tailwindClassesDirty || signature === tailwindSyncSignature)) {
            return Promise.resolve({ skipped: true });
        }

        if (tailwindSyncRunning) {
            tailwindSyncQueued = true;

            return tailwindSyncPromise.then(() => syncTailwindClasses(force, forceCompile, options));
        }

        tailwindSyncRunning = true;
        tailwindClassesDirty = false;
        tailwindSyncSignature = signature;
        setStatus('Preview à jour · CSS en arrière-plan');

        tailwindSyncPromise = fetch(tailwindUrl, {
            method: 'POST',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRF-Token': csrfToken,
            },
            body: JSON.stringify({ classes, force: forceCompile }),
        })
            .then((response) => {
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}`);
                }

                return response.json();
            })
            .then((data) => {
                if (data.compile?.queued) {
                    setStatus('Compilation Tailwind planifiée', 'saved');

                    return data;
                }

                if (data.compile?.skipped) {
                    setStatus(data.updated === false ? 'Styles Tailwind à jour' : 'Classes Tailwind enregistrées', 'saved');

                    return data;
                }

                if (data.compiled) {
                    if (refreshCanvas) {
                        refreshCanvasStylesheets();
                    }
                    setStatus(data.updated === false ? 'Styles Tailwind à jour' : 'Styles Tailwind recompilés', 'saved');

                    return data;
                }

                setStatus('Classes Tailwind enregistrées', data.compile?.error ? 'error' : '');

                return data;
            })
            .catch(() => {
                tailwindSyncSignature = '';
                tailwindClassesDirty = true;
                setStatus('Erreur compilation Tailwind', 'error');

                return { error: true };
            })
            .finally(() => {
                tailwindSyncRunning = false;
                if (tailwindSyncQueued) {
                    tailwindSyncQueued = false;
                    window.setTimeout(() => syncTailwindClasses(), 400);
                }
            });

        return tailwindSyncPromise;
    };

    const scheduleTailwindClassesSync = () => {
        if (!tailwindUrl) {
            return;
        }

        tailwindClassesDirty = true;
        clearTimeout(tailwindSyncTimeout);
        tailwindSyncTimeout = null;
    };

    const flushTailwindClassesSync = (forceCompile = false, options = {}) => {
        clearTimeout(tailwindSyncTimeout);
        tailwindSyncTimeout = null;

        return syncTailwindClasses(true, forceCompile, options);
    };

    const openBackgroundPopoverKeys = () => [...inspector.querySelectorAll('.viti-builder-background-item.is-open, .viti-builder-background-add-popover.is-open')]
        .map((item) => {
            const layer = item.dataset.builderBackgroundLayer
                || item.querySelector('[data-builder-background-layer]')?.dataset.builderBackgroundLayer
                || 'container';
            const id = item.dataset.builderBackgroundItemId || (item.classList.contains('viti-builder-background-add-popover') ? 'new' : '');
            const popover = item.querySelector('.viti-builder-background-popover');
            const left = popover?.style.getPropertyValue('--builder-background-popover-left') || '';
            const top = popover?.style.getPropertyValue('--builder-background-popover-top') || '';

            return id ? {
                key: `${layer}:${id}`,
                left,
                top,
            } : null;
        })
        .filter(Boolean);

    const restoreBackgroundPopoverKeys = (entries) => {
        entries.forEach((entry) => {
            const normalizedEntry = typeof entry === 'string' ? { key: entry } : (entry || {});
            const [layer = 'container', id = ''] = String(normalizedEntry.key || '').split(':');
            const item = id === 'new'
                ? inspector.querySelector(`.viti-builder-background-add-popover [data-builder-background-layer="${cssSelectorValue(layer)}"]`)?.closest('.viti-builder-background-add-popover')
                : inspector.querySelector(`.viti-builder-background-item[data-builder-background-layer="${cssSelectorValue(layer)}"][data-builder-background-item-id="${cssSelectorValue(id)}"]`);
            const popover = item?.querySelector('.viti-builder-background-popover');
            if (!item || !popover) {
                return;
            }

            item.classList.add('is-open');
            if (normalizedEntry.left && normalizedEntry.top) {
                popover.style.setProperty('--builder-background-popover-left', normalizedEntry.left);
                popover.style.setProperty('--builder-background-popover-top', normalizedEntry.top);
            }
            positionBackgroundPopover(item, popover);
        });
    };

    const domInspectorInput = (label, datasetName, datasetValue, value, options = {}) => {
        const type = options.type || 'text';
        const placeholder = options.placeholder ? ` placeholder="${escapeHtml(options.placeholder)}"` : '';
        const readonly = options.readonly ? ' readonly' : '';

        return `
            <label class="viti-builder-inspector-field">
                <span class="viti-builder-property-label"><span>${escapeHtml(label)}</span></span>
                <input type="${escapeHtml(type)}" value="${escapeHtml(value)}" ${datasetName}="${escapeHtml(datasetValue)}"${placeholder}${readonly}>
            </label>
        `;
    };

    const domInspectorTextarea = (label, datasetName, datasetValue, value, options = {}) => {
        const rows = options.rows || 4;
        const placeholder = options.placeholder ? ` placeholder="${escapeHtml(options.placeholder)}"` : '';

        return `
            <label class="viti-builder-inspector-field">
                <span class="viti-builder-property-label"><span>${escapeHtml(label)}</span></span>
                <textarea rows="${escapeHtml(rows)}" ${datasetName}="${escapeHtml(datasetValue)}"${placeholder}>${escapeHtml(value)}</textarea>
            </label>
        `;
    };

    const domSelectedComponent = () => {
        const selected = editor.getSelected();

        if (!selected || selected === editor.getWrapper()) {
            return null;
        }

        return selected;
    };

    const domComponentMeta = (component) => component?.get?.('builderMeta') || {};
    const setDomComponentMeta = (component, nextMeta) => {
        if (!component) {
            return;
        }

        component.set('builderMeta', nextMeta && typeof nextMeta === 'object' ? nextMeta : {});
    };
    const domNodeId = (component) => String(domComponentMeta(component)?.nodeId || '').trim();
    const domCssModeKey = (component, attribute) => {
        const nodeId = domNodeId(component);
        const normalizedAttribute = String(attribute || '').trim();

        return nodeId && normalizedAttribute ? `${nodeId}::${normalizedAttribute}` : '';
    };
    const rememberDomCssMode = (component, attribute, mode) => {
        const key = domCssModeKey(component, attribute);
        if (!key) {
            return;
        }

        if (mode === 'custom' || mode === 'preset') {
            forcedDomCssModes.set(key, mode);

            return;
        }

        forcedDomCssModes.delete(key);
    };
    const forcedDomCssMode = (component, attribute) => forcedDomCssModes.get(domCssModeKey(component, attribute)) || '';

    const domComponentLabel = (component) => {
        if (!component) {
            return 'Nœud';
        }

        if (component.get('type') === 'textnode') {
            return '#text';
        }

        return String(component.get('tagName') || 'div').toLowerCase();
    };

    const syncDomComponentLayerName = (component) => {
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        component.set('name', domComponentLabel(component));
    };

    const stripLayerPanelTooltips = () => {
        document.querySelectorAll('#viti-builder-layers [title]').forEach((element) => {
            element.removeAttribute('title');
        });
    };

    const syncDomComponentTreeLayerNames = (component) => {
        if (!component) {
            return;
        }

        syncDomComponentLayerName(component);
        component.components?.().forEach((child) => syncDomComponentTreeLayerNames(child));
        requestAnimationFrame(stripLayerPanelTooltips);
    };

    const domComponentAttributes = (component) => ({ ...(component?.getAttributes?.() || {}) });
    const pendingDomDraftState = new WeakMap();
    const pendingDomAttributesByBreakpoint = (component) => {
        const values = pendingDomDraftState.get(component);

        return values && typeof values === 'object' && !Array.isArray(values) ? { ...values } : {};
    };
    const pendingDomAttributes = (component, breakpoint = activeInspectorBreakpoint) => {
        const values = pendingDomAttributesByBreakpoint(component)[breakpoint || 'base'];

        return values && typeof values === 'object' && !Array.isArray(values) ? { ...values } : {};
    };
    const domPreviewState = new WeakMap();
    const previewableDomStyleAttributes = new Set();
    const rememberPendingDomAttribute = (component, attribute, value, breakpoint = activeInspectorBreakpoint) => {
        const normalizedAttribute = String(attribute || '').trim();
        if (!component || !normalizedAttribute) {
            return;
        }

        const breakpointKey = breakpoint || 'base';
        const nextValuesByBreakpoint = pendingDomAttributesByBreakpoint(component);
        const nextValues = { ...(nextValuesByBreakpoint[breakpointKey] || {}) };
        const normalizedValue = value ?? '';

        if (normalizedValue === '' || normalizedValue === null) {
            delete nextValues[normalizedAttribute];
        } else {
            nextValues[normalizedAttribute] = normalizedValue;
        }

        if (Object.keys(nextValues).length) {
            nextValuesByBreakpoint[breakpointKey] = nextValues;
        } else {
            delete nextValuesByBreakpoint[breakpointKey];
        }

        if (Object.keys(nextValuesByBreakpoint).length) {
            pendingDomDraftState.set(component, nextValuesByBreakpoint);
            return;
        }

        pendingDomDraftState.delete(component);
    };
    const clearPendingDomAttributes = (component) => {
        if (!component) {
            return;
        }

        pendingDomDraftState.delete(component);
    };

    const domComponentChildren = (component) => {
        const children = component?.components?.();

        return Array.isArray(children) ? children : (children?.models || []);
    };

    const domStyleProperties = Object.keys( builderTextStyleDefaults.dom || {});
    const domLayoutProperties = Object.keys( builderLayoutStyleDefaults.dom || {});
    const domFlexProperties = Object.keys( builderFlexChildDefaults.dom || {});
    [...domStyleProperties, ...domLayoutProperties, ...domFlexProperties].forEach((property) => {
        previewableDomStyleAttributes.add( builderStyleAttribute('dom', property));
        previewableDomStyleAttributes.add( builderLayoutAttribute('dom', property));
        previewableDomStyleAttributes.add( builderFlexChildAttribute('dom', property));
    });

    const parseDomStyle = (value) => String(value || '')
        .split(';')
        .map((item) => item.trim())
        .filter(Boolean)
        .reduce((styles, declaration) => {
            const separator = declaration.indexOf(':');
            if (separator <= 0) {
                return styles;
            }

            const property = declaration.slice(0, separator).trim().toLowerCase();
            const propertyValue = declaration.slice(separator + 1).trim();
            if (property && propertyValue) {
                styles[property] = propertyValue;
            }

            return styles;
        }, {});

    const serializeDomStyle = (styles) => Object.entries(styles || {})
        .filter(([, value]) => String(value || '').trim() !== '')
        .map(([property, value]) => `${property}: ${String(value).trim()}`)
        .join('; ');

    const domBackgroundStyleProperties = [
        'background',
        'background-color',
        'background-image',
        'background-position',
        'background-size',
        'background-repeat',
        'background-origin',
        'background-attachment',
    ];
    const domBackgroundInitialState = new WeakMap();
    const captureDomBackgroundInitialState = (component) => {
        if (!component || component.get('type') === 'textnode' || domBackgroundInitialState.has(component)) {
            return;
        }

        const rawAttributes = domComponentAttributes(component);
        const styleMap = parseDomStyle(rawAttributes.style || '');
        const virtualAttributes = domComponentMeta(component)?.virtualAttributes || {};
        const backgroundStyles = {};
        domBackgroundStyleProperties.forEach((property) => {
            if (styleMap[property]) {
                backgroundStyles[property] = styleMap[property];
            }
        });

        domBackgroundInitialState.set(component, {
            className: normalizeBuilderClassList(rawAttributes.class || ''),
            backgroundClasses: tailwindBackgroundUtilities(rawAttributes.class || ''),
            backgroundStyles,
            collectionValue: typeof virtualAttributes === 'object' && virtualAttributes !== null
                ? String(virtualAttributes[ builderBackgroundItemsAttributeForLayer('dom')] || '')
                : '',
        });
    };
    const domBackgroundInitialValues = (component, layer = 'dom') => {
        captureDomBackgroundInitialState(component);
        const initial = domBackgroundInitialState.get(component) || {};
        const currentClassName = domComponentAttributes(component).class || '';
        const currentBaseClasses = withoutDomBackgroundOwnershipClasses(currentClassName);

        return {
            [ builderClassAttribute('dom')]: classNames(currentBaseClasses, ...(initial.backgroundClasses || [])),
            [ builderBackgroundItemsAttributeForLayer(layer)]: initial.collectionValue || '',
            [ builderStyleAttribute(layer, 'background-color')]: initial.backgroundStyles?.['background-color'] || '',
        };
    };
    const applyForcedDomCssModes = (component) => {
        if (!component) {
            return;
        }

        inspector.querySelectorAll('.viti-builder-css-value').forEach((control) => {
            const attribute = cssControlAttribute(control);
            const forcedMode = forcedDomCssMode(component, attribute);
            if (!forcedMode) {
                return;
            }

            const selector = cssSelectorValue(attribute);
            const unit = control.querySelector(`[data-builder-layer-css-unit="${selector}"]`);
            const token = control.querySelector(`[data-builder-layer-css-token="${selector}"]`);
            const isPresetText = control.dataset.builderCssKind === 'preset-text';
            const supportsToggle = isPresetText || !!token;

            if (!supportsToggle) {
                rememberDomCssMode(component, attribute, null);

                return;
            }

            if (!isPresetText && unit?.value !== 'tw') {
                rememberDomCssMode(component, attribute, null);

                return;
            }

            control.dataset.builderCssMode = forcedMode;
            syncCssControlPresentation(control, attribute);
        });
    };

    const domBuilderAttributes = (component, { includePending = true } = {}) => {
        const rawAttributes = domComponentAttributes(component);
        const classList = normalizeBuilderClassList(rawAttributes.class || '');
        const styleMap = parseDomStyle(rawAttributes.style || '');
        const virtualAttributes = domComponentMeta(component)?.virtualAttributes || {};
        const attributes = {
            [ builderClassAttribute('dom')]: classList,
            [ builderPresetAttribute('dom')]: 'custom',
            ...((typeof virtualAttributes === 'object' && virtualAttributes !== null && !Array.isArray(virtualAttributes)) ? virtualAttributes : {}),
        };

        domStyleProperties.forEach((property) => {
            if (styleMap[property]) {
                attributes[ builderStyleAttribute('dom', property)] = styleMap[property];
            }
        });

        domLayoutProperties.forEach((property) => {
            if (styleMap[property]) {
                attributes[ builderLayoutAttribute('dom', property)] = styleMap[property];
            }
        });

        domFlexProperties.forEach((property) => {
            if (styleMap[property]) {
                attributes[ builderFlexChildAttribute('dom', property)] = styleMap[property];
            }
        });

        if (includePending) {
            Object.assign(attributes, pendingDomAttributes(component));
        }

        return attributes;
    };

    const syncDomElementAttributes = (component, previousAttributes, nextAttributes) => {
        const element = component?.view?.el || component?.getEl?.();
        const nodeId = String(nextAttributes?.id || previousAttributes?.id || '').trim();
        const targets = new Set();
        if (element) {
            targets.add(element);
        }
        if (nodeId) {
            document.querySelectorAll(`#${cssSelectorValue(nodeId)}`).forEach((match) => targets.add(match));
        }
        if (!targets.size) {
            return;
        }

        targets.forEach((target) => {
            Object.keys(previousAttributes || {}).forEach((name) => {
                if (!Object.hasOwn(nextAttributes, name)) {
                    target.removeAttribute(name);
                }
            });
            Object.entries(nextAttributes || {}).forEach(([name, value]) => {
                target.setAttribute(name, String(value));
            });
        });
    };

    const replaceDomComponentAttributes = (component, attributes) => {
        const nextAttributes = { ...(attributes || {}) };

        component.set('attributes', nextAttributes);
        component.setAttributes(nextAttributes);
    };

    const applyDomBuilderAttributes = (component, builderAttributes) => {
        captureDomBackgroundInitialState(component);
        const nextAttributes = domComponentAttributes(component);
        const previousAttributes = { ...nextAttributes };
        const nextMeta = {
            ...domComponentMeta(component),
        };
        let nextClasses = normalizeBuilderClassList(builderAttributes[ builderClassAttribute('dom')] || '');
        const nextStyles = parseDomStyle(nextAttributes.style || '');
        const nextVirtualAttributes = {};

        domStyleProperties.forEach((property) => delete nextStyles[property]);
        domLayoutProperties.forEach((property) => delete nextStyles[property]);
        domFlexProperties.forEach((property) => delete nextStyles[property]);
        domBackgroundStyleProperties.forEach((property) => delete nextStyles[property]);

        Object.entries(builderAttributes || {}).forEach(([attribute, value]) => {
            const parts = builderStyleAttributeParts(attribute);
            if (!parts || parts.target !== 'dom') {
                if (attribute !==  builderClassAttribute('dom') && attribute !==  builderPresetAttribute('dom') && String(value ?? '').trim() !== '') {
                    nextVirtualAttributes[attribute] = value;
                }

                return;
            }

            if (!String(value || '').trim() || !isBuilderStyleOverride(attribute, value)) {
                return;
            }

            if (builderAttributeHasTailwindClassValue(attribute, value)) {
                return;
            }
        });

        const hasExplicitDomBackgroundCollection = hasExplicitBackgroundItemsCollection(nextVirtualAttributes, 'dom');
        const domBackgroundItems = parseBackgroundItems(nextVirtualAttributes, 'dom');
        if (hasExplicitDomBackgroundCollection) {
            nextClasses = classNames(
                withoutManagedTailwindBackgroundUtilities(nextClasses),
                ...(domBackgroundItems[0] ? tailwindBackgroundClassesFromItem(domBackgroundItems[0]) : []),
            );
        } else {
            const initial = domBackgroundInitialState.get(component);
            Object.assign(nextStyles, initial?.backgroundStyles || {});
        }

        if (nextClasses) {
            nextAttributes.class = nextClasses;
        } else {
            delete nextAttributes.class;
        }

        const serializedStyle = serializeDomStyle(nextStyles);
        if (serializedStyle) {
            nextAttributes.style = serializedStyle;
        } else {
            delete nextAttributes.style;
        }

        if (Object.keys(nextVirtualAttributes).length) {
            nextMeta.virtualAttributes = nextVirtualAttributes;
        } else {
            delete nextMeta.virtualAttributes;
        }

        component.set('builderMeta', nextMeta);
        const sanitizedAttributes = sanitizeDomAttributesForSave(nextAttributes);
        replaceDomComponentAttributes(component, sanitizedAttributes);
        syncDomElementAttributes(component, previousAttributes, sanitizedAttributes);
        scheduleTailwindClassesSync();
    };

    const domClassList = (component) => String(domComponentAttributes(component).class || '')
        .split(/\s+/)
        .map((className) => className.trim())
        .filter(Boolean);

    const setSelectedDomClassList = (classes) => {
        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        const attributes = domComponentAttributes(component);
        const normalizedClasses = [...new Set((classes || []).map((className) => String(className || '').trim()).filter(Boolean))];
        if (normalizedClasses.length) {
            attributes.class = normalizedClasses.join(' ');
        } else {
            delete attributes.class;
        }

        setDomComponentAttributes(component, attributes);
    };

    const domReplaceTailwindGroup = (matcher, value) => {
        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        const nextValue = String(value || '').trim();
        const classes = domClassList(component).filter((className) => !matcher.test(className));
        if (nextValue) {
            classes.push(nextValue);
        }

        setSelectedDomClassList(classes);
    };

    const domTailwindGroupValue = (matcher) => {
        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return '';
        }

        return domClassList(component).find((className) => matcher.test(className)) || '';
    };

    const domTailwindSelect = (label, group, matcher, options) => {
        const value = domTailwindGroupValue(matcher);
        const optionMarkup = Object.entries(options || {}).map(([optionValue, optionLabel]) => `
            <option value="${escapeHtml(optionValue)}"${optionValue === value ? ' selected' : ''}>${escapeHtml(optionLabel)}</option>
        `).join('');

        return `
            <label class="viti-builder-inspector-field">
                <span class="viti-builder-property-label"><span>${escapeHtml(label)}</span></span>
                <select data-builder-dom-tailwind="${escapeHtml(group)}">${optionMarkup}</select>
            </label>
        `;
    };

    const domKnownAttributeNames = () => new Set(
        (Array.isArray(documentInspectorSchema?.attributes) ? documentInspectorSchema.attributes : [])
            .map((field) => String(field?.name || '').trim())
            .filter(Boolean),
    );

    const domComponentTextValue = (component) => {
        if (!component) {
            return null;
        }

        if (component.get('type') === 'textnode') {
            return component.get('content') || '';
        }

        const children = domComponentChildren(component);
        if (!children.length) {
            return '';
        }

        if (!children.every((child) => child.get('type') === 'textnode')) {
            return null;
        }

        return children.map((child) => child.get('content') || '').join('');
    };

    const domAdvancedAttributesValue = (attributes) => Object.entries(attributes || {})
        .filter(([name]) => !domKnownAttributeNames().has(name) && !String(name || '').startsWith('data-gjs-'))
        .map(([name, value]) => `${name}=${value}`)
        .join('\n');

    const parseDomAdvancedAttributes = (value) => String(value || '')
        .split(/\r?\n/)
        .map((line) => line.trim())
        .filter(Boolean)
        .reduce((attributes, line) => {
            const separator = line.indexOf('=');
            if (separator <= 0) {
                return attributes;
            }

            const name = line.slice(0, separator).trim();
            const attributeValue = line.slice(separator + 1).trim();
            if (name) {
                attributes[name] = attributeValue;
            }

            return attributes;
        }, {});

    const setDomComponentAttributes = (component, nextAttributes, { rerender = false } = {}) => {
        if (!component) {
            return;
        }

        const previousAttributes = domComponentAttributes(component);
        const sanitizedAttributes = sanitizeDomAttributesForSave(nextAttributes);
        replaceDomComponentAttributes(component, sanitizedAttributes);
        syncDomElementAttributes(component, previousAttributes, sanitizedAttributes);
        scheduleTailwindClassesSync();
        if (rerender) {
            renderInspector({ preserveOpenSections: true, preserveOpenBackgrounds: false });
        }
    };

    const setSelectedDomAttribute = (name, value) => {
        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        const attributes = domComponentAttributes(component);
        const normalizedValue = String(value ?? '');
        if (normalizedValue.trim() === '') {
            delete attributes[name];
        } else {
            attributes[name] = normalizedValue;
        }

        setDomComponentAttributes(component, attributes);
    };

    const setSelectedDomAdvancedAttributes = (value) => {
        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        const currentAttributes = domComponentAttributes(component);
        const preservedAttributes = Object.fromEntries(
            Object.entries(currentAttributes).filter(([name]) => domKnownAttributeNames().has(name)),
        );
        setDomComponentAttributes(component, {
            ...preservedAttributes,
            ...parseDomAdvancedAttributes(value),
        });
    };

    const setSelectedDomTagName = (value) => {
        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        const tagName = String(value || '').trim().toLowerCase() || 'div';
        component.set('tagName', tagName);
        syncDomComponentLayerName(component);
        scheduleTailwindClassesSync();
        renderInspector({ preserveOpenSections: true, preserveOpenBackgrounds: false });
    };

    const setSelectedDomTextValue = (value) => {
        const component = domSelectedComponent();
        if (!component) {
            return;
        }

        if (component.get('type') === 'textnode') {
            component.set('content', value);

            return;
        }

        const currentValue = domComponentTextValue(component);
        if (currentValue === null) {
            return;
        }

        component.components(value === '' ? [] : [{ type: 'textnode', content: value }]);
    };

    const domInspectorFieldMarkup = (field, attributes) => {
        const name = String(field?.name || '').trim();
        if (!name) {
            return '';
        }

        const label = String(field?.label || name);
        const value = attributes[name] || '';
        const rows = Number.parseInt(field?.rows ?? 4, 10) || 4;

        return field?.type === 'textarea'
            ? domInspectorTextarea(label, 'data-builder-dom-attribute', name, value, { rows, placeholder: field?.placeholder || '' })
            : domInspectorInput(label, 'data-builder-dom-attribute', name, value, { type: field?.type || 'text', placeholder: field?.placeholder || '' });
    };

    const domInspectorTagMarkup = (tagName) => {
        const options = Array.isArray(documentInspectorSchema?.tagOptions) ? documentInspectorSchema.tagOptions : [];
        if (!options.length) {
            return domInspectorInput('Tag', 'data-builder-dom-tag', '1', tagName);
        }

        const optionMarkup = options
            .map((option) => {
                const value = String(option || '').trim().toLowerCase();
                if (!value) {
                    return '';
                }

                return `<option value="${escapeHtml(value)}"${value === tagName ? ' selected' : ''}>${escapeHtml(value)}</option>`;
            })
            .join('');

        return `
            <label class="viti-builder-inspector-field">
                <span class="viti-builder-property-label"><span>Tag</span></span>
                <select data-builder-dom-tag="1">${optionMarkup}</select>
            </label>
        `;
    };

    const builderExportSources = () => {
        const html = editor.getHtml({ cleanId: false }) || '';

        return [
            {
                key: 'html',
                label: 'HTML',
                content: html,
                language: 'html',
            },
            ...Object.entries(themeSourceFiles || {}).map(([key, source]) => ({
                key,
                label: source?.label || key,
                content: String(source?.content || ''),
                language: source?.language || 'scss',
            })),
        ].filter((item) => String(item.content || '').trim() !== '');
    };

    const escapeCodeHtml = (value) => String(value || '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
    const createHighlightPlaceholderStore = () => {
        const placeholders = [];

        return {
            put(html) {
                const key = `%%VITI_CODE_${placeholders.length}%%`;
                placeholders.push([key, html]);

                return key;
            },
            restore(content) {
                return placeholders.reduce((result, [key, html]) => result.replaceAll(key, html), content);
            },
        };
    };

    const highlightHtmlSource = (source) => {
        let html = escapeCodeHtml(source);

        html = html.replace(/(&lt;!--[\s\S]*?--&gt;)/g, '<span class="viti-builder-code-comment">$1</span>');
        html = html.replace(/(&lt;\/?)([a-zA-Z][\w:-]*)([^&]*?)(\/?&gt;)/g, (match, open, tag, attrs, close) => {
            const highlightedAttrs = attrs.replace(/([a-zA-Z_:][-a-zA-Z0-9_:.@]*)(=)("[^"]*"|'[^']*')/g, '<span class="viti-builder-code-attr">$1</span>$2<span class="viti-builder-code-string">$3</span>');

            return `<span class="viti-builder-code-punctuation">${open}</span><span class="viti-builder-code-tag">${tag}</span>${highlightedAttrs}<span class="viti-builder-code-punctuation">${close}</span>`;
        });

        return html;
    };

    const highlightScssSource = (source) => {
        const placeholders = createHighlightPlaceholderStore();
        let html = escapeCodeHtml(source);

        html = html.replace(/(\/\*[\s\S]*?\*\/|\/\/[^\n]*)/g, (match) => placeholders.put(`<span class="viti-builder-code-comment">${match}</span>`));
        html = html.replace(/("[^"]*"|'[^']*')/g, (match) => placeholders.put(`<span class="viti-builder-code-string">${match}</span>`));
        html = html.replace(/(^|\n)(\s*)(@[a-zA-Z-]+)/g, '$1$2<span class="viti-builder-code-keyword">$3</span>');
        html = html.replace(/(--[a-zA-Z0-9-]+)/g, '<span class="viti-builder-code-variable">$1</span>');
        html = html.replace(/(^|\n)(\s*)([a-zA-Z-]+)(\s*:)/g, '$1$2<span class="viti-builder-code-attr">$3</span>$4');

        return placeholders.restore(html);
    };

    const highlightSource = (language, source) => {
        if (language === 'html') {
            return highlightHtmlSource(source);
        }

        return highlightScssSource(source);
    };

    const openBuilderSourceViewer = () => {
        const sources = builderExportSources();
        const modal = editor?.Modal;
        if (!modal || !sources.length) {
            return;
        }

        const wrapper = document.createElement('div');
        const controls = document.createElement('div');
        const select = document.createElement('select');
        const copyButton = document.createElement('button');
        const code = document.createElement('code');
        const pre = document.createElement('pre');
        const style = document.createElement('style');

        wrapper.className = 'viti-builder-export-viewer';
        style.textContent = `
            .viti-builder-export-viewer pre {
                margin: 0;
                min-height: 60vh;
                padding: 16px;
                overflow: auto;
                background: #15171c;
                color: #e8eaf0;
                border: 1px solid #2c313a;
                border-radius: 10px;
                white-space: pre-wrap;
                font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
            }
            .viti-builder-export-viewer .viti-builder-code-tag { color: #7dd3fc; }
            .viti-builder-export-viewer .viti-builder-code-attr { color: #f9a8d4; }
            .viti-builder-export-viewer .viti-builder-code-string { color: #86efac; }
            .viti-builder-export-viewer .viti-builder-code-comment { color: #94a3b8; }
            .viti-builder-export-viewer .viti-builder-code-keyword { color: #c4b5fd; }
            .viti-builder-export-viewer .viti-builder-code-variable { color: #fdba74; }
            .viti-builder-export-viewer .viti-builder-code-punctuation { color: #cbd5e1; }
        `;
        controls.style.display = 'flex';
        controls.style.alignItems = 'center';
        controls.style.gap = '12px';
        controls.style.marginBottom = '12px';
        select.className = 'viti-builder-select';
        copyButton.type = 'button';
        copyButton.className = 'viti-builder-button';
        copyButton.textContent = 'Copier';

        sources.forEach((source) => {
            const option = document.createElement('option');
            option.value = source.key;
            option.textContent = source.label;
            select.append(option);
        });

        const update = () => {
            const source = sources.find((item) => item.key === select.value) || sources[0];
            code.innerHTML = highlightSource(source?.language || 'scss', source?.content || '');
        };

        select.addEventListener('change', update);
        copyButton.addEventListener('click', async () => {
            const source = sources.find((item) => item.key === select.value) || sources[0];

            try {
                await navigator.clipboard.writeText(source?.content || '');
                copyButton.textContent = 'Copié';
                window.setTimeout(() => {
                    copyButton.textContent = 'Copier';
                }, 1200);
            } catch (error) {
                const selection = window.getSelection();
                const range = window.document.createRange();
                range.selectNodeContents(code);
                selection?.removeAllRanges();
                selection?.addRange(range);
            }
        });

        pre.append(code);
        controls.append(select, copyButton);
        wrapper.append(style, controls, pre);
        update();

        modal.setTitle('Sources de la page');
        modal.setContent(wrapper);
        modal.open();
    };

    const renderInspector = ({ preserveOpenSections = false, preserveOpenBackgrounds = preserveOpenSections, preserveScroll = preserveOpenSections } = {}) => {
        if (applyingInspectorChange) {
            return;
        }

        const previouslyOpenSections = preserveOpenSections ? openInspectorSectionKeys() : rememberedOpenInspectorSectionKeys;
        const previouslyOpenBackgrounds = preserveOpenBackgrounds ? openBackgroundPopoverKeys() : [];
        const previousScrollElement = inspector.querySelector('.viti-builder-inspector-body') || inspector;
        const previousScrollTop = preserveScroll ? previousScrollElement.scrollTop : null;
        const selected = editor.getSelected();

        const component = domSelectedComponent();
        if (!component) {
            activeInspectorDomNodeId = '';
            inspector.innerHTML = inspectorPanel('Inspector', '<p class="viti-builder-inspector-empty">Sélectionner un nœud dans la page</p>');
            closeInspectorSections();

            return;
        }

        refreshDomInspectorComputedStyleValues(component);

        const type = component.get('type') || 'default';
        const tagName = component.get('tagName') || '';
        const attributes = domComponentAttributes(component);
        const builderAttributes = domBuilderAttributes(component);
        const parent = component.parent?.();
        const parentBuilderAttributes = parent && parent !== editor.getWrapper() && parent.get?.('type') !== 'textnode'
            ? domBuilderAttributes(parent)
            : null;
        const meta = domComponentMeta(component);
        activeInspectorDomNodeId = String(meta.nodeId || '').trim();
        const textValue = domComponentTextValue(component);
        const attributeFields = (Array.isArray(documentInspectorSchema?.attributes) ? documentInspectorSchema.attributes : [])
            .map((field) => domInspectorFieldMarkup(field, attributes))
            .filter(Boolean);
        const advanced = documentInspectorSchema?.advanced || {};
        const activeBreakpoint = builderBreakpointForDevice(activeBuilderDevice);
        const body = [
            inspectorSection('dom-node', 'Nœud', inspectorGrid(
                domInspectorInput('Type', 'data-builder-dom-readonly', type, type, { readonly: true }),
                domInspectorInput('Node ID', 'data-builder-dom-readonly', 'node-id', meta.nodeId || '', { readonly: true }),
                component.get('type') === 'textnode' ? '' : domInspectorTagMarkup(tagName),
            )),
            textValue !== null && meta.editableText !== false
                ? inspectorSection('dom-content', 'Contenu', domInspectorTextarea('Texte', 'data-builder-dom-text', '1', textValue, { rows: 6 }))
                : '',
            component.get('type') === 'textnode'
                ? ''
                : inspectorElementLayoutPanel('dom', builderAttributes, activeBreakpoint, parentBuilderAttributes),
            component.get('type') === 'textnode'
                ? ''
                : inspectorSizePanel('dom', builderAttributes),
            component.get('type') === 'textnode'
                ? ''
                : inspectorSpacePanel('dom', builderAttributes),
            component.get('type') === 'textnode'
                ? ''
                : inspectorPositionPanel('dom', builderAttributes),
            component.get('type') === 'textnode'
                ? ''
                : inspectorTypographyPanel('dom', builderAttributes),
            component.get('type') === 'textnode'
                ? ''
                : inspectorBackgroundPanel('dom', builderAttributes),
            component.get('type') === 'textnode'
                ? ''
                : inspectorBordersPanel('dom', builderAttributes),
            component.get('type') === 'textnode'
                ? ''
                : inspectorEffectsPanel('dom', builderAttributes),
            component.get('type') === 'textnode' || !attributeFields.length
                ? ''
                : inspectorSection('dom-attributes', 'Attributs', inspectorGrid(...attributeFields)),
            component.get('type') === 'textnode'
                ? ''
                : inspectorSection(
                    'dom-advanced',
                    'Attributs avancés',
                    domInspectorTextarea(
                        advanced.label || 'Un attribut par ligne (`nom=valeur`)',
                        'data-builder-dom-advanced-attributes',
                        '1',
                        domAdvancedAttributesValue(attributes),
                        { rows: advanced.rows || 8, placeholder: advanced.placeholder || '' },
                    ),
                ),
        ].filter(Boolean).join('');

        inspector.innerHTML = inspectorPanel(domComponentLabel(component), body, component.get('type') === 'textnode' ? 'Texte' : 'Nœud');
        applyForcedDomCssModes(component);
        restoreInspectorSectionKeys(previouslyOpenSections);
        restoreBackgroundPopoverKeys(previouslyOpenBackgrounds);
        if (previousScrollTop !== null) {
            requestAnimationFrame(() => {
                const nextScrollElement = inspector.querySelector('.viti-builder-inspector-body') || inspector;
                nextScrollElement.scrollTop = previousScrollTop;
            });
        }
    };

    const renderInspectorWhenReady = (options = {}, attempt = 0) => {
        if (applyingInspectorChange && attempt < 12) {
            requestAnimationFrame(() => renderInspectorWhenReady(options, attempt + 1));

            return;
        }

        renderInspector(options);
    };

    const refreshSelectionAfterHistory = () => {
        requestAnimationFrame(() => {
            requestAnimationFrame(() => {
                renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: false });
                requestAnimationFrame(() => applyPendingDomPreview(domSelectedComponent()));
            });
        });
    };

    const backgroundMutationAttributes = new Set([
         builderBackgroundItemsAttribute,
         builderBackgroundOverridesAttribute,
        ...Object.keys( builderBackgroundDefaults),
    ]);

    const isBackgroundMutation = (values) => Object.keys(values || {}).some((attribute) => (
        backgroundMutationAttributes.has(attribute)
        || isBackgroundItemAttribute(attribute)
        || attribute.includes('background')
    ));

    const backgroundHistorySignature = (attributes) => JSON.stringify(Object.fromEntries(
        Object.entries(attributes || {}).filter(([attribute]) => (
            attribute.includes('background')
            || attribute === 'data-overlay'
        )),
    ));

    const rememberBackgroundHistory = (before, after) => {
        if (restoringBackgroundHistory || !before || !after) {
            return;
        }
        if (backgroundHistorySignature(before) === backgroundHistorySignature(after)) {
            return;
        }

        backgroundHistory.push({ before, after });
        backgroundRedoHistory.length = 0;
    };

    const restoreBackgroundHistoryAttributes = (attributes) => {
        const component = domSelectedComponent();
        if (!component) {
            return false;
        }

        restoringBackgroundHistory = true;
        applyingInspectorChange = true;
        persistDomInspectorValues(component, attributes, { rerender: true, preserveSelection: true, clearPending: false, clearPreview: false });
        scheduleTailwindClassesSync();
        requestAnimationFrame(() => {
            applyingInspectorChange = false;
            restoringBackgroundHistory = false;
            renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: false });
            requestAnimationFrame(() => applyPendingDomPreview(component));
        });

        return true;
    };

    const undoBackgroundHistory = () => {
        const entry = backgroundHistory.pop();
        if (!entry) {
            return false;
        }

        backgroundRedoHistory.push(entry);

        return restoreBackgroundHistoryAttributes(entry.before);
    };

    const redoBackgroundHistory = () => {
        const entry = backgroundRedoHistory.pop();
        if (!entry) {
            return false;
        }

        backgroundHistory.push(entry);

        return restoreBackgroundHistoryAttributes(entry.after);
    };

    const createPropertyResetButton = (attribute) => {
        const reset = document.createElement('button');
        reset.type = 'button';
        reset.className = 'viti-builder-property-reset';
        reset.title = 'Annuler la surcharge';
        reset.setAttribute('aria-label', 'Annuler la surcharge');
        const resetHitArea = document.createElement('span');
        resetHitArea.dataset.builderResetAttribute = attribute;
        resetHitArea.textContent = '×';
        reset.append(resetHitArea);

        return reset;
    };

    const setInspectorElementState = (label, state, resetAttribute = '') => {
        if (!label) {
            return;
        }

        const states = String(state || '').split(/\s+/).filter(Boolean);
        const hasDefined = states.includes('defined');
        const hasOverride = states.includes('override');
        const labelMain = label.querySelector('.viti-builder-property-label__main') || label;
        let labelActions = label.querySelector('.viti-builder-property-actions');
        if (!labelActions && label.classList.contains('viti-builder-property-label')) {
            labelActions = document.createElement('span');
            labelActions.className = 'viti-builder-property-actions';
            label.append(labelActions);
        }

        label.classList.toggle('is-defined', hasDefined);
        label.classList.toggle('is-override', hasOverride);
        let reset = label.querySelector('.viti-builder-property-reset');
        label.querySelectorAll('.viti-builder-property-dot, :scope > i').forEach((dot) => dot.remove());

        [
            hasDefined ? 'is-defined-dot' : '',
            hasOverride ? 'is-override-dot' : '',
        ].filter(Boolean).forEach((className) => {
            const dot = document.createElement('i');
            dot.className = label.classList.contains('viti-builder-property-label')
                ? `viti-builder-property-dot ${className}`
                : className;
            dot.setAttribute('aria-hidden', 'true');
            labelMain.append(dot);
        });

        if (hasOverride && resetAttribute && !reset) {
            reset = createPropertyResetButton(resetAttribute);
        }
        if (reset && !hasOverride) {
            reset.remove();
        } else if (reset) {
            label.insertBefore(reset, labelActions || null);
        }
    };

    const syncBackgroundCollectionState = (attributes = currentLayerAttributes()) => {
        inspector.querySelectorAll('[data-builder-background-title-layer]').forEach((title) => {
            const layer = title.dataset.builderBackgroundTitleLayer;
            const state = backgroundCollectionState(attributes, layer);
            const label = title.querySelector('.viti-builder-property-label');
            const section = title.closest('.viti-builder-style-section');

            setInspectorElementState(label, state, backgroundCollectionResetAttribute(layer));

            const sectionLabel = section?.querySelector('.viti-builder-section-label');
            if (sectionLabel) {
                setInspectorElementState(sectionLabel, state);
            }
        });
    };

    const currentInspectorStateAttributes = () => {
        const component = domSelectedComponent();

        return {
            ...(component ? domBuilderAttributes(component) : {}),
            ...readDirtyInspectorAttributes(),
        };
    };

    const syncLiveInspectorControlState = (attribute, value) => {
        if (isBackgroundItemAttribute(attribute)) {
            return;
        }

        syncInspectorControlState(
            attribute,
            inspectorControlState(currentInspectorStateAttributes(), attribute, value),
        );
    };

    const cssValueFieldFromTarget = (target, attribute = '') => {
        if (!target) {
            return null;
        }

        const directField = target.closest?.('.viti-builder-css-value');
        if (directField) {
            return directField;
        }

        const selector = cssSelectorValue(attribute);

        return target.closest?.('.viti-builder-inspector-field')?.querySelector(`[data-builder-layer-css-value="${selector}"]`)?.closest('.viti-builder-css-value') || null;
    };

    const syncInspectorControlState = (attribute, state) => {
        const selector = cssSelectorValue(attribute);
        const hasOverrideState = String(state || '').split(/\s+/).includes('override');
        const controls = inspector.querySelectorAll(`[data-builder-layer-attribute="${selector}"], [data-builder-layer-css-value="${selector}"], [data-builder-layer-css-token="${selector}"], [data-builder-layer-css-unit="${selector}"]`);
        const fields = [...new Set([...controls].map((control) => control.closest('.viti-builder-inspector-field')).filter(Boolean))];
        const customLabels = [...new Set([...controls].map((control) => control.closest('[data-builder-gradient-stops-control]')?.querySelector('.viti-builder-property-label')).filter(Boolean))];
        const effectCollectionLabels = [...inspector.querySelectorAll(`[data-builder-effect-collection-state-for="${selector}"] .viti-builder-property-label`)];
        const sections = new Set();

        fields.forEach((field) => {
            const label = field.querySelector('.viti-builder-property-label');

            setInspectorElementState(label, state, attribute);

            let reset = label?.querySelector('.viti-builder-property-reset');
            if (hasOverrideState && label && !reset) {
                reset = createPropertyResetButton(attribute);
                label.insertBefore(reset, label.querySelector('.viti-builder-property-actions') || null);
            } else if (!hasOverrideState && reset) {
                reset.remove();
            }

            const section = field.closest('.viti-builder-style-section');
            if (section) {
                sections.add(section);
            }
        });
        customLabels.forEach((label) => {
            setInspectorElementState(label, state, attribute);
            const section = label.closest('.viti-builder-style-section');
            if (section) {
                sections.add(section);
            }
        });
        effectCollectionLabels.forEach((label) => {
            const resetAttribute = label.querySelector('[data-builder-reset-attribute]')?.dataset.builderResetAttribute
                || label.closest('[data-builder-effect-collection]')?.dataset.builderEffectCollection
                || attribute;

            setInspectorElementState(label, state, resetAttribute);
            let reset = label?.querySelector('.viti-builder-property-reset');
            if (hasOverrideState && label && !reset) {
                reset = createPropertyResetButton(resetAttribute);
                label.insertBefore(reset, label.querySelector('.viti-builder-property-actions') || null);
            } else if (!hasOverrideState && reset) {
                reset.remove();
            }
            const section = label.closest('.viti-builder-style-section');
            if (section) {
                sections.add(section);
            }
        });

        sections.forEach((section) => {
            const sectionLabel = section.querySelector('.viti-builder-section-label');
            if (!sectionLabel) {
                return;
            }
            const hasOverride = !!section.querySelector('.viti-builder-property-label.is-override');
            const hasDefined = !!section.querySelector('.viti-builder-property-label.is-defined');

            setInspectorElementState(sectionLabel, [
                hasDefined ? 'defined' : '',
                hasOverride ? 'override' : '',
            ].filter(Boolean).join(' ') || null);
        });
    };

    const readCssControlValue = (target, attribute) => {
        const field = cssValueFieldFromTarget(target, attribute);
        if (!field) {
            return '';
        }
        const selector = cssSelectorValue(attribute);
        const rawNumber = field?.querySelector(`[data-builder-layer-css-value="${selector}"]`)?.value ?? '';
        const rawToken = field?.querySelector(`[data-builder-layer-css-token="${selector}"]`)?.value ?? '';
        const unitField = field?.querySelector(`[data-builder-layer-css-unit="${selector}"]`);
        const selectedUnit = unitField?.value ?? '';
        const availableUnits = [...(unitField?.options || [])].map((option) => option.value);
        if (field?.dataset?.builderCssKind === 'preset-text') {
            return String(field?.dataset?.builderCssMode === 'custom' ? rawNumber : (rawToken || rawNumber)).trim();
        }
        if (selectedUnit === 'tw') {
            const useCustomMode = field?.dataset?.builderCssPresetOnly === 'true'
                ? false
                : field?.dataset?.builderCssMode === 'custom';
            const token = String(useCustomMode ? rawNumber : (rawToken || rawNumber)).trim();

            return token ? `tw:${token}` : '';
        }

        const unit = selectedUnit || (/^-?\d*\.?\d+$/.test(String(rawNumber).trim()) ? cssValueDefaultUnit(attribute, availableUnits) : '');
        const number = clampBuilderValue(attribute, rawNumber);

        return `${number}${unit}`;
    };

    const setInspectorCssValueValidity = (field, isValid) => {
        field?.classList.toggle('is-invalid', !isValid);
        field?.closest('.viti-builder-inspector-field')?.classList.toggle('is-invalid', !isValid);
    };

    const isInspectorCssValueValid = (field, attribute) => {
        const value = readCssControlValue(field, attribute);
        if (!String(value).trim()) {
            return true;
        }

        const property = builderStyleAttributeParts(attribute)?.property || '';
        if (['grid-template-columns', 'grid-template-rows'].includes(property)) {
            return window.CSS?.supports?.(property, value) ?? true;
        }

        if (!targetFromBuilderAttribute(attribute)) {
            return true;
        }

        if (String(value).startsWith('tw:')) {
            return tailwindClassFromBuilderAttribute(attribute, value) !== null;
        }

        return window.CSS?.supports?.(property, value) ?? true;
    };

    const validateInspectorCssValue = (target, attribute) => {
        const field = target.closest('.viti-builder-css-value');
        const isValid = isInspectorCssValueValid(field, attribute);
        setInspectorCssValueValidity(field, isValid);

        return isValid;
    };

    const updateBackgroundGradientPreview = (target) => {
        const targetAttribute = target?.dataset?.builderLayerAttribute
            || target?.dataset?.builderLayerCssValue
            || target?.dataset?.builderLayerCssToken
            || target?.dataset?.builderLayerCssUnit
            || '';
        const targetBackgroundItem = backgroundItemAttributeParts(targetAttribute);
        const isGradientAttribute = targetBackgroundItem
            ? targetBackgroundItem.field.startsWith('gradient')
            : ['data-overlay',
                'data-background-gradient-angle',
                'data-background-gradient-repeat',
                'data-background-gradient-radial',
                'data-background-gradient-size',
                'data-background-gradient-x',
                'data-background-gradient-y',
                'data-background-gradient-stops'].includes(targetAttribute);
        if (targetAttribute && !isGradientAttribute) {
            return;
        }

        const popover = target.closest?.('.viti-builder-background-popover');
        const track = popover?.querySelector('.viti-builder-gradient-track');
        if (!popover || !track) {
            return;
        }

        const itemId = popover.dataset.builderBackgroundItemId;
        const layer = popover.dataset.builderBackgroundLayer || 'container';
        const gradientAttribute = (field, fallback) => (itemId ? backgroundItemAttribute(itemId, field, layer) : fallback);
        const readAttribute = (attribute) => {
            const selector = cssSelectorValue(attribute);
            const cssControl = popover.querySelector(`[data-builder-layer-css-value="${selector}"], [data-builder-layer-css-token="${selector}"]`);
            if (cssControl) {
                return readCssControlValue(cssControl, attribute);
            }

            const toggle = popover.querySelector(`button[data-builder-layer-attribute="${selector}"]`);
            if (toggle) {
                return toggle.getAttribute('aria-pressed') === 'true' ? 'true' : 'false';
            }

            return popover.querySelector(`[data-builder-layer-attribute="${selector}"]`)?.value || '';
        };
        const gradient =  builderOverlayGradientValue({
            'data-overlay': readAttribute(gradientAttribute('gradientOverlay', 'data-overlay')) || 'dark',
            'data-background-gradient-angle': readAttribute(gradientAttribute('gradientAngle', 'data-background-gradient-angle')) || 'tw:to-t',
            'data-background-gradient-repeat': readAttribute(gradientAttribute('gradientRepeat', 'data-background-gradient-repeat')) || 'false',
            'data-background-gradient-radial': readAttribute(gradientAttribute('gradientRadial', 'data-background-gradient-radial')) || 'false',
            'data-background-gradient-size': readAttribute(gradientAttribute('gradientSize', 'data-background-gradient-size')) || '100px',
            'data-background-gradient-x': readAttribute(gradientAttribute('gradientX', 'data-background-gradient-x')) || '50%',
            'data-background-gradient-y': readAttribute(gradientAttribute('gradientY', 'data-background-gradient-y')) || '50%',
            'data-background-gradient-stops': readAttribute(gradientAttribute('gradientStops', 'data-background-gradient-stops')) || '',
        }) || `linear-gradient(0deg, ${ builderOverlayGradientStops('dark')})`;
        track.style.background = gradient;

        const canvasElement = itemId
            ? editor.Canvas.getDocument()?.querySelector(`[data-background-item-id="${cssSelectorValue(itemId)}"]`)
            : null;
        if (canvasElement) {
            const previewItem = normalizeBackgroundItem({
                id: itemId,
                type: 'gradient',
                overlay: readAttribute(gradientAttribute('gradientOverlay', 'data-overlay')) || 'dark',
                angle: readAttribute(gradientAttribute('gradientAngle', 'data-background-gradient-angle')) || 'tw:to-t',
                repeat: readAttribute(gradientAttribute('gradientRepeat', 'data-background-gradient-repeat')) || 'false',
                radial: readAttribute(gradientAttribute('gradientRadial', 'data-background-gradient-radial')) || 'false',
                size: readAttribute(gradientAttribute('gradientSize', 'data-background-gradient-size')) || '100px',
                x: readAttribute(gradientAttribute('gradientX', 'data-background-gradient-x')) || '50%',
                y: readAttribute(gradientAttribute('gradientY', 'data-background-gradient-y')) || '50%',
                stops: readAttribute(gradientAttribute('gradientStops', 'data-background-gradient-stops')) || '',
            }, 'gradient');
            const className = backgroundMutationClassName(canvasElement, previewItem);
            if (className) {
                canvasElement.setAttribute('class', className);
            }
            canvasElement.style.background = gradient;
        }
    };

    const writeCssControlValue = (control, attribute, value) => {
        const selector = cssSelectorValue(attribute);
        if (control?.dataset?.builderCssKind === 'preset-text') {
            const normalizedValue = String(value ?? '').trim();
            const input = control?.querySelector(`[data-builder-layer-css-value="${selector}"]`);
            const token = control?.querySelector(`[data-builder-layer-css-token="${selector}"]`);
            const hasKnownTokenValue = [...(token?.options || [])].some((option) => option.value === normalizedValue);

            if (input) {
                input.value = normalizedValue;
            }
            if (token) {
                token.value = hasKnownTokenValue
                    ? normalizedValue
                    : (token.options[0]?.value || '');
            }
            control.dataset.builderCssMode = !hasKnownTokenValue && normalizedValue !== ''
                ? 'custom'
                : 'preset';
            syncCssControlPresentation(control, attribute);

            return;
        }
        const parsed = splitCssValue(value);
        const input = control?.querySelector(`[data-builder-layer-css-value="${selector}"]`);
        const token = control?.querySelector(`[data-builder-layer-css-token="${selector}"]`);
        const unit = control?.querySelector(`[data-builder-layer-css-unit="${selector}"]`);
        if (input) {
            input.value = parsed.number;
        }
        if (token) {
            token.value = [...token.options].some((option) => option.value === parsed.number)
                ? parsed.number
                : token.options[0]?.value || '';
        }
        if (unit) {
            unit.value = [...unit.options].some((option) => option.value === parsed.unit) ? parsed.unit : '';
        }
        if (control) {
            control.dataset.builderCssMode = parsed.unit === 'tw' && token && ![...token.options].some((option) => option.value === parsed.number)
                ? 'custom'
                : 'preset';
        }
        syncCssControlPresentation(control, attribute);
    };

    const clearPendingDomPreview = (component) => {
        if (!component) {
            return;
        }

        const element = component.view?.el || component.getEl?.();
        if (!element) {
            return;
        }

        const state = domPreviewState.get(component);
        if (!state) {
            return;
        }

        if (typeof state.baseStyleAttr === 'string' && state.baseStyleAttr.trim() !== '') {
            element.setAttribute('style', state.baseStyleAttr);
        } else {
            element.removeAttribute('style');
        }

        domPreviewState.delete(component);
        refreshCanvasOverlays();
    };

    const applyPendingDomPreview = (component) => {
        if (!component) {
            return;
        }

        const element = component.view?.el || component.getEl?.();
        if (!element) {
            return;
        }

        const pendingAttributes = pendingDomAttributes(component);
        const previewStyles = {};
        Object.entries(pendingAttributes).forEach(([attribute, rawValue]) => {
            const mutation = builderStyleMutation(attribute, normalizeInspectorFieldValue(attribute, rawValue));
            if (!mutation) {
                return;
            }

            if (!mutation.value || !mutation.previewValue) {
                return;
            }

            previewStyles[mutation.property] = mutation.previewValue;
        });
        const backgroundAttributes = domBuilderAttributes(component);
        if (hasExplicitBackgroundItemsCollection(backgroundAttributes, 'dom')) {
            const backgroundLayers = parseBackgroundItems(backgroundAttributes, 'dom')
                .map((item) => normalizeBackgroundItem(item))
                .reverse()
                .map(backgroundItemCssLayer)
                .filter(Boolean);

            if (backgroundLayers.length) {
                previewStyles['background-image'] = backgroundLayers.map((layer) => layer.image).join(', ');
                previewStyles['background-position'] = backgroundLayers.map((layer) => layer.position).join(', ');
                previewStyles['background-size'] = backgroundLayers.map((layer) => layer.size).join(', ');
                previewStyles['background-repeat'] = backgroundLayers.map((layer) => layer.repeat).join(', ');
                previewStyles['background-origin'] = backgroundLayers.map((layer) => layer.origin).join(', ');
                previewStyles['background-attachment'] = backgroundLayers.map((layer) => layer.attachment).join(', ');
            }
        }

        const existingState = domPreviewState.get(component);
        const baseStyleAttr = existingState ? existingState.baseStyleAttr : (element.getAttribute('style') || '');
        domPreviewState.set(component, { baseStyleAttr });

        if (typeof baseStyleAttr === 'string' && baseStyleAttr.trim() !== '') {
            element.setAttribute('style', baseStyleAttr);
        } else {
            element.removeAttribute('style');
        }

        Object.entries(previewStyles).forEach(([property, propertyValue]) => {
            element.style.setProperty(property, propertyValue, 'important');
        });
        refreshCanvasOverlays();
    };

    const applyLiveInspectorCssPreview = (attribute, rawValue) => {
        const mutation = builderStyleMutation(attribute, normalizeInspectorFieldValue(attribute, rawValue));
        if (!mutation || isBackgroundItemAttribute(attribute)) {
            return;
        }

        const component = domSelectedComponent();
        const element = component?.view?.el || component?.getEl?.();
        if (!element) {
            return;
        }

        if (!mutation.value || !mutation.previewValue) {
            rememberPendingDomAttribute(component, attribute, '');
            applyPendingDomPreview(component);

            return;
        }

        rememberPendingDomAttribute(component, attribute, mutation.value);
        applyPendingDomPreview(component);
    };

    const cssControlAttribute = (target) => {
        const control = target?.closest?.('.viti-builder-css-value') || target;
        const input = control?.querySelector?.('[data-builder-layer-css-value]');
        const token = control?.querySelector?.('[data-builder-layer-css-token]');
        const unit = control?.querySelector?.('[data-builder-layer-css-unit]');

        return target?.dataset?.builderLayerCssValue
            || target?.dataset?.builderLayerCssUnit
            || target?.dataset?.builderLayerCssToken
            || input?.dataset?.builderLayerCssValue
            || token?.dataset?.builderLayerCssToken
            || unit?.dataset?.builderLayerCssUnit
            || '';
    };

    const syncCssControlPresentation = (control, attribute = '') => {
        const resolvedAttribute = attribute || cssControlAttribute(control);
        const selector = cssSelectorValue(resolvedAttribute);
        const input = control?.querySelector(`[data-builder-layer-css-value="${selector}"]`);
        const token = control?.querySelector(`[data-builder-layer-css-token="${selector}"]`);
        const unit = control?.querySelector(`[data-builder-layer-css-unit="${selector}"]`);
        const toggle = control?.closest('.viti-builder-inspector-field')?.querySelector(`[data-builder-layer-css-mode-group="${selector}"]`);
        const isTailwindToken = unit?.value === 'tw';
        const isPresetText = control?.dataset?.builderCssKind === 'preset-text';
        const isPresetOnly = control?.dataset?.builderCssPresetOnly === 'true';
        const isCustomMode = !isPresetOnly && control?.dataset?.builderCssMode === 'custom';
        const usesToggle = !!token && (isTailwindToken || isPresetText);

        input?.classList.toggle('is-hidden', usesToggle && !isCustomMode);
        token?.classList.toggle('is-hidden', !usesToggle || isCustomMode);
        toggle?.classList.toggle('is-hidden', !usesToggle);
        const toggleButton = toggle?.querySelector('[data-builder-layer-css-mode]');
        if (toggleButton) {
            toggleButton.dataset.builderLayerCssModeValue = isCustomMode ? 'custom' : 'preset';
            toggleButton.classList.toggle('is-active', isCustomMode);
            toggleButton.setAttribute('aria-pressed', isCustomMode ? 'true' : 'false');
            toggleButton.setAttribute('aria-label', isCustomMode ? 'Passer en mode Preset' : 'Passer en mode Custom');
            toggleButton.textContent = isCustomMode ? 'C' : 'P';
        }
    };

    const syncBackgroundPositionCustomFields = (target, value) => {
        const parts = backgroundItemAttributeParts(cssControlAttribute(target));
        const fieldsByGlobal = {
            imagePosition: ['imagePositionLeft', 'imagePositionTop'],
            imageSizeValue: ['imageSizeWidth', 'imageSizeHeight'],
        };
        const fields = fieldsByGlobal[parts?.field];
        if (!fields) {
            return;
        }

        const popover = target.closest?.('.viti-builder-background-popover');
        fields.forEach((field) => {
            const attribute = backgroundItemAttribute(parts.itemId, field, parts.layer);
            const control = popover?.querySelector(`[data-builder-layer-css-value="${cssSelectorValue(attribute)}"]`)?.closest('.viti-builder-css-value');
            if (!control) {
                return;
            }

            writeCssControlValue(control, attribute, value);
            syncCssControlPresentation(control, attribute);
        });
    };

    const syncBackgroundImagePositionPreview = (target) => {
        const popover = target.closest?.('.viti-builder-background-popover');
        const itemId = popover?.dataset.builderBackgroundItemId;
        if (!itemId) {
            return;
        }
        const layer = popover?.dataset.builderBackgroundLayer || 'container';

        const readItemField = (field) => {
            const attribute = backgroundItemAttribute(itemId, field, layer);
            const selector = cssSelectorValue(attribute);
            const cssControl = popover.querySelector(`[data-builder-layer-css-value="${selector}"], [data-builder-layer-css-token="${selector}"]`);
            if (cssControl) {
                return readCssControlValue(cssControl, attribute);
            }

            return popover.querySelector(`[data-builder-layer-attribute="${selector}"]`)?.value || '';
        };
        const globalPosition = readItemField('imagePosition');
        const left = readItemField('imagePositionLeft');
        const top = readItemField('imagePositionTop');
        const position = left || top
            ? `${left || backgroundImagePositionParts(globalPosition).left} ${top || backgroundImagePositionParts(globalPosition).top}`
            : backgroundImagePositionValue(globalPosition);
        const imageSize = readItemField('imageSize');
        const globalSize = readItemField('imageSizeValue');
        const width = readItemField('imageSizeWidth');
        const height = readItemField('imageSizeHeight');
        const size = imageSize === 'custom'
            ? `${width || globalSize || 'auto'} ${height || globalSize || width || 'auto'}`
            : (imageSize || 'cover');
        const canvasElement = editor.Canvas.getDocument()?.querySelector(`[data-background-item-id="${cssSelectorValue(itemId)}"]`);
        if (canvasElement) {
            if (position) {
                canvasElement.style.objectPosition = position;
                canvasElement.style.backgroundPosition = position;
            } else {
                canvasElement.style.removeProperty('object-position');
                canvasElement.style.removeProperty('background-position');
            }
            canvasElement.style.objectFit = ['cover', 'contain'].includes(size) ? size : 'none';
            canvasElement.style.backgroundSize = size;
        }
    };

    const normalizeInspectorFieldValue = (attribute, value) => {
        if (String(attribute || '').toLowerCase().includes('opacity')) {
            return normalizeOpacityValue(value);
        }

        if (attribute.endsWith('-flex-order') && String(value).trim() === '-') {
            return '';
        }

        return value;
    };

    const readDirtyInspectorAttributes = () => {
        const attributes = {};
        inspector.querySelectorAll('[data-builder-layer-attribute][data-builder-dirty="1"]').forEach((field) => {
            if (isBackgroundItemAttribute(field.dataset.builderLayerAttribute)) {
                return;
            }
            attributes[field.dataset.builderLayerAttribute] = normalizeInspectorFieldValue(field.dataset.builderLayerAttribute, field.value);
        });
        inspector.querySelectorAll('.viti-builder-css-value[data-builder-dirty="1"]').forEach((field) => {
            const input = field.querySelector('[data-builder-layer-css-value]');
            const attribute = input?.dataset.builderLayerCssValue;
            if (isBackgroundItemAttribute(attribute)) {
                return;
            }
            if (attribute && isInspectorCssValueValid(field, attribute)) {
                attributes[attribute] = readCssControlValue(field, attribute);
            }
        });

        return attributes;
    };

    const persistDomInspectorValues = (component, values, {
        rerender = true,
        preserveOpenBackgrounds = false,
        preserveScroll = true,
        breakpoint = null,
        clearPending = true,
        clearPreview = true,
    } = {}) => {
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        const activeBreakpoint = breakpoint ?? builderBreakpointForDevice(activeBuilderDevice);
        const attributes = domBuilderAttributes(component, { includePending: false });
        const normalizedValues = Object.fromEntries(Object.entries(values || {}).map(([attribute, rawValue]) => [
            attribute,
            normalizeInspectorFieldValue(attribute, rawValue),
        ]));
        const equivalentBackgroundCollections = Object.keys(normalizedValues).filter((attribute) => {
            if (!isBackgroundItemsCollectionAttribute(attribute)) {
                return false;
            }

            const layer = layerFromBackgroundItemsCollectionAttribute(attribute) || 'container';
            const colorAttribute =  builderStyleAttribute(layer, 'background-color');
            const colorValue = normalizedValues[colorAttribute];
            if (!String(colorValue ?? '').trim()) {
                return false;
            }

            const inheritedValue = inheritedBuilderAttributeValue(attributes, colorAttribute);

            return inheritedValue !== null && equivalentInspectorValue(colorValue, inheritedValue);
        });

        applyBuilderStyleValuesToAttributes(attributes, normalizedValues, activeBreakpoint);
        equivalentBackgroundCollections.forEach((attribute) => {
            attributes[attribute] = '';
        });

        const normalizedAttributes = withTailwindClassOverridesForBreakpoint(attributes, activeBreakpoint);
        if (clearPreview) {
            clearPendingDomPreview(component);
        }
        if (clearPending) {
            clearPendingDomAttributes(component);
        }
        applyingInspectorChange = true;
        applyDomBuilderAttributes(component, normalizedAttributes);

        if (!rerender) {
            applyingInspectorChange = false;
            return;
        }

        requestAnimationFrame(() => {
            editor.select(component);
            applyingInspectorChange = false;
            renderInspector({ preserveOpenSections: true, preserveOpenBackgrounds, preserveScroll });
        });
    };

    const flushPendingDomNodeChanges = (component, options = {}) => {
        const valuesByBreakpoint = pendingDomAttributesByBreakpoint(component);
        if (!Object.keys(valuesByBreakpoint).length) {
            return false;
        }

        Object.entries(valuesByBreakpoint).forEach(([breakpointKey, values]) => {
            persistDomInspectorValues(component, values, {
                ...options,
                breakpoint: breakpointKey === 'base' ? '' : breakpointKey,
                clearPending: false,
                clearPreview: options.preservePreview !== true,
                rerender: false,
            });
        });
        if (options.preservePreview !== true) {
            clearPendingDomPreview(component);
        }
        clearPendingDomAttributes(component);
        if (options.rerender !== false) {
            requestAnimationFrame(() => {
                editor.select(component);
                renderInspector({
                    preserveOpenSections: true,
                    preserveOpenBackgrounds: options.preserveOpenBackgrounds === true,
                    preserveScroll: options.preserveScroll !== false,
                });
            });
        }

        return true;
    };

    const walkDomComponents = (component, callback) => {
        if (!component) {
            return;
        }

        callback(component);
        domComponentChildren(component).forEach((child) => walkDomComponents(child, callback));
    };

    const flushPendingDomNodeChangesForAll = (options = {}) => {
        editor.getWrapper().components().forEach((component) => {
            walkDomComponents(component, (item) => {
                flushPendingDomNodeChanges(item, options);
            });
        });
    };

    const persistDomBackgroundMutationValues = (component, values) => {
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        const pendingValues = pendingDomAttributes(component);
        const valuesToPersist = {};
        const pendingValuesToRestore = {};

        Object.entries(pendingValues).forEach(([attribute, rawValue]) => {
            const normalizedValue = normalizeInspectorFieldValue(attribute, rawValue);
            if (isBackgroundMutation({ [attribute]: normalizedValue })) {
                valuesToPersist[attribute] = normalizedValue;
            } else {
                pendingValuesToRestore[attribute] = normalizedValue;
            }
        });

        Object.entries(values || {}).forEach(([attribute, rawValue]) => {
            valuesToPersist[attribute] = normalizeInspectorFieldValue(attribute, rawValue);
        });

        persistDomInspectorValues(component, valuesToPersist, { rerender: false });
        Object.entries(pendingValuesToRestore).forEach(([attribute, value]) => {
            rememberPendingDomAttribute(component, attribute, value);
        });
        applyPendingDomPreview(component);
    };

    const applyInspectorAttributes = (values) => {
        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return;
        }
        const preserveOpenBackgrounds = isBackgroundMutation(values);

        if (preserveOpenBackgrounds) {
            persistDomBackgroundMutationValues(component, values);

            return;
        }

        Object.entries(values || {}).forEach(([attribute, rawValue]) => {
            rememberPendingDomAttribute(component, attribute, normalizeInspectorFieldValue(attribute, rawValue));
            syncInspectorControlState(attribute, inspectorControlState(
                domBuilderAttributes(component),
                attribute,
                normalizeInspectorFieldValue(attribute, rawValue),
            ));
        });
        applyPendingDomPreview(component);

        if (Object.keys(values || {}).some((attribute) => attribute.endsWith('-layout-display')
            || attribute.endsWith('-style-display')
            || attribute.endsWith('-style-position'))) {
            requestAnimationFrame(() => {
                renderInspector({ preserveOpenSections: true, preserveOpenBackgrounds });
                applyPendingDomPreview(component);
            });
        }
    };

    const applyInspectorValue = (attribute, value) => {
        applyInspectorAttributes({ [attribute]: value });
    };

    const currentLayerAttributes = () => {
        const component = domSelectedComponent();

        return component ? domBuilderAttributes(component) : {};
    };

    const applyBackgroundItemValue = (attribute, value) => {
        const parts = backgroundItemAttributeParts(attribute);
        if (!parts) {
            return false;
        }

        const currentAttributes = currentLayerAttributes();
        const values = backgroundItemsUpdateValue(currentAttributes, parts.itemId, parts.field, value, parts.layer);
        const nextAttributes = {
            ...currentAttributes,
            ...values,
        };
        const nextItem = parseBackgroundItems(nextAttributes, parts.layer).find((item) => item.id === parts.itemId);

        applyInspectorAttributes(values);
        syncInspectorControlState(attribute, backgroundItemFieldState(nextItem, parts.field));
        syncBackgroundCollectionState(nextAttributes);

        return true;
    };

    const applyEffectCollectionItems = (layer, property, items, options = {}) => {
        const collectionAttribute =  builderEffectCollectionAttribute(layer, property);
        const styleAttribute =  builderStyleAttribute(layer, property);
        const normalizedItems = (items || []).map((item, index) => normalizeEffectCollectionItem(item, index));
        const singleItems = normalizedItems.length ? [normalizedItems[normalizedItems.length - 1]] : [];
        const values = {
            [collectionAttribute]: singleItems.length ? serializeEffectCollectionItems(singleItems) : '',
            [styleAttribute]: effectCollectionValue(singleItems, property),
        };

        applyInspectorAttributes(values);
        if (options.render !== false) {
            renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: false });
        }
    };

    const gradientStopsControl = (target) => target.closest?.('[data-builder-gradient-stops-control]');

    const readGradientStopsControl = (control) => normalizeGradientStops(
        control?.querySelector('input[data-builder-layer-attribute]')?.value || '',
    );

    const activeGradientStopId = (control) => control?.querySelector('.viti-builder-gradient-stop.is-active')?.dataset.builderGradientStop
        || readGradientStopsControl(control)[0]?.id
        || '';

    const setActiveGradientStop = (control, stopId) => {
        if (!control || !stopId) {
            return;
        }

        control.querySelectorAll('[data-builder-gradient-stop]').forEach((button) => {
            button.classList.toggle('is-active', button.dataset.builderGradientStop === stopId);
        });
        control.querySelectorAll('[data-builder-gradient-stop-editor]').forEach((editor) => {
            editor.classList.toggle('is-active', editor.dataset.builderGradientStopEditor === stopId);
        });
    };

    const writeGradientStopsControl = (control, stops, activeId = '') => {
        if (!control) {
            return;
        }

        const normalizedStops = normalizeGradientStops(stops);
        const serialized = serializeGradientStops(normalizedStops);
        const hiddenInput = control.querySelector('input[data-builder-layer-attribute]');
        const activeStopId = activeId || activeGradientStopId(control) || normalizedStops[0]?.id || '';
        const track = control.querySelector('[data-builder-gradient-track]');
        const editors = control.querySelector('.viti-builder-gradient-stop-editors');
        const existingIds = [...(track?.querySelectorAll('[data-builder-gradient-stop]') || [])].map((button) => button.dataset.builderGradientStop).join('|');
        const nextIds = normalizedStops.map((stop) => stop.id).join('|');
        const shouldRebuild = existingIds !== nextIds;

        if (hiddenInput) {
            hiddenInput.value = serialized;
            hiddenInput.dataset.builderDirty = '1';
        }

        if (track && shouldRebuild) {
            track.innerHTML = normalizedStops.map((stop, index) => `
                <button type="button" class="viti-builder-gradient-stop${(activeStopId || normalizedStops[0]?.id) === stop.id ? ' is-active' : ''}" data-builder-gradient-stop="${escapeHtml(stop.id)}" style="left: ${escapeHtml(stop.position)}%; background: ${escapeHtml(colorWithAlpha(stop.color, stop.alpha))};" aria-label="Modifier le point ${index + 1}"></button>
            `).join('');
        }
        if (editors && shouldRebuild) {
            editors.innerHTML = normalizedStops.map((stop) => `
            <div class="viti-builder-gradient-stop-editor${(activeStopId || normalizedStops[0]?.id) === stop.id ? ' is-active' : ''}" data-builder-gradient-stop-editor="${escapeHtml(stop.id)}">
                <label class="viti-builder-gradient-color-field">
                    <span>Couleur</span>
                    <span class="viti-builder-gradient-color-picker">
                        <button type="button" data-builder-gradient-stop-color="${escapeHtml(stop.id)}" style="--builder-pickr-color: ${escapeHtml(colorWithAlpha(stop.color, stop.alpha))};" aria-label="Modifier la couleur"></button>
                        <span data-builder-gradient-stop-color-label="${escapeHtml(stop.id)}">${escapeHtml(colorWithAlpha(stop.color, stop.alpha))}</span>
                    </span>
                </label>
                <label>
                    <span>Position</span>
                    <span class="viti-builder-gradient-position-control">
                        <input type="number" min="0" max="100" step="1" value="${escapeHtml(Math.round(stop.position * 10) / 10)}" data-builder-gradient-stop-position="${escapeHtml(stop.id)}" title="Glisser pour ajuster">
                        <span>%</span>
                    </span>
                </label>
                <button type="button" data-builder-gradient-stop-remove="${escapeHtml(stop.id)}"${normalizedStops.length <= 2 ? ' disabled' : ''}>×</button>
            </div>
            `).join('');
        }

        normalizedStops.forEach((stop) => {
            const stopButton = control.querySelector(`[data-builder-gradient-stop="${cssSelectorValue(stop.id)}"]`);
            const editor = control.querySelector(`[data-builder-gradient-stop-editor="${cssSelectorValue(stop.id)}"]`);

            if (stopButton) {
                stopButton.style.left = `${stop.position}%`;
                stopButton.style.background = colorWithAlpha(stop.color, stop.alpha);
            }
            if (editor) {
                const colorButton = editor.querySelector('[data-builder-gradient-stop-color]');
                const positionInput = editor.querySelector('[data-builder-gradient-stop-position]');
                const removeButton = editor.querySelector('[data-builder-gradient-stop-remove]');

                if (colorButton) {
                    colorButton.style.setProperty('--builder-pickr-color', colorWithAlpha(stop.color, stop.alpha));
                }
                const colorLabel = editor.querySelector('[data-builder-gradient-stop-color-label]');
                if (colorLabel) {
                    colorLabel.textContent = colorWithAlpha(stop.color, stop.alpha);
                }
                if (positionInput) {
                    positionInput.value = Math.round(stop.position * 10) / 10;
                }
                if (removeButton) {
                    removeButton.disabled = normalizedStops.length <= 2;
                }
            }
        });
        setActiveGradientStop(control, activeStopId);
        updateBackgroundGradientPreview(hiddenInput || control);
    };

    const applyGradientStopsControl = (control, stops, activeId = '') => {
        const hiddenInput = control?.querySelector('input[data-builder-layer-attribute]');
        if (!hiddenInput) {
            return;
        }

        writeGradientStopsControl(control, stops, activeId);
        applyBackgroundItemValue(hiddenInput.dataset.builderLayerAttribute, hiddenInput.value);
    };

    const gradientStopPositionFromPointer = (track, event) => {
        const rect = track.getBoundingClientRect();

        return Math.round(clampNumber(((event.clientX - rect.left) / rect.width) * 100, 0, 100, 0) * 10) / 10;
    };

    let activePickr = null;

    const closeActivePickr = () => {
        if (!activePickr) {
            return;
        }

        const pickr = activePickr;
        activePickr = null;
        pickr.destroyAndRemove();
    };

    const openGradientColorPickr = (trigger) => {
        const control = gradientStopsControl(trigger);
        const stopId = trigger.dataset.builderGradientStopColor;
        const stop = readGradientStopsControl(control).find((item) => item.id === stopId);
        if (!control || !stop) {
            return;
        }

        closeActivePickr();
        setActiveGradientStop(control, stopId);
        activePickr = Pickr.create({
            el: trigger,
            theme: 'nano',
            default: colorWithAlpha(stop.color, stop.alpha),
            useAsButton: true,
            components: {
                preview: true,
                opacity: true,
                hue: true,
                interaction: {
                    hex: true,
                    rgba: true,
                    input: true,
                    clear: false,
                    save: false,
                },
            },
        });
        activePickr
            .on('change', (color) => {
                const [red, green, blue, alpha] = color.toRGBA();
                const nextStops = readGradientStopsControl(control).map((item) => (
                    item.id === stopId
                        ? { ...item, color: hexFromRgb(red, green, blue), alpha: clampNumber(alpha, 0, 1, item.alpha) }
                        : item
                ));

                applyGradientStopsControl(control, nextStops, stopId);
            })
            .on('hide', () => {
                closeActivePickr();
            });
        activePickr.show();
        requestAnimationFrame(constrainActiveColorPickr);
    };

    const openBackgroundColorPickr = (trigger) => {
        const attribute = trigger.dataset.builderBackgroundColorAttribute;
        const control = trigger.closest('.viti-builder-background-color-field');
        const input = control?.querySelector(`[data-builder-layer-attribute="${cssSelectorValue(attribute)}"]`);
        const label = control?.querySelector(`[data-builder-background-color-label="${cssSelectorValue(attribute)}"]`);
        const currentColor = input?.value || 'rgba(0, 0, 0, 0.5)';
        if (!attribute || !input) {
            return;
        }

        closeActivePickr();
        activePickr = Pickr.create({
            el: trigger,
            theme: 'nano',
            default: currentColor,
            useAsButton: true,
            components: {
                preview: true,
                opacity: true,
                hue: true,
                interaction: {
                    hex: true,
                    rgba: true,
                    input: true,
                    clear: false,
                    save: false,
                },
            },
        });
        activePickr
            .on('change', (color) => {
                const [red, green, blue, alpha] = color.toRGBA();
                const nextColor = colorWithAlpha(hexFromRgb(red, green, blue), alpha);

                input.value = nextColor;
                input.dataset.builderDirty = '1';
                trigger.style.setProperty('--builder-pickr-color', nextColor);
                if (label) {
                    label.textContent = nextColor;
                }
                if (!applyBackgroundItemValue(attribute, nextColor)) {
                    applyInspectorValue(attribute, nextColor);
                }
            })
            .on('hide', () => {
                closeActivePickr();
            });
        activePickr.show();
        requestAnimationFrame(constrainActiveColorPickr);
    };

    const openInspectorColorPickr = (trigger) => {
        const attribute = trigger.dataset.builderColorFor;
        const control = trigger.closest('.viti-builder-color-control');
        const input = control?.querySelector(`[data-builder-layer-attribute="${cssSelectorValue(attribute)}"]`);
        const label = control?.querySelector(`[data-builder-color-label="${cssSelectorValue(attribute)}"]`);
        const currentColor = input?.value || 'currentColor';
        const pickrDefault = /^(?:#|rgb|hsl)/i.test(currentColor) ? currentColor : hexColorValue(currentColor);
        if (!attribute || !input) {
            return;
        }

        closeActivePickr();
        activePickr = Pickr.create({
            el: trigger,
            theme: 'nano',
            default: pickrDefault,
            useAsButton: true,
            components: {
                preview: true,
                opacity: true,
                hue: true,
                interaction: {
                    hex: true,
                    rgba: true,
                    input: true,
                    clear: false,
                    save: false,
                },
            },
        });
        activePickr
            .on('change', (color) => {
                const [red, green, blue, alpha] = color.toRGBA();
                const nextColor = colorWithAlpha(hexFromRgb(red, green, blue), alpha);

                input.value = nextColor;
                input.dataset.builderDirty = '1';
                trigger.style.setProperty('--builder-pickr-color', nextColor);
                if (label) {
                    label.textContent = nextColor;
                }
                applyInspectorValue(attribute, nextColor);
            })
            .on('hide', () => {
                closeActivePickr();
            });
        activePickr.show();
        requestAnimationFrame(constrainActiveColorPickr);
    };

    const openGlobalColorPickr = (trigger) => {
        const tokenName = trigger.dataset.builderGlobalColorFor;
        const control = trigger.closest('.viti-builder-color-control');
        const input = control?.querySelector(`[data-builder-global-token="${cssSelectorValue(tokenName)}"]`);
        const label = control?.querySelector(`[data-builder-color-label="${cssSelectorValue(tokenName)}"]`);
        const currentColor = input?.value || globalThemeTokenValue(tokenName) || 'currentColor';
        const pickrDefault = /^(?:#|rgb|hsl)/i.test(currentColor) ? currentColor : hexColorValue(currentColor);
        if (!tokenName || !input) {
            return;
        }

        closeActivePickr();
        activePickr = Pickr.create({
            el: trigger,
            theme: 'nano',
            default: pickrDefault,
            useAsButton: true,
            components: {
                preview: true,
                opacity: true,
                hue: true,
                interaction: {
                    hex: true,
                    rgba: true,
                    input: true,
                    clear: false,
                    save: false,
                },
            },
        });
        activePickr
            .on('change', (color) => {
                const [red, green, blue, alpha] = color.toRGBA();
                const nextColor = colorWithAlpha(hexFromRgb(red, green, blue), alpha);

                input.value = nextColor;
                trigger.style.setProperty('--builder-pickr-color', nextColor);
                if (label) {
                    label.textContent = nextColor;
                }
                setGlobalThemeToken(tokenName, nextColor, { render: false });
            })
            .on('hide', () => {
                closeActivePickr();
            });
        activePickr.show();
        requestAnimationFrame(constrainActiveColorPickr);
    };

    const resetInspectorAttribute = (attribute) => {
        const backgroundItemReset = backgroundItemResetParts(attribute);
        if (backgroundItemReset) {
            const attributes = currentLayerAttributes();
            const nextItems = parseBackgroundItems(attributes, backgroundItemReset.layer).map((item) => (
                item.id === backgroundItemReset.itemId
                    ? resetBackgroundItemToDefaults(item)
                    : item
            ));

            applyInspectorAttributes(backgroundCollectionMutationValues(backgroundItemReset.layer, nextItems));
            renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: true, preserveScroll: true });

            return;
        }

        const backgroundCollectionLayer = layerFromBackgroundCollectionResetAttribute(attribute);
        if (backgroundCollectionLayer) {
            const component = domSelectedComponent();
            if (!component || component.get('type') === 'textnode') {
                return;
            }

            applyInspectorAttributes(domBackgroundInitialValues(component, backgroundCollectionLayer));
            renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: true, preserveScroll: true });

            return;
        }

        const effectCollection = effectCollectionAttributeParts(attribute);
        if (effectCollection) {
            const component = domSelectedComponent();
            if (!component || component.get('type') === 'textnode') {
                return;
            }

            persistDomInspectorValues(component, {
                [ builderEffectCollectionAttribute(effectCollection.layer, effectCollection.property)]: '',
                [ builderStyleAttribute(effectCollection.layer, effectCollection.property)]: '',
            }, { preserveOpenBackgrounds: false, preserveScroll: true });

            return;
        }

        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return;
        }
        const { defaults, property } = defaultsFromBuilderAttribute(attribute);
        const activeBreakpoint = builderBreakpointForDevice(activeBuilderDevice);
        const attributes = domBuilderAttributes(component, { includePending: false });
        const resetAttributes = [...new Set([attribute, ...dependentBuilderAttributesForReset(attribute)])];

        resetAttributes.forEach((resetAttribute) => {
            const resetDefaults = resetAttribute === attribute
                ? { defaults, property }
                : defaultsFromBuilderAttribute(resetAttribute);

            removeImportantClassOverrides(attributes, resetAttribute, activeBreakpoint, { includeInherited: true });
            rememberDomCssMode(component, resetAttribute, null);
            attributes[resetAttribute] = resetDefaults.defaults[resetDefaults.property] ?? '';
        });
        const normalizedAttributes = withTailwindClassOverridesForBreakpoint(attributes, activeBreakpoint);

        clearPendingDomPreview(component);
        resetAttributes.forEach((resetAttribute) => {
            rememberPendingDomAttribute(component, resetAttribute, '', activeBreakpoint);
            rememberDomCssMode(component, resetAttribute, null);
        });
        applyingInspectorChange = true;
        applyDomBuilderAttributes(component, normalizedAttributes);
        applyPendingDomPreview(component);
        requestAnimationFrame(() => {
            editor.select(component);
            applyingInspectorChange = false;
            renderInspector({ preserveOpenSections: true, preserveOpenBackgrounds: false, preserveScroll: true });
            applyPendingDomPreview(component);
        });
    };

    const scheduleInspectorValueApply = (attribute, value, delay = 32) => {
        const component = domSelectedComponent();
        if (!component || component.get('type') === 'textnode') {
            return;
        }

        if (isBackgroundMutation({ [attribute]: value })) {
            if (!applyBackgroundItemValue(attribute, value)) {
                applyInspectorValue(attribute, value);
            }

            return;
        }

        rememberPendingDomAttribute(component, attribute, normalizeInspectorFieldValue(attribute, value));
    };

    const flushInspectorValueApply = () => {
        pendingInspectorApply = null;
        inspectorApplyTimeout = null;
    };

    const constrainFixedElementToViewport = (element, margin = 10) => {
        if (!element) {
            return;
        }

        const rect = element.getBoundingClientRect();
        const maxLeft = Math.max(margin, window.innerWidth - rect.width - margin);
        const maxTop = Math.max(margin, window.innerHeight - rect.height - margin);
        const left = clampNumber(rect.left, margin, maxLeft, margin);
        const top = clampNumber(rect.top, margin, maxTop, margin);

        element.style.left = `${left}px`;
        element.style.top = `${top}px`;
    };

    const constrainActiveColorPickr = () => {
        const app = activePickr?.getRoot?.()?.app || document.querySelector('.pcr-app.visible');
        constrainFixedElementToViewport(app, 10);
    };

    const positionBackgroundPopover = (anchor, popover) => {
        if (!anchor || !popover) {
            return;
        }

        const rect = anchor.getBoundingClientRect();
        const margin = 10;
        const width = Math.min(320, window.innerWidth - (margin * 2));
        const leftSpace = rect.left - margin;
        const rightSpace = window.innerWidth - rect.right - margin;
        const preferredLeft = leftSpace >= width || leftSpace >= rightSpace
            ? rect.left - width - margin
            : rect.right + margin;
        const maxLeft = Math.max(margin, window.innerWidth - width - margin);
        const left = clampNumber(preferredLeft, margin, maxLeft, margin);

        popover.style.setProperty('--builder-background-popover-left', `${left}px`);
        popover.style.setProperty('--builder-background-popover-top', `${Math.max(margin, rect.top)}px`);

        requestAnimationFrame(() => {
            const popoverRect = popover.getBoundingClientRect();
            const maxTop = Math.max(margin, window.innerHeight - popoverRect.height - margin);
            const top = clampNumber(rect.top, margin, maxTop, margin);

            popover.style.setProperty('--builder-background-popover-top', `${top}px`);
        });
    };

    const positionOpenBackgroundPopovers = () => {
        inspector.querySelectorAll('.viti-builder-background-item.is-open, .viti-builder-background-add-popover.is-open').forEach((item) => {
            positionBackgroundPopover(item, item.querySelector('.viti-builder-background-popover'));
        });
    };

    const positionOpenFloatingControls = () => {
        positionOpenBackgroundPopovers();
        requestAnimationFrame(constrainActiveColorPickr);
    };

    const closeBackgroundPopovers = (scope = inspector) => {
        scope.querySelectorAll('.viti-builder-background-item.is-open, .viti-builder-background-add-popover.is-open').forEach((item) => {
            item.classList.remove('is-open');
        });
    };

    const clearBackgroundDropState = () => {
        inspector.querySelectorAll('.viti-builder-background-item.is-dragging, .viti-builder-background-item.is-drop-before, .viti-builder-background-item.is-drop-after').forEach((item) => {
            item.classList.remove('is-dragging', 'is-drop-before', 'is-drop-after');
        });
    };

    const backgroundDropPlacement = (event, item) => {
        const rect = item.getBoundingClientRect();

        return event.clientY < rect.top + (rect.height / 2) ? 'before' : 'after';
    };

    const reorderBackgroundItems = (sourceId, targetId, placement) => {
        if (!sourceId || !targetId || sourceId === targetId) {
            return;
        }

        const attributes = currentLayerAttributes();
        const targetItemElement = inspector.querySelector(`.viti-builder-background-item[data-builder-background-item-id="${cssSelectorValue(targetId)}"]`);
        const layer = targetItemElement?.dataset.builderBackgroundLayer || 'container';
        const items = [...parseBackgroundItems(attributes, layer)].reverse();
        const source = items.find((item) => item.id === sourceId);
        const targetIndex = items.findIndex((item) => item.id === targetId);
        if (!source || targetIndex < 0) {
            return;
        }

        const withoutSource = items.filter((item) => item.id !== sourceId);
        const adjustedTargetIndex = withoutSource.findIndex((item) => item.id === targetId);
        const insertIndex = placement === 'after' ? adjustedTargetIndex + 1 : adjustedTargetIndex;

        withoutSource.splice(insertIndex, 0, source);
        applyInspectorAttributes({
            [ builderBackgroundItemsAttributeForLayer(layer)]: serializeBackgroundItems([...withoutSource].reverse()),
        });
        renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: false });
    };

    const cssStepSize = (attribute, unit) => {
        if (attribute.includes('opacity')) {
            return 0.01;
        }

        if (unit === 'em' || unit === 'rem') {
            return 0.1;
        }

        if (unit === '' && !attribute.includes('z-index') && !attribute.includes('order')) {
            return 0.1;
        }

        return 1;
    };

    const applyCssControlStep = (field, attribute, direction, multiplier = 1, baseValue = null) => {
        const selector = cssSelectorValue(attribute);
        const input = field?.querySelector(`[data-builder-layer-css-value="${selector}"]`);
        const unit = field?.querySelector(`[data-builder-layer-css-unit="${selector}"]`)?.value ?? '';
        if (unit === 'tw') {
            if (!input) {
                return;
            }

            input.value = tailwindScaleTokenStep(baseValue === null ? input.value : baseValue, direction, multiplier);
            field.dataset.builderDirty = '1';
            if (!validateInspectorCssValue(field, attribute)) {
                return;
            }

            const nextValue = readCssControlValue(field, attribute);
            applyLiveInspectorCssPreview(attribute, nextValue);
            scheduleInspectorValueApply(attribute, nextValue);
            syncBackgroundPositionCustomFields(field, nextValue);
            updateBackgroundGradientPreview(field);
            syncBackgroundImagePositionPreview(field);
            syncLiveInspectorControlState(attribute, nextValue);

            return;
        }

        const current = baseValue === null ? Number.parseFloat(input?.value ?? '') : baseValue;

        if (!input || Number.isNaN(current)) {
            return;
        }

        const next = Math.round((current + (direction * cssStepSize(attribute, unit) * multiplier)) * 100) / 100;
        input.value = clampBuilderValue(attribute, next);
        field.dataset.builderDirty = '1';
        if (!validateInspectorCssValue(field, attribute)) {
            return;
        }

        const nextValue = readCssControlValue(field, attribute);
        applyLiveInspectorCssPreview(attribute, nextValue);
        scheduleInspectorValueApply(attribute, nextValue);
        syncBackgroundPositionCustomFields(field, nextValue);
        updateBackgroundGradientPreview(field);
        syncBackgroundImagePositionPreview(field);
        syncLiveInspectorControlState(attribute, nextValue);
    };

    let cssDragHold = null;
    let backgroundDragItemId = '';
    let gradientStopDrag = null;
    let gradientStopPositionDrag = null;

    const stopCssDrag = () => {
        if (!cssDragHold) {
            return;
        }

        cssDragHold.input.classList.remove('is-dragging');
        cssDragHold = null;
    };

    const stopGradientStopDrag = () => {
        gradientStopDrag = null;
    };

    const stopGradientStopPositionDrag = () => {
        if (gradientStopPositionDrag?.input) {
            gradientStopPositionDrag.input.classList.remove('is-dragging');
        }
        gradientStopPositionDrag = null;
    };

    const startGradientStopDrag = (button, event) => {
        const control = gradientStopsControl(button);
        const track = control?.querySelector('[data-builder-gradient-track]');
        const stopId = button.dataset.builderGradientStop;
        if (!control || !track || !stopId) {
            return;
        }

        event.preventDefault();
        event.stopPropagation();
        setActiveGradientStop(control, stopId);
        button.setPointerCapture?.(event.pointerId);
        gradientStopDrag = {
            control,
            pointerId: event.pointerId,
            stopId,
            track,
        };
    };

    const startGradientStopPositionDrag = (input, event) => {
        const control = gradientStopsControl(input);
        const stopId = input.dataset.builderGradientStopPosition;
        let initialValue = Number.parseFloat(input.value ?? '');
        if (!control || !stopId) {
            return;
        }

        if (Number.isNaN(initialValue)) {
            initialValue = 0;
            input.value = '0';
        }

        input.setPointerCapture?.(event.pointerId);
        gradientStopPositionDrag = {
            control,
            input,
            initialValue,
            lastSteps: 0,
            moved: false,
            pointerId: event.pointerId,
            startX: event.clientX,
            startY: event.clientY,
            stopId,
        };
    };

    const startCssDrag = (input, event) => {
        const attribute = input.dataset.builderLayerCssValue;
        const field = input.closest('.viti-builder-css-value');
        if (!field || !attribute) {
            return;
        }
        if (field.dataset.builderCssKind === 'preset-text') {
            return;
        }
        const selector = cssSelectorValue(attribute);
        const unitField = field.querySelector(`[data-builder-layer-css-unit="${selector}"]`);
        const isTailwindToken = unitField?.value === 'tw';
        let initialValue = isTailwindToken ? String(input.value || '').trim() : Number.parseFloat(input.value ?? '');

        if (isTailwindToken) {
            initialValue = nearestTailwindScaleToken(initialValue);
            input.value = initialValue;
        } else if (Number.isNaN(initialValue)) {
            const availableUnits = [...(unitField?.options || [])].map((option) => option.value);
            const defaultUnit = cssValueDefaultUnit(attribute, availableUnits);
            initialValue = 0;
            input.value = '0';
            if (unitField && !unitField.value && defaultUnit) {
                unitField.value = defaultUnit;
            }
        }

        input.setPointerCapture?.(event.pointerId);
        cssDragHold = {
            attribute,
            field,
            input,
            initialValue,
            lastSteps: 0,
            moved: false,
            pointerId: event.pointerId,
            startX: event.clientX,
            startY: event.clientY,
        };
    };

    inspector.addEventListener('dragstart', (event) => {
        const handle = event.target.closest('[data-builder-background-drag]');
        if (!handle) {
            return;
        }

        closeBackgroundPopovers(handle.closest('.viti-builder-background-list'));
        const item = handle.closest('.viti-builder-background-item');
        const itemId = handle.dataset.builderBackgroundDrag;
        backgroundDragItemId = itemId;
        event.dataTransfer.effectAllowed = 'move';
        event.dataTransfer.setData('text/plain', itemId);
        event.dataTransfer.setData('application/x-viti-background-item', itemId);
        item?.classList.add('is-dragging');
    });

    inspector.addEventListener('dragover', (event) => {
        const targetItem = event.target.closest('.viti-builder-background-item');
        const sourceId = backgroundDragItemId || event.dataTransfer?.getData('application/x-viti-background-item') || event.dataTransfer?.getData('text/plain');
        if (!targetItem || !sourceId || targetItem.dataset.builderBackgroundItemId === sourceId) {
            return;
        }

        event.preventDefault();
        event.dataTransfer.dropEffect = 'move';
        inspector.querySelectorAll('.viti-builder-background-item.is-drop-before, .viti-builder-background-item.is-drop-after').forEach((item) => {
            if (item !== targetItem) {
                item.classList.remove('is-drop-before', 'is-drop-after');
            }
        });
        const placement = backgroundDropPlacement(event, targetItem);
        targetItem.classList.toggle('is-drop-before', placement === 'before');
        targetItem.classList.toggle('is-drop-after', placement === 'after');
    });

    inspector.addEventListener('dragleave', (event) => {
        const targetItem = event.target.closest('.viti-builder-background-item');
        if (targetItem && !targetItem.contains(event.relatedTarget)) {
            targetItem.classList.remove('is-drop-before', 'is-drop-after');
        }
    });

    inspector.addEventListener('drop', (event) => {
        const targetItem = event.target.closest('.viti-builder-background-item');
        const sourceId = backgroundDragItemId || event.dataTransfer?.getData('application/x-viti-background-item') || event.dataTransfer?.getData('text/plain');
        if (!targetItem || !sourceId) {
            clearBackgroundDropState();

            return;
        }

        event.preventDefault();
        const placement = backgroundDropPlacement(event, targetItem);
        reorderBackgroundItems(sourceId, targetItem.dataset.builderBackgroundItemId, placement);
        clearBackgroundDropState();
    });

    inspector.addEventListener('dragend', () => {
        backgroundDragItemId = '';
        clearBackgroundDropState();
    });

    const keyboardStepForInput = (input, event) => {
        const explicitStep = Number.parseFloat(input.getAttribute('step') || '');
        const baseStep = Number.isNaN(explicitStep) || explicitStep <= 0 ? 1 : explicitStep;

        if (event.shiftKey) {
            return baseStep * 10;
        }

        if (event.altKey) {
            return baseStep / 10;
        }

        return baseStep;
    };

    const incrementInspectorInputValue = (input, direction, event) => {
        const currentValue = String(input.value || '').trim();
        const currentNumber = Number.parseFloat(currentValue);
        if (Number.isNaN(currentNumber)) {
            if (currentValue === '') {
                input.value = String(direction * keyboardStepForInput(input, event));

                return true;
            }

            return false;
        }

        const decimals = (value) => String(value).split('.')[1]?.length || 0;
        const step = keyboardStepForInput(input, event);
        const precision = Math.max(decimals(currentNumber), decimals(step), 0);
        const nextValue = currentNumber + (direction * step);
        input.value = String(Number(nextValue.toFixed(Math.min(precision, 6))));

        return true;
    };

    const isKeyboardIncrementableInspectorInput = (input) => {
        if (!input || input.disabled || input.readOnly) {
            return false;
        }

        if (input.matches('[data-builder-layer-css-value]')) {
            return true;
        }

        if (!input.matches('input[data-builder-layer-attribute], input[data-builder-gradient-stop-alpha], input[data-builder-gradient-stop-position]')) {
            return false;
        }

        return input.type === 'number' || /^-?\d*\.?\d*$/.test(String(input.value || '').trim());
    };

    inspector.addEventListener('keydown', (event) => {
        if (!['ArrowUp', 'ArrowDown'].includes(event.key) || event.metaKey || event.ctrlKey) {
            return;
        }

        const input = event.target.closest?.('input');
        if (!isKeyboardIncrementableInspectorInput(input)) {
            return;
        }

        const direction = event.key === 'ArrowUp' ? 1 : -1;
        if (!incrementInspectorInputValue(input, direction, event)) {
            return;
        }

        event.preventDefault();
        input.dispatchEvent(new Event('input', { bubbles: true }));
    });

    inspector.addEventListener('input', (event) => {
        const textField = event.target.closest('[data-builder-dom-text]');
        if (textField) {
            setSelectedDomTextValue(textField.value);

            return;
        }

        const attributeField = event.target.closest('[data-builder-dom-attribute]');
        if (attributeField) {
            setSelectedDomAttribute(attributeField.dataset.builderDomAttribute, attributeField.value);

            return;
        }

        const advancedField = event.target.closest('[data-builder-dom-advanced-attributes]');
        if (advancedField) {
            setSelectedDomAdvancedAttributes(advancedField.value);

            return;
        }

        const tagField = event.target.closest('[data-builder-dom-tag]');
        if (tagField) {
            setSelectedDomTagName(tagField.value);
            return;
        }

        const gradientStopInput = event.target.closest('[data-builder-gradient-stop-color], [data-builder-gradient-stop-alpha], [data-builder-gradient-stop-position]');
        if (gradientStopInput) {
            const control = gradientStopsControl(gradientStopInput);
            const stopId = gradientStopInput.dataset.builderGradientStopColor
                || gradientStopInput.dataset.builderGradientStopAlpha
                || gradientStopInput.dataset.builderGradientStopPosition;
            const stops = readGradientStopsControl(control).map((stop) => {
                if (stop.id !== stopId) {
                    return stop;
                }

                if (gradientStopInput.matches('[data-builder-gradient-stop-color]')) {
                    return { ...stop, color: gradientStopInput.value };
                }
                if (gradientStopInput.matches('[data-builder-gradient-stop-alpha]')) {
                    return { ...stop, alpha: clampNumber(gradientStopInput.value, 0, 1, stop.alpha) };
                }

                return { ...stop, position: clampNumber(gradientStopInput.value, 0, 100, stop.position) };
            });

            applyGradientStopsControl(control, stops, stopId);

            return;
        }

        const colorPicker = event.target.closest('[data-builder-color-for]');
        if (colorPicker) {
            const attribute = colorPicker.dataset.builderColorFor;
            const textField = colorPicker.closest('.viti-builder-color-control')?.querySelector(`[data-builder-layer-attribute="${cssSelectorValue(attribute)}"]`);
            if (textField) {
                textField.value = colorPicker.value;
                textField.dataset.builderDirty = '1';
            }
            if (!applyBackgroundItemValue(attribute, colorPicker.value)) {
                applyInspectorValue(attribute, colorPicker.value);
            }

            return;
        }

        const effectCollectionField = event.target.closest('[data-builder-effect-collection-value]');
        if (effectCollectionField) {
            const parts = effectCollectionAttributeParts(effectCollectionField.dataset.builderEffectCollectionValue);
            const itemId = effectCollectionField.dataset.builderEffectCollectionItemId || '';
            if (!parts || !itemId) {
                return;
            }

            const currentItems = parseEffectCollectionItems(currentLayerAttributes(), parts.layer, parts.property);
            const items = (currentItems.length ? currentItems : [{
                id: itemId,
                value: '',
                createdInBuilder: true,
            }])
                .map((item) => (
                    item.id === itemId
                        ? { ...item, value: effectCollectionField.value }
                        : item
                ));
            applyEffectCollectionItems(parts.layer, parts.property, items, { render: false });

            return;
        }

        const cssField = event.target.closest('[data-builder-layer-css-value]');
        if (cssField) {
            const attribute = cssField.dataset.builderLayerCssValue;
            cssField.value = clampBuilderValue(attribute, cssField.value);
            const cssControl = cssField.closest('.viti-builder-css-value');
            const cssToken = cssControl?.querySelector(`[data-builder-layer-css-token="${cssSelectorValue(attribute)}"]`);
            const cssUnit = cssControl?.querySelector(`[data-builder-layer-css-unit="${cssSelectorValue(attribute)}"]`);
            if (cssToken && cssControl?.dataset?.builderCssPresetOnly !== 'true' && (cssUnit?.value === 'tw' || cssControl?.dataset?.builderCssKind === 'preset-text')) {
                cssControl.dataset.builderCssMode = 'custom';
                cssToken.value = [...cssToken.options].some((option) => option.value === cssField.value)
                    ? cssField.value
                    : (cssToken.value || cssToken.options[0]?.value || '');
                syncCssControlPresentation(cssControl, attribute);
                rememberDomCssMode(domSelectedComponent(), attribute, 'custom');
            }
            cssControl.dataset.builderDirty = '1';
            if (!validateInspectorCssValue(cssField, attribute)) {
                return;
            }

            applyLiveInspectorCssPreview(attribute, readCssControlValue(cssField, attribute));
            scheduleInspectorValueApply(attribute, readCssControlValue(cssField, attribute));
            syncBackgroundPositionCustomFields(cssField, readCssControlValue(cssField, attribute));
            updateBackgroundGradientPreview(cssField);
            syncBackgroundImagePositionPreview(cssField);
            if (!isBackgroundItemAttribute(attribute)) {
                syncLiveInspectorControlState(attribute, readCssControlValue(cssField, attribute));
            }

            return;
        }

        const field = event.target.closest('input[data-builder-layer-attribute], textarea[data-builder-layer-attribute]');
        if (!field) {
            return;
        }

        if (field.tagName === 'INPUT' && field.type === 'number') {
            field.value = clampBuilderValue(field.dataset.builderLayerAttribute, field.value);
        }

        field.dataset.builderDirty = '1';
        const attribute = field.dataset.builderLayerAttribute;
        const value = normalizeInspectorFieldValue(attribute, field.value);
        applyLiveInspectorCssPreview(attribute, value);
        if (!applyBackgroundItemValue(attribute, value)) {
            applyInspectorValue(attribute, value);
        }
        updateBackgroundGradientPreview(field);
    });

    inspector.addEventListener('change', (event) => {
        const tagField = event.target.closest('[data-builder-dom-tag]');
        if (tagField) {
            setSelectedDomTagName(tagField.value);

            return;
        }

        const attributeField = event.target.closest('[data-builder-dom-attribute]');
        if (attributeField) {
            setSelectedDomAttribute(attributeField.dataset.builderDomAttribute, attributeField.value);

            return;
        }

        const advancedField = event.target.closest('[data-builder-dom-advanced-attributes]');
        if (advancedField) {
            setSelectedDomAdvancedAttributes(advancedField.value);
            return;
        }

        const cssUnit = event.target.closest('[data-builder-layer-css-unit]');
        if (cssUnit) {
            const attribute = cssUnit.dataset.builderLayerCssUnit;
            const control = cssUnit.closest('.viti-builder-css-value');
            control.dataset.builderDirty = '1';
            const isTailwindToken = cssUnit.value === 'tw';
            const tokenSelect = control.querySelector(`[data-builder-layer-css-token="${cssSelectorValue(attribute)}"]`);
            const cssInput = control.querySelector(`[data-builder-layer-css-value="${cssSelectorValue(attribute)}"]`);
            if (isTailwindToken && cssInput && !cssInput.value.trim()) {
                cssInput.value = tokenSelect?.value || '0';
            }
            if (isTailwindToken && tokenSelect && control.dataset.builderCssPresetOnly !== 'true') {
                control.dataset.builderCssMode = [...tokenSelect.options].some((option) => option.value === cssInput?.value)
                    ? 'preset'
                    : 'custom';
            } else {
                control.dataset.builderCssMode = 'preset';
            }
            rememberDomCssMode(domSelectedComponent(), attribute, isTailwindToken ? control.dataset.builderCssMode : null);
            syncCssControlPresentation(control, attribute);
            if (!validateInspectorCssValue(cssUnit, attribute)) {
                return;
            }

            applyLiveInspectorCssPreview(attribute, readCssControlValue(cssUnit, attribute));
            scheduleInspectorValueApply(attribute, readCssControlValue(cssUnit, attribute));
            syncBackgroundPositionCustomFields(cssUnit, readCssControlValue(cssUnit, attribute));
            updateBackgroundGradientPreview(cssUnit);
            syncBackgroundImagePositionPreview(cssUnit);

            return;
        }

        const cssToken = event.target.closest('[data-builder-layer-css-token]');
        if (cssToken) {
            const attribute = cssToken.dataset.builderLayerCssToken;
            const control = cssToken.closest('.viti-builder-css-value');
            const cssInput = control?.querySelector(`[data-builder-layer-css-value="${cssSelectorValue(attribute)}"]`);
            if (cssInput && cssToken.value) {
                cssInput.value = cssToken.value;
            }
            control.dataset.builderCssMode = 'preset';
            rememberDomCssMode(domSelectedComponent(), attribute, 'preset');
            syncCssControlPresentation(control, attribute);
            control.dataset.builderDirty = '1';
            if (!validateInspectorCssValue(cssToken, attribute)) {
                return;
            }
            applyLiveInspectorCssPreview(attribute, readCssControlValue(cssToken, attribute));
            scheduleInspectorValueApply(attribute, readCssControlValue(cssToken, attribute));
            syncBackgroundPositionCustomFields(cssToken, readCssControlValue(cssToken, attribute));
            updateBackgroundGradientPreview(cssToken);
            syncBackgroundImagePositionPreview(cssToken);
            if (!isBackgroundItemAttribute(attribute)) {
                syncLiveInspectorControlState(attribute, readCssControlValue(cssToken, attribute));
            }

            return;
        }

        const field = event.target.closest('select[data-builder-layer-attribute]');
        if (!field) {
            return;
        }

        field.dataset.builderDirty = '1';
        applyLiveInspectorCssPreview(field.dataset.builderLayerAttribute, field.value);
        if (!applyBackgroundItemValue(field.dataset.builderLayerAttribute, field.value)) {
            applyInspectorValue(field.dataset.builderLayerAttribute, field.value);
        }
        if (backgroundItemAttributeParts(field.dataset.builderLayerAttribute)?.field === 'imageSize') {
            const customSizeBlock = field.closest('.viti-builder-background-popover')
                ?.querySelector('[data-builder-background-size-custom]');

            if (customSizeBlock) {
                const isCustomSize = field.value === 'custom';
                customSizeBlock.classList.toggle('is-hidden', !isCustomSize);
                customSizeBlock.hidden = !isCustomSize;
            }
        }
        updateBackgroundGradientPreview(field);
        syncBackgroundImagePositionPreview(field);
    });

    inspector.addEventListener('pointerdown', (event) => {
        const gradientPositionInput = event.target.closest('[data-builder-gradient-stop-position]');
        if (gradientPositionInput && event.button === 0) {
            startGradientStopPositionDrag(gradientPositionInput, event);

            return;
        }

        const gradientStop = event.target.closest('[data-builder-gradient-stop]');
        if (gradientStop && event.button === 0) {
            startGradientStopDrag(gradientStop, event);

            return;
        }

        const cssInput = event.target.closest('[data-builder-layer-css-value]');
        if (!cssInput || event.button !== 0) {
            return;
        }

        startCssDrag(cssInput, event);
    });

    document.addEventListener('pointermove', (event) => {
        if (gradientStopPositionDrag && event.pointerId === gradientStopPositionDrag.pointerId) {
            const delta = (event.clientX - gradientStopPositionDrag.startX) + (gradientStopPositionDrag.startY - event.clientY);
            const steps = Math.trunc(delta / 4);
            if (steps === gradientStopPositionDrag.lastSteps) {
                return;
            }

            gradientStopPositionDrag.lastSteps = steps;
            if (!gradientStopPositionDrag.moved) {
                gradientStopPositionDrag.input.blur();
            }
            gradientStopPositionDrag.moved = true;
            event.preventDefault();
            gradientStopPositionDrag.input.classList.add('is-dragging');

            const position = clampNumber(gradientStopPositionDrag.initialValue + steps, 0, 100, gradientStopPositionDrag.initialValue);
            const stops = readGradientStopsControl(gradientStopPositionDrag.control).map((stop) => (
                stop.id === gradientStopPositionDrag.stopId ? { ...stop, position } : stop
            ));

            applyGradientStopsControl(gradientStopPositionDrag.control, stops, gradientStopPositionDrag.stopId);

            return;
        }

        if (gradientStopDrag && event.pointerId === gradientStopDrag.pointerId) {
            event.preventDefault();
            const position = gradientStopPositionFromPointer(gradientStopDrag.track, event);
            const stops = readGradientStopsControl(gradientStopDrag.control).map((stop) => (
                stop.id === gradientStopDrag.stopId ? { ...stop, position } : stop
            ));

            applyGradientStopsControl(gradientStopDrag.control, stops, gradientStopDrag.stopId);

            return;
        }

        if (!cssDragHold || event.pointerId !== cssDragHold.pointerId) {
            return;
        }

        const delta = (event.clientX - cssDragHold.startX) + (cssDragHold.startY - event.clientY);
        const steps = Math.trunc(delta / 4);
        if (steps === cssDragHold.lastSteps) {
            return;
        }

        cssDragHold.lastSteps = steps;
        if (!cssDragHold.moved) {
            cssDragHold.input.blur();
        }
        cssDragHold.moved = true;
        event.preventDefault();
        cssDragHold.input.classList.add('is-dragging');
        applyCssControlStep(
            cssDragHold.field,
            cssDragHold.attribute,
            steps < 0 ? -1 : 1,
            Math.abs(steps),
            cssDragHold.initialValue,
        );
    });

    document.addEventListener('pointerup', (event) => {
        if (gradientStopPositionDrag?.pointerId === event.pointerId) {
            if (gradientStopPositionDrag.moved) {
                event.preventDefault();
            }
            stopGradientStopPositionDrag();

            return;
        }

        if (gradientStopDrag?.pointerId === event.pointerId) {
            event.preventDefault();
            stopGradientStopDrag();

            return;
        }

        if (cssDragHold?.moved && event.pointerId === cssDragHold.pointerId) {
            event.preventDefault();
        }
        stopCssDrag();
    });
    document.addEventListener('pointercancel', (event) => {
        if (gradientStopPositionDrag?.pointerId === event.pointerId) {
            stopGradientStopPositionDrag();
        }
        if (gradientStopDrag?.pointerId === event.pointerId) {
            stopGradientStopDrag();
        }
        stopCssDrag();
    });

    inspector.addEventListener('click', (event) => {
        const resetButton = event.target.closest('[data-builder-reset-attribute]');
        if (resetButton) {
            event.preventDefault();
            event.stopPropagation();
            resetInspectorAttribute(resetButton.dataset.builderResetAttribute);

            return;
        }

        if (event.target.closest('.viti-builder-property-reset')) {
            event.preventDefault();
            event.stopPropagation();

            return;
        }

        const backgroundColorTrigger = event.target.closest('[data-builder-background-color-picker]');
        if (backgroundColorTrigger) {
            event.preventDefault();
            event.stopPropagation();
            openBackgroundColorPickr(backgroundColorTrigger);

            return;
        }

        const inspectorColorTrigger = event.target.closest('[data-builder-color-for]');
        if (inspectorColorTrigger) {
            event.preventDefault();
            event.stopPropagation();
            openInspectorColorPickr(inspectorColorTrigger);

            return;
        }

        const globalColorTrigger = event.target.closest('[data-builder-global-color-for]');
        if (globalColorTrigger && globalColorTrigger.tagName !== 'INPUT') {
            event.preventDefault();
            event.stopPropagation();
            openGlobalColorPickr(globalColorTrigger);

            return;
        }

        const gradientColorTrigger = event.target.closest('[data-builder-gradient-stop-color]');
        if (gradientColorTrigger) {
            event.preventDefault();
            event.stopPropagation();
            openGradientColorPickr(gradientColorTrigger);

            return;
        }

        const gradientStopRemove = event.target.closest('[data-builder-gradient-stop-remove]');
        if (gradientStopRemove) {
            event.preventDefault();
            const control = gradientStopsControl(gradientStopRemove);
            const stopId = gradientStopRemove.dataset.builderGradientStopRemove;
            const stops = readGradientStopsControl(control);
            if (stops.length <= 2) {
                return;
            }

            const nextStops = stops.filter((stop) => stop.id !== stopId);
            applyGradientStopsControl(control, nextStops, nextStops[0]?.id || '');

            return;
        }

        const gradientStopButton = event.target.closest('[data-builder-gradient-stop]');
        if (gradientStopButton) {
            event.preventDefault();
            setActiveGradientStop(gradientStopsControl(gradientStopButton), gradientStopButton.dataset.builderGradientStop);

            return;
        }

        const gradientTrack = event.target.closest('[data-builder-gradient-track]');
        if (gradientTrack && event.target === gradientTrack) {
            event.preventDefault();
            const control = gradientStopsControl(gradientTrack);
            const position = gradientStopPositionFromPointer(gradientTrack, event);
            const stops = readGradientStopsControl(control);
            const newStop = {
                id: createGradientStopId(),
                color: '#ffffff',
                alpha: 1,
                position,
            };

            applyGradientStopsControl(control, [...stops, newStop], newStop.id);

            return;
        }

        const customModeButton = event.target.closest('[data-builder-custom-mode]');
        if (customModeButton) {
            event.preventDefault();
            const control = customModeButton.closest('[data-builder-custom-control]');
            const block = control?.querySelector('[data-builder-custom-block]');
            const isCustom = customModeButton.dataset.builderCustomMode === 'custom';
            const group = customModeButton.dataset.builderCustomGroup || '';
            if (group) {
                const nodeId = domNodeId(domSelectedComponent());
                if (nodeId) {
                    forcedInspectorDomCustomGroupModes.set(`${nodeId}::${group}`, isCustom ? 'custom' : 'all');
                }
            }
            control?.querySelectorAll('[data-builder-custom-mode]').forEach((button) => {
                button.classList.toggle('is-active', button === customModeButton);
            });
            block?.classList.toggle('is-hidden', !isCustom);
            requestAnimationFrame(positionOpenBackgroundPopovers);

            const mainInput = control?.querySelector('.viti-builder-custom-control__head [data-builder-layer-css-value]');
            const mainUnit = control?.querySelector('.viti-builder-custom-control__head [data-builder-layer-css-unit]');
            const mainAttribute = mainInput?.dataset.builderLayerCssValue || '';
            const mainBackgroundField = backgroundItemAttributeParts(mainAttribute)?.field;
            const isBackgroundSynchronizedGlobal = ['imagePosition', 'imageSizeValue'].includes(mainBackgroundField);
            if (mainInput) {
                mainInput.disabled = isCustom && !isBackgroundSynchronizedGlobal;
                if (isCustom && mainInput.value === '') {
                    mainInput.placeholder = isBackgroundSynchronizedGlobal ? '-' : 'Custom';
                } else {
                    mainInput.placeholder = '-';
                }
            }
            if (mainUnit) {
                mainUnit.disabled = isCustom && !isBackgroundSynchronizedGlobal;
            }

            if (!mainInput) {
                const mainField = control?.querySelector('.viti-builder-custom-control__head [data-builder-layer-attribute]');
                const mainColorPicker = mainField?.closest('.viti-builder-color-control')?.querySelector('[data-builder-color-for]');

                if (mainField) {
                    mainField.disabled = isCustom;
                }
                if (mainColorPicker) {
                    mainColorPicker.disabled = isCustom;
                }

                if (!isCustom && block) {
                    const resetValues = {};
                    block.querySelectorAll('[data-builder-layer-attribute]').forEach((input) => {
                        const attribute = input.dataset.builderLayerAttribute;
                        resetValues[attribute] = '';
                        input.value = '';
                        input.removeAttribute('data-builder-dirty');
                        if (input.tagName === 'INPUT') {
                            const colorPicker = input.closest('.viti-builder-color-control')?.querySelector('[data-builder-color-for]');
                            if (colorPicker) {
                                colorPicker.style.setProperty('--builder-pickr-color', 'transparent');
                            }
                            const colorLabel = input.closest('.viti-builder-color-control')?.querySelector('[data-builder-color-label]');
                            if (colorLabel) {
                                colorLabel.textContent = 'transparent';
                            }
                        }
                    });
                    if (Object.keys(resetValues).length) {
                        applyInspectorAttributes(resetValues);
                    }
                }

                return;
            }

            if (isCustom && mainInput && isBackgroundItemAttribute(mainInput.dataset.builderLayerCssValue)) {
                const globalFallback = mainBackgroundField === 'imageSizeValue' ? '100%' : '';
                const globalValue = readCssControlValue(mainInput, mainInput.dataset.builderLayerCssValue) || globalFallback;
                block?.querySelectorAll('[data-builder-layer-css-value]').forEach((input) => {
                    const attribute = input.dataset.builderLayerCssValue;
                    const cssControl = input.closest('.viti-builder-css-value');
                    if (!isBackgroundItemAttribute(attribute) || cssControl?.dataset.builderDirty === '1') {
                        return;
                    }

                    writeCssControlValue(cssControl, attribute, globalValue);
                });
                syncBackgroundImagePositionPreview(mainInput);
            }

            if (!isCustom && block) {
                const resetValues = {};
                block.querySelectorAll('[data-builder-layer-css-value]').forEach((input) => {
                    const attribute = input.dataset.builderLayerCssValue;
                    if (isBackgroundItemAttribute(attribute)) {
                        applyBackgroundItemValue(attribute, '');
                    } else {
                        resetValues[attribute] = '';
                    }
                    input.value = '';
                    input.closest('.viti-builder-css-value')?.removeAttribute('data-builder-dirty');
                });
                if (Object.keys(resetValues).length) {
                    applyInspectorAttributes(resetValues);
                }
            }

            return;
        }

        const backgroundClose = event.target.closest('[data-builder-background-close]');
        if (backgroundClose) {
            event.preventDefault();
            closeActivePickr();
            backgroundClose.closest('.viti-builder-background-item, .viti-builder-background-add-popover')?.classList.remove('is-open');
            renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: false });

            return;
        }

        const cssModeButton = event.target.closest('[data-builder-layer-css-mode]');
        if (cssModeButton) {
            event.preventDefault();
            const attribute = cssModeButton.dataset.builderLayerCssMode;
            const requestedMode = cssModeButton.dataset.builderLayerCssModeValue === 'custom' ? 'preset' : 'custom';
            const field = cssModeButton.closest('.viti-builder-inspector-field');
            const control = field?.querySelector('.viti-builder-css-value');
            const selector = cssSelectorValue(attribute);
            const cssInput = control?.querySelector(`[data-builder-layer-css-value="${selector}"]`);
            const cssToken = control?.querySelector(`[data-builder-layer-css-token="${selector}"]`);

            if (!control) {
                return;
            }

            if (requestedMode === 'preset') {
                const matchingToken = [...(cssToken?.options || [])].find((option) => option.value === cssInput?.value)?.value;
                const nextToken = matchingToken || cssToken?.value || cssToken?.options[0]?.value || '';

                if (cssInput && nextToken) {
                    cssInput.value = nextToken;
                }
                if (cssToken && nextToken) {
                    cssToken.value = nextToken;
                }
                control.dataset.builderCssMode = 'preset';
            } else {
                if (cssInput && cssToken?.value) {
                    cssInput.value = cssToken.value;
                }
                control.dataset.builderCssMode = 'custom';
            }
            rememberDomCssMode(domSelectedComponent(), attribute, requestedMode);

            syncCssControlPresentation(control, attribute);
            control.dataset.builderDirty = '1';
            if (!validateInspectorCssValue(cssModeButton, attribute)) {
                return;
            }

            applyLiveInspectorCssPreview(attribute, readCssControlValue(cssModeButton, attribute));
            scheduleInspectorValueApply(attribute, readCssControlValue(cssModeButton, attribute));
            syncBackgroundPositionCustomFields(cssModeButton, readCssControlValue(cssModeButton, attribute));
            updateBackgroundGradientPreview(cssModeButton);
            syncBackgroundImagePositionPreview(cssModeButton);
            if (!isBackgroundItemAttribute(attribute)) {
                syncInspectorControlState(
                    attribute,
                    inspectorControlState(currentInspectorStateAttributes(), attribute, readCssControlValue(cssModeButton, attribute)),
                );
            }

            if (control.dataset.builderCssMode === 'custom') {
                cssInput?.focus();
            }

            return;
        }

        const backgroundType = event.target.closest('[data-builder-background-type]');
        if (backgroundType) {
            event.preventDefault();
            const popover = backgroundType.closest('.viti-builder-background-popover');
            const type = backgroundType.dataset.builderBackgroundType;
            const holder = backgroundType.closest('.viti-builder-background-item, .viti-builder-background-add-popover');
            const isNewBackground = holder?.classList.contains('viti-builder-background-add-popover');
            const layer = popover?.dataset.builderBackgroundLayer || holder?.dataset.builderBackgroundLayer || 'container';
            popover?.querySelectorAll('[data-builder-background-type]').forEach((button) => {
                button.classList.toggle('is-active', button === backgroundType);
            });
            popover?.querySelectorAll('[data-builder-background-pane]').forEach((pane) => {
                pane.classList.toggle('is-active', pane.dataset.builderBackgroundPane === type);
            });
            popover?.setAttribute('data-builder-background-current-type', type);
            if (holder && popover) {
                requestAnimationFrame(() => positionBackgroundPopover(holder, popover));
            }

            const attributes = currentLayerAttributes();
            const items = parseBackgroundItems(attributes, layer);
            if (isNewBackground) {
                applyInspectorAttributes(backgroundCollectionMutationValues(layer, [createdBackgroundItem(type)]));
                holder?.classList.remove('is-open');
                renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: false });

                return;
            }

            const itemId = holder?.dataset.builderBackgroundItemId || popover?.dataset.builderBackgroundItemId;
            if (itemId) {
                applyInspectorAttributes(backgroundCollectionMutationValues(layer, items.map((item) => (
                    item.id === itemId
                        ? backgroundItemAsType(item, type)
                        : item
                ))));
                renderInspectorWhenReady({ preserveOpenSections: true });
            }
            return;
        }

        const backgroundAdd = event.target.closest('[data-builder-background-add]');
        if (backgroundAdd) {
            event.preventDefault();
            const list = backgroundAdd.closest('.viti-builder-background-list');
            const layer = list?.querySelector('[data-builder-background-title-layer]')?.dataset.builderBackgroundTitleLayer || 'container';
            closeBackgroundPopovers(list);

            applyInspectorAttributes(backgroundCollectionMutationValues(layer, [createdBackgroundItem('color')]));
            renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: false });

            return;
        }

        const backgroundEdit = event.target.closest('[data-builder-background-edit]');
        if (backgroundEdit) {
            event.preventDefault();
            const item = backgroundEdit.closest('.viti-builder-background-item');
            const list = backgroundEdit.closest('.viti-builder-background-list');
            const wasOpen = item?.classList.contains('is-open');
            closeBackgroundPopovers(list);
            if (item && !wasOpen) {
                item.classList.add('is-open');
                positionBackgroundPopover(item, item.querySelector('.viti-builder-background-popover'));
            }

            return;
        }

        const backgroundRemove = event.target.closest('[data-builder-background-remove]');
        if (backgroundRemove) {
            event.preventDefault();
            const itemId = backgroundRemove.dataset.builderBackgroundRemove;
            const layer = backgroundRemove.dataset.builderBackgroundLayer || 'container';
            const attributes = currentLayerAttributes();

            applyInspectorAttributes(backgroundCollectionMutationValues(
                layer,
                parseBackgroundItems(attributes, layer).filter((item) => item.id !== itemId),
            ));
            renderInspectorWhenReady({ preserveOpenSections: true, preserveOpenBackgrounds: false });

            return;
        }

        const positionPreset = event.target.closest('[data-builder-position-preset]');
        if (positionPreset) {
            applyInspectorAttributes(positionPresetStyleValues(
                positionPreset.dataset.builderPositionLayer,
                positionPreset.dataset.builderPositionPreset,
            ));

            return;
        }

        const button = event.target.closest('[data-builder-layer-value]');
        if (!button) {
            return;
        }

        if (button.closest('.viti-builder-segmented') && button.classList.contains('is-active')) {
            event.preventDefault();

            return;
        }

        const isBackgroundToggle = button.classList.contains('viti-builder-background-toggle');
        const nextToggleState = button.dataset.builderLayerValue === 'true';
        if (!applyBackgroundItemValue(button.dataset.builderLayerAttribute, button.dataset.builderLayerValue)) {
            applyInspectorValue(button.dataset.builderLayerAttribute, button.dataset.builderLayerValue);
        }
        if (isBackgroundToggle) {
            button.classList.toggle('is-active', nextToggleState);
            button.dataset.builderLayerValue = nextToggleState ? 'false' : 'true';
            button.setAttribute('aria-pressed', nextToggleState ? 'true' : 'false');
            if (button.dataset.builderLayerAttribute === 'data-background-gradient-radial'
                || backgroundItemAttributeParts(button.dataset.builderLayerAttribute)?.field === 'gradientRadial') {
                const isRadial = nextToggleState;
                const popover = button.closest('.viti-builder-background-popover');
                popover?.querySelector('[data-builder-gradient-linear-field]')?.classList.toggle('is-hidden', isRadial);
                popover?.querySelector('[data-builder-gradient-radial-field]')?.classList.toggle('is-hidden', !isRadial);
                popover?.querySelector('[data-builder-gradient-radial-fields]')?.classList.toggle('is-hidden', !isRadial);
                requestAnimationFrame(positionOpenBackgroundPopovers);
            }
            updateBackgroundGradientPreview(button);

            return;
        }

        if (button.dataset.builderLayerAttribute === 'data-background-gradient-radial'
            || backgroundItemAttributeParts(button.dataset.builderLayerAttribute)?.field === 'gradientRadial') {
            const isRadial = button.dataset.builderLayerValue === 'true';
            const popover = button.closest('.viti-builder-background-popover');
            popover?.querySelector('[data-builder-gradient-linear-field]')?.classList.toggle('is-hidden', isRadial);
            popover?.querySelector('[data-builder-gradient-radial-field]')?.classList.toggle('is-hidden', !isRadial);
            popover?.querySelector('[data-builder-gradient-radial-fields]')?.classList.toggle('is-hidden', !isRadial);
            button.dataset.builderLayerValue = isRadial ? 'false' : 'true';
            button.setAttribute('aria-pressed', isRadial ? 'true' : 'false');
            requestAnimationFrame(positionOpenBackgroundPopovers);
            updateBackgroundGradientPreview(button);
        }
        button.parentElement?.querySelectorAll('button').forEach((item) => {
            item.classList.toggle('is-active', item === button);
        });
    });
    inspector.addEventListener('toggle', (event) => {
        if (!event.target.matches?.('.viti-builder-style-section')) {
            return;
        }

        scheduleOpenInspectorSectionsRemember();
    }, true);

    editor.on('component:selected', (component) => {
        lastSelectedLayerName = componentName(component);
        applyPendingDomPreview(component);
        if (applyingInspectorChange) {
            return;
        }
        renderInspector();
    });
    editor.on('component:deselected', () => {
        if (applyingInspectorChange) {
            return;
        }
        renderInspector();
    });
    editor.on('undo redo', refreshSelectionAfterHistory);
    editor.on('component:add component:remove', () => {
        scheduleTailwindClassesSync();
    });
    editor.on('component:add', (component) => {
        syncDomComponentTreeLayerNames(component);
        captureDomBackgroundInitialState(component);
    });
    renderInspector();
    requestAnimationFrame(stripLayerPanelTooltips);

    fetch(loadUrl, { headers: { Accept: 'application/json' } })
        .then((response) => {
            if (!response.ok) {
                throw new Error(`HTTP ${response.status}`);
            }

            return response.json();
        })
        .then((data) => {
            documentInspectorSchema = data.documentInspector || defaultDocumentInspectorSchema;
            themeSourceFiles = data.themeSourceFiles || {};
            const components = Array.isArray(data.components) ? data.components : [];
            editor.setComponents(components);
            editor.getWrapper().components().forEach((component) => {
                syncDomComponentTreeLayerNames(component);
                walkDomComponents(component, captureDomBackgroundInitialState);
            });
            requestAnimationFrame(stripLayerPanelTooltips);
            requestAnimationFrame(() => ensureCanvasComponents(editor, components));
            const blocks = genericDomBlocks();
            blocks.forEach((block) => editor.BlockManager.add(block.id, block));
            pageSettings = {
                headerMode: normalizeLayoutMode(data.pageSettings?.headerMode),
                footerMode: normalizeLayoutMode(data.pageSettings?.footerMode),
            };
            layoutBuilderUrls = {
                header: data.layoutBuilderUrls?.header || '',
                footer: data.layoutBuilderUrls?.footer || '',
            };
            renderPagePanel();
            globalThemeOverrides = normalizeGlobalThemeOverrides(data.globalThemeOverrides || {});
            renderGlobalThemePanel();
            setStatus('Builder prêt');
            preloadCanvasBackgroundAssets(editor);
            requestAnimationFrame(syncCanvasViewport);
            requestAnimationFrame(refreshCanvasOverlays);
            requestAnimationFrame(() => {
                tailwindSyncSignature = collectCurrentTailwindClasses(editor).join(' ');
                tailwindClassesDirty = false;
            });
        })
        .catch(() => {
            setStatus('Impossible de charger le builder', 'error');
        });

    root.querySelectorAll('[data-builder-command]').forEach((button) => {
        button.addEventListener('click', () => {
            editor.runCommand(button.dataset.builderCommand);
        });
    });

    root.querySelector('[data-builder-undo]')?.addEventListener('click', () => {
        if (!undoBackgroundHistory()) {
            editor.UndoManager.undo();
            refreshSelectionAfterHistory();
        }
    });

    root.querySelector('[data-builder-redo]')?.addEventListener('click', () => {
        if (!redoBackgroundHistory()) {
            editor.UndoManager.redo();
            refreshSelectionAfterHistory();
        }
    });

    window.addEventListener('keydown', (event) => {
        const isHistoryShortcut = (event.metaKey || event.ctrlKey)
            && String(event.key || '').toLowerCase() === 'z';
        if (!isHistoryShortcut) {
            return;
        }

        const handled = event.shiftKey ? redoBackgroundHistory() : undoBackgroundHistory();
        if (handled) {
            event.preventDefault();
            event.stopImmediatePropagation();

            return;
        }

        refreshSelectionAfterHistory();
    }, true);

    root.querySelector('[data-builder-device]')?.addEventListener('change', (event) => {
        setActiveBuilderDevice(event.target.value);
        editor.setDevice(event.target.value);
        renderInspector();
        requestAnimationFrame(() => applyPendingDomPreview(domSelectedComponent()));
    });

    window.addEventListener('resize', () => {
        syncCanvasViewport();
        positionOpenFloatingControls();
    });
    inspector.addEventListener('scroll', () => {
        requestAnimationFrame(positionOpenFloatingControls);
    }, true);

    root.querySelectorAll('[data-builder-sidebar]').forEach((button) => {
        button.addEventListener('click', () => {
            const target = button.dataset.builderSidebar;
            const isAlreadyActive = button.classList.contains('is-active');

            if (isAlreadyActive) {
                shell?.classList.toggle('viti-builder-shell--sidebar-collapsed');
                root.querySelectorAll('[data-builder-sidebar-panel]').forEach((panel) => {
                    panel.classList.remove('is-active');
                });
                root.querySelectorAll('[data-builder-sidebar]').forEach((sidebarButton) => {
                    sidebarButton.classList.toggle('is-active', sidebarButton === button && !shell?.classList.contains('viti-builder-shell--sidebar-collapsed'));
                });
                requestAnimationFrame(syncCanvasViewport);

                return;
            }

            shell?.classList.remove('viti-builder-shell--sidebar-collapsed');
            root.querySelectorAll('[data-builder-sidebar]').forEach((sidebarButton) => {
                sidebarButton.classList.toggle('is-active', sidebarButton === button);
            });
            root.querySelectorAll('[data-builder-sidebar-panel]').forEach((panel) => {
                panel.classList.toggle('is-active', panel.dataset.builderSidebarPanel === target);
            });
            if (target === 'globals') {
                renderGlobalThemePanel();
            }
            requestAnimationFrame(syncCanvasViewport);
        });
    });

    root.querySelector('[data-builder-global-reset-all]')?.addEventListener('click', () => {
        globalThemeOverrides = {};
        applyGlobalThemeOverrides();
        renderGlobalThemePanel();
        refreshCanvasOverlays();
    });

    globalsPanel?.addEventListener('click', (event) => {
        const resetButton = event.target.closest('[data-builder-reset-attribute]');
        if (!resetButton) {
            return;
        }

        event.preventDefault();
        event.stopPropagation();
        setGlobalThemeToken(resetButton.dataset.builderResetAttribute, '');
    });

    globalsPanel?.addEventListener('input', (event) => {
        const colorPicker = event.target.closest('[data-builder-global-color-for]');
        if (colorPicker) {
            const tokenName = colorPicker.dataset.builderGlobalColorFor;
            const textField = colorPicker.closest('.viti-builder-color-control')?.querySelector(`[data-builder-global-token="${cssSelectorValue(tokenName)}"]`);
            if (textField) {
                textField.value = colorPicker.value;
            }
            setGlobalThemeToken(tokenName, colorPicker.value, { render: false });

            return;
        }

        const cssField = event.target.closest('[data-builder-global-css-value]');
        if (cssField) {
            const tokenName = cssField.dataset.builderGlobalCssValue;
            cssField.value = clampBuilderValue(tokenName, cssField.value);
            setGlobalThemeToken(tokenName, readGlobalCssControlValue(cssField, tokenName), { render: false });

            return;
        }

        const field = event.target.closest('[data-builder-global-token]');
        if (field) {
            setGlobalThemeToken(field.dataset.builderGlobalToken, field.value, { render: false });
        }
    });

    globalsPanel?.addEventListener('change', (event) => {
        const cssUnit = event.target.closest('[data-builder-global-css-unit]');
        if (cssUnit) {
            const tokenName = cssUnit.dataset.builderGlobalCssUnit;
            setGlobalThemeToken(tokenName, readGlobalCssControlValue(cssUnit, tokenName));

            return;
        }

        const field = event.target.closest('[data-builder-global-token]');
        if (field) {
            setGlobalThemeToken(field.dataset.builderGlobalToken, field.value);
        }
    });

    pagesPanel?.addEventListener('change', (event) => {
        const field = event.target.closest('[data-builder-page-setting]');
        if (!field) {
            return;
        }

        const settingName = field.dataset.builderPageSetting;
        if (settingName === 'headerMode' || settingName === 'footerMode') {
            pageSettings[settingName] = normalizeLayoutMode(field.value);
            renderPagePanel();
        }
    });

    sidebarReopenButton?.addEventListener('click', () => {
        shell?.classList.remove('viti-builder-shell--sidebar-collapsed');
        const defaultButton = root.querySelector('[data-builder-sidebar="pages"]');
        root.querySelectorAll('[data-builder-sidebar]').forEach((sidebarButton) => {
            sidebarButton.classList.toggle('is-active', sidebarButton === defaultButton);
        });
        root.querySelectorAll('[data-builder-sidebar-panel]').forEach((panel) => {
            panel.classList.toggle('is-active', panel.dataset.builderSidebarPanel === 'pages');
        });
        requestAnimationFrame(syncCanvasViewport);
    });

    saveButton.addEventListener('click', () => {
        flushInspectorValueApply();
        flushPendingDomNodeChangesForAll({ rerender: false, preservePreview: true });
        saveButton.disabled = true;
        setStatus('Enregistrement...');

        fetch(saveUrl, {
            method: 'POST',
            headers: {
                Accept: 'application/json',
                'Content-Type': 'application/json',
                'X-Requested-With': 'XMLHttpRequest',
                'X-CSRF-Token': csrfToken,
            },
            body: JSON.stringify({
                documentMode: 'dom',
                components: [],
                builderDocument: toSaveBuilderDocument(editor),
                globalThemeOverrides,
                pageSettings,
            }),
        })
            .then((response) => {
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}`);
                }

                return response.json();
            })
            .then(() => {
                setStatus('Enregistré · compilation CSS...');

                return flushTailwindClassesSync(false, { refreshCanvas: false });
            })
            .then((tailwindResult) => {
                if (tailwindResult?.error) {
                    setStatus('Enregistré · erreur compilation Tailwind', 'error');

                    return;
                }

                setStatus('Enregistré', 'saved');
            })
            .catch(() => {
                setStatus('Erreur pendant l’enregistrement', 'error');
            })
            .finally(() => {
                saveButton.disabled = false;
            });
    });
}

document.querySelectorAll('[data-viti-page-builder]').forEach(initBuilder);
