{"id":113276,"date":"2025-05-28T10:58:56","date_gmt":"2025-05-28T08:58:56","guid":{"rendered":"https:\/\/gabon.yearbook-media.com\/?page_id=113276"},"modified":"2025-06-06T20:04:06","modified_gmt":"2025-06-06T18:04:06","slug":"tourisme","status":"publish","type":"page","link":"https:\/\/gabon.yearbook-media.com\/en\/tourisme\/","title":{"rendered":"Gabon \u2013 Tourism"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"113276\" class=\"elementor elementor-113276\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2a01ecc9 ToBeHidden e-flex e-con-boxed e-con e-child\" data-id=\"2a01ecc9\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-2dc6a3d2 e-con-full droppable e-flex e-con e-child\" data-id=\"2dc6a3d2\" data-element_type=\"container\" id=\"Ele1A\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2414d3e6 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"2414d3e6\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>\t\t<div data-elementor-type=\"page\" data-elementor-id=\"83347\" class=\"elementor elementor-83347\" data-elementor-post-type=\"elementor_library\">\n\t\t\t\t<div class=\"elementor-element elementor-element-46a1d147 e-con-full OrdiMobileConteneurClass e-flex e-con e-child\" data-id=\"46a1d147\" data-element_type=\"container\" id=\"testIdTest\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8a5fac7 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"8a5fac7\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p><style>.elementor-353552 .elementor-element.elementor-element-6da64a60{--display:flex;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--justify-content:space-around;--align-items:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:50px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-6da64a60.e-con{--align-self:center;--flex-grow:0;--flex-shrink:0;}.elementor-353552 .elementor-element.elementor-element-45f807e0{--display:flex;--min-height:0px;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-100px;--margin-bottom:62px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:3;}.elementor-353552 .elementor-element.elementor-element-45f807e0.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-61dcced4{--display:flex;--margin-top:0px;--margin-bottom:-11px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-6c65b106{--display:flex;--margin-top:0px;--margin-bottom:-30px;--margin-left:-25px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-eb74aa8{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-eb74aa8:not(.elementor-motion-effects-element-type-background), .elementor-353552 .elementor-element.elementor-element-eb74aa8 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-1c762c45{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--flex-wrap:nowrap;--margin-top:-3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-5be6a53a{--display:flex;--align-items:flex-start;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-5be6a53a.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-75917e71{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-image .widget-image-caption{color:var( --e-global-color-text );font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-353552 .elementor-element.elementor-element-6be76773 > .elementor-widget-container{margin:38px 5px -38px -40px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-6be76773.elementor-element{--align-self:flex-start;}.elementor-353552 .elementor-element.elementor-element-6be76773{text-align:right;}.elementor-353552 .elementor-element.elementor-element-6be76773 img{width:17px;}.elementor-353552 .elementor-element.elementor-element-5ad25a69{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-5ad25a69.e-con{--align-self:flex-end;}.elementor-353552 .elementor-element.elementor-element-52dec736 > .elementor-widget-container{margin:45px -18px -55px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-52dec736{z-index:101;text-align:right;}.elementor-353552 .elementor-element.elementor-element-469b1a7c{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-25px;--margin-bottom:90px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-469b1a7c:not(.elementor-motion-effects-element-type-background), .elementor-353552 .elementor-element.elementor-element-469b1a7c > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#9FC5F3;}.elementor-353552 .elementor-element.elementor-element-3f5f9124{--display:flex;--min-height:30px;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:1px;--margin-bottom:-6px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:99;}.elementor-353552 .elementor-element.elementor-element-3f5f9124.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-48a37689{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-start;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-text-editor{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:var( --e-global-color-primary );}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap, .elementor-widget-text-editor.elementor-drop-cap-view-default .elementor-drop-cap{color:var( --e-global-color-primary );border-color:var( --e-global-color-primary );}.elementor-353552 .elementor-element.elementor-element-4b68287 > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353552 .elementor-element.elementor-element-4b68287.elementor-element{--align-self:flex-start;}.elementor-353552 .elementor-element.elementor-element-4b68287{text-align:start;font-family:\"Roboto\", Sans-serif;font-size:12px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-79a46db6{--display:flex;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-47d25f98 > .elementor-widget-container{margin:10px 0px -10px 0px;}.elementor-353552 .elementor-element.elementor-element-47d25f98{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:18px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-3aa42503{--display:flex;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-end;--align-items:flex-end;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-12d5dc39 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-12d5dc39.elementor-element{--align-self:flex-start;}.elementor-353552 .elementor-element.elementor-element-12d5dc39{text-align:end;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-21535e15{--display:flex;--justify-content:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--flex-wrap:wrap;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-21535e15.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-de420c1{--display:flex;--min-height:30px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-2f60f3f > .elementor-widget-container{margin:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-2f60f3f.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-2f60f3f{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:500;line-height:1.1em;color:#FB5E2A;}.elementor-353552 .elementor-element.elementor-element-78e1d9f5{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--gap:010px 9px;--row-gap:010px;--column-gap:9px;--flex-wrap:wrap;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:100;}.elementor-353552 .elementor-element.elementor-element-78e1d9f5.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-cf3602e{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-cf3602e.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-7a1bb33 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-7a1bb33.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-7a1bb33{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-6bf72a5{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-1a57dd9 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-1a57dd9.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-1a57dd9{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:500;line-height:1.1em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-34d8bbba{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-32bd35c6 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-32bd35c6.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-32bd35c6{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-5a9252c3{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-8722042 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-8722042.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-8722042{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-19ecc2b3{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-6071d405 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-6071d405.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-6071d405{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-3617569c{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-4b013e71 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-4b013e71.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-4b013e71{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-7450a6f8{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-30970f89 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-30970f89.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-30970f89{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-6aac67a6{--display:flex;--margin-top:-46px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:2;}.elementor-353552 .elementor-element.elementor-element-40d299c{width:100%;max-width:100%;}.elementor-353552 .elementor-element.elementor-element-40d299c.elementor-element{--align-self:flex-end;}.elementor-353552 .elementor-element.elementor-element-1fd15d20{width:100%;max-width:100%;}.elementor-353552 .elementor-element.elementor-element-1fd15d20.elementor-element{--align-self:flex-end;}.elementor-353552 .elementor-element.elementor-element-54e4e530{--display:flex;--justify-content:center;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-531f3cb1{width:100%;max-width:100%;font-family:\"Roboto\", Sans-serif;font-size:10px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353552 .elementor-element.elementor-element-531f3cb1 > .elementor-widget-container{margin:-37px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-531f3cb1.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-3a352c3f > .elementor-widget-container{margin:7px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-3a352c3f.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-3a352c3f{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353552 .elementor-element.elementor-element-7abb0aba > .elementor-widget-container{margin:7px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-7abb0aba.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-7abb0aba{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353552 .elementor-element.elementor-element-68e2b2ca > .elementor-widget-container{margin:-110px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-68e2b2ca.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-68e2b2ca{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353552 .elementor-element.elementor-element-8d50d33{--display:flex;--margin-top:-85px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-8d50d33.e-con{--align-self:center;}.elementor-widget-form .elementor-field-group > label, .elementor-widget-form .elementor-field-subgroup label{color:var( --e-global-color-text );}.elementor-widget-form .elementor-field-group > label{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-field-type-html{color:var( --e-global-color-text );font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-field-group .elementor-field{color:var( --e-global-color-text );}.elementor-widget-form .elementor-field-group .elementor-field, .elementor-widget-form .elementor-field-subgroup label{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-button{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-widget-form .e-form__buttons__wrapper__button-next{background-color:var( --e-global-color-accent );}.elementor-widget-form .elementor-button[type=\"submit\"]{background-color:var( --e-global-color-accent );}.elementor-widget-form .e-form__buttons__wrapper__button-previous{background-color:var( --e-global-color-accent );}.elementor-widget-form .elementor-message{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .e-form__indicators__indicator, .elementor-widget-form .e-form__indicators__indicator__label{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-widget-form{--e-form-steps-indicator-inactive-primary-color:var( --e-global-color-text );--e-form-steps-indicator-active-primary-color:var( --e-global-color-accent );--e-form-steps-indicator-completed-primary-color:var( --e-global-color-accent );--e-form-steps-indicator-progress-color:var( --e-global-color-accent );--e-form-steps-indicator-progress-background-color:var( --e-global-color-text );--e-form-steps-indicator-progress-meter-color:var( --e-global-color-text );}.elementor-widget-form .e-form__indicators__indicator__progress__meter{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-353552 .elementor-element.elementor-element-6ae6ef83{width:var( --container-widget-width, 69.5% );max-width:69.5%;--container-widget-width:69.5%;--container-widget-flex-grow:0;z-index:120;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group{padding-right:calc( 10px\/2 );padding-left:calc( 10px\/2 );margin-bottom:6px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-form-fields-wrapper{margin-left:calc( -10px\/2 );margin-right:calc( -10px\/2 );margin-bottom:-6px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label, .elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{color:#7B88A3;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-type-html{padding-bottom:0px;color:#7A7A7A;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field{color:#484848;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:01px 01px 01px 01px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:01px 01px 01px 01px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353552 .elementor-element.elementor-element-66fd4d36{--display:flex;--min-height:58px;--gap:0px 0px;--row-gap:0px;--column-gap:0px;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:8px 8px 8px 8px;--margin-top:14px;--margin-bottom:10px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353552 .elementor-element.elementor-element-66fd4d36.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4{width:auto;max-width:auto;z-index:120;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 > .elementor-widget-container{margin:2px 0px 2px 1px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group{padding-right:calc( 0px\/2 );padding-left:calc( 0px\/2 );margin-bottom:0px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-form-fields-wrapper{margin-left:calc( -0px\/2 );margin-right:calc( -0px\/2 );margin-bottom:-0px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label, .elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{color:#FB5E2A;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-type-html{padding-bottom:0px;color:#FFFFFF;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field{color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353552 .elementor-element.elementor-element-4a59cf88 > .elementor-widget-container{margin:-7px 0px -24px 0px;}.elementor-353552 .elementor-element.elementor-element-4a59cf88.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-4a59cf88{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:12.5px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-17d3ad0c{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;border-style:none;--border-style:none;--border-radius:8px 8px 8px 8px;--margin-top:-4px;--margin-bottom:05px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353552 .elementor-element.elementor-element-17d3ad0c.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-177c9b71{width:auto;max-width:auto;z-index:5;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 > .elementor-widget-container{margin:2px 0px 2px 1px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-177c9b71.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group{padding-right:calc( 0px\/2 );padding-left:calc( 0px\/2 );margin-bottom:0px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-form-fields-wrapper{margin-left:calc( -0px\/2 );margin-right:calc( -0px\/2 );margin-bottom:-0px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label, .elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{color:#000000;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-type-html{padding-bottom:0px;color:#FFFFFF;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field{color:#000000;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353552 .elementor-element.elementor-element-5bafe1f3{--display:flex;--min-height:0px;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--gap:0px 0px;--row-gap:0px;--column-gap:0px;--overlay-opacity:1;--margin-top:230px;--margin-bottom:-220px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:10;}.elementor-353552 .elementor-element.elementor-element-5bafe1f3::before, .elementor-353552 .elementor-element.elementor-element-5bafe1f3 > .elementor-background-video-container::before, .elementor-353552 .elementor-element.elementor-element-5bafe1f3 > .e-con-inner > .elementor-background-video-container::before, .elementor-353552 .elementor-element.elementor-element-5bafe1f3 > .elementor-background-slideshow::before, .elementor-353552 .elementor-element.elementor-element-5bafe1f3 > .e-con-inner > .elementor-background-slideshow::before, .elementor-353552 .elementor-element.elementor-element-5bafe1f3 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{--background-overlay:'';background-size:cover;}.elementor-353552 .elementor-element.elementor-element-5bafe1f3.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-432cc2a1{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-446e8565 > .elementor-widget-container{margin:0px 3px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-446e8565.elementor-element{--align-self:flex-end;}.elementor-353552 .elementor-element.elementor-element-446e8565{z-index:5;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;line-height:1.1em;color:#213864;}.elementor-353552 .elementor-element.elementor-element-60df49d0 > .elementor-widget-container{margin:35px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-60df49d0.elementor-element{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-60df49d0{z-index:5;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:500;line-height:1.1em;color:#6185C0;}.elementor-353552 .elementor-element.elementor-element-62ef38f0{--display:flex;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:space-between;--align-items:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-287px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-f4f51cb > .elementor-widget-container{margin:0px 3px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-f4f51cb.elementor-element{--align-self:flex-end;}.elementor-353552 .elementor-element.elementor-element-f4f51cb{z-index:5;text-align:left;font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:600;line-height:1.1em;color:#213864;}.elementor-353552 .elementor-element.elementor-element-f214306 > .elementor-widget-container{margin:0px 0px 0px 3px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-f214306.elementor-element{--align-self:flex-end;}.elementor-353552 .elementor-element.elementor-element-f214306{z-index:5;text-align:start;font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:600;line-height:1.1em;color:#213864;}@media(max-width:1001px){.elementor-353552 .elementor-element.elementor-element-6ae6ef83{z-index:11;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-size:10px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4{z-index:11;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-size:10px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-177c9b71{z-index:11;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-size:10px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-button{font-size:12px;}}@media(min-width:1001px){.elementor-353552 .elementor-element.elementor-element-6da64a60{--width:500px;}.elementor-353552 .elementor-element.elementor-element-45f807e0{--width:100%;}.elementor-353552 .elementor-element.elementor-element-6c65b106{--width:500px;}.elementor-353552 .elementor-element.elementor-element-75917e71{--width:10px;}.elementor-353552 .elementor-element.elementor-element-5ad25a69{--width:100%;}.elementor-353552 .elementor-element.elementor-element-469b1a7c{--width:500px;}.elementor-353552 .elementor-element.elementor-element-48a37689{--width:22%;}.elementor-353552 .elementor-element.elementor-element-79a46db6{--width:50%;}.elementor-353552 .elementor-element.elementor-element-3aa42503{--width:22%;}.elementor-353552 .elementor-element.elementor-element-78e1d9f5{--width:100%;}.elementor-353552 .elementor-element.elementor-element-cf3602e{--width:103px;}.elementor-353552 .elementor-element.elementor-element-6bf72a5{--width:103px;}.elementor-353552 .elementor-element.elementor-element-34d8bbba{--width:103px;}.elementor-353552 .elementor-element.elementor-element-5a9252c3{--width:103px;}.elementor-353552 .elementor-element.elementor-element-19ecc2b3{--width:103px;}.elementor-353552 .elementor-element.elementor-element-3617569c{--width:103px;}.elementor-353552 .elementor-element.elementor-element-7450a6f8{--width:103px;}.elementor-353552 .elementor-element.elementor-element-8d50d33{--width:100%;}.elementor-353552 .elementor-element.elementor-element-66fd4d36{--width:390px;}.elementor-353552 .elementor-element.elementor-element-17d3ad0c{--width:100%;}.elementor-353552 .elementor-element.elementor-element-5bafe1f3{--width:150%;}.elementor-353552 .elementor-element.elementor-element-62ef38f0{--width:95%;}}@media(max-width:1000px){.elementor-353552 .elementor-element.elementor-element-6da64a60{--width:390px;--margin-top:105px;--margin-bottom:-75px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-45f807e0{--min-height:150px;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--margin-top:-47px;--margin-bottom:-32px;--margin-left:0px;--margin-right:0px;--z-index:3;}.elementor-353552 .elementor-element.elementor-element-61dcced4{--width:25%;}.elementor-353552 .elementor-element.elementor-element-6c65b106{--width:100%;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--align-items:center;--flex-wrap:nowrap;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-6c65b106.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-eb74aa8{--width:75%;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;}.elementor-353552 .elementor-element.elementor-element-eb74aa8.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-1c762c45{--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-6be76773 > .elementor-widget-container{margin:-3px 40px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-5ad25a69{--width:100%;--justify-content:flex-end;--align-items:flex-end;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-52dec736 > .elementor-widget-container{margin:40px 55px -30px 0px;}.elementor-353552 .elementor-element.elementor-element-52dec736{z-index:100;}.elementor-353552 .elementor-element.elementor-element-469b1a7c{--width:78%;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--flex-wrap:nowrap;--margin-top:-103px;--margin-bottom:-7px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-469b1a7c.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-3f5f9124{--justify-content:center;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-48a37689{--width:25%;--margin-top:2px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-4b68287 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-4b68287{font-size:8px;}.elementor-353552 .elementor-element.elementor-element-79a46db6{--width:46%;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-47d25f98 > .elementor-widget-container{margin:0px -20px 0px -20px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-47d25f98{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-3aa42503{--width:25%;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-12d5dc39 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-12d5dc39{font-size:7px;}.elementor-353552 .elementor-element.elementor-element-21535e15{--margin-top:-15px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-de420c1{--width:83%;--min-height:15px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-de420c1.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-2f60f3f{width:100%;max-width:100%;font-size:10px;}.elementor-353552 .elementor-element.elementor-element-2f60f3f > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-78e1d9f5{--width:100%;--gap:6px 4px;--row-gap:6px;--column-gap:4px;--flex-wrap:wrap;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-cf3602e{--width:61px;--min-height:15px;}.elementor-353552 .elementor-element.elementor-element-7a1bb33{font-size:9px;}.elementor-353552 .elementor-element.elementor-element-6bf72a5{--width:61px;--min-height:15px;}.elementor-353552 .elementor-element.elementor-element-1a57dd9{font-size:9px;}.elementor-353552 .elementor-element.elementor-element-34d8bbba{--width:61px;--min-height:15px;}.elementor-353552 .elementor-element.elementor-element-32bd35c6{font-size:9px;}.elementor-353552 .elementor-element.elementor-element-5a9252c3{--width:61px;--min-height:15px;}.elementor-353552 .elementor-element.elementor-element-8722042{font-size:9px;}.elementor-353552 .elementor-element.elementor-element-19ecc2b3{--width:61px;--min-height:15px;}.elementor-353552 .elementor-element.elementor-element-6071d405{font-size:9px;}.elementor-353552 .elementor-element.elementor-element-3617569c{--width:61px;--min-height:15px;}.elementor-353552 .elementor-element.elementor-element-4b013e71{font-size:9px;}.elementor-353552 .elementor-element.elementor-element-7450a6f8{--width:61px;--min-height:15px;}.elementor-353552 .elementor-element.elementor-element-30970f89{font-size:9px;}.elementor-353552 .elementor-element.elementor-element-6aac67a6{--width:96%;--min-height:250px;--margin-top:-56px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353552 .elementor-element.elementor-element-40d299c > .elementor-widget-container{margin:5px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-1fd15d20 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-54e4e530{--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-54e4e530.e-con{--order:-99999 \/* order start hack *\/;}.elementor-353552 .elementor-element.elementor-element-531f3cb1 > .elementor-widget-container{margin:-30px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-3a352c3f{width:var( --container-widget-width, 63% );max-width:63%;--container-widget-width:63%;--container-widget-flex-grow:0;text-align:center;font-size:10px;line-height:1.2em;}.elementor-353552 .elementor-element.elementor-element-3a352c3f > .elementor-widget-container{margin:-181px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-7abb0aba > .elementor-widget-container{margin:-180px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-7abb0aba{text-align:center;font-size:14px;line-height:1.2em;}.elementor-353552 .elementor-element.elementor-element-68e2b2ca > .elementor-widget-container{margin:-110px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-68e2b2ca{font-size:10px;}.elementor-353552 .elementor-element.elementor-element-8d50d33{--width:184px;--margin-top:-156px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-8d50d33.e-con{--align-self:center;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83{width:var( --container-widget-width, 100% );max-width:100%;--container-widget-width:100%;--container-widget-flex-grow:0;z-index:120;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-size:8px;}.elementor-353552 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-size:10.5px;}.elementor-353552 .elementor-element.elementor-element-66fd4d36{--width:85%;--min-height:42px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;--margin-top:10px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4{z-index:120;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-type-html{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-size:10px;}.elementor-353552 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-size:10.5px;}.elementor-353552 .elementor-element.elementor-element-4a59cf88 > .elementor-widget-container{margin:2px 0px -10px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-4a59cf88{font-size:7.5px;}.elementor-353552 .elementor-element.elementor-element-17d3ad0c{--width:85%;--min-height:42px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;--margin-top:2px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:251;}.elementor-353552 .elementor-element.elementor-element-177c9b71 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-177c9b71{z-index:120;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-type-html{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-size:12px;}.elementor-353552 .elementor-element.elementor-element-177c9b71 .elementor-button{font-size:10.5px;}.elementor-353552 .elementor-element.elementor-element-5bafe1f3{--min-height:150px;--margin-top:-149px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353552 .elementor-element.elementor-element-446e8565 > .elementor-widget-container{margin:-133px 0px 0px 0px;}.elementor-353552 .elementor-element.elementor-element-446e8565.elementor-element{--align-self:flex-start;}.elementor-353552 .elementor-element.elementor-element-446e8565{text-align:left;font-size:10px;}.elementor-353552 .elementor-element.elementor-element-f4f51cb > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353552 .elementor-element.elementor-element-f4f51cb.elementor-element{--align-self:flex-start;}.elementor-353552 .elementor-element.elementor-element-f4f51cb{text-align:center;font-size:10px;}.elementor-353552 .elementor-element.elementor-element-f214306 > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353552 .elementor-element.elementor-element-f214306.elementor-element{--align-self:flex-start;}.elementor-353552 .elementor-element.elementor-element-f214306{text-align:center;font-size:10px;}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"353552\" class=\"elementor elementor-353552 elementor-bc-flex-widget e-loop-item e-loop-item-113276 post-113276 page type-page status-publish\" data-elementor-post-type=\"elementor_library\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-6da64a60 e-con-full OrdiMobileConteneurClass e-flex e-con e-child\" data-id=\"6da64a60\" data-element_type=\"container\" id=\"testIdTest\">\n\t\t<div class=\"elementor-element elementor-element-45f807e0 e-con-full UploadFileConteneur AdUploadedTitle elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop e-flex e-con e-child\" data-id=\"45f807e0\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-61dcced4 e-con-full e-flex e-con e-child\" data-id=\"61dcced4\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6c65b106 e-con-full e-flex e-con e-child\" data-id=\"6c65b106\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-eb74aa8 e-con-full e-flex e-con e-child\" data-id=\"eb74aa8\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1c762c45 e-con-full e-flex e-con e-child\" data-id=\"1c762c45\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-5be6a53a e-con-full e-flex e-con e-child\" data-id=\"5be6a53a\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-75917e71 e-con-full e-flex e-con e-child\" data-id=\"75917e71\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6be76773 AnnonceDragIcone elementor-widget elementor-widget-image\" data-id=\"6be76773\" data-element_type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/via-agency.media\/wp-content\/uploads\/2025\/05\/arrow-drag-64-bleu.png\" title=\"\" alt=\"\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5ad25a69 e-con-full CroixResetAnnonceContainer e-flex e-con e-child\" data-id=\"5ad25a69\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-52dec736 elementor-widget elementor-widget-image\" data-id=\"52dec736\" data-element_type=\"widget\" id=\"CroixResetAnnonce\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a href=\"#\">\n\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-transparent.png\" title=\"\" alt=\"\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-469b1a7c e-con-full UploadFileConteneur e-flex e-con e-child\" data-id=\"469b1a7c\" data-element_type=\"container\" id=\"UploadFileConteneur\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t<div class=\"elementor-element elementor-element-3f5f9124 e-con-full ChoisirEspacePublicitaireDisponibiliteConteneur e-flex e-con e-child\" data-id=\"3f5f9124\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-48a37689 e-con-full e-flex e-con e-child\" data-id=\"48a37689\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4b68287 PositionEspacePublicitaire elementor-widget elementor-widget-text-editor\" data-id=\"4b68287\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPosition\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-79a46db6 e-con-full e-flex e-con e-child\" data-id=\"79a46db6\" data-element_type=\"container\" id=\"ChoixEspacePublicitaireTitre\">\n\t\t\t\t<div class=\"elementor-element elementor-element-47d25f98 elementor-widget elementor-widget-text-editor\" data-id=\"47d25f98\" data-element_type=\"widget\" id=\"ChoixEspacePublicitaireTexte\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Fixed advertising space<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3aa42503 e-con-full e-flex e-con e-child\" data-id=\"3aa42503\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-12d5dc39 ReferenceEspacePublicitaire elementor-widget elementor-widget-text-editor\" data-id=\"12d5dc39\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reference<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-21535e15 e-con-full EspPubFormatMainContainer e-flex e-con e-child\" data-id=\"21535e15\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-de420c1 e-con-full e-flex e-con e-child\" data-id=\"de420c1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2f60f3f elementor-widget-mobile__width-inherit SelectionFormatTitre elementor-hidden-tablet elementor-widget elementor-widget-text-editor\" data-id=\"2f60f3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPlease select or create an ad format\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-78e1d9f5 e-con-full EspPubFormatListe e-flex e-con e-child\" data-id=\"78e1d9f5\" data-element_type=\"container\">\n\t\t<a class=\"elementor-element elementor-element-cf3602e e-con-full EspPubFormatContainer FormatIdCreation e-flex e-con e-child\" data-id=\"cf3602e\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7a1bb33 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"7a1bb33\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tCreation\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-6bf72a5 e-con-full EspPubFormatContainer FormatIdPopUp e-flex e-con e-child\" data-id=\"6bf72a5\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1a57dd9 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"1a57dd9\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPop-up\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-34d8bbba e-con-full EspPubFormatContainer FormatIdBanniere e-flex e-con e-child\" data-id=\"34d8bbba\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-32bd35c6 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"32bd35c6\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tBanner\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-5a9252c3 e-con-full EspPubFormatContainer FormatIdVideo e-flex e-con e-child\" data-id=\"5a9252c3\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8722042 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"8722042\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tVideo\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-19ecc2b3 e-con-full EspPubFormatContainer FormatIdCommunique e-flex e-con e-child\" data-id=\"19ecc2b3\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6071d405 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"6071d405\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPress release\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-3617569c e-con-full EspPubFormatContainer FormatIdInterview e-flex e-con e-child\" data-id=\"3617569c\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4b013e71 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"4b013e71\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tInterview\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-7450a6f8 e-con-full EspPubFormatContainer FormatIdParrainage e-flex e-con e-child\" data-id=\"7450a6f8\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-30970f89 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"30970f89\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tSponsorship\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6aac67a6 e-con-full HTMLUploadfileConteneur e-flex e-con e-child\" data-id=\"6aac67a6\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-40d299c elementor-widget__width-inherit HTMLUploadfileClass elementor-widget elementor-widget-html\" data-id=\"40d299c\" data-element_type=\"widget\" id=\"HTMLUploadfile\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta name=\"google\" content=\"notranslate\">\r\n    \r\n    <script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" src=\"https:\/\/code.jquery.com\/jquery-3.6.0.min.js\"><\/script>\r\n    <script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" src=\"https:\/\/code.jquery.com\/ui\/1.12.1\/jquery-ui.min.js\"><\/script>\r\n\r\n<script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\">\r\n\/\/ Lazy loading des biblioth\u00e8ques - charg\u00e9es uniquement au besoin\r\nwindow.VIALibraries = {\r\n    mammothLoaded: false,\r\n    pdfLoaded: false,\r\n    \r\n    loadMammoth: function(callback) {\r\n        if (this.mammothLoaded) { callback(); return; }\r\n        var script = document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mammoth\/1.4.0\/mammoth.browser.min.js';\r\n        script.onload = function() { \r\n            window.VIALibraries.mammothLoaded = true; \r\n            callback(); \r\n        };\r\n        document.head.appendChild(script);\r\n    },\r\n    \r\n    loadPdfJs: function(callback) {\r\n        if (this.pdfLoaded) { callback(); return; }\r\n        var script = document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.min.js';\r\n        script.onload = function() {\r\n            window.VIALibraries.pdfLoaded = true;\r\n            pdfjsLib.GlobalWorkerOptions.workerSrc = '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n            callback();\r\n        };\r\n        document.head.appendChild(script);\r\n    }\r\n};\r\n<\/script>\r\n\r\n<\/head>\r\n<body>\r\n    <div id=\"PopUpMessageAchattest\" class=\"draggable\" draggable=\"true\" style=\"justify-content: center; align-items: flex-start;\">\r\n        <div id=\"drop_file_zone_achat\" class=\"drop_file_zone_achat_class\" ondrop=\"uploadFile_achat(event)\" ondragover=\"return false\">\r\n        <div id=\"drag_upload_file_achat\">\r\n            <p><input class=\"button-2_achat\" type=\"button\" value=\"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\" onclick=\"fileExplorer_achat(event);\" \/><\/p>\r\n                <input type=\"file\" id=\"selectfile_achat\" \/>\r\n        <\/div>\r\n        <\/div>\r\n        <div class=\"img-content\"><\/div>\r\n    <\/div>\r\n<\/body>\r\n<\/html>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1fd15d20 elementor-widget__width-inherit HTMLUploadfileClass elementor-widget elementor-widget-html\" data-id=\"1fd15d20\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\">\r\nif (!window._espPubScriptLoaded) {\r\nwindow._espPubScriptLoaded = true;\r\n\/**\r\n * Configuration centralis\u00e9e\r\n *\/\r\nconst CONFIG = {\r\n    iframeId: 'yearbook-iframe',\r\n    ajaxUrl: 'https:\/\/via-agency.media\/wp-admin\/admin-ajax.php',\r\n    apercuUrl: 'https:\/\/rdc.yearbook-media.com\/apercu',\r\n    \r\n    allowedExtensions: {\r\n        video: ['mp4', 'mov', 'wmv', 'avi', 'flv', 'f4v', 'swf', 'mkv', 'webm', 'mpeg'],\r\n        image: ['jpg', 'jpeg', 'png', 'gif', 'tif', 'svg', 'webp'],\r\n        document: ['doc', 'docx', 'ppt', 'pptx', 'pdf']\r\n    },\r\n    \r\n    mimeTypes: {\r\n        docx: 'application\/vnd.openxmlformats-officedocument.wordprocessingml.document',\r\n        pdf: 'application\/pdf'\r\n    },\r\n    \r\n    dragScroll: {\r\n        threshold: 150,\r\n        maxSpeed: 65,\r\n        throttleDelay: 50\r\n    },\r\n    \r\n    breakpoints: {\r\n        mobile: 1000\r\n    },\r\n    \r\n    keywords: [\r\n        { normal: \"parrainage\", display: \"Parrainage\" },\r\n        { normal: \"communique\", display: \"Communiqu\u00e9\" },\r\n        { normal: \"interview\", display: \"Interview\" }\r\n    ]\r\n};\r\n\r\n\/**\r\n * \u2705 v1.19.1 : Module de positionnement dans l'iframe\r\n * \r\n * PROBL\u00c8ME : L'iframe ne scrolle pas en desktop \u2014 c'est le parent (#PageWebTitrePage) qui scrolle.\r\n * L'iframe est scal\u00e9e \u00e0 0.85 \u2192 les coordonn\u00e9es iframe \u2260 coordonn\u00e9es parent.\r\n * position:fixed dans l'iframe = relatif au CONTENU TOTAL, pas au viewport visible.\r\n * \r\n * SOLUTION : Le parent calcule visibleTopIframe (la position iframe du haut du viewport)\r\n * et l'envoie dans le message 'PageWebTitrePage-scroll'. L'iframe n'a qu'\u00e0 l'utiliser.\r\n * \r\n * Pour scroller, l'iframe envoie 'scrollToIframeY' au parent qui fait la conversion.\r\n *\/\r\nconst ScrollHelper = {\r\n    _visibleTopIframe: 0,\r\n    _iframeScale: 0.85,\r\n\r\n    init() {\r\n        \/\/ \u00c9couter les donn\u00e9es de scroll envoy\u00e9es par le parent\r\n        window.addEventListener('message', (event) => {\r\n            var msg = event.data;\r\n            if (msg ? msg.action === 'PageWebTitrePage-scroll' : false) {\r\n                if (typeof msg.visibleTopIframe === 'number') {\r\n                    this._visibleTopIframe = msg.visibleTopIframe;\r\n                }\r\n                if (typeof msg.iframeScale === 'number') {\r\n                    this._iframeScale = msg.iframeScale;\r\n                }\r\n            }\r\n        });\r\n        console.log('\u2705 ScrollHelper initialis\u00e9');\r\n    },\r\n\r\n    \/**\r\n     * Position Y dans le document iframe correspondant au haut du viewport visible\r\n     *\/\r\n    getVisibleTop() {\r\n        if (window === window.top) {\r\n            return window.scrollY || 0;\r\n        }\r\n        return this._visibleTopIframe;\r\n    },\r\n\r\n    \/**\r\n     * Demande au parent de scroller pour positionner `element` \u00e0 `offsetFromTop` px du viewport\r\n     *\/\r\n    scrollElementTo(element, offsetFromTop) {\r\n        if (!element) return;\r\n        \/\/ \u2705 v2.7.3 : Pas de scroll auto sur les sites pays (window.top)\r\n        \/\/ Le scroll apr\u00e8s d\u00e9p\u00f4t d'annonce est ind\u00e9sirable sur desktop et mobile\r\n        if (window === window.top) return;\r\n\r\n        \/\/ getBoundingClientRect().top dans une iframe sans scroll = position absolue Y\r\n        var elementAbsY = element.getBoundingClientRect().top;\r\n\r\n        if (window === window.top) {\r\n            window.scrollTo({ top: elementAbsY - offsetFromTop + (window.scrollY || 0), behavior: 'smooth' });\r\n            return;\r\n        }\r\n\r\n        \/\/ Envoyer au parent \u2014 le parent fait la conversion scale\r\n        window.parent.postMessage({\r\n            type: 'scrollToIframeY',\r\n            iframeId: CONFIG.iframeId,\r\n            iframeY: elementAbsY,\r\n            offsetFromTop: offsetFromTop\r\n        }, '*');\r\n        console.log('\ud83d\udcdc scrollToIframeY:', { iframeY: Math.round(elementAbsY), offsetFromTop });\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'\u00e9tat (SessionStorage)\r\n *\/\r\nconst StateManager = {\r\n    init() {\r\n        if (sessionStorage.getItem(\"AchatEspaceCall\") === 'No') {\r\n            sessionStorage.clear();\r\n        }\r\n        \r\n        this.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        this.set('dragstart_Rank_Emplacement_Page_Web', 'No');\r\n        this.set('dragstart_Commande_Emplacement_Page_Web', 'No');\r\n        \r\n        \/\/ \u2705 v2.1.1 : Popup \u2192 ne jamais pr\u00e9remplir le format\r\n        if (this.get('PopUpChoice') === 'Yes') {\r\n            this.set('Formatchoisi', 'No');\r\n            this.set('FormatSelect', '');\r\n            this.set('Commande_Format_Transmis', '');\r\n        }\r\n    },\r\n    \r\n    get(key) {\r\n        return sessionStorage.getItem(key);\r\n    },\r\n    \r\n    set(key, value) {\r\n        sessionStorage.setItem(key, value);\r\n    },\r\n    \r\n    getMultiple(keys) {\r\n        return keys.reduce((acc, key) => {\r\n            acc[key] = this.get(key);\r\n            return acc;\r\n        }, {});\r\n    },\r\n    \r\n    setMultiple(obj) {\r\n        Object.entries(obj).forEach(([key, value]) => {\r\n            this.set(key, value);\r\n        });\r\n    },\r\n    \r\n    buildEmplacementReference(rank) {\r\n        const codeSite = this.get('codeSite');\r\n        const codePage = this.get('codePage');\r\n        return `${codeSite}${codePage}L${rank.substring(3)}`;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des fichiers\r\n *\/\r\nconst FileManager = {\r\n    getExtension(filename) {\r\n        console.log('filename:', filename);\r\n        const parts = filename.split('.');\r\n        return parts.length > 1 ? parts.pop().toLowerCase() : '';\r\n    },\r\n    \r\n    isAllowedExtension(extension) {\r\n        return Object.values(CONFIG.allowedExtensions)\r\n            .flat()\r\n            .includes(extension);\r\n    },\r\n    \r\n    getFileType(extension) {\r\n        for (const [type, extensions] of Object.entries(CONFIG.allowedExtensions)) {\r\n            if (extensions.includes(extension)) {\r\n                return type;\r\n            }\r\n        }\r\n        return null;\r\n    },\r\n    \r\n    async urlToFile(url, filename) {\r\n        console.log(\"Fetching from URL:\", url);\r\n        \r\n        const mimeType = filename.includes('.docx') \r\n            ? CONFIG.mimeTypes.docx \r\n            : filename.includes('.pdf') \r\n            ? CONFIG.mimeTypes.pdf \r\n            : null;\r\n        \r\n        try {\r\n            const response = await fetch(url);\r\n            \r\n            if (!response.ok) {\r\n                throw new Error(`Network response was not ok: ${response.status} ${response.statusText}`);\r\n            }\r\n            \r\n            const blob = await response.blob();\r\n            return new File([blob], filename, { type: mimeType });\r\n        } catch (error) {\r\n            console.error(\"Error in urlToFile:\", error);\r\n            alert(\"Erreur lors du chargement du fichier. Veuillez r\u00e9essayer.\");\r\n            throw error;\r\n        }\r\n    },\r\n    \r\n    createObjectUrl(file) {\r\n        \/\/ R\u00e9voquer l'ancien URL si existant\r\n        const oldUrl = StateManager.get('objectUrl');\r\n        if (oldUrl ? oldUrl.startsWith('blob:') : false) {\r\n            URL.revokeObjectURL(oldUrl);\r\n            console.log('\ud83d\uddd1\ufe0f Old Object URL revoked');\r\n        }\r\n        \r\n        const url = URL.createObjectURL(file);\r\n        StateManager.set('objectUrl', url);\r\n        return url;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion du Drag & Drop\r\n *\/\r\nconst DragDropManager = {\r\n    state: {\r\n        isFileBeingDragged: false,\r\n        draggedFile: null,\r\n        dragInProgress: false,\r\n        throttleTimeout: null\r\n    },\r\n    \r\n    init() {\r\n        this.attachEventListeners();\r\n    },\r\n    \r\n    attachEventListeners() {\r\n        window.addEventListener('dragstart', (e) => this.handleDragStart(e), true);\r\n        jQuery(document).on('dragover', (e) => this.handleDragOver(e));\r\n        jQuery(document).on('dragleave drop', (e) => this.handleDragEnd(e));\r\n        jQuery(\"#drop_file_zone_achat\").on(\"dragover\", (e) => this.handleDropZoneDragOver(e));\r\n        jQuery(\".draggable\").on(\"dragend\", (e) => this.handleDraggableEnd(e));\r\n    },\r\n    \r\n    handleDragStart(e) {\r\n        console.log('Window-level dragstart', e);\r\n        this.state.dragInProgress = true;\r\n    \r\n        const $target = $(e.target);\r\n        const droppableParent = $target.closest('.droppable');\r\n        const parentId = droppableParent.length ? droppableParent.attr('id') : null;\r\n        \r\n        StateManager.set('dragstart_Rank_Emplacement_Page_Web', parentId);\r\n        console.log('dragstart_Rank_Emplacement_Page_Web', parentId);\r\n        \r\n        \/\/ \u2705 CALCULER IMM\u00c9DIATEMENT dragstart_Commande_Emplacement_Page_Web\r\n        if (parentId) {\r\n            const dragstartRef = StateManager.buildEmplacementReference(parentId);\r\n            StateManager.set('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n            console.log('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n            \/\/ \u2705 v2.4.5 : Capturer l'\u00e9tat checkbox R\u00e9server au moment du dragstart\r\n            \/\/ Si l'utilisateur a explicitement d\u00e9coch\u00e9, forcer \u00e0 No m\u00eame si DOM est coch\u00e9\r\n            var $_dragCb = $('#' + parentId).find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n            var _dragCbChecked = $_dragCb.prop('checked') === true;\r\n            var _expliciteDecocheDrag = StateManager.get('_reserverDecoche_' + parentId) === 'Yes';\r\n            var _reserverDrag = _dragCbChecked ? !_expliciteDecocheDrag : false;\r\n            StateManager.set('dragstart_ReserverChecked', _reserverDrag ? 'Yes' : 'No');\r\n            \/\/ R\u00e9initialiser le flag apr\u00e8s lecture\r\n            StateManager.set('_reserverDecoche_' + parentId, 'No');\r\n            console.log('[dragstart] ReserverChecked dom:', _dragCbChecked, '| d\u00e9coch\u00e9 explicitement:', _expliciteDecocheDrag, '| final:', _reserverDrag);\r\n        }\r\n    \r\n        const dataTransfer = e.dataTransfer || e.originalEvent?.dataTransfer;\r\n        const files = dataTransfer?.files || null;\r\n        \r\n        const imgSrc = $target.closest('.draggable').find('img').attr('src');\r\n        const videoSrc = $target.closest('.draggable').find('video').attr('src') || \r\n                        $target.closest('.draggable').find('video source').attr('src');\r\n        const mediaSrc = imgSrc || videoSrc;\r\n    \r\n        console.log('dragstart target:', e.target);\r\n        console.log('files:', files);\r\n        console.log('mediaSrc:', mediaSrc);\r\n    \r\n        if (!mediaSrc ? (!files || files.length === 0) : false) {\r\n            console.log('No media source or files found, preventing drag');\r\n            e.preventDefault();\r\n            this.state.dragInProgress = false;\r\n            return false;\r\n        }\r\n    \r\n        \/\/ \u2705 v4.9ds Pb 5 : poser une image fant\u00f4me (ghost) sur l'\u00e9v\u00e9nement drag pour que\r\n        \/\/   le navigateur affiche l'image qu'on d\u00e9place au curseur, au lieu de la croix\r\n        \/\/   d'interdiction par d\u00e9faut. Sans setDragImage, certains navigateurs (Chrome,\r\n        \/\/   Firefox) affichent une ic\u00f4ne \"non autoris\u00e9\" si l'\u00e9l\u00e9ment source est complexe\r\n        \/\/   ou si la zone de drop n'est pas imm\u00e9diatement reconnue.\r\n        \/\/   On configure aussi effectAllowed='copy' pour standardiser le comportement.\r\n        if (dataTransfer) {\r\n            try {\r\n                dataTransfer.effectAllowed = 'copy';\r\n                var $_dragImg = $target.closest('.draggable').find('img').first();\r\n                if ($_dragImg.length ? $_dragImg[0].complete : false) {\r\n                    var _imgEl = $_dragImg[0];\r\n                    var _r = _imgEl.getBoundingClientRect();\r\n                    \/\/ D\u00e9calage du ghost : centr\u00e9 sur le curseur\r\n                    var _ox = Math.round((_r.width || 100) \/ 2);\r\n                    var _oy = Math.round((_r.height || 60) \/ 2);\r\n                    dataTransfer.setDragImage(_imgEl, _ox, _oy);\r\n                }\r\n            } catch(_eDI) { console.warn('[dragstart] setDragImage \u00e9chec:', _eDI); }\r\n        }\r\n    \r\n        if (mediaSrc) {\r\n            StateManager.set('objectUrl', mediaSrc);\r\n        }\r\n    \r\n        StateManager.set('Commande_Format_Transmis', '');\r\n        \r\n        if ($target.closest('.droppable').find('.doc-preview-container:visible').length > 0) {\r\n            StateManager.set('Commande_Format_Transmis', 'R\u00e9dactionnel');\r\n            StateManager.set('FullPathAdFile', \r\n                $target.closest('.droppable').find('.doc-preview-FullPathAdFile').text());\r\n        }\r\n    \r\n        if (files ? files.length > 0 : false) {\r\n            this.state.isFileBeingDragged = true;\r\n            this.state.draggedFile = files[0];\r\n        }\r\n        \r\n        \/\/ \u2705 TOUJOURS marquer comme \"Moved\" quand on drag depuis un espace existant\r\n        if (parentId ? mediaSrc : false) {\r\n            StateManager.set('FirstUploadFileorMoved', 'Moved');\r\n            console.log('\u2705 Drag depuis espace existant - Moved');\r\n        }\r\n    \r\n        console.log('Drag Started', {\r\n            file: this.state.draggedFile,\r\n            mediaSrc: mediaSrc,\r\n            dragstartRef: StateManager.get('dragstart_Commande_Emplacement_Page_Web')\r\n        });\r\n    },\r\n    \r\n    handleDragOver(e) {\r\n        e.preventDefault();\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            this.handleIframeDragScroll(e);\r\n        } else {\r\n            this.handleDirectPageScroll(e);\r\n        }\r\n    },\r\n    \r\n    handleIframeDragScroll(e) {\r\n        if (!this.state.throttleTimeout) {\r\n            this.state.throttleTimeout = setTimeout(() => {\r\n                MessageManager.sendToParent('dragScroll', { clientY: e.clientY });\r\n                this.state.throttleTimeout = null;\r\n            }, CONFIG.dragScroll.throttleDelay);\r\n        }\r\n    },\r\n    \r\n    handleDirectPageScroll(e) {\r\n        const { threshold, maxSpeed } = CONFIG.dragScroll;\r\n        const windowHeight = window.innerHeight;\r\n        \r\n        if (e.clientY < threshold) {\r\n            const speed = Math.max(1, maxSpeed * (1 - e.clientY \/ threshold));\r\n            window.scrollBy(0, -speed);\r\n        } else if (e.clientY > windowHeight - threshold) {\r\n            const speed = Math.max(1, maxSpeed * (1 - (windowHeight - e.clientY) \/ threshold));\r\n            window.scrollBy(0, speed);\r\n        }\r\n    },\r\n    \r\n    handleDragEnd(e) {\r\n        e.preventDefault();\r\n        MessageManager.sendToParent('dragEnd', {});\r\n        console.log('dragleave drop');\r\n    },\r\n    \r\n    handleDropZoneDragOver(e) {\r\n        e.preventDefault();\r\n        console.log(\"FirstUploadFileorMoved:\", StateManager.get('FirstUploadFileorMoved'));\r\n    },\r\n    \r\n    handleDraggableEnd(e) {\r\n        e.preventDefault();\r\n        this.resetState();\r\n        console.log('dragend');\r\n    },\r\n    \r\n    resetState() {\r\n        this.state.isFileBeingDragged = false;\r\n        this.state.draggedFile = null;\r\n        this.state.dragInProgress = false;\r\n    },\r\n    \r\n    clearDataTransferFiles(e) {\r\n        e.dataTransfer = new DataTransfer();\r\n        console.log('DataTransfer files cleared');\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'interface utilisateur\r\n *\/\r\nconst UIManager = {\r\n    isMobile() {\r\n        return window.outerWidth < CONFIG.breakpoints.mobile;\r\n    },\r\n    \r\n    isDesktop() {\r\n        const userAgent = navigator.userAgent;\r\n        const isWindows = userAgent.indexOf('Windows') > -1;\r\n        const isMac = userAgent.indexOf('Macintosh') > -1;\r\n        const isLinux = userAgent.indexOf('Linux') > -1 ? userAgent.indexOf('Android') === -1 : false;\r\n        return isWindows || isMac || isLinux;\r\n    },\r\n    \r\n    initMobileUI() {\r\n        $(document).ready(() => {\r\n            $('label[for=\"form-field-LienAnnonce\"]').each(function() {\r\n                $(this).text('Renseigner le lien hypertexte de l\\'annonce');\r\n            });\r\n            \r\n            $('input[name=\"form_fields[LienAnnonce]\"]').each(function() {\r\n                $(this).attr('placeholder', 'Renseigner le lien hypertexte de l\\'annonce');\r\n            });\r\n        });\r\n        \r\n        jQuery('.MsgDatesText').hide();\r\n        this.attachMobileEventListeners();\r\n    },\r\n    \r\n    attachMobileEventListeners() {\r\n        jQuery('.TransmettreFichierAnnonceContainer').on('click', (e) => {\r\n            this.handleMobileSpaceSelection(e);\r\n        });\r\n        \r\n        jQuery('[id=\"form-field-selected_currency_Mobile\"]').off('change').on('change', (e) => {\r\n            this.handleMobileCurrencyChange(e);\r\n        });\r\n    },\r\n    \r\n    handleMobileSpaceSelection(e) {\r\n        e.preventDefault();\r\n        jQuery('#IndisponibilitesMsg').hide();\r\n        \r\n        jQuery('input[name=\"form_fields[SelectEspacePublicitaireMobile]\"]').prop('checked', false);\r\n        jQuery(e.currentTarget).find('input[name=\"form_fields[SelectEspacePublicitaireMobile]\"]').prop('checked', true);\r\n        \r\n        StateManager.set(\"PageWebDisplayed\", 'Yes');\r\n        \r\n        jQuery('.FormSelectDevisesMobile').hide();\r\n        jQuery(e.target).closest('.EspacePublicitaireMobile').find('.FormSelectDevisesMobile').show();\r\n        \r\n        const $droppable = jQuery(e.currentTarget).closest('.droppable');\r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n        StateManager.set('Commande_Emplacement_Page_Web', \r\n            StateManager.buildEmplacementReference(rankId));\r\n        \r\n        this.updateEmplacementDisplay();\r\n        this.handleAvailabilityDisplay(e);\r\n        this.displayUploadedAdIfExists(e);\r\n        this.updateTariffDisplay(e);\r\n    },\r\n    \r\n    updateEmplacementDisplay() {\r\n        const emplacement = StateManager.get('Commande_Emplacement_Page_Web');\r\n        jQuery('#EmplacementAnnonceDataStep3').html(emplacement).css({'color': '#56BE50'});\r\n        console.log(\"Emplacement:\", emplacement);\r\n    },\r\n    \r\n    handleAvailabilityDisplay(e) {\r\n        const $espace = jQuery(e.target).closest('.EspacePublicitaireMobile');\r\n        const dispoText = $espace.find('.ChoisirEspacePublicitaireDisponibilite').text();\r\n        \r\n        if (dispoText.length > 46) {\r\n            const espacePublicitaireDispoFrom = dispoText.slice(-10);\r\n            console.log(\"espacePublicitaireDispoFrom:\", espacePublicitaireDispoFrom);\r\n            console.log(\"Date de debut:\", StateManager.get(\"Debut_de_campagne\"));\r\n        } else {\r\n            jQuery('#form-field-DebutCampagne').val(StateManager.get(\"Debut_de_campagne\"));\r\n        }\r\n    },\r\n    \r\n    displayUploadedAdIfExists(e) {\r\n        if (StateManager.get(\"FileReceived\") === 'Yes') {\r\n            jQuery('.VisualisationAnnonceMobile').hide();\r\n            \r\n            const espaceId = StateManager.get('espaceChoisi');\r\n            const $espace = document.querySelector(`[id=\"${espaceId}\"]`);\r\n            \r\n            jQuery($espace).find('.MessageAnnonceMobileTexte')\r\n                .html('Merci de choisir des dates de campagne<br>afin d\\'obtenir le tarif de cet espace publicitaire');\r\n            jQuery($espace).find('.VisualisationAnnonceMobile').show();\r\n        \r\n            const img = document.createElement('img');\r\n            img.src = StateManager.get('objectUrl');\r\n            img.style.width = 'auto';\r\n            img.style.height = 'auto';\r\n            img.style.maxWidth = '100%';\r\n            img.style.maxHeight = '200px';\r\n            \r\n            jQuery('.VisualisationAnnonceMobile').empty();\r\n            jQuery($espace).find('.VisualisationAnnonceMobile').append(img);\r\n        }\r\n    },\r\n    \r\n    updateTariffDisplay(e) {\r\n        jQuery('.TariftobedisplayedMobile').html('-');\r\n        const newTarif = StateManager.get('NewTarifformatted');\r\n        \r\n        if (newTarif ? newTarif !== '-' : false) {\r\n            jQuery(e.target).closest('.EspacePublicitaireMobile')\r\n                .find('.TariftobedisplayedMobile')\r\n                .html(newTarif);\r\n        }\r\n    },\r\n    \r\n    handleMobileCurrencyChange(e) {\r\n        event.preventDefault();\r\n        StateManager.setMultiple({\r\n            \"SelectionCatalogueOuAchat\": 'Catalogue',\r\n            \"selected_currency\": jQuery(e.currentTarget).val()\r\n        });\r\n        \r\n        jQuery('#form-field-selected_currency_2').val(StateManager.get(\"selected_currency\"));\r\n        console.log(\"selected currency:\", StateManager.get(\"selected_currency\"));\r\n        \r\n        setTimeout(() => {\r\n            jQuery(e.target).closest('.EspacePublicitaireMobile')\r\n                .find('.TariftobedisplayedMobile')\r\n                .html(StateManager.get(\"NewTarifformatted\"));\r\n        }, 500);\r\n    },\r\n    \r\n    initDesktopUI() {\r\n        StateManager.setMultiple({\r\n            \"FileReceived\": \"No\",\r\n            \"AdDisplayed\": \"No\"\r\n        });\r\n        jQuery('.MsgAdNotDisplayed').hide();\r\n    },\r\n    \r\n    showUploadProgress($dropZone) {\r\n        \/\/ Cacher les \u00e9l\u00e9ments\r\n        $dropZone.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .TexteMobileAnnonce')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        \/\/ \u2705 v2.7.3 : Loading dans le dropZone (flux normal, pas de fixed)\r\n        (function() {\r\n            var $_drp    = $dropZone.closest('.droppable');\r\n            var _rankId  = $_drp.attr('id') || '';\r\n            var _isEle0A = _rankId === 'Ele0A';\r\n            var _isMob   = UIManager.isMobile();\r\n            var _posLib  = PreviewRenderer._getPositionLibelle(_rankId) || '';\r\n            var _empl    = StateManager.get('Commande_Emplacement_Page_Web') || '';\r\n            \/\/ \u2705 Bug 11 : -2px sur mobile\r\n            var _fsPos   = _isMob ? '8px'  : '12px';\r\n            var _fsTitle = _isMob ? '9px'  : '14px';\r\n            var _fsRef   = _isMob ? '7px'  : '11px';\r\n\r\n            \/\/ Header identique \u00e0 _buildAdOverlay \u2014 couleurs selon l'espace\r\n            var _hdrBg    = _isEle0A ? '#D0C067' : '#9fc5f3';\r\n            var _titleTxt = _isEle0A ? 'Espace publicitaire Pop-up' : 'Espace publicitaire';\r\n            var _fsRefBold = _isMob ? '8.5px' : '12.5px';\r\n            var _hdrHtml = '<div style=\"display:flex;align-items:center;background:' + _hdrBg + ';padding:4px 8px;box-sizing:border-box;font-family:Roboto,Arial,sans-serif;font-weight:700;color:#ffffff;position:relative;border-radius:4px 4px 0 0;\">'\r\n                + '<div style=\"flex:0 0 auto;z-index:1;\">'\r\n                +   (_posLib ? '<span style=\"font-size:' + _fsPos + ';color:#ffffff;\">' + _posLib + '<\/span>' : '')\r\n                + '<\/div>'\r\n                + '<div style=\"position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;pointer-events:none;\">'\r\n                +   '<span style=\"font-size:' + _fsTitle + ';color:#ffffff;\">' + _titleTxt\r\n                +   (_empl ? ' <span style=\"font-size:' + _fsRefBold + ';font-weight:700;color:#ffffff;\">' + _empl + '<\/span>' : '')\r\n                +   '<\/span>'\r\n                + '<\/div>'\r\n                + '<div style=\"flex:0 0 auto;margin-left:auto;z-index:1;\"><\/div>'\r\n                + '<\/div>';\r\n\r\n            var _bodyHtml = '<div style=\"display:flex;align-items:center;justify-content:center;padding:30px 12px;background:#fff;\">'\r\n                + '<span style=\"color:#00ff19;font-weight:700;font-size:' + (_isMob ? '14px' : '18px') + ';font-family:Roboto,Arial,sans-serif;\">'\r\n                + 'Loading in progress <span style=\"display:inline-block;\"><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><\/span>'\r\n                + '<\/span><\/div>';\r\n\r\n            \/\/ Masquer les \u00e9l\u00e9ments parasites SANS toucher aux dimensions\r\n            $_drp.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne').hide();\r\n            $_drp.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n\r\n            \/\/ Neutraliser les marges n\u00e9gatives Elementor pendant le loading\r\n            \/\/ Sauvegarder sur data() AVANT reset \u2192 styleUploadedAd retrouvera les vraies valeurs\r\n            var _drpOrigMt = parseInt($_drp.css('margin-top')) || 0;\r\n            var _drpOrigMb = parseInt($_drp.css('margin-bottom')) || 0;\r\n            if ($_drp.data('orig-mt') === undefined) { $_drp.data('orig-mt', _drpOrigMt); }\r\n            var $_drpOmc = $_drp.find('.OrdiMobileConteneurClass').first();\r\n            if ($_drpOmc.length) {\r\n                if ($_drpOmc.data('orig-mb') === undefined) { $_drpOmc.data('orig-mb', parseInt($_drpOmc.css('margin-bottom')) || 0); }\r\n            }\r\n            if (_drpOrigMt < 0) { $_drp.css('margin-top', '0px'); }\r\n            if (_drpOrigMb < 0) { $_drp.css('margin-bottom', '0px'); }\r\n\r\n            \/\/ Injecter dans $dropZone sans modifier sa hauteur (height:180px CSS natif)\r\n            \/\/ Forcer le dropZone \u00e0 stretcher son enfant (pas align-items:center)\r\n            $dropZone.css({ 'align-items': 'stretch', 'padding': '0' });\r\n            \/\/ Mobile : 50px de marge en dessous pour \u00e9viter chevauchement avec contenu suivant\r\n            \/\/ Poser sur plusieurs \u00e9l\u00e9ments avec !important + MutationObserver pour r\u00e9sister aux \u00e9crasements\r\n            if (_isMob) {\r\n                var _omcLi = $dropZone.closest('.OrdiMobileConteneurClass')[0];\r\n                if (_omcLi) {\r\n                    _omcLi.style.setProperty('margin-bottom', '50px', 'important');\r\n                    \/\/ Observer : si un autre script \u00e9crase, on restaure\r\n                    try {\r\n                        if (_omcLi._loadingMbObs) { _omcLi._loadingMbObs.disconnect(); }\r\n                        _omcLi._loadingMbObs = new MutationObserver(function(muts) {\r\n                            if (!_omcLi.querySelector('.via-loading-inline')) {\r\n                                \/\/ Loading termin\u00e9, arr\u00eat de l'observer\r\n                                _omcLi._loadingMbObs.disconnect();\r\n                                _omcLi._loadingMbObs = null;\r\n                                return;\r\n                            }\r\n                            var _cur = _omcLi.style.marginBottom;\r\n                            var _needRestore = true;\r\n                            if (_cur === '50px !important') { _needRestore = false; }\r\n                            if (_cur === '50px') { _needRestore = false; }\r\n                            if (_needRestore) {\r\n                                _omcLi.style.setProperty('margin-bottom', '50px', 'important');\r\n                            }\r\n                        });\r\n                        _omcLi._loadingMbObs.observe(_omcLi, { attributes: true, attributeFilter: ['style'] });\r\n                        \/\/ S\u00e9curit\u00e9 : stop au bout de 10 secondes\r\n                        setTimeout(function() {\r\n                            if (_omcLi._loadingMbObs) {\r\n                                _omcLi._loadingMbObs.disconnect();\r\n                                _omcLi._loadingMbObs = null;\r\n                            }\r\n                        }, 10000);\r\n                    } catch(_e) { console.warn('[loading mb observer]', _e); }\r\n                }\r\n            }\r\n            $dropZone.html('<div class=\"via-loading-inline\" style=\"'\r\n                + 'width:100%;height:100%;'\r\n                + 'border:2px solid #00FF19;border-radius:4px;'\r\n                + 'overflow:hidden;background:#fff;box-sizing:border-box;'\r\n                + 'display:flex;flex-direction:column;'\r\n                + (_isMob ? 'margin-top:40px;' : 'margin-top:40px;') + '\">'\r\n                + _hdrHtml\r\n                + '<div style=\"flex:1;display:flex;align-items:' + (_isMob ? 'flex-start' : 'center') + ';justify-content:center;padding-top:' + (_isMob ? '50px' : '0') + ';margin-top:' + (_isMob ? '0' : '-15px') + ';\">'\r\n                + '<span style=\"color:#00ff19;font-weight:700;font-size:' + (_isMob ? '14px' : '18px') + ';font-family:Roboto,Arial,sans-serif;\">'\r\n                + 'Loading in progress <span style=\"display:inline-block;\"><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><\/span>'\r\n                + '<\/span><\/div>'\r\n                + '<\/div>');\r\n        })();\r\n    },\r\n    \r\n    showFormatError($dropZone, message = 'Format de fichier incompatible') {\r\n        \/\/ \u2705 v2.4.6 : M\u00eame rendu overlay que le bloc jpeg \u2014 position absolute sur $dropZone\r\n        \/\/ \u2705 v4.9n  : timeout 5s, mobile r\u00e9duit \u00e0 50% via scale\r\n        var _errId = 'fmt-error-msg-' + Date.now();\r\n        jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n        var _isMob = UIManager.isMobile();\r\n        $dropZone.css('position', 'relative');\r\n        var _errHtml = '<div id=\"' + _errId + '\" style=\"'\r\n            + 'position:absolute;top:' + (_isMob ? '54px' : '50px') + ';left:50%;'\r\n            + (_isMob\r\n                ? 'transform:translateX(-50%) scale(0.604);transform-origin:top center;'\r\n                : 'transform:translateX(-50%);')\r\n            + 'width:auto;white-space:nowrap;'\r\n            + 'background:#fff;color:#FB5E2A;font-weight:700;font-size:' + (_isMob ? '13px' : '14px') + ';'\r\n            + 'text-align:center;padding:8px 10px;border-radius:6px;'\r\n            + 'border:2px solid #FB5E2A;box-sizing:border-box;'\r\n            + 'z-index:99999;line-height:1.4;'\r\n            + '\">' + message + '<\/div>';\r\n        $dropZone.append(_errHtml);\r\n        setTimeout(function() { jQuery('#' + _errId).remove(); }, 5000);\r\n    },\r\n    \r\n    updateAfterSuccessfulUpload($dropZone) {\r\n        jQuery('#errorMessageMobileText').hide();\r\n        \r\n        $dropZone.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 Pas de margin-top:150px si d\u00e9p\u00f4t depuis miniature kit (d\u00e9j\u00e0 positionn\u00e9)\r\n        if (!window._dropFromMiniature) {\r\n            $dropZone.closest('.OrdiMobileConteneurClass')\r\n                .css({'margin-top': '150px', 'margin-bottom': '0px'});\r\n        }\r\n    },\r\n    \r\n    finalizeAdDisplay($dropZone) {\r\n        StateManager.setMultiple({\r\n            \"FileReceived\": \"Yes\",\r\n            \"PageWebDisplayed\": \"Yes\"\r\n        });\r\n        \r\n        jQuery('#MsgChoixPageWeb, #MsgInsererAnnonceConteneur').hide();\r\n        jQuery('#ChoixEspacePublicitaire')\r\n            .html('L\\'annonce est plac\u00e9e dans un espace publicitaire')\r\n            .show()\r\n            .css({'color': '#56BE50'});\r\n        \r\n        jQuery('#MsgPlacerAnnoncePosition').hide();\r\n        jQuery('#FonctionMenu4').show();\r\n        jQuery('.AnnonceData').html(\"Transmis\").css({'color': '#56BE50'});\r\n        \r\n        jQuery('#ProcederPaiementConteneur').hide();\r\n        jQuery('#ProcederPaiementTitreIconeUp').hide();\r\n        jQuery('#ProcederPaiementTitreIconeDown').show();\r\n        \r\n        jQuery('#message').remove();\r\n        \r\n        this.styleUploadedAd($dropZone);\r\n        this.adjustLayoutAfterUpload($dropZone);\r\n        \/\/ \u2705 v2.7.3 : Masquer imm\u00e9diatement apr\u00e8s styleUploadedAd\/adjustLayout\r\n        \/\/ (ces 2 fonctions remontrent des \u00e9l\u00e9ments \u2192 les cacher avant _buildAdOverlay)\r\n        (function(_dz) {\r\n            var $_d = jQuery(_dz).closest('.droppable');\r\n            $_d.find('.DeplaceAnnonce, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .CroixResetAnnonceContainer, .via-ad-header, .via-position-label, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_d.find('.PositionEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })($dropZone[0] || $dropZone);\r\n        \/\/ \u2705 v2.7.3 : Injecter header + footer dans le liser\u00e9 vert\r\n        setTimeout(() => { this._buildAdOverlay($dropZone); }, 250);\r\n        \r\n        \/\/ \u2705 v4.9bf : Figer la largeur du wrapper Ele0A sur mobile site pays apr\u00e8s d\u00e9p\u00f4t\r\n        \/\/            (emp\u00eache le r\u00e9tr\u00e9cissement au scroll caus\u00e9 par width:fit-content)\r\n        \/\/            MutationObserver maintient la valeur en restaurant tout \u00e9crasement\r\n        setTimeout(function() {\r\n            var _droppableId = $dropZone.closest('.droppable').attr('id');\r\n            if (_droppableId !== 'Ele0A') return;\r\n            var _isMobSitePays = (window === window.top);\r\n            if (!_isMobSitePays) return;\r\n            var _isMobCheck = false;\r\n            if (navigator.maxTouchPoints > 0) { _isMobCheck = true; }\r\n            if (window.innerWidth < 1000) { _isMobCheck = true; }\r\n            if (window.outerWidth < 1000) { _isMobCheck = true; }\r\n            if (!_isMobCheck) return;\r\n            var $_wrapE0A = jQuery('.ToBeHidden').has('#Ele0A');\r\n            if (!$_wrapE0A.length) return;\r\n            var _wrapEl = $_wrapE0A[0];\r\n            var _w = _wrapEl.getBoundingClientRect().width;\r\n            if (_w < 50) return;\r\n            var _freezeW = Math.round(_w);\r\n            window._ele0AFrozenWidth = _freezeW;\r\n            \/\/ Applique les 3 propri\u00e9t\u00e9s de largeur\r\n            function _applyFrozenWidth() {\r\n                _wrapEl.style.setProperty('width',     _freezeW + 'px', 'important');\r\n                _wrapEl.style.setProperty('max-width', _freezeW + 'px', 'important');\r\n                _wrapEl.style.setProperty('min-width', _freezeW + 'px', 'important');\r\n            }\r\n            _applyFrozenWidth();\r\n            console.log('[v4.9bf] Ele0A wrapper largeur fig\u00e9e \u00e0 ' + _freezeW + 'px');\r\n            \/\/ Observer : si un autre script change la largeur, on restaure\r\n            try {\r\n                if (_wrapEl._e0AWidthObs) { _wrapEl._e0AWidthObs.disconnect(); }\r\n                _wrapEl._e0AWidthObs = new MutationObserver(function() {\r\n                    var _cur = _wrapEl.getBoundingClientRect().width;\r\n                    if (Math.abs(_cur - _freezeW) > 2) {\r\n                        _applyFrozenWidth();\r\n                    }\r\n                });\r\n                _wrapEl._e0AWidthObs.observe(_wrapEl, { attributes: true, attributeFilter: ['style'] });\r\n                \/\/ Observer aussi #Ele0A (si c'est lui qui change la largeur)\r\n                var _e0AInner = _wrapEl.querySelector('#Ele0A');\r\n                if (_e0AInner) {\r\n                    if (_e0AInner._e0AInnerObs) { _e0AInner._e0AInnerObs.disconnect(); }\r\n                    _e0AInner._e0AInnerObs = new MutationObserver(function() {\r\n                        var _cur = _wrapEl.getBoundingClientRect().width;\r\n                        if (Math.abs(_cur - _freezeW) > 2) {\r\n                            _applyFrozenWidth();\r\n                        }\r\n                    });\r\n                    _e0AInner._e0AInnerObs.observe(_e0AInner, { attributes: true, attributeFilter: ['style'] });\r\n                }\r\n            } catch (_e) { console.warn('[v4.9bf obs]', _e); }\r\n            \/\/ R\u00e9appliquer aussi \u00e0 chaque scroll (backup)\r\n            window.addEventListener('scroll', _applyFrozenWidth, { passive: true });\r\n            \/\/ v4.9bo : badge debug retir\u00e9 (sujet 2 en pause)\r\n        }, 200);\r\n        \r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox \"R\u00e9server\" apr\u00e8s upload\r\n        FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n        \/\/ \u2705 Masquer le bandeau bleu format lors d'une restauration (updateReserverCheckboxState le remet sinon)\r\n        if (StateManager.get('_isAdRestoration') === 'Yes') {\r\n            $dropZone.closest('.droppable').find('.SelectionFormatTitreBlanc').hide();\r\n        }\r\n    },\r\n    \r\n    styleUploadedAd($dropZone) {\r\n        const $container = $dropZone.closest('.OrdiMobileConteneurClass');\r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \r\n        \/\/ \u2705 v1.19.5 : V\u00e9rifier si c'est une restauration via flag d\u00e9di\u00e9\r\n        const isRestoration = StateManager.get('_isAdRestoration') === 'Yes';\r\n        \r\n        $container\r\n            .find('.PositionReference, .TexteMobile, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        $container\r\n            .find('.AdUploadedTitle, .DimensionsMaximales')\r\n            .show();\r\n        \r\n        \/\/ \u2705 Bug 2 fix : montrer la croix (positionnement gere par Pb3 Ele0A)\r\n        $droppable.find('.CroixResetAnnonceContainer').show();\r\n        $droppable.find('#CroixResetAnnonce').show();\r\n        \r\n        \/\/ \u2705 v2.2 : Marquer le droppable comme occup\u00e9 pour qu'Entete le skip\r\n        $droppable.attr('data-via-ad-loaded', 'true');\r\n        \r\n        \/\/ \u2705 v2.0.11 : AdUploadedTitle ne doit pas bloquer le touch sur doc-preview-readmore (mobile)\r\n        $container.find('.AdUploadedTitle').css('pointer-events', 'none');\r\n        \r\n        \/\/ \u2705 #CroixResetAnnonce au-dessus de l'image dans son espace pub\r\n        \/\/ v4.9ds : z-index abaiss\u00e9 \u00e0 5 pour que la pastille jaune .popupAchatAnnonce\r\n        \/\/   passe au-dessus.\r\n        \/\/   Diagnostic stacking context (cf user mobile) :\r\n        \/\/     - Pastille est enfant de EnteteBackground (z-index:6, stacking ctx isol\u00e9)\r\n        \/\/       \u2192 son z-index est PLAFONN\u00c9 \u00e0 6 au niveau body, peu importe la valeur\r\n        \/\/     - Croix vit au niveau body (ses anc\u00eatres jusqu'\u00e0 OrdiMobileConteneurClass\r\n        \/\/       n'ont pas de z-index)\r\n        \/\/   Comp\u00e9tition au niveau body : header z:6 vs croix z:N\r\n        \/\/     \u2192 croix doit avoir z < 6 pour passer sous le header (donc sous la pastille)\r\n        \/\/     \u2192 croix doit avoir z > 3 pour rester au-dessus de l'image (cf z-index:3\r\n        \/\/       sur AdUploadedTitle \/ OrdiMobileConteneurClass parents)\r\n        \/\/   Valeur 5 = compromis : sous header (donc sous pastille) + sur image dans son\r\n        \/\/     droppable.\r\n        $container.find('#CroixResetAnnonce').css({\r\n            'position': 'relative',\r\n            'z-index': '5'\r\n        });\r\n        \r\n        \/\/ \u2705 Desktop : les conteneurs superpos\u00e9s bloquent la croix \u2014 les rendre transparents aux clics\r\n        if (!UIManager.isMobile()) {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css('pointer-events', 'none');\r\n            \/\/ R\u00e9activer les \u00e9l\u00e9ments interactifs\r\n            $container.find('#CroixResetAnnonce').css('pointer-events', 'auto');\r\n            $dropZone.closest('#PopUpMessageAchattest').css('pointer-events', 'auto'); \/\/ drag annonce\r\n        }\r\n        \r\n        \/\/ \u2705 v2.4.3 : Sur mobile, masquer titre et logo drag quand annonce d\u00e9pos\u00e9e dans Ele0A\r\n        if (UIManager.isMobile()) {\r\n            if ($droppable.attr('id') === 'Ele0A') {\r\n                $droppable.find('#ChoixEspacePublicitaireTexte').hide();\r\n                $droppable.find('#Ele0ADragHandle').hide();\r\n            }\r\n            \/\/ v2.9 : forcer pointer-events:auto sur la croix pour tous les espaces (Ele0A + standards)\r\n            var _crcEl = $droppable.find('.CroixResetAnnonceContainer')[0];\r\n            if (_crcEl) { _crcEl.style.setProperty('pointer-events', 'auto', 'important'); }\r\n            var _crEl = $droppable.find('#CroixResetAnnonce')[0];\r\n            if (_crEl) { _crEl.style.setProperty('pointer-events', 'auto', 'important'); }\r\n        }\r\n\r\n        \/\/ v2.6 : Desktop Ele0A - masquer icones drag\/close + elements gris parasites\r\n        if (!UIManager.isMobile()) { if ($droppable.attr('id') === 'Ele0A') {\r\n            $droppable.find('#Ele0ADragHandle').hide();\r\n            $droppable.find('#Ele0ACloseBtn').hide();\r\n            \/\/ v2.6 : Quand annonce chargee dans Ele0A : cacher titre+boutons format (zone grise parasite)\r\n            $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $droppable.find('.EspPubFormatMainContainer').hide();\r\n            $droppable.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n            \/\/ v2.9 : Ele0A desktop - masquer elements parasites (avec ou sans iframe pays)\r\n            $droppable.find('.DeplaceAnnonceText').hide();\r\n            console.log('[styleUploadedAd Ele0A] zone grise masquee | UFC bg -> transparent');\r\n            \/\/ v2.9 : iframe pays (AchatEspaceCall=Yes) -> masquer elements supplementaires\r\n            if (StateManager.get('AchatEspaceCall') === 'Yes') {\r\n                $droppable.find('.PositionEspacePublicitaireDeplacer').hide();\r\n                $droppable.find('.RefEspacePublicitaire').hide();\r\n                console.log('[styleUploadedAd Ele0A] iframe pays: PositionEspacePublicitaireDeplacer masque');\r\n            }\r\n        } }\r\n        \r\n        \/\/ \u2705 v2.6 : Reset uniquement les UFC sans annonce d\u00e9pos\u00e9e (data-via-ad-loaded absent)\r\n        \/\/ Les UFC des espaces d\u00e9j\u00e0 remplis (background:white) ne doivent pas \u00eatre remis \u00e0 transparent\r\n        jQuery(\".HTMLUploadfileConteneur\").each(function() {\r\n            var _droppableEl = jQuery(this).closest('.droppable')[0]\r\n                || jQuery(this).find('#drop_file_zone_achat').closest('.droppable')[0];\r\n            var _hasAd = false;\r\n            if (_droppableEl) {\r\n                if (_droppableEl.getAttribute('data-via-ad-loaded') === 'true') {\r\n                    _hasAd = true;\r\n                }\r\n            }\r\n            if (!_hasAd) {\r\n                jQuery(this).css({ 'border': 'none', 'background-color': 'transparent' });\r\n            }\r\n        });\r\n        \r\n        \/\/ \u2705 v2.4.12 : Retirer le liser\u00e9 envoi diff\u00e9r\u00e9 (#UploadFileConteneur) si pr\u00e9sent\r\n        \/\/ (le d\u00e9p\u00f4t d'une annonce cr\u00e9e son propre liser\u00e9 sur .HTMLUploadfileConteneur)\r\n        var $_ufcEd = $dropZone.closest('.droppable').find('#UploadFileConteneur');\r\n        if ($_ufcEd[0]) {\r\n            $_ufcEd[0].style.removeProperty('box-shadow');\r\n            $_ufcEd[0].style.removeProperty('box-sizing');\r\n            if (UIManager.isMobile()) {\r\n                $_ufcEd[0].style.removeProperty('transform');\r\n                $_ufcEd[0].style.removeProperty('transform-origin');\r\n            }\r\n        }\r\n        \r\n        $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n            \/\/ \u2705 v2.7.3 : box-shadow supprim\u00e9 \u2014 le liser\u00e9 est port\u00e9 par .via-ad-wrapper (outline)\r\n            'box-shadow': 'none',\r\n            'background-color': 'white',\r\n            'box-sizing': 'border-box',\r\n            'overflow': 'hidden'\r\n        });\r\n        \/\/ \u2705 v2.7.3 : Mobile sites pays \u2014 afficher la position en haut \u00e0 gauche dans le liser\u00e9 vert\r\n        if (UIManager.isMobile() ? window === window.top : false) {\r\n            var _rankForPos = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            var _posLabel = PreviewRenderer._getPositionLibelle(_rankForPos);\r\n            if (_posLabel) {\r\n                var $_ufcPos = $dropZone.closest('.HTMLUploadfileConteneur');\r\n                $_ufcPos.css('position', 'relative');\r\n                $_ufcPos.find('.via-position-label').remove();\r\n                $_ufcPos.append('<div class=\"via-position-label\" style=\"position:absolute;top:3px;left:3px;color:#213864;font-size:10px;font-weight:700;font-family:Roboto,sans-serif;padding:2px 7px;z-index:999;pointer-events:none;line-height:1.5;\">' + _posLabel + '<\/div>');\r\n            }\r\n        }\r\n        \r\n        \/\/ \u2705 v2.0.9 : Fix mobile \u2014 r\u00e9duire le scale pour que le liser\u00e9 vert soit visible\r\n        \/\/ \u2705 Pas de scale si annonce d\u00e9pos\u00e9e depuis la miniature kit (d\u00e9j\u00e0 aux bonnes dimensions)\r\n        \/\/ \u2705 v2.4.9 : Pas de scale si AchatEspaceCall=Yes (drop depuis miniature dans iframe)\r\n        \/\/ \u2705 data-kit-drop : marqueur DOM pos\u00e9 par kitAdCreated, r\u00e9siste \u00e0 l'async\r\n        var _fromKitDrop = $droppable[0] ? $droppable[0].getAttribute('data-kit-drop') === 'true' : false;\r\n        if (_fromKitDrop) { window._dropFromMiniature = true; } \/\/ sync le flag window\r\n        var _fromMiniature = window._dropFromMiniature || _fromKitDrop;\r\n        var _fromAchat = StateManager.get('AchatEspaceCall') === 'Yes';\r\n        if (UIManager.isMobile() ? (!_fromMiniature ? !_fromAchat : false) : false) {\r\n            \/\/ \u2705 v2.7.3 : Plus de scale (cause des liser\u00e9s parasites) \u2014 le wrapper overlay g\u00e8re l'apparence\r\n            $dropZone.closest('.UploadFileConteneur').css({\r\n                'transform': 'none',\r\n                'transform-origin': ''\r\n            });\r\n        }\r\n        \r\n        \/\/ \u2705 v2.2 : Sur restauration, Entete a d\u00e9j\u00e0 positionn\u00e9 le droppable correctement\r\n        \/\/ et est emp\u00each\u00e9 de le retoucher (data-via-ad-loaded). On ne touche pas aux marges.\r\n        \/\/ Sur premier upload, on applique les marges normalement.\r\n        \/\/ \u2705 Pas de marges si d\u00e9p\u00f4t depuis miniature kit (dimensions naturelles conserv\u00e9es)\r\n        if (!isRestoration ? !_fromMiniature : false) {\r\n            if ($container.data('orig-mb') === undefined) {\r\n                $container.data('orig-mb', parseInt($container.css('margin-bottom')) || 0);\r\n            }\r\n            if ($droppable.data('orig-mt') === undefined) {\r\n                $droppable.data('orig-mt', parseInt($droppable.css('margin-top')) || 0);\r\n            }\r\n            var _isDocPreview = $dropZone.find('.doc-preview-container').length > 0;\r\n            \/\/ \u2705 Sites pays (window.top) : marges r\u00e9duites (iframe offset non n\u00e9cessaire)\r\n            var _isSitesPays = (window === window.top);\r\n            var _marginAdd    = _isSitesPays ? 0 : (_isDocPreview ? 60 : 130);\r\n            var _marginReduce = _isSitesPays ? 0 : (_isDocPreview ? 60 : 130);\r\n            var _marginTopAdj = _isSitesPays ? 0 : 75;\r\n            $container.css({\r\n                'margin-bottom': ($container.data('orig-mb') + _marginAdd) + 'px'\r\n            });\r\n            $droppable.css({\r\n                'margin-top': ($droppable.data('orig-mt') - _marginReduce + _marginTopAdj) + 'px'\r\n            });\r\n        } else {\r\n            \/\/ \u2705 Sur restauration, ajouter 50px en dessous pour \u00e9viter que le contenu soit trop coll\u00e9\r\n            var _curMb = parseInt($container.css('margin-bottom')) || 0;\r\n            $container.css('margin-bottom', (_curMb + 50) + 'px');\r\n        }\r\n        \r\n        \/\/ \u2705 Masquer texte glisser-d\u00e9poser r\u00e9siduel + position\/r\u00e9server\r\n        $droppable.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne').hide();\r\n        \/\/ \u2705 Masquer .PositionEspacePublicitaireContainer et l'ancien .ReserverContainer\r\n        $droppable.find('.PositionEspacePublicitaireContainer').hide();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \/\/ \u2705 Masquer le checkbox statique Elementor (au cas o\u00f9 .ReserverContainer ne l'englobe pas)\r\n        $droppable.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').hide();\r\n        \/\/ \u2705 Masquer la zone bleue format (SelectionFormatTitreBlanc) et le champ lien hypertexte\r\n        $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n        $droppable.find('.EspPubLienAnnonce').hide();\r\n        \r\n        \/\/ \u2705 Supprimer tout ancien bouton dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 Sur restauration desktop : masquer bandeau bleu + fond #UploadFileConteneur\r\n        \/\/ (ChoisirEspacePublicitaireDisponibiliteConteneur et EspPubFormatMainContainer sont\r\n        \/\/  g\u00e9r\u00e9s par updateAfterSuccessfulUpload + adjustDesktopLayout du flux standard,\r\n        \/\/  mais le background #9FC5F3 du parent #UploadFileConteneur reste \u00e0 neutraliser)\r\n        if (isRestoration) {\r\n            if (!UIManager.isMobile()) {\r\n                $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n                $droppable.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v2.1.2 : Cr\u00e9er le bouton R\u00e9server m\u00eame lors d'une restauration\r\n        if (isRestoration) {\r\n            console.log('\ud83d\udd04 Restauration d\u00e9tect\u00e9e - affichage bouton R\u00e9server + DeplaceAnnonce');\r\n            $droppable.find('.DeplaceAnnonce').show();\r\n            \/\/ S'assurer que FileReceived est d\u00e9fini pour la validation\r\n            StateManager.set('FileReceived', 'Yes');\r\n            \/\/ Cocher automatiquement SEULEMENT si c'est une restauration de commande r\u00e9serv\u00e9e (pas pendingAd)\r\n            var _isPendingAdRestore = sessionStorage.getItem('_pendingAdRestoration') === 'Yes';\r\n            if (!_isPendingAdRestore) {\r\n                var _isReservedRestore = sessionStorage.getItem('_isReservedRestoration') === 'Yes';\r\n                setTimeout(function() {\r\n                    var $checkbox = $droppable.find('.reserver-dynamic-checkbox');\r\n                    if ($checkbox.length ? _isReservedRestore : false) {\r\n                        $checkbox.prop('checked', true);\r\n                        $droppable.find('.reserver-dynamic-label').text('Espace publicitaire r\u00e9serv\u00e9').css('color', 'rgb(62, 170, 19)');\r\n                        console.log('\u2705 R\u00e9server coch\u00e9 automatiquement (restauration commande r\u00e9serv\u00e9e)');\r\n                    }\r\n                    sessionStorage.removeItem('_isReservedRestoration');\r\n                    var _wasRestorationA = true; \/\/ \u00e9tait une restauration\r\n                    StateManager.set('_isAdRestoration', 'No');\r\n                    \/\/ \u2705 v2.4.7 : updateReserverCheckboxState APR\u00c8S avoir coch\u00e9 la checkbox\r\n                    \/\/ (l'appel synchrone ligne 614 intervient avant le cocher \u2192 label incorrect)\r\n                    if (typeof FormatUIManager !== 'undefined') {\r\n                        FormatUIManager.updateReserverCheckboxState($droppable);\r\n                        \/\/ \u2705 Masquer bandeau bleu format si restauration (updateReserverCheckboxState le remet)\r\n                        if (_wasRestorationA) { $droppable.find('.SelectionFormatTitreBlanc').hide(); }\r\n                    }\r\n                }, 150);\r\n            } else {\r\n                \/\/ pendingAd : ne pas cocher, ne pas envoyer au parent\r\n                console.log('\u2705 pendingAd restaur\u00e9 \u2014 R\u00e9server NON coch\u00e9');\r\n                sessionStorage.removeItem('_pendingAdRestoration');\r\n                StateManager.set('_isAdRestoration', 'No');\r\n            }\r\n        }\r\n        \r\n        \/\/ \u2705 Supprimer tout bouton R\u00e9server existant avant insertion (\u00e9vite doublons sur restauration)\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 Cr\u00e9er le bouton \"R\u00e9server\" dynamique avec id unique pour \u00e9viter conflit label\/for Elementor\r\n        const _rankId = $droppable.attr('id') || ('drop_' + Date.now());\r\n        const _cbId = 'reserver-cb-' + _rankId;\r\n        const reserverHTML = `\r\n            <div class=\"reserver-dynamic-container\">\r\n                <span class=\"reserver-dynamic-option\">\r\n                    <input type=\"checkbox\" id=\"${_cbId}\" value=\"R\u00e9server cet espace publicitaire\" \r\n                           class=\"reserver-dynamic-checkbox\"\r\n                           name=\"form_fields[ReserverEspacePublicitaire]\">\r\n                    <span class=\"reserver-dynamic-label\">R\u00e9server cet espace publicitaire<\/span>\r\n                <\/span>\r\n            <\/div>\r\n        `;\r\n        \r\n        \/\/ \u2705 Ins\u00e9rer juste avant .DeplaceAnnonceText (position stable)\r\n        const $anchor = $droppable.find('.DeplaceAnnonceText');\r\n        if ($anchor.length) {\r\n            $anchor.before(reserverHTML);\r\n            $anchor.prev('.reserver-dynamic-container').css('margin-bottom', '-40px');\r\n            if (!UIManager.isMobile()) {\r\n                $anchor.prev('.reserver-dynamic-container').find('.reserver-dynamic-label').css({'font-size': '20px', 'font-weight': '700'});\r\n            }\r\n        } else {\r\n            $droppable.after(reserverHTML);\r\n            const $btn = $droppable.next('.reserver-dynamic-container');\r\n            $btn.css('margin-top', UIManager.isMobile() ? '-10px' : '-140px');\r\n        }\r\n        \r\n        console.log('\u2705 Bouton \"R\u00e9server\" dynamique cr\u00e9\u00e9');\r\n    },\r\n    \r\n    \/\/ =========================================================================\r\n    \/\/ \u2705 v2.7.3 : Injection header \/ footer dans le liser\u00e9 vert apr\u00e8s d\u00e9p\u00f4t d'annonce\r\n    \/\/ Structure : .HTMLUploadfileConteneur \u2192 header (.via-ad-header) + body (existant) + footer (.via-ad-footer)\r\n    \/\/ =========================================================================\r\n    _buildAdOverlay($dropZone) {\r\n        const $droppable  = $dropZone.closest('.droppable');\r\n        const $ufc        = $droppable.find('.HTMLUploadfileConteneur').first();\r\n        if (!$ufc.length) return;\r\n\r\n        \/\/ \u2705 Retirer le loading overlay (fixed sur body) + reset styles\r\n        jQuery('.via-loading-overlay-fixed').remove();\r\n        $ufc.find('.via-loading-overlay').remove();\r\n        \/\/ Reset styles pos\u00e9s pendant le loading (UFC + droppable)\r\n        $ufc.css({ 'position': '', 'min-height': '', 'height': '', 'overflow': '' });\r\n        $droppable.css({ 'min-height': '', 'height': '', 'overflow': '' });\r\n        var $_omcReset = $droppable.find('.OrdiMobileConteneurClass').first();\r\n        if ($_omcReset.length) { $_omcReset.css('min-height', ''); }\r\n        $droppable.css('min-height', '');\r\n\r\n        \/\/ Idempotent : ne pas re-injecter si d\u00e9j\u00e0 pr\u00e9sent\r\n        if ($ufc.find('.via-ad-header').length) return;\r\n\r\n        const _isEle0A   = $droppable.attr('id') === 'Ele0A';\r\n        const _isMobile  = UIManager.isMobile();\r\n        const _isDesktop = !_isMobile;\r\n        \/\/ \u2705 v4.9ds Fix 14 (Pb 10) : utiliser l'emplacement du droppable courant, pas\r\n        \/\/   l'emplacement global StateManager. La variable globale refl\u00e8te le DERNIER\r\n        \/\/   emplacement cliqu\u00e9 (par ex L1A si l'user clique sur Banni\u00e8re dans Ele1A\r\n        \/\/   pendant un upload sur Ele0A) \u2014 alors que le header de l'annonce d\u00e9pos\u00e9e\r\n        \/\/   dans Ele0A doit afficher MDG48442L0A. La m\u00e9thode StateManager.buildEmplacementReference\r\n        \/\/   est exactement celle utilis\u00e9e ligne 441-442 pour construire la r\u00e9f\u00e9rence\r\n        \/\/   \u00e0 partir du rankId. Fallback sur l'emplacement global si la m\u00e9thode\r\n        \/\/   n'existe pas (s\u00e9curit\u00e9).\r\n        const _rankId    = $droppable.attr('id') || '';\r\n        var _emplacement = '';\r\n        if (_rankId ? typeof StateManager.buildEmplacementReference === 'function' : false) {\r\n            _emplacement = StateManager.buildEmplacementReference(_rankId) || '';\r\n        }\r\n        if (!_emplacement) {\r\n            _emplacement = StateManager.get('Commande_Emplacement_Page_Web') || '';\r\n        }\r\n        console.log('[Fix 14] _rankId:', _rankId, '| _emplacement r\u00e9solu:', _emplacement, '| global:', StateManager.get('Commande_Emplacement_Page_Web'));\r\n        const _posLib    = PreviewRenderer._getPositionLibelle(_rankId) || '';\r\n\r\n        \/\/ \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ v4.9ds : Ele0A header +3px (padding 4px\u21925.5px top\/bottom = +3px total)\r\n        const _hdrPadV = _isEle0A ? '5.5px' : '4px';\r\n        const $header = jQuery('<div class=\"via-ad-header\" style=\"' +\r\n            'display:flex;align-items:center;' +\r\n            'background:' + (_isEle0A ? '#D0C067' : '#9FC5F3') + ';padding:' + _hdrPadV + ' 8px;box-sizing:border-box;' +\r\n            'font-family:Roboto,Arial,sans-serif;' +\r\n            'font-size:11px;color:#ffffff;font-weight:700;flex-shrink:0;' +\r\n            'position:relative;' +\r\n        '\"><\/div>');\r\n\r\n        \/\/ \u2500\u2500 Structure header 3 colonnes : [drag] [titre centr\u00e9] [ref + X] \u2500\u2500\u2500\u2500\u2500\r\n        const $colLeft   = jQuery('<div style=\"flex:0 0 auto;display:flex;align-items:center;z-index:1;\"><\/div>');\r\n        const $colCenter = jQuery('<div style=\"position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;pointer-events:none;\"><\/div>');\r\n        const $colRight  = jQuery('<div style=\"flex:0 0 auto;display:flex;align-items:center;gap:8px;margin-left:auto;z-index:1;\"><\/div>');\r\n\r\n        \/\/ Tailles : plein \u00e9cran desktop \/ desktop mode mobile \/ mobile\r\n        var _isFullDesk = !_isMobile ? window.outerWidth >= 1200 : false;\r\n        var _isDeskMob  = !_isMobile ? window.outerWidth < 1200 : false; \/\/ desktop r\u00e9tr\u00e9ci\r\n        var _fsPos   = _isFullDesk ? '13px' : (_isDeskMob ? '6px' : '8px');\r\n        var _fsTitle = _isFullDesk ? '16px' : (_isDeskMob ? (_isEle0A ? '6px' : '8px') : (_isEle0A ? '8px' : '10px'));\r\n        var _fsRef   = _isFullDesk ? '13px' : (_isDeskMob ? '6px' : (_isEle0A ? '6px' : '8px'));\r\n\r\n        if (_isEle0A) {\r\n            \/\/ \u2705 Croix drag 4 fl\u00e8ches SVG \u2014 v4.9ds taille r\u00e9duite (17\u219213) pour aligner sur header standard\r\n            $colLeft.append(jQuery('<span class=\"via-ah-drag\" style=\"cursor:move;opacity:0.8;display:flex;align-items:center;\">' +\r\n                '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"#ffffff\">' +\r\n                '<path d=\"M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z\"\/>' +\r\n                '<\/svg><\/span>'));\r\n            \/\/ \u2705 via-ah-pos masqu\u00e9 pour Ele0A (position dans le titre suffit)\r\n            \/\/ \u2705 Bug 11 : sur mobile, limiter la largeur pour \u00e9viter chevauchement titre\/r\u00e9f\r\n            \/\/ pas de troncature \u2014 la r\u00e9duction de taille suffit\r\n            $colCenter.append(jQuery('<span class=\"via-ah-title\" style=\"font-size:' + _fsTitle + ';color:#ffffff;\">Espace publicitaire Pop-up' + (_emplacement ? ' <span style=\"font-weight:700;font-size:' + (parseFloat(_fsRef) + 1.5) + 'px;\">' + _emplacement + '<\/span>' : '') + '<\/span>'));\r\n        } else {\r\n            if (_posLib) {\r\n                $colLeft.append(jQuery('<span class=\"via-ah-pos\" style=\"font-size:' + _fsPos + ';color:#ffffff;font-weight:700;\">' + _posLib + '<\/span>'));\r\n            }\r\n            $colCenter.append(jQuery('<span class=\"via-ah-title\" style=\"font-size:' + _fsTitle + ';color:#ffffff;\">Espace publicitaire' + (_emplacement ? ' <span style=\"font-weight:700;font-size:' + (parseFloat(_fsRef) + 1.5) + 'px;\">' + _emplacement + '<\/span>' : '') + '<\/span>'));\r\n        }\r\n\r\n        \/\/ Ref incluse dans le titre ci-dessus\r\n\r\n        \/\/ v4.9ds : croix de fermeture positionn\u00e9e en absolute dans le coin haut-droite\r\n        \/\/   de .HTMLUploadfileConteneur (la zone d'aper\u00e7u blanche), \u00e0 2px du liser\u00e9 vert.\r\n        \/\/   Carr\u00e9 bleu fonc\u00e9 (#5573a8) avec croix blanche centr\u00e9e pour qu'elle se voie\r\n        \/\/   sur les images d'annonce d\u00e9pos\u00e9es (qui peuvent \u00eatre de toute couleur).\r\n        const $closeBtn = jQuery('<span class=\"via-ah-close\" style=\"' +\r\n            'cursor:pointer;font-size:13px;font-weight:900;color:#ffffff;line-height:1;' +\r\n            'pointer-events:auto;' +\r\n            'position:absolute;top:2px;right:4px;' +\r\n            'width:18px;height:18px;' +\r\n            'background:#5573a8;' +\r\n            'display:flex;align-items:center;justify-content:center;' +\r\n            'z-index:5;' +\r\n            '\" title=\"Supprimer l\\'annonce\">\u2715<\/span>');\r\n        $closeBtn.on('click', function(e) {\r\n            e.preventDefault();\r\n            e.stopPropagation();\r\n            \/\/ \u2705 v4.9ds Fix 28 : idem .via-erase-btn (ligne ~7750) \u2014 #CroixResetAnnonce\r\n            \/\/   n'existe plus dans le DOM. Appel direct \u00e0 AdResetHandler.handle.\r\n            if (typeof AdResetHandler !== 'undefined' ? typeof AdResetHandler.handle === 'function' : false) {\r\n                AdResetHandler.handle(e);\r\n                return;\r\n            }\r\n            \/\/ Fallback historique (FonctionCroixResetAnnonce \/ trigger sur #CroixResetAnnonce)\r\n            if (typeof window.FonctionCroixResetAnnonce === 'function') {\r\n                var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n                if (_croixEl) { window.FonctionCroixResetAnnonce(_croixEl); return; }\r\n            }\r\n            var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n            if (_croixEl) { jQuery(_croixEl).trigger('click'); }\r\n        });\r\n        \/\/ $colRight reste vide \u2014 la croix est pos\u00e9e en absolute sur $wrapper plus bas\r\n        \/\/   (juste apr\u00e8s son assemblage, pour rester ancr\u00e9e au liser\u00e9 vert).\r\n\r\n        $header.append($colLeft).append($colCenter).append($colRight);\r\n\r\n        \/\/ \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        const $footer = jQuery('<div class=\"via-ad-footer\" style=\"' +\r\n            'background:' + (_isEle0A ? '#D0C067' : '#9FC5F3') + ';padding:4px 8px;box-sizing:border-box;' +  \/* v4.9ds : jaune Ele0A, bleu Ele1A+ *\/\r\n            'font-family:Roboto,Arial,sans-serif;' +\r\n            'font-size:11px;color:#ffffff;flex-shrink:0;' +\r\n        '\"><\/div>');\r\n\r\n        \/\/ v4.9ds : footer en flex pour aligner \u0153il + R\u00e9server\r\n        \/\/ v4.9ds (eye-pos) : position:relative pour ancrer l'\u0153il en absolute (centr\u00e9 dans la moiti\u00e9 gauche),\r\n        \/\/   le bouton R\u00e9server reste seul dans le flux flex et donc centr\u00e9 dans le footer\r\n        $footer.css({\r\n            'display': 'flex',\r\n            'align-items': 'center',\r\n            'justify-content': 'center',\r\n            'gap': '10px',\r\n            'position': 'relative'\r\n        });\r\n\r\n        \/\/ v4.9ds : si Communiqu\u00e9\/Interview (doc-preview pr\u00e9sent), masquer title + readmore\r\n        \/\/   et ajouter un bouton \u0153il dans le footer (\u00e0 gauche de R\u00e9server) qui ouvre la\r\n        \/\/   m\u00eame popup que le readmore (PDFHandler.showInlineDocPopup)\r\n        const _hasDocPreview = $droppable.find('.doc-preview-container').length > 0;\r\n        if (_hasDocPreview) {\r\n            $droppable.find('.doc-preview-title').hide();\r\n            $droppable.find('.doc-preview-readmore').hide();\r\n            \/\/ \u0152il SVG (Material icon \"visibility\") \u2014 ouvre la popup au clic\r\n            \/\/ v4.9ds (eye-size+pos) : taille +60% (18\u219229 desk, 14\u219222 mob) ;\r\n            \/\/   position:absolute \u00e0 left:12% pour \u00eatre centr\u00e9 entre le bord gauche du footer\r\n            \/\/   et le bouton R\u00e9server (qui, seul dans le flux flex, est centr\u00e9 au milieu du footer).\r\n            \/\/   z-index:2 pour passer au-dessus de tout. pointer-events:auto pour rester cliquable.\r\n            \/\/ v4.9ds (eye-stroke) : liser\u00e9 bleu Material (#1976D2) sur l'amande ext\u00e9rieure ET sur\r\n            \/\/   le rond int\u00e9rieur (iris) pour qu'il ressorte sur les 2 couleurs de footer.\r\n            \/\/   3 paths : path 1 = \u0153il entier rempli blanc (inchang\u00e9) ; path 2 = contour amande\r\n            \/\/   ext\u00e9rieure ; path 3 = contour iris (rond int\u00e9rieur). Tous deux fill:none + stroke,\r\n            \/\/   m\u00eame stroke-width pour coh\u00e9rence visuelle. stroke-linejoin:round pour adoucir.\r\n            const _eyeColor = '#ffffff';\r\n            const $eyeBtn = jQuery('<span class=\"via-af-eye\" title=\"Voir la publication\" style=\"cursor:pointer;display:inline-flex;align-items:center;flex:0 0 auto;position:absolute;left:12%;top:50%;transform:translate(-50%,-50%);z-index:2;pointer-events:auto;\">' +\r\n                '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"' + (_isFullDesk ? '29' : '22') + '\" height=\"' + (_isFullDesk ? '29' : '22') + '\" viewBox=\"0 0 24 24\">' +\r\n                '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\" fill=\"' + _eyeColor + '\"\/>' +\r\n                '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5z\" fill=\"none\" stroke=\"#1976D2\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>' +\r\n                '<path d=\"M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z\" fill=\"none\" stroke=\"#1976D2\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>' +\r\n                '<\/svg><\/span>');\r\n            \/\/ \u2500\u2500 \u2193 click handler (inchang\u00e9) \u2193 \u2500\u2500\r\n            $eyeBtn.on('click', function(e) {\r\n                e.preventDefault();\r\n                e.stopPropagation();\r\n                \/\/ Trouver la popup \u00e0 ouvrir : m\u00eame logique que le clic sur .doc-preview-readmore\r\n                \/\/ Priorit\u00e9 1 : kitPdfImageDataURL (cr\u00e9\u00e9 via Kit miniature)\r\n                var _kitPdf = $droppable.data('kitPdfImageDataURL');\r\n                var _kitFormat = $droppable.data('kitFormatSelect') || '';\r\n                var _isInterviewE = (_kitFormat || '').toLowerCase().indexOf('interview') !== -1;\r\n                if (!_kitFormat) {\r\n                    \/\/ Fallback : chercher le titre dans le DOM\r\n                    var _titleEl = $droppable.find('.doc-preview-title')[0];\r\n                    if (_titleEl) { _isInterviewE = (_titleEl.textContent || '').toLowerCase().indexOf('interview') !== -1; }\r\n                }\r\n                var _popupTitle = _isInterviewE ? 'Interview' : 'Communiqu\u00e9';\r\n                if (_kitPdf) {\r\n                    PDFHandler.showInlineDocPopup($dropZone, { formatTitle: _popupTitle, pdfDataURL: _kitPdf });\r\n                    return;\r\n                }\r\n                \/\/ Priorit\u00e9 2 : PDF upload\u00e9 (PDFHandler.pdfDataForViewer)\r\n                if (PDFHandler.pdfDataForViewer ? PDFHandler.pdfDataForViewer.byteLength > 0 : false) {\r\n                    PDFHandler.showInlineDocPopup($dropZone, { formatTitle: _popupTitle, pdfArrayBuffer: PDFHandler.pdfDataForViewer });\r\n                    return;\r\n                }\r\n                \/\/ Priorit\u00e9 3 : d\u00e9clencher le clic sur readmore (m\u00eame si masqu\u00e9) \u2014 fallback Word\/htmlContent\r\n                var _readmore = $droppable.find('.doc-preview-readmore')[0];\r\n                if (_readmore) {\r\n                    _readmore.style.display = 'block';\r\n                    jQuery(_readmore).trigger('click');\r\n                    _readmore.style.display = 'none';\r\n                }\r\n            });\r\n            $footer.append($eyeBtn);\r\n        }\r\n\r\n        \/\/ \u2500\u2500 D\u00e9placer le reserver-dynamic-container dans le footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ Chercher dans le droppable ET apr\u00e8s le droppable (les 2 positions possibles)\r\n        const $resBtnIn   = $droppable.find('.reserver-dynamic-container').first();\r\n        const $resBtnNext = $droppable.next('.reserver-dynamic-container');\r\n        const $resBtn     = $resBtnIn.length ? $resBtnIn : $resBtnNext;\r\n        if ($resBtn.length) {\r\n            \/\/ \u2705 Reset complet de TOUS les styles inline de positionnement\r\n            $resBtn[0].style.cssText = '';\r\n            $resBtn.css({\r\n                'margin-top': '0',\r\n                'margin-bottom': '0',\r\n                'position': '',\r\n                'top': '',\r\n                'left': '',\r\n                'bottom': ''\r\n            });\r\n            \/\/ Style minimal inline pour le label\r\n            $resBtn.find('.reserver-dynamic-label').css({\r\n                'font-size': _isFullDesk ? '16px' : '10px',\r\n                'font-weight': '700'\r\n            });\r\n            $footer.append($resBtn);\r\n        }\r\n\r\n        \/\/ v4.9ds : phrase \"Si vous souhaitez un autre emplacement...\" retir\u00e9e des espaces standards\r\n        \/\/   (uniformisation visuelle, espace gagn\u00e9 dans le footer)\r\n\r\n        \/\/ \u2500\u2500 Cr\u00e9er un wrapper global qui portera le liser\u00e9 vert \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ .HTMLUploadfileConteneur garde son box-shadow inset \u2192 overflow:visible\r\n        \/\/ pour que header et footer (qui d\u00e9bordent en haut\/bas) soient dans le liser\u00e9\r\n        \/\/ Solution : envelopper $ufc + $header + $footer dans un wrapper flex\r\n\r\n        \/\/ \u2705 Retirer tous les liser\u00e9s verts r\u00e9siduels \u2014 le wrapper porta le seul outline\r\n        $ufc.css({\r\n            'overflow': 'visible',\r\n            'height': 'auto',\r\n            'min-height': '',\r\n            \/\/ \u2705 Bug 12 : inset gauche\/droite, ext\u00e9rieur haut\/bas\r\n            'box-shadow': 'inset 2px 0 0 #00FF19, inset -2px 0 0 #00FF19, 0 -2px 0 #00FF19, 0 2px 0 #00FF19',\r\n            'border': 'none',\r\n            'background-color': '',\r\n            'display': 'block',\r\n            'margin': '0',\r\n            'padding': '0',\r\n            \/\/ v4.9ds : ancrage absolute pour la croix .via-ah-close\r\n            'position': 'relative'\r\n        });\r\n        \/\/ Nettoyer aussi UploadFileConteneur, UploadFileConteneur, ToBeHidden et dropzone\r\n        $droppable.find('#UploadFileConteneur, .UploadFileConteneur').css({\r\n            'box-shadow': 'none', 'outline': 'none', 'border': 'none'\r\n        });\r\n        $dropZone.css({ 'box-shadow': 'none', 'outline': 'none', 'border': 'none' });\r\n        \/\/ Nettoyer aussi le parent .ToBeHidden qui peut avoir un transform + outline\r\n        $droppable.find('.OrdiMobileConteneurClass').css({ 'box-shadow': 'none', 'outline': 'none', 'border': 'none' });\r\n\r\n\r\n        \/\/ \u2705 Wrapper global lis\u00e9r\u00e9 vert (header + ufc + footer)\r\n        \/\/   v4.9ds : position:relative pour ancrer la croix .via-ah-close dans le coin haut-droite\r\n        const $wrapper = jQuery('<div class=\"via-ad-wrapper\" style=\"' +\r\n            'display:flex;flex-direction:column;box-sizing:border-box;' +\r\n            'background:#fff;position:relative;' +\r\n        '\">' + '<\/div>');\r\n\r\n        $ufc.before($wrapper);\r\n        $wrapper.append($header);\r\n        $wrapper.append($ufc);\r\n        $wrapper.append($footer);\r\n        \/\/ v4.9ds : croix ancr\u00e9e \u00e0 $ufc (zone d'aper\u00e7u blanche), pas au header bleu\/jaune\r\n        $ufc.append($closeBtn);\r\n\r\n        \/\/ max-height + height via setProperty - re-applique apres Elementor (setTimeout)\r\n        \/\/ Desktop (userAgent) \u2192 hauteur fixe 170px (full desktop + desktop reduit)\r\n        \/\/ Mobile device r\u00e9el \u2192 height:auto, plafonn\u00e9 \u00e0 200px\r\n        \/\/ v4.9cy : Ele0A desktop \u2192 260px (comme les espaces agrandis \u22651200px) car les styles\r\n        \/\/          inline pos\u00e9s ici gagnent sur les r\u00e8gles CSS @media.\r\n        (function(_el) {\r\n            var _isDeskUA = UIManager.isDesktop();\r\n            var _hVal1 = 'auto';\r\n            var _maxH1;\r\n            \/\/ v4.9ds : Ele0A align\u00e9 sur standards (280) + 5px suppl\u00e9mentaires sur Ele0A\r\n            if (_isEle0A ? _isDeskUA : false) {\r\n                _maxH1 = '285px';\r\n            } else if (_isDeskUA) {\r\n                _maxH1 = '280px';\r\n            } else {\r\n                _maxH1 = '200px';\r\n            }\r\n            function _applyH() {\r\n                _el.style.setProperty('max-height', _maxH1, 'important');\r\n                _el.style.setProperty('height', _hVal1, 'important');\r\n                _el.style.setProperty('overflow', 'hidden', 'important');\r\n            }\r\n            _applyH();\r\n            setTimeout(_applyH, 100);\r\n            setTimeout(_applyH, 500);\r\n        })($ufc[0]);\r\n\r\n        \/\/ v4.9ds : min-height sur #drop_file_zone_achat = hauteur espace standard - header - footer\r\n        \/\/          \u00c0 chaque dimension correspond la m\u00eame logique que _applyH ci-dessus.\r\n        \/\/          La fonction est stock\u00e9e sur le droppable pour pouvoir \u00eatre rappel\u00e9e au resize.\r\n        (function(_dz, _hdr, _ftr, _drop) {\r\n            if (!_dz) return;\r\n            function _applyDzMinH(_phase) {\r\n                var _isDeskUA = UIManager.isDesktop();\r\n                var _ufcH;\r\n                \/\/ v4.9ds : Ele0A align\u00e9 sur standards (280) + 5px suppl\u00e9mentaires sur Ele0A\r\n                if (_isEle0A ? _isDeskUA : false) { _ufcH = 285; }\r\n                else if (_isDeskUA)               { _ufcH = 280; }\r\n                else                              { _ufcH = 200; }\r\n                var _hdrH = _hdr && _hdr.offsetHeight ? _hdr.offsetHeight : 0;\r\n                var _ftrH = _ftr && _ftr.offsetHeight ? _ftr.offsetHeight : 0;\r\n                var _dzMinH = Math.max(0, _ufcH - _hdrH - _ftrH);\r\n                _dz.style.setProperty('min-height', _dzMinH + 'px', 'important');\r\n                _dz.style.setProperty('height', _dzMinH + 'px', 'important');\r\n                _dz.style.setProperty('flex', '1 1 auto', 'important');\r\n                \/\/ v4.9ds DIAG : mesurer toutes les dimensions cl\u00e9s pour comprendre l'\u00e9cart Ele0A vs standards\r\n                var _ufc = _dz.closest('.HTMLUploadfileConteneur');\r\n                var _wrap = _dz.closest('.via-ad-wrapper');\r\n                var _img = _dz.querySelector('img, video, .doc-preview-container');\r\n                var _ufcCS = _ufc ? window.getComputedStyle(_ufc) : null;\r\n                var _wrapCS = _wrap ? window.getComputedStyle(_wrap) : null;\r\n                var _dzCS = window.getComputedStyle(_dz);\r\n                \/\/ v4.9ds DIAG : aussi rect.height (r\u00e9el \u00e0 l'\u00e9cran apr\u00e8s zoom\/scale) + zoom\/transform sur anc\u00eatres\r\n                var _wrapRect = _wrap ? _wrap.getBoundingClientRect() : null;\r\n                var _ufcRect = _ufc ? _ufc.getBoundingClientRect() : null;\r\n                var _dzRect = _dz.getBoundingClientRect();\r\n                \/\/ Chercher zoom\/transform sur les 5 anc\u00eatres jusqu'au droppable\r\n                var _zoomChain = [];\r\n                var _curEl = _wrap;\r\n                for (var _zi = 0; _zi < 6; _zi++) {\r\n                    if (!_curEl) { break; }\r\n                    var _csEl = window.getComputedStyle(_curEl);\r\n                    var _zm = _csEl.zoom;\r\n                    var _tr = _csEl.transform;\r\n                    var _hasTr = _tr ? (_tr !== 'none') : false;\r\n                    var _hasZoom = (_zm !== '1');\r\n                    if (_hasZoom ? true : _hasTr) {\r\n                        _zoomChain.push((_curEl.tagName + (_curEl.id ? '#' + _curEl.id : '.' + (_curEl.className || '').split(' ')[0])) + ' zoom:' + _zm + ' tr:' + _tr);\r\n                    }\r\n                    _curEl = _curEl.parentElement;\r\n                }\r\n                \/\/ v4.9ds DIAG ULTIME : cha\u00eene compl\u00e8te des anc\u00eatres avec rect.h pour identifier l'\u00e9cart\r\n                var _ancChain = [];\r\n                var _ancEl = _wrap;\r\n                for (var _ai = 0; _ai < 12; _ai++) {\r\n                    if (!_ancEl) { break; }\r\n                    var _aRect = _ancEl.getBoundingClientRect();\r\n                    var _aCS = window.getComputedStyle(_ancEl);\r\n                    var _tag = _ancEl.tagName;\r\n                    var _id = _ancEl.id ? ('#' + _ancEl.id) : '';\r\n                    var _cls = (_ancEl.className || '').split(' ')[0] || '';\r\n                    if (_cls) { _cls = '.' + _cls; }\r\n                    _ancChain.push(_tag + _id + _cls + ' [offH:' + _ancEl.offsetHeight + ' rectH:' + Math.round(_aRect.height) + ' zoom:' + _aCS.zoom + ' tr:' + (_aCS.transform === 'none' ? 'none' : _aCS.transform.substring(0, 30)) + ']');\r\n                    _ancEl = _ancEl.parentElement;\r\n                }\r\n                console.log('[DIAG H ' + _phase + '] '\r\n                    + (_isEle0A ? 'ELE0A' : 'STANDARD ' + (_dz.closest('.droppable') ? _dz.closest('.droppable').id : '?'))\r\n                    + ' | _ufcH:' + _ufcH + ' hdr:' + _hdrH + ' ftr:' + _ftrH + ' dzMinH:' + _dzMinH\r\n                    + '\\n  WRAPPER offsetH:' + (_wrap ? _wrap.offsetHeight : 'null') + ' rect.h:' + (_wrapRect ? Math.round(_wrapRect.height) : 'null')\r\n                    + '\\n  UFC offsetH:' + (_ufc ? _ufc.offsetHeight : 'null') + ' rect.h:' + (_ufcRect ? Math.round(_ufcRect.height) : 'null')\r\n                    + '\\n  DZ offsetH:' + _dz.offsetHeight + ' rect.h:' + Math.round(_dzRect.height)\r\n                    + '\\n  IMG offsetH:' + (_img ? _img.offsetHeight : 'null')\r\n                    + '\\n  CHAIN ancestors:\\n    ' + _ancChain.join('\\n    ')\r\n                );\r\n            }\r\n            _applyDzMinH('T+0');\r\n            setTimeout(function() { _applyDzMinH('T+100'); }, 100);\r\n            setTimeout(function() { _applyDzMinH('T+500'); }, 500);\r\n            \/\/ v4.9ds : exposer pour appel depuis handler resize\r\n            if (_drop) { _drop._applyDzMinH = _applyDzMinH; }\r\n        })($dropZone[0], $header[0], $footer[0], $droppable[0]);\r\n\r\n        \/\/ Sites pays : remonter le wrapper\r\n        if (!_isEle0A ? window === window.top : false) {\r\n            \/\/ \ud83d\udd0d DIAG : loguer la d\u00e9cision de branche + contexte\r\n            console.log('[DIAG _buildAdOverlay] branche sites pays', {\r\n                _isDesktop: _isDesktop,\r\n                isMobile: UIManager.isMobile(),\r\n                outerWidth: window.outerWidth,\r\n                innerWidth: window.innerWidth,\r\n                htmlClass: document.documentElement.className,\r\n                hasRegieClass: document.documentElement.classList.contains('via-regie-iframe'),\r\n                droppableId: $droppable.attr('id')\r\n            });\r\n            if (_isDesktop) {\r\n                \/\/ Kit creation : adjustDesktopLayout modifie deja le droppable margin\r\n                \/\/ -> wrapper margin-top reduit pour eviter le chevauchement\r\n                var _isKitDrop = window._lastDropWasKit === true;\r\n                \/\/ \u2705 DVM (desktop version mobile, inner<1000) : text-editor Elementor pose 297px\r\n                \/\/    \u2192 wrapper trop bas. -150px.\r\n                \/\/    Plein \u00e9cran desktop : -30px (l\u00e9ger d\u00e9calage, coh\u00e9rent avec post-resize).\r\n                var _isDVM = (window.innerWidth < 1000);\r\n                var _mt = _isKitDrop ? '30px' : (_isDVM ? '-150px' : '0px');\r\n                if ($wrapper[0]) {\r\n                    $wrapper[0].style.setProperty('margin-top', _mt, 'important');\r\n                    $wrapper[0].style.setProperty('margin-bottom', _isKitDrop ? '20px' : '15px', 'important');\r\n                }\r\n                \/\/ \u2705 Article (body.single) ou page secteur (body.page) au d\u00e9p\u00f4t plein \u00e9cran :\r\n                \/\/    80px en-dessous du droppable pour \u00e9viter le chevauchement avec le contenu.\r\n                if (!_isDVM) { if (!_isKitDrop) {\r\n                    var _isArticleOrSecteurDep = document.body.classList.contains('single') || document.body.classList.contains('page');\r\n                    if (_isArticleOrSecteurDep) {\r\n                        if ($droppable[0]) {\r\n                            $droppable[0].style.setProperty('margin-bottom', '80px', 'important');\r\n                            console.log('[DIAG _buildAdOverlay] article\/secteur plein \u00e9cran \u2192 droppable.margin-bottom: 80px');\r\n                        }\r\n                    }\r\n                } }\r\n                $wrapper.attr('data-deposited-mode', 'desktop');\r\n                console.log('[DIAG _buildAdOverlay] branche DESKTOP \u2192 margin-top:', _mt, '(DVM=' + _isDVM + ')');\r\n                \/\/ \u2705 D\u00e9clencher un resize simul\u00e9 300ms apr\u00e8s le d\u00e9p\u00f4t pour que le handler resize\r\n                \/\/    applique ses positions (algo inter-espaces, article\/secteur, DVM cleanup, etc.).\r\n                \/\/    Actif aussi en DVM (cas d\u00e9p\u00f4t en version mobile qui chevauche contenu au-dessus).\r\n                \/\/ v4.9ds : retir\u00e9 la condition !_isKitDrop \u2014 les Communiqu\u00e9\/Interview cr\u00e9\u00e9s\r\n                \/\/   depuis le kit ont aussi besoin du resize pour recalculer les espaces en dessous\r\n                setTimeout(function() {\r\n                    try {\r\n                        window.dispatchEvent(new Event('resize'));\r\n                        console.log('[DIAG _buildAdOverlay] resize simul\u00e9 post-d\u00e9p\u00f4t d\u00e9clench\u00e9 (DVM=' + _isDVM + ', kitDrop=' + _isKitDrop + ')');\r\n                    } catch (e) {\r\n                        console.warn('[DIAG _buildAdOverlay] resize simul\u00e9 a throw:', e);\r\n                    }\r\n                }, 300);\r\n                \/\/ \ud83d\udd0d DIAG d\u00e9taill\u00e9 : mesurer la position r\u00e9elle apr\u00e8s application\r\n                setTimeout(function() {\r\n                    var _w = $wrapper[0];\r\n                    var _d = $droppable[0];\r\n                    if (!_w) { return; }\r\n                    var _wRect = _w.getBoundingClientRect();\r\n                    var _dRect = _d ? _d.getBoundingClientRect() : null;\r\n                    var _wCS = getComputedStyle(_w);\r\n                    var _parent = _w.parentElement;\r\n                    var _parentRect = _parent ? _parent.getBoundingClientRect() : null;\r\n                    var _parentCS = _parent ? getComputedStyle(_parent) : null;\r\n                    \/\/ Essayer de trouver l'\u00e9l\u00e9ment juste au-dessus dans la page\r\n                    var _prevEl = null;\r\n                    if (_d) {\r\n                        var _sib = _d.previousElementSibling;\r\n                        while (_sib) {\r\n                            var _sr = _sib.getBoundingClientRect();\r\n                            if (_sr.height > 10) { _prevEl = _sib; break; }\r\n                            _sib = _sib.previousElementSibling;\r\n                        }\r\n                    }\r\n                    var _prevRect = _prevEl ? _prevEl.getBoundingClientRect() : null;\r\n                    console.log('[DIAG DEPOT DVM]', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        wrapperMT_computed: _wCS.marginTop,\r\n                        wrapperRect: { top: Math.round(_wRect.top), bottom: Math.round(_wRect.bottom), height: Math.round(_wRect.height) },\r\n                        droppableId: _d ? _d.id : '?',\r\n                        droppableRect: _dRect ? { top: Math.round(_dRect.top), bottom: Math.round(_dRect.bottom), height: Math.round(_dRect.height) } : null,\r\n                        parentTag: _parent ? (_parent.tagName + '.' + (_parent.className || '').split(' ').slice(0, 3).join('.')) : '?',\r\n                        parentDisplay: _parentCS ? _parentCS.display : '?',\r\n                        parentRect: _parentRect ? { top: Math.round(_parentRect.top), bottom: Math.round(_parentRect.bottom), height: Math.round(_parentRect.height) } : null,\r\n                        prevSibling: _prevEl ? (_prevEl.tagName + '.' + (_prevEl.className || '').split(' ').slice(0, 2).join('.')) : '?',\r\n                        prevSiblingRect: _prevRect ? { top: Math.round(_prevRect.top), bottom: Math.round(_prevRect.bottom) } : null,\r\n                        gapAboveDroppable: (_prevRect ? (_dRect ? Math.round(_dRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        gapAboveWrapper: (_prevRect ? (_wRect ? Math.round(_wRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        inner: window.innerWidth,\r\n                        outer: window.outerWidth\r\n                    });\r\n                }, 150);\r\n            } else {\r\n                \/\/ Sites pays d\u00e9p\u00f4t mobile : -140px (valeur historique qui marche)\r\n                if ($wrapper[0]) {\r\n                    $wrapper[0].style.setProperty('margin-top', '-140px', 'important');\r\n                    $wrapper[0].style.setProperty('margin-bottom', '0px', 'important');\r\n                }\r\n                $wrapper.attr('data-deposited-mode', 'mobile');\r\n                console.log('[DIAG _buildAdOverlay] branche MOBILE \u2192 margin-top: -140px !important');\r\n                \/\/ \u2705 Resize simul\u00e9 300ms apr\u00e8s le d\u00e9p\u00f4t pour que le handler resize applique\r\n                \/\/    ses positions (cleanup DVM, wrapper.mt\/mb=25px, etc.).\r\n                setTimeout(function() {\r\n                    try {\r\n                        window.dispatchEvent(new Event('resize'));\r\n                        console.log('[DIAG _buildAdOverlay] resize simul\u00e9 post-d\u00e9p\u00f4t MOBILE d\u00e9clench\u00e9');\r\n                    } catch (e) {\r\n                        console.warn('[DIAG _buildAdOverlay] resize simul\u00e9 MOBILE a throw:', e);\r\n                    }\r\n                }, 300);\r\n                \/\/ \ud83d\udd0d DIAG d\u00e9taill\u00e9 : mesurer la position r\u00e9elle apr\u00e8s application\r\n                setTimeout(function() {\r\n                    var _w = $wrapper[0];\r\n                    var _d = $droppable[0];\r\n                    if (!_w) { return; }\r\n                    var _wRect = _w.getBoundingClientRect();\r\n                    var _dRect = _d ? _d.getBoundingClientRect() : null;\r\n                    var _wCS = getComputedStyle(_w);\r\n                    var _parent = _w.parentElement;\r\n                    var _parentRect = _parent ? _parent.getBoundingClientRect() : null;\r\n                    var _parentCS = _parent ? getComputedStyle(_parent) : null;\r\n                    var _prevEl = null;\r\n                    if (_d) {\r\n                        var _sib = _d.previousElementSibling;\r\n                        while (_sib) {\r\n                            var _sr = _sib.getBoundingClientRect();\r\n                            if (_sr.height > 10) { _prevEl = _sib; break; }\r\n                            _sib = _sib.previousElementSibling;\r\n                        }\r\n                    }\r\n                    var _prevRect = _prevEl ? _prevEl.getBoundingClientRect() : null;\r\n                    console.log('[DIAG DEPOT MOBILE]', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        wrapperMT_computed: _wCS.marginTop,\r\n                        wrapperRect: { top: Math.round(_wRect.top), bottom: Math.round(_wRect.bottom), height: Math.round(_wRect.height) },\r\n                        droppableId: _d ? _d.id : '?',\r\n                        droppableRect: _dRect ? { top: Math.round(_dRect.top), bottom: Math.round(_dRect.bottom), height: Math.round(_dRect.height) } : null,\r\n                        parentTag: _parent ? (_parent.tagName + '.' + (_parent.className || '').split(' ').slice(0, 3).join('.')) : '?',\r\n                        parentDisplay: _parentCS ? _parentCS.display : '?',\r\n                        parentRect: _parentRect ? { top: Math.round(_parentRect.top), bottom: Math.round(_parentRect.bottom), height: Math.round(_parentRect.height) } : null,\r\n                        prevSibling: _prevEl ? (_prevEl.tagName + '.' + (_prevEl.className || '').split(' ').slice(0, 2).join('.')) : '?',\r\n                        prevSiblingRect: _prevRect ? { top: Math.round(_prevRect.top), bottom: Math.round(_prevRect.bottom) } : null,\r\n                        gapAboveDroppable: (_prevRect ? (_dRect ? Math.round(_dRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        gapAboveWrapper: (_prevRect ? (_wRect ? Math.round(_wRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        inner: window.innerWidth,\r\n                        outer: window.outerWidth\r\n                    });\r\n                }, 150);\r\n            }\r\n            \/\/ \ud83d\udd0d DIAG : v\u00e9rifier l'\u00e9tat effectif apr\u00e8s application\r\n            setTimeout(function() {\r\n                var _w = $wrapper[0];\r\n                if (_w) {\r\n                    var _cs = getComputedStyle(_w);\r\n                    console.log('[DIAG _buildAdOverlay] \u00e9tat wrapper apr\u00e8s 100ms', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        computedMarginTop: _cs.marginTop,\r\n                        computedMarginBottom: _cs.marginBottom,\r\n                        boundingRect: { top: _w.getBoundingClientRect().top, height: _w.getBoundingClientRect().height }\r\n                    });\r\n                }\r\n            }, 100);\r\n        }\r\n\r\n        \/\/ \u2705 Desktop : ajouter margin-bottom pour que le footer ne chevauche pas le contenu\r\n        if (_isDesktop) {\r\n            setTimeout(function() {\r\n                var _footerH = $footer[0] ? $footer[0].getBoundingClientRect().height : 0;\r\n                var _headerH = $header[0] ? $header[0].getBoundingClientRect().height : 0;\r\n                if (_footerH > 0) {\r\n                    $wrapper.css('margin-bottom', (_footerH + _headerH) + 'px');\r\n                }\r\n            }, 50);\r\n        }\r\n\r\n        \/\/ \u2705 body = reset margin du dropzone\r\n        $dropZone.css({ 'margin-top': '0', 'margin-bottom': '0' });\r\n        $dropZone.closest('.UploadFileConteneur').css({ 'margin-top': '0', 'margin-bottom': '0' });\r\n\r\n        \/\/ \u2705 v2.7.3 : Mobile \u2014 largeur = viewport moins marges, hauteur auto\r\n        if (!_isFullDesk) { \/\/ mobile + desktop mode mobile\r\n            \/\/ \u2705 R\u00e9f\u00e9rence CSS : offsetWidth du premier espace standard sans annonce\r\n            \/\/ offsetWidth = largeur CSS pr\u00e9-transform \u2192 m\u00eame base pour tous les espaces\r\n            var _refCssW = 0;\r\n            jQuery('.droppable').not('[id=\"Ele0A\"]').not('[data-via-ad-loaded=\"true\"]').each(function() {\r\n                var $_tbh = jQuery(this).closest('.ToBeHidden');\r\n                if ($_tbh.length) {\r\n                    var _w = $_tbh[0].offsetWidth;\r\n                    if (_w > 50) { _refCssW = _w; return false; }\r\n                }\r\n            });\r\n            if (_refCssW < 50) {\r\n                var $_fbTbh = jQuery('.droppable').not('[id=\"Ele0A\"]').first().closest('.ToBeHidden');\r\n                if ($_fbTbh.length) { _refCssW = $_fbTbh[0].offsetWidth; }\r\n            }\r\n            if (_refCssW < 50) { _refCssW = Math.round(window.innerWidth * 0.90); }\r\n\r\n            \/\/ Largeur CSS du wrapper = m\u00eame offsetWidth CSS que la r\u00e9f\u00e9rence standard\r\n            \/\/ Ele0A : son parent ToBeHidden est plus large \u2192 appliquer ratio rendu vs CSS pour \u00e9quilibrer\r\n            var _wrapCssW = _refCssW;\r\n            if (_isEle0A) {\r\n                var $_e0ATbh = $droppable.closest('.ToBeHidden');\r\n                if ($_e0ATbh.length) {\r\n                    var _e0ARendered = $_e0ATbh[0].getBoundingClientRect().width;\r\n                    var _e0ACss      = $_e0ATbh[0].offsetWidth || _e0ARendered;\r\n                    \/\/ _refCssW * (e0A_rendered \/ e0A_css) = largeur \u00e9cran cible pour Ele0A\r\n                    \/\/ Pour compenser l'exc\u00e9dent de 10% : appliquer le ratio scale Ele0A\r\n                    if (_e0ACss > 0 ? _e0ARendered > 0 : false) {\r\n                        var _e0AScale = _e0ARendered \/ _e0ACss;\r\n                        \/\/ CSS width pour que le rendu = _refRenderedW\r\n                        var $_stdTbhRef = jQuery('.droppable').not('[id=\"Ele0A\"]').not('[data-via-ad-loaded=\"true\"]').first().closest('.ToBeHidden');\r\n                        var _refRendered = $_stdTbhRef.length ? $_stdTbhRef[0].getBoundingClientRect().width : _refCssW;\r\n                        if (_e0AScale > 0.1) { _wrapCssW = Math.round(_refRendered \/ _e0AScale); }\r\n                    }\r\n                }\r\n            }\r\n\r\n            var _wrapPx = Math.round(_wrapCssW) + 'px';\r\n            var $_wrap = $droppable.find('.via-ad-wrapper');\r\n            \/\/ Sites pays : width 100% -> responsive au zoom CSS sans JS au resize\r\n            var _isSitesPays = (window === window.top);\r\n            $_wrap.css({\r\n                'width': _isSitesPays ? '100%' : _wrapPx,\r\n                'max-width': _isSitesPays ? '100%' : _wrapPx,\r\n                'box-sizing': 'border-box',\r\n                'position': 'relative',\r\n                'left': '0',\r\n                'transform': 'none'\r\n            });\r\n            \/\/ \u2705 Laisser les parents en overflow:visible pour ne pas clipper le wrapper\r\n            if (!_isEle0A) {\r\n                $droppable.find('.OrdiMobileConteneurClass').css({ 'overflow': 'visible' });\r\n                $droppable.css({ 'overflow': 'visible' });\r\n            }\r\n            $ufc.css({ 'width': '100%', 'max-width': 'none', 'min-height': '0',\r\n                        'margin': '0', 'padding': '0' });\r\n            (function(_el2) {\r\n                var _omc = _el2.closest ? _el2.closest('.OrdiMobileConteneurClass') : null;\r\n                \/\/ Desktop version mobile (userAgent desktop + fen\u00eatre r\u00e9duite) \u2192 hauteur fixe 170px\r\n                \/\/ Mobile device r\u00e9el \u2192 height:auto, plafonn\u00e9 \u00e0 200px\r\n                var _isDeskMobile = UIManager.isDesktop();\r\n                var _hVal = 'auto';\r\n                var _maxH = _isDeskMobile ? '280px' : '200px';  \/* v4.9ds : 170 \u2192 280 align\u00e9 sur _applyH principal *\/\r\n                function _applyH2() {\r\n                    \/\/ UFC\r\n                    _el2.style.setProperty('height', _hVal, 'important');\r\n                    _el2.style.setProperty('max-height', _maxH, 'important');\r\n                    _el2.style.setProperty('overflow', 'hidden', 'important');\r\n                    \/\/ Parent OMC : hauteur auto, pas de clamp (sinon header+footer clipp\u00e9s)\r\n                    if (_omc) {\r\n                        _omc.style.setProperty('height', 'auto', 'important');\r\n                        _omc.style.removeProperty('max-height');\r\n                        _omc.style.removeProperty('overflow');\r\n                    }\r\n                }\r\n                _applyH2();\r\n                setTimeout(_applyH2, 100);\r\n                setTimeout(_applyH2, 500);\r\n                setTimeout(_applyH2, 1000);\r\n            })($ufc[0]);\r\n            \/\/ Remonter le wrapper : 0 sur sites pays (zoom CSS, pas de scale)\r\n            if (!_isEle0A) {\r\n                var $_wrapStd = $droppable.find('.via-ad-wrapper');\r\n                var _isInIframe = (window !== window.top);\r\n                if (_isInIframe) {\r\n                    \/\/ iframe r\u00e9gie : compensation scale\r\n                    var _ufcMt = parseFloat($ufc.css('margin-top')) || 0;\r\n                    $_wrapStd.css('margin-top', (_ufcMt - 80) + 'px');\r\n                } else {\r\n                    \/\/ sites pays : DVM = -150, plein \u00e9cran = 0\r\n                    if ($_wrapStd[0]) {\r\n                        var _isDVMStd = (window.innerWidth < 1000);\r\n                        $_wrapStd[0].style.setProperty('margin-top', _isDVMStd ? '-150px' : '0px', 'important');\r\n                    }\r\n                }\r\n            }\r\n            \/\/ v4.9ds : dropzone width seulement (height\/min-height g\u00e9r\u00e9s par _applyDzMinH)\r\n            var _imgMaxH = '160px';\r\n            $dropZone.css({ 'width': '100%', 'margin': '0' });\r\n            \/\/ v4.9ds : pour que l'image occupe TOUTE la box du dropZone (comme Ele0A) tout\r\n            \/\/   en restant enti\u00e8re sans crop, on utilise width:100% + height:100% +\r\n            \/\/   object-fit:contain. Le dropZone a sa height pos\u00e9e par _applyDzMinH et le\r\n            \/\/   max-height CSS lignes 7910\/7929 plafonne. object-fit:contain garantit que\r\n            \/\/   l'image enti\u00e8re est visible (letterbox automatique si ratio diff\u00e9rent).\r\n            $dropZone.find('img, video').css({ 'max-width': '100%', 'width': '100%', 'max-height': _imgMaxH, 'height': '100%', 'display': 'block', 'object-fit': 'contain' });\r\n            \/\/ \u2705 Bug 11 : remonter drop_file_zone_achat de 2px sur mobile + margin-bottom 2px\r\n            $dropZone[0].style.setProperty('margin-top', '-2px', 'important');\r\n            $dropZone[0].style.setProperty('margin-bottom', '2px', 'important');\r\n        }\r\n\r\n        \/\/ v4.9ds : Clamp universel image uniquement (le min-height\/height du dropZone\r\n        \/\/          est g\u00e9r\u00e9 par _applyDzMinH ci-dessus). On ne touche plus au dropZone ici.\r\n        (function() {\r\n            var _imgMaxH2 = '160px';\r\n            $dropZone.find('img, video').css({\r\n                'max-height': _imgMaxH2, 'height': '100%', 'object-fit': 'contain'\r\n            });\r\n        })();\r\n\r\n        \/\/ \u2705 Masquer TOUS les anciens \u00e9l\u00e9ments de position\/ref (d\u00e9sormais dans le header)\r\n        $droppable.find('.DeplaceAnnonce, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire').hide();\r\n        $droppable.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne, .UploadIci').hide();\r\n        $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        \/\/ setProperty !important pour \u00e9craser les display:block!important d'adjustMobileLayout\r\n        $droppable.find('.PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer').each(function() {\r\n            this.style.setProperty('display', 'none', 'important');\r\n        });\r\n        \/\/ \u2705 Supprimer via-position-label (position maintenant dans le header)\r\n        $droppable.find('.via-position-label').remove();\r\n\r\n        \/\/ \u2699\ufe0f Override text-editor au d\u00e9p\u00f4t : neutralise la height explicite (ex: 297px)\r\n        \/\/    que Elementor pose sur .elementor-widget-text-editor \u00e0 certains breakpoints \u00e9troits.\r\n        \/\/    Sans \u00e7a, le wrapper se retrouve d\u00e9cal\u00e9 vers le bas dans l'OMC et chevauche le\r\n        \/\/    contenu en dessous (pb visible en desktop version mobile).\r\n        if (typeof window._viaOverrideTextEditor === 'function') {\r\n            var _omcForOverride = $droppable.find('.OrdiMobileConteneurClass')[0];\r\n            if (_omcForOverride) {\r\n                window._viaOverrideTextEditor(_omcForOverride);\r\n            }\r\n        }\r\n\r\n        console.log('\u2705 [via-ad-overlay] header+footer+wrapper inject\u00e9s | Ele0A:', _isEle0A, '| ref:', _emplacement);\r\n    },\r\n\r\n    adjustLayoutAfterUpload($dropZone) {\r\n        console.log('\ud83d\udcf1 adjustLayoutAfterUpload \u2014 isMobile():', this.isMobile(), 'outerWidth:', window.outerWidth);\r\n        if (this.isMobile()) {\r\n            this.adjustMobileLayout($dropZone);\r\n            \r\n            \/\/ Repositionner le bouton R\u00e9server APR\u00c8S les ajustements de layout\r\n            setTimeout(() => {\r\n                const $droppable = $dropZone.closest('.droppable');\r\n                \/\/ \u2705 v2.6 : Ele0A popup \u2014 skip repositionnement offset (g\u00e9r\u00e9 dans _isEle0AMobAdj)\r\n                if ($droppable.attr('id') === 'Ele0A') { return; }\r\n                const $btn = $droppable.find('.reserver-dynamic-container').add($droppable.next('.reserver-dynamic-container')).first();\r\n                const $lisere = $droppable.find('.HTMLUploadfileConteneur');\r\n                if ($btn.length ? $lisere.length : false) {\r\n                    const lisereBottom = $lisere[0].getBoundingClientRect().bottom;\r\n                    const btnTop = $btn[0].getBoundingClientRect().top;\r\n                    const offset = lisereBottom - btnTop - 48;\r\n                    \r\n                    \/\/ 15px suppl\u00e9mentaires pour homepage et articles\r\n                    let extraOffset = 0;\r\n                    const isHomepage = window.location.pathname === \"\/\" || window.location.pathname === \"\/en\/\";\r\n                    const isSectorPage = $('body').hasClass('page');\r\n                    if (isHomepage) {\r\n                        extraOffset = 13;\r\n                    }\r\n                    if (!isSectorPage) {\r\n                        extraOffset = 15;\r\n                    }\r\n                    \r\n                    \/\/ \u2705 v2.0.9 : +4px pour documents (communiqu\u00e9\/interview\/PDF) avec pr\u00e9sentation HTML\r\n                    if ($droppable.find('.doc-preview-container:visible').length > 0) {\r\n                        extraOffset += 4;\r\n                    }\r\n                    \r\n                    \/\/ v2.9 : site pays direct \u2014 r\u00e9duire l'offset (layout neutre vs iframe)\r\n                    var _sitePaysAdj = (window === window.top) ? 10 : 0;\r\n                    $btn.css({\r\n                        'margin-top': (offset + extraOffset + 20 - _sitePaysAdj) + 'px',\r\n                        'margin-bottom': (-(offset + extraOffset) - 100 + _sitePaysAdj) + 'px'\r\n                    });\r\n                    console.log('\ud83d\udcd0 Repositionnement mobile R\u00e9server:', { lisereBottom, btnTop, offset });\r\n                }\r\n            }, 50);\r\n        } else {\r\n            this.adjustDesktopLayout($dropZone);\r\n        }\r\n    },\r\n    \r\n    adjustMobileLayout($dropZone) {\r\n        console.log('\ud83d\udcf1 adjustMobileLayout START');\r\n        console.log('\ud83d\udcf1 AchatEspaceCall:', StateManager.get(\"AchatEspaceCall\"));\r\n        console.log('\ud83d\udcf1 PageAjoutModifAnnonce:', StateManager.get(\"PageAjoutModifAnnonce\"));\r\n        console.log('\ud83d\udcf1 window===window.top:', window === window.top);\r\n        console.log('\ud83d\udcf1 pathname:', location.pathname);\r\n        \r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \r\n        \/\/ \u2705 v2.4.3 : Masquer les textes position\/label\/r\u00e9f\u00e9rence (comme sur desktop)\r\n        $droppable\r\n            .find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 v2.6 : Renseigner .PositionEspacePublicitaire sur mobile (manquait vs desktop)\r\n        (function() {\r\n            var _rankPosMob = StateManager.get('Rank_Emplacement_Page_Web') || $droppable.attr('id') || '';\r\n            var _posLibMob = PreviewRenderer._getPositionLibelle(_rankPosMob);\r\n            if (_posLibMob) {\r\n                $droppable\r\n                    .find('.PositionEspacePublicitaire')\r\n                    .text(_posLibMob)\r\n                    .each(function() {\r\n                        this.style.setProperty('display', 'block', 'important');\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        \/\/ \u2705 Scop\u00e9 au $dropZone courant (\u00e9vite d'affecter les autres espaces pub)\r\n        $dropZone.closest('.droppable').find('#CroixResetAnnonce').css({'zoom': '75%'});\r\n\r\n        \/\/ \u2705 v2.4.3 : Remonter la croix reset sur mobile (override margin-top Elementor)\r\n        \/\/ \u2705 v2.4.5 : Poser aussi margin-right ici (Entete.txt ne l'atteint pas pour Ele0A)\r\n        var _croixContainer = $dropZone.closest('.OrdiMobileConteneurClass').find('.CroixResetAnnonceContainer')[0];\r\n        if (_croixContainer) {\r\n            var _isEle0ACroix = ($dropZone.closest('.droppable').attr('id') === 'Ele0A');\r\n            var _isTopWindowCroix = (window === window.top);\r\n            \/\/ v2.9 : site pays (mt=0px sur OrdiMobile) \u2192 compensate +65px vs ancien 65px\r\n            var _mtCroix = StateManager.get(\"PageAjoutModifAnnonce\") === 'Yes' ? '-88px'\r\n                         : (_isTopWindowCroix ? '70px' : '24px');\r\n            _croixContainer.style.setProperty('margin-top', _mtCroix, 'important');\r\n            _croixContainer.style.setProperty('margin-right', _isEle0ACroix ? '17px' : '12px', 'important');\r\n        }\r\n        \r\n        \/\/ \u2705 v2.6 : data-kit-drop = marqueur DOM fiable m\u00eame si _dropFromMiniature d\u00e9j\u00e0 resett\u00e9\r\n        var _droppableMob = $dropZone.closest('.droppable')[0];\r\n        var _isMiniatureAdj = window._dropFromMiniature\r\n            || (_droppableMob ? (_droppableMob.getAttribute('data-kit-drop') === 'true') : false);\r\n        window._dropFromMiniature = false;\r\n        \/\/ Nettoyer data-kit-drop apr\u00e8s lecture (comme le fait adjustDesktopLayout)\r\n        if (_droppableMob) { _droppableMob.removeAttribute('data-kit-drop'); }\r\n\r\n\r\n        if (_isMiniatureAdj ? window.outerWidth <= 1000 : false) {\r\n            \/\/ \u2705 Miniature doc-preview MOBILE : ajuster HTMLUploadfileConteneur \u00e0 la hauteur du contenu\r\n            \/\/ et neutraliser tous les margins Elementor qui d\u00e9calent le $dropZone hors du parent\r\n            var _isDocPreviewMob = $dropZone.find('.doc-preview-container').length > 0;\r\n            var _docH = _isDocPreviewMob ? 144 : 115;\r\n            \/\/ \u2705 v2.6 : Dans l'iframe r\u00e9gie (window !== window.top) \u2192 mt:70px (comme AchatEspaceCall=Yes)\r\n            \/\/ En standalone : mt:-55px (valeur originale)\r\n            \/\/ Raison : UFC top:658 < DROP top:720 avec mt:-55px \u2192 d\u00e9bordement 62px au-dessus du droppable\r\n            var _miniMt = (window !== window.top) ? '70px' : '-55px';\r\n            $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                'height':     _docH + 'px',\r\n                'min-height': _docH + 'px',\r\n                'max-height': _docH + 'px',\r\n                'margin-top': _miniMt,\r\n                'margin-bottom': '0px',\r\n                'overflow': 'hidden'\r\n            });\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px', 'height': (_docH - 6) + 'px'});\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '65px', 'margin-bottom': '40px'});\r\n            \/\/ \u2705 v2.4.9 : Doc-preview \u2014 overflow:visible pour que le liser\u00e9 du haut ne soit pas clipp\u00e9\r\n            if (_isDocPreviewMob) {\r\n                $dropZone.closest('.OrdiMobileConteneurClass').css('overflow', 'visible');\r\n                $dropZone.closest('.OrdiMobileConteneurClass').parent().css('overflow', 'visible');\r\n            }\r\n            \/\/ \u2705 v2.4.9 : Ele0A popup mobile \u2014 neutraliser translateY(-32px) pos\u00e9 par Entete sur EnvoiUlterieurTexte\r\n            var _isEle0AMob = ($dropZone.closest('.droppable').attr('id') === 'Ele0A')\r\n                || ($dropZone.closest('#PopUpMessageAchattest').length > 0);\r\n            if (_isEle0AMob) {\r\n                var $_euTxt = $dropZone.closest('.droppable').find('.EnvoiUlterieurTexte');\r\n                if ($_euTxt[0]) {\r\n                    $_euTxt[0].style.setProperty('transform', 'translateY(0px)', 'important');\r\n                    $_euTxt[0].style.setProperty('margin-top', '0px', 'important');\r\n                }\r\n                \/\/ \u2705 v2.6 : Pb 2 \u2014 centrage vertical doc-preview dans #PopUpMessageAchattest\r\n                var _popupAchat = document.getElementById('PopUpMessageAchattest');\r\n                if (_popupAchat) {\r\n                    _popupAchat.style.setProperty('align-items', 'center', 'important');\r\n                }\r\n                \/\/ Annuler margin-top:23px du media query sur .doc-preview-container\r\n                setTimeout(function() {\r\n                    var _dp = $dropZone.find('.doc-preview-container')[0];\r\n                    if (_dp) { _dp.style.setProperty('margin-top', '0px', 'important'); }\r\n                }, 50);\r\n\r\n                \/\/ \u2705 v2.6 : Pb 8\/9 \u2014 miniature Ele0A : appliquer les m\u00eames fixes que _isEle0AMobAdj\r\n                \/\/ (croix position, masquage \u00e9l\u00e9ments parasites, bouton R\u00e9server)\r\n                var $_dropMini = $dropZone.closest('.droppable');\r\n                if ($_dropMini.attr('id') === 'Ele0A') {\r\n                    $_dropMini.css({'position': 'relative'});\r\n                    \/\/ Masquer EnvoiUlterieur (inutile si fichier d\u00e9pos\u00e9) et R\u00e9server statique\r\n                    $_dropMini.find('.elementor-field-group-EnvoiUlterieur').each(function() {\r\n                        this.style.setProperty('display', 'none', 'important');\r\n                    });\r\n                    $_dropMini.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').each(function() {\r\n                        this.style.setProperty('display', 'none', 'important');\r\n                    });\r\n                    \/\/ Croix en position absolue dans l'UFC\r\n                    var $_ufcMini = $dropZone.closest('.HTMLUploadfileConteneur');\r\n                    $_ufcMini.css('position', 'relative');\r\n                    var _croixMini = $_dropMini.find('.CroixResetAnnonceContainer')[0];\r\n                    if (_croixMini) {\r\n                        if ($_ufcMini[0]) { $_ufcMini[0].appendChild(_croixMini); }\r\n                        _croixMini.style.setProperty('position', 'absolute', 'important');\r\n                        _croixMini.style.setProperty('top', '-31px', 'important');\r\n                        _croixMini.style.setProperty('right', '-36px', 'important');\r\n                        _croixMini.style.setProperty('margin', '0px', 'important');\r\n                        _croixMini.style.setProperty('z-index', '9999', 'important');\r\n                    }\r\n                    \/\/ Bouton R\u00e9server dynamique \u2014 plac\u00e9 apr\u00e8s UFC\r\n                    var $_reserverMini = $_dropMini.find('.reserver-dynamic-container').first();\r\n                    if (!$_reserverMini.length) { $_reserverMini = $_dropMini.next('.reserver-dynamic-container'); }\r\n                    if ($_reserverMini.length) {\r\n                        $_ufcMini.after($_reserverMini);\r\n                        $_reserverMini.css({\r\n                            'margin-top': '-27px',\r\n                            'margin-bottom': '4px',\r\n                            'position': 'relative',\r\n                            'z-index': '10',\r\n                            'text-align': 'center',\r\n                            'transform': 'scale(0.8)',\r\n                            'transform-origin': 'top center'\r\n                        });\r\n                    }\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.4.12 : AchatEspaceCall=Yes \u2192 override margins (m\u00eame correction que pour upload direct)\r\n            if (StateManager.get('AchatEspaceCall') === 'Yes') {\r\n                $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n                var _docHAchatMini = _isDocPreviewMob ? 150 : 112;\r\n                $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                    'margin-top': '70px',\r\n                    'margin-bottom': '35px',\r\n                    'min-height': _docHAchatMini + 'px',\r\n                    'height': _isDocPreviewMob ? _docHAchatMini + 'px' : '',\r\n                    'max-height': _isDocPreviewMob ? _docHAchatMini + 'px' : ''\r\n                });\r\n                console.log('\ud83d\udcf1 [miniature] AchatEspaceCall=Yes OVERRIDE: mt=70px | docPreview:', _isDocPreviewMob);\r\n            }\r\n            return;\r\n        }\r\n        \/\/ \u2705 v2.4.10 : Miniature DESKTOP \u2192 layout normal appliqu\u00e9 ci-dessous\r\n        if (_isMiniatureAdj) {\r\n            \/\/ Pas de overflow:hidden \u2014 le liser\u00e9 box-shadow inset doit rester visible\r\n        }\r\n\r\n        \/\/ \u2705 v2.6 : Ele0A popup mobile \u2014 pas de margins ajust\u00e9s (le droppable a ses propres dimensions)\r\n        \/\/ Le fond blanc doit \u00eatre appliqu\u00e9 au droppable lui-m\u00eame, pas \u00e0 HTMLUploadfileConteneur d\u00e9cal\u00e9\r\n        var _isEle0AMobAdj = ($dropZone.closest('.droppable').attr('id') === 'Ele0A');\r\n        console.log('[Ele0A] _isEle0AMobAdj:', _isEle0AMobAdj, '| ViaPopupProcessAchat:', (sessionStorage.getItem('_ViaPopupOpen') === 'Yes'));\r\n        if (_isEle0AMobAdj) {\r\n            var $_ele0ADrop = $dropZone.closest('.droppable');\r\n            \/\/ v2.9 : si ViaPopupProcessAchat present, repositionner les elements hors-lisere\r\n            var _isVPP = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n            console.log('[Ele0A] _isVPP:', _isVPP);\r\n            if (_isVPP) {\r\n                \/\/ DeplaceAnnonceSubContainer : forcer margin-top positif pour rentrer dans le lisere\r\n                var $_das = $_ele0ADrop.find('.DeplaceAnnonceSubContainer');\r\n                if ($_das.length) {\r\n                    $_das[0].style.setProperty('margin-top', '5px', 'important');\r\n                    console.log('[Ele0A] DeplaceAnnonceSubContainer margin-top: 5px');\r\n                }\r\n                \/\/ CroixResetAnnonceContainer : forcer position interne\r\n                var $_crc = $_ele0ADrop.find('.CroixResetAnnonceContainer');\r\n                if ($_crc.length) {\r\n                    $_crc[0].style.setProperty('position', 'absolute', 'important');\r\n                    $_crc[0].style.setProperty('top',   '5px', 'important');\r\n                    $_crc[0].style.setProperty('right', '5px', 'important');\r\n                    $_crc[0].style.setProperty('margin', '0px', 'important');\r\n                    console.log('[Ele0A] CroixResetAnnonceContainer repositionne: top:5px right:5px');\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.6 : Ele0A \u2014 neutraliser la hauteur des boutons format cach\u00e9s\r\n            \/\/ Ne pas utiliser position:absolute\/overflow:hidden \u2192 cache le bouton R\u00e9server\r\n            $_ele0ADrop.css({'position': 'relative'});\r\n            var $_omcEle0A = $dropZone.closest('.OrdiMobileConteneurClass');\r\n            var $_ufcEle0APre = $dropZone.closest('.HTMLUploadfileConteneur');\r\n            \/\/ \u2705 OrdiMobileConteneurClass : flex parent Elementor \u00e9tire le UFC \u2192 forcer flex-start\r\n            $_omcEle0A.css({\r\n                'padding': '0',\r\n                'min-height': '0',\r\n                'height': 'auto',\r\n                'margin-top': '0px',\r\n                'margin-bottom': '0px',\r\n                'align-items': 'flex-start',\r\n                'justify-content': 'flex-start'\r\n            });\r\n            \/\/ \u2705 UFC : align-self:flex-start \u2192 prend sa hauteur naturelle (pas \u00e9tir\u00e9e par l'OMC)\r\n            $_ufcEle0APre.css({\r\n                'margin': '0',\r\n                'min-height': '0',\r\n                'height': 'auto',\r\n                'width': '100%',\r\n                'align-self': 'flex-start',\r\n                'display': 'flex',\r\n                'align-items': 'center',\r\n                'justify-content': 'center'\r\n            });\r\n            $dropZone.css({\r\n                'margin-top': '0px',\r\n                'margin-bottom': '0px',\r\n                'top': '0px',\r\n                'display': 'flex',\r\n                'align-items': 'center',\r\n                'justify-content': 'center',\r\n                'width': '100%'\r\n            });\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '0px', 'margin-bottom': '0px'});\r\n            \/\/ \u2705 Pb 2 : Centrage vertical du doc-preview \u2014 mesur\u00e9 apr\u00e8s rendu\r\n            \/\/ Le flexbox seul ne suffit pas si le conteneur n'a pas de hauteur propre\r\n            setTimeout(function() {\r\n                var _ele0AEl = $_ele0ADrop[0];\r\n                var _docPrevEl = $dropZone.find('.doc-preview-container')[0];\r\n                if (_ele0AEl) {\r\n                    if (_docPrevEl) {\r\n                        var _ele0AH = _ele0AEl.getBoundingClientRect().height;\r\n                        var _docPrevH = _docPrevEl.getBoundingClientRect().height;\r\n                        var _mt = Math.max(0, Math.round((_ele0AH - _docPrevH) \/ 2) - 8);\r\n                        _docPrevEl.style.setProperty('margin-top', _mt + 'px', 'important');\r\n                        console.log('\ud83d\udcf1 [Ele0A pb2] centrage vertical: ele0AH=' + Math.round(_ele0AH) + ' docH=' + Math.round(_docPrevH) + ' mt=' + _mt + 'px');\r\n                    }\r\n                }\r\n            }, 100);\r\n            \/\/ \u2705 Pb 3 : Croix d\u00e9plac\u00e9e DANS HTMLUploadfileConteneur (position:relative) \u2192 top-right de l'image\r\n            var $_ufcEle0A = $_ufcEle0APre;\r\n            $_ufcEle0A.css('position', 'relative');\r\n            var _croixEle0A = $_ele0ADrop.find('.CroixResetAnnonceContainer')[0];\r\n            if (_croixEle0A) {\r\n                $_ufcEle0A[0].appendChild(_croixEle0A);\r\n                _croixEle0A.style.setProperty('position', 'absolute', 'important');\r\n                var _isViaPopupCroix = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n                if (_isViaPopupCroix) {\r\n                    \/\/ Popup pays site : croix a l'interieur de l'UFC\r\n                    _croixEle0A.style.setProperty('top',   '4px',  'important');\r\n                    _croixEle0A.style.setProperty('right', '4px',  'important');\r\n                } else {\r\n                    _croixEle0A.style.setProperty('top',   '-31px', 'important');\r\n                    _croixEle0A.style.setProperty('right', '-36px', 'important');\r\n                }\r\n                _croixEle0A.style.setProperty('margin', '0px', 'important');\r\n                _croixEle0A.style.setProperty('z-index', '9999', 'important');\r\n            }\r\n            \/\/ \u2705 v2.6 : Masquer les \u00e9l\u00e9ments au-dessus de l'image dans Ele0A\r\n            \/\/ EnvoiUlterieur : inutile quand un fichier est d\u00e9pos\u00e9\r\n            \/\/ ReserverContainer statique : remplac\u00e9 par le bouton dynamique\r\n            $_ele0ADrop.find('.elementor-field-group-EnvoiUlterieur').each(function() {\r\n                this.style.setProperty('display', 'none', 'important');\r\n            });\r\n            $_ele0ADrop.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').each(function() {\r\n                this.style.setProperty('display', 'none', 'important');\r\n            });\r\n\r\n            \/\/ \u2705 Pb 4 : Bouton R\u00e9server dynamique \u2014 plac\u00e9 apr\u00e8s UFC\r\n            var $_reserverEle0A = $_ele0ADrop.find('.reserver-dynamic-container').first();\r\n            if (!$_reserverEle0A.length) { $_reserverEle0A = $_ele0ADrop.next('.reserver-dynamic-container'); }\r\n            if ($_reserverEle0A.length) {\r\n                $_ufcEle0A.after($_reserverEle0A);\r\n                $_reserverEle0A.css({\r\n                    'margin-top': '-27px',\r\n                    'margin-bottom': '4px',\r\n                    'position': 'relative',\r\n                    'z-index': '10',\r\n                    'text-align': 'center',\r\n                    'transform': 'scale(0.8)',\r\n                    'transform-origin': 'top center'\r\n                });\r\n            }\r\n            console.log('\ud83d\udcf1 [Ele0A popup] marges neutralis\u00e9es + centrage vertical + croix\/r\u00e9server positionn\u00e9s');\r\n            return;\r\n        }\r\n\r\n        \/\/ v2.9 : site pays direct (window.top) = marges neutres; iframe r\u00e9gie = marges compens\u00e9es\r\n        var _isInIframeNonMini = (window !== window.top);\r\n        var _isOnSitePays = (window === window.top);\r\n        $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n            'min-height': '0',\r\n            'margin-top': _isInIframeNonMini ? '70px' : (_isOnSitePays ? '-50px' : '-85px'),\r\n            'margin-bottom': _isMiniatureAdj ? '0px' : (_isInIframeNonMini ? '35px' : (_isOnSitePays ? '10px' : '20px'))\r\n        });\r\n        console.log('\ud83d\udcf1 HTMLUploadfileConteneur margins set: mt=' + (_isOnSitePays ? '10px' : (_isInIframeNonMini ? '70px' : '-85px')) + ', el found:', $dropZone.closest('.HTMLUploadfileConteneur').length);\r\n        \r\n        if (_isInIframeNonMini || _isOnSitePays) {\r\n            \/\/ iframe ou site pays direct : pas de marges n\u00e9gatives sur dropZone\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n        } else {\r\n            $dropZone.css({\r\n                'margin-top': '-50px',\r\n                'margin-bottom': '-140px'\r\n            });\r\n        }\r\n        \r\n        $dropZone.closest('.OrdiMobileConteneurClass').css({\r\n            'margin-top': _isOnSitePays ? '0px' : '65px',\r\n            'margin-bottom': _isOnSitePays ? '50px' : '-10px'\r\n        });\r\n        console.log('\ud83d\udcf1 OrdiMobile margins set: mt=' + (_isOnSitePays ? '0px' : '65px') + ' mb=' + (_isOnSitePays ? '50px' : '-10px'));\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            \/\/ \u2705 v2.4.12 : Reset $dropZone margins (les -50px\/-140px tirent le contenu vers le haut dans l'iframe)\r\n            \/\/ Pour doc-preview (Communiqu\u00e9\/Interview), la hauteur est plus grande \u2192 ajuster min-height\r\n            var _isDocPreviewAchat = $dropZone.find('.doc-preview-container').length > 0;\r\n            var _docHAchat = _isDocPreviewAchat ? 150 : 112;\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n            $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                \/\/ \u2705 v2.4.13 : Miniature desktop \u2192 +40px suppl\u00e9mentaires pour compenser le d\u00e9calage\r\n                'margin-top': _isMiniatureAdj ? '110px' : '70px',\r\n                'margin-bottom': '35px',\r\n                'min-height': _docHAchat + 'px',\r\n                'height': _isDocPreviewAchat ? _docHAchat + 'px' : '',\r\n                'max-height': _isDocPreviewAchat ? _docHAchat + 'px' : ''\r\n            });\r\n            console.log('\ud83d\udcf1 AchatEspaceCall=Yes OVERRIDE: mt=' + (_isMiniatureAdj ? '110px' : '70px') + ' | docPreview:', _isDocPreviewAchat, '| h:', _docHAchat);\r\n        }\r\n        \r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '70px'});\r\n        }\r\n        \r\n        \/\/ v2.9 : site pays mobile \u2014 d\u00e9caler reserver-dynamic-container de 30px vers le bas\r\n        if (_isOnSitePays) {\r\n            setTimeout(function() {\r\n                var $reserverCont = $dropZone.closest('.droppable').find('.reserver-dynamic-container')\r\n                    .add($dropZone.closest('.droppable').next('.reserver-dynamic-container')).first();\r\n                if ($reserverCont.length) {\r\n                    var _curMt = parseInt($reserverCont.css('margin-top')) || 0;\r\n                    $reserverCont.css('margin-top', (_curMt + 30) + 'px');\r\n                    console.log('\ud83d\udcf1 [site pays] reserver-dynamic-container +30px \u2192 mt:', _curMt + 30);\r\n                }\r\n            }, 80);\r\n        }\r\n        \/\/ v2.9 : supprim\u00e9 - margin-top\/bottom -100px causait chevauchement texte sur sites pays\r\n    },\r\n    \r\n    adjustDesktopLayout($dropZone) {\r\n        const emplacement = StateManager.get('Commande_Emplacement_Page_Web');\r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \/\/ \u2705 v2.4.13 : Lire et resetter _dropFromMiniature ici aussi (desktop)\r\n        var _fromMiniatureDesktop = window._dropFromMiniature || ($droppable[0] ? $droppable[0].getAttribute('data-kit-drop') === 'true' : false);\r\n        \/\/ Memoriser pour _buildAdOverlay qui tourne apres\r\n        window._lastDropWasKit = _fromMiniatureDesktop;\r\n        window._dropFromMiniature = false;\r\n        \/\/ \u2705 Nettoyer data-kit-drop apr\u00e8s lecture\r\n        if ($droppable[0]) { $droppable[0].removeAttribute('data-kit-drop'); }\r\n        \r\n        \/\/ \u2705 Masquer les textes enfants (position, label, r\u00e9f\u00e9rence) sans toucher au conteneur\r\n        $droppable\r\n            .find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur > div > .elementor-widget-text-editor')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 +15px sur .droppable \u2014 sauf si drop depuis miniature (dimensions naturelles)\r\n        if (!_fromMiniatureDesktop) {\r\n            const currentMt = parseInt($droppable.css('margin-top')) || 0;\r\n            $droppable.css('margin-top', (currentMt + 15) + 'px');\r\n        } else {\r\n            \/\/ \u2705 v2.4.13 : Drop depuis miniature Kit \u2014 d\u00e9caler de 30px vers le bas\r\n            const currentMt = parseInt($droppable.css('margin-top')) || 0;\r\n            \/\/ \u2705 Homepage : -20px (annonce trop basse sur r\u00e9gie) \u2014 autres pages : +70px\r\n            var _mtOffsetMini = (window.location.pathname === '\/' || window.location.pathname === '\/en\/' || window.location.pathname === '\/fr\/') ? -20 : 70;\r\n            $droppable.css('margin-top', (currentMt + _mtOffsetMini) + 'px');\r\n        }\r\n        \r\n        \/\/ v2.9 : detection popup parent (ViaPopupProcessAchat existe sur site pays)\r\n        var _isViaPopupParent = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n        $dropZone.closest('.OrdiMobileConteneurClass')\r\n            .find('.RefEspacePublicitaire')\r\n            .html(emplacement)\r\n            .attr('id', 'RefEspacePublicitaire')\r\n            .each(function() {\r\n                var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                if (_isViaPopupParent) {\r\n                    \/\/ repositionner a l'interieur du lisere vert\r\n                    this.style.setProperty('display', 'block', 'important');\r\n                    this.style.setProperty('margin-top', '4px', 'important');\r\n                    this.style.setProperty('margin-right', '4px', 'important');\r\n                    if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                } else {\r\n                    if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                    this.style.setProperty('display', 'block', 'important');\r\n                }\r\n            });\r\n\r\n        \/\/ v4.9cp : centrer .RefEspacePublicitaire entre le titre et la croix de fermeture.\r\n        \/\/          Mesure dynamique du bord droit du titre et du bord gauche de la croix,\r\n        \/\/          puis pose position:absolute + left au milieu.\r\n        function _centerRefEspPub() {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').find('.RefEspacePublicitaire').each(function() {\r\n                var _refEl = this;\r\n                var _container = jQuery(_refEl).closest('.droppable')[0] || jQuery(_refEl).closest('.DeplaceAnnonce')[0];\r\n                if (!_container) return;\r\n                \/\/ Bord droit du titre (ChoisirEspacePublicitaireDisponibiliteConteneur ou AdUploadedTitle)\r\n                var _titleEl = _container.querySelector('.ChoisirEspacePublicitaireDisponibiliteConteneur')\r\n                            || _container.querySelector('.AdUploadedTitle');\r\n                \/\/ Bord gauche de la croix\r\n                var _crossEl = _container.querySelector('#CroixResetAnnonce')\r\n                            || _container.querySelector('.CroixResetAnnonceContainer');\r\n                if (!_titleEl || !_crossEl) return;\r\n                var _cRect = _container.getBoundingClientRect();\r\n                var _tRect = _titleEl.getBoundingClientRect();\r\n                var _xRect = _crossEl.getBoundingClientRect();\r\n                if (!_cRect.width || !_tRect.width || !_xRect.width) return;\r\n                \/\/ Milieu entre bord droit titre et bord gauche croix\r\n                var _mid = (_tRect.right + _xRect.left) \/ 2;\r\n                \/\/ Position relative au container (le parent position:relative)\r\n                var _leftPx = Math.round(_mid - _cRect.left);\r\n                _refEl.style.setProperty('position', 'absolute', 'important');\r\n                _refEl.style.setProperty('left', _leftPx + 'px', 'important');\r\n                _refEl.style.setProperty('transform', 'translateX(-50%)', 'important');\r\n                _refEl.style.setProperty('top', (_tRect.top - _cRect.top) + 'px', 'important');\r\n                _refEl.style.removeProperty('margin-right');\r\n                _refEl.style.removeProperty('margin-left');\r\n            });\r\n        }\r\n        \/\/ Apr\u00e8s le rendu (titre + croix visibles)\r\n        setTimeout(_centerRefEspPub, 50);\r\n        setTimeout(_centerRefEspPub, 300);\r\n        setTimeout(_centerRefEspPub, 800);\r\n\r\n        \/\/ \u2705 v2.4.5 : Renseigner et afficher .PositionEspacePublicitaire\r\n        (function() {\r\n            var _rankPos = StateManager.get('Rank_Emplacement_Page_Web') || $droppable.attr('id') || '';\r\n            var _posLib = PreviewRenderer._getPositionLibelle(_rankPos);\r\n            if (_posLib) {\r\n                $dropZone.closest('.OrdiMobileConteneurClass')\r\n                    .find('.PositionEspacePublicitaireDeplacer')\r\n                    .text(_posLib)\r\n                    .each(function() {\r\n                        var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                        if (_isViaPopupParent) {\r\n                            this.style.setProperty('display', 'block', 'important');\r\n                            this.style.setProperty('margin-top', '4px', 'important');\r\n                            if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                        } else {\r\n                            if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                            this.style.setProperty('display', 'block', 'important');\r\n                        }\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        if (window.location.pathname === \"\/\" || window.location.pathname === \"\/en\/\") {\r\n            \/\/ \u2705 v2.4.10 : top selon contexte \u2014 espaces hors .remainingContent d\u00e9calent de 60px, ceux dedans de 20px\r\n            \/\/ \u2705 v2.7.3 : Ele0A est positionn\u00e9 par positionEle0AOverEle1A \u2192 ne pas \u00e9craser son top\r\n            var _isEle0AHP = $droppable.attr('id') === 'Ele0A';\r\n            if (!_isEle0AHP) {\r\n                var _inRemainingContent = $dropZone.closest('.remainingContent').length > 0;\r\n                var _topOffsetHP = _inRemainingContent ? '20px' : '100px';\r\n                $dropZone.closest('.ToBeHidden')\r\n                    .css('top', _topOffsetHP)\r\n                    .css('min-height', '300px');\r\n            }\r\n            \/\/ NB : pas de overflow:hidden ici \u2014 le liser\u00e9 vert (box-shadow inset sur HTMLUploadfileConteneur)\r\n            \/\/ doit rester visible sur les 4 c\u00f4t\u00e9s\r\n        }\r\n        \r\n        \/\/ \u2705 Toutes pages desktop : rendre le parent du droppable non-clippant\r\n        \/\/ Le bouton R\u00e9server est ins\u00e9r\u00e9 apr\u00e8s .droppable avec margin-top n\u00e9gatif \u2014\r\n        \/\/ overflow:hidden sur le parent Elementor le masque sinon.\r\n        $droppable.parent().css('overflow', 'visible');\r\n        \r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '0px'});\r\n            \/\/ \u2705 Page \/annonce : masquer les \u00e9l\u00e9ments de d\u00e9placement inutiles\r\n            $droppable.find('.DeplaceAnnonceText').hide();\r\n            $droppable.find('.PositionEspacePublicitaireDeplacer').hide();\r\n            $droppable.find('.RefEspacePublicitaire').hide();\r\n            $droppable.find('.reserver-dynamic-option').hide();\r\n            $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        }\r\n        \r\n        \/\/ \u2705 v2.7.3 : Ele0A desktop \u2014 ajustements position des labels et de la croix apr\u00e8s d\u00e9p\u00f4t\r\n        if ($droppable.attr('id') === 'Ele0A') {\r\n            \/\/ .PositionEspacePublicitaireDeplacer : +35px bas, +30px droite\r\n            $droppable.find('.PositionEspacePublicitaireDeplacer').each(function() {\r\n                this.style.setProperty('margin-top', '35px', 'important');\r\n                this.style.setProperty('margin-left', '30px', 'important');\r\n            });\r\n            \/\/ .RefEspacePublicitaire : +35px bas, 25px vers la gauche\r\n            $droppable.find('.RefEspacePublicitaire').each(function() {\r\n                this.style.setProperty('margin-top', '35px', 'important');\r\n                this.style.setProperty('margin-right', '25px', 'important');\r\n            });\r\n            \/\/ #CroixResetAnnonce : -30px vers le haut, 22px vers la gauche (+3px droite)\r\n            $droppable.find('#CroixResetAnnonce').each(function() {\r\n                this.style.setProperty('margin-top', '-30px', 'important');\r\n                this.style.setProperty('margin-right', '22px', 'important');\r\n            });\r\n        }\r\n    }\r\n};\r\n\r\n\/**\r\n * \u2705 Module de gestion des formats et du titre .SelectionFormatTitre\r\n *\/\r\nconst FormatUIManager = {\r\n    \r\n    \/**\r\n     * V\u00e9rifie si un format est s\u00e9lectionn\u00e9 dans un espace publicitaire\r\n     * V\u00e9rifie le background-color OU le sessionStorage (format venant de l'accord\u00e9on)\r\n     *\/\r\n    hasSelectedFormat($element) {\r\n        const $droppable = $element.closest('.droppable');\r\n        \/\/ v4.9ds : si une annonce est d\u00e9j\u00e0 d\u00e9pos\u00e9e dans cet espace (data-via-ad-loaded='true'),\r\n        \/\/   le format est implicite (d\u00e9duit par UploadManager.handleFileUpload depuis l'extension\r\n        \/\/   du fichier). Retourner true sans d\u00e9pendre du DOM .EspPubFormatContainer ni du\r\n        \/\/   sessionStorage Upload_File_Name (qui peut \u00eatre nettoy\u00e9 apr\u00e8s une r\u00e9servation\r\n        \/\/   pr\u00e9c\u00e9dente sur un autre espace pub).\r\n        \/\/   Bug constat\u00e9 : 2 annonces d\u00e9pos\u00e9es (Ele0A + Ele1A), Ele0A r\u00e9serv\u00e9e \u2192 popup ouvert,\r\n        \/\/   tentative R\u00e9server Ele1A bloqu\u00e9e car hasFormat=false (sessionStorage vid\u00e9 pour Ele0A,\r\n        \/\/   et le fond blanc EspPubFormatContainer pas pos\u00e9 sur Ele1A en mode popup).\r\n        if ($droppable.attr('data-via-ad-loaded') === 'true') return true;\r\n        \/\/ \u2705 v4.9ds Fix 4 : data-via-current-format prioritaire sur la d\u00e9tection visuelle\r\n        \/\/   \u2192 pos\u00e9 par applyFormatState ; survit aux races qui effacent le background blanc\r\n        \/\/   (un script tiers met temporairement transparent \u2192 l'observer voit \"format perdu\"\r\n        \/\/   \u2192 SelectionFormatTitre s'affiche \u2192 UI cass\u00e9e). L'attribut sur le .droppable\r\n        \/\/   reste pos\u00e9 tant qu'applyFormatState n'a pas \u00e9t\u00e9 appel\u00e9 avec un format diff\u00e9rent.\r\n        if ($droppable.attr('data-via-current-format')) return true;\r\n        const isEle0A = $droppable.attr('id') === 'Ele0A';\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            if (isEle0A) {\r\n                \/\/ \u2705 v2.3.4 : Ele0A popup \u2014 vrai seulement si un format sous-jacent (hors FormatIdPopUp) est s\u00e9lectionn\u00e9\r\n                var _hasDomFmt = $droppable.find('.EspPubFormatContainer').not('.FormatIdPopUp').toArray().some(el => {\r\n                    return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n                });\r\n                if (_hasDomFmt) return true;\r\n                \/\/ \u2705 v2.4.5 : Fallback sessionStorage \u2014 DOM pas encore mis \u00e0 jour (timing MutationObserver)\r\n                return !!sessionStorage.getItem('FormatSelect');\r\n            }\r\n            \/\/ v4.9ds : Autres espaces (Ele1A+) en mode popup \u2014 v\u00e9rifier le DOM r\u00e9el.\r\n            \/\/   AVANT : retournait true syst\u00e9matiquement \u2192 SelectionFormatTitre toujours masqu\u00e9,\r\n            \/\/   m\u00eame apr\u00e8s suppression+restauration d'une annonce dans cet espace.\r\n            \/\/   APR\u00c8S : on cherche un .EspPubFormatContainer (hors Cr\u00e9ation\/PopUp) avec fond\r\n            \/\/   blanc dans CET espace. Si aucun \u2192 return false \u2192 SelectionFormatTitre s'affiche.\r\n            var _hasDomFmtStd = $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').toArray().some(el => {\r\n                return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n            });\r\n            return _hasDomFmtStd;\r\n        }\r\n        \/\/ v4.9ds : exclure FormatIdCreation et FormatIdPopUp \u2014 ce ne sont pas de vrais formats\r\n        const $realFmts = $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp');\r\n        const hasWhiteBg = $realFmts.toArray().some(el => {\r\n            return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n        });\r\n        if (hasWhiteBg) return true;\r\n        \/\/ v4.9ds : si le DOM contient des containers de format dans CE droppable et qu'AUCUN n'est s\u00e9lectionn\u00e9,\r\n        \/\/   alors retourner false EXPLICITEMENT \u2014 ne pas se rabattre sur des flags sessionStorage globaux\r\n        \/\/   qui peuvent rester pos\u00e9s apr\u00e8s cr\u00e9ation dans un autre espace.\r\n        \/\/   Bug constat\u00e9 : apr\u00e8s fermeture du popup cr\u00e9ation (lanc\u00e9 depuis Ele0A), Formatchoisi\/FormatSelect\r\n        \/\/   restaient pos\u00e9s \u2192 SelectionFormatTitre disparaissait sur Ele1A+ alors qu'aucun format n'y \u00e9tait choisi.\r\n        if ($realFmts.length > 0) return false;\r\n        \/\/ Fallbacks sessionStorage UNIQUEMENT si le DOM n'a pas encore de containers (timing initial \/ pr\u00e9-rendu)\r\n        if (sessionStorage.getItem('Formatchoisi') === 'Yes') return true;\r\n        if (sessionStorage.getItem('FormatSelect')) return true;\r\n        return !!sessionStorage.getItem('_FormatSelectApplied');\r\n    },\r\n    \r\n    \/**\r\n     * Met \u00e0 jour la couleur du titre .SelectionFormatTitre\r\n     * Blanc si un format est s\u00e9lectionn\u00e9, rouge #FB5E2A sinon\r\n     *\/\r\n    updateTitleColor($droppable) {\r\n        \/\/ \u2705 v2.4.5 : Ele0A \u2014 ne jamais afficher SelectionFormatTitre\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes' ? $droppable.attr('id') === 'Ele0A' : false) {\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n            return;\r\n        }\r\n\r\n        if (this.hasSelectedFormat($droppable)) {\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n            console.log('\u2705 SelectionFormatTitreBlanc affich\u00e9 (format s\u00e9lectionn\u00e9)');\r\n        } else {\r\n            $droppable.find('.SelectionFormatTitre').show();\r\n            $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n            console.log('\u26a0\ufe0f SelectionFormatTitre affich\u00e9 (aucun format s\u00e9lectionn\u00e9)');\r\n        }\r\n\r\n        \/\/ \u2705 R\u00e8gle Cr\u00e9ation\/Vid\u00e9o : d\u00e9tecter si Vid\u00e9o est actif et d\u00e9sactiver\/r\u00e9activer Cr\u00e9ation\r\n        var _videoActive = false;\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            var _bg = jQuery(this).css('background-color') || '';\r\n            if (_bg === 'rgb(255, 255, 255)') { _videoActive = true; return false; }\r\n            var _fEl = this.querySelector('.EspPubFormat');\r\n            if (_fEl) { var _col = _fEl.style.color || ''; if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') { _videoActive = true; return false; } }\r\n        });\r\n        if (_videoActive) {\r\n            $droppable.find('.FormatIdCreation').each(function() {\r\n                jQuery(this).data('creationActive', false);\r\n                jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n                if ($lbl.text() !== 'D\u00e9sactiv\u00e9') { $lbl.text('D\u00e9sactiv\u00e9'); }\r\n            });\r\n            console.log('\ud83c\udfa8 updateTitleColor : Vid\u00e9o actif \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e');\r\n        } else {\r\n            $droppable.find('.FormatIdCreation').each(function() {\r\n                var _cur = jQuery(this).css('opacity');\r\n                if (_cur === '0.4') {\r\n                    jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                    var $lbl = jQuery(this).find('.EspPubFormat');\r\n                    var _orig = $lbl.attr('data-original-text');\r\n                    if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                }\r\n            });\r\n        }\r\n    },\r\n    \r\n    \/**\r\n     * Flash visuel sur le titre pour indiquer qu'un format est requis\r\n     *\/\r\n    flashTitle($element) {\r\n        const $droppable = $element.closest('.droppable');\r\n        const $titre = $droppable.find('.SelectionFormatTitre');\r\n        if (!$titre.length) return;\r\n        \r\n        \/\/ v2.6 : En mode popup (Ele0A), SelectionFormatTitre est display:none \u2192 le montrer temporairement\r\n        var _wasHidden = $titre.css('display') === 'none';\r\n        \/\/ v2.6 : Elementor impose display:none !important \u2192 setProperty requis\r\n        if (_wasHidden) { $titre.each(function() { this.style.setProperty('display','block','important'); }); }\r\n        \r\n        \/\/ Flash rouge rapide 3 fois\r\n        let count = 0;\r\n        const originalColor = $titre.css('color');\r\n        \r\n        const flash = setInterval(() => {\r\n            $titre.css('color', count % 2 === 0 ? '#FF0000' : '#FB5E2A');\r\n            count++;\r\n            if (count >= 6) {\r\n                clearInterval(flash);\r\n                $titre.css('color', '#FB5E2A');\r\n                if (_wasHidden) { setTimeout(function() { $titre.each(function() { this.style.setProperty('display','none','important'); }); }, 2000); }\r\n            }\r\n        }, 250);\r\n        \r\n        console.log('\u26a0\ufe0f Flash titre format - action bloqu\u00e9e (aucun format s\u00e9lectionn\u00e9)');\r\n    },\r\n    \r\n    \/**\r\n     * Met \u00e0 jour l'\u00e9tat de la checkbox \"R\u00e9server cet espace publicitaire\"\r\n     * La checkbox est activable si : format s\u00e9lectionn\u00e9 ET (fichier d\u00e9pos\u00e9 OU envoi diff\u00e9r\u00e9)\r\n     *\/\r\n    updateReserverCheckboxState($droppable) {\r\n        \/\/ \u2705 Chercher le label : dynamique ou Elementor statique\r\n        let $label = null;\r\n        let $container = null;\r\n        \r\n        \/\/ 1. Bouton dynamique dans le droppable\r\n        $container = $droppable.find('.reserver-dynamic-container');\r\n        if (!$container.length) {\r\n            $container = $droppable.next('.reserver-dynamic-container');\r\n        }\r\n        if (!$container.length) {\r\n            \/\/ Mobile : checkbox attach\u00e9e au body avec data-droppable-id\r\n            $container = $('body > .reserver-dynamic-container[data-droppable-id=\"' + $droppable.attr('id') + '\"]');\r\n        }\r\n        if (!$container.length) {\r\n            $container = $('body > .reserver-dynamic-container');\r\n        }\r\n        \r\n        if ($container.length) {\r\n            $label = $container.find('.reserver-dynamic-label');\r\n            $container.find('.reserver-dynamic-option, .elementor-field-option').css('opacity', '1');\r\n        }\r\n        \r\n        \/\/ 2. Bouton Elementor statique - chercher dans le droppable puis globalement\r\n        let $reserverStatique = $droppable.find('.elementor-field-group-ReserverEspacePublicitaire label');\r\n        if (!$reserverStatique.length) {\r\n            $reserverStatique = $droppable.find('label[for^=\"form-field-ReserverEspacePublicitaire\"]');\r\n        }\r\n        if (!$reserverStatique.length) {\r\n            \/\/ Fallback global : le formulaire peut \u00eatre en dehors du droppable\r\n            $reserverStatique = $('.elementor-field-group-ReserverEspacePublicitaire .elementor-field-option label');\r\n        }\r\n        \r\n        const hasFormat = this.hasSelectedFormat($droppable);\r\n        \/\/ v2.9 : data-via-ad-loaded est specifique au droppable, plus fiable que FileReceived global\r\n        const hasFile = StateManager.get('FileReceived') === 'Yes'\r\n            || $droppable.attr('data-via-ad-loaded') === 'true';\r\n        const hasEnvoiDiffere = $droppable.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        \r\n        const canReserve = hasFormat ? (hasFile || hasEnvoiDiffere) : false;\r\n        \r\n        \/\/ \u2705 Si la checkbox R\u00e9server est coch\u00e9e \u2192 label vert \"Espace publicitaire r\u00e9serv\u00e9\"\r\n        const isChecked = $droppable.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked') ||\r\n                          ($container.length ? $container.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked') : false);\r\n        \/\/ \u2705 v2.4.7 : Signal de restauration r\u00e9serv\u00e9e \u2014 checkbox pas encore coch\u00e9e (setTimeout 150ms)\r\n        \/\/             mais on sait d\u00e9j\u00e0 que l'espace est r\u00e9serv\u00e9 \u2192 traiter comme coch\u00e9\r\n        const _isReservedRestoration = sessionStorage.getItem('_isReservedRestoration') === 'Yes';\r\n        const isCheckedOrReservedRestore = isChecked || _isReservedRestoration;\r\n        \r\n        if ($label ? $label.length : false) {\r\n            if (isCheckedOrReservedRestore) {\r\n                $label.text('Espace publicitaire r\u00e9serv\u00e9').css('color', 'rgb(62, 170, 19)');\r\n            } else {\r\n                $label.text('R\u00e9server cet espace publicitaire').css('color', canReserve ? '#FB5E2A' : '');\r\n            }\r\n        }\r\n        if ($reserverStatique.length) {\r\n            if (isCheckedOrReservedRestore) {\r\n                $reserverStatique.attr('style', 'color: rgb(62, 170, 19) !important;');\r\n            } else if (canReserve) {\r\n                $reserverStatique.attr('style', 'color: #FB5E2A !important;');\r\n            } else {\r\n                $reserverStatique.removeAttr('style');\r\n            }\r\n        }\r\n        \r\n        console.log('\ud83d\udd04 updateReserverCheckboxState:', { hasFormat, hasFile, hasEnvoiDiffere, canReserve, dynamicLabel: !!($label ? $label.length : false), staticLabel: $reserverStatique.length });\r\n    },\r\n    \r\n    \/**\r\n     * Initialise les listeners pour le changement de format et la checkbox R\u00e9server\r\n     *\/\r\n    init() {\r\n        \/\/ \u2705 \u00c9couter les clics sur les conteneurs de format et leurs parents\r\n        jQuery(document).on('click', '.EspPubFormatContainer', (e) => {\r\n            const $droppable = jQuery(e.target).closest('.droppable');\r\n            if (!$droppable.length) return;\r\n\r\n            \/\/ v4.9ds : stocker le format choisi sur le .droppable (gate ind\u00e9pendant)\r\n            \/\/          Exclusion : Creation et PopUp ne sont pas de vrais formats \u2192 ne pas\r\n            \/\/          d\u00e9verrouiller les zones (glisser-d\u00e9poser, envoi diff\u00e9r\u00e9, r\u00e9server)\r\n            const _$btn = jQuery(e.target).closest('.EspPubFormatContainer');\r\n            const _classes = _$btn.attr('class') || '';\r\n            const _m = _classes.match(\/FormatId(\\w+)\/);\r\n            if (_m && _m[1] && _m[1] !== 'Creation' && _m[1] !== 'PopUp') {\r\n                $droppable.attr('data-via-current-format', _m[1]);\r\n                \/\/ Notifier le parent pour mettre \u00e0 jour le format-gate (CSS top-level)\r\n                try {\r\n                    var _topW = window.top || window;\r\n                    if (_topW && typeof _topW._viaSetDroppableFormat === 'function') {\r\n                        _topW._viaSetDroppableFormat($droppable.attr('id'), _m[1]);\r\n                    }\r\n                } catch(_e) {}\r\n            }\r\n\r\n            \/\/ \u2705 Masquer les messages d'erreur format d\u00e8s la s\u00e9lection d'un format\r\n            jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n            jQuery('#fmt-creation-info').remove();\r\n            \r\n            \/\/ v4.9ds : appeler _viaUpdateFormatGate pour TOUS les clics (incluant Cr\u00e9ation\/PopUp)\r\n            \/\/   afin de mettre \u00e0 jour data-via-title-state=\"chosen\" \u2192 CSS SelectionFormatTitre\r\n            \/\/   (transparent + blanc) prend effet aussi pour Cr\u00e9ation.\r\n            [50, 200, 500].forEach(function(delay) {\r\n                setTimeout(function() {\r\n                    try {\r\n                        var _topW2 = window.top || window;\r\n                        if (_topW2 ? typeof _topW2._viaUpdateFormatGate === 'function' : false) {\r\n                            _topW2._viaUpdateFormatGate();\r\n                        }\r\n                    } catch (_e) {}\r\n                }, delay);\r\n            });\r\n\r\n            \/\/ Multiples d\u00e9lais pour couvrir les traitements asynchrones\r\n            [50, 200, 500].forEach(delay => {\r\n                setTimeout(() => {\r\n                    this.updateTitleColor($droppable);\r\n                    this.updateReserverCheckboxState($droppable);\r\n                }, delay);\r\n            });\r\n\r\n            \/\/ \u2705 v2.2.0 : Si un format (non Cr\u00e9ation) est cliqu\u00e9, notifier le parent pour mettre \u00e0 jour le Kit\r\n            const $btn = jQuery(e.target).closest('.EspPubFormatContainer');\r\n            if ($btn.length ? (!$btn.hasClass('FormatIdCreation') ? !$btn.hasClass('FormatIdPopUp') : false) : false) {\r\n                \/\/ \u2705 v2.3.4 : FormatIdPopUp g\u00e9r\u00e9 par clickFormatFromIframe (Entete.txt) \u2014 ne pas doubler\r\n                var classes = $btn.attr('class') || '';\r\n                var match = classes.match(\/FormatId(\\w+)\/);\r\n                if (match ? match[1] : false) {\r\n                    var _rankEP = $droppable.attr('id') || '';\r\n                    setTimeout(function() {\r\n                        MessageManager.sendToParent('formatChangedInIframe', { formatSelect: match[1], rank: _rankEP });\r\n                        console.log('\ud83d\udd04 formatChangedInIframe envoy\u00e9:', match[1], '| rank:', _rankEP);\r\n                    }, 100);\r\n                }\r\n\r\n                \/\/ \u2705 Si Cr\u00e9ation est d\u00e9j\u00e0 actif ET format valide (non Vid\u00e9o) \u2192 ouvrir le popup\r\n                var $creationBtn = $droppable.find('.FormatIdCreation');\r\n                var _creationActive = $creationBtn.data('creationActive') === true;\r\n                var _isVideoBtn = $btn.hasClass('FormatIdVideo');\r\n                if (_creationActive ? !_isVideoBtn : false) {\r\n                    var _rankKit2 = _rankEP;\r\n                    var _cSite2 = sessionStorage.getItem('codeSite') || '';\r\n                    var _cPage2 = sessionStorage.getItem('codePage') || '';\r\n                    var _sfx2 = _rankKit2.replace('Ele', '');\r\n                    var _empl2 = (_cSite2 ? (_cPage2 ? _sfx2 : false) : false) ? (_cSite2 + _cPage2 + 'L' + _sfx2) : (sessionStorage.getItem('Commande_Emplacement_Page_Web') || '');\r\n                    var _fmt2 = sessionStorage.getItem('FormatSelect') || '';\r\n                    var _fmtT2 = sessionStorage.getItem('Commande_Format_Transmis') || '';\r\n                    \/\/ R\u00e9activer Vid\u00e9o + restaurer texte si d\u00e9sactiv\u00e9e\r\n                    $droppable.find('.FormatIdVideo').each(function() {\r\n                        jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                        var $lbl = jQuery(this).find('.EspPubFormat');\r\n                        var _orig = $lbl.attr('data-original-text');\r\n                        if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                    });\r\n                    setTimeout(function() {\r\n                        MessageManager.sendToParent('openAdCreatorFromIframe', { formatSelect: _fmt2, formatTransmis: _fmtT2, rankId: _rankKit2, emplacement: _empl2 });\r\n                        console.log('\ud83c\udfa8 Cr\u00e9ation d\u00e9j\u00e0 actif + format s\u00e9lectionn\u00e9 \u2192 ouverture popup apr\u00e8s 2s | rank:', _rankKit2);\r\n                    }, 2000);\r\n                }\r\n            }\r\n        });\r\n        \r\n        \/\/ \u2705 MutationObserver sur les conteneurs de format pour d\u00e9tecter les changements de style\r\n        setTimeout(() => {\r\n            this.observeFormatChanges();\r\n            \r\n            \/\/ Initialiser les couleurs des titres au chargement\r\n            jQuery('.droppable').each((i, el) => {\r\n                this.updateTitleColor(jQuery(el));\r\n            });\r\n            \r\n            \/\/ \u2705 v2.3.4 : Popup \u2192 restaurer FormatIdPopUp + format sous-jacent (sans \u00e9craser applyFormatState)\r\n            if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n                jQuery('.FormatIdPopUp').css({'background-color': '#ffffff'});\r\n                jQuery('.FormatIdPopUp').find('.EspPubFormat').css({'color': '#37D900'});\r\n                \/\/ Restaurer aussi le format sous-jacent dans Ele0A\r\n                var _fmtSS = sessionStorage.getItem('FormatSelect') || '';\r\n                if (_fmtSS) {\r\n                    var _fmtSSNorm = _fmtSS.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                    jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdCreation').each(function() {\r\n                        var _cls = this.className.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                        if (_cls.includes(_fmtSSNorm)) {\r\n                            jQuery(this).css({'background-color': '#ffffff'});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        }\r\n                    });\r\n                }\r\n                jQuery('.droppable').each((i, el) => {\r\n                    this.updateTitleColor(jQuery(el));\r\n                });\r\n            }\r\n        }, 500);\r\n        \r\n        \/\/ \u2705 v1.16.0 : Re-v\u00e9rifier apr\u00e8s un d\u00e9lai plus long (format venant de l'accord\u00e9on via postMessage)\r\n        [1500, 3000].forEach(delay => {\r\n            setTimeout(() => {\r\n                \/\/ \u2705 v2.3.4 : Popup \u2192 restaurer FormatIdPopUp + format sous-jacent\r\n                if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n                    jQuery('.FormatIdPopUp').css({'background-color': '#ffffff'});\r\n                    jQuery('.FormatIdPopUp').find('.EspPubFormat').css({'color': '#37D900'});\r\n                    var _fmtSSR = sessionStorage.getItem('FormatSelect') || '';\r\n                    if (_fmtSSR) {\r\n                        var _fmtSSRNorm = _fmtSSR.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                        jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdCreation').each(function() {\r\n                            var _cls = this.className.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                            if (_cls.includes(_fmtSSRNorm)) {\r\n                                jQuery(this).css({'background-color': '#ffffff'});\r\n                                jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                            }\r\n                        });\r\n                    }\r\n                    jQuery('.droppable').each((i, el) => {\r\n                        this.updateTitleColor(jQuery(el));\r\n                    });\r\n                    return;\r\n                }\r\n                if (sessionStorage.getItem('Formatchoisi') === 'Yes') {\r\n                    jQuery('.droppable').each((i, el) => {\r\n                        this.updateTitleColor(jQuery(el));\r\n                    });\r\n                }\r\n            }, delay);\r\n        });\r\n    },\r\n    \r\n    \/**\r\n     * Attache un MutationObserver sur chaque .EspPubFormatContainer\r\n     * pour d\u00e9tecter tout changement de style (background-color) \r\n     * quel que soit le script qui le d\u00e9clenche\r\n     *\/\r\n    observeFormatChanges() {\r\n        \/\/ \u2705 Guard global anti-boucle \u2014 un seul verrou pour tous les observers\r\n        window._formatObserverLocked = false;\r\n        \r\n        jQuery('.EspPubFormatContainer').each((i, el) => {\r\n            const observer = new MutationObserver(() => {\r\n                if (window._formatObserverLocked) return;\r\n                window._formatObserverLocked = true;\r\n                clearTimeout(window._formatObserverTimer);\r\n                window._formatObserverTimer = setTimeout(() => {\r\n                    const $droppable = jQuery(el).closest('.droppable');\r\n                    this.updateTitleColor($droppable);\r\n                    this.updateReserverCheckboxState($droppable);\r\n\r\n                    \/\/ \u2705 R\u00e8gle Cr\u00e9ation\/Vid\u00e9o : si Vid\u00e9o est actif \u2192 d\u00e9sactiver Cr\u00e9ation\r\n                    var _videoActive = false;\r\n                    $droppable.find('.FormatIdVideo').each(function() {\r\n                        var _bg = this.style.backgroundColor || '';\r\n                        var _isWhite = _bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white';\r\n                        if (!_isWhite) {\r\n                            var _fEl = this.querySelector('.EspPubFormat');\r\n                            if (_fEl) { var _col = _fEl.style.color || ''; _isWhite = _col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900'; }\r\n                        }\r\n                        if (_isWhite) { _videoActive = true; return false; }\r\n                    });\r\n                    if (_videoActive) {\r\n                        $droppable.find('.FormatIdCreation').each(function() {\r\n                            jQuery(this).data('creationActive', false);\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                            jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n                            var $lbl = jQuery(this).find('.EspPubFormat');\r\n                            if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n                            $lbl.text('D\u00e9sactiv\u00e9');\r\n                        });\r\n                    } else {\r\n                        \/\/ R\u00e9activer Cr\u00e9ation si Vid\u00e9o n'est plus actif\r\n                        $droppable.find('.FormatIdCreation').each(function() {\r\n                            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                            var $lbl = jQuery(this).find('.EspPubFormat');\r\n                            var _orig = $lbl.attr('data-original-text');\r\n                            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                        });\r\n                    }\r\n\r\n                    window._formatObserverLocked = false;\r\n                }, 150);\r\n            });\r\n            observer.observe(el, { \r\n                attributes: true, \r\n                attributeFilter: ['style', 'class'] \r\n            });\r\n        });\r\n        console.log('\u2705 MutationObserver attach\u00e9 aux conteneurs de format');\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de communication avec la page parente\r\n *\/\r\nconst MessageManager = {\r\n    sendToParent(type, data = {}) {\r\n        try {\r\n            window.parent.postMessage({\r\n                type: type,\r\n                iframeId: CONFIG.iframeId,\r\n                data: data\r\n            }, '*');\r\n        } catch (error) {\r\n            console.error('Error sending message to parent:', error);\r\n            window.parent.postMessage({\r\n                type: 'error',\r\n                error: error.message,\r\n                iframeId: CONFIG.iframeId\r\n            }, '*');\r\n        }\r\n    },\r\n    \r\n    sendDataToParent(data) {\r\n        this.sendToParent('dataFromIframeEspacePub', data);\r\n    },\r\n    \r\n    sendDelAdToParent(data) {\r\n        this.sendToParent('dataDelAd', data);\r\n    },\r\n    \r\n    prepareUploadData() {\r\n        const keys = [\r\n            'AddNewRefInVosCampagnes',\r\n            'FirstUploadFileorMoved',\r\n            'LoadedPageUrl',\r\n            'codePage',\r\n            'Rank_Emplacement_Page_Web',\r\n            'Commande_Emplacement_Page_Web',\r\n            'dragstart_Commande_Emplacement_Page_Web',\r\n            'dragstart_Rank_Emplacement_Page_Web',  \r\n            'FullPathAdFile',\r\n            'Upload_File_Name',\r\n            'FileReceived',\r\n            'PageWebDisplayed',\r\n            'Commande_Format_Transmis',\r\n            'EspPubLienAnnonce',\r\n            'EnvoiUlterieur',\r\n            'Formatchoisi'\r\n        ];\r\n        \r\n        const data = StateManager.getMultiple(keys);\r\n        \r\n        \/\/ v4.9ds : r\u00e9cup\u00e9ration FullPathAdFile par rank depuis via_fullpath localStorage.\r\n        \/\/   StateManager.FullPathAdFile est GLOBAL (dernier upload). Dans le sc\u00e9nario\r\n        \/\/   \"drop Ele0A + drop Ele1A + R\u00e9server Ele0A + R\u00e9server Ele1A\", au clic R\u00e9server\r\n        \/\/   Ele1A, StateManager.FullPathAdFile peut contenir la mauvaise valeur (Ele0A\r\n        \/\/   s\u00e9lectionn\u00e9 apr\u00e8s) ou \u00eatre vide. via_fullpath localStorage stocke par rank\r\n        \/\/   {Ele0A: URL_Ele0A, Ele1A: URL_Ele1A} \u2014 source de v\u00e9rit\u00e9 plus fiable.\r\n        try {\r\n            const _rankCur = data.Rank_Emplacement_Page_Web || '';\r\n            if (_rankCur) {\r\n                const _fpMap = JSON.parse(localStorage.getItem('via_fullpath') || '{}');\r\n                const _fpForRank = _fpMap[_rankCur];\r\n                if (_fpForRank) {\r\n                    if (data.FullPathAdFile !== _fpForRank) {\r\n                        console.log('\ud83d\udd27 [prepareUploadData] FullPathAdFile \u00e9cras\u00e9 depuis via_fullpath | rank:', _rankCur,\r\n                            '| ancien:', data.FullPathAdFile || '(vide)', '| nouveau:', _fpForRank);\r\n                    }\r\n                    data.FullPathAdFile = _fpForRank;\r\n                }\r\n            }\r\n        } catch(_eFP) { console.warn('[prepareUploadData] lecture via_fullpath \u00e9chou\u00e9e:', _eFP); }\r\n        \r\n        \/\/ v4.9ds : flag HasAdLoadedDom bas\u00e9 sur l'attribut data-via-ad-loaded du droppable\r\n        \/\/   correspondant \u00e0 l'emplacement courant. Permet \u00e0 Panier_manager.txt de\r\n        \/\/   distinguer \"annonce visuellement d\u00e9pos\u00e9e mais pas upload\u00e9e serveur\" (drop\r\n        \/\/   miniature \u2192 image dans DOM, FileReceived='No') du cas \"rien d\u00e9pos\u00e9\".\r\n        try {\r\n            const _emplCur = data.Commande_Emplacement_Page_Web || '';\r\n            if (_emplCur) {\r\n                const _$dropCur = jQuery('.droppable').filter(function() {\r\n                    return jQuery(this).attr('data-via-empl') === _emplCur\r\n                        || (jQuery(this).attr('id') === 'Ele0A' ? _emplCur.indexOf('L0A') >= 0 : false);\r\n                }).first();\r\n                \/\/ Fallback : matcher par rank si data-via-empl absent\r\n                if (!_$dropCur.length ? data.Rank_Emplacement_Page_Web : false) {\r\n                    const _$byRank = jQuery('.droppable[data-via-rank=\"' + data.Rank_Emplacement_Page_Web + '\"]').first();\r\n                    if (_$byRank.length) {\r\n                        data.HasAdLoadedDom = _$byRank.attr('data-via-ad-loaded') === 'true' ? 'Yes' : 'No';\r\n                    }\r\n                } else if (_$dropCur.length) {\r\n                    data.HasAdLoadedDom = _$dropCur.attr('data-via-ad-loaded') === 'true' ? 'Yes' : 'No';\r\n                }\r\n                \/\/ Dernier fallback : tous les droppables avec data-via-ad-loaded=true \u2192 si un seul, c'est lui\r\n                if (data.HasAdLoadedDom === undefined) {\r\n                    const _$any = jQuery('.droppable[data-via-ad-loaded=\"true\"]');\r\n                    data.HasAdLoadedDom = _$any.length > 0 ? 'Yes' : 'No';\r\n                }\r\n            }\r\n        } catch(_eAd) { data.HasAdLoadedDom = 'No'; }\r\n        \r\n        \/\/ \u2705 v2.3.4 : Popup (Ele0A) \u2014 lire le format sous-jacent depuis le DOM\r\n        \/\/ StateManager contient 'PopUp' (virtuel), mais le vrai format est visuellement s\u00e9lectionn\u00e9 dans #Ele0A\r\n        if (data.Rank_Emplacement_Page_Web === 'Ele0A' || sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            var _ele0AFormatDOM = '';\r\n            jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdPopUp').each(function() {\r\n                if (jQuery(this).css('background-color') === 'rgb(255, 255, 255)') {\r\n                    var _cls = this.className || '';\r\n                    var _match = _cls.match(\/FormatId(\\S+)\/);\r\n                    if (_match) { _ele0AFormatDOM = _match[1]; return false; }\r\n                }\r\n            });\r\n            if (_ele0AFormatDOM) {\r\n                data.Commande_Format_Transmis = _ele0AFormatDOM;\r\n            }\r\n        }\r\n        \r\n        console.log('\ud83d\udce4 prepareUploadData:', {\r\n            FirstUploadFileorMoved: data.FirstUploadFileorMoved,\r\n            dragstart_Commande_Emplacement_Page_Web: data.dragstart_Commande_Emplacement_Page_Web,\r\n            Commande_Emplacement_Page_Web: data.Commande_Emplacement_Page_Web\r\n        });\r\n    \r\n        return data;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de rendu des aper\u00e7us de fichiers\r\n *\/\r\nconst PreviewRenderer = {\r\n    _getPositionLibelle(rankId) {\r\n        if (!rankId) return '';\r\n        var match = rankId.match(\/Ele(\\d+)[A-Z]\/);\r\n        if (!match) return '';\r\n        var rang = parseInt(match[1]);\r\n        if (rang === 0) return 'Pop-up';\r\n        if (rang === 1 || rang === 2) return 'Haut de page';\r\n        return 'Corps de page';\r\n    },\r\n    renderVideo(objectUrl, fileName, $dropZone) {\r\n        const videoElement = jQuery('<video controls autoplay muted>').attr({\r\n            'src': objectUrl,\r\n            'width': 'auto',\r\n            'height': 'auto',\r\n            'id': fileName\r\n        }).css({\r\n            'max-width': '100%',\r\n            'max-height': '100%'\r\n        });\r\n        \r\n        if (UIManager.isDesktop()) {\r\n            $dropZone.empty().append(videoElement.clone());\r\n            jQuery('#ApercuMobile').css({\r\n                'display': 'flex',\r\n                'justify-content': 'center',\r\n                'align-items': 'center'\r\n            }).append(videoElement);\r\n        } else {\r\n            const img = document.createElement('img');\r\n            img.src = objectUrl;\r\n            img.style.width = 'auto';\r\n            img.style.height = 'auto';\r\n            img.style.maxWidth = 'calc(100% - 4px)';  \/\/ \u2705 v2.4.5 : 2px retrait inset box-shadow\r\n            img.style.maxHeight = '115px';\r\n            \r\n            const container = $dropZone[0];\r\n            while (container.firstChild) {\r\n                container.removeChild(container.firstChild);\r\n            }\r\n            container.appendChild(img);\r\n        }\r\n        \r\n        StateManager.setMultiple({\r\n            'Commande_Format': 'Vid\u00e9o',\r\n            'Commande_Format_Transmis': 'Vid\u00e9o',\r\n            'videoSrc': objectUrl,\r\n            'FormatAnnonceSelection': 'Yes',\r\n            'PositionAnnonceSelection': 'Yes'\r\n        });\r\n        \r\n        jQuery('#FormatDataStep3').html('Vid\u00e9o').css({'color': '#56BE50'});\r\n        jQuery('#TarifDataStep3').css({'color': '#96894D'});\r\n    },\r\n    \r\n    renderImage(objectUrl, $dropZone) {\r\n        \/\/ v4.9ds : utiliser la hauteur du dropZone lui-m\u00eame (pos\u00e9e par _applyDzMinH)\r\n        \/\/   au lieu de la hauteur du HTMLUploadfileConteneur parent.\r\n        \/\/   Le UFC inclut header (~24px) + footer (~46px) en plus du dropZone, donc utiliser\r\n        \/\/   sa hauteur produit une image plus haute que le dropZone \u2192 crop par overflow:hidden\r\n        \/\/   du wrapper. La hauteur du dropZone est la cible exacte de la zone visible.\r\n        let maxHeight;\r\n        if (UIManager.isMobile()) {\r\n            maxHeight = 105;\r\n        } else {\r\n            \/\/ Lire la hauteur effective du dropZone (d\u00e9j\u00e0 pos\u00e9e par _applyDzMinH au moment du d\u00e9p\u00f4t)\r\n            var _dzH = $dropZone.height() || 0;\r\n            if (_dzH > 50) {\r\n                maxHeight = _dzH;\r\n            } else {\r\n                \/\/ Fallback : lire UFC mais retirer estimation header+footer (~70px)\r\n                var _ufcH = $dropZone.closest('.HTMLUploadfileConteneur').height() || 0;\r\n                maxHeight = Math.max(0, _ufcH - 70);\r\n                if (maxHeight < 50) { maxHeight = 200; }  \/\/ dernier recours\r\n            }\r\n        }\r\n    \r\n        $dropZone.css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center',\r\n            'width': '100%',\r\n            'height': maxHeight + 'px',\r\n            'overflow': 'hidden',\r\n            'position': UIManager.isMobile() ? 'relative' : '',\r\n            'top': UIManager.isMobile() ? '45px' : '',\r\n            'padding': UIManager.isMobile() ? '0 2px' : ''  \/\/ \u2705 v2.4.5 : 2px retrait inset box-shadow mobile\r\n        });\r\n    \r\n        \/\/ \u2705 v1.19.2 : Image charg\u00e9e en arri\u00e8re-plan, remplace le message d'attente au onload\r\n        \/\/ \u2705 v2.4.5 : max-width calc(100%-4px) sur mobile pour ne pas recouvrir le box-shadow inset 2px\r\n        \/\/ v4.9ds : width:100% + height:100% + object-fit:contain \u2014 l'image remplit la box du\r\n        \/\/   dropZone (comme Ele0A) tout en restant enti\u00e8re. Letterbox automatique si ratio diff.\r\n        var _mxw = UIManager.isMobile() ? 'calc(100% - 4px)' : '100%';\r\n        var img = new Image();\r\n        img.style.cssText = 'max-width:' + _mxw + '; max-height:' + maxHeight + 'px; width:100%; height:100%; object-fit:contain;';\r\n        img.onload = function() {\r\n            $dropZone.empty().append(img);\r\n        };\r\n        img.onerror = function() {\r\n            $dropZone.html('<img decoding=\"async\" src=\"' + objectUrl + '\" style=\"max-width:' + _mxw + '; max-height:' + maxHeight + 'px; width:100%; height:100%; object-fit:contain;\">');\r\n        };\r\n        img.src = objectUrl;\r\n    \r\n        StateManager.setMultiple({\r\n            'Commande_Format_Transmis': 'Image',\r\n            'FormatAnnonceSelection': 'Yes',\r\n            'PositionAnnonceSelection': 'Yes'\r\n        });\r\n    \r\n        console.log('Image ajout\u00e9e avec succ\u00e8s');\r\n    },\r\n    \r\n    async renderWord(fileObj, $dropZone) {\r\n        return new Promise((resolve, reject) => {\r\n            const reader = new FileReader();\r\n            \r\n            reader.onload = async (e) => {\r\n                try {\r\n                    const arrayBuffer = e.target.result;\r\n                    const result = await mammoth.convertToHtml({arrayBuffer: arrayBuffer});\r\n                    \r\n                    const htmlContent = result.value;\r\n                    const tempContainer = document.createElement('div');\r\n                    tempContainer.innerHTML = htmlContent;\r\n                    \r\n                    const textContent = tempContainer.textContent || \"\";\r\n                    const normalizedText = this.normalizeText(textContent);\r\n                    const firstImage = tempContainer.querySelector('img');\r\n                    \r\n                    const titleText = this.findDocumentTitle(normalizedText);\r\n                    \r\n                    if (firstImage) {\r\n                        this.renderDocumentPreview(\r\n                            $dropZone,\r\n                            firstImage.src,\r\n                            titleText,\r\n                            textContent,\r\n                            htmlContent\r\n                        );\r\n                    } else {\r\n                        \/\/ \u2705 v1.19.0 : Accepter les documents sans image\r\n                        this.renderDocumentPreview(\r\n                            $dropZone,\r\n                            null,\r\n                            titleText,\r\n                            textContent,\r\n                            htmlContent\r\n                        );\r\n                        console.log('\u2139\ufe0f Document Word sans image \u2014 aper\u00e7u texte seul');\r\n                    }\r\n                    resolve();\r\n                } catch (error) {\r\n                    $dropZone.html(`<p style=\"color: #FB5E2A; font-weight: 600; font-family: Roboto, Arial, sans-serif; font-size: 12px; text-align: center; padding: 15px;\">Erreur lors de la lecture du document<\/p>`);\r\n                    reject(error);\r\n                }\r\n            };\r\n            \r\n            reader.readAsArrayBuffer(fileObj);\r\n        });\r\n        \r\n        this.finalizeRedactionnelUpload();\r\n    },\r\n    \r\n    renderDocumentPreview($dropZone, imageSrc, title, text, htmlContent = null) {\r\n        \/\/ \u2705 v1.19.0 : Deux layouts \u2014 avec image ou texte seul\r\n        let previewHtml;\r\n\r\n        if (imageSrc) {\r\n            \/\/ Layout avec miniature image\r\n            previewHtml = `\r\n                <div class=\"doc-preview-container\">\r\n                    <div class=\"doc-preview-thumbnail\">\r\n                        <img decoding=\"async\" src=\"${imageSrc}\" alt=\"Document image\">\r\n                    <\/div>\r\n                    <div class=\"doc-preview-info\">\r\n                        <div class=\"doc-preview-title\" id=\"AdTitle\">${title}<\/div>\r\n                        <div class=\"doc-preview-description\" id=\"AdText\">${this.getTextPreview(text, 13)}<\/div>\r\n                        <div class=\"doc-preview-readmore\" id=\"AdLirelasuite\">Ouvrir et visualiser<\/div>\r\n                        <div class=\"doc-preview-FullPathAdFile\" id=\"AdFullPathAdFile\">${StateManager.get(\"FullPathAdFile\")}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n        } else {\r\n            \/\/ Layout texte seul (sans image)\r\n            previewHtml = `\r\n                <div class=\"doc-preview-container doc-preview-noimage\">\r\n                    <div class=\"doc-preview-icon\">\r\n                        <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#213864\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n                            <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"\/><polyline points=\"14 2 14 8 20 8\"\/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"\/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"\/><polyline points=\"10 9 9 9 8 9\"\/>\r\n                        <\/svg>\r\n                    <\/div>\r\n                    <div class=\"doc-preview-info doc-preview-info-full\">\r\n                        <div class=\"doc-preview-title\" id=\"AdTitle\">${title}<\/div>\r\n                        <div class=\"doc-preview-description\" id=\"AdText\">${this.getTextPreview(text, 25)}<\/div>\r\n                        <div class=\"doc-preview-readmore\" id=\"AdLirelasuite\">Ouvrir et visualiser<\/div>\r\n                        <div class=\"doc-preview-FullPathAdFile\" id=\"AdFullPathAdFile\">${StateManager.get(\"FullPathAdFile\")}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n        }\r\n        \r\n        $dropZone.html(previewHtml + this.getPreviewStyles());\r\n        \r\n        if (htmlContent) {\r\n            this.attachWordPreviewHandler($dropZone, title, htmlContent);\r\n        }\r\n    },\r\n    \r\n    attachWordPreviewHandler($dropZone, titleText, htmlContent) {\r\n        var $droppable = $dropZone.closest('.droppable');\r\n\r\n        \/\/ \u2705 Adapter le libell\u00e9 selon le type de document\r\n        var isInterview = (titleText || '').toLowerCase().indexOf('interview') !== -1;\r\n        var formatLabel = isInterview ? 'l\\'interview' : 'le communiqu\u00e9';\r\n        $droppable.find('.doc-preview-readmore').text('Ouvrir et visualiser ' + formatLabel);\r\n\r\n        \/\/ \u2705 v2.0.11 : D\u00e9l\u00e9gation d'\u00e9v\u00e9nement \u2014 plus robuste sur mobile (survit aux manipulations DOM)\r\n        $droppable.off('click.docpreview').on('click.docpreview', '.doc-preview-readmore', (event) => {\r\n            event.preventDefault();\r\n            event.stopPropagation();\r\n            \r\n            if (htmlContent) {\r\n                var popupTitle = isInterview ? 'Interview' : 'Communiqu\u00e9';\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    htmlContent: htmlContent\r\n                });\r\n            } else {\r\n                console.warn('Le document est encore en cours de traitement.');\r\n            }\r\n        });\r\n    },\r\n    \r\n    normalizeText(text) {\r\n        return text\r\n            .toLowerCase()\r\n            .normalize(\"NFD\")\r\n            .replace(\/[\\u0300-\\u036f]\/g, \"\");\r\n    },\r\n    \r\n    findDocumentTitle(normalizedText) {\r\n        const foundKeyword = CONFIG.keywords.find(keyword => \r\n            normalizedText.includes(keyword.normal)\r\n        );\r\n        if (foundKeyword) return foundKeyword.display;\r\n        \r\n        \/\/ \u2705 v1.19.1 : Fallback \u2014 utiliser le format s\u00e9lectionn\u00e9 (FormatSelect)\r\n        var formatSelect = (sessionStorage.getItem('FormatSelect') || '').toLowerCase();\r\n        if (formatSelect.indexOf('interview') !== -1) return 'Interview';\r\n        if (formatSelect.indexOf('communiq') !== -1) return 'Communiqu\u00e9';\r\n        \r\n        return \"Document\";\r\n    },\r\n    \r\n    getTextPreview(text, maxWords) {\r\n        const words = text.split(' ');\r\n        if (words.length > maxWords) {\r\n            return words.slice(0, maxWords).join(' ') + ' ...';\r\n        }\r\n        return text;\r\n    },\r\n    \r\n    getPreviewStyles() {\r\n        return `\r\n            <style>\r\n                @import url('https:\/\/fonts.googleapis.com\/css2?family=Roboto:wght@400;600&display=swap');\r\n                \r\n                .doc-preview-container {\r\n                    \/* v4.9ds : -4px largeur + 2px de chaque c\u00f4t\u00e9 pour laisser voir le liser\u00e9 vert *\/\r\n                    margin: 0px 2px;\r\n                    display: flex;\r\n                    width: calc(100% - 4px);\r\n                    max-height: 140px;\r\n                    overflow: hidden;\r\n                    box-sizing: border-box;\r\n                    background: white;\r\n                    background-color: #f8f8f8;\r\n                }\r\n                .doc-preview-thumbnail {\r\n                    width: 50%; \r\n                    height: 130px;\r\n                    overflow: hidden;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                    margin-top: 0px;\r\n                }\r\n                .doc-preview-thumbnail img {\r\n                    max-width: 75%;\r\n                    max-height: 75%;\r\n                    object-position: center;\r\n                    object-fit: fill;\r\n                }\r\n                .doc-preview-info {\r\n                    width: 50%;\r\n                    height: 130px; \r\n                    flex: 1;\r\n                    padding: 10px;\r\n                    display: flex;\r\n                    flex-direction: column; \r\n                }\r\n                .doc-preview-title {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #213864;\r\n                    font-size: 14px;\r\n                    font-weight: 600;\r\n                    text-align: left;\r\n                    margin-bottom: 5px;\r\n                }\r\n                .doc-preview-description {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #213864;\r\n                    font-size: 11px;\r\n                    font-weight: 400;\r\n                    text-align: left;\r\n                    overflow: hidden;\r\n                    margin-bottom: 5px;\r\n                    flex: 1;\r\n                }\r\n                .doc-preview-readmore {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #958848;\r\n                    font-size: 13px;\r\n                    font-weight: 600;\r\n                    text-align: left;\r\n                    cursor: pointer;\r\n                    padding: 4px 0;\r\n                    -webkit-tap-highlight-color: rgba(149,136,72,0.2);\r\n                }\r\n                @media (max-width: 999px) {\r\n                    .doc-preview-readmore { font-size: 12px; }\r\n                    \/* \u2705 Laisser 3px tout autour pour que le liser\u00e9 vert (box-shadow\/outline sur parent) soit visible *\/\r\n                    .doc-preview-container {\r\n                        width: calc(100% - 16px) !important;\r\n                        max-height: 109px !important;\r\n                        margin: 3px !important;\r\n                        margin-top: 23px !important;\r\n                        box-sizing: border-box !important;\r\n                    }\r\n                }\r\n                .doc-preview-FullPathAdFile {\r\n                    display: none;\r\n                }\r\n                \/* \u2705 v1.19.0 : Layout sans image *\/\r\n                .doc-preview-noimage {\r\n                    flex-direction: row;\r\n                    align-items: flex-start;\r\n                    gap: 10px;\r\n                    padding: 8px 10px;\r\n                }\r\n                .doc-preview-icon {\r\n                    flex-shrink: 0;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                    width: 44px;\r\n                    height: 44px;\r\n                    background: #eef2f7;\r\n                    border-radius: 6px;\r\n                    margin-top: 4px;\r\n                }\r\n                .doc-preview-info-full {\r\n                    width: 100%;\r\n                    height: auto;\r\n                    max-height: 130px;\r\n                }\r\n                .doc-preview-noimage .doc-preview-description {\r\n                    font-size: 11px;\r\n                    line-height: 1.35;\r\n                    max-height: 60px;\r\n                    overflow: hidden;\r\n                }\r\n            <\/style>\r\n        `;\r\n    },\r\n    \r\n    finalizeRedactionnelUpload() {\r\n        \/\/ \u2705 v1.19.5 : D\u00e9terminer le format r\u00e9dactionnel correct\r\n        \/\/ Si le format s\u00e9lectionn\u00e9 est \"Interview\", on garde \"Interview\"\r\n        \/\/ Sinon on met \"Communiqu\u00e9\" par d\u00e9faut (au lieu de \"R\u00e9dactionnel\" qui n'est pas un format tarifaire valide)\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') || '';\r\n        var formatTransmis = 'Communiqu\u00e9'; \/\/ Par d\u00e9faut\r\n        \r\n        if (formatSelect.toLowerCase().indexOf('interview') !== -1) {\r\n            formatTransmis = 'Interview';\r\n        } else if (formatSelect.toLowerCase().indexOf('communiq') !== -1) {\r\n            formatTransmis = 'Communiqu\u00e9';\r\n        }\r\n        \r\n        StateManager.set('Commande_Format_Transmis', formatTransmis);\r\n        console.log('\u2705 Format r\u00e9dactionnel d\u00e9termin\u00e9:', formatTransmis, '(FormatSelect:', formatSelect, ')');\r\n        \r\n        if (StateManager.get(\"TarifDirectSelectionne\") === 'Yes') {\r\n            const formatText = jQuery('#FormatDataStep3').text();\r\n            if (formatText !== 'Vid\u00e9o' ? formatText !== 'Banni\u00e8re' : false) {\r\n                jQuery('#form-field-RedactionnelSelection')\r\n                    .val(formatText)\r\n                    .css({'color': '#56BE50'});\r\n            }\r\n        }\r\n        \r\n        RedactionnelDepose();\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'upload\r\n *\/\r\nconst UploadManager = {\r\n    isRunning: false,\r\n    \r\n    \/\/ \u2705 Reset manuel espace publicitaire\r\n    resetEspaceManuel: function($droppable) {\r\n        console.log('\ud83d\udd27 Reset manuel espace publicitaire');\r\n        \r\n        var $dropZone = $droppable.find('#drop_file_zone_achat');\r\n        var $uploadContainer = $droppable.find('.HTMLUploadfileConteneur');\r\n        var $container = $droppable.find('.OrdiMobileConteneurClass');\r\n        \r\n        \/\/ 1. Vider la zone de drop et restaurer le HTML par d\u00e9faut\r\n        $dropZone.empty().html(\r\n            '<div id=\"drag_upload_file_achat\">' +\r\n                '<p class=\"UploadIci\" style=\"color: #FB5E2A; font-weight: 600;\">Ici glisser \u2013 d\u00e9poser ou<br>t\u00e9l\u00e9charger une annonce<\/p>' +\r\n            '<\/div>'\r\n        );\r\n        \r\n        \/\/ 2. Afficher les \u00e9l\u00e9ments cach\u00e9s lors de l'upload\r\n        $droppable.find('.UploadIci').show();\r\n        $droppable.find('.EnvoiUlterieurTexte').show();\r\n        $droppable.find('.EnvoiUlterieurContainer').show();\r\n        $droppable.find('.EspPubFormatMainContainer').show();\r\n        $droppable.find('.EspPubFormatListe').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireClass').show();\r\n        $droppable.find('.GlisserDeposerConteneur').show();\r\n        $droppable.find('.OUClass').show();\r\n        $droppable.find('.TexteMobileAnnonce').show();\r\n        $droppable.find('.PositionReference').show();\r\n        $droppable.find('.ClassHdpCdp').show();\r\n        $droppable.find('.ClassRefEsp').show();\r\n        \r\n        \/\/ 3. Cacher les \u00e9l\u00e9ments d'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle').hide();\r\n        $droppable.find('#CroixResetAnnonce').hide();\r\n        $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        $droppable.find('.DeplaceAnnonce').hide();\r\n        $droppable.find('.RefEspacePublicitaire').hide();\r\n        $droppable.find('.AdDroppedTextNotDisplayed').hide();\r\n        \r\n        \/\/ 4. Reset des styles\r\n        $uploadContainer.css({\r\n            'border': 'none',\r\n            'background-color': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': ''\r\n        });\r\n        \r\n        $container.css({\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'top': '',\r\n            'height': ''\r\n        });\r\n        \r\n        \/\/ \u2705 Reset des positionnements desktop appliqu\u00e9s par adjustDesktopLayout\r\n        $droppable.closest('.ToBeHidden').css({'top': '', 'min-height': ''});\r\n        $droppable.parent().css('overflow', '');\r\n        \/\/ \u2705 v2.0.11 : Cleanup event namespace docpreview\r\n        $droppable.off('click.docpreview');\r\n        \r\n        \/\/ 5. Reset des formats s\u00e9lectionn\u00e9s\r\n        $droppable.find('.EspPubFormatContainer').css({\r\n            'background-color': '',\r\n            'border': ''\r\n        });\r\n        \r\n        \/\/ 6. Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ 7. Restaurer .PositionEspacePublicitaireContainer et .ReserverContainer\r\n        $droppable.find('.PositionEspacePublicitaireContainer').show();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \r\n        console.log('\u2705 Reset manuel termin\u00e9');\r\n    },\r\n    \r\n    async handleFileUpload(fileObj, $dropZone) {\r\n        console.log('fileObj:', fileObj);\r\n        \r\n        \/\/ \u2705 v2.4.6 : Contr\u00f4les d'extension EN PREMIER \u2014 avant tout affichage\r\n        const extension = FileManager.getExtension(fileObj.name);\r\n        StateManager.set('FileExtension', extension);\r\n        console.log('FileExtension:', extension);\r\n        \r\n        \/\/ \u2705 v2.6 : jpg\/jpeg accept\u00e9s sur desktop et mobile\r\n        \r\n        if (!FileManager.isAllowedExtension(extension)) {\r\n            UIManager.showFormatError($dropZone);\r\n            return;\r\n        }\r\n\r\n        \/\/ \u2705 v4.9o : Guard type de fichier vs format s\u00e9lectionn\u00e9\r\n        var _$droppableGuard = $dropZone.closest('.droppable');\r\n        if (!_fromMiniatureGuard) {\r\n            if (!_$droppableGuard.length) { _$droppableGuard = jQuery(); }\r\n            var _fmtChkLabel = '';\r\n            var _fmtClsMap = {\r\n                'formatidbanniere':   'banni\u00e8re',\r\n                'formatidparrainage': 'parrainage',\r\n                'formatidcommunique': 'communiqu\u00e9',\r\n                'formatidinterview':  'interview',\r\n                'formatidvideo':      'vid\u00e9o'\r\n            };\r\n            _$droppableGuard.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').each(function() {\r\n                var _sel = false;\r\n                var _bgV = this.style.backgroundColor || '';\r\n                if (_bgV === 'rgb(255, 255, 255)') { _sel = true; }\r\n                if (!_sel) { if (_bgV === '#ffffff' || _bgV === 'white') { _sel = true; } }\r\n                if (!_sel) {\r\n                    var _fEl = this.querySelector('.EspPubFormat');\r\n                    if (_fEl) {\r\n                        var _col = _fEl.style.color || '';\r\n                        if (_col === 'rgb(55, 217, 0)') { _sel = true; }\r\n                        if (!_sel) { if (_col.toLowerCase() === '#37d900') { _sel = true; } }\r\n                    }\r\n                }\r\n                if (_sel) {\r\n                    var _clsN = this.className.toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '');\r\n                    var _keys = Object.keys(_fmtClsMap);\r\n                    for (var _ki = 0; _ki < _keys.length; _ki++) {\r\n                        if (_clsN.indexOf(_keys[_ki]) !== -1) { _fmtChkLabel = _fmtClsMap[_keys[_ki]]; return false; }\r\n                    }\r\n                }\r\n            });\r\n            if (_fmtChkLabel) {\r\n                var _extChk  = extension.toLowerCase();\r\n                var _kindChk = CONFIG.allowedExtensions.video.indexOf(_extChk) !== -1 ? 'video'\r\n                             : CONFIG.allowedExtensions.image.indexOf(_extChk) !== -1 ? 'image'\r\n                             : 'document';\r\n                var _imgLbls = ['banni\u00e8re', 'parrainage'];\r\n                var _docLbls = ['communiqu\u00e9', 'interview'];\r\n                var _vidLbls = ['vid\u00e9o'];\r\n                var _compat  = true;\r\n                if (_kindChk === 'image')    { _compat = (_imgLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk === 'document') { _compat = (_docLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk === 'video')    { _compat = (_vidLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk) { if (!_compat) {\r\n                    UIManager.showFormatError($dropZone, 'Merci de d\u00e9poser un format ' + _fmtChkLabel);\r\n                    console.warn('\ud83d\udeab D\u00e9p\u00f4t refus\u00e9 : fichier ' + _kindChk + ' incompatible avec format ' + _fmtChkLabel);\r\n                    return;\r\n                } }\r\n            }\r\n        }\r\n\r\n        \/\/ v4.9ci : Guard format \u2014 refuser le d\u00e9p\u00f4t si aucun format n'a \u00e9t\u00e9 s\u00e9lectionn\u00e9\r\n        \/\/          DANS LE DROPPABLE cible (pas globalement).\r\n        \/\/ Exception : drop depuis la miniature kit (le format a d\u00e9j\u00e0 \u00e9t\u00e9 choisi dans le kit\r\n        \/\/ juste avant le drop).\r\n        \/\/ v4.9co : D\u00e9tection locale via la classe visuelle \u2014 un EspPubFormatContainer est\r\n        \/\/          \"s\u00e9lectionn\u00e9\" si son EspPubFormat a la couleur verte (#37D900) OU son fond est #ffffff.\r\n        \/\/          On exclut FormatIdCreation et FormatIdPopUp qui ont leur propre \u00e9tat.\r\n        var _fromKitDropGuard = _$droppableGuard.length ? (_$droppableGuard[0].getAttribute('data-kit-drop') === 'true') : false;\r\n        var _fromMiniatureGuard = window._dropFromMiniature || _fromKitDropGuard;\r\n        if (!_fromMiniatureGuard ? _$droppableGuard.length : false) {\r\n            var _hasLocalFmt = false;\r\n            _$droppableGuard.find('.EspPubFormatContainer').each(function() {\r\n                if (jQuery(this).hasClass('FormatIdCreation')) return;\r\n                if (jQuery(this).hasClass('FormatIdPopUp')) return;\r\n                \/\/ Crit\u00e8re 1 : fond blanc inline (pos\u00e9 par handler click)\r\n                var _bg = this.style.backgroundColor || '';\r\n                if (_bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white') {\r\n                    _hasLocalFmt = true;\r\n                    return false; \/\/ break\r\n                }\r\n                \/\/ Crit\u00e8re 2 : texte vert sur .EspPubFormat\r\n                var _fmt = this.querySelector('.EspPubFormat');\r\n                if (_fmt) {\r\n                    var _col = _fmt.style.color || '';\r\n                    if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') {\r\n                        _hasLocalFmt = true;\r\n                        return false;\r\n                    }\r\n                }\r\n            });\r\n            if (!_hasLocalFmt) {\r\n                var _rankId = _$droppableGuard.attr('id') || '?';\r\n                console.warn('\ud83d\udeab D\u00e9p\u00f4t refus\u00e9 : aucun format s\u00e9lectionn\u00e9 dans ce droppable | rank=' + _rankId);\r\n                \/\/ R\u00e9activer le message d'erreur format\r\n                FormatUIManager.flashTitle(_$droppableGuard.find('.HTMLUploadfileConteneur, #UploadFileConteneur').first());\r\n                UIManager.showFormatError($dropZone, 'Merci de s\u00e9lectionner un format d\\'annonce avant de d\u00e9poser votre fichier');\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v1.19.5 : Afficher le message d'attente seulement si le format est valide\r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        this.prepareUIForUpload($dropZone);\r\n        \r\n        const formData = new FormData();\r\n        formData.append('file', fileObj);\r\n        formData.append('action', 'upload_annonce_file_v3');\r\n        \r\n        UIManager.showUploadProgress($dropZone);\r\n        \r\n        try {\r\n            const response = await this.sendUploadRequest(formData);\r\n            await this.handleUploadResponse(response, fileObj, $dropZone, extension);\r\n        } catch (error) {\r\n            console.error('Upload error:', error);\r\n        }\r\n    },\r\n    \r\n    prepareUIForUpload($dropZone) {\r\n        if (!UIManager.isMobile()) {\r\n            jQuery('.ChoisirEspacePublicitaireClass').show();\r\n            jQuery('.ChoisirEspacePublicitaire2ndLigne').show();\r\n            jQuery('.GlisserDeposerConteneur').show();\r\n            jQuery('.OUClass').show();\r\n            \/\/ \u2705 Cibler uniquement le droppable courant \u2014 ne pas r\u00e9afficher sur les espaces d\u00e9j\u00e0 upload\u00e9s\r\n            $dropZone.closest('.droppable').find('.UploadIci').show();\r\n        }\r\n        \r\n        if (StateManager.get(\"PopUpChoice\") === 'Yes') {\r\n            \/\/ \u2705 Ne masquer DeplaceAnnonce QUE sur Ele0A \u2014 pas sur Ele1A qui a deja une annonce chargee\r\n            $dropZone.closest('.OrdiMobileConteneurClass').find('.DeplaceAnnonce').each(function() {\r\n                var _d = jQuery(this).closest('.droppable')[0];\r\n                if (_d ? _d.getAttribute('data-via-ad-loaded') === 'true' : false) { return; }\r\n                jQuery(this).hide();\r\n            });\r\n            $('.AnnonceDragIcone').show().find('img').attr('alt', '');\r\n        } else {\r\n            $('.AnnonceDragIcone').hide();\r\n        }\r\n        \r\n        if (StateManager.get(\"Commande_Page\") === 'Home Page') {\r\n            jQuery('#HPTarifConteneur').css({'background-color': '#B5FFB1'});\r\n        }\r\n        \r\n        jQuery('.ChoisirEspacePublicitaireClass').css({'color': '#ffffff'});\r\n        jQuery('.InterfaceTitreDore').not('#PageWebTitreDore')\r\n            .html(\"Merci de choisir les \u00e9l\u00e9ments de votre campagne publicitaire\");\r\n        jQuery('#ProcessCommande').show();\r\n    },\r\n    \r\n    sendUploadRequest(formData) {\r\n        \/\/ \u2705 v2.6 : dataType 'text' pour eviter parsererror si texte parasite apres le JSON\r\n        return jQuery.ajax({\r\n            url: CONFIG.ajaxUrl,\r\n            type: 'POST',\r\n            data: formData,\r\n            cache: false,\r\n            contentType: false,\r\n            processData: false,\r\n            dataType: 'text'\r\n        }).then(function(text) {\r\n            \/\/ Extraire le JSON meme si du texte parasite suit\r\n            var _json = null;\r\n            try {\r\n                var _match = text.match(\/(\\{[\\s\\S]*?\\})(?:[^{]|$)\/);\r\n                _json = JSON.parse(_match ? _match[1] : text);\r\n            } catch(_e) {\r\n                return jQuery.Deferred().reject({ responseText: text }).promise();\r\n            }\r\n            \/\/ Normaliser le format vers {success, data} attendu par handleUploadResponse\r\n            if (_json.status === 'success') {\r\n                var _fp = _json.file_path || '';\r\n                var _baseUrl = CONFIG.ajaxUrl.replace('\/wp-admin\/admin-ajax.php', '\/wp-admin\/');\r\n                return {\r\n                    success: true,\r\n                    data: {\r\n                        file_url:  _baseUrl + _fp,\r\n                        file_name: _fp.replace(\/^.*\\\/\/, ''),\r\n                        file_path: _fp\r\n                    }\r\n                };\r\n            }\r\n            \/\/ Format WordPress standard {success, data} - retourner tel quel\r\n            return _json;\r\n        });\r\n    },\r\n    \r\n    async handleUploadResponse(response, fileObj, $dropZone, extension) {\r\n        console.log('\u2705 R\u00e9ponse re\u00e7ue');\r\n        \r\n        if (response.responseJSON) {\r\n            response = response.responseJSON;\r\n        }\r\n        \r\n        console.log('\u2705 Response pars\u00e9e:', response);\r\n        \r\n        if (!response.success || !response.data) {\r\n            \/\/ \u2705 v2.1.2 : Si c'est une restauration (_isAdRestoration = 'Yes'),\r\n            \/\/ le fichier existe d\u00e9j\u00e0 sur le serveur \u2192 afficher l'image depuis le fileObj local\r\n            if (StateManager.get('_isAdRestoration') === 'Yes' ? fileObj : false) {\r\n                console.log('\ud83d\udd04 Restauration: upload \u00e9chou\u00e9 (fichier existant) \u2192 rendu local');\r\n                \r\n                FileManager.createObjectUrl(fileObj);\r\n                StateManager.set('FileReceived', 'Yes');\r\n                \r\n                \/\/ \u2705 v2.1.2 : Restaurer l'\u00e9tat EnvoiUlterieur depuis le sessionStorage parent\r\n                var _restoredEnvoi = sessionStorage.getItem('_restoredEnvoiUlterieur');\r\n                if (_restoredEnvoi === 'true') {\r\n                    StateManager.set('EnvoiUlterieur', 'true');\r\n                    StateManager.set('FileReceived', 'No');\r\n                    StateManager.set('FullPathAdFile', '');\r\n                    console.log('\ud83d\udce4 Restauration EnvoiUlterieur = true');\r\n                }\r\n                \r\n                var _objUrl = StateManager.get('objectUrl');\r\n                var _fileType = FileManager.getFileType(extension);\r\n                \r\n                StateManager.set('FormatReconnu', 'No');\r\n                \r\n                switch (_fileType) {\r\n                    case 'video':\r\n                        PreviewRenderer.renderVideo(_objUrl, fileObj.name, $dropZone);\r\n                        StateManager.set('FormatReconnu', 'Yes');\r\n                        break;\r\n                    case 'image':\r\n                        PreviewRenderer.renderImage(_objUrl, $dropZone);\r\n                        StateManager.set('FormatReconnu', 'Yes');\r\n                        break;\r\n                    case 'document':\r\n                        \/\/ \u2705 v2.1.2 : Documents Word\/PDF aussi en restauration\r\n                        await this.handleDocumentUpload(extension, fileObj, $dropZone);\r\n                        break;\r\n                }\r\n                \r\n                UIManager.updateAfterSuccessfulUpload($dropZone);\r\n                \r\n                if (StateManager.get('FormatReconnu') === 'Yes') {\r\n                    this.finalizeUpload($dropZone);\r\n                    $('#MsgSelectEspace').hide();\r\n                }\r\n                \r\n                FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n                console.log('\u2705 Restauration visuelle termin\u00e9e (upload bypass)');\r\n                StateManager.set('_isAdRestoration', 'No');\r\n            }\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 SAUVEGARDER les infos de d\u00e9placement AVANT updateStateAfterUpload\r\n        const isMoved = StateManager.get('FirstUploadFileorMoved') === 'Moved';\r\n        const dragstartRankId = StateManager.get('dragstart_Rank_Emplacement_Page_Web');\r\n        const currentRankId = StateManager.get('Rank_Emplacement_Page_Web');\r\n        const shouldResetOldSpace = isMoved ? (dragstartRankId ? dragstartRankId !== currentRankId : false) : false;\r\n        \/\/ \u2705 Bug 10 : self-drop (m\u00eame espace) \u2014 nettoyer pendingAd du rank source\r\n        \/\/   Le drop sur soi-m\u00eame ne passe pas par dataDelAd, donc pendingAd persiste\r\n        \/\/   et provoque la r\u00e9apparition de l'annonce lors d'un d\u00e9placement ult\u00e9rieur\r\n        if (isMoved ? (dragstartRankId ? (dragstartRankId !== 'No' ? dragstartRankId === currentRankId : false) : false) : false) {\r\n            sessionStorage.removeItem('pendingAd_' + dragstartRankId);\r\n            console.log('[Bug10] self-drop d\u00e9tect\u00e9 \u2014 pendingAd_' + dragstartRankId + ' effac\u00e9');\r\n        }\r\n        \r\n        console.log('\ud83d\udd0d D\u00e9placement?', { isMoved, dragstartRankId, currentRankId, shouldResetOldSpace });\r\n        \r\n        this.updateStateAfterUploadWithoutReset(response, fileObj);\r\n        \r\n        const fileType = FileManager.getFileType(extension);\r\n        \r\n        StateManager.set('FormatReconnu', 'No');\r\n        \r\n        \/\/ \u2705 v1.19.2 : Message d'attente IMM\u00c9DIAT avant le rendu de l'annonce\r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        switch (fileType) {\r\n            case 'video':\r\n                PreviewRenderer.renderVideo(StateManager.get('objectUrl'), fileObj.name, $dropZone);\r\n                StateManager.set('FormatReconnu', 'Yes');\r\n                break;\r\n                \r\n            case 'image':\r\n                PreviewRenderer.renderImage(StateManager.get('objectUrl'), $dropZone);\r\n                StateManager.set('FormatReconnu', 'Yes');\r\n                break;\r\n                \r\n            case 'document':\r\n                await this.handleDocumentUpload(extension, fileObj, $dropZone);\r\n                break;\r\n        }\r\n        \r\n        UIManager.updateAfterSuccessfulUpload($dropZone);\r\n        \r\n        if (StateManager.get('FormatReconnu') === 'Yes') {\r\n            this.finalizeUpload($dropZone);\r\n            $('#MsgSelectEspace').hide();\r\n        }\r\n        \r\n        \/\/ \u2705 RESET L'ANCIEN ESPACE ICI - APR\u00c8S avoir affich\u00e9 la nouvelle image\r\n        \/\/ \u2705 v2.4.5 : Toujours initialiser \u00e0 false avant le bloc (\u00e9vite la valeur r\u00e9siduelle)\r\n        var _sourceWasReserved = false;\r\n        StateManager.set('_sourceWasReserved', 'No');\r\n        \r\n        if (shouldResetOldSpace) {\r\n            console.log('\ud83d\udd04 Reset ancien espace:', dragstartRankId);\r\n            \r\n            var $oldDroppable = $('#' + dragstartRankId);\r\n            \r\n            if ($oldDroppable.length) {\r\n                var $container = $oldDroppable.find('.OrdiMobileConteneurClass').first();\r\n                \r\n                \/\/ \u2705 v2.4.5 : Utiliser l'\u00e9tat captur\u00e9 au dragstart (fiable vs re-check async)\r\n                if ($container.length) {\r\n                    _sourceWasReserved = StateManager.get('dragstart_ReserverChecked') === 'Yes';\r\n                    console.log('[d\u00e9placement] ReserverChecked au dragstart:', _sourceWasReserved, '| rank:', dragstartRankId);\r\n                    StateManager.set('_sourceWasReserved', _sourceWasReserved ? 'Yes' : 'No');\r\n                    RestoreadSpaceTemplateLocal($container[0]);\r\n                }\r\n            }\r\n        }\r\n        StateManager.set(\"EnvoiUlterieur\", 'false');\r\n        \r\n        \/\/ \u2705 v1.19.1 : Positionner l'espace \u00e0 10px du haut du viewport apr\u00e8s upload\r\n        setTimeout(function() {\r\n            var el = $dropZone.closest('.droppable').find('.HTMLUploadfileConteneur')[0]\r\n                     || $dropZone.closest('.droppable')[0];\r\n            if (el) {\r\n                ScrollHelper.scrollElementTo(el, 10);\r\n            }\r\n        }, 400);\r\n        \r\n        \/\/ \u2705 NE PLUS envoyer automatiquement - c'est la checkbox \"R\u00e9server\" qui d\u00e9clenche\r\n        \/\/ On met \u00e0 jour l'\u00e9tat de la checkbox pour qu'elle devienne activable\r\n        FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n        \r\n        \/\/ \u2705 v2.4.5 : Si l'espace source \u00e9tait r\u00e9serv\u00e9, cocher la checkbox de l'espace cible\r\n        if (StateManager.get('_sourceWasReserved') === 'Yes') {\r\n            var $targetCb = $dropZone.closest('.droppable').find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n            if ($targetCb.length ? !$targetCb.prop('checked') : false) {\r\n                $targetCb.prop('checked', true).trigger('change');\r\n                console.log('\u2705 [d\u00e9placement] Checkbox R\u00e9server coch\u00e9e sur espace cible');\r\n            }\r\n            StateManager.set('_sourceWasReserved', 'No');\r\n        }\r\n        \r\n        console.log('\ud83d\udccb Upload termin\u00e9 - en attente de validation via checkbox \"R\u00e9server\"');\r\n    },\r\n    \r\n    updateStateAfterUploadWithoutReset(response, fileObj) {\r\n        StateManager.setMultiple({\r\n            \"PageWhithADType\": StateManager.get('SelectedPageType'),\r\n            \"PageWhithADSecteur\": StateManager.get('SelectedPageSecteur')\r\n        });\r\n        \r\n        $('#BackPageWebWithADCroix').hide();\r\n        jQuery('#ApercuMobile').empty().css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center'\r\n        });\r\n        \r\n        const firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n        \r\n        if (firstUploadOrMoved === 'FirstUpload') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": response.data.file_url,\r\n                \"fileObj\": fileObj,\r\n                \"Upload_File_Name\": fileObj.name\r\n            });\r\n            \r\n            FileManager.createObjectUrl(fileObj);\r\n            \r\n            console.log('\ud83d\udcc1 Fichier upload\u00e9:', {\r\n                fullUrl: response.data.file_url,\r\n                fileName: response.data.file_name,\r\n                filePath: response.data.file_path\r\n            });\r\n        }\r\n    },\r\n    \r\n    updateStateAfterUpload(response, fileObj) {\r\n        StateManager.setMultiple({\r\n            \"PageWhithADType\": StateManager.get('SelectedPageType'),\r\n            \"PageWhithADSecteur\": StateManager.get('SelectedPageSecteur')\r\n        });\r\n        \r\n        $('#BackPageWebWithADCroix').hide();\r\n        jQuery('#ApercuMobile').empty().css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center'\r\n        });\r\n        \r\n        const firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n        console.log('\ud83d\udd0d FirstUploadFileorMoved:', firstUploadOrMoved);\r\n        \r\n        if (firstUploadOrMoved === 'FirstUpload') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": response.data.file_url,\r\n                \"fileObj\": fileObj,\r\n                \"Upload_File_Name\": fileObj.name\r\n            });\r\n            \r\n            FileManager.createObjectUrl(fileObj);\r\n            \r\n            console.log('\ud83d\udcc1 Fichier upload\u00e9:', {\r\n                fullUrl: response.data.file_url,\r\n                fileName: response.data.file_name,\r\n                filePath: response.data.file_path\r\n            });\r\n        } else if (firstUploadOrMoved === 'Moved') {\r\n            const dragstartRankId = StateManager.get('dragstart_Rank_Emplacement_Page_Web');\r\n            const currentRankId = StateManager.get('Rank_Emplacement_Page_Web');\r\n            \r\n            console.log('\ud83d\udd04 D\u00c9PLACEMENT D\u00c9TECT\u00c9');\r\n            console.log('   Ancien espace (dragstart):', dragstartRankId);\r\n            console.log('   Nouvel espace (current):', currentRankId);\r\n            \r\n            if (dragstartRankId ? dragstartRankId !== currentRankId : false) {\r\n                const $oldDroppable = $('#' + dragstartRankId);\r\n                \r\n                console.log('   $oldDroppable trouv\u00e9:', $oldDroppable.length > 0);\r\n                \r\n                if ($oldDroppable.length) {\r\n                    const $container = $oldDroppable.find('.OrdiMobileConteneurClass');\r\n                    \r\n                    console.log('   $container trouv\u00e9:', $container.length > 0);\r\n                    \r\n                    if ($container.length) {\r\n                        console.log('\ud83d\udd27 Appel RestoreadSpaceTemplate sur:', $container[0]);\r\n                        \r\n                        if (typeof window.RestoreadSpaceTemplate === 'function') {\r\n                            window.RestoreadSpaceTemplate($container[0]);\r\n                            console.log('\u2705 RestoreadSpaceTemplate ex\u00e9cut\u00e9');\r\n                        }\r\n                    } else {\r\n                        console.warn('\u26a0\ufe0f .OrdiMobileConteneurClass non trouv\u00e9 dans', dragstartRankId);\r\n                    }\r\n                }\r\n            } else {\r\n                console.log('\u23ed\ufe0f M\u00eame espace ou dragstartRankId invalide, pas de reset');\r\n            }\r\n        }\r\n    },\r\n        \r\n    async handleDocumentUpload(extension, fileObj, $dropZone) {\r\n        StateManager.set('FormatReconnu', 'Yes');\r\n        \r\n        \/\/ \u2705 Cacher le File r\u00e9dactionnel pour r\u00e9utilisation lors des d\u00e9placements (\u00e9vite CORS)\r\n        window._lastRedactionnelFile = fileObj;\r\n        \r\n        if (extension === 'doc' || extension === 'docx') {\r\n            await new Promise(resolve => window.VIALibraries.loadMammoth(resolve));\r\n            await PreviewRenderer.renderWord(fileObj, $dropZone);\r\n        } else if (extension === 'ppt' || extension === 'pptx') {\r\n            this.renderPowerPoint($dropZone);\r\n            PreviewRenderer.finalizeRedactionnelUpload();\r\n        } else if (extension === 'pdf') {\r\n            await new Promise(resolve => window.VIALibraries.loadPdfJs(resolve));\r\n            await PDFHandler.renderPDF(fileObj, $dropZone);\r\n            PreviewRenderer.finalizeRedactionnelUpload();\r\n        }\r\n    },\r\n    \r\n    renderPowerPoint($dropZone) {\r\n        const img = jQuery('<img \/>', {\r\n            src: '\/wp-content\/uploads\/2024\/06\/microsoft-powerpoint.png',\r\n            css: {\r\n                'width': 'auto',\r\n                'height': 'auto',\r\n                'margin-bottom': '20px',\r\n                'max-width': '150px',\r\n                'max-height': '160px'\r\n            }\r\n        });\r\n        $dropZone.empty().append(img.clone());\r\n        jQuery('#ApercuMobile').append(img);\r\n    },\r\n    \r\n    finalizeUpload($dropZone) {\r\n        \/\/ \u2705 Reset sendDataToParentFlag : nouvel upload = pas encore r\u00e9serv\u00e9\r\n        StateManager.set('sendDataToParentFlag', 'No');\r\n        \r\n        StateManager.setMultiple({\r\n            \"PageAnnonceSelection\": 'Yes',\r\n            \"FormatReconnu\": 'Yes'\r\n        });\r\n        \r\n        jQuery('#MsgChoixPageWeb, #MsgInsererAnnonceConteneur').hide();\r\n        \r\n        if (StateManager.get(\"Commande_Format\") === 'R\u00e9dactionnel') {\r\n            StateManager.set('Commande_Format', '\u00e0 choisir');\r\n            RedactionnelDepose();\r\n        }\r\n        \r\n        if (StateManager.get(\"Commande_Page\") === ' ') {\r\n            jQuery('#HPTarifConteneur').css({'background-color': '#BCFFAD'});\r\n            jQuery('#EmplacementDataStep3').html(\"Home Page\");\r\n            StateManager.setMultiple({\r\n                \"Commande_Page\": 'Home Page',\r\n                \"PageAnnonceSelection\": 'Yes'\r\n            });\r\n        }\r\n        \r\n        $('#PageWeb').css('zoom', '70%');\r\n        $('#PageWebTitreDore').css({'font-size': '14px'});\r\n        \r\n        if (!UIManager.isMobile()) {\r\n            $('#PageWebTitreDore').css({'color': '#213864'});\r\n        }\r\n        \r\n        StateManager.set(\"Page_Web_with_AD_URL\", StateManager.get(\"Page_Web_URL\"));\r\n        \r\n        UIManager.finalizeAdDisplay($dropZone);\r\n        \r\n        \/\/ \u2705 Notifier le parent de l'annonce d\u00e9pos\u00e9e non r\u00e9serv\u00e9e \u2014 pour restauration au retour sur la page\r\n        var _fileRcv = StateManager.get('FileReceived');\r\n        var _isRestoring = StateManager.get('_isAdRestoration') === 'Yes';\r\n        console.log('\ud83d\udfe1 [finalizeUpload] FileReceived:', _fileRcv, '| _isAdRestoration:', _isRestoring);\r\n        if (_fileRcv === 'Yes' ? !_isRestoring : false) {\r\n            var _pendingRank = StateManager.get('Rank_Emplacement_Page_Web') || $dropZone.closest('.droppable').attr('id') || '';\r\n            console.log('\ud83d\udfe1 [finalizeUpload] pendingRank:', _pendingRank, '| FullPathAdFile:', StateManager.get('FullPathAdFile'));\r\n            if (_pendingRank) {\r\n                console.log('\ud83d\udce4 [finalizeUpload] annonceDeposeeSansReservation \u2192 parent rank:', _pendingRank);\r\n                \/\/ \u2705 D\u00e9terminer le vrai format commercial selon le type de fichier d\u00e9pos\u00e9 + format s\u00e9lectionn\u00e9\r\n                var _formatSelect = sessionStorage.getItem('FormatSelect') || StateManager.get('Commande_Format_Transmis') || '';\r\n                var _uploadedExt = (StateManager.get('Upload_File_Name') || '').split('.').pop().toLowerCase();\r\n                var _fileKind = CONFIG.allowedExtensions.video.indexOf(_uploadedExt) !== -1 ? 'video'\r\n                              : CONFIG.allowedExtensions.image.indexOf(_uploadedExt) !== -1 ? 'image'\r\n                              : CONFIG.allowedExtensions.document.indexOf(_uploadedExt) !== -1 ? 'document'\r\n                              : '';\r\n                var _formatPending;\r\n                if (_fileKind === 'video') {\r\n                    \/\/ Vid\u00e9o \u2192 toujours Vid\u00e9o\r\n                    _formatPending = 'Vid\u00e9o';\r\n                } else if (_fileKind === 'image') {\r\n                    \/\/ Image \u2192 Banni\u00e8re ou Parrainage si s\u00e9lectionn\u00e9, sinon Banni\u00e8re par d\u00e9faut\r\n                    var _imgFormats = ['Banni\u00e8re', 'Banniere', 'Parrainage'];\r\n                    _formatPending = (_imgFormats.indexOf(_formatSelect) !== -1) ? _formatSelect : 'Banni\u00e8re';\r\n                } else if (_fileKind === 'document') {\r\n                    \/\/ Document \u2192 Communiqu\u00e9 ou Interview si s\u00e9lectionn\u00e9, sinon Communiqu\u00e9 par d\u00e9faut\r\n                    var _docFormats = ['Communiqu\u00e9', 'Communique', 'Interview'];\r\n                    _formatPending = (_docFormats.indexOf(_formatSelect) !== -1) ? _formatSelect : 'Communiqu\u00e9';\r\n                } else {\r\n                    \/\/ Fallback\r\n                    _formatPending = _formatSelect || StateManager.get('Commande_Format_Transmis') || '';\r\n                }\r\n                \/\/ \u2705 v2.4.9 : Si le format d\u00e9duit diff\u00e8re du format s\u00e9lectionn\u00e9 \u2192 mettre \u00e0 jour vignette + sessionStorage\r\n                if (_formatPending ? _formatPending !== _formatSelect : false) {\r\n                    StateManager.set('FormatSelect', _formatPending);\r\n                    StateManager.set('Commande_Format_Transmis', _formatPending);\r\n                    StateManager.set('Formatchoisi', 'Yes');\r\n                    \/\/ Mettre \u00e0 jour visuellement la vignette dans le droppable courant\r\n                    var _fmtNorm = _formatPending.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    var $_drp = $dropZone.closest('.droppable');\r\n                    $_drp.find('.EspPubFormatContainer').each(function() {\r\n                        var _cls = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                        if (_cls.includes(_fmtNorm)) {\r\n                            jQuery(this).css({'background-color': '#ffffff'});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        } else if (!jQuery(this).hasClass('FormatIdPopUp')) {\r\n                            jQuery(this).css({'background-color': ''});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': ''});\r\n                        }\r\n                    });\r\n                    \/\/ Mettre \u00e0 jour aussi le bandeau parent via postMessage\r\n                    MessageManager.sendToParent('formatChangedInIframe', { formatSelect: _formatPending, rank: _pendingRank });\r\n                    console.log('\ud83d\udd04 [finalizeUpload] Format corrig\u00e9:', _formatSelect, '\u2192', _formatPending);\r\n                }\r\n                var _firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n                var _isMoved = _firstUploadOrMoved === 'Moved';\r\n                var _oldRankMoved = _isMoved ? (StateManager.get('dragstart_Rank_Emplacement_Page_Web') || '') : '';\r\n                MessageManager.sendToParent('annonceDeposeeSansReservation', {\r\n                    Rank_Emplacement_Page_Web: _pendingRank,\r\n                    FullPathAdFile: StateManager.get('FullPathAdFile') || '',\r\n                    Upload_File_Name: StateManager.get('Upload_File_Name') || '',\r\n                    LoadedPageUrl: window.location.href,\r\n                    FileReceived: 'Yes',\r\n                    \/\/ \u2705 Lire EnvoiUlterieur depuis la checkbox du droppable courant (pas le StateManager global)\r\n                    \/\/ StateManager.get('EnvoiUlterieur') est global \u2192 peut valoir 'true' si un autre espace a sa checkbox coch\u00e9e\r\n                    EnvoiUlterieur: ($dropZone.closest('.droppable').find('input[name*=\"EnvoiUlterieur\"]').prop('checked') ? 'true' : 'false'),\r\n                    Commande_Format_Transmis: _formatPending,\r\n                    codeSite: StateManager.get('codeSite') || '',\r\n                    codePage: StateManager.get('codePage') || '',\r\n                    Commande_Emplacement_Page_Web: StateManager.buildEmplacementReference(_pendingRank),\r\n                    isMoved: _isMoved,\r\n                    oldRank: _oldRankMoved\r\n                });\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 Page \/annonce : pas de checkbox R\u00e9server \u2192 enregistrer imm\u00e9diatement\r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            StateManager.set('sendDataToParentFlag', 'Yes');\r\n            this.activateSendDataToParent($dropZone);\r\n        }\r\n    },\r\n    \r\n    activateSendDataToParent($dropZone) {\r\n        if (StateManager.get(\"sendDataToParentFlag\") !== 'Yes') {\r\n            return;\r\n        }\r\n        \r\n        if (StateManager.get(\"FirstUploadFileorMoved\") === 'Moved') {\r\n            const dragstartRef = StateManager.buildEmplacementReference(\r\n                StateManager.get(\"dragstart_Rank_Emplacement_Page_Web\")\r\n            );\r\n            StateManager.set('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n        }\r\n        \r\n        console.log('Esp Pub Ref FirstUploadFileorMoved:', StateManager.get(\"FirstUploadFileorMoved\"));\r\n        console.log('Esp Pub dragstart_Commande:', StateManager.get(\"dragstart_Commande_Emplacement_Page_Web\"));\r\n        \r\n        \/\/ v4.9ds : si un upload kit (drop miniature) est en cours pour ce rank,\r\n        \/\/   attendre sa fin avant d'envoyer les donn\u00e9es \u2014 sinon FullPathAdFile=null\r\n        \/\/   serait propag\u00e9 \u00e0 l'iframe popup et la BDD serait aliment\u00e9e sans chemin.\r\n        var _self = this;\r\n        var _rankWait = StateManager.get(\"Rank_Emplacement_Page_Web\");\r\n        var _pendingUp = (window._viaPendingUpload ? window._viaPendingUpload[_rankWait] : null);\r\n        if (_pendingUp ? typeof _pendingUp.then === 'function' : false) {\r\n            console.log('\u23f3 [activateSendDataToParent] upload kit en cours sur', _rankWait, '\u2192 wait avant d\\'envoyer');\r\n            _pendingUp.then(function() {\r\n                console.log('\u2705 [activateSendDataToParent] upload termin\u00e9 \u2192 reprise envoi | FullPathAdFile:', StateManager.get('FullPathAdFile'));\r\n                _self._continueActivateSendDataToParent($dropZone);\r\n            }).catch(function(_eUW) {\r\n                console.warn('\u26a0\ufe0f [activateSendDataToParent] upload \u00e9chou\u00e9 \u2192 on continue quand m\u00eame:', _eUW);\r\n                _self._continueActivateSendDataToParent($dropZone);\r\n            });\r\n            return;\r\n        }\r\n        \r\n        this._continueActivateSendDataToParent($dropZone);\r\n    },\r\n    \r\n    _continueActivateSendDataToParent($dropZone) {\r\n        if (StateManager.get(\"PageAjoutModifAnnonce\") === 'Yes') {\r\n            this.handlePageModification($dropZone);\r\n        } else {\r\n            this.handleNormalUpload();\r\n        }\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        setTimeout(() => {\r\n            StateManager.set(\"AddNewRefInVosCampagnes\", 'Yes');\r\n        }, 4000);\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            StateManager.set('Commande_Format_Transmis', '');\r\n        }\r\n    },\r\n    \r\n    handlePageModification($dropZone) {\r\n        $('#CroixResetAnnonce, .DeplaceAnnonce').hide();\r\n        $('.PageAjoutModifAnnonceCroixResetAnnonce').show();\r\n        \r\n        const emplacementRef = $dropZone.closest('.CampagnesTemplateClass')\r\n            .find('.ReferenceEspace').text();\r\n        \r\n        console.log('Commande_Emplacement_Page_Web:', emplacementRef);\r\n        \r\n        jQuery.ajax({\r\n            type: \"POST\",\r\n            url: CONFIG.ajaxUrl,\r\n            data: {\r\n                action: 'via_update_fichier_annonce',\r\n                commande_ref_url: StateManager.get(\"commande_ref_url\"),\r\n                emplacement_page_web: emplacementRef,\r\n                chemin_fichier: StateManager.get(\"FullPathAdFile\")\r\n            },\r\n            xhrFields: { withCredentials: true },\r\n            success: (response) => {\r\n                if (response.success) {\r\n                    console.log('\u2705 Fichier annonce mis \u00e0 jour:', response.data.message);\r\n                    location.reload();\r\n                } else {\r\n                    console.error('\u274c Erreur:', response.data.message);\r\n                }\r\n            },\r\n            error: (xhr, status, error) => {\r\n                console.error('\u274c Erreur AJAX:', error);\r\n            }\r\n        });\r\n    },\r\n    \r\n    handleNormalUpload() {\r\n        if (StateManager.get(\"EnvoiUlterieur\") === 'true') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": '',\r\n                \"Upload_File_Name\": ''\r\n            });\r\n        }\r\n        \r\n        StateManager.set(\"LoadedPageUrl\", window.location.href);\r\n        \r\n        console.log('EnvoiUlterieur:', StateManager.get(\"EnvoiUlterieur\"));\r\n        console.log('LoadedPageUrl:', StateManager.get(\"LoadedPageUrl\"));\r\n        console.log('FileReceived:', StateManager.get(\"FileReceived\"));\r\n        \r\n        const data = MessageManager.prepareUploadData();\r\n        \r\n        \/\/ \u2705 Sauvegarder FullPathAdFile dans localStorage pour restauration apr\u00e8s refresh\r\n        \/\/ (ce chemin couvre aussi AchatEspaceCall=Yes o\u00f9 handleEspacePubData d'Entete.txt n'est pas appel\u00e9)\r\n        \/\/ \u2705 Backup : sauver via_fullpath ici aussi (couvre AchatEspaceCall=Yes)\r\n        if (data.FullPathAdFile) {\r\n            var _rankFP = data.Rank_Emplacement_Page_Web;\r\n            if (_rankFP) {\r\n                try {\r\n                    var _fpItemsEP = JSON.parse(localStorage.getItem('via_fullpath') || '{}');\r\n                    _fpItemsEP[_rankFP] = data.FullPathAdFile;\r\n                    localStorage.setItem('via_fullpath', JSON.stringify(_fpItemsEP));\r\n                } catch(e) {}\r\n            }\r\n        }\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            MessageManager.sendDataToParent(data);\r\n        } else {\r\n            const formatChoisi = StateManager.get(\"Formatchoisi\") === 'Yes';\r\n            const fichierDepose = StateManager.get(\"FileReceived\") === 'Yes';\r\n            const envoiDiffere = StateManager.get(\"EnvoiUlterieur\") === 'true';\r\n            \r\n            console.log('\ud83d\udd0d handleNormalUpload - Conditions:', { formatChoisi, fichierDepose, envoiDiffere });\r\n            \r\n            if (formatChoisi ? (fichierDepose || envoiDiffere) : false) {\r\n                if (typeof window.handleEspacePubData === 'function') {\r\n                    window.handleEspacePubData(data);\r\n                } else {\r\n                    console.warn('\u26a0\ufe0f handleEspacePubData non disponible, fallback direct');\r\n                    if (typeof window.executeWithProcessLoaded === 'function') {\r\n                        window.executeWithProcessLoaded(function() {\r\n                            jQuery('#PopupProcessCommandeContainer').show();\r\n                        });\r\n                    } else {\r\n                        jQuery('#PopupProcessCommandeContainer').show();\r\n                    }\r\n                }\r\n            } else if (formatChoisi) {\r\n                console.log('\ud83d\udcdd Mise \u00e0 jour format uniquement');\r\n                \r\n                const borderStyle = {'box-shadow': 'inset 0 0 0 2px #00FF19', 'box-sizing': 'border-box'};  \/\/ \u2705 v2.4.5\r\n                \r\n                jQuery(\".FormatClassique, .FormatPopup\").css({'border': 'none'});\r\n                \r\n                jQuery('.FormatClassique, .FormatPopup').each(function() {\r\n                    \/\/ \u2705 v2.1.1 : NFD normalize both sides\r\n                    var _cn = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    var _cf = (data.Commande_Format_Transmis || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    if (_cn.includes(_cf) ? _cf : false) {\r\n                        jQuery(this).css(borderStyle);\r\n                    }\r\n                });\r\n            }\r\n        }\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        setTimeout(() => {\r\n            StateManager.set(\"AddNewRefInVosCampagnes\", 'Yes');\r\n        }, 4000);\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            StateManager.set('Commande_Format_Transmis', '');\r\n        }\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des fichiers PDF (s\u00e9par\u00e9 pour \u00e9viter la complexit\u00e9)\r\n *\/\r\nconst PDFHandler = {\r\n    pdfData: null,\r\n    pdfDataForViewer: null,\r\n    \r\n    async renderPDF(fileObj, $dropZone) {\r\n        return new Promise((resolve, reject) => {\r\n            const reader = new FileReader();\r\n            \r\n            reader.onload = async (e) => {\r\n                try {\r\n                    const arrayBuffer = e.target.result;\r\n                    this.pdfData = new Uint8Array(arrayBuffer);\r\n                    this.pdfDataForViewer = arrayBuffer;\r\n                    \r\n                    console.log(\"Initial PDF load, size:\", this.pdfData.byteLength);\r\n                    \r\n                    const pdfjsLib = window['pdfjs-dist\/build\/pdf'];\r\n                    pdfjsLib.GlobalWorkerOptions.workerSrc = \r\n                        '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n                    \r\n                    const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(this.pdfData) }).promise;\r\n                    const page = await pdf.getPage(1);\r\n                    \r\n                    const { titleText, pageText } = await this.extractTextFromPage(page);\r\n                    \/\/ v4.9ds : si une image utilisateur a \u00e9t\u00e9 stock\u00e9e par le kit (mobile PDF JPG-in-PDF),\r\n                    \/\/   l'utiliser directement plut\u00f4t que d'extraire la 1\u00e8re image du PDF\r\n                    \/\/   (qui serait toute la cr\u00e9ation rasteris\u00e9e)\r\n                    const _$drop4ds = $dropZone.closest('.droppable');\r\n                    const _kitFirstImg = _$drop4ds.length ? _$drop4ds.data('kitFirstImageDataURL') : null;\r\n                    let imageData;\r\n                    if (_kitFirstImg) {\r\n                        imageData = { dataUrl: _kitFirstImg };\r\n                        console.log('[v4.9ds] renderPDF \u2192 utilise kitFirstImageDataURL stock\u00e9');\r\n                        try { var _dl5 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl5) _dl5('[v4.9ds] renderPDF utilise firstImg stock\u00e9', 'ok'); } catch(e) {}\r\n                    } else {\r\n                        imageData = await this.extractImageFromPage(page, pdfjsLib);\r\n                        try { var _dl6 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl6) _dl6('[v4.9ds] renderPDF utilise extractImageFromPage (firstImg ABSENT)', 'warn'); } catch(e) {}\r\n                    }\r\n                    \r\n                    if (imageData) {\r\n                        PreviewRenderer.renderDocumentPreview(\r\n                            $dropZone,\r\n                            imageData.dataUrl,\r\n                            titleText,\r\n                            pageText\r\n                        );\r\n                    } else {\r\n                        \/\/ \u2705 v1.19.0 : Accepter les PDF sans image\r\n                        PreviewRenderer.renderDocumentPreview(\r\n                            $dropZone,\r\n                            null,\r\n                            titleText,\r\n                            pageText\r\n                        );\r\n                        console.log('\u2139\ufe0f PDF sans image \u2014 aper\u00e7u texte seul');\r\n                    }\r\n                    \r\n                    this.attachPDFPreviewHandler($dropZone, titleText);\r\n                    resolve();\r\n                } catch (error) {\r\n                    console.error('Error extracting from PDF:', error);\r\n                    \/\/ \u2705 v1.19.0 : Message d'erreur en fran\u00e7ais\r\n                    $dropZone.html(`\r\n                        <div style=\"padding: 15px; background-color: #f8f8f8; border-radius: 4px; text-align: center;\">\r\n                            <p style=\"color: #FB5E2A; font-weight: 600; font-family: Roboto, Arial, sans-serif; font-size: 12px;\">\r\n                                Erreur lors de la lecture du document\r\n                            <\/p>\r\n                        <\/div>\r\n                    `);\r\n                    reject(error);\r\n                }\r\n            };\r\n            \r\n            reader.readAsArrayBuffer(fileObj);\r\n        });\r\n    },\r\n    \r\n    async extractTextFromPage(page) {\r\n        const textContent = await page.getTextContent();\r\n        \r\n        let text = '';\r\n        let lastX = -1;\r\n        let lastY = -1;\r\n        \r\n        for (const item of textContent.items) {\r\n            if (lastY !== -1 ? (Math.abs(lastY - item.transform[5]) > 5) : false) {\r\n                text += '\\n';\r\n            } else if (lastX !== -1 ? (item.transform[4] - lastX > 10) : false) {\r\n                text += ' ';\r\n            }\r\n            \r\n            text += item.str;\r\n            \r\n            lastX = item.transform[4] + (item.width || 0);\r\n            lastY = item.transform[5];\r\n        }\r\n        \r\n        text = text\r\n            .replace(\/(\\w) (\\w)\/g, (match, p1, p2) => {\r\n                if (\/[\u00e9\u00e8\u00ea\u00eb\u00e0\u00e2\u00e4\u00f4\u00f6\u00fb\u00fc\u00ef\u00ee\u00e7]\/i.test(p2)) {\r\n                    return p1 + p2;\r\n                }\r\n                return match;\r\n            })\r\n            .replace(\/ ([.,;:!?])\/g, '$1');\r\n        \r\n        const normalizedText = PreviewRenderer.normalizeText(text);\r\n        const titleText = PreviewRenderer.findDocumentTitle(normalizedText);\r\n        \r\n        return { titleText, pageText: text };\r\n    },\r\n    \r\n    async extractImageFromPage(page, pdfjsLib) {\r\n        const opList = await page.getOperatorList();\r\n        const imageInfo = [];\r\n        let currentTransform = null;\r\n        \r\n        for (let i = 0; i < opList.fnArray.length; i++) {\r\n            const operator = opList.fnArray[i];\r\n            const args = opList.argsArray[i];\r\n            \r\n            if (operator === pdfjsLib.OPS.transform) {\r\n                currentTransform = args;\r\n            } else if (operator === pdfjsLib.OPS.paintImageXObject) {\r\n                const imageName = args[0];\r\n                \r\n                if (!imageInfo.some(info => info.name === imageName)) {\r\n                    const position = currentTransform ? {\r\n                        x: currentTransform[4] || 0,\r\n                        y: currentTransform[5] || 0\r\n                    } : { x: 0, y: 0 };\r\n                    \r\n                    imageInfo.push({ name: imageName, position: position });\r\n                }\r\n            }\r\n        }\r\n        \r\n        if (imageInfo.length === 0) {\r\n            return null;\r\n        }\r\n        \r\n        imageInfo.sort((a, b) => b.position.y - a.position.y);\r\n        \r\n        const targetImageName = imageInfo[0].name;\r\n        const imageObj = page.objs.get(targetImageName);\r\n        \r\n        if (!imageObj) {\r\n            const img = await page.objs.get(targetImageName, true);\r\n            return this.processImageObject(img);\r\n        }\r\n        \r\n        return this.processImageObject(imageObj);\r\n    },\r\n    \r\n    processImageObject(imageObj) {\r\n        if (!imageObj) {\r\n            throw new Error('Image object is null');\r\n        }\r\n        \r\n        if (imageObj.bitmap instanceof ImageBitmap) {\r\n            const width = imageObj.bitmap.width || imageObj.width || imageObj.w;\r\n            const height = imageObj.bitmap.height || imageObj.height || imageObj.h;\r\n            \r\n            const canvas = document.createElement('canvas');\r\n            canvas.width = width;\r\n            canvas.height = height;\r\n            const ctx = canvas.getContext('2d');\r\n            \r\n            ctx.drawImage(imageObj.bitmap, 0, 0);\r\n            \r\n            return {\r\n                canvas: canvas,\r\n                width: width,\r\n                height: height,\r\n                dataUrl: canvas.toDataURL('image\/jpeg', 0.92)\r\n            };\r\n        }\r\n        \r\n        const width = imageObj.width || imageObj.w;\r\n        const height = imageObj.height || imageObj.h;\r\n        \r\n        if (!width || !height) {\r\n            throw new Error('Could not determine image dimensions');\r\n        }\r\n        \r\n        let imgData = imageObj.data || imageObj.bitmap;\r\n        \r\n        if (!imgData) {\r\n            throw new Error('No valid image data found');\r\n        }\r\n        \r\n        const canvas = document.createElement('canvas');\r\n        canvas.width = width;\r\n        canvas.height = height;\r\n        const ctx = canvas.getContext('2d');\r\n        \r\n        ctx.fillStyle = 'white';\r\n        ctx.fillRect(0, 0, width, height);\r\n        \r\n        const imageData = ctx.createImageData(width, height);\r\n        \r\n        this.fillImageData(imageData, imgData, imageObj.kind, width, height);\r\n        \r\n        ctx.putImageData(imageData, 0, 0);\r\n        \r\n        return {\r\n            canvas: canvas,\r\n            width: width,\r\n            height: height,\r\n            dataUrl: canvas.toDataURL('image\/jpeg', 0.92)\r\n        };\r\n    },\r\n    \r\n    fillImageData(imageData, imgData, kind, width, height) {\r\n        if (kind === 'GRAY') {\r\n            for (let i = 0, j = 0; i < imgData.length; i++, j += 4) {\r\n                const value = imgData[i];\r\n                imageData.data[j] = value;\r\n                imageData.data[j + 1] = value;\r\n                imageData.data[j + 2] = value;\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (kind === 'CMYK') {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 4, j += 4) {\r\n                const c = imgData[i] \/ 255;\r\n                const m = imgData[i + 1] \/ 255;\r\n                const y = imgData[i + 2] \/ 255;\r\n                const k = imgData[i + 3] \/ 255;\r\n                \r\n                imageData.data[j] = 255 * (1 - c) * (1 - k);\r\n                imageData.data[j + 1] = 255 * (1 - m) * (1 - k);\r\n                imageData.data[j + 2] = 255 * (1 - y) * (1 - k);\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (kind === 'RGB24') {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 3, j += 4) {\r\n                imageData.data[j] = imgData[i];\r\n                imageData.data[j + 1] = imgData[i + 1];\r\n                imageData.data[j + 2] = imgData[i + 2];\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (imgData.length === width * height * 3) {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 3, j += 4) {\r\n                imageData.data[j] = imgData[i];\r\n                imageData.data[j + 1] = imgData[i + 1];\r\n                imageData.data[j + 2] = imgData[i + 2];\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else {\r\n            const tempArray = new Uint8ClampedArray(imgData.length);\r\n            for (let i = 0; i < imgData.length; i++) {\r\n                tempArray[i] = imgData[i];\r\n            }\r\n            \r\n            if (tempArray.length === imageData.data.length \/ 4 * 3) {\r\n                for (let i = 0, j = 0; i < tempArray.length; i += 3, j += 4) {\r\n                    imageData.data[j] = tempArray[i];\r\n                    imageData.data[j + 1] = tempArray[i + 1];\r\n                    imageData.data[j + 2] = tempArray[i + 2];\r\n                    imageData.data[j + 3] = 255;\r\n                }\r\n            } else if (tempArray.length === imageData.data.length) {\r\n                imageData.data.set(tempArray);\r\n            } else {\r\n                console.warn('Unknown image format - creating placeholder');\r\n                for (let i = 0; i < imageData.data.length; i += 4) {\r\n                    const x = (i\/4) % width;\r\n                    const y = Math.floor((i\/4) \/ width);\r\n                    imageData.data[i] = x % 256;\r\n                    imageData.data[i + 1] = y % 256;\r\n                    imageData.data[i + 2] = 100;\r\n                    imageData.data[i + 3] = 255;\r\n                }\r\n            }\r\n        }\r\n    },\r\n    \r\n    attachPDFPreviewHandler($dropZone, titleText) {\r\n        var $droppable = $dropZone.closest('.droppable');\r\n        var self = this;\r\n\r\n        \/\/ \u2705 Adapter le libell\u00e9 selon le format (communiqu\u00e9 \/ interview)\r\n        var kitFormat = $droppable.data('kitFormatSelect') || '';\r\n        var isInterview = (kitFormat || titleText || '').toLowerCase().indexOf('interview') !== -1;\r\n        var formatLabel = isInterview ? 'l\\'interview' : 'le communiqu\u00e9';\r\n        $droppable.find('.doc-preview-readmore').text('Ouvrir et visualiser ' + formatLabel);\r\n\r\n        \/\/ \u2705 v2.0.11 : D\u00e9l\u00e9gation d'\u00e9v\u00e9nement \u2014 plus robuste sur mobile\r\n        $droppable.off('click.docpreview').on('click.docpreview', '.doc-preview-readmore', (event) => {\r\n            event.preventDefault();\r\n            event.stopPropagation();\r\n            var popupTitle = isInterview ? 'Interview' : 'Communiqu\u00e9';\r\n\r\n            \/\/ \u2705 Priorit\u00e9 1 : pdfImageDataURL dispo (Kit)\r\n            var kitPdfImage = $droppable.data('kitPdfImageDataURL');\r\n            if (kitPdfImage) {\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    pdfDataURL: kitPdfImage\r\n                });\r\n                return;\r\n            }\r\n\r\n            \/\/ \u2705 Priorit\u00e9 2 : PDF upload\u00e9 manuellement \u2192 popup inline aussi\r\n            if (self.pdfDataForViewer ? self.pdfDataForViewer.byteLength > 0 : false) {\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    pdfArrayBuffer: self.pdfDataForViewer\r\n                });\r\n                return;\r\n            }\r\n\r\n            console.error('PDF data is not available');\r\n        });\r\n    },\r\n\r\n    \/\/ \u2705 v1.19.1 : Popup inline document \u2014 position:absolute + ScrollHelper.getVisibleTop()\r\n    \/\/ Le parent envoie visibleTopIframe dans le message scroll \u2192 positionnement fiable.\r\n    showInlineDocPopup($dropZone, options) {\r\n        var existing = document.getElementById('via-pdf-inline-popup');\r\n        if (existing) existing.remove();\r\n\r\n        var $droppable = $dropZone.closest('.droppable');\r\n        var droppableW = $droppable.outerWidth() || 300;\r\n        var popupW = Math.round(droppableW * 1.15);\r\n        \/\/ \u2705 v2.0.11 : Contraindre la largeur au viewport sur mobile (\u00e9vite troncature gauche\/droite)\r\n        var viewportW = document.documentElement.clientWidth || window.innerWidth;\r\n        if (UIManager.isMobile()) {\r\n            popupW = Math.min(popupW, viewportW - 8);\r\n        }\r\n        var popupH = Math.round(popupW * 2.5 * 0.51);\r\n\r\n        \/\/ \u2500\u2500 Position visible r\u00e9elle \u2500\u2500\r\n        var visibleTop = ScrollHelper.getVisibleTop();\r\n        var popupTop = Math.round(visibleTop + 284);\r\n\r\n        \/\/ \u2500\u2500 Popup position:absolute (m\u00eame approche que la r\u00e9gie) \u2500\u2500\r\n        var popup = document.createElement('div');\r\n        popup.id = 'via-pdf-inline-popup';\r\n        \/\/ v4.9ds : initialiser le z-index avec le compteur global window.popupZIndex\r\n        \/\/   pour que le popup PDF s'int\u00e8gre au syst\u00e8me _bringPopupToFront (Entete.txt:921+).\r\n        \/\/   Au prochain mousedown sur ce popup, le s\u00e9lecteur _viaTopMostSel le d\u00e9tecte\r\n        \/\/   et incr\u00e9mente son z-index \u2192 il passe en avant-plan par rapport aux autres\r\n        \/\/   popups (Ele0A, miniature, CGV, etc.). Skip sur mobile (perturberait le rendu).\r\n        var _initZ;\r\n        if (typeof window.popupZIndex === 'number' ? window.innerWidth >= 1000 : false) {\r\n            window.popupZIndex++;\r\n            _initZ = window.popupZIndex;\r\n        } else {\r\n            _initZ = 99999;\r\n        }\r\n        popup.style.cssText =\r\n            'position:absolute;z-index:' + _initZ + ';top:' + popupTop + 'px;' +\r\n            'width:' + popupW + 'px;height:' + popupH + 'px;' +\r\n            'background:#fff;border-radius:8px 8px 0 0;padding:0;' +\r\n            'box-shadow:0 8px 32px rgba(0,0,0,0.35);display:flex;flex-direction:column;' +\r\n            'min-width:200px;min-height:150px;touch-action:none;';\r\n\r\n        \/\/ Centrer horizontalement sur le droppable (coordonn\u00e9es absolues)\r\n        var droppableRect = $droppable[0].getBoundingClientRect();\r\n        var scrollX = window.scrollX || 0;\r\n        var initLeft = Math.round(droppableRect.left + scrollX + (droppableRect.width - popupW) \/ 2);\r\n        initLeft = Math.max(4, initLeft);\r\n        \/\/ \u2705 v2.0.11 : Contraindre \u00e0 droite aussi sur mobile\r\n        if (UIManager.isMobile()) {\r\n            initLeft = Math.min(initLeft, viewportW - popupW - 4);\r\n            initLeft = Math.max(4, initLeft);\r\n        }\r\n        popup.style.left = initLeft + 'px';\r\n\r\n        \/\/ \u2500\u2500 Header \u2500\u2500\r\n        var header = document.createElement('div');\r\n        header.style.cssText =\r\n            'display:flex;align-items:center;justify-content:center;position:relative;' +\r\n            'padding:10px 14px;background:#f0f0f0;color:#494949;flex-shrink:0;' +\r\n            'cursor:grab;user-select:none;border-radius:8px 8px 0 0;';\r\n        var titleEl = document.createElement('span');\r\n        titleEl.style.cssText = 'font-family:Roboto,Arial,sans-serif;font-size:15px;font-weight:600;';\r\n        titleEl.textContent = options.formatTitle || 'Document';\r\n        header.appendChild(titleEl);\r\n\r\n        var closeBtn = document.createElement('button');\r\n        closeBtn.textContent = '\u00d7';\r\n        closeBtn.style.cssText =\r\n            'position:absolute;right:10px;top:50%;transform:translateY(-50%);' +\r\n            'background:transparent;border:none;color:#494949;font-size:20px;' +\r\n            'cursor:pointer;line-height:1;padding:0 4px;';\r\n        closeBtn.addEventListener('click', cleanup);\r\n        header.appendChild(closeBtn);\r\n        popup.appendChild(header);\r\n\r\n        \/\/ \u2500\u2500 Zone scrollable \u2014 pleine largeur \u2500\u2500\r\n        var scrollZone = document.createElement('div');\r\n        scrollZone.style.cssText =\r\n            'flex:1;overflow-y:auto;overflow-x:hidden;padding:0;margin:0;touch-action:pan-y;';\r\n        popup.appendChild(scrollZone);\r\n\r\n        var loader = document.createElement('div');\r\n        loader.style.cssText =\r\n            'text-align:center;padding:30px;font-family:Roboto,Arial,sans-serif;font-size:12px;color:#666;';\r\n        loader.textContent = 'Chargement du document\u2026';\r\n        scrollZone.appendChild(loader);\r\n\r\n        \/\/ \u2500\u2500 8 poign\u00e9es de redimensionnement \u2500\u2500\r\n        var handles = [\r\n            { cursor:'nw-resize', pos:'top:-4px;left:-4px;',       dx:-1, dy:-1 },\r\n            { cursor:'n-resize',  pos:'top:-4px;left:50%;',        dx:0,  dy:-1 },\r\n            { cursor:'ne-resize', pos:'top:-4px;right:-4px;',      dx:1,  dy:-1 },\r\n            { cursor:'w-resize',  pos:'top:50%;left:-4px;',        dx:-1, dy:0  },\r\n            { cursor:'e-resize',  pos:'top:50%;right:-4px;',       dx:1,  dy:0  },\r\n            { cursor:'sw-resize', pos:'bottom:-4px;left:-4px;',    dx:-1, dy:1  },\r\n            { cursor:'s-resize',  pos:'bottom:-4px;left:50%;',     dx:0,  dy:1  },\r\n            { cursor:'se-resize', pos:'bottom:-4px;right:-4px;',   dx:1,  dy:1  }\r\n        ];\r\n        handles.forEach(function(h) {\r\n            var handle = document.createElement('div');\r\n            var isCorner = (h.dx !== 0 ? h.dy !== 0 : false);\r\n            handle.style.cssText =\r\n                'position:absolute;touch-action:none;' + h.pos +\r\n                'width:' + (isCorner ? '12px' : (h.dy === 0 ? '8px' : '30px')) + ';' +\r\n                'height:' + (isCorner ? '12px' : (h.dy === 0 ? '30px' : '8px')) + ';' +\r\n                'cursor:' + h.cursor + ';z-index:10;';\r\n            (function(hInfo) {\r\n                handle.addEventListener('pointerdown', function(e) {\r\n                    e.preventDefault(); e.stopPropagation();\r\n                    handle.setPointerCapture(e.pointerId);\r\n                    var startX = e.clientX, startY = e.clientY;\r\n                    var rect0 = popup.getBoundingClientRect();\r\n                    var sY = window.scrollY || 0, sX = window.scrollX || 0;\r\n                    var startW = rect0.width, startH = rect0.height;\r\n                    var startL = rect0.left + sX, startT = rect0.top + sY;\r\n                    function onResize(ev) {\r\n                        ev.preventDefault();\r\n                        var ddx = ev.clientX - startX, ddy = ev.clientY - startY;\r\n                        var newW = startW, newH = startH, newL = startL, newT = startT;\r\n                        if (hInfo.dx === 1)  newW = Math.max(200, startW + ddx);\r\n                        if (hInfo.dx === -1) { newW = Math.max(200, startW - ddx); newL = startL + ddx; }\r\n                        if (hInfo.dy === 1)  newH = Math.max(150, startH + ddy);\r\n                        if (hInfo.dy === -1) { newH = Math.max(150, startH - ddy); newT = startT + ddy; }\r\n                        popup.style.width  = newW + 'px';\r\n                        popup.style.height = newH + 'px';\r\n                        popup.style.left   = newL + 'px';\r\n                        popup.style.top    = newT + 'px';\r\n                    }\r\n                    function onResizeEnd(ev) {\r\n                        handle.releasePointerCapture(ev.pointerId);\r\n                        handle.removeEventListener('pointermove', onResize);\r\n                        handle.removeEventListener('pointerup', onResizeEnd);\r\n                    }\r\n                    handle.addEventListener('pointermove', onResize);\r\n                    handle.addEventListener('pointerup', onResizeEnd);\r\n                });\r\n            })(h);\r\n            popup.appendChild(handle);\r\n        });\r\n\r\n        \/\/ \u2500\u2500 Drag via header \u2500\u2500\r\n        header.style.touchAction = 'none';\r\n        header.addEventListener('pointerdown', function(e) {\r\n            if (e.target === closeBtn) return;\r\n            e.preventDefault();\r\n            header.setPointerCapture(e.pointerId);\r\n            var mx = e.clientX, my = e.clientY;\r\n            var popupRect = popup.getBoundingClientRect();\r\n            var scrollY = window.scrollY || 0;\r\n            var scrollX2 = window.scrollX || 0;\r\n            var curLeft = popupRect.left + scrollX2;\r\n            var curTop  = popupRect.top  + scrollY;\r\n            header.style.cursor = 'grabbing';\r\n            function onMove(ev) {\r\n                ev.preventDefault();\r\n                var dx = ev.clientX - mx;\r\n                var dy = ev.clientY - my;\r\n                curLeft += dx;\r\n                curTop  += dy;\r\n                popup.style.left = curLeft + 'px';\r\n                popup.style.top  = curTop  + 'px';\r\n                mx = ev.clientX;\r\n                my = ev.clientY;\r\n            }\r\n            function onUp(ev) {\r\n                header.releasePointerCapture(ev.pointerId);\r\n                header.style.cursor = 'grab';\r\n                header.removeEventListener('pointermove', onMove);\r\n                header.removeEventListener('pointerup', onUp);\r\n            }\r\n            header.addEventListener('pointermove', onMove);\r\n            header.addEventListener('pointerup', onUp);\r\n        });\r\n\r\n        \/\/ \u2500\u2500 Cleanup \u2500\u2500\r\n        function cleanup() {\r\n            popup.remove();\r\n            document.removeEventListener('keydown', escHandler);\r\n        }\r\n        var escHandler = function(e) { if (e.key === 'Escape') cleanup(); };\r\n        document.addEventListener('keydown', escHandler);\r\n\r\n        document.body.appendChild(popup);\r\n\r\n        \/\/ \u2500\u2500 Rendre le contenu \u2500\u2500\r\n        if (options.pdfDataURL)          this.renderPdfInPopup(scrollZone, options.pdfDataURL, popupW, 'dataurl');\r\n        else if (options.pdfArrayBuffer) this.renderPdfInPopup(scrollZone, options.pdfArrayBuffer, popupW, 'arraybuffer');\r\n        else if (options.htmlContent)    this.renderHtmlInPopup(scrollZone, options.htmlContent);\r\n    },\r\n\r\n    \/\/ Alias pour compatibilit\u00e9\r\n    showInlinePdfPopup($dropZone, pdfImageDataURL, formatTitle) {\r\n        this.showInlineDocPopup($dropZone, { formatTitle: formatTitle, pdfDataURL: pdfImageDataURL });\r\n    },\r\n\r\n    \/\/ \u2705 Rendu PDF dans popup (dataurl ou arraybuffer) \u2014 avec crop des marges blanches\r\n    async renderPdfInPopup(container, pdfData, popupWidth, mode) {\r\n        try {\r\n            var u8;\r\n            if (mode === 'dataurl') {\r\n                var b64 = pdfData.split(',')[1];\r\n                var bstr = atob(b64);\r\n                u8 = new Uint8Array(bstr.length);\r\n                for (var i = 0; i < bstr.length; i++) { u8[i] = bstr.charCodeAt(i); }\r\n            } else {\r\n                u8 = new Uint8Array(pdfData);\r\n            }\r\n\r\n            var pdfjsLib = window['pdfjs-dist\/build\/pdf'];\r\n            if (!pdfjsLib) {\r\n                container.innerHTML = '<div style=\"padding:20px;text-align:center;color:#c00;\">pdf.js non charg\u00e9<\/div>';\r\n                return;\r\n            }\r\n            pdfjsLib.GlobalWorkerOptions.workerSrc =\r\n                '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n\r\n            var pdf = await pdfjsLib.getDocument({ data: u8 }).promise;\r\n            console.log('\ud83d\udcc4 PDF popup :', pdf.numPages, 'pages');\r\n            container.innerHTML = '';\r\n\r\n            var firstPage = await pdf.getPage(1);\r\n            var vpNative = firstPage.getViewport({ scale: 1 });\r\n            var scale = popupWidth \/ vpNative.width;\r\n\r\n            \/\/ \u2705 R\u00e9f\u00e9rence au popup pour ajuster sa hauteur apr\u00e8s rendu\r\n            var _popup = container.closest('#via-pdf-inline-popup');\r\n            var _headerH = _popup ? (_popup.querySelector('div[style*=\"grab\"]') || {offsetHeight: 44}).offsetHeight : 44;\r\n\r\n            \/\/ \u2705 D\u00e9tecte les marges blanches haut\/bas\/gauche\/droite d'un canvas rendu\r\n            \/\/ v4.9ds (pdf-popup-fullwidth) : scan des 4 c\u00f4t\u00e9s (avant : top\/bottom seulement),\r\n            \/\/   pour que le contenu PDF prenne toute la largeur de la popup m\u00eame si le PDF\r\n            \/\/   d'origine a des marges lat\u00e9rales blanches importantes (cas PDFs kit Interview\/\r\n            \/\/   Communiqu\u00e9 portrait avec contenu centr\u00e9 ~40% de la largeur).\r\n            \/\/   Pour left\/right, on scanne UNIQUEMENT entre topRow et bottomRow d\u00e9j\u00e0 d\u00e9termin\u00e9s\r\n            \/\/   afin de ne pas \u00eatre tromp\u00e9 par du blanc d\u00e9j\u00e0 sur le point d'\u00eatre crop\u00e9.\r\n            function detectWhiteMargins(canvas) {\r\n                var ctx = canvas.getContext('2d');\r\n                var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;\r\n                var W = canvas.width, H = canvas.height;\r\n                var topRow = 0, bottomRow = H - 1, leftCol = 0, rightCol = W - 1;\r\n                \/\/ Top : premi\u00e8re ligne non-blanche (tol\u00e9rance 252 pour anti-aliasing)\r\n                outer: for (var y = 0; y < H; y++) {\r\n                    for (var x = 0; x < W; x++) {\r\n                        var i = (y * W + x) * 4;\r\n                        if (data[i] < 252 || data[i+1] < 252 || data[i+2] < 252) { topRow = y; break outer; }\r\n                    }\r\n                }\r\n                \/\/ Bottom : derni\u00e8re ligne non-blanche\r\n                outer2: for (var y2 = H - 1; y2 >= topRow; y2--) {\r\n                    for (var x2 = 0; x2 < W; x2++) {\r\n                        var i2 = (y2 * W + x2) * 4;\r\n                        if (data[i2] < 252 || data[i2+1] < 252 || data[i2+2] < 252) { bottomRow = y2; break outer2; }\r\n                    }\r\n                }\r\n                \/\/ Left : premi\u00e8re colonne non-blanche (scan limit\u00e9 \u00e0 la zone [topRow..bottomRow])\r\n                outer3: for (var x3 = 0; x3 < W; x3++) {\r\n                    for (var y3 = topRow; y3 <= bottomRow; y3++) {\r\n                        var i3 = (y3 * W + x3) * 4;\r\n                        if (data[i3] < 252 || data[i3+1] < 252 || data[i3+2] < 252) { leftCol = x3; break outer3; }\r\n                    }\r\n                }\r\n                \/\/ Right : derni\u00e8re colonne non-blanche\r\n                outer4: for (var x4 = W - 1; x4 >= leftCol; x4--) {\r\n                    for (var y4 = topRow; y4 <= bottomRow; y4++) {\r\n                        var i4 = (y4 * W + x4) * 4;\r\n                        if (data[i4] < 252 || data[i4+1] < 252 || data[i4+2] < 252) { rightCol = x4; break outer4; }\r\n                    }\r\n                }\r\n                return { top: topRow, bottom: bottomRow, left: leftCol, right: rightCol };\r\n            }\r\n\r\n            for (var pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\r\n                var page = await pdf.getPage(pageNum);\r\n                var vp = page.getViewport({ scale: scale });\r\n                \/\/ Rendre dans un canvas temporaire\r\n                var tmpCanvas = document.createElement('canvas');\r\n                tmpCanvas.width = vp.width;\r\n                tmpCanvas.height = vp.height;\r\n                await page.render({ canvasContext: tmpCanvas.getContext('2d'), viewport: vp }).promise;\r\n\r\n                \/\/ \u2705 Crop marges blanches sur les 4 c\u00f4t\u00e9s (avec petite marge de s\u00e9curit\u00e9 de 4px)\r\n                var margins = detectWhiteMargins(tmpCanvas);\r\n                var cropTop    = Math.max(0, margins.top - 4);\r\n                var cropBottom = Math.min(tmpCanvas.height - 1, margins.bottom + 4);\r\n                var cropLeft   = Math.max(0, margins.left - 4);\r\n                var cropRight  = Math.min(tmpCanvas.width - 1, margins.right + 4);\r\n                var croppedH = cropBottom - cropTop + 1;\r\n                var croppedW = cropRight - cropLeft + 1;\r\n\r\n                \/\/ Canvas final crop\u00e9 sur les 4 c\u00f4t\u00e9s\r\n                var canvas = document.createElement('canvas');\r\n                canvas.width = croppedW;\r\n                canvas.height = croppedH;\r\n                canvas.getContext('2d').drawImage(tmpCanvas, cropLeft, cropTop, croppedW, croppedH, 0, 0, croppedW, croppedH);\r\n                canvas.style.cssText = 'display:block;width:100%;height:auto;margin:0;padding:0;';\r\n                container.appendChild(canvas);\r\n                console.log('\ud83d\udcc4 Page', pageNum, '- crop H:', cropTop, '\u2192', cropBottom, '\/ W:', cropLeft, '\u2192', cropRight, '(' + Math.round((cropLeft + (tmpCanvas.width - cropRight - 1)) \/ tmpCanvas.width * 100) + '% width trimmed)');\r\n            }\r\n\r\n            \/\/ \u2705 Ajuster la hauteur du popup \u00e0 la hauteur r\u00e9elle du contenu rendu\r\n            \/\/ (popup \u00e9tait calcul\u00e9 sur popupW*2.5*0.51 qui peut \u00eatre trop grand ou trop petit)\r\n            \/\/ v4.9ds (pdf-popup-fullwidth) : _maxPopupH calcul\u00e9 en fonction de la position\r\n            \/\/   r\u00e9elle de la popup (popupTop dans le viewport) + 20px de marge en bas, pour\r\n            \/\/   garantir que la popup ne d\u00e9passe jamais le viewport. Si le contenu est plus\r\n            \/\/   haut, le overflow-y:auto de scrollZone affiche la barre de d\u00e9filement.\r\n            if (_popup) {\r\n                var _totalH = 0;\r\n                container.querySelectorAll('canvas').forEach(function(c) { _totalH += c.height * (popupWidth \/ c.width); });\r\n                var _viewportH = window.innerHeight || document.documentElement.clientHeight || 600;\r\n                var _popupRectNow = _popup.getBoundingClientRect();\r\n                var _popupTopVisible = _popupRectNow.top;\r\n                \/\/ Plafond strict : viewport - top de la popup - 20px de marge en bas\r\n                var _maxPopupH = Math.max(200, _viewportH - _popupTopVisible - 20);\r\n                var _idealH = Math.min(_totalH + _headerH + 8, _maxPopupH);\r\n                _popup.style.height = _idealH + 'px';\r\n                console.log('\ud83d\udcd0 Popup redimensionn\u00e9:', Math.round(_idealH), 'px (contenu:', Math.round(_totalH), ', max:', Math.round(_maxPopupH), ')');\r\n            }\r\n        } catch (err) {\r\n            console.error('\u274c Erreur rendu PDF popup:', err);\r\n            container.innerHTML =\r\n                '<div style=\"padding:20px;text-align:center;color:#c00;font-size:12px;\">Erreur lors du chargement du document<\/div>';\r\n        }\r\n    },\r\n\r\n    \/\/ \u2705 Rendu HTML (Word) dans popup \u2014 avec CSS complet pour mammoth\r\n    renderHtmlInPopup(container, htmlContent) {\r\n        container.innerHTML = '';\r\n        \/\/ \u2705 Nettoyer les <p> vides en d\u00e9but\/fin produits par mammoth (marges parasites)\r\n        htmlContent = htmlContent\r\n            .replace(\/^(\\s*<p[^>]*>\\s*(<br\\s*\\\/?>\\s*)*<\\\/p>\\s*)+\/i, '')\r\n            .replace(\/(\\s*<p[^>]*>\\s*(<br\\s*\\\/?>\\s*)*<\\\/p>\\s*)+$\/i, '');\r\n        \/\/ \u2500\u2500 Styles pour le HTML g\u00e9n\u00e9r\u00e9 par mammoth (titres, paragraphes, listes, images) \u2500\u2500\r\n        var style = document.createElement('style');\r\n        style.textContent = [\r\n            '.via-html-popup-body { padding:20px 24px; background:#fff; color:#222; font-family:Georgia,\"Times New Roman\",serif; font-size:15px; line-height:1.7; }',\r\n            '.via-html-popup-body h1 { font-size:22px; font-weight:700; margin:0 0 12px; line-height:1.3; }',\r\n            '.via-html-popup-body h2 { font-size:18px; font-weight:700; margin:18px 0 8px; line-height:1.3; }',\r\n            '.via-html-popup-body h3 { font-size:15px; font-weight:700; margin:14px 0 6px; }',\r\n            '.via-html-popup-body p  { margin:0 0 10px; }',\r\n            '.via-html-popup-body p:first-child { margin-top:0; }',\r\n            '.via-html-popup-body strong, .via-html-popup-body b { font-weight:700; }',\r\n            '.via-html-popup-body em, .via-html-popup-body i { font-style:italic; }',\r\n            '.via-html-popup-body ul, .via-html-popup-body ol { margin:6px 0 10px 22px; padding:0; }',\r\n            '.via-html-popup-body li { margin-bottom:4px; }',\r\n            '.via-html-popup-body img { max-width:100%; height:auto; display:block; margin:10px auto; border-radius:4px; }',\r\n            '.via-html-popup-body table { width:100%; border-collapse:collapse; margin:10px 0; font-size:13px; }',\r\n            '.via-html-popup-body td, .via-html-popup-body th { border:1px solid #ddd; padding:6px 8px; vertical-align:top; }',\r\n            '.via-html-popup-body th { background:#f0f4f8; font-weight:700; }',\r\n            '.via-html-popup-body a { color:#225da9; text-decoration:underline; }'\r\n        ].join('');\r\n        container.appendChild(style);\r\n        var wrapper = document.createElement('div');\r\n        wrapper.className = 'via-html-popup-body';\r\n        wrapper.innerHTML = htmlContent;\r\n        container.appendChild(wrapper);\r\n    },\r\n    \r\n    async renderPDFInWindow(childWindow, container) {\r\n        container.innerHTML = '';\r\n        \r\n        const script = childWindow.document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.4.120\/pdf.min.js';\r\n        childWindow.document.head.appendChild(script);\r\n        \r\n        script.onload = async () => {\r\n            const pdfjsLib = childWindow.pdfjsLib;\r\n            const viewerContainer = childWindow.document.createElement('div');\r\n            viewerContainer.style.width = '100%';\r\n            viewerContainer.style.backgroundColor = 'white';\r\n            viewerContainer.style.position = 'relative';\r\n            container.appendChild(viewerContainer);\r\n            \r\n            const loadingTask = pdfjsLib.getDocument({data: window.pdfDataToTransfer});\r\n            const pdf = await loadingTask.promise;\r\n            \r\n            const firstPage = await pdf.getPage(1);\r\n            const viewport = firstPage.getViewport({scale: 1.5});\r\n            const pageHeight = viewport.height;\r\n            \r\n            const spacing = -15;\r\n            const totalHeight = (pageHeight * pdf.numPages) + (spacing * (pdf.numPages - 1));\r\n            viewerContainer.style.height = `${totalHeight}px`;\r\n            \r\n            for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\r\n                const page = await pdf.getPage(pageNum);\r\n                const canvas = childWindow.document.createElement('canvas');\r\n                const context = canvas.getContext('2d');\r\n                \r\n                canvas.width = viewport.width;\r\n                canvas.height = viewport.height;\r\n                \r\n                canvas.style.position = 'absolute';\r\n                canvas.style.left = '50%';\r\n                canvas.style.transform = 'translateX(-50%)';\r\n                canvas.style.top = `${(pageNum - 1) * (pageHeight + spacing)}px`;\r\n                \r\n                await page.render({\r\n                    canvasContext: context,\r\n                    viewport: viewport\r\n                }).promise;\r\n                \r\n                viewerContainer.appendChild(canvas);\r\n            }\r\n        };\r\n    },\r\n    \r\n    arrayBufferToBase64(buffer) {\r\n        let binary = '';\r\n        const bytes = new Uint8Array(buffer);\r\n        const len = bytes.byteLength;\r\n        for (let i = 0; i < len; i++) {\r\n            binary += String.fromCharCode(bytes[i]);\r\n        }\r\n        return window.btoa(binary);\r\n    },\r\n    \r\n    base64ToArrayBuffer(base64) {\r\n        const binaryString = window.atob(base64);\r\n        const len = binaryString.length;\r\n        const bytes = new Uint8Array(len);\r\n        for (let i = 0; i < len; i++) {\r\n            bytes[i] = binaryString.charCodeAt(i);\r\n        }\r\n        return bytes.buffer;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des \u00e9v\u00e9nements de drop et d'upload\r\n *\/\r\nconst DropHandler = {\r\n    async handleDrop(e) {\r\n        e.preventDefault();\r\n        \r\n        const $currentTarget = this.findDropTarget(e);\r\n        \r\n        if (!$currentTarget) {\r\n            console.log('No valid drop target found');\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 V\u00c9RIFIER SI C'EST UN D\u00c9PLACEMENT (pas besoin de v\u00e9rifier le format)\r\n        const isMoved = StateManager.get('FirstUploadFileorMoved') === 'Moved';\r\n        const dragstartRef = StateManager.get('dragstart_Commande_Emplacement_Page_Web');\r\n        let hasDragstartRef = false;\r\n        \r\n        if (dragstartRef) {\r\n            if (dragstartRef !== 'No') {\r\n                hasDragstartRef = true;\r\n            }\r\n        }\r\n        \r\n        let isDeplacementAnnonce = false;\r\n        if (isMoved) {\r\n            if (hasDragstartRef) {\r\n                isDeplacementAnnonce = true;\r\n            }\r\n        }\r\n        \r\n        if (isDeplacementAnnonce) {\r\n            console.log('\ud83d\udd04 D\u00e9placement d\u00e9tect\u00e9 - contr\u00f4le format ignor\u00e9');\r\n        } else {\r\n            \/\/ \u2705 v2.7.3 : Le format est d\u00e9duit de l'extension du fichier d\u00e9pos\u00e9\r\n            \/\/ \u2192 ne plus bloquer le d\u00e9p\u00f4t si aucun format s\u00e9lectionn\u00e9\r\n            console.log('\ud83d\udd04 Nouveau d\u00e9p\u00f4t \u2014 format sera d\u00e9duit du fichier, contr\u00f4le format ignor\u00e9');\r\n        }\r\n    \r\n        console.log(\"Drop at:\", $currentTarget);\r\n        \r\n        this.updateEmplacementState($currentTarget);\r\n        \r\n        const shouldProcess = this.shouldProcessDrop(e, $currentTarget);\r\n        \r\n        if (shouldProcess) {\r\n            await this.processFileDrop(e, $currentTarget);\r\n        } else {\r\n            this.processVideoDrop($currentTarget);\r\n        }\r\n        \r\n        DragDropManager.clearDataTransferFiles(e);\r\n    },\r\n    \r\n    findDropTarget(e) {\r\n        \/\/ \u2705 v2.4.3 : Si Ele0A est actif et le drop arrive sur Ele1A \u2192 rediriger vers Ele0A\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            var _ele0ADrop = document.querySelector('#Ele0A #drop_file_zone_achat');\r\n            if (_ele0ADrop) {\r\n                console.log('\ud83c\udfaf findDropTarget \u2014 PopUpChoice=Yes \u2192 cible forc\u00e9e: Ele0A');\r\n                return jQuery(_ele0ADrop);\r\n            }\r\n        }\r\n\r\n        let target = e.target.closest('#drop_file_zone_achat');\r\n        \r\n        if (!target) {\r\n            target = e.currentTarget.closest('#drop_file_zone_achat');\r\n            console.log('Drop target found 1');\r\n        }\r\n        \r\n        if (!target) {\r\n            target = jQuery(e.target).closest('#drop_file_zone_achat')[0];\r\n            console.log('Drop target found 2');\r\n        }\r\n        \r\n        return target ? jQuery(target) : null;\r\n    },\r\n    \r\n    updateEmplacementState($target) {\r\n        const espaceId = $target.closest('.droppable').attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', espaceId);\r\n        StateManager.set('Commande_Emplacement_Page_Web',\r\n            StateManager.buildEmplacementReference(espaceId));\r\n        \r\n        UIManager.updateEmplacementDisplay();\r\n        \r\n        console.log(\"Rank_Emplacement_Page_Web:\", StateManager.get('Rank_Emplacement_Page_Web'));\r\n        console.log(\"Droppable at:\", StateManager.get('Commande_Emplacement_Page_Web'));\r\n    },\r\n    \r\n    shouldProcessDrop(e, $target) {\r\n        const hasFiles = e.dataTransfer.files.length > 0;\r\n        const isRedactionnel = StateManager.get('Commande_Format_Transmis') === 'R\u00e9dactionnel';\r\n        const isValidBackground = window.getComputedStyle(\r\n            $target.closest('#UploadFileConteneur')[0]\r\n        ).backgroundColor !== 'rgba(0, 0, 0, 0)';\r\n        \r\n        return (hasFiles || isRedactionnel) ? isValidBackground : false;\r\n    },\r\n    \r\n    async processFileDrop(e, $target) {\r\n        jQuery('.MsgAdNotDisplayed').hide();\r\n        StateManager.setMultiple({\r\n            \"AdDisplayed\": 'Yes',\r\n            \/\/ \u2705 NE PLUS mettre sendDataToParentFlag ici - c'est la checkbox \"R\u00e9server\" qui le fera\r\n            \/\/ \"sendDataToParentFlag\": 'Yes'\r\n        });\r\n        \r\n        console.log('ajaxFileUpload_achat launched');\r\n        console.log(\"FirstUploadFileorMoved:\", StateManager.get('FirstUploadFileorMoved'));\r\n        \r\n        if (UIManager.isMobile()) {\r\n            $target = jQuery('#Ele1A').find('#drop_file_zone_achat');\r\n        }\r\n        \r\n        if (StateManager.get('Commande_Format_Transmis') === 'R\u00e9dactionnel') {\r\n            const fileURL = StateManager.get('FullPathAdFile');\r\n            const filename = fileURL.substring(fileURL.indexOf('_') + 1);\r\n            \r\n            try {\r\n                \/\/ \u2705 R\u00e9utiliser le File cach\u00e9 (\u00e9vite CORS cross-domain)\r\n                let file;\r\n                if (window._lastRedactionnelFile) {\r\n                    file = window._lastRedactionnelFile;\r\n                    console.log('\u267b\ufe0f R\u00e9utilisation du File cach\u00e9:', file.name, Math.round(file.size \/ 1024) + 'KB');\r\n                } else {\r\n                    file = await FileManager.urlToFile(fileURL, filename);\r\n                }\r\n                await UploadManager.handleFileUpload(file, $target);\r\n            } catch (error) {\r\n                console.error('Error:', error);\r\n            }\r\n        } else {\r\n            await UploadManager.handleFileUpload(e.dataTransfer.files[0], $target);\r\n        }\r\n    },\r\n    \r\n    processVideoDrop($target) {\r\n        const isValidBackground = window.getComputedStyle(\r\n            $target.closest('#UploadFileConteneur')[0]\r\n        ).backgroundColor !== 'rgba(0, 0, 0, 0)';\r\n        \r\n        if (!isValidBackground) {\r\n            jQuery('.MsgAdNotDisplayed').show();\r\n            StateManager.set(\"AdDisplayed\", 'No');\r\n            return;\r\n        }\r\n        \r\n        StateManager.set(\"AdDisplayed\", 'Yes');\r\n        \r\n        const objectUrl = StateManager.get('videoSrc');\r\n        const $previousDropZone = $('.drop_file_zone_achat_class').has(`video[src=\"${objectUrl}\"]`);\r\n        window.RestoreadSpaceTemplate($previousDropZone);\r\n        \r\n        const videoElement = jQuery('<video controls autoplay muted>').attr({\r\n            'src': objectUrl,\r\n            'max-width': '100%',\r\n            'max-height': '100%',\r\n            'draggable': 'true'\r\n        }).css({\r\n            'max-width': '100%',\r\n            'max-height': '100%'\r\n        });\r\n        \r\n        $target.empty().append(videoElement);\r\n        \r\n        $target.closest('#UploadFileConteneur').css({'background-color': '#FFFFFF00'});\r\n        \r\n        var _isViaPopupParentV = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n        $target.closest('.OrdiMobileConteneurClass')\r\n            .find('.RefEspacePublicitaire')\r\n            .html('R\u00e9f\u00e9rence de l\\'espace : ' + StateManager.get('Commande_Emplacement_Page_Web'))\r\n            .attr('id', 'RefEspacePublicitaire')\r\n            .each(function() {\r\n                var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                this.style.setProperty('display', 'block', 'important');\r\n                if (_isViaPopupParentV) { this.style.setProperty('margin-top', '4px', 'important'); }\r\n            });\r\n        (function() {\r\n            var _rankPosV = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            var _posLibV = PreviewRenderer._getPositionLibelle(_rankPosV);\r\n            if (_posLibV) {\r\n                $target.closest('.OrdiMobileConteneurClass')\r\n                    .find('.PositionEspacePublicitaireDeplacer')\r\n                    .text(_posLibV)\r\n                    .each(function() {\r\n                        var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                        if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                        this.style.setProperty('display', 'block', 'important');\r\n                        if (_isViaPopupParentV) { this.style.setProperty('margin-top', '4px', 'important'); }\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        $target.closest('.OrdiMobileConteneurClass').find('.AdUploadedTitle').show();\r\n        \r\n        $target.closest('.OrdiMobileConteneurClass')\r\n            .css({'top': '60px', 'margin-bottom': '80px'});\r\n        \r\n        $target.closest('.HTMLUploadfileConteneur')\r\n            .not('.AdUploadedTitle')\r\n            .css({\r\n                'top': '0px',\r\n                'margin-bottom': '0px',\r\n                'box-shadow': 'inset 0 0 0 2px #00FF19',  \/\/ \u2705 v2.4.5\r\n                'background-color': 'white'\r\n            });\r\n        \r\n        $target.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, .ChoisirEspacePublicitaire2ndLigne, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        jQuery('#MsgElementsCommandeValides, #MessageOptionsacompleterConteneur').hide();\r\n        \r\n        StateManager.set(\"FirstUploadFileorMoved\", 'Moved');\r\n        \r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server (au lieu d'envoyer automatiquement)\r\n        FormatUIManager.updateReserverCheckboxState($target.closest('.droppable'));\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'explorateur de fichiers\r\n *\/\r\nconst FileExplorer = {\r\n    isRunning: false,\r\n\r\n    open(e) {\r\n        if (this.isRunning) {\r\n            return;\r\n        }\r\n        \r\n        const $element = jQuery(e.target).closest('.droppable');\r\n        \r\n        \/\/ \u2705 v2.7.3 : Le format sera d\u00e9duit de l'extension du fichier s\u00e9lectionn\u00e9\r\n        \/\/ \u2192 ne plus bloquer l'ouverture du s\u00e9lecteur si aucun format n'est encore choisi\r\n        this.isRunning = true;\r\n        \r\n        const rankId = $element.attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n        StateManager.set('Commande_Emplacement_Page_Web',\r\n            StateManager.buildEmplacementReference(rankId));\r\n        \r\n        UIManager.updateEmplacementDisplay();\r\n        \r\n        console.log(\"Drop at:\", StateManager.get('Commande_Emplacement_Page_Web'));\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        \r\n        const $currentTarget = jQuery(e.target).closest('#drop_file_zone_achat');\r\n        const $fileInput = jQuery('#selectfile_achat');\r\n        \r\n        console.log(\"currentTarget\", $currentTarget);\r\n        \r\n        $fileInput.off('change');\r\n        $fileInput.off('click');\r\n        \r\n        const onChange = (event) => {\r\n            this.isRunning = false;\r\n            $fileInput.off('change', onChange);\r\n            \r\n            \/\/ \u2705 NE PLUS mettre sendDataToParentFlag ici\r\n            \/\/ StateManager.set(\"sendDataToParentFlag\", 'Yes');\r\n            \r\n            if ($fileInput[0].files.length > 0) {\r\n                UploadManager.handleFileUpload($fileInput[0].files[0], $currentTarget);\r\n            }\r\n        };\r\n        \r\n        const onClick = () => {\r\n            $fileInput.off('click', onClick);\r\n            $fileInput.on('focus', function onFocus() {\r\n                setTimeout(() => {\r\n                    if (!$fileInput.val()) {\r\n                        FileExplorer.isRunning = false;\r\n                    }\r\n                }, 200);\r\n                $fileInput.off('focus', onFocus);\r\n            });\r\n        };\r\n        \r\n        $fileInput.on('change', onChange);\r\n        $fileInput.on('click', onClick);\r\n        \r\n        \/\/ \u2705 Contr\u00f4le format imm\u00e9diat \u2014 avant d'ouvrir le s\u00e9lecteur de fichiers\r\n        var _$dropGuard = $currentTarget.closest('.droppable');\r\n        if (_$dropGuard.length) {\r\n            var _hasFmtNow = false;\r\n            _$dropGuard.find('.EspPubFormatContainer').each(function() {\r\n                if (jQuery(this).hasClass('FormatIdCreation')) return;\r\n                if (jQuery(this).hasClass('FormatIdPopUp')) return;\r\n                var _bg = this.style.backgroundColor || '';\r\n                if (_bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white') { _hasFmtNow = true; return false; }\r\n                var _fmtEl = this.querySelector('.EspPubFormat');\r\n                if (_fmtEl) { var _col = _fmtEl.style.color || ''; if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') { _hasFmtNow = true; return false; } }\r\n            });\r\n            if (!_hasFmtNow) {\r\n                UIManager.showFormatError($currentTarget, 'Merci de s\u00e9lectionner un format d\\'annonce avant de t\u00e9l\u00e9charger votre fichier');\r\n                FormatUIManager.flashTitle($currentTarget);\r\n                FileExplorer.isRunning = false;\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v2.7.3 : R\u00e9initialiser la valeur avant d'ouvrir le s\u00e9lecteur\r\n        \/\/ Sans ce reset, certains navigateurs (Safari mobile) ne d\u00e9clenchent pas\r\n        \/\/ l'\u00e9v\u00e9nement 'change' si un fichier avait d\u00e9j\u00e0 \u00e9t\u00e9 s\u00e9lectionn\u00e9 pr\u00e9c\u00e9demment\r\n        $fileInput.val('');\r\n        $fileInput.click();\r\n        \r\n        setTimeout(() => {\r\n            this.isRunning = false;\r\n        }, 300);\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion du reset d'annonce\r\n *\/\r\nconst AdResetHandler = {\r\n    handle(e) {\r\n        e.preventDefault();\r\n        console.log(\"CroixResetAnnonce click\");\r\n        \r\n        \/\/ Reset l'\u00e9tat EnvoiUlterieur\r\n        StateManager.set('EnvoiUlterieur', 'false');\r\n        \r\n        \/\/ \u2705 v1.19.6 : Reset FileReceived mais garder le format s\u00e9lectionn\u00e9\r\n        StateManager.set('FileReceived', 'No');\r\n        \r\n        const $element = jQuery(e.currentTarget);\r\n        const $droppable = $element.closest('.droppable');\r\n        const resetRef = StateManager.buildEmplacementReference($droppable.attr('id'));\r\n        \r\n        console.log(\"Reset_Commande_Emplacement_Page_Web:\", resetRef);\r\n        \r\n        \/\/ \u2705 Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \r\n        var _rankForDel = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n        \/\/ \u2705 Bug 10 : envoyer via postMessage si dans une iframe (pas seulement mode=popup)\r\n        \/\/   AchatEspaceCall=Yes uniquement pour mode=popup \u2014 pour les sites pays standards\r\n        \/\/   (iframe r\u00e9gie, non-popup), window.processdataDelAd n'existe pas dans le contexte iframe\r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes' || window.self !== window.top) {\r\n            MessageManager.sendDelAdToParent({\r\n                Commande_Emplacement_Page_Web: resetRef,\r\n                Rank_Emplacement_Page_Web: _rankForDel,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n        } else {\r\n            window.processdataDelAd({\r\n                Commande_Emplacement_Page_Web: resetRef,\r\n                Rank_Emplacement_Page_Web: _rankForDel,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n        }\r\n        \r\n        \/\/ \u2705 v1.16.0 : Appeler RestoreadSpaceTemplateLocal directement (accessible dans ce scope)\r\n        RestoreadSpaceTemplateLocal(e.currentTarget);\r\n        \r\n        window.FonctionCroixResetAnnonce(e.currentTarget);\r\n        \r\n        \/\/ \u2705 v2.1.1 : Sauf popup\r\n        var formatWasSelected = sessionStorage.getItem('FormatSelect') \r\n            || sessionStorage.getItem('Commande_Format_Transmis')\r\n            || $droppable.data('kitFormatSelect');\r\n        \r\n        if (sessionStorage.getItem('PopUpChoice') !== 'Yes' ? (formatWasSelected || sessionStorage.getItem('Formatchoisi') === 'Yes') : false) {\r\n            sessionStorage.setItem('Formatchoisi', 'Yes');\r\n            console.log('\u2705 Formatchoisi forc\u00e9 \u00e0 Yes apr\u00e8s reset');\r\n        }\r\n        \r\n        \/\/ \u2705 v1.19.6 : Remettre \u00e0 jour le titre format et r\u00e9afficher la checkbox R\u00e9server\r\n        setTimeout(() => {\r\n            FormatUIManager.updateTitleColor($droppable);\r\n            FormatUIManager.updateReserverCheckboxState($droppable);\r\n            \r\n            \/\/ \u2705 R\u00e9afficher le .ReserverContainer statique\r\n            $droppable.find('.ReserverContainer').show();\r\n        }, 150);\r\n    }\r\n};\r\n\r\n\/**\r\n * \u2705 Template pour r\u00e9initialisation des espaces publicitaires (IFRAME)\r\n *\/\r\nvar adSpaceTemplatesLocal = {};\r\n\r\nfunction saveAdSpaceTemplateLocal() {\r\n    var saved = 0;\r\n    jQuery('.droppable').each(function() {\r\n        var droppableId = jQuery(this).attr('id');\r\n        if (!droppableId) return;\r\n        \r\n        \/\/ \u2705 Ne jamais sauvegarder Ele0A (clone temporaire popup, pas un template d'origine)\r\n        if (droppableId === 'Ele0A') return;\r\n        \r\n        \/\/ \u2705 Ne pas r\u00e9-\u00e9craser un template d\u00e9j\u00e0 sauvegard\u00e9\r\n        if (adSpaceTemplatesLocal[droppableId]) return;\r\n        \r\n        \/\/ \u2705 v2.3.4 : Ne pas sauvegarder si template d\u00e9j\u00e0 connu (\u00e9tat propre sauvegard\u00e9)\r\n        \/\/ Le guard hasAd est supprim\u00e9 \u2014 on veut capturer le template le plus t\u00f4t possible\r\n        \/\/ Si le template existe d\u00e9j\u00e0, on ne le r\u00e9\u00e9crase pas\r\n        \/\/ (le guard anti-\u00e9crasement if (adSpaceTemplatesLocal[droppableId]) return; suffit)\r\n        \r\n        var $content = jQuery(this).find('.OrdiMobileConteneurClass').first();\r\n        if ($content.length > 0) {\r\n            adSpaceTemplatesLocal[droppableId] = $content.clone(true, true);\r\n            saved++;\r\n        }\r\n    });\r\n    if (saved > 0) {\r\n        console.log('\u2705 Templates espaces pub sauvegard\u00e9s:', saved, 'espaces -', Object.keys(adSpaceTemplatesLocal));\r\n        return true;\r\n    }\r\n    return false;\r\n}\r\n\r\nfunction RestoreadSpaceTemplateLocal(element) {\r\n    console.log('\ud83d\udd04 RestoreadSpaceTemplateLocal', element);\r\n    \r\n    var $element = jQuery(element);\r\n    var $droppable = $element.closest('.droppable');\r\n    var droppableId = $droppable.attr('id');\r\n    \r\n    \/\/ \u2705 FIX Ele0A : pas de template sauvegard\u00e9 (clone temporaire popup)\r\n    \/\/ \u2192 pas de remplacement DOM, reset visuel direct sur le contenu existant\r\n    var _isEle0A = (droppableId === 'Ele0A');\r\n\r\n    \/\/ \u2705 v1.19.3 : Chercher le template sp\u00e9cifique \u00e0 CET espace (sauf Ele0A)\r\n    if (!_isEle0A) {\r\n        if (!droppableId || !adSpaceTemplatesLocal[droppableId]) {\r\n            if (!saveAdSpaceTemplateLocal()) {\r\n                console.error('\u274c Template non disponible');\r\n                return false;\r\n            }\r\n            if (!adSpaceTemplatesLocal[droppableId]) {\r\n                console.error('\u274c Template non trouv\u00e9 pour', droppableId);\r\n                return false;\r\n            }\r\n        }\r\n    }\r\n    \r\n    var adSpaceElement = $droppable.find('.OrdiMobileConteneurClass').first();\r\n    \r\n    if (!adSpaceElement || !adSpaceElement.length) {\r\n        console.error('\u274c OrdiMobileConteneurClass non trouv\u00e9');\r\n        return false;\r\n    }\r\n    \r\n    var newElement;\r\n    if (_isEle0A) {\r\n        \/\/ Ele0A : pas de clone \u2014 on remet en \u00e9tat le contenu existant directement\r\n        newElement = adSpaceElement;\r\n        console.log('\u2705 [Ele0A] reset visuel direct (pas de template clone)');\r\n        \/\/ \u2705 Vider le contenu du dropzone et restaurer le HTML par d\u00e9faut\r\n        \/\/ (le replaceWith n'ayant pas lieu, l'image d\u00e9pos\u00e9e et le fond blanc restent sinon)\r\n        var $dz0A = $droppable.find('#drop_file_zone_achat');\r\n        $dz0A.empty().html(\r\n            '<div id=\"drag_upload_file_achat\">' +\r\n                '<p class=\"UploadIci\" style=\"color:#FB5E2A;font-weight:600;\">Ici glisser \u2013 d\u00e9poser ou<br>t\u00e9l\u00e9charger une annonce<\/p>' +\r\n            '<\/div>'\r\n        );\r\n        \/\/ Restaurer le fond bleu #9FC5F3 et retirer les styles d'annonce upload\u00e9e\r\n        $droppable.find('#UploadFileConteneur').css({\r\n            'background-color': '#9FC5F3',\r\n            'border': '',\r\n            'box-sizing': ''\r\n        });\r\n        $droppable.find('.HTMLUploadfileConteneur').css({\r\n            'box-shadow': '',\r\n            'background-color': '',\r\n            'border': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': '',\r\n            'overflow': '',\r\n            'height': '',\r\n            'max-height': ''\r\n        });\r\n        \/\/ Masquer la croix et le titre d'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle, #CroixResetAnnonce, .CroixResetAnnonceContainer, .DeplaceAnnonce').hide();\r\n        $droppable.removeAttr('data-via-ad-loaded').removeAttr('data-from-miniature');\r\n        console.log('\u2705 [Ele0A] dropzone vid\u00e9 + fond restaur\u00e9');\r\n    } else {\r\n        \/\/ \u2705 v1.19.3 : Cloner le template SP\u00c9CIFIQUE \u00e0 cet espace (pas le premier)\r\n        newElement = adSpaceTemplatesLocal[droppableId].clone(true, true);\r\n        console.log('\u2705 Template utilis\u00e9 pour', droppableId);\r\n        adSpaceElement.replaceWith(newElement);\r\n        \/\/ \u2705 v2.4.12 : Effacer data-via-ad-loaded (sinon selectEspaceActif masque .ReserverContainer)\r\n        $droppable.removeAttr('data-via-ad-loaded').removeAttr('data-from-miniature');\r\n        if (window.outerWidth < 1000) {\r\n            newElement.css({'margin-top': '-25px', 'bottom': '0px'});\r\n        }\r\n    }\r\n    \r\n    \/\/ D\u00e9cocher la case Envoi diff\u00e9r\u00e9 si elle existe\r\n    newElement.find('input[name*=\"EnvoiUlterieur\"]').prop('checked', false);\r\n    \r\n    \/\/ \u2705 D\u00e9cocher la checkbox \"R\u00e9server\"\r\n    newElement.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked', false);\r\n    \r\n    \/\/ Reset l'\u00e9tat EnvoiUlterieur\r\n    StateManager.set('EnvoiUlterieur', 'false');\r\n    \r\n    \/\/ \u2705 v1.19.3 : R\u00e9initialiser TOUS les styles inline des conteneurs parents modifi\u00e9s pendant le d\u00e9p\u00f4t\r\n    if ($droppable.length) {\r\n        \/\/ \u2705 v2.0.9 : Restaurer les marges de l'algorithme de positionnement (sauvegard\u00e9es par styleUploadedAd)\r\n        var origMt = $droppable.data('orig-mt');\r\n        $droppable.css({\r\n            'margin-top': (origMt !== undefined) ? origMt + 'px' : '',\r\n            'margin-bottom': '',\r\n            'margin-left': '',\r\n            'margin-right': ''\r\n        });\r\n        \/\/ \u2705 v2.0.11 : Cleanup event namespace docpreview\r\n        $droppable.off('click.docpreview');\r\n        \r\n        \/\/ Reset .OrdiMobileConteneurClass margins\r\n        var $container = $droppable.find('.OrdiMobileConteneurClass');\r\n        var origMb = $container.data('orig-mb');\r\n        $container.css({\r\n            'margin-top': '',\r\n            'margin-bottom': (origMb !== undefined) ? origMb + 'px' : ''\r\n        });\r\n        \r\n        \/\/ Reset .HTMLUploadfileConteneur (set by styleUploadedAd: box-shadow inset, background-color:white)\r\n        $droppable.find('.HTMLUploadfileConteneur').css({\r\n            'border': '',\r\n            'box-shadow': '',\r\n            'outline': '',\r\n            'outline-offset': '',\r\n            'background-color': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': '',\r\n            'overflow': '',\r\n            'padding': '',\r\n            'position': ''\r\n        });\r\n        $droppable.find('.via-green-border-overlay').remove();\r\n        $droppable.find('.via-position-label').remove();\r\n        \/\/ \u2705 v2.7.3 : Nettoyage header\/footer\/wrapper inject\u00e9s par _buildAdOverlay\r\n        var $_ufcReset = $droppable.find('.HTMLUploadfileConteneur');\r\n        \/\/ Sortir HTMLUploadfileConteneur du wrapper avant de supprimer le wrapper\r\n        var $_wrapReset = $droppable.find('.via-ad-wrapper');\r\n        if ($_wrapReset.length) {\r\n            $_wrapReset.before($_ufcReset);\r\n            $_wrapReset.remove();\r\n        }\r\n        $droppable.find('.via-ad-header').remove();\r\n        $droppable.find('.via-ad-footer').remove();\r\n        $_ufcReset.css({'display': '', 'flex-direction': '', 'overflow': '', 'box-shadow': '', 'background-color': '', 'margin': '', 'padding': ''});\r\n        \r\n        \/\/ \u2705 Restaurer pointer-events sur OrdiMobileConteneurClass et ses enfants\r\n        $droppable.find('.OrdiMobileConteneurClass').css('pointer-events', '');\r\n        $droppable.find('#CroixResetAnnonce').css({'pointer-events': '', 'position': '', 'z-index': ''});\r\n        \/\/ \u2705 v2.4.5 : Reset margin-right Ele0A (pos\u00e9 par adjustMobileLayout)\r\n        var _croixContReset = $droppable.find('.CroixResetAnnonceContainer')[0];\r\n        if (_croixContReset) { _croixContReset.style.removeProperty('margin-right'); _croixContReset.style.removeProperty('margin-top'); }\r\n        $droppable.find('#PopUpMessageAchattest').css('pointer-events', '');\r\n        \r\n        \/\/ \u2705 v2.0.9 : Restaurer le scale(1.4) sur .UploadFileConteneur (r\u00e9duit \u00e0 scale(1) par styleUploadedAd sur mobile)\r\n        $droppable.find('.UploadFileConteneur').css({\r\n            'transform': '',\r\n            'transform-origin': ''\r\n        });\r\n        \r\n        \/\/ Reset #UploadFileConteneur - Restaurer le fond bleu #9FC5F3 + retirer liser\u00e9 envoi diff\u00e9r\u00e9\r\n        $droppable.find('#UploadFileConteneur').css({\r\n            'width': '',\r\n            'background-color': '#9FC5F3',\r\n            'border': '',\r\n            'box-sizing': ''\r\n        });\r\n        \r\n        \/\/ Reset .ToBeHidden (set by adjustDesktopLayout: top:105px, min-height:300px + overflow mobile)\r\n        $droppable.closest('.ToBeHidden').css({\r\n            'top': '',\r\n            'min-height': '',\r\n            'overflow': ''\r\n        });\r\n        \r\n        \/\/ Reset overflow sur le parent du droppable (set by adjustDesktopLayout)\r\n        $droppable.parent().css('overflow', '');\r\n        \r\n        \/\/ \u2705 v1.19.5 : Gestion device-specific pour les textes\r\n        var isDesktop = window.outerWidth >= 1000;\r\n        \r\n        if (isDesktop) {\r\n            \/\/ Desktop : cacher les textes mobiles, afficher UploadIci\r\n            $droppable.find('.TexteMobile').hide();\r\n            $droppable.find('.TexteMobileAnnonce').hide();\r\n            $droppable.find('.TexteMobileAjoutAnnonce').hide();\r\n            $droppable.find('.UploadIci').show();\r\n        } else {\r\n            \/\/ Mobile : afficher TexteMobileAnnonce\r\n            $droppable.find('.TexteMobileAnnonce').show();\r\n        }\r\n        \r\n        \/\/ R\u00e9-afficher les \u00e9l\u00e9ments masqu\u00e9s pendant l'upload\r\n        $droppable.find('.PositionEspacePublicitaireContainer').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').show();\r\n        $droppable.find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur > div > .elementor-widget-text-editor').show();\r\n        $droppable.find('.AdDroppedTextNotDisplayed').hide();\r\n        \/\/ \u2705 v2.4.5 : Ele0A + PopUpChoice=Yes \u2192 ne pas r\u00e9-afficher le titre si format d\u00e9j\u00e0 s\u00e9lectionn\u00e9\r\n        \/\/ v4.9ds : condition _skipTitre retir\u00e9e \u00e0 la restauration. Sur les sites pays\r\n        \/\/   (PopUpChoice=Yes) avec Ele0A, le titre Format n'\u00e9tait PAS r\u00e9affich\u00e9 apr\u00e8s\r\n        \/\/   suppression d'annonce \u2192 l'utilisateur ne voyait plus la consigne pour\r\n        \/\/   res\u00e9lectionner un format. \u00c0 la restauration, on force toujours le show().\r\n        $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n        $droppable.find('.SelectionFormatTitre').show();\r\n        $droppable.find('span.ClassHdpCdp, .ClassRefEsp').show();\r\n        $droppable.find('.EspPubFormatMainContainer').show();\r\n        $droppable.find('.EspPubFormatListe').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireClass').show();\r\n        $droppable.find('.GlisserDeposerConteneur').show();\r\n        $droppable.find('.OUClass').show();\r\n        $droppable.find('.PositionReference').show();\r\n        $droppable.find('.ClassHdpCdp').show();\r\n        $droppable.find('.ClassRefEsp').show();\r\n        $droppable.find('.EnvoiUlterieurTexte').show();\r\n        $droppable.find('.EnvoiUlterieurContainer').show();\r\n        \r\n        \/\/ \u2705 v1.19.5 : R\u00e9afficher le .ReserverContainer (bouton Elementor statique)\r\n        $droppable.find('.ReserverContainer').show();\r\n        newElement.find('.ReserverContainer').show();\r\n        \r\n        \/\/ Masquer les \u00e9l\u00e9ments sp\u00e9cifiques \u00e0 l'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle, #CroixResetAnnonce, .CroixResetAnnonceContainer, .DeplaceAnnonce').hide();\r\n        $droppable.find('#CroixResetAnnonce').css({'position': '', 'z-index': ''});\r\n        $droppable.find('.RefEspacePublicitaire').hide();\r\n        \r\n        \/\/ Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 v1.19.6 : Restaurer le format s\u00e9lectionn\u00e9 visuellement\r\n        \/\/ Chercher le format dans plusieurs sources possibles\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') \r\n            || sessionStorage.getItem('Commande_Format_Transmis')\r\n            || $droppable.data('kitFormatSelect')\r\n            || '';\r\n        \r\n        var formatchoisi = sessionStorage.getItem('Formatchoisi');\r\n        console.log('\ud83d\udcd0 Format \u00e0 restaurer:', formatSelect, '| Formatchoisi:', formatchoisi);\r\n        \r\n        \/\/ D'abord reset tous les formats visuellement (sur newElement ET $droppable)\r\n        var $allFormats = newElement.find('.EspPubFormatContainer').add($droppable.find('.EspPubFormatContainer'));\r\n        $allFormats.css({\r\n            'background-color': '',\r\n            'border': ''\r\n        });\r\n        newElement.find('.EspPubFormat').add($droppable.find('.EspPubFormat')).css({\r\n            'color': ''\r\n        });\r\n        \r\n        \/\/ Si un format \u00e9tait s\u00e9lectionn\u00e9 (via sessionStorage OU visuellement avant)\r\n        \/\/ \u2705 v2.1.1 : Sauf popup sur Ele0A (g\u00e9r\u00e9 s\u00e9par\u00e9ment via setTimeout)\r\n        \/\/ \u2705 v2.6 : Pour Ele1A\/2A\/3A, restaurer le format m\u00eame en mode popup (pop-up = format sous-jacent identique)\r\n        var _isEle0APopup = ($droppable.attr('id') === 'Ele0A' ? sessionStorage.getItem('PopUpChoice') === 'Yes' : false);\r\n        if ((formatSelect || formatchoisi === 'Yes') ? !_isEle0APopup : false) {\r\n            var formatFound = false;\r\n            \r\n            if (formatSelect) {\r\n                \/\/ \u2705 v2.1.1 : Normaliser via NFD (plus fiable que les replace manuels)\r\n                var formatLower = formatSelect.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase().replace(\/ \/g, '');\r\n                \r\n                \/\/ Appliquer sur newElement ET $droppable pour \u00eatre s\u00fbr\r\n                var $allContainers = newElement.find('.EspPubFormatContainer').add($droppable.find('.EspPubFormatContainer'));\r\n                \r\n                $allContainers.each(function() {\r\n                    var className = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    \r\n                    if (className.includes(formatLower)) {\r\n                        jQuery(this).css({'background-color': '#ffffff'});\r\n                        jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        formatFound = true;\r\n                        \/\/ \u2705 v2.4.12 : M\u00e9moriser le format restaur\u00e9 (lu par selectEspaceActif pour re-surligner apr\u00e8s reset global)\r\n                        $droppable.attr('data-restored-format', formatSelect);\r\n                        console.log('\u2705 Format restaur\u00e9 visuellement:', formatSelect, '- classe:', this.className);\r\n                    }\r\n                });\r\n            }\r\n            \r\n            \/\/ \u2705 IMPORTANT : Pr\u00e9server Formatchoisi = Yes pour permettre l'upload\r\n            sessionStorage.setItem('Formatchoisi', 'Yes');\r\n            console.log('\u2705 Formatchoisi maintenu \u00e0 Yes');\r\n            \r\n            \/\/ Basculer les titres format (sur newElement ET $droppable)\r\n            newElement.find('.SelectionFormatTitre').hide();\r\n            newElement.find('.SelectionFormatTitreBlanc').show();\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n        }\r\n    }\r\n    \r\n    \/\/ \u2705 v1.19.2 : Relancer InitLoadedPage pour recalculer positions et tailles\r\n    setTimeout(function() {\r\n        if (typeof window.InitLoadedPage === 'function') {\r\n            window.InitLoadedPage();\r\n        }\r\n        \r\n        \/\/ \u2705 v1.19.6 : R\u00e9attacher les MutationObservers sur les formats\r\n        if (typeof FormatUIManager !== 'undefined' ? FormatUIManager.observeFormatChanges : false) {\r\n            FormatUIManager.observeFormatChanges();\r\n        }\r\n    }, 100);\r\n    \r\n    console.log('\u2705 Espace publicitaire r\u00e9initialis\u00e9');\r\n    return true;\r\n}\r\n\r\n\/\/ \u2705 Exposer pour appel depuis yearbook-media.js (suppression popup mode=popup)\r\nwindow.RestoreadSpaceTemplateLocal = RestoreadSpaceTemplateLocal;\r\n\/\/ \u2705 Alias pour appel depuis processVideoDrop (drag d'annonce)\r\nwindow.RestoreadSpaceTemplate = RestoreadSpaceTemplateLocal;\r\n\r\nwindow.verifierAffichageMsgSelectEspaceLocal = function() {\r\n    const formatChoisi = StateManager.get('Formatchoisi') === 'Yes';\r\n    const fileReceived = StateManager.get('FileReceived');\r\n    const envoiUlterieur = StateManager.get('EnvoiUlterieur');\r\n    \r\n    var dateDebut, dateFin, pays;\r\n    try {\r\n        dateDebut = window.parent.$('#form-field-DebutCampagne').val();\r\n        dateFin = window.parent.$('#form-field-FinCampagne').val();\r\n        pays = window.parent.$('#form-field-Nationalite_Societe').val();\r\n    } catch(e) {\r\n        jQuery('#MsgSelectEspace').hide();\r\n        return;\r\n    }\r\n    \r\n    if (!dateDebut || !dateFin || !pays || !formatChoisi) {\r\n        jQuery('#MsgSelectEspace').hide();\r\n        return;\r\n    }\r\n    \r\n    if (fileReceived !== 'Yes') {\r\n        if (envoiUlterieur !== 'true') {\r\n            jQuery('#MsgSelectEspace').show();\r\n        } else {\r\n            jQuery('#MsgSelectEspace').hide();\r\n        }\r\n    } else {\r\n        jQuery('#MsgSelectEspace').hide();\r\n    }\r\n};\r\n\r\n\/\/ Sauvegarder les templates au chargement (apr\u00e8s rendu complet Elementor)\r\njQuery(document).ready(function() {\r\n    \/\/ \u2705 v1.19.3 : D\u00e9lai augment\u00e9 + double sauvegarde pour garantir les bonnes dimensions\r\n    setTimeout(saveAdSpaceTemplateLocal, 3000);\r\n    setTimeout(saveAdSpaceTemplateLocal, 6000);\r\n    \/\/ \u2705 v2.3.4 : Sauvegarde imm\u00e9diate d\u00e8s que la page est pr\u00eate dans l'iframe\r\n    \/\/ (avant toute interaction utilisateur)\r\n    setTimeout(saveAdSpaceTemplateLocal, 100);\r\n    setTimeout(saveAdSpaceTemplateLocal, 500);\r\n});\r\n\r\n\/\/ \u2705 v2.3.4 : Forcer sauvegarde \u00e0 la r\u00e9ception de elementsRemoved (espaces visibles + vierges)\r\nwindow.addEventListener('message', function(e) {\r\n    if (e.data ? e.data.type === 'elementsRemoved' : false) {\r\n        \/\/ Les espaces sont vierges \u00e0 ce stade \u2192 sauvegarder imm\u00e9diatement\r\n        setTimeout(saveAdSpaceTemplateLocal, 50);\r\n        setTimeout(saveAdSpaceTemplateLocal, 300);\r\n    }\r\n});\r\n\r\n\/**\r\n * Fonction helper pour le d\u00e9p\u00f4t r\u00e9dactionnel\r\n *\/\r\nfunction RedactionnelDepose() {\r\n    jQuery('#Tariftobedisplayed').html('-');\r\n    jQuery('#TarifDataStep3').html('\u00e0 choisir').css({'color': '#FB5E2A'});\r\n    jQuery('#FormatDataStep3').html('\u00e0 choisir').css({'color': '#FB5E2A'});\r\n    jQuery('#ListePaysDirect, #ListeThemeDirect, .ListeArticles, #PageAfficheeMessage').hide();\r\n    StateManager.set(\"PositionAnnonceSelection\", 'Yes');\r\n}\r\n\r\n\/**\r\n * Exposition des fonctions globales n\u00e9cessaires\r\n *\/\r\nwindow.uploadFile_achat = (e) => DropHandler.handleDrop(e);\r\nwindow.fileExplorer_achat = (e) => FileExplorer.open(e);\r\nwindow.ajaxFileUpload_achat = (fileObj, dropZone) => UploadManager.handleFileUpload(fileObj, dropZone);\r\nwindow.ActivatesendDataToParent = (dropZone) => UploadManager.activateSendDataToParent(dropZone);\r\n\/\/ \u2705 v2.3.4 : Exposer FormatUIManager pour appel depuis Entete.txt (selectEspaceActif)\r\nwindow.FormatUIManagerRef = FormatUIManager;\r\n\r\n\/\/ \u2705 v2.6 : Pb 12 \u2014 Restaurer l'affichage d'une annonce depuis son URL (sans re-upload)\r\n\/\/ Appel\u00e9 par Entete.txt > selectEspaceActif quand fileReceived=Yes mais annonce non affich\u00e9e\r\nwindow.restoreAdFromUrl = function(rankId, adUrl, formatLabel) {\r\n    console.log('\ud83d\udd04 [restoreAdFromUrl] appel | rank:', rankId, '| url:', adUrl);\r\n    if (!rankId || !adUrl) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] rank ou url manquant'); return; }\r\n    var $droppable = jQuery('#' + rankId);\r\n    if (!$droppable.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] #' + rankId + ' introuvable dans le DOM'); return; }\r\n    if ($droppable.attr('data-via-ad-loaded') === 'true') { console.log('\u23ed\ufe0f [restoreAdFromUrl] d\u00e9j\u00e0 charg\u00e9, skip'); return; }\r\n    \/\/ \u2705 v2.6 : Guard anti-concurrent \u2014 si un probe est d\u00e9j\u00e0 en cours, ne pas relancer\r\n    if ($droppable.attr('data-via-ad-restoring') === 'true') {\r\n        console.log('\u23ed\ufe0f [restoreAdFromUrl] probe en cours pour #' + rankId + ', skip | url demand\u00e9e:', adUrl);\r\n        return;\r\n    }\r\n    $droppable.attr('data-via-ad-restoring', 'true');\r\n    var $dropZone = $droppable.find('#drop_file_zone_achat');\r\n    if (!$dropZone.length) {\r\n        $droppable.removeAttr('data-via-ad-restoring');\r\n        console.warn('\u26a0\ufe0f [restoreAdFromUrl] #drop_file_zone_achat introuvable dans #' + rankId, '| enfants:', jQuery('#' + rankId).children().length);\r\n        return;\r\n    }\r\n    var _ext = (adUrl.split('?')[0].split('.').pop() || '').toLowerCase();\r\n    var _fileType = FileManager.getFileType(_ext);\r\n    var _fmt = (formatLabel || '').trim() || 'Document';\r\n    console.log('\ud83d\udce6 [restoreAdFromUrl] probe d\u00e9marr\u00e9 | ext:', _ext, '| type:', _fileType);\r\n    \/\/ \u2705 Ne pas \u00e9craser Rank_Emplacement_Page_Web \/ FullPathAdFile \/ FileReceived dans StateManager\r\n    \/\/ \u2192 \u00e9vite la race condition avec un upload en cours (finalizeUpload lirait le mauvais rank)\r\n    \/\/ \u2705 _isAdRestoration positionn\u00e9 juste avant styleUploadedAd dans chaque callback probe\r\n    \/\/ \u2192 \u00e9vite qu'un finalizeUpload concurrent ne remette le flag \u00e0 'No' avant le callback\r\n    if (_fileType === 'image') {\r\n        \/\/ \u2705 Probe: v\u00e9rifier que l'image est r\u00e9ellement accessible avant de marquer l'espace\r\n        \/\/ \u2705 _isAdRestoration positionn\u00e9 juste avant styleUploadedAd (pas avant le probe)\r\n        \/\/ \u2192 \u00e9vite qu'un finalizeUpload concurrent ne remette le flag \u00e0 'No' entre-temps\r\n        var _probe = new Image();\r\n        _probe.onload = function() {\r\n            console.log('\u2705 [restoreAdFromUrl] probe1 onload | url:', adUrl);\r\n            jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n            var $_dz = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n            if (!$_dz.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe1 onload: dropZone disparu dans #' + rankId); return; }\r\n            StateManager.set('_isAdRestoration', 'Yes');\r\n            StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n            \/\/ \u2705 Tronc commun : reset DOM propre puis flux standard\r\n            RestoreadSpaceTemplateLocal(jQuery('#' + rankId).find('.OrdiMobileConteneurClass')[0]);\r\n            var $_dz = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n            if (!$_dz.length) { return; }\r\n            PreviewRenderer.renderImage(adUrl, $_dz);\r\n            window._dropFromMiniature = true; \/\/ skip margin-top 150px\r\n            UIManager.updateAfterSuccessfulUpload($_dz);\r\n            window._dropFromMiniature = false;\r\n            UIManager.finalizeAdDisplay($_dz);\r\n            \/\/ v2.9 : si ViaPopupProcessAchat present, repositionner elements hors-lisere dans Ele0A\r\n            if (rankId === 'Ele0A') {\r\n                if ((sessionStorage.getItem('_ViaPopupOpen') === 'Yes')) {\r\n                    setTimeout(function() {\r\n                        var $_drp0A = jQuery('#Ele0A');\r\n                        \/\/ DeplaceAnnonceSubContainer : forcer margin-top positif\r\n                        var $_das = $_drp0A.find('.DeplaceAnnonceSubContainer');\r\n                        if ($_das.length) { $_das[0].style.setProperty('margin-top', '5px', 'important'); }\r\n                        \/\/ PositionEspacePublicitaireDeplacer + RefEspacePublicitaire\r\n                        $_drp0A.find('.PositionEspacePublicitaireDeplacer').each(function() {\r\n                            this.style.setProperty('margin-top',  '5px', 'important');\r\n                        });\r\n                        $_drp0A.find('.RefEspacePublicitaire').each(function() {\r\n                            this.style.setProperty('margin-top',  '5px', 'important');\r\n                            this.style.setProperty('margin-right', '5px', 'important');\r\n                        });\r\n                        \/\/ CroixResetAnnonceContainer : forcer a l'interieur du lisere\r\n                        var $_crc = $_drp0A.find('.CroixResetAnnonceContainer');\r\n                        if ($_crc.length) {\r\n                            $_crc[0].style.setProperty('top',    '5px', 'important');\r\n                            $_crc[0].style.setProperty('right',  '5px', 'important');\r\n                            $_crc[0].style.setProperty('margin', '0px', 'important');\r\n                        }\r\n                        console.log('[restoreAdFromUrl] v2.9 : Ele0A elements repositionnes (ViaPopupProcessAchat)');\r\n                    }, 200);\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.6 : Repositionner Ele0A apr\u00e8s restauration (page peinte, rect valide)\r\n            if (rankId === 'Ele0A') {\r\n                if (typeof positionEle0AOverEle1A === 'function') {\r\n                    requestAnimationFrame(function() {\r\n                        var $_e1W = jQuery('.ToBeHidden').has('[id=\"Ele1A\"]').not(jQuery('.ToBeHidden').has('#Ele0A')).first();\r\n                        var $_e1U = $_e1W.find('#UploadFileConteneur').first();\r\n                        var _lr = $_e1U.length ? $_e1U[0].getBoundingClientRect() : null;\r\n                        window._ele1ASnapRect = (_lr ? (_lr.width > 0 || _lr.height > 0) : false) ? _lr : null;\r\n                        positionEle0AOverEle1A(window._ele1ASnapRect);\r\n                        console.log('[restoreAdFromUrl] repo Ele0A liveRect:', window._ele1ASnapRect ? Math.round(window._ele1ASnapRect.top)+'\/'+Math.round(window._ele1ASnapRect.left) : 'null->fallback');\r\n                    });\r\n                }\r\n            }\r\n            console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e | rank:', rankId, '| type: image | url:', adUrl);\r\n        };\r\n        _probe.onerror = function() {\r\n            console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe1 onerror | url:', adUrl);\r\n            \/\/ \u2705 Fallback : essayer wp-admin\/uploads si wp-content\/uploads \u00e9choue\r\n            \/\/ (fichier physiquement dans wp-admin\/uploads mais URL stock\u00e9e avec wp-content)\r\n            var _altUrl = adUrl.replace('\/wp-content\/uploads\/', '\/wp-admin\/uploads\/');\r\n            if (_altUrl !== adUrl) {\r\n                var _probe2 = new Image();\r\n                _probe2.onload = function() {\r\n                    console.log('\u2705 [restoreAdFromUrl] probe2 onload | alt url:', _altUrl);\r\n                    jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n                    var $_dz2 = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n                    if (!$_dz2.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe2 onload: dropZone disparu dans #' + rankId); return; }\r\n                    StateManager.set('_isAdRestoration', 'Yes');\r\n                    StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n                    \/\/ \u2705 Tronc commun : reset DOM propre puis flux standard\r\n                    RestoreadSpaceTemplateLocal(jQuery('#' + rankId).find('.OrdiMobileConteneurClass')[0]);\r\n                    var $_dz2 = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n                    if (!$_dz2.length) { return; }\r\n                    PreviewRenderer.renderImage(_altUrl, $_dz2);\r\n                    window._dropFromMiniature = true;\r\n                    UIManager.updateAfterSuccessfulUpload($_dz2);\r\n                    window._dropFromMiniature = false;\r\n                    UIManager.finalizeAdDisplay($_dz2);\r\n                    \/\/ \u2705 v2.6 : Repositionner Ele0A apr\u00e8s restauration (page peinte, rect valide)\r\n                    if (rankId === 'Ele0A') {\r\n                        if (typeof positionEle0AOverEle1A === 'function') {\r\n                            requestAnimationFrame(function() {\r\n                                var $_e1W = jQuery('.ToBeHidden').has('[id=\"Ele1A\"]').not(jQuery('.ToBeHidden').has('#Ele0A')).first();\r\n                                var $_e1U = $_e1W.find('#UploadFileConteneur').first();\r\n                                var _lr = $_e1U.length ? $_e1U[0].getBoundingClientRect() : null;\r\n                                window._ele1ASnapRect = (_lr ? (_lr.width > 0 || _lr.height > 0) : false) ? _lr : null;\r\n                                positionEle0AOverEle1A(window._ele1ASnapRect);\r\n                                console.log('[restoreAdFromUrl] repo Ele0A liveRect:', window._ele1ASnapRect ? Math.round(window._ele1ASnapRect.top)+'\/'+Math.round(window._ele1ASnapRect.left) : 'null->fallback');\r\n                    \/\/ Fix fond gris UFC apres restauration\r\n                    var $_ufc0R = jQuery('#Ele0A').find('#UploadFileConteneur');\r\n                    if ($_ufc0R.length) { $_ufc0R.css('background-color', 'white'); console.log('[restoreAdFromUrl] UFC Ele0A -> blanc'); }\r\n                            });\r\n                        }\r\n                    }\r\n                    console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e (alt) | rank:', rankId, '| url:', _altUrl);\r\n                };\r\n                _probe2.onerror = function() {\r\n                    jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n                    console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe2 onerror AUSSI | alt url:', _altUrl, '| rank:', rankId);\r\n                };\r\n                _probe2.src = _altUrl;\r\n            } else {\r\n                console.warn('\u26a0\ufe0f [restoreAdFromUrl] image inaccessible \u2192 skip | rank:', rankId);\r\n            }\r\n        };\r\n        _probe.src = adUrl;\r\n        return;\r\n    } else if (_fileType === 'video') {\r\n        PreviewRenderer.renderVideo(adUrl, adUrl.split('\/').pop(), $dropZone);\r\n    } else {\r\n        PreviewRenderer.renderDocumentPreviewNoImage($dropZone, _fmt, '');\r\n    }\r\n    StateManager.set('_isAdRestoration', 'Yes');\r\n    UIManager.styleUploadedAd($dropZone);\r\n    if (UIManager.isMobile()) { UIManager.adjustMobileLayout($dropZone); }\r\n    console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e | rank:', rankId, '| type:', _fileType, '| url:', adUrl);\r\n};\r\n\r\n\r\n\/**\r\n * Initialisation de l'application\r\n *\/\r\njQuery(document).ready(() => {\r\n    StateManager.init();\r\n    ScrollHelper.init();\r\n    DragDropManager.init();\r\n    FormatUIManager.init();\r\n\r\n    \/\/ v4.9ca : Centrage texte dans EspPubFormatContainer via styles inline\r\n    \/\/ (le CSS inject\u00e9 dans head est \u00e9cras\u00e9 par Elementor \u2014 styles inline = priorit\u00e9 absolue)\r\n    \/\/ v4.9cc : reset complet des variables CSS Elementor + height:100% cha\u00eene compl\u00e8te\r\n    function _centerEspPubFormats() {\r\n        jQuery('.EspPubFormatContainer').each(function() {\r\n            \/\/ Container <a> lui-m\u00eame\r\n            this.style.setProperty('display', 'flex', 'important');\r\n            this.style.setProperty('flex-direction', 'row', 'important');\r\n            this.style.setProperty('align-items', 'center', 'important');\r\n            this.style.setProperty('justify-content', 'center', 'important');\r\n            this.style.setProperty('padding', '0', 'important');\r\n            this.style.setProperty('padding-top', '0', 'important');\r\n            this.style.setProperty('padding-bottom', '0', 'important');\r\n            this.style.setProperty('padding-left', '0', 'important');\r\n            this.style.setProperty('padding-right', '0', 'important');\r\n            \/\/ Variables Elementor (padding-block-*, --padding-*)\r\n            this.style.setProperty('--padding-block-start', '0px', 'important');\r\n            this.style.setProperty('--padding-block-end', '0px', 'important');\r\n            this.style.setProperty('--padding-inline-start', '0px', 'important');\r\n            this.style.setProperty('--padding-inline-end', '0px', 'important');\r\n            this.style.setProperty('--padding-top', '0px', 'important');\r\n            this.style.setProperty('--padding-bottom', '0px', 'important');\r\n            this.style.setProperty('row-gap', '0', 'important');\r\n\r\n            \/\/ Widget .EspPubFormat\r\n            var _widget = this.querySelector('.EspPubFormat');\r\n            if (_widget) {\r\n                _widget.style.setProperty('display', 'flex', 'important');\r\n                _widget.style.setProperty('flex-direction', 'row', 'important');\r\n                _widget.style.setProperty('align-items', 'center', 'important');\r\n                _widget.style.setProperty('justify-content', 'center', 'important');\r\n                _widget.style.setProperty('width', '100%', 'important');\r\n                _widget.style.setProperty('height', '100%', 'important');\r\n                _widget.style.setProperty('margin', '0', 'important');\r\n                _widget.style.setProperty('padding', '0', 'important');\r\n                _widget.style.setProperty('padding-top', '0', 'important');\r\n                _widget.style.setProperty('padding-bottom', '0', 'important');\r\n            }\r\n\r\n            \/\/ .elementor-widget-container\r\n            var _wc = this.querySelector('.elementor-widget-container');\r\n            if (_wc) {\r\n                _wc.style.setProperty('display', 'flex', 'important');\r\n                _wc.style.setProperty('align-items', 'center', 'important');\r\n                _wc.style.setProperty('justify-content', 'center', 'important');\r\n                _wc.style.setProperty('width', '100%', 'important');\r\n                _wc.style.setProperty('height', '100%', 'important');\r\n                \/\/ v4.9dr : padding-top 1px pour centrage vertical\r\n                \/\/ v4.9ds : padding-top 3px sur Communiqu\u00e9\/Interview\/Parrainage (desktop)\r\n                \/\/          (lettres descendantes q\/j abaissent le baseline visuel)\r\n                \/\/          Mobile : 1px partout (centrage flex suffit \u00e0 cette \u00e9chelle)\r\n                var _isMob = UIManager.isMobile();\r\n                var _hasDescender = this.classList.contains('FormatIdCommunique')\r\n                                 || this.classList.contains('FormatIdInterview')\r\n                                 || this.classList.contains('FormatIdParrainage');\r\n                var _padTop = _isMob ? '1px' : (_hasDescender ? '3px' : '1px');\r\n                _wc.style.setProperty('padding-top', _padTop, 'important');\r\n                _wc.style.setProperty('padding-left', '0', 'important');\r\n                _wc.style.setProperty('padding-right', '0', 'important');\r\n                _wc.style.setProperty('padding-bottom', '0px', 'important');\r\n                _wc.style.setProperty('margin', '0', 'important');\r\n                _wc.style.setProperty('margin-top', '0', 'important');\r\n                _wc.style.setProperty('margin-bottom', '0', 'important');\r\n                _wc.style.setProperty('text-align', 'center', 'important');\r\n                _wc.style.setProperty('line-height', '1', 'important');\r\n            }\r\n\r\n            \/\/ Paragraphes \u00e9ventuels g\u00e9n\u00e9r\u00e9s par Elementor text-editor\r\n            jQuery(this).find('p').each(function() {\r\n                this.style.setProperty('margin', '0', 'important');\r\n                this.style.setProperty('padding', '0', 'important');\r\n                this.style.setProperty('line-height', 'inherit', 'important');\r\n            });\r\n        });\r\n    }\r\n    \/\/ Lancer imm\u00e9diatement + apr\u00e8s un d\u00e9lai (Elementor peut appliquer ses styles apr\u00e8s le ready)\r\n    _centerEspPubFormats();\r\n    setTimeout(_centerEspPubFormats, 500);\r\n    setTimeout(_centerEspPubFormats, 1500);\r\n\r\n    \/\/ v4.9cb : FormatIdPopUp ne doit s'afficher QUE sur le site r\u00e9gie (via-regie-iframe).\r\n    \/\/ Sur les sites pays, masquer tous les items .FormatIdPopUp.\r\n    function _hidePopupFormatOnPays() {\r\n        var _isRegie = document.documentElement.classList.contains('via-regie-iframe');\r\n        if (_isRegie) return; \/\/ site r\u00e9gie \u2192 on laisse le Pop-up visible\r\n        jQuery('.FormatIdPopUp').each(function() {\r\n            this.style.setProperty('display', 'none', 'important');\r\n        });\r\n    }\r\n    _hidePopupFormatOnPays();\r\n    setTimeout(_hidePopupFormatOnPays, 500);\r\n    setTimeout(_hidePopupFormatOnPays, 1500);\r\n    \r\n    if (UIManager.isMobile()) {\r\n        UIManager.initMobileUI();\r\n    } else {\r\n        UIManager.initDesktopUI();\r\n    }\r\n    \r\n    jQuery(document).on('click', '#CroixResetAnnonce', (e) => AdResetHandler.handle(e));\r\n\r\n    \/\/ \ud83d\udd27 Fonction globale r\u00e9utilisable : force height:auto + padding\/margin 0 sur le\r\n    \/\/    .elementor-widget-text-editor qui CONTIENT l'OMC. Elementor impose une height\r\n    \/\/    explicite (ex: 297px) \u00e0 certains breakpoints \u00e9troits qui d\u00e9cale le wrapper vers le bas.\r\n    \/\/    Appel\u00e9e au d\u00e9p\u00f4t (depuis _buildAdOverlay) et \u00e0 chaque resize.\r\n    window._viaOverrideTextEditor = function(omcEl) {\r\n        if (!omcEl) return;\r\n        \/\/ Chercher l'anc\u00eatre .elementor-widget-text-editor (pas un enfant)\r\n        var editor = omcEl.closest('.elementor-widget-text-editor');\r\n        if (!editor) return;\r\n        editor.style.setProperty('height', 'auto', 'important');\r\n        editor.style.setProperty('min-height', '0', 'important');\r\n        editor.style.setProperty('padding', '0', 'important');\r\n        editor.style.setProperty('margin', '0', 'important');\r\n        var editorCont = editor.querySelector(':scope > .elementor-widget-container');\r\n        if (editorCont) {\r\n            editorCont.style.setProperty('height', 'auto', 'important');\r\n            editorCont.style.setProperty('min-height', '0', 'important');\r\n            editorCont.style.setProperty('padding', '0', 'important');\r\n            editorCont.style.setProperty('margin', '0', 'important');\r\n        }\r\n    };\r\n\r\n    \/\/ \ud83d\udd0d DIAG : log sur resize pour voir l'\u00e9tat du wrapper quand la fen\u00eatre change\r\n    \/\/ Handler de resize : override text-editor + enforcement + algo + scale\r\n    \/\/ v4.9by : EXCLURE les wrappers \u00e0 l'int\u00e9rieur de #Ele0A (popup fixed) du traitement\r\n    \/\/         Le scroll iOS fire resize \u2192 ce handler retirait position\/left\/transform sur le wrapper\r\n    \/\/         via-ad-wrapper DANS Ele0A \u2192 wrapper r\u00e9tr\u00e9cissait visuellement\r\n    var _diagResizeTimer = null;\r\n    window.addEventListener('resize', function() {\r\n        clearTimeout(_diagResizeTimer);\r\n        _diagResizeTimer = setTimeout(function() {\r\n            var $_wrappers = jQuery('.via-ad-wrapper').filter(function() {\r\n                \/\/ v4.9by : ne pas toucher aux wrappers dans Ele0A (popup fixed, g\u00e9r\u00e9 ailleurs)\r\n                return jQuery(this).closest('#Ele0A').length === 0;\r\n            });\r\n            if (!$_wrappers.length) return;\r\n            var _isRegieIframe = document.documentElement.classList.contains('via-regie-iframe');\r\n            console.log('[RESIZE] handler fire \u2014 inner=' + window.innerWidth + ' outer=' + window.outerWidth + ' isMobile=' + UIManager.isMobile() + ' wrappers=' + $_wrappers.length + ' isRegie=' + _isRegieIframe);\r\n\r\n            \/\/ \u00c9TAPE 1 : enforcement + reset transform\/mb sur tous les wrappers\r\n            $_wrappers.each(function() {\r\n                var $_drop = jQuery(this).closest('.droppable');\r\n                var $_omc = $_drop.find('.OrdiMobileConteneurClass').first();\r\n                var $_ufc = jQuery(this).find('.HTMLUploadfileConteneur').first();\r\n                var _dropEl = $_drop[0], _omcEl = $_omc[0], _ufcEl = $_ufc[0];\r\n\r\n                var _wAvantMt = this.style.marginTop || '(none)';\r\n                var _dAvantMt = _dropEl ? (_dropEl.style.marginTop || '(none)') : '?';\r\n                var _rAvant = _dropEl ? Math.round(_dropEl.getBoundingClientRect().top) : '?';\r\n\r\n                if (_omcEl) { window._viaOverrideTextEditor(_omcEl); }\r\n\r\n                \/\/ \u26a0\ufe0f CLEANUP DVM\/MOBILE : d\u00e8s que inner<1000, nettoyer les paddings\/min-height\r\n                \/\/    pos\u00e9s en plein \u00e9cran sur article\/secteur qui cr\u00e9ent de grands espaces blancs.\r\n                \/\/    Ce bloc s'ex\u00e9cute AVANT le gate UIManager.isMobile() pour couvrir \u00e0 la fois\r\n                \/\/    DVM (outer>=1000, inner<1000) et mobile vrai (outer<1000).\r\n                if (!_isRegieIframe) {\r\n                    if (window.innerWidth < 1000) {\r\n                        \/\/ Reset d'abord les marges n\u00e9gatives pour pouvoir mesurer la VRAIE hauteur\r\n                        this.style.setProperty('margin-top', '0px', 'important');\r\n                        this.style.setProperty('margin-bottom', '0px', 'important');\r\n                        \/\/ Mesure hauteur r\u00e9elle (apr\u00e8s reset)\r\n                        var _dvmWrapperH = Math.round(this.getBoundingClientRect().height);\r\n                        var _dvmWrapperW = Math.round(this.getBoundingClientRect().width);\r\n                        var _dvmDropW = _dropEl ? Math.round(_dropEl.getBoundingClientRect().width) : 0;\r\n                        \/\/ Force min fallback 200px si mesure inf\u00e9rieure\r\n                        if (_dvmWrapperH < 150) { _dvmWrapperH = 200; }\r\n                        \/\/ \u2705 Reset margin-top\/bottom sur OrdiMobileConteneurClass (pose +65px en mobile)\r\n                        if (_omcEl) {\r\n                            _omcEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _omcEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                        }\r\n                        \/\/ \u2705 Reset margin sur HTMLUploadfileConteneur (pose -85px en mobile)\r\n                        if (_ufcEl) {\r\n                            _ufcEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _ufcEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                        }\r\n                        if (_dropEl) {\r\n                            _dropEl.style.setProperty('padding-top', '0px', 'important');\r\n                            _dropEl.style.setProperty('padding-bottom', '0px', 'important');\r\n                            \/\/ PAS de min-height \u2014 laisser la hauteur naturelle du wrapper d\u00e9finir\r\n                            _dropEl.style.removeProperty('min-height');\r\n                            _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _dropEl.style.setProperty('margin-bottom', '40px', 'important');\r\n                            _dropEl.style.setProperty('height', 'auto', 'important');\r\n                            _dropEl.style.removeProperty('box-sizing');\r\n                            \/\/ \u2705 Centrer via flex sur le droppable\r\n                            _dropEl.style.setProperty('display', 'flex', 'important');\r\n                            _dropEl.style.setProperty('flex-direction', 'column', 'important');\r\n                            _dropEl.style.setProperty('justify-content', 'flex-start', 'important');\r\n                            _dropEl.style.setProperty('align-items', 'center', 'important');\r\n                        }\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM DIMS wrapperW=' + _dvmWrapperW + ' dropW=' + _dvmDropW);\r\n                        \/\/ \ud83d\udd0d DIAG chain anc\u00eatres du droppable avec leurs TOP et margins\r\n                        var _chainInfo = [];\r\n                        var _curAnc = _dropEl;\r\n                        var _chainCount = 0;\r\n                        while (_curAnc) {\r\n                            if (_chainCount >= 10) break;\r\n                            var _rCur = _curAnc.getBoundingClientRect();\r\n                            var _csCur = getComputedStyle(_curAnc);\r\n                            var _tagC = _curAnc.tagName + (_curAnc.id ? '#' + _curAnc.id : '') + '.' + ((_curAnc.className || '').toString().split(' ').slice(0, 2).join('.') || '');\r\n                            _chainInfo.push(_tagC + ' top=' + Math.round(_rCur.top) + ' mt=' + _csCur.marginTop + ' pt=' + _csCur.paddingTop);\r\n                            _curAnc = _curAnc.parentElement;\r\n                            _chainCount++;\r\n                        }\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM CHAIN from droppable up: ' + _chainInfo.join(' | '));\r\n                        var _ancD = _dropEl;\r\n                        var _ancDCount = 0;\r\n                        var _ancDDebug = [];\r\n                        while (_ancD) {\r\n                            if (_ancDCount >= 8) break;\r\n                            _ancD = _ancD.parentElement;\r\n                            _ancDCount++;\r\n                            if (!_ancD) break;\r\n                            var _inPt = _ancD.style.paddingTop;\r\n                            var _inPb = _ancD.style.paddingBottom;\r\n                            var _inMh = _ancD.style.minHeight;\r\n                            if (_inPt || _inPb || _inMh) {\r\n                                _ancDDebug.push(_ancD.tagName + '.' + ((_ancD.className || '').toString().split(' ').slice(0, 2).join('.') || '') + ' [pt=' + _inPt + ' pb=' + _inPb + ' mh=' + _inMh + ']');\r\n                            }\r\n                            _ancD.style.setProperty('padding-top', '0px', 'important');\r\n                            _ancD.style.setProperty('padding-bottom', '0px', 'important');\r\n                            _ancD.style.setProperty('min-height', '0px', 'important');\r\n                            if (_ancD.nextElementSibling) { break; }\r\n                        }\r\n                        if (_ancDDebug.length) {\r\n                            console.log('[DIAG resize] \u2699\ufe0f cleanup DVM\/mobile (inner<1000, outer=' + window.outerWidth + ') : wrapper.mt\/mb=25px droppable.min-h=' + (_dvmWrapperH + 60) + ' mb=40 | anc\u00eatres reset \u2192 ' + _ancDDebug.join(' | '));\r\n                        } else {\r\n                            console.log('[DIAG resize] \u2699\ufe0f cleanup DVM\/mobile (inner<1000, outer=' + window.outerWidth + ') : wrapper.mt\/mb=25px droppable.min-h=' + (_dvmWrapperH + 60) + ' mb=40 | wrapperH mesur\u00e9=' + _dvmWrapperH);\r\n                        }\r\n                        \/\/ \u2705 DVM\/mobile : margin-top n\u00e9gatif compense le gros vide entre le contenu\r\n                        \/\/    au-dessus et le droppable (structure Elementor pose ~260px de vide).\r\n                        this.style.setProperty('margin-top', '-120px', 'important');\r\n                        this.style.setProperty('margin-bottom', '25px', 'important');\r\n                        this.style.removeProperty('position');\r\n                        this.style.removeProperty('left');\r\n                        this.style.removeProperty('transform');\r\n                        this.style.setProperty('width', '100%', 'important');\r\n                        this.style.setProperty('max-width', '100%', 'important');\r\n                        this.style.removeProperty('margin-left');\r\n                        this.style.removeProperty('margin-right');\r\n                        this.style.setProperty('display', 'flex', 'important');\r\n                        \/\/ \ud83d\udd0d DIAG POST\r\n                        var _postCS = getComputedStyle(this);\r\n                        var _postRect = this.getBoundingClientRect();\r\n                        var _dropRect2 = _dropEl ? _dropEl.getBoundingClientRect() : null;\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM POST-APPLY | computed w=' + _postCS.width + ' | wrapper RECT left=' + Math.round(_postRect.left) + ' w=' + Math.round(_postRect.width) + ' top=' + Math.round(_postRect.top) + ' | drop RECT left=' + (_dropRect2 ? Math.round(_dropRect2.left) : '?') + ' w=' + (_dropRect2 ? Math.round(_dropRect2.width) : '?') + ' top=' + (_dropRect2 ? Math.round(_dropRect2.top) : '?'));\r\n                    }\r\n                }\r\n\r\n                if (!UIManager.isMobile()) {\r\n                    if (!_isRegieIframe) {\r\n                        \/\/ \ud83d\udd27 ENFORCEMENT JS (version qui marchait) : neutralise marges n\u00e9gatives\r\n                        \/\/    h\u00e9rit\u00e9es du mode mobile + restaure UFC + appelle l'algo inter-espaces.\r\n\r\n                        \/\/ \ud83d\udd0d DIAG : snapshot \u00e9tat AVANT\r\n                        var _cs = getComputedStyle(this);\r\n                        var _rect = this.getBoundingClientRect();\r\n                        var _dropCS = _dropEl ? getComputedStyle(_dropEl) : null;\r\n                        var _dropR = _dropEl ? _dropEl.getBoundingClientRect() : null;\r\n                        var _ufcCS = _ufcEl ? getComputedStyle(_ufcEl) : null;\r\n                        var _ufcR = _ufcEl ? _ufcEl.getBoundingClientRect() : null;\r\n                        console.log('[DIAG resize] inner=' + window.innerWidth + ' outer=' + window.outerWidth\r\n                            + ' | htmlClass=\"' + document.documentElement.className + '\"'\r\n                            + ' | bodyClass=\"' + document.body.className + '\"');\r\n                        console.log('[DIAG resize] WRAPPER inline mt=' + this.style.marginTop + ' mb=' + this.style.marginBottom\r\n                            + ' | computed mt=' + _cs.marginTop + ' mb=' + _cs.marginBottom\r\n                            + ' | rect top=' + Math.round(_rect.top) + ' h=' + Math.round(_rect.height) + ' w=' + Math.round(_rect.width));\r\n                        if (_dropEl) {\r\n                            console.log('[DIAG resize] DROPPABLE#' + _dropEl.id\r\n                                + ' inline mt=' + _dropEl.style.marginTop + ' mb=' + _dropEl.style.marginBottom\r\n                                + ' | computed mt=' + _dropCS.marginTop + ' mb=' + _dropCS.marginBottom\r\n                                + ' | rect top=' + Math.round(_dropR.top) + ' h=' + Math.round(_dropR.height));\r\n                        }\r\n                        if (_ufcEl) {\r\n                            console.log('[DIAG resize] UFC inline h=' + _ufcEl.style.height + ' maxH=' + _ufcEl.style.maxHeight\r\n                                + ' | computed h=' + _ufcCS.height + ' maxH=' + _ufcCS.maxHeight\r\n                                + ' | rect top=' + Math.round(_ufcR.top) + ' h=' + Math.round(_ufcR.height));\r\n                        }\r\n\r\n                        \/\/ 1. Neutraliser les marges n\u00e9gatives h\u00e9rit\u00e9es du mode mobile (wrapper + droppable)\r\n                        var _mt = parseFloat(this.style.marginTop) || 0;\r\n                        if (_mt < 0) {\r\n                            this.style.setProperty('margin-top', '0px', 'important');\r\n                            console.log('[DIAG resize] \u2699\ufe0f wrapper : margin-top forc\u00e9 \u00e0 0 (\u00e9tait ' + _mt + ')');\r\n                        }\r\n                        if (_dropEl) {\r\n                            var _dmt = parseFloat(_dropEl.style.marginTop) || 0;\r\n                            if (_dmt < 0) {\r\n                                _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                                console.log('[DIAG resize] \u2699\ufe0f droppable : margin-top forc\u00e9 \u00e0 0 (\u00e9tait ' + _dmt + ')');\r\n                            }\r\n                        }\r\n\r\n                        \/\/ 2. Restaurer max-height UFC + force height auto\r\n                        \/\/ v4.9ds : Ele0A=260, Ele1A+=280 ; ne pas toucher au dropZone (g\u00e9r\u00e9 par _applyDzMinH)\r\n                        if (_ufcEl) {\r\n                            var _isE0AResize = $_drop.attr('id') === 'Ele0A';\r\n                            _ufcEl.style.removeProperty('min-height');\r\n                            _ufcEl.style.setProperty('max-height', _isE0AResize ? '260px' : '280px', 'important');\r\n                            _ufcEl.style.setProperty('height', 'auto', 'important');\r\n                        }\r\n                        \/\/ v4.9ds : ne PAS toucher au dropZone height\/min-height ici\r\n                        \/\/          (sinon \u00e9crase ce que _applyDzMinH a pos\u00e9 sur Ele1A+)\r\n                        \/\/          Au lieu de \u00e7a, on rejoue _applyDzMinH si elle est expos\u00e9e sur le droppable\r\n                        if (_dropEl ? _dropEl._applyDzMinH : false) {\r\n                            try { _dropEl._applyDzMinH('resize'); } catch(_e) { console.warn('[resize] _applyDzMinH a throw', _e); }\r\n                        }\r\n                        var _imgInner = _ufcEl ? _ufcEl.querySelector('img, video') : null;\r\n                        if (_imgInner) {\r\n                            \/\/ v4.9ds : aligner max-height image sur la hauteur r\u00e9elle du dropZone\r\n                            \/\/   pour \u00e9viter le crop par overflow:hidden du wrapper.\r\n                            \/\/   IMPORTANT : utiliser offsetHeight (qui n'est PAS affect\u00e9 par\r\n                            \/\/   le zoom\/transform\/scale d'un parent) plut\u00f4t que\r\n                            \/\/   getBoundingClientRect().height (qui retourne les pixels \u00e9cran\r\n                            \/\/   r\u00e9els apr\u00e8s transformations). Le rect.h peut \u00eatre r\u00e9duit par\r\n                            \/\/   un zoom:55% appliqu\u00e9 \u00e0 un anc\u00eatre via _viaRunInterEspaces, ce\r\n                            \/\/   qui produirait une max-height absurde.\r\n                            var _dzInnerH = _ufcEl ? _ufcEl.querySelector('#drop_file_zone_achat') : null;\r\n                            var _dzH = _dzInnerH ? _dzInnerH.offsetHeight : 0;\r\n                            var _imgMaxHRsz = _dzH > 50 ? Math.max(0, Math.floor(_dzH - 5)) : 250;\r\n                            _imgInner.style.setProperty('max-height', _imgMaxHRsz + 'px', 'important');\r\n                        }\r\n\r\n                        \/\/ 3. Gater l'algo + scale : n\u00e9cessite inner >= 1000 (viewport CSS desktop).\r\n                        \/\/    En \"desktop version mobile\" (outer>=1000 mais inner<1000), le layout CSS\r\n                        \/\/    est en mode mobile \u2014 l'algo se tromperait.\r\n                        if (window.innerWidth < 1000) {\r\n                            \/\/ \u26a0\ufe0f DVM : nettoyer les paddings\/min-height pos\u00e9s pr\u00e9c\u00e9demment\r\n                            \/\/    (article\/secteur) qui cr\u00e9aient de grands espaces blancs.\r\n                            \/\/    removeProperty ne bat pas un !important inline \u2192 forcer \u00e0 0 !important.\r\n                            if (_dropEl) {\r\n                                _dropEl.style.setProperty('padding-top', '0px', 'important');\r\n                                _dropEl.style.setProperty('padding-bottom', '0px', 'important');\r\n                                _dropEl.style.setProperty('min-height', '0px', 'important');\r\n                                _dropEl.style.removeProperty('box-sizing');\r\n                            }\r\n                            \/\/ Nettoyer TOUS les anc\u00eatres qui ont un padding ou min-height inline\r\n                            \/\/ (pas seulement ceux avec classList e-con-inner\/elementor-element)\r\n                            var _ancC = _dropEl;\r\n                            var _ancCCount = 0;\r\n                            var _ancDebug = [];\r\n                            while (_ancC) {\r\n                                if (_ancCCount >= 8) break;\r\n                                _ancC = _ancC.parentElement;\r\n                                _ancCCount++;\r\n                                if (!_ancC) break;\r\n                                var _ancTag = _ancC.tagName + '.' + ((_ancC.className || '').toString().split(' ').slice(0, 2).join('.') || '');\r\n                                var _hasInlineStyle = false;\r\n                                var _inlinePt = _ancC.style.paddingTop;\r\n                                var _inlinePb = _ancC.style.paddingBottom;\r\n                                var _inlineMh = _ancC.style.minHeight;\r\n                                if (_inlinePt || _inlinePb || _inlineMh) {\r\n                                    _hasInlineStyle = true;\r\n                                    _ancDebug.push(_ancTag + ' [pt=' + _inlinePt + ' pb=' + _inlinePb + ' mh=' + _inlineMh + ']');\r\n                                }\r\n                                \/\/ Reset inline style dans tous les cas\r\n                                _ancC.style.setProperty('padding-top', '0px', 'important');\r\n                                _ancC.style.setProperty('padding-bottom', '0px', 'important');\r\n                                _ancC.style.setProperty('min-height', '0px', 'important');\r\n                                if (_ancC.nextElementSibling) { break; }\r\n                            }\r\n                            \/\/ \u2705 DVM : ~25px d'espace blanc au-dessus et en-dessous de l'annonce.\r\n                            \/\/    Appliqu\u00e9 sur le wrapper (margin-top\/bottom) qui \u00e9tait \u00e0 -150 au d\u00e9p\u00f4t.\r\n                            this.style.setProperty('margin-top', '25px', 'important');\r\n                            this.style.setProperty('margin-bottom', '25px', 'important');\r\n                            console.log('[DIAG resize] \u2699\ufe0f DVM (inner<1000) : wrapper.mt\/mb=25px | anc\u00eatres avec style inline: ' + (_ancDebug.length ? _ancDebug.join(' | ') : 'aucun'));\r\n                        }\r\n\r\n                        if (window.innerWidth >= 1000) {\r\n                            \/\/ 3a. Reset transform AVANT la mesure (hauteur naturelle)\r\n                            this.style.removeProperty('transform');\r\n                            this.style.removeProperty('margin-bottom');\r\n                            if (_dropEl) { _dropEl.style.removeProperty('margin-bottom'); }\r\n\r\n                            \/\/ 3b. Appeler l'algo inter-espaces avec droppable temporairement d\u00e9marqu\u00e9\r\n                            if (_dropEl) { if (typeof window._viaRunInterEspaces === 'function') {\r\n                                var _wasLoaded = _dropEl.getAttribute('data-via-ad-loaded') === 'true';\r\n                                if (_wasLoaded) { _dropEl.removeAttribute('data-via-ad-loaded'); }\r\n                                try {\r\n                                    window._viaInterEspacesRun = false;\r\n                                    window._viaRunInterEspaces();\r\n                                    console.log('[DIAG resize] \u2699\ufe0f _viaRunInterEspaces appel\u00e9 (droppable d\u00e9marqu\u00e9)');\r\n                                } catch (e) {\r\n                                    console.warn('[DIAG resize] \u26a0\ufe0f _viaRunInterEspaces a throw :', e);\r\n                                }\r\n                                if (_wasLoaded) { _dropEl.setAttribute('data-via-ad-loaded', 'true'); }\r\n                            } }\r\n\r\n                            \/\/ 3c. \u2705 Article (body.single) ou page secteur (body.page) plein \u00e9cran :\r\n                            \/\/    80px margin-bottom sur le DROPPABLE pour \u00e9viter chevauchement\r\n                            \/\/    avec le contenu en dessous. Appliqu\u00e9 APR\u00c8S l'algo pour qu'il persiste.\r\n                            var _isArticleOrSecteur = document.body.classList.contains('single') || document.body.classList.contains('page');\r\n                            if (_isArticleOrSecteur) {\r\n                                if (_dropEl) {\r\n                                    \/\/ Force droppable \u00e0 englober wrapper (wrapper=171 > droppable=110)\r\n                                    var _wrH = Math.round(this.getBoundingClientRect().height);\r\n                                    \/\/ \u2705 Padding-top ET padding-bottom : padding fait partie de la hauteur\r\n                                    \/\/    de la bo\u00eete et est respect\u00e9 par les parents flex.\r\n                                    \/\/    50px d'espace au-dessus + wrapperH + 0px en-dessous.\r\n                                    var _pbTotal = _wrH + 0;\r\n                                    _dropEl.style.setProperty('padding-top', '50px', 'important');\r\n                                    _dropEl.style.setProperty('padding-bottom', _pbTotal + 'px', 'important');\r\n                                    _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                                    _dropEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                                    _dropEl.style.setProperty('min-height', (_wrH + 50) + 'px', 'important');\r\n                                    _dropEl.style.setProperty('height', 'auto', 'important');\r\n                                    _dropEl.style.setProperty('overflow', 'visible', 'important');\r\n                                    _dropEl.style.setProperty('box-sizing', 'content-box', 'important');\r\n\r\n                                    \/\/ \u2705 Force height:auto sur tous les conteneurs Elementor entre\r\n                                    \/\/    le droppable et le wrapper, pour que le wrapper ne d\u00e9borde\r\n                                    \/\/    pas de son conteneur vers le contenu du dessous.\r\n                                    var _anc = this; \/\/ wrapper\r\n                                    var _ancCount = 0;\r\n                                    while (_anc) {\r\n                                        if (_ancCount >= 5) break;\r\n                                        _anc = _anc.parentElement;\r\n                                        _ancCount++;\r\n                                        if (!_anc) break;\r\n                                        if (_anc === _dropEl) break;\r\n                                        _anc.style.setProperty('height', 'auto', 'important');\r\n                                        _anc.style.setProperty('min-height', _wrH + 'px', 'important');\r\n                                        _anc.style.setProperty('overflow', 'visible', 'important');\r\n                                    }\r\n\r\n                                    \/\/ Remonter au-del\u00e0 du droppable : padding sur section Elementor\r\n                                    var _anc2 = _dropEl;\r\n                                    var _ancChain = _dropEl.tagName + '#' + _dropEl.id;\r\n                                    var _ancCount2 = 0;\r\n                                    while (_anc2) {\r\n                                        if (_ancCount2 >= 6) break;\r\n                                        _anc2 = _anc2.parentElement;\r\n                                        _ancCount2++;\r\n                                        if (!_anc2) break;\r\n                                        _ancChain += ' > ' + _anc2.tagName + '.' + ((_anc2.className || '').split(' ')[0] || '');\r\n                                        if (_anc2.classList) {\r\n                                            if (_anc2.classList.contains('e-con-inner') || _anc2.classList.contains('elementor-element')) {\r\n                                                _anc2.style.setProperty('padding-top', '50px', 'important');\r\n                                                _anc2.style.setProperty('padding-bottom', '0px', 'important');\r\n                                                _anc2.style.setProperty('min-height', (_wrH + 50) + 'px', 'important');\r\n                                            }\r\n                                        }\r\n                                        if (_anc2.nextElementSibling) { break; }\r\n                                    }\r\n                                    console.log('[DIAG resize] \u2699\ufe0f article\/secteur \u2192 droppable.padding-top=50 padding-bottom=' + _pbTotal + ' (wrapperH=' + _wrH + ') | chain=' + _ancChain);\r\n                                }\r\n                            }\r\n\r\n                            \/\/ 3d. Scale 1.3 conditionnel : uniquement si d\u00e9p\u00f4t mobile\r\n                            var _depositedMode = this.getAttribute('data-deposited-mode') || '';\r\n                            if (_depositedMode === 'mobile') {\r\n                                var _naturalRect = this.getBoundingClientRect();\r\n                                var _naturalH = _naturalRect.height;\r\n                                var _scaleFactor = 1.3;\r\n                                this.style.setProperty('transform', 'scale(' + _scaleFactor + ')', 'important');\r\n                                this.style.setProperty('transform-origin', 'top center', 'important');\r\n                                var _extraH = Math.round(_naturalH * (_scaleFactor - 1));\r\n                                this.style.setProperty('margin-bottom', _extraH + 'px', 'important');\r\n                                console.log('[DIAG resize] \u2699\ufe0f scale ' + _scaleFactor + ' (d\u00e9p\u00f4t mobile) | naturalH=' + Math.round(_naturalH) + ' | wrapper.mb +' + _extraH + 'px');\r\n                            } else {\r\n                                console.log('[DIAG resize] \u2699\ufe0f pas de scale (depositedMode=\"' + _depositedMode + '\")');\r\n                            }\r\n                        } else {\r\n                            console.log('[DIAG resize] \u2699\ufe0f layout \u00e9troit (inner=' + window.innerWidth + '<1000) \u2192 skip algo+scale');\r\n                        }\r\n                        return;\r\n                    }\r\n                    \/\/ R\u00c9GIE IFRAME\r\n                    if (_ufcEl) {\r\n                        _ufcEl.style.setProperty('max-height', '260px', 'important');\r\n                        _ufcEl.style.setProperty('height', 'auto', 'important');\r\n                    }\r\n                    var _dzInner = _ufcEl ? _ufcEl.querySelector('#drop_file_zone_achat') : null;\r\n                    if (_dzInner) {\r\n                        _dzInner.style.setProperty('max-height', '250px', 'important');\r\n                        _dzInner.style.setProperty('height', 'auto', 'important');\r\n                    }\r\n                    var _imgInner = _ufcEl ? _ufcEl.querySelector('img, video') : null;\r\n                    if (_imgInner) {\r\n                        \/\/ v4.9ds : aligner max-height image sur offsetHeight du dropZone\r\n                        \/\/   (cf. commentaire branche desktop ci-dessus \u2014 rect.h fauss\u00e9 par zoom parent)\r\n                        var _dzH2 = _dzInner ? _dzInner.offsetHeight : 0;\r\n                        var _imgMaxHRsz2 = _dzH2 > 50 ? Math.max(0, Math.floor(_dzH2 - 5)) : 250;\r\n                        _imgInner.style.setProperty('max-height', _imgMaxHRsz2 + 'px', 'important');\r\n                    }\r\n                    this.style.removeProperty('transform');\r\n                }\r\n\r\n                var _wApr\u00e8sMt = this.style.marginTop || '(none)';\r\n                var _dApr\u00e8sMt = _dropEl ? (_dropEl.style.marginTop || '(none)') : '?';\r\n                var _rApr\u00e8s = _dropEl ? Math.round(_dropEl.getBoundingClientRect().top) : '?';\r\n                console.log('[RESIZE wrapper] #' + (_dropEl ? _dropEl.id : '?') + ' loaded=' + (_dropEl ? _dropEl.getAttribute('data-via-ad-loaded') : '?') + ' | AVANT wrapper.mt=' + _wAvantMt + ' droppable.mt=' + _dAvantMt + ' rect.top=' + _rAvant + ' | APR\u00c8S wrapper.mt=' + _wApr\u00e8sMt + ' droppable.mt=' + _dApr\u00e8sMt + ' rect.top=' + _rApr\u00e8s);\r\n            });\r\n\r\n            \/\/ \u00c9TAPE 2 : appel de l'algo une seule fois avec vue coh\u00e9rente\r\n            \/\/ \u26a0\ufe0f Sur sites pays, on NE RUN PAS l'algo \u00e0 l'agrandissement : \u00e7a cascade sur tous\r\n            \/\/    les droppables (y compris vides) et fait remonter les ads de plusieurs centaines\r\n            \/\/    de pixels. Layout naturel suffit.\r\n            if (!UIManager.isMobile()) { if (_isRegieIframe) {\r\n                if (typeof window._viaRunInterEspaces === 'function') {\r\n                    try {\r\n                        window._viaInterEspacesRun = false;\r\n                        window._viaRunInterEspaces();\r\n                    } catch (e) { \/* silent *\/ }\r\n                }\r\n            } else {\r\n                \/\/ Sites pays : rien de sp\u00e9cial, on laisse la layout naturelle\r\n            } }\r\n\r\n            \/\/ \u00c9TAPE 3 : scale 1.3 sur les wrappers en d\u00e9p\u00f4t mobile\r\n            if (!UIManager.isMobile()) {\r\n                $_wrappers.each(function() {\r\n                    var _depositedMode = this.getAttribute('data-deposited-mode') || '';\r\n                    if (_depositedMode === 'mobile') {\r\n                        var _naturalRect = this.getBoundingClientRect();\r\n                        var _naturalH = _naturalRect.height;\r\n                        var _scaleFactor = 1.3;\r\n                        this.style.setProperty('transform', 'scale(' + _scaleFactor + ')', 'important');\r\n                        this.style.setProperty('transform-origin', 'top center', 'important');\r\n                        var _extraH = Math.round(_naturalH * (_scaleFactor - 1));\r\n                        this.style.setProperty('margin-bottom', _extraH + 'px', 'important');\r\n                    }\r\n                });\r\n            }\r\n        }, 300);\r\n    });\r\n\r\n    \/\/ \ud83d\udd0d DIAG : fonction globale \u00e0 appeler en console \u2192 diagViaAd()\r\n    window.diagViaAd = function() {\r\n        var out = {\r\n            env: {\r\n                outerWidth: window.outerWidth,\r\n                innerWidth: window.innerWidth,\r\n                isMobileJS: UIManager.isMobile(),\r\n                isDesktopUA: UIManager.isDesktop(),\r\n                isTopWindow: window === window.top,\r\n                htmlClass: document.documentElement.className,\r\n                hasRegieClass: document.documentElement.classList.contains('via-regie-iframe'),\r\n                bodyClass: document.body.className\r\n            },\r\n            wrappers: []\r\n        };\r\n        jQuery('.via-ad-wrapper').each(function() {\r\n            var _cs = getComputedStyle(this);\r\n            var _rect = this.getBoundingClientRect();\r\n            var $_drop = jQuery(this).closest('.droppable');\r\n            var $_omc = $_drop.find('.OrdiMobileConteneurClass').first();\r\n            out.wrappers.push({\r\n                droppableId: $_drop.attr('id'),\r\n                droppableDataLoaded: $_drop.attr('data-via-ad-loaded'),\r\n                wrapperInlineStyle: this.getAttribute('style'),\r\n                wrapperComputedMarginTop: _cs.marginTop,\r\n                wrapperComputedMarginBottom: _cs.marginBottom,\r\n                wrapperRect: { top: Math.round(_rect.top), height: Math.round(_rect.height), width: Math.round(_rect.width) },\r\n                droppableInlineMarginTop: $_drop[0].style.marginTop,\r\n                omcInlineMarginTop: $_omc[0] ? $_omc[0].style.marginTop : '(no omc)',\r\n                omcInlineMarginBottom: $_omc[0] ? $_omc[0].style.marginBottom : '(no omc)'\r\n            });\r\n        });\r\n        console.log('[diagViaAd]', out);\r\n        return out;\r\n    };\r\n    console.log('[DIAG] window.diagViaAd() disponible \u2014 appelle-la en console apr\u00e8s agrandissement pour voir l\\'\u00e9tat');\r\n    \r\n    \/\/ \u2705 Clic sur le texte du label \u2192 toggle manuel de la checkbox du m\u00eame conteneur\r\n    jQuery(document).on('click', '.reserver-dynamic-label', function(e) {\r\n        e.preventDefault();\r\n        e.stopPropagation();\r\n        const $cb = $(this).closest('.reserver-dynamic-option, .reserver-dynamic-container').find('.reserver-dynamic-checkbox');\r\n        if ($cb.length) {\r\n            $cb.prop('checked', !$cb.prop('checked')).trigger('change');\r\n        }\r\n    });\r\n\r\n    \/\/ \u2705 CHECKBOX \"R\u00e9server cet espace publicitaire\" \u2014 VALIDATION ET ENVOI DES DONN\u00c9ES\r\n    \/\/ \u2705 v2.4.13 : iOS touchend\r\n    \/\/ \u2705 v2.4.13 : Mobile \u2014 \u00e9couter touchend sur input ET click\/touchend sur label\r\n    jQuery(document).on('touchend', 'input[name=\"form_fields[ReserverEspacePublicitaire]\"]', function() {\r\n        if (this._viaResTouch) { return; }\r\n        this._viaResTouch = true;\r\n        var _self = this;\r\n        setTimeout(function() { _self._viaResTouch = false; }, 500);\r\n        setTimeout(function() { jQuery(_self).trigger('change'); }, 100);\r\n    });\r\n    jQuery(document).on('touchend click', '.elementor-field-group-ReserverEspacePublicitaire label, .reserver-dynamic-label, .reserver-dynamic-option label', function(e) {\r\n        \/\/ Trouver l'input associ\u00e9\r\n        var $input = jQuery(this).closest('.elementor-field-option, .reserver-dynamic-option').find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n        if (!$input.length) { $input = jQuery(this).siblings('input[name=\"form_fields[ReserverEspacePublicitaire]\"]'); }\r\n        if (!$input.length) { $input = jQuery('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').first(); }\r\n        if ($input.length) {\r\n            if (e.type === 'touchend') { e.preventDefault(); }\r\n            var _wasChecked = $input.prop('checked');\r\n            $input.prop('checked', !_wasChecked);\r\n            setTimeout(function() {\r\n                $input.trigger('change');\r\n            }, 50);\r\n        }\r\n    });\r\n    jQuery(document).on('change', 'input[name=\"form_fields[ReserverEspacePublicitaire]\"]', function(e) {\r\n        const $checkbox = $(this);\r\n        let $droppable = $checkbox.closest('.droppable');\r\n        if (!$droppable.length) {\r\n            $droppable = $checkbox.closest('.reserver-dynamic-container').prev('.droppable');\r\n        }\r\n        \/\/ \u2705 v2.4.13 : Fallback mobile \u2014 checkbox dans body > .reserver-dynamic-container[data-droppable-id]\r\n        if (!$droppable.length) {\r\n            const $dynContainer = $checkbox.closest('.reserver-dynamic-container');\r\n            const _droppableId = $dynContainer.attr('data-droppable-id') || '';\r\n            if (_droppableId) {\r\n                $droppable = $('#' + _droppableId);\r\n            }\r\n        }\r\n        \/\/ Dernier recours : utiliser le rank en sessionStorage\r\n        if (!$droppable.length) {\r\n            const _rankFallback = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            if (_rankFallback) { $droppable = $('#' + _rankFallback); }\r\n        }\r\n        \r\n        \/\/ Si on d\u00e9coche\r\n        if (!$checkbox.is(':checked')) {\r\n            console.log('\u2610 Checkbox \"R\u00e9server\" d\u00e9coch\u00e9e');\r\n            \/\/ \u2705 Mettre \u00e0 jour le label\r\n            const $lbl = $checkbox.closest('.reserver-dynamic-option, .elementor-field-option').find('.reserver-dynamic-label, label').not('input');\r\n            $lbl.text('R\u00e9server cet espace publicitaire').css('color', '');\r\n            \/\/ \u2705 Notifier le parent pour d\u00e9-r\u00e9server l'item dans le r\u00e9cap\r\n            const _rankDecoche = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            const _emplacementDecoche = StateManager.buildEmplacementReference(_rankDecoche);\r\n            MessageManager.sendToParent('annulationReservation', {\r\n                Rank_Emplacement_Page_Web: _rankDecoche,\r\n                Commande_Emplacement_Page_Web: _emplacementDecoche,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n            \/\/ \u2705 v2.4.5 : M\u00e9moriser que ce rank a \u00e9t\u00e9 explicitement d\u00e9coch\u00e9\r\n            StateManager.set('_reserverDecoche_' + _rankDecoche, 'Yes');\r\n            console.log('\ud83d\udce4 annulationReservation envoy\u00e9 \u2192 parent | rank:', _rankDecoche, '| emplacement:', _emplacementDecoche);\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 V\u00e9rifier les conditions : format s\u00e9lectionn\u00e9 ET (fichier d\u00e9pos\u00e9 OU envoi diff\u00e9r\u00e9)\r\n        const hasFormat = FormatUIManager.hasSelectedFormat($droppable);\r\n        \/\/ v2.9 : data-via-ad-loaded est specifique au droppable, plus fiable que FileReceived global\r\n        const hasFile = StateManager.get('FileReceived') === 'Yes'\r\n            || $droppable.attr('data-via-ad-loaded') === 'true';\r\n        const hasEnvoiDiffere = $droppable.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        \r\n        console.log('\ud83d\udd0d Checkbox \"R\u00e9server\" - Validation:', { hasFormat, hasFile, hasEnvoiDiffere });\r\n        \r\n        if (!hasFormat) {\r\n            \/\/ \u2705 Si un fichier est d\u00e9j\u00e0 d\u00e9pos\u00e9 \u2192 d\u00e9duire le format depuis l'extension (ne pas bloquer)\r\n            if (hasFile) {\r\n                const _uploadedName = StateManager.get('Upload_File_Name') || '';\r\n                const _ext = _uploadedName.split('.').pop().toLowerCase();\r\n                const _fileType = FileManager.getFileType(_ext);\r\n                let _deducedFormat = '';\r\n                if (_fileType === 'video') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Video' : 'Vid\u00e9o';\r\n                } else if (_fileType === 'image') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Banner' : 'Banni\u00e8re';\r\n                } else if (_fileType === 'document') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Press release' : 'Communiqu\u00e9';\r\n                }\r\n                if (_deducedFormat) {\r\n                    StateManager.set('Commande_Format_Transmis', _deducedFormat);\r\n                    StateManager.set('FormatSelect', _deducedFormat);\r\n                    StateManager.set('Formatchoisi', 'Yes');\r\n                    console.log('\u2705 Format d\u00e9duit depuis extension (' + _ext + '):', _deducedFormat);\r\n                    \/\/ Continuer vers l'envoi (pas de return false)\r\n                } else {\r\n                    e.preventDefault();\r\n                    $checkbox.prop('checked', false);\r\n                    FormatUIManager.flashTitle($droppable);\r\n                    console.log('\u274c R\u00e9servation bloqu\u00e9e : format non d\u00e9ductible depuis extension:', _ext);\r\n                    return false;\r\n                }\r\n            } else {\r\n                e.preventDefault();\r\n                $checkbox.prop('checked', false);\r\n                FormatUIManager.flashTitle($droppable);\r\n                console.log('\u274c R\u00e9servation bloqu\u00e9e : aucun format s\u00e9lectionn\u00e9');\r\n                return false;\r\n            }\r\n        }\r\n        \r\n        if (!hasFile ? !hasEnvoiDiffere : false) {\r\n            e.preventDefault();\r\n            $checkbox.prop('checked', false);\r\n            console.log('\u274c R\u00e9servation bloqu\u00e9e : ni annonce d\u00e9pos\u00e9e ni envoi diff\u00e9r\u00e9');\r\n            return false;\r\n        }\r\n        \r\n        \/\/ \u2705 Conditions remplies \u2014 Pr\u00e9parer et envoyer les donn\u00e9es\r\n        console.log('\u2705 Checkbox \"R\u00e9server\" valid\u00e9e \u2014 envoi des donn\u00e9es');\r\n        \r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.setMultiple({\r\n            \"sendDataToParentFlag\": \"Yes\",\r\n            \"Formatchoisi\": \"Yes\",\r\n            \"Rank_Emplacement_Page_Web\": rankId,\r\n            \"Commande_Emplacement_Page_Web\": StateManager.buildEmplacementReference(rankId),\r\n            \"LoadedPageUrl\": window.location.href,\r\n            \/\/ \u2705 v2.3.0 : Forcer avant l'envoi \u2014 le setTimeout(4000) dans activateSendDataToParent\r\n            \/\/ arrive trop tard sur le 1er clic \u2192 AddNewRefInVosCampagnes restait null\r\n            \"AddNewRefInVosCampagnes\": \"Yes\"\r\n        });\r\n        \r\n        \/\/ \u2705 D\u00e9clencher l'envoi des donn\u00e9es via activateSendDataToParent\r\n        const $dropZone = $droppable.find('#drop_file_zone_achat');\r\n        UploadManager.activateSendDataToParent($dropZone);\r\n    });\r\n    \r\n    \/\/ G\u00c9RER \"Envoi diff\u00e9r\u00e9\" \u2014 marquer l'\u00e9tat sans envoyer (c'est \"R\u00e9server\" qui envoie)\r\n    \/\/ \u2705 v2.4.13 : iOS touchend\r\n    jQuery(document).on('touchend', 'input[name*=\"EnvoiUlterieur\"]', function() {\r\n        if (this._viaTouch) { return; }\r\n        this._viaTouch = true;\r\n        var _self = this;\r\n        setTimeout(function() { _self._viaTouch = false; }, 500);\r\n        setTimeout(function() { jQuery(_self).trigger('change'); }, 100);\r\n    });\r\n    jQuery(document).on('change', 'input[name*=\"EnvoiUlterieur\"]', function(e) {\r\n        const $checkbox = $(this);\r\n        let $droppable = $checkbox.closest('.droppable');\r\n        \/\/ \u2705 v2.4.13 : Fallback mobile\r\n        if (!$droppable.length) {\r\n            const $dynContainer = $checkbox.closest('.reserver-dynamic-container');\r\n            const _droppableId = $dynContainer.attr('data-droppable-id') || '';\r\n            if (_droppableId) { $droppable = $('#' + _droppableId); }\r\n        }\r\n        if (!$droppable.length) {\r\n            const _rankFallback = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            if (_rankFallback) { $droppable = $('#' + _rankFallback); }\r\n        }\r\n  \r\n        \/\/ Si on d\u00e9coche\r\n        if (!$checkbox.is(':checked')) {\r\n            StateManager.set('EnvoiUlterieur', 'false');\r\n            StateManager.set('sendDataToParentFlag', 'No'); \/\/ v4.9ds\r\n            \r\n            if (typeof window.verifierAffichageMsgSelectEspaceLocal === 'function') {\r\n                window.verifierAffichageMsgSelectEspaceLocal();\r\n            }\r\n            \r\n            \/\/ Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server\r\n            FormatUIManager.updateReserverCheckboxState($droppable);\r\n            return;\r\n        }\r\n        \r\n        \/\/ Si on coche, masquer le message\r\n        jQuery('#MsgSelectEspace').hide();\r\n    \r\n        \/\/ V\u00e9rifier si un format est s\u00e9lectionn\u00e9\r\n        const hasSelectedFormat = FormatUIManager.hasSelectedFormat($droppable);\r\n        \r\n        if (!hasSelectedFormat) {\r\n            e.preventDefault();\r\n            $checkbox.prop('checked', false);\r\n            FormatUIManager.flashTitle($droppable);\r\n            return false;\r\n        }\r\n        \r\n        \/\/ \u2705 FORMAT OK \u2014 Marquer l'\u00e9tat envoi diff\u00e9r\u00e9 (sans envoyer les donn\u00e9es)\r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.setMultiple({\r\n            \"EnvoiUlterieur\": 'true',\r\n            \"Rank_Emplacement_Page_Web\": rankId,\r\n            \"Commande_Emplacement_Page_Web\": StateManager.buildEmplacementReference(rankId),\r\n            \"LoadedPageUrl\": window.location.href,\r\n            \"FileReceived\": \"No\",\r\n            \"FullPathAdFile\": '',\r\n            \"Upload_File_Name\": '',\r\n            \"Formatchoisi\": 'Yes',\r\n            \"sendDataToParentFlag\": 'No' \/\/ v4.9ds : emp\u00eache l'ouverture du popup\r\n        });\r\n        \r\n        console.log('\ud83d\udcdd Envoi diff\u00e9r\u00e9 coch\u00e9 \u2014 en attente de validation via checkbox \"R\u00e9server\"');\r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server (maintenant activable)\r\n        FormatUIManager.updateReserverCheckboxState($droppable);\r\n    });\r\n\r\n    \/\/ \u2705 v2.2.0 : Bouton \"Cr\u00e9ation\" dans espace publicitaire \u2192 Kit Ad Creator dans le parent\r\n    jQuery(document).on('click', '.FormatIdCreation', function(e) {\r\n        e.preventDefault();\r\n        var $btn = jQuery(this);\r\n        var $droppable = $btn.closest('.droppable');\r\n        var isActive = $btn.data('creationActive') === true;\r\n\r\n        if (isActive) {\r\n            \/\/ D\u00e9sactiver\r\n            $btn.data('creationActive', false);\r\n            $btn.find('.EspPubFormat').css({'color': '#ffffff'});\r\n            $btn.css({'background-color': 'transparent'});\r\n            MessageManager.sendToParent('closeAdCreatorFromIframe', {});\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2192 toggle OFF, fermeture popup parent');\r\n            return;\r\n        }\r\n\r\n        \/\/ \u2705 Nouvelle logique sites pays\r\n        \/\/ V\u00e9rifier quel format est s\u00e9lectionn\u00e9 (fond blanc, hors Cr\u00e9ation et PopUp)\r\n        var _selFormat = '';\r\n        var _selIsVideo = false;\r\n        $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').each(function() {\r\n            var _bg = this.style.backgroundColor || '';\r\n            var _isWhite = _bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white';\r\n            if (!_isWhite) {\r\n                var _fEl = this.querySelector('.EspPubFormat');\r\n                if (_fEl) { var _col = _fEl.style.color || ''; _isWhite = _col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900'; }\r\n            }\r\n            if (_isWhite) {\r\n                var _cls = this.className || '';\r\n                _selFormat = _cls.match(\/FormatId(\\w+)\/) ? _cls.match(\/FormatId(\\w+)\/)[1] : 'unknown';\r\n                _selIsVideo = jQuery(this).hasClass('FormatIdVideo');\r\n                return false;\r\n            }\r\n        });\r\n\r\n        \/\/ R\u00e8gle 3 : si Vid\u00e9o est s\u00e9lectionn\u00e9 \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e\r\n        if (_selIsVideo) {\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation bloqu\u00e9e \u2014 format Vid\u00e9o actif');\r\n            UIManager.showFormatError($droppable, 'La cr\u00e9ation n\\'est pas disponible pour le format Vid\u00e9o');\r\n            return;\r\n        }\r\n\r\n        \/\/ R\u00e8gle 2 : aucun format s\u00e9lectionn\u00e9 \u2192 message, mais Cr\u00e9ation reste active visuellement\r\n        if (!_selFormat) {\r\n            \/\/ S\u00e9lectionner Cr\u00e9ation visuellement (fond blanc, texte vert)\r\n            $btn.data('creationActive', true);\r\n            $btn.find('.EspPubFormat').css({'color': '#37D900'});\r\n            $btn.css({'background-color': '#ffffff'});\r\n            \/\/ \u2705 Message persistant \u2014 pas de timeout, supprim\u00e9 au clic format\r\n            var $dropZone2 = $droppable.find('#drop_file_zone_achat, .drop_file_zone_achat_class').first();\r\n            if (!$dropZone2.length) { $dropZone2 = $droppable; }\r\n            var $dropZone2snap = $dropZone2;\r\n            setTimeout(function() {\r\n                jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n                jQuery('#fmt-creation-info').remove();\r\n                var _isMob2 = UIManager.isMobile();\r\n                \/\/ v4.9ds : Attacher le message au BODY (pas dans le drop zone) pour \u00e9chapper\r\n                \/\/          \u00e0 l'opacity 0.4 du format-gate appliqu\u00e9 sur .HTMLUploadfileConteneur.\r\n                \/\/          Position calcul\u00e9e dynamiquement \u00e0 partir du dropZone (au-dessus du curseur).\r\n                \/\/ v4.9ds (suit-drag) : si on est sur Ele0A et qu'un wrapper .ToBeHidden parent\r\n                \/\/          existe (cas espace pop-up draggable), on attache le message au wrapper\r\n                \/\/          au lieu du body. Le wrapper est ce qui se d\u00e9place lors du drag d'Ele0A,\r\n                \/\/          donc le message suit naturellement. L'opacity 0.4 est sur\r\n                \/\/          .HTMLUploadfileConteneur (enfant d'Ele0A) \u2014 le wrapper, parent\r\n                \/\/          d'Ele0A, y \u00e9chappe.\r\n                var _dzEl = $dropZone2snap[0];\r\n                if (!_dzEl) return;\r\n                var _rect = _dzEl.getBoundingClientRect();\r\n                \/\/ v4.9ds : desktop top:50\u219240, mobile top:54 inchang\u00e9\r\n                \/\/   + dans l'iframe r\u00e9gie (window !== window.top) : descendre de 50px\r\n                \/\/   (le message tombait sur les formats au lieu de la dropZone)\r\n                var _isInIframe = (window !== window.top);\r\n                var _extraOffset = _isInIframe ? 50 : 0;\r\n                \/\/ D\u00e9tection wrapper Ele0A pour suivi drag\r\n                var _$ele0A = jQuery(_dzEl).closest('#Ele0A');\r\n                var _$wrapper = _$ele0A.length ? _$ele0A.closest('.ToBeHidden') : jQuery();\r\n                var _useWrapper = _$wrapper.length > 0;\r\n                var _topPx, _leftPx, _$parent, _positionMode;\r\n                if (_useWrapper) {\r\n                    \/\/ Position RELATIVE au wrapper (pas au document) \u2192 suit le drag\r\n                    var _wRect = _$wrapper[0].getBoundingClientRect();\r\n                    _topPx = Math.round((_rect.top - _wRect.top) + (_isMob2 ? 54 : 40) + _extraOffset);\r\n                    _leftPx = Math.round((_rect.left - _wRect.left) + _rect.width \/ 2);\r\n                    _$parent = _$wrapper;\r\n                    _positionMode = 'absolute';\r\n                    \/\/ S'assurer que le wrapper peut h\u00e9berger un absolute (position non-static)\r\n                    var _curPos = window.getComputedStyle(_$wrapper[0]).position;\r\n                    if (_curPos === 'static') { _$wrapper.css('position', 'relative'); }\r\n                    \/\/ Permettre au message de d\u00e9border si le wrapper a overflow:hidden\r\n                    var _curOv = window.getComputedStyle(_$wrapper[0]).overflow;\r\n                    if (_curOv === 'hidden') { _$wrapper.css('overflow', 'visible'); }\r\n                } else {\r\n                    \/\/ Fallback historique : attacher au body (cas miniature, ele1a+, etc.)\r\n                    _topPx = Math.round(_rect.top + (_isMob2 ? 54 : 40) + _extraOffset + (window.scrollY || 0));\r\n                    _leftPx = Math.round(_rect.left + _rect.width \/ 2 + (window.scrollX || 0));\r\n                    _$parent = jQuery('body');\r\n                    _positionMode = 'absolute';\r\n                }\r\n                \/\/ v4.9ds : mobile scale 0.6, desktop scale 0.7\r\n                var _transform = _isMob2\r\n                    ? 'transform:translateX(-50%) scale(0.6);transform-origin:top center;'\r\n                    : 'transform:translateX(-50%) scale(0.7);transform-origin:top center;';\r\n                var _fontSize = _isMob2 ? '13px' : '14px';\r\n                \/\/ v4.9ds (fmt-info-z-fix) : z-index dynamique pour rester au-dessus du\r\n                \/\/   wrapper .ToBeHidden d'Ele0A quand celui-ci est mis en avant-plan par\r\n                \/\/   _bringPopupToFront (Entete.txt). On lit window.popupZIndex (variable\r\n                \/\/   globale du compteur z-index dans Entete.txt) et on prend max+10. Si\r\n                \/\/   absent (cas iframe ou page sans Entete.txt), fallback 99999.\r\n                \/\/   NB : si on est attach\u00e9 au wrapper, le z-index est relatif au stacking\r\n                \/\/   context du wrapper, mais on garde la valeur \u00e9lev\u00e9e par s\u00e9curit\u00e9.\r\n                var _baseZ = (typeof window.popupZIndex === 'number') ? window.popupZIndex : 99999;\r\n                var _msgZ = Math.max(99999, _baseZ + 10);\r\n                _$parent.append('<div id=\"fmt-creation-info\" style=\"'\r\n                    + 'position:' + _positionMode + ';top:' + _topPx + 'px;left:' + _leftPx + 'px;'\r\n                    + _transform\r\n                    + 'width:auto;white-space:nowrap;z-index:' + _msgZ + ';'\r\n                    + 'background:#fff;color:#FB5E2A;font-weight:700;font-size:' + _fontSize + ';'\r\n                    + 'text-align:center;padding:8px 10px;border-radius:6px;'\r\n                    + 'border:2px solid #FB5E2A;box-sizing:border-box;line-height:1.4;'\r\n                    + 'opacity:1;pointer-events:auto;'\r\n                    + '\">Merci de s\u00e9lectionner le format d\\'annonce que vous souhaitez cr\u00e9er<\/div>');\r\n            }, 80);\r\n            \/\/ D\u00e9sactiver visuellement le bouton Vid\u00e9o + changer son texte en \"D\u00e9sactiv\u00e9\"\r\n            $droppable.find('.FormatIdVideo').each(function() {\r\n                jQuery(this).css({'opacity': '0.4', 'pointer-events': 'none'});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                $lbl.attr('data-original-text', $lbl.text());\r\n                $lbl.text('D\u00e9sactiv\u00e9');\r\n            });\r\n            \/\/ NE PAS d\u00e9s\u00e9lectionner Cr\u00e9ation \u2014 laisser l'utilisateur choisir un format\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2014 aucun format s\u00e9lectionn\u00e9 \u2192 message + Vid\u00e9o d\u00e9sactiv\u00e9e');\r\n            return;\r\n        }\r\n\r\n        \/\/ R\u00e8gle 1 : format valide s\u00e9lectionn\u00e9 \u2192 d\u00e9sactiver Vid\u00e9o + ouvrir le popup apr\u00e8s 3 secondes\r\n        $btn.data('creationActive', true);\r\n        $btn.find('.EspPubFormat').css({'color': '#37D900'});\r\n        $btn.css({'background-color': '#ffffff'});\r\n        \/\/ \u2705 D\u00e9sactiver Vid\u00e9o\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '0.4', 'pointer-events': 'none'});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n            $lbl.text('D\u00e9sactiv\u00e9');\r\n        });\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') || '';\r\n        var formatTransmis = sessionStorage.getItem('Commande_Format_Transmis') || '';\r\n        var _rankKit = $droppable.attr('id') || sessionStorage.getItem('Rank_Emplacement_Page_Web') || '';\r\n        var _cSite = sessionStorage.getItem('codeSite') || '';\r\n        var _cPage = sessionStorage.getItem('codePage') || '';\r\n        var _sfxKit = _rankKit.replace('Ele', '');\r\n        var _emplKit = (_cSite ? (_cPage ? _sfxKit : false) : false) ? (_cSite + _cPage + 'L' + _sfxKit) : (sessionStorage.getItem('Commande_Emplacement_Page_Web') || '');\r\n        var _fmtSnap = formatSelect;\r\n        var _fmtTSnap = formatTransmis;\r\n        var _rkSnap = _rankKit;\r\n        var _emplSnap = _emplKit;\r\n        setTimeout(function() {\r\n            MessageManager.sendToParent('openAdCreatorFromIframe', { formatSelect: _fmtSnap, formatTransmis: _fmtTSnap, rankId: _rkSnap, emplacement: _emplSnap });\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2192 toggle ON apr\u00e8s 2s | format:', _selFormat, '| FormatSelect:', _fmtSnap);\r\n        }, 2000);\r\n    });\r\n\r\n    \/\/ \u2705 R\u00e8gle : si Vid\u00e9o est cliqu\u00e9, d\u00e9sactiver Cr\u00e9ation\r\n    jQuery(document).on('click', '.FormatIdVideo', function(e) {\r\n        var $droppable = jQuery(this).closest('.droppable');\r\n        \/\/ R\u00e9activer Vid\u00e9o (peut avoir \u00e9t\u00e9 d\u00e9sactiv\u00e9e par la r\u00e8gle 2) + restaurer texte\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ D\u00e9sactiver visuellement Cr\u00e9ation + changer texte en \"D\u00e9sactiv\u00e9\"\r\n        $droppable.find('.FormatIdCreation').each(function() {\r\n            jQuery(this).data('creationActive', false);\r\n            jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n            jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            $lbl.attr('data-original-text', $lbl.attr('data-original-text') || $lbl.text());\r\n            $lbl.text('D\u00e9sactiv\u00e9');\r\n        });\r\n        \/\/ Fermer le popup si ouvert\r\n        MessageManager.sendToParent('closeAdCreatorFromIframe', {});\r\n        console.log('\ud83c\udfa8 Vid\u00e9o s\u00e9lectionn\u00e9 \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e');\r\n    });\r\n\r\n    \/\/ \u2705 R\u00e8gle : si un autre format est s\u00e9lectionn\u00e9, r\u00e9activer Cr\u00e9ation et Vid\u00e9o + restaurer textes\r\n    jQuery(document).on('click', '.EspPubFormatContainer:not(.FormatIdCreation):not(.FormatIdPopUp):not(.FormatIdVideo)', function(e) {\r\n        var $droppable = jQuery(this).closest('.droppable');\r\n        \/\/ R\u00e9activer Vid\u00e9o + restaurer texte\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ R\u00e9activer Cr\u00e9ation + restaurer texte\r\n        $droppable.find('.FormatIdCreation').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ Supprimer les messages d'erreur\r\n        jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n    });\r\n\r\n    \/\/ \u2705 v2.2.0 : Messages depuis le parent concernant le bouton Cr\u00e9ation\r\n    window.addEventListener('message', function(event) {\r\n        var msg = event.data;\r\n        if (!msg) return;\r\n\r\n        \/\/ \u2705 v2.2.1 : Fournir la position du titre R\u00e9server pour positionner la miniature\r\n        if (msg.type === 'getReserverLabelRect') {\r\n            var $label = jQuery('.reserver-dynamic-label').first();\r\n            if ($label.length) {\r\n                var r = $label[0].getBoundingClientRect();\r\n                var iframeScale = (window.outerWidth > 1000) ? 0.75 : 0.80; \/\/ \u2705 v2.4.12 : 0.85 \u2192 0.75 (scale r\u00e9el du parent)\r\n                event.source.postMessage({\r\n                    type: 'reserVeurLabelRectResult',\r\n                    \/\/ Retourner bottom en coordonn\u00e9es iframe internes (non scal\u00e9es)\r\n                    bottom: r.bottom,\r\n                    left: r.left,\r\n                    right: r.right,\r\n                    iframeScale: iframeScale\r\n                }, '*');\r\n            } else {\r\n                event.source.postMessage({ type: 'reserVeurLabelRectResult', bottom: null }, '*');\r\n            }\r\n        }\r\n\r\n        \/\/ Fermeture du popup \u2192 d\u00e9s\u00e9lectionner Cr\u00e9ation + r\u00e9activer Vid\u00e9o dans tous les espaces\r\n        if (msg.type === 'adCreatorClosedFromParent') {\r\n            jQuery('.FormatIdCreation').each(function() {\r\n                jQuery(this).data('creationActive', false);\r\n                jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                jQuery(this).css({'background-color': 'transparent'});\r\n            });\r\n            \/\/ \u2705 R\u00e9activer Vid\u00e9o dans tous les espaces de la page\r\n            jQuery('.FormatIdVideo').each(function() {\r\n                jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                var _orig = $lbl.attr('data-original-text');\r\n                if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n            });\r\n            \/\/ v4.9ds : r\u00e9-\u00e9valuer SelectionFormatTitre \/ SelectionFormatTitreBlanc sur tous les droppables\r\n            \/\/          (Cr\u00e9ation d\u00e9s\u00e9lectionn\u00e9 \u2192 si aucun autre format actif dans le DOM, le titre rouge doit r\u00e9appara\u00eetre)\r\n            \/\/          Check DOM direct (pas updateTitleColor qui retombe sur sessionStorage Formatchoisi=Yes)\r\n            jQuery('.droppable').not('#Ele0A').each(function() {\r\n                var $_d = jQuery(this);\r\n                var _hasDomFmt = $_d.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').filter(function() {\r\n                    var _bg = jQuery(this).css('background-color') || '';\r\n                    return _bg === 'rgb(255, 255, 255)';\r\n                }).length > 0;\r\n                if (_hasDomFmt) {\r\n                    $_d.find('.SelectionFormatTitre').hide();\r\n                    $_d.find('.SelectionFormatTitreBlanc').show();\r\n                } else {\r\n                    $_d.find('.SelectionFormatTitre').each(function() { this.style.setProperty('display','block','important'); });\r\n                    $_d.find('.SelectionFormatTitreBlanc').hide();\r\n                }\r\n            });\r\n            console.log('\ud83c\udfa8 adCreatorClosedFromParent \u2192 Cr\u00e9ation d\u00e9s\u00e9lectionn\u00e9 + Vid\u00e9o r\u00e9activ\u00e9 + titres r\u00e9-\u00e9valu\u00e9s (DOM direct)');\r\n        }\r\n\r\n        \/\/ Restaurer le style du bouton Cr\u00e9ation apr\u00e8s un reset de removeElements\r\n        if (msg.type === 'restoreCreationButton') {\r\n            jQuery('.FormatIdCreation').each(function() {\r\n                if (jQuery(this).data('creationActive') === true) {\r\n                    jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                    jQuery(this).css({'background-color': '#ffffff'});\r\n                }\r\n            });\r\n            console.log('\ud83c\udfa8 restoreCreationButton re\u00e7u \u2192 style Cr\u00e9ation restaur\u00e9');\r\n        }\r\n    });\r\n\r\n});\r\n\r\n\/\/ =========================================================================\r\n\/\/ \u2705 v4.9ds Pb 9.62 R\u00e8gle 2 : Listener disableEspacesPris \u2014 grise\/d\u00e9sactive les\r\n\/\/   espaces publicitaires d\u00e9j\u00e0 pris par d'autres items du panier sur la m\u00eame page.\r\n\/\/   Re\u00e7u depuis Panier_manager.txt (notifierEspacesPrisIframe).\r\n\/\/   \"1 espace pub = 1 r\u00e9servation\" \u2192 emp\u00eache le user de cliquer \u00e0 nouveau dessus.\r\n\/\/ =========================================================================\r\n(function _viaInitDisableEspacesPris() {\r\n    \/\/ CSS inject\u00e9 une seule fois pour le grisage + overlay \"D\u00e9j\u00e0 r\u00e9serv\u00e9\"\r\n    if (!document.getElementById('via-espace-pris-style')) {\r\n        var _styleEPS = document.createElement('style');\r\n        _styleEPS.id = 'via-espace-pris-style';\r\n        _styleEPS.textContent =\r\n            '.via-espace-pris-greyed { ' +\r\n            '  opacity: 0.45 !important; ' +\r\n            '  pointer-events: none !important; ' +\r\n            '  cursor: not-allowed !important; ' +\r\n            '  position: relative !important; ' +\r\n            '  filter: grayscale(80%) !important; ' +\r\n            '}' +\r\n            '.via-espace-pris-greyed::after { ' +\r\n            '  content: \"D\u00e9j\u00e0 r\u00e9serv\u00e9\"; ' +\r\n            '  position: absolute; ' +\r\n            '  top: 50%; ' +\r\n            '  left: 50%; ' +\r\n            '  transform: translate(-50%, -50%); ' +\r\n            '  background: rgba(34, 93, 169, 0.92); ' +\r\n            '  color: #ffffff; ' +\r\n            '  padding: 5px 12px; ' +\r\n            '  border-radius: 4px; ' +\r\n            '  font-size: 13px; ' +\r\n            '  font-weight: 600; ' +\r\n            '  white-space: nowrap; ' +\r\n            '  pointer-events: none; ' +\r\n            '  z-index: 10; ' +\r\n            '  box-shadow: 0 2px 6px rgba(0,0,0,0.25); ' +\r\n            '}';\r\n        document.head.appendChild(_styleEPS);\r\n    }\r\n\r\n    function _applyEspacesPris(emplacements) {\r\n        \/\/ 1. Retirer le grisage de tous les droppables (reset)\r\n        jQuery('.droppable').removeClass('via-espace-pris-greyed');\r\n        \/\/ 2. Griser ceux dans la liste\r\n        if (!Array.isArray(emplacements)) { return; }\r\n        var _count = 0;\r\n        emplacements.forEach(function(_item) {\r\n            if (!_item) return;\r\n            \/\/ Cibler par rank (Ele1A, Ele2A, etc.) ou par data-empl\r\n            var _rank = _item.rank || '';\r\n            var _empl = _item.emplacement || '';\r\n            var $cible = jQuery();\r\n            if (_rank) {\r\n                $cible = jQuery('#' + _rank);\r\n            }\r\n            if (!$cible.length ? !!_empl : false) {\r\n                \/\/ Fallback : chercher un droppable dont la r\u00e9f\u00e9rence d'emplacement matche\r\n                $cible = jQuery('.droppable').filter(function() {\r\n                    var _t = jQuery(this).find('[data-empl]').attr('data-empl') || '';\r\n                    return _t === _empl;\r\n                });\r\n            }\r\n            if ($cible.length) {\r\n                $cible.addClass('via-espace-pris-greyed');\r\n                _count++;\r\n            }\r\n        });\r\n        console.log('\ud83d\udeab [disableEspacesPris]', _count, '\/', emplacements.length, 'espace(s) gris\u00e9(s)');\r\n    }\r\n\r\n    window.addEventListener('message', function(_eEPS) {\r\n        var _msg = _eEPS.data;\r\n        if (!_msg ? true : _msg.type !== 'disableEspacesPris') return;\r\n        _applyEspacesPris(_msg.emplacements);\r\n    });\r\n\r\n    \/\/ Exposer pour appel manuel \/ test depuis console\r\n    window._viaApplyEspacesPris = _applyEspacesPris;\r\n    console.log('\u2705 [disableEspacesPris] listener install\u00e9');\r\n})();\r\n\r\n\/\/ =========================================================================\r\n\/\/ \u2705 Listener kitAdCreated \u2014 annonce cr\u00e9\u00e9e par le Kit overlay (mode=kit)\r\n\/\/ Injecte directement dans l'espace pub identifi\u00e9 par rankId\r\nwindow.addEventListener('message', function(event) {\r\n    var msg = event.data;\r\n\r\n    if (!msg || msg.action !== 'kitAdCreated') return;\r\n\r\n    console.log('\ud83d\udd0d [DIAG kitAdCreated entr\u00e9e] receveur=' + (window === window.top ? 'PARENT' : 'IFRAME') + ' | location=' + window.location.href.substring(0, 80) + ' | rank=' + (msg.rankId || 'NULL'));\r\n\r\n    var _rankId = msg.rankId || '';\r\n    var _emplacement = msg.emplacement || '';\r\n    console.log('\ud83c\udfa8 [espace_pub] kitAdCreated re\u00e7u | rankId:', _rankId, '| emplacement:', _emplacement);\r\n\r\n    if (!_rankId) { console.warn('\u26a0\ufe0f [kitAdCreated] rankId manquant'); return; }\r\n\r\n    var $droppable = jQuery('#' + _rankId);\r\n    if (!$droppable.length) { console.warn('\u26a0\ufe0f [kitAdCreated] droppable non trouv\u00e9:', _rankId); return; }\r\n    var $dropZone = $droppable.find('.drop_file_zone_achat_class').first();\r\n    if (!$dropZone.length) { $dropZone = $droppable.find('#drop_file_zone_achat').first(); }\r\n    if (!$dropZone.length) { console.warn('\u26a0\ufe0f [kitAdCreated] dropZone non trouv\u00e9e dans', _rankId); return; }\r\n\r\n    \/\/ v4.9ds : d\u00e9verrouiller le format-gate sur le droppable cible\r\n    \/\/   Si l'utilisateur d\u00e9pose une cr\u00e9ation depuis la miniature dans un espace sans format pr\u00e9alable,\r\n    \/\/   data-via-format-gate=\"locked\" reste pos\u00e9 \u2192 opacity 0.4 sur l'annonce d\u00e9pos\u00e9e.\r\n    \/\/   On pose le format de la miniature pour lib\u00e9rer le gate.\r\n    try {\r\n        var _topW = window.top || window;\r\n        var _kitFmt = msg.format || sessionStorage.getItem('FormatSelect') || '';\r\n        if (_kitFmt && typeof _topW._viaSetDroppableFormat === 'function') {\r\n            _topW._viaSetDroppableFormat(_rankId, _kitFmt);\r\n            console.log('\ud83d\udd13 [kitAdCreated] format-gate d\u00e9verrouill\u00e9 sur', _rankId, '| format:', _kitFmt);\r\n        }\r\n    } catch(_eGate) { console.warn('\u26a0\ufe0f [kitAdCreated] _viaSetDroppableFormat error:', _eGate); }\r\n\r\n    \/\/ \u2705 Bug 7 sites pays : stocker pdfImageDataURL pour \"Ouvrir et visualiser\"\r\n    \/\/   (m\u00eame logique que thumbnailDropped qui fonctionne sur la r\u00e9gie)\r\n    if (msg.pdfImageDataURL) {\r\n        $droppable.data('kitPdfImageDataURL', msg.pdfImageDataURL);\r\n        $droppable.data('kitFormatSelect', msg.format || '');\r\n        console.log('\ud83d\udcce [kitAdCreated] pdfImageDataURL stock\u00e9 sur', _rankId, '| format:', msg.format);\r\n    } else {\r\n        $droppable.removeData('kitPdfImageDataURL');\r\n        $droppable.removeData('kitFormatSelect');\r\n    }\r\n    \/\/ v4.9ds : 1\u00e8re image utilisateur (pour preview mobile JPG-in-PDF)\r\n    if (msg.firstImageDataURL) {\r\n        $droppable.data('kitFirstImageDataURL', msg.firstImageDataURL);\r\n        console.log('\ud83d\uddbc\ufe0f [kitAdCreated] kitFirstImageDataURL stock\u00e9 sur', _rankId);\r\n        try { var _dl3 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl3) _dl3('[v4.9ds] kitAdCreated firstImg stock\u00e9 ' + _rankId, 'ok'); } catch(e) {}\r\n    } else {\r\n        $droppable.removeData('kitFirstImageDataURL');\r\n        try { var _dl4 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl4) _dl4('[v4.9ds] kitAdCreated SANS firstImg ' + _rankId, 'warn'); } catch(e) {}\r\n    }\r\n\r\n    var _dataURL = msg.fullResDataURL || msg.pdfDataURL || null;\r\n    var _filename = msg.filename || 'annonce.png';\r\n    var _isPDF = msg.isPDF || false;\r\n    if (!_dataURL) { console.warn('\u26a0\ufe0f [kitAdCreated] aucune donn\u00e9e image'); return; }\r\n\r\n    var _parts = _dataURL.split(',');\r\n    var _mime = (_parts[0].match(\/:(.*?);\/) || [])[1] || (_isPDF ? 'application\/pdf' : 'image\/png');\r\n    var _bStr = atob(_parts[1]);\r\n    var _bytes = new Uint8Array(_bStr.length);\r\n    for (var _i = 0; _i < _bStr.length; _i++) { _bytes[_i] = _bStr.charCodeAt(_i); }\r\n    var _blob = new Blob([_bytes], { type: _mime });\r\n    \/\/ \u2705 Ajouter extension si absente\r\n    var _MIME_EXT = {'image\/png':'.png','image\/jpeg':'.jpg','image\/jpg':'.jpg','image\/webp':'.webp','application\/pdf':'.pdf'};\r\n    if (_filename.indexOf('.') === -1) { _filename = _filename + (_MIME_EXT[_mime] || (_isPDF ? '.pdf' : '.png')); }\r\n    var _file = new File([_blob], _filename, { type: _mime });\r\n\r\n    \/\/ \u2705 Pr\u00e9parer StateManager\r\n    StateManager.set('Rank_Emplacement_Page_Web', _rankId);\r\n    StateManager.set('Commande_Emplacement_Page_Web', _emplacement);\r\n    StateManager.set('Formatchoisi', 'Yes');\r\n    \/\/ \u2705 Marquer le droppable directement en DOM \u2014 r\u00e9siste \u00e0 l'async, lu par styleUploadedAd\r\n    $droppable[0].setAttribute('data-kit-drop', 'true');\r\n    window._dropFromMiniature = true;\r\n\r\n    console.log('\u2705 [kitAdCreated] \u2192 UploadManager.handleFileUpload | rankId:', _rankId);\r\n    \/\/ \u2550\u2550\u2550\u2550 DIAG TEMPORAIRE v4.9ds \u2014 cha\u00eene upload kitAdCreated \u2550\u2550\u2550\u2550\r\n    console.log('\ud83d\udd0d [DIAG kit-upload] AVANT handleFileUpload | StateManager FullPathAdFile:', StateManager.get('FullPathAdFile'), '| FileReceived:', StateManager.get('FileReceived'), '| _file size:', _file.size, '| name:', _file.name);\r\n    \/\/ v4.9ds : exposer une promise globale pour que prepareUploadData puisse attendre\r\n    \/\/   l'upload avant de poster les donn\u00e9es au popup (sinon FullPathAdFile=null en BDD).\r\n    window._viaPendingUpload = window._viaPendingUpload || {};\r\n    window._viaPendingUpload[_rankId] = UploadManager.handleFileUpload(_file, $dropZone).then(function() {\r\n        console.log('\u2705 [kitAdCreated] handleFileUpload termin\u00e9');\r\n        console.log('\ud83d\udd0d [DIAG kit-upload] APR\u00c8S handleFileUpload | StateManager FullPathAdFile:', StateManager.get('FullPathAdFile'), '| FileReceived:', StateManager.get('FileReceived'), '| Upload_File_Name:', StateManager.get('Upload_File_Name'));\r\n        delete window._viaPendingUpload[_rankId];\r\n    }).catch(function(err) {\r\n        window._dropFromMiniature = false;\r\n        $droppable[0].removeAttribute('data-kit-drop');\r\n        console.error('\u274c [kitAdCreated] handleFileUpload erreur:', err);\r\n        delete window._viaPendingUpload[_rankId];\r\n    });\r\n});\r\n\r\n\/\/ \u2705 v1.17.0 : Listener \"thumbnailDropped\" \u2014 d\u00e9p\u00f4t de l'annonce depuis la miniature\r\n\/\/ =========================================================================\r\n\/**\r\n * Re\u00e7oit le drop de l'image-annonce depuis la miniature dans la page parente.\r\n * Identifie l'espace publicitaire sous le curseur, s\u00e9lectionne cet espace,\r\n * et d\u00e9clenche le flux dataFromIframeEspacePub en mode \"envoi diff\u00e9r\u00e9\"\r\n * (l'utilisateur uploadera le vrai fichier depuis le formulaire de commande).\r\n *\r\n * Coordonn\u00e9es re\u00e7ues : viewport de l'iframe (clientX\/clientY relatifs \u00e0 l'iframe)\r\n *\/\r\nwindow.addEventListener('message', function(event) {\r\n    var msg = event.data;\r\n    if (!msg || msg.action !== 'thumbnailDropped') return;\r\n\r\n    console.log('\ud83d\udce8 thumbnailDropped re\u00e7u \u2014 coords iframe:', msg.xRel, msg.yRel);\r\n\r\n    \/\/ \u2705 v1.19.3 : Corriger les coordonn\u00e9es pour le facteur de scale de l'iframe\r\n    \/\/ \u2705 v2.4.10 : 0.75 = scale appliqu\u00e9 par le PARENT sur l'\u00e9l\u00e9ment iframe (et non 0.85 qui est le scale interne OrdiMobileConteneurClass)\r\n    \/\/ Les coords xRel\/yRel viennent de getBoundingClientRect() sur l'iframe scal\u00e9 \u00e0 0.75 dans le parent \u2192 diviser par 0.75\r\n    var iframeScale = (window.outerWidth > 1000) ? 0.75 : 0.80;\r\n    \/\/ \u2705 v2.4.3 : Si Entete a intercept\u00e9 et pos\u00e9 un redirect vers Ele0A, utiliser ces coords\r\n    var _redirect = window._thumbnailDropRedirect || null;\r\n    window._thumbnailDropRedirect = null;\r\n    \/\/ \u2705 v2.4.4 : Les coords de redirect viennent de getBoundingClientRect() dans l'iframe (espace logique)\r\n    \/\/            \u2192 ne pas re-diviser par iframeScale (d\u00e9j\u00e0 en coords iframe-logiques)\r\n    var xAdjusted = _redirect ? _redirect.xRel : (msg.xRel \/ iframeScale);\r\n    var yAdjusted = _redirect ? _redirect.yRel : (msg.yRel \/ iframeScale);\r\n    if (_redirect) { console.log('\ud83d\udd00 [thumbnailDropped] coords redirig\u00e9es vers Ele0A:', Math.round(xAdjusted), Math.round(yAdjusted)); }\r\n    else { console.log('\ud83d\udcd0 Coords ajust\u00e9es (scale', iframeScale, '):', Math.round(xAdjusted), Math.round(yAdjusted)); }\r\n\r\n    \/\/ 1. Identifier l'espace publicitaire le plus proche du point de drop\r\n    \/\/    \u2705 v1.18.1 : Recherche par distance \u2014 coords \u00e9ventuellement redirig\u00e9es par Entete.txt\r\n    var allDroppables = document.querySelectorAll('.droppable');\r\n    var droppableEl = null;\r\n    var closestDist = Infinity;\r\n\r\n    \/\/ \u2705 v2.4.4 : Guard rect Ele0A \u2014 si PopUpChoice=Yes ET curseur dans le rect d'Ele0A \u2192 forcer\r\n    \/\/            (Ele0A est un popup flottant : son centre peut \u00eatre loin du curseur \u2192 algo distance l'ignore)\r\n    \/\/            Ne force PAS si le drop est hors d'Ele0A \u2192 les autres espaces restent accessibles\r\n    if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n        var _ele0ACheck = document.getElementById('Ele0A');\r\n        if (_ele0ACheck) {\r\n            var _r0A = _ele0ACheck.getBoundingClientRect();\r\n            var _margin = 30;\r\n            var _inR0A = (xAdjusted >= _r0A.left - _margin);\r\n            if (_inR0A) { _inR0A = (xAdjusted <= _r0A.right + _margin); }\r\n            if (_inR0A) { _inR0A = (yAdjusted >= _r0A.top - _margin); }\r\n            if (_inR0A) { _inR0A = (yAdjusted <= _r0A.bottom + _margin); }\r\n            if (_inR0A) {\r\n                droppableEl = _ele0ACheck;\r\n                closestDist = 0;\r\n                console.log('\ud83c\udfaf [thumbnailDropped] Ele0A forc\u00e9 (curseur dans rect Ele0A +30px)');\r\n            }\r\n        }\r\n    }\r\n\r\n    if (!droppableEl) {\r\n        allDroppables.forEach(function(d) {\r\n            var r = d.getBoundingClientRect();\r\n            var dCenterX = r.left + r.width  \/ 2;\r\n            var dCenterY = r.top  + r.height \/ 2;\r\n            var dist = Math.abs(dCenterX - xAdjusted) + Math.abs(dCenterY - yAdjusted);\r\n            if (dist < closestDist) { closestDist = dist; droppableEl = d; }\r\n        });\r\n        \/\/ \u2705 v2.4.3 : Si Entete a d\u00e9tect\u00e9 un drop sur Ele0A \u2192 forcer directement\r\n        if (_redirect) { if (_redirect.forceEle0A) {\r\n            var _ele0AForced = document.getElementById('Ele0A');\r\n            if (_ele0AForced) { droppableEl = _ele0AForced; console.log('\ud83c\udfaf [thumbnailDropped] Ele0A forc\u00e9 (redirect Entete)'); }\r\n        } }\r\n    }\r\n\r\n    if (!droppableEl || closestDist > 800) {\r\n        console.warn('thumbnailDropped \u2014 aucun droppable proche (dist min:', closestDist, ')');\r\n        return;\r\n    }\r\n    console.log('\ud83d\udccd Droppable trouv\u00e9:', droppableEl.id, '(dist:', Math.round(closestDist), ')');\r\n\r\n    var $droppable = jQuery(droppableEl);\r\n    var rankId = $droppable.attr('id');\r\n    if (!rankId) { console.warn('thumbnailDropped \u2014 .droppable sans id'); return; }\r\n\r\n    var $dropZone = $droppable.find('#drop_file_zone_achat').first();\r\n    if (!$dropZone.length) { console.warn('thumbnailDropped \u2014 #drop_file_zone_achat introuvable dans', rankId); return; }\r\n\r\n    var emplacementRef = StateManager.buildEmplacementReference(rankId);\r\n    console.log('\u2705 thumbnailDropped \u2014 espace pub:', rankId, '\u2192', emplacementRef);\r\n\r\n    \/\/ 2. Pr\u00e9parer l'\u00e9tat SessionStorage (comme un vrai drop de fichier)\r\n    StateManager.setMultiple({\r\n        'Rank_Emplacement_Page_Web':     rankId,\r\n        'Commande_Emplacement_Page_Web': emplacementRef,\r\n        'FirstUploadFileorMoved':        'FirstUpload',\r\n        'Formatchoisi':                  'Yes',\r\n        'Commande_Format_Transmis':      'Image',\r\n        'EnvoiUlterieur':                'false',\r\n        'FileReceived':                  'No',\r\n        'AdDisplayed':                   'Yes'\r\n    });\r\n\r\n    UIManager.updateEmplacementDisplay();\r\n\r\n    \/\/ 3. Construire le File \u00e0 partir du PDF (communiqu\u00e9\/interview) ou de l'image PNG (autres formats)\r\n    \/\/    puis appeler handleFileUpload \u2192 m\u00eame flux qu'un vrai drag&drop : liser\u00e9 vert, aper\u00e7u, R\u00e9server\r\n    var dataURL, mime, ext;\r\n\r\n    \/\/ \u2705 Stocker le PDF image et le format sur le droppable pour le popup de visualisation inline\r\n    if (msg.pdfImageDataURL) {\r\n        $droppable.data('kitPdfImageDataURL', msg.pdfImageDataURL);\r\n        $droppable.data('kitFormatSelect', msg.FormatSelect || '');\r\n        console.log('\ud83d\udcce pdfImageDataURL stock\u00e9 sur', rankId, '| format:', msg.FormatSelect);\r\n    } else {\r\n        $droppable.removeData('kitPdfImageDataURL');\r\n        $droppable.removeData('kitFormatSelect');\r\n    }\r\n    \/\/ v4.9ds : 1\u00e8re image utilisateur extraite par le kit (pour preview mobile JPG-in-PDF)\r\n    if (msg.firstImageDataURL) {\r\n        $droppable.data('kitFirstImageDataURL', msg.firstImageDataURL);\r\n        console.log('\ud83d\uddbc\ufe0f kitFirstImageDataURL stock\u00e9 sur', rankId);\r\n        try { var _dl1 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl1) _dl1('[v4.9ds] thumbnailDropped firstImg stock\u00e9 ' + rankId, 'ok'); } catch(e) {}\r\n    } else {\r\n        $droppable.removeData('kitFirstImageDataURL');\r\n        try { var _dl2 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl2) _dl2('[v4.9ds] thumbnailDropped SANS firstImg ' + rankId, 'warn'); } catch(e) {}\r\n    }\r\n\r\n    if (msg.isPDF ? msg.pdfDataURL : false) {\r\n        \/\/ \u2500\u2500 Format PDF (communiqu\u00e9 \/ interview) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        dataURL = msg.pdfDataURL;                         \/\/ \"data:application\/pdf;base64,\u2026\"\r\n        mime    = 'application\/pdf';\r\n        ext     = '.pdf';\r\n    } else if (msg.imageDataURL) {\r\n        \/\/ \u2500\u2500 Format image (banni\u00e8re \/ parrainage) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        dataURL = msg.imageDataURL;\r\n        var mimeMatch = dataURL.match(\/^data:([^;]+);base64,\/);\r\n        mime    = mimeMatch ? mimeMatch[1] : 'image\/png';\r\n        ext     = mime === 'image\/jpeg' ? '.jpg' : '.png';\r\n    } else {\r\n        console.warn('thumbnailDropped \u2014 aucune donn\u00e9e image\/pdf disponible');\r\n        return;\r\n    }\r\n\r\n    var baseName = msg.filename ? msg.filename.replace(\/\\.[^.]+$\/, '') : 'annonce-kit';\r\n    var fileName = baseName + ext;\r\n\r\n    \/\/ dataURL \u2192 Uint8Array \u2192 Blob \u2192 File\r\n    var b64  = dataURL.split(',')[1];\r\n    var bstr = atob(b64);\r\n    var u8   = new Uint8Array(bstr.length);\r\n    for (var i = 0; i < bstr.length; i++) { u8[i] = bstr.charCodeAt(i); }\r\n    var blob    = new Blob([u8], { type: mime });\r\n    var fileObj = new File([blob], fileName, { type: mime });\r\n\r\n    console.log('thumbnailDropped \u2192 handleFileUpload:', fileName, Math.round(fileObj.size \/ 1024) + 'KB');\r\n\r\n    \/\/ \u2705 Cacher le File pour r\u00e9utilisation lors des d\u00e9placements (\u00e9vite CORS)\r\n    window._lastRedactionnelFile = fileObj;\r\n\r\n    \/\/ \u2705 v2.4.10 : Signaler \u00e0 adjustDesktopLayout que c'est un drop depuis la miniature\r\n    \/\/ \u2192 applique max-height sur HTMLUploadfileConteneur pour \u00e9viter le d\u00e9bordement vertical (homepage corps de page)\r\n    window._dropFromMiniature = true;\r\n    \/\/ \u2705 v2.6 : Poser data-from-miniature sur le droppable pour que selectEspaceActif ignore le scale(1.35)\r\n    \/\/ Sans ce flag, selectEspaceActif applique scale(1.35) sur .HTMLUploadfileConteneur \u2192 zoom Communiqu\u00e9\/Interview\r\n    if ($droppable[0]) { $droppable[0].setAttribute('data-from-miniature', 'true'); }\r\n    UploadManager.handleFileUpload(fileObj, $dropZone);\r\n\r\n    \/\/ \u2705 v1.19.1 : Positionner l'espace \u00e0 10px du haut du viewport (tous formats)\r\n    \/\/ \u2705 v2.4.13 : Skip si drop depuis miniature \u2014 selectEspaceActif g\u00e8re d\u00e9j\u00e0 le scroll\r\n    if (!window._dropFromMiniature) {\r\n        setTimeout(function() {\r\n            var el = $droppable.find('.HTMLUploadfileConteneur')[0] || $droppable[0];\r\n            if (el) {\r\n                ScrollHelper.scrollElementTo(el, 10);\r\n            }\r\n        }, 400);\r\n    }\r\n});\r\n\r\nconsole.log('This is a ' + (UIManager.isDesktop() ? 'desktop' : 'non-desktop') + ' device.');\r\n\r\n\/\/ v2.9 : Masquer les zones pub en mode previsualisation achat\r\nwindow.addEventListener('message', function(e) {\r\n    if (!e.data) { return; }\r\n    if (e.data.action !== 'removeElements') { return; }\r\n    if (!e.data.viaPurchasePreview) { return; }\r\n    var _sid = 'via-purchase-preview-css';\r\n    if (!document.getElementById(_sid)) {\r\n        var _s = document.createElement('style');\r\n        _s.id = _sid;\r\n        _s.textContent = [\r\n            \/* Masquer le formulaire upload complet en mode previsualisation *\/\r\n            '.droppable .OrdiMobileConteneurClass { display: none !important; }',\r\n            '.droppable .reserver-dynamic-container { display: none !important; }',\r\n            '.droppable .HTMLUploadfileConteneur { display: none !important; }',\r\n            '.droppable .EspPubFormatMainContainer { display: none !important; }',\r\n            '.droppable .GlisserDeposerConteneur { display: none !important; }',\r\n            '.droppable .OUClass { display: none !important; }',\r\n            '.droppable .ChoisirEspacePublicitaireClass { display: none !important; }',\r\n            '.droppable .ChoisirEspacePublicitaire2ndLigne { display: none !important; }',\r\n            '.droppable .UploadIci { display: none !important; }',\r\n            '.droppable .EnvoiUlterieurContainer { display: none !important; }',\r\n            '.droppable .TexteMobileAnnonce { display: none !important; }',\r\n            '.droppable .AdDroppedTextNotDisplayed { display: none !important; }',\r\n            '.droppable .HideFormButton { display: none !important; }'\r\n        ].join('\\n');\r\n        document.head.appendChild(_s);\r\n    }\r\n    window.parent.postMessage({ type: 'elementsRemoved' }, '*');\r\n});\r\n\r\n\/\/ \u2705 Bug 10 : handler DelAdInIframe\r\nwindow.addEventListener('message', function(e) {\r\n    var msg = e.data;\r\n    if (!msg || msg.action !== 'DelAdInIframe') return;\r\n    var rankId = msg.RankId || '';\r\n    if (!rankId) return;\r\n    console.log('[Bug10] DelAdInIframe re\u00e7u | rank:', rankId);\r\n    var $droppable = jQuery('#' + rankId);\r\n    if (!$droppable.length) return;\r\n    var $cont = $droppable.find('.OrdiMobileConteneurClass').first();\r\n    var el = $cont.length ? $cont[0] : $droppable[0];\r\n    if (typeof RestoreadSpaceTemplateLocal === 'function') RestoreadSpaceTemplateLocal(el);\r\n    else if (typeof window.RestoreadSpaceTemplate === 'function') window.RestoreadSpaceTemplate(el);\r\n    console.log('[Bug10] espace r\u00e9initialis\u00e9 | rank:', rankId);\r\n});\r\n\r\n\/\/ ============================================================================\r\n\/\/ v4.9ds : Num\u00e9rotation 1\/2\/3 + croix \"Effacer\" sur les espaces publicitaires\r\n\/\/   - Num\u00e9ro 1 : \u00e0 hauteur de la zone Formats (.EspPubFormatMainContainer)\r\n\/\/   - Num\u00e9ro 2 : \u00e0 hauteur de la zone Glisser-d\u00e9poser (.UploadFileConteneur)\r\n\/\/   - Num\u00e9ro 3 : \u00e0 hauteur du label R\u00e9server (statique ou dynamique)\r\n\/\/   - Croix : haut-droite du wrapper visuel (.OrdiMobileConteneurClass), title=\"Effacer\"\r\n\/\/     simple X blanc sans fond ni bordure.\r\n\/\/   Couleurs num\u00e9ros : dor\u00e9 (#D0C067) Ele0A, bleu clair (#9FC5F3) Ele1A+\r\n\/\/   Tous les num\u00e9ros sont plac\u00e9s sur le .droppable directement (top calcul\u00e9 depuis\r\n\/\/   les ancres) \u2192 align\u00e9s verticalement sur la m\u00eame colonne left:8px.\r\n\/\/   Quand annonce d\u00e9pos\u00e9e (.via-ad-header pr\u00e9sent) \u2192 tous masqu\u00e9s (.via-ad-loaded).\r\n\/\/ ============================================================================\r\n(function() {\r\n    if (window._viaNumCroixReady) return;\r\n    window._viaNumCroixReady = true;\r\n\r\n    function _injectStyles() {\r\n        \/\/ \u2705 v4.9ds : marquer le body si la page est dans une iframe (cas du site r\u00e9gie qui\r\n        \/\/   charge les pages pays). Permet \u00e0 la r\u00e8gle CSS .via-step-num de masquer les\r\n        \/\/   num\u00e9ros 1\/2\/3 dans ce contexte. Demande user.\r\n        try {\r\n            if (window !== window.top) {\r\n                if (document.body) {\r\n                    document.body.classList.add('via-page-in-iframe');\r\n                } else {\r\n                    \/\/ Body pas encore pr\u00eat \u2192 re-tenter d\u00e8s qu'il l'est\r\n                    document.addEventListener('DOMContentLoaded', function() {\r\n                        if (document.body) { document.body.classList.add('via-page-in-iframe'); }\r\n                    });\r\n                }\r\n            }\r\n        } catch(_eIf) { \/* cross-origin silencieux *\/ }\r\n        if (document.getElementById('via-num-croix-styles')) return;\r\n        var s = document.createElement('style');\r\n        s.id = 'via-num-croix-styles';\r\n        s.textContent =\r\n            \/\/ Num\u00e9ros mobile (par d\u00e9faut) : 20\u00d720, font 12, left:4\r\n            '.via-step-num{position:absolute;left:4px;width:20px;height:20px;border-radius:50%;' +\r\n              'background:#ffffff;display:flex;align-items:center;justify-content:center;' +\r\n              'font-family:Roboto,Arial,sans-serif;font-weight:700;font-size:12px;line-height:1;' +\r\n              'box-shadow:0 1px 3px rgba(0,0,0,0.15);z-index:10;pointer-events:none;' +\r\n              'transform:translateY(-50%);transition:opacity 0.2s}' +\r\n            \/\/ Desktop (\u22651000px) : +40% (20\u219228, font 12\u219217), left:13 (mobile +9)\r\n            '@media (min-width:1000px){' +\r\n              '.via-step-num{width:28px;height:28px;font-size:17px;left:13px}' +\r\n            '}' +\r\n            \/\/ Couleurs avec sp\u00e9cificit\u00e9 forte + !important\r\n            'html body .via-step-num.via-num-ele0a{color:#F1C40F !important}' +\r\n            'html body .via-step-num.via-num-ele1a{color:#9FC5F3 !important}' +\r\n            \/\/ v4.9ds : \u00e9tapes valid\u00e9es (num\u00e9ros pr\u00e9c\u00e9dents l'\u00e9tape courante) \u2192 vert\r\n            \/\/   Logique data-via-format-gate (cf _updateStepGate) :\r\n            \/\/     - \"locked\" = \u00e9tape 1 active   \u2192 aucun num\u00e9ro vert\r\n            \/\/     - \"step2\"  = \u00e9tape 2 active   \u2192 num\u00e9ro 1 vert (\u00e9tape 1 valid\u00e9e)\r\n            \/\/     - (absent) = \u00e9tape 3 active   \u2192 num\u00e9ros 1 et 2 verts (\u00e9tapes 1+2 valid\u00e9es)\r\n            \/\/   \u00c9tape 4 (R\u00e9server coch\u00e9) \u2192 num\u00e9ros 1, 2 et 3 verts (les 3 \u00e9tapes valid\u00e9es).\r\n            \/\/   D\u00e9tection R\u00e9server : pr\u00e9sence de input[name=\"form_fields[ReserverEspacePublicitaire]\"]:checked\r\n            \/\/   dans la droppable. Le s\u00e9lecteur :has() est support\u00e9 par tous les navigateurs\r\n            \/\/   modernes (Safari 15.4+, Chrome 105+, Firefox 121+) \u2014 si un user a un\r\n            \/\/   navigateur trop ancien, le num\u00e9ro 3 reste \u00e0 sa couleur d'origine (jaune\/bleu),\r\n            \/\/   les 1 et 2 restent verts par les r\u00e8gles pr\u00e9c\u00e9dentes \u2014 pas de r\u00e9gression.\r\n            \/\/   On surcharge la couleur du chiffre via !important + sp\u00e9cificit\u00e9 combin\u00e9e\r\n            \/\/   pour battre la r\u00e8gle de couleur jaune\/bleu pos\u00e9e juste au-dessus.\r\n            \/\/   Vert utilis\u00e9 : #2ECC71 (coh\u00e9rent avec les liser\u00e9s de s\u00e9lection vifs).\r\n            \/\/ \u00c9tape 2 active \u2192 num\u00e9ro 1 valid\u00e9\r\n            'html body .droppable[data-via-format-gate=\"step2\"] .via-step-num[data-via-num=\"1\"],' +\r\n            \/\/ \u00c9tape 3 active (annonce d\u00e9pos\u00e9e OU envoi diff\u00e9r\u00e9 coch\u00e9 \u2014 pas d'attribut gate) \u2192 num\u00e9ros 1 et 2 valid\u00e9s\r\n            'html body .droppable:not([data-via-format-gate]) .via-step-num[data-via-num=\"1\"],' +\r\n            'html body .droppable:not([data-via-format-gate]) .via-step-num[data-via-num=\"2\"],' +\r\n            \/\/ \u00c9tape 4 : R\u00e9server coch\u00e9 \u2192 num\u00e9ro 3 valid\u00e9 (en plus de 1 et 2 d\u00e9j\u00e0 verts via r\u00e8gles ci-dessus)\r\n            'html body .droppable:has(input[name=\"form_fields[ReserverEspacePublicitaire]\"]:checked) .via-step-num[data-via-num=\"3\"]{' +\r\n              'color:#2ECC71 !important' +\r\n            '}' +\r\n            \/\/ Croix mobile : top:0 right:-2 (d\u00e9calage 2px vers la droite demand\u00e9), font 14\r\n            \/\/   pointer-events:auto !important : override l'h\u00e9ritage de pointer-events:none\r\n            \/\/     pos\u00e9 par le format-gate \"locked\" sur le parent .HTMLUploadfileConteneur\r\n            \/\/   z-index 999 : passe AU-DESSUS de tous les \u00e9l\u00e9ments locaux du droppable\r\n            \/\/     (widgets Elementor, overlays format, etc.) MAIS reste SOUS la pastille\r\n            \/\/     jaune .popupAchatAnnonce qui vit dans EnteteBackground (stacking context\r\n            \/\/     isol\u00e9 z:1000 \u2192 pastille plafonn\u00e9e ~1000 au niveau body).\r\n            \/\/   v4.9ds Fix 29 v2 : z:100 toujours insuffisant \u2014 un \u00e9l\u00e9ment local du\r\n            \/\/     droppable avec z-index entre 100 et la pastille captait encore le clic.\r\n            \/\/     Remont\u00e9 \u00e0 999 pour passer au-dessus de tout sauf la pastille.\r\n            \/\/   padding 6 8 : zone de clic plus large (mobile)\r\n            '.via-erase-btn{position:absolute;top:0px;right:-2px;' +\r\n              'cursor:pointer;z-index:999;pointer-events:auto !important;' +\r\n              'font-family:Roboto,Arial,sans-serif;font-weight:900;font-size:14px;line-height:1;' +\r\n              'color:#ffffff;user-select:none;padding:6px 8px;' +\r\n              'background:transparent !important;border:none !important;border-radius:0 !important;' +\r\n              'box-shadow:none !important;text-shadow:none !important}' +\r\n            \/\/ Desktop : croix top:3 right:3, font 16 (taille originale)\r\n            '@media (min-width:1000px){' +\r\n              '.via-erase-btn{top:3px;right:3px;font-size:16px}' +\r\n            '}' +\r\n            '.via-erase-btn:hover{opacity:0.8}' +\r\n            \/\/ Quand annonce d\u00e9pos\u00e9e \u2192 masquer imm\u00e9diatement num\u00e9ros + croix\r\n            \/\/   D\u00e9tection par 2 signaux pour activation au plus t\u00f4t :\r\n            \/\/   - data-via-ad-loaded=\"true\" (pos\u00e9 par espace_publicitaire.txt:805 d\u00e8s d\u00e9p\u00f4t)\r\n            \/\/   - .via-ad-loaded (classe pos\u00e9e par mon _updateAdLoadedClass via MutationObserver)\r\n            'html body .droppable[data-via-ad-loaded=\"true\"] .via-step-num,' +\r\n            'html body .droppable[data-via-ad-loaded=\"true\"] .via-erase-btn,' +\r\n            'html body .droppable.via-ad-loaded .via-step-num,' +\r\n            'html body .droppable.via-ad-loaded .via-erase-btn{display:none !important}' +\r\n            \/\/ \u2705 v4.9ds : sur le site r\u00e9gie, les pages pays sont charg\u00e9es dans une iframe.\r\n            \/\/   L'utilisateur a demand\u00e9 que les num\u00e9ros 1\/2\/3 ne soient pas affich\u00e9s dans\r\n            \/\/   ce contexte (coh\u00e9rent : sur la r\u00e9gie, le user manipule un panier \u2014 pas un\r\n            \/\/   process d'\u00e9tapes complet, donc les num\u00e9ros n'ont pas de sens).\r\n            \/\/   D\u00e9tection : window !== window.top (la page est dans une iframe). Marqueur\r\n            \/\/   pos\u00e9 en classe sur <body> pour permettre une r\u00e8gle CSS pure dans ce bloc.\r\n            \/\/   Le marqueur est pos\u00e9 via JS plus bas (apr\u00e8s DOM ready).\r\n            'html body.via-page-in-iframe .via-step-num{display:none !important}' +\r\n            \/\/ Conteneur ancr\u00e9 en relative + overflow visible (num\u00e9ros pouvant d\u00e9border)\r\n            '.via-num-anchor{position:relative !important;overflow:visible !important}' +\r\n            \/\/ \u2500\u2500 OPACIT\u00c9S DES NUM\u00c9ROS calqu\u00e9es sur les zones d\u00e9sactiv\u00e9es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/   Num\u00e9ro 2 (zone GD+Envoi diff\u00e9r\u00e9) : opacit\u00e9 0.6 quand \"locked\" (\u00e9tape 1)\r\n            \/\/   Num\u00e9ro 3 (zone R\u00e9server) : opacit\u00e9 0.6 quand \"locked\" OU \"step2\" (\u00e9tapes 1+2)\r\n            'html body .droppable[data-via-format-gate=\"locked\"] .via-step-num[data-via-num=\"2\"],' +\r\n            'html body .droppable[data-via-format-gate=\"locked\"] .via-step-num[data-via-num=\"3\"],' +\r\n            'html body .droppable[data-via-format-gate=\"step2\"] .via-step-num[data-via-num=\"3\"]{' +\r\n              'opacity:0.6}' +\r\n            \/\/ \u2500\u2500 v4.9ds : OVERRIDES SITE R\u00c9GIE (html.via-regie-iframe) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/   Sur la r\u00e9gie (iframe yearbook-iframe sous la page r\u00e9gie), demande user :\r\n            \/\/   - Mobile : 10px en dessous du bouton .HideFormButton.ReserverBouton\r\n            \/\/              (espace pub plus haut visuellement par le bas)\r\n            \/\/   - Le `left` est pos\u00e9 EN INLINE dans _placeNum (CSS pouvait \u00eatre outrepass\u00e9)\r\n            \/\/   - D\u00e9calage vertical N\u00b02\/N\u00b03 desktop+mobile g\u00e9r\u00e9 dans _numTopOffset\r\n            '@media (max-width:999px){' +\r\n              'html.via-regie-iframe .HideFormButton.ReserverBouton{margin-bottom:10px !important}' +\r\n            '}';\r\n        document.head.appendChild(s);\r\n    }\r\n\r\n    function _makeNum(n, isEle0A) {\r\n        var span = document.createElement('span');\r\n        span.className = 'via-step-num ' + (isEle0A ? 'via-num-ele0a' : 'via-num-ele1a');\r\n        span.textContent = String(n);\r\n        span.setAttribute('data-via-num', String(n));\r\n        return span;\r\n    }\r\n\r\n    function _makeEraseBtn() {\r\n        var btn = document.createElement('span');\r\n        btn.className = 'via-erase-btn';\r\n        btn.setAttribute('title', 'Effacer');\r\n        btn.setAttribute('data-via-erase', '1');\r\n        btn.textContent = '\\u2715'; \/\/ \u2715\r\n        return btn;\r\n    }\r\n\r\n    \/\/ Trouver l'ancre pour un num\u00e9ro donn\u00e9\r\n    function _findAnchor(drop, n) {\r\n        var $drop = jQuery(drop);\r\n        \/\/ Pour n=3, .ReserverContainer est un FR\u00c8RE du droppable (pas un descendant) \u2192\r\n        \/\/   \u00e9largir le scope au parent. Pour n=1 et n=2 on reste dans le droppable.\r\n        var $scope = (n === 3) ? $drop.parent() : $drop;\r\n        \/\/ Helper : essayer plusieurs s\u00e9lecteurs dans l'ordre, retourner le premier visible non vide\r\n        function _firstVisible(selectors) {\r\n            for (var i = 0; i < selectors.length; i++) {\r\n                var $set = $scope.find(selectors[i]);\r\n                var $vis = $set.filter(':visible').filter(function() {\r\n                    var r = this.getBoundingClientRect();\r\n                    return r.width > 0 ? r.height > 0 : false;\r\n                });\r\n                if ($vis.length) {\r\n                    return { el: $vis.first()[0], sel: selectors[i] };\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n        \/\/ Variante permissive : ne filtre PAS sur :visible (utile pour R\u00e9server qui peut\r\n        \/\/   \u00eatre en pointer-events:none ou opacity:0 mais avec dimensions r\u00e9elles)\r\n        function _firstWithDims(selectors) {\r\n            for (var i = 0; i < selectors.length; i++) {\r\n                var $set = $scope.find(selectors[i]);\r\n                for (var j = 0; j < $set.length; j++) {\r\n                    var el = $set[j];\r\n                    var r = el.getBoundingClientRect();\r\n                    if (r.width > 0 ? r.height > 0 : false) {\r\n                        return { el: el, sel: selectors[i] + ' (permissif)' };\r\n                    }\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n        if (n === 1) {\r\n            return _firstVisible([\r\n                '.SelectionFormatTitre',\r\n                '.EspPubFormatMainContainer .EspPubFormatContainer'\r\n            ]);\r\n        }\r\n        if (n === 2) {\r\n            return _firstVisible([\r\n                '.UploadIci',\r\n                '#drop_file_zone_achat .UploadIci',\r\n                '#drag_upload_file_achat',\r\n                '#drop_file_zone_achat',\r\n                '.GlisserDeposerConteneur'\r\n            ]);\r\n        }\r\n        if (n === 3) {\r\n            \/\/ Bouton R\u00e9server Elementor : .ReserverContainer \/ .ReserverBouton sont\r\n            \/\/   FR\u00c8RES du droppable (cf HTML user). Le label visible est dans\r\n            \/\/   .elementor-field-subgroup label (le screen-only n'a pas de dimensions).\r\n            return _firstVisible([\r\n                'label[for=\"form-field-ReserverEspacePublicitaire-0\"]',  \/\/ label visible\r\n                '.ReserverBouton .elementor-field-subgroup label',\r\n                '.ReserverContainer .elementor-field-subgroup label',\r\n                '.ReserverBouton',\r\n                '.ReserverContainer',\r\n                '.reserver-dynamic-label',\r\n                '.reserver-dynamic-container'\r\n            ]) || _firstWithDims([\r\n                '.ReserverBouton',\r\n                '.ReserverContainer',\r\n                '.elementor-field-group-ReserverEspacePublicitaire'\r\n            ]) || (function() {\r\n                \/\/ Dernier recours : recherche par TEXTE dans le scope \u00e9largi\r\n                var found = null;\r\n                $scope.find('label, span, p, div').each(function() {\r\n                    if (found) return;\r\n                    var t = (this.textContent || '').replace(\/\\s+\/g, ' ').trim();\r\n                    if (t.indexOf('R\u00e9server cet espace') === -1) return;\r\n                    var hasChildWithText = jQuery(this).children().toArray().some(function(c) {\r\n                        return ((c.textContent || '').replace(\/\\s+\/g, ' ').trim()).indexOf('R\u00e9server cet espace') !== -1;\r\n                    });\r\n                    if (hasChildWithText) return;\r\n                    var r = this.getBoundingClientRect();\r\n                    if (r.width > 0 ? r.height > 0 : false) {\r\n                        found = this;\r\n                    }\r\n                });\r\n                return found ? { el: found, sel: ':contains(R\u00e9server) (texte)' } : null;\r\n            })();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    \/\/ D\u00e9calage vertical (en px) \u00e0 appliquer \u00e0 un num\u00e9ro selon position + plateforme + Ele0A vs Ele1A+\r\n    \/\/   Base = 40\r\n    \/\/   Desktop  : N\u00b01 = 47 \/ N\u00b02 = 70 \/ N\u00b03 = 56 (Ele0A et Ele1A+ identiques)\r\n    \/\/   Mobile Ele1A+ : N\u00b01 = 24 \/ N\u00b02 = 23 \/ N\u00b03 = 1\r\n    \/\/   Mobile Ele0A  : N\u00b01 = 42 \/ N\u00b02 = 32 \/ N\u00b03 = 9\r\n    \/\/   v4.9ds : Compensation Ele0A mobile quand bouton Cr\u00e9ation est actif (creationActive===true).\r\n    \/\/     Le clic sur Cr\u00e9ation d\u00e9clenche un effet de layout (probablement repaint Elementor \/\r\n    \/\/     stacking flexbox \/ re-application de styles inline) qui d\u00e9cale les 3 num\u00e9ros de\r\n    \/\/     +10px vers le bas. On compense ici pour stabilit\u00e9 visuelle. Au toggle OFF\r\n    \/\/     (re-click Cr\u00e9ation), creationActive repasse \u00e0 false \u2192 compensation 0 \u2192 num\u00e9ros\r\n    \/\/     reviennent \u00e0 leur position d'origine.\r\n    \/\/   v4.9ds : d\u00e9calage vertical SITE R\u00c9GIE (Ele1A+) \u2014 demande user :\r\n    \/\/     - Desktop r\u00e9gie Ele1A+ : N\u00b01 = 0, N\u00b02 -10, N\u00b03 -15 (vers le haut)\r\n    \/\/     - Mobile r\u00e9gie Ele1A+  : N\u00b01 = 0, N\u00b02 +10 (bas), N\u00b03 = 0\r\n    \/\/   Le d\u00e9calage horizontal -3px desktop est pos\u00e9 en inline dans _placeNum.\r\n    function _numTopOffset(n, isEle0A, drop) {\r\n        var isMobile = window.innerWidth < 1000;\r\n        var base = 40;\r\n        \/\/ v4.9ds : compensation _creaComp retir\u00e9e \u2014 les num\u00e9ros restent fixes\r\n        \/\/   peu importe le format s\u00e9lectionn\u00e9. ResizeObserver g\u00e8re d\u00e9sormais les\r\n        \/\/   reflows r\u00e9els (cf _viaNumObserve). Les ancres bougent peu au clic\r\n        \/\/   format en r\u00e9alit\u00e9, le d\u00e9calage per\u00e7u \u00e9tait d\u00fb au calcul fait juste\r\n        \/\/   avant que le re-layout Elementor soit appliqu\u00e9.\r\n        \/\/ v4.9ds : d\u00e9calage r\u00e9gie pour Ele1A+ uniquement\r\n        \/\/   Desktop : N\u00b02 -10, N\u00b03 -15 ; Mobile : N\u00b02 +10 ; reste \u00e0 0\r\n        var _regieCompN1 = 0;\r\n        var _regieCompN2 = 0;\r\n        var _regieCompN3 = 0;\r\n        if (!isEle0A) {\r\n            var _isRegie = document.documentElement.classList.contains('via-regie-iframe');\r\n            if (_isRegie) {\r\n                if (isMobile) {\r\n                    _regieCompN2 = 10;\r\n                } else {\r\n                    _regieCompN2 = -10;\r\n                    _regieCompN3 = -15;\r\n                }\r\n            }\r\n        }\r\n        if (isMobile) {\r\n            if (isEle0A) {\r\n                if (n === 1) return base + 2;    \/\/ 42\r\n                if (n === 2) return base - 18;   \/\/ 22\r\n                return base - 41;                 \/\/ -1\r\n            }\r\n            \/\/ Ele1A+ mobile\r\n            if (n === 1) return base - 16 + _regieCompN1;    \/\/ 24\r\n            if (n === 2) return base - 17 + _regieCompN2;    \/\/ 23 \u2192 33 r\u00e9gie\r\n            return base - 39 + _regieCompN3;                  \/\/  1\r\n        } else {\r\n            \/\/ Desktop (Ele0A et Ele1A+ identiques sauf r\u00e9gie sur Ele1A+)\r\n            if (n === 1) return base + 7 + _regieCompN1;     \/\/ 47\r\n            if (n === 2) return base + 30 + _regieCompN2;    \/\/ 70 \u2192 60 r\u00e9gie\r\n            return base + 16 + _regieCompN3;                  \/\/ 56 \u2192 41 r\u00e9gie\r\n        }\r\n    }\r\n\r\n    \/\/ Placer (ou repositionner) un num\u00e9ro dans le container selon son ancre\r\n    \/\/ v4.9ds : ResizeObserver global pour stabiliser les positions des num\u00e9ros\r\n    \/\/   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    \/\/   Probl\u00e8me de fond : les num\u00e9ros 1\/2\/3 sont positionn\u00e9s en `position:absolute;\r\n    \/\/   top:<calcul\u00e9>` \u00e0 partir de `getBoundingClientRect()` sur leur ancre. Ce calcul\r\n    \/\/   est un snapshot \u2014 si l'ancre bouge ensuite (Elementor async, images charg\u00e9es,\r\n    \/\/   fonts custom, transitions, transforms\u2026), le num\u00e9ro reste fig\u00e9.\r\n    \/\/   \r\n    \/\/   Solution : observer les changements de TAILLE\/POSITION des ancres et du\r\n    \/\/   conteneur via ResizeObserver. Quand un changement est d\u00e9tect\u00e9, on relance\r\n    \/\/   le calcul automatiquement. Plus propre que les passes setTimeout (instantan\u00e9,\r\n    \/\/   pas de polling, d\u00e9clench\u00e9 exactement quand c'est n\u00e9cessaire).\r\n    \/\/   \r\n    \/\/   Support : Safari 13.1+, Chrome 64+, Firefox 69+ (>97% du march\u00e9).\r\n    \/\/   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    var _viaNumResizeObs = null;\r\n    var _viaNumObservedEls = (typeof WeakSet !== 'undefined') ? new WeakSet() : null;\r\n    var _viaNumScheduleAfterRO = null; \/\/ inject\u00e9 par _installWatcher au boot\r\n    function _ensureNumResizeObs() {\r\n        if (_viaNumResizeObs) return _viaNumResizeObs;\r\n        if (typeof ResizeObserver === 'undefined') return null; \/\/ navigateur trop ancien\r\n        _viaNumResizeObs = new ResizeObserver(function() {\r\n            \/\/ Replanification debounced via _processAll (passe par _scheduleProcess\r\n            \/\/   du watcher pour rAF debounce, sinon fallback direct)\r\n            if (typeof _viaNumScheduleAfterRO === 'function') {\r\n                _viaNumScheduleAfterRO();\r\n            } else {\r\n                try { _processAll(); } catch (_e) {}\r\n            }\r\n        });\r\n        return _viaNumResizeObs;\r\n    }\r\n    function _viaNumObserve(el) {\r\n        if (!el) return;\r\n        var ro = _ensureNumResizeObs();\r\n        if (!ro) return;\r\n        if (_viaNumObservedEls) {\r\n            if (_viaNumObservedEls.has(el)) return;\r\n            _viaNumObservedEls.add(el);\r\n        }\r\n        try { ro.observe(el); } catch (_e) {}\r\n    }\r\n\r\n    function _placeNum(container, drop, n, isEle0A) {\r\n        var num = container.querySelector(':scope > .via-step-num[data-via-num=\"' + n + '\"]');\r\n        if (!num) {\r\n            num = _makeNum(n, isEle0A);\r\n            container.appendChild(num);\r\n        }\r\n        \/\/ Forcer la bonne classe couleur \u00e0 chaque passe (au cas o\u00f9 isEle0A aurait chang\u00e9,\r\n        \/\/ ou si une classe parasite a \u00e9t\u00e9 pos\u00e9e \u00e0 la cr\u00e9ation)\r\n        var wantedClass = 'via-step-num ' + (isEle0A ? 'via-num-ele0a' : 'via-num-ele1a');\r\n        if (num.className !== wantedClass) { num.className = wantedClass; }\r\n\r\n        \/\/ v4.9ds : forcer le `left` inline (le CSS via-regie-iframe peut \u00eatre outrepass\u00e9\r\n        \/\/   par des r\u00e8gles plus sp\u00e9cifiques dans certains cas). Hors r\u00e9gie : valeurs par\r\n        \/\/   d\u00e9faut (4 mobile \/ 13 desktop). R\u00e9gie : -3px desktop (10), inchang\u00e9 mobile (4).\r\n        var _isRegieLeft = document.documentElement.classList.contains('via-regie-iframe');\r\n        var _isMobLeft = window.innerWidth < 1000;\r\n        var _leftPx;\r\n        if (_isRegieLeft) {\r\n            _leftPx = _isMobLeft ? 4 : 10;\r\n        } else {\r\n            _leftPx = _isMobLeft ? 4 : 13;\r\n        }\r\n        num.style.setProperty('left', _leftPx + 'px', 'important');\r\n\r\n        var anchorInfo = _findAnchor(drop, n);\r\n        var anchor = anchorInfo ? anchorInfo.el : null;\r\n\r\n        if (anchor) {\r\n            \/\/ v4.9ds : observer l'ancre \u2014 son moindre changement de taille\/position\r\n            \/\/   relancera le calcul automatiquement (pas de polling setTimeout).\r\n            _viaNumObserve(anchor);\r\n            var ancRect = anchor.getBoundingClientRect();\r\n            if (ancRect.height > 0 ? ancRect.width > 0 : false) {\r\n                var contRect = container.getBoundingClientRect();\r\n                num.style.display = '';\r\n                num.style.top = (ancRect.top - contRect.top + ancRect.height \/ 2 + _numTopOffset(n, isEle0A, drop)) + 'px';\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ Pas d'ancre exploitable\r\n        if (n === 3) {\r\n            \/\/ Pour le 3, fallback fixe en bas du container \u2014 mais seulement si le container\r\n            \/\/   a une hauteur exploitable (sinon le num\u00e9ro serait \u00e0 top:30 invisible).\r\n            var contRect3 = container.getBoundingClientRect();\r\n            if (contRect3.height > 50) {\r\n                num.style.display = '';\r\n                num.style.top = (contRect3.height + 30) + 'px';\r\n            } else {\r\n                num.style.display = 'none';\r\n            }\r\n            return;\r\n        }\r\n        \/\/ 1 et 2 : masquer si pas d'ancre\r\n        num.style.display = 'none';\r\n    }\r\n\r\n    \/\/ D\u00e9corer un seul espace .droppable\r\n    function _decorateDroppable(drop) {\r\n        if (!drop) return;\r\n        var rankId = drop.id || '';\r\n        var isEle0A = rankId === 'Ele0A';\r\n        var isEleNA = \/^Ele\\d+A$\/.test(rankId);\r\n        if (!isEle0A ? !isEleNA : false) return;\r\n\r\n        var $drop = jQuery(drop);\r\n        \/\/ Conteneur pour num\u00e9ros + croix = #UploadFileConteneur (wrapper visuel color\u00e9)\r\n        var container = drop.querySelector('#UploadFileConteneur')\r\n            || drop.querySelector('.UploadFileConteneur')\r\n            || drop;\r\n        jQuery(container).addClass('via-num-anchor');\r\n\r\n        \/\/ v4.9ds : observer le container et le droppable \u2014 un changement de taille\r\n        \/\/   sur l'un de ces \u00e9l\u00e9ments d\u00e9cale aussi les num\u00e9ros (via contRect dans\r\n        \/\/   le calcul de top). Idempotent gr\u00e2ce au WeakSet anti-doublons.\r\n        _viaNumObserve(container);\r\n        _viaNumObserve(drop);\r\n\r\n        \/\/ \u2500\u2500 Croix Effacer : top-right du #UploadFileConteneur \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/    Le listener click est pos\u00e9 via d\u00e9l\u00e9gation document dans _installStepHooks\r\n        \/\/    (r\u00e9siste aux clones DOM Ele0A). Ici on cr\u00e9e juste l'\u00e9l\u00e9ment.\r\n        if (!container.querySelector(':scope > .via-erase-btn')) {\r\n            container.appendChild(_makeEraseBtn());\r\n        }\r\n\r\n        \/\/ \u2500\u2500 Num\u00e9ros : tous dans #UploadFileConteneur (align\u00e9s left:8) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        _placeNum(container, drop, 1, isEle0A);\r\n        _placeNum(container, drop, 2, isEle0A);\r\n        _placeNum(container, drop, 3, isEle0A);\r\n    }\r\n\r\n    \/\/ Mise \u00e0 jour de la classe .via-ad-loaded selon pr\u00e9sence de .via-ad-header\r\n    function _updateAdLoadedClass(drop) {\r\n        if (!drop ? true : !drop.classList) return;\r\n        \/\/ D\u00e9tecter \"annonce d\u00e9pos\u00e9e OU en cours de d\u00e9p\u00f4t\" via plusieurs signaux \u2014\r\n        \/\/   pris dans l'ordre du flux d'upload (du plus pr\u00e9coce au plus tardif) :\r\n        \/\/   - .via-loading-inline \/ .via-loading-overlay (loading ins\u00e9r\u00e9 d\u00e8s le d\u00e9but upload)\r\n        \/\/   - <img> dans #drop_file_zone_achat ou .HTMLUploadfileConteneur (preview rendue)\r\n        \/\/   - data-via-ad-loaded=\"true\" (attribut pos\u00e9 par espace_publicitaire.txt:805)\r\n        \/\/   - .via-ad-header (header dynamique, pos\u00e9 apr\u00e8s tout)\r\n        var hasLoading = !!drop.querySelector('.via-loading-inline, .via-loading-overlay');\r\n        var hasImg = !!drop.querySelector('#drop_file_zone_achat img, .HTMLUploadfileConteneur img');\r\n        var hasAttr = drop.getAttribute('data-via-ad-loaded') === 'true';\r\n        var hasHeader = !!drop.querySelector('.via-ad-header');\r\n        if (hasLoading ? true : (hasImg ? true : (hasAttr ? true : hasHeader))) {\r\n            drop.classList.add('via-ad-loaded');\r\n        } else {\r\n            drop.classList.remove('via-ad-loaded');\r\n        }\r\n    }\r\n\r\n    \/\/ v4.9ds : m\u00e9canisme \u00e0 3 \u00e9tapes \u2014 pose data-via-format-gate selon l'\u00e9tat r\u00e9el\r\n    \/\/   - \"locked\"  \u2192 \u00e9tape 1 : aucun format \u2192 2 (GD+Envoi diff) ET 3 (R\u00e9server) gris\u00e9es\r\n    \/\/   - \"step2\"   \u2192 \u00e9tape 2 : format choisi \u2192 seul 3 (R\u00e9server) gris\u00e9\r\n    \/\/   - (rien)    \u2192 \u00e9tape 3 : envoi diff\u00e9r\u00e9 coch\u00e9 OU annonce d\u00e9pos\u00e9e \u2192 tout actif\r\n    function _updateStepGate(drop) {\r\n        if (!drop) return;\r\n        var $drop = jQuery(drop);\r\n\r\n        \/\/ Annonce d\u00e9pos\u00e9e \u2192 \u00e9tape 3 (tout actif, pas d'attribut)\r\n        var hasAdHeader = !!drop.querySelector('.via-ad-header');\r\n        if (hasAdHeader) {\r\n            drop.removeAttribute('data-via-format-gate');\r\n            return;\r\n        }\r\n\r\n        \/\/ Envoi diff\u00e9r\u00e9 coch\u00e9 DANS CE droppable \u2192 \u00e9tape 3\r\n        var envoiDiffereCoche = $drop.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        if (envoiDiffereCoche) {\r\n            drop.removeAttribute('data-via-format-gate');\r\n            return;\r\n        }\r\n\r\n        \/\/ Format choisi (un .EspPubFormatContainer hors Cr\u00e9ation\/PopUp avec fond blanc)\r\n        \/\/ OU une vignette Cr\u00e9ation active (data creationActive=true)\r\n        var formatChoisi = $drop.find('.EspPubFormatContainer')\r\n            .not('.FormatIdCreation').not('.FormatIdPopUp')\r\n            .toArray().some(function(el) {\r\n                return jQuery(el).css('background-color') === 'rgb(255, 255, 255)';\r\n            });\r\n        if (!formatChoisi) {\r\n            \/\/ V\u00e9rifier aussi Cr\u00e9ation active\r\n            var $crea = $drop.find('.FormatIdCreation').first();\r\n            if ($crea.length ? $crea.data('creationActive') === true : false) {\r\n                formatChoisi = true;\r\n            }\r\n        }\r\n        if (formatChoisi) {\r\n            drop.setAttribute('data-via-format-gate', 'step2');\r\n            return;\r\n        }\r\n\r\n        \/\/ Sinon \u2192 \u00e9tape 1\r\n        drop.setAttribute('data-via-format-gate', 'locked');\r\n    }\r\n\r\n    \/\/ D\u00e9corer + maintenir l'\u00e9tat pour tous les espaces pr\u00e9sents\r\n    function _processAll() {\r\n        _injectStyles();\r\n        document.querySelectorAll('.droppable').forEach(function(drop) {\r\n            _decorateDroppable(drop);\r\n            _updateAdLoadedClass(drop);\r\n        });\r\n        \/\/ Format-gate : pr\u00e9f\u00e9rer la fonction officielle (couvre tous les droppables)\r\n        try {\r\n            if (typeof window._viaUpdateFormatGate === 'function') {\r\n                window._viaUpdateFormatGate();\r\n            } else {\r\n                document.querySelectorAll('.droppable').forEach(_updateStepGate);\r\n            }\r\n        } catch (_e) {}\r\n    }\r\n\r\n    \/\/ Installer hooks pour r\u00e9agir aux clics format \/ change envoi diff\u00e9r\u00e9\r\n    \/\/   (transition imm\u00e9diate sans attendre MutationObserver)\r\n    function _refreshGate(drop) {\r\n        \/\/ Pr\u00e9f\u00e9rer la fonction officielle qui parcourt tous les droppables\r\n        try {\r\n            if (typeof window._viaUpdateFormatGate === 'function') {\r\n                window._viaUpdateFormatGate();\r\n                return;\r\n            }\r\n        } catch (_e) {}\r\n        \/\/ Fallback local\r\n        try { _updateStepGate(drop); } catch (_e) {}\r\n    }\r\n    function _installStepHooks() {\r\n        try {\r\n            jQuery(document).on('click', '.droppable .EspPubFormatContainer', function() {\r\n                var drop = jQuery(this).closest('.droppable')[0];\r\n                \/\/ D\u00e9lai pour laisser les autres handlers (s\u00e9lection format) finir\r\n                \/\/   v4.9ds : NE PAS appeler _processAll ici \u2014 le ferait dans un \u00e9tat DOM\r\n                \/\/   instable (post-clic, styles Elementor en cours d'application) \u2192 calcul\r\n                \/\/   des rects produit +10px parasite. Le _processAll est triggered ailleurs\r\n                \/\/   (MutationObserver childList sur popup parent, re-passes initiales).\r\n                setTimeout(function() { _refreshGate(drop); }, 100);\r\n                setTimeout(function() { _refreshGate(drop); }, 300);\r\n            });\r\n            jQuery(document).on('change', '.droppable input[name*=\"EnvoiUlterieur\"]', function() {\r\n                var drop = jQuery(this).closest('.droppable')[0];\r\n                setTimeout(function() { _refreshGate(drop); }, 50);\r\n            });\r\n            \/\/ \u2500\u2500 Croix Effacer : d\u00e9l\u00e9gation document \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/    \u00c9vite la perte de listener si le DOM est reclon\u00e9 (popup Ele0A).\r\n            \/\/ \u2705 v4.9ds Fix 28 : le handler appelait window.FonctionCroixResetAnnonce\r\n            \/\/   ou un trigger sur #CroixResetAnnonce \u2014 mais cet \u00e9l\u00e9ment n'existe plus\r\n            \/\/   dans le DOM (remplac\u00e9 par .via-erase-btn). R\u00e9sultat : le clic \u00e9tait\r\n            \/\/   bien d\u00e9tect\u00e9 ([via-erase-btn] click d\u00e9tect\u00e9 logg\u00e9) mais aucune action\r\n            \/\/   ne se d\u00e9clenchait. Solution : appel direct \u00e0 AdResetHandler.handle(e)\r\n            \/\/   d\u00e9fini ligne 5212 \u2014 c'est exactement la m\u00eame fonction qui \u00e9tait\r\n            \/\/   attach\u00e9e \u00e0 #CroixResetAnnonce ligne 5948 (`(e) => AdResetHandler.handle(e)`).\r\n            \/\/   AdResetHandler.handle utilise e.currentTarget pour trouver le droppable\r\n            \/\/   parent \u2014 \u00e7a fonctionne avec .via-erase-btn comme avec #CroixResetAnnonce.\r\n            jQuery(document).on('click', '.via-erase-btn', function(e) {\r\n                console.log('[via-erase-btn] click d\u00e9tect\u00e9 | rank:', jQuery(this).closest('.droppable').attr('id'));\r\n                e.preventDefault();\r\n                e.stopPropagation();\r\n                if (e.stopImmediatePropagation) { e.stopImmediatePropagation(); }\r\n                \/\/ \u2705 Fix 28 : appel direct du handler AdResetHandler (anciennement bind \u00e0\r\n                \/\/   #CroixResetAnnonce qui n'existe plus). Garde un fallback sur\r\n                \/\/   FonctionCroixResetAnnonce si AdResetHandler n'est pas accessible.\r\n                if (typeof AdResetHandler !== 'undefined' ? typeof AdResetHandler.handle === 'function' : false) {\r\n                    AdResetHandler.handle(e);\r\n                    return;\r\n                }\r\n                \/\/ Fallback historique (au cas o\u00f9 AdResetHandler n'est pas dans ce scope)\r\n                var $droppable = jQuery(this).closest('.droppable');\r\n                var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n                if (typeof window.FonctionCroixResetAnnonce === 'function' ? !!_croixEl : false) {\r\n                    window.FonctionCroixResetAnnonce(_croixEl);\r\n                    return;\r\n                }\r\n                if (_croixEl) { jQuery(_croixEl).trigger('click'); }\r\n                console.warn('[via-erase-btn] ni AdResetHandler ni #CroixResetAnnonce trouv\u00e9s \u2014 reset non effectu\u00e9');\r\n            });\r\n        } catch (_e) {}\r\n    }\r\n\r\n    function _installWatcher() {\r\n        try {\r\n            var _scheduled = false;\r\n            var _scheduleProcess = function() {\r\n                if (_scheduled) return;\r\n                _scheduled = true;\r\n                requestAnimationFrame(function() {\r\n                    _scheduled = false;\r\n                    _processAll();\r\n                });\r\n            };\r\n            \/\/ v4.9ds : exposer _scheduleProcess au ResizeObserver pour debounce rAF\r\n            \/\/   commun. Quand une ancre\/container change de taille, le RO d\u00e9clenche\r\n            \/\/   _scheduleProcess qui replanifie un seul _processAll par frame.\r\n            _viaNumScheduleAfterRO = _scheduleProcess;\r\n            var obs = new MutationObserver(function(muts) {\r\n                \/\/ R\u00e9action INSTANTAN\u00c9E (pas via rAF) sur les changements d'attribut\r\n                \/\/   data-via-ad-loaded \u2014 masquage imm\u00e9diat des num\u00e9ros + croix\r\n                for (var i = 0; i < muts.length; i++) {\r\n                    var m = muts[i];\r\n                    if (m.type === 'attributes' ? m.attributeName === 'data-via-ad-loaded' : false) {\r\n                        if (m.target ? m.target.classList : false) {\r\n                            _updateAdLoadedClass(m.target);\r\n                        }\r\n                    }\r\n                }\r\n                \/\/ Replanification standard pour le reste (num\u00e9ros, croix, gate)\r\n                \/\/   Cela couvre l'ajout\/retrait de droppables, changements DOM\r\n                \/\/   (pop-up Ele0A cr\u00e9\u00e9e\/clon\u00e9e, etc.). Le ResizeObserver couvre\r\n                \/\/   les changements de g\u00e9om\u00e9trie sur les ancres existantes.\r\n                _scheduleProcess();\r\n            });\r\n            obs.observe(document.body, {\r\n                childList: true,\r\n                subtree: true,\r\n                attributes: true,\r\n                attributeFilter: ['data-via-ad-loaded']\r\n            });\r\n            \/\/ v4.9ds : resize viewport reste branch\u00e9 (ResizeObserver couvre les\r\n            \/\/   \u00e9l\u00e9ments individuels mais pas les changements globaux de viewport\r\n            \/\/   qui peuvent affecter les valeurs de _numTopOffset bas\u00e9es sur\r\n            \/\/   window.innerWidth).\r\n            window.addEventListener('resize', _scheduleProcess);\r\n        } catch (_e) {}\r\n    }\r\n\r\n    function _boot() {\r\n        _injectStyles();\r\n        _processAll();\r\n        _installWatcher();\r\n        _installStepHooks();\r\n        \/\/ v4.9ds : passes setTimeout supprim\u00e9es \u2014 le ResizeObserver branch\u00e9 sur\r\n        \/\/   chaque ancre\/container d\u00e9tecte automatiquement les changements de\r\n        \/\/   g\u00e9om\u00e9trie (Elementor async, images charg\u00e9es, fonts custom, etc.)\r\n        \/\/   et relance _processAll au bon moment.\r\n        \/\/   On garde une seule passe \u00e0 1000ms pour le cas o\u00f9 le ResizeObserver\r\n        \/\/   ne serait pas support\u00e9 (tr\u00e8s anciens navigateurs) ou pour couvrir\r\n        \/\/   les ancres qui apparaissent en display:none initial puis sont\r\n        \/\/   ajout\u00e9es au DOM (le RO ne les observe pas tant qu'elles n'existent\r\n        \/\/   pas dans la d\u00e9coration \u2192 un retry \u00e0 1s couvre ce cas).\r\n        setTimeout(_processAll, 1000);\r\n    }\r\n\r\n    if (document.readyState === 'loading') {\r\n        document.addEventListener('DOMContentLoaded', _boot);\r\n    } else {\r\n        _boot();\r\n    }\r\n})();\r\n\r\n} \/\/ end _espPubScriptLoaded guard\r\n<\/script>\r\n\r\n\r\n<style>\r\n\/* ============================================================================\r\n   UFC HAUTEUR \u2014 colle \u00e0 l'image, cap via max-height (source unique de v\u00e9rit\u00e9)\r\n   ============================================================================ *\/\r\n.via-ad-wrapper .HTMLUploadfileConteneur,\r\n.via-ad-wrapper .HTMLUploadfileConteneur .elementor-widget-container,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #HTMLUploadfile,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #PopUpMessageAchattest,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n    height: auto !important;\r\n    min-height: 0 !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur {\r\n    max-height: 170px !important;\r\n    overflow: hidden !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n    max-height: 160px !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur img,\r\n.via-ad-wrapper .HTMLUploadfileConteneur video {\r\n    \/* v4.9ds : width\/height 100% + object-fit:contain \u2192 image remplit la box du dropZone\r\n       (m\u00eames contraintes que desktop) avec letterbox automatique selon ratio.\r\n       Avant : height:auto + width:auto \u2192 image gardait son ratio mais pouvait d\u00e9border\r\n       (overflow:hidden du wrapper \u2192 crop). Le max-height 160px est la limite mobile\r\n       (renderImage pose dropZone.height \u00e0 105 sur mobile mais le CSS doit autoriser\r\n       jusqu'\u00e0 160 pour les autres branches qui ne passent pas par renderImage). *\/\r\n    max-height: 160px !important;\r\n    max-width: 100% !important;\r\n    height: 100% !important;\r\n    width: 100% !important;\r\n    object-fit: contain !important;\r\n}\r\n\r\n\/* v4.9ds : doc-preview Communiqu\u00e9\/Interview occupe toute la hauteur du dropZone\r\n   (sinon vide blanc en bas du liser\u00e9 vert) *\/\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container {\r\n    height: 100% !important;\r\n    max-height: 100% !important;\r\n    align-items: stretch !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-thumbnail,\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-info {\r\n    height: 100% !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container.doc-preview-noimage {\r\n    align-items: flex-start !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container.doc-preview-noimage .doc-preview-info-full {\r\n    height: 100% !important;\r\n    max-height: 100% !important;\r\n}\r\n\r\n\/* v4.9ds : r\u00e8gles Ele0A supprim\u00e9es ici, d\u00e9plac\u00e9es en fin de fichier CSS\r\n   (apr\u00e8s les @media min-width:1200px) pour gagner par ordre de d\u00e9claration. *\/\r\n\r\n\/* v4.9ds : normaliser padding-top sur tous les libell\u00e9s de format.\r\n   Elementor pose en inline padding:1px 0 0 sur Cr\u00e9ation\/Pop-up\/Banni\u00e8re\/Vid\u00e9o\r\n   et padding:3px 0 0 sur Communiqu\u00e9\/Interview\/Parrainage (configur\u00e9 c\u00f4t\u00e9 admin\r\n   Elementor) \u2192 d\u00e9calage visuel entre la 1\u00e8re et 2e ligne de la grille de formats.\r\n   On force 1px partout pour alignement vertical homog\u00e8ne. *\/\r\n.EspPubFormatContainer .EspPubFormat .elementor-widget-container {\r\n    padding-top: 1px !important;\r\n}\r\n\r\n@media (max-width: 999px) {\r\n    .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        max-height: 200px !important;\r\n    }\r\n    \/* v4.9ds : Mobile uniquement \u2014 libell\u00e9s des formats en 800 (au lieu de 600) *\/\r\n    .EspPubFormatContainer .EspPubFormat,\r\n    .EspPubFormatContainer .EspPubFormat .elementor-widget-container {\r\n        font-weight: 800 !important;\r\n    }\r\n}\r\n\r\n\/* Les styles CSS restent inchang\u00e9s *\/\r\n\r\n\/* R\u00e9gie iframe : wrapper adaptatif *\/\r\nhtml.via-regie-iframe .via-ad-wrapper {\r\n    width: 100% !important;\r\n    max-width: 100% !important;\r\n}\r\n\/* Padding-bottom sur droppable d\u00e9pos\u00e9 \u2014 DESKTOP uniquement (\u2265600px viewport iframe).\r\n   Sur mobile, le flow naturel suffit : pas de padding-bottom (\u00e9vite gros vide blanc). *\/\r\n@media (min-width: 600px) {\r\n    html.via-regie-iframe body.home .droppable[data-via-ad-loaded=\"true\"] {\r\n        padding-bottom: 240px !important;\r\n    }\r\n    html.via-regie-iframe body:not(.home) .droppable[data-via-ad-loaded=\"true\"] {\r\n        padding-bottom: 130px !important;\r\n    }\r\n}\r\n\r\n\/* R\u00e9gie iframe MOBILE \u2014 annonce charg\u00e9e : r\u00e9duire les margins \u00e9normes pos\u00e9s par\r\n   Entete.txt sur .ToBeHidden (200px+290px calibr\u00e9s pour espaces vides). *\/\r\n@media (max-width: 599px) {\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) {\r\n        margin-top: -100px !important;\r\n        margin-bottom: 5px !important;\r\n    }\r\n    \/* Neutraliser aussi les margins sur l'OrdiMobileConteneurClass et ses descendants\r\n       qui pourraient ajouter du padding\/margin additionnel. *\/\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) .OrdiMobileConteneurClass {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n        padding-bottom: 0 !important;\r\n    }\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) .UploadFileConteneur {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n        padding-bottom: 0 !important;\r\n    }\r\n    \/* Pages secteurs mobile : Entete.txt pose margin-bottom 180\/176px directement\r\n       sur .droppable (pas sur ToBeHidden). R\u00e9duire pour les droppables charg\u00e9s. *\/\r\n    html.via-regie-iframe body.page .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-bottom: 10px !important;\r\n    }\r\n}\r\n\r\n\/* R\u00e9gie iframe desktop (\u2265600px de viewport iframe) : UFC standard + loading spacing *\/\r\n@media (min-width: 600px) {\r\n    \/* UFC : hauteur fixe 170px comme les espaces vides (standard) au lieu de hug auto *\/\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: 170px !important;\r\n        max-height: 170px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: 160px !important;\r\n        display: flex !important;\r\n        align-items: center !important;\r\n        justify-content: center !important;\r\n    }\r\n    \/* Homepages : loading container 30px plus bas *\/\r\n    html.via-regie-iframe body.home .via-loading-inline {\r\n        margin-top: 30px !important;\r\n    }\r\n}\r\n\r\n\/* ============================================================================\r\n   R\u00e9gie iframe desktop plein \u00e9cran (\u22651200px) : neutralise les marges JS\r\n   pos\u00e9es par styleUploadedAd + _buildAdOverlay (calibr\u00e9es pour mode mobile \u00e9troit)\r\n   ============================================================================ *\/\r\n@media (min-width: 1200px) {\r\n    \/* Annule la remont\u00e9e de -55px sur .droppable qui cause le chevauchement *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-top: 0 !important;\r\n    }\r\n    \/* Annule le +130px sur margin-bottom de l'OMC interne *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] .OrdiMobileConteneurClass {\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Annule le -140px (ou -105px) sur .via-ad-wrapper pos\u00e9 par _buildAdOverlay *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] .via-ad-wrapper {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Agrandit l'annonce : UFC 260px max au lieu de 170px *\/\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 260px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        max-height: 250px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        max-height: 250px !important;\r\n    }\r\n}\r\n\r\n\/* ============================================================================\r\n   Sites pays desktop\/tablette (pas dans iframe r\u00e9gie, \u2265768px) : neutralise\r\n   les marges n\u00e9gatives inline h\u00e9rit\u00e9es du mode mobile (margin-top: -75px sur\r\n   droppable, -140px sur wrapper) qui persistent au resize et causent le\r\n   chevauchement avec le contenu du dessus.\r\n   Seuil 768 (Elementor tablette) pour couvrir les cas o\u00f9 innerWidth < 1000\r\n   alors que outerWidth est en mode desktop.\r\n   ============================================================================ *\/\r\n@media (min-width: 768px) {\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] .via-ad-wrapper {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Pas de margin-top: 0 !important sur .droppable \u2014 on laisse l'inline pos\u00e9 par\r\n       _viaRunInterEspaces (algo inter-espaces) appliquer sa valeur n\u00e9gative pour\r\n       standardiser le gap au-dessus \u00e0 20px. *\/\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Annule le margin-bottom sur OMC (pos\u00e9 par styleUploadedAd) *\/\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] .OrdiMobileConteneurClass {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Agrandit l'annonce : UFC 260px max au lieu de 170px *\/\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 260px !important;\r\n    }\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        \/* v4.9ds : aligner sur la hauteur pos\u00e9e par _applyDzMinH (Ele1A+ = 207) *\/\r\n        max-height: 207px !important;\r\n    }\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        \/* v4.9ds : aligner sur la hauteur r\u00e9elle du dropZone (pos\u00e9e par _applyDzMinH).\r\n           Avant : 250px en dur \u2192 image plus haute que dropZone (~207 sur Ele1A+) \u2192 crop\r\n           par overflow:hidden du wrapper. *\/\r\n        max-height: 207px !important;\r\n    }\r\n}\r\n\r\n\/* v4.9cv : Ele0A desktop \u2014 aligner les contraintes de hauteur sur celles d'Ele1A.\r\n   Ces r\u00e8gles arrivent APR\u00c8S les @media min-width:1200px et 768px pour gagner par\r\n   ordre de d\u00e9claration. Pr\u00e9fixe html# boost la sp\u00e9cificit\u00e9 face \u00e0 html.via-regie-iframe. *\/\r\n\/* v4.9cx : force brute + d\u00e9sactiver overflow:hidden sur le wrapper parent *\/\r\n@media (min-width: 768px) {\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 285px !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        max-height: 215px !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        \/* v4.9ds : aligner sur la hauteur r\u00e9elle du dropZone (pos\u00e9e par _applyDzMinH = 215)\r\n           Avant : 275px en dur \u2192 image d\u00e9passait dropZone de 60px \u2192 crop.\r\n           v4.9ds : retir\u00e9 'height: auto !important' pour que le JS (height:100%) puisse\r\n           s'appliquer \u2192 image remplit la box comme Ele1A+ (comportement uniforme).\r\n           object-fit:contain garantit que l'image reste enti\u00e8re (letterbox auto). *\/\r\n        max-height: 215px !important;\r\n    }\r\n    \/* Laisser le contenu d\u00e9border visuellement si jamais une r\u00e8gle cach\u00e9e pose une height trop petite *\/\r\n    html body .ToBeHidden:has(#Ele0A) {\r\n        overflow: visible !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper {\r\n        overflow: visible !important;\r\n    }\r\n    \/* v4.9ds : Ele0A desktop \u2014 d\u00e9caler .EnvoiUlterieurContainer de 17px vers le bas pour\r\n       espacer visuellement le bloc \"ou Envoi diff\u00e9r\u00e9 \/ Envoyer l'annonce \/ Un lien\u2026\" du\r\n       champ hypertext qui le pr\u00e9c\u00e8de. Le margin pousse aussi tout ce qui suit (R\u00e9server). *\/\r\n    html #Ele0A .EnvoiUlterieurContainer {\r\n        margin-top: 17px !important;\r\n    }\r\n}\r\n\r\n\/* \u2705 via-ad-wrapper responsive : s'adapte quand la fen\u00eatre est r\u00e9duite *\/\r\n.via-ad-wrapper {\r\n    min-width: 0;\r\n    overflow: hidden;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: flex-end;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-wrapper .via-ad-header,\r\n.via-ad-wrapper .HTMLUploadfileConteneur,\r\n.via-ad-wrapper .via-ad-footer {\r\n    width: 100%;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-header {\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n}\r\n.via-ah-title {\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n    white-space: nowrap;\r\n    max-width: 65%;\r\n}\r\n\/* desktop mode mobile : reduire les tailles de char du header *\/\r\n@media only screen and (max-width: 999px) {\r\n    .via-ah-pos  { font-size: 8px !important; }\r\n    .via-ah-title { font-size: 9px !important; max-width: 70% !important; }\r\n    .via-ah-ref  { font-size: 8px !important; }\r\n    .via-ad-wrapper .HTMLUploadfileConteneur { max-height: 200px !important; overflow: hidden !important; height: auto !important; }\r\n    \/* v4.9ds : exclure img\/video du s\u00e9lecteur * pour ne pas \u00e9craser leur r\u00e8gle d\u00e9di\u00e9e\r\n       (qui pose width:100%\/height:100%\/object-fit:contain). Sans cette exclusion,\r\n       la max-height:200 inline ici ne pose pas de pb mais le * peut interf\u00e9rer. *\/\r\n    .via-ad-wrapper .HTMLUploadfileConteneur *:not(img):not(video) { max-height: 200px !important; }\r\n    .reserver-dynamic-label { font-size: 12px !important; }\r\n    .via-ad-wrapper .via-af-move { font-size: 8px !important; }\r\n}\r\n.via-ah-pos, .via-ah-ref {\r\n    white-space: nowrap;\r\n    flex-shrink: 1;\r\n    min-width: 0;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n}\r\n\/* v4.9ct : d\u00e9caler la r\u00e9f\u00e9rence MDG... vers la gauche (uniquement desktop).\r\n   - Espaces pub standards : 8px\r\n   - Ele0A (popup) : 5px\r\n   Le margin-right sur .via-ah-ref pousse la ref \u00e0 gauche tandis que la croix reste ancr\u00e9e \u00e0 droite. *\/\r\n\/* v4.9cu : +8\/+5px suppl\u00e9mentaires \u2192 standards 16px, Ele0A 10px *\/\r\n\/* v4.9cv : +8\/+5px suppl\u00e9mentaires \u2192 standards 24px, Ele0A 15px *\/\r\n@media (min-width: 1000px) {\r\n    .via-ad-header .via-ah-ref { margin-right: 24px; }\r\n    #Ele0A .via-ad-header .via-ah-ref { margin-right: 15px; }\r\n    \/* v4.9ds : Ele0A \u2014 d\u00e9caler via-eu-wrapper de +2px vers le bas (cumul avec margin-top:-5px pos\u00e9 par JS Entete) *\/\r\n    \/*          R\u00e9sultat : -5px + 2px = -3px effectif *\/\r\n    #Ele0A .via-eu-wrapper {\r\n        margin-top: -3px !important;\r\n    }\r\n    \/* v4.9ds : Ele1A+ \u2014 d\u00e9caler EnvoiUlterieur de -2px vers le haut *\/\r\n    .droppable:not(#Ele0A) .EnvoiUlterieurContainer .EnvoiUlterieur {\r\n        margin-top: -2px !important;\r\n    }\r\n    \/* v4.9ds : Ele1A+ \u2014 EnvoiUlterieurTexte de -1px (cumul -2px + 1px bas) *\/\r\n    .droppable:not(#Ele0A) .EnvoiUlterieurContainer .EnvoiUlterieurTexte {\r\n        margin-top: -1px !important;\r\n    }\r\n}\r\n\/* HTMLUploadfileConteneur : hauteur auto dans le wrapper *\/\r\n.via-ad-wrapper .HTMLUploadfileConteneur {\r\n    max-height: 200px;\r\n    overflow: hidden !important;\r\n    margin-top: 0 !important;\r\n    margin-bottom: 0 !important;\r\n}\r\n\/* v4.9cs : NE PAS ajouter de zoom suppl\u00e9mentaire sur Ele0A \u2014 #Ele0A a d\u00e9j\u00e0 zoom:55%\r\n   pos\u00e9 par Entete.txt (ligne 5899), donc un second zoom 55% ici donnerait 30% (trop petit). *\/\r\n.via-ad-wrapper #drop_file_zone_achat {\r\n    margin-top: 0 !important;\r\n    padding: 0 2px;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat img,\r\n.via-ad-wrapper #drop_file_zone_achat video {\r\n    \/* v4.9ds : height:100% (au lieu de auto) + object-fit:contain \u2192 image remplit le\r\n       dropZone (coh\u00e9rent avec width:100%) en restant enti\u00e8re. Letterbox auto si ratio\r\n       diff\u00e9rent. Sans !important pour ne pas surclasser les r\u00e8gles plus sp\u00e9cifiques. *\/\r\n    max-width: 100%;\r\n    width: 100%;\r\n    height: 100%;\r\n    object-fit: contain;\r\n    display: block;\r\n}\r\n\/* via-af-move *\/\r\n.via-ad-wrapper .via-af-move {\r\n    white-space: normal;\r\n    color: #67758c;\r\n    overflow: hidden;\r\n    text-align: center;\r\n}\r\n\r\n\r\n#drop_file_zone_achat {\r\n    background-color: #FFFFFF00;\r\n    color: #225DA9;\r\n    font-weight: 500;\r\n    text-align: center;\r\n    border: #999 0px dashed;\r\n    width: 100%;\r\n    height: 180px;\r\n    font-size: 11.5px;\r\n    display: flex;\r\n    justify-content: center; \r\n    align-items: center; \r\n    flex-wrap: wrap;\r\n}\r\n\r\n#drag_upload_file_achat {\r\n    margin: 0 auto;\r\n    width: 90%;\r\n    height: 60px;\r\n}\r\n\r\n#drag_upload_file_achat p {\r\n    text-align: center;\r\n}\r\n\r\n\/* \u2705 v1.16.0 : Liser\u00e9 noir fin autour du texte \"Ici glisser \u2013 d\u00e9poser\" *\/\r\n.GlisserDeposerConteneur .elementor-widget-container p {\r\n    border: 1px solid #000000;\r\n    border-radius: 4px;\r\n    padding: 4px 10px;\r\n    display: inline-block;\r\n}\r\n\r\n#selectfile_achat {\r\n    display: none;\r\n}\r\n\r\n\/* v4.9ds : Ele0A - EnvoiUlterieurTexte +6px bas (cumul 2px + 2px + 2px) *\/\r\n#Ele0A .EnvoiUlterieurTexte {\r\n    margin-top: 6px !important;\r\n}\r\n\r\n.button-2_achat, .button-2_achat-after-reset {\r\n    background-color: #ffffff00!important;\r\n    border: 1px solid white!important;\r\n    border-radius: 8px;\r\n    color: #225DA9!important;\r\n    cursor: pointer;\r\n    display: inline-block;\r\n    font-size: 11px;\r\n    font-weight: 500;\r\n    list-style: none;\r\n    width: 390px;\r\n    height: 62px;\r\n    top: 0px!important; \r\n    margin-top: 0px!important; \r\n    padding-left: 5px!important;\r\n    padding-right: 5px!important;\r\n    margin-bottom: 70px!important;\r\n    margin: 0;\r\n    text-align: center;\r\n    transition: all 200ms;\r\n    vertical-align: baseline;\r\n    white-space: wrap!important;\r\n}\r\n\r\n@media only screen and (max-width: 1000px) {\r\n    .button-2_achat, .button-2_achat-after-reset {\r\n        width: 95%;\r\n        height: 50px;\r\n    }\r\n}\r\n\r\n.newMessageClass {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #56BE50;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 45px;\r\n    bottom: 0px;\r\n    right: 0px;\r\n    left: 0px;\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n    text-align: center;\r\n}\r\n\r\n.MessageClassFormatnonReconnuTitre {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #FB5E2A;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    margin-top: -300px;\r\n    right: 0px;\r\n    text-align: center;\r\n    line-height: 15px;\r\n}\r\n\r\n.MessageClassFormatnonReconnu {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #ffffff;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    margin-top: -280px;\r\n    right: 0px;\r\n    text-align: center;\r\n    line-height: 15px;\r\n}\r\n\r\n.newButtonClass {\r\n    font-size: 12px; \r\n    font-weight: 500;\r\n    background-color: #ffffff00;\r\n    color: #717171;\r\n    height: 10px;\r\n    width: 100%; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 45px;\r\n    bottom: 0px;\r\n    text-align: center;\r\n    right:  50px;\r\n    line-height: 10px;\r\n    border-radius: 3px;\r\n    border: 0px solid #E8ECF1;\r\n}\r\n\r\n.linkClass {\r\n    width: 15px; \r\n    height: 15px;\r\n    margin-top: -310px;\r\n    margin-bottom: -25px;\r\n    margin-right: -50px;\r\n    position: relative;\r\n    top: 0; \r\n    z-index: 99;\r\n    display: block;\r\n    background-image: url('https:\/\/rdc.via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-blanc.jpg');\r\n    background-size: cover;\r\n}\r\n\r\n.newButtonClassVideo {\r\n    font-size: 12px; \r\n    font-weight: 500;\r\n    background-color: #ffffff00;\r\n    color: #717171;\r\n    height: 10px;\r\n    width: 100%; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 0px;\r\n    bottom: 0px;\r\n    right: 10px;\r\n    text-align: center;\r\n    line-height: 10px;\r\n    border-radius: 3px;\r\n    border: 0px solid #E8ECF1;\r\n}\r\n\r\n.linkClassVideo {\r\n    width: 15px; \r\n    height: 15px;\r\n    margin-top: -438px;\r\n    margin-bottom: -25px;\r\n    margin-right: -15px;\r\n    position: relative;\r\n    top: 0; \r\n    z-index: 99;\r\n    display: block;\r\n    background-image: url('https:\/\/rdc.via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-blanc.jpg');\r\n    background-size: cover;\r\n}\r\n\r\n.FinCampagneMobileClass .elementor-button,\r\n.DebutCampagneMobileClass .elementor-button,\r\n.FormSelectDevisesMobile .elementor-button,\r\n.HideFormButton .elementor-button {\r\n    display: none !important;\r\n}\r\n\r\n#drag_upload_file_achat {\r\n    margin: 0;\r\n    padding: 0;\r\n    position: relative;\r\n}\r\n\r\n.dot-container {\r\n    display: inline-block;\r\n}\r\n\r\n.dot {\r\n    opacity: 0;\r\n    transition: opacity 0.3s ease;\r\n}\r\n\r\n@keyframes firstDot {\r\n    0%, 100% { opacity: 0; }\r\n    10%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n@keyframes secondDot {\r\n    0%, 20%, 100% { opacity: 0; }\r\n    30%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n@keyframes thirdDot {\r\n    0%, 40%, 100% { opacity: 0; }\r\n    50%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n.dot:nth-child(1) {\r\n    animation: firstDot 3s infinite;\r\n}\r\n\r\n.dot:nth-child(2) {\r\n    animation: secondDot 3s infinite;\r\n}\r\n\r\n.dot:nth-child(3) {\r\n    animation: thirdDot 3s infinite;\r\n}\r\n\r\n.droppable .elementor-field-type-checkbox .elementor-field-option {\r\n    display: flex;\r\n    flex-direction: row-reverse;\r\n    align-items: center;\r\n    gap: 8px;\r\n}\r\n\r\n.droppable .elementor-field-type-checkbox .elementor-field-option input[type=\"checkbox\"] {\r\n    width: 12px;\r\n    height: 12px;\r\n    min-width: 12px;\r\n    min-height: 12px;\r\n}\r\n\r\n\/* \u2705 Bouton \"R\u00e9server\" dynamique *\/\r\n.reserver-dynamic-container {\r\n    text-align: center;\r\n    margin-top: -7px;\r\n    margin-bottom: 15px;\r\n    padding: 5px 0;\r\n    transform: scale(1.4);\r\n    transform-origin: center top;\r\n    position: relative;\r\n    z-index: 200;\r\n}\r\n\r\n@media only screen and (min-width: 1001px) {\r\n    .reserver-dynamic-container {\r\n        transform: scale(1);\r\n        margin-top: -200px;\r\n        margin-bottom: 0;\r\n    }\r\n}\r\n\r\n.reserver-dynamic-option {\r\n    display: inline-flex;\r\n    flex-direction: row-reverse;\r\n    align-items: center;\r\n    gap: 8px;\r\n    cursor: pointer;\r\n    color: #213864;\r\n    background-color: #ffffff;\r\n    padding: 2px 4px;\r\n    border-radius: 6px;\r\n}\r\n\r\n.reserver-dynamic-checkbox {\r\n    width: 16px;\r\n    height: 16px;\r\n    min-width: 16px;\r\n    min-height: 16px;\r\n    cursor: pointer;\r\n    pointer-events: auto;\r\n}\r\n\r\n.reserver-dynamic-label {\r\n    cursor: pointer;\r\n    color: #213864;\r\n    font-size: 16px;\r\n    font-weight: 600;\r\n    user-select: none;\r\n}\r\n\r\n@media only screen and (max-width: 1000px) {\r\n    .reserver-dynamic-container {\r\n        transform: scale(0.98);\r\n        margin-bottom: 20px;\r\n        white-space: nowrap;\r\n        z-index: 300;\r\n        pointer-events: auto;\r\n    }\r\n    .reserver-dynamic-option {\r\n        padding-top: 0px;\r\n        padding-bottom: 0px;\r\n    }\r\n}\r\n\r\n\/* \u2705 v2.1.3 : DeplaceAnnonceText \/ DeplaceAnnonce \u2014 desktop uniquement *\/\r\n@media only screen and (min-width: 1001px) {\r\n    .DeplaceAnnonceText {\r\n        margin-top: -5px;\r\n        position: relative;\r\n        z-index: 201;\r\n    }\r\n    .DeplaceAnnonce {\r\n        margin-bottom: -150px;\r\n    }\r\n}\r\n<\/style>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-54e4e530 e-con-full MsgFormatIncorrectConteneur elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile e-flex e-con e-child\" data-id=\"54e4e530\" data-element_type=\"container\" id=\"NotusedAnymore\">\n\t\t\t\t<div class=\"elementor-element elementor-element-531f3cb1 MsgFormatIncorrect elementor-widget__width-inherit elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"531f3cb1\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">The file format is not recognized<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3a352c3f elementor-hidden-desktop TexteMobile TexteMobileAnnonce elementor-widget-mobile__width-initial elementor-widget elementor-widget-text-editor\" data-id=\"3a352c3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tDrag and drop or click here<br>\nto download an ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7abb0aba elementor-hidden-desktop TexteMobileAjoutAnnonce elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"7abb0aba\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Click here to download an ad<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-68e2b2ca UploadIci elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"68e2b2ca\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tHere you can drag and drop or upload an ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-8d50d33 e-con-full e-flex e-con e-child\" data-id=\"8d50d33\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6ae6ef83 elementor-button-align-center elementor-widget__width-initial HideFormButton EspPubLienAnnonce elementor-widget-mobile__width-initial elementor-widget elementor-widget-form\" data-id=\"6ae6ef83\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_URL_annonce\" aria-label=\"Form_URL_ad\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353552\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"6ae6ef83\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-text elementor-field-group elementor-column elementor-field-group-LienAnnonce elementor-col-100 elementor-sm-100 elementor-field-required\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-LienAnnonce\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tHere, if necessary, enter the hyperlink to the advertisement.\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<input size=\"1\" type=\"text\" name=\"form_fields[LienAnnonce]\" id=\"form-field-LienAnnonce\" class=\"elementor-field elementor-size-xs  elementor-field-textual\" placeholder=\"Here, if necessary, enter the hyperlink to the advertisement.\" required=\"required\">\n\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidURLAnnonce\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-66fd4d36 e-con-full EnvoiUlterieurContainer e-flex e-con e-child\" data-id=\"66fd4d36\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4ea8e2b4 elementor-button-align-center elementor-widget__width-auto HideFormButton EnvoiUlterieur elementor-widget elementor-widget-form\" data-id=\"4ea8e2b4\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_Envoi_Ulterieur\" aria-label=\"Submit_Form_Later\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353552\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"4ea8e2b4\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-checkbox elementor-field-group elementor-column elementor-field-group-EnvoiUlterieur elementor-col-100 elementor-sm-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-EnvoiUlterieur\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tor Delayed posting of the advertisement\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t<div class=\"elementor-field-subgroup\"><span class=\"elementor-field-option\"><input type=\"checkbox\" value=\"ou Envoi diff\u00e9r\u00e9 de l&#039;annonce\" id=\"form-field-EnvoiUlterieur-0\" name=\"form_fields[EnvoiUlterieur]\"> <label for=\"form-field-EnvoiUlterieur-0\">or Delayed posting of the advertisement<\/label><\/span><\/div>\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidEnvoiUlterieur\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4a59cf88 EnvoiUlterieurTexte elementor-widget elementor-widget-text-editor\" data-id=\"4a59cf88\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Send the ad up to 8 days after payment<br>\nA link will be sent to you by <span style=\"color: #ffffff;\"><a style=\"color: #ffffff;\" href=\"mailto:contact@via-agency.media\">contact@via-agency.media<\/a><\/span><\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-17d3ad0c e-con-full ReserverContainer e-flex e-con e-child\" data-id=\"17d3ad0c\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-177c9b71 elementor-button-align-center elementor-widget__width-auto HideFormButton ReserverBouton elementor-widget elementor-widget-form\" data-id=\"177c9b71\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_Envoi_Ulterieur\" aria-label=\"Submit_Form_Later\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353552\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"177c9b71\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-checkbox elementor-field-group elementor-column elementor-field-group-ReserverEspacePublicitaire elementor-col-100 elementor-sm-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-ReserverEspacePublicitaire\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tReserve this advertising space\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t<div class=\"elementor-field-subgroup\"><span class=\"elementor-field-option\"><input type=\"checkbox\" value=\"R\u00e9server cet espace publicitaire\" id=\"form-field-ReserverEspacePublicitaire-0\" name=\"form_fields[ReserverEspacePublicitaire]\"> <label for=\"form-field-ReserverEspacePublicitaire-0\">Reserve this advertising space<\/label><\/span><\/div>\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidEnvoiUlterieur\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5bafe1f3 e-con-full AdUploadedTitle DeplaceAnnonce elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop e-flex e-con e-child\" data-id=\"5bafe1f3\" data-element_type=\"container\" id=\"DeplaceAnnonceId\">\n\t\t<div class=\"elementor-element elementor-element-432cc2a1 e-con-full DeplaceAnnonceSubContainer e-flex e-con e-child\" data-id=\"432cc2a1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-446e8565 elementor-hidden-tablet elementor-hidden-mobile EspaceReserve elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"446e8565\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reserved space<br \/>Announcement transmitted<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-60df49d0 elementor-hidden-tablet elementor-hidden-mobile DeplaceAnnonceText elementor-widget elementor-widget-text-editor\" data-id=\"60df49d0\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tIf you want another location, you can move this ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-62ef38f0 e-con-full e-flex e-con e-child\" data-id=\"62ef38f0\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f4f51cb elementor-hidden-tablet elementor-hidden-mobile PositionEspacePublicitaireDeplacer elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"f4f51cb\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Position<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f214306 elementor-hidden-tablet elementor-hidden-mobile RefEspacePublicitaire elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"f214306\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reference<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1ddc6ac e-flex e-con-boxed e-con e-parent\" data-id=\"1ddc6ac\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-2153d38 e-con-full e-flex e-con e-child\" data-id=\"2153d38\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f35bcf9 elementor-widget elementor-widget-heading\" data-id=\"f35bcf9\" data-element_type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">The tourism sector in Gabon<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-160de18 e-con-full e-flex e-con e-child\" data-id=\"160de18\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9ea9f2f elementor-widget__width-initial elementor-widget elementor-widget-text-editor\" data-id=\"9ea9f2f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Gabon&#039;s tourism sector showcases an exceptional natural ecosystem, with thirteen preserved national parks and unique marine biodiversity on the Atlantic coast. Thanks to its high-quality ecotourism, pristine beaches, and diverse wildlife, Gabon is developing into a high-end, environmentally friendly tourist destination.<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1de6112 e-con-full e-flex e-con e-child\" data-id=\"1de6112\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2263574 elementor-widget__width-initial elementor-pagination-position-inside elementor-pagination-type-bullets elementor-widget elementor-widget-loop-carousel\" data-id=\"2263574\" data-element_type=\"widget\" data-settings=\"{&quot;template_id&quot;:&quot;114717&quot;,&quot;autoplay_speed&quot;:7000,&quot;speed&quot;:0,&quot;image_spacing_custom&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:20,&quot;sizes&quot;:[]},&quot;slides_to_show&quot;:&quot;1&quot;,&quot;slides_to_show_tablet&quot;:&quot;1&quot;,&quot;_skin&quot;:&quot;post&quot;,&quot;slides_to_show_mobile&quot;:&quot;1&quot;,&quot;edit_handle_selector&quot;:&quot;.elementor-loop-container&quot;,&quot;autoplay&quot;:&quot;yes&quot;,&quot;infinite&quot;:&quot;yes&quot;,&quot;offset_sides&quot;:&quot;none&quot;,&quot;pagination&quot;:&quot;bullets&quot;,&quot;image_spacing_custom_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;image_spacing_custom_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]}}\" data-widget_type=\"loop-carousel.post\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"swiper elementor-loop-container elementor-grid\" role=\"list\" dir=\"ltr\">\n\t\t\t\t<div class=\"swiper-wrapper\" aria-live=\"off\">\n\t\t<style id=\"loop-dynamic-114717\">.e-loop-item-40151 .elementor-element.elementor-element-484c9f9::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .elementor-background-video-container::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .elementor-background-slideshow::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/856261307_d501ff70c4_c-e1710254450210.jpg\");}@media(max-width:1000px){.e-loop-item-40151 .elementor-element.elementor-element-484c9f9::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .elementor-background-video-container::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .elementor-background-slideshow::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-40151 .elementor-element.elementor-element-484c9f9 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/856261307_d501ff70c4_c-e1710254450210.jpg\");}}<\/style><style id=\"loop-114717\">.elementor-114717 .elementor-element.elementor-element-ffeb86e{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-484c9f9{--display:flex;--min-height:300px;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-start;--align-items:flex-start;--gap:40px 40px;--row-gap:40px;--column-gap:40px;--overlay-opacity:1;--overlay-mix-blend-mode:darken;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-484c9f9::before, .elementor-114717 .elementor-element.elementor-element-484c9f9 > .elementor-background-video-container::before, .elementor-114717 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-video-container::before, .elementor-114717 .elementor-element.elementor-element-484c9f9 > .elementor-background-slideshow::before, .elementor-114717 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-slideshow::before, .elementor-114717 .elementor-element.elementor-element-484c9f9 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-color:#FFFFFF;--background-overlay:'';background-position:center center;background-repeat:no-repeat;background-size:cover;}.elementor-114717 .elementor-element.elementor-element-484c9f9::before{filter:brightness( 73% ) contrast( 100% ) saturate( 100% ) blur( 0px ) hue-rotate( 0deg );}.elementor-114717 .elementor-element.elementor-element-484c9f9.e-con{--align-self:flex-start;}.elementor-114717 .elementor-element.elementor-element-79255b6{--display:flex;--margin-top:75px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-text-editor{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:var( --e-global-color-primary );}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap, .elementor-widget-text-editor.elementor-drop-cap-view-default .elementor-drop-cap{color:var( --e-global-color-primary );border-color:var( --e-global-color-primary );}.elementor-114717 .elementor-element.elementor-element-5d3a74d{width:var( --container-widget-width, 300px );max-width:300px;--container-widget-width:300px;--container-widget-flex-grow:0;columns:1;text-align:justify;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;color:#FFFFFF;}.elementor-114717 .elementor-element.elementor-element-5d3a74d > .elementor-widget-container{margin:0px 0px 0px 0px;}.elementor-114717 .elementor-element.elementor-element-5d3a74d.elementor-element{--align-self:flex-end;}.elementor-114717 .elementor-element.elementor-element-dcc095f{--display:flex;--justify-content:flex-start;--margin-top:75px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-2aa68b5{--display:flex;--justify-content:flex-start;--gap:15px 15px;--row-gap:15px;--column-gap:15px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-theme-post-title .elementor-heading-title{font-family:var( --e-global-typography-primary-font-family ), Sans-serif;font-weight:var( --e-global-typography-primary-font-weight );color:var( --e-global-color-primary );}.elementor-114717 .elementor-element.elementor-element-2ec311b > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-114717 .elementor-element.elementor-element-2ec311b .elementor-heading-title{font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.3em;color:#FFFFFF;}.elementor-widget-theme-post-excerpt .elementor-widget-container{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-114717 .elementor-element.elementor-element-ecffd3d > .elementor-widget-container{margin:0px 0px -20px 0px;padding:0px 0px 0px 0px;}.elementor-114717 .elementor-element.elementor-element-ecffd3d .elementor-widget-container{font-family:\"Roboto\", Sans-serif;font-size:13.5px;font-weight:600;line-height:1.3em;color:#FFFFFF;}.elementor-114717 .elementor-element.elementor-element-1f0c8a2{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-0afb454 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-114717 .elementor-element.elementor-element-0afb454{font-family:\"Roboto\", Sans-serif;font-size:13.5px;font-weight:600;color:#FFFFFF;}@media(max-width:1001px){.elementor-114717 .elementor-element.elementor-element-484c9f9{--min-height:230px;--align-items:flex-start;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--border-radius:0px 0px 0px 0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-ecffd3d > .elementor-widget-container{margin:0px 0px 0px 20px;}.elementor-114717 .elementor-element.elementor-element-ecffd3d .elementor-widget-container{font-size:11.5px;}.elementor-114717 .elementor-element.elementor-element-1f0c8a2{--margin-top:-20px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-0afb454 > .elementor-widget-container{margin:0px 0px 0px 20px;padding:0px 0px 0px 0px;}.elementor-114717 .elementor-element.elementor-element-0afb454{font-size:11.5px;}}@media(min-width:1001px){.elementor-114717 .elementor-element.elementor-element-ffeb86e{--content-width:1250px;}.elementor-114717 .elementor-element.elementor-element-2aa68b5{--width:300px;}}@media(max-width:1001px) and (min-width:1001px){.elementor-114717 .elementor-element.elementor-element-484c9f9{--width:100%;}}@media(max-width:1000px){.elementor-114717 .elementor-element.elementor-element-484c9f9{--width:100%;--min-height:260px;--gap:20px 20px;--row-gap:20px;--column-gap:20px;--flex-wrap:nowrap;--border-radius:0px 0px 0px 0px;--margin-top:20px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-79255b6{--margin-top:50px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-5d3a74d > .elementor-widget-container{margin:0px 0px 0px 10px;padding:0px 0px 0px 0px;}.elementor-114717 .elementor-element.elementor-element-5d3a74d{--container-widget-width:100%;--container-widget-flex-grow:0;width:var( --container-widget-width, 100% );max-width:100%;font-size:12px;line-height:1.3em;}.elementor-114717 .elementor-element.elementor-element-dcc095f{--margin-top:50px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-2aa68b5{--width:100%;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114717 .elementor-element.elementor-element-2ec311b{width:100%;max-width:100%;}.elementor-114717 .elementor-element.elementor-element-2ec311b > .elementor-widget-container{margin:0px 10px 0px 0px;padding:0px 0px 0px 0px;}.elementor-114717 .elementor-element.elementor-element-2ec311b .elementor-heading-title{font-size:13px;}.elementor-114717 .elementor-element.elementor-element-ecffd3d > .elementor-widget-container{margin:0px 10px 0px 0px;padding:0% 0% 0% 0%;}.elementor-114717 .elementor-element.elementor-element-ecffd3d .elementor-widget-container{font-size:12px;}.elementor-114717 .elementor-element.elementor-element-1f0c8a2{--margin-top:-10px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-114717 .elementor-element.elementor-element-0afb454 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-114717 .elementor-element.elementor-element-0afb454{font-size:12px;}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"114717\" class=\"elementor elementor-114717 swiper-slide e-loop-item e-loop-item-40151 post-40151 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon-tourisme category-gabon category-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" role=\"group\" aria-roledescription=\"slide\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-ffeb86e e-flex e-con-boxed e-con e-parent\" data-id=\"ffeb86e\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-484c9f9 e-con-full e-flex e-con e-child\" data-id=\"484c9f9\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t<div class=\"elementor-element elementor-element-79255b6 e-con-full e-flex e-con e-child\" data-id=\"79255b6\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-dcc095f e-con-full e-flex e-con e-child\" data-id=\"dcc095f\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-2aa68b5 e-con-full e-flex e-con e-child\" data-id=\"2aa68b5\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2ec311b elementor-widget-mobile__width-inherit elementor-widget elementor-widget-theme-post-title elementor-page-title elementor-widget-heading\" data-id=\"2ec311b\" data-element_type=\"widget\" data-widget_type=\"theme-post-title.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Gabon \u2013 Tourism <br>\u2013 The tourism sector recovery plan<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ecffd3d elementor-widget elementor-widget-theme-post-excerpt\" data-id=\"ecffd3d\" data-element_type=\"widget\" data-widget_type=\"theme-post-excerpt.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<p>Endowed with 8.15 billion FCFA, the post-Covid-19 recovery plan is structured around three components: creating a...\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<a class=\"elementor-element elementor-element-1f0c8a2 e-con-full e-flex e-con e-child\" data-id=\"1f0c8a2\" data-element_type=\"container\" href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-le-plan-de-relance-du-secteur-touristique\/\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0afb454 elementor-widget elementor-widget-text-editor\" data-id=\"0afb454\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Read more \u00bb<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<style id=\"loop-dynamic-114717\">.e-loop-item-37515 .elementor-element.elementor-element-484c9f9::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .elementor-background-video-container::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .elementor-background-slideshow::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Baie_de_Nyonie_au_Gabon-e1709385883433.jpg\");}@media(max-width:1000px){.e-loop-item-37515 .elementor-element.elementor-element-484c9f9::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .elementor-background-video-container::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .elementor-background-slideshow::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-37515 .elementor-element.elementor-element-484c9f9 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Baie_de_Nyonie_au_Gabon-e1709385883433.jpg\");}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"114717\" class=\"elementor elementor-114717 swiper-slide e-loop-item e-loop-item-37515 post-37515 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon category-gabon-tourisme category-page-pays-only category-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" role=\"group\" aria-roledescription=\"slide\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-ffeb86e e-flex e-con-boxed e-con e-parent\" data-id=\"ffeb86e\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-484c9f9 e-con-full e-flex e-con e-child\" data-id=\"484c9f9\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t<div class=\"elementor-element elementor-element-79255b6 e-con-full e-flex e-con e-child\" data-id=\"79255b6\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-dcc095f e-con-full e-flex e-con e-child\" data-id=\"dcc095f\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-2aa68b5 e-con-full e-flex e-con e-child\" data-id=\"2aa68b5\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2ec311b elementor-widget-mobile__width-inherit elementor-widget elementor-widget-theme-post-title elementor-page-title elementor-widget-heading\" data-id=\"2ec311b\" data-element_type=\"widget\" data-widget_type=\"theme-post-title.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Gabon \u2013 Tourism <br>\u2013 A true Eden to discover<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ecffd3d elementor-widget elementor-widget-theme-post-excerpt\" data-id=\"ecffd3d\" data-element_type=\"widget\" data-widget_type=\"theme-post-excerpt.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<p>Thanks in particular to its numerous beaches, the richness of its national parks and its incredible biodiversity, Gabon is one of the...\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<a class=\"elementor-element elementor-element-1f0c8a2 e-con-full e-flex e-con e-child\" data-id=\"1f0c8a2\" data-element_type=\"container\" href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-un-veritable-eden-a-decouvrir\/\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0afb454 elementor-widget elementor-widget-text-editor\" data-id=\"0afb454\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Read more \u00bb<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<style id=\"loop-dynamic-114717\">.e-loop-item-39045 .elementor-element.elementor-element-484c9f9::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .elementor-background-video-container::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .elementor-background-slideshow::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Pool_area_of_the_Le_Meridien_Libreville.jpg\");}@media(max-width:1000px){.e-loop-item-39045 .elementor-element.elementor-element-484c9f9::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .elementor-background-video-container::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .elementor-background-slideshow::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-39045 .elementor-element.elementor-element-484c9f9 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Pool_area_of_the_Le_Meridien_Libreville.jpg\");}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"114717\" class=\"elementor elementor-114717 swiper-slide e-loop-item e-loop-item-39045 post-39045 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon-tourisme category-gabon category-page-pays-only category-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" role=\"group\" aria-roledescription=\"slide\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-ffeb86e e-flex e-con-boxed e-con e-parent\" data-id=\"ffeb86e\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-484c9f9 e-con-full e-flex e-con e-child\" data-id=\"484c9f9\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t<div class=\"elementor-element elementor-element-79255b6 e-con-full e-flex e-con e-child\" data-id=\"79255b6\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-dcc095f e-con-full e-flex e-con e-child\" data-id=\"dcc095f\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-2aa68b5 e-con-full e-flex e-con e-child\" data-id=\"2aa68b5\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2ec311b elementor-widget-mobile__width-inherit elementor-widget elementor-widget-theme-post-title elementor-page-title elementor-widget-heading\" data-id=\"2ec311b\" data-element_type=\"widget\" data-widget_type=\"theme-post-title.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h3 class=\"elementor-heading-title elementor-size-default\">Gabon \u2013 Tourism <br>\u2013 Hotel and catering<\/h3>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-ecffd3d elementor-widget elementor-widget-theme-post-excerpt\" data-id=\"ecffd3d\" data-element_type=\"widget\" data-widget_type=\"theme-post-excerpt.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<p>The hotel, restaurant and tourism sector was the hardest hit by the...\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<a class=\"elementor-element elementor-element-1f0c8a2 e-con-full e-flex e-con e-child\" data-id=\"1f0c8a2\" data-element_type=\"container\" href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-lhotellerie-et-la-restauration\/\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0afb454 elementor-widget elementor-widget-text-editor\" data-id=\"0afb454\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Read more \u00bb<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<div class=\"swiper-pagination\"><\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-50fe7155 ToBeHidden elementor-hidden-tablet elementor-hidden-mobile e-flex e-con-boxed e-con e-child\" data-id=\"50fe7155\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-69b5695c e-con-full droppable e-flex e-con e-child\" data-id=\"69b5695c\" data-element_type=\"container\" id=\"Ele2A\">\n\t\t\t\t<div class=\"elementor-element elementor-element-583bd8d5 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"583bd8d5\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>\t\t<div data-elementor-type=\"page\" data-elementor-id=\"83347\" class=\"elementor elementor-83347\" data-elementor-post-type=\"elementor_library\">\n\t\t\t\t<div class=\"elementor-element elementor-element-46a1d147 e-con-full OrdiMobileConteneurClass e-flex e-con e-child\" data-id=\"46a1d147\" data-element_type=\"container\" id=\"testIdTest\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8a5fac7 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"8a5fac7\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p><style>.elementor-353554 .elementor-element.elementor-element-6da64a60{--display:flex;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--justify-content:space-around;--align-items:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:50px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-6da64a60.e-con{--align-self:center;--flex-grow:0;--flex-shrink:0;}.elementor-353554 .elementor-element.elementor-element-45f807e0{--display:flex;--min-height:0px;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-100px;--margin-bottom:62px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:3;}.elementor-353554 .elementor-element.elementor-element-45f807e0.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-61dcced4{--display:flex;--margin-top:0px;--margin-bottom:-11px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-6c65b106{--display:flex;--margin-top:0px;--margin-bottom:-30px;--margin-left:-25px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-eb74aa8{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-eb74aa8:not(.elementor-motion-effects-element-type-background), .elementor-353554 .elementor-element.elementor-element-eb74aa8 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-1c762c45{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--flex-wrap:nowrap;--margin-top:-3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-5be6a53a{--display:flex;--align-items:flex-start;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-5be6a53a.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-75917e71{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-image .widget-image-caption{color:var( --e-global-color-text );font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-353554 .elementor-element.elementor-element-6be76773 > .elementor-widget-container{margin:38px 5px -38px -40px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-6be76773.elementor-element{--align-self:flex-start;}.elementor-353554 .elementor-element.elementor-element-6be76773{text-align:right;}.elementor-353554 .elementor-element.elementor-element-6be76773 img{width:17px;}.elementor-353554 .elementor-element.elementor-element-5ad25a69{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-5ad25a69.e-con{--align-self:flex-end;}.elementor-353554 .elementor-element.elementor-element-52dec736 > .elementor-widget-container{margin:45px -18px -55px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-52dec736{z-index:101;text-align:right;}.elementor-353554 .elementor-element.elementor-element-469b1a7c{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-25px;--margin-bottom:90px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-469b1a7c:not(.elementor-motion-effects-element-type-background), .elementor-353554 .elementor-element.elementor-element-469b1a7c > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#9FC5F3;}.elementor-353554 .elementor-element.elementor-element-3f5f9124{--display:flex;--min-height:30px;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:1px;--margin-bottom:-6px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:99;}.elementor-353554 .elementor-element.elementor-element-3f5f9124.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-48a37689{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-start;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-text-editor{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:var( --e-global-color-primary );}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap, .elementor-widget-text-editor.elementor-drop-cap-view-default .elementor-drop-cap{color:var( --e-global-color-primary );border-color:var( --e-global-color-primary );}.elementor-353554 .elementor-element.elementor-element-4b68287 > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353554 .elementor-element.elementor-element-4b68287.elementor-element{--align-self:flex-start;}.elementor-353554 .elementor-element.elementor-element-4b68287{text-align:start;font-family:\"Roboto\", Sans-serif;font-size:12px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-79a46db6{--display:flex;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-47d25f98 > .elementor-widget-container{margin:10px 0px -10px 0px;}.elementor-353554 .elementor-element.elementor-element-47d25f98{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:18px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-3aa42503{--display:flex;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-end;--align-items:flex-end;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-12d5dc39 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-12d5dc39.elementor-element{--align-self:flex-start;}.elementor-353554 .elementor-element.elementor-element-12d5dc39{text-align:end;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-21535e15{--display:flex;--justify-content:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--flex-wrap:wrap;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-21535e15.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-de420c1{--display:flex;--min-height:30px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-2f60f3f > .elementor-widget-container{margin:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-2f60f3f.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-2f60f3f{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:500;line-height:1.1em;color:#FB5E2A;}.elementor-353554 .elementor-element.elementor-element-78e1d9f5{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--gap:010px 9px;--row-gap:010px;--column-gap:9px;--flex-wrap:wrap;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:100;}.elementor-353554 .elementor-element.elementor-element-78e1d9f5.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-cf3602e{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-cf3602e.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-7a1bb33 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-7a1bb33.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-7a1bb33{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-6bf72a5{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-1a57dd9 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-1a57dd9.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-1a57dd9{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:500;line-height:1.1em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-34d8bbba{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-32bd35c6 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-32bd35c6.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-32bd35c6{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-5a9252c3{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-8722042 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-8722042.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-8722042{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-19ecc2b3{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-6071d405 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-6071d405.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-6071d405{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-3617569c{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-4b013e71 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-4b013e71.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-4b013e71{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-7450a6f8{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-30970f89 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-30970f89.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-30970f89{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-6aac67a6{--display:flex;--margin-top:-46px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:2;}.elementor-353554 .elementor-element.elementor-element-40d299c{width:100%;max-width:100%;}.elementor-353554 .elementor-element.elementor-element-40d299c.elementor-element{--align-self:flex-end;}.elementor-353554 .elementor-element.elementor-element-1fd15d20{width:100%;max-width:100%;}.elementor-353554 .elementor-element.elementor-element-1fd15d20.elementor-element{--align-self:flex-end;}.elementor-353554 .elementor-element.elementor-element-54e4e530{--display:flex;--justify-content:center;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-531f3cb1{width:100%;max-width:100%;font-family:\"Roboto\", Sans-serif;font-size:10px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353554 .elementor-element.elementor-element-531f3cb1 > .elementor-widget-container{margin:-37px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-531f3cb1.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-3a352c3f > .elementor-widget-container{margin:7px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-3a352c3f.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-3a352c3f{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353554 .elementor-element.elementor-element-7abb0aba > .elementor-widget-container{margin:7px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-7abb0aba.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-7abb0aba{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353554 .elementor-element.elementor-element-68e2b2ca > .elementor-widget-container{margin:-110px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-68e2b2ca.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-68e2b2ca{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353554 .elementor-element.elementor-element-8d50d33{--display:flex;--margin-top:-85px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-8d50d33.e-con{--align-self:center;}.elementor-widget-form .elementor-field-group > label, .elementor-widget-form .elementor-field-subgroup label{color:var( --e-global-color-text );}.elementor-widget-form .elementor-field-group > label{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-field-type-html{color:var( --e-global-color-text );font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-field-group .elementor-field{color:var( --e-global-color-text );}.elementor-widget-form .elementor-field-group .elementor-field, .elementor-widget-form .elementor-field-subgroup label{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-button{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-widget-form .e-form__buttons__wrapper__button-next{background-color:var( --e-global-color-accent );}.elementor-widget-form .elementor-button[type=\"submit\"]{background-color:var( --e-global-color-accent );}.elementor-widget-form .e-form__buttons__wrapper__button-previous{background-color:var( --e-global-color-accent );}.elementor-widget-form .elementor-message{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .e-form__indicators__indicator, .elementor-widget-form .e-form__indicators__indicator__label{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-widget-form{--e-form-steps-indicator-inactive-primary-color:var( --e-global-color-text );--e-form-steps-indicator-active-primary-color:var( --e-global-color-accent );--e-form-steps-indicator-completed-primary-color:var( --e-global-color-accent );--e-form-steps-indicator-progress-color:var( --e-global-color-accent );--e-form-steps-indicator-progress-background-color:var( --e-global-color-text );--e-form-steps-indicator-progress-meter-color:var( --e-global-color-text );}.elementor-widget-form .e-form__indicators__indicator__progress__meter{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-353554 .elementor-element.elementor-element-6ae6ef83{width:var( --container-widget-width, 69.5% );max-width:69.5%;--container-widget-width:69.5%;--container-widget-flex-grow:0;z-index:120;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group{padding-right:calc( 10px\/2 );padding-left:calc( 10px\/2 );margin-bottom:6px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-form-fields-wrapper{margin-left:calc( -10px\/2 );margin-right:calc( -10px\/2 );margin-bottom:-6px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label, .elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{color:#7B88A3;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-type-html{padding-bottom:0px;color:#7A7A7A;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field{color:#484848;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:01px 01px 01px 01px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:01px 01px 01px 01px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353554 .elementor-element.elementor-element-66fd4d36{--display:flex;--min-height:58px;--gap:0px 0px;--row-gap:0px;--column-gap:0px;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:8px 8px 8px 8px;--margin-top:14px;--margin-bottom:10px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353554 .elementor-element.elementor-element-66fd4d36.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4{width:auto;max-width:auto;z-index:120;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 > .elementor-widget-container{margin:2px 0px 2px 1px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group{padding-right:calc( 0px\/2 );padding-left:calc( 0px\/2 );margin-bottom:0px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-form-fields-wrapper{margin-left:calc( -0px\/2 );margin-right:calc( -0px\/2 );margin-bottom:-0px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label, .elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{color:#FB5E2A;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-type-html{padding-bottom:0px;color:#FFFFFF;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field{color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353554 .elementor-element.elementor-element-4a59cf88 > .elementor-widget-container{margin:-7px 0px -24px 0px;}.elementor-353554 .elementor-element.elementor-element-4a59cf88.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-4a59cf88{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:12.5px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-17d3ad0c{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;border-style:none;--border-style:none;--border-radius:8px 8px 8px 8px;--margin-top:-4px;--margin-bottom:05px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353554 .elementor-element.elementor-element-17d3ad0c.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-177c9b71{width:auto;max-width:auto;z-index:5;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 > .elementor-widget-container{margin:2px 0px 2px 1px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-177c9b71.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group{padding-right:calc( 0px\/2 );padding-left:calc( 0px\/2 );margin-bottom:0px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-form-fields-wrapper{margin-left:calc( -0px\/2 );margin-right:calc( -0px\/2 );margin-bottom:-0px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label, .elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{color:#000000;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-type-html{padding-bottom:0px;color:#FFFFFF;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field{color:#000000;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353554 .elementor-element.elementor-element-5bafe1f3{--display:flex;--min-height:0px;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--gap:0px 0px;--row-gap:0px;--column-gap:0px;--overlay-opacity:1;--margin-top:230px;--margin-bottom:-220px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:10;}.elementor-353554 .elementor-element.elementor-element-5bafe1f3::before, .elementor-353554 .elementor-element.elementor-element-5bafe1f3 > .elementor-background-video-container::before, .elementor-353554 .elementor-element.elementor-element-5bafe1f3 > .e-con-inner > .elementor-background-video-container::before, .elementor-353554 .elementor-element.elementor-element-5bafe1f3 > .elementor-background-slideshow::before, .elementor-353554 .elementor-element.elementor-element-5bafe1f3 > .e-con-inner > .elementor-background-slideshow::before, .elementor-353554 .elementor-element.elementor-element-5bafe1f3 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{--background-overlay:'';background-size:cover;}.elementor-353554 .elementor-element.elementor-element-5bafe1f3.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-432cc2a1{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-446e8565 > .elementor-widget-container{margin:0px 3px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-446e8565.elementor-element{--align-self:flex-end;}.elementor-353554 .elementor-element.elementor-element-446e8565{z-index:5;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;line-height:1.1em;color:#213864;}.elementor-353554 .elementor-element.elementor-element-60df49d0 > .elementor-widget-container{margin:35px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-60df49d0.elementor-element{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-60df49d0{z-index:5;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:500;line-height:1.1em;color:#6185C0;}.elementor-353554 .elementor-element.elementor-element-62ef38f0{--display:flex;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:space-between;--align-items:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-287px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-f4f51cb > .elementor-widget-container{margin:0px 3px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-f4f51cb.elementor-element{--align-self:flex-end;}.elementor-353554 .elementor-element.elementor-element-f4f51cb{z-index:5;text-align:left;font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:600;line-height:1.1em;color:#213864;}.elementor-353554 .elementor-element.elementor-element-f214306 > .elementor-widget-container{margin:0px 0px 0px 3px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-f214306.elementor-element{--align-self:flex-end;}.elementor-353554 .elementor-element.elementor-element-f214306{z-index:5;text-align:start;font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:600;line-height:1.1em;color:#213864;}@media(max-width:1001px){.elementor-353554 .elementor-element.elementor-element-6ae6ef83{z-index:11;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-size:10px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4{z-index:11;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-size:10px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-177c9b71{z-index:11;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-size:10px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-button{font-size:12px;}}@media(min-width:1001px){.elementor-353554 .elementor-element.elementor-element-6da64a60{--width:500px;}.elementor-353554 .elementor-element.elementor-element-45f807e0{--width:100%;}.elementor-353554 .elementor-element.elementor-element-6c65b106{--width:500px;}.elementor-353554 .elementor-element.elementor-element-75917e71{--width:10px;}.elementor-353554 .elementor-element.elementor-element-5ad25a69{--width:100%;}.elementor-353554 .elementor-element.elementor-element-469b1a7c{--width:500px;}.elementor-353554 .elementor-element.elementor-element-48a37689{--width:22%;}.elementor-353554 .elementor-element.elementor-element-79a46db6{--width:50%;}.elementor-353554 .elementor-element.elementor-element-3aa42503{--width:22%;}.elementor-353554 .elementor-element.elementor-element-78e1d9f5{--width:100%;}.elementor-353554 .elementor-element.elementor-element-cf3602e{--width:103px;}.elementor-353554 .elementor-element.elementor-element-6bf72a5{--width:103px;}.elementor-353554 .elementor-element.elementor-element-34d8bbba{--width:103px;}.elementor-353554 .elementor-element.elementor-element-5a9252c3{--width:103px;}.elementor-353554 .elementor-element.elementor-element-19ecc2b3{--width:103px;}.elementor-353554 .elementor-element.elementor-element-3617569c{--width:103px;}.elementor-353554 .elementor-element.elementor-element-7450a6f8{--width:103px;}.elementor-353554 .elementor-element.elementor-element-8d50d33{--width:100%;}.elementor-353554 .elementor-element.elementor-element-66fd4d36{--width:390px;}.elementor-353554 .elementor-element.elementor-element-17d3ad0c{--width:100%;}.elementor-353554 .elementor-element.elementor-element-5bafe1f3{--width:150%;}.elementor-353554 .elementor-element.elementor-element-62ef38f0{--width:95%;}}@media(max-width:1000px){.elementor-353554 .elementor-element.elementor-element-6da64a60{--width:390px;--margin-top:105px;--margin-bottom:-75px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-45f807e0{--min-height:150px;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--margin-top:-47px;--margin-bottom:-32px;--margin-left:0px;--margin-right:0px;--z-index:3;}.elementor-353554 .elementor-element.elementor-element-61dcced4{--width:25%;}.elementor-353554 .elementor-element.elementor-element-6c65b106{--width:100%;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--align-items:center;--flex-wrap:nowrap;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-6c65b106.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-eb74aa8{--width:75%;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;}.elementor-353554 .elementor-element.elementor-element-eb74aa8.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-1c762c45{--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-6be76773 > .elementor-widget-container{margin:-3px 40px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-5ad25a69{--width:100%;--justify-content:flex-end;--align-items:flex-end;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-52dec736 > .elementor-widget-container{margin:40px 55px -30px 0px;}.elementor-353554 .elementor-element.elementor-element-52dec736{z-index:100;}.elementor-353554 .elementor-element.elementor-element-469b1a7c{--width:78%;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--flex-wrap:nowrap;--margin-top:-103px;--margin-bottom:-7px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-469b1a7c.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-3f5f9124{--justify-content:center;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-48a37689{--width:25%;--margin-top:2px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-4b68287 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-4b68287{font-size:8px;}.elementor-353554 .elementor-element.elementor-element-79a46db6{--width:46%;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-47d25f98 > .elementor-widget-container{margin:0px -20px 0px -20px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-47d25f98{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-3aa42503{--width:25%;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-12d5dc39 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-12d5dc39{font-size:7px;}.elementor-353554 .elementor-element.elementor-element-21535e15{--margin-top:-15px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-de420c1{--width:83%;--min-height:15px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-de420c1.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-2f60f3f{width:100%;max-width:100%;font-size:10px;}.elementor-353554 .elementor-element.elementor-element-2f60f3f > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-78e1d9f5{--width:100%;--gap:6px 4px;--row-gap:6px;--column-gap:4px;--flex-wrap:wrap;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-cf3602e{--width:61px;--min-height:15px;}.elementor-353554 .elementor-element.elementor-element-7a1bb33{font-size:9px;}.elementor-353554 .elementor-element.elementor-element-6bf72a5{--width:61px;--min-height:15px;}.elementor-353554 .elementor-element.elementor-element-1a57dd9{font-size:9px;}.elementor-353554 .elementor-element.elementor-element-34d8bbba{--width:61px;--min-height:15px;}.elementor-353554 .elementor-element.elementor-element-32bd35c6{font-size:9px;}.elementor-353554 .elementor-element.elementor-element-5a9252c3{--width:61px;--min-height:15px;}.elementor-353554 .elementor-element.elementor-element-8722042{font-size:9px;}.elementor-353554 .elementor-element.elementor-element-19ecc2b3{--width:61px;--min-height:15px;}.elementor-353554 .elementor-element.elementor-element-6071d405{font-size:9px;}.elementor-353554 .elementor-element.elementor-element-3617569c{--width:61px;--min-height:15px;}.elementor-353554 .elementor-element.elementor-element-4b013e71{font-size:9px;}.elementor-353554 .elementor-element.elementor-element-7450a6f8{--width:61px;--min-height:15px;}.elementor-353554 .elementor-element.elementor-element-30970f89{font-size:9px;}.elementor-353554 .elementor-element.elementor-element-6aac67a6{--width:96%;--min-height:250px;--margin-top:-56px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353554 .elementor-element.elementor-element-40d299c > .elementor-widget-container{margin:5px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-1fd15d20 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-54e4e530{--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-54e4e530.e-con{--order:-99999 \/* order start hack *\/;}.elementor-353554 .elementor-element.elementor-element-531f3cb1 > .elementor-widget-container{margin:-30px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-3a352c3f{width:var( --container-widget-width, 63% );max-width:63%;--container-widget-width:63%;--container-widget-flex-grow:0;text-align:center;font-size:10px;line-height:1.2em;}.elementor-353554 .elementor-element.elementor-element-3a352c3f > .elementor-widget-container{margin:-181px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-7abb0aba > .elementor-widget-container{margin:-180px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-7abb0aba{text-align:center;font-size:14px;line-height:1.2em;}.elementor-353554 .elementor-element.elementor-element-68e2b2ca > .elementor-widget-container{margin:-110px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-68e2b2ca{font-size:10px;}.elementor-353554 .elementor-element.elementor-element-8d50d33{--width:184px;--margin-top:-156px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-8d50d33.e-con{--align-self:center;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83{width:var( --container-widget-width, 100% );max-width:100%;--container-widget-width:100%;--container-widget-flex-grow:0;z-index:120;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-size:8px;}.elementor-353554 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-size:10.5px;}.elementor-353554 .elementor-element.elementor-element-66fd4d36{--width:85%;--min-height:42px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;--margin-top:10px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4{z-index:120;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-type-html{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-size:10px;}.elementor-353554 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-size:10.5px;}.elementor-353554 .elementor-element.elementor-element-4a59cf88 > .elementor-widget-container{margin:2px 0px -10px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-4a59cf88{font-size:7.5px;}.elementor-353554 .elementor-element.elementor-element-17d3ad0c{--width:85%;--min-height:42px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;--margin-top:2px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:251;}.elementor-353554 .elementor-element.elementor-element-177c9b71 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-177c9b71{z-index:120;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-type-html{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-size:12px;}.elementor-353554 .elementor-element.elementor-element-177c9b71 .elementor-button{font-size:10.5px;}.elementor-353554 .elementor-element.elementor-element-5bafe1f3{--min-height:150px;--margin-top:-149px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353554 .elementor-element.elementor-element-446e8565 > .elementor-widget-container{margin:-133px 0px 0px 0px;}.elementor-353554 .elementor-element.elementor-element-446e8565.elementor-element{--align-self:flex-start;}.elementor-353554 .elementor-element.elementor-element-446e8565{text-align:left;font-size:10px;}.elementor-353554 .elementor-element.elementor-element-f4f51cb > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353554 .elementor-element.elementor-element-f4f51cb.elementor-element{--align-self:flex-start;}.elementor-353554 .elementor-element.elementor-element-f4f51cb{text-align:center;font-size:10px;}.elementor-353554 .elementor-element.elementor-element-f214306 > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353554 .elementor-element.elementor-element-f214306.elementor-element{--align-self:flex-start;}.elementor-353554 .elementor-element.elementor-element-f214306{text-align:center;font-size:10px;}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"353554\" class=\"elementor elementor-353554 elementor-bc-flex-widget e-loop-item e-loop-item-39045 post-39045 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon-tourisme category-gabon category-page-pays-only category-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-6da64a60 e-con-full OrdiMobileConteneurClass e-flex e-con e-child\" data-id=\"6da64a60\" data-element_type=\"container\" id=\"testIdTest\">\n\t\t<div class=\"elementor-element elementor-element-45f807e0 e-con-full UploadFileConteneur AdUploadedTitle elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop e-flex e-con e-child\" data-id=\"45f807e0\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-61dcced4 e-con-full e-flex e-con e-child\" data-id=\"61dcced4\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6c65b106 e-con-full e-flex e-con e-child\" data-id=\"6c65b106\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-eb74aa8 e-con-full e-flex e-con e-child\" data-id=\"eb74aa8\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1c762c45 e-con-full e-flex e-con e-child\" data-id=\"1c762c45\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-5be6a53a e-con-full e-flex e-con e-child\" data-id=\"5be6a53a\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-75917e71 e-con-full e-flex e-con e-child\" data-id=\"75917e71\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6be76773 AnnonceDragIcone elementor-widget elementor-widget-image\" data-id=\"6be76773\" data-element_type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/via-agency.media\/wp-content\/uploads\/2025\/05\/arrow-drag-64-bleu.png\" title=\"\" alt=\"\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5ad25a69 e-con-full CroixResetAnnonceContainer e-flex e-con e-child\" data-id=\"5ad25a69\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-52dec736 elementor-widget elementor-widget-image\" data-id=\"52dec736\" data-element_type=\"widget\" id=\"CroixResetAnnonce\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a href=\"#\">\n\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-transparent.png\" title=\"\" alt=\"\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-469b1a7c e-con-full UploadFileConteneur e-flex e-con e-child\" data-id=\"469b1a7c\" data-element_type=\"container\" id=\"UploadFileConteneur\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t<div class=\"elementor-element elementor-element-3f5f9124 e-con-full ChoisirEspacePublicitaireDisponibiliteConteneur e-flex e-con e-child\" data-id=\"3f5f9124\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-48a37689 e-con-full e-flex e-con e-child\" data-id=\"48a37689\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4b68287 PositionEspacePublicitaire elementor-widget elementor-widget-text-editor\" data-id=\"4b68287\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPosition\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-79a46db6 e-con-full e-flex e-con e-child\" data-id=\"79a46db6\" data-element_type=\"container\" id=\"ChoixEspacePublicitaireTitre\">\n\t\t\t\t<div class=\"elementor-element elementor-element-47d25f98 elementor-widget elementor-widget-text-editor\" data-id=\"47d25f98\" data-element_type=\"widget\" id=\"ChoixEspacePublicitaireTexte\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Fixed advertising space<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3aa42503 e-con-full e-flex e-con e-child\" data-id=\"3aa42503\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-12d5dc39 ReferenceEspacePublicitaire elementor-widget elementor-widget-text-editor\" data-id=\"12d5dc39\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reference<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-21535e15 e-con-full EspPubFormatMainContainer e-flex e-con e-child\" data-id=\"21535e15\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-de420c1 e-con-full e-flex e-con e-child\" data-id=\"de420c1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2f60f3f elementor-widget-mobile__width-inherit SelectionFormatTitre elementor-hidden-tablet elementor-widget elementor-widget-text-editor\" data-id=\"2f60f3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPlease select or create an ad format\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-78e1d9f5 e-con-full EspPubFormatListe e-flex e-con e-child\" data-id=\"78e1d9f5\" data-element_type=\"container\">\n\t\t<a class=\"elementor-element elementor-element-cf3602e e-con-full EspPubFormatContainer FormatIdCreation e-flex e-con e-child\" data-id=\"cf3602e\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7a1bb33 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"7a1bb33\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tCreation\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-6bf72a5 e-con-full EspPubFormatContainer FormatIdPopUp e-flex e-con e-child\" data-id=\"6bf72a5\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1a57dd9 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"1a57dd9\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPop-up\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-34d8bbba e-con-full EspPubFormatContainer FormatIdBanniere e-flex e-con e-child\" data-id=\"34d8bbba\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-32bd35c6 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"32bd35c6\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tBanner\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-5a9252c3 e-con-full EspPubFormatContainer FormatIdVideo e-flex e-con e-child\" data-id=\"5a9252c3\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8722042 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"8722042\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tVideo\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-19ecc2b3 e-con-full EspPubFormatContainer FormatIdCommunique e-flex e-con e-child\" data-id=\"19ecc2b3\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6071d405 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"6071d405\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPress release\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-3617569c e-con-full EspPubFormatContainer FormatIdInterview e-flex e-con e-child\" data-id=\"3617569c\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4b013e71 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"4b013e71\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tInterview\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-7450a6f8 e-con-full EspPubFormatContainer FormatIdParrainage e-flex e-con e-child\" data-id=\"7450a6f8\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-30970f89 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"30970f89\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tSponsorship\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6aac67a6 e-con-full HTMLUploadfileConteneur e-flex e-con e-child\" data-id=\"6aac67a6\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-40d299c elementor-widget__width-inherit HTMLUploadfileClass elementor-widget elementor-widget-html\" data-id=\"40d299c\" data-element_type=\"widget\" id=\"HTMLUploadfile\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta name=\"google\" content=\"notranslate\">\r\n    \r\n    <script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" src=\"https:\/\/code.jquery.com\/jquery-3.6.0.min.js\"><\/script>\r\n    <script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" src=\"https:\/\/code.jquery.com\/ui\/1.12.1\/jquery-ui.min.js\"><\/script>\r\n\r\n<script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\">\r\n\/\/ Lazy loading des biblioth\u00e8ques - charg\u00e9es uniquement au besoin\r\nwindow.VIALibraries = {\r\n    mammothLoaded: false,\r\n    pdfLoaded: false,\r\n    \r\n    loadMammoth: function(callback) {\r\n        if (this.mammothLoaded) { callback(); return; }\r\n        var script = document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mammoth\/1.4.0\/mammoth.browser.min.js';\r\n        script.onload = function() { \r\n            window.VIALibraries.mammothLoaded = true; \r\n            callback(); \r\n        };\r\n        document.head.appendChild(script);\r\n    },\r\n    \r\n    loadPdfJs: function(callback) {\r\n        if (this.pdfLoaded) { callback(); return; }\r\n        var script = document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.min.js';\r\n        script.onload = function() {\r\n            window.VIALibraries.pdfLoaded = true;\r\n            pdfjsLib.GlobalWorkerOptions.workerSrc = '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n            callback();\r\n        };\r\n        document.head.appendChild(script);\r\n    }\r\n};\r\n<\/script>\r\n\r\n<\/head>\r\n<body>\r\n    <div id=\"PopUpMessageAchattest\" class=\"draggable\" draggable=\"true\" style=\"justify-content: center; align-items: flex-start;\">\r\n        <div id=\"drop_file_zone_achat\" class=\"drop_file_zone_achat_class\" ondrop=\"uploadFile_achat(event)\" ondragover=\"return false\">\r\n        <div id=\"drag_upload_file_achat\">\r\n            <p><input class=\"button-2_achat\" type=\"button\" value=\"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\" onclick=\"fileExplorer_achat(event);\" \/><\/p>\r\n                <input type=\"file\" id=\"selectfile_achat\" \/>\r\n        <\/div>\r\n        <\/div>\r\n        <div class=\"img-content\"><\/div>\r\n    <\/div>\r\n<\/body>\r\n<\/html>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1fd15d20 elementor-widget__width-inherit HTMLUploadfileClass elementor-widget elementor-widget-html\" data-id=\"1fd15d20\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\">\r\nif (!window._espPubScriptLoaded) {\r\nwindow._espPubScriptLoaded = true;\r\n\/**\r\n * Configuration centralis\u00e9e\r\n *\/\r\nconst CONFIG = {\r\n    iframeId: 'yearbook-iframe',\r\n    ajaxUrl: 'https:\/\/via-agency.media\/wp-admin\/admin-ajax.php',\r\n    apercuUrl: 'https:\/\/rdc.yearbook-media.com\/apercu',\r\n    \r\n    allowedExtensions: {\r\n        video: ['mp4', 'mov', 'wmv', 'avi', 'flv', 'f4v', 'swf', 'mkv', 'webm', 'mpeg'],\r\n        image: ['jpg', 'jpeg', 'png', 'gif', 'tif', 'svg', 'webp'],\r\n        document: ['doc', 'docx', 'ppt', 'pptx', 'pdf']\r\n    },\r\n    \r\n    mimeTypes: {\r\n        docx: 'application\/vnd.openxmlformats-officedocument.wordprocessingml.document',\r\n        pdf: 'application\/pdf'\r\n    },\r\n    \r\n    dragScroll: {\r\n        threshold: 150,\r\n        maxSpeed: 65,\r\n        throttleDelay: 50\r\n    },\r\n    \r\n    breakpoints: {\r\n        mobile: 1000\r\n    },\r\n    \r\n    keywords: [\r\n        { normal: \"parrainage\", display: \"Parrainage\" },\r\n        { normal: \"communique\", display: \"Communiqu\u00e9\" },\r\n        { normal: \"interview\", display: \"Interview\" }\r\n    ]\r\n};\r\n\r\n\/**\r\n * \u2705 v1.19.1 : Module de positionnement dans l'iframe\r\n * \r\n * PROBL\u00c8ME : L'iframe ne scrolle pas en desktop \u2014 c'est le parent (#PageWebTitrePage) qui scrolle.\r\n * L'iframe est scal\u00e9e \u00e0 0.85 \u2192 les coordonn\u00e9es iframe \u2260 coordonn\u00e9es parent.\r\n * position:fixed dans l'iframe = relatif au CONTENU TOTAL, pas au viewport visible.\r\n * \r\n * SOLUTION : Le parent calcule visibleTopIframe (la position iframe du haut du viewport)\r\n * et l'envoie dans le message 'PageWebTitrePage-scroll'. L'iframe n'a qu'\u00e0 l'utiliser.\r\n * \r\n * Pour scroller, l'iframe envoie 'scrollToIframeY' au parent qui fait la conversion.\r\n *\/\r\nconst ScrollHelper = {\r\n    _visibleTopIframe: 0,\r\n    _iframeScale: 0.85,\r\n\r\n    init() {\r\n        \/\/ \u00c9couter les donn\u00e9es de scroll envoy\u00e9es par le parent\r\n        window.addEventListener('message', (event) => {\r\n            var msg = event.data;\r\n            if (msg ? msg.action === 'PageWebTitrePage-scroll' : false) {\r\n                if (typeof msg.visibleTopIframe === 'number') {\r\n                    this._visibleTopIframe = msg.visibleTopIframe;\r\n                }\r\n                if (typeof msg.iframeScale === 'number') {\r\n                    this._iframeScale = msg.iframeScale;\r\n                }\r\n            }\r\n        });\r\n        console.log('\u2705 ScrollHelper initialis\u00e9');\r\n    },\r\n\r\n    \/**\r\n     * Position Y dans le document iframe correspondant au haut du viewport visible\r\n     *\/\r\n    getVisibleTop() {\r\n        if (window === window.top) {\r\n            return window.scrollY || 0;\r\n        }\r\n        return this._visibleTopIframe;\r\n    },\r\n\r\n    \/**\r\n     * Demande au parent de scroller pour positionner `element` \u00e0 `offsetFromTop` px du viewport\r\n     *\/\r\n    scrollElementTo(element, offsetFromTop) {\r\n        if (!element) return;\r\n        \/\/ \u2705 v2.7.3 : Pas de scroll auto sur les sites pays (window.top)\r\n        \/\/ Le scroll apr\u00e8s d\u00e9p\u00f4t d'annonce est ind\u00e9sirable sur desktop et mobile\r\n        if (window === window.top) return;\r\n\r\n        \/\/ getBoundingClientRect().top dans une iframe sans scroll = position absolue Y\r\n        var elementAbsY = element.getBoundingClientRect().top;\r\n\r\n        if (window === window.top) {\r\n            window.scrollTo({ top: elementAbsY - offsetFromTop + (window.scrollY || 0), behavior: 'smooth' });\r\n            return;\r\n        }\r\n\r\n        \/\/ Envoyer au parent \u2014 le parent fait la conversion scale\r\n        window.parent.postMessage({\r\n            type: 'scrollToIframeY',\r\n            iframeId: CONFIG.iframeId,\r\n            iframeY: elementAbsY,\r\n            offsetFromTop: offsetFromTop\r\n        }, '*');\r\n        console.log('\ud83d\udcdc scrollToIframeY:', { iframeY: Math.round(elementAbsY), offsetFromTop });\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'\u00e9tat (SessionStorage)\r\n *\/\r\nconst StateManager = {\r\n    init() {\r\n        if (sessionStorage.getItem(\"AchatEspaceCall\") === 'No') {\r\n            sessionStorage.clear();\r\n        }\r\n        \r\n        this.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        this.set('dragstart_Rank_Emplacement_Page_Web', 'No');\r\n        this.set('dragstart_Commande_Emplacement_Page_Web', 'No');\r\n        \r\n        \/\/ \u2705 v2.1.1 : Popup \u2192 ne jamais pr\u00e9remplir le format\r\n        if (this.get('PopUpChoice') === 'Yes') {\r\n            this.set('Formatchoisi', 'No');\r\n            this.set('FormatSelect', '');\r\n            this.set('Commande_Format_Transmis', '');\r\n        }\r\n    },\r\n    \r\n    get(key) {\r\n        return sessionStorage.getItem(key);\r\n    },\r\n    \r\n    set(key, value) {\r\n        sessionStorage.setItem(key, value);\r\n    },\r\n    \r\n    getMultiple(keys) {\r\n        return keys.reduce((acc, key) => {\r\n            acc[key] = this.get(key);\r\n            return acc;\r\n        }, {});\r\n    },\r\n    \r\n    setMultiple(obj) {\r\n        Object.entries(obj).forEach(([key, value]) => {\r\n            this.set(key, value);\r\n        });\r\n    },\r\n    \r\n    buildEmplacementReference(rank) {\r\n        const codeSite = this.get('codeSite');\r\n        const codePage = this.get('codePage');\r\n        return `${codeSite}${codePage}L${rank.substring(3)}`;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des fichiers\r\n *\/\r\nconst FileManager = {\r\n    getExtension(filename) {\r\n        console.log('filename:', filename);\r\n        const parts = filename.split('.');\r\n        return parts.length > 1 ? parts.pop().toLowerCase() : '';\r\n    },\r\n    \r\n    isAllowedExtension(extension) {\r\n        return Object.values(CONFIG.allowedExtensions)\r\n            .flat()\r\n            .includes(extension);\r\n    },\r\n    \r\n    getFileType(extension) {\r\n        for (const [type, extensions] of Object.entries(CONFIG.allowedExtensions)) {\r\n            if (extensions.includes(extension)) {\r\n                return type;\r\n            }\r\n        }\r\n        return null;\r\n    },\r\n    \r\n    async urlToFile(url, filename) {\r\n        console.log(\"Fetching from URL:\", url);\r\n        \r\n        const mimeType = filename.includes('.docx') \r\n            ? CONFIG.mimeTypes.docx \r\n            : filename.includes('.pdf') \r\n            ? CONFIG.mimeTypes.pdf \r\n            : null;\r\n        \r\n        try {\r\n            const response = await fetch(url);\r\n            \r\n            if (!response.ok) {\r\n                throw new Error(`Network response was not ok: ${response.status} ${response.statusText}`);\r\n            }\r\n            \r\n            const blob = await response.blob();\r\n            return new File([blob], filename, { type: mimeType });\r\n        } catch (error) {\r\n            console.error(\"Error in urlToFile:\", error);\r\n            alert(\"Erreur lors du chargement du fichier. Veuillez r\u00e9essayer.\");\r\n            throw error;\r\n        }\r\n    },\r\n    \r\n    createObjectUrl(file) {\r\n        \/\/ R\u00e9voquer l'ancien URL si existant\r\n        const oldUrl = StateManager.get('objectUrl');\r\n        if (oldUrl ? oldUrl.startsWith('blob:') : false) {\r\n            URL.revokeObjectURL(oldUrl);\r\n            console.log('\ud83d\uddd1\ufe0f Old Object URL revoked');\r\n        }\r\n        \r\n        const url = URL.createObjectURL(file);\r\n        StateManager.set('objectUrl', url);\r\n        return url;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion du Drag & Drop\r\n *\/\r\nconst DragDropManager = {\r\n    state: {\r\n        isFileBeingDragged: false,\r\n        draggedFile: null,\r\n        dragInProgress: false,\r\n        throttleTimeout: null\r\n    },\r\n    \r\n    init() {\r\n        this.attachEventListeners();\r\n    },\r\n    \r\n    attachEventListeners() {\r\n        window.addEventListener('dragstart', (e) => this.handleDragStart(e), true);\r\n        jQuery(document).on('dragover', (e) => this.handleDragOver(e));\r\n        jQuery(document).on('dragleave drop', (e) => this.handleDragEnd(e));\r\n        jQuery(\"#drop_file_zone_achat\").on(\"dragover\", (e) => this.handleDropZoneDragOver(e));\r\n        jQuery(\".draggable\").on(\"dragend\", (e) => this.handleDraggableEnd(e));\r\n    },\r\n    \r\n    handleDragStart(e) {\r\n        console.log('Window-level dragstart', e);\r\n        this.state.dragInProgress = true;\r\n    \r\n        const $target = $(e.target);\r\n        const droppableParent = $target.closest('.droppable');\r\n        const parentId = droppableParent.length ? droppableParent.attr('id') : null;\r\n        \r\n        StateManager.set('dragstart_Rank_Emplacement_Page_Web', parentId);\r\n        console.log('dragstart_Rank_Emplacement_Page_Web', parentId);\r\n        \r\n        \/\/ \u2705 CALCULER IMM\u00c9DIATEMENT dragstart_Commande_Emplacement_Page_Web\r\n        if (parentId) {\r\n            const dragstartRef = StateManager.buildEmplacementReference(parentId);\r\n            StateManager.set('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n            console.log('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n            \/\/ \u2705 v2.4.5 : Capturer l'\u00e9tat checkbox R\u00e9server au moment du dragstart\r\n            \/\/ Si l'utilisateur a explicitement d\u00e9coch\u00e9, forcer \u00e0 No m\u00eame si DOM est coch\u00e9\r\n            var $_dragCb = $('#' + parentId).find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n            var _dragCbChecked = $_dragCb.prop('checked') === true;\r\n            var _expliciteDecocheDrag = StateManager.get('_reserverDecoche_' + parentId) === 'Yes';\r\n            var _reserverDrag = _dragCbChecked ? !_expliciteDecocheDrag : false;\r\n            StateManager.set('dragstart_ReserverChecked', _reserverDrag ? 'Yes' : 'No');\r\n            \/\/ R\u00e9initialiser le flag apr\u00e8s lecture\r\n            StateManager.set('_reserverDecoche_' + parentId, 'No');\r\n            console.log('[dragstart] ReserverChecked dom:', _dragCbChecked, '| d\u00e9coch\u00e9 explicitement:', _expliciteDecocheDrag, '| final:', _reserverDrag);\r\n        }\r\n    \r\n        const dataTransfer = e.dataTransfer || e.originalEvent?.dataTransfer;\r\n        const files = dataTransfer?.files || null;\r\n        \r\n        const imgSrc = $target.closest('.draggable').find('img').attr('src');\r\n        const videoSrc = $target.closest('.draggable').find('video').attr('src') || \r\n                        $target.closest('.draggable').find('video source').attr('src');\r\n        const mediaSrc = imgSrc || videoSrc;\r\n    \r\n        console.log('dragstart target:', e.target);\r\n        console.log('files:', files);\r\n        console.log('mediaSrc:', mediaSrc);\r\n    \r\n        if (!mediaSrc ? (!files || files.length === 0) : false) {\r\n            console.log('No media source or files found, preventing drag');\r\n            e.preventDefault();\r\n            this.state.dragInProgress = false;\r\n            return false;\r\n        }\r\n    \r\n        \/\/ \u2705 v4.9ds Pb 5 : poser une image fant\u00f4me (ghost) sur l'\u00e9v\u00e9nement drag pour que\r\n        \/\/   le navigateur affiche l'image qu'on d\u00e9place au curseur, au lieu de la croix\r\n        \/\/   d'interdiction par d\u00e9faut. Sans setDragImage, certains navigateurs (Chrome,\r\n        \/\/   Firefox) affichent une ic\u00f4ne \"non autoris\u00e9\" si l'\u00e9l\u00e9ment source est complexe\r\n        \/\/   ou si la zone de drop n'est pas imm\u00e9diatement reconnue.\r\n        \/\/   On configure aussi effectAllowed='copy' pour standardiser le comportement.\r\n        if (dataTransfer) {\r\n            try {\r\n                dataTransfer.effectAllowed = 'copy';\r\n                var $_dragImg = $target.closest('.draggable').find('img').first();\r\n                if ($_dragImg.length ? $_dragImg[0].complete : false) {\r\n                    var _imgEl = $_dragImg[0];\r\n                    var _r = _imgEl.getBoundingClientRect();\r\n                    \/\/ D\u00e9calage du ghost : centr\u00e9 sur le curseur\r\n                    var _ox = Math.round((_r.width || 100) \/ 2);\r\n                    var _oy = Math.round((_r.height || 60) \/ 2);\r\n                    dataTransfer.setDragImage(_imgEl, _ox, _oy);\r\n                }\r\n            } catch(_eDI) { console.warn('[dragstart] setDragImage \u00e9chec:', _eDI); }\r\n        }\r\n    \r\n        if (mediaSrc) {\r\n            StateManager.set('objectUrl', mediaSrc);\r\n        }\r\n    \r\n        StateManager.set('Commande_Format_Transmis', '');\r\n        \r\n        if ($target.closest('.droppable').find('.doc-preview-container:visible').length > 0) {\r\n            StateManager.set('Commande_Format_Transmis', 'R\u00e9dactionnel');\r\n            StateManager.set('FullPathAdFile', \r\n                $target.closest('.droppable').find('.doc-preview-FullPathAdFile').text());\r\n        }\r\n    \r\n        if (files ? files.length > 0 : false) {\r\n            this.state.isFileBeingDragged = true;\r\n            this.state.draggedFile = files[0];\r\n        }\r\n        \r\n        \/\/ \u2705 TOUJOURS marquer comme \"Moved\" quand on drag depuis un espace existant\r\n        if (parentId ? mediaSrc : false) {\r\n            StateManager.set('FirstUploadFileorMoved', 'Moved');\r\n            console.log('\u2705 Drag depuis espace existant - Moved');\r\n        }\r\n    \r\n        console.log('Drag Started', {\r\n            file: this.state.draggedFile,\r\n            mediaSrc: mediaSrc,\r\n            dragstartRef: StateManager.get('dragstart_Commande_Emplacement_Page_Web')\r\n        });\r\n    },\r\n    \r\n    handleDragOver(e) {\r\n        e.preventDefault();\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            this.handleIframeDragScroll(e);\r\n        } else {\r\n            this.handleDirectPageScroll(e);\r\n        }\r\n    },\r\n    \r\n    handleIframeDragScroll(e) {\r\n        if (!this.state.throttleTimeout) {\r\n            this.state.throttleTimeout = setTimeout(() => {\r\n                MessageManager.sendToParent('dragScroll', { clientY: e.clientY });\r\n                this.state.throttleTimeout = null;\r\n            }, CONFIG.dragScroll.throttleDelay);\r\n        }\r\n    },\r\n    \r\n    handleDirectPageScroll(e) {\r\n        const { threshold, maxSpeed } = CONFIG.dragScroll;\r\n        const windowHeight = window.innerHeight;\r\n        \r\n        if (e.clientY < threshold) {\r\n            const speed = Math.max(1, maxSpeed * (1 - e.clientY \/ threshold));\r\n            window.scrollBy(0, -speed);\r\n        } else if (e.clientY > windowHeight - threshold) {\r\n            const speed = Math.max(1, maxSpeed * (1 - (windowHeight - e.clientY) \/ threshold));\r\n            window.scrollBy(0, speed);\r\n        }\r\n    },\r\n    \r\n    handleDragEnd(e) {\r\n        e.preventDefault();\r\n        MessageManager.sendToParent('dragEnd', {});\r\n        console.log('dragleave drop');\r\n    },\r\n    \r\n    handleDropZoneDragOver(e) {\r\n        e.preventDefault();\r\n        console.log(\"FirstUploadFileorMoved:\", StateManager.get('FirstUploadFileorMoved'));\r\n    },\r\n    \r\n    handleDraggableEnd(e) {\r\n        e.preventDefault();\r\n        this.resetState();\r\n        console.log('dragend');\r\n    },\r\n    \r\n    resetState() {\r\n        this.state.isFileBeingDragged = false;\r\n        this.state.draggedFile = null;\r\n        this.state.dragInProgress = false;\r\n    },\r\n    \r\n    clearDataTransferFiles(e) {\r\n        e.dataTransfer = new DataTransfer();\r\n        console.log('DataTransfer files cleared');\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'interface utilisateur\r\n *\/\r\nconst UIManager = {\r\n    isMobile() {\r\n        return window.outerWidth < CONFIG.breakpoints.mobile;\r\n    },\r\n    \r\n    isDesktop() {\r\n        const userAgent = navigator.userAgent;\r\n        const isWindows = userAgent.indexOf('Windows') > -1;\r\n        const isMac = userAgent.indexOf('Macintosh') > -1;\r\n        const isLinux = userAgent.indexOf('Linux') > -1 ? userAgent.indexOf('Android') === -1 : false;\r\n        return isWindows || isMac || isLinux;\r\n    },\r\n    \r\n    initMobileUI() {\r\n        $(document).ready(() => {\r\n            $('label[for=\"form-field-LienAnnonce\"]').each(function() {\r\n                $(this).text('Renseigner le lien hypertexte de l\\'annonce');\r\n            });\r\n            \r\n            $('input[name=\"form_fields[LienAnnonce]\"]').each(function() {\r\n                $(this).attr('placeholder', 'Renseigner le lien hypertexte de l\\'annonce');\r\n            });\r\n        });\r\n        \r\n        jQuery('.MsgDatesText').hide();\r\n        this.attachMobileEventListeners();\r\n    },\r\n    \r\n    attachMobileEventListeners() {\r\n        jQuery('.TransmettreFichierAnnonceContainer').on('click', (e) => {\r\n            this.handleMobileSpaceSelection(e);\r\n        });\r\n        \r\n        jQuery('[id=\"form-field-selected_currency_Mobile\"]').off('change').on('change', (e) => {\r\n            this.handleMobileCurrencyChange(e);\r\n        });\r\n    },\r\n    \r\n    handleMobileSpaceSelection(e) {\r\n        e.preventDefault();\r\n        jQuery('#IndisponibilitesMsg').hide();\r\n        \r\n        jQuery('input[name=\"form_fields[SelectEspacePublicitaireMobile]\"]').prop('checked', false);\r\n        jQuery(e.currentTarget).find('input[name=\"form_fields[SelectEspacePublicitaireMobile]\"]').prop('checked', true);\r\n        \r\n        StateManager.set(\"PageWebDisplayed\", 'Yes');\r\n        \r\n        jQuery('.FormSelectDevisesMobile').hide();\r\n        jQuery(e.target).closest('.EspacePublicitaireMobile').find('.FormSelectDevisesMobile').show();\r\n        \r\n        const $droppable = jQuery(e.currentTarget).closest('.droppable');\r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n        StateManager.set('Commande_Emplacement_Page_Web', \r\n            StateManager.buildEmplacementReference(rankId));\r\n        \r\n        this.updateEmplacementDisplay();\r\n        this.handleAvailabilityDisplay(e);\r\n        this.displayUploadedAdIfExists(e);\r\n        this.updateTariffDisplay(e);\r\n    },\r\n    \r\n    updateEmplacementDisplay() {\r\n        const emplacement = StateManager.get('Commande_Emplacement_Page_Web');\r\n        jQuery('#EmplacementAnnonceDataStep3').html(emplacement).css({'color': '#56BE50'});\r\n        console.log(\"Emplacement:\", emplacement);\r\n    },\r\n    \r\n    handleAvailabilityDisplay(e) {\r\n        const $espace = jQuery(e.target).closest('.EspacePublicitaireMobile');\r\n        const dispoText = $espace.find('.ChoisirEspacePublicitaireDisponibilite').text();\r\n        \r\n        if (dispoText.length > 46) {\r\n            const espacePublicitaireDispoFrom = dispoText.slice(-10);\r\n            console.log(\"espacePublicitaireDispoFrom:\", espacePublicitaireDispoFrom);\r\n            console.log(\"Date de debut:\", StateManager.get(\"Debut_de_campagne\"));\r\n        } else {\r\n            jQuery('#form-field-DebutCampagne').val(StateManager.get(\"Debut_de_campagne\"));\r\n        }\r\n    },\r\n    \r\n    displayUploadedAdIfExists(e) {\r\n        if (StateManager.get(\"FileReceived\") === 'Yes') {\r\n            jQuery('.VisualisationAnnonceMobile').hide();\r\n            \r\n            const espaceId = StateManager.get('espaceChoisi');\r\n            const $espace = document.querySelector(`[id=\"${espaceId}\"]`);\r\n            \r\n            jQuery($espace).find('.MessageAnnonceMobileTexte')\r\n                .html('Merci de choisir des dates de campagne<br>afin d\\'obtenir le tarif de cet espace publicitaire');\r\n            jQuery($espace).find('.VisualisationAnnonceMobile').show();\r\n        \r\n            const img = document.createElement('img');\r\n            img.src = StateManager.get('objectUrl');\r\n            img.style.width = 'auto';\r\n            img.style.height = 'auto';\r\n            img.style.maxWidth = '100%';\r\n            img.style.maxHeight = '200px';\r\n            \r\n            jQuery('.VisualisationAnnonceMobile').empty();\r\n            jQuery($espace).find('.VisualisationAnnonceMobile').append(img);\r\n        }\r\n    },\r\n    \r\n    updateTariffDisplay(e) {\r\n        jQuery('.TariftobedisplayedMobile').html('-');\r\n        const newTarif = StateManager.get('NewTarifformatted');\r\n        \r\n        if (newTarif ? newTarif !== '-' : false) {\r\n            jQuery(e.target).closest('.EspacePublicitaireMobile')\r\n                .find('.TariftobedisplayedMobile')\r\n                .html(newTarif);\r\n        }\r\n    },\r\n    \r\n    handleMobileCurrencyChange(e) {\r\n        event.preventDefault();\r\n        StateManager.setMultiple({\r\n            \"SelectionCatalogueOuAchat\": 'Catalogue',\r\n            \"selected_currency\": jQuery(e.currentTarget).val()\r\n        });\r\n        \r\n        jQuery('#form-field-selected_currency_2').val(StateManager.get(\"selected_currency\"));\r\n        console.log(\"selected currency:\", StateManager.get(\"selected_currency\"));\r\n        \r\n        setTimeout(() => {\r\n            jQuery(e.target).closest('.EspacePublicitaireMobile')\r\n                .find('.TariftobedisplayedMobile')\r\n                .html(StateManager.get(\"NewTarifformatted\"));\r\n        }, 500);\r\n    },\r\n    \r\n    initDesktopUI() {\r\n        StateManager.setMultiple({\r\n            \"FileReceived\": \"No\",\r\n            \"AdDisplayed\": \"No\"\r\n        });\r\n        jQuery('.MsgAdNotDisplayed').hide();\r\n    },\r\n    \r\n    showUploadProgress($dropZone) {\r\n        \/\/ Cacher les \u00e9l\u00e9ments\r\n        $dropZone.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .TexteMobileAnnonce')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        \/\/ \u2705 v2.7.3 : Loading dans le dropZone (flux normal, pas de fixed)\r\n        (function() {\r\n            var $_drp    = $dropZone.closest('.droppable');\r\n            var _rankId  = $_drp.attr('id') || '';\r\n            var _isEle0A = _rankId === 'Ele0A';\r\n            var _isMob   = UIManager.isMobile();\r\n            var _posLib  = PreviewRenderer._getPositionLibelle(_rankId) || '';\r\n            var _empl    = StateManager.get('Commande_Emplacement_Page_Web') || '';\r\n            \/\/ \u2705 Bug 11 : -2px sur mobile\r\n            var _fsPos   = _isMob ? '8px'  : '12px';\r\n            var _fsTitle = _isMob ? '9px'  : '14px';\r\n            var _fsRef   = _isMob ? '7px'  : '11px';\r\n\r\n            \/\/ Header identique \u00e0 _buildAdOverlay \u2014 couleurs selon l'espace\r\n            var _hdrBg    = _isEle0A ? '#D0C067' : '#9fc5f3';\r\n            var _titleTxt = _isEle0A ? 'Espace publicitaire Pop-up' : 'Espace publicitaire';\r\n            var _fsRefBold = _isMob ? '8.5px' : '12.5px';\r\n            var _hdrHtml = '<div style=\"display:flex;align-items:center;background:' + _hdrBg + ';padding:4px 8px;box-sizing:border-box;font-family:Roboto,Arial,sans-serif;font-weight:700;color:#ffffff;position:relative;border-radius:4px 4px 0 0;\">'\r\n                + '<div style=\"flex:0 0 auto;z-index:1;\">'\r\n                +   (_posLib ? '<span style=\"font-size:' + _fsPos + ';color:#ffffff;\">' + _posLib + '<\/span>' : '')\r\n                + '<\/div>'\r\n                + '<div style=\"position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;pointer-events:none;\">'\r\n                +   '<span style=\"font-size:' + _fsTitle + ';color:#ffffff;\">' + _titleTxt\r\n                +   (_empl ? ' <span style=\"font-size:' + _fsRefBold + ';font-weight:700;color:#ffffff;\">' + _empl + '<\/span>' : '')\r\n                +   '<\/span>'\r\n                + '<\/div>'\r\n                + '<div style=\"flex:0 0 auto;margin-left:auto;z-index:1;\"><\/div>'\r\n                + '<\/div>';\r\n\r\n            var _bodyHtml = '<div style=\"display:flex;align-items:center;justify-content:center;padding:30px 12px;background:#fff;\">'\r\n                + '<span style=\"color:#00ff19;font-weight:700;font-size:' + (_isMob ? '14px' : '18px') + ';font-family:Roboto,Arial,sans-serif;\">'\r\n                + 'Loading in progress <span style=\"display:inline-block;\"><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><\/span>'\r\n                + '<\/span><\/div>';\r\n\r\n            \/\/ Masquer les \u00e9l\u00e9ments parasites SANS toucher aux dimensions\r\n            $_drp.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne').hide();\r\n            $_drp.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n\r\n            \/\/ Neutraliser les marges n\u00e9gatives Elementor pendant le loading\r\n            \/\/ Sauvegarder sur data() AVANT reset \u2192 styleUploadedAd retrouvera les vraies valeurs\r\n            var _drpOrigMt = parseInt($_drp.css('margin-top')) || 0;\r\n            var _drpOrigMb = parseInt($_drp.css('margin-bottom')) || 0;\r\n            if ($_drp.data('orig-mt') === undefined) { $_drp.data('orig-mt', _drpOrigMt); }\r\n            var $_drpOmc = $_drp.find('.OrdiMobileConteneurClass').first();\r\n            if ($_drpOmc.length) {\r\n                if ($_drpOmc.data('orig-mb') === undefined) { $_drpOmc.data('orig-mb', parseInt($_drpOmc.css('margin-bottom')) || 0); }\r\n            }\r\n            if (_drpOrigMt < 0) { $_drp.css('margin-top', '0px'); }\r\n            if (_drpOrigMb < 0) { $_drp.css('margin-bottom', '0px'); }\r\n\r\n            \/\/ Injecter dans $dropZone sans modifier sa hauteur (height:180px CSS natif)\r\n            \/\/ Forcer le dropZone \u00e0 stretcher son enfant (pas align-items:center)\r\n            $dropZone.css({ 'align-items': 'stretch', 'padding': '0' });\r\n            \/\/ Mobile : 50px de marge en dessous pour \u00e9viter chevauchement avec contenu suivant\r\n            \/\/ Poser sur plusieurs \u00e9l\u00e9ments avec !important + MutationObserver pour r\u00e9sister aux \u00e9crasements\r\n            if (_isMob) {\r\n                var _omcLi = $dropZone.closest('.OrdiMobileConteneurClass')[0];\r\n                if (_omcLi) {\r\n                    _omcLi.style.setProperty('margin-bottom', '50px', 'important');\r\n                    \/\/ Observer : si un autre script \u00e9crase, on restaure\r\n                    try {\r\n                        if (_omcLi._loadingMbObs) { _omcLi._loadingMbObs.disconnect(); }\r\n                        _omcLi._loadingMbObs = new MutationObserver(function(muts) {\r\n                            if (!_omcLi.querySelector('.via-loading-inline')) {\r\n                                \/\/ Loading termin\u00e9, arr\u00eat de l'observer\r\n                                _omcLi._loadingMbObs.disconnect();\r\n                                _omcLi._loadingMbObs = null;\r\n                                return;\r\n                            }\r\n                            var _cur = _omcLi.style.marginBottom;\r\n                            var _needRestore = true;\r\n                            if (_cur === '50px !important') { _needRestore = false; }\r\n                            if (_cur === '50px') { _needRestore = false; }\r\n                            if (_needRestore) {\r\n                                _omcLi.style.setProperty('margin-bottom', '50px', 'important');\r\n                            }\r\n                        });\r\n                        _omcLi._loadingMbObs.observe(_omcLi, { attributes: true, attributeFilter: ['style'] });\r\n                        \/\/ S\u00e9curit\u00e9 : stop au bout de 10 secondes\r\n                        setTimeout(function() {\r\n                            if (_omcLi._loadingMbObs) {\r\n                                _omcLi._loadingMbObs.disconnect();\r\n                                _omcLi._loadingMbObs = null;\r\n                            }\r\n                        }, 10000);\r\n                    } catch(_e) { console.warn('[loading mb observer]', _e); }\r\n                }\r\n            }\r\n            $dropZone.html('<div class=\"via-loading-inline\" style=\"'\r\n                + 'width:100%;height:100%;'\r\n                + 'border:2px solid #00FF19;border-radius:4px;'\r\n                + 'overflow:hidden;background:#fff;box-sizing:border-box;'\r\n                + 'display:flex;flex-direction:column;'\r\n                + (_isMob ? 'margin-top:40px;' : 'margin-top:40px;') + '\">'\r\n                + _hdrHtml\r\n                + '<div style=\"flex:1;display:flex;align-items:' + (_isMob ? 'flex-start' : 'center') + ';justify-content:center;padding-top:' + (_isMob ? '50px' : '0') + ';margin-top:' + (_isMob ? '0' : '-15px') + ';\">'\r\n                + '<span style=\"color:#00ff19;font-weight:700;font-size:' + (_isMob ? '14px' : '18px') + ';font-family:Roboto,Arial,sans-serif;\">'\r\n                + 'Loading in progress <span style=\"display:inline-block;\"><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><\/span>'\r\n                + '<\/span><\/div>'\r\n                + '<\/div>');\r\n        })();\r\n    },\r\n    \r\n    showFormatError($dropZone, message = 'Format de fichier incompatible') {\r\n        \/\/ \u2705 v2.4.6 : M\u00eame rendu overlay que le bloc jpeg \u2014 position absolute sur $dropZone\r\n        \/\/ \u2705 v4.9n  : timeout 5s, mobile r\u00e9duit \u00e0 50% via scale\r\n        var _errId = 'fmt-error-msg-' + Date.now();\r\n        jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n        var _isMob = UIManager.isMobile();\r\n        $dropZone.css('position', 'relative');\r\n        var _errHtml = '<div id=\"' + _errId + '\" style=\"'\r\n            + 'position:absolute;top:' + (_isMob ? '54px' : '50px') + ';left:50%;'\r\n            + (_isMob\r\n                ? 'transform:translateX(-50%) scale(0.604);transform-origin:top center;'\r\n                : 'transform:translateX(-50%);')\r\n            + 'width:auto;white-space:nowrap;'\r\n            + 'background:#fff;color:#FB5E2A;font-weight:700;font-size:' + (_isMob ? '13px' : '14px') + ';'\r\n            + 'text-align:center;padding:8px 10px;border-radius:6px;'\r\n            + 'border:2px solid #FB5E2A;box-sizing:border-box;'\r\n            + 'z-index:99999;line-height:1.4;'\r\n            + '\">' + message + '<\/div>';\r\n        $dropZone.append(_errHtml);\r\n        setTimeout(function() { jQuery('#' + _errId).remove(); }, 5000);\r\n    },\r\n    \r\n    updateAfterSuccessfulUpload($dropZone) {\r\n        jQuery('#errorMessageMobileText').hide();\r\n        \r\n        $dropZone.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 Pas de margin-top:150px si d\u00e9p\u00f4t depuis miniature kit (d\u00e9j\u00e0 positionn\u00e9)\r\n        if (!window._dropFromMiniature) {\r\n            $dropZone.closest('.OrdiMobileConteneurClass')\r\n                .css({'margin-top': '150px', 'margin-bottom': '0px'});\r\n        }\r\n    },\r\n    \r\n    finalizeAdDisplay($dropZone) {\r\n        StateManager.setMultiple({\r\n            \"FileReceived\": \"Yes\",\r\n            \"PageWebDisplayed\": \"Yes\"\r\n        });\r\n        \r\n        jQuery('#MsgChoixPageWeb, #MsgInsererAnnonceConteneur').hide();\r\n        jQuery('#ChoixEspacePublicitaire')\r\n            .html('L\\'annonce est plac\u00e9e dans un espace publicitaire')\r\n            .show()\r\n            .css({'color': '#56BE50'});\r\n        \r\n        jQuery('#MsgPlacerAnnoncePosition').hide();\r\n        jQuery('#FonctionMenu4').show();\r\n        jQuery('.AnnonceData').html(\"Transmis\").css({'color': '#56BE50'});\r\n        \r\n        jQuery('#ProcederPaiementConteneur').hide();\r\n        jQuery('#ProcederPaiementTitreIconeUp').hide();\r\n        jQuery('#ProcederPaiementTitreIconeDown').show();\r\n        \r\n        jQuery('#message').remove();\r\n        \r\n        this.styleUploadedAd($dropZone);\r\n        this.adjustLayoutAfterUpload($dropZone);\r\n        \/\/ \u2705 v2.7.3 : Masquer imm\u00e9diatement apr\u00e8s styleUploadedAd\/adjustLayout\r\n        \/\/ (ces 2 fonctions remontrent des \u00e9l\u00e9ments \u2192 les cacher avant _buildAdOverlay)\r\n        (function(_dz) {\r\n            var $_d = jQuery(_dz).closest('.droppable');\r\n            $_d.find('.DeplaceAnnonce, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .CroixResetAnnonceContainer, .via-ad-header, .via-position-label, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_d.find('.PositionEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })($dropZone[0] || $dropZone);\r\n        \/\/ \u2705 v2.7.3 : Injecter header + footer dans le liser\u00e9 vert\r\n        setTimeout(() => { this._buildAdOverlay($dropZone); }, 250);\r\n        \r\n        \/\/ \u2705 v4.9bf : Figer la largeur du wrapper Ele0A sur mobile site pays apr\u00e8s d\u00e9p\u00f4t\r\n        \/\/            (emp\u00eache le r\u00e9tr\u00e9cissement au scroll caus\u00e9 par width:fit-content)\r\n        \/\/            MutationObserver maintient la valeur en restaurant tout \u00e9crasement\r\n        setTimeout(function() {\r\n            var _droppableId = $dropZone.closest('.droppable').attr('id');\r\n            if (_droppableId !== 'Ele0A') return;\r\n            var _isMobSitePays = (window === window.top);\r\n            if (!_isMobSitePays) return;\r\n            var _isMobCheck = false;\r\n            if (navigator.maxTouchPoints > 0) { _isMobCheck = true; }\r\n            if (window.innerWidth < 1000) { _isMobCheck = true; }\r\n            if (window.outerWidth < 1000) { _isMobCheck = true; }\r\n            if (!_isMobCheck) return;\r\n            var $_wrapE0A = jQuery('.ToBeHidden').has('#Ele0A');\r\n            if (!$_wrapE0A.length) return;\r\n            var _wrapEl = $_wrapE0A[0];\r\n            var _w = _wrapEl.getBoundingClientRect().width;\r\n            if (_w < 50) return;\r\n            var _freezeW = Math.round(_w);\r\n            window._ele0AFrozenWidth = _freezeW;\r\n            \/\/ Applique les 3 propri\u00e9t\u00e9s de largeur\r\n            function _applyFrozenWidth() {\r\n                _wrapEl.style.setProperty('width',     _freezeW + 'px', 'important');\r\n                _wrapEl.style.setProperty('max-width', _freezeW + 'px', 'important');\r\n                _wrapEl.style.setProperty('min-width', _freezeW + 'px', 'important');\r\n            }\r\n            _applyFrozenWidth();\r\n            console.log('[v4.9bf] Ele0A wrapper largeur fig\u00e9e \u00e0 ' + _freezeW + 'px');\r\n            \/\/ Observer : si un autre script change la largeur, on restaure\r\n            try {\r\n                if (_wrapEl._e0AWidthObs) { _wrapEl._e0AWidthObs.disconnect(); }\r\n                _wrapEl._e0AWidthObs = new MutationObserver(function() {\r\n                    var _cur = _wrapEl.getBoundingClientRect().width;\r\n                    if (Math.abs(_cur - _freezeW) > 2) {\r\n                        _applyFrozenWidth();\r\n                    }\r\n                });\r\n                _wrapEl._e0AWidthObs.observe(_wrapEl, { attributes: true, attributeFilter: ['style'] });\r\n                \/\/ Observer aussi #Ele0A (si c'est lui qui change la largeur)\r\n                var _e0AInner = _wrapEl.querySelector('#Ele0A');\r\n                if (_e0AInner) {\r\n                    if (_e0AInner._e0AInnerObs) { _e0AInner._e0AInnerObs.disconnect(); }\r\n                    _e0AInner._e0AInnerObs = new MutationObserver(function() {\r\n                        var _cur = _wrapEl.getBoundingClientRect().width;\r\n                        if (Math.abs(_cur - _freezeW) > 2) {\r\n                            _applyFrozenWidth();\r\n                        }\r\n                    });\r\n                    _e0AInner._e0AInnerObs.observe(_e0AInner, { attributes: true, attributeFilter: ['style'] });\r\n                }\r\n            } catch (_e) { console.warn('[v4.9bf obs]', _e); }\r\n            \/\/ R\u00e9appliquer aussi \u00e0 chaque scroll (backup)\r\n            window.addEventListener('scroll', _applyFrozenWidth, { passive: true });\r\n            \/\/ v4.9bo : badge debug retir\u00e9 (sujet 2 en pause)\r\n        }, 200);\r\n        \r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox \"R\u00e9server\" apr\u00e8s upload\r\n        FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n        \/\/ \u2705 Masquer le bandeau bleu format lors d'une restauration (updateReserverCheckboxState le remet sinon)\r\n        if (StateManager.get('_isAdRestoration') === 'Yes') {\r\n            $dropZone.closest('.droppable').find('.SelectionFormatTitreBlanc').hide();\r\n        }\r\n    },\r\n    \r\n    styleUploadedAd($dropZone) {\r\n        const $container = $dropZone.closest('.OrdiMobileConteneurClass');\r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \r\n        \/\/ \u2705 v1.19.5 : V\u00e9rifier si c'est une restauration via flag d\u00e9di\u00e9\r\n        const isRestoration = StateManager.get('_isAdRestoration') === 'Yes';\r\n        \r\n        $container\r\n            .find('.PositionReference, .TexteMobile, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        $container\r\n            .find('.AdUploadedTitle, .DimensionsMaximales')\r\n            .show();\r\n        \r\n        \/\/ \u2705 Bug 2 fix : montrer la croix (positionnement gere par Pb3 Ele0A)\r\n        $droppable.find('.CroixResetAnnonceContainer').show();\r\n        $droppable.find('#CroixResetAnnonce').show();\r\n        \r\n        \/\/ \u2705 v2.2 : Marquer le droppable comme occup\u00e9 pour qu'Entete le skip\r\n        $droppable.attr('data-via-ad-loaded', 'true');\r\n        \r\n        \/\/ \u2705 v2.0.11 : AdUploadedTitle ne doit pas bloquer le touch sur doc-preview-readmore (mobile)\r\n        $container.find('.AdUploadedTitle').css('pointer-events', 'none');\r\n        \r\n        \/\/ \u2705 #CroixResetAnnonce au-dessus de l'image dans son espace pub\r\n        \/\/ v4.9ds : z-index abaiss\u00e9 \u00e0 5 pour que la pastille jaune .popupAchatAnnonce\r\n        \/\/   passe au-dessus.\r\n        \/\/   Diagnostic stacking context (cf user mobile) :\r\n        \/\/     - Pastille est enfant de EnteteBackground (z-index:6, stacking ctx isol\u00e9)\r\n        \/\/       \u2192 son z-index est PLAFONN\u00c9 \u00e0 6 au niveau body, peu importe la valeur\r\n        \/\/     - Croix vit au niveau body (ses anc\u00eatres jusqu'\u00e0 OrdiMobileConteneurClass\r\n        \/\/       n'ont pas de z-index)\r\n        \/\/   Comp\u00e9tition au niveau body : header z:6 vs croix z:N\r\n        \/\/     \u2192 croix doit avoir z < 6 pour passer sous le header (donc sous la pastille)\r\n        \/\/     \u2192 croix doit avoir z > 3 pour rester au-dessus de l'image (cf z-index:3\r\n        \/\/       sur AdUploadedTitle \/ OrdiMobileConteneurClass parents)\r\n        \/\/   Valeur 5 = compromis : sous header (donc sous pastille) + sur image dans son\r\n        \/\/     droppable.\r\n        $container.find('#CroixResetAnnonce').css({\r\n            'position': 'relative',\r\n            'z-index': '5'\r\n        });\r\n        \r\n        \/\/ \u2705 Desktop : les conteneurs superpos\u00e9s bloquent la croix \u2014 les rendre transparents aux clics\r\n        if (!UIManager.isMobile()) {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css('pointer-events', 'none');\r\n            \/\/ R\u00e9activer les \u00e9l\u00e9ments interactifs\r\n            $container.find('#CroixResetAnnonce').css('pointer-events', 'auto');\r\n            $dropZone.closest('#PopUpMessageAchattest').css('pointer-events', 'auto'); \/\/ drag annonce\r\n        }\r\n        \r\n        \/\/ \u2705 v2.4.3 : Sur mobile, masquer titre et logo drag quand annonce d\u00e9pos\u00e9e dans Ele0A\r\n        if (UIManager.isMobile()) {\r\n            if ($droppable.attr('id') === 'Ele0A') {\r\n                $droppable.find('#ChoixEspacePublicitaireTexte').hide();\r\n                $droppable.find('#Ele0ADragHandle').hide();\r\n            }\r\n            \/\/ v2.9 : forcer pointer-events:auto sur la croix pour tous les espaces (Ele0A + standards)\r\n            var _crcEl = $droppable.find('.CroixResetAnnonceContainer')[0];\r\n            if (_crcEl) { _crcEl.style.setProperty('pointer-events', 'auto', 'important'); }\r\n            var _crEl = $droppable.find('#CroixResetAnnonce')[0];\r\n            if (_crEl) { _crEl.style.setProperty('pointer-events', 'auto', 'important'); }\r\n        }\r\n\r\n        \/\/ v2.6 : Desktop Ele0A - masquer icones drag\/close + elements gris parasites\r\n        if (!UIManager.isMobile()) { if ($droppable.attr('id') === 'Ele0A') {\r\n            $droppable.find('#Ele0ADragHandle').hide();\r\n            $droppable.find('#Ele0ACloseBtn').hide();\r\n            \/\/ v2.6 : Quand annonce chargee dans Ele0A : cacher titre+boutons format (zone grise parasite)\r\n            $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $droppable.find('.EspPubFormatMainContainer').hide();\r\n            $droppable.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n            \/\/ v2.9 : Ele0A desktop - masquer elements parasites (avec ou sans iframe pays)\r\n            $droppable.find('.DeplaceAnnonceText').hide();\r\n            console.log('[styleUploadedAd Ele0A] zone grise masquee | UFC bg -> transparent');\r\n            \/\/ v2.9 : iframe pays (AchatEspaceCall=Yes) -> masquer elements supplementaires\r\n            if (StateManager.get('AchatEspaceCall') === 'Yes') {\r\n                $droppable.find('.PositionEspacePublicitaireDeplacer').hide();\r\n                $droppable.find('.RefEspacePublicitaire').hide();\r\n                console.log('[styleUploadedAd Ele0A] iframe pays: PositionEspacePublicitaireDeplacer masque');\r\n            }\r\n        } }\r\n        \r\n        \/\/ \u2705 v2.6 : Reset uniquement les UFC sans annonce d\u00e9pos\u00e9e (data-via-ad-loaded absent)\r\n        \/\/ Les UFC des espaces d\u00e9j\u00e0 remplis (background:white) ne doivent pas \u00eatre remis \u00e0 transparent\r\n        jQuery(\".HTMLUploadfileConteneur\").each(function() {\r\n            var _droppableEl = jQuery(this).closest('.droppable')[0]\r\n                || jQuery(this).find('#drop_file_zone_achat').closest('.droppable')[0];\r\n            var _hasAd = false;\r\n            if (_droppableEl) {\r\n                if (_droppableEl.getAttribute('data-via-ad-loaded') === 'true') {\r\n                    _hasAd = true;\r\n                }\r\n            }\r\n            if (!_hasAd) {\r\n                jQuery(this).css({ 'border': 'none', 'background-color': 'transparent' });\r\n            }\r\n        });\r\n        \r\n        \/\/ \u2705 v2.4.12 : Retirer le liser\u00e9 envoi diff\u00e9r\u00e9 (#UploadFileConteneur) si pr\u00e9sent\r\n        \/\/ (le d\u00e9p\u00f4t d'une annonce cr\u00e9e son propre liser\u00e9 sur .HTMLUploadfileConteneur)\r\n        var $_ufcEd = $dropZone.closest('.droppable').find('#UploadFileConteneur');\r\n        if ($_ufcEd[0]) {\r\n            $_ufcEd[0].style.removeProperty('box-shadow');\r\n            $_ufcEd[0].style.removeProperty('box-sizing');\r\n            if (UIManager.isMobile()) {\r\n                $_ufcEd[0].style.removeProperty('transform');\r\n                $_ufcEd[0].style.removeProperty('transform-origin');\r\n            }\r\n        }\r\n        \r\n        $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n            \/\/ \u2705 v2.7.3 : box-shadow supprim\u00e9 \u2014 le liser\u00e9 est port\u00e9 par .via-ad-wrapper (outline)\r\n            'box-shadow': 'none',\r\n            'background-color': 'white',\r\n            'box-sizing': 'border-box',\r\n            'overflow': 'hidden'\r\n        });\r\n        \/\/ \u2705 v2.7.3 : Mobile sites pays \u2014 afficher la position en haut \u00e0 gauche dans le liser\u00e9 vert\r\n        if (UIManager.isMobile() ? window === window.top : false) {\r\n            var _rankForPos = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            var _posLabel = PreviewRenderer._getPositionLibelle(_rankForPos);\r\n            if (_posLabel) {\r\n                var $_ufcPos = $dropZone.closest('.HTMLUploadfileConteneur');\r\n                $_ufcPos.css('position', 'relative');\r\n                $_ufcPos.find('.via-position-label').remove();\r\n                $_ufcPos.append('<div class=\"via-position-label\" style=\"position:absolute;top:3px;left:3px;color:#213864;font-size:10px;font-weight:700;font-family:Roboto,sans-serif;padding:2px 7px;z-index:999;pointer-events:none;line-height:1.5;\">' + _posLabel + '<\/div>');\r\n            }\r\n        }\r\n        \r\n        \/\/ \u2705 v2.0.9 : Fix mobile \u2014 r\u00e9duire le scale pour que le liser\u00e9 vert soit visible\r\n        \/\/ \u2705 Pas de scale si annonce d\u00e9pos\u00e9e depuis la miniature kit (d\u00e9j\u00e0 aux bonnes dimensions)\r\n        \/\/ \u2705 v2.4.9 : Pas de scale si AchatEspaceCall=Yes (drop depuis miniature dans iframe)\r\n        \/\/ \u2705 data-kit-drop : marqueur DOM pos\u00e9 par kitAdCreated, r\u00e9siste \u00e0 l'async\r\n        var _fromKitDrop = $droppable[0] ? $droppable[0].getAttribute('data-kit-drop') === 'true' : false;\r\n        if (_fromKitDrop) { window._dropFromMiniature = true; } \/\/ sync le flag window\r\n        var _fromMiniature = window._dropFromMiniature || _fromKitDrop;\r\n        var _fromAchat = StateManager.get('AchatEspaceCall') === 'Yes';\r\n        if (UIManager.isMobile() ? (!_fromMiniature ? !_fromAchat : false) : false) {\r\n            \/\/ \u2705 v2.7.3 : Plus de scale (cause des liser\u00e9s parasites) \u2014 le wrapper overlay g\u00e8re l'apparence\r\n            $dropZone.closest('.UploadFileConteneur').css({\r\n                'transform': 'none',\r\n                'transform-origin': ''\r\n            });\r\n        }\r\n        \r\n        \/\/ \u2705 v2.2 : Sur restauration, Entete a d\u00e9j\u00e0 positionn\u00e9 le droppable correctement\r\n        \/\/ et est emp\u00each\u00e9 de le retoucher (data-via-ad-loaded). On ne touche pas aux marges.\r\n        \/\/ Sur premier upload, on applique les marges normalement.\r\n        \/\/ \u2705 Pas de marges si d\u00e9p\u00f4t depuis miniature kit (dimensions naturelles conserv\u00e9es)\r\n        if (!isRestoration ? !_fromMiniature : false) {\r\n            if ($container.data('orig-mb') === undefined) {\r\n                $container.data('orig-mb', parseInt($container.css('margin-bottom')) || 0);\r\n            }\r\n            if ($droppable.data('orig-mt') === undefined) {\r\n                $droppable.data('orig-mt', parseInt($droppable.css('margin-top')) || 0);\r\n            }\r\n            var _isDocPreview = $dropZone.find('.doc-preview-container').length > 0;\r\n            \/\/ \u2705 Sites pays (window.top) : marges r\u00e9duites (iframe offset non n\u00e9cessaire)\r\n            var _isSitesPays = (window === window.top);\r\n            var _marginAdd    = _isSitesPays ? 0 : (_isDocPreview ? 60 : 130);\r\n            var _marginReduce = _isSitesPays ? 0 : (_isDocPreview ? 60 : 130);\r\n            var _marginTopAdj = _isSitesPays ? 0 : 75;\r\n            $container.css({\r\n                'margin-bottom': ($container.data('orig-mb') + _marginAdd) + 'px'\r\n            });\r\n            $droppable.css({\r\n                'margin-top': ($droppable.data('orig-mt') - _marginReduce + _marginTopAdj) + 'px'\r\n            });\r\n        } else {\r\n            \/\/ \u2705 Sur restauration, ajouter 50px en dessous pour \u00e9viter que le contenu soit trop coll\u00e9\r\n            var _curMb = parseInt($container.css('margin-bottom')) || 0;\r\n            $container.css('margin-bottom', (_curMb + 50) + 'px');\r\n        }\r\n        \r\n        \/\/ \u2705 Masquer texte glisser-d\u00e9poser r\u00e9siduel + position\/r\u00e9server\r\n        $droppable.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne').hide();\r\n        \/\/ \u2705 Masquer .PositionEspacePublicitaireContainer et l'ancien .ReserverContainer\r\n        $droppable.find('.PositionEspacePublicitaireContainer').hide();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \/\/ \u2705 Masquer le checkbox statique Elementor (au cas o\u00f9 .ReserverContainer ne l'englobe pas)\r\n        $droppable.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').hide();\r\n        \/\/ \u2705 Masquer la zone bleue format (SelectionFormatTitreBlanc) et le champ lien hypertexte\r\n        $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n        $droppable.find('.EspPubLienAnnonce').hide();\r\n        \r\n        \/\/ \u2705 Supprimer tout ancien bouton dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 Sur restauration desktop : masquer bandeau bleu + fond #UploadFileConteneur\r\n        \/\/ (ChoisirEspacePublicitaireDisponibiliteConteneur et EspPubFormatMainContainer sont\r\n        \/\/  g\u00e9r\u00e9s par updateAfterSuccessfulUpload + adjustDesktopLayout du flux standard,\r\n        \/\/  mais le background #9FC5F3 du parent #UploadFileConteneur reste \u00e0 neutraliser)\r\n        if (isRestoration) {\r\n            if (!UIManager.isMobile()) {\r\n                $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n                $droppable.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v2.1.2 : Cr\u00e9er le bouton R\u00e9server m\u00eame lors d'une restauration\r\n        if (isRestoration) {\r\n            console.log('\ud83d\udd04 Restauration d\u00e9tect\u00e9e - affichage bouton R\u00e9server + DeplaceAnnonce');\r\n            $droppable.find('.DeplaceAnnonce').show();\r\n            \/\/ S'assurer que FileReceived est d\u00e9fini pour la validation\r\n            StateManager.set('FileReceived', 'Yes');\r\n            \/\/ Cocher automatiquement SEULEMENT si c'est une restauration de commande r\u00e9serv\u00e9e (pas pendingAd)\r\n            var _isPendingAdRestore = sessionStorage.getItem('_pendingAdRestoration') === 'Yes';\r\n            if (!_isPendingAdRestore) {\r\n                var _isReservedRestore = sessionStorage.getItem('_isReservedRestoration') === 'Yes';\r\n                setTimeout(function() {\r\n                    var $checkbox = $droppable.find('.reserver-dynamic-checkbox');\r\n                    if ($checkbox.length ? _isReservedRestore : false) {\r\n                        $checkbox.prop('checked', true);\r\n                        $droppable.find('.reserver-dynamic-label').text('Espace publicitaire r\u00e9serv\u00e9').css('color', 'rgb(62, 170, 19)');\r\n                        console.log('\u2705 R\u00e9server coch\u00e9 automatiquement (restauration commande r\u00e9serv\u00e9e)');\r\n                    }\r\n                    sessionStorage.removeItem('_isReservedRestoration');\r\n                    var _wasRestorationA = true; \/\/ \u00e9tait une restauration\r\n                    StateManager.set('_isAdRestoration', 'No');\r\n                    \/\/ \u2705 v2.4.7 : updateReserverCheckboxState APR\u00c8S avoir coch\u00e9 la checkbox\r\n                    \/\/ (l'appel synchrone ligne 614 intervient avant le cocher \u2192 label incorrect)\r\n                    if (typeof FormatUIManager !== 'undefined') {\r\n                        FormatUIManager.updateReserverCheckboxState($droppable);\r\n                        \/\/ \u2705 Masquer bandeau bleu format si restauration (updateReserverCheckboxState le remet)\r\n                        if (_wasRestorationA) { $droppable.find('.SelectionFormatTitreBlanc').hide(); }\r\n                    }\r\n                }, 150);\r\n            } else {\r\n                \/\/ pendingAd : ne pas cocher, ne pas envoyer au parent\r\n                console.log('\u2705 pendingAd restaur\u00e9 \u2014 R\u00e9server NON coch\u00e9');\r\n                sessionStorage.removeItem('_pendingAdRestoration');\r\n                StateManager.set('_isAdRestoration', 'No');\r\n            }\r\n        }\r\n        \r\n        \/\/ \u2705 Supprimer tout bouton R\u00e9server existant avant insertion (\u00e9vite doublons sur restauration)\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 Cr\u00e9er le bouton \"R\u00e9server\" dynamique avec id unique pour \u00e9viter conflit label\/for Elementor\r\n        const _rankId = $droppable.attr('id') || ('drop_' + Date.now());\r\n        const _cbId = 'reserver-cb-' + _rankId;\r\n        const reserverHTML = `\r\n            <div class=\"reserver-dynamic-container\">\r\n                <span class=\"reserver-dynamic-option\">\r\n                    <input type=\"checkbox\" id=\"${_cbId}\" value=\"R\u00e9server cet espace publicitaire\" \r\n                           class=\"reserver-dynamic-checkbox\"\r\n                           name=\"form_fields[ReserverEspacePublicitaire]\">\r\n                    <span class=\"reserver-dynamic-label\">R\u00e9server cet espace publicitaire<\/span>\r\n                <\/span>\r\n            <\/div>\r\n        `;\r\n        \r\n        \/\/ \u2705 Ins\u00e9rer juste avant .DeplaceAnnonceText (position stable)\r\n        const $anchor = $droppable.find('.DeplaceAnnonceText');\r\n        if ($anchor.length) {\r\n            $anchor.before(reserverHTML);\r\n            $anchor.prev('.reserver-dynamic-container').css('margin-bottom', '-40px');\r\n            if (!UIManager.isMobile()) {\r\n                $anchor.prev('.reserver-dynamic-container').find('.reserver-dynamic-label').css({'font-size': '20px', 'font-weight': '700'});\r\n            }\r\n        } else {\r\n            $droppable.after(reserverHTML);\r\n            const $btn = $droppable.next('.reserver-dynamic-container');\r\n            $btn.css('margin-top', UIManager.isMobile() ? '-10px' : '-140px');\r\n        }\r\n        \r\n        console.log('\u2705 Bouton \"R\u00e9server\" dynamique cr\u00e9\u00e9');\r\n    },\r\n    \r\n    \/\/ =========================================================================\r\n    \/\/ \u2705 v2.7.3 : Injection header \/ footer dans le liser\u00e9 vert apr\u00e8s d\u00e9p\u00f4t d'annonce\r\n    \/\/ Structure : .HTMLUploadfileConteneur \u2192 header (.via-ad-header) + body (existant) + footer (.via-ad-footer)\r\n    \/\/ =========================================================================\r\n    _buildAdOverlay($dropZone) {\r\n        const $droppable  = $dropZone.closest('.droppable');\r\n        const $ufc        = $droppable.find('.HTMLUploadfileConteneur').first();\r\n        if (!$ufc.length) return;\r\n\r\n        \/\/ \u2705 Retirer le loading overlay (fixed sur body) + reset styles\r\n        jQuery('.via-loading-overlay-fixed').remove();\r\n        $ufc.find('.via-loading-overlay').remove();\r\n        \/\/ Reset styles pos\u00e9s pendant le loading (UFC + droppable)\r\n        $ufc.css({ 'position': '', 'min-height': '', 'height': '', 'overflow': '' });\r\n        $droppable.css({ 'min-height': '', 'height': '', 'overflow': '' });\r\n        var $_omcReset = $droppable.find('.OrdiMobileConteneurClass').first();\r\n        if ($_omcReset.length) { $_omcReset.css('min-height', ''); }\r\n        $droppable.css('min-height', '');\r\n\r\n        \/\/ Idempotent : ne pas re-injecter si d\u00e9j\u00e0 pr\u00e9sent\r\n        if ($ufc.find('.via-ad-header').length) return;\r\n\r\n        const _isEle0A   = $droppable.attr('id') === 'Ele0A';\r\n        const _isMobile  = UIManager.isMobile();\r\n        const _isDesktop = !_isMobile;\r\n        \/\/ \u2705 v4.9ds Fix 14 (Pb 10) : utiliser l'emplacement du droppable courant, pas\r\n        \/\/   l'emplacement global StateManager. La variable globale refl\u00e8te le DERNIER\r\n        \/\/   emplacement cliqu\u00e9 (par ex L1A si l'user clique sur Banni\u00e8re dans Ele1A\r\n        \/\/   pendant un upload sur Ele0A) \u2014 alors que le header de l'annonce d\u00e9pos\u00e9e\r\n        \/\/   dans Ele0A doit afficher MDG48442L0A. La m\u00e9thode StateManager.buildEmplacementReference\r\n        \/\/   est exactement celle utilis\u00e9e ligne 441-442 pour construire la r\u00e9f\u00e9rence\r\n        \/\/   \u00e0 partir du rankId. Fallback sur l'emplacement global si la m\u00e9thode\r\n        \/\/   n'existe pas (s\u00e9curit\u00e9).\r\n        const _rankId    = $droppable.attr('id') || '';\r\n        var _emplacement = '';\r\n        if (_rankId ? typeof StateManager.buildEmplacementReference === 'function' : false) {\r\n            _emplacement = StateManager.buildEmplacementReference(_rankId) || '';\r\n        }\r\n        if (!_emplacement) {\r\n            _emplacement = StateManager.get('Commande_Emplacement_Page_Web') || '';\r\n        }\r\n        console.log('[Fix 14] _rankId:', _rankId, '| _emplacement r\u00e9solu:', _emplacement, '| global:', StateManager.get('Commande_Emplacement_Page_Web'));\r\n        const _posLib    = PreviewRenderer._getPositionLibelle(_rankId) || '';\r\n\r\n        \/\/ \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ v4.9ds : Ele0A header +3px (padding 4px\u21925.5px top\/bottom = +3px total)\r\n        const _hdrPadV = _isEle0A ? '5.5px' : '4px';\r\n        const $header = jQuery('<div class=\"via-ad-header\" style=\"' +\r\n            'display:flex;align-items:center;' +\r\n            'background:' + (_isEle0A ? '#D0C067' : '#9FC5F3') + ';padding:' + _hdrPadV + ' 8px;box-sizing:border-box;' +\r\n            'font-family:Roboto,Arial,sans-serif;' +\r\n            'font-size:11px;color:#ffffff;font-weight:700;flex-shrink:0;' +\r\n            'position:relative;' +\r\n        '\"><\/div>');\r\n\r\n        \/\/ \u2500\u2500 Structure header 3 colonnes : [drag] [titre centr\u00e9] [ref + X] \u2500\u2500\u2500\u2500\u2500\r\n        const $colLeft   = jQuery('<div style=\"flex:0 0 auto;display:flex;align-items:center;z-index:1;\"><\/div>');\r\n        const $colCenter = jQuery('<div style=\"position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;pointer-events:none;\"><\/div>');\r\n        const $colRight  = jQuery('<div style=\"flex:0 0 auto;display:flex;align-items:center;gap:8px;margin-left:auto;z-index:1;\"><\/div>');\r\n\r\n        \/\/ Tailles : plein \u00e9cran desktop \/ desktop mode mobile \/ mobile\r\n        var _isFullDesk = !_isMobile ? window.outerWidth >= 1200 : false;\r\n        var _isDeskMob  = !_isMobile ? window.outerWidth < 1200 : false; \/\/ desktop r\u00e9tr\u00e9ci\r\n        var _fsPos   = _isFullDesk ? '13px' : (_isDeskMob ? '6px' : '8px');\r\n        var _fsTitle = _isFullDesk ? '16px' : (_isDeskMob ? (_isEle0A ? '6px' : '8px') : (_isEle0A ? '8px' : '10px'));\r\n        var _fsRef   = _isFullDesk ? '13px' : (_isDeskMob ? '6px' : (_isEle0A ? '6px' : '8px'));\r\n\r\n        if (_isEle0A) {\r\n            \/\/ \u2705 Croix drag 4 fl\u00e8ches SVG \u2014 v4.9ds taille r\u00e9duite (17\u219213) pour aligner sur header standard\r\n            $colLeft.append(jQuery('<span class=\"via-ah-drag\" style=\"cursor:move;opacity:0.8;display:flex;align-items:center;\">' +\r\n                '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"#ffffff\">' +\r\n                '<path d=\"M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z\"\/>' +\r\n                '<\/svg><\/span>'));\r\n            \/\/ \u2705 via-ah-pos masqu\u00e9 pour Ele0A (position dans le titre suffit)\r\n            \/\/ \u2705 Bug 11 : sur mobile, limiter la largeur pour \u00e9viter chevauchement titre\/r\u00e9f\r\n            \/\/ pas de troncature \u2014 la r\u00e9duction de taille suffit\r\n            $colCenter.append(jQuery('<span class=\"via-ah-title\" style=\"font-size:' + _fsTitle + ';color:#ffffff;\">Espace publicitaire Pop-up' + (_emplacement ? ' <span style=\"font-weight:700;font-size:' + (parseFloat(_fsRef) + 1.5) + 'px;\">' + _emplacement + '<\/span>' : '') + '<\/span>'));\r\n        } else {\r\n            if (_posLib) {\r\n                $colLeft.append(jQuery('<span class=\"via-ah-pos\" style=\"font-size:' + _fsPos + ';color:#ffffff;font-weight:700;\">' + _posLib + '<\/span>'));\r\n            }\r\n            $colCenter.append(jQuery('<span class=\"via-ah-title\" style=\"font-size:' + _fsTitle + ';color:#ffffff;\">Espace publicitaire' + (_emplacement ? ' <span style=\"font-weight:700;font-size:' + (parseFloat(_fsRef) + 1.5) + 'px;\">' + _emplacement + '<\/span>' : '') + '<\/span>'));\r\n        }\r\n\r\n        \/\/ Ref incluse dans le titre ci-dessus\r\n\r\n        \/\/ v4.9ds : croix de fermeture positionn\u00e9e en absolute dans le coin haut-droite\r\n        \/\/   de .HTMLUploadfileConteneur (la zone d'aper\u00e7u blanche), \u00e0 2px du liser\u00e9 vert.\r\n        \/\/   Carr\u00e9 bleu fonc\u00e9 (#5573a8) avec croix blanche centr\u00e9e pour qu'elle se voie\r\n        \/\/   sur les images d'annonce d\u00e9pos\u00e9es (qui peuvent \u00eatre de toute couleur).\r\n        const $closeBtn = jQuery('<span class=\"via-ah-close\" style=\"' +\r\n            'cursor:pointer;font-size:13px;font-weight:900;color:#ffffff;line-height:1;' +\r\n            'pointer-events:auto;' +\r\n            'position:absolute;top:2px;right:4px;' +\r\n            'width:18px;height:18px;' +\r\n            'background:#5573a8;' +\r\n            'display:flex;align-items:center;justify-content:center;' +\r\n            'z-index:5;' +\r\n            '\" title=\"Supprimer l\\'annonce\">\u2715<\/span>');\r\n        $closeBtn.on('click', function(e) {\r\n            e.preventDefault();\r\n            e.stopPropagation();\r\n            \/\/ \u2705 v4.9ds Fix 28 : idem .via-erase-btn (ligne ~7750) \u2014 #CroixResetAnnonce\r\n            \/\/   n'existe plus dans le DOM. Appel direct \u00e0 AdResetHandler.handle.\r\n            if (typeof AdResetHandler !== 'undefined' ? typeof AdResetHandler.handle === 'function' : false) {\r\n                AdResetHandler.handle(e);\r\n                return;\r\n            }\r\n            \/\/ Fallback historique (FonctionCroixResetAnnonce \/ trigger sur #CroixResetAnnonce)\r\n            if (typeof window.FonctionCroixResetAnnonce === 'function') {\r\n                var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n                if (_croixEl) { window.FonctionCroixResetAnnonce(_croixEl); return; }\r\n            }\r\n            var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n            if (_croixEl) { jQuery(_croixEl).trigger('click'); }\r\n        });\r\n        \/\/ $colRight reste vide \u2014 la croix est pos\u00e9e en absolute sur $wrapper plus bas\r\n        \/\/   (juste apr\u00e8s son assemblage, pour rester ancr\u00e9e au liser\u00e9 vert).\r\n\r\n        $header.append($colLeft).append($colCenter).append($colRight);\r\n\r\n        \/\/ \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        const $footer = jQuery('<div class=\"via-ad-footer\" style=\"' +\r\n            'background:' + (_isEle0A ? '#D0C067' : '#9FC5F3') + ';padding:4px 8px;box-sizing:border-box;' +  \/* v4.9ds : jaune Ele0A, bleu Ele1A+ *\/\r\n            'font-family:Roboto,Arial,sans-serif;' +\r\n            'font-size:11px;color:#ffffff;flex-shrink:0;' +\r\n        '\"><\/div>');\r\n\r\n        \/\/ v4.9ds : footer en flex pour aligner \u0153il + R\u00e9server\r\n        \/\/ v4.9ds (eye-pos) : position:relative pour ancrer l'\u0153il en absolute (centr\u00e9 dans la moiti\u00e9 gauche),\r\n        \/\/   le bouton R\u00e9server reste seul dans le flux flex et donc centr\u00e9 dans le footer\r\n        $footer.css({\r\n            'display': 'flex',\r\n            'align-items': 'center',\r\n            'justify-content': 'center',\r\n            'gap': '10px',\r\n            'position': 'relative'\r\n        });\r\n\r\n        \/\/ v4.9ds : si Communiqu\u00e9\/Interview (doc-preview pr\u00e9sent), masquer title + readmore\r\n        \/\/   et ajouter un bouton \u0153il dans le footer (\u00e0 gauche de R\u00e9server) qui ouvre la\r\n        \/\/   m\u00eame popup que le readmore (PDFHandler.showInlineDocPopup)\r\n        const _hasDocPreview = $droppable.find('.doc-preview-container').length > 0;\r\n        if (_hasDocPreview) {\r\n            $droppable.find('.doc-preview-title').hide();\r\n            $droppable.find('.doc-preview-readmore').hide();\r\n            \/\/ \u0152il SVG (Material icon \"visibility\") \u2014 ouvre la popup au clic\r\n            \/\/ v4.9ds (eye-size+pos) : taille +60% (18\u219229 desk, 14\u219222 mob) ;\r\n            \/\/   position:absolute \u00e0 left:12% pour \u00eatre centr\u00e9 entre le bord gauche du footer\r\n            \/\/   et le bouton R\u00e9server (qui, seul dans le flux flex, est centr\u00e9 au milieu du footer).\r\n            \/\/   z-index:2 pour passer au-dessus de tout. pointer-events:auto pour rester cliquable.\r\n            \/\/ v4.9ds (eye-stroke) : liser\u00e9 bleu Material (#1976D2) sur l'amande ext\u00e9rieure ET sur\r\n            \/\/   le rond int\u00e9rieur (iris) pour qu'il ressorte sur les 2 couleurs de footer.\r\n            \/\/   3 paths : path 1 = \u0153il entier rempli blanc (inchang\u00e9) ; path 2 = contour amande\r\n            \/\/   ext\u00e9rieure ; path 3 = contour iris (rond int\u00e9rieur). Tous deux fill:none + stroke,\r\n            \/\/   m\u00eame stroke-width pour coh\u00e9rence visuelle. stroke-linejoin:round pour adoucir.\r\n            const _eyeColor = '#ffffff';\r\n            const $eyeBtn = jQuery('<span class=\"via-af-eye\" title=\"Voir la publication\" style=\"cursor:pointer;display:inline-flex;align-items:center;flex:0 0 auto;position:absolute;left:12%;top:50%;transform:translate(-50%,-50%);z-index:2;pointer-events:auto;\">' +\r\n                '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"' + (_isFullDesk ? '29' : '22') + '\" height=\"' + (_isFullDesk ? '29' : '22') + '\" viewBox=\"0 0 24 24\">' +\r\n                '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\" fill=\"' + _eyeColor + '\"\/>' +\r\n                '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5z\" fill=\"none\" stroke=\"#1976D2\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>' +\r\n                '<path d=\"M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z\" fill=\"none\" stroke=\"#1976D2\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>' +\r\n                '<\/svg><\/span>');\r\n            \/\/ \u2500\u2500 \u2193 click handler (inchang\u00e9) \u2193 \u2500\u2500\r\n            $eyeBtn.on('click', function(e) {\r\n                e.preventDefault();\r\n                e.stopPropagation();\r\n                \/\/ Trouver la popup \u00e0 ouvrir : m\u00eame logique que le clic sur .doc-preview-readmore\r\n                \/\/ Priorit\u00e9 1 : kitPdfImageDataURL (cr\u00e9\u00e9 via Kit miniature)\r\n                var _kitPdf = $droppable.data('kitPdfImageDataURL');\r\n                var _kitFormat = $droppable.data('kitFormatSelect') || '';\r\n                var _isInterviewE = (_kitFormat || '').toLowerCase().indexOf('interview') !== -1;\r\n                if (!_kitFormat) {\r\n                    \/\/ Fallback : chercher le titre dans le DOM\r\n                    var _titleEl = $droppable.find('.doc-preview-title')[0];\r\n                    if (_titleEl) { _isInterviewE = (_titleEl.textContent || '').toLowerCase().indexOf('interview') !== -1; }\r\n                }\r\n                var _popupTitle = _isInterviewE ? 'Interview' : 'Communiqu\u00e9';\r\n                if (_kitPdf) {\r\n                    PDFHandler.showInlineDocPopup($dropZone, { formatTitle: _popupTitle, pdfDataURL: _kitPdf });\r\n                    return;\r\n                }\r\n                \/\/ Priorit\u00e9 2 : PDF upload\u00e9 (PDFHandler.pdfDataForViewer)\r\n                if (PDFHandler.pdfDataForViewer ? PDFHandler.pdfDataForViewer.byteLength > 0 : false) {\r\n                    PDFHandler.showInlineDocPopup($dropZone, { formatTitle: _popupTitle, pdfArrayBuffer: PDFHandler.pdfDataForViewer });\r\n                    return;\r\n                }\r\n                \/\/ Priorit\u00e9 3 : d\u00e9clencher le clic sur readmore (m\u00eame si masqu\u00e9) \u2014 fallback Word\/htmlContent\r\n                var _readmore = $droppable.find('.doc-preview-readmore')[0];\r\n                if (_readmore) {\r\n                    _readmore.style.display = 'block';\r\n                    jQuery(_readmore).trigger('click');\r\n                    _readmore.style.display = 'none';\r\n                }\r\n            });\r\n            $footer.append($eyeBtn);\r\n        }\r\n\r\n        \/\/ \u2500\u2500 D\u00e9placer le reserver-dynamic-container dans le footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ Chercher dans le droppable ET apr\u00e8s le droppable (les 2 positions possibles)\r\n        const $resBtnIn   = $droppable.find('.reserver-dynamic-container').first();\r\n        const $resBtnNext = $droppable.next('.reserver-dynamic-container');\r\n        const $resBtn     = $resBtnIn.length ? $resBtnIn : $resBtnNext;\r\n        if ($resBtn.length) {\r\n            \/\/ \u2705 Reset complet de TOUS les styles inline de positionnement\r\n            $resBtn[0].style.cssText = '';\r\n            $resBtn.css({\r\n                'margin-top': '0',\r\n                'margin-bottom': '0',\r\n                'position': '',\r\n                'top': '',\r\n                'left': '',\r\n                'bottom': ''\r\n            });\r\n            \/\/ Style minimal inline pour le label\r\n            $resBtn.find('.reserver-dynamic-label').css({\r\n                'font-size': _isFullDesk ? '16px' : '10px',\r\n                'font-weight': '700'\r\n            });\r\n            $footer.append($resBtn);\r\n        }\r\n\r\n        \/\/ v4.9ds : phrase \"Si vous souhaitez un autre emplacement...\" retir\u00e9e des espaces standards\r\n        \/\/   (uniformisation visuelle, espace gagn\u00e9 dans le footer)\r\n\r\n        \/\/ \u2500\u2500 Cr\u00e9er un wrapper global qui portera le liser\u00e9 vert \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ .HTMLUploadfileConteneur garde son box-shadow inset \u2192 overflow:visible\r\n        \/\/ pour que header et footer (qui d\u00e9bordent en haut\/bas) soient dans le liser\u00e9\r\n        \/\/ Solution : envelopper $ufc + $header + $footer dans un wrapper flex\r\n\r\n        \/\/ \u2705 Retirer tous les liser\u00e9s verts r\u00e9siduels \u2014 le wrapper porta le seul outline\r\n        $ufc.css({\r\n            'overflow': 'visible',\r\n            'height': 'auto',\r\n            'min-height': '',\r\n            \/\/ \u2705 Bug 12 : inset gauche\/droite, ext\u00e9rieur haut\/bas\r\n            'box-shadow': 'inset 2px 0 0 #00FF19, inset -2px 0 0 #00FF19, 0 -2px 0 #00FF19, 0 2px 0 #00FF19',\r\n            'border': 'none',\r\n            'background-color': '',\r\n            'display': 'block',\r\n            'margin': '0',\r\n            'padding': '0',\r\n            \/\/ v4.9ds : ancrage absolute pour la croix .via-ah-close\r\n            'position': 'relative'\r\n        });\r\n        \/\/ Nettoyer aussi UploadFileConteneur, UploadFileConteneur, ToBeHidden et dropzone\r\n        $droppable.find('#UploadFileConteneur, .UploadFileConteneur').css({\r\n            'box-shadow': 'none', 'outline': 'none', 'border': 'none'\r\n        });\r\n        $dropZone.css({ 'box-shadow': 'none', 'outline': 'none', 'border': 'none' });\r\n        \/\/ Nettoyer aussi le parent .ToBeHidden qui peut avoir un transform + outline\r\n        $droppable.find('.OrdiMobileConteneurClass').css({ 'box-shadow': 'none', 'outline': 'none', 'border': 'none' });\r\n\r\n\r\n        \/\/ \u2705 Wrapper global lis\u00e9r\u00e9 vert (header + ufc + footer)\r\n        \/\/   v4.9ds : position:relative pour ancrer la croix .via-ah-close dans le coin haut-droite\r\n        const $wrapper = jQuery('<div class=\"via-ad-wrapper\" style=\"' +\r\n            'display:flex;flex-direction:column;box-sizing:border-box;' +\r\n            'background:#fff;position:relative;' +\r\n        '\">' + '<\/div>');\r\n\r\n        $ufc.before($wrapper);\r\n        $wrapper.append($header);\r\n        $wrapper.append($ufc);\r\n        $wrapper.append($footer);\r\n        \/\/ v4.9ds : croix ancr\u00e9e \u00e0 $ufc (zone d'aper\u00e7u blanche), pas au header bleu\/jaune\r\n        $ufc.append($closeBtn);\r\n\r\n        \/\/ max-height + height via setProperty - re-applique apres Elementor (setTimeout)\r\n        \/\/ Desktop (userAgent) \u2192 hauteur fixe 170px (full desktop + desktop reduit)\r\n        \/\/ Mobile device r\u00e9el \u2192 height:auto, plafonn\u00e9 \u00e0 200px\r\n        \/\/ v4.9cy : Ele0A desktop \u2192 260px (comme les espaces agrandis \u22651200px) car les styles\r\n        \/\/          inline pos\u00e9s ici gagnent sur les r\u00e8gles CSS @media.\r\n        (function(_el) {\r\n            var _isDeskUA = UIManager.isDesktop();\r\n            var _hVal1 = 'auto';\r\n            var _maxH1;\r\n            \/\/ v4.9ds : Ele0A align\u00e9 sur standards (280) + 5px suppl\u00e9mentaires sur Ele0A\r\n            if (_isEle0A ? _isDeskUA : false) {\r\n                _maxH1 = '285px';\r\n            } else if (_isDeskUA) {\r\n                _maxH1 = '280px';\r\n            } else {\r\n                _maxH1 = '200px';\r\n            }\r\n            function _applyH() {\r\n                _el.style.setProperty('max-height', _maxH1, 'important');\r\n                _el.style.setProperty('height', _hVal1, 'important');\r\n                _el.style.setProperty('overflow', 'hidden', 'important');\r\n            }\r\n            _applyH();\r\n            setTimeout(_applyH, 100);\r\n            setTimeout(_applyH, 500);\r\n        })($ufc[0]);\r\n\r\n        \/\/ v4.9ds : min-height sur #drop_file_zone_achat = hauteur espace standard - header - footer\r\n        \/\/          \u00c0 chaque dimension correspond la m\u00eame logique que _applyH ci-dessus.\r\n        \/\/          La fonction est stock\u00e9e sur le droppable pour pouvoir \u00eatre rappel\u00e9e au resize.\r\n        (function(_dz, _hdr, _ftr, _drop) {\r\n            if (!_dz) return;\r\n            function _applyDzMinH(_phase) {\r\n                var _isDeskUA = UIManager.isDesktop();\r\n                var _ufcH;\r\n                \/\/ v4.9ds : Ele0A align\u00e9 sur standards (280) + 5px suppl\u00e9mentaires sur Ele0A\r\n                if (_isEle0A ? _isDeskUA : false) { _ufcH = 285; }\r\n                else if (_isDeskUA)               { _ufcH = 280; }\r\n                else                              { _ufcH = 200; }\r\n                var _hdrH = _hdr && _hdr.offsetHeight ? _hdr.offsetHeight : 0;\r\n                var _ftrH = _ftr && _ftr.offsetHeight ? _ftr.offsetHeight : 0;\r\n                var _dzMinH = Math.max(0, _ufcH - _hdrH - _ftrH);\r\n                _dz.style.setProperty('min-height', _dzMinH + 'px', 'important');\r\n                _dz.style.setProperty('height', _dzMinH + 'px', 'important');\r\n                _dz.style.setProperty('flex', '1 1 auto', 'important');\r\n                \/\/ v4.9ds DIAG : mesurer toutes les dimensions cl\u00e9s pour comprendre l'\u00e9cart Ele0A vs standards\r\n                var _ufc = _dz.closest('.HTMLUploadfileConteneur');\r\n                var _wrap = _dz.closest('.via-ad-wrapper');\r\n                var _img = _dz.querySelector('img, video, .doc-preview-container');\r\n                var _ufcCS = _ufc ? window.getComputedStyle(_ufc) : null;\r\n                var _wrapCS = _wrap ? window.getComputedStyle(_wrap) : null;\r\n                var _dzCS = window.getComputedStyle(_dz);\r\n                \/\/ v4.9ds DIAG : aussi rect.height (r\u00e9el \u00e0 l'\u00e9cran apr\u00e8s zoom\/scale) + zoom\/transform sur anc\u00eatres\r\n                var _wrapRect = _wrap ? _wrap.getBoundingClientRect() : null;\r\n                var _ufcRect = _ufc ? _ufc.getBoundingClientRect() : null;\r\n                var _dzRect = _dz.getBoundingClientRect();\r\n                \/\/ Chercher zoom\/transform sur les 5 anc\u00eatres jusqu'au droppable\r\n                var _zoomChain = [];\r\n                var _curEl = _wrap;\r\n                for (var _zi = 0; _zi < 6; _zi++) {\r\n                    if (!_curEl) { break; }\r\n                    var _csEl = window.getComputedStyle(_curEl);\r\n                    var _zm = _csEl.zoom;\r\n                    var _tr = _csEl.transform;\r\n                    var _hasTr = _tr ? (_tr !== 'none') : false;\r\n                    var _hasZoom = (_zm !== '1');\r\n                    if (_hasZoom ? true : _hasTr) {\r\n                        _zoomChain.push((_curEl.tagName + (_curEl.id ? '#' + _curEl.id : '.' + (_curEl.className || '').split(' ')[0])) + ' zoom:' + _zm + ' tr:' + _tr);\r\n                    }\r\n                    _curEl = _curEl.parentElement;\r\n                }\r\n                \/\/ v4.9ds DIAG ULTIME : cha\u00eene compl\u00e8te des anc\u00eatres avec rect.h pour identifier l'\u00e9cart\r\n                var _ancChain = [];\r\n                var _ancEl = _wrap;\r\n                for (var _ai = 0; _ai < 12; _ai++) {\r\n                    if (!_ancEl) { break; }\r\n                    var _aRect = _ancEl.getBoundingClientRect();\r\n                    var _aCS = window.getComputedStyle(_ancEl);\r\n                    var _tag = _ancEl.tagName;\r\n                    var _id = _ancEl.id ? ('#' + _ancEl.id) : '';\r\n                    var _cls = (_ancEl.className || '').split(' ')[0] || '';\r\n                    if (_cls) { _cls = '.' + _cls; }\r\n                    _ancChain.push(_tag + _id + _cls + ' [offH:' + _ancEl.offsetHeight + ' rectH:' + Math.round(_aRect.height) + ' zoom:' + _aCS.zoom + ' tr:' + (_aCS.transform === 'none' ? 'none' : _aCS.transform.substring(0, 30)) + ']');\r\n                    _ancEl = _ancEl.parentElement;\r\n                }\r\n                console.log('[DIAG H ' + _phase + '] '\r\n                    + (_isEle0A ? 'ELE0A' : 'STANDARD ' + (_dz.closest('.droppable') ? _dz.closest('.droppable').id : '?'))\r\n                    + ' | _ufcH:' + _ufcH + ' hdr:' + _hdrH + ' ftr:' + _ftrH + ' dzMinH:' + _dzMinH\r\n                    + '\\n  WRAPPER offsetH:' + (_wrap ? _wrap.offsetHeight : 'null') + ' rect.h:' + (_wrapRect ? Math.round(_wrapRect.height) : 'null')\r\n                    + '\\n  UFC offsetH:' + (_ufc ? _ufc.offsetHeight : 'null') + ' rect.h:' + (_ufcRect ? Math.round(_ufcRect.height) : 'null')\r\n                    + '\\n  DZ offsetH:' + _dz.offsetHeight + ' rect.h:' + Math.round(_dzRect.height)\r\n                    + '\\n  IMG offsetH:' + (_img ? _img.offsetHeight : 'null')\r\n                    + '\\n  CHAIN ancestors:\\n    ' + _ancChain.join('\\n    ')\r\n                );\r\n            }\r\n            _applyDzMinH('T+0');\r\n            setTimeout(function() { _applyDzMinH('T+100'); }, 100);\r\n            setTimeout(function() { _applyDzMinH('T+500'); }, 500);\r\n            \/\/ v4.9ds : exposer pour appel depuis handler resize\r\n            if (_drop) { _drop._applyDzMinH = _applyDzMinH; }\r\n        })($dropZone[0], $header[0], $footer[0], $droppable[0]);\r\n\r\n        \/\/ Sites pays : remonter le wrapper\r\n        if (!_isEle0A ? window === window.top : false) {\r\n            \/\/ \ud83d\udd0d DIAG : loguer la d\u00e9cision de branche + contexte\r\n            console.log('[DIAG _buildAdOverlay] branche sites pays', {\r\n                _isDesktop: _isDesktop,\r\n                isMobile: UIManager.isMobile(),\r\n                outerWidth: window.outerWidth,\r\n                innerWidth: window.innerWidth,\r\n                htmlClass: document.documentElement.className,\r\n                hasRegieClass: document.documentElement.classList.contains('via-regie-iframe'),\r\n                droppableId: $droppable.attr('id')\r\n            });\r\n            if (_isDesktop) {\r\n                \/\/ Kit creation : adjustDesktopLayout modifie deja le droppable margin\r\n                \/\/ -> wrapper margin-top reduit pour eviter le chevauchement\r\n                var _isKitDrop = window._lastDropWasKit === true;\r\n                \/\/ \u2705 DVM (desktop version mobile, inner<1000) : text-editor Elementor pose 297px\r\n                \/\/    \u2192 wrapper trop bas. -150px.\r\n                \/\/    Plein \u00e9cran desktop : -30px (l\u00e9ger d\u00e9calage, coh\u00e9rent avec post-resize).\r\n                var _isDVM = (window.innerWidth < 1000);\r\n                var _mt = _isKitDrop ? '30px' : (_isDVM ? '-150px' : '0px');\r\n                if ($wrapper[0]) {\r\n                    $wrapper[0].style.setProperty('margin-top', _mt, 'important');\r\n                    $wrapper[0].style.setProperty('margin-bottom', _isKitDrop ? '20px' : '15px', 'important');\r\n                }\r\n                \/\/ \u2705 Article (body.single) ou page secteur (body.page) au d\u00e9p\u00f4t plein \u00e9cran :\r\n                \/\/    80px en-dessous du droppable pour \u00e9viter le chevauchement avec le contenu.\r\n                if (!_isDVM) { if (!_isKitDrop) {\r\n                    var _isArticleOrSecteurDep = document.body.classList.contains('single') || document.body.classList.contains('page');\r\n                    if (_isArticleOrSecteurDep) {\r\n                        if ($droppable[0]) {\r\n                            $droppable[0].style.setProperty('margin-bottom', '80px', 'important');\r\n                            console.log('[DIAG _buildAdOverlay] article\/secteur plein \u00e9cran \u2192 droppable.margin-bottom: 80px');\r\n                        }\r\n                    }\r\n                } }\r\n                $wrapper.attr('data-deposited-mode', 'desktop');\r\n                console.log('[DIAG _buildAdOverlay] branche DESKTOP \u2192 margin-top:', _mt, '(DVM=' + _isDVM + ')');\r\n                \/\/ \u2705 D\u00e9clencher un resize simul\u00e9 300ms apr\u00e8s le d\u00e9p\u00f4t pour que le handler resize\r\n                \/\/    applique ses positions (algo inter-espaces, article\/secteur, DVM cleanup, etc.).\r\n                \/\/    Actif aussi en DVM (cas d\u00e9p\u00f4t en version mobile qui chevauche contenu au-dessus).\r\n                \/\/ v4.9ds : retir\u00e9 la condition !_isKitDrop \u2014 les Communiqu\u00e9\/Interview cr\u00e9\u00e9s\r\n                \/\/   depuis le kit ont aussi besoin du resize pour recalculer les espaces en dessous\r\n                setTimeout(function() {\r\n                    try {\r\n                        window.dispatchEvent(new Event('resize'));\r\n                        console.log('[DIAG _buildAdOverlay] resize simul\u00e9 post-d\u00e9p\u00f4t d\u00e9clench\u00e9 (DVM=' + _isDVM + ', kitDrop=' + _isKitDrop + ')');\r\n                    } catch (e) {\r\n                        console.warn('[DIAG _buildAdOverlay] resize simul\u00e9 a throw:', e);\r\n                    }\r\n                }, 300);\r\n                \/\/ \ud83d\udd0d DIAG d\u00e9taill\u00e9 : mesurer la position r\u00e9elle apr\u00e8s application\r\n                setTimeout(function() {\r\n                    var _w = $wrapper[0];\r\n                    var _d = $droppable[0];\r\n                    if (!_w) { return; }\r\n                    var _wRect = _w.getBoundingClientRect();\r\n                    var _dRect = _d ? _d.getBoundingClientRect() : null;\r\n                    var _wCS = getComputedStyle(_w);\r\n                    var _parent = _w.parentElement;\r\n                    var _parentRect = _parent ? _parent.getBoundingClientRect() : null;\r\n                    var _parentCS = _parent ? getComputedStyle(_parent) : null;\r\n                    \/\/ Essayer de trouver l'\u00e9l\u00e9ment juste au-dessus dans la page\r\n                    var _prevEl = null;\r\n                    if (_d) {\r\n                        var _sib = _d.previousElementSibling;\r\n                        while (_sib) {\r\n                            var _sr = _sib.getBoundingClientRect();\r\n                            if (_sr.height > 10) { _prevEl = _sib; break; }\r\n                            _sib = _sib.previousElementSibling;\r\n                        }\r\n                    }\r\n                    var _prevRect = _prevEl ? _prevEl.getBoundingClientRect() : null;\r\n                    console.log('[DIAG DEPOT DVM]', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        wrapperMT_computed: _wCS.marginTop,\r\n                        wrapperRect: { top: Math.round(_wRect.top), bottom: Math.round(_wRect.bottom), height: Math.round(_wRect.height) },\r\n                        droppableId: _d ? _d.id : '?',\r\n                        droppableRect: _dRect ? { top: Math.round(_dRect.top), bottom: Math.round(_dRect.bottom), height: Math.round(_dRect.height) } : null,\r\n                        parentTag: _parent ? (_parent.tagName + '.' + (_parent.className || '').split(' ').slice(0, 3).join('.')) : '?',\r\n                        parentDisplay: _parentCS ? _parentCS.display : '?',\r\n                        parentRect: _parentRect ? { top: Math.round(_parentRect.top), bottom: Math.round(_parentRect.bottom), height: Math.round(_parentRect.height) } : null,\r\n                        prevSibling: _prevEl ? (_prevEl.tagName + '.' + (_prevEl.className || '').split(' ').slice(0, 2).join('.')) : '?',\r\n                        prevSiblingRect: _prevRect ? { top: Math.round(_prevRect.top), bottom: Math.round(_prevRect.bottom) } : null,\r\n                        gapAboveDroppable: (_prevRect ? (_dRect ? Math.round(_dRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        gapAboveWrapper: (_prevRect ? (_wRect ? Math.round(_wRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        inner: window.innerWidth,\r\n                        outer: window.outerWidth\r\n                    });\r\n                }, 150);\r\n            } else {\r\n                \/\/ Sites pays d\u00e9p\u00f4t mobile : -140px (valeur historique qui marche)\r\n                if ($wrapper[0]) {\r\n                    $wrapper[0].style.setProperty('margin-top', '-140px', 'important');\r\n                    $wrapper[0].style.setProperty('margin-bottom', '0px', 'important');\r\n                }\r\n                $wrapper.attr('data-deposited-mode', 'mobile');\r\n                console.log('[DIAG _buildAdOverlay] branche MOBILE \u2192 margin-top: -140px !important');\r\n                \/\/ \u2705 Resize simul\u00e9 300ms apr\u00e8s le d\u00e9p\u00f4t pour que le handler resize applique\r\n                \/\/    ses positions (cleanup DVM, wrapper.mt\/mb=25px, etc.).\r\n                setTimeout(function() {\r\n                    try {\r\n                        window.dispatchEvent(new Event('resize'));\r\n                        console.log('[DIAG _buildAdOverlay] resize simul\u00e9 post-d\u00e9p\u00f4t MOBILE d\u00e9clench\u00e9');\r\n                    } catch (e) {\r\n                        console.warn('[DIAG _buildAdOverlay] resize simul\u00e9 MOBILE a throw:', e);\r\n                    }\r\n                }, 300);\r\n                \/\/ \ud83d\udd0d DIAG d\u00e9taill\u00e9 : mesurer la position r\u00e9elle apr\u00e8s application\r\n                setTimeout(function() {\r\n                    var _w = $wrapper[0];\r\n                    var _d = $droppable[0];\r\n                    if (!_w) { return; }\r\n                    var _wRect = _w.getBoundingClientRect();\r\n                    var _dRect = _d ? _d.getBoundingClientRect() : null;\r\n                    var _wCS = getComputedStyle(_w);\r\n                    var _parent = _w.parentElement;\r\n                    var _parentRect = _parent ? _parent.getBoundingClientRect() : null;\r\n                    var _parentCS = _parent ? getComputedStyle(_parent) : null;\r\n                    var _prevEl = null;\r\n                    if (_d) {\r\n                        var _sib = _d.previousElementSibling;\r\n                        while (_sib) {\r\n                            var _sr = _sib.getBoundingClientRect();\r\n                            if (_sr.height > 10) { _prevEl = _sib; break; }\r\n                            _sib = _sib.previousElementSibling;\r\n                        }\r\n                    }\r\n                    var _prevRect = _prevEl ? _prevEl.getBoundingClientRect() : null;\r\n                    console.log('[DIAG DEPOT MOBILE]', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        wrapperMT_computed: _wCS.marginTop,\r\n                        wrapperRect: { top: Math.round(_wRect.top), bottom: Math.round(_wRect.bottom), height: Math.round(_wRect.height) },\r\n                        droppableId: _d ? _d.id : '?',\r\n                        droppableRect: _dRect ? { top: Math.round(_dRect.top), bottom: Math.round(_dRect.bottom), height: Math.round(_dRect.height) } : null,\r\n                        parentTag: _parent ? (_parent.tagName + '.' + (_parent.className || '').split(' ').slice(0, 3).join('.')) : '?',\r\n                        parentDisplay: _parentCS ? _parentCS.display : '?',\r\n                        parentRect: _parentRect ? { top: Math.round(_parentRect.top), bottom: Math.round(_parentRect.bottom), height: Math.round(_parentRect.height) } : null,\r\n                        prevSibling: _prevEl ? (_prevEl.tagName + '.' + (_prevEl.className || '').split(' ').slice(0, 2).join('.')) : '?',\r\n                        prevSiblingRect: _prevRect ? { top: Math.round(_prevRect.top), bottom: Math.round(_prevRect.bottom) } : null,\r\n                        gapAboveDroppable: (_prevRect ? (_dRect ? Math.round(_dRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        gapAboveWrapper: (_prevRect ? (_wRect ? Math.round(_wRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        inner: window.innerWidth,\r\n                        outer: window.outerWidth\r\n                    });\r\n                }, 150);\r\n            }\r\n            \/\/ \ud83d\udd0d DIAG : v\u00e9rifier l'\u00e9tat effectif apr\u00e8s application\r\n            setTimeout(function() {\r\n                var _w = $wrapper[0];\r\n                if (_w) {\r\n                    var _cs = getComputedStyle(_w);\r\n                    console.log('[DIAG _buildAdOverlay] \u00e9tat wrapper apr\u00e8s 100ms', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        computedMarginTop: _cs.marginTop,\r\n                        computedMarginBottom: _cs.marginBottom,\r\n                        boundingRect: { top: _w.getBoundingClientRect().top, height: _w.getBoundingClientRect().height }\r\n                    });\r\n                }\r\n            }, 100);\r\n        }\r\n\r\n        \/\/ \u2705 Desktop : ajouter margin-bottom pour que le footer ne chevauche pas le contenu\r\n        if (_isDesktop) {\r\n            setTimeout(function() {\r\n                var _footerH = $footer[0] ? $footer[0].getBoundingClientRect().height : 0;\r\n                var _headerH = $header[0] ? $header[0].getBoundingClientRect().height : 0;\r\n                if (_footerH > 0) {\r\n                    $wrapper.css('margin-bottom', (_footerH + _headerH) + 'px');\r\n                }\r\n            }, 50);\r\n        }\r\n\r\n        \/\/ \u2705 body = reset margin du dropzone\r\n        $dropZone.css({ 'margin-top': '0', 'margin-bottom': '0' });\r\n        $dropZone.closest('.UploadFileConteneur').css({ 'margin-top': '0', 'margin-bottom': '0' });\r\n\r\n        \/\/ \u2705 v2.7.3 : Mobile \u2014 largeur = viewport moins marges, hauteur auto\r\n        if (!_isFullDesk) { \/\/ mobile + desktop mode mobile\r\n            \/\/ \u2705 R\u00e9f\u00e9rence CSS : offsetWidth du premier espace standard sans annonce\r\n            \/\/ offsetWidth = largeur CSS pr\u00e9-transform \u2192 m\u00eame base pour tous les espaces\r\n            var _refCssW = 0;\r\n            jQuery('.droppable').not('[id=\"Ele0A\"]').not('[data-via-ad-loaded=\"true\"]').each(function() {\r\n                var $_tbh = jQuery(this).closest('.ToBeHidden');\r\n                if ($_tbh.length) {\r\n                    var _w = $_tbh[0].offsetWidth;\r\n                    if (_w > 50) { _refCssW = _w; return false; }\r\n                }\r\n            });\r\n            if (_refCssW < 50) {\r\n                var $_fbTbh = jQuery('.droppable').not('[id=\"Ele0A\"]').first().closest('.ToBeHidden');\r\n                if ($_fbTbh.length) { _refCssW = $_fbTbh[0].offsetWidth; }\r\n            }\r\n            if (_refCssW < 50) { _refCssW = Math.round(window.innerWidth * 0.90); }\r\n\r\n            \/\/ Largeur CSS du wrapper = m\u00eame offsetWidth CSS que la r\u00e9f\u00e9rence standard\r\n            \/\/ Ele0A : son parent ToBeHidden est plus large \u2192 appliquer ratio rendu vs CSS pour \u00e9quilibrer\r\n            var _wrapCssW = _refCssW;\r\n            if (_isEle0A) {\r\n                var $_e0ATbh = $droppable.closest('.ToBeHidden');\r\n                if ($_e0ATbh.length) {\r\n                    var _e0ARendered = $_e0ATbh[0].getBoundingClientRect().width;\r\n                    var _e0ACss      = $_e0ATbh[0].offsetWidth || _e0ARendered;\r\n                    \/\/ _refCssW * (e0A_rendered \/ e0A_css) = largeur \u00e9cran cible pour Ele0A\r\n                    \/\/ Pour compenser l'exc\u00e9dent de 10% : appliquer le ratio scale Ele0A\r\n                    if (_e0ACss > 0 ? _e0ARendered > 0 : false) {\r\n                        var _e0AScale = _e0ARendered \/ _e0ACss;\r\n                        \/\/ CSS width pour que le rendu = _refRenderedW\r\n                        var $_stdTbhRef = jQuery('.droppable').not('[id=\"Ele0A\"]').not('[data-via-ad-loaded=\"true\"]').first().closest('.ToBeHidden');\r\n                        var _refRendered = $_stdTbhRef.length ? $_stdTbhRef[0].getBoundingClientRect().width : _refCssW;\r\n                        if (_e0AScale > 0.1) { _wrapCssW = Math.round(_refRendered \/ _e0AScale); }\r\n                    }\r\n                }\r\n            }\r\n\r\n            var _wrapPx = Math.round(_wrapCssW) + 'px';\r\n            var $_wrap = $droppable.find('.via-ad-wrapper');\r\n            \/\/ Sites pays : width 100% -> responsive au zoom CSS sans JS au resize\r\n            var _isSitesPays = (window === window.top);\r\n            $_wrap.css({\r\n                'width': _isSitesPays ? '100%' : _wrapPx,\r\n                'max-width': _isSitesPays ? '100%' : _wrapPx,\r\n                'box-sizing': 'border-box',\r\n                'position': 'relative',\r\n                'left': '0',\r\n                'transform': 'none'\r\n            });\r\n            \/\/ \u2705 Laisser les parents en overflow:visible pour ne pas clipper le wrapper\r\n            if (!_isEle0A) {\r\n                $droppable.find('.OrdiMobileConteneurClass').css({ 'overflow': 'visible' });\r\n                $droppable.css({ 'overflow': 'visible' });\r\n            }\r\n            $ufc.css({ 'width': '100%', 'max-width': 'none', 'min-height': '0',\r\n                        'margin': '0', 'padding': '0' });\r\n            (function(_el2) {\r\n                var _omc = _el2.closest ? _el2.closest('.OrdiMobileConteneurClass') : null;\r\n                \/\/ Desktop version mobile (userAgent desktop + fen\u00eatre r\u00e9duite) \u2192 hauteur fixe 170px\r\n                \/\/ Mobile device r\u00e9el \u2192 height:auto, plafonn\u00e9 \u00e0 200px\r\n                var _isDeskMobile = UIManager.isDesktop();\r\n                var _hVal = 'auto';\r\n                var _maxH = _isDeskMobile ? '280px' : '200px';  \/* v4.9ds : 170 \u2192 280 align\u00e9 sur _applyH principal *\/\r\n                function _applyH2() {\r\n                    \/\/ UFC\r\n                    _el2.style.setProperty('height', _hVal, 'important');\r\n                    _el2.style.setProperty('max-height', _maxH, 'important');\r\n                    _el2.style.setProperty('overflow', 'hidden', 'important');\r\n                    \/\/ Parent OMC : hauteur auto, pas de clamp (sinon header+footer clipp\u00e9s)\r\n                    if (_omc) {\r\n                        _omc.style.setProperty('height', 'auto', 'important');\r\n                        _omc.style.removeProperty('max-height');\r\n                        _omc.style.removeProperty('overflow');\r\n                    }\r\n                }\r\n                _applyH2();\r\n                setTimeout(_applyH2, 100);\r\n                setTimeout(_applyH2, 500);\r\n                setTimeout(_applyH2, 1000);\r\n            })($ufc[0]);\r\n            \/\/ Remonter le wrapper : 0 sur sites pays (zoom CSS, pas de scale)\r\n            if (!_isEle0A) {\r\n                var $_wrapStd = $droppable.find('.via-ad-wrapper');\r\n                var _isInIframe = (window !== window.top);\r\n                if (_isInIframe) {\r\n                    \/\/ iframe r\u00e9gie : compensation scale\r\n                    var _ufcMt = parseFloat($ufc.css('margin-top')) || 0;\r\n                    $_wrapStd.css('margin-top', (_ufcMt - 80) + 'px');\r\n                } else {\r\n                    \/\/ sites pays : DVM = -150, plein \u00e9cran = 0\r\n                    if ($_wrapStd[0]) {\r\n                        var _isDVMStd = (window.innerWidth < 1000);\r\n                        $_wrapStd[0].style.setProperty('margin-top', _isDVMStd ? '-150px' : '0px', 'important');\r\n                    }\r\n                }\r\n            }\r\n            \/\/ v4.9ds : dropzone width seulement (height\/min-height g\u00e9r\u00e9s par _applyDzMinH)\r\n            var _imgMaxH = '160px';\r\n            $dropZone.css({ 'width': '100%', 'margin': '0' });\r\n            \/\/ v4.9ds : pour que l'image occupe TOUTE la box du dropZone (comme Ele0A) tout\r\n            \/\/   en restant enti\u00e8re sans crop, on utilise width:100% + height:100% +\r\n            \/\/   object-fit:contain. Le dropZone a sa height pos\u00e9e par _applyDzMinH et le\r\n            \/\/   max-height CSS lignes 7910\/7929 plafonne. object-fit:contain garantit que\r\n            \/\/   l'image enti\u00e8re est visible (letterbox automatique si ratio diff\u00e9rent).\r\n            $dropZone.find('img, video').css({ 'max-width': '100%', 'width': '100%', 'max-height': _imgMaxH, 'height': '100%', 'display': 'block', 'object-fit': 'contain' });\r\n            \/\/ \u2705 Bug 11 : remonter drop_file_zone_achat de 2px sur mobile + margin-bottom 2px\r\n            $dropZone[0].style.setProperty('margin-top', '-2px', 'important');\r\n            $dropZone[0].style.setProperty('margin-bottom', '2px', 'important');\r\n        }\r\n\r\n        \/\/ v4.9ds : Clamp universel image uniquement (le min-height\/height du dropZone\r\n        \/\/          est g\u00e9r\u00e9 par _applyDzMinH ci-dessus). On ne touche plus au dropZone ici.\r\n        (function() {\r\n            var _imgMaxH2 = '160px';\r\n            $dropZone.find('img, video').css({\r\n                'max-height': _imgMaxH2, 'height': '100%', 'object-fit': 'contain'\r\n            });\r\n        })();\r\n\r\n        \/\/ \u2705 Masquer TOUS les anciens \u00e9l\u00e9ments de position\/ref (d\u00e9sormais dans le header)\r\n        $droppable.find('.DeplaceAnnonce, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire').hide();\r\n        $droppable.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne, .UploadIci').hide();\r\n        $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        \/\/ setProperty !important pour \u00e9craser les display:block!important d'adjustMobileLayout\r\n        $droppable.find('.PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer').each(function() {\r\n            this.style.setProperty('display', 'none', 'important');\r\n        });\r\n        \/\/ \u2705 Supprimer via-position-label (position maintenant dans le header)\r\n        $droppable.find('.via-position-label').remove();\r\n\r\n        \/\/ \u2699\ufe0f Override text-editor au d\u00e9p\u00f4t : neutralise la height explicite (ex: 297px)\r\n        \/\/    que Elementor pose sur .elementor-widget-text-editor \u00e0 certains breakpoints \u00e9troits.\r\n        \/\/    Sans \u00e7a, le wrapper se retrouve d\u00e9cal\u00e9 vers le bas dans l'OMC et chevauche le\r\n        \/\/    contenu en dessous (pb visible en desktop version mobile).\r\n        if (typeof window._viaOverrideTextEditor === 'function') {\r\n            var _omcForOverride = $droppable.find('.OrdiMobileConteneurClass')[0];\r\n            if (_omcForOverride) {\r\n                window._viaOverrideTextEditor(_omcForOverride);\r\n            }\r\n        }\r\n\r\n        console.log('\u2705 [via-ad-overlay] header+footer+wrapper inject\u00e9s | Ele0A:', _isEle0A, '| ref:', _emplacement);\r\n    },\r\n\r\n    adjustLayoutAfterUpload($dropZone) {\r\n        console.log('\ud83d\udcf1 adjustLayoutAfterUpload \u2014 isMobile():', this.isMobile(), 'outerWidth:', window.outerWidth);\r\n        if (this.isMobile()) {\r\n            this.adjustMobileLayout($dropZone);\r\n            \r\n            \/\/ Repositionner le bouton R\u00e9server APR\u00c8S les ajustements de layout\r\n            setTimeout(() => {\r\n                const $droppable = $dropZone.closest('.droppable');\r\n                \/\/ \u2705 v2.6 : Ele0A popup \u2014 skip repositionnement offset (g\u00e9r\u00e9 dans _isEle0AMobAdj)\r\n                if ($droppable.attr('id') === 'Ele0A') { return; }\r\n                const $btn = $droppable.find('.reserver-dynamic-container').add($droppable.next('.reserver-dynamic-container')).first();\r\n                const $lisere = $droppable.find('.HTMLUploadfileConteneur');\r\n                if ($btn.length ? $lisere.length : false) {\r\n                    const lisereBottom = $lisere[0].getBoundingClientRect().bottom;\r\n                    const btnTop = $btn[0].getBoundingClientRect().top;\r\n                    const offset = lisereBottom - btnTop - 48;\r\n                    \r\n                    \/\/ 15px suppl\u00e9mentaires pour homepage et articles\r\n                    let extraOffset = 0;\r\n                    const isHomepage = window.location.pathname === \"\/\" || window.location.pathname === \"\/en\/\";\r\n                    const isSectorPage = $('body').hasClass('page');\r\n                    if (isHomepage) {\r\n                        extraOffset = 13;\r\n                    }\r\n                    if (!isSectorPage) {\r\n                        extraOffset = 15;\r\n                    }\r\n                    \r\n                    \/\/ \u2705 v2.0.9 : +4px pour documents (communiqu\u00e9\/interview\/PDF) avec pr\u00e9sentation HTML\r\n                    if ($droppable.find('.doc-preview-container:visible').length > 0) {\r\n                        extraOffset += 4;\r\n                    }\r\n                    \r\n                    \/\/ v2.9 : site pays direct \u2014 r\u00e9duire l'offset (layout neutre vs iframe)\r\n                    var _sitePaysAdj = (window === window.top) ? 10 : 0;\r\n                    $btn.css({\r\n                        'margin-top': (offset + extraOffset + 20 - _sitePaysAdj) + 'px',\r\n                        'margin-bottom': (-(offset + extraOffset) - 100 + _sitePaysAdj) + 'px'\r\n                    });\r\n                    console.log('\ud83d\udcd0 Repositionnement mobile R\u00e9server:', { lisereBottom, btnTop, offset });\r\n                }\r\n            }, 50);\r\n        } else {\r\n            this.adjustDesktopLayout($dropZone);\r\n        }\r\n    },\r\n    \r\n    adjustMobileLayout($dropZone) {\r\n        console.log('\ud83d\udcf1 adjustMobileLayout START');\r\n        console.log('\ud83d\udcf1 AchatEspaceCall:', StateManager.get(\"AchatEspaceCall\"));\r\n        console.log('\ud83d\udcf1 PageAjoutModifAnnonce:', StateManager.get(\"PageAjoutModifAnnonce\"));\r\n        console.log('\ud83d\udcf1 window===window.top:', window === window.top);\r\n        console.log('\ud83d\udcf1 pathname:', location.pathname);\r\n        \r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \r\n        \/\/ \u2705 v2.4.3 : Masquer les textes position\/label\/r\u00e9f\u00e9rence (comme sur desktop)\r\n        $droppable\r\n            .find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 v2.6 : Renseigner .PositionEspacePublicitaire sur mobile (manquait vs desktop)\r\n        (function() {\r\n            var _rankPosMob = StateManager.get('Rank_Emplacement_Page_Web') || $droppable.attr('id') || '';\r\n            var _posLibMob = PreviewRenderer._getPositionLibelle(_rankPosMob);\r\n            if (_posLibMob) {\r\n                $droppable\r\n                    .find('.PositionEspacePublicitaire')\r\n                    .text(_posLibMob)\r\n                    .each(function() {\r\n                        this.style.setProperty('display', 'block', 'important');\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        \/\/ \u2705 Scop\u00e9 au $dropZone courant (\u00e9vite d'affecter les autres espaces pub)\r\n        $dropZone.closest('.droppable').find('#CroixResetAnnonce').css({'zoom': '75%'});\r\n\r\n        \/\/ \u2705 v2.4.3 : Remonter la croix reset sur mobile (override margin-top Elementor)\r\n        \/\/ \u2705 v2.4.5 : Poser aussi margin-right ici (Entete.txt ne l'atteint pas pour Ele0A)\r\n        var _croixContainer = $dropZone.closest('.OrdiMobileConteneurClass').find('.CroixResetAnnonceContainer')[0];\r\n        if (_croixContainer) {\r\n            var _isEle0ACroix = ($dropZone.closest('.droppable').attr('id') === 'Ele0A');\r\n            var _isTopWindowCroix = (window === window.top);\r\n            \/\/ v2.9 : site pays (mt=0px sur OrdiMobile) \u2192 compensate +65px vs ancien 65px\r\n            var _mtCroix = StateManager.get(\"PageAjoutModifAnnonce\") === 'Yes' ? '-88px'\r\n                         : (_isTopWindowCroix ? '70px' : '24px');\r\n            _croixContainer.style.setProperty('margin-top', _mtCroix, 'important');\r\n            _croixContainer.style.setProperty('margin-right', _isEle0ACroix ? '17px' : '12px', 'important');\r\n        }\r\n        \r\n        \/\/ \u2705 v2.6 : data-kit-drop = marqueur DOM fiable m\u00eame si _dropFromMiniature d\u00e9j\u00e0 resett\u00e9\r\n        var _droppableMob = $dropZone.closest('.droppable')[0];\r\n        var _isMiniatureAdj = window._dropFromMiniature\r\n            || (_droppableMob ? (_droppableMob.getAttribute('data-kit-drop') === 'true') : false);\r\n        window._dropFromMiniature = false;\r\n        \/\/ Nettoyer data-kit-drop apr\u00e8s lecture (comme le fait adjustDesktopLayout)\r\n        if (_droppableMob) { _droppableMob.removeAttribute('data-kit-drop'); }\r\n\r\n\r\n        if (_isMiniatureAdj ? window.outerWidth <= 1000 : false) {\r\n            \/\/ \u2705 Miniature doc-preview MOBILE : ajuster HTMLUploadfileConteneur \u00e0 la hauteur du contenu\r\n            \/\/ et neutraliser tous les margins Elementor qui d\u00e9calent le $dropZone hors du parent\r\n            var _isDocPreviewMob = $dropZone.find('.doc-preview-container').length > 0;\r\n            var _docH = _isDocPreviewMob ? 144 : 115;\r\n            \/\/ \u2705 v2.6 : Dans l'iframe r\u00e9gie (window !== window.top) \u2192 mt:70px (comme AchatEspaceCall=Yes)\r\n            \/\/ En standalone : mt:-55px (valeur originale)\r\n            \/\/ Raison : UFC top:658 < DROP top:720 avec mt:-55px \u2192 d\u00e9bordement 62px au-dessus du droppable\r\n            var _miniMt = (window !== window.top) ? '70px' : '-55px';\r\n            $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                'height':     _docH + 'px',\r\n                'min-height': _docH + 'px',\r\n                'max-height': _docH + 'px',\r\n                'margin-top': _miniMt,\r\n                'margin-bottom': '0px',\r\n                'overflow': 'hidden'\r\n            });\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px', 'height': (_docH - 6) + 'px'});\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '65px', 'margin-bottom': '40px'});\r\n            \/\/ \u2705 v2.4.9 : Doc-preview \u2014 overflow:visible pour que le liser\u00e9 du haut ne soit pas clipp\u00e9\r\n            if (_isDocPreviewMob) {\r\n                $dropZone.closest('.OrdiMobileConteneurClass').css('overflow', 'visible');\r\n                $dropZone.closest('.OrdiMobileConteneurClass').parent().css('overflow', 'visible');\r\n            }\r\n            \/\/ \u2705 v2.4.9 : Ele0A popup mobile \u2014 neutraliser translateY(-32px) pos\u00e9 par Entete sur EnvoiUlterieurTexte\r\n            var _isEle0AMob = ($dropZone.closest('.droppable').attr('id') === 'Ele0A')\r\n                || ($dropZone.closest('#PopUpMessageAchattest').length > 0);\r\n            if (_isEle0AMob) {\r\n                var $_euTxt = $dropZone.closest('.droppable').find('.EnvoiUlterieurTexte');\r\n                if ($_euTxt[0]) {\r\n                    $_euTxt[0].style.setProperty('transform', 'translateY(0px)', 'important');\r\n                    $_euTxt[0].style.setProperty('margin-top', '0px', 'important');\r\n                }\r\n                \/\/ \u2705 v2.6 : Pb 2 \u2014 centrage vertical doc-preview dans #PopUpMessageAchattest\r\n                var _popupAchat = document.getElementById('PopUpMessageAchattest');\r\n                if (_popupAchat) {\r\n                    _popupAchat.style.setProperty('align-items', 'center', 'important');\r\n                }\r\n                \/\/ Annuler margin-top:23px du media query sur .doc-preview-container\r\n                setTimeout(function() {\r\n                    var _dp = $dropZone.find('.doc-preview-container')[0];\r\n                    if (_dp) { _dp.style.setProperty('margin-top', '0px', 'important'); }\r\n                }, 50);\r\n\r\n                \/\/ \u2705 v2.6 : Pb 8\/9 \u2014 miniature Ele0A : appliquer les m\u00eames fixes que _isEle0AMobAdj\r\n                \/\/ (croix position, masquage \u00e9l\u00e9ments parasites, bouton R\u00e9server)\r\n                var $_dropMini = $dropZone.closest('.droppable');\r\n                if ($_dropMini.attr('id') === 'Ele0A') {\r\n                    $_dropMini.css({'position': 'relative'});\r\n                    \/\/ Masquer EnvoiUlterieur (inutile si fichier d\u00e9pos\u00e9) et R\u00e9server statique\r\n                    $_dropMini.find('.elementor-field-group-EnvoiUlterieur').each(function() {\r\n                        this.style.setProperty('display', 'none', 'important');\r\n                    });\r\n                    $_dropMini.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').each(function() {\r\n                        this.style.setProperty('display', 'none', 'important');\r\n                    });\r\n                    \/\/ Croix en position absolue dans l'UFC\r\n                    var $_ufcMini = $dropZone.closest('.HTMLUploadfileConteneur');\r\n                    $_ufcMini.css('position', 'relative');\r\n                    var _croixMini = $_dropMini.find('.CroixResetAnnonceContainer')[0];\r\n                    if (_croixMini) {\r\n                        if ($_ufcMini[0]) { $_ufcMini[0].appendChild(_croixMini); }\r\n                        _croixMini.style.setProperty('position', 'absolute', 'important');\r\n                        _croixMini.style.setProperty('top', '-31px', 'important');\r\n                        _croixMini.style.setProperty('right', '-36px', 'important');\r\n                        _croixMini.style.setProperty('margin', '0px', 'important');\r\n                        _croixMini.style.setProperty('z-index', '9999', 'important');\r\n                    }\r\n                    \/\/ Bouton R\u00e9server dynamique \u2014 plac\u00e9 apr\u00e8s UFC\r\n                    var $_reserverMini = $_dropMini.find('.reserver-dynamic-container').first();\r\n                    if (!$_reserverMini.length) { $_reserverMini = $_dropMini.next('.reserver-dynamic-container'); }\r\n                    if ($_reserverMini.length) {\r\n                        $_ufcMini.after($_reserverMini);\r\n                        $_reserverMini.css({\r\n                            'margin-top': '-27px',\r\n                            'margin-bottom': '4px',\r\n                            'position': 'relative',\r\n                            'z-index': '10',\r\n                            'text-align': 'center',\r\n                            'transform': 'scale(0.8)',\r\n                            'transform-origin': 'top center'\r\n                        });\r\n                    }\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.4.12 : AchatEspaceCall=Yes \u2192 override margins (m\u00eame correction que pour upload direct)\r\n            if (StateManager.get('AchatEspaceCall') === 'Yes') {\r\n                $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n                var _docHAchatMini = _isDocPreviewMob ? 150 : 112;\r\n                $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                    'margin-top': '70px',\r\n                    'margin-bottom': '35px',\r\n                    'min-height': _docHAchatMini + 'px',\r\n                    'height': _isDocPreviewMob ? _docHAchatMini + 'px' : '',\r\n                    'max-height': _isDocPreviewMob ? _docHAchatMini + 'px' : ''\r\n                });\r\n                console.log('\ud83d\udcf1 [miniature] AchatEspaceCall=Yes OVERRIDE: mt=70px | docPreview:', _isDocPreviewMob);\r\n            }\r\n            return;\r\n        }\r\n        \/\/ \u2705 v2.4.10 : Miniature DESKTOP \u2192 layout normal appliqu\u00e9 ci-dessous\r\n        if (_isMiniatureAdj) {\r\n            \/\/ Pas de overflow:hidden \u2014 le liser\u00e9 box-shadow inset doit rester visible\r\n        }\r\n\r\n        \/\/ \u2705 v2.6 : Ele0A popup mobile \u2014 pas de margins ajust\u00e9s (le droppable a ses propres dimensions)\r\n        \/\/ Le fond blanc doit \u00eatre appliqu\u00e9 au droppable lui-m\u00eame, pas \u00e0 HTMLUploadfileConteneur d\u00e9cal\u00e9\r\n        var _isEle0AMobAdj = ($dropZone.closest('.droppable').attr('id') === 'Ele0A');\r\n        console.log('[Ele0A] _isEle0AMobAdj:', _isEle0AMobAdj, '| ViaPopupProcessAchat:', (sessionStorage.getItem('_ViaPopupOpen') === 'Yes'));\r\n        if (_isEle0AMobAdj) {\r\n            var $_ele0ADrop = $dropZone.closest('.droppable');\r\n            \/\/ v2.9 : si ViaPopupProcessAchat present, repositionner les elements hors-lisere\r\n            var _isVPP = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n            console.log('[Ele0A] _isVPP:', _isVPP);\r\n            if (_isVPP) {\r\n                \/\/ DeplaceAnnonceSubContainer : forcer margin-top positif pour rentrer dans le lisere\r\n                var $_das = $_ele0ADrop.find('.DeplaceAnnonceSubContainer');\r\n                if ($_das.length) {\r\n                    $_das[0].style.setProperty('margin-top', '5px', 'important');\r\n                    console.log('[Ele0A] DeplaceAnnonceSubContainer margin-top: 5px');\r\n                }\r\n                \/\/ CroixResetAnnonceContainer : forcer position interne\r\n                var $_crc = $_ele0ADrop.find('.CroixResetAnnonceContainer');\r\n                if ($_crc.length) {\r\n                    $_crc[0].style.setProperty('position', 'absolute', 'important');\r\n                    $_crc[0].style.setProperty('top',   '5px', 'important');\r\n                    $_crc[0].style.setProperty('right', '5px', 'important');\r\n                    $_crc[0].style.setProperty('margin', '0px', 'important');\r\n                    console.log('[Ele0A] CroixResetAnnonceContainer repositionne: top:5px right:5px');\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.6 : Ele0A \u2014 neutraliser la hauteur des boutons format cach\u00e9s\r\n            \/\/ Ne pas utiliser position:absolute\/overflow:hidden \u2192 cache le bouton R\u00e9server\r\n            $_ele0ADrop.css({'position': 'relative'});\r\n            var $_omcEle0A = $dropZone.closest('.OrdiMobileConteneurClass');\r\n            var $_ufcEle0APre = $dropZone.closest('.HTMLUploadfileConteneur');\r\n            \/\/ \u2705 OrdiMobileConteneurClass : flex parent Elementor \u00e9tire le UFC \u2192 forcer flex-start\r\n            $_omcEle0A.css({\r\n                'padding': '0',\r\n                'min-height': '0',\r\n                'height': 'auto',\r\n                'margin-top': '0px',\r\n                'margin-bottom': '0px',\r\n                'align-items': 'flex-start',\r\n                'justify-content': 'flex-start'\r\n            });\r\n            \/\/ \u2705 UFC : align-self:flex-start \u2192 prend sa hauteur naturelle (pas \u00e9tir\u00e9e par l'OMC)\r\n            $_ufcEle0APre.css({\r\n                'margin': '0',\r\n                'min-height': '0',\r\n                'height': 'auto',\r\n                'width': '100%',\r\n                'align-self': 'flex-start',\r\n                'display': 'flex',\r\n                'align-items': 'center',\r\n                'justify-content': 'center'\r\n            });\r\n            $dropZone.css({\r\n                'margin-top': '0px',\r\n                'margin-bottom': '0px',\r\n                'top': '0px',\r\n                'display': 'flex',\r\n                'align-items': 'center',\r\n                'justify-content': 'center',\r\n                'width': '100%'\r\n            });\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '0px', 'margin-bottom': '0px'});\r\n            \/\/ \u2705 Pb 2 : Centrage vertical du doc-preview \u2014 mesur\u00e9 apr\u00e8s rendu\r\n            \/\/ Le flexbox seul ne suffit pas si le conteneur n'a pas de hauteur propre\r\n            setTimeout(function() {\r\n                var _ele0AEl = $_ele0ADrop[0];\r\n                var _docPrevEl = $dropZone.find('.doc-preview-container')[0];\r\n                if (_ele0AEl) {\r\n                    if (_docPrevEl) {\r\n                        var _ele0AH = _ele0AEl.getBoundingClientRect().height;\r\n                        var _docPrevH = _docPrevEl.getBoundingClientRect().height;\r\n                        var _mt = Math.max(0, Math.round((_ele0AH - _docPrevH) \/ 2) - 8);\r\n                        _docPrevEl.style.setProperty('margin-top', _mt + 'px', 'important');\r\n                        console.log('\ud83d\udcf1 [Ele0A pb2] centrage vertical: ele0AH=' + Math.round(_ele0AH) + ' docH=' + Math.round(_docPrevH) + ' mt=' + _mt + 'px');\r\n                    }\r\n                }\r\n            }, 100);\r\n            \/\/ \u2705 Pb 3 : Croix d\u00e9plac\u00e9e DANS HTMLUploadfileConteneur (position:relative) \u2192 top-right de l'image\r\n            var $_ufcEle0A = $_ufcEle0APre;\r\n            $_ufcEle0A.css('position', 'relative');\r\n            var _croixEle0A = $_ele0ADrop.find('.CroixResetAnnonceContainer')[0];\r\n            if (_croixEle0A) {\r\n                $_ufcEle0A[0].appendChild(_croixEle0A);\r\n                _croixEle0A.style.setProperty('position', 'absolute', 'important');\r\n                var _isViaPopupCroix = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n                if (_isViaPopupCroix) {\r\n                    \/\/ Popup pays site : croix a l'interieur de l'UFC\r\n                    _croixEle0A.style.setProperty('top',   '4px',  'important');\r\n                    _croixEle0A.style.setProperty('right', '4px',  'important');\r\n                } else {\r\n                    _croixEle0A.style.setProperty('top',   '-31px', 'important');\r\n                    _croixEle0A.style.setProperty('right', '-36px', 'important');\r\n                }\r\n                _croixEle0A.style.setProperty('margin', '0px', 'important');\r\n                _croixEle0A.style.setProperty('z-index', '9999', 'important');\r\n            }\r\n            \/\/ \u2705 v2.6 : Masquer les \u00e9l\u00e9ments au-dessus de l'image dans Ele0A\r\n            \/\/ EnvoiUlterieur : inutile quand un fichier est d\u00e9pos\u00e9\r\n            \/\/ ReserverContainer statique : remplac\u00e9 par le bouton dynamique\r\n            $_ele0ADrop.find('.elementor-field-group-EnvoiUlterieur').each(function() {\r\n                this.style.setProperty('display', 'none', 'important');\r\n            });\r\n            $_ele0ADrop.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').each(function() {\r\n                this.style.setProperty('display', 'none', 'important');\r\n            });\r\n\r\n            \/\/ \u2705 Pb 4 : Bouton R\u00e9server dynamique \u2014 plac\u00e9 apr\u00e8s UFC\r\n            var $_reserverEle0A = $_ele0ADrop.find('.reserver-dynamic-container').first();\r\n            if (!$_reserverEle0A.length) { $_reserverEle0A = $_ele0ADrop.next('.reserver-dynamic-container'); }\r\n            if ($_reserverEle0A.length) {\r\n                $_ufcEle0A.after($_reserverEle0A);\r\n                $_reserverEle0A.css({\r\n                    'margin-top': '-27px',\r\n                    'margin-bottom': '4px',\r\n                    'position': 'relative',\r\n                    'z-index': '10',\r\n                    'text-align': 'center',\r\n                    'transform': 'scale(0.8)',\r\n                    'transform-origin': 'top center'\r\n                });\r\n            }\r\n            console.log('\ud83d\udcf1 [Ele0A popup] marges neutralis\u00e9es + centrage vertical + croix\/r\u00e9server positionn\u00e9s');\r\n            return;\r\n        }\r\n\r\n        \/\/ v2.9 : site pays direct (window.top) = marges neutres; iframe r\u00e9gie = marges compens\u00e9es\r\n        var _isInIframeNonMini = (window !== window.top);\r\n        var _isOnSitePays = (window === window.top);\r\n        $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n            'min-height': '0',\r\n            'margin-top': _isInIframeNonMini ? '70px' : (_isOnSitePays ? '-50px' : '-85px'),\r\n            'margin-bottom': _isMiniatureAdj ? '0px' : (_isInIframeNonMini ? '35px' : (_isOnSitePays ? '10px' : '20px'))\r\n        });\r\n        console.log('\ud83d\udcf1 HTMLUploadfileConteneur margins set: mt=' + (_isOnSitePays ? '10px' : (_isInIframeNonMini ? '70px' : '-85px')) + ', el found:', $dropZone.closest('.HTMLUploadfileConteneur').length);\r\n        \r\n        if (_isInIframeNonMini || _isOnSitePays) {\r\n            \/\/ iframe ou site pays direct : pas de marges n\u00e9gatives sur dropZone\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n        } else {\r\n            $dropZone.css({\r\n                'margin-top': '-50px',\r\n                'margin-bottom': '-140px'\r\n            });\r\n        }\r\n        \r\n        $dropZone.closest('.OrdiMobileConteneurClass').css({\r\n            'margin-top': _isOnSitePays ? '0px' : '65px',\r\n            'margin-bottom': _isOnSitePays ? '50px' : '-10px'\r\n        });\r\n        console.log('\ud83d\udcf1 OrdiMobile margins set: mt=' + (_isOnSitePays ? '0px' : '65px') + ' mb=' + (_isOnSitePays ? '50px' : '-10px'));\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            \/\/ \u2705 v2.4.12 : Reset $dropZone margins (les -50px\/-140px tirent le contenu vers le haut dans l'iframe)\r\n            \/\/ Pour doc-preview (Communiqu\u00e9\/Interview), la hauteur est plus grande \u2192 ajuster min-height\r\n            var _isDocPreviewAchat = $dropZone.find('.doc-preview-container').length > 0;\r\n            var _docHAchat = _isDocPreviewAchat ? 150 : 112;\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n            $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                \/\/ \u2705 v2.4.13 : Miniature desktop \u2192 +40px suppl\u00e9mentaires pour compenser le d\u00e9calage\r\n                'margin-top': _isMiniatureAdj ? '110px' : '70px',\r\n                'margin-bottom': '35px',\r\n                'min-height': _docHAchat + 'px',\r\n                'height': _isDocPreviewAchat ? _docHAchat + 'px' : '',\r\n                'max-height': _isDocPreviewAchat ? _docHAchat + 'px' : ''\r\n            });\r\n            console.log('\ud83d\udcf1 AchatEspaceCall=Yes OVERRIDE: mt=' + (_isMiniatureAdj ? '110px' : '70px') + ' | docPreview:', _isDocPreviewAchat, '| h:', _docHAchat);\r\n        }\r\n        \r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '70px'});\r\n        }\r\n        \r\n        \/\/ v2.9 : site pays mobile \u2014 d\u00e9caler reserver-dynamic-container de 30px vers le bas\r\n        if (_isOnSitePays) {\r\n            setTimeout(function() {\r\n                var $reserverCont = $dropZone.closest('.droppable').find('.reserver-dynamic-container')\r\n                    .add($dropZone.closest('.droppable').next('.reserver-dynamic-container')).first();\r\n                if ($reserverCont.length) {\r\n                    var _curMt = parseInt($reserverCont.css('margin-top')) || 0;\r\n                    $reserverCont.css('margin-top', (_curMt + 30) + 'px');\r\n                    console.log('\ud83d\udcf1 [site pays] reserver-dynamic-container +30px \u2192 mt:', _curMt + 30);\r\n                }\r\n            }, 80);\r\n        }\r\n        \/\/ v2.9 : supprim\u00e9 - margin-top\/bottom -100px causait chevauchement texte sur sites pays\r\n    },\r\n    \r\n    adjustDesktopLayout($dropZone) {\r\n        const emplacement = StateManager.get('Commande_Emplacement_Page_Web');\r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \/\/ \u2705 v2.4.13 : Lire et resetter _dropFromMiniature ici aussi (desktop)\r\n        var _fromMiniatureDesktop = window._dropFromMiniature || ($droppable[0] ? $droppable[0].getAttribute('data-kit-drop') === 'true' : false);\r\n        \/\/ Memoriser pour _buildAdOverlay qui tourne apres\r\n        window._lastDropWasKit = _fromMiniatureDesktop;\r\n        window._dropFromMiniature = false;\r\n        \/\/ \u2705 Nettoyer data-kit-drop apr\u00e8s lecture\r\n        if ($droppable[0]) { $droppable[0].removeAttribute('data-kit-drop'); }\r\n        \r\n        \/\/ \u2705 Masquer les textes enfants (position, label, r\u00e9f\u00e9rence) sans toucher au conteneur\r\n        $droppable\r\n            .find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur > div > .elementor-widget-text-editor')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 +15px sur .droppable \u2014 sauf si drop depuis miniature (dimensions naturelles)\r\n        if (!_fromMiniatureDesktop) {\r\n            const currentMt = parseInt($droppable.css('margin-top')) || 0;\r\n            $droppable.css('margin-top', (currentMt + 15) + 'px');\r\n        } else {\r\n            \/\/ \u2705 v2.4.13 : Drop depuis miniature Kit \u2014 d\u00e9caler de 30px vers le bas\r\n            const currentMt = parseInt($droppable.css('margin-top')) || 0;\r\n            \/\/ \u2705 Homepage : -20px (annonce trop basse sur r\u00e9gie) \u2014 autres pages : +70px\r\n            var _mtOffsetMini = (window.location.pathname === '\/' || window.location.pathname === '\/en\/' || window.location.pathname === '\/fr\/') ? -20 : 70;\r\n            $droppable.css('margin-top', (currentMt + _mtOffsetMini) + 'px');\r\n        }\r\n        \r\n        \/\/ v2.9 : detection popup parent (ViaPopupProcessAchat existe sur site pays)\r\n        var _isViaPopupParent = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n        $dropZone.closest('.OrdiMobileConteneurClass')\r\n            .find('.RefEspacePublicitaire')\r\n            .html(emplacement)\r\n            .attr('id', 'RefEspacePublicitaire')\r\n            .each(function() {\r\n                var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                if (_isViaPopupParent) {\r\n                    \/\/ repositionner a l'interieur du lisere vert\r\n                    this.style.setProperty('display', 'block', 'important');\r\n                    this.style.setProperty('margin-top', '4px', 'important');\r\n                    this.style.setProperty('margin-right', '4px', 'important');\r\n                    if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                } else {\r\n                    if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                    this.style.setProperty('display', 'block', 'important');\r\n                }\r\n            });\r\n\r\n        \/\/ v4.9cp : centrer .RefEspacePublicitaire entre le titre et la croix de fermeture.\r\n        \/\/          Mesure dynamique du bord droit du titre et du bord gauche de la croix,\r\n        \/\/          puis pose position:absolute + left au milieu.\r\n        function _centerRefEspPub() {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').find('.RefEspacePublicitaire').each(function() {\r\n                var _refEl = this;\r\n                var _container = jQuery(_refEl).closest('.droppable')[0] || jQuery(_refEl).closest('.DeplaceAnnonce')[0];\r\n                if (!_container) return;\r\n                \/\/ Bord droit du titre (ChoisirEspacePublicitaireDisponibiliteConteneur ou AdUploadedTitle)\r\n                var _titleEl = _container.querySelector('.ChoisirEspacePublicitaireDisponibiliteConteneur')\r\n                            || _container.querySelector('.AdUploadedTitle');\r\n                \/\/ Bord gauche de la croix\r\n                var _crossEl = _container.querySelector('#CroixResetAnnonce')\r\n                            || _container.querySelector('.CroixResetAnnonceContainer');\r\n                if (!_titleEl || !_crossEl) return;\r\n                var _cRect = _container.getBoundingClientRect();\r\n                var _tRect = _titleEl.getBoundingClientRect();\r\n                var _xRect = _crossEl.getBoundingClientRect();\r\n                if (!_cRect.width || !_tRect.width || !_xRect.width) return;\r\n                \/\/ Milieu entre bord droit titre et bord gauche croix\r\n                var _mid = (_tRect.right + _xRect.left) \/ 2;\r\n                \/\/ Position relative au container (le parent position:relative)\r\n                var _leftPx = Math.round(_mid - _cRect.left);\r\n                _refEl.style.setProperty('position', 'absolute', 'important');\r\n                _refEl.style.setProperty('left', _leftPx + 'px', 'important');\r\n                _refEl.style.setProperty('transform', 'translateX(-50%)', 'important');\r\n                _refEl.style.setProperty('top', (_tRect.top - _cRect.top) + 'px', 'important');\r\n                _refEl.style.removeProperty('margin-right');\r\n                _refEl.style.removeProperty('margin-left');\r\n            });\r\n        }\r\n        \/\/ Apr\u00e8s le rendu (titre + croix visibles)\r\n        setTimeout(_centerRefEspPub, 50);\r\n        setTimeout(_centerRefEspPub, 300);\r\n        setTimeout(_centerRefEspPub, 800);\r\n\r\n        \/\/ \u2705 v2.4.5 : Renseigner et afficher .PositionEspacePublicitaire\r\n        (function() {\r\n            var _rankPos = StateManager.get('Rank_Emplacement_Page_Web') || $droppable.attr('id') || '';\r\n            var _posLib = PreviewRenderer._getPositionLibelle(_rankPos);\r\n            if (_posLib) {\r\n                $dropZone.closest('.OrdiMobileConteneurClass')\r\n                    .find('.PositionEspacePublicitaireDeplacer')\r\n                    .text(_posLib)\r\n                    .each(function() {\r\n                        var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                        if (_isViaPopupParent) {\r\n                            this.style.setProperty('display', 'block', 'important');\r\n                            this.style.setProperty('margin-top', '4px', 'important');\r\n                            if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                        } else {\r\n                            if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                            this.style.setProperty('display', 'block', 'important');\r\n                        }\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        if (window.location.pathname === \"\/\" || window.location.pathname === \"\/en\/\") {\r\n            \/\/ \u2705 v2.4.10 : top selon contexte \u2014 espaces hors .remainingContent d\u00e9calent de 60px, ceux dedans de 20px\r\n            \/\/ \u2705 v2.7.3 : Ele0A est positionn\u00e9 par positionEle0AOverEle1A \u2192 ne pas \u00e9craser son top\r\n            var _isEle0AHP = $droppable.attr('id') === 'Ele0A';\r\n            if (!_isEle0AHP) {\r\n                var _inRemainingContent = $dropZone.closest('.remainingContent').length > 0;\r\n                var _topOffsetHP = _inRemainingContent ? '20px' : '100px';\r\n                $dropZone.closest('.ToBeHidden')\r\n                    .css('top', _topOffsetHP)\r\n                    .css('min-height', '300px');\r\n            }\r\n            \/\/ NB : pas de overflow:hidden ici \u2014 le liser\u00e9 vert (box-shadow inset sur HTMLUploadfileConteneur)\r\n            \/\/ doit rester visible sur les 4 c\u00f4t\u00e9s\r\n        }\r\n        \r\n        \/\/ \u2705 Toutes pages desktop : rendre le parent du droppable non-clippant\r\n        \/\/ Le bouton R\u00e9server est ins\u00e9r\u00e9 apr\u00e8s .droppable avec margin-top n\u00e9gatif \u2014\r\n        \/\/ overflow:hidden sur le parent Elementor le masque sinon.\r\n        $droppable.parent().css('overflow', 'visible');\r\n        \r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '0px'});\r\n            \/\/ \u2705 Page \/annonce : masquer les \u00e9l\u00e9ments de d\u00e9placement inutiles\r\n            $droppable.find('.DeplaceAnnonceText').hide();\r\n            $droppable.find('.PositionEspacePublicitaireDeplacer').hide();\r\n            $droppable.find('.RefEspacePublicitaire').hide();\r\n            $droppable.find('.reserver-dynamic-option').hide();\r\n            $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        }\r\n        \r\n        \/\/ \u2705 v2.7.3 : Ele0A desktop \u2014 ajustements position des labels et de la croix apr\u00e8s d\u00e9p\u00f4t\r\n        if ($droppable.attr('id') === 'Ele0A') {\r\n            \/\/ .PositionEspacePublicitaireDeplacer : +35px bas, +30px droite\r\n            $droppable.find('.PositionEspacePublicitaireDeplacer').each(function() {\r\n                this.style.setProperty('margin-top', '35px', 'important');\r\n                this.style.setProperty('margin-left', '30px', 'important');\r\n            });\r\n            \/\/ .RefEspacePublicitaire : +35px bas, 25px vers la gauche\r\n            $droppable.find('.RefEspacePublicitaire').each(function() {\r\n                this.style.setProperty('margin-top', '35px', 'important');\r\n                this.style.setProperty('margin-right', '25px', 'important');\r\n            });\r\n            \/\/ #CroixResetAnnonce : -30px vers le haut, 22px vers la gauche (+3px droite)\r\n            $droppable.find('#CroixResetAnnonce').each(function() {\r\n                this.style.setProperty('margin-top', '-30px', 'important');\r\n                this.style.setProperty('margin-right', '22px', 'important');\r\n            });\r\n        }\r\n    }\r\n};\r\n\r\n\/**\r\n * \u2705 Module de gestion des formats et du titre .SelectionFormatTitre\r\n *\/\r\nconst FormatUIManager = {\r\n    \r\n    \/**\r\n     * V\u00e9rifie si un format est s\u00e9lectionn\u00e9 dans un espace publicitaire\r\n     * V\u00e9rifie le background-color OU le sessionStorage (format venant de l'accord\u00e9on)\r\n     *\/\r\n    hasSelectedFormat($element) {\r\n        const $droppable = $element.closest('.droppable');\r\n        \/\/ v4.9ds : si une annonce est d\u00e9j\u00e0 d\u00e9pos\u00e9e dans cet espace (data-via-ad-loaded='true'),\r\n        \/\/   le format est implicite (d\u00e9duit par UploadManager.handleFileUpload depuis l'extension\r\n        \/\/   du fichier). Retourner true sans d\u00e9pendre du DOM .EspPubFormatContainer ni du\r\n        \/\/   sessionStorage Upload_File_Name (qui peut \u00eatre nettoy\u00e9 apr\u00e8s une r\u00e9servation\r\n        \/\/   pr\u00e9c\u00e9dente sur un autre espace pub).\r\n        \/\/   Bug constat\u00e9 : 2 annonces d\u00e9pos\u00e9es (Ele0A + Ele1A), Ele0A r\u00e9serv\u00e9e \u2192 popup ouvert,\r\n        \/\/   tentative R\u00e9server Ele1A bloqu\u00e9e car hasFormat=false (sessionStorage vid\u00e9 pour Ele0A,\r\n        \/\/   et le fond blanc EspPubFormatContainer pas pos\u00e9 sur Ele1A en mode popup).\r\n        if ($droppable.attr('data-via-ad-loaded') === 'true') return true;\r\n        \/\/ \u2705 v4.9ds Fix 4 : data-via-current-format prioritaire sur la d\u00e9tection visuelle\r\n        \/\/   \u2192 pos\u00e9 par applyFormatState ; survit aux races qui effacent le background blanc\r\n        \/\/   (un script tiers met temporairement transparent \u2192 l'observer voit \"format perdu\"\r\n        \/\/   \u2192 SelectionFormatTitre s'affiche \u2192 UI cass\u00e9e). L'attribut sur le .droppable\r\n        \/\/   reste pos\u00e9 tant qu'applyFormatState n'a pas \u00e9t\u00e9 appel\u00e9 avec un format diff\u00e9rent.\r\n        if ($droppable.attr('data-via-current-format')) return true;\r\n        const isEle0A = $droppable.attr('id') === 'Ele0A';\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            if (isEle0A) {\r\n                \/\/ \u2705 v2.3.4 : Ele0A popup \u2014 vrai seulement si un format sous-jacent (hors FormatIdPopUp) est s\u00e9lectionn\u00e9\r\n                var _hasDomFmt = $droppable.find('.EspPubFormatContainer').not('.FormatIdPopUp').toArray().some(el => {\r\n                    return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n                });\r\n                if (_hasDomFmt) return true;\r\n                \/\/ \u2705 v2.4.5 : Fallback sessionStorage \u2014 DOM pas encore mis \u00e0 jour (timing MutationObserver)\r\n                return !!sessionStorage.getItem('FormatSelect');\r\n            }\r\n            \/\/ v4.9ds : Autres espaces (Ele1A+) en mode popup \u2014 v\u00e9rifier le DOM r\u00e9el.\r\n            \/\/   AVANT : retournait true syst\u00e9matiquement \u2192 SelectionFormatTitre toujours masqu\u00e9,\r\n            \/\/   m\u00eame apr\u00e8s suppression+restauration d'une annonce dans cet espace.\r\n            \/\/   APR\u00c8S : on cherche un .EspPubFormatContainer (hors Cr\u00e9ation\/PopUp) avec fond\r\n            \/\/   blanc dans CET espace. Si aucun \u2192 return false \u2192 SelectionFormatTitre s'affiche.\r\n            var _hasDomFmtStd = $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').toArray().some(el => {\r\n                return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n            });\r\n            return _hasDomFmtStd;\r\n        }\r\n        \/\/ v4.9ds : exclure FormatIdCreation et FormatIdPopUp \u2014 ce ne sont pas de vrais formats\r\n        const $realFmts = $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp');\r\n        const hasWhiteBg = $realFmts.toArray().some(el => {\r\n            return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n        });\r\n        if (hasWhiteBg) return true;\r\n        \/\/ v4.9ds : si le DOM contient des containers de format dans CE droppable et qu'AUCUN n'est s\u00e9lectionn\u00e9,\r\n        \/\/   alors retourner false EXPLICITEMENT \u2014 ne pas se rabattre sur des flags sessionStorage globaux\r\n        \/\/   qui peuvent rester pos\u00e9s apr\u00e8s cr\u00e9ation dans un autre espace.\r\n        \/\/   Bug constat\u00e9 : apr\u00e8s fermeture du popup cr\u00e9ation (lanc\u00e9 depuis Ele0A), Formatchoisi\/FormatSelect\r\n        \/\/   restaient pos\u00e9s \u2192 SelectionFormatTitre disparaissait sur Ele1A+ alors qu'aucun format n'y \u00e9tait choisi.\r\n        if ($realFmts.length > 0) return false;\r\n        \/\/ Fallbacks sessionStorage UNIQUEMENT si le DOM n'a pas encore de containers (timing initial \/ pr\u00e9-rendu)\r\n        if (sessionStorage.getItem('Formatchoisi') === 'Yes') return true;\r\n        if (sessionStorage.getItem('FormatSelect')) return true;\r\n        return !!sessionStorage.getItem('_FormatSelectApplied');\r\n    },\r\n    \r\n    \/**\r\n     * Met \u00e0 jour la couleur du titre .SelectionFormatTitre\r\n     * Blanc si un format est s\u00e9lectionn\u00e9, rouge #FB5E2A sinon\r\n     *\/\r\n    updateTitleColor($droppable) {\r\n        \/\/ \u2705 v2.4.5 : Ele0A \u2014 ne jamais afficher SelectionFormatTitre\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes' ? $droppable.attr('id') === 'Ele0A' : false) {\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n            return;\r\n        }\r\n\r\n        if (this.hasSelectedFormat($droppable)) {\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n            console.log('\u2705 SelectionFormatTitreBlanc affich\u00e9 (format s\u00e9lectionn\u00e9)');\r\n        } else {\r\n            $droppable.find('.SelectionFormatTitre').show();\r\n            $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n            console.log('\u26a0\ufe0f SelectionFormatTitre affich\u00e9 (aucun format s\u00e9lectionn\u00e9)');\r\n        }\r\n\r\n        \/\/ \u2705 R\u00e8gle Cr\u00e9ation\/Vid\u00e9o : d\u00e9tecter si Vid\u00e9o est actif et d\u00e9sactiver\/r\u00e9activer Cr\u00e9ation\r\n        var _videoActive = false;\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            var _bg = jQuery(this).css('background-color') || '';\r\n            if (_bg === 'rgb(255, 255, 255)') { _videoActive = true; return false; }\r\n            var _fEl = this.querySelector('.EspPubFormat');\r\n            if (_fEl) { var _col = _fEl.style.color || ''; if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') { _videoActive = true; return false; } }\r\n        });\r\n        if (_videoActive) {\r\n            $droppable.find('.FormatIdCreation').each(function() {\r\n                jQuery(this).data('creationActive', false);\r\n                jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n                if ($lbl.text() !== 'D\u00e9sactiv\u00e9') { $lbl.text('D\u00e9sactiv\u00e9'); }\r\n            });\r\n            console.log('\ud83c\udfa8 updateTitleColor : Vid\u00e9o actif \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e');\r\n        } else {\r\n            $droppable.find('.FormatIdCreation').each(function() {\r\n                var _cur = jQuery(this).css('opacity');\r\n                if (_cur === '0.4') {\r\n                    jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                    var $lbl = jQuery(this).find('.EspPubFormat');\r\n                    var _orig = $lbl.attr('data-original-text');\r\n                    if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                }\r\n            });\r\n        }\r\n    },\r\n    \r\n    \/**\r\n     * Flash visuel sur le titre pour indiquer qu'un format est requis\r\n     *\/\r\n    flashTitle($element) {\r\n        const $droppable = $element.closest('.droppable');\r\n        const $titre = $droppable.find('.SelectionFormatTitre');\r\n        if (!$titre.length) return;\r\n        \r\n        \/\/ v2.6 : En mode popup (Ele0A), SelectionFormatTitre est display:none \u2192 le montrer temporairement\r\n        var _wasHidden = $titre.css('display') === 'none';\r\n        \/\/ v2.6 : Elementor impose display:none !important \u2192 setProperty requis\r\n        if (_wasHidden) { $titre.each(function() { this.style.setProperty('display','block','important'); }); }\r\n        \r\n        \/\/ Flash rouge rapide 3 fois\r\n        let count = 0;\r\n        const originalColor = $titre.css('color');\r\n        \r\n        const flash = setInterval(() => {\r\n            $titre.css('color', count % 2 === 0 ? '#FF0000' : '#FB5E2A');\r\n            count++;\r\n            if (count >= 6) {\r\n                clearInterval(flash);\r\n                $titre.css('color', '#FB5E2A');\r\n                if (_wasHidden) { setTimeout(function() { $titre.each(function() { this.style.setProperty('display','none','important'); }); }, 2000); }\r\n            }\r\n        }, 250);\r\n        \r\n        console.log('\u26a0\ufe0f Flash titre format - action bloqu\u00e9e (aucun format s\u00e9lectionn\u00e9)');\r\n    },\r\n    \r\n    \/**\r\n     * Met \u00e0 jour l'\u00e9tat de la checkbox \"R\u00e9server cet espace publicitaire\"\r\n     * La checkbox est activable si : format s\u00e9lectionn\u00e9 ET (fichier d\u00e9pos\u00e9 OU envoi diff\u00e9r\u00e9)\r\n     *\/\r\n    updateReserverCheckboxState($droppable) {\r\n        \/\/ \u2705 Chercher le label : dynamique ou Elementor statique\r\n        let $label = null;\r\n        let $container = null;\r\n        \r\n        \/\/ 1. Bouton dynamique dans le droppable\r\n        $container = $droppable.find('.reserver-dynamic-container');\r\n        if (!$container.length) {\r\n            $container = $droppable.next('.reserver-dynamic-container');\r\n        }\r\n        if (!$container.length) {\r\n            \/\/ Mobile : checkbox attach\u00e9e au body avec data-droppable-id\r\n            $container = $('body > .reserver-dynamic-container[data-droppable-id=\"' + $droppable.attr('id') + '\"]');\r\n        }\r\n        if (!$container.length) {\r\n            $container = $('body > .reserver-dynamic-container');\r\n        }\r\n        \r\n        if ($container.length) {\r\n            $label = $container.find('.reserver-dynamic-label');\r\n            $container.find('.reserver-dynamic-option, .elementor-field-option').css('opacity', '1');\r\n        }\r\n        \r\n        \/\/ 2. Bouton Elementor statique - chercher dans le droppable puis globalement\r\n        let $reserverStatique = $droppable.find('.elementor-field-group-ReserverEspacePublicitaire label');\r\n        if (!$reserverStatique.length) {\r\n            $reserverStatique = $droppable.find('label[for^=\"form-field-ReserverEspacePublicitaire\"]');\r\n        }\r\n        if (!$reserverStatique.length) {\r\n            \/\/ Fallback global : le formulaire peut \u00eatre en dehors du droppable\r\n            $reserverStatique = $('.elementor-field-group-ReserverEspacePublicitaire .elementor-field-option label');\r\n        }\r\n        \r\n        const hasFormat = this.hasSelectedFormat($droppable);\r\n        \/\/ v2.9 : data-via-ad-loaded est specifique au droppable, plus fiable que FileReceived global\r\n        const hasFile = StateManager.get('FileReceived') === 'Yes'\r\n            || $droppable.attr('data-via-ad-loaded') === 'true';\r\n        const hasEnvoiDiffere = $droppable.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        \r\n        const canReserve = hasFormat ? (hasFile || hasEnvoiDiffere) : false;\r\n        \r\n        \/\/ \u2705 Si la checkbox R\u00e9server est coch\u00e9e \u2192 label vert \"Espace publicitaire r\u00e9serv\u00e9\"\r\n        const isChecked = $droppable.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked') ||\r\n                          ($container.length ? $container.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked') : false);\r\n        \/\/ \u2705 v2.4.7 : Signal de restauration r\u00e9serv\u00e9e \u2014 checkbox pas encore coch\u00e9e (setTimeout 150ms)\r\n        \/\/             mais on sait d\u00e9j\u00e0 que l'espace est r\u00e9serv\u00e9 \u2192 traiter comme coch\u00e9\r\n        const _isReservedRestoration = sessionStorage.getItem('_isReservedRestoration') === 'Yes';\r\n        const isCheckedOrReservedRestore = isChecked || _isReservedRestoration;\r\n        \r\n        if ($label ? $label.length : false) {\r\n            if (isCheckedOrReservedRestore) {\r\n                $label.text('Espace publicitaire r\u00e9serv\u00e9').css('color', 'rgb(62, 170, 19)');\r\n            } else {\r\n                $label.text('R\u00e9server cet espace publicitaire').css('color', canReserve ? '#FB5E2A' : '');\r\n            }\r\n        }\r\n        if ($reserverStatique.length) {\r\n            if (isCheckedOrReservedRestore) {\r\n                $reserverStatique.attr('style', 'color: rgb(62, 170, 19) !important;');\r\n            } else if (canReserve) {\r\n                $reserverStatique.attr('style', 'color: #FB5E2A !important;');\r\n            } else {\r\n                $reserverStatique.removeAttr('style');\r\n            }\r\n        }\r\n        \r\n        console.log('\ud83d\udd04 updateReserverCheckboxState:', { hasFormat, hasFile, hasEnvoiDiffere, canReserve, dynamicLabel: !!($label ? $label.length : false), staticLabel: $reserverStatique.length });\r\n    },\r\n    \r\n    \/**\r\n     * Initialise les listeners pour le changement de format et la checkbox R\u00e9server\r\n     *\/\r\n    init() {\r\n        \/\/ \u2705 \u00c9couter les clics sur les conteneurs de format et leurs parents\r\n        jQuery(document).on('click', '.EspPubFormatContainer', (e) => {\r\n            const $droppable = jQuery(e.target).closest('.droppable');\r\n            if (!$droppable.length) return;\r\n\r\n            \/\/ v4.9ds : stocker le format choisi sur le .droppable (gate ind\u00e9pendant)\r\n            \/\/          Exclusion : Creation et PopUp ne sont pas de vrais formats \u2192 ne pas\r\n            \/\/          d\u00e9verrouiller les zones (glisser-d\u00e9poser, envoi diff\u00e9r\u00e9, r\u00e9server)\r\n            const _$btn = jQuery(e.target).closest('.EspPubFormatContainer');\r\n            const _classes = _$btn.attr('class') || '';\r\n            const _m = _classes.match(\/FormatId(\\w+)\/);\r\n            if (_m && _m[1] && _m[1] !== 'Creation' && _m[1] !== 'PopUp') {\r\n                $droppable.attr('data-via-current-format', _m[1]);\r\n                \/\/ Notifier le parent pour mettre \u00e0 jour le format-gate (CSS top-level)\r\n                try {\r\n                    var _topW = window.top || window;\r\n                    if (_topW && typeof _topW._viaSetDroppableFormat === 'function') {\r\n                        _topW._viaSetDroppableFormat($droppable.attr('id'), _m[1]);\r\n                    }\r\n                } catch(_e) {}\r\n            }\r\n\r\n            \/\/ \u2705 Masquer les messages d'erreur format d\u00e8s la s\u00e9lection d'un format\r\n            jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n            jQuery('#fmt-creation-info').remove();\r\n            \r\n            \/\/ v4.9ds : appeler _viaUpdateFormatGate pour TOUS les clics (incluant Cr\u00e9ation\/PopUp)\r\n            \/\/   afin de mettre \u00e0 jour data-via-title-state=\"chosen\" \u2192 CSS SelectionFormatTitre\r\n            \/\/   (transparent + blanc) prend effet aussi pour Cr\u00e9ation.\r\n            [50, 200, 500].forEach(function(delay) {\r\n                setTimeout(function() {\r\n                    try {\r\n                        var _topW2 = window.top || window;\r\n                        if (_topW2 ? typeof _topW2._viaUpdateFormatGate === 'function' : false) {\r\n                            _topW2._viaUpdateFormatGate();\r\n                        }\r\n                    } catch (_e) {}\r\n                }, delay);\r\n            });\r\n\r\n            \/\/ Multiples d\u00e9lais pour couvrir les traitements asynchrones\r\n            [50, 200, 500].forEach(delay => {\r\n                setTimeout(() => {\r\n                    this.updateTitleColor($droppable);\r\n                    this.updateReserverCheckboxState($droppable);\r\n                }, delay);\r\n            });\r\n\r\n            \/\/ \u2705 v2.2.0 : Si un format (non Cr\u00e9ation) est cliqu\u00e9, notifier le parent pour mettre \u00e0 jour le Kit\r\n            const $btn = jQuery(e.target).closest('.EspPubFormatContainer');\r\n            if ($btn.length ? (!$btn.hasClass('FormatIdCreation') ? !$btn.hasClass('FormatIdPopUp') : false) : false) {\r\n                \/\/ \u2705 v2.3.4 : FormatIdPopUp g\u00e9r\u00e9 par clickFormatFromIframe (Entete.txt) \u2014 ne pas doubler\r\n                var classes = $btn.attr('class') || '';\r\n                var match = classes.match(\/FormatId(\\w+)\/);\r\n                if (match ? match[1] : false) {\r\n                    var _rankEP = $droppable.attr('id') || '';\r\n                    setTimeout(function() {\r\n                        MessageManager.sendToParent('formatChangedInIframe', { formatSelect: match[1], rank: _rankEP });\r\n                        console.log('\ud83d\udd04 formatChangedInIframe envoy\u00e9:', match[1], '| rank:', _rankEP);\r\n                    }, 100);\r\n                }\r\n\r\n                \/\/ \u2705 Si Cr\u00e9ation est d\u00e9j\u00e0 actif ET format valide (non Vid\u00e9o) \u2192 ouvrir le popup\r\n                var $creationBtn = $droppable.find('.FormatIdCreation');\r\n                var _creationActive = $creationBtn.data('creationActive') === true;\r\n                var _isVideoBtn = $btn.hasClass('FormatIdVideo');\r\n                if (_creationActive ? !_isVideoBtn : false) {\r\n                    var _rankKit2 = _rankEP;\r\n                    var _cSite2 = sessionStorage.getItem('codeSite') || '';\r\n                    var _cPage2 = sessionStorage.getItem('codePage') || '';\r\n                    var _sfx2 = _rankKit2.replace('Ele', '');\r\n                    var _empl2 = (_cSite2 ? (_cPage2 ? _sfx2 : false) : false) ? (_cSite2 + _cPage2 + 'L' + _sfx2) : (sessionStorage.getItem('Commande_Emplacement_Page_Web') || '');\r\n                    var _fmt2 = sessionStorage.getItem('FormatSelect') || '';\r\n                    var _fmtT2 = sessionStorage.getItem('Commande_Format_Transmis') || '';\r\n                    \/\/ R\u00e9activer Vid\u00e9o + restaurer texte si d\u00e9sactiv\u00e9e\r\n                    $droppable.find('.FormatIdVideo').each(function() {\r\n                        jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                        var $lbl = jQuery(this).find('.EspPubFormat');\r\n                        var _orig = $lbl.attr('data-original-text');\r\n                        if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                    });\r\n                    setTimeout(function() {\r\n                        MessageManager.sendToParent('openAdCreatorFromIframe', { formatSelect: _fmt2, formatTransmis: _fmtT2, rankId: _rankKit2, emplacement: _empl2 });\r\n                        console.log('\ud83c\udfa8 Cr\u00e9ation d\u00e9j\u00e0 actif + format s\u00e9lectionn\u00e9 \u2192 ouverture popup apr\u00e8s 2s | rank:', _rankKit2);\r\n                    }, 2000);\r\n                }\r\n            }\r\n        });\r\n        \r\n        \/\/ \u2705 MutationObserver sur les conteneurs de format pour d\u00e9tecter les changements de style\r\n        setTimeout(() => {\r\n            this.observeFormatChanges();\r\n            \r\n            \/\/ Initialiser les couleurs des titres au chargement\r\n            jQuery('.droppable').each((i, el) => {\r\n                this.updateTitleColor(jQuery(el));\r\n            });\r\n            \r\n            \/\/ \u2705 v2.3.4 : Popup \u2192 restaurer FormatIdPopUp + format sous-jacent (sans \u00e9craser applyFormatState)\r\n            if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n                jQuery('.FormatIdPopUp').css({'background-color': '#ffffff'});\r\n                jQuery('.FormatIdPopUp').find('.EspPubFormat').css({'color': '#37D900'});\r\n                \/\/ Restaurer aussi le format sous-jacent dans Ele0A\r\n                var _fmtSS = sessionStorage.getItem('FormatSelect') || '';\r\n                if (_fmtSS) {\r\n                    var _fmtSSNorm = _fmtSS.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                    jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdCreation').each(function() {\r\n                        var _cls = this.className.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                        if (_cls.includes(_fmtSSNorm)) {\r\n                            jQuery(this).css({'background-color': '#ffffff'});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        }\r\n                    });\r\n                }\r\n                jQuery('.droppable').each((i, el) => {\r\n                    this.updateTitleColor(jQuery(el));\r\n                });\r\n            }\r\n        }, 500);\r\n        \r\n        \/\/ \u2705 v1.16.0 : Re-v\u00e9rifier apr\u00e8s un d\u00e9lai plus long (format venant de l'accord\u00e9on via postMessage)\r\n        [1500, 3000].forEach(delay => {\r\n            setTimeout(() => {\r\n                \/\/ \u2705 v2.3.4 : Popup \u2192 restaurer FormatIdPopUp + format sous-jacent\r\n                if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n                    jQuery('.FormatIdPopUp').css({'background-color': '#ffffff'});\r\n                    jQuery('.FormatIdPopUp').find('.EspPubFormat').css({'color': '#37D900'});\r\n                    var _fmtSSR = sessionStorage.getItem('FormatSelect') || '';\r\n                    if (_fmtSSR) {\r\n                        var _fmtSSRNorm = _fmtSSR.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                        jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdCreation').each(function() {\r\n                            var _cls = this.className.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                            if (_cls.includes(_fmtSSRNorm)) {\r\n                                jQuery(this).css({'background-color': '#ffffff'});\r\n                                jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                            }\r\n                        });\r\n                    }\r\n                    jQuery('.droppable').each((i, el) => {\r\n                        this.updateTitleColor(jQuery(el));\r\n                    });\r\n                    return;\r\n                }\r\n                if (sessionStorage.getItem('Formatchoisi') === 'Yes') {\r\n                    jQuery('.droppable').each((i, el) => {\r\n                        this.updateTitleColor(jQuery(el));\r\n                    });\r\n                }\r\n            }, delay);\r\n        });\r\n    },\r\n    \r\n    \/**\r\n     * Attache un MutationObserver sur chaque .EspPubFormatContainer\r\n     * pour d\u00e9tecter tout changement de style (background-color) \r\n     * quel que soit le script qui le d\u00e9clenche\r\n     *\/\r\n    observeFormatChanges() {\r\n        \/\/ \u2705 Guard global anti-boucle \u2014 un seul verrou pour tous les observers\r\n        window._formatObserverLocked = false;\r\n        \r\n        jQuery('.EspPubFormatContainer').each((i, el) => {\r\n            const observer = new MutationObserver(() => {\r\n                if (window._formatObserverLocked) return;\r\n                window._formatObserverLocked = true;\r\n                clearTimeout(window._formatObserverTimer);\r\n                window._formatObserverTimer = setTimeout(() => {\r\n                    const $droppable = jQuery(el).closest('.droppable');\r\n                    this.updateTitleColor($droppable);\r\n                    this.updateReserverCheckboxState($droppable);\r\n\r\n                    \/\/ \u2705 R\u00e8gle Cr\u00e9ation\/Vid\u00e9o : si Vid\u00e9o est actif \u2192 d\u00e9sactiver Cr\u00e9ation\r\n                    var _videoActive = false;\r\n                    $droppable.find('.FormatIdVideo').each(function() {\r\n                        var _bg = this.style.backgroundColor || '';\r\n                        var _isWhite = _bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white';\r\n                        if (!_isWhite) {\r\n                            var _fEl = this.querySelector('.EspPubFormat');\r\n                            if (_fEl) { var _col = _fEl.style.color || ''; _isWhite = _col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900'; }\r\n                        }\r\n                        if (_isWhite) { _videoActive = true; return false; }\r\n                    });\r\n                    if (_videoActive) {\r\n                        $droppable.find('.FormatIdCreation').each(function() {\r\n                            jQuery(this).data('creationActive', false);\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                            jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n                            var $lbl = jQuery(this).find('.EspPubFormat');\r\n                            if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n                            $lbl.text('D\u00e9sactiv\u00e9');\r\n                        });\r\n                    } else {\r\n                        \/\/ R\u00e9activer Cr\u00e9ation si Vid\u00e9o n'est plus actif\r\n                        $droppable.find('.FormatIdCreation').each(function() {\r\n                            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                            var $lbl = jQuery(this).find('.EspPubFormat');\r\n                            var _orig = $lbl.attr('data-original-text');\r\n                            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                        });\r\n                    }\r\n\r\n                    window._formatObserverLocked = false;\r\n                }, 150);\r\n            });\r\n            observer.observe(el, { \r\n                attributes: true, \r\n                attributeFilter: ['style', 'class'] \r\n            });\r\n        });\r\n        console.log('\u2705 MutationObserver attach\u00e9 aux conteneurs de format');\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de communication avec la page parente\r\n *\/\r\nconst MessageManager = {\r\n    sendToParent(type, data = {}) {\r\n        try {\r\n            window.parent.postMessage({\r\n                type: type,\r\n                iframeId: CONFIG.iframeId,\r\n                data: data\r\n            }, '*');\r\n        } catch (error) {\r\n            console.error('Error sending message to parent:', error);\r\n            window.parent.postMessage({\r\n                type: 'error',\r\n                error: error.message,\r\n                iframeId: CONFIG.iframeId\r\n            }, '*');\r\n        }\r\n    },\r\n    \r\n    sendDataToParent(data) {\r\n        this.sendToParent('dataFromIframeEspacePub', data);\r\n    },\r\n    \r\n    sendDelAdToParent(data) {\r\n        this.sendToParent('dataDelAd', data);\r\n    },\r\n    \r\n    prepareUploadData() {\r\n        const keys = [\r\n            'AddNewRefInVosCampagnes',\r\n            'FirstUploadFileorMoved',\r\n            'LoadedPageUrl',\r\n            'codePage',\r\n            'Rank_Emplacement_Page_Web',\r\n            'Commande_Emplacement_Page_Web',\r\n            'dragstart_Commande_Emplacement_Page_Web',\r\n            'dragstart_Rank_Emplacement_Page_Web',  \r\n            'FullPathAdFile',\r\n            'Upload_File_Name',\r\n            'FileReceived',\r\n            'PageWebDisplayed',\r\n            'Commande_Format_Transmis',\r\n            'EspPubLienAnnonce',\r\n            'EnvoiUlterieur',\r\n            'Formatchoisi'\r\n        ];\r\n        \r\n        const data = StateManager.getMultiple(keys);\r\n        \r\n        \/\/ v4.9ds : r\u00e9cup\u00e9ration FullPathAdFile par rank depuis via_fullpath localStorage.\r\n        \/\/   StateManager.FullPathAdFile est GLOBAL (dernier upload). Dans le sc\u00e9nario\r\n        \/\/   \"drop Ele0A + drop Ele1A + R\u00e9server Ele0A + R\u00e9server Ele1A\", au clic R\u00e9server\r\n        \/\/   Ele1A, StateManager.FullPathAdFile peut contenir la mauvaise valeur (Ele0A\r\n        \/\/   s\u00e9lectionn\u00e9 apr\u00e8s) ou \u00eatre vide. via_fullpath localStorage stocke par rank\r\n        \/\/   {Ele0A: URL_Ele0A, Ele1A: URL_Ele1A} \u2014 source de v\u00e9rit\u00e9 plus fiable.\r\n        try {\r\n            const _rankCur = data.Rank_Emplacement_Page_Web || '';\r\n            if (_rankCur) {\r\n                const _fpMap = JSON.parse(localStorage.getItem('via_fullpath') || '{}');\r\n                const _fpForRank = _fpMap[_rankCur];\r\n                if (_fpForRank) {\r\n                    if (data.FullPathAdFile !== _fpForRank) {\r\n                        console.log('\ud83d\udd27 [prepareUploadData] FullPathAdFile \u00e9cras\u00e9 depuis via_fullpath | rank:', _rankCur,\r\n                            '| ancien:', data.FullPathAdFile || '(vide)', '| nouveau:', _fpForRank);\r\n                    }\r\n                    data.FullPathAdFile = _fpForRank;\r\n                }\r\n            }\r\n        } catch(_eFP) { console.warn('[prepareUploadData] lecture via_fullpath \u00e9chou\u00e9e:', _eFP); }\r\n        \r\n        \/\/ v4.9ds : flag HasAdLoadedDom bas\u00e9 sur l'attribut data-via-ad-loaded du droppable\r\n        \/\/   correspondant \u00e0 l'emplacement courant. Permet \u00e0 Panier_manager.txt de\r\n        \/\/   distinguer \"annonce visuellement d\u00e9pos\u00e9e mais pas upload\u00e9e serveur\" (drop\r\n        \/\/   miniature \u2192 image dans DOM, FileReceived='No') du cas \"rien d\u00e9pos\u00e9\".\r\n        try {\r\n            const _emplCur = data.Commande_Emplacement_Page_Web || '';\r\n            if (_emplCur) {\r\n                const _$dropCur = jQuery('.droppable').filter(function() {\r\n                    return jQuery(this).attr('data-via-empl') === _emplCur\r\n                        || (jQuery(this).attr('id') === 'Ele0A' ? _emplCur.indexOf('L0A') >= 0 : false);\r\n                }).first();\r\n                \/\/ Fallback : matcher par rank si data-via-empl absent\r\n                if (!_$dropCur.length ? data.Rank_Emplacement_Page_Web : false) {\r\n                    const _$byRank = jQuery('.droppable[data-via-rank=\"' + data.Rank_Emplacement_Page_Web + '\"]').first();\r\n                    if (_$byRank.length) {\r\n                        data.HasAdLoadedDom = _$byRank.attr('data-via-ad-loaded') === 'true' ? 'Yes' : 'No';\r\n                    }\r\n                } else if (_$dropCur.length) {\r\n                    data.HasAdLoadedDom = _$dropCur.attr('data-via-ad-loaded') === 'true' ? 'Yes' : 'No';\r\n                }\r\n                \/\/ Dernier fallback : tous les droppables avec data-via-ad-loaded=true \u2192 si un seul, c'est lui\r\n                if (data.HasAdLoadedDom === undefined) {\r\n                    const _$any = jQuery('.droppable[data-via-ad-loaded=\"true\"]');\r\n                    data.HasAdLoadedDom = _$any.length > 0 ? 'Yes' : 'No';\r\n                }\r\n            }\r\n        } catch(_eAd) { data.HasAdLoadedDom = 'No'; }\r\n        \r\n        \/\/ \u2705 v2.3.4 : Popup (Ele0A) \u2014 lire le format sous-jacent depuis le DOM\r\n        \/\/ StateManager contient 'PopUp' (virtuel), mais le vrai format est visuellement s\u00e9lectionn\u00e9 dans #Ele0A\r\n        if (data.Rank_Emplacement_Page_Web === 'Ele0A' || sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            var _ele0AFormatDOM = '';\r\n            jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdPopUp').each(function() {\r\n                if (jQuery(this).css('background-color') === 'rgb(255, 255, 255)') {\r\n                    var _cls = this.className || '';\r\n                    var _match = _cls.match(\/FormatId(\\S+)\/);\r\n                    if (_match) { _ele0AFormatDOM = _match[1]; return false; }\r\n                }\r\n            });\r\n            if (_ele0AFormatDOM) {\r\n                data.Commande_Format_Transmis = _ele0AFormatDOM;\r\n            }\r\n        }\r\n        \r\n        console.log('\ud83d\udce4 prepareUploadData:', {\r\n            FirstUploadFileorMoved: data.FirstUploadFileorMoved,\r\n            dragstart_Commande_Emplacement_Page_Web: data.dragstart_Commande_Emplacement_Page_Web,\r\n            Commande_Emplacement_Page_Web: data.Commande_Emplacement_Page_Web\r\n        });\r\n    \r\n        return data;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de rendu des aper\u00e7us de fichiers\r\n *\/\r\nconst PreviewRenderer = {\r\n    _getPositionLibelle(rankId) {\r\n        if (!rankId) return '';\r\n        var match = rankId.match(\/Ele(\\d+)[A-Z]\/);\r\n        if (!match) return '';\r\n        var rang = parseInt(match[1]);\r\n        if (rang === 0) return 'Pop-up';\r\n        if (rang === 1 || rang === 2) return 'Haut de page';\r\n        return 'Corps de page';\r\n    },\r\n    renderVideo(objectUrl, fileName, $dropZone) {\r\n        const videoElement = jQuery('<video controls autoplay muted>').attr({\r\n            'src': objectUrl,\r\n            'width': 'auto',\r\n            'height': 'auto',\r\n            'id': fileName\r\n        }).css({\r\n            'max-width': '100%',\r\n            'max-height': '100%'\r\n        });\r\n        \r\n        if (UIManager.isDesktop()) {\r\n            $dropZone.empty().append(videoElement.clone());\r\n            jQuery('#ApercuMobile').css({\r\n                'display': 'flex',\r\n                'justify-content': 'center',\r\n                'align-items': 'center'\r\n            }).append(videoElement);\r\n        } else {\r\n            const img = document.createElement('img');\r\n            img.src = objectUrl;\r\n            img.style.width = 'auto';\r\n            img.style.height = 'auto';\r\n            img.style.maxWidth = 'calc(100% - 4px)';  \/\/ \u2705 v2.4.5 : 2px retrait inset box-shadow\r\n            img.style.maxHeight = '115px';\r\n            \r\n            const container = $dropZone[0];\r\n            while (container.firstChild) {\r\n                container.removeChild(container.firstChild);\r\n            }\r\n            container.appendChild(img);\r\n        }\r\n        \r\n        StateManager.setMultiple({\r\n            'Commande_Format': 'Vid\u00e9o',\r\n            'Commande_Format_Transmis': 'Vid\u00e9o',\r\n            'videoSrc': objectUrl,\r\n            'FormatAnnonceSelection': 'Yes',\r\n            'PositionAnnonceSelection': 'Yes'\r\n        });\r\n        \r\n        jQuery('#FormatDataStep3').html('Vid\u00e9o').css({'color': '#56BE50'});\r\n        jQuery('#TarifDataStep3').css({'color': '#96894D'});\r\n    },\r\n    \r\n    renderImage(objectUrl, $dropZone) {\r\n        \/\/ v4.9ds : utiliser la hauteur du dropZone lui-m\u00eame (pos\u00e9e par _applyDzMinH)\r\n        \/\/   au lieu de la hauteur du HTMLUploadfileConteneur parent.\r\n        \/\/   Le UFC inclut header (~24px) + footer (~46px) en plus du dropZone, donc utiliser\r\n        \/\/   sa hauteur produit une image plus haute que le dropZone \u2192 crop par overflow:hidden\r\n        \/\/   du wrapper. La hauteur du dropZone est la cible exacte de la zone visible.\r\n        let maxHeight;\r\n        if (UIManager.isMobile()) {\r\n            maxHeight = 105;\r\n        } else {\r\n            \/\/ Lire la hauteur effective du dropZone (d\u00e9j\u00e0 pos\u00e9e par _applyDzMinH au moment du d\u00e9p\u00f4t)\r\n            var _dzH = $dropZone.height() || 0;\r\n            if (_dzH > 50) {\r\n                maxHeight = _dzH;\r\n            } else {\r\n                \/\/ Fallback : lire UFC mais retirer estimation header+footer (~70px)\r\n                var _ufcH = $dropZone.closest('.HTMLUploadfileConteneur').height() || 0;\r\n                maxHeight = Math.max(0, _ufcH - 70);\r\n                if (maxHeight < 50) { maxHeight = 200; }  \/\/ dernier recours\r\n            }\r\n        }\r\n    \r\n        $dropZone.css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center',\r\n            'width': '100%',\r\n            'height': maxHeight + 'px',\r\n            'overflow': 'hidden',\r\n            'position': UIManager.isMobile() ? 'relative' : '',\r\n            'top': UIManager.isMobile() ? '45px' : '',\r\n            'padding': UIManager.isMobile() ? '0 2px' : ''  \/\/ \u2705 v2.4.5 : 2px retrait inset box-shadow mobile\r\n        });\r\n    \r\n        \/\/ \u2705 v1.19.2 : Image charg\u00e9e en arri\u00e8re-plan, remplace le message d'attente au onload\r\n        \/\/ \u2705 v2.4.5 : max-width calc(100%-4px) sur mobile pour ne pas recouvrir le box-shadow inset 2px\r\n        \/\/ v4.9ds : width:100% + height:100% + object-fit:contain \u2014 l'image remplit la box du\r\n        \/\/   dropZone (comme Ele0A) tout en restant enti\u00e8re. Letterbox automatique si ratio diff.\r\n        var _mxw = UIManager.isMobile() ? 'calc(100% - 4px)' : '100%';\r\n        var img = new Image();\r\n        img.style.cssText = 'max-width:' + _mxw + '; max-height:' + maxHeight + 'px; width:100%; height:100%; object-fit:contain;';\r\n        img.onload = function() {\r\n            $dropZone.empty().append(img);\r\n        };\r\n        img.onerror = function() {\r\n            $dropZone.html('<img decoding=\"async\" src=\"' + objectUrl + '\" style=\"max-width:' + _mxw + '; max-height:' + maxHeight + 'px; width:100%; height:100%; object-fit:contain;\">');\r\n        };\r\n        img.src = objectUrl;\r\n    \r\n        StateManager.setMultiple({\r\n            'Commande_Format_Transmis': 'Image',\r\n            'FormatAnnonceSelection': 'Yes',\r\n            'PositionAnnonceSelection': 'Yes'\r\n        });\r\n    \r\n        console.log('Image ajout\u00e9e avec succ\u00e8s');\r\n    },\r\n    \r\n    async renderWord(fileObj, $dropZone) {\r\n        return new Promise((resolve, reject) => {\r\n            const reader = new FileReader();\r\n            \r\n            reader.onload = async (e) => {\r\n                try {\r\n                    const arrayBuffer = e.target.result;\r\n                    const result = await mammoth.convertToHtml({arrayBuffer: arrayBuffer});\r\n                    \r\n                    const htmlContent = result.value;\r\n                    const tempContainer = document.createElement('div');\r\n                    tempContainer.innerHTML = htmlContent;\r\n                    \r\n                    const textContent = tempContainer.textContent || \"\";\r\n                    const normalizedText = this.normalizeText(textContent);\r\n                    const firstImage = tempContainer.querySelector('img');\r\n                    \r\n                    const titleText = this.findDocumentTitle(normalizedText);\r\n                    \r\n                    if (firstImage) {\r\n                        this.renderDocumentPreview(\r\n                            $dropZone,\r\n                            firstImage.src,\r\n                            titleText,\r\n                            textContent,\r\n                            htmlContent\r\n                        );\r\n                    } else {\r\n                        \/\/ \u2705 v1.19.0 : Accepter les documents sans image\r\n                        this.renderDocumentPreview(\r\n                            $dropZone,\r\n                            null,\r\n                            titleText,\r\n                            textContent,\r\n                            htmlContent\r\n                        );\r\n                        console.log('\u2139\ufe0f Document Word sans image \u2014 aper\u00e7u texte seul');\r\n                    }\r\n                    resolve();\r\n                } catch (error) {\r\n                    $dropZone.html(`<p style=\"color: #FB5E2A; font-weight: 600; font-family: Roboto, Arial, sans-serif; font-size: 12px; text-align: center; padding: 15px;\">Erreur lors de la lecture du document<\/p>`);\r\n                    reject(error);\r\n                }\r\n            };\r\n            \r\n            reader.readAsArrayBuffer(fileObj);\r\n        });\r\n        \r\n        this.finalizeRedactionnelUpload();\r\n    },\r\n    \r\n    renderDocumentPreview($dropZone, imageSrc, title, text, htmlContent = null) {\r\n        \/\/ \u2705 v1.19.0 : Deux layouts \u2014 avec image ou texte seul\r\n        let previewHtml;\r\n\r\n        if (imageSrc) {\r\n            \/\/ Layout avec miniature image\r\n            previewHtml = `\r\n                <div class=\"doc-preview-container\">\r\n                    <div class=\"doc-preview-thumbnail\">\r\n                        <img decoding=\"async\" src=\"${imageSrc}\" alt=\"Document image\">\r\n                    <\/div>\r\n                    <div class=\"doc-preview-info\">\r\n                        <div class=\"doc-preview-title\" id=\"AdTitle\">${title}<\/div>\r\n                        <div class=\"doc-preview-description\" id=\"AdText\">${this.getTextPreview(text, 13)}<\/div>\r\n                        <div class=\"doc-preview-readmore\" id=\"AdLirelasuite\">Ouvrir et visualiser<\/div>\r\n                        <div class=\"doc-preview-FullPathAdFile\" id=\"AdFullPathAdFile\">${StateManager.get(\"FullPathAdFile\")}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n        } else {\r\n            \/\/ Layout texte seul (sans image)\r\n            previewHtml = `\r\n                <div class=\"doc-preview-container doc-preview-noimage\">\r\n                    <div class=\"doc-preview-icon\">\r\n                        <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#213864\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n                            <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"\/><polyline points=\"14 2 14 8 20 8\"\/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"\/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"\/><polyline points=\"10 9 9 9 8 9\"\/>\r\n                        <\/svg>\r\n                    <\/div>\r\n                    <div class=\"doc-preview-info doc-preview-info-full\">\r\n                        <div class=\"doc-preview-title\" id=\"AdTitle\">${title}<\/div>\r\n                        <div class=\"doc-preview-description\" id=\"AdText\">${this.getTextPreview(text, 25)}<\/div>\r\n                        <div class=\"doc-preview-readmore\" id=\"AdLirelasuite\">Ouvrir et visualiser<\/div>\r\n                        <div class=\"doc-preview-FullPathAdFile\" id=\"AdFullPathAdFile\">${StateManager.get(\"FullPathAdFile\")}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n        }\r\n        \r\n        $dropZone.html(previewHtml + this.getPreviewStyles());\r\n        \r\n        if (htmlContent) {\r\n            this.attachWordPreviewHandler($dropZone, title, htmlContent);\r\n        }\r\n    },\r\n    \r\n    attachWordPreviewHandler($dropZone, titleText, htmlContent) {\r\n        var $droppable = $dropZone.closest('.droppable');\r\n\r\n        \/\/ \u2705 Adapter le libell\u00e9 selon le type de document\r\n        var isInterview = (titleText || '').toLowerCase().indexOf('interview') !== -1;\r\n        var formatLabel = isInterview ? 'l\\'interview' : 'le communiqu\u00e9';\r\n        $droppable.find('.doc-preview-readmore').text('Ouvrir et visualiser ' + formatLabel);\r\n\r\n        \/\/ \u2705 v2.0.11 : D\u00e9l\u00e9gation d'\u00e9v\u00e9nement \u2014 plus robuste sur mobile (survit aux manipulations DOM)\r\n        $droppable.off('click.docpreview').on('click.docpreview', '.doc-preview-readmore', (event) => {\r\n            event.preventDefault();\r\n            event.stopPropagation();\r\n            \r\n            if (htmlContent) {\r\n                var popupTitle = isInterview ? 'Interview' : 'Communiqu\u00e9';\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    htmlContent: htmlContent\r\n                });\r\n            } else {\r\n                console.warn('Le document est encore en cours de traitement.');\r\n            }\r\n        });\r\n    },\r\n    \r\n    normalizeText(text) {\r\n        return text\r\n            .toLowerCase()\r\n            .normalize(\"NFD\")\r\n            .replace(\/[\\u0300-\\u036f]\/g, \"\");\r\n    },\r\n    \r\n    findDocumentTitle(normalizedText) {\r\n        const foundKeyword = CONFIG.keywords.find(keyword => \r\n            normalizedText.includes(keyword.normal)\r\n        );\r\n        if (foundKeyword) return foundKeyword.display;\r\n        \r\n        \/\/ \u2705 v1.19.1 : Fallback \u2014 utiliser le format s\u00e9lectionn\u00e9 (FormatSelect)\r\n        var formatSelect = (sessionStorage.getItem('FormatSelect') || '').toLowerCase();\r\n        if (formatSelect.indexOf('interview') !== -1) return 'Interview';\r\n        if (formatSelect.indexOf('communiq') !== -1) return 'Communiqu\u00e9';\r\n        \r\n        return \"Document\";\r\n    },\r\n    \r\n    getTextPreview(text, maxWords) {\r\n        const words = text.split(' ');\r\n        if (words.length > maxWords) {\r\n            return words.slice(0, maxWords).join(' ') + ' ...';\r\n        }\r\n        return text;\r\n    },\r\n    \r\n    getPreviewStyles() {\r\n        return `\r\n            <style>\r\n                @import url('https:\/\/fonts.googleapis.com\/css2?family=Roboto:wght@400;600&display=swap');\r\n                \r\n                .doc-preview-container {\r\n                    \/* v4.9ds : -4px largeur + 2px de chaque c\u00f4t\u00e9 pour laisser voir le liser\u00e9 vert *\/\r\n                    margin: 0px 2px;\r\n                    display: flex;\r\n                    width: calc(100% - 4px);\r\n                    max-height: 140px;\r\n                    overflow: hidden;\r\n                    box-sizing: border-box;\r\n                    background: white;\r\n                    background-color: #f8f8f8;\r\n                }\r\n                .doc-preview-thumbnail {\r\n                    width: 50%; \r\n                    height: 130px;\r\n                    overflow: hidden;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                    margin-top: 0px;\r\n                }\r\n                .doc-preview-thumbnail img {\r\n                    max-width: 75%;\r\n                    max-height: 75%;\r\n                    object-position: center;\r\n                    object-fit: fill;\r\n                }\r\n                .doc-preview-info {\r\n                    width: 50%;\r\n                    height: 130px; \r\n                    flex: 1;\r\n                    padding: 10px;\r\n                    display: flex;\r\n                    flex-direction: column; \r\n                }\r\n                .doc-preview-title {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #213864;\r\n                    font-size: 14px;\r\n                    font-weight: 600;\r\n                    text-align: left;\r\n                    margin-bottom: 5px;\r\n                }\r\n                .doc-preview-description {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #213864;\r\n                    font-size: 11px;\r\n                    font-weight: 400;\r\n                    text-align: left;\r\n                    overflow: hidden;\r\n                    margin-bottom: 5px;\r\n                    flex: 1;\r\n                }\r\n                .doc-preview-readmore {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #958848;\r\n                    font-size: 13px;\r\n                    font-weight: 600;\r\n                    text-align: left;\r\n                    cursor: pointer;\r\n                    padding: 4px 0;\r\n                    -webkit-tap-highlight-color: rgba(149,136,72,0.2);\r\n                }\r\n                @media (max-width: 999px) {\r\n                    .doc-preview-readmore { font-size: 12px; }\r\n                    \/* \u2705 Laisser 3px tout autour pour que le liser\u00e9 vert (box-shadow\/outline sur parent) soit visible *\/\r\n                    .doc-preview-container {\r\n                        width: calc(100% - 16px) !important;\r\n                        max-height: 109px !important;\r\n                        margin: 3px !important;\r\n                        margin-top: 23px !important;\r\n                        box-sizing: border-box !important;\r\n                    }\r\n                }\r\n                .doc-preview-FullPathAdFile {\r\n                    display: none;\r\n                }\r\n                \/* \u2705 v1.19.0 : Layout sans image *\/\r\n                .doc-preview-noimage {\r\n                    flex-direction: row;\r\n                    align-items: flex-start;\r\n                    gap: 10px;\r\n                    padding: 8px 10px;\r\n                }\r\n                .doc-preview-icon {\r\n                    flex-shrink: 0;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                    width: 44px;\r\n                    height: 44px;\r\n                    background: #eef2f7;\r\n                    border-radius: 6px;\r\n                    margin-top: 4px;\r\n                }\r\n                .doc-preview-info-full {\r\n                    width: 100%;\r\n                    height: auto;\r\n                    max-height: 130px;\r\n                }\r\n                .doc-preview-noimage .doc-preview-description {\r\n                    font-size: 11px;\r\n                    line-height: 1.35;\r\n                    max-height: 60px;\r\n                    overflow: hidden;\r\n                }\r\n            <\/style>\r\n        `;\r\n    },\r\n    \r\n    finalizeRedactionnelUpload() {\r\n        \/\/ \u2705 v1.19.5 : D\u00e9terminer le format r\u00e9dactionnel correct\r\n        \/\/ Si le format s\u00e9lectionn\u00e9 est \"Interview\", on garde \"Interview\"\r\n        \/\/ Sinon on met \"Communiqu\u00e9\" par d\u00e9faut (au lieu de \"R\u00e9dactionnel\" qui n'est pas un format tarifaire valide)\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') || '';\r\n        var formatTransmis = 'Communiqu\u00e9'; \/\/ Par d\u00e9faut\r\n        \r\n        if (formatSelect.toLowerCase().indexOf('interview') !== -1) {\r\n            formatTransmis = 'Interview';\r\n        } else if (formatSelect.toLowerCase().indexOf('communiq') !== -1) {\r\n            formatTransmis = 'Communiqu\u00e9';\r\n        }\r\n        \r\n        StateManager.set('Commande_Format_Transmis', formatTransmis);\r\n        console.log('\u2705 Format r\u00e9dactionnel d\u00e9termin\u00e9:', formatTransmis, '(FormatSelect:', formatSelect, ')');\r\n        \r\n        if (StateManager.get(\"TarifDirectSelectionne\") === 'Yes') {\r\n            const formatText = jQuery('#FormatDataStep3').text();\r\n            if (formatText !== 'Vid\u00e9o' ? formatText !== 'Banni\u00e8re' : false) {\r\n                jQuery('#form-field-RedactionnelSelection')\r\n                    .val(formatText)\r\n                    .css({'color': '#56BE50'});\r\n            }\r\n        }\r\n        \r\n        RedactionnelDepose();\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'upload\r\n *\/\r\nconst UploadManager = {\r\n    isRunning: false,\r\n    \r\n    \/\/ \u2705 Reset manuel espace publicitaire\r\n    resetEspaceManuel: function($droppable) {\r\n        console.log('\ud83d\udd27 Reset manuel espace publicitaire');\r\n        \r\n        var $dropZone = $droppable.find('#drop_file_zone_achat');\r\n        var $uploadContainer = $droppable.find('.HTMLUploadfileConteneur');\r\n        var $container = $droppable.find('.OrdiMobileConteneurClass');\r\n        \r\n        \/\/ 1. Vider la zone de drop et restaurer le HTML par d\u00e9faut\r\n        $dropZone.empty().html(\r\n            '<div id=\"drag_upload_file_achat\">' +\r\n                '<p class=\"UploadIci\" style=\"color: #FB5E2A; font-weight: 600;\">Ici glisser \u2013 d\u00e9poser ou<br>t\u00e9l\u00e9charger une annonce<\/p>' +\r\n            '<\/div>'\r\n        );\r\n        \r\n        \/\/ 2. Afficher les \u00e9l\u00e9ments cach\u00e9s lors de l'upload\r\n        $droppable.find('.UploadIci').show();\r\n        $droppable.find('.EnvoiUlterieurTexte').show();\r\n        $droppable.find('.EnvoiUlterieurContainer').show();\r\n        $droppable.find('.EspPubFormatMainContainer').show();\r\n        $droppable.find('.EspPubFormatListe').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireClass').show();\r\n        $droppable.find('.GlisserDeposerConteneur').show();\r\n        $droppable.find('.OUClass').show();\r\n        $droppable.find('.TexteMobileAnnonce').show();\r\n        $droppable.find('.PositionReference').show();\r\n        $droppable.find('.ClassHdpCdp').show();\r\n        $droppable.find('.ClassRefEsp').show();\r\n        \r\n        \/\/ 3. Cacher les \u00e9l\u00e9ments d'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle').hide();\r\n        $droppable.find('#CroixResetAnnonce').hide();\r\n        $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        $droppable.find('.DeplaceAnnonce').hide();\r\n        $droppable.find('.RefEspacePublicitaire').hide();\r\n        $droppable.find('.AdDroppedTextNotDisplayed').hide();\r\n        \r\n        \/\/ 4. Reset des styles\r\n        $uploadContainer.css({\r\n            'border': 'none',\r\n            'background-color': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': ''\r\n        });\r\n        \r\n        $container.css({\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'top': '',\r\n            'height': ''\r\n        });\r\n        \r\n        \/\/ \u2705 Reset des positionnements desktop appliqu\u00e9s par adjustDesktopLayout\r\n        $droppable.closest('.ToBeHidden').css({'top': '', 'min-height': ''});\r\n        $droppable.parent().css('overflow', '');\r\n        \/\/ \u2705 v2.0.11 : Cleanup event namespace docpreview\r\n        $droppable.off('click.docpreview');\r\n        \r\n        \/\/ 5. Reset des formats s\u00e9lectionn\u00e9s\r\n        $droppable.find('.EspPubFormatContainer').css({\r\n            'background-color': '',\r\n            'border': ''\r\n        });\r\n        \r\n        \/\/ 6. Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ 7. Restaurer .PositionEspacePublicitaireContainer et .ReserverContainer\r\n        $droppable.find('.PositionEspacePublicitaireContainer').show();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \r\n        console.log('\u2705 Reset manuel termin\u00e9');\r\n    },\r\n    \r\n    async handleFileUpload(fileObj, $dropZone) {\r\n        console.log('fileObj:', fileObj);\r\n        \r\n        \/\/ \u2705 v2.4.6 : Contr\u00f4les d'extension EN PREMIER \u2014 avant tout affichage\r\n        const extension = FileManager.getExtension(fileObj.name);\r\n        StateManager.set('FileExtension', extension);\r\n        console.log('FileExtension:', extension);\r\n        \r\n        \/\/ \u2705 v2.6 : jpg\/jpeg accept\u00e9s sur desktop et mobile\r\n        \r\n        if (!FileManager.isAllowedExtension(extension)) {\r\n            UIManager.showFormatError($dropZone);\r\n            return;\r\n        }\r\n\r\n        \/\/ \u2705 v4.9o : Guard type de fichier vs format s\u00e9lectionn\u00e9\r\n        var _$droppableGuard = $dropZone.closest('.droppable');\r\n        if (!_fromMiniatureGuard) {\r\n            if (!_$droppableGuard.length) { _$droppableGuard = jQuery(); }\r\n            var _fmtChkLabel = '';\r\n            var _fmtClsMap = {\r\n                'formatidbanniere':   'banni\u00e8re',\r\n                'formatidparrainage': 'parrainage',\r\n                'formatidcommunique': 'communiqu\u00e9',\r\n                'formatidinterview':  'interview',\r\n                'formatidvideo':      'vid\u00e9o'\r\n            };\r\n            _$droppableGuard.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').each(function() {\r\n                var _sel = false;\r\n                var _bgV = this.style.backgroundColor || '';\r\n                if (_bgV === 'rgb(255, 255, 255)') { _sel = true; }\r\n                if (!_sel) { if (_bgV === '#ffffff' || _bgV === 'white') { _sel = true; } }\r\n                if (!_sel) {\r\n                    var _fEl = this.querySelector('.EspPubFormat');\r\n                    if (_fEl) {\r\n                        var _col = _fEl.style.color || '';\r\n                        if (_col === 'rgb(55, 217, 0)') { _sel = true; }\r\n                        if (!_sel) { if (_col.toLowerCase() === '#37d900') { _sel = true; } }\r\n                    }\r\n                }\r\n                if (_sel) {\r\n                    var _clsN = this.className.toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '');\r\n                    var _keys = Object.keys(_fmtClsMap);\r\n                    for (var _ki = 0; _ki < _keys.length; _ki++) {\r\n                        if (_clsN.indexOf(_keys[_ki]) !== -1) { _fmtChkLabel = _fmtClsMap[_keys[_ki]]; return false; }\r\n                    }\r\n                }\r\n            });\r\n            if (_fmtChkLabel) {\r\n                var _extChk  = extension.toLowerCase();\r\n                var _kindChk = CONFIG.allowedExtensions.video.indexOf(_extChk) !== -1 ? 'video'\r\n                             : CONFIG.allowedExtensions.image.indexOf(_extChk) !== -1 ? 'image'\r\n                             : 'document';\r\n                var _imgLbls = ['banni\u00e8re', 'parrainage'];\r\n                var _docLbls = ['communiqu\u00e9', 'interview'];\r\n                var _vidLbls = ['vid\u00e9o'];\r\n                var _compat  = true;\r\n                if (_kindChk === 'image')    { _compat = (_imgLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk === 'document') { _compat = (_docLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk === 'video')    { _compat = (_vidLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk) { if (!_compat) {\r\n                    UIManager.showFormatError($dropZone, 'Merci de d\u00e9poser un format ' + _fmtChkLabel);\r\n                    console.warn('\ud83d\udeab D\u00e9p\u00f4t refus\u00e9 : fichier ' + _kindChk + ' incompatible avec format ' + _fmtChkLabel);\r\n                    return;\r\n                } }\r\n            }\r\n        }\r\n\r\n        \/\/ v4.9ci : Guard format \u2014 refuser le d\u00e9p\u00f4t si aucun format n'a \u00e9t\u00e9 s\u00e9lectionn\u00e9\r\n        \/\/          DANS LE DROPPABLE cible (pas globalement).\r\n        \/\/ Exception : drop depuis la miniature kit (le format a d\u00e9j\u00e0 \u00e9t\u00e9 choisi dans le kit\r\n        \/\/ juste avant le drop).\r\n        \/\/ v4.9co : D\u00e9tection locale via la classe visuelle \u2014 un EspPubFormatContainer est\r\n        \/\/          \"s\u00e9lectionn\u00e9\" si son EspPubFormat a la couleur verte (#37D900) OU son fond est #ffffff.\r\n        \/\/          On exclut FormatIdCreation et FormatIdPopUp qui ont leur propre \u00e9tat.\r\n        var _fromKitDropGuard = _$droppableGuard.length ? (_$droppableGuard[0].getAttribute('data-kit-drop') === 'true') : false;\r\n        var _fromMiniatureGuard = window._dropFromMiniature || _fromKitDropGuard;\r\n        if (!_fromMiniatureGuard ? _$droppableGuard.length : false) {\r\n            var _hasLocalFmt = false;\r\n            _$droppableGuard.find('.EspPubFormatContainer').each(function() {\r\n                if (jQuery(this).hasClass('FormatIdCreation')) return;\r\n                if (jQuery(this).hasClass('FormatIdPopUp')) return;\r\n                \/\/ Crit\u00e8re 1 : fond blanc inline (pos\u00e9 par handler click)\r\n                var _bg = this.style.backgroundColor || '';\r\n                if (_bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white') {\r\n                    _hasLocalFmt = true;\r\n                    return false; \/\/ break\r\n                }\r\n                \/\/ Crit\u00e8re 2 : texte vert sur .EspPubFormat\r\n                var _fmt = this.querySelector('.EspPubFormat');\r\n                if (_fmt) {\r\n                    var _col = _fmt.style.color || '';\r\n                    if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') {\r\n                        _hasLocalFmt = true;\r\n                        return false;\r\n                    }\r\n                }\r\n            });\r\n            if (!_hasLocalFmt) {\r\n                var _rankId = _$droppableGuard.attr('id') || '?';\r\n                console.warn('\ud83d\udeab D\u00e9p\u00f4t refus\u00e9 : aucun format s\u00e9lectionn\u00e9 dans ce droppable | rank=' + _rankId);\r\n                \/\/ R\u00e9activer le message d'erreur format\r\n                FormatUIManager.flashTitle(_$droppableGuard.find('.HTMLUploadfileConteneur, #UploadFileConteneur').first());\r\n                UIManager.showFormatError($dropZone, 'Merci de s\u00e9lectionner un format d\\'annonce avant de d\u00e9poser votre fichier');\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v1.19.5 : Afficher le message d'attente seulement si le format est valide\r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        this.prepareUIForUpload($dropZone);\r\n        \r\n        const formData = new FormData();\r\n        formData.append('file', fileObj);\r\n        formData.append('action', 'upload_annonce_file_v3');\r\n        \r\n        UIManager.showUploadProgress($dropZone);\r\n        \r\n        try {\r\n            const response = await this.sendUploadRequest(formData);\r\n            await this.handleUploadResponse(response, fileObj, $dropZone, extension);\r\n        } catch (error) {\r\n            console.error('Upload error:', error);\r\n        }\r\n    },\r\n    \r\n    prepareUIForUpload($dropZone) {\r\n        if (!UIManager.isMobile()) {\r\n            jQuery('.ChoisirEspacePublicitaireClass').show();\r\n            jQuery('.ChoisirEspacePublicitaire2ndLigne').show();\r\n            jQuery('.GlisserDeposerConteneur').show();\r\n            jQuery('.OUClass').show();\r\n            \/\/ \u2705 Cibler uniquement le droppable courant \u2014 ne pas r\u00e9afficher sur les espaces d\u00e9j\u00e0 upload\u00e9s\r\n            $dropZone.closest('.droppable').find('.UploadIci').show();\r\n        }\r\n        \r\n        if (StateManager.get(\"PopUpChoice\") === 'Yes') {\r\n            \/\/ \u2705 Ne masquer DeplaceAnnonce QUE sur Ele0A \u2014 pas sur Ele1A qui a deja une annonce chargee\r\n            $dropZone.closest('.OrdiMobileConteneurClass').find('.DeplaceAnnonce').each(function() {\r\n                var _d = jQuery(this).closest('.droppable')[0];\r\n                if (_d ? _d.getAttribute('data-via-ad-loaded') === 'true' : false) { return; }\r\n                jQuery(this).hide();\r\n            });\r\n            $('.AnnonceDragIcone').show().find('img').attr('alt', '');\r\n        } else {\r\n            $('.AnnonceDragIcone').hide();\r\n        }\r\n        \r\n        if (StateManager.get(\"Commande_Page\") === 'Home Page') {\r\n            jQuery('#HPTarifConteneur').css({'background-color': '#B5FFB1'});\r\n        }\r\n        \r\n        jQuery('.ChoisirEspacePublicitaireClass').css({'color': '#ffffff'});\r\n        jQuery('.InterfaceTitreDore').not('#PageWebTitreDore')\r\n            .html(\"Merci de choisir les \u00e9l\u00e9ments de votre campagne publicitaire\");\r\n        jQuery('#ProcessCommande').show();\r\n    },\r\n    \r\n    sendUploadRequest(formData) {\r\n        \/\/ \u2705 v2.6 : dataType 'text' pour eviter parsererror si texte parasite apres le JSON\r\n        return jQuery.ajax({\r\n            url: CONFIG.ajaxUrl,\r\n            type: 'POST',\r\n            data: formData,\r\n            cache: false,\r\n            contentType: false,\r\n            processData: false,\r\n            dataType: 'text'\r\n        }).then(function(text) {\r\n            \/\/ Extraire le JSON meme si du texte parasite suit\r\n            var _json = null;\r\n            try {\r\n                var _match = text.match(\/(\\{[\\s\\S]*?\\})(?:[^{]|$)\/);\r\n                _json = JSON.parse(_match ? _match[1] : text);\r\n            } catch(_e) {\r\n                return jQuery.Deferred().reject({ responseText: text }).promise();\r\n            }\r\n            \/\/ Normaliser le format vers {success, data} attendu par handleUploadResponse\r\n            if (_json.status === 'success') {\r\n                var _fp = _json.file_path || '';\r\n                var _baseUrl = CONFIG.ajaxUrl.replace('\/wp-admin\/admin-ajax.php', '\/wp-admin\/');\r\n                return {\r\n                    success: true,\r\n                    data: {\r\n                        file_url:  _baseUrl + _fp,\r\n                        file_name: _fp.replace(\/^.*\\\/\/, ''),\r\n                        file_path: _fp\r\n                    }\r\n                };\r\n            }\r\n            \/\/ Format WordPress standard {success, data} - retourner tel quel\r\n            return _json;\r\n        });\r\n    },\r\n    \r\n    async handleUploadResponse(response, fileObj, $dropZone, extension) {\r\n        console.log('\u2705 R\u00e9ponse re\u00e7ue');\r\n        \r\n        if (response.responseJSON) {\r\n            response = response.responseJSON;\r\n        }\r\n        \r\n        console.log('\u2705 Response pars\u00e9e:', response);\r\n        \r\n        if (!response.success || !response.data) {\r\n            \/\/ \u2705 v2.1.2 : Si c'est une restauration (_isAdRestoration = 'Yes'),\r\n            \/\/ le fichier existe d\u00e9j\u00e0 sur le serveur \u2192 afficher l'image depuis le fileObj local\r\n            if (StateManager.get('_isAdRestoration') === 'Yes' ? fileObj : false) {\r\n                console.log('\ud83d\udd04 Restauration: upload \u00e9chou\u00e9 (fichier existant) \u2192 rendu local');\r\n                \r\n                FileManager.createObjectUrl(fileObj);\r\n                StateManager.set('FileReceived', 'Yes');\r\n                \r\n                \/\/ \u2705 v2.1.2 : Restaurer l'\u00e9tat EnvoiUlterieur depuis le sessionStorage parent\r\n                var _restoredEnvoi = sessionStorage.getItem('_restoredEnvoiUlterieur');\r\n                if (_restoredEnvoi === 'true') {\r\n                    StateManager.set('EnvoiUlterieur', 'true');\r\n                    StateManager.set('FileReceived', 'No');\r\n                    StateManager.set('FullPathAdFile', '');\r\n                    console.log('\ud83d\udce4 Restauration EnvoiUlterieur = true');\r\n                }\r\n                \r\n                var _objUrl = StateManager.get('objectUrl');\r\n                var _fileType = FileManager.getFileType(extension);\r\n                \r\n                StateManager.set('FormatReconnu', 'No');\r\n                \r\n                switch (_fileType) {\r\n                    case 'video':\r\n                        PreviewRenderer.renderVideo(_objUrl, fileObj.name, $dropZone);\r\n                        StateManager.set('FormatReconnu', 'Yes');\r\n                        break;\r\n                    case 'image':\r\n                        PreviewRenderer.renderImage(_objUrl, $dropZone);\r\n                        StateManager.set('FormatReconnu', 'Yes');\r\n                        break;\r\n                    case 'document':\r\n                        \/\/ \u2705 v2.1.2 : Documents Word\/PDF aussi en restauration\r\n                        await this.handleDocumentUpload(extension, fileObj, $dropZone);\r\n                        break;\r\n                }\r\n                \r\n                UIManager.updateAfterSuccessfulUpload($dropZone);\r\n                \r\n                if (StateManager.get('FormatReconnu') === 'Yes') {\r\n                    this.finalizeUpload($dropZone);\r\n                    $('#MsgSelectEspace').hide();\r\n                }\r\n                \r\n                FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n                console.log('\u2705 Restauration visuelle termin\u00e9e (upload bypass)');\r\n                StateManager.set('_isAdRestoration', 'No');\r\n            }\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 SAUVEGARDER les infos de d\u00e9placement AVANT updateStateAfterUpload\r\n        const isMoved = StateManager.get('FirstUploadFileorMoved') === 'Moved';\r\n        const dragstartRankId = StateManager.get('dragstart_Rank_Emplacement_Page_Web');\r\n        const currentRankId = StateManager.get('Rank_Emplacement_Page_Web');\r\n        const shouldResetOldSpace = isMoved ? (dragstartRankId ? dragstartRankId !== currentRankId : false) : false;\r\n        \/\/ \u2705 Bug 10 : self-drop (m\u00eame espace) \u2014 nettoyer pendingAd du rank source\r\n        \/\/   Le drop sur soi-m\u00eame ne passe pas par dataDelAd, donc pendingAd persiste\r\n        \/\/   et provoque la r\u00e9apparition de l'annonce lors d'un d\u00e9placement ult\u00e9rieur\r\n        if (isMoved ? (dragstartRankId ? (dragstartRankId !== 'No' ? dragstartRankId === currentRankId : false) : false) : false) {\r\n            sessionStorage.removeItem('pendingAd_' + dragstartRankId);\r\n            console.log('[Bug10] self-drop d\u00e9tect\u00e9 \u2014 pendingAd_' + dragstartRankId + ' effac\u00e9');\r\n        }\r\n        \r\n        console.log('\ud83d\udd0d D\u00e9placement?', { isMoved, dragstartRankId, currentRankId, shouldResetOldSpace });\r\n        \r\n        this.updateStateAfterUploadWithoutReset(response, fileObj);\r\n        \r\n        const fileType = FileManager.getFileType(extension);\r\n        \r\n        StateManager.set('FormatReconnu', 'No');\r\n        \r\n        \/\/ \u2705 v1.19.2 : Message d'attente IMM\u00c9DIAT avant le rendu de l'annonce\r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        switch (fileType) {\r\n            case 'video':\r\n                PreviewRenderer.renderVideo(StateManager.get('objectUrl'), fileObj.name, $dropZone);\r\n                StateManager.set('FormatReconnu', 'Yes');\r\n                break;\r\n                \r\n            case 'image':\r\n                PreviewRenderer.renderImage(StateManager.get('objectUrl'), $dropZone);\r\n                StateManager.set('FormatReconnu', 'Yes');\r\n                break;\r\n                \r\n            case 'document':\r\n                await this.handleDocumentUpload(extension, fileObj, $dropZone);\r\n                break;\r\n        }\r\n        \r\n        UIManager.updateAfterSuccessfulUpload($dropZone);\r\n        \r\n        if (StateManager.get('FormatReconnu') === 'Yes') {\r\n            this.finalizeUpload($dropZone);\r\n            $('#MsgSelectEspace').hide();\r\n        }\r\n        \r\n        \/\/ \u2705 RESET L'ANCIEN ESPACE ICI - APR\u00c8S avoir affich\u00e9 la nouvelle image\r\n        \/\/ \u2705 v2.4.5 : Toujours initialiser \u00e0 false avant le bloc (\u00e9vite la valeur r\u00e9siduelle)\r\n        var _sourceWasReserved = false;\r\n        StateManager.set('_sourceWasReserved', 'No');\r\n        \r\n        if (shouldResetOldSpace) {\r\n            console.log('\ud83d\udd04 Reset ancien espace:', dragstartRankId);\r\n            \r\n            var $oldDroppable = $('#' + dragstartRankId);\r\n            \r\n            if ($oldDroppable.length) {\r\n                var $container = $oldDroppable.find('.OrdiMobileConteneurClass').first();\r\n                \r\n                \/\/ \u2705 v2.4.5 : Utiliser l'\u00e9tat captur\u00e9 au dragstart (fiable vs re-check async)\r\n                if ($container.length) {\r\n                    _sourceWasReserved = StateManager.get('dragstart_ReserverChecked') === 'Yes';\r\n                    console.log('[d\u00e9placement] ReserverChecked au dragstart:', _sourceWasReserved, '| rank:', dragstartRankId);\r\n                    StateManager.set('_sourceWasReserved', _sourceWasReserved ? 'Yes' : 'No');\r\n                    RestoreadSpaceTemplateLocal($container[0]);\r\n                }\r\n            }\r\n        }\r\n        StateManager.set(\"EnvoiUlterieur\", 'false');\r\n        \r\n        \/\/ \u2705 v1.19.1 : Positionner l'espace \u00e0 10px du haut du viewport apr\u00e8s upload\r\n        setTimeout(function() {\r\n            var el = $dropZone.closest('.droppable').find('.HTMLUploadfileConteneur')[0]\r\n                     || $dropZone.closest('.droppable')[0];\r\n            if (el) {\r\n                ScrollHelper.scrollElementTo(el, 10);\r\n            }\r\n        }, 400);\r\n        \r\n        \/\/ \u2705 NE PLUS envoyer automatiquement - c'est la checkbox \"R\u00e9server\" qui d\u00e9clenche\r\n        \/\/ On met \u00e0 jour l'\u00e9tat de la checkbox pour qu'elle devienne activable\r\n        FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n        \r\n        \/\/ \u2705 v2.4.5 : Si l'espace source \u00e9tait r\u00e9serv\u00e9, cocher la checkbox de l'espace cible\r\n        if (StateManager.get('_sourceWasReserved') === 'Yes') {\r\n            var $targetCb = $dropZone.closest('.droppable').find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n            if ($targetCb.length ? !$targetCb.prop('checked') : false) {\r\n                $targetCb.prop('checked', true).trigger('change');\r\n                console.log('\u2705 [d\u00e9placement] Checkbox R\u00e9server coch\u00e9e sur espace cible');\r\n            }\r\n            StateManager.set('_sourceWasReserved', 'No');\r\n        }\r\n        \r\n        console.log('\ud83d\udccb Upload termin\u00e9 - en attente de validation via checkbox \"R\u00e9server\"');\r\n    },\r\n    \r\n    updateStateAfterUploadWithoutReset(response, fileObj) {\r\n        StateManager.setMultiple({\r\n            \"PageWhithADType\": StateManager.get('SelectedPageType'),\r\n            \"PageWhithADSecteur\": StateManager.get('SelectedPageSecteur')\r\n        });\r\n        \r\n        $('#BackPageWebWithADCroix').hide();\r\n        jQuery('#ApercuMobile').empty().css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center'\r\n        });\r\n        \r\n        const firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n        \r\n        if (firstUploadOrMoved === 'FirstUpload') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": response.data.file_url,\r\n                \"fileObj\": fileObj,\r\n                \"Upload_File_Name\": fileObj.name\r\n            });\r\n            \r\n            FileManager.createObjectUrl(fileObj);\r\n            \r\n            console.log('\ud83d\udcc1 Fichier upload\u00e9:', {\r\n                fullUrl: response.data.file_url,\r\n                fileName: response.data.file_name,\r\n                filePath: response.data.file_path\r\n            });\r\n        }\r\n    },\r\n    \r\n    updateStateAfterUpload(response, fileObj) {\r\n        StateManager.setMultiple({\r\n            \"PageWhithADType\": StateManager.get('SelectedPageType'),\r\n            \"PageWhithADSecteur\": StateManager.get('SelectedPageSecteur')\r\n        });\r\n        \r\n        $('#BackPageWebWithADCroix').hide();\r\n        jQuery('#ApercuMobile').empty().css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center'\r\n        });\r\n        \r\n        const firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n        console.log('\ud83d\udd0d FirstUploadFileorMoved:', firstUploadOrMoved);\r\n        \r\n        if (firstUploadOrMoved === 'FirstUpload') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": response.data.file_url,\r\n                \"fileObj\": fileObj,\r\n                \"Upload_File_Name\": fileObj.name\r\n            });\r\n            \r\n            FileManager.createObjectUrl(fileObj);\r\n            \r\n            console.log('\ud83d\udcc1 Fichier upload\u00e9:', {\r\n                fullUrl: response.data.file_url,\r\n                fileName: response.data.file_name,\r\n                filePath: response.data.file_path\r\n            });\r\n        } else if (firstUploadOrMoved === 'Moved') {\r\n            const dragstartRankId = StateManager.get('dragstart_Rank_Emplacement_Page_Web');\r\n            const currentRankId = StateManager.get('Rank_Emplacement_Page_Web');\r\n            \r\n            console.log('\ud83d\udd04 D\u00c9PLACEMENT D\u00c9TECT\u00c9');\r\n            console.log('   Ancien espace (dragstart):', dragstartRankId);\r\n            console.log('   Nouvel espace (current):', currentRankId);\r\n            \r\n            if (dragstartRankId ? dragstartRankId !== currentRankId : false) {\r\n                const $oldDroppable = $('#' + dragstartRankId);\r\n                \r\n                console.log('   $oldDroppable trouv\u00e9:', $oldDroppable.length > 0);\r\n                \r\n                if ($oldDroppable.length) {\r\n                    const $container = $oldDroppable.find('.OrdiMobileConteneurClass');\r\n                    \r\n                    console.log('   $container trouv\u00e9:', $container.length > 0);\r\n                    \r\n                    if ($container.length) {\r\n                        console.log('\ud83d\udd27 Appel RestoreadSpaceTemplate sur:', $container[0]);\r\n                        \r\n                        if (typeof window.RestoreadSpaceTemplate === 'function') {\r\n                            window.RestoreadSpaceTemplate($container[0]);\r\n                            console.log('\u2705 RestoreadSpaceTemplate ex\u00e9cut\u00e9');\r\n                        }\r\n                    } else {\r\n                        console.warn('\u26a0\ufe0f .OrdiMobileConteneurClass non trouv\u00e9 dans', dragstartRankId);\r\n                    }\r\n                }\r\n            } else {\r\n                console.log('\u23ed\ufe0f M\u00eame espace ou dragstartRankId invalide, pas de reset');\r\n            }\r\n        }\r\n    },\r\n        \r\n    async handleDocumentUpload(extension, fileObj, $dropZone) {\r\n        StateManager.set('FormatReconnu', 'Yes');\r\n        \r\n        \/\/ \u2705 Cacher le File r\u00e9dactionnel pour r\u00e9utilisation lors des d\u00e9placements (\u00e9vite CORS)\r\n        window._lastRedactionnelFile = fileObj;\r\n        \r\n        if (extension === 'doc' || extension === 'docx') {\r\n            await new Promise(resolve => window.VIALibraries.loadMammoth(resolve));\r\n            await PreviewRenderer.renderWord(fileObj, $dropZone);\r\n        } else if (extension === 'ppt' || extension === 'pptx') {\r\n            this.renderPowerPoint($dropZone);\r\n            PreviewRenderer.finalizeRedactionnelUpload();\r\n        } else if (extension === 'pdf') {\r\n            await new Promise(resolve => window.VIALibraries.loadPdfJs(resolve));\r\n            await PDFHandler.renderPDF(fileObj, $dropZone);\r\n            PreviewRenderer.finalizeRedactionnelUpload();\r\n        }\r\n    },\r\n    \r\n    renderPowerPoint($dropZone) {\r\n        const img = jQuery('<img \/>', {\r\n            src: '\/wp-content\/uploads\/2024\/06\/microsoft-powerpoint.png',\r\n            css: {\r\n                'width': 'auto',\r\n                'height': 'auto',\r\n                'margin-bottom': '20px',\r\n                'max-width': '150px',\r\n                'max-height': '160px'\r\n            }\r\n        });\r\n        $dropZone.empty().append(img.clone());\r\n        jQuery('#ApercuMobile').append(img);\r\n    },\r\n    \r\n    finalizeUpload($dropZone) {\r\n        \/\/ \u2705 Reset sendDataToParentFlag : nouvel upload = pas encore r\u00e9serv\u00e9\r\n        StateManager.set('sendDataToParentFlag', 'No');\r\n        \r\n        StateManager.setMultiple({\r\n            \"PageAnnonceSelection\": 'Yes',\r\n            \"FormatReconnu\": 'Yes'\r\n        });\r\n        \r\n        jQuery('#MsgChoixPageWeb, #MsgInsererAnnonceConteneur').hide();\r\n        \r\n        if (StateManager.get(\"Commande_Format\") === 'R\u00e9dactionnel') {\r\n            StateManager.set('Commande_Format', '\u00e0 choisir');\r\n            RedactionnelDepose();\r\n        }\r\n        \r\n        if (StateManager.get(\"Commande_Page\") === ' ') {\r\n            jQuery('#HPTarifConteneur').css({'background-color': '#BCFFAD'});\r\n            jQuery('#EmplacementDataStep3').html(\"Home Page\");\r\n            StateManager.setMultiple({\r\n                \"Commande_Page\": 'Home Page',\r\n                \"PageAnnonceSelection\": 'Yes'\r\n            });\r\n        }\r\n        \r\n        $('#PageWeb').css('zoom', '70%');\r\n        $('#PageWebTitreDore').css({'font-size': '14px'});\r\n        \r\n        if (!UIManager.isMobile()) {\r\n            $('#PageWebTitreDore').css({'color': '#213864'});\r\n        }\r\n        \r\n        StateManager.set(\"Page_Web_with_AD_URL\", StateManager.get(\"Page_Web_URL\"));\r\n        \r\n        UIManager.finalizeAdDisplay($dropZone);\r\n        \r\n        \/\/ \u2705 Notifier le parent de l'annonce d\u00e9pos\u00e9e non r\u00e9serv\u00e9e \u2014 pour restauration au retour sur la page\r\n        var _fileRcv = StateManager.get('FileReceived');\r\n        var _isRestoring = StateManager.get('_isAdRestoration') === 'Yes';\r\n        console.log('\ud83d\udfe1 [finalizeUpload] FileReceived:', _fileRcv, '| _isAdRestoration:', _isRestoring);\r\n        if (_fileRcv === 'Yes' ? !_isRestoring : false) {\r\n            var _pendingRank = StateManager.get('Rank_Emplacement_Page_Web') || $dropZone.closest('.droppable').attr('id') || '';\r\n            console.log('\ud83d\udfe1 [finalizeUpload] pendingRank:', _pendingRank, '| FullPathAdFile:', StateManager.get('FullPathAdFile'));\r\n            if (_pendingRank) {\r\n                console.log('\ud83d\udce4 [finalizeUpload] annonceDeposeeSansReservation \u2192 parent rank:', _pendingRank);\r\n                \/\/ \u2705 D\u00e9terminer le vrai format commercial selon le type de fichier d\u00e9pos\u00e9 + format s\u00e9lectionn\u00e9\r\n                var _formatSelect = sessionStorage.getItem('FormatSelect') || StateManager.get('Commande_Format_Transmis') || '';\r\n                var _uploadedExt = (StateManager.get('Upload_File_Name') || '').split('.').pop().toLowerCase();\r\n                var _fileKind = CONFIG.allowedExtensions.video.indexOf(_uploadedExt) !== -1 ? 'video'\r\n                              : CONFIG.allowedExtensions.image.indexOf(_uploadedExt) !== -1 ? 'image'\r\n                              : CONFIG.allowedExtensions.document.indexOf(_uploadedExt) !== -1 ? 'document'\r\n                              : '';\r\n                var _formatPending;\r\n                if (_fileKind === 'video') {\r\n                    \/\/ Vid\u00e9o \u2192 toujours Vid\u00e9o\r\n                    _formatPending = 'Vid\u00e9o';\r\n                } else if (_fileKind === 'image') {\r\n                    \/\/ Image \u2192 Banni\u00e8re ou Parrainage si s\u00e9lectionn\u00e9, sinon Banni\u00e8re par d\u00e9faut\r\n                    var _imgFormats = ['Banni\u00e8re', 'Banniere', 'Parrainage'];\r\n                    _formatPending = (_imgFormats.indexOf(_formatSelect) !== -1) ? _formatSelect : 'Banni\u00e8re';\r\n                } else if (_fileKind === 'document') {\r\n                    \/\/ Document \u2192 Communiqu\u00e9 ou Interview si s\u00e9lectionn\u00e9, sinon Communiqu\u00e9 par d\u00e9faut\r\n                    var _docFormats = ['Communiqu\u00e9', 'Communique', 'Interview'];\r\n                    _formatPending = (_docFormats.indexOf(_formatSelect) !== -1) ? _formatSelect : 'Communiqu\u00e9';\r\n                } else {\r\n                    \/\/ Fallback\r\n                    _formatPending = _formatSelect || StateManager.get('Commande_Format_Transmis') || '';\r\n                }\r\n                \/\/ \u2705 v2.4.9 : Si le format d\u00e9duit diff\u00e8re du format s\u00e9lectionn\u00e9 \u2192 mettre \u00e0 jour vignette + sessionStorage\r\n                if (_formatPending ? _formatPending !== _formatSelect : false) {\r\n                    StateManager.set('FormatSelect', _formatPending);\r\n                    StateManager.set('Commande_Format_Transmis', _formatPending);\r\n                    StateManager.set('Formatchoisi', 'Yes');\r\n                    \/\/ Mettre \u00e0 jour visuellement la vignette dans le droppable courant\r\n                    var _fmtNorm = _formatPending.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    var $_drp = $dropZone.closest('.droppable');\r\n                    $_drp.find('.EspPubFormatContainer').each(function() {\r\n                        var _cls = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                        if (_cls.includes(_fmtNorm)) {\r\n                            jQuery(this).css({'background-color': '#ffffff'});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        } else if (!jQuery(this).hasClass('FormatIdPopUp')) {\r\n                            jQuery(this).css({'background-color': ''});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': ''});\r\n                        }\r\n                    });\r\n                    \/\/ Mettre \u00e0 jour aussi le bandeau parent via postMessage\r\n                    MessageManager.sendToParent('formatChangedInIframe', { formatSelect: _formatPending, rank: _pendingRank });\r\n                    console.log('\ud83d\udd04 [finalizeUpload] Format corrig\u00e9:', _formatSelect, '\u2192', _formatPending);\r\n                }\r\n                var _firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n                var _isMoved = _firstUploadOrMoved === 'Moved';\r\n                var _oldRankMoved = _isMoved ? (StateManager.get('dragstart_Rank_Emplacement_Page_Web') || '') : '';\r\n                MessageManager.sendToParent('annonceDeposeeSansReservation', {\r\n                    Rank_Emplacement_Page_Web: _pendingRank,\r\n                    FullPathAdFile: StateManager.get('FullPathAdFile') || '',\r\n                    Upload_File_Name: StateManager.get('Upload_File_Name') || '',\r\n                    LoadedPageUrl: window.location.href,\r\n                    FileReceived: 'Yes',\r\n                    \/\/ \u2705 Lire EnvoiUlterieur depuis la checkbox du droppable courant (pas le StateManager global)\r\n                    \/\/ StateManager.get('EnvoiUlterieur') est global \u2192 peut valoir 'true' si un autre espace a sa checkbox coch\u00e9e\r\n                    EnvoiUlterieur: ($dropZone.closest('.droppable').find('input[name*=\"EnvoiUlterieur\"]').prop('checked') ? 'true' : 'false'),\r\n                    Commande_Format_Transmis: _formatPending,\r\n                    codeSite: StateManager.get('codeSite') || '',\r\n                    codePage: StateManager.get('codePage') || '',\r\n                    Commande_Emplacement_Page_Web: StateManager.buildEmplacementReference(_pendingRank),\r\n                    isMoved: _isMoved,\r\n                    oldRank: _oldRankMoved\r\n                });\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 Page \/annonce : pas de checkbox R\u00e9server \u2192 enregistrer imm\u00e9diatement\r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            StateManager.set('sendDataToParentFlag', 'Yes');\r\n            this.activateSendDataToParent($dropZone);\r\n        }\r\n    },\r\n    \r\n    activateSendDataToParent($dropZone) {\r\n        if (StateManager.get(\"sendDataToParentFlag\") !== 'Yes') {\r\n            return;\r\n        }\r\n        \r\n        if (StateManager.get(\"FirstUploadFileorMoved\") === 'Moved') {\r\n            const dragstartRef = StateManager.buildEmplacementReference(\r\n                StateManager.get(\"dragstart_Rank_Emplacement_Page_Web\")\r\n            );\r\n            StateManager.set('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n        }\r\n        \r\n        console.log('Esp Pub Ref FirstUploadFileorMoved:', StateManager.get(\"FirstUploadFileorMoved\"));\r\n        console.log('Esp Pub dragstart_Commande:', StateManager.get(\"dragstart_Commande_Emplacement_Page_Web\"));\r\n        \r\n        \/\/ v4.9ds : si un upload kit (drop miniature) est en cours pour ce rank,\r\n        \/\/   attendre sa fin avant d'envoyer les donn\u00e9es \u2014 sinon FullPathAdFile=null\r\n        \/\/   serait propag\u00e9 \u00e0 l'iframe popup et la BDD serait aliment\u00e9e sans chemin.\r\n        var _self = this;\r\n        var _rankWait = StateManager.get(\"Rank_Emplacement_Page_Web\");\r\n        var _pendingUp = (window._viaPendingUpload ? window._viaPendingUpload[_rankWait] : null);\r\n        if (_pendingUp ? typeof _pendingUp.then === 'function' : false) {\r\n            console.log('\u23f3 [activateSendDataToParent] upload kit en cours sur', _rankWait, '\u2192 wait avant d\\'envoyer');\r\n            _pendingUp.then(function() {\r\n                console.log('\u2705 [activateSendDataToParent] upload termin\u00e9 \u2192 reprise envoi | FullPathAdFile:', StateManager.get('FullPathAdFile'));\r\n                _self._continueActivateSendDataToParent($dropZone);\r\n            }).catch(function(_eUW) {\r\n                console.warn('\u26a0\ufe0f [activateSendDataToParent] upload \u00e9chou\u00e9 \u2192 on continue quand m\u00eame:', _eUW);\r\n                _self._continueActivateSendDataToParent($dropZone);\r\n            });\r\n            return;\r\n        }\r\n        \r\n        this._continueActivateSendDataToParent($dropZone);\r\n    },\r\n    \r\n    _continueActivateSendDataToParent($dropZone) {\r\n        if (StateManager.get(\"PageAjoutModifAnnonce\") === 'Yes') {\r\n            this.handlePageModification($dropZone);\r\n        } else {\r\n            this.handleNormalUpload();\r\n        }\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        setTimeout(() => {\r\n            StateManager.set(\"AddNewRefInVosCampagnes\", 'Yes');\r\n        }, 4000);\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            StateManager.set('Commande_Format_Transmis', '');\r\n        }\r\n    },\r\n    \r\n    handlePageModification($dropZone) {\r\n        $('#CroixResetAnnonce, .DeplaceAnnonce').hide();\r\n        $('.PageAjoutModifAnnonceCroixResetAnnonce').show();\r\n        \r\n        const emplacementRef = $dropZone.closest('.CampagnesTemplateClass')\r\n            .find('.ReferenceEspace').text();\r\n        \r\n        console.log('Commande_Emplacement_Page_Web:', emplacementRef);\r\n        \r\n        jQuery.ajax({\r\n            type: \"POST\",\r\n            url: CONFIG.ajaxUrl,\r\n            data: {\r\n                action: 'via_update_fichier_annonce',\r\n                commande_ref_url: StateManager.get(\"commande_ref_url\"),\r\n                emplacement_page_web: emplacementRef,\r\n                chemin_fichier: StateManager.get(\"FullPathAdFile\")\r\n            },\r\n            xhrFields: { withCredentials: true },\r\n            success: (response) => {\r\n                if (response.success) {\r\n                    console.log('\u2705 Fichier annonce mis \u00e0 jour:', response.data.message);\r\n                    location.reload();\r\n                } else {\r\n                    console.error('\u274c Erreur:', response.data.message);\r\n                }\r\n            },\r\n            error: (xhr, status, error) => {\r\n                console.error('\u274c Erreur AJAX:', error);\r\n            }\r\n        });\r\n    },\r\n    \r\n    handleNormalUpload() {\r\n        if (StateManager.get(\"EnvoiUlterieur\") === 'true') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": '',\r\n                \"Upload_File_Name\": ''\r\n            });\r\n        }\r\n        \r\n        StateManager.set(\"LoadedPageUrl\", window.location.href);\r\n        \r\n        console.log('EnvoiUlterieur:', StateManager.get(\"EnvoiUlterieur\"));\r\n        console.log('LoadedPageUrl:', StateManager.get(\"LoadedPageUrl\"));\r\n        console.log('FileReceived:', StateManager.get(\"FileReceived\"));\r\n        \r\n        const data = MessageManager.prepareUploadData();\r\n        \r\n        \/\/ \u2705 Sauvegarder FullPathAdFile dans localStorage pour restauration apr\u00e8s refresh\r\n        \/\/ (ce chemin couvre aussi AchatEspaceCall=Yes o\u00f9 handleEspacePubData d'Entete.txt n'est pas appel\u00e9)\r\n        \/\/ \u2705 Backup : sauver via_fullpath ici aussi (couvre AchatEspaceCall=Yes)\r\n        if (data.FullPathAdFile) {\r\n            var _rankFP = data.Rank_Emplacement_Page_Web;\r\n            if (_rankFP) {\r\n                try {\r\n                    var _fpItemsEP = JSON.parse(localStorage.getItem('via_fullpath') || '{}');\r\n                    _fpItemsEP[_rankFP] = data.FullPathAdFile;\r\n                    localStorage.setItem('via_fullpath', JSON.stringify(_fpItemsEP));\r\n                } catch(e) {}\r\n            }\r\n        }\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            MessageManager.sendDataToParent(data);\r\n        } else {\r\n            const formatChoisi = StateManager.get(\"Formatchoisi\") === 'Yes';\r\n            const fichierDepose = StateManager.get(\"FileReceived\") === 'Yes';\r\n            const envoiDiffere = StateManager.get(\"EnvoiUlterieur\") === 'true';\r\n            \r\n            console.log('\ud83d\udd0d handleNormalUpload - Conditions:', { formatChoisi, fichierDepose, envoiDiffere });\r\n            \r\n            if (formatChoisi ? (fichierDepose || envoiDiffere) : false) {\r\n                if (typeof window.handleEspacePubData === 'function') {\r\n                    window.handleEspacePubData(data);\r\n                } else {\r\n                    console.warn('\u26a0\ufe0f handleEspacePubData non disponible, fallback direct');\r\n                    if (typeof window.executeWithProcessLoaded === 'function') {\r\n                        window.executeWithProcessLoaded(function() {\r\n                            jQuery('#PopupProcessCommandeContainer').show();\r\n                        });\r\n                    } else {\r\n                        jQuery('#PopupProcessCommandeContainer').show();\r\n                    }\r\n                }\r\n            } else if (formatChoisi) {\r\n                console.log('\ud83d\udcdd Mise \u00e0 jour format uniquement');\r\n                \r\n                const borderStyle = {'box-shadow': 'inset 0 0 0 2px #00FF19', 'box-sizing': 'border-box'};  \/\/ \u2705 v2.4.5\r\n                \r\n                jQuery(\".FormatClassique, .FormatPopup\").css({'border': 'none'});\r\n                \r\n                jQuery('.FormatClassique, .FormatPopup').each(function() {\r\n                    \/\/ \u2705 v2.1.1 : NFD normalize both sides\r\n                    var _cn = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    var _cf = (data.Commande_Format_Transmis || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    if (_cn.includes(_cf) ? _cf : false) {\r\n                        jQuery(this).css(borderStyle);\r\n                    }\r\n                });\r\n            }\r\n        }\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        setTimeout(() => {\r\n            StateManager.set(\"AddNewRefInVosCampagnes\", 'Yes');\r\n        }, 4000);\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            StateManager.set('Commande_Format_Transmis', '');\r\n        }\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des fichiers PDF (s\u00e9par\u00e9 pour \u00e9viter la complexit\u00e9)\r\n *\/\r\nconst PDFHandler = {\r\n    pdfData: null,\r\n    pdfDataForViewer: null,\r\n    \r\n    async renderPDF(fileObj, $dropZone) {\r\n        return new Promise((resolve, reject) => {\r\n            const reader = new FileReader();\r\n            \r\n            reader.onload = async (e) => {\r\n                try {\r\n                    const arrayBuffer = e.target.result;\r\n                    this.pdfData = new Uint8Array(arrayBuffer);\r\n                    this.pdfDataForViewer = arrayBuffer;\r\n                    \r\n                    console.log(\"Initial PDF load, size:\", this.pdfData.byteLength);\r\n                    \r\n                    const pdfjsLib = window['pdfjs-dist\/build\/pdf'];\r\n                    pdfjsLib.GlobalWorkerOptions.workerSrc = \r\n                        '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n                    \r\n                    const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(this.pdfData) }).promise;\r\n                    const page = await pdf.getPage(1);\r\n                    \r\n                    const { titleText, pageText } = await this.extractTextFromPage(page);\r\n                    \/\/ v4.9ds : si une image utilisateur a \u00e9t\u00e9 stock\u00e9e par le kit (mobile PDF JPG-in-PDF),\r\n                    \/\/   l'utiliser directement plut\u00f4t que d'extraire la 1\u00e8re image du PDF\r\n                    \/\/   (qui serait toute la cr\u00e9ation rasteris\u00e9e)\r\n                    const _$drop4ds = $dropZone.closest('.droppable');\r\n                    const _kitFirstImg = _$drop4ds.length ? _$drop4ds.data('kitFirstImageDataURL') : null;\r\n                    let imageData;\r\n                    if (_kitFirstImg) {\r\n                        imageData = { dataUrl: _kitFirstImg };\r\n                        console.log('[v4.9ds] renderPDF \u2192 utilise kitFirstImageDataURL stock\u00e9');\r\n                        try { var _dl5 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl5) _dl5('[v4.9ds] renderPDF utilise firstImg stock\u00e9', 'ok'); } catch(e) {}\r\n                    } else {\r\n                        imageData = await this.extractImageFromPage(page, pdfjsLib);\r\n                        try { var _dl6 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl6) _dl6('[v4.9ds] renderPDF utilise extractImageFromPage (firstImg ABSENT)', 'warn'); } catch(e) {}\r\n                    }\r\n                    \r\n                    if (imageData) {\r\n                        PreviewRenderer.renderDocumentPreview(\r\n                            $dropZone,\r\n                            imageData.dataUrl,\r\n                            titleText,\r\n                            pageText\r\n                        );\r\n                    } else {\r\n                        \/\/ \u2705 v1.19.0 : Accepter les PDF sans image\r\n                        PreviewRenderer.renderDocumentPreview(\r\n                            $dropZone,\r\n                            null,\r\n                            titleText,\r\n                            pageText\r\n                        );\r\n                        console.log('\u2139\ufe0f PDF sans image \u2014 aper\u00e7u texte seul');\r\n                    }\r\n                    \r\n                    this.attachPDFPreviewHandler($dropZone, titleText);\r\n                    resolve();\r\n                } catch (error) {\r\n                    console.error('Error extracting from PDF:', error);\r\n                    \/\/ \u2705 v1.19.0 : Message d'erreur en fran\u00e7ais\r\n                    $dropZone.html(`\r\n                        <div style=\"padding: 15px; background-color: #f8f8f8; border-radius: 4px; text-align: center;\">\r\n                            <p style=\"color: #FB5E2A; font-weight: 600; font-family: Roboto, Arial, sans-serif; font-size: 12px;\">\r\n                                Erreur lors de la lecture du document\r\n                            <\/p>\r\n                        <\/div>\r\n                    `);\r\n                    reject(error);\r\n                }\r\n            };\r\n            \r\n            reader.readAsArrayBuffer(fileObj);\r\n        });\r\n    },\r\n    \r\n    async extractTextFromPage(page) {\r\n        const textContent = await page.getTextContent();\r\n        \r\n        let text = '';\r\n        let lastX = -1;\r\n        let lastY = -1;\r\n        \r\n        for (const item of textContent.items) {\r\n            if (lastY !== -1 ? (Math.abs(lastY - item.transform[5]) > 5) : false) {\r\n                text += '\\n';\r\n            } else if (lastX !== -1 ? (item.transform[4] - lastX > 10) : false) {\r\n                text += ' ';\r\n            }\r\n            \r\n            text += item.str;\r\n            \r\n            lastX = item.transform[4] + (item.width || 0);\r\n            lastY = item.transform[5];\r\n        }\r\n        \r\n        text = text\r\n            .replace(\/(\\w) (\\w)\/g, (match, p1, p2) => {\r\n                if (\/[\u00e9\u00e8\u00ea\u00eb\u00e0\u00e2\u00e4\u00f4\u00f6\u00fb\u00fc\u00ef\u00ee\u00e7]\/i.test(p2)) {\r\n                    return p1 + p2;\r\n                }\r\n                return match;\r\n            })\r\n            .replace(\/ ([.,;:!?])\/g, '$1');\r\n        \r\n        const normalizedText = PreviewRenderer.normalizeText(text);\r\n        const titleText = PreviewRenderer.findDocumentTitle(normalizedText);\r\n        \r\n        return { titleText, pageText: text };\r\n    },\r\n    \r\n    async extractImageFromPage(page, pdfjsLib) {\r\n        const opList = await page.getOperatorList();\r\n        const imageInfo = [];\r\n        let currentTransform = null;\r\n        \r\n        for (let i = 0; i < opList.fnArray.length; i++) {\r\n            const operator = opList.fnArray[i];\r\n            const args = opList.argsArray[i];\r\n            \r\n            if (operator === pdfjsLib.OPS.transform) {\r\n                currentTransform = args;\r\n            } else if (operator === pdfjsLib.OPS.paintImageXObject) {\r\n                const imageName = args[0];\r\n                \r\n                if (!imageInfo.some(info => info.name === imageName)) {\r\n                    const position = currentTransform ? {\r\n                        x: currentTransform[4] || 0,\r\n                        y: currentTransform[5] || 0\r\n                    } : { x: 0, y: 0 };\r\n                    \r\n                    imageInfo.push({ name: imageName, position: position });\r\n                }\r\n            }\r\n        }\r\n        \r\n        if (imageInfo.length === 0) {\r\n            return null;\r\n        }\r\n        \r\n        imageInfo.sort((a, b) => b.position.y - a.position.y);\r\n        \r\n        const targetImageName = imageInfo[0].name;\r\n        const imageObj = page.objs.get(targetImageName);\r\n        \r\n        if (!imageObj) {\r\n            const img = await page.objs.get(targetImageName, true);\r\n            return this.processImageObject(img);\r\n        }\r\n        \r\n        return this.processImageObject(imageObj);\r\n    },\r\n    \r\n    processImageObject(imageObj) {\r\n        if (!imageObj) {\r\n            throw new Error('Image object is null');\r\n        }\r\n        \r\n        if (imageObj.bitmap instanceof ImageBitmap) {\r\n            const width = imageObj.bitmap.width || imageObj.width || imageObj.w;\r\n            const height = imageObj.bitmap.height || imageObj.height || imageObj.h;\r\n            \r\n            const canvas = document.createElement('canvas');\r\n            canvas.width = width;\r\n            canvas.height = height;\r\n            const ctx = canvas.getContext('2d');\r\n            \r\n            ctx.drawImage(imageObj.bitmap, 0, 0);\r\n            \r\n            return {\r\n                canvas: canvas,\r\n                width: width,\r\n                height: height,\r\n                dataUrl: canvas.toDataURL('image\/jpeg', 0.92)\r\n            };\r\n        }\r\n        \r\n        const width = imageObj.width || imageObj.w;\r\n        const height = imageObj.height || imageObj.h;\r\n        \r\n        if (!width || !height) {\r\n            throw new Error('Could not determine image dimensions');\r\n        }\r\n        \r\n        let imgData = imageObj.data || imageObj.bitmap;\r\n        \r\n        if (!imgData) {\r\n            throw new Error('No valid image data found');\r\n        }\r\n        \r\n        const canvas = document.createElement('canvas');\r\n        canvas.width = width;\r\n        canvas.height = height;\r\n        const ctx = canvas.getContext('2d');\r\n        \r\n        ctx.fillStyle = 'white';\r\n        ctx.fillRect(0, 0, width, height);\r\n        \r\n        const imageData = ctx.createImageData(width, height);\r\n        \r\n        this.fillImageData(imageData, imgData, imageObj.kind, width, height);\r\n        \r\n        ctx.putImageData(imageData, 0, 0);\r\n        \r\n        return {\r\n            canvas: canvas,\r\n            width: width,\r\n            height: height,\r\n            dataUrl: canvas.toDataURL('image\/jpeg', 0.92)\r\n        };\r\n    },\r\n    \r\n    fillImageData(imageData, imgData, kind, width, height) {\r\n        if (kind === 'GRAY') {\r\n            for (let i = 0, j = 0; i < imgData.length; i++, j += 4) {\r\n                const value = imgData[i];\r\n                imageData.data[j] = value;\r\n                imageData.data[j + 1] = value;\r\n                imageData.data[j + 2] = value;\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (kind === 'CMYK') {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 4, j += 4) {\r\n                const c = imgData[i] \/ 255;\r\n                const m = imgData[i + 1] \/ 255;\r\n                const y = imgData[i + 2] \/ 255;\r\n                const k = imgData[i + 3] \/ 255;\r\n                \r\n                imageData.data[j] = 255 * (1 - c) * (1 - k);\r\n                imageData.data[j + 1] = 255 * (1 - m) * (1 - k);\r\n                imageData.data[j + 2] = 255 * (1 - y) * (1 - k);\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (kind === 'RGB24') {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 3, j += 4) {\r\n                imageData.data[j] = imgData[i];\r\n                imageData.data[j + 1] = imgData[i + 1];\r\n                imageData.data[j + 2] = imgData[i + 2];\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (imgData.length === width * height * 3) {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 3, j += 4) {\r\n                imageData.data[j] = imgData[i];\r\n                imageData.data[j + 1] = imgData[i + 1];\r\n                imageData.data[j + 2] = imgData[i + 2];\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else {\r\n            const tempArray = new Uint8ClampedArray(imgData.length);\r\n            for (let i = 0; i < imgData.length; i++) {\r\n                tempArray[i] = imgData[i];\r\n            }\r\n            \r\n            if (tempArray.length === imageData.data.length \/ 4 * 3) {\r\n                for (let i = 0, j = 0; i < tempArray.length; i += 3, j += 4) {\r\n                    imageData.data[j] = tempArray[i];\r\n                    imageData.data[j + 1] = tempArray[i + 1];\r\n                    imageData.data[j + 2] = tempArray[i + 2];\r\n                    imageData.data[j + 3] = 255;\r\n                }\r\n            } else if (tempArray.length === imageData.data.length) {\r\n                imageData.data.set(tempArray);\r\n            } else {\r\n                console.warn('Unknown image format - creating placeholder');\r\n                for (let i = 0; i < imageData.data.length; i += 4) {\r\n                    const x = (i\/4) % width;\r\n                    const y = Math.floor((i\/4) \/ width);\r\n                    imageData.data[i] = x % 256;\r\n                    imageData.data[i + 1] = y % 256;\r\n                    imageData.data[i + 2] = 100;\r\n                    imageData.data[i + 3] = 255;\r\n                }\r\n            }\r\n        }\r\n    },\r\n    \r\n    attachPDFPreviewHandler($dropZone, titleText) {\r\n        var $droppable = $dropZone.closest('.droppable');\r\n        var self = this;\r\n\r\n        \/\/ \u2705 Adapter le libell\u00e9 selon le format (communiqu\u00e9 \/ interview)\r\n        var kitFormat = $droppable.data('kitFormatSelect') || '';\r\n        var isInterview = (kitFormat || titleText || '').toLowerCase().indexOf('interview') !== -1;\r\n        var formatLabel = isInterview ? 'l\\'interview' : 'le communiqu\u00e9';\r\n        $droppable.find('.doc-preview-readmore').text('Ouvrir et visualiser ' + formatLabel);\r\n\r\n        \/\/ \u2705 v2.0.11 : D\u00e9l\u00e9gation d'\u00e9v\u00e9nement \u2014 plus robuste sur mobile\r\n        $droppable.off('click.docpreview').on('click.docpreview', '.doc-preview-readmore', (event) => {\r\n            event.preventDefault();\r\n            event.stopPropagation();\r\n            var popupTitle = isInterview ? 'Interview' : 'Communiqu\u00e9';\r\n\r\n            \/\/ \u2705 Priorit\u00e9 1 : pdfImageDataURL dispo (Kit)\r\n            var kitPdfImage = $droppable.data('kitPdfImageDataURL');\r\n            if (kitPdfImage) {\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    pdfDataURL: kitPdfImage\r\n                });\r\n                return;\r\n            }\r\n\r\n            \/\/ \u2705 Priorit\u00e9 2 : PDF upload\u00e9 manuellement \u2192 popup inline aussi\r\n            if (self.pdfDataForViewer ? self.pdfDataForViewer.byteLength > 0 : false) {\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    pdfArrayBuffer: self.pdfDataForViewer\r\n                });\r\n                return;\r\n            }\r\n\r\n            console.error('PDF data is not available');\r\n        });\r\n    },\r\n\r\n    \/\/ \u2705 v1.19.1 : Popup inline document \u2014 position:absolute + ScrollHelper.getVisibleTop()\r\n    \/\/ Le parent envoie visibleTopIframe dans le message scroll \u2192 positionnement fiable.\r\n    showInlineDocPopup($dropZone, options) {\r\n        var existing = document.getElementById('via-pdf-inline-popup');\r\n        if (existing) existing.remove();\r\n\r\n        var $droppable = $dropZone.closest('.droppable');\r\n        var droppableW = $droppable.outerWidth() || 300;\r\n        var popupW = Math.round(droppableW * 1.15);\r\n        \/\/ \u2705 v2.0.11 : Contraindre la largeur au viewport sur mobile (\u00e9vite troncature gauche\/droite)\r\n        var viewportW = document.documentElement.clientWidth || window.innerWidth;\r\n        if (UIManager.isMobile()) {\r\n            popupW = Math.min(popupW, viewportW - 8);\r\n        }\r\n        var popupH = Math.round(popupW * 2.5 * 0.51);\r\n\r\n        \/\/ \u2500\u2500 Position visible r\u00e9elle \u2500\u2500\r\n        var visibleTop = ScrollHelper.getVisibleTop();\r\n        var popupTop = Math.round(visibleTop + 284);\r\n\r\n        \/\/ \u2500\u2500 Popup position:absolute (m\u00eame approche que la r\u00e9gie) \u2500\u2500\r\n        var popup = document.createElement('div');\r\n        popup.id = 'via-pdf-inline-popup';\r\n        \/\/ v4.9ds : initialiser le z-index avec le compteur global window.popupZIndex\r\n        \/\/   pour que le popup PDF s'int\u00e8gre au syst\u00e8me _bringPopupToFront (Entete.txt:921+).\r\n        \/\/   Au prochain mousedown sur ce popup, le s\u00e9lecteur _viaTopMostSel le d\u00e9tecte\r\n        \/\/   et incr\u00e9mente son z-index \u2192 il passe en avant-plan par rapport aux autres\r\n        \/\/   popups (Ele0A, miniature, CGV, etc.). Skip sur mobile (perturberait le rendu).\r\n        var _initZ;\r\n        if (typeof window.popupZIndex === 'number' ? window.innerWidth >= 1000 : false) {\r\n            window.popupZIndex++;\r\n            _initZ = window.popupZIndex;\r\n        } else {\r\n            _initZ = 99999;\r\n        }\r\n        popup.style.cssText =\r\n            'position:absolute;z-index:' + _initZ + ';top:' + popupTop + 'px;' +\r\n            'width:' + popupW + 'px;height:' + popupH + 'px;' +\r\n            'background:#fff;border-radius:8px 8px 0 0;padding:0;' +\r\n            'box-shadow:0 8px 32px rgba(0,0,0,0.35);display:flex;flex-direction:column;' +\r\n            'min-width:200px;min-height:150px;touch-action:none;';\r\n\r\n        \/\/ Centrer horizontalement sur le droppable (coordonn\u00e9es absolues)\r\n        var droppableRect = $droppable[0].getBoundingClientRect();\r\n        var scrollX = window.scrollX || 0;\r\n        var initLeft = Math.round(droppableRect.left + scrollX + (droppableRect.width - popupW) \/ 2);\r\n        initLeft = Math.max(4, initLeft);\r\n        \/\/ \u2705 v2.0.11 : Contraindre \u00e0 droite aussi sur mobile\r\n        if (UIManager.isMobile()) {\r\n            initLeft = Math.min(initLeft, viewportW - popupW - 4);\r\n            initLeft = Math.max(4, initLeft);\r\n        }\r\n        popup.style.left = initLeft + 'px';\r\n\r\n        \/\/ \u2500\u2500 Header \u2500\u2500\r\n        var header = document.createElement('div');\r\n        header.style.cssText =\r\n            'display:flex;align-items:center;justify-content:center;position:relative;' +\r\n            'padding:10px 14px;background:#f0f0f0;color:#494949;flex-shrink:0;' +\r\n            'cursor:grab;user-select:none;border-radius:8px 8px 0 0;';\r\n        var titleEl = document.createElement('span');\r\n        titleEl.style.cssText = 'font-family:Roboto,Arial,sans-serif;font-size:15px;font-weight:600;';\r\n        titleEl.textContent = options.formatTitle || 'Document';\r\n        header.appendChild(titleEl);\r\n\r\n        var closeBtn = document.createElement('button');\r\n        closeBtn.textContent = '\u00d7';\r\n        closeBtn.style.cssText =\r\n            'position:absolute;right:10px;top:50%;transform:translateY(-50%);' +\r\n            'background:transparent;border:none;color:#494949;font-size:20px;' +\r\n            'cursor:pointer;line-height:1;padding:0 4px;';\r\n        closeBtn.addEventListener('click', cleanup);\r\n        header.appendChild(closeBtn);\r\n        popup.appendChild(header);\r\n\r\n        \/\/ \u2500\u2500 Zone scrollable \u2014 pleine largeur \u2500\u2500\r\n        var scrollZone = document.createElement('div');\r\n        scrollZone.style.cssText =\r\n            'flex:1;overflow-y:auto;overflow-x:hidden;padding:0;margin:0;touch-action:pan-y;';\r\n        popup.appendChild(scrollZone);\r\n\r\n        var loader = document.createElement('div');\r\n        loader.style.cssText =\r\n            'text-align:center;padding:30px;font-family:Roboto,Arial,sans-serif;font-size:12px;color:#666;';\r\n        loader.textContent = 'Chargement du document\u2026';\r\n        scrollZone.appendChild(loader);\r\n\r\n        \/\/ \u2500\u2500 8 poign\u00e9es de redimensionnement \u2500\u2500\r\n        var handles = [\r\n            { cursor:'nw-resize', pos:'top:-4px;left:-4px;',       dx:-1, dy:-1 },\r\n            { cursor:'n-resize',  pos:'top:-4px;left:50%;',        dx:0,  dy:-1 },\r\n            { cursor:'ne-resize', pos:'top:-4px;right:-4px;',      dx:1,  dy:-1 },\r\n            { cursor:'w-resize',  pos:'top:50%;left:-4px;',        dx:-1, dy:0  },\r\n            { cursor:'e-resize',  pos:'top:50%;right:-4px;',       dx:1,  dy:0  },\r\n            { cursor:'sw-resize', pos:'bottom:-4px;left:-4px;',    dx:-1, dy:1  },\r\n            { cursor:'s-resize',  pos:'bottom:-4px;left:50%;',     dx:0,  dy:1  },\r\n            { cursor:'se-resize', pos:'bottom:-4px;right:-4px;',   dx:1,  dy:1  }\r\n        ];\r\n        handles.forEach(function(h) {\r\n            var handle = document.createElement('div');\r\n            var isCorner = (h.dx !== 0 ? h.dy !== 0 : false);\r\n            handle.style.cssText =\r\n                'position:absolute;touch-action:none;' + h.pos +\r\n                'width:' + (isCorner ? '12px' : (h.dy === 0 ? '8px' : '30px')) + ';' +\r\n                'height:' + (isCorner ? '12px' : (h.dy === 0 ? '30px' : '8px')) + ';' +\r\n                'cursor:' + h.cursor + ';z-index:10;';\r\n            (function(hInfo) {\r\n                handle.addEventListener('pointerdown', function(e) {\r\n                    e.preventDefault(); e.stopPropagation();\r\n                    handle.setPointerCapture(e.pointerId);\r\n                    var startX = e.clientX, startY = e.clientY;\r\n                    var rect0 = popup.getBoundingClientRect();\r\n                    var sY = window.scrollY || 0, sX = window.scrollX || 0;\r\n                    var startW = rect0.width, startH = rect0.height;\r\n                    var startL = rect0.left + sX, startT = rect0.top + sY;\r\n                    function onResize(ev) {\r\n                        ev.preventDefault();\r\n                        var ddx = ev.clientX - startX, ddy = ev.clientY - startY;\r\n                        var newW = startW, newH = startH, newL = startL, newT = startT;\r\n                        if (hInfo.dx === 1)  newW = Math.max(200, startW + ddx);\r\n                        if (hInfo.dx === -1) { newW = Math.max(200, startW - ddx); newL = startL + ddx; }\r\n                        if (hInfo.dy === 1)  newH = Math.max(150, startH + ddy);\r\n                        if (hInfo.dy === -1) { newH = Math.max(150, startH - ddy); newT = startT + ddy; }\r\n                        popup.style.width  = newW + 'px';\r\n                        popup.style.height = newH + 'px';\r\n                        popup.style.left   = newL + 'px';\r\n                        popup.style.top    = newT + 'px';\r\n                    }\r\n                    function onResizeEnd(ev) {\r\n                        handle.releasePointerCapture(ev.pointerId);\r\n                        handle.removeEventListener('pointermove', onResize);\r\n                        handle.removeEventListener('pointerup', onResizeEnd);\r\n                    }\r\n                    handle.addEventListener('pointermove', onResize);\r\n                    handle.addEventListener('pointerup', onResizeEnd);\r\n                });\r\n            })(h);\r\n            popup.appendChild(handle);\r\n        });\r\n\r\n        \/\/ \u2500\u2500 Drag via header \u2500\u2500\r\n        header.style.touchAction = 'none';\r\n        header.addEventListener('pointerdown', function(e) {\r\n            if (e.target === closeBtn) return;\r\n            e.preventDefault();\r\n            header.setPointerCapture(e.pointerId);\r\n            var mx = e.clientX, my = e.clientY;\r\n            var popupRect = popup.getBoundingClientRect();\r\n            var scrollY = window.scrollY || 0;\r\n            var scrollX2 = window.scrollX || 0;\r\n            var curLeft = popupRect.left + scrollX2;\r\n            var curTop  = popupRect.top  + scrollY;\r\n            header.style.cursor = 'grabbing';\r\n            function onMove(ev) {\r\n                ev.preventDefault();\r\n                var dx = ev.clientX - mx;\r\n                var dy = ev.clientY - my;\r\n                curLeft += dx;\r\n                curTop  += dy;\r\n                popup.style.left = curLeft + 'px';\r\n                popup.style.top  = curTop  + 'px';\r\n                mx = ev.clientX;\r\n                my = ev.clientY;\r\n            }\r\n            function onUp(ev) {\r\n                header.releasePointerCapture(ev.pointerId);\r\n                header.style.cursor = 'grab';\r\n                header.removeEventListener('pointermove', onMove);\r\n                header.removeEventListener('pointerup', onUp);\r\n            }\r\n            header.addEventListener('pointermove', onMove);\r\n            header.addEventListener('pointerup', onUp);\r\n        });\r\n\r\n        \/\/ \u2500\u2500 Cleanup \u2500\u2500\r\n        function cleanup() {\r\n            popup.remove();\r\n            document.removeEventListener('keydown', escHandler);\r\n        }\r\n        var escHandler = function(e) { if (e.key === 'Escape') cleanup(); };\r\n        document.addEventListener('keydown', escHandler);\r\n\r\n        document.body.appendChild(popup);\r\n\r\n        \/\/ \u2500\u2500 Rendre le contenu \u2500\u2500\r\n        if (options.pdfDataURL)          this.renderPdfInPopup(scrollZone, options.pdfDataURL, popupW, 'dataurl');\r\n        else if (options.pdfArrayBuffer) this.renderPdfInPopup(scrollZone, options.pdfArrayBuffer, popupW, 'arraybuffer');\r\n        else if (options.htmlContent)    this.renderHtmlInPopup(scrollZone, options.htmlContent);\r\n    },\r\n\r\n    \/\/ Alias pour compatibilit\u00e9\r\n    showInlinePdfPopup($dropZone, pdfImageDataURL, formatTitle) {\r\n        this.showInlineDocPopup($dropZone, { formatTitle: formatTitle, pdfDataURL: pdfImageDataURL });\r\n    },\r\n\r\n    \/\/ \u2705 Rendu PDF dans popup (dataurl ou arraybuffer) \u2014 avec crop des marges blanches\r\n    async renderPdfInPopup(container, pdfData, popupWidth, mode) {\r\n        try {\r\n            var u8;\r\n            if (mode === 'dataurl') {\r\n                var b64 = pdfData.split(',')[1];\r\n                var bstr = atob(b64);\r\n                u8 = new Uint8Array(bstr.length);\r\n                for (var i = 0; i < bstr.length; i++) { u8[i] = bstr.charCodeAt(i); }\r\n            } else {\r\n                u8 = new Uint8Array(pdfData);\r\n            }\r\n\r\n            var pdfjsLib = window['pdfjs-dist\/build\/pdf'];\r\n            if (!pdfjsLib) {\r\n                container.innerHTML = '<div style=\"padding:20px;text-align:center;color:#c00;\">pdf.js non charg\u00e9<\/div>';\r\n                return;\r\n            }\r\n            pdfjsLib.GlobalWorkerOptions.workerSrc =\r\n                '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n\r\n            var pdf = await pdfjsLib.getDocument({ data: u8 }).promise;\r\n            console.log('\ud83d\udcc4 PDF popup :', pdf.numPages, 'pages');\r\n            container.innerHTML = '';\r\n\r\n            var firstPage = await pdf.getPage(1);\r\n            var vpNative = firstPage.getViewport({ scale: 1 });\r\n            var scale = popupWidth \/ vpNative.width;\r\n\r\n            \/\/ \u2705 R\u00e9f\u00e9rence au popup pour ajuster sa hauteur apr\u00e8s rendu\r\n            var _popup = container.closest('#via-pdf-inline-popup');\r\n            var _headerH = _popup ? (_popup.querySelector('div[style*=\"grab\"]') || {offsetHeight: 44}).offsetHeight : 44;\r\n\r\n            \/\/ \u2705 D\u00e9tecte les marges blanches haut\/bas\/gauche\/droite d'un canvas rendu\r\n            \/\/ v4.9ds (pdf-popup-fullwidth) : scan des 4 c\u00f4t\u00e9s (avant : top\/bottom seulement),\r\n            \/\/   pour que le contenu PDF prenne toute la largeur de la popup m\u00eame si le PDF\r\n            \/\/   d'origine a des marges lat\u00e9rales blanches importantes (cas PDFs kit Interview\/\r\n            \/\/   Communiqu\u00e9 portrait avec contenu centr\u00e9 ~40% de la largeur).\r\n            \/\/   Pour left\/right, on scanne UNIQUEMENT entre topRow et bottomRow d\u00e9j\u00e0 d\u00e9termin\u00e9s\r\n            \/\/   afin de ne pas \u00eatre tromp\u00e9 par du blanc d\u00e9j\u00e0 sur le point d'\u00eatre crop\u00e9.\r\n            function detectWhiteMargins(canvas) {\r\n                var ctx = canvas.getContext('2d');\r\n                var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;\r\n                var W = canvas.width, H = canvas.height;\r\n                var topRow = 0, bottomRow = H - 1, leftCol = 0, rightCol = W - 1;\r\n                \/\/ Top : premi\u00e8re ligne non-blanche (tol\u00e9rance 252 pour anti-aliasing)\r\n                outer: for (var y = 0; y < H; y++) {\r\n                    for (var x = 0; x < W; x++) {\r\n                        var i = (y * W + x) * 4;\r\n                        if (data[i] < 252 || data[i+1] < 252 || data[i+2] < 252) { topRow = y; break outer; }\r\n                    }\r\n                }\r\n                \/\/ Bottom : derni\u00e8re ligne non-blanche\r\n                outer2: for (var y2 = H - 1; y2 >= topRow; y2--) {\r\n                    for (var x2 = 0; x2 < W; x2++) {\r\n                        var i2 = (y2 * W + x2) * 4;\r\n                        if (data[i2] < 252 || data[i2+1] < 252 || data[i2+2] < 252) { bottomRow = y2; break outer2; }\r\n                    }\r\n                }\r\n                \/\/ Left : premi\u00e8re colonne non-blanche (scan limit\u00e9 \u00e0 la zone [topRow..bottomRow])\r\n                outer3: for (var x3 = 0; x3 < W; x3++) {\r\n                    for (var y3 = topRow; y3 <= bottomRow; y3++) {\r\n                        var i3 = (y3 * W + x3) * 4;\r\n                        if (data[i3] < 252 || data[i3+1] < 252 || data[i3+2] < 252) { leftCol = x3; break outer3; }\r\n                    }\r\n                }\r\n                \/\/ Right : derni\u00e8re colonne non-blanche\r\n                outer4: for (var x4 = W - 1; x4 >= leftCol; x4--) {\r\n                    for (var y4 = topRow; y4 <= bottomRow; y4++) {\r\n                        var i4 = (y4 * W + x4) * 4;\r\n                        if (data[i4] < 252 || data[i4+1] < 252 || data[i4+2] < 252) { rightCol = x4; break outer4; }\r\n                    }\r\n                }\r\n                return { top: topRow, bottom: bottomRow, left: leftCol, right: rightCol };\r\n            }\r\n\r\n            for (var pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\r\n                var page = await pdf.getPage(pageNum);\r\n                var vp = page.getViewport({ scale: scale });\r\n                \/\/ Rendre dans un canvas temporaire\r\n                var tmpCanvas = document.createElement('canvas');\r\n                tmpCanvas.width = vp.width;\r\n                tmpCanvas.height = vp.height;\r\n                await page.render({ canvasContext: tmpCanvas.getContext('2d'), viewport: vp }).promise;\r\n\r\n                \/\/ \u2705 Crop marges blanches sur les 4 c\u00f4t\u00e9s (avec petite marge de s\u00e9curit\u00e9 de 4px)\r\n                var margins = detectWhiteMargins(tmpCanvas);\r\n                var cropTop    = Math.max(0, margins.top - 4);\r\n                var cropBottom = Math.min(tmpCanvas.height - 1, margins.bottom + 4);\r\n                var cropLeft   = Math.max(0, margins.left - 4);\r\n                var cropRight  = Math.min(tmpCanvas.width - 1, margins.right + 4);\r\n                var croppedH = cropBottom - cropTop + 1;\r\n                var croppedW = cropRight - cropLeft + 1;\r\n\r\n                \/\/ Canvas final crop\u00e9 sur les 4 c\u00f4t\u00e9s\r\n                var canvas = document.createElement('canvas');\r\n                canvas.width = croppedW;\r\n                canvas.height = croppedH;\r\n                canvas.getContext('2d').drawImage(tmpCanvas, cropLeft, cropTop, croppedW, croppedH, 0, 0, croppedW, croppedH);\r\n                canvas.style.cssText = 'display:block;width:100%;height:auto;margin:0;padding:0;';\r\n                container.appendChild(canvas);\r\n                console.log('\ud83d\udcc4 Page', pageNum, '- crop H:', cropTop, '\u2192', cropBottom, '\/ W:', cropLeft, '\u2192', cropRight, '(' + Math.round((cropLeft + (tmpCanvas.width - cropRight - 1)) \/ tmpCanvas.width * 100) + '% width trimmed)');\r\n            }\r\n\r\n            \/\/ \u2705 Ajuster la hauteur du popup \u00e0 la hauteur r\u00e9elle du contenu rendu\r\n            \/\/ (popup \u00e9tait calcul\u00e9 sur popupW*2.5*0.51 qui peut \u00eatre trop grand ou trop petit)\r\n            \/\/ v4.9ds (pdf-popup-fullwidth) : _maxPopupH calcul\u00e9 en fonction de la position\r\n            \/\/   r\u00e9elle de la popup (popupTop dans le viewport) + 20px de marge en bas, pour\r\n            \/\/   garantir que la popup ne d\u00e9passe jamais le viewport. Si le contenu est plus\r\n            \/\/   haut, le overflow-y:auto de scrollZone affiche la barre de d\u00e9filement.\r\n            if (_popup) {\r\n                var _totalH = 0;\r\n                container.querySelectorAll('canvas').forEach(function(c) { _totalH += c.height * (popupWidth \/ c.width); });\r\n                var _viewportH = window.innerHeight || document.documentElement.clientHeight || 600;\r\n                var _popupRectNow = _popup.getBoundingClientRect();\r\n                var _popupTopVisible = _popupRectNow.top;\r\n                \/\/ Plafond strict : viewport - top de la popup - 20px de marge en bas\r\n                var _maxPopupH = Math.max(200, _viewportH - _popupTopVisible - 20);\r\n                var _idealH = Math.min(_totalH + _headerH + 8, _maxPopupH);\r\n                _popup.style.height = _idealH + 'px';\r\n                console.log('\ud83d\udcd0 Popup redimensionn\u00e9:', Math.round(_idealH), 'px (contenu:', Math.round(_totalH), ', max:', Math.round(_maxPopupH), ')');\r\n            }\r\n        } catch (err) {\r\n            console.error('\u274c Erreur rendu PDF popup:', err);\r\n            container.innerHTML =\r\n                '<div style=\"padding:20px;text-align:center;color:#c00;font-size:12px;\">Erreur lors du chargement du document<\/div>';\r\n        }\r\n    },\r\n\r\n    \/\/ \u2705 Rendu HTML (Word) dans popup \u2014 avec CSS complet pour mammoth\r\n    renderHtmlInPopup(container, htmlContent) {\r\n        container.innerHTML = '';\r\n        \/\/ \u2705 Nettoyer les <p> vides en d\u00e9but\/fin produits par mammoth (marges parasites)\r\n        htmlContent = htmlContent\r\n            .replace(\/^(\\s*<p[^>]*>\\s*(<br\\s*\\\/?>\\s*)*<\\\/p>\\s*)+\/i, '')\r\n            .replace(\/(\\s*<p[^>]*>\\s*(<br\\s*\\\/?>\\s*)*<\\\/p>\\s*)+$\/i, '');\r\n        \/\/ \u2500\u2500 Styles pour le HTML g\u00e9n\u00e9r\u00e9 par mammoth (titres, paragraphes, listes, images) \u2500\u2500\r\n        var style = document.createElement('style');\r\n        style.textContent = [\r\n            '.via-html-popup-body { padding:20px 24px; background:#fff; color:#222; font-family:Georgia,\"Times New Roman\",serif; font-size:15px; line-height:1.7; }',\r\n            '.via-html-popup-body h1 { font-size:22px; font-weight:700; margin:0 0 12px; line-height:1.3; }',\r\n            '.via-html-popup-body h2 { font-size:18px; font-weight:700; margin:18px 0 8px; line-height:1.3; }',\r\n            '.via-html-popup-body h3 { font-size:15px; font-weight:700; margin:14px 0 6px; }',\r\n            '.via-html-popup-body p  { margin:0 0 10px; }',\r\n            '.via-html-popup-body p:first-child { margin-top:0; }',\r\n            '.via-html-popup-body strong, .via-html-popup-body b { font-weight:700; }',\r\n            '.via-html-popup-body em, .via-html-popup-body i { font-style:italic; }',\r\n            '.via-html-popup-body ul, .via-html-popup-body ol { margin:6px 0 10px 22px; padding:0; }',\r\n            '.via-html-popup-body li { margin-bottom:4px; }',\r\n            '.via-html-popup-body img { max-width:100%; height:auto; display:block; margin:10px auto; border-radius:4px; }',\r\n            '.via-html-popup-body table { width:100%; border-collapse:collapse; margin:10px 0; font-size:13px; }',\r\n            '.via-html-popup-body td, .via-html-popup-body th { border:1px solid #ddd; padding:6px 8px; vertical-align:top; }',\r\n            '.via-html-popup-body th { background:#f0f4f8; font-weight:700; }',\r\n            '.via-html-popup-body a { color:#225da9; text-decoration:underline; }'\r\n        ].join('');\r\n        container.appendChild(style);\r\n        var wrapper = document.createElement('div');\r\n        wrapper.className = 'via-html-popup-body';\r\n        wrapper.innerHTML = htmlContent;\r\n        container.appendChild(wrapper);\r\n    },\r\n    \r\n    async renderPDFInWindow(childWindow, container) {\r\n        container.innerHTML = '';\r\n        \r\n        const script = childWindow.document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.4.120\/pdf.min.js';\r\n        childWindow.document.head.appendChild(script);\r\n        \r\n        script.onload = async () => {\r\n            const pdfjsLib = childWindow.pdfjsLib;\r\n            const viewerContainer = childWindow.document.createElement('div');\r\n            viewerContainer.style.width = '100%';\r\n            viewerContainer.style.backgroundColor = 'white';\r\n            viewerContainer.style.position = 'relative';\r\n            container.appendChild(viewerContainer);\r\n            \r\n            const loadingTask = pdfjsLib.getDocument({data: window.pdfDataToTransfer});\r\n            const pdf = await loadingTask.promise;\r\n            \r\n            const firstPage = await pdf.getPage(1);\r\n            const viewport = firstPage.getViewport({scale: 1.5});\r\n            const pageHeight = viewport.height;\r\n            \r\n            const spacing = -15;\r\n            const totalHeight = (pageHeight * pdf.numPages) + (spacing * (pdf.numPages - 1));\r\n            viewerContainer.style.height = `${totalHeight}px`;\r\n            \r\n            for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\r\n                const page = await pdf.getPage(pageNum);\r\n                const canvas = childWindow.document.createElement('canvas');\r\n                const context = canvas.getContext('2d');\r\n                \r\n                canvas.width = viewport.width;\r\n                canvas.height = viewport.height;\r\n                \r\n                canvas.style.position = 'absolute';\r\n                canvas.style.left = '50%';\r\n                canvas.style.transform = 'translateX(-50%)';\r\n                canvas.style.top = `${(pageNum - 1) * (pageHeight + spacing)}px`;\r\n                \r\n                await page.render({\r\n                    canvasContext: context,\r\n                    viewport: viewport\r\n                }).promise;\r\n                \r\n                viewerContainer.appendChild(canvas);\r\n            }\r\n        };\r\n    },\r\n    \r\n    arrayBufferToBase64(buffer) {\r\n        let binary = '';\r\n        const bytes = new Uint8Array(buffer);\r\n        const len = bytes.byteLength;\r\n        for (let i = 0; i < len; i++) {\r\n            binary += String.fromCharCode(bytes[i]);\r\n        }\r\n        return window.btoa(binary);\r\n    },\r\n    \r\n    base64ToArrayBuffer(base64) {\r\n        const binaryString = window.atob(base64);\r\n        const len = binaryString.length;\r\n        const bytes = new Uint8Array(len);\r\n        for (let i = 0; i < len; i++) {\r\n            bytes[i] = binaryString.charCodeAt(i);\r\n        }\r\n        return bytes.buffer;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des \u00e9v\u00e9nements de drop et d'upload\r\n *\/\r\nconst DropHandler = {\r\n    async handleDrop(e) {\r\n        e.preventDefault();\r\n        \r\n        const $currentTarget = this.findDropTarget(e);\r\n        \r\n        if (!$currentTarget) {\r\n            console.log('No valid drop target found');\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 V\u00c9RIFIER SI C'EST UN D\u00c9PLACEMENT (pas besoin de v\u00e9rifier le format)\r\n        const isMoved = StateManager.get('FirstUploadFileorMoved') === 'Moved';\r\n        const dragstartRef = StateManager.get('dragstart_Commande_Emplacement_Page_Web');\r\n        let hasDragstartRef = false;\r\n        \r\n        if (dragstartRef) {\r\n            if (dragstartRef !== 'No') {\r\n                hasDragstartRef = true;\r\n            }\r\n        }\r\n        \r\n        let isDeplacementAnnonce = false;\r\n        if (isMoved) {\r\n            if (hasDragstartRef) {\r\n                isDeplacementAnnonce = true;\r\n            }\r\n        }\r\n        \r\n        if (isDeplacementAnnonce) {\r\n            console.log('\ud83d\udd04 D\u00e9placement d\u00e9tect\u00e9 - contr\u00f4le format ignor\u00e9');\r\n        } else {\r\n            \/\/ \u2705 v2.7.3 : Le format est d\u00e9duit de l'extension du fichier d\u00e9pos\u00e9\r\n            \/\/ \u2192 ne plus bloquer le d\u00e9p\u00f4t si aucun format s\u00e9lectionn\u00e9\r\n            console.log('\ud83d\udd04 Nouveau d\u00e9p\u00f4t \u2014 format sera d\u00e9duit du fichier, contr\u00f4le format ignor\u00e9');\r\n        }\r\n    \r\n        console.log(\"Drop at:\", $currentTarget);\r\n        \r\n        this.updateEmplacementState($currentTarget);\r\n        \r\n        const shouldProcess = this.shouldProcessDrop(e, $currentTarget);\r\n        \r\n        if (shouldProcess) {\r\n            await this.processFileDrop(e, $currentTarget);\r\n        } else {\r\n            this.processVideoDrop($currentTarget);\r\n        }\r\n        \r\n        DragDropManager.clearDataTransferFiles(e);\r\n    },\r\n    \r\n    findDropTarget(e) {\r\n        \/\/ \u2705 v2.4.3 : Si Ele0A est actif et le drop arrive sur Ele1A \u2192 rediriger vers Ele0A\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            var _ele0ADrop = document.querySelector('#Ele0A #drop_file_zone_achat');\r\n            if (_ele0ADrop) {\r\n                console.log('\ud83c\udfaf findDropTarget \u2014 PopUpChoice=Yes \u2192 cible forc\u00e9e: Ele0A');\r\n                return jQuery(_ele0ADrop);\r\n            }\r\n        }\r\n\r\n        let target = e.target.closest('#drop_file_zone_achat');\r\n        \r\n        if (!target) {\r\n            target = e.currentTarget.closest('#drop_file_zone_achat');\r\n            console.log('Drop target found 1');\r\n        }\r\n        \r\n        if (!target) {\r\n            target = jQuery(e.target).closest('#drop_file_zone_achat')[0];\r\n            console.log('Drop target found 2');\r\n        }\r\n        \r\n        return target ? jQuery(target) : null;\r\n    },\r\n    \r\n    updateEmplacementState($target) {\r\n        const espaceId = $target.closest('.droppable').attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', espaceId);\r\n        StateManager.set('Commande_Emplacement_Page_Web',\r\n            StateManager.buildEmplacementReference(espaceId));\r\n        \r\n        UIManager.updateEmplacementDisplay();\r\n        \r\n        console.log(\"Rank_Emplacement_Page_Web:\", StateManager.get('Rank_Emplacement_Page_Web'));\r\n        console.log(\"Droppable at:\", StateManager.get('Commande_Emplacement_Page_Web'));\r\n    },\r\n    \r\n    shouldProcessDrop(e, $target) {\r\n        const hasFiles = e.dataTransfer.files.length > 0;\r\n        const isRedactionnel = StateManager.get('Commande_Format_Transmis') === 'R\u00e9dactionnel';\r\n        const isValidBackground = window.getComputedStyle(\r\n            $target.closest('#UploadFileConteneur')[0]\r\n        ).backgroundColor !== 'rgba(0, 0, 0, 0)';\r\n        \r\n        return (hasFiles || isRedactionnel) ? isValidBackground : false;\r\n    },\r\n    \r\n    async processFileDrop(e, $target) {\r\n        jQuery('.MsgAdNotDisplayed').hide();\r\n        StateManager.setMultiple({\r\n            \"AdDisplayed\": 'Yes',\r\n            \/\/ \u2705 NE PLUS mettre sendDataToParentFlag ici - c'est la checkbox \"R\u00e9server\" qui le fera\r\n            \/\/ \"sendDataToParentFlag\": 'Yes'\r\n        });\r\n        \r\n        console.log('ajaxFileUpload_achat launched');\r\n        console.log(\"FirstUploadFileorMoved:\", StateManager.get('FirstUploadFileorMoved'));\r\n        \r\n        if (UIManager.isMobile()) {\r\n            $target = jQuery('#Ele1A').find('#drop_file_zone_achat');\r\n        }\r\n        \r\n        if (StateManager.get('Commande_Format_Transmis') === 'R\u00e9dactionnel') {\r\n            const fileURL = StateManager.get('FullPathAdFile');\r\n            const filename = fileURL.substring(fileURL.indexOf('_') + 1);\r\n            \r\n            try {\r\n                \/\/ \u2705 R\u00e9utiliser le File cach\u00e9 (\u00e9vite CORS cross-domain)\r\n                let file;\r\n                if (window._lastRedactionnelFile) {\r\n                    file = window._lastRedactionnelFile;\r\n                    console.log('\u267b\ufe0f R\u00e9utilisation du File cach\u00e9:', file.name, Math.round(file.size \/ 1024) + 'KB');\r\n                } else {\r\n                    file = await FileManager.urlToFile(fileURL, filename);\r\n                }\r\n                await UploadManager.handleFileUpload(file, $target);\r\n            } catch (error) {\r\n                console.error('Error:', error);\r\n            }\r\n        } else {\r\n            await UploadManager.handleFileUpload(e.dataTransfer.files[0], $target);\r\n        }\r\n    },\r\n    \r\n    processVideoDrop($target) {\r\n        const isValidBackground = window.getComputedStyle(\r\n            $target.closest('#UploadFileConteneur')[0]\r\n        ).backgroundColor !== 'rgba(0, 0, 0, 0)';\r\n        \r\n        if (!isValidBackground) {\r\n            jQuery('.MsgAdNotDisplayed').show();\r\n            StateManager.set(\"AdDisplayed\", 'No');\r\n            return;\r\n        }\r\n        \r\n        StateManager.set(\"AdDisplayed\", 'Yes');\r\n        \r\n        const objectUrl = StateManager.get('videoSrc');\r\n        const $previousDropZone = $('.drop_file_zone_achat_class').has(`video[src=\"${objectUrl}\"]`);\r\n        window.RestoreadSpaceTemplate($previousDropZone);\r\n        \r\n        const videoElement = jQuery('<video controls autoplay muted>').attr({\r\n            'src': objectUrl,\r\n            'max-width': '100%',\r\n            'max-height': '100%',\r\n            'draggable': 'true'\r\n        }).css({\r\n            'max-width': '100%',\r\n            'max-height': '100%'\r\n        });\r\n        \r\n        $target.empty().append(videoElement);\r\n        \r\n        $target.closest('#UploadFileConteneur').css({'background-color': '#FFFFFF00'});\r\n        \r\n        var _isViaPopupParentV = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n        $target.closest('.OrdiMobileConteneurClass')\r\n            .find('.RefEspacePublicitaire')\r\n            .html('R\u00e9f\u00e9rence de l\\'espace : ' + StateManager.get('Commande_Emplacement_Page_Web'))\r\n            .attr('id', 'RefEspacePublicitaire')\r\n            .each(function() {\r\n                var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                this.style.setProperty('display', 'block', 'important');\r\n                if (_isViaPopupParentV) { this.style.setProperty('margin-top', '4px', 'important'); }\r\n            });\r\n        (function() {\r\n            var _rankPosV = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            var _posLibV = PreviewRenderer._getPositionLibelle(_rankPosV);\r\n            if (_posLibV) {\r\n                $target.closest('.OrdiMobileConteneurClass')\r\n                    .find('.PositionEspacePublicitaireDeplacer')\r\n                    .text(_posLibV)\r\n                    .each(function() {\r\n                        var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                        if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                        this.style.setProperty('display', 'block', 'important');\r\n                        if (_isViaPopupParentV) { this.style.setProperty('margin-top', '4px', 'important'); }\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        $target.closest('.OrdiMobileConteneurClass').find('.AdUploadedTitle').show();\r\n        \r\n        $target.closest('.OrdiMobileConteneurClass')\r\n            .css({'top': '60px', 'margin-bottom': '80px'});\r\n        \r\n        $target.closest('.HTMLUploadfileConteneur')\r\n            .not('.AdUploadedTitle')\r\n            .css({\r\n                'top': '0px',\r\n                'margin-bottom': '0px',\r\n                'box-shadow': 'inset 0 0 0 2px #00FF19',  \/\/ \u2705 v2.4.5\r\n                'background-color': 'white'\r\n            });\r\n        \r\n        $target.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, .ChoisirEspacePublicitaire2ndLigne, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        jQuery('#MsgElementsCommandeValides, #MessageOptionsacompleterConteneur').hide();\r\n        \r\n        StateManager.set(\"FirstUploadFileorMoved\", 'Moved');\r\n        \r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server (au lieu d'envoyer automatiquement)\r\n        FormatUIManager.updateReserverCheckboxState($target.closest('.droppable'));\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'explorateur de fichiers\r\n *\/\r\nconst FileExplorer = {\r\n    isRunning: false,\r\n\r\n    open(e) {\r\n        if (this.isRunning) {\r\n            return;\r\n        }\r\n        \r\n        const $element = jQuery(e.target).closest('.droppable');\r\n        \r\n        \/\/ \u2705 v2.7.3 : Le format sera d\u00e9duit de l'extension du fichier s\u00e9lectionn\u00e9\r\n        \/\/ \u2192 ne plus bloquer l'ouverture du s\u00e9lecteur si aucun format n'est encore choisi\r\n        this.isRunning = true;\r\n        \r\n        const rankId = $element.attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n        StateManager.set('Commande_Emplacement_Page_Web',\r\n            StateManager.buildEmplacementReference(rankId));\r\n        \r\n        UIManager.updateEmplacementDisplay();\r\n        \r\n        console.log(\"Drop at:\", StateManager.get('Commande_Emplacement_Page_Web'));\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        \r\n        const $currentTarget = jQuery(e.target).closest('#drop_file_zone_achat');\r\n        const $fileInput = jQuery('#selectfile_achat');\r\n        \r\n        console.log(\"currentTarget\", $currentTarget);\r\n        \r\n        $fileInput.off('change');\r\n        $fileInput.off('click');\r\n        \r\n        const onChange = (event) => {\r\n            this.isRunning = false;\r\n            $fileInput.off('change', onChange);\r\n            \r\n            \/\/ \u2705 NE PLUS mettre sendDataToParentFlag ici\r\n            \/\/ StateManager.set(\"sendDataToParentFlag\", 'Yes');\r\n            \r\n            if ($fileInput[0].files.length > 0) {\r\n                UploadManager.handleFileUpload($fileInput[0].files[0], $currentTarget);\r\n            }\r\n        };\r\n        \r\n        const onClick = () => {\r\n            $fileInput.off('click', onClick);\r\n            $fileInput.on('focus', function onFocus() {\r\n                setTimeout(() => {\r\n                    if (!$fileInput.val()) {\r\n                        FileExplorer.isRunning = false;\r\n                    }\r\n                }, 200);\r\n                $fileInput.off('focus', onFocus);\r\n            });\r\n        };\r\n        \r\n        $fileInput.on('change', onChange);\r\n        $fileInput.on('click', onClick);\r\n        \r\n        \/\/ \u2705 Contr\u00f4le format imm\u00e9diat \u2014 avant d'ouvrir le s\u00e9lecteur de fichiers\r\n        var _$dropGuard = $currentTarget.closest('.droppable');\r\n        if (_$dropGuard.length) {\r\n            var _hasFmtNow = false;\r\n            _$dropGuard.find('.EspPubFormatContainer').each(function() {\r\n                if (jQuery(this).hasClass('FormatIdCreation')) return;\r\n                if (jQuery(this).hasClass('FormatIdPopUp')) return;\r\n                var _bg = this.style.backgroundColor || '';\r\n                if (_bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white') { _hasFmtNow = true; return false; }\r\n                var _fmtEl = this.querySelector('.EspPubFormat');\r\n                if (_fmtEl) { var _col = _fmtEl.style.color || ''; if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') { _hasFmtNow = true; return false; } }\r\n            });\r\n            if (!_hasFmtNow) {\r\n                UIManager.showFormatError($currentTarget, 'Merci de s\u00e9lectionner un format d\\'annonce avant de t\u00e9l\u00e9charger votre fichier');\r\n                FormatUIManager.flashTitle($currentTarget);\r\n                FileExplorer.isRunning = false;\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v2.7.3 : R\u00e9initialiser la valeur avant d'ouvrir le s\u00e9lecteur\r\n        \/\/ Sans ce reset, certains navigateurs (Safari mobile) ne d\u00e9clenchent pas\r\n        \/\/ l'\u00e9v\u00e9nement 'change' si un fichier avait d\u00e9j\u00e0 \u00e9t\u00e9 s\u00e9lectionn\u00e9 pr\u00e9c\u00e9demment\r\n        $fileInput.val('');\r\n        $fileInput.click();\r\n        \r\n        setTimeout(() => {\r\n            this.isRunning = false;\r\n        }, 300);\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion du reset d'annonce\r\n *\/\r\nconst AdResetHandler = {\r\n    handle(e) {\r\n        e.preventDefault();\r\n        console.log(\"CroixResetAnnonce click\");\r\n        \r\n        \/\/ Reset l'\u00e9tat EnvoiUlterieur\r\n        StateManager.set('EnvoiUlterieur', 'false');\r\n        \r\n        \/\/ \u2705 v1.19.6 : Reset FileReceived mais garder le format s\u00e9lectionn\u00e9\r\n        StateManager.set('FileReceived', 'No');\r\n        \r\n        const $element = jQuery(e.currentTarget);\r\n        const $droppable = $element.closest('.droppable');\r\n        const resetRef = StateManager.buildEmplacementReference($droppable.attr('id'));\r\n        \r\n        console.log(\"Reset_Commande_Emplacement_Page_Web:\", resetRef);\r\n        \r\n        \/\/ \u2705 Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \r\n        var _rankForDel = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n        \/\/ \u2705 Bug 10 : envoyer via postMessage si dans une iframe (pas seulement mode=popup)\r\n        \/\/   AchatEspaceCall=Yes uniquement pour mode=popup \u2014 pour les sites pays standards\r\n        \/\/   (iframe r\u00e9gie, non-popup), window.processdataDelAd n'existe pas dans le contexte iframe\r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes' || window.self !== window.top) {\r\n            MessageManager.sendDelAdToParent({\r\n                Commande_Emplacement_Page_Web: resetRef,\r\n                Rank_Emplacement_Page_Web: _rankForDel,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n        } else {\r\n            window.processdataDelAd({\r\n                Commande_Emplacement_Page_Web: resetRef,\r\n                Rank_Emplacement_Page_Web: _rankForDel,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n        }\r\n        \r\n        \/\/ \u2705 v1.16.0 : Appeler RestoreadSpaceTemplateLocal directement (accessible dans ce scope)\r\n        RestoreadSpaceTemplateLocal(e.currentTarget);\r\n        \r\n        window.FonctionCroixResetAnnonce(e.currentTarget);\r\n        \r\n        \/\/ \u2705 v2.1.1 : Sauf popup\r\n        var formatWasSelected = sessionStorage.getItem('FormatSelect') \r\n            || sessionStorage.getItem('Commande_Format_Transmis')\r\n            || $droppable.data('kitFormatSelect');\r\n        \r\n        if (sessionStorage.getItem('PopUpChoice') !== 'Yes' ? (formatWasSelected || sessionStorage.getItem('Formatchoisi') === 'Yes') : false) {\r\n            sessionStorage.setItem('Formatchoisi', 'Yes');\r\n            console.log('\u2705 Formatchoisi forc\u00e9 \u00e0 Yes apr\u00e8s reset');\r\n        }\r\n        \r\n        \/\/ \u2705 v1.19.6 : Remettre \u00e0 jour le titre format et r\u00e9afficher la checkbox R\u00e9server\r\n        setTimeout(() => {\r\n            FormatUIManager.updateTitleColor($droppable);\r\n            FormatUIManager.updateReserverCheckboxState($droppable);\r\n            \r\n            \/\/ \u2705 R\u00e9afficher le .ReserverContainer statique\r\n            $droppable.find('.ReserverContainer').show();\r\n        }, 150);\r\n    }\r\n};\r\n\r\n\/**\r\n * \u2705 Template pour r\u00e9initialisation des espaces publicitaires (IFRAME)\r\n *\/\r\nvar adSpaceTemplatesLocal = {};\r\n\r\nfunction saveAdSpaceTemplateLocal() {\r\n    var saved = 0;\r\n    jQuery('.droppable').each(function() {\r\n        var droppableId = jQuery(this).attr('id');\r\n        if (!droppableId) return;\r\n        \r\n        \/\/ \u2705 Ne jamais sauvegarder Ele0A (clone temporaire popup, pas un template d'origine)\r\n        if (droppableId === 'Ele0A') return;\r\n        \r\n        \/\/ \u2705 Ne pas r\u00e9-\u00e9craser un template d\u00e9j\u00e0 sauvegard\u00e9\r\n        if (adSpaceTemplatesLocal[droppableId]) return;\r\n        \r\n        \/\/ \u2705 v2.3.4 : Ne pas sauvegarder si template d\u00e9j\u00e0 connu (\u00e9tat propre sauvegard\u00e9)\r\n        \/\/ Le guard hasAd est supprim\u00e9 \u2014 on veut capturer le template le plus t\u00f4t possible\r\n        \/\/ Si le template existe d\u00e9j\u00e0, on ne le r\u00e9\u00e9crase pas\r\n        \/\/ (le guard anti-\u00e9crasement if (adSpaceTemplatesLocal[droppableId]) return; suffit)\r\n        \r\n        var $content = jQuery(this).find('.OrdiMobileConteneurClass').first();\r\n        if ($content.length > 0) {\r\n            adSpaceTemplatesLocal[droppableId] = $content.clone(true, true);\r\n            saved++;\r\n        }\r\n    });\r\n    if (saved > 0) {\r\n        console.log('\u2705 Templates espaces pub sauvegard\u00e9s:', saved, 'espaces -', Object.keys(adSpaceTemplatesLocal));\r\n        return true;\r\n    }\r\n    return false;\r\n}\r\n\r\nfunction RestoreadSpaceTemplateLocal(element) {\r\n    console.log('\ud83d\udd04 RestoreadSpaceTemplateLocal', element);\r\n    \r\n    var $element = jQuery(element);\r\n    var $droppable = $element.closest('.droppable');\r\n    var droppableId = $droppable.attr('id');\r\n    \r\n    \/\/ \u2705 FIX Ele0A : pas de template sauvegard\u00e9 (clone temporaire popup)\r\n    \/\/ \u2192 pas de remplacement DOM, reset visuel direct sur le contenu existant\r\n    var _isEle0A = (droppableId === 'Ele0A');\r\n\r\n    \/\/ \u2705 v1.19.3 : Chercher le template sp\u00e9cifique \u00e0 CET espace (sauf Ele0A)\r\n    if (!_isEle0A) {\r\n        if (!droppableId || !adSpaceTemplatesLocal[droppableId]) {\r\n            if (!saveAdSpaceTemplateLocal()) {\r\n                console.error('\u274c Template non disponible');\r\n                return false;\r\n            }\r\n            if (!adSpaceTemplatesLocal[droppableId]) {\r\n                console.error('\u274c Template non trouv\u00e9 pour', droppableId);\r\n                return false;\r\n            }\r\n        }\r\n    }\r\n    \r\n    var adSpaceElement = $droppable.find('.OrdiMobileConteneurClass').first();\r\n    \r\n    if (!adSpaceElement || !adSpaceElement.length) {\r\n        console.error('\u274c OrdiMobileConteneurClass non trouv\u00e9');\r\n        return false;\r\n    }\r\n    \r\n    var newElement;\r\n    if (_isEle0A) {\r\n        \/\/ Ele0A : pas de clone \u2014 on remet en \u00e9tat le contenu existant directement\r\n        newElement = adSpaceElement;\r\n        console.log('\u2705 [Ele0A] reset visuel direct (pas de template clone)');\r\n        \/\/ \u2705 Vider le contenu du dropzone et restaurer le HTML par d\u00e9faut\r\n        \/\/ (le replaceWith n'ayant pas lieu, l'image d\u00e9pos\u00e9e et le fond blanc restent sinon)\r\n        var $dz0A = $droppable.find('#drop_file_zone_achat');\r\n        $dz0A.empty().html(\r\n            '<div id=\"drag_upload_file_achat\">' +\r\n                '<p class=\"UploadIci\" style=\"color:#FB5E2A;font-weight:600;\">Ici glisser \u2013 d\u00e9poser ou<br>t\u00e9l\u00e9charger une annonce<\/p>' +\r\n            '<\/div>'\r\n        );\r\n        \/\/ Restaurer le fond bleu #9FC5F3 et retirer les styles d'annonce upload\u00e9e\r\n        $droppable.find('#UploadFileConteneur').css({\r\n            'background-color': '#9FC5F3',\r\n            'border': '',\r\n            'box-sizing': ''\r\n        });\r\n        $droppable.find('.HTMLUploadfileConteneur').css({\r\n            'box-shadow': '',\r\n            'background-color': '',\r\n            'border': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': '',\r\n            'overflow': '',\r\n            'height': '',\r\n            'max-height': ''\r\n        });\r\n        \/\/ Masquer la croix et le titre d'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle, #CroixResetAnnonce, .CroixResetAnnonceContainer, .DeplaceAnnonce').hide();\r\n        $droppable.removeAttr('data-via-ad-loaded').removeAttr('data-from-miniature');\r\n        console.log('\u2705 [Ele0A] dropzone vid\u00e9 + fond restaur\u00e9');\r\n    } else {\r\n        \/\/ \u2705 v1.19.3 : Cloner le template SP\u00c9CIFIQUE \u00e0 cet espace (pas le premier)\r\n        newElement = adSpaceTemplatesLocal[droppableId].clone(true, true);\r\n        console.log('\u2705 Template utilis\u00e9 pour', droppableId);\r\n        adSpaceElement.replaceWith(newElement);\r\n        \/\/ \u2705 v2.4.12 : Effacer data-via-ad-loaded (sinon selectEspaceActif masque .ReserverContainer)\r\n        $droppable.removeAttr('data-via-ad-loaded').removeAttr('data-from-miniature');\r\n        if (window.outerWidth < 1000) {\r\n            newElement.css({'margin-top': '-25px', 'bottom': '0px'});\r\n        }\r\n    }\r\n    \r\n    \/\/ D\u00e9cocher la case Envoi diff\u00e9r\u00e9 si elle existe\r\n    newElement.find('input[name*=\"EnvoiUlterieur\"]').prop('checked', false);\r\n    \r\n    \/\/ \u2705 D\u00e9cocher la checkbox \"R\u00e9server\"\r\n    newElement.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked', false);\r\n    \r\n    \/\/ Reset l'\u00e9tat EnvoiUlterieur\r\n    StateManager.set('EnvoiUlterieur', 'false');\r\n    \r\n    \/\/ \u2705 v1.19.3 : R\u00e9initialiser TOUS les styles inline des conteneurs parents modifi\u00e9s pendant le d\u00e9p\u00f4t\r\n    if ($droppable.length) {\r\n        \/\/ \u2705 v2.0.9 : Restaurer les marges de l'algorithme de positionnement (sauvegard\u00e9es par styleUploadedAd)\r\n        var origMt = $droppable.data('orig-mt');\r\n        $droppable.css({\r\n            'margin-top': (origMt !== undefined) ? origMt + 'px' : '',\r\n            'margin-bottom': '',\r\n            'margin-left': '',\r\n            'margin-right': ''\r\n        });\r\n        \/\/ \u2705 v2.0.11 : Cleanup event namespace docpreview\r\n        $droppable.off('click.docpreview');\r\n        \r\n        \/\/ Reset .OrdiMobileConteneurClass margins\r\n        var $container = $droppable.find('.OrdiMobileConteneurClass');\r\n        var origMb = $container.data('orig-mb');\r\n        $container.css({\r\n            'margin-top': '',\r\n            'margin-bottom': (origMb !== undefined) ? origMb + 'px' : ''\r\n        });\r\n        \r\n        \/\/ Reset .HTMLUploadfileConteneur (set by styleUploadedAd: box-shadow inset, background-color:white)\r\n        $droppable.find('.HTMLUploadfileConteneur').css({\r\n            'border': '',\r\n            'box-shadow': '',\r\n            'outline': '',\r\n            'outline-offset': '',\r\n            'background-color': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': '',\r\n            'overflow': '',\r\n            'padding': '',\r\n            'position': ''\r\n        });\r\n        $droppable.find('.via-green-border-overlay').remove();\r\n        $droppable.find('.via-position-label').remove();\r\n        \/\/ \u2705 v2.7.3 : Nettoyage header\/footer\/wrapper inject\u00e9s par _buildAdOverlay\r\n        var $_ufcReset = $droppable.find('.HTMLUploadfileConteneur');\r\n        \/\/ Sortir HTMLUploadfileConteneur du wrapper avant de supprimer le wrapper\r\n        var $_wrapReset = $droppable.find('.via-ad-wrapper');\r\n        if ($_wrapReset.length) {\r\n            $_wrapReset.before($_ufcReset);\r\n            $_wrapReset.remove();\r\n        }\r\n        $droppable.find('.via-ad-header').remove();\r\n        $droppable.find('.via-ad-footer').remove();\r\n        $_ufcReset.css({'display': '', 'flex-direction': '', 'overflow': '', 'box-shadow': '', 'background-color': '', 'margin': '', 'padding': ''});\r\n        \r\n        \/\/ \u2705 Restaurer pointer-events sur OrdiMobileConteneurClass et ses enfants\r\n        $droppable.find('.OrdiMobileConteneurClass').css('pointer-events', '');\r\n        $droppable.find('#CroixResetAnnonce').css({'pointer-events': '', 'position': '', 'z-index': ''});\r\n        \/\/ \u2705 v2.4.5 : Reset margin-right Ele0A (pos\u00e9 par adjustMobileLayout)\r\n        var _croixContReset = $droppable.find('.CroixResetAnnonceContainer')[0];\r\n        if (_croixContReset) { _croixContReset.style.removeProperty('margin-right'); _croixContReset.style.removeProperty('margin-top'); }\r\n        $droppable.find('#PopUpMessageAchattest').css('pointer-events', '');\r\n        \r\n        \/\/ \u2705 v2.0.9 : Restaurer le scale(1.4) sur .UploadFileConteneur (r\u00e9duit \u00e0 scale(1) par styleUploadedAd sur mobile)\r\n        $droppable.find('.UploadFileConteneur').css({\r\n            'transform': '',\r\n            'transform-origin': ''\r\n        });\r\n        \r\n        \/\/ Reset #UploadFileConteneur - Restaurer le fond bleu #9FC5F3 + retirer liser\u00e9 envoi diff\u00e9r\u00e9\r\n        $droppable.find('#UploadFileConteneur').css({\r\n            'width': '',\r\n            'background-color': '#9FC5F3',\r\n            'border': '',\r\n            'box-sizing': ''\r\n        });\r\n        \r\n        \/\/ Reset .ToBeHidden (set by adjustDesktopLayout: top:105px, min-height:300px + overflow mobile)\r\n        $droppable.closest('.ToBeHidden').css({\r\n            'top': '',\r\n            'min-height': '',\r\n            'overflow': ''\r\n        });\r\n        \r\n        \/\/ Reset overflow sur le parent du droppable (set by adjustDesktopLayout)\r\n        $droppable.parent().css('overflow', '');\r\n        \r\n        \/\/ \u2705 v1.19.5 : Gestion device-specific pour les textes\r\n        var isDesktop = window.outerWidth >= 1000;\r\n        \r\n        if (isDesktop) {\r\n            \/\/ Desktop : cacher les textes mobiles, afficher UploadIci\r\n            $droppable.find('.TexteMobile').hide();\r\n            $droppable.find('.TexteMobileAnnonce').hide();\r\n            $droppable.find('.TexteMobileAjoutAnnonce').hide();\r\n            $droppable.find('.UploadIci').show();\r\n        } else {\r\n            \/\/ Mobile : afficher TexteMobileAnnonce\r\n            $droppable.find('.TexteMobileAnnonce').show();\r\n        }\r\n        \r\n        \/\/ R\u00e9-afficher les \u00e9l\u00e9ments masqu\u00e9s pendant l'upload\r\n        $droppable.find('.PositionEspacePublicitaireContainer').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').show();\r\n        $droppable.find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur > div > .elementor-widget-text-editor').show();\r\n        $droppable.find('.AdDroppedTextNotDisplayed').hide();\r\n        \/\/ \u2705 v2.4.5 : Ele0A + PopUpChoice=Yes \u2192 ne pas r\u00e9-afficher le titre si format d\u00e9j\u00e0 s\u00e9lectionn\u00e9\r\n        \/\/ v4.9ds : condition _skipTitre retir\u00e9e \u00e0 la restauration. Sur les sites pays\r\n        \/\/   (PopUpChoice=Yes) avec Ele0A, le titre Format n'\u00e9tait PAS r\u00e9affich\u00e9 apr\u00e8s\r\n        \/\/   suppression d'annonce \u2192 l'utilisateur ne voyait plus la consigne pour\r\n        \/\/   res\u00e9lectionner un format. \u00c0 la restauration, on force toujours le show().\r\n        $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n        $droppable.find('.SelectionFormatTitre').show();\r\n        $droppable.find('span.ClassHdpCdp, .ClassRefEsp').show();\r\n        $droppable.find('.EspPubFormatMainContainer').show();\r\n        $droppable.find('.EspPubFormatListe').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireClass').show();\r\n        $droppable.find('.GlisserDeposerConteneur').show();\r\n        $droppable.find('.OUClass').show();\r\n        $droppable.find('.PositionReference').show();\r\n        $droppable.find('.ClassHdpCdp').show();\r\n        $droppable.find('.ClassRefEsp').show();\r\n        $droppable.find('.EnvoiUlterieurTexte').show();\r\n        $droppable.find('.EnvoiUlterieurContainer').show();\r\n        \r\n        \/\/ \u2705 v1.19.5 : R\u00e9afficher le .ReserverContainer (bouton Elementor statique)\r\n        $droppable.find('.ReserverContainer').show();\r\n        newElement.find('.ReserverContainer').show();\r\n        \r\n        \/\/ Masquer les \u00e9l\u00e9ments sp\u00e9cifiques \u00e0 l'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle, #CroixResetAnnonce, .CroixResetAnnonceContainer, .DeplaceAnnonce').hide();\r\n        $droppable.find('#CroixResetAnnonce').css({'position': '', 'z-index': ''});\r\n        $droppable.find('.RefEspacePublicitaire').hide();\r\n        \r\n        \/\/ Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 v1.19.6 : Restaurer le format s\u00e9lectionn\u00e9 visuellement\r\n        \/\/ Chercher le format dans plusieurs sources possibles\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') \r\n            || sessionStorage.getItem('Commande_Format_Transmis')\r\n            || $droppable.data('kitFormatSelect')\r\n            || '';\r\n        \r\n        var formatchoisi = sessionStorage.getItem('Formatchoisi');\r\n        console.log('\ud83d\udcd0 Format \u00e0 restaurer:', formatSelect, '| Formatchoisi:', formatchoisi);\r\n        \r\n        \/\/ D'abord reset tous les formats visuellement (sur newElement ET $droppable)\r\n        var $allFormats = newElement.find('.EspPubFormatContainer').add($droppable.find('.EspPubFormatContainer'));\r\n        $allFormats.css({\r\n            'background-color': '',\r\n            'border': ''\r\n        });\r\n        newElement.find('.EspPubFormat').add($droppable.find('.EspPubFormat')).css({\r\n            'color': ''\r\n        });\r\n        \r\n        \/\/ Si un format \u00e9tait s\u00e9lectionn\u00e9 (via sessionStorage OU visuellement avant)\r\n        \/\/ \u2705 v2.1.1 : Sauf popup sur Ele0A (g\u00e9r\u00e9 s\u00e9par\u00e9ment via setTimeout)\r\n        \/\/ \u2705 v2.6 : Pour Ele1A\/2A\/3A, restaurer le format m\u00eame en mode popup (pop-up = format sous-jacent identique)\r\n        var _isEle0APopup = ($droppable.attr('id') === 'Ele0A' ? sessionStorage.getItem('PopUpChoice') === 'Yes' : false);\r\n        if ((formatSelect || formatchoisi === 'Yes') ? !_isEle0APopup : false) {\r\n            var formatFound = false;\r\n            \r\n            if (formatSelect) {\r\n                \/\/ \u2705 v2.1.1 : Normaliser via NFD (plus fiable que les replace manuels)\r\n                var formatLower = formatSelect.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase().replace(\/ \/g, '');\r\n                \r\n                \/\/ Appliquer sur newElement ET $droppable pour \u00eatre s\u00fbr\r\n                var $allContainers = newElement.find('.EspPubFormatContainer').add($droppable.find('.EspPubFormatContainer'));\r\n                \r\n                $allContainers.each(function() {\r\n                    var className = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    \r\n                    if (className.includes(formatLower)) {\r\n                        jQuery(this).css({'background-color': '#ffffff'});\r\n                        jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        formatFound = true;\r\n                        \/\/ \u2705 v2.4.12 : M\u00e9moriser le format restaur\u00e9 (lu par selectEspaceActif pour re-surligner apr\u00e8s reset global)\r\n                        $droppable.attr('data-restored-format', formatSelect);\r\n                        console.log('\u2705 Format restaur\u00e9 visuellement:', formatSelect, '- classe:', this.className);\r\n                    }\r\n                });\r\n            }\r\n            \r\n            \/\/ \u2705 IMPORTANT : Pr\u00e9server Formatchoisi = Yes pour permettre l'upload\r\n            sessionStorage.setItem('Formatchoisi', 'Yes');\r\n            console.log('\u2705 Formatchoisi maintenu \u00e0 Yes');\r\n            \r\n            \/\/ Basculer les titres format (sur newElement ET $droppable)\r\n            newElement.find('.SelectionFormatTitre').hide();\r\n            newElement.find('.SelectionFormatTitreBlanc').show();\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n        }\r\n    }\r\n    \r\n    \/\/ \u2705 v1.19.2 : Relancer InitLoadedPage pour recalculer positions et tailles\r\n    setTimeout(function() {\r\n        if (typeof window.InitLoadedPage === 'function') {\r\n            window.InitLoadedPage();\r\n        }\r\n        \r\n        \/\/ \u2705 v1.19.6 : R\u00e9attacher les MutationObservers sur les formats\r\n        if (typeof FormatUIManager !== 'undefined' ? FormatUIManager.observeFormatChanges : false) {\r\n            FormatUIManager.observeFormatChanges();\r\n        }\r\n    }, 100);\r\n    \r\n    console.log('\u2705 Espace publicitaire r\u00e9initialis\u00e9');\r\n    return true;\r\n}\r\n\r\n\/\/ \u2705 Exposer pour appel depuis yearbook-media.js (suppression popup mode=popup)\r\nwindow.RestoreadSpaceTemplateLocal = RestoreadSpaceTemplateLocal;\r\n\/\/ \u2705 Alias pour appel depuis processVideoDrop (drag d'annonce)\r\nwindow.RestoreadSpaceTemplate = RestoreadSpaceTemplateLocal;\r\n\r\nwindow.verifierAffichageMsgSelectEspaceLocal = function() {\r\n    const formatChoisi = StateManager.get('Formatchoisi') === 'Yes';\r\n    const fileReceived = StateManager.get('FileReceived');\r\n    const envoiUlterieur = StateManager.get('EnvoiUlterieur');\r\n    \r\n    var dateDebut, dateFin, pays;\r\n    try {\r\n        dateDebut = window.parent.$('#form-field-DebutCampagne').val();\r\n        dateFin = window.parent.$('#form-field-FinCampagne').val();\r\n        pays = window.parent.$('#form-field-Nationalite_Societe').val();\r\n    } catch(e) {\r\n        jQuery('#MsgSelectEspace').hide();\r\n        return;\r\n    }\r\n    \r\n    if (!dateDebut || !dateFin || !pays || !formatChoisi) {\r\n        jQuery('#MsgSelectEspace').hide();\r\n        return;\r\n    }\r\n    \r\n    if (fileReceived !== 'Yes') {\r\n        if (envoiUlterieur !== 'true') {\r\n            jQuery('#MsgSelectEspace').show();\r\n        } else {\r\n            jQuery('#MsgSelectEspace').hide();\r\n        }\r\n    } else {\r\n        jQuery('#MsgSelectEspace').hide();\r\n    }\r\n};\r\n\r\n\/\/ Sauvegarder les templates au chargement (apr\u00e8s rendu complet Elementor)\r\njQuery(document).ready(function() {\r\n    \/\/ \u2705 v1.19.3 : D\u00e9lai augment\u00e9 + double sauvegarde pour garantir les bonnes dimensions\r\n    setTimeout(saveAdSpaceTemplateLocal, 3000);\r\n    setTimeout(saveAdSpaceTemplateLocal, 6000);\r\n    \/\/ \u2705 v2.3.4 : Sauvegarde imm\u00e9diate d\u00e8s que la page est pr\u00eate dans l'iframe\r\n    \/\/ (avant toute interaction utilisateur)\r\n    setTimeout(saveAdSpaceTemplateLocal, 100);\r\n    setTimeout(saveAdSpaceTemplateLocal, 500);\r\n});\r\n\r\n\/\/ \u2705 v2.3.4 : Forcer sauvegarde \u00e0 la r\u00e9ception de elementsRemoved (espaces visibles + vierges)\r\nwindow.addEventListener('message', function(e) {\r\n    if (e.data ? e.data.type === 'elementsRemoved' : false) {\r\n        \/\/ Les espaces sont vierges \u00e0 ce stade \u2192 sauvegarder imm\u00e9diatement\r\n        setTimeout(saveAdSpaceTemplateLocal, 50);\r\n        setTimeout(saveAdSpaceTemplateLocal, 300);\r\n    }\r\n});\r\n\r\n\/**\r\n * Fonction helper pour le d\u00e9p\u00f4t r\u00e9dactionnel\r\n *\/\r\nfunction RedactionnelDepose() {\r\n    jQuery('#Tariftobedisplayed').html('-');\r\n    jQuery('#TarifDataStep3').html('\u00e0 choisir').css({'color': '#FB5E2A'});\r\n    jQuery('#FormatDataStep3').html('\u00e0 choisir').css({'color': '#FB5E2A'});\r\n    jQuery('#ListePaysDirect, #ListeThemeDirect, .ListeArticles, #PageAfficheeMessage').hide();\r\n    StateManager.set(\"PositionAnnonceSelection\", 'Yes');\r\n}\r\n\r\n\/**\r\n * Exposition des fonctions globales n\u00e9cessaires\r\n *\/\r\nwindow.uploadFile_achat = (e) => DropHandler.handleDrop(e);\r\nwindow.fileExplorer_achat = (e) => FileExplorer.open(e);\r\nwindow.ajaxFileUpload_achat = (fileObj, dropZone) => UploadManager.handleFileUpload(fileObj, dropZone);\r\nwindow.ActivatesendDataToParent = (dropZone) => UploadManager.activateSendDataToParent(dropZone);\r\n\/\/ \u2705 v2.3.4 : Exposer FormatUIManager pour appel depuis Entete.txt (selectEspaceActif)\r\nwindow.FormatUIManagerRef = FormatUIManager;\r\n\r\n\/\/ \u2705 v2.6 : Pb 12 \u2014 Restaurer l'affichage d'une annonce depuis son URL (sans re-upload)\r\n\/\/ Appel\u00e9 par Entete.txt > selectEspaceActif quand fileReceived=Yes mais annonce non affich\u00e9e\r\nwindow.restoreAdFromUrl = function(rankId, adUrl, formatLabel) {\r\n    console.log('\ud83d\udd04 [restoreAdFromUrl] appel | rank:', rankId, '| url:', adUrl);\r\n    if (!rankId || !adUrl) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] rank ou url manquant'); return; }\r\n    var $droppable = jQuery('#' + rankId);\r\n    if (!$droppable.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] #' + rankId + ' introuvable dans le DOM'); return; }\r\n    if ($droppable.attr('data-via-ad-loaded') === 'true') { console.log('\u23ed\ufe0f [restoreAdFromUrl] d\u00e9j\u00e0 charg\u00e9, skip'); return; }\r\n    \/\/ \u2705 v2.6 : Guard anti-concurrent \u2014 si un probe est d\u00e9j\u00e0 en cours, ne pas relancer\r\n    if ($droppable.attr('data-via-ad-restoring') === 'true') {\r\n        console.log('\u23ed\ufe0f [restoreAdFromUrl] probe en cours pour #' + rankId + ', skip | url demand\u00e9e:', adUrl);\r\n        return;\r\n    }\r\n    $droppable.attr('data-via-ad-restoring', 'true');\r\n    var $dropZone = $droppable.find('#drop_file_zone_achat');\r\n    if (!$dropZone.length) {\r\n        $droppable.removeAttr('data-via-ad-restoring');\r\n        console.warn('\u26a0\ufe0f [restoreAdFromUrl] #drop_file_zone_achat introuvable dans #' + rankId, '| enfants:', jQuery('#' + rankId).children().length);\r\n        return;\r\n    }\r\n    var _ext = (adUrl.split('?')[0].split('.').pop() || '').toLowerCase();\r\n    var _fileType = FileManager.getFileType(_ext);\r\n    var _fmt = (formatLabel || '').trim() || 'Document';\r\n    console.log('\ud83d\udce6 [restoreAdFromUrl] probe d\u00e9marr\u00e9 | ext:', _ext, '| type:', _fileType);\r\n    \/\/ \u2705 Ne pas \u00e9craser Rank_Emplacement_Page_Web \/ FullPathAdFile \/ FileReceived dans StateManager\r\n    \/\/ \u2192 \u00e9vite la race condition avec un upload en cours (finalizeUpload lirait le mauvais rank)\r\n    \/\/ \u2705 _isAdRestoration positionn\u00e9 juste avant styleUploadedAd dans chaque callback probe\r\n    \/\/ \u2192 \u00e9vite qu'un finalizeUpload concurrent ne remette le flag \u00e0 'No' avant le callback\r\n    if (_fileType === 'image') {\r\n        \/\/ \u2705 Probe: v\u00e9rifier que l'image est r\u00e9ellement accessible avant de marquer l'espace\r\n        \/\/ \u2705 _isAdRestoration positionn\u00e9 juste avant styleUploadedAd (pas avant le probe)\r\n        \/\/ \u2192 \u00e9vite qu'un finalizeUpload concurrent ne remette le flag \u00e0 'No' entre-temps\r\n        var _probe = new Image();\r\n        _probe.onload = function() {\r\n            console.log('\u2705 [restoreAdFromUrl] probe1 onload | url:', adUrl);\r\n            jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n            var $_dz = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n            if (!$_dz.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe1 onload: dropZone disparu dans #' + rankId); return; }\r\n            StateManager.set('_isAdRestoration', 'Yes');\r\n            StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n            \/\/ \u2705 Tronc commun : reset DOM propre puis flux standard\r\n            RestoreadSpaceTemplateLocal(jQuery('#' + rankId).find('.OrdiMobileConteneurClass')[0]);\r\n            var $_dz = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n            if (!$_dz.length) { return; }\r\n            PreviewRenderer.renderImage(adUrl, $_dz);\r\n            window._dropFromMiniature = true; \/\/ skip margin-top 150px\r\n            UIManager.updateAfterSuccessfulUpload($_dz);\r\n            window._dropFromMiniature = false;\r\n            UIManager.finalizeAdDisplay($_dz);\r\n            \/\/ v2.9 : si ViaPopupProcessAchat present, repositionner elements hors-lisere dans Ele0A\r\n            if (rankId === 'Ele0A') {\r\n                if ((sessionStorage.getItem('_ViaPopupOpen') === 'Yes')) {\r\n                    setTimeout(function() {\r\n                        var $_drp0A = jQuery('#Ele0A');\r\n                        \/\/ DeplaceAnnonceSubContainer : forcer margin-top positif\r\n                        var $_das = $_drp0A.find('.DeplaceAnnonceSubContainer');\r\n                        if ($_das.length) { $_das[0].style.setProperty('margin-top', '5px', 'important'); }\r\n                        \/\/ PositionEspacePublicitaireDeplacer + RefEspacePublicitaire\r\n                        $_drp0A.find('.PositionEspacePublicitaireDeplacer').each(function() {\r\n                            this.style.setProperty('margin-top',  '5px', 'important');\r\n                        });\r\n                        $_drp0A.find('.RefEspacePublicitaire').each(function() {\r\n                            this.style.setProperty('margin-top',  '5px', 'important');\r\n                            this.style.setProperty('margin-right', '5px', 'important');\r\n                        });\r\n                        \/\/ CroixResetAnnonceContainer : forcer a l'interieur du lisere\r\n                        var $_crc = $_drp0A.find('.CroixResetAnnonceContainer');\r\n                        if ($_crc.length) {\r\n                            $_crc[0].style.setProperty('top',    '5px', 'important');\r\n                            $_crc[0].style.setProperty('right',  '5px', 'important');\r\n                            $_crc[0].style.setProperty('margin', '0px', 'important');\r\n                        }\r\n                        console.log('[restoreAdFromUrl] v2.9 : Ele0A elements repositionnes (ViaPopupProcessAchat)');\r\n                    }, 200);\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.6 : Repositionner Ele0A apr\u00e8s restauration (page peinte, rect valide)\r\n            if (rankId === 'Ele0A') {\r\n                if (typeof positionEle0AOverEle1A === 'function') {\r\n                    requestAnimationFrame(function() {\r\n                        var $_e1W = jQuery('.ToBeHidden').has('[id=\"Ele1A\"]').not(jQuery('.ToBeHidden').has('#Ele0A')).first();\r\n                        var $_e1U = $_e1W.find('#UploadFileConteneur').first();\r\n                        var _lr = $_e1U.length ? $_e1U[0].getBoundingClientRect() : null;\r\n                        window._ele1ASnapRect = (_lr ? (_lr.width > 0 || _lr.height > 0) : false) ? _lr : null;\r\n                        positionEle0AOverEle1A(window._ele1ASnapRect);\r\n                        console.log('[restoreAdFromUrl] repo Ele0A liveRect:', window._ele1ASnapRect ? Math.round(window._ele1ASnapRect.top)+'\/'+Math.round(window._ele1ASnapRect.left) : 'null->fallback');\r\n                    });\r\n                }\r\n            }\r\n            console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e | rank:', rankId, '| type: image | url:', adUrl);\r\n        };\r\n        _probe.onerror = function() {\r\n            console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe1 onerror | url:', adUrl);\r\n            \/\/ \u2705 Fallback : essayer wp-admin\/uploads si wp-content\/uploads \u00e9choue\r\n            \/\/ (fichier physiquement dans wp-admin\/uploads mais URL stock\u00e9e avec wp-content)\r\n            var _altUrl = adUrl.replace('\/wp-content\/uploads\/', '\/wp-admin\/uploads\/');\r\n            if (_altUrl !== adUrl) {\r\n                var _probe2 = new Image();\r\n                _probe2.onload = function() {\r\n                    console.log('\u2705 [restoreAdFromUrl] probe2 onload | alt url:', _altUrl);\r\n                    jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n                    var $_dz2 = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n                    if (!$_dz2.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe2 onload: dropZone disparu dans #' + rankId); return; }\r\n                    StateManager.set('_isAdRestoration', 'Yes');\r\n                    StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n                    \/\/ \u2705 Tronc commun : reset DOM propre puis flux standard\r\n                    RestoreadSpaceTemplateLocal(jQuery('#' + rankId).find('.OrdiMobileConteneurClass')[0]);\r\n                    var $_dz2 = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n                    if (!$_dz2.length) { return; }\r\n                    PreviewRenderer.renderImage(_altUrl, $_dz2);\r\n                    window._dropFromMiniature = true;\r\n                    UIManager.updateAfterSuccessfulUpload($_dz2);\r\n                    window._dropFromMiniature = false;\r\n                    UIManager.finalizeAdDisplay($_dz2);\r\n                    \/\/ \u2705 v2.6 : Repositionner Ele0A apr\u00e8s restauration (page peinte, rect valide)\r\n                    if (rankId === 'Ele0A') {\r\n                        if (typeof positionEle0AOverEle1A === 'function') {\r\n                            requestAnimationFrame(function() {\r\n                                var $_e1W = jQuery('.ToBeHidden').has('[id=\"Ele1A\"]').not(jQuery('.ToBeHidden').has('#Ele0A')).first();\r\n                                var $_e1U = $_e1W.find('#UploadFileConteneur').first();\r\n                                var _lr = $_e1U.length ? $_e1U[0].getBoundingClientRect() : null;\r\n                                window._ele1ASnapRect = (_lr ? (_lr.width > 0 || _lr.height > 0) : false) ? _lr : null;\r\n                                positionEle0AOverEle1A(window._ele1ASnapRect);\r\n                                console.log('[restoreAdFromUrl] repo Ele0A liveRect:', window._ele1ASnapRect ? Math.round(window._ele1ASnapRect.top)+'\/'+Math.round(window._ele1ASnapRect.left) : 'null->fallback');\r\n                    \/\/ Fix fond gris UFC apres restauration\r\n                    var $_ufc0R = jQuery('#Ele0A').find('#UploadFileConteneur');\r\n                    if ($_ufc0R.length) { $_ufc0R.css('background-color', 'white'); console.log('[restoreAdFromUrl] UFC Ele0A -> blanc'); }\r\n                            });\r\n                        }\r\n                    }\r\n                    console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e (alt) | rank:', rankId, '| url:', _altUrl);\r\n                };\r\n                _probe2.onerror = function() {\r\n                    jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n                    console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe2 onerror AUSSI | alt url:', _altUrl, '| rank:', rankId);\r\n                };\r\n                _probe2.src = _altUrl;\r\n            } else {\r\n                console.warn('\u26a0\ufe0f [restoreAdFromUrl] image inaccessible \u2192 skip | rank:', rankId);\r\n            }\r\n        };\r\n        _probe.src = adUrl;\r\n        return;\r\n    } else if (_fileType === 'video') {\r\n        PreviewRenderer.renderVideo(adUrl, adUrl.split('\/').pop(), $dropZone);\r\n    } else {\r\n        PreviewRenderer.renderDocumentPreviewNoImage($dropZone, _fmt, '');\r\n    }\r\n    StateManager.set('_isAdRestoration', 'Yes');\r\n    UIManager.styleUploadedAd($dropZone);\r\n    if (UIManager.isMobile()) { UIManager.adjustMobileLayout($dropZone); }\r\n    console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e | rank:', rankId, '| type:', _fileType, '| url:', adUrl);\r\n};\r\n\r\n\r\n\/**\r\n * Initialisation de l'application\r\n *\/\r\njQuery(document).ready(() => {\r\n    StateManager.init();\r\n    ScrollHelper.init();\r\n    DragDropManager.init();\r\n    FormatUIManager.init();\r\n\r\n    \/\/ v4.9ca : Centrage texte dans EspPubFormatContainer via styles inline\r\n    \/\/ (le CSS inject\u00e9 dans head est \u00e9cras\u00e9 par Elementor \u2014 styles inline = priorit\u00e9 absolue)\r\n    \/\/ v4.9cc : reset complet des variables CSS Elementor + height:100% cha\u00eene compl\u00e8te\r\n    function _centerEspPubFormats() {\r\n        jQuery('.EspPubFormatContainer').each(function() {\r\n            \/\/ Container <a> lui-m\u00eame\r\n            this.style.setProperty('display', 'flex', 'important');\r\n            this.style.setProperty('flex-direction', 'row', 'important');\r\n            this.style.setProperty('align-items', 'center', 'important');\r\n            this.style.setProperty('justify-content', 'center', 'important');\r\n            this.style.setProperty('padding', '0', 'important');\r\n            this.style.setProperty('padding-top', '0', 'important');\r\n            this.style.setProperty('padding-bottom', '0', 'important');\r\n            this.style.setProperty('padding-left', '0', 'important');\r\n            this.style.setProperty('padding-right', '0', 'important');\r\n            \/\/ Variables Elementor (padding-block-*, --padding-*)\r\n            this.style.setProperty('--padding-block-start', '0px', 'important');\r\n            this.style.setProperty('--padding-block-end', '0px', 'important');\r\n            this.style.setProperty('--padding-inline-start', '0px', 'important');\r\n            this.style.setProperty('--padding-inline-end', '0px', 'important');\r\n            this.style.setProperty('--padding-top', '0px', 'important');\r\n            this.style.setProperty('--padding-bottom', '0px', 'important');\r\n            this.style.setProperty('row-gap', '0', 'important');\r\n\r\n            \/\/ Widget .EspPubFormat\r\n            var _widget = this.querySelector('.EspPubFormat');\r\n            if (_widget) {\r\n                _widget.style.setProperty('display', 'flex', 'important');\r\n                _widget.style.setProperty('flex-direction', 'row', 'important');\r\n                _widget.style.setProperty('align-items', 'center', 'important');\r\n                _widget.style.setProperty('justify-content', 'center', 'important');\r\n                _widget.style.setProperty('width', '100%', 'important');\r\n                _widget.style.setProperty('height', '100%', 'important');\r\n                _widget.style.setProperty('margin', '0', 'important');\r\n                _widget.style.setProperty('padding', '0', 'important');\r\n                _widget.style.setProperty('padding-top', '0', 'important');\r\n                _widget.style.setProperty('padding-bottom', '0', 'important');\r\n            }\r\n\r\n            \/\/ .elementor-widget-container\r\n            var _wc = this.querySelector('.elementor-widget-container');\r\n            if (_wc) {\r\n                _wc.style.setProperty('display', 'flex', 'important');\r\n                _wc.style.setProperty('align-items', 'center', 'important');\r\n                _wc.style.setProperty('justify-content', 'center', 'important');\r\n                _wc.style.setProperty('width', '100%', 'important');\r\n                _wc.style.setProperty('height', '100%', 'important');\r\n                \/\/ v4.9dr : padding-top 1px pour centrage vertical\r\n                \/\/ v4.9ds : padding-top 3px sur Communiqu\u00e9\/Interview\/Parrainage (desktop)\r\n                \/\/          (lettres descendantes q\/j abaissent le baseline visuel)\r\n                \/\/          Mobile : 1px partout (centrage flex suffit \u00e0 cette \u00e9chelle)\r\n                var _isMob = UIManager.isMobile();\r\n                var _hasDescender = this.classList.contains('FormatIdCommunique')\r\n                                 || this.classList.contains('FormatIdInterview')\r\n                                 || this.classList.contains('FormatIdParrainage');\r\n                var _padTop = _isMob ? '1px' : (_hasDescender ? '3px' : '1px');\r\n                _wc.style.setProperty('padding-top', _padTop, 'important');\r\n                _wc.style.setProperty('padding-left', '0', 'important');\r\n                _wc.style.setProperty('padding-right', '0', 'important');\r\n                _wc.style.setProperty('padding-bottom', '0px', 'important');\r\n                _wc.style.setProperty('margin', '0', 'important');\r\n                _wc.style.setProperty('margin-top', '0', 'important');\r\n                _wc.style.setProperty('margin-bottom', '0', 'important');\r\n                _wc.style.setProperty('text-align', 'center', 'important');\r\n                _wc.style.setProperty('line-height', '1', 'important');\r\n            }\r\n\r\n            \/\/ Paragraphes \u00e9ventuels g\u00e9n\u00e9r\u00e9s par Elementor text-editor\r\n            jQuery(this).find('p').each(function() {\r\n                this.style.setProperty('margin', '0', 'important');\r\n                this.style.setProperty('padding', '0', 'important');\r\n                this.style.setProperty('line-height', 'inherit', 'important');\r\n            });\r\n        });\r\n    }\r\n    \/\/ Lancer imm\u00e9diatement + apr\u00e8s un d\u00e9lai (Elementor peut appliquer ses styles apr\u00e8s le ready)\r\n    _centerEspPubFormats();\r\n    setTimeout(_centerEspPubFormats, 500);\r\n    setTimeout(_centerEspPubFormats, 1500);\r\n\r\n    \/\/ v4.9cb : FormatIdPopUp ne doit s'afficher QUE sur le site r\u00e9gie (via-regie-iframe).\r\n    \/\/ Sur les sites pays, masquer tous les items .FormatIdPopUp.\r\n    function _hidePopupFormatOnPays() {\r\n        var _isRegie = document.documentElement.classList.contains('via-regie-iframe');\r\n        if (_isRegie) return; \/\/ site r\u00e9gie \u2192 on laisse le Pop-up visible\r\n        jQuery('.FormatIdPopUp').each(function() {\r\n            this.style.setProperty('display', 'none', 'important');\r\n        });\r\n    }\r\n    _hidePopupFormatOnPays();\r\n    setTimeout(_hidePopupFormatOnPays, 500);\r\n    setTimeout(_hidePopupFormatOnPays, 1500);\r\n    \r\n    if (UIManager.isMobile()) {\r\n        UIManager.initMobileUI();\r\n    } else {\r\n        UIManager.initDesktopUI();\r\n    }\r\n    \r\n    jQuery(document).on('click', '#CroixResetAnnonce', (e) => AdResetHandler.handle(e));\r\n\r\n    \/\/ \ud83d\udd27 Fonction globale r\u00e9utilisable : force height:auto + padding\/margin 0 sur le\r\n    \/\/    .elementor-widget-text-editor qui CONTIENT l'OMC. Elementor impose une height\r\n    \/\/    explicite (ex: 297px) \u00e0 certains breakpoints \u00e9troits qui d\u00e9cale le wrapper vers le bas.\r\n    \/\/    Appel\u00e9e au d\u00e9p\u00f4t (depuis _buildAdOverlay) et \u00e0 chaque resize.\r\n    window._viaOverrideTextEditor = function(omcEl) {\r\n        if (!omcEl) return;\r\n        \/\/ Chercher l'anc\u00eatre .elementor-widget-text-editor (pas un enfant)\r\n        var editor = omcEl.closest('.elementor-widget-text-editor');\r\n        if (!editor) return;\r\n        editor.style.setProperty('height', 'auto', 'important');\r\n        editor.style.setProperty('min-height', '0', 'important');\r\n        editor.style.setProperty('padding', '0', 'important');\r\n        editor.style.setProperty('margin', '0', 'important');\r\n        var editorCont = editor.querySelector(':scope > .elementor-widget-container');\r\n        if (editorCont) {\r\n            editorCont.style.setProperty('height', 'auto', 'important');\r\n            editorCont.style.setProperty('min-height', '0', 'important');\r\n            editorCont.style.setProperty('padding', '0', 'important');\r\n            editorCont.style.setProperty('margin', '0', 'important');\r\n        }\r\n    };\r\n\r\n    \/\/ \ud83d\udd0d DIAG : log sur resize pour voir l'\u00e9tat du wrapper quand la fen\u00eatre change\r\n    \/\/ Handler de resize : override text-editor + enforcement + algo + scale\r\n    \/\/ v4.9by : EXCLURE les wrappers \u00e0 l'int\u00e9rieur de #Ele0A (popup fixed) du traitement\r\n    \/\/         Le scroll iOS fire resize \u2192 ce handler retirait position\/left\/transform sur le wrapper\r\n    \/\/         via-ad-wrapper DANS Ele0A \u2192 wrapper r\u00e9tr\u00e9cissait visuellement\r\n    var _diagResizeTimer = null;\r\n    window.addEventListener('resize', function() {\r\n        clearTimeout(_diagResizeTimer);\r\n        _diagResizeTimer = setTimeout(function() {\r\n            var $_wrappers = jQuery('.via-ad-wrapper').filter(function() {\r\n                \/\/ v4.9by : ne pas toucher aux wrappers dans Ele0A (popup fixed, g\u00e9r\u00e9 ailleurs)\r\n                return jQuery(this).closest('#Ele0A').length === 0;\r\n            });\r\n            if (!$_wrappers.length) return;\r\n            var _isRegieIframe = document.documentElement.classList.contains('via-regie-iframe');\r\n            console.log('[RESIZE] handler fire \u2014 inner=' + window.innerWidth + ' outer=' + window.outerWidth + ' isMobile=' + UIManager.isMobile() + ' wrappers=' + $_wrappers.length + ' isRegie=' + _isRegieIframe);\r\n\r\n            \/\/ \u00c9TAPE 1 : enforcement + reset transform\/mb sur tous les wrappers\r\n            $_wrappers.each(function() {\r\n                var $_drop = jQuery(this).closest('.droppable');\r\n                var $_omc = $_drop.find('.OrdiMobileConteneurClass').first();\r\n                var $_ufc = jQuery(this).find('.HTMLUploadfileConteneur').first();\r\n                var _dropEl = $_drop[0], _omcEl = $_omc[0], _ufcEl = $_ufc[0];\r\n\r\n                var _wAvantMt = this.style.marginTop || '(none)';\r\n                var _dAvantMt = _dropEl ? (_dropEl.style.marginTop || '(none)') : '?';\r\n                var _rAvant = _dropEl ? Math.round(_dropEl.getBoundingClientRect().top) : '?';\r\n\r\n                if (_omcEl) { window._viaOverrideTextEditor(_omcEl); }\r\n\r\n                \/\/ \u26a0\ufe0f CLEANUP DVM\/MOBILE : d\u00e8s que inner<1000, nettoyer les paddings\/min-height\r\n                \/\/    pos\u00e9s en plein \u00e9cran sur article\/secteur qui cr\u00e9ent de grands espaces blancs.\r\n                \/\/    Ce bloc s'ex\u00e9cute AVANT le gate UIManager.isMobile() pour couvrir \u00e0 la fois\r\n                \/\/    DVM (outer>=1000, inner<1000) et mobile vrai (outer<1000).\r\n                if (!_isRegieIframe) {\r\n                    if (window.innerWidth < 1000) {\r\n                        \/\/ Reset d'abord les marges n\u00e9gatives pour pouvoir mesurer la VRAIE hauteur\r\n                        this.style.setProperty('margin-top', '0px', 'important');\r\n                        this.style.setProperty('margin-bottom', '0px', 'important');\r\n                        \/\/ Mesure hauteur r\u00e9elle (apr\u00e8s reset)\r\n                        var _dvmWrapperH = Math.round(this.getBoundingClientRect().height);\r\n                        var _dvmWrapperW = Math.round(this.getBoundingClientRect().width);\r\n                        var _dvmDropW = _dropEl ? Math.round(_dropEl.getBoundingClientRect().width) : 0;\r\n                        \/\/ Force min fallback 200px si mesure inf\u00e9rieure\r\n                        if (_dvmWrapperH < 150) { _dvmWrapperH = 200; }\r\n                        \/\/ \u2705 Reset margin-top\/bottom sur OrdiMobileConteneurClass (pose +65px en mobile)\r\n                        if (_omcEl) {\r\n                            _omcEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _omcEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                        }\r\n                        \/\/ \u2705 Reset margin sur HTMLUploadfileConteneur (pose -85px en mobile)\r\n                        if (_ufcEl) {\r\n                            _ufcEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _ufcEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                        }\r\n                        if (_dropEl) {\r\n                            _dropEl.style.setProperty('padding-top', '0px', 'important');\r\n                            _dropEl.style.setProperty('padding-bottom', '0px', 'important');\r\n                            \/\/ PAS de min-height \u2014 laisser la hauteur naturelle du wrapper d\u00e9finir\r\n                            _dropEl.style.removeProperty('min-height');\r\n                            _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _dropEl.style.setProperty('margin-bottom', '40px', 'important');\r\n                            _dropEl.style.setProperty('height', 'auto', 'important');\r\n                            _dropEl.style.removeProperty('box-sizing');\r\n                            \/\/ \u2705 Centrer via flex sur le droppable\r\n                            _dropEl.style.setProperty('display', 'flex', 'important');\r\n                            _dropEl.style.setProperty('flex-direction', 'column', 'important');\r\n                            _dropEl.style.setProperty('justify-content', 'flex-start', 'important');\r\n                            _dropEl.style.setProperty('align-items', 'center', 'important');\r\n                        }\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM DIMS wrapperW=' + _dvmWrapperW + ' dropW=' + _dvmDropW);\r\n                        \/\/ \ud83d\udd0d DIAG chain anc\u00eatres du droppable avec leurs TOP et margins\r\n                        var _chainInfo = [];\r\n                        var _curAnc = _dropEl;\r\n                        var _chainCount = 0;\r\n                        while (_curAnc) {\r\n                            if (_chainCount >= 10) break;\r\n                            var _rCur = _curAnc.getBoundingClientRect();\r\n                            var _csCur = getComputedStyle(_curAnc);\r\n                            var _tagC = _curAnc.tagName + (_curAnc.id ? '#' + _curAnc.id : '') + '.' + ((_curAnc.className || '').toString().split(' ').slice(0, 2).join('.') || '');\r\n                            _chainInfo.push(_tagC + ' top=' + Math.round(_rCur.top) + ' mt=' + _csCur.marginTop + ' pt=' + _csCur.paddingTop);\r\n                            _curAnc = _curAnc.parentElement;\r\n                            _chainCount++;\r\n                        }\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM CHAIN from droppable up: ' + _chainInfo.join(' | '));\r\n                        var _ancD = _dropEl;\r\n                        var _ancDCount = 0;\r\n                        var _ancDDebug = [];\r\n                        while (_ancD) {\r\n                            if (_ancDCount >= 8) break;\r\n                            _ancD = _ancD.parentElement;\r\n                            _ancDCount++;\r\n                            if (!_ancD) break;\r\n                            var _inPt = _ancD.style.paddingTop;\r\n                            var _inPb = _ancD.style.paddingBottom;\r\n                            var _inMh = _ancD.style.minHeight;\r\n                            if (_inPt || _inPb || _inMh) {\r\n                                _ancDDebug.push(_ancD.tagName + '.' + ((_ancD.className || '').toString().split(' ').slice(0, 2).join('.') || '') + ' [pt=' + _inPt + ' pb=' + _inPb + ' mh=' + _inMh + ']');\r\n                            }\r\n                            _ancD.style.setProperty('padding-top', '0px', 'important');\r\n                            _ancD.style.setProperty('padding-bottom', '0px', 'important');\r\n                            _ancD.style.setProperty('min-height', '0px', 'important');\r\n                            if (_ancD.nextElementSibling) { break; }\r\n                        }\r\n                        if (_ancDDebug.length) {\r\n                            console.log('[DIAG resize] \u2699\ufe0f cleanup DVM\/mobile (inner<1000, outer=' + window.outerWidth + ') : wrapper.mt\/mb=25px droppable.min-h=' + (_dvmWrapperH + 60) + ' mb=40 | anc\u00eatres reset \u2192 ' + _ancDDebug.join(' | '));\r\n                        } else {\r\n                            console.log('[DIAG resize] \u2699\ufe0f cleanup DVM\/mobile (inner<1000, outer=' + window.outerWidth + ') : wrapper.mt\/mb=25px droppable.min-h=' + (_dvmWrapperH + 60) + ' mb=40 | wrapperH mesur\u00e9=' + _dvmWrapperH);\r\n                        }\r\n                        \/\/ \u2705 DVM\/mobile : margin-top n\u00e9gatif compense le gros vide entre le contenu\r\n                        \/\/    au-dessus et le droppable (structure Elementor pose ~260px de vide).\r\n                        this.style.setProperty('margin-top', '-120px', 'important');\r\n                        this.style.setProperty('margin-bottom', '25px', 'important');\r\n                        this.style.removeProperty('position');\r\n                        this.style.removeProperty('left');\r\n                        this.style.removeProperty('transform');\r\n                        this.style.setProperty('width', '100%', 'important');\r\n                        this.style.setProperty('max-width', '100%', 'important');\r\n                        this.style.removeProperty('margin-left');\r\n                        this.style.removeProperty('margin-right');\r\n                        this.style.setProperty('display', 'flex', 'important');\r\n                        \/\/ \ud83d\udd0d DIAG POST\r\n                        var _postCS = getComputedStyle(this);\r\n                        var _postRect = this.getBoundingClientRect();\r\n                        var _dropRect2 = _dropEl ? _dropEl.getBoundingClientRect() : null;\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM POST-APPLY | computed w=' + _postCS.width + ' | wrapper RECT left=' + Math.round(_postRect.left) + ' w=' + Math.round(_postRect.width) + ' top=' + Math.round(_postRect.top) + ' | drop RECT left=' + (_dropRect2 ? Math.round(_dropRect2.left) : '?') + ' w=' + (_dropRect2 ? Math.round(_dropRect2.width) : '?') + ' top=' + (_dropRect2 ? Math.round(_dropRect2.top) : '?'));\r\n                    }\r\n                }\r\n\r\n                if (!UIManager.isMobile()) {\r\n                    if (!_isRegieIframe) {\r\n                        \/\/ \ud83d\udd27 ENFORCEMENT JS (version qui marchait) : neutralise marges n\u00e9gatives\r\n                        \/\/    h\u00e9rit\u00e9es du mode mobile + restaure UFC + appelle l'algo inter-espaces.\r\n\r\n                        \/\/ \ud83d\udd0d DIAG : snapshot \u00e9tat AVANT\r\n                        var _cs = getComputedStyle(this);\r\n                        var _rect = this.getBoundingClientRect();\r\n                        var _dropCS = _dropEl ? getComputedStyle(_dropEl) : null;\r\n                        var _dropR = _dropEl ? _dropEl.getBoundingClientRect() : null;\r\n                        var _ufcCS = _ufcEl ? getComputedStyle(_ufcEl) : null;\r\n                        var _ufcR = _ufcEl ? _ufcEl.getBoundingClientRect() : null;\r\n                        console.log('[DIAG resize] inner=' + window.innerWidth + ' outer=' + window.outerWidth\r\n                            + ' | htmlClass=\"' + document.documentElement.className + '\"'\r\n                            + ' | bodyClass=\"' + document.body.className + '\"');\r\n                        console.log('[DIAG resize] WRAPPER inline mt=' + this.style.marginTop + ' mb=' + this.style.marginBottom\r\n                            + ' | computed mt=' + _cs.marginTop + ' mb=' + _cs.marginBottom\r\n                            + ' | rect top=' + Math.round(_rect.top) + ' h=' + Math.round(_rect.height) + ' w=' + Math.round(_rect.width));\r\n                        if (_dropEl) {\r\n                            console.log('[DIAG resize] DROPPABLE#' + _dropEl.id\r\n                                + ' inline mt=' + _dropEl.style.marginTop + ' mb=' + _dropEl.style.marginBottom\r\n                                + ' | computed mt=' + _dropCS.marginTop + ' mb=' + _dropCS.marginBottom\r\n                                + ' | rect top=' + Math.round(_dropR.top) + ' h=' + Math.round(_dropR.height));\r\n                        }\r\n                        if (_ufcEl) {\r\n                            console.log('[DIAG resize] UFC inline h=' + _ufcEl.style.height + ' maxH=' + _ufcEl.style.maxHeight\r\n                                + ' | computed h=' + _ufcCS.height + ' maxH=' + _ufcCS.maxHeight\r\n                                + ' | rect top=' + Math.round(_ufcR.top) + ' h=' + Math.round(_ufcR.height));\r\n                        }\r\n\r\n                        \/\/ 1. Neutraliser les marges n\u00e9gatives h\u00e9rit\u00e9es du mode mobile (wrapper + droppable)\r\n                        var _mt = parseFloat(this.style.marginTop) || 0;\r\n                        if (_mt < 0) {\r\n                            this.style.setProperty('margin-top', '0px', 'important');\r\n                            console.log('[DIAG resize] \u2699\ufe0f wrapper : margin-top forc\u00e9 \u00e0 0 (\u00e9tait ' + _mt + ')');\r\n                        }\r\n                        if (_dropEl) {\r\n                            var _dmt = parseFloat(_dropEl.style.marginTop) || 0;\r\n                            if (_dmt < 0) {\r\n                                _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                                console.log('[DIAG resize] \u2699\ufe0f droppable : margin-top forc\u00e9 \u00e0 0 (\u00e9tait ' + _dmt + ')');\r\n                            }\r\n                        }\r\n\r\n                        \/\/ 2. Restaurer max-height UFC + force height auto\r\n                        \/\/ v4.9ds : Ele0A=260, Ele1A+=280 ; ne pas toucher au dropZone (g\u00e9r\u00e9 par _applyDzMinH)\r\n                        if (_ufcEl) {\r\n                            var _isE0AResize = $_drop.attr('id') === 'Ele0A';\r\n                            _ufcEl.style.removeProperty('min-height');\r\n                            _ufcEl.style.setProperty('max-height', _isE0AResize ? '260px' : '280px', 'important');\r\n                            _ufcEl.style.setProperty('height', 'auto', 'important');\r\n                        }\r\n                        \/\/ v4.9ds : ne PAS toucher au dropZone height\/min-height ici\r\n                        \/\/          (sinon \u00e9crase ce que _applyDzMinH a pos\u00e9 sur Ele1A+)\r\n                        \/\/          Au lieu de \u00e7a, on rejoue _applyDzMinH si elle est expos\u00e9e sur le droppable\r\n                        if (_dropEl ? _dropEl._applyDzMinH : false) {\r\n                            try { _dropEl._applyDzMinH('resize'); } catch(_e) { console.warn('[resize] _applyDzMinH a throw', _e); }\r\n                        }\r\n                        var _imgInner = _ufcEl ? _ufcEl.querySelector('img, video') : null;\r\n                        if (_imgInner) {\r\n                            \/\/ v4.9ds : aligner max-height image sur la hauteur r\u00e9elle du dropZone\r\n                            \/\/   pour \u00e9viter le crop par overflow:hidden du wrapper.\r\n                            \/\/   IMPORTANT : utiliser offsetHeight (qui n'est PAS affect\u00e9 par\r\n                            \/\/   le zoom\/transform\/scale d'un parent) plut\u00f4t que\r\n                            \/\/   getBoundingClientRect().height (qui retourne les pixels \u00e9cran\r\n                            \/\/   r\u00e9els apr\u00e8s transformations). Le rect.h peut \u00eatre r\u00e9duit par\r\n                            \/\/   un zoom:55% appliqu\u00e9 \u00e0 un anc\u00eatre via _viaRunInterEspaces, ce\r\n                            \/\/   qui produirait une max-height absurde.\r\n                            var _dzInnerH = _ufcEl ? _ufcEl.querySelector('#drop_file_zone_achat') : null;\r\n                            var _dzH = _dzInnerH ? _dzInnerH.offsetHeight : 0;\r\n                            var _imgMaxHRsz = _dzH > 50 ? Math.max(0, Math.floor(_dzH - 5)) : 250;\r\n                            _imgInner.style.setProperty('max-height', _imgMaxHRsz + 'px', 'important');\r\n                        }\r\n\r\n                        \/\/ 3. Gater l'algo + scale : n\u00e9cessite inner >= 1000 (viewport CSS desktop).\r\n                        \/\/    En \"desktop version mobile\" (outer>=1000 mais inner<1000), le layout CSS\r\n                        \/\/    est en mode mobile \u2014 l'algo se tromperait.\r\n                        if (window.innerWidth < 1000) {\r\n                            \/\/ \u26a0\ufe0f DVM : nettoyer les paddings\/min-height pos\u00e9s pr\u00e9c\u00e9demment\r\n                            \/\/    (article\/secteur) qui cr\u00e9aient de grands espaces blancs.\r\n                            \/\/    removeProperty ne bat pas un !important inline \u2192 forcer \u00e0 0 !important.\r\n                            if (_dropEl) {\r\n                                _dropEl.style.setProperty('padding-top', '0px', 'important');\r\n                                _dropEl.style.setProperty('padding-bottom', '0px', 'important');\r\n                                _dropEl.style.setProperty('min-height', '0px', 'important');\r\n                                _dropEl.style.removeProperty('box-sizing');\r\n                            }\r\n                            \/\/ Nettoyer TOUS les anc\u00eatres qui ont un padding ou min-height inline\r\n                            \/\/ (pas seulement ceux avec classList e-con-inner\/elementor-element)\r\n                            var _ancC = _dropEl;\r\n                            var _ancCCount = 0;\r\n                            var _ancDebug = [];\r\n                            while (_ancC) {\r\n                                if (_ancCCount >= 8) break;\r\n                                _ancC = _ancC.parentElement;\r\n                                _ancCCount++;\r\n                                if (!_ancC) break;\r\n                                var _ancTag = _ancC.tagName + '.' + ((_ancC.className || '').toString().split(' ').slice(0, 2).join('.') || '');\r\n                                var _hasInlineStyle = false;\r\n                                var _inlinePt = _ancC.style.paddingTop;\r\n                                var _inlinePb = _ancC.style.paddingBottom;\r\n                                var _inlineMh = _ancC.style.minHeight;\r\n                                if (_inlinePt || _inlinePb || _inlineMh) {\r\n                                    _hasInlineStyle = true;\r\n                                    _ancDebug.push(_ancTag + ' [pt=' + _inlinePt + ' pb=' + _inlinePb + ' mh=' + _inlineMh + ']');\r\n                                }\r\n                                \/\/ Reset inline style dans tous les cas\r\n                                _ancC.style.setProperty('padding-top', '0px', 'important');\r\n                                _ancC.style.setProperty('padding-bottom', '0px', 'important');\r\n                                _ancC.style.setProperty('min-height', '0px', 'important');\r\n                                if (_ancC.nextElementSibling) { break; }\r\n                            }\r\n                            \/\/ \u2705 DVM : ~25px d'espace blanc au-dessus et en-dessous de l'annonce.\r\n                            \/\/    Appliqu\u00e9 sur le wrapper (margin-top\/bottom) qui \u00e9tait \u00e0 -150 au d\u00e9p\u00f4t.\r\n                            this.style.setProperty('margin-top', '25px', 'important');\r\n                            this.style.setProperty('margin-bottom', '25px', 'important');\r\n                            console.log('[DIAG resize] \u2699\ufe0f DVM (inner<1000) : wrapper.mt\/mb=25px | anc\u00eatres avec style inline: ' + (_ancDebug.length ? _ancDebug.join(' | ') : 'aucun'));\r\n                        }\r\n\r\n                        if (window.innerWidth >= 1000) {\r\n                            \/\/ 3a. Reset transform AVANT la mesure (hauteur naturelle)\r\n                            this.style.removeProperty('transform');\r\n                            this.style.removeProperty('margin-bottom');\r\n                            if (_dropEl) { _dropEl.style.removeProperty('margin-bottom'); }\r\n\r\n                            \/\/ 3b. Appeler l'algo inter-espaces avec droppable temporairement d\u00e9marqu\u00e9\r\n                            if (_dropEl) { if (typeof window._viaRunInterEspaces === 'function') {\r\n                                var _wasLoaded = _dropEl.getAttribute('data-via-ad-loaded') === 'true';\r\n                                if (_wasLoaded) { _dropEl.removeAttribute('data-via-ad-loaded'); }\r\n                                try {\r\n                                    window._viaInterEspacesRun = false;\r\n                                    window._viaRunInterEspaces();\r\n                                    console.log('[DIAG resize] \u2699\ufe0f _viaRunInterEspaces appel\u00e9 (droppable d\u00e9marqu\u00e9)');\r\n                                } catch (e) {\r\n                                    console.warn('[DIAG resize] \u26a0\ufe0f _viaRunInterEspaces a throw :', e);\r\n                                }\r\n                                if (_wasLoaded) { _dropEl.setAttribute('data-via-ad-loaded', 'true'); }\r\n                            } }\r\n\r\n                            \/\/ 3c. \u2705 Article (body.single) ou page secteur (body.page) plein \u00e9cran :\r\n                            \/\/    80px margin-bottom sur le DROPPABLE pour \u00e9viter chevauchement\r\n                            \/\/    avec le contenu en dessous. Appliqu\u00e9 APR\u00c8S l'algo pour qu'il persiste.\r\n                            var _isArticleOrSecteur = document.body.classList.contains('single') || document.body.classList.contains('page');\r\n                            if (_isArticleOrSecteur) {\r\n                                if (_dropEl) {\r\n                                    \/\/ Force droppable \u00e0 englober wrapper (wrapper=171 > droppable=110)\r\n                                    var _wrH = Math.round(this.getBoundingClientRect().height);\r\n                                    \/\/ \u2705 Padding-top ET padding-bottom : padding fait partie de la hauteur\r\n                                    \/\/    de la bo\u00eete et est respect\u00e9 par les parents flex.\r\n                                    \/\/    50px d'espace au-dessus + wrapperH + 0px en-dessous.\r\n                                    var _pbTotal = _wrH + 0;\r\n                                    _dropEl.style.setProperty('padding-top', '50px', 'important');\r\n                                    _dropEl.style.setProperty('padding-bottom', _pbTotal + 'px', 'important');\r\n                                    _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                                    _dropEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                                    _dropEl.style.setProperty('min-height', (_wrH + 50) + 'px', 'important');\r\n                                    _dropEl.style.setProperty('height', 'auto', 'important');\r\n                                    _dropEl.style.setProperty('overflow', 'visible', 'important');\r\n                                    _dropEl.style.setProperty('box-sizing', 'content-box', 'important');\r\n\r\n                                    \/\/ \u2705 Force height:auto sur tous les conteneurs Elementor entre\r\n                                    \/\/    le droppable et le wrapper, pour que le wrapper ne d\u00e9borde\r\n                                    \/\/    pas de son conteneur vers le contenu du dessous.\r\n                                    var _anc = this; \/\/ wrapper\r\n                                    var _ancCount = 0;\r\n                                    while (_anc) {\r\n                                        if (_ancCount >= 5) break;\r\n                                        _anc = _anc.parentElement;\r\n                                        _ancCount++;\r\n                                        if (!_anc) break;\r\n                                        if (_anc === _dropEl) break;\r\n                                        _anc.style.setProperty('height', 'auto', 'important');\r\n                                        _anc.style.setProperty('min-height', _wrH + 'px', 'important');\r\n                                        _anc.style.setProperty('overflow', 'visible', 'important');\r\n                                    }\r\n\r\n                                    \/\/ Remonter au-del\u00e0 du droppable : padding sur section Elementor\r\n                                    var _anc2 = _dropEl;\r\n                                    var _ancChain = _dropEl.tagName + '#' + _dropEl.id;\r\n                                    var _ancCount2 = 0;\r\n                                    while (_anc2) {\r\n                                        if (_ancCount2 >= 6) break;\r\n                                        _anc2 = _anc2.parentElement;\r\n                                        _ancCount2++;\r\n                                        if (!_anc2) break;\r\n                                        _ancChain += ' > ' + _anc2.tagName + '.' + ((_anc2.className || '').split(' ')[0] || '');\r\n                                        if (_anc2.classList) {\r\n                                            if (_anc2.classList.contains('e-con-inner') || _anc2.classList.contains('elementor-element')) {\r\n                                                _anc2.style.setProperty('padding-top', '50px', 'important');\r\n                                                _anc2.style.setProperty('padding-bottom', '0px', 'important');\r\n                                                _anc2.style.setProperty('min-height', (_wrH + 50) + 'px', 'important');\r\n                                            }\r\n                                        }\r\n                                        if (_anc2.nextElementSibling) { break; }\r\n                                    }\r\n                                    console.log('[DIAG resize] \u2699\ufe0f article\/secteur \u2192 droppable.padding-top=50 padding-bottom=' + _pbTotal + ' (wrapperH=' + _wrH + ') | chain=' + _ancChain);\r\n                                }\r\n                            }\r\n\r\n                            \/\/ 3d. Scale 1.3 conditionnel : uniquement si d\u00e9p\u00f4t mobile\r\n                            var _depositedMode = this.getAttribute('data-deposited-mode') || '';\r\n                            if (_depositedMode === 'mobile') {\r\n                                var _naturalRect = this.getBoundingClientRect();\r\n                                var _naturalH = _naturalRect.height;\r\n                                var _scaleFactor = 1.3;\r\n                                this.style.setProperty('transform', 'scale(' + _scaleFactor + ')', 'important');\r\n                                this.style.setProperty('transform-origin', 'top center', 'important');\r\n                                var _extraH = Math.round(_naturalH * (_scaleFactor - 1));\r\n                                this.style.setProperty('margin-bottom', _extraH + 'px', 'important');\r\n                                console.log('[DIAG resize] \u2699\ufe0f scale ' + _scaleFactor + ' (d\u00e9p\u00f4t mobile) | naturalH=' + Math.round(_naturalH) + ' | wrapper.mb +' + _extraH + 'px');\r\n                            } else {\r\n                                console.log('[DIAG resize] \u2699\ufe0f pas de scale (depositedMode=\"' + _depositedMode + '\")');\r\n                            }\r\n                        } else {\r\n                            console.log('[DIAG resize] \u2699\ufe0f layout \u00e9troit (inner=' + window.innerWidth + '<1000) \u2192 skip algo+scale');\r\n                        }\r\n                        return;\r\n                    }\r\n                    \/\/ R\u00c9GIE IFRAME\r\n                    if (_ufcEl) {\r\n                        _ufcEl.style.setProperty('max-height', '260px', 'important');\r\n                        _ufcEl.style.setProperty('height', 'auto', 'important');\r\n                    }\r\n                    var _dzInner = _ufcEl ? _ufcEl.querySelector('#drop_file_zone_achat') : null;\r\n                    if (_dzInner) {\r\n                        _dzInner.style.setProperty('max-height', '250px', 'important');\r\n                        _dzInner.style.setProperty('height', 'auto', 'important');\r\n                    }\r\n                    var _imgInner = _ufcEl ? _ufcEl.querySelector('img, video') : null;\r\n                    if (_imgInner) {\r\n                        \/\/ v4.9ds : aligner max-height image sur offsetHeight du dropZone\r\n                        \/\/   (cf. commentaire branche desktop ci-dessus \u2014 rect.h fauss\u00e9 par zoom parent)\r\n                        var _dzH2 = _dzInner ? _dzInner.offsetHeight : 0;\r\n                        var _imgMaxHRsz2 = _dzH2 > 50 ? Math.max(0, Math.floor(_dzH2 - 5)) : 250;\r\n                        _imgInner.style.setProperty('max-height', _imgMaxHRsz2 + 'px', 'important');\r\n                    }\r\n                    this.style.removeProperty('transform');\r\n                }\r\n\r\n                var _wApr\u00e8sMt = this.style.marginTop || '(none)';\r\n                var _dApr\u00e8sMt = _dropEl ? (_dropEl.style.marginTop || '(none)') : '?';\r\n                var _rApr\u00e8s = _dropEl ? Math.round(_dropEl.getBoundingClientRect().top) : '?';\r\n                console.log('[RESIZE wrapper] #' + (_dropEl ? _dropEl.id : '?') + ' loaded=' + (_dropEl ? _dropEl.getAttribute('data-via-ad-loaded') : '?') + ' | AVANT wrapper.mt=' + _wAvantMt + ' droppable.mt=' + _dAvantMt + ' rect.top=' + _rAvant + ' | APR\u00c8S wrapper.mt=' + _wApr\u00e8sMt + ' droppable.mt=' + _dApr\u00e8sMt + ' rect.top=' + _rApr\u00e8s);\r\n            });\r\n\r\n            \/\/ \u00c9TAPE 2 : appel de l'algo une seule fois avec vue coh\u00e9rente\r\n            \/\/ \u26a0\ufe0f Sur sites pays, on NE RUN PAS l'algo \u00e0 l'agrandissement : \u00e7a cascade sur tous\r\n            \/\/    les droppables (y compris vides) et fait remonter les ads de plusieurs centaines\r\n            \/\/    de pixels. Layout naturel suffit.\r\n            if (!UIManager.isMobile()) { if (_isRegieIframe) {\r\n                if (typeof window._viaRunInterEspaces === 'function') {\r\n                    try {\r\n                        window._viaInterEspacesRun = false;\r\n                        window._viaRunInterEspaces();\r\n                    } catch (e) { \/* silent *\/ }\r\n                }\r\n            } else {\r\n                \/\/ Sites pays : rien de sp\u00e9cial, on laisse la layout naturelle\r\n            } }\r\n\r\n            \/\/ \u00c9TAPE 3 : scale 1.3 sur les wrappers en d\u00e9p\u00f4t mobile\r\n            if (!UIManager.isMobile()) {\r\n                $_wrappers.each(function() {\r\n                    var _depositedMode = this.getAttribute('data-deposited-mode') || '';\r\n                    if (_depositedMode === 'mobile') {\r\n                        var _naturalRect = this.getBoundingClientRect();\r\n                        var _naturalH = _naturalRect.height;\r\n                        var _scaleFactor = 1.3;\r\n                        this.style.setProperty('transform', 'scale(' + _scaleFactor + ')', 'important');\r\n                        this.style.setProperty('transform-origin', 'top center', 'important');\r\n                        var _extraH = Math.round(_naturalH * (_scaleFactor - 1));\r\n                        this.style.setProperty('margin-bottom', _extraH + 'px', 'important');\r\n                    }\r\n                });\r\n            }\r\n        }, 300);\r\n    });\r\n\r\n    \/\/ \ud83d\udd0d DIAG : fonction globale \u00e0 appeler en console \u2192 diagViaAd()\r\n    window.diagViaAd = function() {\r\n        var out = {\r\n            env: {\r\n                outerWidth: window.outerWidth,\r\n                innerWidth: window.innerWidth,\r\n                isMobileJS: UIManager.isMobile(),\r\n                isDesktopUA: UIManager.isDesktop(),\r\n                isTopWindow: window === window.top,\r\n                htmlClass: document.documentElement.className,\r\n                hasRegieClass: document.documentElement.classList.contains('via-regie-iframe'),\r\n                bodyClass: document.body.className\r\n            },\r\n            wrappers: []\r\n        };\r\n        jQuery('.via-ad-wrapper').each(function() {\r\n            var _cs = getComputedStyle(this);\r\n            var _rect = this.getBoundingClientRect();\r\n            var $_drop = jQuery(this).closest('.droppable');\r\n            var $_omc = $_drop.find('.OrdiMobileConteneurClass').first();\r\n            out.wrappers.push({\r\n                droppableId: $_drop.attr('id'),\r\n                droppableDataLoaded: $_drop.attr('data-via-ad-loaded'),\r\n                wrapperInlineStyle: this.getAttribute('style'),\r\n                wrapperComputedMarginTop: _cs.marginTop,\r\n                wrapperComputedMarginBottom: _cs.marginBottom,\r\n                wrapperRect: { top: Math.round(_rect.top), height: Math.round(_rect.height), width: Math.round(_rect.width) },\r\n                droppableInlineMarginTop: $_drop[0].style.marginTop,\r\n                omcInlineMarginTop: $_omc[0] ? $_omc[0].style.marginTop : '(no omc)',\r\n                omcInlineMarginBottom: $_omc[0] ? $_omc[0].style.marginBottom : '(no omc)'\r\n            });\r\n        });\r\n        console.log('[diagViaAd]', out);\r\n        return out;\r\n    };\r\n    console.log('[DIAG] window.diagViaAd() disponible \u2014 appelle-la en console apr\u00e8s agrandissement pour voir l\\'\u00e9tat');\r\n    \r\n    \/\/ \u2705 Clic sur le texte du label \u2192 toggle manuel de la checkbox du m\u00eame conteneur\r\n    jQuery(document).on('click', '.reserver-dynamic-label', function(e) {\r\n        e.preventDefault();\r\n        e.stopPropagation();\r\n        const $cb = $(this).closest('.reserver-dynamic-option, .reserver-dynamic-container').find('.reserver-dynamic-checkbox');\r\n        if ($cb.length) {\r\n            $cb.prop('checked', !$cb.prop('checked')).trigger('change');\r\n        }\r\n    });\r\n\r\n    \/\/ \u2705 CHECKBOX \"R\u00e9server cet espace publicitaire\" \u2014 VALIDATION ET ENVOI DES DONN\u00c9ES\r\n    \/\/ \u2705 v2.4.13 : iOS touchend\r\n    \/\/ \u2705 v2.4.13 : Mobile \u2014 \u00e9couter touchend sur input ET click\/touchend sur label\r\n    jQuery(document).on('touchend', 'input[name=\"form_fields[ReserverEspacePublicitaire]\"]', function() {\r\n        if (this._viaResTouch) { return; }\r\n        this._viaResTouch = true;\r\n        var _self = this;\r\n        setTimeout(function() { _self._viaResTouch = false; }, 500);\r\n        setTimeout(function() { jQuery(_self).trigger('change'); }, 100);\r\n    });\r\n    jQuery(document).on('touchend click', '.elementor-field-group-ReserverEspacePublicitaire label, .reserver-dynamic-label, .reserver-dynamic-option label', function(e) {\r\n        \/\/ Trouver l'input associ\u00e9\r\n        var $input = jQuery(this).closest('.elementor-field-option, .reserver-dynamic-option').find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n        if (!$input.length) { $input = jQuery(this).siblings('input[name=\"form_fields[ReserverEspacePublicitaire]\"]'); }\r\n        if (!$input.length) { $input = jQuery('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').first(); }\r\n        if ($input.length) {\r\n            if (e.type === 'touchend') { e.preventDefault(); }\r\n            var _wasChecked = $input.prop('checked');\r\n            $input.prop('checked', !_wasChecked);\r\n            setTimeout(function() {\r\n                $input.trigger('change');\r\n            }, 50);\r\n        }\r\n    });\r\n    jQuery(document).on('change', 'input[name=\"form_fields[ReserverEspacePublicitaire]\"]', function(e) {\r\n        const $checkbox = $(this);\r\n        let $droppable = $checkbox.closest('.droppable');\r\n        if (!$droppable.length) {\r\n            $droppable = $checkbox.closest('.reserver-dynamic-container').prev('.droppable');\r\n        }\r\n        \/\/ \u2705 v2.4.13 : Fallback mobile \u2014 checkbox dans body > .reserver-dynamic-container[data-droppable-id]\r\n        if (!$droppable.length) {\r\n            const $dynContainer = $checkbox.closest('.reserver-dynamic-container');\r\n            const _droppableId = $dynContainer.attr('data-droppable-id') || '';\r\n            if (_droppableId) {\r\n                $droppable = $('#' + _droppableId);\r\n            }\r\n        }\r\n        \/\/ Dernier recours : utiliser le rank en sessionStorage\r\n        if (!$droppable.length) {\r\n            const _rankFallback = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            if (_rankFallback) { $droppable = $('#' + _rankFallback); }\r\n        }\r\n        \r\n        \/\/ Si on d\u00e9coche\r\n        if (!$checkbox.is(':checked')) {\r\n            console.log('\u2610 Checkbox \"R\u00e9server\" d\u00e9coch\u00e9e');\r\n            \/\/ \u2705 Mettre \u00e0 jour le label\r\n            const $lbl = $checkbox.closest('.reserver-dynamic-option, .elementor-field-option').find('.reserver-dynamic-label, label').not('input');\r\n            $lbl.text('R\u00e9server cet espace publicitaire').css('color', '');\r\n            \/\/ \u2705 Notifier le parent pour d\u00e9-r\u00e9server l'item dans le r\u00e9cap\r\n            const _rankDecoche = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            const _emplacementDecoche = StateManager.buildEmplacementReference(_rankDecoche);\r\n            MessageManager.sendToParent('annulationReservation', {\r\n                Rank_Emplacement_Page_Web: _rankDecoche,\r\n                Commande_Emplacement_Page_Web: _emplacementDecoche,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n            \/\/ \u2705 v2.4.5 : M\u00e9moriser que ce rank a \u00e9t\u00e9 explicitement d\u00e9coch\u00e9\r\n            StateManager.set('_reserverDecoche_' + _rankDecoche, 'Yes');\r\n            console.log('\ud83d\udce4 annulationReservation envoy\u00e9 \u2192 parent | rank:', _rankDecoche, '| emplacement:', _emplacementDecoche);\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 V\u00e9rifier les conditions : format s\u00e9lectionn\u00e9 ET (fichier d\u00e9pos\u00e9 OU envoi diff\u00e9r\u00e9)\r\n        const hasFormat = FormatUIManager.hasSelectedFormat($droppable);\r\n        \/\/ v2.9 : data-via-ad-loaded est specifique au droppable, plus fiable que FileReceived global\r\n        const hasFile = StateManager.get('FileReceived') === 'Yes'\r\n            || $droppable.attr('data-via-ad-loaded') === 'true';\r\n        const hasEnvoiDiffere = $droppable.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        \r\n        console.log('\ud83d\udd0d Checkbox \"R\u00e9server\" - Validation:', { hasFormat, hasFile, hasEnvoiDiffere });\r\n        \r\n        if (!hasFormat) {\r\n            \/\/ \u2705 Si un fichier est d\u00e9j\u00e0 d\u00e9pos\u00e9 \u2192 d\u00e9duire le format depuis l'extension (ne pas bloquer)\r\n            if (hasFile) {\r\n                const _uploadedName = StateManager.get('Upload_File_Name') || '';\r\n                const _ext = _uploadedName.split('.').pop().toLowerCase();\r\n                const _fileType = FileManager.getFileType(_ext);\r\n                let _deducedFormat = '';\r\n                if (_fileType === 'video') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Video' : 'Vid\u00e9o';\r\n                } else if (_fileType === 'image') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Banner' : 'Banni\u00e8re';\r\n                } else if (_fileType === 'document') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Press release' : 'Communiqu\u00e9';\r\n                }\r\n                if (_deducedFormat) {\r\n                    StateManager.set('Commande_Format_Transmis', _deducedFormat);\r\n                    StateManager.set('FormatSelect', _deducedFormat);\r\n                    StateManager.set('Formatchoisi', 'Yes');\r\n                    console.log('\u2705 Format d\u00e9duit depuis extension (' + _ext + '):', _deducedFormat);\r\n                    \/\/ Continuer vers l'envoi (pas de return false)\r\n                } else {\r\n                    e.preventDefault();\r\n                    $checkbox.prop('checked', false);\r\n                    FormatUIManager.flashTitle($droppable);\r\n                    console.log('\u274c R\u00e9servation bloqu\u00e9e : format non d\u00e9ductible depuis extension:', _ext);\r\n                    return false;\r\n                }\r\n            } else {\r\n                e.preventDefault();\r\n                $checkbox.prop('checked', false);\r\n                FormatUIManager.flashTitle($droppable);\r\n                console.log('\u274c R\u00e9servation bloqu\u00e9e : aucun format s\u00e9lectionn\u00e9');\r\n                return false;\r\n            }\r\n        }\r\n        \r\n        if (!hasFile ? !hasEnvoiDiffere : false) {\r\n            e.preventDefault();\r\n            $checkbox.prop('checked', false);\r\n            console.log('\u274c R\u00e9servation bloqu\u00e9e : ni annonce d\u00e9pos\u00e9e ni envoi diff\u00e9r\u00e9');\r\n            return false;\r\n        }\r\n        \r\n        \/\/ \u2705 Conditions remplies \u2014 Pr\u00e9parer et envoyer les donn\u00e9es\r\n        console.log('\u2705 Checkbox \"R\u00e9server\" valid\u00e9e \u2014 envoi des donn\u00e9es');\r\n        \r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.setMultiple({\r\n            \"sendDataToParentFlag\": \"Yes\",\r\n            \"Formatchoisi\": \"Yes\",\r\n            \"Rank_Emplacement_Page_Web\": rankId,\r\n            \"Commande_Emplacement_Page_Web\": StateManager.buildEmplacementReference(rankId),\r\n            \"LoadedPageUrl\": window.location.href,\r\n            \/\/ \u2705 v2.3.0 : Forcer avant l'envoi \u2014 le setTimeout(4000) dans activateSendDataToParent\r\n            \/\/ arrive trop tard sur le 1er clic \u2192 AddNewRefInVosCampagnes restait null\r\n            \"AddNewRefInVosCampagnes\": \"Yes\"\r\n        });\r\n        \r\n        \/\/ \u2705 D\u00e9clencher l'envoi des donn\u00e9es via activateSendDataToParent\r\n        const $dropZone = $droppable.find('#drop_file_zone_achat');\r\n        UploadManager.activateSendDataToParent($dropZone);\r\n    });\r\n    \r\n    \/\/ G\u00c9RER \"Envoi diff\u00e9r\u00e9\" \u2014 marquer l'\u00e9tat sans envoyer (c'est \"R\u00e9server\" qui envoie)\r\n    \/\/ \u2705 v2.4.13 : iOS touchend\r\n    jQuery(document).on('touchend', 'input[name*=\"EnvoiUlterieur\"]', function() {\r\n        if (this._viaTouch) { return; }\r\n        this._viaTouch = true;\r\n        var _self = this;\r\n        setTimeout(function() { _self._viaTouch = false; }, 500);\r\n        setTimeout(function() { jQuery(_self).trigger('change'); }, 100);\r\n    });\r\n    jQuery(document).on('change', 'input[name*=\"EnvoiUlterieur\"]', function(e) {\r\n        const $checkbox = $(this);\r\n        let $droppable = $checkbox.closest('.droppable');\r\n        \/\/ \u2705 v2.4.13 : Fallback mobile\r\n        if (!$droppable.length) {\r\n            const $dynContainer = $checkbox.closest('.reserver-dynamic-container');\r\n            const _droppableId = $dynContainer.attr('data-droppable-id') || '';\r\n            if (_droppableId) { $droppable = $('#' + _droppableId); }\r\n        }\r\n        if (!$droppable.length) {\r\n            const _rankFallback = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            if (_rankFallback) { $droppable = $('#' + _rankFallback); }\r\n        }\r\n  \r\n        \/\/ Si on d\u00e9coche\r\n        if (!$checkbox.is(':checked')) {\r\n            StateManager.set('EnvoiUlterieur', 'false');\r\n            StateManager.set('sendDataToParentFlag', 'No'); \/\/ v4.9ds\r\n            \r\n            if (typeof window.verifierAffichageMsgSelectEspaceLocal === 'function') {\r\n                window.verifierAffichageMsgSelectEspaceLocal();\r\n            }\r\n            \r\n            \/\/ Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server\r\n            FormatUIManager.updateReserverCheckboxState($droppable);\r\n            return;\r\n        }\r\n        \r\n        \/\/ Si on coche, masquer le message\r\n        jQuery('#MsgSelectEspace').hide();\r\n    \r\n        \/\/ V\u00e9rifier si un format est s\u00e9lectionn\u00e9\r\n        const hasSelectedFormat = FormatUIManager.hasSelectedFormat($droppable);\r\n        \r\n        if (!hasSelectedFormat) {\r\n            e.preventDefault();\r\n            $checkbox.prop('checked', false);\r\n            FormatUIManager.flashTitle($droppable);\r\n            return false;\r\n        }\r\n        \r\n        \/\/ \u2705 FORMAT OK \u2014 Marquer l'\u00e9tat envoi diff\u00e9r\u00e9 (sans envoyer les donn\u00e9es)\r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.setMultiple({\r\n            \"EnvoiUlterieur\": 'true',\r\n            \"Rank_Emplacement_Page_Web\": rankId,\r\n            \"Commande_Emplacement_Page_Web\": StateManager.buildEmplacementReference(rankId),\r\n            \"LoadedPageUrl\": window.location.href,\r\n            \"FileReceived\": \"No\",\r\n            \"FullPathAdFile\": '',\r\n            \"Upload_File_Name\": '',\r\n            \"Formatchoisi\": 'Yes',\r\n            \"sendDataToParentFlag\": 'No' \/\/ v4.9ds : emp\u00eache l'ouverture du popup\r\n        });\r\n        \r\n        console.log('\ud83d\udcdd Envoi diff\u00e9r\u00e9 coch\u00e9 \u2014 en attente de validation via checkbox \"R\u00e9server\"');\r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server (maintenant activable)\r\n        FormatUIManager.updateReserverCheckboxState($droppable);\r\n    });\r\n\r\n    \/\/ \u2705 v2.2.0 : Bouton \"Cr\u00e9ation\" dans espace publicitaire \u2192 Kit Ad Creator dans le parent\r\n    jQuery(document).on('click', '.FormatIdCreation', function(e) {\r\n        e.preventDefault();\r\n        var $btn = jQuery(this);\r\n        var $droppable = $btn.closest('.droppable');\r\n        var isActive = $btn.data('creationActive') === true;\r\n\r\n        if (isActive) {\r\n            \/\/ D\u00e9sactiver\r\n            $btn.data('creationActive', false);\r\n            $btn.find('.EspPubFormat').css({'color': '#ffffff'});\r\n            $btn.css({'background-color': 'transparent'});\r\n            MessageManager.sendToParent('closeAdCreatorFromIframe', {});\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2192 toggle OFF, fermeture popup parent');\r\n            return;\r\n        }\r\n\r\n        \/\/ \u2705 Nouvelle logique sites pays\r\n        \/\/ V\u00e9rifier quel format est s\u00e9lectionn\u00e9 (fond blanc, hors Cr\u00e9ation et PopUp)\r\n        var _selFormat = '';\r\n        var _selIsVideo = false;\r\n        $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').each(function() {\r\n            var _bg = this.style.backgroundColor || '';\r\n            var _isWhite = _bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white';\r\n            if (!_isWhite) {\r\n                var _fEl = this.querySelector('.EspPubFormat');\r\n                if (_fEl) { var _col = _fEl.style.color || ''; _isWhite = _col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900'; }\r\n            }\r\n            if (_isWhite) {\r\n                var _cls = this.className || '';\r\n                _selFormat = _cls.match(\/FormatId(\\w+)\/) ? _cls.match(\/FormatId(\\w+)\/)[1] : 'unknown';\r\n                _selIsVideo = jQuery(this).hasClass('FormatIdVideo');\r\n                return false;\r\n            }\r\n        });\r\n\r\n        \/\/ R\u00e8gle 3 : si Vid\u00e9o est s\u00e9lectionn\u00e9 \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e\r\n        if (_selIsVideo) {\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation bloqu\u00e9e \u2014 format Vid\u00e9o actif');\r\n            UIManager.showFormatError($droppable, 'La cr\u00e9ation n\\'est pas disponible pour le format Vid\u00e9o');\r\n            return;\r\n        }\r\n\r\n        \/\/ R\u00e8gle 2 : aucun format s\u00e9lectionn\u00e9 \u2192 message, mais Cr\u00e9ation reste active visuellement\r\n        if (!_selFormat) {\r\n            \/\/ S\u00e9lectionner Cr\u00e9ation visuellement (fond blanc, texte vert)\r\n            $btn.data('creationActive', true);\r\n            $btn.find('.EspPubFormat').css({'color': '#37D900'});\r\n            $btn.css({'background-color': '#ffffff'});\r\n            \/\/ \u2705 Message persistant \u2014 pas de timeout, supprim\u00e9 au clic format\r\n            var $dropZone2 = $droppable.find('#drop_file_zone_achat, .drop_file_zone_achat_class').first();\r\n            if (!$dropZone2.length) { $dropZone2 = $droppable; }\r\n            var $dropZone2snap = $dropZone2;\r\n            setTimeout(function() {\r\n                jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n                jQuery('#fmt-creation-info').remove();\r\n                var _isMob2 = UIManager.isMobile();\r\n                \/\/ v4.9ds : Attacher le message au BODY (pas dans le drop zone) pour \u00e9chapper\r\n                \/\/          \u00e0 l'opacity 0.4 du format-gate appliqu\u00e9 sur .HTMLUploadfileConteneur.\r\n                \/\/          Position calcul\u00e9e dynamiquement \u00e0 partir du dropZone (au-dessus du curseur).\r\n                \/\/ v4.9ds (suit-drag) : si on est sur Ele0A et qu'un wrapper .ToBeHidden parent\r\n                \/\/          existe (cas espace pop-up draggable), on attache le message au wrapper\r\n                \/\/          au lieu du body. Le wrapper est ce qui se d\u00e9place lors du drag d'Ele0A,\r\n                \/\/          donc le message suit naturellement. L'opacity 0.4 est sur\r\n                \/\/          .HTMLUploadfileConteneur (enfant d'Ele0A) \u2014 le wrapper, parent\r\n                \/\/          d'Ele0A, y \u00e9chappe.\r\n                var _dzEl = $dropZone2snap[0];\r\n                if (!_dzEl) return;\r\n                var _rect = _dzEl.getBoundingClientRect();\r\n                \/\/ v4.9ds : desktop top:50\u219240, mobile top:54 inchang\u00e9\r\n                \/\/   + dans l'iframe r\u00e9gie (window !== window.top) : descendre de 50px\r\n                \/\/   (le message tombait sur les formats au lieu de la dropZone)\r\n                var _isInIframe = (window !== window.top);\r\n                var _extraOffset = _isInIframe ? 50 : 0;\r\n                \/\/ D\u00e9tection wrapper Ele0A pour suivi drag\r\n                var _$ele0A = jQuery(_dzEl).closest('#Ele0A');\r\n                var _$wrapper = _$ele0A.length ? _$ele0A.closest('.ToBeHidden') : jQuery();\r\n                var _useWrapper = _$wrapper.length > 0;\r\n                var _topPx, _leftPx, _$parent, _positionMode;\r\n                if (_useWrapper) {\r\n                    \/\/ Position RELATIVE au wrapper (pas au document) \u2192 suit le drag\r\n                    var _wRect = _$wrapper[0].getBoundingClientRect();\r\n                    _topPx = Math.round((_rect.top - _wRect.top) + (_isMob2 ? 54 : 40) + _extraOffset);\r\n                    _leftPx = Math.round((_rect.left - _wRect.left) + _rect.width \/ 2);\r\n                    _$parent = _$wrapper;\r\n                    _positionMode = 'absolute';\r\n                    \/\/ S'assurer que le wrapper peut h\u00e9berger un absolute (position non-static)\r\n                    var _curPos = window.getComputedStyle(_$wrapper[0]).position;\r\n                    if (_curPos === 'static') { _$wrapper.css('position', 'relative'); }\r\n                    \/\/ Permettre au message de d\u00e9border si le wrapper a overflow:hidden\r\n                    var _curOv = window.getComputedStyle(_$wrapper[0]).overflow;\r\n                    if (_curOv === 'hidden') { _$wrapper.css('overflow', 'visible'); }\r\n                } else {\r\n                    \/\/ Fallback historique : attacher au body (cas miniature, ele1a+, etc.)\r\n                    _topPx = Math.round(_rect.top + (_isMob2 ? 54 : 40) + _extraOffset + (window.scrollY || 0));\r\n                    _leftPx = Math.round(_rect.left + _rect.width \/ 2 + (window.scrollX || 0));\r\n                    _$parent = jQuery('body');\r\n                    _positionMode = 'absolute';\r\n                }\r\n                \/\/ v4.9ds : mobile scale 0.6, desktop scale 0.7\r\n                var _transform = _isMob2\r\n                    ? 'transform:translateX(-50%) scale(0.6);transform-origin:top center;'\r\n                    : 'transform:translateX(-50%) scale(0.7);transform-origin:top center;';\r\n                var _fontSize = _isMob2 ? '13px' : '14px';\r\n                \/\/ v4.9ds (fmt-info-z-fix) : z-index dynamique pour rester au-dessus du\r\n                \/\/   wrapper .ToBeHidden d'Ele0A quand celui-ci est mis en avant-plan par\r\n                \/\/   _bringPopupToFront (Entete.txt). On lit window.popupZIndex (variable\r\n                \/\/   globale du compteur z-index dans Entete.txt) et on prend max+10. Si\r\n                \/\/   absent (cas iframe ou page sans Entete.txt), fallback 99999.\r\n                \/\/   NB : si on est attach\u00e9 au wrapper, le z-index est relatif au stacking\r\n                \/\/   context du wrapper, mais on garde la valeur \u00e9lev\u00e9e par s\u00e9curit\u00e9.\r\n                var _baseZ = (typeof window.popupZIndex === 'number') ? window.popupZIndex : 99999;\r\n                var _msgZ = Math.max(99999, _baseZ + 10);\r\n                _$parent.append('<div id=\"fmt-creation-info\" style=\"'\r\n                    + 'position:' + _positionMode + ';top:' + _topPx + 'px;left:' + _leftPx + 'px;'\r\n                    + _transform\r\n                    + 'width:auto;white-space:nowrap;z-index:' + _msgZ + ';'\r\n                    + 'background:#fff;color:#FB5E2A;font-weight:700;font-size:' + _fontSize + ';'\r\n                    + 'text-align:center;padding:8px 10px;border-radius:6px;'\r\n                    + 'border:2px solid #FB5E2A;box-sizing:border-box;line-height:1.4;'\r\n                    + 'opacity:1;pointer-events:auto;'\r\n                    + '\">Merci de s\u00e9lectionner le format d\\'annonce que vous souhaitez cr\u00e9er<\/div>');\r\n            }, 80);\r\n            \/\/ D\u00e9sactiver visuellement le bouton Vid\u00e9o + changer son texte en \"D\u00e9sactiv\u00e9\"\r\n            $droppable.find('.FormatIdVideo').each(function() {\r\n                jQuery(this).css({'opacity': '0.4', 'pointer-events': 'none'});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                $lbl.attr('data-original-text', $lbl.text());\r\n                $lbl.text('D\u00e9sactiv\u00e9');\r\n            });\r\n            \/\/ NE PAS d\u00e9s\u00e9lectionner Cr\u00e9ation \u2014 laisser l'utilisateur choisir un format\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2014 aucun format s\u00e9lectionn\u00e9 \u2192 message + Vid\u00e9o d\u00e9sactiv\u00e9e');\r\n            return;\r\n        }\r\n\r\n        \/\/ R\u00e8gle 1 : format valide s\u00e9lectionn\u00e9 \u2192 d\u00e9sactiver Vid\u00e9o + ouvrir le popup apr\u00e8s 3 secondes\r\n        $btn.data('creationActive', true);\r\n        $btn.find('.EspPubFormat').css({'color': '#37D900'});\r\n        $btn.css({'background-color': '#ffffff'});\r\n        \/\/ \u2705 D\u00e9sactiver Vid\u00e9o\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '0.4', 'pointer-events': 'none'});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n            $lbl.text('D\u00e9sactiv\u00e9');\r\n        });\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') || '';\r\n        var formatTransmis = sessionStorage.getItem('Commande_Format_Transmis') || '';\r\n        var _rankKit = $droppable.attr('id') || sessionStorage.getItem('Rank_Emplacement_Page_Web') || '';\r\n        var _cSite = sessionStorage.getItem('codeSite') || '';\r\n        var _cPage = sessionStorage.getItem('codePage') || '';\r\n        var _sfxKit = _rankKit.replace('Ele', '');\r\n        var _emplKit = (_cSite ? (_cPage ? _sfxKit : false) : false) ? (_cSite + _cPage + 'L' + _sfxKit) : (sessionStorage.getItem('Commande_Emplacement_Page_Web') || '');\r\n        var _fmtSnap = formatSelect;\r\n        var _fmtTSnap = formatTransmis;\r\n        var _rkSnap = _rankKit;\r\n        var _emplSnap = _emplKit;\r\n        setTimeout(function() {\r\n            MessageManager.sendToParent('openAdCreatorFromIframe', { formatSelect: _fmtSnap, formatTransmis: _fmtTSnap, rankId: _rkSnap, emplacement: _emplSnap });\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2192 toggle ON apr\u00e8s 2s | format:', _selFormat, '| FormatSelect:', _fmtSnap);\r\n        }, 2000);\r\n    });\r\n\r\n    \/\/ \u2705 R\u00e8gle : si Vid\u00e9o est cliqu\u00e9, d\u00e9sactiver Cr\u00e9ation\r\n    jQuery(document).on('click', '.FormatIdVideo', function(e) {\r\n        var $droppable = jQuery(this).closest('.droppable');\r\n        \/\/ R\u00e9activer Vid\u00e9o (peut avoir \u00e9t\u00e9 d\u00e9sactiv\u00e9e par la r\u00e8gle 2) + restaurer texte\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ D\u00e9sactiver visuellement Cr\u00e9ation + changer texte en \"D\u00e9sactiv\u00e9\"\r\n        $droppable.find('.FormatIdCreation').each(function() {\r\n            jQuery(this).data('creationActive', false);\r\n            jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n            jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            $lbl.attr('data-original-text', $lbl.attr('data-original-text') || $lbl.text());\r\n            $lbl.text('D\u00e9sactiv\u00e9');\r\n        });\r\n        \/\/ Fermer le popup si ouvert\r\n        MessageManager.sendToParent('closeAdCreatorFromIframe', {});\r\n        console.log('\ud83c\udfa8 Vid\u00e9o s\u00e9lectionn\u00e9 \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e');\r\n    });\r\n\r\n    \/\/ \u2705 R\u00e8gle : si un autre format est s\u00e9lectionn\u00e9, r\u00e9activer Cr\u00e9ation et Vid\u00e9o + restaurer textes\r\n    jQuery(document).on('click', '.EspPubFormatContainer:not(.FormatIdCreation):not(.FormatIdPopUp):not(.FormatIdVideo)', function(e) {\r\n        var $droppable = jQuery(this).closest('.droppable');\r\n        \/\/ R\u00e9activer Vid\u00e9o + restaurer texte\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ R\u00e9activer Cr\u00e9ation + restaurer texte\r\n        $droppable.find('.FormatIdCreation').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ Supprimer les messages d'erreur\r\n        jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n    });\r\n\r\n    \/\/ \u2705 v2.2.0 : Messages depuis le parent concernant le bouton Cr\u00e9ation\r\n    window.addEventListener('message', function(event) {\r\n        var msg = event.data;\r\n        if (!msg) return;\r\n\r\n        \/\/ \u2705 v2.2.1 : Fournir la position du titre R\u00e9server pour positionner la miniature\r\n        if (msg.type === 'getReserverLabelRect') {\r\n            var $label = jQuery('.reserver-dynamic-label').first();\r\n            if ($label.length) {\r\n                var r = $label[0].getBoundingClientRect();\r\n                var iframeScale = (window.outerWidth > 1000) ? 0.75 : 0.80; \/\/ \u2705 v2.4.12 : 0.85 \u2192 0.75 (scale r\u00e9el du parent)\r\n                event.source.postMessage({\r\n                    type: 'reserVeurLabelRectResult',\r\n                    \/\/ Retourner bottom en coordonn\u00e9es iframe internes (non scal\u00e9es)\r\n                    bottom: r.bottom,\r\n                    left: r.left,\r\n                    right: r.right,\r\n                    iframeScale: iframeScale\r\n                }, '*');\r\n            } else {\r\n                event.source.postMessage({ type: 'reserVeurLabelRectResult', bottom: null }, '*');\r\n            }\r\n        }\r\n\r\n        \/\/ Fermeture du popup \u2192 d\u00e9s\u00e9lectionner Cr\u00e9ation + r\u00e9activer Vid\u00e9o dans tous les espaces\r\n        if (msg.type === 'adCreatorClosedFromParent') {\r\n            jQuery('.FormatIdCreation').each(function() {\r\n                jQuery(this).data('creationActive', false);\r\n                jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                jQuery(this).css({'background-color': 'transparent'});\r\n            });\r\n            \/\/ \u2705 R\u00e9activer Vid\u00e9o dans tous les espaces de la page\r\n            jQuery('.FormatIdVideo').each(function() {\r\n                jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                var _orig = $lbl.attr('data-original-text');\r\n                if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n            });\r\n            \/\/ v4.9ds : r\u00e9-\u00e9valuer SelectionFormatTitre \/ SelectionFormatTitreBlanc sur tous les droppables\r\n            \/\/          (Cr\u00e9ation d\u00e9s\u00e9lectionn\u00e9 \u2192 si aucun autre format actif dans le DOM, le titre rouge doit r\u00e9appara\u00eetre)\r\n            \/\/          Check DOM direct (pas updateTitleColor qui retombe sur sessionStorage Formatchoisi=Yes)\r\n            jQuery('.droppable').not('#Ele0A').each(function() {\r\n                var $_d = jQuery(this);\r\n                var _hasDomFmt = $_d.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').filter(function() {\r\n                    var _bg = jQuery(this).css('background-color') || '';\r\n                    return _bg === 'rgb(255, 255, 255)';\r\n                }).length > 0;\r\n                if (_hasDomFmt) {\r\n                    $_d.find('.SelectionFormatTitre').hide();\r\n                    $_d.find('.SelectionFormatTitreBlanc').show();\r\n                } else {\r\n                    $_d.find('.SelectionFormatTitre').each(function() { this.style.setProperty('display','block','important'); });\r\n                    $_d.find('.SelectionFormatTitreBlanc').hide();\r\n                }\r\n            });\r\n            console.log('\ud83c\udfa8 adCreatorClosedFromParent \u2192 Cr\u00e9ation d\u00e9s\u00e9lectionn\u00e9 + Vid\u00e9o r\u00e9activ\u00e9 + titres r\u00e9-\u00e9valu\u00e9s (DOM direct)');\r\n        }\r\n\r\n        \/\/ Restaurer le style du bouton Cr\u00e9ation apr\u00e8s un reset de removeElements\r\n        if (msg.type === 'restoreCreationButton') {\r\n            jQuery('.FormatIdCreation').each(function() {\r\n                if (jQuery(this).data('creationActive') === true) {\r\n                    jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                    jQuery(this).css({'background-color': '#ffffff'});\r\n                }\r\n            });\r\n            console.log('\ud83c\udfa8 restoreCreationButton re\u00e7u \u2192 style Cr\u00e9ation restaur\u00e9');\r\n        }\r\n    });\r\n\r\n});\r\n\r\n\/\/ =========================================================================\r\n\/\/ \u2705 v4.9ds Pb 9.62 R\u00e8gle 2 : Listener disableEspacesPris \u2014 grise\/d\u00e9sactive les\r\n\/\/   espaces publicitaires d\u00e9j\u00e0 pris par d'autres items du panier sur la m\u00eame page.\r\n\/\/   Re\u00e7u depuis Panier_manager.txt (notifierEspacesPrisIframe).\r\n\/\/   \"1 espace pub = 1 r\u00e9servation\" \u2192 emp\u00eache le user de cliquer \u00e0 nouveau dessus.\r\n\/\/ =========================================================================\r\n(function _viaInitDisableEspacesPris() {\r\n    \/\/ CSS inject\u00e9 une seule fois pour le grisage + overlay \"D\u00e9j\u00e0 r\u00e9serv\u00e9\"\r\n    if (!document.getElementById('via-espace-pris-style')) {\r\n        var _styleEPS = document.createElement('style');\r\n        _styleEPS.id = 'via-espace-pris-style';\r\n        _styleEPS.textContent =\r\n            '.via-espace-pris-greyed { ' +\r\n            '  opacity: 0.45 !important; ' +\r\n            '  pointer-events: none !important; ' +\r\n            '  cursor: not-allowed !important; ' +\r\n            '  position: relative !important; ' +\r\n            '  filter: grayscale(80%) !important; ' +\r\n            '}' +\r\n            '.via-espace-pris-greyed::after { ' +\r\n            '  content: \"D\u00e9j\u00e0 r\u00e9serv\u00e9\"; ' +\r\n            '  position: absolute; ' +\r\n            '  top: 50%; ' +\r\n            '  left: 50%; ' +\r\n            '  transform: translate(-50%, -50%); ' +\r\n            '  background: rgba(34, 93, 169, 0.92); ' +\r\n            '  color: #ffffff; ' +\r\n            '  padding: 5px 12px; ' +\r\n            '  border-radius: 4px; ' +\r\n            '  font-size: 13px; ' +\r\n            '  font-weight: 600; ' +\r\n            '  white-space: nowrap; ' +\r\n            '  pointer-events: none; ' +\r\n            '  z-index: 10; ' +\r\n            '  box-shadow: 0 2px 6px rgba(0,0,0,0.25); ' +\r\n            '}';\r\n        document.head.appendChild(_styleEPS);\r\n    }\r\n\r\n    function _applyEspacesPris(emplacements) {\r\n        \/\/ 1. Retirer le grisage de tous les droppables (reset)\r\n        jQuery('.droppable').removeClass('via-espace-pris-greyed');\r\n        \/\/ 2. Griser ceux dans la liste\r\n        if (!Array.isArray(emplacements)) { return; }\r\n        var _count = 0;\r\n        emplacements.forEach(function(_item) {\r\n            if (!_item) return;\r\n            \/\/ Cibler par rank (Ele1A, Ele2A, etc.) ou par data-empl\r\n            var _rank = _item.rank || '';\r\n            var _empl = _item.emplacement || '';\r\n            var $cible = jQuery();\r\n            if (_rank) {\r\n                $cible = jQuery('#' + _rank);\r\n            }\r\n            if (!$cible.length ? !!_empl : false) {\r\n                \/\/ Fallback : chercher un droppable dont la r\u00e9f\u00e9rence d'emplacement matche\r\n                $cible = jQuery('.droppable').filter(function() {\r\n                    var _t = jQuery(this).find('[data-empl]').attr('data-empl') || '';\r\n                    return _t === _empl;\r\n                });\r\n            }\r\n            if ($cible.length) {\r\n                $cible.addClass('via-espace-pris-greyed');\r\n                _count++;\r\n            }\r\n        });\r\n        console.log('\ud83d\udeab [disableEspacesPris]', _count, '\/', emplacements.length, 'espace(s) gris\u00e9(s)');\r\n    }\r\n\r\n    window.addEventListener('message', function(_eEPS) {\r\n        var _msg = _eEPS.data;\r\n        if (!_msg ? true : _msg.type !== 'disableEspacesPris') return;\r\n        _applyEspacesPris(_msg.emplacements);\r\n    });\r\n\r\n    \/\/ Exposer pour appel manuel \/ test depuis console\r\n    window._viaApplyEspacesPris = _applyEspacesPris;\r\n    console.log('\u2705 [disableEspacesPris] listener install\u00e9');\r\n})();\r\n\r\n\/\/ =========================================================================\r\n\/\/ \u2705 Listener kitAdCreated \u2014 annonce cr\u00e9\u00e9e par le Kit overlay (mode=kit)\r\n\/\/ Injecte directement dans l'espace pub identifi\u00e9 par rankId\r\nwindow.addEventListener('message', function(event) {\r\n    var msg = event.data;\r\n\r\n    if (!msg || msg.action !== 'kitAdCreated') return;\r\n\r\n    console.log('\ud83d\udd0d [DIAG kitAdCreated entr\u00e9e] receveur=' + (window === window.top ? 'PARENT' : 'IFRAME') + ' | location=' + window.location.href.substring(0, 80) + ' | rank=' + (msg.rankId || 'NULL'));\r\n\r\n    var _rankId = msg.rankId || '';\r\n    var _emplacement = msg.emplacement || '';\r\n    console.log('\ud83c\udfa8 [espace_pub] kitAdCreated re\u00e7u | rankId:', _rankId, '| emplacement:', _emplacement);\r\n\r\n    if (!_rankId) { console.warn('\u26a0\ufe0f [kitAdCreated] rankId manquant'); return; }\r\n\r\n    var $droppable = jQuery('#' + _rankId);\r\n    if (!$droppable.length) { console.warn('\u26a0\ufe0f [kitAdCreated] droppable non trouv\u00e9:', _rankId); return; }\r\n    var $dropZone = $droppable.find('.drop_file_zone_achat_class').first();\r\n    if (!$dropZone.length) { $dropZone = $droppable.find('#drop_file_zone_achat').first(); }\r\n    if (!$dropZone.length) { console.warn('\u26a0\ufe0f [kitAdCreated] dropZone non trouv\u00e9e dans', _rankId); return; }\r\n\r\n    \/\/ v4.9ds : d\u00e9verrouiller le format-gate sur le droppable cible\r\n    \/\/   Si l'utilisateur d\u00e9pose une cr\u00e9ation depuis la miniature dans un espace sans format pr\u00e9alable,\r\n    \/\/   data-via-format-gate=\"locked\" reste pos\u00e9 \u2192 opacity 0.4 sur l'annonce d\u00e9pos\u00e9e.\r\n    \/\/   On pose le format de la miniature pour lib\u00e9rer le gate.\r\n    try {\r\n        var _topW = window.top || window;\r\n        var _kitFmt = msg.format || sessionStorage.getItem('FormatSelect') || '';\r\n        if (_kitFmt && typeof _topW._viaSetDroppableFormat === 'function') {\r\n            _topW._viaSetDroppableFormat(_rankId, _kitFmt);\r\n            console.log('\ud83d\udd13 [kitAdCreated] format-gate d\u00e9verrouill\u00e9 sur', _rankId, '| format:', _kitFmt);\r\n        }\r\n    } catch(_eGate) { console.warn('\u26a0\ufe0f [kitAdCreated] _viaSetDroppableFormat error:', _eGate); }\r\n\r\n    \/\/ \u2705 Bug 7 sites pays : stocker pdfImageDataURL pour \"Ouvrir et visualiser\"\r\n    \/\/   (m\u00eame logique que thumbnailDropped qui fonctionne sur la r\u00e9gie)\r\n    if (msg.pdfImageDataURL) {\r\n        $droppable.data('kitPdfImageDataURL', msg.pdfImageDataURL);\r\n        $droppable.data('kitFormatSelect', msg.format || '');\r\n        console.log('\ud83d\udcce [kitAdCreated] pdfImageDataURL stock\u00e9 sur', _rankId, '| format:', msg.format);\r\n    } else {\r\n        $droppable.removeData('kitPdfImageDataURL');\r\n        $droppable.removeData('kitFormatSelect');\r\n    }\r\n    \/\/ v4.9ds : 1\u00e8re image utilisateur (pour preview mobile JPG-in-PDF)\r\n    if (msg.firstImageDataURL) {\r\n        $droppable.data('kitFirstImageDataURL', msg.firstImageDataURL);\r\n        console.log('\ud83d\uddbc\ufe0f [kitAdCreated] kitFirstImageDataURL stock\u00e9 sur', _rankId);\r\n        try { var _dl3 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl3) _dl3('[v4.9ds] kitAdCreated firstImg stock\u00e9 ' + _rankId, 'ok'); } catch(e) {}\r\n    } else {\r\n        $droppable.removeData('kitFirstImageDataURL');\r\n        try { var _dl4 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl4) _dl4('[v4.9ds] kitAdCreated SANS firstImg ' + _rankId, 'warn'); } catch(e) {}\r\n    }\r\n\r\n    var _dataURL = msg.fullResDataURL || msg.pdfDataURL || null;\r\n    var _filename = msg.filename || 'annonce.png';\r\n    var _isPDF = msg.isPDF || false;\r\n    if (!_dataURL) { console.warn('\u26a0\ufe0f [kitAdCreated] aucune donn\u00e9e image'); return; }\r\n\r\n    var _parts = _dataURL.split(',');\r\n    var _mime = (_parts[0].match(\/:(.*?);\/) || [])[1] || (_isPDF ? 'application\/pdf' : 'image\/png');\r\n    var _bStr = atob(_parts[1]);\r\n    var _bytes = new Uint8Array(_bStr.length);\r\n    for (var _i = 0; _i < _bStr.length; _i++) { _bytes[_i] = _bStr.charCodeAt(_i); }\r\n    var _blob = new Blob([_bytes], { type: _mime });\r\n    \/\/ \u2705 Ajouter extension si absente\r\n    var _MIME_EXT = {'image\/png':'.png','image\/jpeg':'.jpg','image\/jpg':'.jpg','image\/webp':'.webp','application\/pdf':'.pdf'};\r\n    if (_filename.indexOf('.') === -1) { _filename = _filename + (_MIME_EXT[_mime] || (_isPDF ? '.pdf' : '.png')); }\r\n    var _file = new File([_blob], _filename, { type: _mime });\r\n\r\n    \/\/ \u2705 Pr\u00e9parer StateManager\r\n    StateManager.set('Rank_Emplacement_Page_Web', _rankId);\r\n    StateManager.set('Commande_Emplacement_Page_Web', _emplacement);\r\n    StateManager.set('Formatchoisi', 'Yes');\r\n    \/\/ \u2705 Marquer le droppable directement en DOM \u2014 r\u00e9siste \u00e0 l'async, lu par styleUploadedAd\r\n    $droppable[0].setAttribute('data-kit-drop', 'true');\r\n    window._dropFromMiniature = true;\r\n\r\n    console.log('\u2705 [kitAdCreated] \u2192 UploadManager.handleFileUpload | rankId:', _rankId);\r\n    \/\/ \u2550\u2550\u2550\u2550 DIAG TEMPORAIRE v4.9ds \u2014 cha\u00eene upload kitAdCreated \u2550\u2550\u2550\u2550\r\n    console.log('\ud83d\udd0d [DIAG kit-upload] AVANT handleFileUpload | StateManager FullPathAdFile:', StateManager.get('FullPathAdFile'), '| FileReceived:', StateManager.get('FileReceived'), '| _file size:', _file.size, '| name:', _file.name);\r\n    \/\/ v4.9ds : exposer une promise globale pour que prepareUploadData puisse attendre\r\n    \/\/   l'upload avant de poster les donn\u00e9es au popup (sinon FullPathAdFile=null en BDD).\r\n    window._viaPendingUpload = window._viaPendingUpload || {};\r\n    window._viaPendingUpload[_rankId] = UploadManager.handleFileUpload(_file, $dropZone).then(function() {\r\n        console.log('\u2705 [kitAdCreated] handleFileUpload termin\u00e9');\r\n        console.log('\ud83d\udd0d [DIAG kit-upload] APR\u00c8S handleFileUpload | StateManager FullPathAdFile:', StateManager.get('FullPathAdFile'), '| FileReceived:', StateManager.get('FileReceived'), '| Upload_File_Name:', StateManager.get('Upload_File_Name'));\r\n        delete window._viaPendingUpload[_rankId];\r\n    }).catch(function(err) {\r\n        window._dropFromMiniature = false;\r\n        $droppable[0].removeAttribute('data-kit-drop');\r\n        console.error('\u274c [kitAdCreated] handleFileUpload erreur:', err);\r\n        delete window._viaPendingUpload[_rankId];\r\n    });\r\n});\r\n\r\n\/\/ \u2705 v1.17.0 : Listener \"thumbnailDropped\" \u2014 d\u00e9p\u00f4t de l'annonce depuis la miniature\r\n\/\/ =========================================================================\r\n\/**\r\n * Re\u00e7oit le drop de l'image-annonce depuis la miniature dans la page parente.\r\n * Identifie l'espace publicitaire sous le curseur, s\u00e9lectionne cet espace,\r\n * et d\u00e9clenche le flux dataFromIframeEspacePub en mode \"envoi diff\u00e9r\u00e9\"\r\n * (l'utilisateur uploadera le vrai fichier depuis le formulaire de commande).\r\n *\r\n * Coordonn\u00e9es re\u00e7ues : viewport de l'iframe (clientX\/clientY relatifs \u00e0 l'iframe)\r\n *\/\r\nwindow.addEventListener('message', function(event) {\r\n    var msg = event.data;\r\n    if (!msg || msg.action !== 'thumbnailDropped') return;\r\n\r\n    console.log('\ud83d\udce8 thumbnailDropped re\u00e7u \u2014 coords iframe:', msg.xRel, msg.yRel);\r\n\r\n    \/\/ \u2705 v1.19.3 : Corriger les coordonn\u00e9es pour le facteur de scale de l'iframe\r\n    \/\/ \u2705 v2.4.10 : 0.75 = scale appliqu\u00e9 par le PARENT sur l'\u00e9l\u00e9ment iframe (et non 0.85 qui est le scale interne OrdiMobileConteneurClass)\r\n    \/\/ Les coords xRel\/yRel viennent de getBoundingClientRect() sur l'iframe scal\u00e9 \u00e0 0.75 dans le parent \u2192 diviser par 0.75\r\n    var iframeScale = (window.outerWidth > 1000) ? 0.75 : 0.80;\r\n    \/\/ \u2705 v2.4.3 : Si Entete a intercept\u00e9 et pos\u00e9 un redirect vers Ele0A, utiliser ces coords\r\n    var _redirect = window._thumbnailDropRedirect || null;\r\n    window._thumbnailDropRedirect = null;\r\n    \/\/ \u2705 v2.4.4 : Les coords de redirect viennent de getBoundingClientRect() dans l'iframe (espace logique)\r\n    \/\/            \u2192 ne pas re-diviser par iframeScale (d\u00e9j\u00e0 en coords iframe-logiques)\r\n    var xAdjusted = _redirect ? _redirect.xRel : (msg.xRel \/ iframeScale);\r\n    var yAdjusted = _redirect ? _redirect.yRel : (msg.yRel \/ iframeScale);\r\n    if (_redirect) { console.log('\ud83d\udd00 [thumbnailDropped] coords redirig\u00e9es vers Ele0A:', Math.round(xAdjusted), Math.round(yAdjusted)); }\r\n    else { console.log('\ud83d\udcd0 Coords ajust\u00e9es (scale', iframeScale, '):', Math.round(xAdjusted), Math.round(yAdjusted)); }\r\n\r\n    \/\/ 1. Identifier l'espace publicitaire le plus proche du point de drop\r\n    \/\/    \u2705 v1.18.1 : Recherche par distance \u2014 coords \u00e9ventuellement redirig\u00e9es par Entete.txt\r\n    var allDroppables = document.querySelectorAll('.droppable');\r\n    var droppableEl = null;\r\n    var closestDist = Infinity;\r\n\r\n    \/\/ \u2705 v2.4.4 : Guard rect Ele0A \u2014 si PopUpChoice=Yes ET curseur dans le rect d'Ele0A \u2192 forcer\r\n    \/\/            (Ele0A est un popup flottant : son centre peut \u00eatre loin du curseur \u2192 algo distance l'ignore)\r\n    \/\/            Ne force PAS si le drop est hors d'Ele0A \u2192 les autres espaces restent accessibles\r\n    if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n        var _ele0ACheck = document.getElementById('Ele0A');\r\n        if (_ele0ACheck) {\r\n            var _r0A = _ele0ACheck.getBoundingClientRect();\r\n            var _margin = 30;\r\n            var _inR0A = (xAdjusted >= _r0A.left - _margin);\r\n            if (_inR0A) { _inR0A = (xAdjusted <= _r0A.right + _margin); }\r\n            if (_inR0A) { _inR0A = (yAdjusted >= _r0A.top - _margin); }\r\n            if (_inR0A) { _inR0A = (yAdjusted <= _r0A.bottom + _margin); }\r\n            if (_inR0A) {\r\n                droppableEl = _ele0ACheck;\r\n                closestDist = 0;\r\n                console.log('\ud83c\udfaf [thumbnailDropped] Ele0A forc\u00e9 (curseur dans rect Ele0A +30px)');\r\n            }\r\n        }\r\n    }\r\n\r\n    if (!droppableEl) {\r\n        allDroppables.forEach(function(d) {\r\n            var r = d.getBoundingClientRect();\r\n            var dCenterX = r.left + r.width  \/ 2;\r\n            var dCenterY = r.top  + r.height \/ 2;\r\n            var dist = Math.abs(dCenterX - xAdjusted) + Math.abs(dCenterY - yAdjusted);\r\n            if (dist < closestDist) { closestDist = dist; droppableEl = d; }\r\n        });\r\n        \/\/ \u2705 v2.4.3 : Si Entete a d\u00e9tect\u00e9 un drop sur Ele0A \u2192 forcer directement\r\n        if (_redirect) { if (_redirect.forceEle0A) {\r\n            var _ele0AForced = document.getElementById('Ele0A');\r\n            if (_ele0AForced) { droppableEl = _ele0AForced; console.log('\ud83c\udfaf [thumbnailDropped] Ele0A forc\u00e9 (redirect Entete)'); }\r\n        } }\r\n    }\r\n\r\n    if (!droppableEl || closestDist > 800) {\r\n        console.warn('thumbnailDropped \u2014 aucun droppable proche (dist min:', closestDist, ')');\r\n        return;\r\n    }\r\n    console.log('\ud83d\udccd Droppable trouv\u00e9:', droppableEl.id, '(dist:', Math.round(closestDist), ')');\r\n\r\n    var $droppable = jQuery(droppableEl);\r\n    var rankId = $droppable.attr('id');\r\n    if (!rankId) { console.warn('thumbnailDropped \u2014 .droppable sans id'); return; }\r\n\r\n    var $dropZone = $droppable.find('#drop_file_zone_achat').first();\r\n    if (!$dropZone.length) { console.warn('thumbnailDropped \u2014 #drop_file_zone_achat introuvable dans', rankId); return; }\r\n\r\n    var emplacementRef = StateManager.buildEmplacementReference(rankId);\r\n    console.log('\u2705 thumbnailDropped \u2014 espace pub:', rankId, '\u2192', emplacementRef);\r\n\r\n    \/\/ 2. Pr\u00e9parer l'\u00e9tat SessionStorage (comme un vrai drop de fichier)\r\n    StateManager.setMultiple({\r\n        'Rank_Emplacement_Page_Web':     rankId,\r\n        'Commande_Emplacement_Page_Web': emplacementRef,\r\n        'FirstUploadFileorMoved':        'FirstUpload',\r\n        'Formatchoisi':                  'Yes',\r\n        'Commande_Format_Transmis':      'Image',\r\n        'EnvoiUlterieur':                'false',\r\n        'FileReceived':                  'No',\r\n        'AdDisplayed':                   'Yes'\r\n    });\r\n\r\n    UIManager.updateEmplacementDisplay();\r\n\r\n    \/\/ 3. Construire le File \u00e0 partir du PDF (communiqu\u00e9\/interview) ou de l'image PNG (autres formats)\r\n    \/\/    puis appeler handleFileUpload \u2192 m\u00eame flux qu'un vrai drag&drop : liser\u00e9 vert, aper\u00e7u, R\u00e9server\r\n    var dataURL, mime, ext;\r\n\r\n    \/\/ \u2705 Stocker le PDF image et le format sur le droppable pour le popup de visualisation inline\r\n    if (msg.pdfImageDataURL) {\r\n        $droppable.data('kitPdfImageDataURL', msg.pdfImageDataURL);\r\n        $droppable.data('kitFormatSelect', msg.FormatSelect || '');\r\n        console.log('\ud83d\udcce pdfImageDataURL stock\u00e9 sur', rankId, '| format:', msg.FormatSelect);\r\n    } else {\r\n        $droppable.removeData('kitPdfImageDataURL');\r\n        $droppable.removeData('kitFormatSelect');\r\n    }\r\n    \/\/ v4.9ds : 1\u00e8re image utilisateur extraite par le kit (pour preview mobile JPG-in-PDF)\r\n    if (msg.firstImageDataURL) {\r\n        $droppable.data('kitFirstImageDataURL', msg.firstImageDataURL);\r\n        console.log('\ud83d\uddbc\ufe0f kitFirstImageDataURL stock\u00e9 sur', rankId);\r\n        try { var _dl1 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl1) _dl1('[v4.9ds] thumbnailDropped firstImg stock\u00e9 ' + rankId, 'ok'); } catch(e) {}\r\n    } else {\r\n        $droppable.removeData('kitFirstImageDataURL');\r\n        try { var _dl2 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl2) _dl2('[v4.9ds] thumbnailDropped SANS firstImg ' + rankId, 'warn'); } catch(e) {}\r\n    }\r\n\r\n    if (msg.isPDF ? msg.pdfDataURL : false) {\r\n        \/\/ \u2500\u2500 Format PDF (communiqu\u00e9 \/ interview) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        dataURL = msg.pdfDataURL;                         \/\/ \"data:application\/pdf;base64,\u2026\"\r\n        mime    = 'application\/pdf';\r\n        ext     = '.pdf';\r\n    } else if (msg.imageDataURL) {\r\n        \/\/ \u2500\u2500 Format image (banni\u00e8re \/ parrainage) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        dataURL = msg.imageDataURL;\r\n        var mimeMatch = dataURL.match(\/^data:([^;]+);base64,\/);\r\n        mime    = mimeMatch ? mimeMatch[1] : 'image\/png';\r\n        ext     = mime === 'image\/jpeg' ? '.jpg' : '.png';\r\n    } else {\r\n        console.warn('thumbnailDropped \u2014 aucune donn\u00e9e image\/pdf disponible');\r\n        return;\r\n    }\r\n\r\n    var baseName = msg.filename ? msg.filename.replace(\/\\.[^.]+$\/, '') : 'annonce-kit';\r\n    var fileName = baseName + ext;\r\n\r\n    \/\/ dataURL \u2192 Uint8Array \u2192 Blob \u2192 File\r\n    var b64  = dataURL.split(',')[1];\r\n    var bstr = atob(b64);\r\n    var u8   = new Uint8Array(bstr.length);\r\n    for (var i = 0; i < bstr.length; i++) { u8[i] = bstr.charCodeAt(i); }\r\n    var blob    = new Blob([u8], { type: mime });\r\n    var fileObj = new File([blob], fileName, { type: mime });\r\n\r\n    console.log('thumbnailDropped \u2192 handleFileUpload:', fileName, Math.round(fileObj.size \/ 1024) + 'KB');\r\n\r\n    \/\/ \u2705 Cacher le File pour r\u00e9utilisation lors des d\u00e9placements (\u00e9vite CORS)\r\n    window._lastRedactionnelFile = fileObj;\r\n\r\n    \/\/ \u2705 v2.4.10 : Signaler \u00e0 adjustDesktopLayout que c'est un drop depuis la miniature\r\n    \/\/ \u2192 applique max-height sur HTMLUploadfileConteneur pour \u00e9viter le d\u00e9bordement vertical (homepage corps de page)\r\n    window._dropFromMiniature = true;\r\n    \/\/ \u2705 v2.6 : Poser data-from-miniature sur le droppable pour que selectEspaceActif ignore le scale(1.35)\r\n    \/\/ Sans ce flag, selectEspaceActif applique scale(1.35) sur .HTMLUploadfileConteneur \u2192 zoom Communiqu\u00e9\/Interview\r\n    if ($droppable[0]) { $droppable[0].setAttribute('data-from-miniature', 'true'); }\r\n    UploadManager.handleFileUpload(fileObj, $dropZone);\r\n\r\n    \/\/ \u2705 v1.19.1 : Positionner l'espace \u00e0 10px du haut du viewport (tous formats)\r\n    \/\/ \u2705 v2.4.13 : Skip si drop depuis miniature \u2014 selectEspaceActif g\u00e8re d\u00e9j\u00e0 le scroll\r\n    if (!window._dropFromMiniature) {\r\n        setTimeout(function() {\r\n            var el = $droppable.find('.HTMLUploadfileConteneur')[0] || $droppable[0];\r\n            if (el) {\r\n                ScrollHelper.scrollElementTo(el, 10);\r\n            }\r\n        }, 400);\r\n    }\r\n});\r\n\r\nconsole.log('This is a ' + (UIManager.isDesktop() ? 'desktop' : 'non-desktop') + ' device.');\r\n\r\n\/\/ v2.9 : Masquer les zones pub en mode previsualisation achat\r\nwindow.addEventListener('message', function(e) {\r\n    if (!e.data) { return; }\r\n    if (e.data.action !== 'removeElements') { return; }\r\n    if (!e.data.viaPurchasePreview) { return; }\r\n    var _sid = 'via-purchase-preview-css';\r\n    if (!document.getElementById(_sid)) {\r\n        var _s = document.createElement('style');\r\n        _s.id = _sid;\r\n        _s.textContent = [\r\n            \/* Masquer le formulaire upload complet en mode previsualisation *\/\r\n            '.droppable .OrdiMobileConteneurClass { display: none !important; }',\r\n            '.droppable .reserver-dynamic-container { display: none !important; }',\r\n            '.droppable .HTMLUploadfileConteneur { display: none !important; }',\r\n            '.droppable .EspPubFormatMainContainer { display: none !important; }',\r\n            '.droppable .GlisserDeposerConteneur { display: none !important; }',\r\n            '.droppable .OUClass { display: none !important; }',\r\n            '.droppable .ChoisirEspacePublicitaireClass { display: none !important; }',\r\n            '.droppable .ChoisirEspacePublicitaire2ndLigne { display: none !important; }',\r\n            '.droppable .UploadIci { display: none !important; }',\r\n            '.droppable .EnvoiUlterieurContainer { display: none !important; }',\r\n            '.droppable .TexteMobileAnnonce { display: none !important; }',\r\n            '.droppable .AdDroppedTextNotDisplayed { display: none !important; }',\r\n            '.droppable .HideFormButton { display: none !important; }'\r\n        ].join('\\n');\r\n        document.head.appendChild(_s);\r\n    }\r\n    window.parent.postMessage({ type: 'elementsRemoved' }, '*');\r\n});\r\n\r\n\/\/ \u2705 Bug 10 : handler DelAdInIframe\r\nwindow.addEventListener('message', function(e) {\r\n    var msg = e.data;\r\n    if (!msg || msg.action !== 'DelAdInIframe') return;\r\n    var rankId = msg.RankId || '';\r\n    if (!rankId) return;\r\n    console.log('[Bug10] DelAdInIframe re\u00e7u | rank:', rankId);\r\n    var $droppable = jQuery('#' + rankId);\r\n    if (!$droppable.length) return;\r\n    var $cont = $droppable.find('.OrdiMobileConteneurClass').first();\r\n    var el = $cont.length ? $cont[0] : $droppable[0];\r\n    if (typeof RestoreadSpaceTemplateLocal === 'function') RestoreadSpaceTemplateLocal(el);\r\n    else if (typeof window.RestoreadSpaceTemplate === 'function') window.RestoreadSpaceTemplate(el);\r\n    console.log('[Bug10] espace r\u00e9initialis\u00e9 | rank:', rankId);\r\n});\r\n\r\n\/\/ ============================================================================\r\n\/\/ v4.9ds : Num\u00e9rotation 1\/2\/3 + croix \"Effacer\" sur les espaces publicitaires\r\n\/\/   - Num\u00e9ro 1 : \u00e0 hauteur de la zone Formats (.EspPubFormatMainContainer)\r\n\/\/   - Num\u00e9ro 2 : \u00e0 hauteur de la zone Glisser-d\u00e9poser (.UploadFileConteneur)\r\n\/\/   - Num\u00e9ro 3 : \u00e0 hauteur du label R\u00e9server (statique ou dynamique)\r\n\/\/   - Croix : haut-droite du wrapper visuel (.OrdiMobileConteneurClass), title=\"Effacer\"\r\n\/\/     simple X blanc sans fond ni bordure.\r\n\/\/   Couleurs num\u00e9ros : dor\u00e9 (#D0C067) Ele0A, bleu clair (#9FC5F3) Ele1A+\r\n\/\/   Tous les num\u00e9ros sont plac\u00e9s sur le .droppable directement (top calcul\u00e9 depuis\r\n\/\/   les ancres) \u2192 align\u00e9s verticalement sur la m\u00eame colonne left:8px.\r\n\/\/   Quand annonce d\u00e9pos\u00e9e (.via-ad-header pr\u00e9sent) \u2192 tous masqu\u00e9s (.via-ad-loaded).\r\n\/\/ ============================================================================\r\n(function() {\r\n    if (window._viaNumCroixReady) return;\r\n    window._viaNumCroixReady = true;\r\n\r\n    function _injectStyles() {\r\n        \/\/ \u2705 v4.9ds : marquer le body si la page est dans une iframe (cas du site r\u00e9gie qui\r\n        \/\/   charge les pages pays). Permet \u00e0 la r\u00e8gle CSS .via-step-num de masquer les\r\n        \/\/   num\u00e9ros 1\/2\/3 dans ce contexte. Demande user.\r\n        try {\r\n            if (window !== window.top) {\r\n                if (document.body) {\r\n                    document.body.classList.add('via-page-in-iframe');\r\n                } else {\r\n                    \/\/ Body pas encore pr\u00eat \u2192 re-tenter d\u00e8s qu'il l'est\r\n                    document.addEventListener('DOMContentLoaded', function() {\r\n                        if (document.body) { document.body.classList.add('via-page-in-iframe'); }\r\n                    });\r\n                }\r\n            }\r\n        } catch(_eIf) { \/* cross-origin silencieux *\/ }\r\n        if (document.getElementById('via-num-croix-styles')) return;\r\n        var s = document.createElement('style');\r\n        s.id = 'via-num-croix-styles';\r\n        s.textContent =\r\n            \/\/ Num\u00e9ros mobile (par d\u00e9faut) : 20\u00d720, font 12, left:4\r\n            '.via-step-num{position:absolute;left:4px;width:20px;height:20px;border-radius:50%;' +\r\n              'background:#ffffff;display:flex;align-items:center;justify-content:center;' +\r\n              'font-family:Roboto,Arial,sans-serif;font-weight:700;font-size:12px;line-height:1;' +\r\n              'box-shadow:0 1px 3px rgba(0,0,0,0.15);z-index:10;pointer-events:none;' +\r\n              'transform:translateY(-50%);transition:opacity 0.2s}' +\r\n            \/\/ Desktop (\u22651000px) : +40% (20\u219228, font 12\u219217), left:13 (mobile +9)\r\n            '@media (min-width:1000px){' +\r\n              '.via-step-num{width:28px;height:28px;font-size:17px;left:13px}' +\r\n            '}' +\r\n            \/\/ Couleurs avec sp\u00e9cificit\u00e9 forte + !important\r\n            'html body .via-step-num.via-num-ele0a{color:#F1C40F !important}' +\r\n            'html body .via-step-num.via-num-ele1a{color:#9FC5F3 !important}' +\r\n            \/\/ v4.9ds : \u00e9tapes valid\u00e9es (num\u00e9ros pr\u00e9c\u00e9dents l'\u00e9tape courante) \u2192 vert\r\n            \/\/   Logique data-via-format-gate (cf _updateStepGate) :\r\n            \/\/     - \"locked\" = \u00e9tape 1 active   \u2192 aucun num\u00e9ro vert\r\n            \/\/     - \"step2\"  = \u00e9tape 2 active   \u2192 num\u00e9ro 1 vert (\u00e9tape 1 valid\u00e9e)\r\n            \/\/     - (absent) = \u00e9tape 3 active   \u2192 num\u00e9ros 1 et 2 verts (\u00e9tapes 1+2 valid\u00e9es)\r\n            \/\/   \u00c9tape 4 (R\u00e9server coch\u00e9) \u2192 num\u00e9ros 1, 2 et 3 verts (les 3 \u00e9tapes valid\u00e9es).\r\n            \/\/   D\u00e9tection R\u00e9server : pr\u00e9sence de input[name=\"form_fields[ReserverEspacePublicitaire]\"]:checked\r\n            \/\/   dans la droppable. Le s\u00e9lecteur :has() est support\u00e9 par tous les navigateurs\r\n            \/\/   modernes (Safari 15.4+, Chrome 105+, Firefox 121+) \u2014 si un user a un\r\n            \/\/   navigateur trop ancien, le num\u00e9ro 3 reste \u00e0 sa couleur d'origine (jaune\/bleu),\r\n            \/\/   les 1 et 2 restent verts par les r\u00e8gles pr\u00e9c\u00e9dentes \u2014 pas de r\u00e9gression.\r\n            \/\/   On surcharge la couleur du chiffre via !important + sp\u00e9cificit\u00e9 combin\u00e9e\r\n            \/\/   pour battre la r\u00e8gle de couleur jaune\/bleu pos\u00e9e juste au-dessus.\r\n            \/\/   Vert utilis\u00e9 : #2ECC71 (coh\u00e9rent avec les liser\u00e9s de s\u00e9lection vifs).\r\n            \/\/ \u00c9tape 2 active \u2192 num\u00e9ro 1 valid\u00e9\r\n            'html body .droppable[data-via-format-gate=\"step2\"] .via-step-num[data-via-num=\"1\"],' +\r\n            \/\/ \u00c9tape 3 active (annonce d\u00e9pos\u00e9e OU envoi diff\u00e9r\u00e9 coch\u00e9 \u2014 pas d'attribut gate) \u2192 num\u00e9ros 1 et 2 valid\u00e9s\r\n            'html body .droppable:not([data-via-format-gate]) .via-step-num[data-via-num=\"1\"],' +\r\n            'html body .droppable:not([data-via-format-gate]) .via-step-num[data-via-num=\"2\"],' +\r\n            \/\/ \u00c9tape 4 : R\u00e9server coch\u00e9 \u2192 num\u00e9ro 3 valid\u00e9 (en plus de 1 et 2 d\u00e9j\u00e0 verts via r\u00e8gles ci-dessus)\r\n            'html body .droppable:has(input[name=\"form_fields[ReserverEspacePublicitaire]\"]:checked) .via-step-num[data-via-num=\"3\"]{' +\r\n              'color:#2ECC71 !important' +\r\n            '}' +\r\n            \/\/ Croix mobile : top:0 right:-2 (d\u00e9calage 2px vers la droite demand\u00e9), font 14\r\n            \/\/   pointer-events:auto !important : override l'h\u00e9ritage de pointer-events:none\r\n            \/\/     pos\u00e9 par le format-gate \"locked\" sur le parent .HTMLUploadfileConteneur\r\n            \/\/   z-index 999 : passe AU-DESSUS de tous les \u00e9l\u00e9ments locaux du droppable\r\n            \/\/     (widgets Elementor, overlays format, etc.) MAIS reste SOUS la pastille\r\n            \/\/     jaune .popupAchatAnnonce qui vit dans EnteteBackground (stacking context\r\n            \/\/     isol\u00e9 z:1000 \u2192 pastille plafonn\u00e9e ~1000 au niveau body).\r\n            \/\/   v4.9ds Fix 29 v2 : z:100 toujours insuffisant \u2014 un \u00e9l\u00e9ment local du\r\n            \/\/     droppable avec z-index entre 100 et la pastille captait encore le clic.\r\n            \/\/     Remont\u00e9 \u00e0 999 pour passer au-dessus de tout sauf la pastille.\r\n            \/\/   padding 6 8 : zone de clic plus large (mobile)\r\n            '.via-erase-btn{position:absolute;top:0px;right:-2px;' +\r\n              'cursor:pointer;z-index:999;pointer-events:auto !important;' +\r\n              'font-family:Roboto,Arial,sans-serif;font-weight:900;font-size:14px;line-height:1;' +\r\n              'color:#ffffff;user-select:none;padding:6px 8px;' +\r\n              'background:transparent !important;border:none !important;border-radius:0 !important;' +\r\n              'box-shadow:none !important;text-shadow:none !important}' +\r\n            \/\/ Desktop : croix top:3 right:3, font 16 (taille originale)\r\n            '@media (min-width:1000px){' +\r\n              '.via-erase-btn{top:3px;right:3px;font-size:16px}' +\r\n            '}' +\r\n            '.via-erase-btn:hover{opacity:0.8}' +\r\n            \/\/ Quand annonce d\u00e9pos\u00e9e \u2192 masquer imm\u00e9diatement num\u00e9ros + croix\r\n            \/\/   D\u00e9tection par 2 signaux pour activation au plus t\u00f4t :\r\n            \/\/   - data-via-ad-loaded=\"true\" (pos\u00e9 par espace_publicitaire.txt:805 d\u00e8s d\u00e9p\u00f4t)\r\n            \/\/   - .via-ad-loaded (classe pos\u00e9e par mon _updateAdLoadedClass via MutationObserver)\r\n            'html body .droppable[data-via-ad-loaded=\"true\"] .via-step-num,' +\r\n            'html body .droppable[data-via-ad-loaded=\"true\"] .via-erase-btn,' +\r\n            'html body .droppable.via-ad-loaded .via-step-num,' +\r\n            'html body .droppable.via-ad-loaded .via-erase-btn{display:none !important}' +\r\n            \/\/ \u2705 v4.9ds : sur le site r\u00e9gie, les pages pays sont charg\u00e9es dans une iframe.\r\n            \/\/   L'utilisateur a demand\u00e9 que les num\u00e9ros 1\/2\/3 ne soient pas affich\u00e9s dans\r\n            \/\/   ce contexte (coh\u00e9rent : sur la r\u00e9gie, le user manipule un panier \u2014 pas un\r\n            \/\/   process d'\u00e9tapes complet, donc les num\u00e9ros n'ont pas de sens).\r\n            \/\/   D\u00e9tection : window !== window.top (la page est dans une iframe). Marqueur\r\n            \/\/   pos\u00e9 en classe sur <body> pour permettre une r\u00e8gle CSS pure dans ce bloc.\r\n            \/\/   Le marqueur est pos\u00e9 via JS plus bas (apr\u00e8s DOM ready).\r\n            'html body.via-page-in-iframe .via-step-num{display:none !important}' +\r\n            \/\/ Conteneur ancr\u00e9 en relative + overflow visible (num\u00e9ros pouvant d\u00e9border)\r\n            '.via-num-anchor{position:relative !important;overflow:visible !important}' +\r\n            \/\/ \u2500\u2500 OPACIT\u00c9S DES NUM\u00c9ROS calqu\u00e9es sur les zones d\u00e9sactiv\u00e9es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/   Num\u00e9ro 2 (zone GD+Envoi diff\u00e9r\u00e9) : opacit\u00e9 0.6 quand \"locked\" (\u00e9tape 1)\r\n            \/\/   Num\u00e9ro 3 (zone R\u00e9server) : opacit\u00e9 0.6 quand \"locked\" OU \"step2\" (\u00e9tapes 1+2)\r\n            'html body .droppable[data-via-format-gate=\"locked\"] .via-step-num[data-via-num=\"2\"],' +\r\n            'html body .droppable[data-via-format-gate=\"locked\"] .via-step-num[data-via-num=\"3\"],' +\r\n            'html body .droppable[data-via-format-gate=\"step2\"] .via-step-num[data-via-num=\"3\"]{' +\r\n              'opacity:0.6}' +\r\n            \/\/ \u2500\u2500 v4.9ds : OVERRIDES SITE R\u00c9GIE (html.via-regie-iframe) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/   Sur la r\u00e9gie (iframe yearbook-iframe sous la page r\u00e9gie), demande user :\r\n            \/\/   - Mobile : 10px en dessous du bouton .HideFormButton.ReserverBouton\r\n            \/\/              (espace pub plus haut visuellement par le bas)\r\n            \/\/   - Le `left` est pos\u00e9 EN INLINE dans _placeNum (CSS pouvait \u00eatre outrepass\u00e9)\r\n            \/\/   - D\u00e9calage vertical N\u00b02\/N\u00b03 desktop+mobile g\u00e9r\u00e9 dans _numTopOffset\r\n            '@media (max-width:999px){' +\r\n              'html.via-regie-iframe .HideFormButton.ReserverBouton{margin-bottom:10px !important}' +\r\n            '}';\r\n        document.head.appendChild(s);\r\n    }\r\n\r\n    function _makeNum(n, isEle0A) {\r\n        var span = document.createElement('span');\r\n        span.className = 'via-step-num ' + (isEle0A ? 'via-num-ele0a' : 'via-num-ele1a');\r\n        span.textContent = String(n);\r\n        span.setAttribute('data-via-num', String(n));\r\n        return span;\r\n    }\r\n\r\n    function _makeEraseBtn() {\r\n        var btn = document.createElement('span');\r\n        btn.className = 'via-erase-btn';\r\n        btn.setAttribute('title', 'Effacer');\r\n        btn.setAttribute('data-via-erase', '1');\r\n        btn.textContent = '\\u2715'; \/\/ \u2715\r\n        return btn;\r\n    }\r\n\r\n    \/\/ Trouver l'ancre pour un num\u00e9ro donn\u00e9\r\n    function _findAnchor(drop, n) {\r\n        var $drop = jQuery(drop);\r\n        \/\/ Pour n=3, .ReserverContainer est un FR\u00c8RE du droppable (pas un descendant) \u2192\r\n        \/\/   \u00e9largir le scope au parent. Pour n=1 et n=2 on reste dans le droppable.\r\n        var $scope = (n === 3) ? $drop.parent() : $drop;\r\n        \/\/ Helper : essayer plusieurs s\u00e9lecteurs dans l'ordre, retourner le premier visible non vide\r\n        function _firstVisible(selectors) {\r\n            for (var i = 0; i < selectors.length; i++) {\r\n                var $set = $scope.find(selectors[i]);\r\n                var $vis = $set.filter(':visible').filter(function() {\r\n                    var r = this.getBoundingClientRect();\r\n                    return r.width > 0 ? r.height > 0 : false;\r\n                });\r\n                if ($vis.length) {\r\n                    return { el: $vis.first()[0], sel: selectors[i] };\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n        \/\/ Variante permissive : ne filtre PAS sur :visible (utile pour R\u00e9server qui peut\r\n        \/\/   \u00eatre en pointer-events:none ou opacity:0 mais avec dimensions r\u00e9elles)\r\n        function _firstWithDims(selectors) {\r\n            for (var i = 0; i < selectors.length; i++) {\r\n                var $set = $scope.find(selectors[i]);\r\n                for (var j = 0; j < $set.length; j++) {\r\n                    var el = $set[j];\r\n                    var r = el.getBoundingClientRect();\r\n                    if (r.width > 0 ? r.height > 0 : false) {\r\n                        return { el: el, sel: selectors[i] + ' (permissif)' };\r\n                    }\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n        if (n === 1) {\r\n            return _firstVisible([\r\n                '.SelectionFormatTitre',\r\n                '.EspPubFormatMainContainer .EspPubFormatContainer'\r\n            ]);\r\n        }\r\n        if (n === 2) {\r\n            return _firstVisible([\r\n                '.UploadIci',\r\n                '#drop_file_zone_achat .UploadIci',\r\n                '#drag_upload_file_achat',\r\n                '#drop_file_zone_achat',\r\n                '.GlisserDeposerConteneur'\r\n            ]);\r\n        }\r\n        if (n === 3) {\r\n            \/\/ Bouton R\u00e9server Elementor : .ReserverContainer \/ .ReserverBouton sont\r\n            \/\/   FR\u00c8RES du droppable (cf HTML user). Le label visible est dans\r\n            \/\/   .elementor-field-subgroup label (le screen-only n'a pas de dimensions).\r\n            return _firstVisible([\r\n                'label[for=\"form-field-ReserverEspacePublicitaire-0\"]',  \/\/ label visible\r\n                '.ReserverBouton .elementor-field-subgroup label',\r\n                '.ReserverContainer .elementor-field-subgroup label',\r\n                '.ReserverBouton',\r\n                '.ReserverContainer',\r\n                '.reserver-dynamic-label',\r\n                '.reserver-dynamic-container'\r\n            ]) || _firstWithDims([\r\n                '.ReserverBouton',\r\n                '.ReserverContainer',\r\n                '.elementor-field-group-ReserverEspacePublicitaire'\r\n            ]) || (function() {\r\n                \/\/ Dernier recours : recherche par TEXTE dans le scope \u00e9largi\r\n                var found = null;\r\n                $scope.find('label, span, p, div').each(function() {\r\n                    if (found) return;\r\n                    var t = (this.textContent || '').replace(\/\\s+\/g, ' ').trim();\r\n                    if (t.indexOf('R\u00e9server cet espace') === -1) return;\r\n                    var hasChildWithText = jQuery(this).children().toArray().some(function(c) {\r\n                        return ((c.textContent || '').replace(\/\\s+\/g, ' ').trim()).indexOf('R\u00e9server cet espace') !== -1;\r\n                    });\r\n                    if (hasChildWithText) return;\r\n                    var r = this.getBoundingClientRect();\r\n                    if (r.width > 0 ? r.height > 0 : false) {\r\n                        found = this;\r\n                    }\r\n                });\r\n                return found ? { el: found, sel: ':contains(R\u00e9server) (texte)' } : null;\r\n            })();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    \/\/ D\u00e9calage vertical (en px) \u00e0 appliquer \u00e0 un num\u00e9ro selon position + plateforme + Ele0A vs Ele1A+\r\n    \/\/   Base = 40\r\n    \/\/   Desktop  : N\u00b01 = 47 \/ N\u00b02 = 70 \/ N\u00b03 = 56 (Ele0A et Ele1A+ identiques)\r\n    \/\/   Mobile Ele1A+ : N\u00b01 = 24 \/ N\u00b02 = 23 \/ N\u00b03 = 1\r\n    \/\/   Mobile Ele0A  : N\u00b01 = 42 \/ N\u00b02 = 32 \/ N\u00b03 = 9\r\n    \/\/   v4.9ds : Compensation Ele0A mobile quand bouton Cr\u00e9ation est actif (creationActive===true).\r\n    \/\/     Le clic sur Cr\u00e9ation d\u00e9clenche un effet de layout (probablement repaint Elementor \/\r\n    \/\/     stacking flexbox \/ re-application de styles inline) qui d\u00e9cale les 3 num\u00e9ros de\r\n    \/\/     +10px vers le bas. On compense ici pour stabilit\u00e9 visuelle. Au toggle OFF\r\n    \/\/     (re-click Cr\u00e9ation), creationActive repasse \u00e0 false \u2192 compensation 0 \u2192 num\u00e9ros\r\n    \/\/     reviennent \u00e0 leur position d'origine.\r\n    \/\/   v4.9ds : d\u00e9calage vertical SITE R\u00c9GIE (Ele1A+) \u2014 demande user :\r\n    \/\/     - Desktop r\u00e9gie Ele1A+ : N\u00b01 = 0, N\u00b02 -10, N\u00b03 -15 (vers le haut)\r\n    \/\/     - Mobile r\u00e9gie Ele1A+  : N\u00b01 = 0, N\u00b02 +10 (bas), N\u00b03 = 0\r\n    \/\/   Le d\u00e9calage horizontal -3px desktop est pos\u00e9 en inline dans _placeNum.\r\n    function _numTopOffset(n, isEle0A, drop) {\r\n        var isMobile = window.innerWidth < 1000;\r\n        var base = 40;\r\n        \/\/ v4.9ds : compensation _creaComp retir\u00e9e \u2014 les num\u00e9ros restent fixes\r\n        \/\/   peu importe le format s\u00e9lectionn\u00e9. ResizeObserver g\u00e8re d\u00e9sormais les\r\n        \/\/   reflows r\u00e9els (cf _viaNumObserve). Les ancres bougent peu au clic\r\n        \/\/   format en r\u00e9alit\u00e9, le d\u00e9calage per\u00e7u \u00e9tait d\u00fb au calcul fait juste\r\n        \/\/   avant que le re-layout Elementor soit appliqu\u00e9.\r\n        \/\/ v4.9ds : d\u00e9calage r\u00e9gie pour Ele1A+ uniquement\r\n        \/\/   Desktop : N\u00b02 -10, N\u00b03 -15 ; Mobile : N\u00b02 +10 ; reste \u00e0 0\r\n        var _regieCompN1 = 0;\r\n        var _regieCompN2 = 0;\r\n        var _regieCompN3 = 0;\r\n        if (!isEle0A) {\r\n            var _isRegie = document.documentElement.classList.contains('via-regie-iframe');\r\n            if (_isRegie) {\r\n                if (isMobile) {\r\n                    _regieCompN2 = 10;\r\n                } else {\r\n                    _regieCompN2 = -10;\r\n                    _regieCompN3 = -15;\r\n                }\r\n            }\r\n        }\r\n        if (isMobile) {\r\n            if (isEle0A) {\r\n                if (n === 1) return base + 2;    \/\/ 42\r\n                if (n === 2) return base - 18;   \/\/ 22\r\n                return base - 41;                 \/\/ -1\r\n            }\r\n            \/\/ Ele1A+ mobile\r\n            if (n === 1) return base - 16 + _regieCompN1;    \/\/ 24\r\n            if (n === 2) return base - 17 + _regieCompN2;    \/\/ 23 \u2192 33 r\u00e9gie\r\n            return base - 39 + _regieCompN3;                  \/\/  1\r\n        } else {\r\n            \/\/ Desktop (Ele0A et Ele1A+ identiques sauf r\u00e9gie sur Ele1A+)\r\n            if (n === 1) return base + 7 + _regieCompN1;     \/\/ 47\r\n            if (n === 2) return base + 30 + _regieCompN2;    \/\/ 70 \u2192 60 r\u00e9gie\r\n            return base + 16 + _regieCompN3;                  \/\/ 56 \u2192 41 r\u00e9gie\r\n        }\r\n    }\r\n\r\n    \/\/ Placer (ou repositionner) un num\u00e9ro dans le container selon son ancre\r\n    \/\/ v4.9ds : ResizeObserver global pour stabiliser les positions des num\u00e9ros\r\n    \/\/   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    \/\/   Probl\u00e8me de fond : les num\u00e9ros 1\/2\/3 sont positionn\u00e9s en `position:absolute;\r\n    \/\/   top:<calcul\u00e9>` \u00e0 partir de `getBoundingClientRect()` sur leur ancre. Ce calcul\r\n    \/\/   est un snapshot \u2014 si l'ancre bouge ensuite (Elementor async, images charg\u00e9es,\r\n    \/\/   fonts custom, transitions, transforms\u2026), le num\u00e9ro reste fig\u00e9.\r\n    \/\/   \r\n    \/\/   Solution : observer les changements de TAILLE\/POSITION des ancres et du\r\n    \/\/   conteneur via ResizeObserver. Quand un changement est d\u00e9tect\u00e9, on relance\r\n    \/\/   le calcul automatiquement. Plus propre que les passes setTimeout (instantan\u00e9,\r\n    \/\/   pas de polling, d\u00e9clench\u00e9 exactement quand c'est n\u00e9cessaire).\r\n    \/\/   \r\n    \/\/   Support : Safari 13.1+, Chrome 64+, Firefox 69+ (>97% du march\u00e9).\r\n    \/\/   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    var _viaNumResizeObs = null;\r\n    var _viaNumObservedEls = (typeof WeakSet !== 'undefined') ? new WeakSet() : null;\r\n    var _viaNumScheduleAfterRO = null; \/\/ inject\u00e9 par _installWatcher au boot\r\n    function _ensureNumResizeObs() {\r\n        if (_viaNumResizeObs) return _viaNumResizeObs;\r\n        if (typeof ResizeObserver === 'undefined') return null; \/\/ navigateur trop ancien\r\n        _viaNumResizeObs = new ResizeObserver(function() {\r\n            \/\/ Replanification debounced via _processAll (passe par _scheduleProcess\r\n            \/\/   du watcher pour rAF debounce, sinon fallback direct)\r\n            if (typeof _viaNumScheduleAfterRO === 'function') {\r\n                _viaNumScheduleAfterRO();\r\n            } else {\r\n                try { _processAll(); } catch (_e) {}\r\n            }\r\n        });\r\n        return _viaNumResizeObs;\r\n    }\r\n    function _viaNumObserve(el) {\r\n        if (!el) return;\r\n        var ro = _ensureNumResizeObs();\r\n        if (!ro) return;\r\n        if (_viaNumObservedEls) {\r\n            if (_viaNumObservedEls.has(el)) return;\r\n            _viaNumObservedEls.add(el);\r\n        }\r\n        try { ro.observe(el); } catch (_e) {}\r\n    }\r\n\r\n    function _placeNum(container, drop, n, isEle0A) {\r\n        var num = container.querySelector(':scope > .via-step-num[data-via-num=\"' + n + '\"]');\r\n        if (!num) {\r\n            num = _makeNum(n, isEle0A);\r\n            container.appendChild(num);\r\n        }\r\n        \/\/ Forcer la bonne classe couleur \u00e0 chaque passe (au cas o\u00f9 isEle0A aurait chang\u00e9,\r\n        \/\/ ou si une classe parasite a \u00e9t\u00e9 pos\u00e9e \u00e0 la cr\u00e9ation)\r\n        var wantedClass = 'via-step-num ' + (isEle0A ? 'via-num-ele0a' : 'via-num-ele1a');\r\n        if (num.className !== wantedClass) { num.className = wantedClass; }\r\n\r\n        \/\/ v4.9ds : forcer le `left` inline (le CSS via-regie-iframe peut \u00eatre outrepass\u00e9\r\n        \/\/   par des r\u00e8gles plus sp\u00e9cifiques dans certains cas). Hors r\u00e9gie : valeurs par\r\n        \/\/   d\u00e9faut (4 mobile \/ 13 desktop). R\u00e9gie : -3px desktop (10), inchang\u00e9 mobile (4).\r\n        var _isRegieLeft = document.documentElement.classList.contains('via-regie-iframe');\r\n        var _isMobLeft = window.innerWidth < 1000;\r\n        var _leftPx;\r\n        if (_isRegieLeft) {\r\n            _leftPx = _isMobLeft ? 4 : 10;\r\n        } else {\r\n            _leftPx = _isMobLeft ? 4 : 13;\r\n        }\r\n        num.style.setProperty('left', _leftPx + 'px', 'important');\r\n\r\n        var anchorInfo = _findAnchor(drop, n);\r\n        var anchor = anchorInfo ? anchorInfo.el : null;\r\n\r\n        if (anchor) {\r\n            \/\/ v4.9ds : observer l'ancre \u2014 son moindre changement de taille\/position\r\n            \/\/   relancera le calcul automatiquement (pas de polling setTimeout).\r\n            _viaNumObserve(anchor);\r\n            var ancRect = anchor.getBoundingClientRect();\r\n            if (ancRect.height > 0 ? ancRect.width > 0 : false) {\r\n                var contRect = container.getBoundingClientRect();\r\n                num.style.display = '';\r\n                num.style.top = (ancRect.top - contRect.top + ancRect.height \/ 2 + _numTopOffset(n, isEle0A, drop)) + 'px';\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ Pas d'ancre exploitable\r\n        if (n === 3) {\r\n            \/\/ Pour le 3, fallback fixe en bas du container \u2014 mais seulement si le container\r\n            \/\/   a une hauteur exploitable (sinon le num\u00e9ro serait \u00e0 top:30 invisible).\r\n            var contRect3 = container.getBoundingClientRect();\r\n            if (contRect3.height > 50) {\r\n                num.style.display = '';\r\n                num.style.top = (contRect3.height + 30) + 'px';\r\n            } else {\r\n                num.style.display = 'none';\r\n            }\r\n            return;\r\n        }\r\n        \/\/ 1 et 2 : masquer si pas d'ancre\r\n        num.style.display = 'none';\r\n    }\r\n\r\n    \/\/ D\u00e9corer un seul espace .droppable\r\n    function _decorateDroppable(drop) {\r\n        if (!drop) return;\r\n        var rankId = drop.id || '';\r\n        var isEle0A = rankId === 'Ele0A';\r\n        var isEleNA = \/^Ele\\d+A$\/.test(rankId);\r\n        if (!isEle0A ? !isEleNA : false) return;\r\n\r\n        var $drop = jQuery(drop);\r\n        \/\/ Conteneur pour num\u00e9ros + croix = #UploadFileConteneur (wrapper visuel color\u00e9)\r\n        var container = drop.querySelector('#UploadFileConteneur')\r\n            || drop.querySelector('.UploadFileConteneur')\r\n            || drop;\r\n        jQuery(container).addClass('via-num-anchor');\r\n\r\n        \/\/ v4.9ds : observer le container et le droppable \u2014 un changement de taille\r\n        \/\/   sur l'un de ces \u00e9l\u00e9ments d\u00e9cale aussi les num\u00e9ros (via contRect dans\r\n        \/\/   le calcul de top). Idempotent gr\u00e2ce au WeakSet anti-doublons.\r\n        _viaNumObserve(container);\r\n        _viaNumObserve(drop);\r\n\r\n        \/\/ \u2500\u2500 Croix Effacer : top-right du #UploadFileConteneur \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/    Le listener click est pos\u00e9 via d\u00e9l\u00e9gation document dans _installStepHooks\r\n        \/\/    (r\u00e9siste aux clones DOM Ele0A). Ici on cr\u00e9e juste l'\u00e9l\u00e9ment.\r\n        if (!container.querySelector(':scope > .via-erase-btn')) {\r\n            container.appendChild(_makeEraseBtn());\r\n        }\r\n\r\n        \/\/ \u2500\u2500 Num\u00e9ros : tous dans #UploadFileConteneur (align\u00e9s left:8) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        _placeNum(container, drop, 1, isEle0A);\r\n        _placeNum(container, drop, 2, isEle0A);\r\n        _placeNum(container, drop, 3, isEle0A);\r\n    }\r\n\r\n    \/\/ Mise \u00e0 jour de la classe .via-ad-loaded selon pr\u00e9sence de .via-ad-header\r\n    function _updateAdLoadedClass(drop) {\r\n        if (!drop ? true : !drop.classList) return;\r\n        \/\/ D\u00e9tecter \"annonce d\u00e9pos\u00e9e OU en cours de d\u00e9p\u00f4t\" via plusieurs signaux \u2014\r\n        \/\/   pris dans l'ordre du flux d'upload (du plus pr\u00e9coce au plus tardif) :\r\n        \/\/   - .via-loading-inline \/ .via-loading-overlay (loading ins\u00e9r\u00e9 d\u00e8s le d\u00e9but upload)\r\n        \/\/   - <img> dans #drop_file_zone_achat ou .HTMLUploadfileConteneur (preview rendue)\r\n        \/\/   - data-via-ad-loaded=\"true\" (attribut pos\u00e9 par espace_publicitaire.txt:805)\r\n        \/\/   - .via-ad-header (header dynamique, pos\u00e9 apr\u00e8s tout)\r\n        var hasLoading = !!drop.querySelector('.via-loading-inline, .via-loading-overlay');\r\n        var hasImg = !!drop.querySelector('#drop_file_zone_achat img, .HTMLUploadfileConteneur img');\r\n        var hasAttr = drop.getAttribute('data-via-ad-loaded') === 'true';\r\n        var hasHeader = !!drop.querySelector('.via-ad-header');\r\n        if (hasLoading ? true : (hasImg ? true : (hasAttr ? true : hasHeader))) {\r\n            drop.classList.add('via-ad-loaded');\r\n        } else {\r\n            drop.classList.remove('via-ad-loaded');\r\n        }\r\n    }\r\n\r\n    \/\/ v4.9ds : m\u00e9canisme \u00e0 3 \u00e9tapes \u2014 pose data-via-format-gate selon l'\u00e9tat r\u00e9el\r\n    \/\/   - \"locked\"  \u2192 \u00e9tape 1 : aucun format \u2192 2 (GD+Envoi diff) ET 3 (R\u00e9server) gris\u00e9es\r\n    \/\/   - \"step2\"   \u2192 \u00e9tape 2 : format choisi \u2192 seul 3 (R\u00e9server) gris\u00e9\r\n    \/\/   - (rien)    \u2192 \u00e9tape 3 : envoi diff\u00e9r\u00e9 coch\u00e9 OU annonce d\u00e9pos\u00e9e \u2192 tout actif\r\n    function _updateStepGate(drop) {\r\n        if (!drop) return;\r\n        var $drop = jQuery(drop);\r\n\r\n        \/\/ Annonce d\u00e9pos\u00e9e \u2192 \u00e9tape 3 (tout actif, pas d'attribut)\r\n        var hasAdHeader = !!drop.querySelector('.via-ad-header');\r\n        if (hasAdHeader) {\r\n            drop.removeAttribute('data-via-format-gate');\r\n            return;\r\n        }\r\n\r\n        \/\/ Envoi diff\u00e9r\u00e9 coch\u00e9 DANS CE droppable \u2192 \u00e9tape 3\r\n        var envoiDiffereCoche = $drop.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        if (envoiDiffereCoche) {\r\n            drop.removeAttribute('data-via-format-gate');\r\n            return;\r\n        }\r\n\r\n        \/\/ Format choisi (un .EspPubFormatContainer hors Cr\u00e9ation\/PopUp avec fond blanc)\r\n        \/\/ OU une vignette Cr\u00e9ation active (data creationActive=true)\r\n        var formatChoisi = $drop.find('.EspPubFormatContainer')\r\n            .not('.FormatIdCreation').not('.FormatIdPopUp')\r\n            .toArray().some(function(el) {\r\n                return jQuery(el).css('background-color') === 'rgb(255, 255, 255)';\r\n            });\r\n        if (!formatChoisi) {\r\n            \/\/ V\u00e9rifier aussi Cr\u00e9ation active\r\n            var $crea = $drop.find('.FormatIdCreation').first();\r\n            if ($crea.length ? $crea.data('creationActive') === true : false) {\r\n                formatChoisi = true;\r\n            }\r\n        }\r\n        if (formatChoisi) {\r\n            drop.setAttribute('data-via-format-gate', 'step2');\r\n            return;\r\n        }\r\n\r\n        \/\/ Sinon \u2192 \u00e9tape 1\r\n        drop.setAttribute('data-via-format-gate', 'locked');\r\n    }\r\n\r\n    \/\/ D\u00e9corer + maintenir l'\u00e9tat pour tous les espaces pr\u00e9sents\r\n    function _processAll() {\r\n        _injectStyles();\r\n        document.querySelectorAll('.droppable').forEach(function(drop) {\r\n            _decorateDroppable(drop);\r\n            _updateAdLoadedClass(drop);\r\n        });\r\n        \/\/ Format-gate : pr\u00e9f\u00e9rer la fonction officielle (couvre tous les droppables)\r\n        try {\r\n            if (typeof window._viaUpdateFormatGate === 'function') {\r\n                window._viaUpdateFormatGate();\r\n            } else {\r\n                document.querySelectorAll('.droppable').forEach(_updateStepGate);\r\n            }\r\n        } catch (_e) {}\r\n    }\r\n\r\n    \/\/ Installer hooks pour r\u00e9agir aux clics format \/ change envoi diff\u00e9r\u00e9\r\n    \/\/   (transition imm\u00e9diate sans attendre MutationObserver)\r\n    function _refreshGate(drop) {\r\n        \/\/ Pr\u00e9f\u00e9rer la fonction officielle qui parcourt tous les droppables\r\n        try {\r\n            if (typeof window._viaUpdateFormatGate === 'function') {\r\n                window._viaUpdateFormatGate();\r\n                return;\r\n            }\r\n        } catch (_e) {}\r\n        \/\/ Fallback local\r\n        try { _updateStepGate(drop); } catch (_e) {}\r\n    }\r\n    function _installStepHooks() {\r\n        try {\r\n            jQuery(document).on('click', '.droppable .EspPubFormatContainer', function() {\r\n                var drop = jQuery(this).closest('.droppable')[0];\r\n                \/\/ D\u00e9lai pour laisser les autres handlers (s\u00e9lection format) finir\r\n                \/\/   v4.9ds : NE PAS appeler _processAll ici \u2014 le ferait dans un \u00e9tat DOM\r\n                \/\/   instable (post-clic, styles Elementor en cours d'application) \u2192 calcul\r\n                \/\/   des rects produit +10px parasite. Le _processAll est triggered ailleurs\r\n                \/\/   (MutationObserver childList sur popup parent, re-passes initiales).\r\n                setTimeout(function() { _refreshGate(drop); }, 100);\r\n                setTimeout(function() { _refreshGate(drop); }, 300);\r\n            });\r\n            jQuery(document).on('change', '.droppable input[name*=\"EnvoiUlterieur\"]', function() {\r\n                var drop = jQuery(this).closest('.droppable')[0];\r\n                setTimeout(function() { _refreshGate(drop); }, 50);\r\n            });\r\n            \/\/ \u2500\u2500 Croix Effacer : d\u00e9l\u00e9gation document \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/    \u00c9vite la perte de listener si le DOM est reclon\u00e9 (popup Ele0A).\r\n            \/\/ \u2705 v4.9ds Fix 28 : le handler appelait window.FonctionCroixResetAnnonce\r\n            \/\/   ou un trigger sur #CroixResetAnnonce \u2014 mais cet \u00e9l\u00e9ment n'existe plus\r\n            \/\/   dans le DOM (remplac\u00e9 par .via-erase-btn). R\u00e9sultat : le clic \u00e9tait\r\n            \/\/   bien d\u00e9tect\u00e9 ([via-erase-btn] click d\u00e9tect\u00e9 logg\u00e9) mais aucune action\r\n            \/\/   ne se d\u00e9clenchait. Solution : appel direct \u00e0 AdResetHandler.handle(e)\r\n            \/\/   d\u00e9fini ligne 5212 \u2014 c'est exactement la m\u00eame fonction qui \u00e9tait\r\n            \/\/   attach\u00e9e \u00e0 #CroixResetAnnonce ligne 5948 (`(e) => AdResetHandler.handle(e)`).\r\n            \/\/   AdResetHandler.handle utilise e.currentTarget pour trouver le droppable\r\n            \/\/   parent \u2014 \u00e7a fonctionne avec .via-erase-btn comme avec #CroixResetAnnonce.\r\n            jQuery(document).on('click', '.via-erase-btn', function(e) {\r\n                console.log('[via-erase-btn] click d\u00e9tect\u00e9 | rank:', jQuery(this).closest('.droppable').attr('id'));\r\n                e.preventDefault();\r\n                e.stopPropagation();\r\n                if (e.stopImmediatePropagation) { e.stopImmediatePropagation(); }\r\n                \/\/ \u2705 Fix 28 : appel direct du handler AdResetHandler (anciennement bind \u00e0\r\n                \/\/   #CroixResetAnnonce qui n'existe plus). Garde un fallback sur\r\n                \/\/   FonctionCroixResetAnnonce si AdResetHandler n'est pas accessible.\r\n                if (typeof AdResetHandler !== 'undefined' ? typeof AdResetHandler.handle === 'function' : false) {\r\n                    AdResetHandler.handle(e);\r\n                    return;\r\n                }\r\n                \/\/ Fallback historique (au cas o\u00f9 AdResetHandler n'est pas dans ce scope)\r\n                var $droppable = jQuery(this).closest('.droppable');\r\n                var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n                if (typeof window.FonctionCroixResetAnnonce === 'function' ? !!_croixEl : false) {\r\n                    window.FonctionCroixResetAnnonce(_croixEl);\r\n                    return;\r\n                }\r\n                if (_croixEl) { jQuery(_croixEl).trigger('click'); }\r\n                console.warn('[via-erase-btn] ni AdResetHandler ni #CroixResetAnnonce trouv\u00e9s \u2014 reset non effectu\u00e9');\r\n            });\r\n        } catch (_e) {}\r\n    }\r\n\r\n    function _installWatcher() {\r\n        try {\r\n            var _scheduled = false;\r\n            var _scheduleProcess = function() {\r\n                if (_scheduled) return;\r\n                _scheduled = true;\r\n                requestAnimationFrame(function() {\r\n                    _scheduled = false;\r\n                    _processAll();\r\n                });\r\n            };\r\n            \/\/ v4.9ds : exposer _scheduleProcess au ResizeObserver pour debounce rAF\r\n            \/\/   commun. Quand une ancre\/container change de taille, le RO d\u00e9clenche\r\n            \/\/   _scheduleProcess qui replanifie un seul _processAll par frame.\r\n            _viaNumScheduleAfterRO = _scheduleProcess;\r\n            var obs = new MutationObserver(function(muts) {\r\n                \/\/ R\u00e9action INSTANTAN\u00c9E (pas via rAF) sur les changements d'attribut\r\n                \/\/   data-via-ad-loaded \u2014 masquage imm\u00e9diat des num\u00e9ros + croix\r\n                for (var i = 0; i < muts.length; i++) {\r\n                    var m = muts[i];\r\n                    if (m.type === 'attributes' ? m.attributeName === 'data-via-ad-loaded' : false) {\r\n                        if (m.target ? m.target.classList : false) {\r\n                            _updateAdLoadedClass(m.target);\r\n                        }\r\n                    }\r\n                }\r\n                \/\/ Replanification standard pour le reste (num\u00e9ros, croix, gate)\r\n                \/\/   Cela couvre l'ajout\/retrait de droppables, changements DOM\r\n                \/\/   (pop-up Ele0A cr\u00e9\u00e9e\/clon\u00e9e, etc.). Le ResizeObserver couvre\r\n                \/\/   les changements de g\u00e9om\u00e9trie sur les ancres existantes.\r\n                _scheduleProcess();\r\n            });\r\n            obs.observe(document.body, {\r\n                childList: true,\r\n                subtree: true,\r\n                attributes: true,\r\n                attributeFilter: ['data-via-ad-loaded']\r\n            });\r\n            \/\/ v4.9ds : resize viewport reste branch\u00e9 (ResizeObserver couvre les\r\n            \/\/   \u00e9l\u00e9ments individuels mais pas les changements globaux de viewport\r\n            \/\/   qui peuvent affecter les valeurs de _numTopOffset bas\u00e9es sur\r\n            \/\/   window.innerWidth).\r\n            window.addEventListener('resize', _scheduleProcess);\r\n        } catch (_e) {}\r\n    }\r\n\r\n    function _boot() {\r\n        _injectStyles();\r\n        _processAll();\r\n        _installWatcher();\r\n        _installStepHooks();\r\n        \/\/ v4.9ds : passes setTimeout supprim\u00e9es \u2014 le ResizeObserver branch\u00e9 sur\r\n        \/\/   chaque ancre\/container d\u00e9tecte automatiquement les changements de\r\n        \/\/   g\u00e9om\u00e9trie (Elementor async, images charg\u00e9es, fonts custom, etc.)\r\n        \/\/   et relance _processAll au bon moment.\r\n        \/\/   On garde une seule passe \u00e0 1000ms pour le cas o\u00f9 le ResizeObserver\r\n        \/\/   ne serait pas support\u00e9 (tr\u00e8s anciens navigateurs) ou pour couvrir\r\n        \/\/   les ancres qui apparaissent en display:none initial puis sont\r\n        \/\/   ajout\u00e9es au DOM (le RO ne les observe pas tant qu'elles n'existent\r\n        \/\/   pas dans la d\u00e9coration \u2192 un retry \u00e0 1s couvre ce cas).\r\n        setTimeout(_processAll, 1000);\r\n    }\r\n\r\n    if (document.readyState === 'loading') {\r\n        document.addEventListener('DOMContentLoaded', _boot);\r\n    } else {\r\n        _boot();\r\n    }\r\n})();\r\n\r\n} \/\/ end _espPubScriptLoaded guard\r\n<\/script>\r\n\r\n\r\n<style>\r\n\/* ============================================================================\r\n   UFC HAUTEUR \u2014 colle \u00e0 l'image, cap via max-height (source unique de v\u00e9rit\u00e9)\r\n   ============================================================================ *\/\r\n.via-ad-wrapper .HTMLUploadfileConteneur,\r\n.via-ad-wrapper .HTMLUploadfileConteneur .elementor-widget-container,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #HTMLUploadfile,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #PopUpMessageAchattest,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n    height: auto !important;\r\n    min-height: 0 !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur {\r\n    max-height: 170px !important;\r\n    overflow: hidden !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n    max-height: 160px !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur img,\r\n.via-ad-wrapper .HTMLUploadfileConteneur video {\r\n    \/* v4.9ds : width\/height 100% + object-fit:contain \u2192 image remplit la box du dropZone\r\n       (m\u00eames contraintes que desktop) avec letterbox automatique selon ratio.\r\n       Avant : height:auto + width:auto \u2192 image gardait son ratio mais pouvait d\u00e9border\r\n       (overflow:hidden du wrapper \u2192 crop). Le max-height 160px est la limite mobile\r\n       (renderImage pose dropZone.height \u00e0 105 sur mobile mais le CSS doit autoriser\r\n       jusqu'\u00e0 160 pour les autres branches qui ne passent pas par renderImage). *\/\r\n    max-height: 160px !important;\r\n    max-width: 100% !important;\r\n    height: 100% !important;\r\n    width: 100% !important;\r\n    object-fit: contain !important;\r\n}\r\n\r\n\/* v4.9ds : doc-preview Communiqu\u00e9\/Interview occupe toute la hauteur du dropZone\r\n   (sinon vide blanc en bas du liser\u00e9 vert) *\/\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container {\r\n    height: 100% !important;\r\n    max-height: 100% !important;\r\n    align-items: stretch !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-thumbnail,\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-info {\r\n    height: 100% !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container.doc-preview-noimage {\r\n    align-items: flex-start !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container.doc-preview-noimage .doc-preview-info-full {\r\n    height: 100% !important;\r\n    max-height: 100% !important;\r\n}\r\n\r\n\/* v4.9ds : r\u00e8gles Ele0A supprim\u00e9es ici, d\u00e9plac\u00e9es en fin de fichier CSS\r\n   (apr\u00e8s les @media min-width:1200px) pour gagner par ordre de d\u00e9claration. *\/\r\n\r\n\/* v4.9ds : normaliser padding-top sur tous les libell\u00e9s de format.\r\n   Elementor pose en inline padding:1px 0 0 sur Cr\u00e9ation\/Pop-up\/Banni\u00e8re\/Vid\u00e9o\r\n   et padding:3px 0 0 sur Communiqu\u00e9\/Interview\/Parrainage (configur\u00e9 c\u00f4t\u00e9 admin\r\n   Elementor) \u2192 d\u00e9calage visuel entre la 1\u00e8re et 2e ligne de la grille de formats.\r\n   On force 1px partout pour alignement vertical homog\u00e8ne. *\/\r\n.EspPubFormatContainer .EspPubFormat .elementor-widget-container {\r\n    padding-top: 1px !important;\r\n}\r\n\r\n@media (max-width: 999px) {\r\n    .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        max-height: 200px !important;\r\n    }\r\n    \/* v4.9ds : Mobile uniquement \u2014 libell\u00e9s des formats en 800 (au lieu de 600) *\/\r\n    .EspPubFormatContainer .EspPubFormat,\r\n    .EspPubFormatContainer .EspPubFormat .elementor-widget-container {\r\n        font-weight: 800 !important;\r\n    }\r\n}\r\n\r\n\/* Les styles CSS restent inchang\u00e9s *\/\r\n\r\n\/* R\u00e9gie iframe : wrapper adaptatif *\/\r\nhtml.via-regie-iframe .via-ad-wrapper {\r\n    width: 100% !important;\r\n    max-width: 100% !important;\r\n}\r\n\/* Padding-bottom sur droppable d\u00e9pos\u00e9 \u2014 DESKTOP uniquement (\u2265600px viewport iframe).\r\n   Sur mobile, le flow naturel suffit : pas de padding-bottom (\u00e9vite gros vide blanc). *\/\r\n@media (min-width: 600px) {\r\n    html.via-regie-iframe body.home .droppable[data-via-ad-loaded=\"true\"] {\r\n        padding-bottom: 240px !important;\r\n    }\r\n    html.via-regie-iframe body:not(.home) .droppable[data-via-ad-loaded=\"true\"] {\r\n        padding-bottom: 130px !important;\r\n    }\r\n}\r\n\r\n\/* R\u00e9gie iframe MOBILE \u2014 annonce charg\u00e9e : r\u00e9duire les margins \u00e9normes pos\u00e9s par\r\n   Entete.txt sur .ToBeHidden (200px+290px calibr\u00e9s pour espaces vides). *\/\r\n@media (max-width: 599px) {\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) {\r\n        margin-top: -100px !important;\r\n        margin-bottom: 5px !important;\r\n    }\r\n    \/* Neutraliser aussi les margins sur l'OrdiMobileConteneurClass et ses descendants\r\n       qui pourraient ajouter du padding\/margin additionnel. *\/\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) .OrdiMobileConteneurClass {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n        padding-bottom: 0 !important;\r\n    }\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) .UploadFileConteneur {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n        padding-bottom: 0 !important;\r\n    }\r\n    \/* Pages secteurs mobile : Entete.txt pose margin-bottom 180\/176px directement\r\n       sur .droppable (pas sur ToBeHidden). R\u00e9duire pour les droppables charg\u00e9s. *\/\r\n    html.via-regie-iframe body.page .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-bottom: 10px !important;\r\n    }\r\n}\r\n\r\n\/* R\u00e9gie iframe desktop (\u2265600px de viewport iframe) : UFC standard + loading spacing *\/\r\n@media (min-width: 600px) {\r\n    \/* UFC : hauteur fixe 170px comme les espaces vides (standard) au lieu de hug auto *\/\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: 170px !important;\r\n        max-height: 170px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: 160px !important;\r\n        display: flex !important;\r\n        align-items: center !important;\r\n        justify-content: center !important;\r\n    }\r\n    \/* Homepages : loading container 30px plus bas *\/\r\n    html.via-regie-iframe body.home .via-loading-inline {\r\n        margin-top: 30px !important;\r\n    }\r\n}\r\n\r\n\/* ============================================================================\r\n   R\u00e9gie iframe desktop plein \u00e9cran (\u22651200px) : neutralise les marges JS\r\n   pos\u00e9es par styleUploadedAd + _buildAdOverlay (calibr\u00e9es pour mode mobile \u00e9troit)\r\n   ============================================================================ *\/\r\n@media (min-width: 1200px) {\r\n    \/* Annule la remont\u00e9e de -55px sur .droppable qui cause le chevauchement *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-top: 0 !important;\r\n    }\r\n    \/* Annule le +130px sur margin-bottom de l'OMC interne *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] .OrdiMobileConteneurClass {\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Annule le -140px (ou -105px) sur .via-ad-wrapper pos\u00e9 par _buildAdOverlay *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] .via-ad-wrapper {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Agrandit l'annonce : UFC 260px max au lieu de 170px *\/\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 260px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        max-height: 250px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        max-height: 250px !important;\r\n    }\r\n}\r\n\r\n\/* ============================================================================\r\n   Sites pays desktop\/tablette (pas dans iframe r\u00e9gie, \u2265768px) : neutralise\r\n   les marges n\u00e9gatives inline h\u00e9rit\u00e9es du mode mobile (margin-top: -75px sur\r\n   droppable, -140px sur wrapper) qui persistent au resize et causent le\r\n   chevauchement avec le contenu du dessus.\r\n   Seuil 768 (Elementor tablette) pour couvrir les cas o\u00f9 innerWidth < 1000\r\n   alors que outerWidth est en mode desktop.\r\n   ============================================================================ *\/\r\n@media (min-width: 768px) {\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] .via-ad-wrapper {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Pas de margin-top: 0 !important sur .droppable \u2014 on laisse l'inline pos\u00e9 par\r\n       _viaRunInterEspaces (algo inter-espaces) appliquer sa valeur n\u00e9gative pour\r\n       standardiser le gap au-dessus \u00e0 20px. *\/\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Annule le margin-bottom sur OMC (pos\u00e9 par styleUploadedAd) *\/\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] .OrdiMobileConteneurClass {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Agrandit l'annonce : UFC 260px max au lieu de 170px *\/\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 260px !important;\r\n    }\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        \/* v4.9ds : aligner sur la hauteur pos\u00e9e par _applyDzMinH (Ele1A+ = 207) *\/\r\n        max-height: 207px !important;\r\n    }\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        \/* v4.9ds : aligner sur la hauteur r\u00e9elle du dropZone (pos\u00e9e par _applyDzMinH).\r\n           Avant : 250px en dur \u2192 image plus haute que dropZone (~207 sur Ele1A+) \u2192 crop\r\n           par overflow:hidden du wrapper. *\/\r\n        max-height: 207px !important;\r\n    }\r\n}\r\n\r\n\/* v4.9cv : Ele0A desktop \u2014 aligner les contraintes de hauteur sur celles d'Ele1A.\r\n   Ces r\u00e8gles arrivent APR\u00c8S les @media min-width:1200px et 768px pour gagner par\r\n   ordre de d\u00e9claration. Pr\u00e9fixe html# boost la sp\u00e9cificit\u00e9 face \u00e0 html.via-regie-iframe. *\/\r\n\/* v4.9cx : force brute + d\u00e9sactiver overflow:hidden sur le wrapper parent *\/\r\n@media (min-width: 768px) {\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 285px !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        max-height: 215px !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        \/* v4.9ds : aligner sur la hauteur r\u00e9elle du dropZone (pos\u00e9e par _applyDzMinH = 215)\r\n           Avant : 275px en dur \u2192 image d\u00e9passait dropZone de 60px \u2192 crop.\r\n           v4.9ds : retir\u00e9 'height: auto !important' pour que le JS (height:100%) puisse\r\n           s'appliquer \u2192 image remplit la box comme Ele1A+ (comportement uniforme).\r\n           object-fit:contain garantit que l'image reste enti\u00e8re (letterbox auto). *\/\r\n        max-height: 215px !important;\r\n    }\r\n    \/* Laisser le contenu d\u00e9border visuellement si jamais une r\u00e8gle cach\u00e9e pose une height trop petite *\/\r\n    html body .ToBeHidden:has(#Ele0A) {\r\n        overflow: visible !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper {\r\n        overflow: visible !important;\r\n    }\r\n    \/* v4.9ds : Ele0A desktop \u2014 d\u00e9caler .EnvoiUlterieurContainer de 17px vers le bas pour\r\n       espacer visuellement le bloc \"ou Envoi diff\u00e9r\u00e9 \/ Envoyer l'annonce \/ Un lien\u2026\" du\r\n       champ hypertext qui le pr\u00e9c\u00e8de. Le margin pousse aussi tout ce qui suit (R\u00e9server). *\/\r\n    html #Ele0A .EnvoiUlterieurContainer {\r\n        margin-top: 17px !important;\r\n    }\r\n}\r\n\r\n\/* \u2705 via-ad-wrapper responsive : s'adapte quand la fen\u00eatre est r\u00e9duite *\/\r\n.via-ad-wrapper {\r\n    min-width: 0;\r\n    overflow: hidden;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: flex-end;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-wrapper .via-ad-header,\r\n.via-ad-wrapper .HTMLUploadfileConteneur,\r\n.via-ad-wrapper .via-ad-footer {\r\n    width: 100%;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-header {\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n}\r\n.via-ah-title {\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n    white-space: nowrap;\r\n    max-width: 65%;\r\n}\r\n\/* desktop mode mobile : reduire les tailles de char du header *\/\r\n@media only screen and (max-width: 999px) {\r\n    .via-ah-pos  { font-size: 8px !important; }\r\n    .via-ah-title { font-size: 9px !important; max-width: 70% !important; }\r\n    .via-ah-ref  { font-size: 8px !important; }\r\n    .via-ad-wrapper .HTMLUploadfileConteneur { max-height: 200px !important; overflow: hidden !important; height: auto !important; }\r\n    \/* v4.9ds : exclure img\/video du s\u00e9lecteur * pour ne pas \u00e9craser leur r\u00e8gle d\u00e9di\u00e9e\r\n       (qui pose width:100%\/height:100%\/object-fit:contain). Sans cette exclusion,\r\n       la max-height:200 inline ici ne pose pas de pb mais le * peut interf\u00e9rer. *\/\r\n    .via-ad-wrapper .HTMLUploadfileConteneur *:not(img):not(video) { max-height: 200px !important; }\r\n    .reserver-dynamic-label { font-size: 12px !important; }\r\n    .via-ad-wrapper .via-af-move { font-size: 8px !important; }\r\n}\r\n.via-ah-pos, .via-ah-ref {\r\n    white-space: nowrap;\r\n    flex-shrink: 1;\r\n    min-width: 0;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n}\r\n\/* v4.9ct : d\u00e9caler la r\u00e9f\u00e9rence MDG... vers la gauche (uniquement desktop).\r\n   - Espaces pub standards : 8px\r\n   - Ele0A (popup) : 5px\r\n   Le margin-right sur .via-ah-ref pousse la ref \u00e0 gauche tandis que la croix reste ancr\u00e9e \u00e0 droite. *\/\r\n\/* v4.9cu : +8\/+5px suppl\u00e9mentaires \u2192 standards 16px, Ele0A 10px *\/\r\n\/* v4.9cv : +8\/+5px suppl\u00e9mentaires \u2192 standards 24px, Ele0A 15px *\/\r\n@media (min-width: 1000px) {\r\n    .via-ad-header .via-ah-ref { margin-right: 24px; }\r\n    #Ele0A .via-ad-header .via-ah-ref { margin-right: 15px; }\r\n    \/* v4.9ds : Ele0A \u2014 d\u00e9caler via-eu-wrapper de +2px vers le bas (cumul avec margin-top:-5px pos\u00e9 par JS Entete) *\/\r\n    \/*          R\u00e9sultat : -5px + 2px = -3px effectif *\/\r\n    #Ele0A .via-eu-wrapper {\r\n        margin-top: -3px !important;\r\n    }\r\n    \/* v4.9ds : Ele1A+ \u2014 d\u00e9caler EnvoiUlterieur de -2px vers le haut *\/\r\n    .droppable:not(#Ele0A) .EnvoiUlterieurContainer .EnvoiUlterieur {\r\n        margin-top: -2px !important;\r\n    }\r\n    \/* v4.9ds : Ele1A+ \u2014 EnvoiUlterieurTexte de -1px (cumul -2px + 1px bas) *\/\r\n    .droppable:not(#Ele0A) .EnvoiUlterieurContainer .EnvoiUlterieurTexte {\r\n        margin-top: -1px !important;\r\n    }\r\n}\r\n\/* HTMLUploadfileConteneur : hauteur auto dans le wrapper *\/\r\n.via-ad-wrapper .HTMLUploadfileConteneur {\r\n    max-height: 200px;\r\n    overflow: hidden !important;\r\n    margin-top: 0 !important;\r\n    margin-bottom: 0 !important;\r\n}\r\n\/* v4.9cs : NE PAS ajouter de zoom suppl\u00e9mentaire sur Ele0A \u2014 #Ele0A a d\u00e9j\u00e0 zoom:55%\r\n   pos\u00e9 par Entete.txt (ligne 5899), donc un second zoom 55% ici donnerait 30% (trop petit). *\/\r\n.via-ad-wrapper #drop_file_zone_achat {\r\n    margin-top: 0 !important;\r\n    padding: 0 2px;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat img,\r\n.via-ad-wrapper #drop_file_zone_achat video {\r\n    \/* v4.9ds : height:100% (au lieu de auto) + object-fit:contain \u2192 image remplit le\r\n       dropZone (coh\u00e9rent avec width:100%) en restant enti\u00e8re. Letterbox auto si ratio\r\n       diff\u00e9rent. Sans !important pour ne pas surclasser les r\u00e8gles plus sp\u00e9cifiques. *\/\r\n    max-width: 100%;\r\n    width: 100%;\r\n    height: 100%;\r\n    object-fit: contain;\r\n    display: block;\r\n}\r\n\/* via-af-move *\/\r\n.via-ad-wrapper .via-af-move {\r\n    white-space: normal;\r\n    color: #67758c;\r\n    overflow: hidden;\r\n    text-align: center;\r\n}\r\n\r\n\r\n#drop_file_zone_achat {\r\n    background-color: #FFFFFF00;\r\n    color: #225DA9;\r\n    font-weight: 500;\r\n    text-align: center;\r\n    border: #999 0px dashed;\r\n    width: 100%;\r\n    height: 180px;\r\n    font-size: 11.5px;\r\n    display: flex;\r\n    justify-content: center; \r\n    align-items: center; \r\n    flex-wrap: wrap;\r\n}\r\n\r\n#drag_upload_file_achat {\r\n    margin: 0 auto;\r\n    width: 90%;\r\n    height: 60px;\r\n}\r\n\r\n#drag_upload_file_achat p {\r\n    text-align: center;\r\n}\r\n\r\n\/* \u2705 v1.16.0 : Liser\u00e9 noir fin autour du texte \"Ici glisser \u2013 d\u00e9poser\" *\/\r\n.GlisserDeposerConteneur .elementor-widget-container p {\r\n    border: 1px solid #000000;\r\n    border-radius: 4px;\r\n    padding: 4px 10px;\r\n    display: inline-block;\r\n}\r\n\r\n#selectfile_achat {\r\n    display: none;\r\n}\r\n\r\n\/* v4.9ds : Ele0A - EnvoiUlterieurTexte +6px bas (cumul 2px + 2px + 2px) *\/\r\n#Ele0A .EnvoiUlterieurTexte {\r\n    margin-top: 6px !important;\r\n}\r\n\r\n.button-2_achat, .button-2_achat-after-reset {\r\n    background-color: #ffffff00!important;\r\n    border: 1px solid white!important;\r\n    border-radius: 8px;\r\n    color: #225DA9!important;\r\n    cursor: pointer;\r\n    display: inline-block;\r\n    font-size: 11px;\r\n    font-weight: 500;\r\n    list-style: none;\r\n    width: 390px;\r\n    height: 62px;\r\n    top: 0px!important; \r\n    margin-top: 0px!important; \r\n    padding-left: 5px!important;\r\n    padding-right: 5px!important;\r\n    margin-bottom: 70px!important;\r\n    margin: 0;\r\n    text-align: center;\r\n    transition: all 200ms;\r\n    vertical-align: baseline;\r\n    white-space: wrap!important;\r\n}\r\n\r\n@media only screen and (max-width: 1000px) {\r\n    .button-2_achat, .button-2_achat-after-reset {\r\n        width: 95%;\r\n        height: 50px;\r\n    }\r\n}\r\n\r\n.newMessageClass {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #56BE50;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 45px;\r\n    bottom: 0px;\r\n    right: 0px;\r\n    left: 0px;\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n    text-align: center;\r\n}\r\n\r\n.MessageClassFormatnonReconnuTitre {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #FB5E2A;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    margin-top: -300px;\r\n    right: 0px;\r\n    text-align: center;\r\n    line-height: 15px;\r\n}\r\n\r\n.MessageClassFormatnonReconnu {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #ffffff;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    margin-top: -280px;\r\n    right: 0px;\r\n    text-align: center;\r\n    line-height: 15px;\r\n}\r\n\r\n.newButtonClass {\r\n    font-size: 12px; \r\n    font-weight: 500;\r\n    background-color: #ffffff00;\r\n    color: #717171;\r\n    height: 10px;\r\n    width: 100%; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 45px;\r\n    bottom: 0px;\r\n    text-align: center;\r\n    right:  50px;\r\n    line-height: 10px;\r\n    border-radius: 3px;\r\n    border: 0px solid #E8ECF1;\r\n}\r\n\r\n.linkClass {\r\n    width: 15px; \r\n    height: 15px;\r\n    margin-top: -310px;\r\n    margin-bottom: -25px;\r\n    margin-right: -50px;\r\n    position: relative;\r\n    top: 0; \r\n    z-index: 99;\r\n    display: block;\r\n    background-image: url('https:\/\/rdc.via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-blanc.jpg');\r\n    background-size: cover;\r\n}\r\n\r\n.newButtonClassVideo {\r\n    font-size: 12px; \r\n    font-weight: 500;\r\n    background-color: #ffffff00;\r\n    color: #717171;\r\n    height: 10px;\r\n    width: 100%; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 0px;\r\n    bottom: 0px;\r\n    right: 10px;\r\n    text-align: center;\r\n    line-height: 10px;\r\n    border-radius: 3px;\r\n    border: 0px solid #E8ECF1;\r\n}\r\n\r\n.linkClassVideo {\r\n    width: 15px; \r\n    height: 15px;\r\n    margin-top: -438px;\r\n    margin-bottom: -25px;\r\n    margin-right: -15px;\r\n    position: relative;\r\n    top: 0; \r\n    z-index: 99;\r\n    display: block;\r\n    background-image: url('https:\/\/rdc.via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-blanc.jpg');\r\n    background-size: cover;\r\n}\r\n\r\n.FinCampagneMobileClass .elementor-button,\r\n.DebutCampagneMobileClass .elementor-button,\r\n.FormSelectDevisesMobile .elementor-button,\r\n.HideFormButton .elementor-button {\r\n    display: none !important;\r\n}\r\n\r\n#drag_upload_file_achat {\r\n    margin: 0;\r\n    padding: 0;\r\n    position: relative;\r\n}\r\n\r\n.dot-container {\r\n    display: inline-block;\r\n}\r\n\r\n.dot {\r\n    opacity: 0;\r\n    transition: opacity 0.3s ease;\r\n}\r\n\r\n@keyframes firstDot {\r\n    0%, 100% { opacity: 0; }\r\n    10%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n@keyframes secondDot {\r\n    0%, 20%, 100% { opacity: 0; }\r\n    30%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n@keyframes thirdDot {\r\n    0%, 40%, 100% { opacity: 0; }\r\n    50%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n.dot:nth-child(1) {\r\n    animation: firstDot 3s infinite;\r\n}\r\n\r\n.dot:nth-child(2) {\r\n    animation: secondDot 3s infinite;\r\n}\r\n\r\n.dot:nth-child(3) {\r\n    animation: thirdDot 3s infinite;\r\n}\r\n\r\n.droppable .elementor-field-type-checkbox .elementor-field-option {\r\n    display: flex;\r\n    flex-direction: row-reverse;\r\n    align-items: center;\r\n    gap: 8px;\r\n}\r\n\r\n.droppable .elementor-field-type-checkbox .elementor-field-option input[type=\"checkbox\"] {\r\n    width: 12px;\r\n    height: 12px;\r\n    min-width: 12px;\r\n    min-height: 12px;\r\n}\r\n\r\n\/* \u2705 Bouton \"R\u00e9server\" dynamique *\/\r\n.reserver-dynamic-container {\r\n    text-align: center;\r\n    margin-top: -7px;\r\n    margin-bottom: 15px;\r\n    padding: 5px 0;\r\n    transform: scale(1.4);\r\n    transform-origin: center top;\r\n    position: relative;\r\n    z-index: 200;\r\n}\r\n\r\n@media only screen and (min-width: 1001px) {\r\n    .reserver-dynamic-container {\r\n        transform: scale(1);\r\n        margin-top: -200px;\r\n        margin-bottom: 0;\r\n    }\r\n}\r\n\r\n.reserver-dynamic-option {\r\n    display: inline-flex;\r\n    flex-direction: row-reverse;\r\n    align-items: center;\r\n    gap: 8px;\r\n    cursor: pointer;\r\n    color: #213864;\r\n    background-color: #ffffff;\r\n    padding: 2px 4px;\r\n    border-radius: 6px;\r\n}\r\n\r\n.reserver-dynamic-checkbox {\r\n    width: 16px;\r\n    height: 16px;\r\n    min-width: 16px;\r\n    min-height: 16px;\r\n    cursor: pointer;\r\n    pointer-events: auto;\r\n}\r\n\r\n.reserver-dynamic-label {\r\n    cursor: pointer;\r\n    color: #213864;\r\n    font-size: 16px;\r\n    font-weight: 600;\r\n    user-select: none;\r\n}\r\n\r\n@media only screen and (max-width: 1000px) {\r\n    .reserver-dynamic-container {\r\n        transform: scale(0.98);\r\n        margin-bottom: 20px;\r\n        white-space: nowrap;\r\n        z-index: 300;\r\n        pointer-events: auto;\r\n    }\r\n    .reserver-dynamic-option {\r\n        padding-top: 0px;\r\n        padding-bottom: 0px;\r\n    }\r\n}\r\n\r\n\/* \u2705 v2.1.3 : DeplaceAnnonceText \/ DeplaceAnnonce \u2014 desktop uniquement *\/\r\n@media only screen and (min-width: 1001px) {\r\n    .DeplaceAnnonceText {\r\n        margin-top: -5px;\r\n        position: relative;\r\n        z-index: 201;\r\n    }\r\n    .DeplaceAnnonce {\r\n        margin-bottom: -150px;\r\n    }\r\n}\r\n<\/style>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-54e4e530 e-con-full MsgFormatIncorrectConteneur elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile e-flex e-con e-child\" data-id=\"54e4e530\" data-element_type=\"container\" id=\"NotusedAnymore\">\n\t\t\t\t<div class=\"elementor-element elementor-element-531f3cb1 MsgFormatIncorrect elementor-widget__width-inherit elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"531f3cb1\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">The file format is not recognized<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3a352c3f elementor-hidden-desktop TexteMobile TexteMobileAnnonce elementor-widget-mobile__width-initial elementor-widget elementor-widget-text-editor\" data-id=\"3a352c3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tDrag and drop or click here<br>\nto download an ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7abb0aba elementor-hidden-desktop TexteMobileAjoutAnnonce elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"7abb0aba\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Click here to download an ad<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-68e2b2ca UploadIci elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"68e2b2ca\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tHere you can drag and drop or upload an ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-8d50d33 e-con-full e-flex e-con e-child\" data-id=\"8d50d33\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6ae6ef83 elementor-button-align-center elementor-widget__width-initial HideFormButton EspPubLienAnnonce elementor-widget-mobile__width-initial elementor-widget elementor-widget-form\" data-id=\"6ae6ef83\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_URL_annonce\" aria-label=\"Form_URL_ad\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353554\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"6ae6ef83\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-text elementor-field-group elementor-column elementor-field-group-LienAnnonce elementor-col-100 elementor-sm-100 elementor-field-required\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-LienAnnonce\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tHere, if necessary, enter the hyperlink to the advertisement.\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<input size=\"1\" type=\"text\" name=\"form_fields[LienAnnonce]\" id=\"form-field-LienAnnonce\" class=\"elementor-field elementor-size-xs  elementor-field-textual\" placeholder=\"Here, if necessary, enter the hyperlink to the advertisement.\" required=\"required\">\n\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidURLAnnonce\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-66fd4d36 e-con-full EnvoiUlterieurContainer e-flex e-con e-child\" data-id=\"66fd4d36\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4ea8e2b4 elementor-button-align-center elementor-widget__width-auto HideFormButton EnvoiUlterieur elementor-widget elementor-widget-form\" data-id=\"4ea8e2b4\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_Envoi_Ulterieur\" aria-label=\"Submit_Form_Later\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353554\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"4ea8e2b4\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-checkbox elementor-field-group elementor-column elementor-field-group-EnvoiUlterieur elementor-col-100 elementor-sm-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-EnvoiUlterieur\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tor Delayed posting of the advertisement\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t<div class=\"elementor-field-subgroup\"><span class=\"elementor-field-option\"><input type=\"checkbox\" value=\"ou Envoi diff\u00e9r\u00e9 de l&#039;annonce\" id=\"form-field-EnvoiUlterieur-0\" name=\"form_fields[EnvoiUlterieur]\"> <label for=\"form-field-EnvoiUlterieur-0\">or Delayed posting of the advertisement<\/label><\/span><\/div>\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidEnvoiUlterieur\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4a59cf88 EnvoiUlterieurTexte elementor-widget elementor-widget-text-editor\" data-id=\"4a59cf88\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Send the ad up to 8 days after payment<br>\nA link will be sent to you by <span style=\"color: #ffffff;\"><a style=\"color: #ffffff;\" href=\"mailto:contact@via-agency.media\">contact@via-agency.media<\/a><\/span><\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-17d3ad0c e-con-full ReserverContainer e-flex e-con e-child\" data-id=\"17d3ad0c\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-177c9b71 elementor-button-align-center elementor-widget__width-auto HideFormButton ReserverBouton elementor-widget elementor-widget-form\" data-id=\"177c9b71\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_Envoi_Ulterieur\" aria-label=\"Submit_Form_Later\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353554\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"177c9b71\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-checkbox elementor-field-group elementor-column elementor-field-group-ReserverEspacePublicitaire elementor-col-100 elementor-sm-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-ReserverEspacePublicitaire\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tReserve this advertising space\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t<div class=\"elementor-field-subgroup\"><span class=\"elementor-field-option\"><input type=\"checkbox\" value=\"R\u00e9server cet espace publicitaire\" id=\"form-field-ReserverEspacePublicitaire-0\" name=\"form_fields[ReserverEspacePublicitaire]\"> <label for=\"form-field-ReserverEspacePublicitaire-0\">Reserve this advertising space<\/label><\/span><\/div>\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidEnvoiUlterieur\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5bafe1f3 e-con-full AdUploadedTitle DeplaceAnnonce elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop e-flex e-con e-child\" data-id=\"5bafe1f3\" data-element_type=\"container\" id=\"DeplaceAnnonceId\">\n\t\t<div class=\"elementor-element elementor-element-432cc2a1 e-con-full DeplaceAnnonceSubContainer e-flex e-con e-child\" data-id=\"432cc2a1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-446e8565 elementor-hidden-tablet elementor-hidden-mobile EspaceReserve elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"446e8565\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reserved space<br \/>Announcement transmitted<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-60df49d0 elementor-hidden-tablet elementor-hidden-mobile DeplaceAnnonceText elementor-widget elementor-widget-text-editor\" data-id=\"60df49d0\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tIf you want another location, you can move this ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-62ef38f0 e-con-full e-flex e-con e-child\" data-id=\"62ef38f0\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f4f51cb elementor-hidden-tablet elementor-hidden-mobile PositionEspacePublicitaireDeplacer elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"f4f51cb\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Position<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f214306 elementor-hidden-tablet elementor-hidden-mobile RefEspacePublicitaire elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"f214306\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reference<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-618723bf e-flex e-con-boxed e-con e-child\" data-id=\"618723bf\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-61d6ef12 e-con-full e-flex e-con e-child\" data-id=\"61d6ef12\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-575683da e-con-full e-flex e-con e-child\" data-id=\"575683da\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-706ea050 e-con-full elementor-hidden-desktop elementor-hidden-tablet e-flex e-con e-child\" data-id=\"706ea050\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4b460d48 elementor-widget__width-initial elementor-pagination-position-inside CarrouselLoop elementor-pagination-type-bullets elementor-widget elementor-widget-loop-carousel\" data-id=\"4b460d48\" data-element_type=\"widget\" data-settings=\"{&quot;template_id&quot;:&quot;46309&quot;,&quot;speed&quot;:0,&quot;image_spacing_custom&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:2,&quot;sizes&quot;:[]},&quot;offset_sides&quot;:&quot;both&quot;,&quot;offset_width&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;slides_to_show&quot;:&quot;1&quot;,&quot;slides_to_show_tablet&quot;:&quot;1&quot;,&quot;_skin&quot;:&quot;post&quot;,&quot;slides_to_show_mobile&quot;:&quot;1&quot;,&quot;slides_to_scroll&quot;:&quot;1&quot;,&quot;edit_handle_selector&quot;:&quot;.elementor-loop-container&quot;,&quot;infinite&quot;:&quot;yes&quot;,&quot;offset_width_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;offset_width_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;pagination&quot;:&quot;bullets&quot;,&quot;image_spacing_custom_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;image_spacing_custom_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]}}\" data-widget_type=\"loop-carousel.post\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"swiper elementor-loop-container elementor-grid\" role=\"list\" dir=\"ltr\">\n\t\t\t\t<div class=\"swiper-wrapper\" aria-live=\"polite\">\n\t\t<style id=\"loop-dynamic-46309\">.e-loop-item-49198 .elementor-element.elementor-element-aaa4580::before, .e-loop-item-49198 .elementor-element.elementor-element-aaa4580 > .elementor-background-video-container::before, .e-loop-item-49198 .elementor-element.elementor-element-aaa4580 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-49198 .elementor-element.elementor-element-aaa4580 > .elementor-background-slideshow::before, .e-loop-item-49198 .elementor-element.elementor-element-aaa4580 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-49198 .elementor-element.elementor-element-aaa4580 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Gabon-DG-AGATOUR-e1748875162167.png\");}<\/style><style id=\"loop-46309\">.elementor-46309 .elementor-element.elementor-element-64ec6a5{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-46309 .elementor-element.elementor-element-aaa4580{--display:flex;--min-height:220px;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--align-items:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-46309 .elementor-element.elementor-element-aaa4580::before, .elementor-46309 .elementor-element.elementor-element-aaa4580 > .elementor-background-video-container::before, .elementor-46309 .elementor-element.elementor-element-aaa4580 > .e-con-inner > .elementor-background-video-container::before, .elementor-46309 .elementor-element.elementor-element-aaa4580 > .elementor-background-slideshow::before, .elementor-46309 .elementor-element.elementor-element-aaa4580 > .e-con-inner > .elementor-background-slideshow::before, .elementor-46309 .elementor-element.elementor-element-aaa4580 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{--background-overlay:'';background-position:center center;background-repeat:no-repeat;background-size:cover;}.elementor-46309 .elementor-element.elementor-element-aaa4580::before{filter:brightness( 95% ) contrast( 100% ) saturate( 100% ) blur( 0px ) hue-rotate( 0deg );}.elementor-46309 .elementor-element.elementor-element-aaa4580.e-con{--align-self:center;}.elementor-widget-text-editor{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:var( --e-global-color-primary );}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap, .elementor-widget-text-editor.elementor-drop-cap-view-default .elementor-drop-cap{color:var( --e-global-color-primary );border-color:var( --e-global-color-primary );}.elementor-46309 .elementor-element.elementor-element-4975b30 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-4975b30{z-index:2;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:500;text-transform:uppercase;color:#FFFFFF;}.elementor-46309 .elementor-element.elementor-element-edda081{--display:flex;--min-height:200px;--justify-content:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--border-radius:0px 0px 0px 0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-46309 .elementor-element.elementor-element-edda081:not(.elementor-motion-effects-element-type-background), .elementor-46309 .elementor-element.elementor-element-edda081 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#F5F5F5B0;}.elementor-46309 .elementor-element.elementor-element-edda081:hover{--border-radius:0px 0px 5px 5px;--border-top-left-radius:0px;--border-top-right-radius:0px;--border-bottom-right-radius:5px;--border-bottom-left-radius:5px;}.elementor-46309 .elementor-element.elementor-element-edda081, .elementor-46309 .elementor-element.elementor-element-edda081::before{--border-transition:0s;}.elementor-widget-theme-post-title .elementor-heading-title{font-family:var( --e-global-typography-primary-font-family ), Sans-serif;font-weight:var( --e-global-typography-primary-font-weight );color:var( --e-global-color-primary );}.elementor-46309 .elementor-element.elementor-element-1aea037 > .elementor-widget-container{margin:20px 0px 0px 20px;padding:0px 010px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-1aea037 .elementor-heading-title{font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;color:#213864;}.elementor-widget-theme-post-excerpt .elementor-widget-container{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-46309 .elementor-element.elementor-element-0e2bf7f > .elementor-widget-container{margin:15px 0px 0px 20px;padding:0px 10px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-0e2bf7f .elementor-widget-container{text-align:left;font-family:\"Roboto\", Sans-serif;font-size:12px;font-weight:400;line-height:1.3em;color:#213864;}.elementor-46309 .elementor-element.elementor-element-230a166{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-46309 .elementor-element.elementor-element-bd149b2 > .elementor-widget-container{margin:0px 0px 0px 020px;padding:0px 20px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-bd149b2{font-family:\"Roboto\", Sans-serif;font-size:12px;font-weight:600;color:#213864;}@media(min-width:1001px){.elementor-46309 .elementor-element.elementor-element-64ec6a5{--width:100%;}.elementor-46309 .elementor-element.elementor-element-aaa4580{--width:100%;}.elementor-46309 .elementor-element.elementor-element-edda081{--width:100%;}}@media(max-width:1001px) and (min-width:1001px){.elementor-46309 .elementor-element.elementor-element-aaa4580{--width:100%;}.elementor-46309 .elementor-element.elementor-element-edda081{--width:100%;}}@media(max-width:1001px){.elementor-46309 .elementor-element.elementor-element-64ec6a5{--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-46309 .elementor-element.elementor-element-aaa4580{--min-height:180px;--border-radius:0px 0px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-4975b30 > .elementor-widget-container{margin:-17px 0px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-4975b30{font-size:12px;}.elementor-46309 .elementor-element.elementor-element-edda081{--min-height:180px;--border-radius:0px 0px 0px 0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-46309 .elementor-element.elementor-element-1aea037 > .elementor-widget-container{margin:010px 0px 0px 10px;padding:0px 10px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-1aea037 .elementor-heading-title{font-size:14px;}.elementor-46309 .elementor-element.elementor-element-0e2bf7f > .elementor-widget-container{margin:15px 0px 0px 10px;padding:0px 10px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-0e2bf7f .elementor-widget-container{font-size:11px;}.elementor-46309 .elementor-element.elementor-element-bd149b2 > .elementor-widget-container{margin:0px 0px 0px 10px;padding:0px 10px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-bd149b2{font-size:11px;}}@media(max-width:1000px){.elementor-46309 .elementor-element.elementor-element-64ec6a5{--width:100%;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--margin-top:0%;--margin-bottom:0%;--margin-left:0%;--margin-right:0%;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-46309 .elementor-element.elementor-element-aaa4580{--width:100%;--min-height:120px;--border-radius:0px 0px 0px 0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-46309 .elementor-element.elementor-element-4975b30 > .elementor-widget-container{margin:2% 0% 0% 0%;}.elementor-46309 .elementor-element.elementor-element-4975b30{font-size:14px;}.elementor-46309 .elementor-element.elementor-element-edda081{--width:100%;--min-height:100px;--align-items:flex-start;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--border-radius:0px 0px 0px 0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-46309 .elementor-element.elementor-element-edda081.e-con{--align-self:flex-start;}.elementor-46309 .elementor-element.elementor-element-1aea037 > .elementor-widget-container{margin:5px 0px 0px 5px;padding:0px 10px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-1aea037{text-align:left;}.elementor-46309 .elementor-element.elementor-element-1aea037 .elementor-heading-title{font-size:12px;}.elementor-46309 .elementor-element.elementor-element-0e2bf7f > .elementor-widget-container{margin:15px 0px 0px 5px;padding:0px 10px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-0e2bf7f .elementor-widget-container{font-size:11px;}.elementor-46309 .elementor-element.elementor-element-230a166{--margin-top:20px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;}.elementor-46309 .elementor-element.elementor-element-bd149b2 > .elementor-widget-container{margin:0px 0px 0px 5px;padding:0px 010px 0px 0px;}.elementor-46309 .elementor-element.elementor-element-bd149b2{font-size:12px;line-height:1.2em;}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"46309\" class=\"elementor elementor-46309 swiper-slide e-loop-item e-loop-item-49198 post-49198 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon category-ministre-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" role=\"group\" aria-roledescription=\"slide\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-64ec6a5 e-con-full e-flex e-con e-parent\" data-id=\"64ec6a5\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-aaa4580 e-con-full e-flex e-con e-child\" data-id=\"aaa4580\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-edda081 e-con-full e-flex e-con e-child\" data-id=\"edda081\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1aea037 elementor-widget elementor-widget-theme-post-title elementor-page-title elementor-widget-heading\" data-id=\"1aea037\" data-element_type=\"widget\" data-widget_type=\"theme-post-title.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h5 class=\"elementor-heading-title elementor-size-default\"><a href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-m-christian-mbina-directeur-general-de-lagence-gabonaise-de-developpement-et-de-promotion-du-tourisme-et-de-lhotellerie\/\">Gabon \u2013 Mr. Christian Mbina, Director General of the Gabonese Agency for the Development and Promotion of Tourism and Hospitality <span style=\"font-size: 11px;font-weight: 400\">\u2013 in office in 2023<\/span><\/a><\/h5>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-0e2bf7f elementor-hidden-mobile VignetteSecteurExtraitPublication elementor-widget elementor-widget-theme-post-excerpt\" data-id=\"0e2bf7f\" data-element_type=\"widget\" data-widget_type=\"theme-post-excerpt.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<p>Interview with Christian Mbina, Director General of the Gabonese Agency for Development and...\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<a class=\"elementor-element elementor-element-230a166 e-con-full e-flex e-con e-child\" data-id=\"230a166\" data-element_type=\"container\" href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-m-christian-mbina-directeur-general-de-lagence-gabonaise-de-developpement-et-de-promotion-du-tourisme-et-de-lhotellerie\/\">\n\t\t\t\t<div class=\"elementor-element elementor-element-bd149b2 elementor-widget elementor-widget-text-editor\" data-id=\"bd149b2\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tRead more \u00bb\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<div class=\"swiper-pagination\"><\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-20179f3 ToBeHidden e-con-full e-flex e-con e-child\" data-id=\"20179f3\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-e74a814 e-con-full droppable e-flex e-con e-child\" data-id=\"e74a814\" data-element_type=\"container\" id=\"Ele3A\">\n\t\t\t\t<div class=\"elementor-element elementor-element-3f2cb3f elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"3f2cb3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>\t\t<div data-elementor-type=\"page\" data-elementor-id=\"83347\" class=\"elementor elementor-83347\" data-elementor-post-type=\"elementor_library\">\n\t\t\t\t<div class=\"elementor-element elementor-element-46a1d147 e-con-full OrdiMobileConteneurClass e-flex e-con e-child\" data-id=\"46a1d147\" data-element_type=\"container\" id=\"testIdTest\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8a5fac7 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"8a5fac7\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p><style>.elementor-353556 .elementor-element.elementor-element-6da64a60{--display:flex;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--justify-content:space-around;--align-items:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:50px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-6da64a60.e-con{--align-self:center;--flex-grow:0;--flex-shrink:0;}.elementor-353556 .elementor-element.elementor-element-45f807e0{--display:flex;--min-height:0px;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-100px;--margin-bottom:62px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:3;}.elementor-353556 .elementor-element.elementor-element-45f807e0.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-61dcced4{--display:flex;--margin-top:0px;--margin-bottom:-11px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-6c65b106{--display:flex;--margin-top:0px;--margin-bottom:-30px;--margin-left:-25px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-eb74aa8{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-eb74aa8:not(.elementor-motion-effects-element-type-background), .elementor-353556 .elementor-element.elementor-element-eb74aa8 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-1c762c45{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--flex-wrap:nowrap;--margin-top:-3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-5be6a53a{--display:flex;--align-items:flex-start;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-5be6a53a.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-75917e71{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-image .widget-image-caption{color:var( --e-global-color-text );font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-353556 .elementor-element.elementor-element-6be76773 > .elementor-widget-container{margin:38px 5px -38px -40px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-6be76773.elementor-element{--align-self:flex-start;}.elementor-353556 .elementor-element.elementor-element-6be76773{text-align:right;}.elementor-353556 .elementor-element.elementor-element-6be76773 img{width:17px;}.elementor-353556 .elementor-element.elementor-element-5ad25a69{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-5ad25a69.e-con{--align-self:flex-end;}.elementor-353556 .elementor-element.elementor-element-52dec736 > .elementor-widget-container{margin:45px -18px -55px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-52dec736{z-index:101;text-align:right;}.elementor-353556 .elementor-element.elementor-element-469b1a7c{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-25px;--margin-bottom:90px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-469b1a7c:not(.elementor-motion-effects-element-type-background), .elementor-353556 .elementor-element.elementor-element-469b1a7c > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#9FC5F3;}.elementor-353556 .elementor-element.elementor-element-3f5f9124{--display:flex;--min-height:30px;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:1px;--margin-bottom:-6px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:99;}.elementor-353556 .elementor-element.elementor-element-3f5f9124.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-48a37689{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-start;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-text-editor{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:var( --e-global-color-primary );}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap, .elementor-widget-text-editor.elementor-drop-cap-view-default .elementor-drop-cap{color:var( --e-global-color-primary );border-color:var( --e-global-color-primary );}.elementor-353556 .elementor-element.elementor-element-4b68287 > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353556 .elementor-element.elementor-element-4b68287.elementor-element{--align-self:flex-start;}.elementor-353556 .elementor-element.elementor-element-4b68287{text-align:start;font-family:\"Roboto\", Sans-serif;font-size:12px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-79a46db6{--display:flex;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-47d25f98 > .elementor-widget-container{margin:10px 0px -10px 0px;}.elementor-353556 .elementor-element.elementor-element-47d25f98{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:18px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-3aa42503{--display:flex;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-end;--align-items:flex-end;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-12d5dc39 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-12d5dc39.elementor-element{--align-self:flex-start;}.elementor-353556 .elementor-element.elementor-element-12d5dc39{text-align:end;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-21535e15{--display:flex;--justify-content:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--flex-wrap:wrap;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-21535e15.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-de420c1{--display:flex;--min-height:30px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-2f60f3f > .elementor-widget-container{margin:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-2f60f3f.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-2f60f3f{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:500;line-height:1.1em;color:#FB5E2A;}.elementor-353556 .elementor-element.elementor-element-78e1d9f5{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--gap:010px 9px;--row-gap:010px;--column-gap:9px;--flex-wrap:wrap;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:100;}.elementor-353556 .elementor-element.elementor-element-78e1d9f5.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-cf3602e{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-cf3602e.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-7a1bb33 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-7a1bb33.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-7a1bb33{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-6bf72a5{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-1a57dd9 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-1a57dd9.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-1a57dd9{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:500;line-height:1.1em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-34d8bbba{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-32bd35c6 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-32bd35c6.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-32bd35c6{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-5a9252c3{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-8722042 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-8722042.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-8722042{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-19ecc2b3{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-6071d405 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-6071d405.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-6071d405{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-3617569c{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-4b013e71 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-4b013e71.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-4b013e71{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-7450a6f8{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-30970f89 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-30970f89.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-30970f89{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-6aac67a6{--display:flex;--margin-top:-46px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:2;}.elementor-353556 .elementor-element.elementor-element-40d299c{width:100%;max-width:100%;}.elementor-353556 .elementor-element.elementor-element-40d299c.elementor-element{--align-self:flex-end;}.elementor-353556 .elementor-element.elementor-element-1fd15d20{width:100%;max-width:100%;}.elementor-353556 .elementor-element.elementor-element-1fd15d20.elementor-element{--align-self:flex-end;}.elementor-353556 .elementor-element.elementor-element-54e4e530{--display:flex;--justify-content:center;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-531f3cb1{width:100%;max-width:100%;font-family:\"Roboto\", Sans-serif;font-size:10px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353556 .elementor-element.elementor-element-531f3cb1 > .elementor-widget-container{margin:-37px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-531f3cb1.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-3a352c3f > .elementor-widget-container{margin:7px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-3a352c3f.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-3a352c3f{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353556 .elementor-element.elementor-element-7abb0aba > .elementor-widget-container{margin:7px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-7abb0aba.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-7abb0aba{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353556 .elementor-element.elementor-element-68e2b2ca > .elementor-widget-container{margin:-110px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-68e2b2ca.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-68e2b2ca{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353556 .elementor-element.elementor-element-8d50d33{--display:flex;--margin-top:-85px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-8d50d33.e-con{--align-self:center;}.elementor-widget-form .elementor-field-group > label, .elementor-widget-form .elementor-field-subgroup label{color:var( --e-global-color-text );}.elementor-widget-form .elementor-field-group > label{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-field-type-html{color:var( --e-global-color-text );font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-field-group .elementor-field{color:var( --e-global-color-text );}.elementor-widget-form .elementor-field-group .elementor-field, .elementor-widget-form .elementor-field-subgroup label{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-button{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-widget-form .e-form__buttons__wrapper__button-next{background-color:var( --e-global-color-accent );}.elementor-widget-form .elementor-button[type=\"submit\"]{background-color:var( --e-global-color-accent );}.elementor-widget-form .e-form__buttons__wrapper__button-previous{background-color:var( --e-global-color-accent );}.elementor-widget-form .elementor-message{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .e-form__indicators__indicator, .elementor-widget-form .e-form__indicators__indicator__label{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-widget-form{--e-form-steps-indicator-inactive-primary-color:var( --e-global-color-text );--e-form-steps-indicator-active-primary-color:var( --e-global-color-accent );--e-form-steps-indicator-completed-primary-color:var( --e-global-color-accent );--e-form-steps-indicator-progress-color:var( --e-global-color-accent );--e-form-steps-indicator-progress-background-color:var( --e-global-color-text );--e-form-steps-indicator-progress-meter-color:var( --e-global-color-text );}.elementor-widget-form .e-form__indicators__indicator__progress__meter{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-353556 .elementor-element.elementor-element-6ae6ef83{width:var( --container-widget-width, 69.5% );max-width:69.5%;--container-widget-width:69.5%;--container-widget-flex-grow:0;z-index:120;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group{padding-right:calc( 10px\/2 );padding-left:calc( 10px\/2 );margin-bottom:6px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-form-fields-wrapper{margin-left:calc( -10px\/2 );margin-right:calc( -10px\/2 );margin-bottom:-6px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label, .elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{color:#7B88A3;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-type-html{padding-bottom:0px;color:#7A7A7A;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field{color:#484848;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:01px 01px 01px 01px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:01px 01px 01px 01px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353556 .elementor-element.elementor-element-66fd4d36{--display:flex;--min-height:58px;--gap:0px 0px;--row-gap:0px;--column-gap:0px;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:8px 8px 8px 8px;--margin-top:14px;--margin-bottom:10px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353556 .elementor-element.elementor-element-66fd4d36.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4{width:auto;max-width:auto;z-index:120;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 > .elementor-widget-container{margin:2px 0px 2px 1px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group{padding-right:calc( 0px\/2 );padding-left:calc( 0px\/2 );margin-bottom:0px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-form-fields-wrapper{margin-left:calc( -0px\/2 );margin-right:calc( -0px\/2 );margin-bottom:-0px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label, .elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{color:#FB5E2A;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-type-html{padding-bottom:0px;color:#FFFFFF;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field{color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353556 .elementor-element.elementor-element-4a59cf88 > .elementor-widget-container{margin:-7px 0px -24px 0px;}.elementor-353556 .elementor-element.elementor-element-4a59cf88.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-4a59cf88{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:12.5px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-17d3ad0c{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;border-style:none;--border-style:none;--border-radius:8px 8px 8px 8px;--margin-top:-4px;--margin-bottom:05px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353556 .elementor-element.elementor-element-17d3ad0c.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-177c9b71{width:auto;max-width:auto;z-index:5;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 > .elementor-widget-container{margin:2px 0px 2px 1px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-177c9b71.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group{padding-right:calc( 0px\/2 );padding-left:calc( 0px\/2 );margin-bottom:0px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-form-fields-wrapper{margin-left:calc( -0px\/2 );margin-right:calc( -0px\/2 );margin-bottom:-0px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label, .elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{color:#000000;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-type-html{padding-bottom:0px;color:#FFFFFF;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field{color:#000000;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353556 .elementor-element.elementor-element-5bafe1f3{--display:flex;--min-height:0px;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--gap:0px 0px;--row-gap:0px;--column-gap:0px;--overlay-opacity:1;--margin-top:230px;--margin-bottom:-220px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:10;}.elementor-353556 .elementor-element.elementor-element-5bafe1f3::before, .elementor-353556 .elementor-element.elementor-element-5bafe1f3 > .elementor-background-video-container::before, .elementor-353556 .elementor-element.elementor-element-5bafe1f3 > .e-con-inner > .elementor-background-video-container::before, .elementor-353556 .elementor-element.elementor-element-5bafe1f3 > .elementor-background-slideshow::before, .elementor-353556 .elementor-element.elementor-element-5bafe1f3 > .e-con-inner > .elementor-background-slideshow::before, .elementor-353556 .elementor-element.elementor-element-5bafe1f3 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{--background-overlay:'';background-size:cover;}.elementor-353556 .elementor-element.elementor-element-5bafe1f3.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-432cc2a1{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-446e8565 > .elementor-widget-container{margin:0px 3px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-446e8565.elementor-element{--align-self:flex-end;}.elementor-353556 .elementor-element.elementor-element-446e8565{z-index:5;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;line-height:1.1em;color:#213864;}.elementor-353556 .elementor-element.elementor-element-60df49d0 > .elementor-widget-container{margin:35px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-60df49d0.elementor-element{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-60df49d0{z-index:5;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:500;line-height:1.1em;color:#6185C0;}.elementor-353556 .elementor-element.elementor-element-62ef38f0{--display:flex;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:space-between;--align-items:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-287px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-f4f51cb > .elementor-widget-container{margin:0px 3px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-f4f51cb.elementor-element{--align-self:flex-end;}.elementor-353556 .elementor-element.elementor-element-f4f51cb{z-index:5;text-align:left;font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:600;line-height:1.1em;color:#213864;}.elementor-353556 .elementor-element.elementor-element-f214306 > .elementor-widget-container{margin:0px 0px 0px 3px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-f214306.elementor-element{--align-self:flex-end;}.elementor-353556 .elementor-element.elementor-element-f214306{z-index:5;text-align:start;font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:600;line-height:1.1em;color:#213864;}@media(max-width:1001px){.elementor-353556 .elementor-element.elementor-element-6ae6ef83{z-index:11;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-size:10px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4{z-index:11;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-size:10px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-177c9b71{z-index:11;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-size:10px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-button{font-size:12px;}}@media(min-width:1001px){.elementor-353556 .elementor-element.elementor-element-6da64a60{--width:500px;}.elementor-353556 .elementor-element.elementor-element-45f807e0{--width:100%;}.elementor-353556 .elementor-element.elementor-element-6c65b106{--width:500px;}.elementor-353556 .elementor-element.elementor-element-75917e71{--width:10px;}.elementor-353556 .elementor-element.elementor-element-5ad25a69{--width:100%;}.elementor-353556 .elementor-element.elementor-element-469b1a7c{--width:500px;}.elementor-353556 .elementor-element.elementor-element-48a37689{--width:22%;}.elementor-353556 .elementor-element.elementor-element-79a46db6{--width:50%;}.elementor-353556 .elementor-element.elementor-element-3aa42503{--width:22%;}.elementor-353556 .elementor-element.elementor-element-78e1d9f5{--width:100%;}.elementor-353556 .elementor-element.elementor-element-cf3602e{--width:103px;}.elementor-353556 .elementor-element.elementor-element-6bf72a5{--width:103px;}.elementor-353556 .elementor-element.elementor-element-34d8bbba{--width:103px;}.elementor-353556 .elementor-element.elementor-element-5a9252c3{--width:103px;}.elementor-353556 .elementor-element.elementor-element-19ecc2b3{--width:103px;}.elementor-353556 .elementor-element.elementor-element-3617569c{--width:103px;}.elementor-353556 .elementor-element.elementor-element-7450a6f8{--width:103px;}.elementor-353556 .elementor-element.elementor-element-8d50d33{--width:100%;}.elementor-353556 .elementor-element.elementor-element-66fd4d36{--width:390px;}.elementor-353556 .elementor-element.elementor-element-17d3ad0c{--width:100%;}.elementor-353556 .elementor-element.elementor-element-5bafe1f3{--width:150%;}.elementor-353556 .elementor-element.elementor-element-62ef38f0{--width:95%;}}@media(max-width:1000px){.elementor-353556 .elementor-element.elementor-element-6da64a60{--width:390px;--margin-top:105px;--margin-bottom:-75px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-45f807e0{--min-height:150px;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--margin-top:-47px;--margin-bottom:-32px;--margin-left:0px;--margin-right:0px;--z-index:3;}.elementor-353556 .elementor-element.elementor-element-61dcced4{--width:25%;}.elementor-353556 .elementor-element.elementor-element-6c65b106{--width:100%;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--align-items:center;--flex-wrap:nowrap;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-6c65b106.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-eb74aa8{--width:75%;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;}.elementor-353556 .elementor-element.elementor-element-eb74aa8.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-1c762c45{--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-6be76773 > .elementor-widget-container{margin:-3px 40px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-5ad25a69{--width:100%;--justify-content:flex-end;--align-items:flex-end;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-52dec736 > .elementor-widget-container{margin:40px 55px -30px 0px;}.elementor-353556 .elementor-element.elementor-element-52dec736{z-index:100;}.elementor-353556 .elementor-element.elementor-element-469b1a7c{--width:78%;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--flex-wrap:nowrap;--margin-top:-103px;--margin-bottom:-7px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-469b1a7c.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-3f5f9124{--justify-content:center;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-48a37689{--width:25%;--margin-top:2px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-4b68287 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-4b68287{font-size:8px;}.elementor-353556 .elementor-element.elementor-element-79a46db6{--width:46%;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-47d25f98 > .elementor-widget-container{margin:0px -20px 0px -20px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-47d25f98{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-3aa42503{--width:25%;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-12d5dc39 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-12d5dc39{font-size:7px;}.elementor-353556 .elementor-element.elementor-element-21535e15{--margin-top:-15px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-de420c1{--width:83%;--min-height:15px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-de420c1.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-2f60f3f{width:100%;max-width:100%;font-size:10px;}.elementor-353556 .elementor-element.elementor-element-2f60f3f > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-78e1d9f5{--width:100%;--gap:6px 4px;--row-gap:6px;--column-gap:4px;--flex-wrap:wrap;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-cf3602e{--width:61px;--min-height:15px;}.elementor-353556 .elementor-element.elementor-element-7a1bb33{font-size:9px;}.elementor-353556 .elementor-element.elementor-element-6bf72a5{--width:61px;--min-height:15px;}.elementor-353556 .elementor-element.elementor-element-1a57dd9{font-size:9px;}.elementor-353556 .elementor-element.elementor-element-34d8bbba{--width:61px;--min-height:15px;}.elementor-353556 .elementor-element.elementor-element-32bd35c6{font-size:9px;}.elementor-353556 .elementor-element.elementor-element-5a9252c3{--width:61px;--min-height:15px;}.elementor-353556 .elementor-element.elementor-element-8722042{font-size:9px;}.elementor-353556 .elementor-element.elementor-element-19ecc2b3{--width:61px;--min-height:15px;}.elementor-353556 .elementor-element.elementor-element-6071d405{font-size:9px;}.elementor-353556 .elementor-element.elementor-element-3617569c{--width:61px;--min-height:15px;}.elementor-353556 .elementor-element.elementor-element-4b013e71{font-size:9px;}.elementor-353556 .elementor-element.elementor-element-7450a6f8{--width:61px;--min-height:15px;}.elementor-353556 .elementor-element.elementor-element-30970f89{font-size:9px;}.elementor-353556 .elementor-element.elementor-element-6aac67a6{--width:96%;--min-height:250px;--margin-top:-56px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353556 .elementor-element.elementor-element-40d299c > .elementor-widget-container{margin:5px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-1fd15d20 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-54e4e530{--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-54e4e530.e-con{--order:-99999 \/* order start hack *\/;}.elementor-353556 .elementor-element.elementor-element-531f3cb1 > .elementor-widget-container{margin:-30px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-3a352c3f{width:var( --container-widget-width, 63% );max-width:63%;--container-widget-width:63%;--container-widget-flex-grow:0;text-align:center;font-size:10px;line-height:1.2em;}.elementor-353556 .elementor-element.elementor-element-3a352c3f > .elementor-widget-container{margin:-181px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-7abb0aba > .elementor-widget-container{margin:-180px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-7abb0aba{text-align:center;font-size:14px;line-height:1.2em;}.elementor-353556 .elementor-element.elementor-element-68e2b2ca > .elementor-widget-container{margin:-110px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-68e2b2ca{font-size:10px;}.elementor-353556 .elementor-element.elementor-element-8d50d33{--width:184px;--margin-top:-156px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-8d50d33.e-con{--align-self:center;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83{width:var( --container-widget-width, 100% );max-width:100%;--container-widget-width:100%;--container-widget-flex-grow:0;z-index:120;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-size:8px;}.elementor-353556 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-size:10.5px;}.elementor-353556 .elementor-element.elementor-element-66fd4d36{--width:85%;--min-height:42px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;--margin-top:10px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4{z-index:120;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-type-html{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-size:10px;}.elementor-353556 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-size:10.5px;}.elementor-353556 .elementor-element.elementor-element-4a59cf88 > .elementor-widget-container{margin:2px 0px -10px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-4a59cf88{font-size:7.5px;}.elementor-353556 .elementor-element.elementor-element-17d3ad0c{--width:85%;--min-height:42px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;--margin-top:2px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:251;}.elementor-353556 .elementor-element.elementor-element-177c9b71 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-177c9b71{z-index:120;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-type-html{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-size:12px;}.elementor-353556 .elementor-element.elementor-element-177c9b71 .elementor-button{font-size:10.5px;}.elementor-353556 .elementor-element.elementor-element-5bafe1f3{--min-height:150px;--margin-top:-149px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353556 .elementor-element.elementor-element-446e8565 > .elementor-widget-container{margin:-133px 0px 0px 0px;}.elementor-353556 .elementor-element.elementor-element-446e8565.elementor-element{--align-self:flex-start;}.elementor-353556 .elementor-element.elementor-element-446e8565{text-align:left;font-size:10px;}.elementor-353556 .elementor-element.elementor-element-f4f51cb > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353556 .elementor-element.elementor-element-f4f51cb.elementor-element{--align-self:flex-start;}.elementor-353556 .elementor-element.elementor-element-f4f51cb{text-align:center;font-size:10px;}.elementor-353556 .elementor-element.elementor-element-f214306 > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353556 .elementor-element.elementor-element-f214306.elementor-element{--align-self:flex-start;}.elementor-353556 .elementor-element.elementor-element-f214306{text-align:center;font-size:10px;}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"353556\" class=\"elementor elementor-353556 elementor-bc-flex-widget e-loop-item e-loop-item-49198 post-49198 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon category-ministre-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-6da64a60 e-con-full OrdiMobileConteneurClass e-flex e-con e-child\" data-id=\"6da64a60\" data-element_type=\"container\" id=\"testIdTest\">\n\t\t<div class=\"elementor-element elementor-element-45f807e0 e-con-full UploadFileConteneur AdUploadedTitle elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop e-flex e-con e-child\" data-id=\"45f807e0\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-61dcced4 e-con-full e-flex e-con e-child\" data-id=\"61dcced4\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6c65b106 e-con-full e-flex e-con e-child\" data-id=\"6c65b106\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-eb74aa8 e-con-full e-flex e-con e-child\" data-id=\"eb74aa8\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1c762c45 e-con-full e-flex e-con e-child\" data-id=\"1c762c45\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-5be6a53a e-con-full e-flex e-con e-child\" data-id=\"5be6a53a\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-75917e71 e-con-full e-flex e-con e-child\" data-id=\"75917e71\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6be76773 AnnonceDragIcone elementor-widget elementor-widget-image\" data-id=\"6be76773\" data-element_type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/via-agency.media\/wp-content\/uploads\/2025\/05\/arrow-drag-64-bleu.png\" title=\"\" alt=\"\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5ad25a69 e-con-full CroixResetAnnonceContainer e-flex e-con e-child\" data-id=\"5ad25a69\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-52dec736 elementor-widget elementor-widget-image\" data-id=\"52dec736\" data-element_type=\"widget\" id=\"CroixResetAnnonce\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a href=\"#\">\n\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-transparent.png\" title=\"\" alt=\"\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-469b1a7c e-con-full UploadFileConteneur e-flex e-con e-child\" data-id=\"469b1a7c\" data-element_type=\"container\" id=\"UploadFileConteneur\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t<div class=\"elementor-element elementor-element-3f5f9124 e-con-full ChoisirEspacePublicitaireDisponibiliteConteneur e-flex e-con e-child\" data-id=\"3f5f9124\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-48a37689 e-con-full e-flex e-con e-child\" data-id=\"48a37689\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4b68287 PositionEspacePublicitaire elementor-widget elementor-widget-text-editor\" data-id=\"4b68287\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPosition\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-79a46db6 e-con-full e-flex e-con e-child\" data-id=\"79a46db6\" data-element_type=\"container\" id=\"ChoixEspacePublicitaireTitre\">\n\t\t\t\t<div class=\"elementor-element elementor-element-47d25f98 elementor-widget elementor-widget-text-editor\" data-id=\"47d25f98\" data-element_type=\"widget\" id=\"ChoixEspacePublicitaireTexte\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Fixed advertising space<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3aa42503 e-con-full e-flex e-con e-child\" data-id=\"3aa42503\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-12d5dc39 ReferenceEspacePublicitaire elementor-widget elementor-widget-text-editor\" data-id=\"12d5dc39\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reference<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-21535e15 e-con-full EspPubFormatMainContainer e-flex e-con e-child\" data-id=\"21535e15\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-de420c1 e-con-full e-flex e-con e-child\" data-id=\"de420c1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2f60f3f elementor-widget-mobile__width-inherit SelectionFormatTitre elementor-hidden-tablet elementor-widget elementor-widget-text-editor\" data-id=\"2f60f3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPlease select or create an ad format\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-78e1d9f5 e-con-full EspPubFormatListe e-flex e-con e-child\" data-id=\"78e1d9f5\" data-element_type=\"container\">\n\t\t<a class=\"elementor-element elementor-element-cf3602e e-con-full EspPubFormatContainer FormatIdCreation e-flex e-con e-child\" data-id=\"cf3602e\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7a1bb33 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"7a1bb33\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tCreation\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-6bf72a5 e-con-full EspPubFormatContainer FormatIdPopUp e-flex e-con e-child\" data-id=\"6bf72a5\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1a57dd9 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"1a57dd9\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPop-up\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-34d8bbba e-con-full EspPubFormatContainer FormatIdBanniere e-flex e-con e-child\" data-id=\"34d8bbba\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-32bd35c6 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"32bd35c6\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tBanner\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-5a9252c3 e-con-full EspPubFormatContainer FormatIdVideo e-flex e-con e-child\" data-id=\"5a9252c3\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8722042 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"8722042\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tVideo\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-19ecc2b3 e-con-full EspPubFormatContainer FormatIdCommunique e-flex e-con e-child\" data-id=\"19ecc2b3\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6071d405 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"6071d405\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPress release\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-3617569c e-con-full EspPubFormatContainer FormatIdInterview e-flex e-con e-child\" data-id=\"3617569c\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4b013e71 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"4b013e71\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tInterview\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-7450a6f8 e-con-full EspPubFormatContainer FormatIdParrainage e-flex e-con e-child\" data-id=\"7450a6f8\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-30970f89 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"30970f89\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tSponsorship\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6aac67a6 e-con-full HTMLUploadfileConteneur e-flex e-con e-child\" data-id=\"6aac67a6\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-40d299c elementor-widget__width-inherit HTMLUploadfileClass elementor-widget elementor-widget-html\" data-id=\"40d299c\" data-element_type=\"widget\" id=\"HTMLUploadfile\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta name=\"google\" content=\"notranslate\">\r\n    \r\n    <script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" src=\"https:\/\/code.jquery.com\/jquery-3.6.0.min.js\"><\/script>\r\n    <script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" src=\"https:\/\/code.jquery.com\/ui\/1.12.1\/jquery-ui.min.js\"><\/script>\r\n\r\n<script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\">\r\n\/\/ Lazy loading des biblioth\u00e8ques - charg\u00e9es uniquement au besoin\r\nwindow.VIALibraries = {\r\n    mammothLoaded: false,\r\n    pdfLoaded: false,\r\n    \r\n    loadMammoth: function(callback) {\r\n        if (this.mammothLoaded) { callback(); return; }\r\n        var script = document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mammoth\/1.4.0\/mammoth.browser.min.js';\r\n        script.onload = function() { \r\n            window.VIALibraries.mammothLoaded = true; \r\n            callback(); \r\n        };\r\n        document.head.appendChild(script);\r\n    },\r\n    \r\n    loadPdfJs: function(callback) {\r\n        if (this.pdfLoaded) { callback(); return; }\r\n        var script = document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.min.js';\r\n        script.onload = function() {\r\n            window.VIALibraries.pdfLoaded = true;\r\n            pdfjsLib.GlobalWorkerOptions.workerSrc = '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n            callback();\r\n        };\r\n        document.head.appendChild(script);\r\n    }\r\n};\r\n<\/script>\r\n\r\n<\/head>\r\n<body>\r\n    <div id=\"PopUpMessageAchattest\" class=\"draggable\" draggable=\"true\" style=\"justify-content: center; align-items: flex-start;\">\r\n        <div id=\"drop_file_zone_achat\" class=\"drop_file_zone_achat_class\" ondrop=\"uploadFile_achat(event)\" ondragover=\"return false\">\r\n        <div id=\"drag_upload_file_achat\">\r\n            <p><input class=\"button-2_achat\" type=\"button\" value=\"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\" onclick=\"fileExplorer_achat(event);\" \/><\/p>\r\n                <input type=\"file\" id=\"selectfile_achat\" \/>\r\n        <\/div>\r\n        <\/div>\r\n        <div class=\"img-content\"><\/div>\r\n    <\/div>\r\n<\/body>\r\n<\/html>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1fd15d20 elementor-widget__width-inherit HTMLUploadfileClass elementor-widget elementor-widget-html\" data-id=\"1fd15d20\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\">\r\nif (!window._espPubScriptLoaded) {\r\nwindow._espPubScriptLoaded = true;\r\n\/**\r\n * Configuration centralis\u00e9e\r\n *\/\r\nconst CONFIG = {\r\n    iframeId: 'yearbook-iframe',\r\n    ajaxUrl: 'https:\/\/via-agency.media\/wp-admin\/admin-ajax.php',\r\n    apercuUrl: 'https:\/\/rdc.yearbook-media.com\/apercu',\r\n    \r\n    allowedExtensions: {\r\n        video: ['mp4', 'mov', 'wmv', 'avi', 'flv', 'f4v', 'swf', 'mkv', 'webm', 'mpeg'],\r\n        image: ['jpg', 'jpeg', 'png', 'gif', 'tif', 'svg', 'webp'],\r\n        document: ['doc', 'docx', 'ppt', 'pptx', 'pdf']\r\n    },\r\n    \r\n    mimeTypes: {\r\n        docx: 'application\/vnd.openxmlformats-officedocument.wordprocessingml.document',\r\n        pdf: 'application\/pdf'\r\n    },\r\n    \r\n    dragScroll: {\r\n        threshold: 150,\r\n        maxSpeed: 65,\r\n        throttleDelay: 50\r\n    },\r\n    \r\n    breakpoints: {\r\n        mobile: 1000\r\n    },\r\n    \r\n    keywords: [\r\n        { normal: \"parrainage\", display: \"Parrainage\" },\r\n        { normal: \"communique\", display: \"Communiqu\u00e9\" },\r\n        { normal: \"interview\", display: \"Interview\" }\r\n    ]\r\n};\r\n\r\n\/**\r\n * \u2705 v1.19.1 : Module de positionnement dans l'iframe\r\n * \r\n * PROBL\u00c8ME : L'iframe ne scrolle pas en desktop \u2014 c'est le parent (#PageWebTitrePage) qui scrolle.\r\n * L'iframe est scal\u00e9e \u00e0 0.85 \u2192 les coordonn\u00e9es iframe \u2260 coordonn\u00e9es parent.\r\n * position:fixed dans l'iframe = relatif au CONTENU TOTAL, pas au viewport visible.\r\n * \r\n * SOLUTION : Le parent calcule visibleTopIframe (la position iframe du haut du viewport)\r\n * et l'envoie dans le message 'PageWebTitrePage-scroll'. L'iframe n'a qu'\u00e0 l'utiliser.\r\n * \r\n * Pour scroller, l'iframe envoie 'scrollToIframeY' au parent qui fait la conversion.\r\n *\/\r\nconst ScrollHelper = {\r\n    _visibleTopIframe: 0,\r\n    _iframeScale: 0.85,\r\n\r\n    init() {\r\n        \/\/ \u00c9couter les donn\u00e9es de scroll envoy\u00e9es par le parent\r\n        window.addEventListener('message', (event) => {\r\n            var msg = event.data;\r\n            if (msg ? msg.action === 'PageWebTitrePage-scroll' : false) {\r\n                if (typeof msg.visibleTopIframe === 'number') {\r\n                    this._visibleTopIframe = msg.visibleTopIframe;\r\n                }\r\n                if (typeof msg.iframeScale === 'number') {\r\n                    this._iframeScale = msg.iframeScale;\r\n                }\r\n            }\r\n        });\r\n        console.log('\u2705 ScrollHelper initialis\u00e9');\r\n    },\r\n\r\n    \/**\r\n     * Position Y dans le document iframe correspondant au haut du viewport visible\r\n     *\/\r\n    getVisibleTop() {\r\n        if (window === window.top) {\r\n            return window.scrollY || 0;\r\n        }\r\n        return this._visibleTopIframe;\r\n    },\r\n\r\n    \/**\r\n     * Demande au parent de scroller pour positionner `element` \u00e0 `offsetFromTop` px du viewport\r\n     *\/\r\n    scrollElementTo(element, offsetFromTop) {\r\n        if (!element) return;\r\n        \/\/ \u2705 v2.7.3 : Pas de scroll auto sur les sites pays (window.top)\r\n        \/\/ Le scroll apr\u00e8s d\u00e9p\u00f4t d'annonce est ind\u00e9sirable sur desktop et mobile\r\n        if (window === window.top) return;\r\n\r\n        \/\/ getBoundingClientRect().top dans une iframe sans scroll = position absolue Y\r\n        var elementAbsY = element.getBoundingClientRect().top;\r\n\r\n        if (window === window.top) {\r\n            window.scrollTo({ top: elementAbsY - offsetFromTop + (window.scrollY || 0), behavior: 'smooth' });\r\n            return;\r\n        }\r\n\r\n        \/\/ Envoyer au parent \u2014 le parent fait la conversion scale\r\n        window.parent.postMessage({\r\n            type: 'scrollToIframeY',\r\n            iframeId: CONFIG.iframeId,\r\n            iframeY: elementAbsY,\r\n            offsetFromTop: offsetFromTop\r\n        }, '*');\r\n        console.log('\ud83d\udcdc scrollToIframeY:', { iframeY: Math.round(elementAbsY), offsetFromTop });\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'\u00e9tat (SessionStorage)\r\n *\/\r\nconst StateManager = {\r\n    init() {\r\n        if (sessionStorage.getItem(\"AchatEspaceCall\") === 'No') {\r\n            sessionStorage.clear();\r\n        }\r\n        \r\n        this.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        this.set('dragstart_Rank_Emplacement_Page_Web', 'No');\r\n        this.set('dragstart_Commande_Emplacement_Page_Web', 'No');\r\n        \r\n        \/\/ \u2705 v2.1.1 : Popup \u2192 ne jamais pr\u00e9remplir le format\r\n        if (this.get('PopUpChoice') === 'Yes') {\r\n            this.set('Formatchoisi', 'No');\r\n            this.set('FormatSelect', '');\r\n            this.set('Commande_Format_Transmis', '');\r\n        }\r\n    },\r\n    \r\n    get(key) {\r\n        return sessionStorage.getItem(key);\r\n    },\r\n    \r\n    set(key, value) {\r\n        sessionStorage.setItem(key, value);\r\n    },\r\n    \r\n    getMultiple(keys) {\r\n        return keys.reduce((acc, key) => {\r\n            acc[key] = this.get(key);\r\n            return acc;\r\n        }, {});\r\n    },\r\n    \r\n    setMultiple(obj) {\r\n        Object.entries(obj).forEach(([key, value]) => {\r\n            this.set(key, value);\r\n        });\r\n    },\r\n    \r\n    buildEmplacementReference(rank) {\r\n        const codeSite = this.get('codeSite');\r\n        const codePage = this.get('codePage');\r\n        return `${codeSite}${codePage}L${rank.substring(3)}`;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des fichiers\r\n *\/\r\nconst FileManager = {\r\n    getExtension(filename) {\r\n        console.log('filename:', filename);\r\n        const parts = filename.split('.');\r\n        return parts.length > 1 ? parts.pop().toLowerCase() : '';\r\n    },\r\n    \r\n    isAllowedExtension(extension) {\r\n        return Object.values(CONFIG.allowedExtensions)\r\n            .flat()\r\n            .includes(extension);\r\n    },\r\n    \r\n    getFileType(extension) {\r\n        for (const [type, extensions] of Object.entries(CONFIG.allowedExtensions)) {\r\n            if (extensions.includes(extension)) {\r\n                return type;\r\n            }\r\n        }\r\n        return null;\r\n    },\r\n    \r\n    async urlToFile(url, filename) {\r\n        console.log(\"Fetching from URL:\", url);\r\n        \r\n        const mimeType = filename.includes('.docx') \r\n            ? CONFIG.mimeTypes.docx \r\n            : filename.includes('.pdf') \r\n            ? CONFIG.mimeTypes.pdf \r\n            : null;\r\n        \r\n        try {\r\n            const response = await fetch(url);\r\n            \r\n            if (!response.ok) {\r\n                throw new Error(`Network response was not ok: ${response.status} ${response.statusText}`);\r\n            }\r\n            \r\n            const blob = await response.blob();\r\n            return new File([blob], filename, { type: mimeType });\r\n        } catch (error) {\r\n            console.error(\"Error in urlToFile:\", error);\r\n            alert(\"Erreur lors du chargement du fichier. Veuillez r\u00e9essayer.\");\r\n            throw error;\r\n        }\r\n    },\r\n    \r\n    createObjectUrl(file) {\r\n        \/\/ R\u00e9voquer l'ancien URL si existant\r\n        const oldUrl = StateManager.get('objectUrl');\r\n        if (oldUrl ? oldUrl.startsWith('blob:') : false) {\r\n            URL.revokeObjectURL(oldUrl);\r\n            console.log('\ud83d\uddd1\ufe0f Old Object URL revoked');\r\n        }\r\n        \r\n        const url = URL.createObjectURL(file);\r\n        StateManager.set('objectUrl', url);\r\n        return url;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion du Drag & Drop\r\n *\/\r\nconst DragDropManager = {\r\n    state: {\r\n        isFileBeingDragged: false,\r\n        draggedFile: null,\r\n        dragInProgress: false,\r\n        throttleTimeout: null\r\n    },\r\n    \r\n    init() {\r\n        this.attachEventListeners();\r\n    },\r\n    \r\n    attachEventListeners() {\r\n        window.addEventListener('dragstart', (e) => this.handleDragStart(e), true);\r\n        jQuery(document).on('dragover', (e) => this.handleDragOver(e));\r\n        jQuery(document).on('dragleave drop', (e) => this.handleDragEnd(e));\r\n        jQuery(\"#drop_file_zone_achat\").on(\"dragover\", (e) => this.handleDropZoneDragOver(e));\r\n        jQuery(\".draggable\").on(\"dragend\", (e) => this.handleDraggableEnd(e));\r\n    },\r\n    \r\n    handleDragStart(e) {\r\n        console.log('Window-level dragstart', e);\r\n        this.state.dragInProgress = true;\r\n    \r\n        const $target = $(e.target);\r\n        const droppableParent = $target.closest('.droppable');\r\n        const parentId = droppableParent.length ? droppableParent.attr('id') : null;\r\n        \r\n        StateManager.set('dragstart_Rank_Emplacement_Page_Web', parentId);\r\n        console.log('dragstart_Rank_Emplacement_Page_Web', parentId);\r\n        \r\n        \/\/ \u2705 CALCULER IMM\u00c9DIATEMENT dragstart_Commande_Emplacement_Page_Web\r\n        if (parentId) {\r\n            const dragstartRef = StateManager.buildEmplacementReference(parentId);\r\n            StateManager.set('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n            console.log('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n            \/\/ \u2705 v2.4.5 : Capturer l'\u00e9tat checkbox R\u00e9server au moment du dragstart\r\n            \/\/ Si l'utilisateur a explicitement d\u00e9coch\u00e9, forcer \u00e0 No m\u00eame si DOM est coch\u00e9\r\n            var $_dragCb = $('#' + parentId).find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n            var _dragCbChecked = $_dragCb.prop('checked') === true;\r\n            var _expliciteDecocheDrag = StateManager.get('_reserverDecoche_' + parentId) === 'Yes';\r\n            var _reserverDrag = _dragCbChecked ? !_expliciteDecocheDrag : false;\r\n            StateManager.set('dragstart_ReserverChecked', _reserverDrag ? 'Yes' : 'No');\r\n            \/\/ R\u00e9initialiser le flag apr\u00e8s lecture\r\n            StateManager.set('_reserverDecoche_' + parentId, 'No');\r\n            console.log('[dragstart] ReserverChecked dom:', _dragCbChecked, '| d\u00e9coch\u00e9 explicitement:', _expliciteDecocheDrag, '| final:', _reserverDrag);\r\n        }\r\n    \r\n        const dataTransfer = e.dataTransfer || e.originalEvent?.dataTransfer;\r\n        const files = dataTransfer?.files || null;\r\n        \r\n        const imgSrc = $target.closest('.draggable').find('img').attr('src');\r\n        const videoSrc = $target.closest('.draggable').find('video').attr('src') || \r\n                        $target.closest('.draggable').find('video source').attr('src');\r\n        const mediaSrc = imgSrc || videoSrc;\r\n    \r\n        console.log('dragstart target:', e.target);\r\n        console.log('files:', files);\r\n        console.log('mediaSrc:', mediaSrc);\r\n    \r\n        if (!mediaSrc ? (!files || files.length === 0) : false) {\r\n            console.log('No media source or files found, preventing drag');\r\n            e.preventDefault();\r\n            this.state.dragInProgress = false;\r\n            return false;\r\n        }\r\n    \r\n        \/\/ \u2705 v4.9ds Pb 5 : poser une image fant\u00f4me (ghost) sur l'\u00e9v\u00e9nement drag pour que\r\n        \/\/   le navigateur affiche l'image qu'on d\u00e9place au curseur, au lieu de la croix\r\n        \/\/   d'interdiction par d\u00e9faut. Sans setDragImage, certains navigateurs (Chrome,\r\n        \/\/   Firefox) affichent une ic\u00f4ne \"non autoris\u00e9\" si l'\u00e9l\u00e9ment source est complexe\r\n        \/\/   ou si la zone de drop n'est pas imm\u00e9diatement reconnue.\r\n        \/\/   On configure aussi effectAllowed='copy' pour standardiser le comportement.\r\n        if (dataTransfer) {\r\n            try {\r\n                dataTransfer.effectAllowed = 'copy';\r\n                var $_dragImg = $target.closest('.draggable').find('img').first();\r\n                if ($_dragImg.length ? $_dragImg[0].complete : false) {\r\n                    var _imgEl = $_dragImg[0];\r\n                    var _r = _imgEl.getBoundingClientRect();\r\n                    \/\/ D\u00e9calage du ghost : centr\u00e9 sur le curseur\r\n                    var _ox = Math.round((_r.width || 100) \/ 2);\r\n                    var _oy = Math.round((_r.height || 60) \/ 2);\r\n                    dataTransfer.setDragImage(_imgEl, _ox, _oy);\r\n                }\r\n            } catch(_eDI) { console.warn('[dragstart] setDragImage \u00e9chec:', _eDI); }\r\n        }\r\n    \r\n        if (mediaSrc) {\r\n            StateManager.set('objectUrl', mediaSrc);\r\n        }\r\n    \r\n        StateManager.set('Commande_Format_Transmis', '');\r\n        \r\n        if ($target.closest('.droppable').find('.doc-preview-container:visible').length > 0) {\r\n            StateManager.set('Commande_Format_Transmis', 'R\u00e9dactionnel');\r\n            StateManager.set('FullPathAdFile', \r\n                $target.closest('.droppable').find('.doc-preview-FullPathAdFile').text());\r\n        }\r\n    \r\n        if (files ? files.length > 0 : false) {\r\n            this.state.isFileBeingDragged = true;\r\n            this.state.draggedFile = files[0];\r\n        }\r\n        \r\n        \/\/ \u2705 TOUJOURS marquer comme \"Moved\" quand on drag depuis un espace existant\r\n        if (parentId ? mediaSrc : false) {\r\n            StateManager.set('FirstUploadFileorMoved', 'Moved');\r\n            console.log('\u2705 Drag depuis espace existant - Moved');\r\n        }\r\n    \r\n        console.log('Drag Started', {\r\n            file: this.state.draggedFile,\r\n            mediaSrc: mediaSrc,\r\n            dragstartRef: StateManager.get('dragstart_Commande_Emplacement_Page_Web')\r\n        });\r\n    },\r\n    \r\n    handleDragOver(e) {\r\n        e.preventDefault();\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            this.handleIframeDragScroll(e);\r\n        } else {\r\n            this.handleDirectPageScroll(e);\r\n        }\r\n    },\r\n    \r\n    handleIframeDragScroll(e) {\r\n        if (!this.state.throttleTimeout) {\r\n            this.state.throttleTimeout = setTimeout(() => {\r\n                MessageManager.sendToParent('dragScroll', { clientY: e.clientY });\r\n                this.state.throttleTimeout = null;\r\n            }, CONFIG.dragScroll.throttleDelay);\r\n        }\r\n    },\r\n    \r\n    handleDirectPageScroll(e) {\r\n        const { threshold, maxSpeed } = CONFIG.dragScroll;\r\n        const windowHeight = window.innerHeight;\r\n        \r\n        if (e.clientY < threshold) {\r\n            const speed = Math.max(1, maxSpeed * (1 - e.clientY \/ threshold));\r\n            window.scrollBy(0, -speed);\r\n        } else if (e.clientY > windowHeight - threshold) {\r\n            const speed = Math.max(1, maxSpeed * (1 - (windowHeight - e.clientY) \/ threshold));\r\n            window.scrollBy(0, speed);\r\n        }\r\n    },\r\n    \r\n    handleDragEnd(e) {\r\n        e.preventDefault();\r\n        MessageManager.sendToParent('dragEnd', {});\r\n        console.log('dragleave drop');\r\n    },\r\n    \r\n    handleDropZoneDragOver(e) {\r\n        e.preventDefault();\r\n        console.log(\"FirstUploadFileorMoved:\", StateManager.get('FirstUploadFileorMoved'));\r\n    },\r\n    \r\n    handleDraggableEnd(e) {\r\n        e.preventDefault();\r\n        this.resetState();\r\n        console.log('dragend');\r\n    },\r\n    \r\n    resetState() {\r\n        this.state.isFileBeingDragged = false;\r\n        this.state.draggedFile = null;\r\n        this.state.dragInProgress = false;\r\n    },\r\n    \r\n    clearDataTransferFiles(e) {\r\n        e.dataTransfer = new DataTransfer();\r\n        console.log('DataTransfer files cleared');\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'interface utilisateur\r\n *\/\r\nconst UIManager = {\r\n    isMobile() {\r\n        return window.outerWidth < CONFIG.breakpoints.mobile;\r\n    },\r\n    \r\n    isDesktop() {\r\n        const userAgent = navigator.userAgent;\r\n        const isWindows = userAgent.indexOf('Windows') > -1;\r\n        const isMac = userAgent.indexOf('Macintosh') > -1;\r\n        const isLinux = userAgent.indexOf('Linux') > -1 ? userAgent.indexOf('Android') === -1 : false;\r\n        return isWindows || isMac || isLinux;\r\n    },\r\n    \r\n    initMobileUI() {\r\n        $(document).ready(() => {\r\n            $('label[for=\"form-field-LienAnnonce\"]').each(function() {\r\n                $(this).text('Renseigner le lien hypertexte de l\\'annonce');\r\n            });\r\n            \r\n            $('input[name=\"form_fields[LienAnnonce]\"]').each(function() {\r\n                $(this).attr('placeholder', 'Renseigner le lien hypertexte de l\\'annonce');\r\n            });\r\n        });\r\n        \r\n        jQuery('.MsgDatesText').hide();\r\n        this.attachMobileEventListeners();\r\n    },\r\n    \r\n    attachMobileEventListeners() {\r\n        jQuery('.TransmettreFichierAnnonceContainer').on('click', (e) => {\r\n            this.handleMobileSpaceSelection(e);\r\n        });\r\n        \r\n        jQuery('[id=\"form-field-selected_currency_Mobile\"]').off('change').on('change', (e) => {\r\n            this.handleMobileCurrencyChange(e);\r\n        });\r\n    },\r\n    \r\n    handleMobileSpaceSelection(e) {\r\n        e.preventDefault();\r\n        jQuery('#IndisponibilitesMsg').hide();\r\n        \r\n        jQuery('input[name=\"form_fields[SelectEspacePublicitaireMobile]\"]').prop('checked', false);\r\n        jQuery(e.currentTarget).find('input[name=\"form_fields[SelectEspacePublicitaireMobile]\"]').prop('checked', true);\r\n        \r\n        StateManager.set(\"PageWebDisplayed\", 'Yes');\r\n        \r\n        jQuery('.FormSelectDevisesMobile').hide();\r\n        jQuery(e.target).closest('.EspacePublicitaireMobile').find('.FormSelectDevisesMobile').show();\r\n        \r\n        const $droppable = jQuery(e.currentTarget).closest('.droppable');\r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n        StateManager.set('Commande_Emplacement_Page_Web', \r\n            StateManager.buildEmplacementReference(rankId));\r\n        \r\n        this.updateEmplacementDisplay();\r\n        this.handleAvailabilityDisplay(e);\r\n        this.displayUploadedAdIfExists(e);\r\n        this.updateTariffDisplay(e);\r\n    },\r\n    \r\n    updateEmplacementDisplay() {\r\n        const emplacement = StateManager.get('Commande_Emplacement_Page_Web');\r\n        jQuery('#EmplacementAnnonceDataStep3').html(emplacement).css({'color': '#56BE50'});\r\n        console.log(\"Emplacement:\", emplacement);\r\n    },\r\n    \r\n    handleAvailabilityDisplay(e) {\r\n        const $espace = jQuery(e.target).closest('.EspacePublicitaireMobile');\r\n        const dispoText = $espace.find('.ChoisirEspacePublicitaireDisponibilite').text();\r\n        \r\n        if (dispoText.length > 46) {\r\n            const espacePublicitaireDispoFrom = dispoText.slice(-10);\r\n            console.log(\"espacePublicitaireDispoFrom:\", espacePublicitaireDispoFrom);\r\n            console.log(\"Date de debut:\", StateManager.get(\"Debut_de_campagne\"));\r\n        } else {\r\n            jQuery('#form-field-DebutCampagne').val(StateManager.get(\"Debut_de_campagne\"));\r\n        }\r\n    },\r\n    \r\n    displayUploadedAdIfExists(e) {\r\n        if (StateManager.get(\"FileReceived\") === 'Yes') {\r\n            jQuery('.VisualisationAnnonceMobile').hide();\r\n            \r\n            const espaceId = StateManager.get('espaceChoisi');\r\n            const $espace = document.querySelector(`[id=\"${espaceId}\"]`);\r\n            \r\n            jQuery($espace).find('.MessageAnnonceMobileTexte')\r\n                .html('Merci de choisir des dates de campagne<br>afin d\\'obtenir le tarif de cet espace publicitaire');\r\n            jQuery($espace).find('.VisualisationAnnonceMobile').show();\r\n        \r\n            const img = document.createElement('img');\r\n            img.src = StateManager.get('objectUrl');\r\n            img.style.width = 'auto';\r\n            img.style.height = 'auto';\r\n            img.style.maxWidth = '100%';\r\n            img.style.maxHeight = '200px';\r\n            \r\n            jQuery('.VisualisationAnnonceMobile').empty();\r\n            jQuery($espace).find('.VisualisationAnnonceMobile').append(img);\r\n        }\r\n    },\r\n    \r\n    updateTariffDisplay(e) {\r\n        jQuery('.TariftobedisplayedMobile').html('-');\r\n        const newTarif = StateManager.get('NewTarifformatted');\r\n        \r\n        if (newTarif ? newTarif !== '-' : false) {\r\n            jQuery(e.target).closest('.EspacePublicitaireMobile')\r\n                .find('.TariftobedisplayedMobile')\r\n                .html(newTarif);\r\n        }\r\n    },\r\n    \r\n    handleMobileCurrencyChange(e) {\r\n        event.preventDefault();\r\n        StateManager.setMultiple({\r\n            \"SelectionCatalogueOuAchat\": 'Catalogue',\r\n            \"selected_currency\": jQuery(e.currentTarget).val()\r\n        });\r\n        \r\n        jQuery('#form-field-selected_currency_2').val(StateManager.get(\"selected_currency\"));\r\n        console.log(\"selected currency:\", StateManager.get(\"selected_currency\"));\r\n        \r\n        setTimeout(() => {\r\n            jQuery(e.target).closest('.EspacePublicitaireMobile')\r\n                .find('.TariftobedisplayedMobile')\r\n                .html(StateManager.get(\"NewTarifformatted\"));\r\n        }, 500);\r\n    },\r\n    \r\n    initDesktopUI() {\r\n        StateManager.setMultiple({\r\n            \"FileReceived\": \"No\",\r\n            \"AdDisplayed\": \"No\"\r\n        });\r\n        jQuery('.MsgAdNotDisplayed').hide();\r\n    },\r\n    \r\n    showUploadProgress($dropZone) {\r\n        \/\/ Cacher les \u00e9l\u00e9ments\r\n        $dropZone.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .TexteMobileAnnonce')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        \/\/ \u2705 v2.7.3 : Loading dans le dropZone (flux normal, pas de fixed)\r\n        (function() {\r\n            var $_drp    = $dropZone.closest('.droppable');\r\n            var _rankId  = $_drp.attr('id') || '';\r\n            var _isEle0A = _rankId === 'Ele0A';\r\n            var _isMob   = UIManager.isMobile();\r\n            var _posLib  = PreviewRenderer._getPositionLibelle(_rankId) || '';\r\n            var _empl    = StateManager.get('Commande_Emplacement_Page_Web') || '';\r\n            \/\/ \u2705 Bug 11 : -2px sur mobile\r\n            var _fsPos   = _isMob ? '8px'  : '12px';\r\n            var _fsTitle = _isMob ? '9px'  : '14px';\r\n            var _fsRef   = _isMob ? '7px'  : '11px';\r\n\r\n            \/\/ Header identique \u00e0 _buildAdOverlay \u2014 couleurs selon l'espace\r\n            var _hdrBg    = _isEle0A ? '#D0C067' : '#9fc5f3';\r\n            var _titleTxt = _isEle0A ? 'Espace publicitaire Pop-up' : 'Espace publicitaire';\r\n            var _fsRefBold = _isMob ? '8.5px' : '12.5px';\r\n            var _hdrHtml = '<div style=\"display:flex;align-items:center;background:' + _hdrBg + ';padding:4px 8px;box-sizing:border-box;font-family:Roboto,Arial,sans-serif;font-weight:700;color:#ffffff;position:relative;border-radius:4px 4px 0 0;\">'\r\n                + '<div style=\"flex:0 0 auto;z-index:1;\">'\r\n                +   (_posLib ? '<span style=\"font-size:' + _fsPos + ';color:#ffffff;\">' + _posLib + '<\/span>' : '')\r\n                + '<\/div>'\r\n                + '<div style=\"position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;pointer-events:none;\">'\r\n                +   '<span style=\"font-size:' + _fsTitle + ';color:#ffffff;\">' + _titleTxt\r\n                +   (_empl ? ' <span style=\"font-size:' + _fsRefBold + ';font-weight:700;color:#ffffff;\">' + _empl + '<\/span>' : '')\r\n                +   '<\/span>'\r\n                + '<\/div>'\r\n                + '<div style=\"flex:0 0 auto;margin-left:auto;z-index:1;\"><\/div>'\r\n                + '<\/div>';\r\n\r\n            var _bodyHtml = '<div style=\"display:flex;align-items:center;justify-content:center;padding:30px 12px;background:#fff;\">'\r\n                + '<span style=\"color:#00ff19;font-weight:700;font-size:' + (_isMob ? '14px' : '18px') + ';font-family:Roboto,Arial,sans-serif;\">'\r\n                + 'Loading in progress <span style=\"display:inline-block;\"><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><\/span>'\r\n                + '<\/span><\/div>';\r\n\r\n            \/\/ Masquer les \u00e9l\u00e9ments parasites SANS toucher aux dimensions\r\n            $_drp.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne').hide();\r\n            $_drp.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n\r\n            \/\/ Neutraliser les marges n\u00e9gatives Elementor pendant le loading\r\n            \/\/ Sauvegarder sur data() AVANT reset \u2192 styleUploadedAd retrouvera les vraies valeurs\r\n            var _drpOrigMt = parseInt($_drp.css('margin-top')) || 0;\r\n            var _drpOrigMb = parseInt($_drp.css('margin-bottom')) || 0;\r\n            if ($_drp.data('orig-mt') === undefined) { $_drp.data('orig-mt', _drpOrigMt); }\r\n            var $_drpOmc = $_drp.find('.OrdiMobileConteneurClass').first();\r\n            if ($_drpOmc.length) {\r\n                if ($_drpOmc.data('orig-mb') === undefined) { $_drpOmc.data('orig-mb', parseInt($_drpOmc.css('margin-bottom')) || 0); }\r\n            }\r\n            if (_drpOrigMt < 0) { $_drp.css('margin-top', '0px'); }\r\n            if (_drpOrigMb < 0) { $_drp.css('margin-bottom', '0px'); }\r\n\r\n            \/\/ Injecter dans $dropZone sans modifier sa hauteur (height:180px CSS natif)\r\n            \/\/ Forcer le dropZone \u00e0 stretcher son enfant (pas align-items:center)\r\n            $dropZone.css({ 'align-items': 'stretch', 'padding': '0' });\r\n            \/\/ Mobile : 50px de marge en dessous pour \u00e9viter chevauchement avec contenu suivant\r\n            \/\/ Poser sur plusieurs \u00e9l\u00e9ments avec !important + MutationObserver pour r\u00e9sister aux \u00e9crasements\r\n            if (_isMob) {\r\n                var _omcLi = $dropZone.closest('.OrdiMobileConteneurClass')[0];\r\n                if (_omcLi) {\r\n                    _omcLi.style.setProperty('margin-bottom', '50px', 'important');\r\n                    \/\/ Observer : si un autre script \u00e9crase, on restaure\r\n                    try {\r\n                        if (_omcLi._loadingMbObs) { _omcLi._loadingMbObs.disconnect(); }\r\n                        _omcLi._loadingMbObs = new MutationObserver(function(muts) {\r\n                            if (!_omcLi.querySelector('.via-loading-inline')) {\r\n                                \/\/ Loading termin\u00e9, arr\u00eat de l'observer\r\n                                _omcLi._loadingMbObs.disconnect();\r\n                                _omcLi._loadingMbObs = null;\r\n                                return;\r\n                            }\r\n                            var _cur = _omcLi.style.marginBottom;\r\n                            var _needRestore = true;\r\n                            if (_cur === '50px !important') { _needRestore = false; }\r\n                            if (_cur === '50px') { _needRestore = false; }\r\n                            if (_needRestore) {\r\n                                _omcLi.style.setProperty('margin-bottom', '50px', 'important');\r\n                            }\r\n                        });\r\n                        _omcLi._loadingMbObs.observe(_omcLi, { attributes: true, attributeFilter: ['style'] });\r\n                        \/\/ S\u00e9curit\u00e9 : stop au bout de 10 secondes\r\n                        setTimeout(function() {\r\n                            if (_omcLi._loadingMbObs) {\r\n                                _omcLi._loadingMbObs.disconnect();\r\n                                _omcLi._loadingMbObs = null;\r\n                            }\r\n                        }, 10000);\r\n                    } catch(_e) { console.warn('[loading mb observer]', _e); }\r\n                }\r\n            }\r\n            $dropZone.html('<div class=\"via-loading-inline\" style=\"'\r\n                + 'width:100%;height:100%;'\r\n                + 'border:2px solid #00FF19;border-radius:4px;'\r\n                + 'overflow:hidden;background:#fff;box-sizing:border-box;'\r\n                + 'display:flex;flex-direction:column;'\r\n                + (_isMob ? 'margin-top:40px;' : 'margin-top:40px;') + '\">'\r\n                + _hdrHtml\r\n                + '<div style=\"flex:1;display:flex;align-items:' + (_isMob ? 'flex-start' : 'center') + ';justify-content:center;padding-top:' + (_isMob ? '50px' : '0') + ';margin-top:' + (_isMob ? '0' : '-15px') + ';\">'\r\n                + '<span style=\"color:#00ff19;font-weight:700;font-size:' + (_isMob ? '14px' : '18px') + ';font-family:Roboto,Arial,sans-serif;\">'\r\n                + 'Loading in progress <span style=\"display:inline-block;\"><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><\/span>'\r\n                + '<\/span><\/div>'\r\n                + '<\/div>');\r\n        })();\r\n    },\r\n    \r\n    showFormatError($dropZone, message = 'Format de fichier incompatible') {\r\n        \/\/ \u2705 v2.4.6 : M\u00eame rendu overlay que le bloc jpeg \u2014 position absolute sur $dropZone\r\n        \/\/ \u2705 v4.9n  : timeout 5s, mobile r\u00e9duit \u00e0 50% via scale\r\n        var _errId = 'fmt-error-msg-' + Date.now();\r\n        jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n        var _isMob = UIManager.isMobile();\r\n        $dropZone.css('position', 'relative');\r\n        var _errHtml = '<div id=\"' + _errId + '\" style=\"'\r\n            + 'position:absolute;top:' + (_isMob ? '54px' : '50px') + ';left:50%;'\r\n            + (_isMob\r\n                ? 'transform:translateX(-50%) scale(0.604);transform-origin:top center;'\r\n                : 'transform:translateX(-50%);')\r\n            + 'width:auto;white-space:nowrap;'\r\n            + 'background:#fff;color:#FB5E2A;font-weight:700;font-size:' + (_isMob ? '13px' : '14px') + ';'\r\n            + 'text-align:center;padding:8px 10px;border-radius:6px;'\r\n            + 'border:2px solid #FB5E2A;box-sizing:border-box;'\r\n            + 'z-index:99999;line-height:1.4;'\r\n            + '\">' + message + '<\/div>';\r\n        $dropZone.append(_errHtml);\r\n        setTimeout(function() { jQuery('#' + _errId).remove(); }, 5000);\r\n    },\r\n    \r\n    updateAfterSuccessfulUpload($dropZone) {\r\n        jQuery('#errorMessageMobileText').hide();\r\n        \r\n        $dropZone.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 Pas de margin-top:150px si d\u00e9p\u00f4t depuis miniature kit (d\u00e9j\u00e0 positionn\u00e9)\r\n        if (!window._dropFromMiniature) {\r\n            $dropZone.closest('.OrdiMobileConteneurClass')\r\n                .css({'margin-top': '150px', 'margin-bottom': '0px'});\r\n        }\r\n    },\r\n    \r\n    finalizeAdDisplay($dropZone) {\r\n        StateManager.setMultiple({\r\n            \"FileReceived\": \"Yes\",\r\n            \"PageWebDisplayed\": \"Yes\"\r\n        });\r\n        \r\n        jQuery('#MsgChoixPageWeb, #MsgInsererAnnonceConteneur').hide();\r\n        jQuery('#ChoixEspacePublicitaire')\r\n            .html('L\\'annonce est plac\u00e9e dans un espace publicitaire')\r\n            .show()\r\n            .css({'color': '#56BE50'});\r\n        \r\n        jQuery('#MsgPlacerAnnoncePosition').hide();\r\n        jQuery('#FonctionMenu4').show();\r\n        jQuery('.AnnonceData').html(\"Transmis\").css({'color': '#56BE50'});\r\n        \r\n        jQuery('#ProcederPaiementConteneur').hide();\r\n        jQuery('#ProcederPaiementTitreIconeUp').hide();\r\n        jQuery('#ProcederPaiementTitreIconeDown').show();\r\n        \r\n        jQuery('#message').remove();\r\n        \r\n        this.styleUploadedAd($dropZone);\r\n        this.adjustLayoutAfterUpload($dropZone);\r\n        \/\/ \u2705 v2.7.3 : Masquer imm\u00e9diatement apr\u00e8s styleUploadedAd\/adjustLayout\r\n        \/\/ (ces 2 fonctions remontrent des \u00e9l\u00e9ments \u2192 les cacher avant _buildAdOverlay)\r\n        (function(_dz) {\r\n            var $_d = jQuery(_dz).closest('.droppable');\r\n            $_d.find('.DeplaceAnnonce, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .CroixResetAnnonceContainer, .via-ad-header, .via-position-label, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_d.find('.PositionEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })($dropZone[0] || $dropZone);\r\n        \/\/ \u2705 v2.7.3 : Injecter header + footer dans le liser\u00e9 vert\r\n        setTimeout(() => { this._buildAdOverlay($dropZone); }, 250);\r\n        \r\n        \/\/ \u2705 v4.9bf : Figer la largeur du wrapper Ele0A sur mobile site pays apr\u00e8s d\u00e9p\u00f4t\r\n        \/\/            (emp\u00eache le r\u00e9tr\u00e9cissement au scroll caus\u00e9 par width:fit-content)\r\n        \/\/            MutationObserver maintient la valeur en restaurant tout \u00e9crasement\r\n        setTimeout(function() {\r\n            var _droppableId = $dropZone.closest('.droppable').attr('id');\r\n            if (_droppableId !== 'Ele0A') return;\r\n            var _isMobSitePays = (window === window.top);\r\n            if (!_isMobSitePays) return;\r\n            var _isMobCheck = false;\r\n            if (navigator.maxTouchPoints > 0) { _isMobCheck = true; }\r\n            if (window.innerWidth < 1000) { _isMobCheck = true; }\r\n            if (window.outerWidth < 1000) { _isMobCheck = true; }\r\n            if (!_isMobCheck) return;\r\n            var $_wrapE0A = jQuery('.ToBeHidden').has('#Ele0A');\r\n            if (!$_wrapE0A.length) return;\r\n            var _wrapEl = $_wrapE0A[0];\r\n            var _w = _wrapEl.getBoundingClientRect().width;\r\n            if (_w < 50) return;\r\n            var _freezeW = Math.round(_w);\r\n            window._ele0AFrozenWidth = _freezeW;\r\n            \/\/ Applique les 3 propri\u00e9t\u00e9s de largeur\r\n            function _applyFrozenWidth() {\r\n                _wrapEl.style.setProperty('width',     _freezeW + 'px', 'important');\r\n                _wrapEl.style.setProperty('max-width', _freezeW + 'px', 'important');\r\n                _wrapEl.style.setProperty('min-width', _freezeW + 'px', 'important');\r\n            }\r\n            _applyFrozenWidth();\r\n            console.log('[v4.9bf] Ele0A wrapper largeur fig\u00e9e \u00e0 ' + _freezeW + 'px');\r\n            \/\/ Observer : si un autre script change la largeur, on restaure\r\n            try {\r\n                if (_wrapEl._e0AWidthObs) { _wrapEl._e0AWidthObs.disconnect(); }\r\n                _wrapEl._e0AWidthObs = new MutationObserver(function() {\r\n                    var _cur = _wrapEl.getBoundingClientRect().width;\r\n                    if (Math.abs(_cur - _freezeW) > 2) {\r\n                        _applyFrozenWidth();\r\n                    }\r\n                });\r\n                _wrapEl._e0AWidthObs.observe(_wrapEl, { attributes: true, attributeFilter: ['style'] });\r\n                \/\/ Observer aussi #Ele0A (si c'est lui qui change la largeur)\r\n                var _e0AInner = _wrapEl.querySelector('#Ele0A');\r\n                if (_e0AInner) {\r\n                    if (_e0AInner._e0AInnerObs) { _e0AInner._e0AInnerObs.disconnect(); }\r\n                    _e0AInner._e0AInnerObs = new MutationObserver(function() {\r\n                        var _cur = _wrapEl.getBoundingClientRect().width;\r\n                        if (Math.abs(_cur - _freezeW) > 2) {\r\n                            _applyFrozenWidth();\r\n                        }\r\n                    });\r\n                    _e0AInner._e0AInnerObs.observe(_e0AInner, { attributes: true, attributeFilter: ['style'] });\r\n                }\r\n            } catch (_e) { console.warn('[v4.9bf obs]', _e); }\r\n            \/\/ R\u00e9appliquer aussi \u00e0 chaque scroll (backup)\r\n            window.addEventListener('scroll', _applyFrozenWidth, { passive: true });\r\n            \/\/ v4.9bo : badge debug retir\u00e9 (sujet 2 en pause)\r\n        }, 200);\r\n        \r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox \"R\u00e9server\" apr\u00e8s upload\r\n        FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n        \/\/ \u2705 Masquer le bandeau bleu format lors d'une restauration (updateReserverCheckboxState le remet sinon)\r\n        if (StateManager.get('_isAdRestoration') === 'Yes') {\r\n            $dropZone.closest('.droppable').find('.SelectionFormatTitreBlanc').hide();\r\n        }\r\n    },\r\n    \r\n    styleUploadedAd($dropZone) {\r\n        const $container = $dropZone.closest('.OrdiMobileConteneurClass');\r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \r\n        \/\/ \u2705 v1.19.5 : V\u00e9rifier si c'est une restauration via flag d\u00e9di\u00e9\r\n        const isRestoration = StateManager.get('_isAdRestoration') === 'Yes';\r\n        \r\n        $container\r\n            .find('.PositionReference, .TexteMobile, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        $container\r\n            .find('.AdUploadedTitle, .DimensionsMaximales')\r\n            .show();\r\n        \r\n        \/\/ \u2705 Bug 2 fix : montrer la croix (positionnement gere par Pb3 Ele0A)\r\n        $droppable.find('.CroixResetAnnonceContainer').show();\r\n        $droppable.find('#CroixResetAnnonce').show();\r\n        \r\n        \/\/ \u2705 v2.2 : Marquer le droppable comme occup\u00e9 pour qu'Entete le skip\r\n        $droppable.attr('data-via-ad-loaded', 'true');\r\n        \r\n        \/\/ \u2705 v2.0.11 : AdUploadedTitle ne doit pas bloquer le touch sur doc-preview-readmore (mobile)\r\n        $container.find('.AdUploadedTitle').css('pointer-events', 'none');\r\n        \r\n        \/\/ \u2705 #CroixResetAnnonce au-dessus de l'image dans son espace pub\r\n        \/\/ v4.9ds : z-index abaiss\u00e9 \u00e0 5 pour que la pastille jaune .popupAchatAnnonce\r\n        \/\/   passe au-dessus.\r\n        \/\/   Diagnostic stacking context (cf user mobile) :\r\n        \/\/     - Pastille est enfant de EnteteBackground (z-index:6, stacking ctx isol\u00e9)\r\n        \/\/       \u2192 son z-index est PLAFONN\u00c9 \u00e0 6 au niveau body, peu importe la valeur\r\n        \/\/     - Croix vit au niveau body (ses anc\u00eatres jusqu'\u00e0 OrdiMobileConteneurClass\r\n        \/\/       n'ont pas de z-index)\r\n        \/\/   Comp\u00e9tition au niveau body : header z:6 vs croix z:N\r\n        \/\/     \u2192 croix doit avoir z < 6 pour passer sous le header (donc sous la pastille)\r\n        \/\/     \u2192 croix doit avoir z > 3 pour rester au-dessus de l'image (cf z-index:3\r\n        \/\/       sur AdUploadedTitle \/ OrdiMobileConteneurClass parents)\r\n        \/\/   Valeur 5 = compromis : sous header (donc sous pastille) + sur image dans son\r\n        \/\/     droppable.\r\n        $container.find('#CroixResetAnnonce').css({\r\n            'position': 'relative',\r\n            'z-index': '5'\r\n        });\r\n        \r\n        \/\/ \u2705 Desktop : les conteneurs superpos\u00e9s bloquent la croix \u2014 les rendre transparents aux clics\r\n        if (!UIManager.isMobile()) {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css('pointer-events', 'none');\r\n            \/\/ R\u00e9activer les \u00e9l\u00e9ments interactifs\r\n            $container.find('#CroixResetAnnonce').css('pointer-events', 'auto');\r\n            $dropZone.closest('#PopUpMessageAchattest').css('pointer-events', 'auto'); \/\/ drag annonce\r\n        }\r\n        \r\n        \/\/ \u2705 v2.4.3 : Sur mobile, masquer titre et logo drag quand annonce d\u00e9pos\u00e9e dans Ele0A\r\n        if (UIManager.isMobile()) {\r\n            if ($droppable.attr('id') === 'Ele0A') {\r\n                $droppable.find('#ChoixEspacePublicitaireTexte').hide();\r\n                $droppable.find('#Ele0ADragHandle').hide();\r\n            }\r\n            \/\/ v2.9 : forcer pointer-events:auto sur la croix pour tous les espaces (Ele0A + standards)\r\n            var _crcEl = $droppable.find('.CroixResetAnnonceContainer')[0];\r\n            if (_crcEl) { _crcEl.style.setProperty('pointer-events', 'auto', 'important'); }\r\n            var _crEl = $droppable.find('#CroixResetAnnonce')[0];\r\n            if (_crEl) { _crEl.style.setProperty('pointer-events', 'auto', 'important'); }\r\n        }\r\n\r\n        \/\/ v2.6 : Desktop Ele0A - masquer icones drag\/close + elements gris parasites\r\n        if (!UIManager.isMobile()) { if ($droppable.attr('id') === 'Ele0A') {\r\n            $droppable.find('#Ele0ADragHandle').hide();\r\n            $droppable.find('#Ele0ACloseBtn').hide();\r\n            \/\/ v2.6 : Quand annonce chargee dans Ele0A : cacher titre+boutons format (zone grise parasite)\r\n            $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $droppable.find('.EspPubFormatMainContainer').hide();\r\n            $droppable.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n            \/\/ v2.9 : Ele0A desktop - masquer elements parasites (avec ou sans iframe pays)\r\n            $droppable.find('.DeplaceAnnonceText').hide();\r\n            console.log('[styleUploadedAd Ele0A] zone grise masquee | UFC bg -> transparent');\r\n            \/\/ v2.9 : iframe pays (AchatEspaceCall=Yes) -> masquer elements supplementaires\r\n            if (StateManager.get('AchatEspaceCall') === 'Yes') {\r\n                $droppable.find('.PositionEspacePublicitaireDeplacer').hide();\r\n                $droppable.find('.RefEspacePublicitaire').hide();\r\n                console.log('[styleUploadedAd Ele0A] iframe pays: PositionEspacePublicitaireDeplacer masque');\r\n            }\r\n        } }\r\n        \r\n        \/\/ \u2705 v2.6 : Reset uniquement les UFC sans annonce d\u00e9pos\u00e9e (data-via-ad-loaded absent)\r\n        \/\/ Les UFC des espaces d\u00e9j\u00e0 remplis (background:white) ne doivent pas \u00eatre remis \u00e0 transparent\r\n        jQuery(\".HTMLUploadfileConteneur\").each(function() {\r\n            var _droppableEl = jQuery(this).closest('.droppable')[0]\r\n                || jQuery(this).find('#drop_file_zone_achat').closest('.droppable')[0];\r\n            var _hasAd = false;\r\n            if (_droppableEl) {\r\n                if (_droppableEl.getAttribute('data-via-ad-loaded') === 'true') {\r\n                    _hasAd = true;\r\n                }\r\n            }\r\n            if (!_hasAd) {\r\n                jQuery(this).css({ 'border': 'none', 'background-color': 'transparent' });\r\n            }\r\n        });\r\n        \r\n        \/\/ \u2705 v2.4.12 : Retirer le liser\u00e9 envoi diff\u00e9r\u00e9 (#UploadFileConteneur) si pr\u00e9sent\r\n        \/\/ (le d\u00e9p\u00f4t d'une annonce cr\u00e9e son propre liser\u00e9 sur .HTMLUploadfileConteneur)\r\n        var $_ufcEd = $dropZone.closest('.droppable').find('#UploadFileConteneur');\r\n        if ($_ufcEd[0]) {\r\n            $_ufcEd[0].style.removeProperty('box-shadow');\r\n            $_ufcEd[0].style.removeProperty('box-sizing');\r\n            if (UIManager.isMobile()) {\r\n                $_ufcEd[0].style.removeProperty('transform');\r\n                $_ufcEd[0].style.removeProperty('transform-origin');\r\n            }\r\n        }\r\n        \r\n        $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n            \/\/ \u2705 v2.7.3 : box-shadow supprim\u00e9 \u2014 le liser\u00e9 est port\u00e9 par .via-ad-wrapper (outline)\r\n            'box-shadow': 'none',\r\n            'background-color': 'white',\r\n            'box-sizing': 'border-box',\r\n            'overflow': 'hidden'\r\n        });\r\n        \/\/ \u2705 v2.7.3 : Mobile sites pays \u2014 afficher la position en haut \u00e0 gauche dans le liser\u00e9 vert\r\n        if (UIManager.isMobile() ? window === window.top : false) {\r\n            var _rankForPos = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            var _posLabel = PreviewRenderer._getPositionLibelle(_rankForPos);\r\n            if (_posLabel) {\r\n                var $_ufcPos = $dropZone.closest('.HTMLUploadfileConteneur');\r\n                $_ufcPos.css('position', 'relative');\r\n                $_ufcPos.find('.via-position-label').remove();\r\n                $_ufcPos.append('<div class=\"via-position-label\" style=\"position:absolute;top:3px;left:3px;color:#213864;font-size:10px;font-weight:700;font-family:Roboto,sans-serif;padding:2px 7px;z-index:999;pointer-events:none;line-height:1.5;\">' + _posLabel + '<\/div>');\r\n            }\r\n        }\r\n        \r\n        \/\/ \u2705 v2.0.9 : Fix mobile \u2014 r\u00e9duire le scale pour que le liser\u00e9 vert soit visible\r\n        \/\/ \u2705 Pas de scale si annonce d\u00e9pos\u00e9e depuis la miniature kit (d\u00e9j\u00e0 aux bonnes dimensions)\r\n        \/\/ \u2705 v2.4.9 : Pas de scale si AchatEspaceCall=Yes (drop depuis miniature dans iframe)\r\n        \/\/ \u2705 data-kit-drop : marqueur DOM pos\u00e9 par kitAdCreated, r\u00e9siste \u00e0 l'async\r\n        var _fromKitDrop = $droppable[0] ? $droppable[0].getAttribute('data-kit-drop') === 'true' : false;\r\n        if (_fromKitDrop) { window._dropFromMiniature = true; } \/\/ sync le flag window\r\n        var _fromMiniature = window._dropFromMiniature || _fromKitDrop;\r\n        var _fromAchat = StateManager.get('AchatEspaceCall') === 'Yes';\r\n        if (UIManager.isMobile() ? (!_fromMiniature ? !_fromAchat : false) : false) {\r\n            \/\/ \u2705 v2.7.3 : Plus de scale (cause des liser\u00e9s parasites) \u2014 le wrapper overlay g\u00e8re l'apparence\r\n            $dropZone.closest('.UploadFileConteneur').css({\r\n                'transform': 'none',\r\n                'transform-origin': ''\r\n            });\r\n        }\r\n        \r\n        \/\/ \u2705 v2.2 : Sur restauration, Entete a d\u00e9j\u00e0 positionn\u00e9 le droppable correctement\r\n        \/\/ et est emp\u00each\u00e9 de le retoucher (data-via-ad-loaded). On ne touche pas aux marges.\r\n        \/\/ Sur premier upload, on applique les marges normalement.\r\n        \/\/ \u2705 Pas de marges si d\u00e9p\u00f4t depuis miniature kit (dimensions naturelles conserv\u00e9es)\r\n        if (!isRestoration ? !_fromMiniature : false) {\r\n            if ($container.data('orig-mb') === undefined) {\r\n                $container.data('orig-mb', parseInt($container.css('margin-bottom')) || 0);\r\n            }\r\n            if ($droppable.data('orig-mt') === undefined) {\r\n                $droppable.data('orig-mt', parseInt($droppable.css('margin-top')) || 0);\r\n            }\r\n            var _isDocPreview = $dropZone.find('.doc-preview-container').length > 0;\r\n            \/\/ \u2705 Sites pays (window.top) : marges r\u00e9duites (iframe offset non n\u00e9cessaire)\r\n            var _isSitesPays = (window === window.top);\r\n            var _marginAdd    = _isSitesPays ? 0 : (_isDocPreview ? 60 : 130);\r\n            var _marginReduce = _isSitesPays ? 0 : (_isDocPreview ? 60 : 130);\r\n            var _marginTopAdj = _isSitesPays ? 0 : 75;\r\n            $container.css({\r\n                'margin-bottom': ($container.data('orig-mb') + _marginAdd) + 'px'\r\n            });\r\n            $droppable.css({\r\n                'margin-top': ($droppable.data('orig-mt') - _marginReduce + _marginTopAdj) + 'px'\r\n            });\r\n        } else {\r\n            \/\/ \u2705 Sur restauration, ajouter 50px en dessous pour \u00e9viter que le contenu soit trop coll\u00e9\r\n            var _curMb = parseInt($container.css('margin-bottom')) || 0;\r\n            $container.css('margin-bottom', (_curMb + 50) + 'px');\r\n        }\r\n        \r\n        \/\/ \u2705 Masquer texte glisser-d\u00e9poser r\u00e9siduel + position\/r\u00e9server\r\n        $droppable.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne').hide();\r\n        \/\/ \u2705 Masquer .PositionEspacePublicitaireContainer et l'ancien .ReserverContainer\r\n        $droppable.find('.PositionEspacePublicitaireContainer').hide();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \/\/ \u2705 Masquer le checkbox statique Elementor (au cas o\u00f9 .ReserverContainer ne l'englobe pas)\r\n        $droppable.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').hide();\r\n        \/\/ \u2705 Masquer la zone bleue format (SelectionFormatTitreBlanc) et le champ lien hypertexte\r\n        $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n        $droppable.find('.EspPubLienAnnonce').hide();\r\n        \r\n        \/\/ \u2705 Supprimer tout ancien bouton dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 Sur restauration desktop : masquer bandeau bleu + fond #UploadFileConteneur\r\n        \/\/ (ChoisirEspacePublicitaireDisponibiliteConteneur et EspPubFormatMainContainer sont\r\n        \/\/  g\u00e9r\u00e9s par updateAfterSuccessfulUpload + adjustDesktopLayout du flux standard,\r\n        \/\/  mais le background #9FC5F3 du parent #UploadFileConteneur reste \u00e0 neutraliser)\r\n        if (isRestoration) {\r\n            if (!UIManager.isMobile()) {\r\n                $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n                $droppable.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v2.1.2 : Cr\u00e9er le bouton R\u00e9server m\u00eame lors d'une restauration\r\n        if (isRestoration) {\r\n            console.log('\ud83d\udd04 Restauration d\u00e9tect\u00e9e - affichage bouton R\u00e9server + DeplaceAnnonce');\r\n            $droppable.find('.DeplaceAnnonce').show();\r\n            \/\/ S'assurer que FileReceived est d\u00e9fini pour la validation\r\n            StateManager.set('FileReceived', 'Yes');\r\n            \/\/ Cocher automatiquement SEULEMENT si c'est une restauration de commande r\u00e9serv\u00e9e (pas pendingAd)\r\n            var _isPendingAdRestore = sessionStorage.getItem('_pendingAdRestoration') === 'Yes';\r\n            if (!_isPendingAdRestore) {\r\n                var _isReservedRestore = sessionStorage.getItem('_isReservedRestoration') === 'Yes';\r\n                setTimeout(function() {\r\n                    var $checkbox = $droppable.find('.reserver-dynamic-checkbox');\r\n                    if ($checkbox.length ? _isReservedRestore : false) {\r\n                        $checkbox.prop('checked', true);\r\n                        $droppable.find('.reserver-dynamic-label').text('Espace publicitaire r\u00e9serv\u00e9').css('color', 'rgb(62, 170, 19)');\r\n                        console.log('\u2705 R\u00e9server coch\u00e9 automatiquement (restauration commande r\u00e9serv\u00e9e)');\r\n                    }\r\n                    sessionStorage.removeItem('_isReservedRestoration');\r\n                    var _wasRestorationA = true; \/\/ \u00e9tait une restauration\r\n                    StateManager.set('_isAdRestoration', 'No');\r\n                    \/\/ \u2705 v2.4.7 : updateReserverCheckboxState APR\u00c8S avoir coch\u00e9 la checkbox\r\n                    \/\/ (l'appel synchrone ligne 614 intervient avant le cocher \u2192 label incorrect)\r\n                    if (typeof FormatUIManager !== 'undefined') {\r\n                        FormatUIManager.updateReserverCheckboxState($droppable);\r\n                        \/\/ \u2705 Masquer bandeau bleu format si restauration (updateReserverCheckboxState le remet)\r\n                        if (_wasRestorationA) { $droppable.find('.SelectionFormatTitreBlanc').hide(); }\r\n                    }\r\n                }, 150);\r\n            } else {\r\n                \/\/ pendingAd : ne pas cocher, ne pas envoyer au parent\r\n                console.log('\u2705 pendingAd restaur\u00e9 \u2014 R\u00e9server NON coch\u00e9');\r\n                sessionStorage.removeItem('_pendingAdRestoration');\r\n                StateManager.set('_isAdRestoration', 'No');\r\n            }\r\n        }\r\n        \r\n        \/\/ \u2705 Supprimer tout bouton R\u00e9server existant avant insertion (\u00e9vite doublons sur restauration)\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 Cr\u00e9er le bouton \"R\u00e9server\" dynamique avec id unique pour \u00e9viter conflit label\/for Elementor\r\n        const _rankId = $droppable.attr('id') || ('drop_' + Date.now());\r\n        const _cbId = 'reserver-cb-' + _rankId;\r\n        const reserverHTML = `\r\n            <div class=\"reserver-dynamic-container\">\r\n                <span class=\"reserver-dynamic-option\">\r\n                    <input type=\"checkbox\" id=\"${_cbId}\" value=\"R\u00e9server cet espace publicitaire\" \r\n                           class=\"reserver-dynamic-checkbox\"\r\n                           name=\"form_fields[ReserverEspacePublicitaire]\">\r\n                    <span class=\"reserver-dynamic-label\">R\u00e9server cet espace publicitaire<\/span>\r\n                <\/span>\r\n            <\/div>\r\n        `;\r\n        \r\n        \/\/ \u2705 Ins\u00e9rer juste avant .DeplaceAnnonceText (position stable)\r\n        const $anchor = $droppable.find('.DeplaceAnnonceText');\r\n        if ($anchor.length) {\r\n            $anchor.before(reserverHTML);\r\n            $anchor.prev('.reserver-dynamic-container').css('margin-bottom', '-40px');\r\n            if (!UIManager.isMobile()) {\r\n                $anchor.prev('.reserver-dynamic-container').find('.reserver-dynamic-label').css({'font-size': '20px', 'font-weight': '700'});\r\n            }\r\n        } else {\r\n            $droppable.after(reserverHTML);\r\n            const $btn = $droppable.next('.reserver-dynamic-container');\r\n            $btn.css('margin-top', UIManager.isMobile() ? '-10px' : '-140px');\r\n        }\r\n        \r\n        console.log('\u2705 Bouton \"R\u00e9server\" dynamique cr\u00e9\u00e9');\r\n    },\r\n    \r\n    \/\/ =========================================================================\r\n    \/\/ \u2705 v2.7.3 : Injection header \/ footer dans le liser\u00e9 vert apr\u00e8s d\u00e9p\u00f4t d'annonce\r\n    \/\/ Structure : .HTMLUploadfileConteneur \u2192 header (.via-ad-header) + body (existant) + footer (.via-ad-footer)\r\n    \/\/ =========================================================================\r\n    _buildAdOverlay($dropZone) {\r\n        const $droppable  = $dropZone.closest('.droppable');\r\n        const $ufc        = $droppable.find('.HTMLUploadfileConteneur').first();\r\n        if (!$ufc.length) return;\r\n\r\n        \/\/ \u2705 Retirer le loading overlay (fixed sur body) + reset styles\r\n        jQuery('.via-loading-overlay-fixed').remove();\r\n        $ufc.find('.via-loading-overlay').remove();\r\n        \/\/ Reset styles pos\u00e9s pendant le loading (UFC + droppable)\r\n        $ufc.css({ 'position': '', 'min-height': '', 'height': '', 'overflow': '' });\r\n        $droppable.css({ 'min-height': '', 'height': '', 'overflow': '' });\r\n        var $_omcReset = $droppable.find('.OrdiMobileConteneurClass').first();\r\n        if ($_omcReset.length) { $_omcReset.css('min-height', ''); }\r\n        $droppable.css('min-height', '');\r\n\r\n        \/\/ Idempotent : ne pas re-injecter si d\u00e9j\u00e0 pr\u00e9sent\r\n        if ($ufc.find('.via-ad-header').length) return;\r\n\r\n        const _isEle0A   = $droppable.attr('id') === 'Ele0A';\r\n        const _isMobile  = UIManager.isMobile();\r\n        const _isDesktop = !_isMobile;\r\n        \/\/ \u2705 v4.9ds Fix 14 (Pb 10) : utiliser l'emplacement du droppable courant, pas\r\n        \/\/   l'emplacement global StateManager. La variable globale refl\u00e8te le DERNIER\r\n        \/\/   emplacement cliqu\u00e9 (par ex L1A si l'user clique sur Banni\u00e8re dans Ele1A\r\n        \/\/   pendant un upload sur Ele0A) \u2014 alors que le header de l'annonce d\u00e9pos\u00e9e\r\n        \/\/   dans Ele0A doit afficher MDG48442L0A. La m\u00e9thode StateManager.buildEmplacementReference\r\n        \/\/   est exactement celle utilis\u00e9e ligne 441-442 pour construire la r\u00e9f\u00e9rence\r\n        \/\/   \u00e0 partir du rankId. Fallback sur l'emplacement global si la m\u00e9thode\r\n        \/\/   n'existe pas (s\u00e9curit\u00e9).\r\n        const _rankId    = $droppable.attr('id') || '';\r\n        var _emplacement = '';\r\n        if (_rankId ? typeof StateManager.buildEmplacementReference === 'function' : false) {\r\n            _emplacement = StateManager.buildEmplacementReference(_rankId) || '';\r\n        }\r\n        if (!_emplacement) {\r\n            _emplacement = StateManager.get('Commande_Emplacement_Page_Web') || '';\r\n        }\r\n        console.log('[Fix 14] _rankId:', _rankId, '| _emplacement r\u00e9solu:', _emplacement, '| global:', StateManager.get('Commande_Emplacement_Page_Web'));\r\n        const _posLib    = PreviewRenderer._getPositionLibelle(_rankId) || '';\r\n\r\n        \/\/ \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ v4.9ds : Ele0A header +3px (padding 4px\u21925.5px top\/bottom = +3px total)\r\n        const _hdrPadV = _isEle0A ? '5.5px' : '4px';\r\n        const $header = jQuery('<div class=\"via-ad-header\" style=\"' +\r\n            'display:flex;align-items:center;' +\r\n            'background:' + (_isEle0A ? '#D0C067' : '#9FC5F3') + ';padding:' + _hdrPadV + ' 8px;box-sizing:border-box;' +\r\n            'font-family:Roboto,Arial,sans-serif;' +\r\n            'font-size:11px;color:#ffffff;font-weight:700;flex-shrink:0;' +\r\n            'position:relative;' +\r\n        '\"><\/div>');\r\n\r\n        \/\/ \u2500\u2500 Structure header 3 colonnes : [drag] [titre centr\u00e9] [ref + X] \u2500\u2500\u2500\u2500\u2500\r\n        const $colLeft   = jQuery('<div style=\"flex:0 0 auto;display:flex;align-items:center;z-index:1;\"><\/div>');\r\n        const $colCenter = jQuery('<div style=\"position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;pointer-events:none;\"><\/div>');\r\n        const $colRight  = jQuery('<div style=\"flex:0 0 auto;display:flex;align-items:center;gap:8px;margin-left:auto;z-index:1;\"><\/div>');\r\n\r\n        \/\/ Tailles : plein \u00e9cran desktop \/ desktop mode mobile \/ mobile\r\n        var _isFullDesk = !_isMobile ? window.outerWidth >= 1200 : false;\r\n        var _isDeskMob  = !_isMobile ? window.outerWidth < 1200 : false; \/\/ desktop r\u00e9tr\u00e9ci\r\n        var _fsPos   = _isFullDesk ? '13px' : (_isDeskMob ? '6px' : '8px');\r\n        var _fsTitle = _isFullDesk ? '16px' : (_isDeskMob ? (_isEle0A ? '6px' : '8px') : (_isEle0A ? '8px' : '10px'));\r\n        var _fsRef   = _isFullDesk ? '13px' : (_isDeskMob ? '6px' : (_isEle0A ? '6px' : '8px'));\r\n\r\n        if (_isEle0A) {\r\n            \/\/ \u2705 Croix drag 4 fl\u00e8ches SVG \u2014 v4.9ds taille r\u00e9duite (17\u219213) pour aligner sur header standard\r\n            $colLeft.append(jQuery('<span class=\"via-ah-drag\" style=\"cursor:move;opacity:0.8;display:flex;align-items:center;\">' +\r\n                '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"#ffffff\">' +\r\n                '<path d=\"M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z\"\/>' +\r\n                '<\/svg><\/span>'));\r\n            \/\/ \u2705 via-ah-pos masqu\u00e9 pour Ele0A (position dans le titre suffit)\r\n            \/\/ \u2705 Bug 11 : sur mobile, limiter la largeur pour \u00e9viter chevauchement titre\/r\u00e9f\r\n            \/\/ pas de troncature \u2014 la r\u00e9duction de taille suffit\r\n            $colCenter.append(jQuery('<span class=\"via-ah-title\" style=\"font-size:' + _fsTitle + ';color:#ffffff;\">Espace publicitaire Pop-up' + (_emplacement ? ' <span style=\"font-weight:700;font-size:' + (parseFloat(_fsRef) + 1.5) + 'px;\">' + _emplacement + '<\/span>' : '') + '<\/span>'));\r\n        } else {\r\n            if (_posLib) {\r\n                $colLeft.append(jQuery('<span class=\"via-ah-pos\" style=\"font-size:' + _fsPos + ';color:#ffffff;font-weight:700;\">' + _posLib + '<\/span>'));\r\n            }\r\n            $colCenter.append(jQuery('<span class=\"via-ah-title\" style=\"font-size:' + _fsTitle + ';color:#ffffff;\">Espace publicitaire' + (_emplacement ? ' <span style=\"font-weight:700;font-size:' + (parseFloat(_fsRef) + 1.5) + 'px;\">' + _emplacement + '<\/span>' : '') + '<\/span>'));\r\n        }\r\n\r\n        \/\/ Ref incluse dans le titre ci-dessus\r\n\r\n        \/\/ v4.9ds : croix de fermeture positionn\u00e9e en absolute dans le coin haut-droite\r\n        \/\/   de .HTMLUploadfileConteneur (la zone d'aper\u00e7u blanche), \u00e0 2px du liser\u00e9 vert.\r\n        \/\/   Carr\u00e9 bleu fonc\u00e9 (#5573a8) avec croix blanche centr\u00e9e pour qu'elle se voie\r\n        \/\/   sur les images d'annonce d\u00e9pos\u00e9es (qui peuvent \u00eatre de toute couleur).\r\n        const $closeBtn = jQuery('<span class=\"via-ah-close\" style=\"' +\r\n            'cursor:pointer;font-size:13px;font-weight:900;color:#ffffff;line-height:1;' +\r\n            'pointer-events:auto;' +\r\n            'position:absolute;top:2px;right:4px;' +\r\n            'width:18px;height:18px;' +\r\n            'background:#5573a8;' +\r\n            'display:flex;align-items:center;justify-content:center;' +\r\n            'z-index:5;' +\r\n            '\" title=\"Supprimer l\\'annonce\">\u2715<\/span>');\r\n        $closeBtn.on('click', function(e) {\r\n            e.preventDefault();\r\n            e.stopPropagation();\r\n            \/\/ \u2705 v4.9ds Fix 28 : idem .via-erase-btn (ligne ~7750) \u2014 #CroixResetAnnonce\r\n            \/\/   n'existe plus dans le DOM. Appel direct \u00e0 AdResetHandler.handle.\r\n            if (typeof AdResetHandler !== 'undefined' ? typeof AdResetHandler.handle === 'function' : false) {\r\n                AdResetHandler.handle(e);\r\n                return;\r\n            }\r\n            \/\/ Fallback historique (FonctionCroixResetAnnonce \/ trigger sur #CroixResetAnnonce)\r\n            if (typeof window.FonctionCroixResetAnnonce === 'function') {\r\n                var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n                if (_croixEl) { window.FonctionCroixResetAnnonce(_croixEl); return; }\r\n            }\r\n            var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n            if (_croixEl) { jQuery(_croixEl).trigger('click'); }\r\n        });\r\n        \/\/ $colRight reste vide \u2014 la croix est pos\u00e9e en absolute sur $wrapper plus bas\r\n        \/\/   (juste apr\u00e8s son assemblage, pour rester ancr\u00e9e au liser\u00e9 vert).\r\n\r\n        $header.append($colLeft).append($colCenter).append($colRight);\r\n\r\n        \/\/ \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        const $footer = jQuery('<div class=\"via-ad-footer\" style=\"' +\r\n            'background:' + (_isEle0A ? '#D0C067' : '#9FC5F3') + ';padding:4px 8px;box-sizing:border-box;' +  \/* v4.9ds : jaune Ele0A, bleu Ele1A+ *\/\r\n            'font-family:Roboto,Arial,sans-serif;' +\r\n            'font-size:11px;color:#ffffff;flex-shrink:0;' +\r\n        '\"><\/div>');\r\n\r\n        \/\/ v4.9ds : footer en flex pour aligner \u0153il + R\u00e9server\r\n        \/\/ v4.9ds (eye-pos) : position:relative pour ancrer l'\u0153il en absolute (centr\u00e9 dans la moiti\u00e9 gauche),\r\n        \/\/   le bouton R\u00e9server reste seul dans le flux flex et donc centr\u00e9 dans le footer\r\n        $footer.css({\r\n            'display': 'flex',\r\n            'align-items': 'center',\r\n            'justify-content': 'center',\r\n            'gap': '10px',\r\n            'position': 'relative'\r\n        });\r\n\r\n        \/\/ v4.9ds : si Communiqu\u00e9\/Interview (doc-preview pr\u00e9sent), masquer title + readmore\r\n        \/\/   et ajouter un bouton \u0153il dans le footer (\u00e0 gauche de R\u00e9server) qui ouvre la\r\n        \/\/   m\u00eame popup que le readmore (PDFHandler.showInlineDocPopup)\r\n        const _hasDocPreview = $droppable.find('.doc-preview-container').length > 0;\r\n        if (_hasDocPreview) {\r\n            $droppable.find('.doc-preview-title').hide();\r\n            $droppable.find('.doc-preview-readmore').hide();\r\n            \/\/ \u0152il SVG (Material icon \"visibility\") \u2014 ouvre la popup au clic\r\n            \/\/ v4.9ds (eye-size+pos) : taille +60% (18\u219229 desk, 14\u219222 mob) ;\r\n            \/\/   position:absolute \u00e0 left:12% pour \u00eatre centr\u00e9 entre le bord gauche du footer\r\n            \/\/   et le bouton R\u00e9server (qui, seul dans le flux flex, est centr\u00e9 au milieu du footer).\r\n            \/\/   z-index:2 pour passer au-dessus de tout. pointer-events:auto pour rester cliquable.\r\n            \/\/ v4.9ds (eye-stroke) : liser\u00e9 bleu Material (#1976D2) sur l'amande ext\u00e9rieure ET sur\r\n            \/\/   le rond int\u00e9rieur (iris) pour qu'il ressorte sur les 2 couleurs de footer.\r\n            \/\/   3 paths : path 1 = \u0153il entier rempli blanc (inchang\u00e9) ; path 2 = contour amande\r\n            \/\/   ext\u00e9rieure ; path 3 = contour iris (rond int\u00e9rieur). Tous deux fill:none + stroke,\r\n            \/\/   m\u00eame stroke-width pour coh\u00e9rence visuelle. stroke-linejoin:round pour adoucir.\r\n            const _eyeColor = '#ffffff';\r\n            const $eyeBtn = jQuery('<span class=\"via-af-eye\" title=\"Voir la publication\" style=\"cursor:pointer;display:inline-flex;align-items:center;flex:0 0 auto;position:absolute;left:12%;top:50%;transform:translate(-50%,-50%);z-index:2;pointer-events:auto;\">' +\r\n                '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"' + (_isFullDesk ? '29' : '22') + '\" height=\"' + (_isFullDesk ? '29' : '22') + '\" viewBox=\"0 0 24 24\">' +\r\n                '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\" fill=\"' + _eyeColor + '\"\/>' +\r\n                '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5z\" fill=\"none\" stroke=\"#1976D2\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>' +\r\n                '<path d=\"M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z\" fill=\"none\" stroke=\"#1976D2\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>' +\r\n                '<\/svg><\/span>');\r\n            \/\/ \u2500\u2500 \u2193 click handler (inchang\u00e9) \u2193 \u2500\u2500\r\n            $eyeBtn.on('click', function(e) {\r\n                e.preventDefault();\r\n                e.stopPropagation();\r\n                \/\/ Trouver la popup \u00e0 ouvrir : m\u00eame logique que le clic sur .doc-preview-readmore\r\n                \/\/ Priorit\u00e9 1 : kitPdfImageDataURL (cr\u00e9\u00e9 via Kit miniature)\r\n                var _kitPdf = $droppable.data('kitPdfImageDataURL');\r\n                var _kitFormat = $droppable.data('kitFormatSelect') || '';\r\n                var _isInterviewE = (_kitFormat || '').toLowerCase().indexOf('interview') !== -1;\r\n                if (!_kitFormat) {\r\n                    \/\/ Fallback : chercher le titre dans le DOM\r\n                    var _titleEl = $droppable.find('.doc-preview-title')[0];\r\n                    if (_titleEl) { _isInterviewE = (_titleEl.textContent || '').toLowerCase().indexOf('interview') !== -1; }\r\n                }\r\n                var _popupTitle = _isInterviewE ? 'Interview' : 'Communiqu\u00e9';\r\n                if (_kitPdf) {\r\n                    PDFHandler.showInlineDocPopup($dropZone, { formatTitle: _popupTitle, pdfDataURL: _kitPdf });\r\n                    return;\r\n                }\r\n                \/\/ Priorit\u00e9 2 : PDF upload\u00e9 (PDFHandler.pdfDataForViewer)\r\n                if (PDFHandler.pdfDataForViewer ? PDFHandler.pdfDataForViewer.byteLength > 0 : false) {\r\n                    PDFHandler.showInlineDocPopup($dropZone, { formatTitle: _popupTitle, pdfArrayBuffer: PDFHandler.pdfDataForViewer });\r\n                    return;\r\n                }\r\n                \/\/ Priorit\u00e9 3 : d\u00e9clencher le clic sur readmore (m\u00eame si masqu\u00e9) \u2014 fallback Word\/htmlContent\r\n                var _readmore = $droppable.find('.doc-preview-readmore')[0];\r\n                if (_readmore) {\r\n                    _readmore.style.display = 'block';\r\n                    jQuery(_readmore).trigger('click');\r\n                    _readmore.style.display = 'none';\r\n                }\r\n            });\r\n            $footer.append($eyeBtn);\r\n        }\r\n\r\n        \/\/ \u2500\u2500 D\u00e9placer le reserver-dynamic-container dans le footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ Chercher dans le droppable ET apr\u00e8s le droppable (les 2 positions possibles)\r\n        const $resBtnIn   = $droppable.find('.reserver-dynamic-container').first();\r\n        const $resBtnNext = $droppable.next('.reserver-dynamic-container');\r\n        const $resBtn     = $resBtnIn.length ? $resBtnIn : $resBtnNext;\r\n        if ($resBtn.length) {\r\n            \/\/ \u2705 Reset complet de TOUS les styles inline de positionnement\r\n            $resBtn[0].style.cssText = '';\r\n            $resBtn.css({\r\n                'margin-top': '0',\r\n                'margin-bottom': '0',\r\n                'position': '',\r\n                'top': '',\r\n                'left': '',\r\n                'bottom': ''\r\n            });\r\n            \/\/ Style minimal inline pour le label\r\n            $resBtn.find('.reserver-dynamic-label').css({\r\n                'font-size': _isFullDesk ? '16px' : '10px',\r\n                'font-weight': '700'\r\n            });\r\n            $footer.append($resBtn);\r\n        }\r\n\r\n        \/\/ v4.9ds : phrase \"Si vous souhaitez un autre emplacement...\" retir\u00e9e des espaces standards\r\n        \/\/   (uniformisation visuelle, espace gagn\u00e9 dans le footer)\r\n\r\n        \/\/ \u2500\u2500 Cr\u00e9er un wrapper global qui portera le liser\u00e9 vert \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ .HTMLUploadfileConteneur garde son box-shadow inset \u2192 overflow:visible\r\n        \/\/ pour que header et footer (qui d\u00e9bordent en haut\/bas) soient dans le liser\u00e9\r\n        \/\/ Solution : envelopper $ufc + $header + $footer dans un wrapper flex\r\n\r\n        \/\/ \u2705 Retirer tous les liser\u00e9s verts r\u00e9siduels \u2014 le wrapper porta le seul outline\r\n        $ufc.css({\r\n            'overflow': 'visible',\r\n            'height': 'auto',\r\n            'min-height': '',\r\n            \/\/ \u2705 Bug 12 : inset gauche\/droite, ext\u00e9rieur haut\/bas\r\n            'box-shadow': 'inset 2px 0 0 #00FF19, inset -2px 0 0 #00FF19, 0 -2px 0 #00FF19, 0 2px 0 #00FF19',\r\n            'border': 'none',\r\n            'background-color': '',\r\n            'display': 'block',\r\n            'margin': '0',\r\n            'padding': '0',\r\n            \/\/ v4.9ds : ancrage absolute pour la croix .via-ah-close\r\n            'position': 'relative'\r\n        });\r\n        \/\/ Nettoyer aussi UploadFileConteneur, UploadFileConteneur, ToBeHidden et dropzone\r\n        $droppable.find('#UploadFileConteneur, .UploadFileConteneur').css({\r\n            'box-shadow': 'none', 'outline': 'none', 'border': 'none'\r\n        });\r\n        $dropZone.css({ 'box-shadow': 'none', 'outline': 'none', 'border': 'none' });\r\n        \/\/ Nettoyer aussi le parent .ToBeHidden qui peut avoir un transform + outline\r\n        $droppable.find('.OrdiMobileConteneurClass').css({ 'box-shadow': 'none', 'outline': 'none', 'border': 'none' });\r\n\r\n\r\n        \/\/ \u2705 Wrapper global lis\u00e9r\u00e9 vert (header + ufc + footer)\r\n        \/\/   v4.9ds : position:relative pour ancrer la croix .via-ah-close dans le coin haut-droite\r\n        const $wrapper = jQuery('<div class=\"via-ad-wrapper\" style=\"' +\r\n            'display:flex;flex-direction:column;box-sizing:border-box;' +\r\n            'background:#fff;position:relative;' +\r\n        '\">' + '<\/div>');\r\n\r\n        $ufc.before($wrapper);\r\n        $wrapper.append($header);\r\n        $wrapper.append($ufc);\r\n        $wrapper.append($footer);\r\n        \/\/ v4.9ds : croix ancr\u00e9e \u00e0 $ufc (zone d'aper\u00e7u blanche), pas au header bleu\/jaune\r\n        $ufc.append($closeBtn);\r\n\r\n        \/\/ max-height + height via setProperty - re-applique apres Elementor (setTimeout)\r\n        \/\/ Desktop (userAgent) \u2192 hauteur fixe 170px (full desktop + desktop reduit)\r\n        \/\/ Mobile device r\u00e9el \u2192 height:auto, plafonn\u00e9 \u00e0 200px\r\n        \/\/ v4.9cy : Ele0A desktop \u2192 260px (comme les espaces agrandis \u22651200px) car les styles\r\n        \/\/          inline pos\u00e9s ici gagnent sur les r\u00e8gles CSS @media.\r\n        (function(_el) {\r\n            var _isDeskUA = UIManager.isDesktop();\r\n            var _hVal1 = 'auto';\r\n            var _maxH1;\r\n            \/\/ v4.9ds : Ele0A align\u00e9 sur standards (280) + 5px suppl\u00e9mentaires sur Ele0A\r\n            if (_isEle0A ? _isDeskUA : false) {\r\n                _maxH1 = '285px';\r\n            } else if (_isDeskUA) {\r\n                _maxH1 = '280px';\r\n            } else {\r\n                _maxH1 = '200px';\r\n            }\r\n            function _applyH() {\r\n                _el.style.setProperty('max-height', _maxH1, 'important');\r\n                _el.style.setProperty('height', _hVal1, 'important');\r\n                _el.style.setProperty('overflow', 'hidden', 'important');\r\n            }\r\n            _applyH();\r\n            setTimeout(_applyH, 100);\r\n            setTimeout(_applyH, 500);\r\n        })($ufc[0]);\r\n\r\n        \/\/ v4.9ds : min-height sur #drop_file_zone_achat = hauteur espace standard - header - footer\r\n        \/\/          \u00c0 chaque dimension correspond la m\u00eame logique que _applyH ci-dessus.\r\n        \/\/          La fonction est stock\u00e9e sur le droppable pour pouvoir \u00eatre rappel\u00e9e au resize.\r\n        (function(_dz, _hdr, _ftr, _drop) {\r\n            if (!_dz) return;\r\n            function _applyDzMinH(_phase) {\r\n                var _isDeskUA = UIManager.isDesktop();\r\n                var _ufcH;\r\n                \/\/ v4.9ds : Ele0A align\u00e9 sur standards (280) + 5px suppl\u00e9mentaires sur Ele0A\r\n                if (_isEle0A ? _isDeskUA : false) { _ufcH = 285; }\r\n                else if (_isDeskUA)               { _ufcH = 280; }\r\n                else                              { _ufcH = 200; }\r\n                var _hdrH = _hdr && _hdr.offsetHeight ? _hdr.offsetHeight : 0;\r\n                var _ftrH = _ftr && _ftr.offsetHeight ? _ftr.offsetHeight : 0;\r\n                var _dzMinH = Math.max(0, _ufcH - _hdrH - _ftrH);\r\n                _dz.style.setProperty('min-height', _dzMinH + 'px', 'important');\r\n                _dz.style.setProperty('height', _dzMinH + 'px', 'important');\r\n                _dz.style.setProperty('flex', '1 1 auto', 'important');\r\n                \/\/ v4.9ds DIAG : mesurer toutes les dimensions cl\u00e9s pour comprendre l'\u00e9cart Ele0A vs standards\r\n                var _ufc = _dz.closest('.HTMLUploadfileConteneur');\r\n                var _wrap = _dz.closest('.via-ad-wrapper');\r\n                var _img = _dz.querySelector('img, video, .doc-preview-container');\r\n                var _ufcCS = _ufc ? window.getComputedStyle(_ufc) : null;\r\n                var _wrapCS = _wrap ? window.getComputedStyle(_wrap) : null;\r\n                var _dzCS = window.getComputedStyle(_dz);\r\n                \/\/ v4.9ds DIAG : aussi rect.height (r\u00e9el \u00e0 l'\u00e9cran apr\u00e8s zoom\/scale) + zoom\/transform sur anc\u00eatres\r\n                var _wrapRect = _wrap ? _wrap.getBoundingClientRect() : null;\r\n                var _ufcRect = _ufc ? _ufc.getBoundingClientRect() : null;\r\n                var _dzRect = _dz.getBoundingClientRect();\r\n                \/\/ Chercher zoom\/transform sur les 5 anc\u00eatres jusqu'au droppable\r\n                var _zoomChain = [];\r\n                var _curEl = _wrap;\r\n                for (var _zi = 0; _zi < 6; _zi++) {\r\n                    if (!_curEl) { break; }\r\n                    var _csEl = window.getComputedStyle(_curEl);\r\n                    var _zm = _csEl.zoom;\r\n                    var _tr = _csEl.transform;\r\n                    var _hasTr = _tr ? (_tr !== 'none') : false;\r\n                    var _hasZoom = (_zm !== '1');\r\n                    if (_hasZoom ? true : _hasTr) {\r\n                        _zoomChain.push((_curEl.tagName + (_curEl.id ? '#' + _curEl.id : '.' + (_curEl.className || '').split(' ')[0])) + ' zoom:' + _zm + ' tr:' + _tr);\r\n                    }\r\n                    _curEl = _curEl.parentElement;\r\n                }\r\n                \/\/ v4.9ds DIAG ULTIME : cha\u00eene compl\u00e8te des anc\u00eatres avec rect.h pour identifier l'\u00e9cart\r\n                var _ancChain = [];\r\n                var _ancEl = _wrap;\r\n                for (var _ai = 0; _ai < 12; _ai++) {\r\n                    if (!_ancEl) { break; }\r\n                    var _aRect = _ancEl.getBoundingClientRect();\r\n                    var _aCS = window.getComputedStyle(_ancEl);\r\n                    var _tag = _ancEl.tagName;\r\n                    var _id = _ancEl.id ? ('#' + _ancEl.id) : '';\r\n                    var _cls = (_ancEl.className || '').split(' ')[0] || '';\r\n                    if (_cls) { _cls = '.' + _cls; }\r\n                    _ancChain.push(_tag + _id + _cls + ' [offH:' + _ancEl.offsetHeight + ' rectH:' + Math.round(_aRect.height) + ' zoom:' + _aCS.zoom + ' tr:' + (_aCS.transform === 'none' ? 'none' : _aCS.transform.substring(0, 30)) + ']');\r\n                    _ancEl = _ancEl.parentElement;\r\n                }\r\n                console.log('[DIAG H ' + _phase + '] '\r\n                    + (_isEle0A ? 'ELE0A' : 'STANDARD ' + (_dz.closest('.droppable') ? _dz.closest('.droppable').id : '?'))\r\n                    + ' | _ufcH:' + _ufcH + ' hdr:' + _hdrH + ' ftr:' + _ftrH + ' dzMinH:' + _dzMinH\r\n                    + '\\n  WRAPPER offsetH:' + (_wrap ? _wrap.offsetHeight : 'null') + ' rect.h:' + (_wrapRect ? Math.round(_wrapRect.height) : 'null')\r\n                    + '\\n  UFC offsetH:' + (_ufc ? _ufc.offsetHeight : 'null') + ' rect.h:' + (_ufcRect ? Math.round(_ufcRect.height) : 'null')\r\n                    + '\\n  DZ offsetH:' + _dz.offsetHeight + ' rect.h:' + Math.round(_dzRect.height)\r\n                    + '\\n  IMG offsetH:' + (_img ? _img.offsetHeight : 'null')\r\n                    + '\\n  CHAIN ancestors:\\n    ' + _ancChain.join('\\n    ')\r\n                );\r\n            }\r\n            _applyDzMinH('T+0');\r\n            setTimeout(function() { _applyDzMinH('T+100'); }, 100);\r\n            setTimeout(function() { _applyDzMinH('T+500'); }, 500);\r\n            \/\/ v4.9ds : exposer pour appel depuis handler resize\r\n            if (_drop) { _drop._applyDzMinH = _applyDzMinH; }\r\n        })($dropZone[0], $header[0], $footer[0], $droppable[0]);\r\n\r\n        \/\/ Sites pays : remonter le wrapper\r\n        if (!_isEle0A ? window === window.top : false) {\r\n            \/\/ \ud83d\udd0d DIAG : loguer la d\u00e9cision de branche + contexte\r\n            console.log('[DIAG _buildAdOverlay] branche sites pays', {\r\n                _isDesktop: _isDesktop,\r\n                isMobile: UIManager.isMobile(),\r\n                outerWidth: window.outerWidth,\r\n                innerWidth: window.innerWidth,\r\n                htmlClass: document.documentElement.className,\r\n                hasRegieClass: document.documentElement.classList.contains('via-regie-iframe'),\r\n                droppableId: $droppable.attr('id')\r\n            });\r\n            if (_isDesktop) {\r\n                \/\/ Kit creation : adjustDesktopLayout modifie deja le droppable margin\r\n                \/\/ -> wrapper margin-top reduit pour eviter le chevauchement\r\n                var _isKitDrop = window._lastDropWasKit === true;\r\n                \/\/ \u2705 DVM (desktop version mobile, inner<1000) : text-editor Elementor pose 297px\r\n                \/\/    \u2192 wrapper trop bas. -150px.\r\n                \/\/    Plein \u00e9cran desktop : -30px (l\u00e9ger d\u00e9calage, coh\u00e9rent avec post-resize).\r\n                var _isDVM = (window.innerWidth < 1000);\r\n                var _mt = _isKitDrop ? '30px' : (_isDVM ? '-150px' : '0px');\r\n                if ($wrapper[0]) {\r\n                    $wrapper[0].style.setProperty('margin-top', _mt, 'important');\r\n                    $wrapper[0].style.setProperty('margin-bottom', _isKitDrop ? '20px' : '15px', 'important');\r\n                }\r\n                \/\/ \u2705 Article (body.single) ou page secteur (body.page) au d\u00e9p\u00f4t plein \u00e9cran :\r\n                \/\/    80px en-dessous du droppable pour \u00e9viter le chevauchement avec le contenu.\r\n                if (!_isDVM) { if (!_isKitDrop) {\r\n                    var _isArticleOrSecteurDep = document.body.classList.contains('single') || document.body.classList.contains('page');\r\n                    if (_isArticleOrSecteurDep) {\r\n                        if ($droppable[0]) {\r\n                            $droppable[0].style.setProperty('margin-bottom', '80px', 'important');\r\n                            console.log('[DIAG _buildAdOverlay] article\/secteur plein \u00e9cran \u2192 droppable.margin-bottom: 80px');\r\n                        }\r\n                    }\r\n                } }\r\n                $wrapper.attr('data-deposited-mode', 'desktop');\r\n                console.log('[DIAG _buildAdOverlay] branche DESKTOP \u2192 margin-top:', _mt, '(DVM=' + _isDVM + ')');\r\n                \/\/ \u2705 D\u00e9clencher un resize simul\u00e9 300ms apr\u00e8s le d\u00e9p\u00f4t pour que le handler resize\r\n                \/\/    applique ses positions (algo inter-espaces, article\/secteur, DVM cleanup, etc.).\r\n                \/\/    Actif aussi en DVM (cas d\u00e9p\u00f4t en version mobile qui chevauche contenu au-dessus).\r\n                \/\/ v4.9ds : retir\u00e9 la condition !_isKitDrop \u2014 les Communiqu\u00e9\/Interview cr\u00e9\u00e9s\r\n                \/\/   depuis le kit ont aussi besoin du resize pour recalculer les espaces en dessous\r\n                setTimeout(function() {\r\n                    try {\r\n                        window.dispatchEvent(new Event('resize'));\r\n                        console.log('[DIAG _buildAdOverlay] resize simul\u00e9 post-d\u00e9p\u00f4t d\u00e9clench\u00e9 (DVM=' + _isDVM + ', kitDrop=' + _isKitDrop + ')');\r\n                    } catch (e) {\r\n                        console.warn('[DIAG _buildAdOverlay] resize simul\u00e9 a throw:', e);\r\n                    }\r\n                }, 300);\r\n                \/\/ \ud83d\udd0d DIAG d\u00e9taill\u00e9 : mesurer la position r\u00e9elle apr\u00e8s application\r\n                setTimeout(function() {\r\n                    var _w = $wrapper[0];\r\n                    var _d = $droppable[0];\r\n                    if (!_w) { return; }\r\n                    var _wRect = _w.getBoundingClientRect();\r\n                    var _dRect = _d ? _d.getBoundingClientRect() : null;\r\n                    var _wCS = getComputedStyle(_w);\r\n                    var _parent = _w.parentElement;\r\n                    var _parentRect = _parent ? _parent.getBoundingClientRect() : null;\r\n                    var _parentCS = _parent ? getComputedStyle(_parent) : null;\r\n                    \/\/ Essayer de trouver l'\u00e9l\u00e9ment juste au-dessus dans la page\r\n                    var _prevEl = null;\r\n                    if (_d) {\r\n                        var _sib = _d.previousElementSibling;\r\n                        while (_sib) {\r\n                            var _sr = _sib.getBoundingClientRect();\r\n                            if (_sr.height > 10) { _prevEl = _sib; break; }\r\n                            _sib = _sib.previousElementSibling;\r\n                        }\r\n                    }\r\n                    var _prevRect = _prevEl ? _prevEl.getBoundingClientRect() : null;\r\n                    console.log('[DIAG DEPOT DVM]', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        wrapperMT_computed: _wCS.marginTop,\r\n                        wrapperRect: { top: Math.round(_wRect.top), bottom: Math.round(_wRect.bottom), height: Math.round(_wRect.height) },\r\n                        droppableId: _d ? _d.id : '?',\r\n                        droppableRect: _dRect ? { top: Math.round(_dRect.top), bottom: Math.round(_dRect.bottom), height: Math.round(_dRect.height) } : null,\r\n                        parentTag: _parent ? (_parent.tagName + '.' + (_parent.className || '').split(' ').slice(0, 3).join('.')) : '?',\r\n                        parentDisplay: _parentCS ? _parentCS.display : '?',\r\n                        parentRect: _parentRect ? { top: Math.round(_parentRect.top), bottom: Math.round(_parentRect.bottom), height: Math.round(_parentRect.height) } : null,\r\n                        prevSibling: _prevEl ? (_prevEl.tagName + '.' + (_prevEl.className || '').split(' ').slice(0, 2).join('.')) : '?',\r\n                        prevSiblingRect: _prevRect ? { top: Math.round(_prevRect.top), bottom: Math.round(_prevRect.bottom) } : null,\r\n                        gapAboveDroppable: (_prevRect ? (_dRect ? Math.round(_dRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        gapAboveWrapper: (_prevRect ? (_wRect ? Math.round(_wRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        inner: window.innerWidth,\r\n                        outer: window.outerWidth\r\n                    });\r\n                }, 150);\r\n            } else {\r\n                \/\/ Sites pays d\u00e9p\u00f4t mobile : -140px (valeur historique qui marche)\r\n                if ($wrapper[0]) {\r\n                    $wrapper[0].style.setProperty('margin-top', '-140px', 'important');\r\n                    $wrapper[0].style.setProperty('margin-bottom', '0px', 'important');\r\n                }\r\n                $wrapper.attr('data-deposited-mode', 'mobile');\r\n                console.log('[DIAG _buildAdOverlay] branche MOBILE \u2192 margin-top: -140px !important');\r\n                \/\/ \u2705 Resize simul\u00e9 300ms apr\u00e8s le d\u00e9p\u00f4t pour que le handler resize applique\r\n                \/\/    ses positions (cleanup DVM, wrapper.mt\/mb=25px, etc.).\r\n                setTimeout(function() {\r\n                    try {\r\n                        window.dispatchEvent(new Event('resize'));\r\n                        console.log('[DIAG _buildAdOverlay] resize simul\u00e9 post-d\u00e9p\u00f4t MOBILE d\u00e9clench\u00e9');\r\n                    } catch (e) {\r\n                        console.warn('[DIAG _buildAdOverlay] resize simul\u00e9 MOBILE a throw:', e);\r\n                    }\r\n                }, 300);\r\n                \/\/ \ud83d\udd0d DIAG d\u00e9taill\u00e9 : mesurer la position r\u00e9elle apr\u00e8s application\r\n                setTimeout(function() {\r\n                    var _w = $wrapper[0];\r\n                    var _d = $droppable[0];\r\n                    if (!_w) { return; }\r\n                    var _wRect = _w.getBoundingClientRect();\r\n                    var _dRect = _d ? _d.getBoundingClientRect() : null;\r\n                    var _wCS = getComputedStyle(_w);\r\n                    var _parent = _w.parentElement;\r\n                    var _parentRect = _parent ? _parent.getBoundingClientRect() : null;\r\n                    var _parentCS = _parent ? getComputedStyle(_parent) : null;\r\n                    var _prevEl = null;\r\n                    if (_d) {\r\n                        var _sib = _d.previousElementSibling;\r\n                        while (_sib) {\r\n                            var _sr = _sib.getBoundingClientRect();\r\n                            if (_sr.height > 10) { _prevEl = _sib; break; }\r\n                            _sib = _sib.previousElementSibling;\r\n                        }\r\n                    }\r\n                    var _prevRect = _prevEl ? _prevEl.getBoundingClientRect() : null;\r\n                    console.log('[DIAG DEPOT MOBILE]', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        wrapperMT_computed: _wCS.marginTop,\r\n                        wrapperRect: { top: Math.round(_wRect.top), bottom: Math.round(_wRect.bottom), height: Math.round(_wRect.height) },\r\n                        droppableId: _d ? _d.id : '?',\r\n                        droppableRect: _dRect ? { top: Math.round(_dRect.top), bottom: Math.round(_dRect.bottom), height: Math.round(_dRect.height) } : null,\r\n                        parentTag: _parent ? (_parent.tagName + '.' + (_parent.className || '').split(' ').slice(0, 3).join('.')) : '?',\r\n                        parentDisplay: _parentCS ? _parentCS.display : '?',\r\n                        parentRect: _parentRect ? { top: Math.round(_parentRect.top), bottom: Math.round(_parentRect.bottom), height: Math.round(_parentRect.height) } : null,\r\n                        prevSibling: _prevEl ? (_prevEl.tagName + '.' + (_prevEl.className || '').split(' ').slice(0, 2).join('.')) : '?',\r\n                        prevSiblingRect: _prevRect ? { top: Math.round(_prevRect.top), bottom: Math.round(_prevRect.bottom) } : null,\r\n                        gapAboveDroppable: (_prevRect ? (_dRect ? Math.round(_dRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        gapAboveWrapper: (_prevRect ? (_wRect ? Math.round(_wRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        inner: window.innerWidth,\r\n                        outer: window.outerWidth\r\n                    });\r\n                }, 150);\r\n            }\r\n            \/\/ \ud83d\udd0d DIAG : v\u00e9rifier l'\u00e9tat effectif apr\u00e8s application\r\n            setTimeout(function() {\r\n                var _w = $wrapper[0];\r\n                if (_w) {\r\n                    var _cs = getComputedStyle(_w);\r\n                    console.log('[DIAG _buildAdOverlay] \u00e9tat wrapper apr\u00e8s 100ms', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        computedMarginTop: _cs.marginTop,\r\n                        computedMarginBottom: _cs.marginBottom,\r\n                        boundingRect: { top: _w.getBoundingClientRect().top, height: _w.getBoundingClientRect().height }\r\n                    });\r\n                }\r\n            }, 100);\r\n        }\r\n\r\n        \/\/ \u2705 Desktop : ajouter margin-bottom pour que le footer ne chevauche pas le contenu\r\n        if (_isDesktop) {\r\n            setTimeout(function() {\r\n                var _footerH = $footer[0] ? $footer[0].getBoundingClientRect().height : 0;\r\n                var _headerH = $header[0] ? $header[0].getBoundingClientRect().height : 0;\r\n                if (_footerH > 0) {\r\n                    $wrapper.css('margin-bottom', (_footerH + _headerH) + 'px');\r\n                }\r\n            }, 50);\r\n        }\r\n\r\n        \/\/ \u2705 body = reset margin du dropzone\r\n        $dropZone.css({ 'margin-top': '0', 'margin-bottom': '0' });\r\n        $dropZone.closest('.UploadFileConteneur').css({ 'margin-top': '0', 'margin-bottom': '0' });\r\n\r\n        \/\/ \u2705 v2.7.3 : Mobile \u2014 largeur = viewport moins marges, hauteur auto\r\n        if (!_isFullDesk) { \/\/ mobile + desktop mode mobile\r\n            \/\/ \u2705 R\u00e9f\u00e9rence CSS : offsetWidth du premier espace standard sans annonce\r\n            \/\/ offsetWidth = largeur CSS pr\u00e9-transform \u2192 m\u00eame base pour tous les espaces\r\n            var _refCssW = 0;\r\n            jQuery('.droppable').not('[id=\"Ele0A\"]').not('[data-via-ad-loaded=\"true\"]').each(function() {\r\n                var $_tbh = jQuery(this).closest('.ToBeHidden');\r\n                if ($_tbh.length) {\r\n                    var _w = $_tbh[0].offsetWidth;\r\n                    if (_w > 50) { _refCssW = _w; return false; }\r\n                }\r\n            });\r\n            if (_refCssW < 50) {\r\n                var $_fbTbh = jQuery('.droppable').not('[id=\"Ele0A\"]').first().closest('.ToBeHidden');\r\n                if ($_fbTbh.length) { _refCssW = $_fbTbh[0].offsetWidth; }\r\n            }\r\n            if (_refCssW < 50) { _refCssW = Math.round(window.innerWidth * 0.90); }\r\n\r\n            \/\/ Largeur CSS du wrapper = m\u00eame offsetWidth CSS que la r\u00e9f\u00e9rence standard\r\n            \/\/ Ele0A : son parent ToBeHidden est plus large \u2192 appliquer ratio rendu vs CSS pour \u00e9quilibrer\r\n            var _wrapCssW = _refCssW;\r\n            if (_isEle0A) {\r\n                var $_e0ATbh = $droppable.closest('.ToBeHidden');\r\n                if ($_e0ATbh.length) {\r\n                    var _e0ARendered = $_e0ATbh[0].getBoundingClientRect().width;\r\n                    var _e0ACss      = $_e0ATbh[0].offsetWidth || _e0ARendered;\r\n                    \/\/ _refCssW * (e0A_rendered \/ e0A_css) = largeur \u00e9cran cible pour Ele0A\r\n                    \/\/ Pour compenser l'exc\u00e9dent de 10% : appliquer le ratio scale Ele0A\r\n                    if (_e0ACss > 0 ? _e0ARendered > 0 : false) {\r\n                        var _e0AScale = _e0ARendered \/ _e0ACss;\r\n                        \/\/ CSS width pour que le rendu = _refRenderedW\r\n                        var $_stdTbhRef = jQuery('.droppable').not('[id=\"Ele0A\"]').not('[data-via-ad-loaded=\"true\"]').first().closest('.ToBeHidden');\r\n                        var _refRendered = $_stdTbhRef.length ? $_stdTbhRef[0].getBoundingClientRect().width : _refCssW;\r\n                        if (_e0AScale > 0.1) { _wrapCssW = Math.round(_refRendered \/ _e0AScale); }\r\n                    }\r\n                }\r\n            }\r\n\r\n            var _wrapPx = Math.round(_wrapCssW) + 'px';\r\n            var $_wrap = $droppable.find('.via-ad-wrapper');\r\n            \/\/ Sites pays : width 100% -> responsive au zoom CSS sans JS au resize\r\n            var _isSitesPays = (window === window.top);\r\n            $_wrap.css({\r\n                'width': _isSitesPays ? '100%' : _wrapPx,\r\n                'max-width': _isSitesPays ? '100%' : _wrapPx,\r\n                'box-sizing': 'border-box',\r\n                'position': 'relative',\r\n                'left': '0',\r\n                'transform': 'none'\r\n            });\r\n            \/\/ \u2705 Laisser les parents en overflow:visible pour ne pas clipper le wrapper\r\n            if (!_isEle0A) {\r\n                $droppable.find('.OrdiMobileConteneurClass').css({ 'overflow': 'visible' });\r\n                $droppable.css({ 'overflow': 'visible' });\r\n            }\r\n            $ufc.css({ 'width': '100%', 'max-width': 'none', 'min-height': '0',\r\n                        'margin': '0', 'padding': '0' });\r\n            (function(_el2) {\r\n                var _omc = _el2.closest ? _el2.closest('.OrdiMobileConteneurClass') : null;\r\n                \/\/ Desktop version mobile (userAgent desktop + fen\u00eatre r\u00e9duite) \u2192 hauteur fixe 170px\r\n                \/\/ Mobile device r\u00e9el \u2192 height:auto, plafonn\u00e9 \u00e0 200px\r\n                var _isDeskMobile = UIManager.isDesktop();\r\n                var _hVal = 'auto';\r\n                var _maxH = _isDeskMobile ? '280px' : '200px';  \/* v4.9ds : 170 \u2192 280 align\u00e9 sur _applyH principal *\/\r\n                function _applyH2() {\r\n                    \/\/ UFC\r\n                    _el2.style.setProperty('height', _hVal, 'important');\r\n                    _el2.style.setProperty('max-height', _maxH, 'important');\r\n                    _el2.style.setProperty('overflow', 'hidden', 'important');\r\n                    \/\/ Parent OMC : hauteur auto, pas de clamp (sinon header+footer clipp\u00e9s)\r\n                    if (_omc) {\r\n                        _omc.style.setProperty('height', 'auto', 'important');\r\n                        _omc.style.removeProperty('max-height');\r\n                        _omc.style.removeProperty('overflow');\r\n                    }\r\n                }\r\n                _applyH2();\r\n                setTimeout(_applyH2, 100);\r\n                setTimeout(_applyH2, 500);\r\n                setTimeout(_applyH2, 1000);\r\n            })($ufc[0]);\r\n            \/\/ Remonter le wrapper : 0 sur sites pays (zoom CSS, pas de scale)\r\n            if (!_isEle0A) {\r\n                var $_wrapStd = $droppable.find('.via-ad-wrapper');\r\n                var _isInIframe = (window !== window.top);\r\n                if (_isInIframe) {\r\n                    \/\/ iframe r\u00e9gie : compensation scale\r\n                    var _ufcMt = parseFloat($ufc.css('margin-top')) || 0;\r\n                    $_wrapStd.css('margin-top', (_ufcMt - 80) + 'px');\r\n                } else {\r\n                    \/\/ sites pays : DVM = -150, plein \u00e9cran = 0\r\n                    if ($_wrapStd[0]) {\r\n                        var _isDVMStd = (window.innerWidth < 1000);\r\n                        $_wrapStd[0].style.setProperty('margin-top', _isDVMStd ? '-150px' : '0px', 'important');\r\n                    }\r\n                }\r\n            }\r\n            \/\/ v4.9ds : dropzone width seulement (height\/min-height g\u00e9r\u00e9s par _applyDzMinH)\r\n            var _imgMaxH = '160px';\r\n            $dropZone.css({ 'width': '100%', 'margin': '0' });\r\n            \/\/ v4.9ds : pour que l'image occupe TOUTE la box du dropZone (comme Ele0A) tout\r\n            \/\/   en restant enti\u00e8re sans crop, on utilise width:100% + height:100% +\r\n            \/\/   object-fit:contain. Le dropZone a sa height pos\u00e9e par _applyDzMinH et le\r\n            \/\/   max-height CSS lignes 7910\/7929 plafonne. object-fit:contain garantit que\r\n            \/\/   l'image enti\u00e8re est visible (letterbox automatique si ratio diff\u00e9rent).\r\n            $dropZone.find('img, video').css({ 'max-width': '100%', 'width': '100%', 'max-height': _imgMaxH, 'height': '100%', 'display': 'block', 'object-fit': 'contain' });\r\n            \/\/ \u2705 Bug 11 : remonter drop_file_zone_achat de 2px sur mobile + margin-bottom 2px\r\n            $dropZone[0].style.setProperty('margin-top', '-2px', 'important');\r\n            $dropZone[0].style.setProperty('margin-bottom', '2px', 'important');\r\n        }\r\n\r\n        \/\/ v4.9ds : Clamp universel image uniquement (le min-height\/height du dropZone\r\n        \/\/          est g\u00e9r\u00e9 par _applyDzMinH ci-dessus). On ne touche plus au dropZone ici.\r\n        (function() {\r\n            var _imgMaxH2 = '160px';\r\n            $dropZone.find('img, video').css({\r\n                'max-height': _imgMaxH2, 'height': '100%', 'object-fit': 'contain'\r\n            });\r\n        })();\r\n\r\n        \/\/ \u2705 Masquer TOUS les anciens \u00e9l\u00e9ments de position\/ref (d\u00e9sormais dans le header)\r\n        $droppable.find('.DeplaceAnnonce, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire').hide();\r\n        $droppable.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne, .UploadIci').hide();\r\n        $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        \/\/ setProperty !important pour \u00e9craser les display:block!important d'adjustMobileLayout\r\n        $droppable.find('.PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer').each(function() {\r\n            this.style.setProperty('display', 'none', 'important');\r\n        });\r\n        \/\/ \u2705 Supprimer via-position-label (position maintenant dans le header)\r\n        $droppable.find('.via-position-label').remove();\r\n\r\n        \/\/ \u2699\ufe0f Override text-editor au d\u00e9p\u00f4t : neutralise la height explicite (ex: 297px)\r\n        \/\/    que Elementor pose sur .elementor-widget-text-editor \u00e0 certains breakpoints \u00e9troits.\r\n        \/\/    Sans \u00e7a, le wrapper se retrouve d\u00e9cal\u00e9 vers le bas dans l'OMC et chevauche le\r\n        \/\/    contenu en dessous (pb visible en desktop version mobile).\r\n        if (typeof window._viaOverrideTextEditor === 'function') {\r\n            var _omcForOverride = $droppable.find('.OrdiMobileConteneurClass')[0];\r\n            if (_omcForOverride) {\r\n                window._viaOverrideTextEditor(_omcForOverride);\r\n            }\r\n        }\r\n\r\n        console.log('\u2705 [via-ad-overlay] header+footer+wrapper inject\u00e9s | Ele0A:', _isEle0A, '| ref:', _emplacement);\r\n    },\r\n\r\n    adjustLayoutAfterUpload($dropZone) {\r\n        console.log('\ud83d\udcf1 adjustLayoutAfterUpload \u2014 isMobile():', this.isMobile(), 'outerWidth:', window.outerWidth);\r\n        if (this.isMobile()) {\r\n            this.adjustMobileLayout($dropZone);\r\n            \r\n            \/\/ Repositionner le bouton R\u00e9server APR\u00c8S les ajustements de layout\r\n            setTimeout(() => {\r\n                const $droppable = $dropZone.closest('.droppable');\r\n                \/\/ \u2705 v2.6 : Ele0A popup \u2014 skip repositionnement offset (g\u00e9r\u00e9 dans _isEle0AMobAdj)\r\n                if ($droppable.attr('id') === 'Ele0A') { return; }\r\n                const $btn = $droppable.find('.reserver-dynamic-container').add($droppable.next('.reserver-dynamic-container')).first();\r\n                const $lisere = $droppable.find('.HTMLUploadfileConteneur');\r\n                if ($btn.length ? $lisere.length : false) {\r\n                    const lisereBottom = $lisere[0].getBoundingClientRect().bottom;\r\n                    const btnTop = $btn[0].getBoundingClientRect().top;\r\n                    const offset = lisereBottom - btnTop - 48;\r\n                    \r\n                    \/\/ 15px suppl\u00e9mentaires pour homepage et articles\r\n                    let extraOffset = 0;\r\n                    const isHomepage = window.location.pathname === \"\/\" || window.location.pathname === \"\/en\/\";\r\n                    const isSectorPage = $('body').hasClass('page');\r\n                    if (isHomepage) {\r\n                        extraOffset = 13;\r\n                    }\r\n                    if (!isSectorPage) {\r\n                        extraOffset = 15;\r\n                    }\r\n                    \r\n                    \/\/ \u2705 v2.0.9 : +4px pour documents (communiqu\u00e9\/interview\/PDF) avec pr\u00e9sentation HTML\r\n                    if ($droppable.find('.doc-preview-container:visible').length > 0) {\r\n                        extraOffset += 4;\r\n                    }\r\n                    \r\n                    \/\/ v2.9 : site pays direct \u2014 r\u00e9duire l'offset (layout neutre vs iframe)\r\n                    var _sitePaysAdj = (window === window.top) ? 10 : 0;\r\n                    $btn.css({\r\n                        'margin-top': (offset + extraOffset + 20 - _sitePaysAdj) + 'px',\r\n                        'margin-bottom': (-(offset + extraOffset) - 100 + _sitePaysAdj) + 'px'\r\n                    });\r\n                    console.log('\ud83d\udcd0 Repositionnement mobile R\u00e9server:', { lisereBottom, btnTop, offset });\r\n                }\r\n            }, 50);\r\n        } else {\r\n            this.adjustDesktopLayout($dropZone);\r\n        }\r\n    },\r\n    \r\n    adjustMobileLayout($dropZone) {\r\n        console.log('\ud83d\udcf1 adjustMobileLayout START');\r\n        console.log('\ud83d\udcf1 AchatEspaceCall:', StateManager.get(\"AchatEspaceCall\"));\r\n        console.log('\ud83d\udcf1 PageAjoutModifAnnonce:', StateManager.get(\"PageAjoutModifAnnonce\"));\r\n        console.log('\ud83d\udcf1 window===window.top:', window === window.top);\r\n        console.log('\ud83d\udcf1 pathname:', location.pathname);\r\n        \r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \r\n        \/\/ \u2705 v2.4.3 : Masquer les textes position\/label\/r\u00e9f\u00e9rence (comme sur desktop)\r\n        $droppable\r\n            .find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 v2.6 : Renseigner .PositionEspacePublicitaire sur mobile (manquait vs desktop)\r\n        (function() {\r\n            var _rankPosMob = StateManager.get('Rank_Emplacement_Page_Web') || $droppable.attr('id') || '';\r\n            var _posLibMob = PreviewRenderer._getPositionLibelle(_rankPosMob);\r\n            if (_posLibMob) {\r\n                $droppable\r\n                    .find('.PositionEspacePublicitaire')\r\n                    .text(_posLibMob)\r\n                    .each(function() {\r\n                        this.style.setProperty('display', 'block', 'important');\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        \/\/ \u2705 Scop\u00e9 au $dropZone courant (\u00e9vite d'affecter les autres espaces pub)\r\n        $dropZone.closest('.droppable').find('#CroixResetAnnonce').css({'zoom': '75%'});\r\n\r\n        \/\/ \u2705 v2.4.3 : Remonter la croix reset sur mobile (override margin-top Elementor)\r\n        \/\/ \u2705 v2.4.5 : Poser aussi margin-right ici (Entete.txt ne l'atteint pas pour Ele0A)\r\n        var _croixContainer = $dropZone.closest('.OrdiMobileConteneurClass').find('.CroixResetAnnonceContainer')[0];\r\n        if (_croixContainer) {\r\n            var _isEle0ACroix = ($dropZone.closest('.droppable').attr('id') === 'Ele0A');\r\n            var _isTopWindowCroix = (window === window.top);\r\n            \/\/ v2.9 : site pays (mt=0px sur OrdiMobile) \u2192 compensate +65px vs ancien 65px\r\n            var _mtCroix = StateManager.get(\"PageAjoutModifAnnonce\") === 'Yes' ? '-88px'\r\n                         : (_isTopWindowCroix ? '70px' : '24px');\r\n            _croixContainer.style.setProperty('margin-top', _mtCroix, 'important');\r\n            _croixContainer.style.setProperty('margin-right', _isEle0ACroix ? '17px' : '12px', 'important');\r\n        }\r\n        \r\n        \/\/ \u2705 v2.6 : data-kit-drop = marqueur DOM fiable m\u00eame si _dropFromMiniature d\u00e9j\u00e0 resett\u00e9\r\n        var _droppableMob = $dropZone.closest('.droppable')[0];\r\n        var _isMiniatureAdj = window._dropFromMiniature\r\n            || (_droppableMob ? (_droppableMob.getAttribute('data-kit-drop') === 'true') : false);\r\n        window._dropFromMiniature = false;\r\n        \/\/ Nettoyer data-kit-drop apr\u00e8s lecture (comme le fait adjustDesktopLayout)\r\n        if (_droppableMob) { _droppableMob.removeAttribute('data-kit-drop'); }\r\n\r\n\r\n        if (_isMiniatureAdj ? window.outerWidth <= 1000 : false) {\r\n            \/\/ \u2705 Miniature doc-preview MOBILE : ajuster HTMLUploadfileConteneur \u00e0 la hauteur du contenu\r\n            \/\/ et neutraliser tous les margins Elementor qui d\u00e9calent le $dropZone hors du parent\r\n            var _isDocPreviewMob = $dropZone.find('.doc-preview-container').length > 0;\r\n            var _docH = _isDocPreviewMob ? 144 : 115;\r\n            \/\/ \u2705 v2.6 : Dans l'iframe r\u00e9gie (window !== window.top) \u2192 mt:70px (comme AchatEspaceCall=Yes)\r\n            \/\/ En standalone : mt:-55px (valeur originale)\r\n            \/\/ Raison : UFC top:658 < DROP top:720 avec mt:-55px \u2192 d\u00e9bordement 62px au-dessus du droppable\r\n            var _miniMt = (window !== window.top) ? '70px' : '-55px';\r\n            $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                'height':     _docH + 'px',\r\n                'min-height': _docH + 'px',\r\n                'max-height': _docH + 'px',\r\n                'margin-top': _miniMt,\r\n                'margin-bottom': '0px',\r\n                'overflow': 'hidden'\r\n            });\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px', 'height': (_docH - 6) + 'px'});\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '65px', 'margin-bottom': '40px'});\r\n            \/\/ \u2705 v2.4.9 : Doc-preview \u2014 overflow:visible pour que le liser\u00e9 du haut ne soit pas clipp\u00e9\r\n            if (_isDocPreviewMob) {\r\n                $dropZone.closest('.OrdiMobileConteneurClass').css('overflow', 'visible');\r\n                $dropZone.closest('.OrdiMobileConteneurClass').parent().css('overflow', 'visible');\r\n            }\r\n            \/\/ \u2705 v2.4.9 : Ele0A popup mobile \u2014 neutraliser translateY(-32px) pos\u00e9 par Entete sur EnvoiUlterieurTexte\r\n            var _isEle0AMob = ($dropZone.closest('.droppable').attr('id') === 'Ele0A')\r\n                || ($dropZone.closest('#PopUpMessageAchattest').length > 0);\r\n            if (_isEle0AMob) {\r\n                var $_euTxt = $dropZone.closest('.droppable').find('.EnvoiUlterieurTexte');\r\n                if ($_euTxt[0]) {\r\n                    $_euTxt[0].style.setProperty('transform', 'translateY(0px)', 'important');\r\n                    $_euTxt[0].style.setProperty('margin-top', '0px', 'important');\r\n                }\r\n                \/\/ \u2705 v2.6 : Pb 2 \u2014 centrage vertical doc-preview dans #PopUpMessageAchattest\r\n                var _popupAchat = document.getElementById('PopUpMessageAchattest');\r\n                if (_popupAchat) {\r\n                    _popupAchat.style.setProperty('align-items', 'center', 'important');\r\n                }\r\n                \/\/ Annuler margin-top:23px du media query sur .doc-preview-container\r\n                setTimeout(function() {\r\n                    var _dp = $dropZone.find('.doc-preview-container')[0];\r\n                    if (_dp) { _dp.style.setProperty('margin-top', '0px', 'important'); }\r\n                }, 50);\r\n\r\n                \/\/ \u2705 v2.6 : Pb 8\/9 \u2014 miniature Ele0A : appliquer les m\u00eames fixes que _isEle0AMobAdj\r\n                \/\/ (croix position, masquage \u00e9l\u00e9ments parasites, bouton R\u00e9server)\r\n                var $_dropMini = $dropZone.closest('.droppable');\r\n                if ($_dropMini.attr('id') === 'Ele0A') {\r\n                    $_dropMini.css({'position': 'relative'});\r\n                    \/\/ Masquer EnvoiUlterieur (inutile si fichier d\u00e9pos\u00e9) et R\u00e9server statique\r\n                    $_dropMini.find('.elementor-field-group-EnvoiUlterieur').each(function() {\r\n                        this.style.setProperty('display', 'none', 'important');\r\n                    });\r\n                    $_dropMini.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').each(function() {\r\n                        this.style.setProperty('display', 'none', 'important');\r\n                    });\r\n                    \/\/ Croix en position absolue dans l'UFC\r\n                    var $_ufcMini = $dropZone.closest('.HTMLUploadfileConteneur');\r\n                    $_ufcMini.css('position', 'relative');\r\n                    var _croixMini = $_dropMini.find('.CroixResetAnnonceContainer')[0];\r\n                    if (_croixMini) {\r\n                        if ($_ufcMini[0]) { $_ufcMini[0].appendChild(_croixMini); }\r\n                        _croixMini.style.setProperty('position', 'absolute', 'important');\r\n                        _croixMini.style.setProperty('top', '-31px', 'important');\r\n                        _croixMini.style.setProperty('right', '-36px', 'important');\r\n                        _croixMini.style.setProperty('margin', '0px', 'important');\r\n                        _croixMini.style.setProperty('z-index', '9999', 'important');\r\n                    }\r\n                    \/\/ Bouton R\u00e9server dynamique \u2014 plac\u00e9 apr\u00e8s UFC\r\n                    var $_reserverMini = $_dropMini.find('.reserver-dynamic-container').first();\r\n                    if (!$_reserverMini.length) { $_reserverMini = $_dropMini.next('.reserver-dynamic-container'); }\r\n                    if ($_reserverMini.length) {\r\n                        $_ufcMini.after($_reserverMini);\r\n                        $_reserverMini.css({\r\n                            'margin-top': '-27px',\r\n                            'margin-bottom': '4px',\r\n                            'position': 'relative',\r\n                            'z-index': '10',\r\n                            'text-align': 'center',\r\n                            'transform': 'scale(0.8)',\r\n                            'transform-origin': 'top center'\r\n                        });\r\n                    }\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.4.12 : AchatEspaceCall=Yes \u2192 override margins (m\u00eame correction que pour upload direct)\r\n            if (StateManager.get('AchatEspaceCall') === 'Yes') {\r\n                $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n                var _docHAchatMini = _isDocPreviewMob ? 150 : 112;\r\n                $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                    'margin-top': '70px',\r\n                    'margin-bottom': '35px',\r\n                    'min-height': _docHAchatMini + 'px',\r\n                    'height': _isDocPreviewMob ? _docHAchatMini + 'px' : '',\r\n                    'max-height': _isDocPreviewMob ? _docHAchatMini + 'px' : ''\r\n                });\r\n                console.log('\ud83d\udcf1 [miniature] AchatEspaceCall=Yes OVERRIDE: mt=70px | docPreview:', _isDocPreviewMob);\r\n            }\r\n            return;\r\n        }\r\n        \/\/ \u2705 v2.4.10 : Miniature DESKTOP \u2192 layout normal appliqu\u00e9 ci-dessous\r\n        if (_isMiniatureAdj) {\r\n            \/\/ Pas de overflow:hidden \u2014 le liser\u00e9 box-shadow inset doit rester visible\r\n        }\r\n\r\n        \/\/ \u2705 v2.6 : Ele0A popup mobile \u2014 pas de margins ajust\u00e9s (le droppable a ses propres dimensions)\r\n        \/\/ Le fond blanc doit \u00eatre appliqu\u00e9 au droppable lui-m\u00eame, pas \u00e0 HTMLUploadfileConteneur d\u00e9cal\u00e9\r\n        var _isEle0AMobAdj = ($dropZone.closest('.droppable').attr('id') === 'Ele0A');\r\n        console.log('[Ele0A] _isEle0AMobAdj:', _isEle0AMobAdj, '| ViaPopupProcessAchat:', (sessionStorage.getItem('_ViaPopupOpen') === 'Yes'));\r\n        if (_isEle0AMobAdj) {\r\n            var $_ele0ADrop = $dropZone.closest('.droppable');\r\n            \/\/ v2.9 : si ViaPopupProcessAchat present, repositionner les elements hors-lisere\r\n            var _isVPP = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n            console.log('[Ele0A] _isVPP:', _isVPP);\r\n            if (_isVPP) {\r\n                \/\/ DeplaceAnnonceSubContainer : forcer margin-top positif pour rentrer dans le lisere\r\n                var $_das = $_ele0ADrop.find('.DeplaceAnnonceSubContainer');\r\n                if ($_das.length) {\r\n                    $_das[0].style.setProperty('margin-top', '5px', 'important');\r\n                    console.log('[Ele0A] DeplaceAnnonceSubContainer margin-top: 5px');\r\n                }\r\n                \/\/ CroixResetAnnonceContainer : forcer position interne\r\n                var $_crc = $_ele0ADrop.find('.CroixResetAnnonceContainer');\r\n                if ($_crc.length) {\r\n                    $_crc[0].style.setProperty('position', 'absolute', 'important');\r\n                    $_crc[0].style.setProperty('top',   '5px', 'important');\r\n                    $_crc[0].style.setProperty('right', '5px', 'important');\r\n                    $_crc[0].style.setProperty('margin', '0px', 'important');\r\n                    console.log('[Ele0A] CroixResetAnnonceContainer repositionne: top:5px right:5px');\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.6 : Ele0A \u2014 neutraliser la hauteur des boutons format cach\u00e9s\r\n            \/\/ Ne pas utiliser position:absolute\/overflow:hidden \u2192 cache le bouton R\u00e9server\r\n            $_ele0ADrop.css({'position': 'relative'});\r\n            var $_omcEle0A = $dropZone.closest('.OrdiMobileConteneurClass');\r\n            var $_ufcEle0APre = $dropZone.closest('.HTMLUploadfileConteneur');\r\n            \/\/ \u2705 OrdiMobileConteneurClass : flex parent Elementor \u00e9tire le UFC \u2192 forcer flex-start\r\n            $_omcEle0A.css({\r\n                'padding': '0',\r\n                'min-height': '0',\r\n                'height': 'auto',\r\n                'margin-top': '0px',\r\n                'margin-bottom': '0px',\r\n                'align-items': 'flex-start',\r\n                'justify-content': 'flex-start'\r\n            });\r\n            \/\/ \u2705 UFC : align-self:flex-start \u2192 prend sa hauteur naturelle (pas \u00e9tir\u00e9e par l'OMC)\r\n            $_ufcEle0APre.css({\r\n                'margin': '0',\r\n                'min-height': '0',\r\n                'height': 'auto',\r\n                'width': '100%',\r\n                'align-self': 'flex-start',\r\n                'display': 'flex',\r\n                'align-items': 'center',\r\n                'justify-content': 'center'\r\n            });\r\n            $dropZone.css({\r\n                'margin-top': '0px',\r\n                'margin-bottom': '0px',\r\n                'top': '0px',\r\n                'display': 'flex',\r\n                'align-items': 'center',\r\n                'justify-content': 'center',\r\n                'width': '100%'\r\n            });\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '0px', 'margin-bottom': '0px'});\r\n            \/\/ \u2705 Pb 2 : Centrage vertical du doc-preview \u2014 mesur\u00e9 apr\u00e8s rendu\r\n            \/\/ Le flexbox seul ne suffit pas si le conteneur n'a pas de hauteur propre\r\n            setTimeout(function() {\r\n                var _ele0AEl = $_ele0ADrop[0];\r\n                var _docPrevEl = $dropZone.find('.doc-preview-container')[0];\r\n                if (_ele0AEl) {\r\n                    if (_docPrevEl) {\r\n                        var _ele0AH = _ele0AEl.getBoundingClientRect().height;\r\n                        var _docPrevH = _docPrevEl.getBoundingClientRect().height;\r\n                        var _mt = Math.max(0, Math.round((_ele0AH - _docPrevH) \/ 2) - 8);\r\n                        _docPrevEl.style.setProperty('margin-top', _mt + 'px', 'important');\r\n                        console.log('\ud83d\udcf1 [Ele0A pb2] centrage vertical: ele0AH=' + Math.round(_ele0AH) + ' docH=' + Math.round(_docPrevH) + ' mt=' + _mt + 'px');\r\n                    }\r\n                }\r\n            }, 100);\r\n            \/\/ \u2705 Pb 3 : Croix d\u00e9plac\u00e9e DANS HTMLUploadfileConteneur (position:relative) \u2192 top-right de l'image\r\n            var $_ufcEle0A = $_ufcEle0APre;\r\n            $_ufcEle0A.css('position', 'relative');\r\n            var _croixEle0A = $_ele0ADrop.find('.CroixResetAnnonceContainer')[0];\r\n            if (_croixEle0A) {\r\n                $_ufcEle0A[0].appendChild(_croixEle0A);\r\n                _croixEle0A.style.setProperty('position', 'absolute', 'important');\r\n                var _isViaPopupCroix = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n                if (_isViaPopupCroix) {\r\n                    \/\/ Popup pays site : croix a l'interieur de l'UFC\r\n                    _croixEle0A.style.setProperty('top',   '4px',  'important');\r\n                    _croixEle0A.style.setProperty('right', '4px',  'important');\r\n                } else {\r\n                    _croixEle0A.style.setProperty('top',   '-31px', 'important');\r\n                    _croixEle0A.style.setProperty('right', '-36px', 'important');\r\n                }\r\n                _croixEle0A.style.setProperty('margin', '0px', 'important');\r\n                _croixEle0A.style.setProperty('z-index', '9999', 'important');\r\n            }\r\n            \/\/ \u2705 v2.6 : Masquer les \u00e9l\u00e9ments au-dessus de l'image dans Ele0A\r\n            \/\/ EnvoiUlterieur : inutile quand un fichier est d\u00e9pos\u00e9\r\n            \/\/ ReserverContainer statique : remplac\u00e9 par le bouton dynamique\r\n            $_ele0ADrop.find('.elementor-field-group-EnvoiUlterieur').each(function() {\r\n                this.style.setProperty('display', 'none', 'important');\r\n            });\r\n            $_ele0ADrop.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').each(function() {\r\n                this.style.setProperty('display', 'none', 'important');\r\n            });\r\n\r\n            \/\/ \u2705 Pb 4 : Bouton R\u00e9server dynamique \u2014 plac\u00e9 apr\u00e8s UFC\r\n            var $_reserverEle0A = $_ele0ADrop.find('.reserver-dynamic-container').first();\r\n            if (!$_reserverEle0A.length) { $_reserverEle0A = $_ele0ADrop.next('.reserver-dynamic-container'); }\r\n            if ($_reserverEle0A.length) {\r\n                $_ufcEle0A.after($_reserverEle0A);\r\n                $_reserverEle0A.css({\r\n                    'margin-top': '-27px',\r\n                    'margin-bottom': '4px',\r\n                    'position': 'relative',\r\n                    'z-index': '10',\r\n                    'text-align': 'center',\r\n                    'transform': 'scale(0.8)',\r\n                    'transform-origin': 'top center'\r\n                });\r\n            }\r\n            console.log('\ud83d\udcf1 [Ele0A popup] marges neutralis\u00e9es + centrage vertical + croix\/r\u00e9server positionn\u00e9s');\r\n            return;\r\n        }\r\n\r\n        \/\/ v2.9 : site pays direct (window.top) = marges neutres; iframe r\u00e9gie = marges compens\u00e9es\r\n        var _isInIframeNonMini = (window !== window.top);\r\n        var _isOnSitePays = (window === window.top);\r\n        $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n            'min-height': '0',\r\n            'margin-top': _isInIframeNonMini ? '70px' : (_isOnSitePays ? '-50px' : '-85px'),\r\n            'margin-bottom': _isMiniatureAdj ? '0px' : (_isInIframeNonMini ? '35px' : (_isOnSitePays ? '10px' : '20px'))\r\n        });\r\n        console.log('\ud83d\udcf1 HTMLUploadfileConteneur margins set: mt=' + (_isOnSitePays ? '10px' : (_isInIframeNonMini ? '70px' : '-85px')) + ', el found:', $dropZone.closest('.HTMLUploadfileConteneur').length);\r\n        \r\n        if (_isInIframeNonMini || _isOnSitePays) {\r\n            \/\/ iframe ou site pays direct : pas de marges n\u00e9gatives sur dropZone\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n        } else {\r\n            $dropZone.css({\r\n                'margin-top': '-50px',\r\n                'margin-bottom': '-140px'\r\n            });\r\n        }\r\n        \r\n        $dropZone.closest('.OrdiMobileConteneurClass').css({\r\n            'margin-top': _isOnSitePays ? '0px' : '65px',\r\n            'margin-bottom': _isOnSitePays ? '50px' : '-10px'\r\n        });\r\n        console.log('\ud83d\udcf1 OrdiMobile margins set: mt=' + (_isOnSitePays ? '0px' : '65px') + ' mb=' + (_isOnSitePays ? '50px' : '-10px'));\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            \/\/ \u2705 v2.4.12 : Reset $dropZone margins (les -50px\/-140px tirent le contenu vers le haut dans l'iframe)\r\n            \/\/ Pour doc-preview (Communiqu\u00e9\/Interview), la hauteur est plus grande \u2192 ajuster min-height\r\n            var _isDocPreviewAchat = $dropZone.find('.doc-preview-container').length > 0;\r\n            var _docHAchat = _isDocPreviewAchat ? 150 : 112;\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n            $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                \/\/ \u2705 v2.4.13 : Miniature desktop \u2192 +40px suppl\u00e9mentaires pour compenser le d\u00e9calage\r\n                'margin-top': _isMiniatureAdj ? '110px' : '70px',\r\n                'margin-bottom': '35px',\r\n                'min-height': _docHAchat + 'px',\r\n                'height': _isDocPreviewAchat ? _docHAchat + 'px' : '',\r\n                'max-height': _isDocPreviewAchat ? _docHAchat + 'px' : ''\r\n            });\r\n            console.log('\ud83d\udcf1 AchatEspaceCall=Yes OVERRIDE: mt=' + (_isMiniatureAdj ? '110px' : '70px') + ' | docPreview:', _isDocPreviewAchat, '| h:', _docHAchat);\r\n        }\r\n        \r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '70px'});\r\n        }\r\n        \r\n        \/\/ v2.9 : site pays mobile \u2014 d\u00e9caler reserver-dynamic-container de 30px vers le bas\r\n        if (_isOnSitePays) {\r\n            setTimeout(function() {\r\n                var $reserverCont = $dropZone.closest('.droppable').find('.reserver-dynamic-container')\r\n                    .add($dropZone.closest('.droppable').next('.reserver-dynamic-container')).first();\r\n                if ($reserverCont.length) {\r\n                    var _curMt = parseInt($reserverCont.css('margin-top')) || 0;\r\n                    $reserverCont.css('margin-top', (_curMt + 30) + 'px');\r\n                    console.log('\ud83d\udcf1 [site pays] reserver-dynamic-container +30px \u2192 mt:', _curMt + 30);\r\n                }\r\n            }, 80);\r\n        }\r\n        \/\/ v2.9 : supprim\u00e9 - margin-top\/bottom -100px causait chevauchement texte sur sites pays\r\n    },\r\n    \r\n    adjustDesktopLayout($dropZone) {\r\n        const emplacement = StateManager.get('Commande_Emplacement_Page_Web');\r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \/\/ \u2705 v2.4.13 : Lire et resetter _dropFromMiniature ici aussi (desktop)\r\n        var _fromMiniatureDesktop = window._dropFromMiniature || ($droppable[0] ? $droppable[0].getAttribute('data-kit-drop') === 'true' : false);\r\n        \/\/ Memoriser pour _buildAdOverlay qui tourne apres\r\n        window._lastDropWasKit = _fromMiniatureDesktop;\r\n        window._dropFromMiniature = false;\r\n        \/\/ \u2705 Nettoyer data-kit-drop apr\u00e8s lecture\r\n        if ($droppable[0]) { $droppable[0].removeAttribute('data-kit-drop'); }\r\n        \r\n        \/\/ \u2705 Masquer les textes enfants (position, label, r\u00e9f\u00e9rence) sans toucher au conteneur\r\n        $droppable\r\n            .find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur > div > .elementor-widget-text-editor')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 +15px sur .droppable \u2014 sauf si drop depuis miniature (dimensions naturelles)\r\n        if (!_fromMiniatureDesktop) {\r\n            const currentMt = parseInt($droppable.css('margin-top')) || 0;\r\n            $droppable.css('margin-top', (currentMt + 15) + 'px');\r\n        } else {\r\n            \/\/ \u2705 v2.4.13 : Drop depuis miniature Kit \u2014 d\u00e9caler de 30px vers le bas\r\n            const currentMt = parseInt($droppable.css('margin-top')) || 0;\r\n            \/\/ \u2705 Homepage : -20px (annonce trop basse sur r\u00e9gie) \u2014 autres pages : +70px\r\n            var _mtOffsetMini = (window.location.pathname === '\/' || window.location.pathname === '\/en\/' || window.location.pathname === '\/fr\/') ? -20 : 70;\r\n            $droppable.css('margin-top', (currentMt + _mtOffsetMini) + 'px');\r\n        }\r\n        \r\n        \/\/ v2.9 : detection popup parent (ViaPopupProcessAchat existe sur site pays)\r\n        var _isViaPopupParent = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n        $dropZone.closest('.OrdiMobileConteneurClass')\r\n            .find('.RefEspacePublicitaire')\r\n            .html(emplacement)\r\n            .attr('id', 'RefEspacePublicitaire')\r\n            .each(function() {\r\n                var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                if (_isViaPopupParent) {\r\n                    \/\/ repositionner a l'interieur du lisere vert\r\n                    this.style.setProperty('display', 'block', 'important');\r\n                    this.style.setProperty('margin-top', '4px', 'important');\r\n                    this.style.setProperty('margin-right', '4px', 'important');\r\n                    if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                } else {\r\n                    if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                    this.style.setProperty('display', 'block', 'important');\r\n                }\r\n            });\r\n\r\n        \/\/ v4.9cp : centrer .RefEspacePublicitaire entre le titre et la croix de fermeture.\r\n        \/\/          Mesure dynamique du bord droit du titre et du bord gauche de la croix,\r\n        \/\/          puis pose position:absolute + left au milieu.\r\n        function _centerRefEspPub() {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').find('.RefEspacePublicitaire').each(function() {\r\n                var _refEl = this;\r\n                var _container = jQuery(_refEl).closest('.droppable')[0] || jQuery(_refEl).closest('.DeplaceAnnonce')[0];\r\n                if (!_container) return;\r\n                \/\/ Bord droit du titre (ChoisirEspacePublicitaireDisponibiliteConteneur ou AdUploadedTitle)\r\n                var _titleEl = _container.querySelector('.ChoisirEspacePublicitaireDisponibiliteConteneur')\r\n                            || _container.querySelector('.AdUploadedTitle');\r\n                \/\/ Bord gauche de la croix\r\n                var _crossEl = _container.querySelector('#CroixResetAnnonce')\r\n                            || _container.querySelector('.CroixResetAnnonceContainer');\r\n                if (!_titleEl || !_crossEl) return;\r\n                var _cRect = _container.getBoundingClientRect();\r\n                var _tRect = _titleEl.getBoundingClientRect();\r\n                var _xRect = _crossEl.getBoundingClientRect();\r\n                if (!_cRect.width || !_tRect.width || !_xRect.width) return;\r\n                \/\/ Milieu entre bord droit titre et bord gauche croix\r\n                var _mid = (_tRect.right + _xRect.left) \/ 2;\r\n                \/\/ Position relative au container (le parent position:relative)\r\n                var _leftPx = Math.round(_mid - _cRect.left);\r\n                _refEl.style.setProperty('position', 'absolute', 'important');\r\n                _refEl.style.setProperty('left', _leftPx + 'px', 'important');\r\n                _refEl.style.setProperty('transform', 'translateX(-50%)', 'important');\r\n                _refEl.style.setProperty('top', (_tRect.top - _cRect.top) + 'px', 'important');\r\n                _refEl.style.removeProperty('margin-right');\r\n                _refEl.style.removeProperty('margin-left');\r\n            });\r\n        }\r\n        \/\/ Apr\u00e8s le rendu (titre + croix visibles)\r\n        setTimeout(_centerRefEspPub, 50);\r\n        setTimeout(_centerRefEspPub, 300);\r\n        setTimeout(_centerRefEspPub, 800);\r\n\r\n        \/\/ \u2705 v2.4.5 : Renseigner et afficher .PositionEspacePublicitaire\r\n        (function() {\r\n            var _rankPos = StateManager.get('Rank_Emplacement_Page_Web') || $droppable.attr('id') || '';\r\n            var _posLib = PreviewRenderer._getPositionLibelle(_rankPos);\r\n            if (_posLib) {\r\n                $dropZone.closest('.OrdiMobileConteneurClass')\r\n                    .find('.PositionEspacePublicitaireDeplacer')\r\n                    .text(_posLib)\r\n                    .each(function() {\r\n                        var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                        if (_isViaPopupParent) {\r\n                            this.style.setProperty('display', 'block', 'important');\r\n                            this.style.setProperty('margin-top', '4px', 'important');\r\n                            if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                        } else {\r\n                            if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                            this.style.setProperty('display', 'block', 'important');\r\n                        }\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        if (window.location.pathname === \"\/\" || window.location.pathname === \"\/en\/\") {\r\n            \/\/ \u2705 v2.4.10 : top selon contexte \u2014 espaces hors .remainingContent d\u00e9calent de 60px, ceux dedans de 20px\r\n            \/\/ \u2705 v2.7.3 : Ele0A est positionn\u00e9 par positionEle0AOverEle1A \u2192 ne pas \u00e9craser son top\r\n            var _isEle0AHP = $droppable.attr('id') === 'Ele0A';\r\n            if (!_isEle0AHP) {\r\n                var _inRemainingContent = $dropZone.closest('.remainingContent').length > 0;\r\n                var _topOffsetHP = _inRemainingContent ? '20px' : '100px';\r\n                $dropZone.closest('.ToBeHidden')\r\n                    .css('top', _topOffsetHP)\r\n                    .css('min-height', '300px');\r\n            }\r\n            \/\/ NB : pas de overflow:hidden ici \u2014 le liser\u00e9 vert (box-shadow inset sur HTMLUploadfileConteneur)\r\n            \/\/ doit rester visible sur les 4 c\u00f4t\u00e9s\r\n        }\r\n        \r\n        \/\/ \u2705 Toutes pages desktop : rendre le parent du droppable non-clippant\r\n        \/\/ Le bouton R\u00e9server est ins\u00e9r\u00e9 apr\u00e8s .droppable avec margin-top n\u00e9gatif \u2014\r\n        \/\/ overflow:hidden sur le parent Elementor le masque sinon.\r\n        $droppable.parent().css('overflow', 'visible');\r\n        \r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '0px'});\r\n            \/\/ \u2705 Page \/annonce : masquer les \u00e9l\u00e9ments de d\u00e9placement inutiles\r\n            $droppable.find('.DeplaceAnnonceText').hide();\r\n            $droppable.find('.PositionEspacePublicitaireDeplacer').hide();\r\n            $droppable.find('.RefEspacePublicitaire').hide();\r\n            $droppable.find('.reserver-dynamic-option').hide();\r\n            $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        }\r\n        \r\n        \/\/ \u2705 v2.7.3 : Ele0A desktop \u2014 ajustements position des labels et de la croix apr\u00e8s d\u00e9p\u00f4t\r\n        if ($droppable.attr('id') === 'Ele0A') {\r\n            \/\/ .PositionEspacePublicitaireDeplacer : +35px bas, +30px droite\r\n            $droppable.find('.PositionEspacePublicitaireDeplacer').each(function() {\r\n                this.style.setProperty('margin-top', '35px', 'important');\r\n                this.style.setProperty('margin-left', '30px', 'important');\r\n            });\r\n            \/\/ .RefEspacePublicitaire : +35px bas, 25px vers la gauche\r\n            $droppable.find('.RefEspacePublicitaire').each(function() {\r\n                this.style.setProperty('margin-top', '35px', 'important');\r\n                this.style.setProperty('margin-right', '25px', 'important');\r\n            });\r\n            \/\/ #CroixResetAnnonce : -30px vers le haut, 22px vers la gauche (+3px droite)\r\n            $droppable.find('#CroixResetAnnonce').each(function() {\r\n                this.style.setProperty('margin-top', '-30px', 'important');\r\n                this.style.setProperty('margin-right', '22px', 'important');\r\n            });\r\n        }\r\n    }\r\n};\r\n\r\n\/**\r\n * \u2705 Module de gestion des formats et du titre .SelectionFormatTitre\r\n *\/\r\nconst FormatUIManager = {\r\n    \r\n    \/**\r\n     * V\u00e9rifie si un format est s\u00e9lectionn\u00e9 dans un espace publicitaire\r\n     * V\u00e9rifie le background-color OU le sessionStorage (format venant de l'accord\u00e9on)\r\n     *\/\r\n    hasSelectedFormat($element) {\r\n        const $droppable = $element.closest('.droppable');\r\n        \/\/ v4.9ds : si une annonce est d\u00e9j\u00e0 d\u00e9pos\u00e9e dans cet espace (data-via-ad-loaded='true'),\r\n        \/\/   le format est implicite (d\u00e9duit par UploadManager.handleFileUpload depuis l'extension\r\n        \/\/   du fichier). Retourner true sans d\u00e9pendre du DOM .EspPubFormatContainer ni du\r\n        \/\/   sessionStorage Upload_File_Name (qui peut \u00eatre nettoy\u00e9 apr\u00e8s une r\u00e9servation\r\n        \/\/   pr\u00e9c\u00e9dente sur un autre espace pub).\r\n        \/\/   Bug constat\u00e9 : 2 annonces d\u00e9pos\u00e9es (Ele0A + Ele1A), Ele0A r\u00e9serv\u00e9e \u2192 popup ouvert,\r\n        \/\/   tentative R\u00e9server Ele1A bloqu\u00e9e car hasFormat=false (sessionStorage vid\u00e9 pour Ele0A,\r\n        \/\/   et le fond blanc EspPubFormatContainer pas pos\u00e9 sur Ele1A en mode popup).\r\n        if ($droppable.attr('data-via-ad-loaded') === 'true') return true;\r\n        \/\/ \u2705 v4.9ds Fix 4 : data-via-current-format prioritaire sur la d\u00e9tection visuelle\r\n        \/\/   \u2192 pos\u00e9 par applyFormatState ; survit aux races qui effacent le background blanc\r\n        \/\/   (un script tiers met temporairement transparent \u2192 l'observer voit \"format perdu\"\r\n        \/\/   \u2192 SelectionFormatTitre s'affiche \u2192 UI cass\u00e9e). L'attribut sur le .droppable\r\n        \/\/   reste pos\u00e9 tant qu'applyFormatState n'a pas \u00e9t\u00e9 appel\u00e9 avec un format diff\u00e9rent.\r\n        if ($droppable.attr('data-via-current-format')) return true;\r\n        const isEle0A = $droppable.attr('id') === 'Ele0A';\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            if (isEle0A) {\r\n                \/\/ \u2705 v2.3.4 : Ele0A popup \u2014 vrai seulement si un format sous-jacent (hors FormatIdPopUp) est s\u00e9lectionn\u00e9\r\n                var _hasDomFmt = $droppable.find('.EspPubFormatContainer').not('.FormatIdPopUp').toArray().some(el => {\r\n                    return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n                });\r\n                if (_hasDomFmt) return true;\r\n                \/\/ \u2705 v2.4.5 : Fallback sessionStorage \u2014 DOM pas encore mis \u00e0 jour (timing MutationObserver)\r\n                return !!sessionStorage.getItem('FormatSelect');\r\n            }\r\n            \/\/ v4.9ds : Autres espaces (Ele1A+) en mode popup \u2014 v\u00e9rifier le DOM r\u00e9el.\r\n            \/\/   AVANT : retournait true syst\u00e9matiquement \u2192 SelectionFormatTitre toujours masqu\u00e9,\r\n            \/\/   m\u00eame apr\u00e8s suppression+restauration d'une annonce dans cet espace.\r\n            \/\/   APR\u00c8S : on cherche un .EspPubFormatContainer (hors Cr\u00e9ation\/PopUp) avec fond\r\n            \/\/   blanc dans CET espace. Si aucun \u2192 return false \u2192 SelectionFormatTitre s'affiche.\r\n            var _hasDomFmtStd = $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').toArray().some(el => {\r\n                return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n            });\r\n            return _hasDomFmtStd;\r\n        }\r\n        \/\/ v4.9ds : exclure FormatIdCreation et FormatIdPopUp \u2014 ce ne sont pas de vrais formats\r\n        const $realFmts = $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp');\r\n        const hasWhiteBg = $realFmts.toArray().some(el => {\r\n            return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n        });\r\n        if (hasWhiteBg) return true;\r\n        \/\/ v4.9ds : si le DOM contient des containers de format dans CE droppable et qu'AUCUN n'est s\u00e9lectionn\u00e9,\r\n        \/\/   alors retourner false EXPLICITEMENT \u2014 ne pas se rabattre sur des flags sessionStorage globaux\r\n        \/\/   qui peuvent rester pos\u00e9s apr\u00e8s cr\u00e9ation dans un autre espace.\r\n        \/\/   Bug constat\u00e9 : apr\u00e8s fermeture du popup cr\u00e9ation (lanc\u00e9 depuis Ele0A), Formatchoisi\/FormatSelect\r\n        \/\/   restaient pos\u00e9s \u2192 SelectionFormatTitre disparaissait sur Ele1A+ alors qu'aucun format n'y \u00e9tait choisi.\r\n        if ($realFmts.length > 0) return false;\r\n        \/\/ Fallbacks sessionStorage UNIQUEMENT si le DOM n'a pas encore de containers (timing initial \/ pr\u00e9-rendu)\r\n        if (sessionStorage.getItem('Formatchoisi') === 'Yes') return true;\r\n        if (sessionStorage.getItem('FormatSelect')) return true;\r\n        return !!sessionStorage.getItem('_FormatSelectApplied');\r\n    },\r\n    \r\n    \/**\r\n     * Met \u00e0 jour la couleur du titre .SelectionFormatTitre\r\n     * Blanc si un format est s\u00e9lectionn\u00e9, rouge #FB5E2A sinon\r\n     *\/\r\n    updateTitleColor($droppable) {\r\n        \/\/ \u2705 v2.4.5 : Ele0A \u2014 ne jamais afficher SelectionFormatTitre\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes' ? $droppable.attr('id') === 'Ele0A' : false) {\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n            return;\r\n        }\r\n\r\n        if (this.hasSelectedFormat($droppable)) {\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n            console.log('\u2705 SelectionFormatTitreBlanc affich\u00e9 (format s\u00e9lectionn\u00e9)');\r\n        } else {\r\n            $droppable.find('.SelectionFormatTitre').show();\r\n            $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n            console.log('\u26a0\ufe0f SelectionFormatTitre affich\u00e9 (aucun format s\u00e9lectionn\u00e9)');\r\n        }\r\n\r\n        \/\/ \u2705 R\u00e8gle Cr\u00e9ation\/Vid\u00e9o : d\u00e9tecter si Vid\u00e9o est actif et d\u00e9sactiver\/r\u00e9activer Cr\u00e9ation\r\n        var _videoActive = false;\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            var _bg = jQuery(this).css('background-color') || '';\r\n            if (_bg === 'rgb(255, 255, 255)') { _videoActive = true; return false; }\r\n            var _fEl = this.querySelector('.EspPubFormat');\r\n            if (_fEl) { var _col = _fEl.style.color || ''; if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') { _videoActive = true; return false; } }\r\n        });\r\n        if (_videoActive) {\r\n            $droppable.find('.FormatIdCreation').each(function() {\r\n                jQuery(this).data('creationActive', false);\r\n                jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n                if ($lbl.text() !== 'D\u00e9sactiv\u00e9') { $lbl.text('D\u00e9sactiv\u00e9'); }\r\n            });\r\n            console.log('\ud83c\udfa8 updateTitleColor : Vid\u00e9o actif \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e');\r\n        } else {\r\n            $droppable.find('.FormatIdCreation').each(function() {\r\n                var _cur = jQuery(this).css('opacity');\r\n                if (_cur === '0.4') {\r\n                    jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                    var $lbl = jQuery(this).find('.EspPubFormat');\r\n                    var _orig = $lbl.attr('data-original-text');\r\n                    if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                }\r\n            });\r\n        }\r\n    },\r\n    \r\n    \/**\r\n     * Flash visuel sur le titre pour indiquer qu'un format est requis\r\n     *\/\r\n    flashTitle($element) {\r\n        const $droppable = $element.closest('.droppable');\r\n        const $titre = $droppable.find('.SelectionFormatTitre');\r\n        if (!$titre.length) return;\r\n        \r\n        \/\/ v2.6 : En mode popup (Ele0A), SelectionFormatTitre est display:none \u2192 le montrer temporairement\r\n        var _wasHidden = $titre.css('display') === 'none';\r\n        \/\/ v2.6 : Elementor impose display:none !important \u2192 setProperty requis\r\n        if (_wasHidden) { $titre.each(function() { this.style.setProperty('display','block','important'); }); }\r\n        \r\n        \/\/ Flash rouge rapide 3 fois\r\n        let count = 0;\r\n        const originalColor = $titre.css('color');\r\n        \r\n        const flash = setInterval(() => {\r\n            $titre.css('color', count % 2 === 0 ? '#FF0000' : '#FB5E2A');\r\n            count++;\r\n            if (count >= 6) {\r\n                clearInterval(flash);\r\n                $titre.css('color', '#FB5E2A');\r\n                if (_wasHidden) { setTimeout(function() { $titre.each(function() { this.style.setProperty('display','none','important'); }); }, 2000); }\r\n            }\r\n        }, 250);\r\n        \r\n        console.log('\u26a0\ufe0f Flash titre format - action bloqu\u00e9e (aucun format s\u00e9lectionn\u00e9)');\r\n    },\r\n    \r\n    \/**\r\n     * Met \u00e0 jour l'\u00e9tat de la checkbox \"R\u00e9server cet espace publicitaire\"\r\n     * La checkbox est activable si : format s\u00e9lectionn\u00e9 ET (fichier d\u00e9pos\u00e9 OU envoi diff\u00e9r\u00e9)\r\n     *\/\r\n    updateReserverCheckboxState($droppable) {\r\n        \/\/ \u2705 Chercher le label : dynamique ou Elementor statique\r\n        let $label = null;\r\n        let $container = null;\r\n        \r\n        \/\/ 1. Bouton dynamique dans le droppable\r\n        $container = $droppable.find('.reserver-dynamic-container');\r\n        if (!$container.length) {\r\n            $container = $droppable.next('.reserver-dynamic-container');\r\n        }\r\n        if (!$container.length) {\r\n            \/\/ Mobile : checkbox attach\u00e9e au body avec data-droppable-id\r\n            $container = $('body > .reserver-dynamic-container[data-droppable-id=\"' + $droppable.attr('id') + '\"]');\r\n        }\r\n        if (!$container.length) {\r\n            $container = $('body > .reserver-dynamic-container');\r\n        }\r\n        \r\n        if ($container.length) {\r\n            $label = $container.find('.reserver-dynamic-label');\r\n            $container.find('.reserver-dynamic-option, .elementor-field-option').css('opacity', '1');\r\n        }\r\n        \r\n        \/\/ 2. Bouton Elementor statique - chercher dans le droppable puis globalement\r\n        let $reserverStatique = $droppable.find('.elementor-field-group-ReserverEspacePublicitaire label');\r\n        if (!$reserverStatique.length) {\r\n            $reserverStatique = $droppable.find('label[for^=\"form-field-ReserverEspacePublicitaire\"]');\r\n        }\r\n        if (!$reserverStatique.length) {\r\n            \/\/ Fallback global : le formulaire peut \u00eatre en dehors du droppable\r\n            $reserverStatique = $('.elementor-field-group-ReserverEspacePublicitaire .elementor-field-option label');\r\n        }\r\n        \r\n        const hasFormat = this.hasSelectedFormat($droppable);\r\n        \/\/ v2.9 : data-via-ad-loaded est specifique au droppable, plus fiable que FileReceived global\r\n        const hasFile = StateManager.get('FileReceived') === 'Yes'\r\n            || $droppable.attr('data-via-ad-loaded') === 'true';\r\n        const hasEnvoiDiffere = $droppable.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        \r\n        const canReserve = hasFormat ? (hasFile || hasEnvoiDiffere) : false;\r\n        \r\n        \/\/ \u2705 Si la checkbox R\u00e9server est coch\u00e9e \u2192 label vert \"Espace publicitaire r\u00e9serv\u00e9\"\r\n        const isChecked = $droppable.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked') ||\r\n                          ($container.length ? $container.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked') : false);\r\n        \/\/ \u2705 v2.4.7 : Signal de restauration r\u00e9serv\u00e9e \u2014 checkbox pas encore coch\u00e9e (setTimeout 150ms)\r\n        \/\/             mais on sait d\u00e9j\u00e0 que l'espace est r\u00e9serv\u00e9 \u2192 traiter comme coch\u00e9\r\n        const _isReservedRestoration = sessionStorage.getItem('_isReservedRestoration') === 'Yes';\r\n        const isCheckedOrReservedRestore = isChecked || _isReservedRestoration;\r\n        \r\n        if ($label ? $label.length : false) {\r\n            if (isCheckedOrReservedRestore) {\r\n                $label.text('Espace publicitaire r\u00e9serv\u00e9').css('color', 'rgb(62, 170, 19)');\r\n            } else {\r\n                $label.text('R\u00e9server cet espace publicitaire').css('color', canReserve ? '#FB5E2A' : '');\r\n            }\r\n        }\r\n        if ($reserverStatique.length) {\r\n            if (isCheckedOrReservedRestore) {\r\n                $reserverStatique.attr('style', 'color: rgb(62, 170, 19) !important;');\r\n            } else if (canReserve) {\r\n                $reserverStatique.attr('style', 'color: #FB5E2A !important;');\r\n            } else {\r\n                $reserverStatique.removeAttr('style');\r\n            }\r\n        }\r\n        \r\n        console.log('\ud83d\udd04 updateReserverCheckboxState:', { hasFormat, hasFile, hasEnvoiDiffere, canReserve, dynamicLabel: !!($label ? $label.length : false), staticLabel: $reserverStatique.length });\r\n    },\r\n    \r\n    \/**\r\n     * Initialise les listeners pour le changement de format et la checkbox R\u00e9server\r\n     *\/\r\n    init() {\r\n        \/\/ \u2705 \u00c9couter les clics sur les conteneurs de format et leurs parents\r\n        jQuery(document).on('click', '.EspPubFormatContainer', (e) => {\r\n            const $droppable = jQuery(e.target).closest('.droppable');\r\n            if (!$droppable.length) return;\r\n\r\n            \/\/ v4.9ds : stocker le format choisi sur le .droppable (gate ind\u00e9pendant)\r\n            \/\/          Exclusion : Creation et PopUp ne sont pas de vrais formats \u2192 ne pas\r\n            \/\/          d\u00e9verrouiller les zones (glisser-d\u00e9poser, envoi diff\u00e9r\u00e9, r\u00e9server)\r\n            const _$btn = jQuery(e.target).closest('.EspPubFormatContainer');\r\n            const _classes = _$btn.attr('class') || '';\r\n            const _m = _classes.match(\/FormatId(\\w+)\/);\r\n            if (_m && _m[1] && _m[1] !== 'Creation' && _m[1] !== 'PopUp') {\r\n                $droppable.attr('data-via-current-format', _m[1]);\r\n                \/\/ Notifier le parent pour mettre \u00e0 jour le format-gate (CSS top-level)\r\n                try {\r\n                    var _topW = window.top || window;\r\n                    if (_topW && typeof _topW._viaSetDroppableFormat === 'function') {\r\n                        _topW._viaSetDroppableFormat($droppable.attr('id'), _m[1]);\r\n                    }\r\n                } catch(_e) {}\r\n            }\r\n\r\n            \/\/ \u2705 Masquer les messages d'erreur format d\u00e8s la s\u00e9lection d'un format\r\n            jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n            jQuery('#fmt-creation-info').remove();\r\n            \r\n            \/\/ v4.9ds : appeler _viaUpdateFormatGate pour TOUS les clics (incluant Cr\u00e9ation\/PopUp)\r\n            \/\/   afin de mettre \u00e0 jour data-via-title-state=\"chosen\" \u2192 CSS SelectionFormatTitre\r\n            \/\/   (transparent + blanc) prend effet aussi pour Cr\u00e9ation.\r\n            [50, 200, 500].forEach(function(delay) {\r\n                setTimeout(function() {\r\n                    try {\r\n                        var _topW2 = window.top || window;\r\n                        if (_topW2 ? typeof _topW2._viaUpdateFormatGate === 'function' : false) {\r\n                            _topW2._viaUpdateFormatGate();\r\n                        }\r\n                    } catch (_e) {}\r\n                }, delay);\r\n            });\r\n\r\n            \/\/ Multiples d\u00e9lais pour couvrir les traitements asynchrones\r\n            [50, 200, 500].forEach(delay => {\r\n                setTimeout(() => {\r\n                    this.updateTitleColor($droppable);\r\n                    this.updateReserverCheckboxState($droppable);\r\n                }, delay);\r\n            });\r\n\r\n            \/\/ \u2705 v2.2.0 : Si un format (non Cr\u00e9ation) est cliqu\u00e9, notifier le parent pour mettre \u00e0 jour le Kit\r\n            const $btn = jQuery(e.target).closest('.EspPubFormatContainer');\r\n            if ($btn.length ? (!$btn.hasClass('FormatIdCreation') ? !$btn.hasClass('FormatIdPopUp') : false) : false) {\r\n                \/\/ \u2705 v2.3.4 : FormatIdPopUp g\u00e9r\u00e9 par clickFormatFromIframe (Entete.txt) \u2014 ne pas doubler\r\n                var classes = $btn.attr('class') || '';\r\n                var match = classes.match(\/FormatId(\\w+)\/);\r\n                if (match ? match[1] : false) {\r\n                    var _rankEP = $droppable.attr('id') || '';\r\n                    setTimeout(function() {\r\n                        MessageManager.sendToParent('formatChangedInIframe', { formatSelect: match[1], rank: _rankEP });\r\n                        console.log('\ud83d\udd04 formatChangedInIframe envoy\u00e9:', match[1], '| rank:', _rankEP);\r\n                    }, 100);\r\n                }\r\n\r\n                \/\/ \u2705 Si Cr\u00e9ation est d\u00e9j\u00e0 actif ET format valide (non Vid\u00e9o) \u2192 ouvrir le popup\r\n                var $creationBtn = $droppable.find('.FormatIdCreation');\r\n                var _creationActive = $creationBtn.data('creationActive') === true;\r\n                var _isVideoBtn = $btn.hasClass('FormatIdVideo');\r\n                if (_creationActive ? !_isVideoBtn : false) {\r\n                    var _rankKit2 = _rankEP;\r\n                    var _cSite2 = sessionStorage.getItem('codeSite') || '';\r\n                    var _cPage2 = sessionStorage.getItem('codePage') || '';\r\n                    var _sfx2 = _rankKit2.replace('Ele', '');\r\n                    var _empl2 = (_cSite2 ? (_cPage2 ? _sfx2 : false) : false) ? (_cSite2 + _cPage2 + 'L' + _sfx2) : (sessionStorage.getItem('Commande_Emplacement_Page_Web') || '');\r\n                    var _fmt2 = sessionStorage.getItem('FormatSelect') || '';\r\n                    var _fmtT2 = sessionStorage.getItem('Commande_Format_Transmis') || '';\r\n                    \/\/ R\u00e9activer Vid\u00e9o + restaurer texte si d\u00e9sactiv\u00e9e\r\n                    $droppable.find('.FormatIdVideo').each(function() {\r\n                        jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                        var $lbl = jQuery(this).find('.EspPubFormat');\r\n                        var _orig = $lbl.attr('data-original-text');\r\n                        if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                    });\r\n                    setTimeout(function() {\r\n                        MessageManager.sendToParent('openAdCreatorFromIframe', { formatSelect: _fmt2, formatTransmis: _fmtT2, rankId: _rankKit2, emplacement: _empl2 });\r\n                        console.log('\ud83c\udfa8 Cr\u00e9ation d\u00e9j\u00e0 actif + format s\u00e9lectionn\u00e9 \u2192 ouverture popup apr\u00e8s 2s | rank:', _rankKit2);\r\n                    }, 2000);\r\n                }\r\n            }\r\n        });\r\n        \r\n        \/\/ \u2705 MutationObserver sur les conteneurs de format pour d\u00e9tecter les changements de style\r\n        setTimeout(() => {\r\n            this.observeFormatChanges();\r\n            \r\n            \/\/ Initialiser les couleurs des titres au chargement\r\n            jQuery('.droppable').each((i, el) => {\r\n                this.updateTitleColor(jQuery(el));\r\n            });\r\n            \r\n            \/\/ \u2705 v2.3.4 : Popup \u2192 restaurer FormatIdPopUp + format sous-jacent (sans \u00e9craser applyFormatState)\r\n            if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n                jQuery('.FormatIdPopUp').css({'background-color': '#ffffff'});\r\n                jQuery('.FormatIdPopUp').find('.EspPubFormat').css({'color': '#37D900'});\r\n                \/\/ Restaurer aussi le format sous-jacent dans Ele0A\r\n                var _fmtSS = sessionStorage.getItem('FormatSelect') || '';\r\n                if (_fmtSS) {\r\n                    var _fmtSSNorm = _fmtSS.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                    jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdCreation').each(function() {\r\n                        var _cls = this.className.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                        if (_cls.includes(_fmtSSNorm)) {\r\n                            jQuery(this).css({'background-color': '#ffffff'});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        }\r\n                    });\r\n                }\r\n                jQuery('.droppable').each((i, el) => {\r\n                    this.updateTitleColor(jQuery(el));\r\n                });\r\n            }\r\n        }, 500);\r\n        \r\n        \/\/ \u2705 v1.16.0 : Re-v\u00e9rifier apr\u00e8s un d\u00e9lai plus long (format venant de l'accord\u00e9on via postMessage)\r\n        [1500, 3000].forEach(delay => {\r\n            setTimeout(() => {\r\n                \/\/ \u2705 v2.3.4 : Popup \u2192 restaurer FormatIdPopUp + format sous-jacent\r\n                if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n                    jQuery('.FormatIdPopUp').css({'background-color': '#ffffff'});\r\n                    jQuery('.FormatIdPopUp').find('.EspPubFormat').css({'color': '#37D900'});\r\n                    var _fmtSSR = sessionStorage.getItem('FormatSelect') || '';\r\n                    if (_fmtSSR) {\r\n                        var _fmtSSRNorm = _fmtSSR.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                        jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdCreation').each(function() {\r\n                            var _cls = this.className.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                            if (_cls.includes(_fmtSSRNorm)) {\r\n                                jQuery(this).css({'background-color': '#ffffff'});\r\n                                jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                            }\r\n                        });\r\n                    }\r\n                    jQuery('.droppable').each((i, el) => {\r\n                        this.updateTitleColor(jQuery(el));\r\n                    });\r\n                    return;\r\n                }\r\n                if (sessionStorage.getItem('Formatchoisi') === 'Yes') {\r\n                    jQuery('.droppable').each((i, el) => {\r\n                        this.updateTitleColor(jQuery(el));\r\n                    });\r\n                }\r\n            }, delay);\r\n        });\r\n    },\r\n    \r\n    \/**\r\n     * Attache un MutationObserver sur chaque .EspPubFormatContainer\r\n     * pour d\u00e9tecter tout changement de style (background-color) \r\n     * quel que soit le script qui le d\u00e9clenche\r\n     *\/\r\n    observeFormatChanges() {\r\n        \/\/ \u2705 Guard global anti-boucle \u2014 un seul verrou pour tous les observers\r\n        window._formatObserverLocked = false;\r\n        \r\n        jQuery('.EspPubFormatContainer').each((i, el) => {\r\n            const observer = new MutationObserver(() => {\r\n                if (window._formatObserverLocked) return;\r\n                window._formatObserverLocked = true;\r\n                clearTimeout(window._formatObserverTimer);\r\n                window._formatObserverTimer = setTimeout(() => {\r\n                    const $droppable = jQuery(el).closest('.droppable');\r\n                    this.updateTitleColor($droppable);\r\n                    this.updateReserverCheckboxState($droppable);\r\n\r\n                    \/\/ \u2705 R\u00e8gle Cr\u00e9ation\/Vid\u00e9o : si Vid\u00e9o est actif \u2192 d\u00e9sactiver Cr\u00e9ation\r\n                    var _videoActive = false;\r\n                    $droppable.find('.FormatIdVideo').each(function() {\r\n                        var _bg = this.style.backgroundColor || '';\r\n                        var _isWhite = _bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white';\r\n                        if (!_isWhite) {\r\n                            var _fEl = this.querySelector('.EspPubFormat');\r\n                            if (_fEl) { var _col = _fEl.style.color || ''; _isWhite = _col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900'; }\r\n                        }\r\n                        if (_isWhite) { _videoActive = true; return false; }\r\n                    });\r\n                    if (_videoActive) {\r\n                        $droppable.find('.FormatIdCreation').each(function() {\r\n                            jQuery(this).data('creationActive', false);\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                            jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n                            var $lbl = jQuery(this).find('.EspPubFormat');\r\n                            if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n                            $lbl.text('D\u00e9sactiv\u00e9');\r\n                        });\r\n                    } else {\r\n                        \/\/ R\u00e9activer Cr\u00e9ation si Vid\u00e9o n'est plus actif\r\n                        $droppable.find('.FormatIdCreation').each(function() {\r\n                            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                            var $lbl = jQuery(this).find('.EspPubFormat');\r\n                            var _orig = $lbl.attr('data-original-text');\r\n                            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                        });\r\n                    }\r\n\r\n                    window._formatObserverLocked = false;\r\n                }, 150);\r\n            });\r\n            observer.observe(el, { \r\n                attributes: true, \r\n                attributeFilter: ['style', 'class'] \r\n            });\r\n        });\r\n        console.log('\u2705 MutationObserver attach\u00e9 aux conteneurs de format');\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de communication avec la page parente\r\n *\/\r\nconst MessageManager = {\r\n    sendToParent(type, data = {}) {\r\n        try {\r\n            window.parent.postMessage({\r\n                type: type,\r\n                iframeId: CONFIG.iframeId,\r\n                data: data\r\n            }, '*');\r\n        } catch (error) {\r\n            console.error('Error sending message to parent:', error);\r\n            window.parent.postMessage({\r\n                type: 'error',\r\n                error: error.message,\r\n                iframeId: CONFIG.iframeId\r\n            }, '*');\r\n        }\r\n    },\r\n    \r\n    sendDataToParent(data) {\r\n        this.sendToParent('dataFromIframeEspacePub', data);\r\n    },\r\n    \r\n    sendDelAdToParent(data) {\r\n        this.sendToParent('dataDelAd', data);\r\n    },\r\n    \r\n    prepareUploadData() {\r\n        const keys = [\r\n            'AddNewRefInVosCampagnes',\r\n            'FirstUploadFileorMoved',\r\n            'LoadedPageUrl',\r\n            'codePage',\r\n            'Rank_Emplacement_Page_Web',\r\n            'Commande_Emplacement_Page_Web',\r\n            'dragstart_Commande_Emplacement_Page_Web',\r\n            'dragstart_Rank_Emplacement_Page_Web',  \r\n            'FullPathAdFile',\r\n            'Upload_File_Name',\r\n            'FileReceived',\r\n            'PageWebDisplayed',\r\n            'Commande_Format_Transmis',\r\n            'EspPubLienAnnonce',\r\n            'EnvoiUlterieur',\r\n            'Formatchoisi'\r\n        ];\r\n        \r\n        const data = StateManager.getMultiple(keys);\r\n        \r\n        \/\/ v4.9ds : r\u00e9cup\u00e9ration FullPathAdFile par rank depuis via_fullpath localStorage.\r\n        \/\/   StateManager.FullPathAdFile est GLOBAL (dernier upload). Dans le sc\u00e9nario\r\n        \/\/   \"drop Ele0A + drop Ele1A + R\u00e9server Ele0A + R\u00e9server Ele1A\", au clic R\u00e9server\r\n        \/\/   Ele1A, StateManager.FullPathAdFile peut contenir la mauvaise valeur (Ele0A\r\n        \/\/   s\u00e9lectionn\u00e9 apr\u00e8s) ou \u00eatre vide. via_fullpath localStorage stocke par rank\r\n        \/\/   {Ele0A: URL_Ele0A, Ele1A: URL_Ele1A} \u2014 source de v\u00e9rit\u00e9 plus fiable.\r\n        try {\r\n            const _rankCur = data.Rank_Emplacement_Page_Web || '';\r\n            if (_rankCur) {\r\n                const _fpMap = JSON.parse(localStorage.getItem('via_fullpath') || '{}');\r\n                const _fpForRank = _fpMap[_rankCur];\r\n                if (_fpForRank) {\r\n                    if (data.FullPathAdFile !== _fpForRank) {\r\n                        console.log('\ud83d\udd27 [prepareUploadData] FullPathAdFile \u00e9cras\u00e9 depuis via_fullpath | rank:', _rankCur,\r\n                            '| ancien:', data.FullPathAdFile || '(vide)', '| nouveau:', _fpForRank);\r\n                    }\r\n                    data.FullPathAdFile = _fpForRank;\r\n                }\r\n            }\r\n        } catch(_eFP) { console.warn('[prepareUploadData] lecture via_fullpath \u00e9chou\u00e9e:', _eFP); }\r\n        \r\n        \/\/ v4.9ds : flag HasAdLoadedDom bas\u00e9 sur l'attribut data-via-ad-loaded du droppable\r\n        \/\/   correspondant \u00e0 l'emplacement courant. Permet \u00e0 Panier_manager.txt de\r\n        \/\/   distinguer \"annonce visuellement d\u00e9pos\u00e9e mais pas upload\u00e9e serveur\" (drop\r\n        \/\/   miniature \u2192 image dans DOM, FileReceived='No') du cas \"rien d\u00e9pos\u00e9\".\r\n        try {\r\n            const _emplCur = data.Commande_Emplacement_Page_Web || '';\r\n            if (_emplCur) {\r\n                const _$dropCur = jQuery('.droppable').filter(function() {\r\n                    return jQuery(this).attr('data-via-empl') === _emplCur\r\n                        || (jQuery(this).attr('id') === 'Ele0A' ? _emplCur.indexOf('L0A') >= 0 : false);\r\n                }).first();\r\n                \/\/ Fallback : matcher par rank si data-via-empl absent\r\n                if (!_$dropCur.length ? data.Rank_Emplacement_Page_Web : false) {\r\n                    const _$byRank = jQuery('.droppable[data-via-rank=\"' + data.Rank_Emplacement_Page_Web + '\"]').first();\r\n                    if (_$byRank.length) {\r\n                        data.HasAdLoadedDom = _$byRank.attr('data-via-ad-loaded') === 'true' ? 'Yes' : 'No';\r\n                    }\r\n                } else if (_$dropCur.length) {\r\n                    data.HasAdLoadedDom = _$dropCur.attr('data-via-ad-loaded') === 'true' ? 'Yes' : 'No';\r\n                }\r\n                \/\/ Dernier fallback : tous les droppables avec data-via-ad-loaded=true \u2192 si un seul, c'est lui\r\n                if (data.HasAdLoadedDom === undefined) {\r\n                    const _$any = jQuery('.droppable[data-via-ad-loaded=\"true\"]');\r\n                    data.HasAdLoadedDom = _$any.length > 0 ? 'Yes' : 'No';\r\n                }\r\n            }\r\n        } catch(_eAd) { data.HasAdLoadedDom = 'No'; }\r\n        \r\n        \/\/ \u2705 v2.3.4 : Popup (Ele0A) \u2014 lire le format sous-jacent depuis le DOM\r\n        \/\/ StateManager contient 'PopUp' (virtuel), mais le vrai format est visuellement s\u00e9lectionn\u00e9 dans #Ele0A\r\n        if (data.Rank_Emplacement_Page_Web === 'Ele0A' || sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            var _ele0AFormatDOM = '';\r\n            jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdPopUp').each(function() {\r\n                if (jQuery(this).css('background-color') === 'rgb(255, 255, 255)') {\r\n                    var _cls = this.className || '';\r\n                    var _match = _cls.match(\/FormatId(\\S+)\/);\r\n                    if (_match) { _ele0AFormatDOM = _match[1]; return false; }\r\n                }\r\n            });\r\n            if (_ele0AFormatDOM) {\r\n                data.Commande_Format_Transmis = _ele0AFormatDOM;\r\n            }\r\n        }\r\n        \r\n        console.log('\ud83d\udce4 prepareUploadData:', {\r\n            FirstUploadFileorMoved: data.FirstUploadFileorMoved,\r\n            dragstart_Commande_Emplacement_Page_Web: data.dragstart_Commande_Emplacement_Page_Web,\r\n            Commande_Emplacement_Page_Web: data.Commande_Emplacement_Page_Web\r\n        });\r\n    \r\n        return data;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de rendu des aper\u00e7us de fichiers\r\n *\/\r\nconst PreviewRenderer = {\r\n    _getPositionLibelle(rankId) {\r\n        if (!rankId) return '';\r\n        var match = rankId.match(\/Ele(\\d+)[A-Z]\/);\r\n        if (!match) return '';\r\n        var rang = parseInt(match[1]);\r\n        if (rang === 0) return 'Pop-up';\r\n        if (rang === 1 || rang === 2) return 'Haut de page';\r\n        return 'Corps de page';\r\n    },\r\n    renderVideo(objectUrl, fileName, $dropZone) {\r\n        const videoElement = jQuery('<video controls autoplay muted>').attr({\r\n            'src': objectUrl,\r\n            'width': 'auto',\r\n            'height': 'auto',\r\n            'id': fileName\r\n        }).css({\r\n            'max-width': '100%',\r\n            'max-height': '100%'\r\n        });\r\n        \r\n        if (UIManager.isDesktop()) {\r\n            $dropZone.empty().append(videoElement.clone());\r\n            jQuery('#ApercuMobile').css({\r\n                'display': 'flex',\r\n                'justify-content': 'center',\r\n                'align-items': 'center'\r\n            }).append(videoElement);\r\n        } else {\r\n            const img = document.createElement('img');\r\n            img.src = objectUrl;\r\n            img.style.width = 'auto';\r\n            img.style.height = 'auto';\r\n            img.style.maxWidth = 'calc(100% - 4px)';  \/\/ \u2705 v2.4.5 : 2px retrait inset box-shadow\r\n            img.style.maxHeight = '115px';\r\n            \r\n            const container = $dropZone[0];\r\n            while (container.firstChild) {\r\n                container.removeChild(container.firstChild);\r\n            }\r\n            container.appendChild(img);\r\n        }\r\n        \r\n        StateManager.setMultiple({\r\n            'Commande_Format': 'Vid\u00e9o',\r\n            'Commande_Format_Transmis': 'Vid\u00e9o',\r\n            'videoSrc': objectUrl,\r\n            'FormatAnnonceSelection': 'Yes',\r\n            'PositionAnnonceSelection': 'Yes'\r\n        });\r\n        \r\n        jQuery('#FormatDataStep3').html('Vid\u00e9o').css({'color': '#56BE50'});\r\n        jQuery('#TarifDataStep3').css({'color': '#96894D'});\r\n    },\r\n    \r\n    renderImage(objectUrl, $dropZone) {\r\n        \/\/ v4.9ds : utiliser la hauteur du dropZone lui-m\u00eame (pos\u00e9e par _applyDzMinH)\r\n        \/\/   au lieu de la hauteur du HTMLUploadfileConteneur parent.\r\n        \/\/   Le UFC inclut header (~24px) + footer (~46px) en plus du dropZone, donc utiliser\r\n        \/\/   sa hauteur produit une image plus haute que le dropZone \u2192 crop par overflow:hidden\r\n        \/\/   du wrapper. La hauteur du dropZone est la cible exacte de la zone visible.\r\n        let maxHeight;\r\n        if (UIManager.isMobile()) {\r\n            maxHeight = 105;\r\n        } else {\r\n            \/\/ Lire la hauteur effective du dropZone (d\u00e9j\u00e0 pos\u00e9e par _applyDzMinH au moment du d\u00e9p\u00f4t)\r\n            var _dzH = $dropZone.height() || 0;\r\n            if (_dzH > 50) {\r\n                maxHeight = _dzH;\r\n            } else {\r\n                \/\/ Fallback : lire UFC mais retirer estimation header+footer (~70px)\r\n                var _ufcH = $dropZone.closest('.HTMLUploadfileConteneur').height() || 0;\r\n                maxHeight = Math.max(0, _ufcH - 70);\r\n                if (maxHeight < 50) { maxHeight = 200; }  \/\/ dernier recours\r\n            }\r\n        }\r\n    \r\n        $dropZone.css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center',\r\n            'width': '100%',\r\n            'height': maxHeight + 'px',\r\n            'overflow': 'hidden',\r\n            'position': UIManager.isMobile() ? 'relative' : '',\r\n            'top': UIManager.isMobile() ? '45px' : '',\r\n            'padding': UIManager.isMobile() ? '0 2px' : ''  \/\/ \u2705 v2.4.5 : 2px retrait inset box-shadow mobile\r\n        });\r\n    \r\n        \/\/ \u2705 v1.19.2 : Image charg\u00e9e en arri\u00e8re-plan, remplace le message d'attente au onload\r\n        \/\/ \u2705 v2.4.5 : max-width calc(100%-4px) sur mobile pour ne pas recouvrir le box-shadow inset 2px\r\n        \/\/ v4.9ds : width:100% + height:100% + object-fit:contain \u2014 l'image remplit la box du\r\n        \/\/   dropZone (comme Ele0A) tout en restant enti\u00e8re. Letterbox automatique si ratio diff.\r\n        var _mxw = UIManager.isMobile() ? 'calc(100% - 4px)' : '100%';\r\n        var img = new Image();\r\n        img.style.cssText = 'max-width:' + _mxw + '; max-height:' + maxHeight + 'px; width:100%; height:100%; object-fit:contain;';\r\n        img.onload = function() {\r\n            $dropZone.empty().append(img);\r\n        };\r\n        img.onerror = function() {\r\n            $dropZone.html('<img decoding=\"async\" src=\"' + objectUrl + '\" style=\"max-width:' + _mxw + '; max-height:' + maxHeight + 'px; width:100%; height:100%; object-fit:contain;\">');\r\n        };\r\n        img.src = objectUrl;\r\n    \r\n        StateManager.setMultiple({\r\n            'Commande_Format_Transmis': 'Image',\r\n            'FormatAnnonceSelection': 'Yes',\r\n            'PositionAnnonceSelection': 'Yes'\r\n        });\r\n    \r\n        console.log('Image ajout\u00e9e avec succ\u00e8s');\r\n    },\r\n    \r\n    async renderWord(fileObj, $dropZone) {\r\n        return new Promise((resolve, reject) => {\r\n            const reader = new FileReader();\r\n            \r\n            reader.onload = async (e) => {\r\n                try {\r\n                    const arrayBuffer = e.target.result;\r\n                    const result = await mammoth.convertToHtml({arrayBuffer: arrayBuffer});\r\n                    \r\n                    const htmlContent = result.value;\r\n                    const tempContainer = document.createElement('div');\r\n                    tempContainer.innerHTML = htmlContent;\r\n                    \r\n                    const textContent = tempContainer.textContent || \"\";\r\n                    const normalizedText = this.normalizeText(textContent);\r\n                    const firstImage = tempContainer.querySelector('img');\r\n                    \r\n                    const titleText = this.findDocumentTitle(normalizedText);\r\n                    \r\n                    if (firstImage) {\r\n                        this.renderDocumentPreview(\r\n                            $dropZone,\r\n                            firstImage.src,\r\n                            titleText,\r\n                            textContent,\r\n                            htmlContent\r\n                        );\r\n                    } else {\r\n                        \/\/ \u2705 v1.19.0 : Accepter les documents sans image\r\n                        this.renderDocumentPreview(\r\n                            $dropZone,\r\n                            null,\r\n                            titleText,\r\n                            textContent,\r\n                            htmlContent\r\n                        );\r\n                        console.log('\u2139\ufe0f Document Word sans image \u2014 aper\u00e7u texte seul');\r\n                    }\r\n                    resolve();\r\n                } catch (error) {\r\n                    $dropZone.html(`<p style=\"color: #FB5E2A; font-weight: 600; font-family: Roboto, Arial, sans-serif; font-size: 12px; text-align: center; padding: 15px;\">Erreur lors de la lecture du document<\/p>`);\r\n                    reject(error);\r\n                }\r\n            };\r\n            \r\n            reader.readAsArrayBuffer(fileObj);\r\n        });\r\n        \r\n        this.finalizeRedactionnelUpload();\r\n    },\r\n    \r\n    renderDocumentPreview($dropZone, imageSrc, title, text, htmlContent = null) {\r\n        \/\/ \u2705 v1.19.0 : Deux layouts \u2014 avec image ou texte seul\r\n        let previewHtml;\r\n\r\n        if (imageSrc) {\r\n            \/\/ Layout avec miniature image\r\n            previewHtml = `\r\n                <div class=\"doc-preview-container\">\r\n                    <div class=\"doc-preview-thumbnail\">\r\n                        <img decoding=\"async\" src=\"${imageSrc}\" alt=\"Document image\">\r\n                    <\/div>\r\n                    <div class=\"doc-preview-info\">\r\n                        <div class=\"doc-preview-title\" id=\"AdTitle\">${title}<\/div>\r\n                        <div class=\"doc-preview-description\" id=\"AdText\">${this.getTextPreview(text, 13)}<\/div>\r\n                        <div class=\"doc-preview-readmore\" id=\"AdLirelasuite\">Ouvrir et visualiser<\/div>\r\n                        <div class=\"doc-preview-FullPathAdFile\" id=\"AdFullPathAdFile\">${StateManager.get(\"FullPathAdFile\")}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n        } else {\r\n            \/\/ Layout texte seul (sans image)\r\n            previewHtml = `\r\n                <div class=\"doc-preview-container doc-preview-noimage\">\r\n                    <div class=\"doc-preview-icon\">\r\n                        <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#213864\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n                            <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"\/><polyline points=\"14 2 14 8 20 8\"\/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"\/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"\/><polyline points=\"10 9 9 9 8 9\"\/>\r\n                        <\/svg>\r\n                    <\/div>\r\n                    <div class=\"doc-preview-info doc-preview-info-full\">\r\n                        <div class=\"doc-preview-title\" id=\"AdTitle\">${title}<\/div>\r\n                        <div class=\"doc-preview-description\" id=\"AdText\">${this.getTextPreview(text, 25)}<\/div>\r\n                        <div class=\"doc-preview-readmore\" id=\"AdLirelasuite\">Ouvrir et visualiser<\/div>\r\n                        <div class=\"doc-preview-FullPathAdFile\" id=\"AdFullPathAdFile\">${StateManager.get(\"FullPathAdFile\")}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n        }\r\n        \r\n        $dropZone.html(previewHtml + this.getPreviewStyles());\r\n        \r\n        if (htmlContent) {\r\n            this.attachWordPreviewHandler($dropZone, title, htmlContent);\r\n        }\r\n    },\r\n    \r\n    attachWordPreviewHandler($dropZone, titleText, htmlContent) {\r\n        var $droppable = $dropZone.closest('.droppable');\r\n\r\n        \/\/ \u2705 Adapter le libell\u00e9 selon le type de document\r\n        var isInterview = (titleText || '').toLowerCase().indexOf('interview') !== -1;\r\n        var formatLabel = isInterview ? 'l\\'interview' : 'le communiqu\u00e9';\r\n        $droppable.find('.doc-preview-readmore').text('Ouvrir et visualiser ' + formatLabel);\r\n\r\n        \/\/ \u2705 v2.0.11 : D\u00e9l\u00e9gation d'\u00e9v\u00e9nement \u2014 plus robuste sur mobile (survit aux manipulations DOM)\r\n        $droppable.off('click.docpreview').on('click.docpreview', '.doc-preview-readmore', (event) => {\r\n            event.preventDefault();\r\n            event.stopPropagation();\r\n            \r\n            if (htmlContent) {\r\n                var popupTitle = isInterview ? 'Interview' : 'Communiqu\u00e9';\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    htmlContent: htmlContent\r\n                });\r\n            } else {\r\n                console.warn('Le document est encore en cours de traitement.');\r\n            }\r\n        });\r\n    },\r\n    \r\n    normalizeText(text) {\r\n        return text\r\n            .toLowerCase()\r\n            .normalize(\"NFD\")\r\n            .replace(\/[\\u0300-\\u036f]\/g, \"\");\r\n    },\r\n    \r\n    findDocumentTitle(normalizedText) {\r\n        const foundKeyword = CONFIG.keywords.find(keyword => \r\n            normalizedText.includes(keyword.normal)\r\n        );\r\n        if (foundKeyword) return foundKeyword.display;\r\n        \r\n        \/\/ \u2705 v1.19.1 : Fallback \u2014 utiliser le format s\u00e9lectionn\u00e9 (FormatSelect)\r\n        var formatSelect = (sessionStorage.getItem('FormatSelect') || '').toLowerCase();\r\n        if (formatSelect.indexOf('interview') !== -1) return 'Interview';\r\n        if (formatSelect.indexOf('communiq') !== -1) return 'Communiqu\u00e9';\r\n        \r\n        return \"Document\";\r\n    },\r\n    \r\n    getTextPreview(text, maxWords) {\r\n        const words = text.split(' ');\r\n        if (words.length > maxWords) {\r\n            return words.slice(0, maxWords).join(' ') + ' ...';\r\n        }\r\n        return text;\r\n    },\r\n    \r\n    getPreviewStyles() {\r\n        return `\r\n            <style>\r\n                @import url('https:\/\/fonts.googleapis.com\/css2?family=Roboto:wght@400;600&display=swap');\r\n                \r\n                .doc-preview-container {\r\n                    \/* v4.9ds : -4px largeur + 2px de chaque c\u00f4t\u00e9 pour laisser voir le liser\u00e9 vert *\/\r\n                    margin: 0px 2px;\r\n                    display: flex;\r\n                    width: calc(100% - 4px);\r\n                    max-height: 140px;\r\n                    overflow: hidden;\r\n                    box-sizing: border-box;\r\n                    background: white;\r\n                    background-color: #f8f8f8;\r\n                }\r\n                .doc-preview-thumbnail {\r\n                    width: 50%; \r\n                    height: 130px;\r\n                    overflow: hidden;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                    margin-top: 0px;\r\n                }\r\n                .doc-preview-thumbnail img {\r\n                    max-width: 75%;\r\n                    max-height: 75%;\r\n                    object-position: center;\r\n                    object-fit: fill;\r\n                }\r\n                .doc-preview-info {\r\n                    width: 50%;\r\n                    height: 130px; \r\n                    flex: 1;\r\n                    padding: 10px;\r\n                    display: flex;\r\n                    flex-direction: column; \r\n                }\r\n                .doc-preview-title {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #213864;\r\n                    font-size: 14px;\r\n                    font-weight: 600;\r\n                    text-align: left;\r\n                    margin-bottom: 5px;\r\n                }\r\n                .doc-preview-description {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #213864;\r\n                    font-size: 11px;\r\n                    font-weight: 400;\r\n                    text-align: left;\r\n                    overflow: hidden;\r\n                    margin-bottom: 5px;\r\n                    flex: 1;\r\n                }\r\n                .doc-preview-readmore {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #958848;\r\n                    font-size: 13px;\r\n                    font-weight: 600;\r\n                    text-align: left;\r\n                    cursor: pointer;\r\n                    padding: 4px 0;\r\n                    -webkit-tap-highlight-color: rgba(149,136,72,0.2);\r\n                }\r\n                @media (max-width: 999px) {\r\n                    .doc-preview-readmore { font-size: 12px; }\r\n                    \/* \u2705 Laisser 3px tout autour pour que le liser\u00e9 vert (box-shadow\/outline sur parent) soit visible *\/\r\n                    .doc-preview-container {\r\n                        width: calc(100% - 16px) !important;\r\n                        max-height: 109px !important;\r\n                        margin: 3px !important;\r\n                        margin-top: 23px !important;\r\n                        box-sizing: border-box !important;\r\n                    }\r\n                }\r\n                .doc-preview-FullPathAdFile {\r\n                    display: none;\r\n                }\r\n                \/* \u2705 v1.19.0 : Layout sans image *\/\r\n                .doc-preview-noimage {\r\n                    flex-direction: row;\r\n                    align-items: flex-start;\r\n                    gap: 10px;\r\n                    padding: 8px 10px;\r\n                }\r\n                .doc-preview-icon {\r\n                    flex-shrink: 0;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                    width: 44px;\r\n                    height: 44px;\r\n                    background: #eef2f7;\r\n                    border-radius: 6px;\r\n                    margin-top: 4px;\r\n                }\r\n                .doc-preview-info-full {\r\n                    width: 100%;\r\n                    height: auto;\r\n                    max-height: 130px;\r\n                }\r\n                .doc-preview-noimage .doc-preview-description {\r\n                    font-size: 11px;\r\n                    line-height: 1.35;\r\n                    max-height: 60px;\r\n                    overflow: hidden;\r\n                }\r\n            <\/style>\r\n        `;\r\n    },\r\n    \r\n    finalizeRedactionnelUpload() {\r\n        \/\/ \u2705 v1.19.5 : D\u00e9terminer le format r\u00e9dactionnel correct\r\n        \/\/ Si le format s\u00e9lectionn\u00e9 est \"Interview\", on garde \"Interview\"\r\n        \/\/ Sinon on met \"Communiqu\u00e9\" par d\u00e9faut (au lieu de \"R\u00e9dactionnel\" qui n'est pas un format tarifaire valide)\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') || '';\r\n        var formatTransmis = 'Communiqu\u00e9'; \/\/ Par d\u00e9faut\r\n        \r\n        if (formatSelect.toLowerCase().indexOf('interview') !== -1) {\r\n            formatTransmis = 'Interview';\r\n        } else if (formatSelect.toLowerCase().indexOf('communiq') !== -1) {\r\n            formatTransmis = 'Communiqu\u00e9';\r\n        }\r\n        \r\n        StateManager.set('Commande_Format_Transmis', formatTransmis);\r\n        console.log('\u2705 Format r\u00e9dactionnel d\u00e9termin\u00e9:', formatTransmis, '(FormatSelect:', formatSelect, ')');\r\n        \r\n        if (StateManager.get(\"TarifDirectSelectionne\") === 'Yes') {\r\n            const formatText = jQuery('#FormatDataStep3').text();\r\n            if (formatText !== 'Vid\u00e9o' ? formatText !== 'Banni\u00e8re' : false) {\r\n                jQuery('#form-field-RedactionnelSelection')\r\n                    .val(formatText)\r\n                    .css({'color': '#56BE50'});\r\n            }\r\n        }\r\n        \r\n        RedactionnelDepose();\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'upload\r\n *\/\r\nconst UploadManager = {\r\n    isRunning: false,\r\n    \r\n    \/\/ \u2705 Reset manuel espace publicitaire\r\n    resetEspaceManuel: function($droppable) {\r\n        console.log('\ud83d\udd27 Reset manuel espace publicitaire');\r\n        \r\n        var $dropZone = $droppable.find('#drop_file_zone_achat');\r\n        var $uploadContainer = $droppable.find('.HTMLUploadfileConteneur');\r\n        var $container = $droppable.find('.OrdiMobileConteneurClass');\r\n        \r\n        \/\/ 1. Vider la zone de drop et restaurer le HTML par d\u00e9faut\r\n        $dropZone.empty().html(\r\n            '<div id=\"drag_upload_file_achat\">' +\r\n                '<p class=\"UploadIci\" style=\"color: #FB5E2A; font-weight: 600;\">Ici glisser \u2013 d\u00e9poser ou<br>t\u00e9l\u00e9charger une annonce<\/p>' +\r\n            '<\/div>'\r\n        );\r\n        \r\n        \/\/ 2. Afficher les \u00e9l\u00e9ments cach\u00e9s lors de l'upload\r\n        $droppable.find('.UploadIci').show();\r\n        $droppable.find('.EnvoiUlterieurTexte').show();\r\n        $droppable.find('.EnvoiUlterieurContainer').show();\r\n        $droppable.find('.EspPubFormatMainContainer').show();\r\n        $droppable.find('.EspPubFormatListe').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireClass').show();\r\n        $droppable.find('.GlisserDeposerConteneur').show();\r\n        $droppable.find('.OUClass').show();\r\n        $droppable.find('.TexteMobileAnnonce').show();\r\n        $droppable.find('.PositionReference').show();\r\n        $droppable.find('.ClassHdpCdp').show();\r\n        $droppable.find('.ClassRefEsp').show();\r\n        \r\n        \/\/ 3. Cacher les \u00e9l\u00e9ments d'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle').hide();\r\n        $droppable.find('#CroixResetAnnonce').hide();\r\n        $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        $droppable.find('.DeplaceAnnonce').hide();\r\n        $droppable.find('.RefEspacePublicitaire').hide();\r\n        $droppable.find('.AdDroppedTextNotDisplayed').hide();\r\n        \r\n        \/\/ 4. Reset des styles\r\n        $uploadContainer.css({\r\n            'border': 'none',\r\n            'background-color': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': ''\r\n        });\r\n        \r\n        $container.css({\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'top': '',\r\n            'height': ''\r\n        });\r\n        \r\n        \/\/ \u2705 Reset des positionnements desktop appliqu\u00e9s par adjustDesktopLayout\r\n        $droppable.closest('.ToBeHidden').css({'top': '', 'min-height': ''});\r\n        $droppable.parent().css('overflow', '');\r\n        \/\/ \u2705 v2.0.11 : Cleanup event namespace docpreview\r\n        $droppable.off('click.docpreview');\r\n        \r\n        \/\/ 5. Reset des formats s\u00e9lectionn\u00e9s\r\n        $droppable.find('.EspPubFormatContainer').css({\r\n            'background-color': '',\r\n            'border': ''\r\n        });\r\n        \r\n        \/\/ 6. Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ 7. Restaurer .PositionEspacePublicitaireContainer et .ReserverContainer\r\n        $droppable.find('.PositionEspacePublicitaireContainer').show();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \r\n        console.log('\u2705 Reset manuel termin\u00e9');\r\n    },\r\n    \r\n    async handleFileUpload(fileObj, $dropZone) {\r\n        console.log('fileObj:', fileObj);\r\n        \r\n        \/\/ \u2705 v2.4.6 : Contr\u00f4les d'extension EN PREMIER \u2014 avant tout affichage\r\n        const extension = FileManager.getExtension(fileObj.name);\r\n        StateManager.set('FileExtension', extension);\r\n        console.log('FileExtension:', extension);\r\n        \r\n        \/\/ \u2705 v2.6 : jpg\/jpeg accept\u00e9s sur desktop et mobile\r\n        \r\n        if (!FileManager.isAllowedExtension(extension)) {\r\n            UIManager.showFormatError($dropZone);\r\n            return;\r\n        }\r\n\r\n        \/\/ \u2705 v4.9o : Guard type de fichier vs format s\u00e9lectionn\u00e9\r\n        var _$droppableGuard = $dropZone.closest('.droppable');\r\n        if (!_fromMiniatureGuard) {\r\n            if (!_$droppableGuard.length) { _$droppableGuard = jQuery(); }\r\n            var _fmtChkLabel = '';\r\n            var _fmtClsMap = {\r\n                'formatidbanniere':   'banni\u00e8re',\r\n                'formatidparrainage': 'parrainage',\r\n                'formatidcommunique': 'communiqu\u00e9',\r\n                'formatidinterview':  'interview',\r\n                'formatidvideo':      'vid\u00e9o'\r\n            };\r\n            _$droppableGuard.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').each(function() {\r\n                var _sel = false;\r\n                var _bgV = this.style.backgroundColor || '';\r\n                if (_bgV === 'rgb(255, 255, 255)') { _sel = true; }\r\n                if (!_sel) { if (_bgV === '#ffffff' || _bgV === 'white') { _sel = true; } }\r\n                if (!_sel) {\r\n                    var _fEl = this.querySelector('.EspPubFormat');\r\n                    if (_fEl) {\r\n                        var _col = _fEl.style.color || '';\r\n                        if (_col === 'rgb(55, 217, 0)') { _sel = true; }\r\n                        if (!_sel) { if (_col.toLowerCase() === '#37d900') { _sel = true; } }\r\n                    }\r\n                }\r\n                if (_sel) {\r\n                    var _clsN = this.className.toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '');\r\n                    var _keys = Object.keys(_fmtClsMap);\r\n                    for (var _ki = 0; _ki < _keys.length; _ki++) {\r\n                        if (_clsN.indexOf(_keys[_ki]) !== -1) { _fmtChkLabel = _fmtClsMap[_keys[_ki]]; return false; }\r\n                    }\r\n                }\r\n            });\r\n            if (_fmtChkLabel) {\r\n                var _extChk  = extension.toLowerCase();\r\n                var _kindChk = CONFIG.allowedExtensions.video.indexOf(_extChk) !== -1 ? 'video'\r\n                             : CONFIG.allowedExtensions.image.indexOf(_extChk) !== -1 ? 'image'\r\n                             : 'document';\r\n                var _imgLbls = ['banni\u00e8re', 'parrainage'];\r\n                var _docLbls = ['communiqu\u00e9', 'interview'];\r\n                var _vidLbls = ['vid\u00e9o'];\r\n                var _compat  = true;\r\n                if (_kindChk === 'image')    { _compat = (_imgLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk === 'document') { _compat = (_docLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk === 'video')    { _compat = (_vidLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk) { if (!_compat) {\r\n                    UIManager.showFormatError($dropZone, 'Merci de d\u00e9poser un format ' + _fmtChkLabel);\r\n                    console.warn('\ud83d\udeab D\u00e9p\u00f4t refus\u00e9 : fichier ' + _kindChk + ' incompatible avec format ' + _fmtChkLabel);\r\n                    return;\r\n                } }\r\n            }\r\n        }\r\n\r\n        \/\/ v4.9ci : Guard format \u2014 refuser le d\u00e9p\u00f4t si aucun format n'a \u00e9t\u00e9 s\u00e9lectionn\u00e9\r\n        \/\/          DANS LE DROPPABLE cible (pas globalement).\r\n        \/\/ Exception : drop depuis la miniature kit (le format a d\u00e9j\u00e0 \u00e9t\u00e9 choisi dans le kit\r\n        \/\/ juste avant le drop).\r\n        \/\/ v4.9co : D\u00e9tection locale via la classe visuelle \u2014 un EspPubFormatContainer est\r\n        \/\/          \"s\u00e9lectionn\u00e9\" si son EspPubFormat a la couleur verte (#37D900) OU son fond est #ffffff.\r\n        \/\/          On exclut FormatIdCreation et FormatIdPopUp qui ont leur propre \u00e9tat.\r\n        var _fromKitDropGuard = _$droppableGuard.length ? (_$droppableGuard[0].getAttribute('data-kit-drop') === 'true') : false;\r\n        var _fromMiniatureGuard = window._dropFromMiniature || _fromKitDropGuard;\r\n        if (!_fromMiniatureGuard ? _$droppableGuard.length : false) {\r\n            var _hasLocalFmt = false;\r\n            _$droppableGuard.find('.EspPubFormatContainer').each(function() {\r\n                if (jQuery(this).hasClass('FormatIdCreation')) return;\r\n                if (jQuery(this).hasClass('FormatIdPopUp')) return;\r\n                \/\/ Crit\u00e8re 1 : fond blanc inline (pos\u00e9 par handler click)\r\n                var _bg = this.style.backgroundColor || '';\r\n                if (_bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white') {\r\n                    _hasLocalFmt = true;\r\n                    return false; \/\/ break\r\n                }\r\n                \/\/ Crit\u00e8re 2 : texte vert sur .EspPubFormat\r\n                var _fmt = this.querySelector('.EspPubFormat');\r\n                if (_fmt) {\r\n                    var _col = _fmt.style.color || '';\r\n                    if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') {\r\n                        _hasLocalFmt = true;\r\n                        return false;\r\n                    }\r\n                }\r\n            });\r\n            if (!_hasLocalFmt) {\r\n                var _rankId = _$droppableGuard.attr('id') || '?';\r\n                console.warn('\ud83d\udeab D\u00e9p\u00f4t refus\u00e9 : aucun format s\u00e9lectionn\u00e9 dans ce droppable | rank=' + _rankId);\r\n                \/\/ R\u00e9activer le message d'erreur format\r\n                FormatUIManager.flashTitle(_$droppableGuard.find('.HTMLUploadfileConteneur, #UploadFileConteneur').first());\r\n                UIManager.showFormatError($dropZone, 'Merci de s\u00e9lectionner un format d\\'annonce avant de d\u00e9poser votre fichier');\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v1.19.5 : Afficher le message d'attente seulement si le format est valide\r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        this.prepareUIForUpload($dropZone);\r\n        \r\n        const formData = new FormData();\r\n        formData.append('file', fileObj);\r\n        formData.append('action', 'upload_annonce_file_v3');\r\n        \r\n        UIManager.showUploadProgress($dropZone);\r\n        \r\n        try {\r\n            const response = await this.sendUploadRequest(formData);\r\n            await this.handleUploadResponse(response, fileObj, $dropZone, extension);\r\n        } catch (error) {\r\n            console.error('Upload error:', error);\r\n        }\r\n    },\r\n    \r\n    prepareUIForUpload($dropZone) {\r\n        if (!UIManager.isMobile()) {\r\n            jQuery('.ChoisirEspacePublicitaireClass').show();\r\n            jQuery('.ChoisirEspacePublicitaire2ndLigne').show();\r\n            jQuery('.GlisserDeposerConteneur').show();\r\n            jQuery('.OUClass').show();\r\n            \/\/ \u2705 Cibler uniquement le droppable courant \u2014 ne pas r\u00e9afficher sur les espaces d\u00e9j\u00e0 upload\u00e9s\r\n            $dropZone.closest('.droppable').find('.UploadIci').show();\r\n        }\r\n        \r\n        if (StateManager.get(\"PopUpChoice\") === 'Yes') {\r\n            \/\/ \u2705 Ne masquer DeplaceAnnonce QUE sur Ele0A \u2014 pas sur Ele1A qui a deja une annonce chargee\r\n            $dropZone.closest('.OrdiMobileConteneurClass').find('.DeplaceAnnonce').each(function() {\r\n                var _d = jQuery(this).closest('.droppable')[0];\r\n                if (_d ? _d.getAttribute('data-via-ad-loaded') === 'true' : false) { return; }\r\n                jQuery(this).hide();\r\n            });\r\n            $('.AnnonceDragIcone').show().find('img').attr('alt', '');\r\n        } else {\r\n            $('.AnnonceDragIcone').hide();\r\n        }\r\n        \r\n        if (StateManager.get(\"Commande_Page\") === 'Home Page') {\r\n            jQuery('#HPTarifConteneur').css({'background-color': '#B5FFB1'});\r\n        }\r\n        \r\n        jQuery('.ChoisirEspacePublicitaireClass').css({'color': '#ffffff'});\r\n        jQuery('.InterfaceTitreDore').not('#PageWebTitreDore')\r\n            .html(\"Merci de choisir les \u00e9l\u00e9ments de votre campagne publicitaire\");\r\n        jQuery('#ProcessCommande').show();\r\n    },\r\n    \r\n    sendUploadRequest(formData) {\r\n        \/\/ \u2705 v2.6 : dataType 'text' pour eviter parsererror si texte parasite apres le JSON\r\n        return jQuery.ajax({\r\n            url: CONFIG.ajaxUrl,\r\n            type: 'POST',\r\n            data: formData,\r\n            cache: false,\r\n            contentType: false,\r\n            processData: false,\r\n            dataType: 'text'\r\n        }).then(function(text) {\r\n            \/\/ Extraire le JSON meme si du texte parasite suit\r\n            var _json = null;\r\n            try {\r\n                var _match = text.match(\/(\\{[\\s\\S]*?\\})(?:[^{]|$)\/);\r\n                _json = JSON.parse(_match ? _match[1] : text);\r\n            } catch(_e) {\r\n                return jQuery.Deferred().reject({ responseText: text }).promise();\r\n            }\r\n            \/\/ Normaliser le format vers {success, data} attendu par handleUploadResponse\r\n            if (_json.status === 'success') {\r\n                var _fp = _json.file_path || '';\r\n                var _baseUrl = CONFIG.ajaxUrl.replace('\/wp-admin\/admin-ajax.php', '\/wp-admin\/');\r\n                return {\r\n                    success: true,\r\n                    data: {\r\n                        file_url:  _baseUrl + _fp,\r\n                        file_name: _fp.replace(\/^.*\\\/\/, ''),\r\n                        file_path: _fp\r\n                    }\r\n                };\r\n            }\r\n            \/\/ Format WordPress standard {success, data} - retourner tel quel\r\n            return _json;\r\n        });\r\n    },\r\n    \r\n    async handleUploadResponse(response, fileObj, $dropZone, extension) {\r\n        console.log('\u2705 R\u00e9ponse re\u00e7ue');\r\n        \r\n        if (response.responseJSON) {\r\n            response = response.responseJSON;\r\n        }\r\n        \r\n        console.log('\u2705 Response pars\u00e9e:', response);\r\n        \r\n        if (!response.success || !response.data) {\r\n            \/\/ \u2705 v2.1.2 : Si c'est une restauration (_isAdRestoration = 'Yes'),\r\n            \/\/ le fichier existe d\u00e9j\u00e0 sur le serveur \u2192 afficher l'image depuis le fileObj local\r\n            if (StateManager.get('_isAdRestoration') === 'Yes' ? fileObj : false) {\r\n                console.log('\ud83d\udd04 Restauration: upload \u00e9chou\u00e9 (fichier existant) \u2192 rendu local');\r\n                \r\n                FileManager.createObjectUrl(fileObj);\r\n                StateManager.set('FileReceived', 'Yes');\r\n                \r\n                \/\/ \u2705 v2.1.2 : Restaurer l'\u00e9tat EnvoiUlterieur depuis le sessionStorage parent\r\n                var _restoredEnvoi = sessionStorage.getItem('_restoredEnvoiUlterieur');\r\n                if (_restoredEnvoi === 'true') {\r\n                    StateManager.set('EnvoiUlterieur', 'true');\r\n                    StateManager.set('FileReceived', 'No');\r\n                    StateManager.set('FullPathAdFile', '');\r\n                    console.log('\ud83d\udce4 Restauration EnvoiUlterieur = true');\r\n                }\r\n                \r\n                var _objUrl = StateManager.get('objectUrl');\r\n                var _fileType = FileManager.getFileType(extension);\r\n                \r\n                StateManager.set('FormatReconnu', 'No');\r\n                \r\n                switch (_fileType) {\r\n                    case 'video':\r\n                        PreviewRenderer.renderVideo(_objUrl, fileObj.name, $dropZone);\r\n                        StateManager.set('FormatReconnu', 'Yes');\r\n                        break;\r\n                    case 'image':\r\n                        PreviewRenderer.renderImage(_objUrl, $dropZone);\r\n                        StateManager.set('FormatReconnu', 'Yes');\r\n                        break;\r\n                    case 'document':\r\n                        \/\/ \u2705 v2.1.2 : Documents Word\/PDF aussi en restauration\r\n                        await this.handleDocumentUpload(extension, fileObj, $dropZone);\r\n                        break;\r\n                }\r\n                \r\n                UIManager.updateAfterSuccessfulUpload($dropZone);\r\n                \r\n                if (StateManager.get('FormatReconnu') === 'Yes') {\r\n                    this.finalizeUpload($dropZone);\r\n                    $('#MsgSelectEspace').hide();\r\n                }\r\n                \r\n                FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n                console.log('\u2705 Restauration visuelle termin\u00e9e (upload bypass)');\r\n                StateManager.set('_isAdRestoration', 'No');\r\n            }\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 SAUVEGARDER les infos de d\u00e9placement AVANT updateStateAfterUpload\r\n        const isMoved = StateManager.get('FirstUploadFileorMoved') === 'Moved';\r\n        const dragstartRankId = StateManager.get('dragstart_Rank_Emplacement_Page_Web');\r\n        const currentRankId = StateManager.get('Rank_Emplacement_Page_Web');\r\n        const shouldResetOldSpace = isMoved ? (dragstartRankId ? dragstartRankId !== currentRankId : false) : false;\r\n        \/\/ \u2705 Bug 10 : self-drop (m\u00eame espace) \u2014 nettoyer pendingAd du rank source\r\n        \/\/   Le drop sur soi-m\u00eame ne passe pas par dataDelAd, donc pendingAd persiste\r\n        \/\/   et provoque la r\u00e9apparition de l'annonce lors d'un d\u00e9placement ult\u00e9rieur\r\n        if (isMoved ? (dragstartRankId ? (dragstartRankId !== 'No' ? dragstartRankId === currentRankId : false) : false) : false) {\r\n            sessionStorage.removeItem('pendingAd_' + dragstartRankId);\r\n            console.log('[Bug10] self-drop d\u00e9tect\u00e9 \u2014 pendingAd_' + dragstartRankId + ' effac\u00e9');\r\n        }\r\n        \r\n        console.log('\ud83d\udd0d D\u00e9placement?', { isMoved, dragstartRankId, currentRankId, shouldResetOldSpace });\r\n        \r\n        this.updateStateAfterUploadWithoutReset(response, fileObj);\r\n        \r\n        const fileType = FileManager.getFileType(extension);\r\n        \r\n        StateManager.set('FormatReconnu', 'No');\r\n        \r\n        \/\/ \u2705 v1.19.2 : Message d'attente IMM\u00c9DIAT avant le rendu de l'annonce\r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        switch (fileType) {\r\n            case 'video':\r\n                PreviewRenderer.renderVideo(StateManager.get('objectUrl'), fileObj.name, $dropZone);\r\n                StateManager.set('FormatReconnu', 'Yes');\r\n                break;\r\n                \r\n            case 'image':\r\n                PreviewRenderer.renderImage(StateManager.get('objectUrl'), $dropZone);\r\n                StateManager.set('FormatReconnu', 'Yes');\r\n                break;\r\n                \r\n            case 'document':\r\n                await this.handleDocumentUpload(extension, fileObj, $dropZone);\r\n                break;\r\n        }\r\n        \r\n        UIManager.updateAfterSuccessfulUpload($dropZone);\r\n        \r\n        if (StateManager.get('FormatReconnu') === 'Yes') {\r\n            this.finalizeUpload($dropZone);\r\n            $('#MsgSelectEspace').hide();\r\n        }\r\n        \r\n        \/\/ \u2705 RESET L'ANCIEN ESPACE ICI - APR\u00c8S avoir affich\u00e9 la nouvelle image\r\n        \/\/ \u2705 v2.4.5 : Toujours initialiser \u00e0 false avant le bloc (\u00e9vite la valeur r\u00e9siduelle)\r\n        var _sourceWasReserved = false;\r\n        StateManager.set('_sourceWasReserved', 'No');\r\n        \r\n        if (shouldResetOldSpace) {\r\n            console.log('\ud83d\udd04 Reset ancien espace:', dragstartRankId);\r\n            \r\n            var $oldDroppable = $('#' + dragstartRankId);\r\n            \r\n            if ($oldDroppable.length) {\r\n                var $container = $oldDroppable.find('.OrdiMobileConteneurClass').first();\r\n                \r\n                \/\/ \u2705 v2.4.5 : Utiliser l'\u00e9tat captur\u00e9 au dragstart (fiable vs re-check async)\r\n                if ($container.length) {\r\n                    _sourceWasReserved = StateManager.get('dragstart_ReserverChecked') === 'Yes';\r\n                    console.log('[d\u00e9placement] ReserverChecked au dragstart:', _sourceWasReserved, '| rank:', dragstartRankId);\r\n                    StateManager.set('_sourceWasReserved', _sourceWasReserved ? 'Yes' : 'No');\r\n                    RestoreadSpaceTemplateLocal($container[0]);\r\n                }\r\n            }\r\n        }\r\n        StateManager.set(\"EnvoiUlterieur\", 'false');\r\n        \r\n        \/\/ \u2705 v1.19.1 : Positionner l'espace \u00e0 10px du haut du viewport apr\u00e8s upload\r\n        setTimeout(function() {\r\n            var el = $dropZone.closest('.droppable').find('.HTMLUploadfileConteneur')[0]\r\n                     || $dropZone.closest('.droppable')[0];\r\n            if (el) {\r\n                ScrollHelper.scrollElementTo(el, 10);\r\n            }\r\n        }, 400);\r\n        \r\n        \/\/ \u2705 NE PLUS envoyer automatiquement - c'est la checkbox \"R\u00e9server\" qui d\u00e9clenche\r\n        \/\/ On met \u00e0 jour l'\u00e9tat de la checkbox pour qu'elle devienne activable\r\n        FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n        \r\n        \/\/ \u2705 v2.4.5 : Si l'espace source \u00e9tait r\u00e9serv\u00e9, cocher la checkbox de l'espace cible\r\n        if (StateManager.get('_sourceWasReserved') === 'Yes') {\r\n            var $targetCb = $dropZone.closest('.droppable').find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n            if ($targetCb.length ? !$targetCb.prop('checked') : false) {\r\n                $targetCb.prop('checked', true).trigger('change');\r\n                console.log('\u2705 [d\u00e9placement] Checkbox R\u00e9server coch\u00e9e sur espace cible');\r\n            }\r\n            StateManager.set('_sourceWasReserved', 'No');\r\n        }\r\n        \r\n        console.log('\ud83d\udccb Upload termin\u00e9 - en attente de validation via checkbox \"R\u00e9server\"');\r\n    },\r\n    \r\n    updateStateAfterUploadWithoutReset(response, fileObj) {\r\n        StateManager.setMultiple({\r\n            \"PageWhithADType\": StateManager.get('SelectedPageType'),\r\n            \"PageWhithADSecteur\": StateManager.get('SelectedPageSecteur')\r\n        });\r\n        \r\n        $('#BackPageWebWithADCroix').hide();\r\n        jQuery('#ApercuMobile').empty().css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center'\r\n        });\r\n        \r\n        const firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n        \r\n        if (firstUploadOrMoved === 'FirstUpload') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": response.data.file_url,\r\n                \"fileObj\": fileObj,\r\n                \"Upload_File_Name\": fileObj.name\r\n            });\r\n            \r\n            FileManager.createObjectUrl(fileObj);\r\n            \r\n            console.log('\ud83d\udcc1 Fichier upload\u00e9:', {\r\n                fullUrl: response.data.file_url,\r\n                fileName: response.data.file_name,\r\n                filePath: response.data.file_path\r\n            });\r\n        }\r\n    },\r\n    \r\n    updateStateAfterUpload(response, fileObj) {\r\n        StateManager.setMultiple({\r\n            \"PageWhithADType\": StateManager.get('SelectedPageType'),\r\n            \"PageWhithADSecteur\": StateManager.get('SelectedPageSecteur')\r\n        });\r\n        \r\n        $('#BackPageWebWithADCroix').hide();\r\n        jQuery('#ApercuMobile').empty().css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center'\r\n        });\r\n        \r\n        const firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n        console.log('\ud83d\udd0d FirstUploadFileorMoved:', firstUploadOrMoved);\r\n        \r\n        if (firstUploadOrMoved === 'FirstUpload') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": response.data.file_url,\r\n                \"fileObj\": fileObj,\r\n                \"Upload_File_Name\": fileObj.name\r\n            });\r\n            \r\n            FileManager.createObjectUrl(fileObj);\r\n            \r\n            console.log('\ud83d\udcc1 Fichier upload\u00e9:', {\r\n                fullUrl: response.data.file_url,\r\n                fileName: response.data.file_name,\r\n                filePath: response.data.file_path\r\n            });\r\n        } else if (firstUploadOrMoved === 'Moved') {\r\n            const dragstartRankId = StateManager.get('dragstart_Rank_Emplacement_Page_Web');\r\n            const currentRankId = StateManager.get('Rank_Emplacement_Page_Web');\r\n            \r\n            console.log('\ud83d\udd04 D\u00c9PLACEMENT D\u00c9TECT\u00c9');\r\n            console.log('   Ancien espace (dragstart):', dragstartRankId);\r\n            console.log('   Nouvel espace (current):', currentRankId);\r\n            \r\n            if (dragstartRankId ? dragstartRankId !== currentRankId : false) {\r\n                const $oldDroppable = $('#' + dragstartRankId);\r\n                \r\n                console.log('   $oldDroppable trouv\u00e9:', $oldDroppable.length > 0);\r\n                \r\n                if ($oldDroppable.length) {\r\n                    const $container = $oldDroppable.find('.OrdiMobileConteneurClass');\r\n                    \r\n                    console.log('   $container trouv\u00e9:', $container.length > 0);\r\n                    \r\n                    if ($container.length) {\r\n                        console.log('\ud83d\udd27 Appel RestoreadSpaceTemplate sur:', $container[0]);\r\n                        \r\n                        if (typeof window.RestoreadSpaceTemplate === 'function') {\r\n                            window.RestoreadSpaceTemplate($container[0]);\r\n                            console.log('\u2705 RestoreadSpaceTemplate ex\u00e9cut\u00e9');\r\n                        }\r\n                    } else {\r\n                        console.warn('\u26a0\ufe0f .OrdiMobileConteneurClass non trouv\u00e9 dans', dragstartRankId);\r\n                    }\r\n                }\r\n            } else {\r\n                console.log('\u23ed\ufe0f M\u00eame espace ou dragstartRankId invalide, pas de reset');\r\n            }\r\n        }\r\n    },\r\n        \r\n    async handleDocumentUpload(extension, fileObj, $dropZone) {\r\n        StateManager.set('FormatReconnu', 'Yes');\r\n        \r\n        \/\/ \u2705 Cacher le File r\u00e9dactionnel pour r\u00e9utilisation lors des d\u00e9placements (\u00e9vite CORS)\r\n        window._lastRedactionnelFile = fileObj;\r\n        \r\n        if (extension === 'doc' || extension === 'docx') {\r\n            await new Promise(resolve => window.VIALibraries.loadMammoth(resolve));\r\n            await PreviewRenderer.renderWord(fileObj, $dropZone);\r\n        } else if (extension === 'ppt' || extension === 'pptx') {\r\n            this.renderPowerPoint($dropZone);\r\n            PreviewRenderer.finalizeRedactionnelUpload();\r\n        } else if (extension === 'pdf') {\r\n            await new Promise(resolve => window.VIALibraries.loadPdfJs(resolve));\r\n            await PDFHandler.renderPDF(fileObj, $dropZone);\r\n            PreviewRenderer.finalizeRedactionnelUpload();\r\n        }\r\n    },\r\n    \r\n    renderPowerPoint($dropZone) {\r\n        const img = jQuery('<img \/>', {\r\n            src: '\/wp-content\/uploads\/2024\/06\/microsoft-powerpoint.png',\r\n            css: {\r\n                'width': 'auto',\r\n                'height': 'auto',\r\n                'margin-bottom': '20px',\r\n                'max-width': '150px',\r\n                'max-height': '160px'\r\n            }\r\n        });\r\n        $dropZone.empty().append(img.clone());\r\n        jQuery('#ApercuMobile').append(img);\r\n    },\r\n    \r\n    finalizeUpload($dropZone) {\r\n        \/\/ \u2705 Reset sendDataToParentFlag : nouvel upload = pas encore r\u00e9serv\u00e9\r\n        StateManager.set('sendDataToParentFlag', 'No');\r\n        \r\n        StateManager.setMultiple({\r\n            \"PageAnnonceSelection\": 'Yes',\r\n            \"FormatReconnu\": 'Yes'\r\n        });\r\n        \r\n        jQuery('#MsgChoixPageWeb, #MsgInsererAnnonceConteneur').hide();\r\n        \r\n        if (StateManager.get(\"Commande_Format\") === 'R\u00e9dactionnel') {\r\n            StateManager.set('Commande_Format', '\u00e0 choisir');\r\n            RedactionnelDepose();\r\n        }\r\n        \r\n        if (StateManager.get(\"Commande_Page\") === ' ') {\r\n            jQuery('#HPTarifConteneur').css({'background-color': '#BCFFAD'});\r\n            jQuery('#EmplacementDataStep3').html(\"Home Page\");\r\n            StateManager.setMultiple({\r\n                \"Commande_Page\": 'Home Page',\r\n                \"PageAnnonceSelection\": 'Yes'\r\n            });\r\n        }\r\n        \r\n        $('#PageWeb').css('zoom', '70%');\r\n        $('#PageWebTitreDore').css({'font-size': '14px'});\r\n        \r\n        if (!UIManager.isMobile()) {\r\n            $('#PageWebTitreDore').css({'color': '#213864'});\r\n        }\r\n        \r\n        StateManager.set(\"Page_Web_with_AD_URL\", StateManager.get(\"Page_Web_URL\"));\r\n        \r\n        UIManager.finalizeAdDisplay($dropZone);\r\n        \r\n        \/\/ \u2705 Notifier le parent de l'annonce d\u00e9pos\u00e9e non r\u00e9serv\u00e9e \u2014 pour restauration au retour sur la page\r\n        var _fileRcv = StateManager.get('FileReceived');\r\n        var _isRestoring = StateManager.get('_isAdRestoration') === 'Yes';\r\n        console.log('\ud83d\udfe1 [finalizeUpload] FileReceived:', _fileRcv, '| _isAdRestoration:', _isRestoring);\r\n        if (_fileRcv === 'Yes' ? !_isRestoring : false) {\r\n            var _pendingRank = StateManager.get('Rank_Emplacement_Page_Web') || $dropZone.closest('.droppable').attr('id') || '';\r\n            console.log('\ud83d\udfe1 [finalizeUpload] pendingRank:', _pendingRank, '| FullPathAdFile:', StateManager.get('FullPathAdFile'));\r\n            if (_pendingRank) {\r\n                console.log('\ud83d\udce4 [finalizeUpload] annonceDeposeeSansReservation \u2192 parent rank:', _pendingRank);\r\n                \/\/ \u2705 D\u00e9terminer le vrai format commercial selon le type de fichier d\u00e9pos\u00e9 + format s\u00e9lectionn\u00e9\r\n                var _formatSelect = sessionStorage.getItem('FormatSelect') || StateManager.get('Commande_Format_Transmis') || '';\r\n                var _uploadedExt = (StateManager.get('Upload_File_Name') || '').split('.').pop().toLowerCase();\r\n                var _fileKind = CONFIG.allowedExtensions.video.indexOf(_uploadedExt) !== -1 ? 'video'\r\n                              : CONFIG.allowedExtensions.image.indexOf(_uploadedExt) !== -1 ? 'image'\r\n                              : CONFIG.allowedExtensions.document.indexOf(_uploadedExt) !== -1 ? 'document'\r\n                              : '';\r\n                var _formatPending;\r\n                if (_fileKind === 'video') {\r\n                    \/\/ Vid\u00e9o \u2192 toujours Vid\u00e9o\r\n                    _formatPending = 'Vid\u00e9o';\r\n                } else if (_fileKind === 'image') {\r\n                    \/\/ Image \u2192 Banni\u00e8re ou Parrainage si s\u00e9lectionn\u00e9, sinon Banni\u00e8re par d\u00e9faut\r\n                    var _imgFormats = ['Banni\u00e8re', 'Banniere', 'Parrainage'];\r\n                    _formatPending = (_imgFormats.indexOf(_formatSelect) !== -1) ? _formatSelect : 'Banni\u00e8re';\r\n                } else if (_fileKind === 'document') {\r\n                    \/\/ Document \u2192 Communiqu\u00e9 ou Interview si s\u00e9lectionn\u00e9, sinon Communiqu\u00e9 par d\u00e9faut\r\n                    var _docFormats = ['Communiqu\u00e9', 'Communique', 'Interview'];\r\n                    _formatPending = (_docFormats.indexOf(_formatSelect) !== -1) ? _formatSelect : 'Communiqu\u00e9';\r\n                } else {\r\n                    \/\/ Fallback\r\n                    _formatPending = _formatSelect || StateManager.get('Commande_Format_Transmis') || '';\r\n                }\r\n                \/\/ \u2705 v2.4.9 : Si le format d\u00e9duit diff\u00e8re du format s\u00e9lectionn\u00e9 \u2192 mettre \u00e0 jour vignette + sessionStorage\r\n                if (_formatPending ? _formatPending !== _formatSelect : false) {\r\n                    StateManager.set('FormatSelect', _formatPending);\r\n                    StateManager.set('Commande_Format_Transmis', _formatPending);\r\n                    StateManager.set('Formatchoisi', 'Yes');\r\n                    \/\/ Mettre \u00e0 jour visuellement la vignette dans le droppable courant\r\n                    var _fmtNorm = _formatPending.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    var $_drp = $dropZone.closest('.droppable');\r\n                    $_drp.find('.EspPubFormatContainer').each(function() {\r\n                        var _cls = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                        if (_cls.includes(_fmtNorm)) {\r\n                            jQuery(this).css({'background-color': '#ffffff'});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        } else if (!jQuery(this).hasClass('FormatIdPopUp')) {\r\n                            jQuery(this).css({'background-color': ''});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': ''});\r\n                        }\r\n                    });\r\n                    \/\/ Mettre \u00e0 jour aussi le bandeau parent via postMessage\r\n                    MessageManager.sendToParent('formatChangedInIframe', { formatSelect: _formatPending, rank: _pendingRank });\r\n                    console.log('\ud83d\udd04 [finalizeUpload] Format corrig\u00e9:', _formatSelect, '\u2192', _formatPending);\r\n                }\r\n                var _firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n                var _isMoved = _firstUploadOrMoved === 'Moved';\r\n                var _oldRankMoved = _isMoved ? (StateManager.get('dragstart_Rank_Emplacement_Page_Web') || '') : '';\r\n                MessageManager.sendToParent('annonceDeposeeSansReservation', {\r\n                    Rank_Emplacement_Page_Web: _pendingRank,\r\n                    FullPathAdFile: StateManager.get('FullPathAdFile') || '',\r\n                    Upload_File_Name: StateManager.get('Upload_File_Name') || '',\r\n                    LoadedPageUrl: window.location.href,\r\n                    FileReceived: 'Yes',\r\n                    \/\/ \u2705 Lire EnvoiUlterieur depuis la checkbox du droppable courant (pas le StateManager global)\r\n                    \/\/ StateManager.get('EnvoiUlterieur') est global \u2192 peut valoir 'true' si un autre espace a sa checkbox coch\u00e9e\r\n                    EnvoiUlterieur: ($dropZone.closest('.droppable').find('input[name*=\"EnvoiUlterieur\"]').prop('checked') ? 'true' : 'false'),\r\n                    Commande_Format_Transmis: _formatPending,\r\n                    codeSite: StateManager.get('codeSite') || '',\r\n                    codePage: StateManager.get('codePage') || '',\r\n                    Commande_Emplacement_Page_Web: StateManager.buildEmplacementReference(_pendingRank),\r\n                    isMoved: _isMoved,\r\n                    oldRank: _oldRankMoved\r\n                });\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 Page \/annonce : pas de checkbox R\u00e9server \u2192 enregistrer imm\u00e9diatement\r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            StateManager.set('sendDataToParentFlag', 'Yes');\r\n            this.activateSendDataToParent($dropZone);\r\n        }\r\n    },\r\n    \r\n    activateSendDataToParent($dropZone) {\r\n        if (StateManager.get(\"sendDataToParentFlag\") !== 'Yes') {\r\n            return;\r\n        }\r\n        \r\n        if (StateManager.get(\"FirstUploadFileorMoved\") === 'Moved') {\r\n            const dragstartRef = StateManager.buildEmplacementReference(\r\n                StateManager.get(\"dragstart_Rank_Emplacement_Page_Web\")\r\n            );\r\n            StateManager.set('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n        }\r\n        \r\n        console.log('Esp Pub Ref FirstUploadFileorMoved:', StateManager.get(\"FirstUploadFileorMoved\"));\r\n        console.log('Esp Pub dragstart_Commande:', StateManager.get(\"dragstart_Commande_Emplacement_Page_Web\"));\r\n        \r\n        \/\/ v4.9ds : si un upload kit (drop miniature) est en cours pour ce rank,\r\n        \/\/   attendre sa fin avant d'envoyer les donn\u00e9es \u2014 sinon FullPathAdFile=null\r\n        \/\/   serait propag\u00e9 \u00e0 l'iframe popup et la BDD serait aliment\u00e9e sans chemin.\r\n        var _self = this;\r\n        var _rankWait = StateManager.get(\"Rank_Emplacement_Page_Web\");\r\n        var _pendingUp = (window._viaPendingUpload ? window._viaPendingUpload[_rankWait] : null);\r\n        if (_pendingUp ? typeof _pendingUp.then === 'function' : false) {\r\n            console.log('\u23f3 [activateSendDataToParent] upload kit en cours sur', _rankWait, '\u2192 wait avant d\\'envoyer');\r\n            _pendingUp.then(function() {\r\n                console.log('\u2705 [activateSendDataToParent] upload termin\u00e9 \u2192 reprise envoi | FullPathAdFile:', StateManager.get('FullPathAdFile'));\r\n                _self._continueActivateSendDataToParent($dropZone);\r\n            }).catch(function(_eUW) {\r\n                console.warn('\u26a0\ufe0f [activateSendDataToParent] upload \u00e9chou\u00e9 \u2192 on continue quand m\u00eame:', _eUW);\r\n                _self._continueActivateSendDataToParent($dropZone);\r\n            });\r\n            return;\r\n        }\r\n        \r\n        this._continueActivateSendDataToParent($dropZone);\r\n    },\r\n    \r\n    _continueActivateSendDataToParent($dropZone) {\r\n        if (StateManager.get(\"PageAjoutModifAnnonce\") === 'Yes') {\r\n            this.handlePageModification($dropZone);\r\n        } else {\r\n            this.handleNormalUpload();\r\n        }\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        setTimeout(() => {\r\n            StateManager.set(\"AddNewRefInVosCampagnes\", 'Yes');\r\n        }, 4000);\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            StateManager.set('Commande_Format_Transmis', '');\r\n        }\r\n    },\r\n    \r\n    handlePageModification($dropZone) {\r\n        $('#CroixResetAnnonce, .DeplaceAnnonce').hide();\r\n        $('.PageAjoutModifAnnonceCroixResetAnnonce').show();\r\n        \r\n        const emplacementRef = $dropZone.closest('.CampagnesTemplateClass')\r\n            .find('.ReferenceEspace').text();\r\n        \r\n        console.log('Commande_Emplacement_Page_Web:', emplacementRef);\r\n        \r\n        jQuery.ajax({\r\n            type: \"POST\",\r\n            url: CONFIG.ajaxUrl,\r\n            data: {\r\n                action: 'via_update_fichier_annonce',\r\n                commande_ref_url: StateManager.get(\"commande_ref_url\"),\r\n                emplacement_page_web: emplacementRef,\r\n                chemin_fichier: StateManager.get(\"FullPathAdFile\")\r\n            },\r\n            xhrFields: { withCredentials: true },\r\n            success: (response) => {\r\n                if (response.success) {\r\n                    console.log('\u2705 Fichier annonce mis \u00e0 jour:', response.data.message);\r\n                    location.reload();\r\n                } else {\r\n                    console.error('\u274c Erreur:', response.data.message);\r\n                }\r\n            },\r\n            error: (xhr, status, error) => {\r\n                console.error('\u274c Erreur AJAX:', error);\r\n            }\r\n        });\r\n    },\r\n    \r\n    handleNormalUpload() {\r\n        if (StateManager.get(\"EnvoiUlterieur\") === 'true') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": '',\r\n                \"Upload_File_Name\": ''\r\n            });\r\n        }\r\n        \r\n        StateManager.set(\"LoadedPageUrl\", window.location.href);\r\n        \r\n        console.log('EnvoiUlterieur:', StateManager.get(\"EnvoiUlterieur\"));\r\n        console.log('LoadedPageUrl:', StateManager.get(\"LoadedPageUrl\"));\r\n        console.log('FileReceived:', StateManager.get(\"FileReceived\"));\r\n        \r\n        const data = MessageManager.prepareUploadData();\r\n        \r\n        \/\/ \u2705 Sauvegarder FullPathAdFile dans localStorage pour restauration apr\u00e8s refresh\r\n        \/\/ (ce chemin couvre aussi AchatEspaceCall=Yes o\u00f9 handleEspacePubData d'Entete.txt n'est pas appel\u00e9)\r\n        \/\/ \u2705 Backup : sauver via_fullpath ici aussi (couvre AchatEspaceCall=Yes)\r\n        if (data.FullPathAdFile) {\r\n            var _rankFP = data.Rank_Emplacement_Page_Web;\r\n            if (_rankFP) {\r\n                try {\r\n                    var _fpItemsEP = JSON.parse(localStorage.getItem('via_fullpath') || '{}');\r\n                    _fpItemsEP[_rankFP] = data.FullPathAdFile;\r\n                    localStorage.setItem('via_fullpath', JSON.stringify(_fpItemsEP));\r\n                } catch(e) {}\r\n            }\r\n        }\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            MessageManager.sendDataToParent(data);\r\n        } else {\r\n            const formatChoisi = StateManager.get(\"Formatchoisi\") === 'Yes';\r\n            const fichierDepose = StateManager.get(\"FileReceived\") === 'Yes';\r\n            const envoiDiffere = StateManager.get(\"EnvoiUlterieur\") === 'true';\r\n            \r\n            console.log('\ud83d\udd0d handleNormalUpload - Conditions:', { formatChoisi, fichierDepose, envoiDiffere });\r\n            \r\n            if (formatChoisi ? (fichierDepose || envoiDiffere) : false) {\r\n                if (typeof window.handleEspacePubData === 'function') {\r\n                    window.handleEspacePubData(data);\r\n                } else {\r\n                    console.warn('\u26a0\ufe0f handleEspacePubData non disponible, fallback direct');\r\n                    if (typeof window.executeWithProcessLoaded === 'function') {\r\n                        window.executeWithProcessLoaded(function() {\r\n                            jQuery('#PopupProcessCommandeContainer').show();\r\n                        });\r\n                    } else {\r\n                        jQuery('#PopupProcessCommandeContainer').show();\r\n                    }\r\n                }\r\n            } else if (formatChoisi) {\r\n                console.log('\ud83d\udcdd Mise \u00e0 jour format uniquement');\r\n                \r\n                const borderStyle = {'box-shadow': 'inset 0 0 0 2px #00FF19', 'box-sizing': 'border-box'};  \/\/ \u2705 v2.4.5\r\n                \r\n                jQuery(\".FormatClassique, .FormatPopup\").css({'border': 'none'});\r\n                \r\n                jQuery('.FormatClassique, .FormatPopup').each(function() {\r\n                    \/\/ \u2705 v2.1.1 : NFD normalize both sides\r\n                    var _cn = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    var _cf = (data.Commande_Format_Transmis || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    if (_cn.includes(_cf) ? _cf : false) {\r\n                        jQuery(this).css(borderStyle);\r\n                    }\r\n                });\r\n            }\r\n        }\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        setTimeout(() => {\r\n            StateManager.set(\"AddNewRefInVosCampagnes\", 'Yes');\r\n        }, 4000);\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            StateManager.set('Commande_Format_Transmis', '');\r\n        }\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des fichiers PDF (s\u00e9par\u00e9 pour \u00e9viter la complexit\u00e9)\r\n *\/\r\nconst PDFHandler = {\r\n    pdfData: null,\r\n    pdfDataForViewer: null,\r\n    \r\n    async renderPDF(fileObj, $dropZone) {\r\n        return new Promise((resolve, reject) => {\r\n            const reader = new FileReader();\r\n            \r\n            reader.onload = async (e) => {\r\n                try {\r\n                    const arrayBuffer = e.target.result;\r\n                    this.pdfData = new Uint8Array(arrayBuffer);\r\n                    this.pdfDataForViewer = arrayBuffer;\r\n                    \r\n                    console.log(\"Initial PDF load, size:\", this.pdfData.byteLength);\r\n                    \r\n                    const pdfjsLib = window['pdfjs-dist\/build\/pdf'];\r\n                    pdfjsLib.GlobalWorkerOptions.workerSrc = \r\n                        '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n                    \r\n                    const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(this.pdfData) }).promise;\r\n                    const page = await pdf.getPage(1);\r\n                    \r\n                    const { titleText, pageText } = await this.extractTextFromPage(page);\r\n                    \/\/ v4.9ds : si une image utilisateur a \u00e9t\u00e9 stock\u00e9e par le kit (mobile PDF JPG-in-PDF),\r\n                    \/\/   l'utiliser directement plut\u00f4t que d'extraire la 1\u00e8re image du PDF\r\n                    \/\/   (qui serait toute la cr\u00e9ation rasteris\u00e9e)\r\n                    const _$drop4ds = $dropZone.closest('.droppable');\r\n                    const _kitFirstImg = _$drop4ds.length ? _$drop4ds.data('kitFirstImageDataURL') : null;\r\n                    let imageData;\r\n                    if (_kitFirstImg) {\r\n                        imageData = { dataUrl: _kitFirstImg };\r\n                        console.log('[v4.9ds] renderPDF \u2192 utilise kitFirstImageDataURL stock\u00e9');\r\n                        try { var _dl5 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl5) _dl5('[v4.9ds] renderPDF utilise firstImg stock\u00e9', 'ok'); } catch(e) {}\r\n                    } else {\r\n                        imageData = await this.extractImageFromPage(page, pdfjsLib);\r\n                        try { var _dl6 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl6) _dl6('[v4.9ds] renderPDF utilise extractImageFromPage (firstImg ABSENT)', 'warn'); } catch(e) {}\r\n                    }\r\n                    \r\n                    if (imageData) {\r\n                        PreviewRenderer.renderDocumentPreview(\r\n                            $dropZone,\r\n                            imageData.dataUrl,\r\n                            titleText,\r\n                            pageText\r\n                        );\r\n                    } else {\r\n                        \/\/ \u2705 v1.19.0 : Accepter les PDF sans image\r\n                        PreviewRenderer.renderDocumentPreview(\r\n                            $dropZone,\r\n                            null,\r\n                            titleText,\r\n                            pageText\r\n                        );\r\n                        console.log('\u2139\ufe0f PDF sans image \u2014 aper\u00e7u texte seul');\r\n                    }\r\n                    \r\n                    this.attachPDFPreviewHandler($dropZone, titleText);\r\n                    resolve();\r\n                } catch (error) {\r\n                    console.error('Error extracting from PDF:', error);\r\n                    \/\/ \u2705 v1.19.0 : Message d'erreur en fran\u00e7ais\r\n                    $dropZone.html(`\r\n                        <div style=\"padding: 15px; background-color: #f8f8f8; border-radius: 4px; text-align: center;\">\r\n                            <p style=\"color: #FB5E2A; font-weight: 600; font-family: Roboto, Arial, sans-serif; font-size: 12px;\">\r\n                                Erreur lors de la lecture du document\r\n                            <\/p>\r\n                        <\/div>\r\n                    `);\r\n                    reject(error);\r\n                }\r\n            };\r\n            \r\n            reader.readAsArrayBuffer(fileObj);\r\n        });\r\n    },\r\n    \r\n    async extractTextFromPage(page) {\r\n        const textContent = await page.getTextContent();\r\n        \r\n        let text = '';\r\n        let lastX = -1;\r\n        let lastY = -1;\r\n        \r\n        for (const item of textContent.items) {\r\n            if (lastY !== -1 ? (Math.abs(lastY - item.transform[5]) > 5) : false) {\r\n                text += '\\n';\r\n            } else if (lastX !== -1 ? (item.transform[4] - lastX > 10) : false) {\r\n                text += ' ';\r\n            }\r\n            \r\n            text += item.str;\r\n            \r\n            lastX = item.transform[4] + (item.width || 0);\r\n            lastY = item.transform[5];\r\n        }\r\n        \r\n        text = text\r\n            .replace(\/(\\w) (\\w)\/g, (match, p1, p2) => {\r\n                if (\/[\u00e9\u00e8\u00ea\u00eb\u00e0\u00e2\u00e4\u00f4\u00f6\u00fb\u00fc\u00ef\u00ee\u00e7]\/i.test(p2)) {\r\n                    return p1 + p2;\r\n                }\r\n                return match;\r\n            })\r\n            .replace(\/ ([.,;:!?])\/g, '$1');\r\n        \r\n        const normalizedText = PreviewRenderer.normalizeText(text);\r\n        const titleText = PreviewRenderer.findDocumentTitle(normalizedText);\r\n        \r\n        return { titleText, pageText: text };\r\n    },\r\n    \r\n    async extractImageFromPage(page, pdfjsLib) {\r\n        const opList = await page.getOperatorList();\r\n        const imageInfo = [];\r\n        let currentTransform = null;\r\n        \r\n        for (let i = 0; i < opList.fnArray.length; i++) {\r\n            const operator = opList.fnArray[i];\r\n            const args = opList.argsArray[i];\r\n            \r\n            if (operator === pdfjsLib.OPS.transform) {\r\n                currentTransform = args;\r\n            } else if (operator === pdfjsLib.OPS.paintImageXObject) {\r\n                const imageName = args[0];\r\n                \r\n                if (!imageInfo.some(info => info.name === imageName)) {\r\n                    const position = currentTransform ? {\r\n                        x: currentTransform[4] || 0,\r\n                        y: currentTransform[5] || 0\r\n                    } : { x: 0, y: 0 };\r\n                    \r\n                    imageInfo.push({ name: imageName, position: position });\r\n                }\r\n            }\r\n        }\r\n        \r\n        if (imageInfo.length === 0) {\r\n            return null;\r\n        }\r\n        \r\n        imageInfo.sort((a, b) => b.position.y - a.position.y);\r\n        \r\n        const targetImageName = imageInfo[0].name;\r\n        const imageObj = page.objs.get(targetImageName);\r\n        \r\n        if (!imageObj) {\r\n            const img = await page.objs.get(targetImageName, true);\r\n            return this.processImageObject(img);\r\n        }\r\n        \r\n        return this.processImageObject(imageObj);\r\n    },\r\n    \r\n    processImageObject(imageObj) {\r\n        if (!imageObj) {\r\n            throw new Error('Image object is null');\r\n        }\r\n        \r\n        if (imageObj.bitmap instanceof ImageBitmap) {\r\n            const width = imageObj.bitmap.width || imageObj.width || imageObj.w;\r\n            const height = imageObj.bitmap.height || imageObj.height || imageObj.h;\r\n            \r\n            const canvas = document.createElement('canvas');\r\n            canvas.width = width;\r\n            canvas.height = height;\r\n            const ctx = canvas.getContext('2d');\r\n            \r\n            ctx.drawImage(imageObj.bitmap, 0, 0);\r\n            \r\n            return {\r\n                canvas: canvas,\r\n                width: width,\r\n                height: height,\r\n                dataUrl: canvas.toDataURL('image\/jpeg', 0.92)\r\n            };\r\n        }\r\n        \r\n        const width = imageObj.width || imageObj.w;\r\n        const height = imageObj.height || imageObj.h;\r\n        \r\n        if (!width || !height) {\r\n            throw new Error('Could not determine image dimensions');\r\n        }\r\n        \r\n        let imgData = imageObj.data || imageObj.bitmap;\r\n        \r\n        if (!imgData) {\r\n            throw new Error('No valid image data found');\r\n        }\r\n        \r\n        const canvas = document.createElement('canvas');\r\n        canvas.width = width;\r\n        canvas.height = height;\r\n        const ctx = canvas.getContext('2d');\r\n        \r\n        ctx.fillStyle = 'white';\r\n        ctx.fillRect(0, 0, width, height);\r\n        \r\n        const imageData = ctx.createImageData(width, height);\r\n        \r\n        this.fillImageData(imageData, imgData, imageObj.kind, width, height);\r\n        \r\n        ctx.putImageData(imageData, 0, 0);\r\n        \r\n        return {\r\n            canvas: canvas,\r\n            width: width,\r\n            height: height,\r\n            dataUrl: canvas.toDataURL('image\/jpeg', 0.92)\r\n        };\r\n    },\r\n    \r\n    fillImageData(imageData, imgData, kind, width, height) {\r\n        if (kind === 'GRAY') {\r\n            for (let i = 0, j = 0; i < imgData.length; i++, j += 4) {\r\n                const value = imgData[i];\r\n                imageData.data[j] = value;\r\n                imageData.data[j + 1] = value;\r\n                imageData.data[j + 2] = value;\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (kind === 'CMYK') {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 4, j += 4) {\r\n                const c = imgData[i] \/ 255;\r\n                const m = imgData[i + 1] \/ 255;\r\n                const y = imgData[i + 2] \/ 255;\r\n                const k = imgData[i + 3] \/ 255;\r\n                \r\n                imageData.data[j] = 255 * (1 - c) * (1 - k);\r\n                imageData.data[j + 1] = 255 * (1 - m) * (1 - k);\r\n                imageData.data[j + 2] = 255 * (1 - y) * (1 - k);\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (kind === 'RGB24') {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 3, j += 4) {\r\n                imageData.data[j] = imgData[i];\r\n                imageData.data[j + 1] = imgData[i + 1];\r\n                imageData.data[j + 2] = imgData[i + 2];\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (imgData.length === width * height * 3) {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 3, j += 4) {\r\n                imageData.data[j] = imgData[i];\r\n                imageData.data[j + 1] = imgData[i + 1];\r\n                imageData.data[j + 2] = imgData[i + 2];\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else {\r\n            const tempArray = new Uint8ClampedArray(imgData.length);\r\n            for (let i = 0; i < imgData.length; i++) {\r\n                tempArray[i] = imgData[i];\r\n            }\r\n            \r\n            if (tempArray.length === imageData.data.length \/ 4 * 3) {\r\n                for (let i = 0, j = 0; i < tempArray.length; i += 3, j += 4) {\r\n                    imageData.data[j] = tempArray[i];\r\n                    imageData.data[j + 1] = tempArray[i + 1];\r\n                    imageData.data[j + 2] = tempArray[i + 2];\r\n                    imageData.data[j + 3] = 255;\r\n                }\r\n            } else if (tempArray.length === imageData.data.length) {\r\n                imageData.data.set(tempArray);\r\n            } else {\r\n                console.warn('Unknown image format - creating placeholder');\r\n                for (let i = 0; i < imageData.data.length; i += 4) {\r\n                    const x = (i\/4) % width;\r\n                    const y = Math.floor((i\/4) \/ width);\r\n                    imageData.data[i] = x % 256;\r\n                    imageData.data[i + 1] = y % 256;\r\n                    imageData.data[i + 2] = 100;\r\n                    imageData.data[i + 3] = 255;\r\n                }\r\n            }\r\n        }\r\n    },\r\n    \r\n    attachPDFPreviewHandler($dropZone, titleText) {\r\n        var $droppable = $dropZone.closest('.droppable');\r\n        var self = this;\r\n\r\n        \/\/ \u2705 Adapter le libell\u00e9 selon le format (communiqu\u00e9 \/ interview)\r\n        var kitFormat = $droppable.data('kitFormatSelect') || '';\r\n        var isInterview = (kitFormat || titleText || '').toLowerCase().indexOf('interview') !== -1;\r\n        var formatLabel = isInterview ? 'l\\'interview' : 'le communiqu\u00e9';\r\n        $droppable.find('.doc-preview-readmore').text('Ouvrir et visualiser ' + formatLabel);\r\n\r\n        \/\/ \u2705 v2.0.11 : D\u00e9l\u00e9gation d'\u00e9v\u00e9nement \u2014 plus robuste sur mobile\r\n        $droppable.off('click.docpreview').on('click.docpreview', '.doc-preview-readmore', (event) => {\r\n            event.preventDefault();\r\n            event.stopPropagation();\r\n            var popupTitle = isInterview ? 'Interview' : 'Communiqu\u00e9';\r\n\r\n            \/\/ \u2705 Priorit\u00e9 1 : pdfImageDataURL dispo (Kit)\r\n            var kitPdfImage = $droppable.data('kitPdfImageDataURL');\r\n            if (kitPdfImage) {\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    pdfDataURL: kitPdfImage\r\n                });\r\n                return;\r\n            }\r\n\r\n            \/\/ \u2705 Priorit\u00e9 2 : PDF upload\u00e9 manuellement \u2192 popup inline aussi\r\n            if (self.pdfDataForViewer ? self.pdfDataForViewer.byteLength > 0 : false) {\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    pdfArrayBuffer: self.pdfDataForViewer\r\n                });\r\n                return;\r\n            }\r\n\r\n            console.error('PDF data is not available');\r\n        });\r\n    },\r\n\r\n    \/\/ \u2705 v1.19.1 : Popup inline document \u2014 position:absolute + ScrollHelper.getVisibleTop()\r\n    \/\/ Le parent envoie visibleTopIframe dans le message scroll \u2192 positionnement fiable.\r\n    showInlineDocPopup($dropZone, options) {\r\n        var existing = document.getElementById('via-pdf-inline-popup');\r\n        if (existing) existing.remove();\r\n\r\n        var $droppable = $dropZone.closest('.droppable');\r\n        var droppableW = $droppable.outerWidth() || 300;\r\n        var popupW = Math.round(droppableW * 1.15);\r\n        \/\/ \u2705 v2.0.11 : Contraindre la largeur au viewport sur mobile (\u00e9vite troncature gauche\/droite)\r\n        var viewportW = document.documentElement.clientWidth || window.innerWidth;\r\n        if (UIManager.isMobile()) {\r\n            popupW = Math.min(popupW, viewportW - 8);\r\n        }\r\n        var popupH = Math.round(popupW * 2.5 * 0.51);\r\n\r\n        \/\/ \u2500\u2500 Position visible r\u00e9elle \u2500\u2500\r\n        var visibleTop = ScrollHelper.getVisibleTop();\r\n        var popupTop = Math.round(visibleTop + 284);\r\n\r\n        \/\/ \u2500\u2500 Popup position:absolute (m\u00eame approche que la r\u00e9gie) \u2500\u2500\r\n        var popup = document.createElement('div');\r\n        popup.id = 'via-pdf-inline-popup';\r\n        \/\/ v4.9ds : initialiser le z-index avec le compteur global window.popupZIndex\r\n        \/\/   pour que le popup PDF s'int\u00e8gre au syst\u00e8me _bringPopupToFront (Entete.txt:921+).\r\n        \/\/   Au prochain mousedown sur ce popup, le s\u00e9lecteur _viaTopMostSel le d\u00e9tecte\r\n        \/\/   et incr\u00e9mente son z-index \u2192 il passe en avant-plan par rapport aux autres\r\n        \/\/   popups (Ele0A, miniature, CGV, etc.). Skip sur mobile (perturberait le rendu).\r\n        var _initZ;\r\n        if (typeof window.popupZIndex === 'number' ? window.innerWidth >= 1000 : false) {\r\n            window.popupZIndex++;\r\n            _initZ = window.popupZIndex;\r\n        } else {\r\n            _initZ = 99999;\r\n        }\r\n        popup.style.cssText =\r\n            'position:absolute;z-index:' + _initZ + ';top:' + popupTop + 'px;' +\r\n            'width:' + popupW + 'px;height:' + popupH + 'px;' +\r\n            'background:#fff;border-radius:8px 8px 0 0;padding:0;' +\r\n            'box-shadow:0 8px 32px rgba(0,0,0,0.35);display:flex;flex-direction:column;' +\r\n            'min-width:200px;min-height:150px;touch-action:none;';\r\n\r\n        \/\/ Centrer horizontalement sur le droppable (coordonn\u00e9es absolues)\r\n        var droppableRect = $droppable[0].getBoundingClientRect();\r\n        var scrollX = window.scrollX || 0;\r\n        var initLeft = Math.round(droppableRect.left + scrollX + (droppableRect.width - popupW) \/ 2);\r\n        initLeft = Math.max(4, initLeft);\r\n        \/\/ \u2705 v2.0.11 : Contraindre \u00e0 droite aussi sur mobile\r\n        if (UIManager.isMobile()) {\r\n            initLeft = Math.min(initLeft, viewportW - popupW - 4);\r\n            initLeft = Math.max(4, initLeft);\r\n        }\r\n        popup.style.left = initLeft + 'px';\r\n\r\n        \/\/ \u2500\u2500 Header \u2500\u2500\r\n        var header = document.createElement('div');\r\n        header.style.cssText =\r\n            'display:flex;align-items:center;justify-content:center;position:relative;' +\r\n            'padding:10px 14px;background:#f0f0f0;color:#494949;flex-shrink:0;' +\r\n            'cursor:grab;user-select:none;border-radius:8px 8px 0 0;';\r\n        var titleEl = document.createElement('span');\r\n        titleEl.style.cssText = 'font-family:Roboto,Arial,sans-serif;font-size:15px;font-weight:600;';\r\n        titleEl.textContent = options.formatTitle || 'Document';\r\n        header.appendChild(titleEl);\r\n\r\n        var closeBtn = document.createElement('button');\r\n        closeBtn.textContent = '\u00d7';\r\n        closeBtn.style.cssText =\r\n            'position:absolute;right:10px;top:50%;transform:translateY(-50%);' +\r\n            'background:transparent;border:none;color:#494949;font-size:20px;' +\r\n            'cursor:pointer;line-height:1;padding:0 4px;';\r\n        closeBtn.addEventListener('click', cleanup);\r\n        header.appendChild(closeBtn);\r\n        popup.appendChild(header);\r\n\r\n        \/\/ \u2500\u2500 Zone scrollable \u2014 pleine largeur \u2500\u2500\r\n        var scrollZone = document.createElement('div');\r\n        scrollZone.style.cssText =\r\n            'flex:1;overflow-y:auto;overflow-x:hidden;padding:0;margin:0;touch-action:pan-y;';\r\n        popup.appendChild(scrollZone);\r\n\r\n        var loader = document.createElement('div');\r\n        loader.style.cssText =\r\n            'text-align:center;padding:30px;font-family:Roboto,Arial,sans-serif;font-size:12px;color:#666;';\r\n        loader.textContent = 'Chargement du document\u2026';\r\n        scrollZone.appendChild(loader);\r\n\r\n        \/\/ \u2500\u2500 8 poign\u00e9es de redimensionnement \u2500\u2500\r\n        var handles = [\r\n            { cursor:'nw-resize', pos:'top:-4px;left:-4px;',       dx:-1, dy:-1 },\r\n            { cursor:'n-resize',  pos:'top:-4px;left:50%;',        dx:0,  dy:-1 },\r\n            { cursor:'ne-resize', pos:'top:-4px;right:-4px;',      dx:1,  dy:-1 },\r\n            { cursor:'w-resize',  pos:'top:50%;left:-4px;',        dx:-1, dy:0  },\r\n            { cursor:'e-resize',  pos:'top:50%;right:-4px;',       dx:1,  dy:0  },\r\n            { cursor:'sw-resize', pos:'bottom:-4px;left:-4px;',    dx:-1, dy:1  },\r\n            { cursor:'s-resize',  pos:'bottom:-4px;left:50%;',     dx:0,  dy:1  },\r\n            { cursor:'se-resize', pos:'bottom:-4px;right:-4px;',   dx:1,  dy:1  }\r\n        ];\r\n        handles.forEach(function(h) {\r\n            var handle = document.createElement('div');\r\n            var isCorner = (h.dx !== 0 ? h.dy !== 0 : false);\r\n            handle.style.cssText =\r\n                'position:absolute;touch-action:none;' + h.pos +\r\n                'width:' + (isCorner ? '12px' : (h.dy === 0 ? '8px' : '30px')) + ';' +\r\n                'height:' + (isCorner ? '12px' : (h.dy === 0 ? '30px' : '8px')) + ';' +\r\n                'cursor:' + h.cursor + ';z-index:10;';\r\n            (function(hInfo) {\r\n                handle.addEventListener('pointerdown', function(e) {\r\n                    e.preventDefault(); e.stopPropagation();\r\n                    handle.setPointerCapture(e.pointerId);\r\n                    var startX = e.clientX, startY = e.clientY;\r\n                    var rect0 = popup.getBoundingClientRect();\r\n                    var sY = window.scrollY || 0, sX = window.scrollX || 0;\r\n                    var startW = rect0.width, startH = rect0.height;\r\n                    var startL = rect0.left + sX, startT = rect0.top + sY;\r\n                    function onResize(ev) {\r\n                        ev.preventDefault();\r\n                        var ddx = ev.clientX - startX, ddy = ev.clientY - startY;\r\n                        var newW = startW, newH = startH, newL = startL, newT = startT;\r\n                        if (hInfo.dx === 1)  newW = Math.max(200, startW + ddx);\r\n                        if (hInfo.dx === -1) { newW = Math.max(200, startW - ddx); newL = startL + ddx; }\r\n                        if (hInfo.dy === 1)  newH = Math.max(150, startH + ddy);\r\n                        if (hInfo.dy === -1) { newH = Math.max(150, startH - ddy); newT = startT + ddy; }\r\n                        popup.style.width  = newW + 'px';\r\n                        popup.style.height = newH + 'px';\r\n                        popup.style.left   = newL + 'px';\r\n                        popup.style.top    = newT + 'px';\r\n                    }\r\n                    function onResizeEnd(ev) {\r\n                        handle.releasePointerCapture(ev.pointerId);\r\n                        handle.removeEventListener('pointermove', onResize);\r\n                        handle.removeEventListener('pointerup', onResizeEnd);\r\n                    }\r\n                    handle.addEventListener('pointermove', onResize);\r\n                    handle.addEventListener('pointerup', onResizeEnd);\r\n                });\r\n            })(h);\r\n            popup.appendChild(handle);\r\n        });\r\n\r\n        \/\/ \u2500\u2500 Drag via header \u2500\u2500\r\n        header.style.touchAction = 'none';\r\n        header.addEventListener('pointerdown', function(e) {\r\n            if (e.target === closeBtn) return;\r\n            e.preventDefault();\r\n            header.setPointerCapture(e.pointerId);\r\n            var mx = e.clientX, my = e.clientY;\r\n            var popupRect = popup.getBoundingClientRect();\r\n            var scrollY = window.scrollY || 0;\r\n            var scrollX2 = window.scrollX || 0;\r\n            var curLeft = popupRect.left + scrollX2;\r\n            var curTop  = popupRect.top  + scrollY;\r\n            header.style.cursor = 'grabbing';\r\n            function onMove(ev) {\r\n                ev.preventDefault();\r\n                var dx = ev.clientX - mx;\r\n                var dy = ev.clientY - my;\r\n                curLeft += dx;\r\n                curTop  += dy;\r\n                popup.style.left = curLeft + 'px';\r\n                popup.style.top  = curTop  + 'px';\r\n                mx = ev.clientX;\r\n                my = ev.clientY;\r\n            }\r\n            function onUp(ev) {\r\n                header.releasePointerCapture(ev.pointerId);\r\n                header.style.cursor = 'grab';\r\n                header.removeEventListener('pointermove', onMove);\r\n                header.removeEventListener('pointerup', onUp);\r\n            }\r\n            header.addEventListener('pointermove', onMove);\r\n            header.addEventListener('pointerup', onUp);\r\n        });\r\n\r\n        \/\/ \u2500\u2500 Cleanup \u2500\u2500\r\n        function cleanup() {\r\n            popup.remove();\r\n            document.removeEventListener('keydown', escHandler);\r\n        }\r\n        var escHandler = function(e) { if (e.key === 'Escape') cleanup(); };\r\n        document.addEventListener('keydown', escHandler);\r\n\r\n        document.body.appendChild(popup);\r\n\r\n        \/\/ \u2500\u2500 Rendre le contenu \u2500\u2500\r\n        if (options.pdfDataURL)          this.renderPdfInPopup(scrollZone, options.pdfDataURL, popupW, 'dataurl');\r\n        else if (options.pdfArrayBuffer) this.renderPdfInPopup(scrollZone, options.pdfArrayBuffer, popupW, 'arraybuffer');\r\n        else if (options.htmlContent)    this.renderHtmlInPopup(scrollZone, options.htmlContent);\r\n    },\r\n\r\n    \/\/ Alias pour compatibilit\u00e9\r\n    showInlinePdfPopup($dropZone, pdfImageDataURL, formatTitle) {\r\n        this.showInlineDocPopup($dropZone, { formatTitle: formatTitle, pdfDataURL: pdfImageDataURL });\r\n    },\r\n\r\n    \/\/ \u2705 Rendu PDF dans popup (dataurl ou arraybuffer) \u2014 avec crop des marges blanches\r\n    async renderPdfInPopup(container, pdfData, popupWidth, mode) {\r\n        try {\r\n            var u8;\r\n            if (mode === 'dataurl') {\r\n                var b64 = pdfData.split(',')[1];\r\n                var bstr = atob(b64);\r\n                u8 = new Uint8Array(bstr.length);\r\n                for (var i = 0; i < bstr.length; i++) { u8[i] = bstr.charCodeAt(i); }\r\n            } else {\r\n                u8 = new Uint8Array(pdfData);\r\n            }\r\n\r\n            var pdfjsLib = window['pdfjs-dist\/build\/pdf'];\r\n            if (!pdfjsLib) {\r\n                container.innerHTML = '<div style=\"padding:20px;text-align:center;color:#c00;\">pdf.js non charg\u00e9<\/div>';\r\n                return;\r\n            }\r\n            pdfjsLib.GlobalWorkerOptions.workerSrc =\r\n                '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n\r\n            var pdf = await pdfjsLib.getDocument({ data: u8 }).promise;\r\n            console.log('\ud83d\udcc4 PDF popup :', pdf.numPages, 'pages');\r\n            container.innerHTML = '';\r\n\r\n            var firstPage = await pdf.getPage(1);\r\n            var vpNative = firstPage.getViewport({ scale: 1 });\r\n            var scale = popupWidth \/ vpNative.width;\r\n\r\n            \/\/ \u2705 R\u00e9f\u00e9rence au popup pour ajuster sa hauteur apr\u00e8s rendu\r\n            var _popup = container.closest('#via-pdf-inline-popup');\r\n            var _headerH = _popup ? (_popup.querySelector('div[style*=\"grab\"]') || {offsetHeight: 44}).offsetHeight : 44;\r\n\r\n            \/\/ \u2705 D\u00e9tecte les marges blanches haut\/bas\/gauche\/droite d'un canvas rendu\r\n            \/\/ v4.9ds (pdf-popup-fullwidth) : scan des 4 c\u00f4t\u00e9s (avant : top\/bottom seulement),\r\n            \/\/   pour que le contenu PDF prenne toute la largeur de la popup m\u00eame si le PDF\r\n            \/\/   d'origine a des marges lat\u00e9rales blanches importantes (cas PDFs kit Interview\/\r\n            \/\/   Communiqu\u00e9 portrait avec contenu centr\u00e9 ~40% de la largeur).\r\n            \/\/   Pour left\/right, on scanne UNIQUEMENT entre topRow et bottomRow d\u00e9j\u00e0 d\u00e9termin\u00e9s\r\n            \/\/   afin de ne pas \u00eatre tromp\u00e9 par du blanc d\u00e9j\u00e0 sur le point d'\u00eatre crop\u00e9.\r\n            function detectWhiteMargins(canvas) {\r\n                var ctx = canvas.getContext('2d');\r\n                var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;\r\n                var W = canvas.width, H = canvas.height;\r\n                var topRow = 0, bottomRow = H - 1, leftCol = 0, rightCol = W - 1;\r\n                \/\/ Top : premi\u00e8re ligne non-blanche (tol\u00e9rance 252 pour anti-aliasing)\r\n                outer: for (var y = 0; y < H; y++) {\r\n                    for (var x = 0; x < W; x++) {\r\n                        var i = (y * W + x) * 4;\r\n                        if (data[i] < 252 || data[i+1] < 252 || data[i+2] < 252) { topRow = y; break outer; }\r\n                    }\r\n                }\r\n                \/\/ Bottom : derni\u00e8re ligne non-blanche\r\n                outer2: for (var y2 = H - 1; y2 >= topRow; y2--) {\r\n                    for (var x2 = 0; x2 < W; x2++) {\r\n                        var i2 = (y2 * W + x2) * 4;\r\n                        if (data[i2] < 252 || data[i2+1] < 252 || data[i2+2] < 252) { bottomRow = y2; break outer2; }\r\n                    }\r\n                }\r\n                \/\/ Left : premi\u00e8re colonne non-blanche (scan limit\u00e9 \u00e0 la zone [topRow..bottomRow])\r\n                outer3: for (var x3 = 0; x3 < W; x3++) {\r\n                    for (var y3 = topRow; y3 <= bottomRow; y3++) {\r\n                        var i3 = (y3 * W + x3) * 4;\r\n                        if (data[i3] < 252 || data[i3+1] < 252 || data[i3+2] < 252) { leftCol = x3; break outer3; }\r\n                    }\r\n                }\r\n                \/\/ Right : derni\u00e8re colonne non-blanche\r\n                outer4: for (var x4 = W - 1; x4 >= leftCol; x4--) {\r\n                    for (var y4 = topRow; y4 <= bottomRow; y4++) {\r\n                        var i4 = (y4 * W + x4) * 4;\r\n                        if (data[i4] < 252 || data[i4+1] < 252 || data[i4+2] < 252) { rightCol = x4; break outer4; }\r\n                    }\r\n                }\r\n                return { top: topRow, bottom: bottomRow, left: leftCol, right: rightCol };\r\n            }\r\n\r\n            for (var pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\r\n                var page = await pdf.getPage(pageNum);\r\n                var vp = page.getViewport({ scale: scale });\r\n                \/\/ Rendre dans un canvas temporaire\r\n                var tmpCanvas = document.createElement('canvas');\r\n                tmpCanvas.width = vp.width;\r\n                tmpCanvas.height = vp.height;\r\n                await page.render({ canvasContext: tmpCanvas.getContext('2d'), viewport: vp }).promise;\r\n\r\n                \/\/ \u2705 Crop marges blanches sur les 4 c\u00f4t\u00e9s (avec petite marge de s\u00e9curit\u00e9 de 4px)\r\n                var margins = detectWhiteMargins(tmpCanvas);\r\n                var cropTop    = Math.max(0, margins.top - 4);\r\n                var cropBottom = Math.min(tmpCanvas.height - 1, margins.bottom + 4);\r\n                var cropLeft   = Math.max(0, margins.left - 4);\r\n                var cropRight  = Math.min(tmpCanvas.width - 1, margins.right + 4);\r\n                var croppedH = cropBottom - cropTop + 1;\r\n                var croppedW = cropRight - cropLeft + 1;\r\n\r\n                \/\/ Canvas final crop\u00e9 sur les 4 c\u00f4t\u00e9s\r\n                var canvas = document.createElement('canvas');\r\n                canvas.width = croppedW;\r\n                canvas.height = croppedH;\r\n                canvas.getContext('2d').drawImage(tmpCanvas, cropLeft, cropTop, croppedW, croppedH, 0, 0, croppedW, croppedH);\r\n                canvas.style.cssText = 'display:block;width:100%;height:auto;margin:0;padding:0;';\r\n                container.appendChild(canvas);\r\n                console.log('\ud83d\udcc4 Page', pageNum, '- crop H:', cropTop, '\u2192', cropBottom, '\/ W:', cropLeft, '\u2192', cropRight, '(' + Math.round((cropLeft + (tmpCanvas.width - cropRight - 1)) \/ tmpCanvas.width * 100) + '% width trimmed)');\r\n            }\r\n\r\n            \/\/ \u2705 Ajuster la hauteur du popup \u00e0 la hauteur r\u00e9elle du contenu rendu\r\n            \/\/ (popup \u00e9tait calcul\u00e9 sur popupW*2.5*0.51 qui peut \u00eatre trop grand ou trop petit)\r\n            \/\/ v4.9ds (pdf-popup-fullwidth) : _maxPopupH calcul\u00e9 en fonction de la position\r\n            \/\/   r\u00e9elle de la popup (popupTop dans le viewport) + 20px de marge en bas, pour\r\n            \/\/   garantir que la popup ne d\u00e9passe jamais le viewport. Si le contenu est plus\r\n            \/\/   haut, le overflow-y:auto de scrollZone affiche la barre de d\u00e9filement.\r\n            if (_popup) {\r\n                var _totalH = 0;\r\n                container.querySelectorAll('canvas').forEach(function(c) { _totalH += c.height * (popupWidth \/ c.width); });\r\n                var _viewportH = window.innerHeight || document.documentElement.clientHeight || 600;\r\n                var _popupRectNow = _popup.getBoundingClientRect();\r\n                var _popupTopVisible = _popupRectNow.top;\r\n                \/\/ Plafond strict : viewport - top de la popup - 20px de marge en bas\r\n                var _maxPopupH = Math.max(200, _viewportH - _popupTopVisible - 20);\r\n                var _idealH = Math.min(_totalH + _headerH + 8, _maxPopupH);\r\n                _popup.style.height = _idealH + 'px';\r\n                console.log('\ud83d\udcd0 Popup redimensionn\u00e9:', Math.round(_idealH), 'px (contenu:', Math.round(_totalH), ', max:', Math.round(_maxPopupH), ')');\r\n            }\r\n        } catch (err) {\r\n            console.error('\u274c Erreur rendu PDF popup:', err);\r\n            container.innerHTML =\r\n                '<div style=\"padding:20px;text-align:center;color:#c00;font-size:12px;\">Erreur lors du chargement du document<\/div>';\r\n        }\r\n    },\r\n\r\n    \/\/ \u2705 Rendu HTML (Word) dans popup \u2014 avec CSS complet pour mammoth\r\n    renderHtmlInPopup(container, htmlContent) {\r\n        container.innerHTML = '';\r\n        \/\/ \u2705 Nettoyer les <p> vides en d\u00e9but\/fin produits par mammoth (marges parasites)\r\n        htmlContent = htmlContent\r\n            .replace(\/^(\\s*<p[^>]*>\\s*(<br\\s*\\\/?>\\s*)*<\\\/p>\\s*)+\/i, '')\r\n            .replace(\/(\\s*<p[^>]*>\\s*(<br\\s*\\\/?>\\s*)*<\\\/p>\\s*)+$\/i, '');\r\n        \/\/ \u2500\u2500 Styles pour le HTML g\u00e9n\u00e9r\u00e9 par mammoth (titres, paragraphes, listes, images) \u2500\u2500\r\n        var style = document.createElement('style');\r\n        style.textContent = [\r\n            '.via-html-popup-body { padding:20px 24px; background:#fff; color:#222; font-family:Georgia,\"Times New Roman\",serif; font-size:15px; line-height:1.7; }',\r\n            '.via-html-popup-body h1 { font-size:22px; font-weight:700; margin:0 0 12px; line-height:1.3; }',\r\n            '.via-html-popup-body h2 { font-size:18px; font-weight:700; margin:18px 0 8px; line-height:1.3; }',\r\n            '.via-html-popup-body h3 { font-size:15px; font-weight:700; margin:14px 0 6px; }',\r\n            '.via-html-popup-body p  { margin:0 0 10px; }',\r\n            '.via-html-popup-body p:first-child { margin-top:0; }',\r\n            '.via-html-popup-body strong, .via-html-popup-body b { font-weight:700; }',\r\n            '.via-html-popup-body em, .via-html-popup-body i { font-style:italic; }',\r\n            '.via-html-popup-body ul, .via-html-popup-body ol { margin:6px 0 10px 22px; padding:0; }',\r\n            '.via-html-popup-body li { margin-bottom:4px; }',\r\n            '.via-html-popup-body img { max-width:100%; height:auto; display:block; margin:10px auto; border-radius:4px; }',\r\n            '.via-html-popup-body table { width:100%; border-collapse:collapse; margin:10px 0; font-size:13px; }',\r\n            '.via-html-popup-body td, .via-html-popup-body th { border:1px solid #ddd; padding:6px 8px; vertical-align:top; }',\r\n            '.via-html-popup-body th { background:#f0f4f8; font-weight:700; }',\r\n            '.via-html-popup-body a { color:#225da9; text-decoration:underline; }'\r\n        ].join('');\r\n        container.appendChild(style);\r\n        var wrapper = document.createElement('div');\r\n        wrapper.className = 'via-html-popup-body';\r\n        wrapper.innerHTML = htmlContent;\r\n        container.appendChild(wrapper);\r\n    },\r\n    \r\n    async renderPDFInWindow(childWindow, container) {\r\n        container.innerHTML = '';\r\n        \r\n        const script = childWindow.document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.4.120\/pdf.min.js';\r\n        childWindow.document.head.appendChild(script);\r\n        \r\n        script.onload = async () => {\r\n            const pdfjsLib = childWindow.pdfjsLib;\r\n            const viewerContainer = childWindow.document.createElement('div');\r\n            viewerContainer.style.width = '100%';\r\n            viewerContainer.style.backgroundColor = 'white';\r\n            viewerContainer.style.position = 'relative';\r\n            container.appendChild(viewerContainer);\r\n            \r\n            const loadingTask = pdfjsLib.getDocument({data: window.pdfDataToTransfer});\r\n            const pdf = await loadingTask.promise;\r\n            \r\n            const firstPage = await pdf.getPage(1);\r\n            const viewport = firstPage.getViewport({scale: 1.5});\r\n            const pageHeight = viewport.height;\r\n            \r\n            const spacing = -15;\r\n            const totalHeight = (pageHeight * pdf.numPages) + (spacing * (pdf.numPages - 1));\r\n            viewerContainer.style.height = `${totalHeight}px`;\r\n            \r\n            for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\r\n                const page = await pdf.getPage(pageNum);\r\n                const canvas = childWindow.document.createElement('canvas');\r\n                const context = canvas.getContext('2d');\r\n                \r\n                canvas.width = viewport.width;\r\n                canvas.height = viewport.height;\r\n                \r\n                canvas.style.position = 'absolute';\r\n                canvas.style.left = '50%';\r\n                canvas.style.transform = 'translateX(-50%)';\r\n                canvas.style.top = `${(pageNum - 1) * (pageHeight + spacing)}px`;\r\n                \r\n                await page.render({\r\n                    canvasContext: context,\r\n                    viewport: viewport\r\n                }).promise;\r\n                \r\n                viewerContainer.appendChild(canvas);\r\n            }\r\n        };\r\n    },\r\n    \r\n    arrayBufferToBase64(buffer) {\r\n        let binary = '';\r\n        const bytes = new Uint8Array(buffer);\r\n        const len = bytes.byteLength;\r\n        for (let i = 0; i < len; i++) {\r\n            binary += String.fromCharCode(bytes[i]);\r\n        }\r\n        return window.btoa(binary);\r\n    },\r\n    \r\n    base64ToArrayBuffer(base64) {\r\n        const binaryString = window.atob(base64);\r\n        const len = binaryString.length;\r\n        const bytes = new Uint8Array(len);\r\n        for (let i = 0; i < len; i++) {\r\n            bytes[i] = binaryString.charCodeAt(i);\r\n        }\r\n        return bytes.buffer;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des \u00e9v\u00e9nements de drop et d'upload\r\n *\/\r\nconst DropHandler = {\r\n    async handleDrop(e) {\r\n        e.preventDefault();\r\n        \r\n        const $currentTarget = this.findDropTarget(e);\r\n        \r\n        if (!$currentTarget) {\r\n            console.log('No valid drop target found');\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 V\u00c9RIFIER SI C'EST UN D\u00c9PLACEMENT (pas besoin de v\u00e9rifier le format)\r\n        const isMoved = StateManager.get('FirstUploadFileorMoved') === 'Moved';\r\n        const dragstartRef = StateManager.get('dragstart_Commande_Emplacement_Page_Web');\r\n        let hasDragstartRef = false;\r\n        \r\n        if (dragstartRef) {\r\n            if (dragstartRef !== 'No') {\r\n                hasDragstartRef = true;\r\n            }\r\n        }\r\n        \r\n        let isDeplacementAnnonce = false;\r\n        if (isMoved) {\r\n            if (hasDragstartRef) {\r\n                isDeplacementAnnonce = true;\r\n            }\r\n        }\r\n        \r\n        if (isDeplacementAnnonce) {\r\n            console.log('\ud83d\udd04 D\u00e9placement d\u00e9tect\u00e9 - contr\u00f4le format ignor\u00e9');\r\n        } else {\r\n            \/\/ \u2705 v2.7.3 : Le format est d\u00e9duit de l'extension du fichier d\u00e9pos\u00e9\r\n            \/\/ \u2192 ne plus bloquer le d\u00e9p\u00f4t si aucun format s\u00e9lectionn\u00e9\r\n            console.log('\ud83d\udd04 Nouveau d\u00e9p\u00f4t \u2014 format sera d\u00e9duit du fichier, contr\u00f4le format ignor\u00e9');\r\n        }\r\n    \r\n        console.log(\"Drop at:\", $currentTarget);\r\n        \r\n        this.updateEmplacementState($currentTarget);\r\n        \r\n        const shouldProcess = this.shouldProcessDrop(e, $currentTarget);\r\n        \r\n        if (shouldProcess) {\r\n            await this.processFileDrop(e, $currentTarget);\r\n        } else {\r\n            this.processVideoDrop($currentTarget);\r\n        }\r\n        \r\n        DragDropManager.clearDataTransferFiles(e);\r\n    },\r\n    \r\n    findDropTarget(e) {\r\n        \/\/ \u2705 v2.4.3 : Si Ele0A est actif et le drop arrive sur Ele1A \u2192 rediriger vers Ele0A\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            var _ele0ADrop = document.querySelector('#Ele0A #drop_file_zone_achat');\r\n            if (_ele0ADrop) {\r\n                console.log('\ud83c\udfaf findDropTarget \u2014 PopUpChoice=Yes \u2192 cible forc\u00e9e: Ele0A');\r\n                return jQuery(_ele0ADrop);\r\n            }\r\n        }\r\n\r\n        let target = e.target.closest('#drop_file_zone_achat');\r\n        \r\n        if (!target) {\r\n            target = e.currentTarget.closest('#drop_file_zone_achat');\r\n            console.log('Drop target found 1');\r\n        }\r\n        \r\n        if (!target) {\r\n            target = jQuery(e.target).closest('#drop_file_zone_achat')[0];\r\n            console.log('Drop target found 2');\r\n        }\r\n        \r\n        return target ? jQuery(target) : null;\r\n    },\r\n    \r\n    updateEmplacementState($target) {\r\n        const espaceId = $target.closest('.droppable').attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', espaceId);\r\n        StateManager.set('Commande_Emplacement_Page_Web',\r\n            StateManager.buildEmplacementReference(espaceId));\r\n        \r\n        UIManager.updateEmplacementDisplay();\r\n        \r\n        console.log(\"Rank_Emplacement_Page_Web:\", StateManager.get('Rank_Emplacement_Page_Web'));\r\n        console.log(\"Droppable at:\", StateManager.get('Commande_Emplacement_Page_Web'));\r\n    },\r\n    \r\n    shouldProcessDrop(e, $target) {\r\n        const hasFiles = e.dataTransfer.files.length > 0;\r\n        const isRedactionnel = StateManager.get('Commande_Format_Transmis') === 'R\u00e9dactionnel';\r\n        const isValidBackground = window.getComputedStyle(\r\n            $target.closest('#UploadFileConteneur')[0]\r\n        ).backgroundColor !== 'rgba(0, 0, 0, 0)';\r\n        \r\n        return (hasFiles || isRedactionnel) ? isValidBackground : false;\r\n    },\r\n    \r\n    async processFileDrop(e, $target) {\r\n        jQuery('.MsgAdNotDisplayed').hide();\r\n        StateManager.setMultiple({\r\n            \"AdDisplayed\": 'Yes',\r\n            \/\/ \u2705 NE PLUS mettre sendDataToParentFlag ici - c'est la checkbox \"R\u00e9server\" qui le fera\r\n            \/\/ \"sendDataToParentFlag\": 'Yes'\r\n        });\r\n        \r\n        console.log('ajaxFileUpload_achat launched');\r\n        console.log(\"FirstUploadFileorMoved:\", StateManager.get('FirstUploadFileorMoved'));\r\n        \r\n        if (UIManager.isMobile()) {\r\n            $target = jQuery('#Ele1A').find('#drop_file_zone_achat');\r\n        }\r\n        \r\n        if (StateManager.get('Commande_Format_Transmis') === 'R\u00e9dactionnel') {\r\n            const fileURL = StateManager.get('FullPathAdFile');\r\n            const filename = fileURL.substring(fileURL.indexOf('_') + 1);\r\n            \r\n            try {\r\n                \/\/ \u2705 R\u00e9utiliser le File cach\u00e9 (\u00e9vite CORS cross-domain)\r\n                let file;\r\n                if (window._lastRedactionnelFile) {\r\n                    file = window._lastRedactionnelFile;\r\n                    console.log('\u267b\ufe0f R\u00e9utilisation du File cach\u00e9:', file.name, Math.round(file.size \/ 1024) + 'KB');\r\n                } else {\r\n                    file = await FileManager.urlToFile(fileURL, filename);\r\n                }\r\n                await UploadManager.handleFileUpload(file, $target);\r\n            } catch (error) {\r\n                console.error('Error:', error);\r\n            }\r\n        } else {\r\n            await UploadManager.handleFileUpload(e.dataTransfer.files[0], $target);\r\n        }\r\n    },\r\n    \r\n    processVideoDrop($target) {\r\n        const isValidBackground = window.getComputedStyle(\r\n            $target.closest('#UploadFileConteneur')[0]\r\n        ).backgroundColor !== 'rgba(0, 0, 0, 0)';\r\n        \r\n        if (!isValidBackground) {\r\n            jQuery('.MsgAdNotDisplayed').show();\r\n            StateManager.set(\"AdDisplayed\", 'No');\r\n            return;\r\n        }\r\n        \r\n        StateManager.set(\"AdDisplayed\", 'Yes');\r\n        \r\n        const objectUrl = StateManager.get('videoSrc');\r\n        const $previousDropZone = $('.drop_file_zone_achat_class').has(`video[src=\"${objectUrl}\"]`);\r\n        window.RestoreadSpaceTemplate($previousDropZone);\r\n        \r\n        const videoElement = jQuery('<video controls autoplay muted>').attr({\r\n            'src': objectUrl,\r\n            'max-width': '100%',\r\n            'max-height': '100%',\r\n            'draggable': 'true'\r\n        }).css({\r\n            'max-width': '100%',\r\n            'max-height': '100%'\r\n        });\r\n        \r\n        $target.empty().append(videoElement);\r\n        \r\n        $target.closest('#UploadFileConteneur').css({'background-color': '#FFFFFF00'});\r\n        \r\n        var _isViaPopupParentV = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n        $target.closest('.OrdiMobileConteneurClass')\r\n            .find('.RefEspacePublicitaire')\r\n            .html('R\u00e9f\u00e9rence de l\\'espace : ' + StateManager.get('Commande_Emplacement_Page_Web'))\r\n            .attr('id', 'RefEspacePublicitaire')\r\n            .each(function() {\r\n                var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                this.style.setProperty('display', 'block', 'important');\r\n                if (_isViaPopupParentV) { this.style.setProperty('margin-top', '4px', 'important'); }\r\n            });\r\n        (function() {\r\n            var _rankPosV = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            var _posLibV = PreviewRenderer._getPositionLibelle(_rankPosV);\r\n            if (_posLibV) {\r\n                $target.closest('.OrdiMobileConteneurClass')\r\n                    .find('.PositionEspacePublicitaireDeplacer')\r\n                    .text(_posLibV)\r\n                    .each(function() {\r\n                        var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                        if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                        this.style.setProperty('display', 'block', 'important');\r\n                        if (_isViaPopupParentV) { this.style.setProperty('margin-top', '4px', 'important'); }\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        $target.closest('.OrdiMobileConteneurClass').find('.AdUploadedTitle').show();\r\n        \r\n        $target.closest('.OrdiMobileConteneurClass')\r\n            .css({'top': '60px', 'margin-bottom': '80px'});\r\n        \r\n        $target.closest('.HTMLUploadfileConteneur')\r\n            .not('.AdUploadedTitle')\r\n            .css({\r\n                'top': '0px',\r\n                'margin-bottom': '0px',\r\n                'box-shadow': 'inset 0 0 0 2px #00FF19',  \/\/ \u2705 v2.4.5\r\n                'background-color': 'white'\r\n            });\r\n        \r\n        $target.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, .ChoisirEspacePublicitaire2ndLigne, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        jQuery('#MsgElementsCommandeValides, #MessageOptionsacompleterConteneur').hide();\r\n        \r\n        StateManager.set(\"FirstUploadFileorMoved\", 'Moved');\r\n        \r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server (au lieu d'envoyer automatiquement)\r\n        FormatUIManager.updateReserverCheckboxState($target.closest('.droppable'));\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'explorateur de fichiers\r\n *\/\r\nconst FileExplorer = {\r\n    isRunning: false,\r\n\r\n    open(e) {\r\n        if (this.isRunning) {\r\n            return;\r\n        }\r\n        \r\n        const $element = jQuery(e.target).closest('.droppable');\r\n        \r\n        \/\/ \u2705 v2.7.3 : Le format sera d\u00e9duit de l'extension du fichier s\u00e9lectionn\u00e9\r\n        \/\/ \u2192 ne plus bloquer l'ouverture du s\u00e9lecteur si aucun format n'est encore choisi\r\n        this.isRunning = true;\r\n        \r\n        const rankId = $element.attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n        StateManager.set('Commande_Emplacement_Page_Web',\r\n            StateManager.buildEmplacementReference(rankId));\r\n        \r\n        UIManager.updateEmplacementDisplay();\r\n        \r\n        console.log(\"Drop at:\", StateManager.get('Commande_Emplacement_Page_Web'));\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        \r\n        const $currentTarget = jQuery(e.target).closest('#drop_file_zone_achat');\r\n        const $fileInput = jQuery('#selectfile_achat');\r\n        \r\n        console.log(\"currentTarget\", $currentTarget);\r\n        \r\n        $fileInput.off('change');\r\n        $fileInput.off('click');\r\n        \r\n        const onChange = (event) => {\r\n            this.isRunning = false;\r\n            $fileInput.off('change', onChange);\r\n            \r\n            \/\/ \u2705 NE PLUS mettre sendDataToParentFlag ici\r\n            \/\/ StateManager.set(\"sendDataToParentFlag\", 'Yes');\r\n            \r\n            if ($fileInput[0].files.length > 0) {\r\n                UploadManager.handleFileUpload($fileInput[0].files[0], $currentTarget);\r\n            }\r\n        };\r\n        \r\n        const onClick = () => {\r\n            $fileInput.off('click', onClick);\r\n            $fileInput.on('focus', function onFocus() {\r\n                setTimeout(() => {\r\n                    if (!$fileInput.val()) {\r\n                        FileExplorer.isRunning = false;\r\n                    }\r\n                }, 200);\r\n                $fileInput.off('focus', onFocus);\r\n            });\r\n        };\r\n        \r\n        $fileInput.on('change', onChange);\r\n        $fileInput.on('click', onClick);\r\n        \r\n        \/\/ \u2705 Contr\u00f4le format imm\u00e9diat \u2014 avant d'ouvrir le s\u00e9lecteur de fichiers\r\n        var _$dropGuard = $currentTarget.closest('.droppable');\r\n        if (_$dropGuard.length) {\r\n            var _hasFmtNow = false;\r\n            _$dropGuard.find('.EspPubFormatContainer').each(function() {\r\n                if (jQuery(this).hasClass('FormatIdCreation')) return;\r\n                if (jQuery(this).hasClass('FormatIdPopUp')) return;\r\n                var _bg = this.style.backgroundColor || '';\r\n                if (_bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white') { _hasFmtNow = true; return false; }\r\n                var _fmtEl = this.querySelector('.EspPubFormat');\r\n                if (_fmtEl) { var _col = _fmtEl.style.color || ''; if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') { _hasFmtNow = true; return false; } }\r\n            });\r\n            if (!_hasFmtNow) {\r\n                UIManager.showFormatError($currentTarget, 'Merci de s\u00e9lectionner un format d\\'annonce avant de t\u00e9l\u00e9charger votre fichier');\r\n                FormatUIManager.flashTitle($currentTarget);\r\n                FileExplorer.isRunning = false;\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v2.7.3 : R\u00e9initialiser la valeur avant d'ouvrir le s\u00e9lecteur\r\n        \/\/ Sans ce reset, certains navigateurs (Safari mobile) ne d\u00e9clenchent pas\r\n        \/\/ l'\u00e9v\u00e9nement 'change' si un fichier avait d\u00e9j\u00e0 \u00e9t\u00e9 s\u00e9lectionn\u00e9 pr\u00e9c\u00e9demment\r\n        $fileInput.val('');\r\n        $fileInput.click();\r\n        \r\n        setTimeout(() => {\r\n            this.isRunning = false;\r\n        }, 300);\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion du reset d'annonce\r\n *\/\r\nconst AdResetHandler = {\r\n    handle(e) {\r\n        e.preventDefault();\r\n        console.log(\"CroixResetAnnonce click\");\r\n        \r\n        \/\/ Reset l'\u00e9tat EnvoiUlterieur\r\n        StateManager.set('EnvoiUlterieur', 'false');\r\n        \r\n        \/\/ \u2705 v1.19.6 : Reset FileReceived mais garder le format s\u00e9lectionn\u00e9\r\n        StateManager.set('FileReceived', 'No');\r\n        \r\n        const $element = jQuery(e.currentTarget);\r\n        const $droppable = $element.closest('.droppable');\r\n        const resetRef = StateManager.buildEmplacementReference($droppable.attr('id'));\r\n        \r\n        console.log(\"Reset_Commande_Emplacement_Page_Web:\", resetRef);\r\n        \r\n        \/\/ \u2705 Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \r\n        var _rankForDel = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n        \/\/ \u2705 Bug 10 : envoyer via postMessage si dans une iframe (pas seulement mode=popup)\r\n        \/\/   AchatEspaceCall=Yes uniquement pour mode=popup \u2014 pour les sites pays standards\r\n        \/\/   (iframe r\u00e9gie, non-popup), window.processdataDelAd n'existe pas dans le contexte iframe\r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes' || window.self !== window.top) {\r\n            MessageManager.sendDelAdToParent({\r\n                Commande_Emplacement_Page_Web: resetRef,\r\n                Rank_Emplacement_Page_Web: _rankForDel,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n        } else {\r\n            window.processdataDelAd({\r\n                Commande_Emplacement_Page_Web: resetRef,\r\n                Rank_Emplacement_Page_Web: _rankForDel,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n        }\r\n        \r\n        \/\/ \u2705 v1.16.0 : Appeler RestoreadSpaceTemplateLocal directement (accessible dans ce scope)\r\n        RestoreadSpaceTemplateLocal(e.currentTarget);\r\n        \r\n        window.FonctionCroixResetAnnonce(e.currentTarget);\r\n        \r\n        \/\/ \u2705 v2.1.1 : Sauf popup\r\n        var formatWasSelected = sessionStorage.getItem('FormatSelect') \r\n            || sessionStorage.getItem('Commande_Format_Transmis')\r\n            || $droppable.data('kitFormatSelect');\r\n        \r\n        if (sessionStorage.getItem('PopUpChoice') !== 'Yes' ? (formatWasSelected || sessionStorage.getItem('Formatchoisi') === 'Yes') : false) {\r\n            sessionStorage.setItem('Formatchoisi', 'Yes');\r\n            console.log('\u2705 Formatchoisi forc\u00e9 \u00e0 Yes apr\u00e8s reset');\r\n        }\r\n        \r\n        \/\/ \u2705 v1.19.6 : Remettre \u00e0 jour le titre format et r\u00e9afficher la checkbox R\u00e9server\r\n        setTimeout(() => {\r\n            FormatUIManager.updateTitleColor($droppable);\r\n            FormatUIManager.updateReserverCheckboxState($droppable);\r\n            \r\n            \/\/ \u2705 R\u00e9afficher le .ReserverContainer statique\r\n            $droppable.find('.ReserverContainer').show();\r\n        }, 150);\r\n    }\r\n};\r\n\r\n\/**\r\n * \u2705 Template pour r\u00e9initialisation des espaces publicitaires (IFRAME)\r\n *\/\r\nvar adSpaceTemplatesLocal = {};\r\n\r\nfunction saveAdSpaceTemplateLocal() {\r\n    var saved = 0;\r\n    jQuery('.droppable').each(function() {\r\n        var droppableId = jQuery(this).attr('id');\r\n        if (!droppableId) return;\r\n        \r\n        \/\/ \u2705 Ne jamais sauvegarder Ele0A (clone temporaire popup, pas un template d'origine)\r\n        if (droppableId === 'Ele0A') return;\r\n        \r\n        \/\/ \u2705 Ne pas r\u00e9-\u00e9craser un template d\u00e9j\u00e0 sauvegard\u00e9\r\n        if (adSpaceTemplatesLocal[droppableId]) return;\r\n        \r\n        \/\/ \u2705 v2.3.4 : Ne pas sauvegarder si template d\u00e9j\u00e0 connu (\u00e9tat propre sauvegard\u00e9)\r\n        \/\/ Le guard hasAd est supprim\u00e9 \u2014 on veut capturer le template le plus t\u00f4t possible\r\n        \/\/ Si le template existe d\u00e9j\u00e0, on ne le r\u00e9\u00e9crase pas\r\n        \/\/ (le guard anti-\u00e9crasement if (adSpaceTemplatesLocal[droppableId]) return; suffit)\r\n        \r\n        var $content = jQuery(this).find('.OrdiMobileConteneurClass').first();\r\n        if ($content.length > 0) {\r\n            adSpaceTemplatesLocal[droppableId] = $content.clone(true, true);\r\n            saved++;\r\n        }\r\n    });\r\n    if (saved > 0) {\r\n        console.log('\u2705 Templates espaces pub sauvegard\u00e9s:', saved, 'espaces -', Object.keys(adSpaceTemplatesLocal));\r\n        return true;\r\n    }\r\n    return false;\r\n}\r\n\r\nfunction RestoreadSpaceTemplateLocal(element) {\r\n    console.log('\ud83d\udd04 RestoreadSpaceTemplateLocal', element);\r\n    \r\n    var $element = jQuery(element);\r\n    var $droppable = $element.closest('.droppable');\r\n    var droppableId = $droppable.attr('id');\r\n    \r\n    \/\/ \u2705 FIX Ele0A : pas de template sauvegard\u00e9 (clone temporaire popup)\r\n    \/\/ \u2192 pas de remplacement DOM, reset visuel direct sur le contenu existant\r\n    var _isEle0A = (droppableId === 'Ele0A');\r\n\r\n    \/\/ \u2705 v1.19.3 : Chercher le template sp\u00e9cifique \u00e0 CET espace (sauf Ele0A)\r\n    if (!_isEle0A) {\r\n        if (!droppableId || !adSpaceTemplatesLocal[droppableId]) {\r\n            if (!saveAdSpaceTemplateLocal()) {\r\n                console.error('\u274c Template non disponible');\r\n                return false;\r\n            }\r\n            if (!adSpaceTemplatesLocal[droppableId]) {\r\n                console.error('\u274c Template non trouv\u00e9 pour', droppableId);\r\n                return false;\r\n            }\r\n        }\r\n    }\r\n    \r\n    var adSpaceElement = $droppable.find('.OrdiMobileConteneurClass').first();\r\n    \r\n    if (!adSpaceElement || !adSpaceElement.length) {\r\n        console.error('\u274c OrdiMobileConteneurClass non trouv\u00e9');\r\n        return false;\r\n    }\r\n    \r\n    var newElement;\r\n    if (_isEle0A) {\r\n        \/\/ Ele0A : pas de clone \u2014 on remet en \u00e9tat le contenu existant directement\r\n        newElement = adSpaceElement;\r\n        console.log('\u2705 [Ele0A] reset visuel direct (pas de template clone)');\r\n        \/\/ \u2705 Vider le contenu du dropzone et restaurer le HTML par d\u00e9faut\r\n        \/\/ (le replaceWith n'ayant pas lieu, l'image d\u00e9pos\u00e9e et le fond blanc restent sinon)\r\n        var $dz0A = $droppable.find('#drop_file_zone_achat');\r\n        $dz0A.empty().html(\r\n            '<div id=\"drag_upload_file_achat\">' +\r\n                '<p class=\"UploadIci\" style=\"color:#FB5E2A;font-weight:600;\">Ici glisser \u2013 d\u00e9poser ou<br>t\u00e9l\u00e9charger une annonce<\/p>' +\r\n            '<\/div>'\r\n        );\r\n        \/\/ Restaurer le fond bleu #9FC5F3 et retirer les styles d'annonce upload\u00e9e\r\n        $droppable.find('#UploadFileConteneur').css({\r\n            'background-color': '#9FC5F3',\r\n            'border': '',\r\n            'box-sizing': ''\r\n        });\r\n        $droppable.find('.HTMLUploadfileConteneur').css({\r\n            'box-shadow': '',\r\n            'background-color': '',\r\n            'border': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': '',\r\n            'overflow': '',\r\n            'height': '',\r\n            'max-height': ''\r\n        });\r\n        \/\/ Masquer la croix et le titre d'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle, #CroixResetAnnonce, .CroixResetAnnonceContainer, .DeplaceAnnonce').hide();\r\n        $droppable.removeAttr('data-via-ad-loaded').removeAttr('data-from-miniature');\r\n        console.log('\u2705 [Ele0A] dropzone vid\u00e9 + fond restaur\u00e9');\r\n    } else {\r\n        \/\/ \u2705 v1.19.3 : Cloner le template SP\u00c9CIFIQUE \u00e0 cet espace (pas le premier)\r\n        newElement = adSpaceTemplatesLocal[droppableId].clone(true, true);\r\n        console.log('\u2705 Template utilis\u00e9 pour', droppableId);\r\n        adSpaceElement.replaceWith(newElement);\r\n        \/\/ \u2705 v2.4.12 : Effacer data-via-ad-loaded (sinon selectEspaceActif masque .ReserverContainer)\r\n        $droppable.removeAttr('data-via-ad-loaded').removeAttr('data-from-miniature');\r\n        if (window.outerWidth < 1000) {\r\n            newElement.css({'margin-top': '-25px', 'bottom': '0px'});\r\n        }\r\n    }\r\n    \r\n    \/\/ D\u00e9cocher la case Envoi diff\u00e9r\u00e9 si elle existe\r\n    newElement.find('input[name*=\"EnvoiUlterieur\"]').prop('checked', false);\r\n    \r\n    \/\/ \u2705 D\u00e9cocher la checkbox \"R\u00e9server\"\r\n    newElement.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked', false);\r\n    \r\n    \/\/ Reset l'\u00e9tat EnvoiUlterieur\r\n    StateManager.set('EnvoiUlterieur', 'false');\r\n    \r\n    \/\/ \u2705 v1.19.3 : R\u00e9initialiser TOUS les styles inline des conteneurs parents modifi\u00e9s pendant le d\u00e9p\u00f4t\r\n    if ($droppable.length) {\r\n        \/\/ \u2705 v2.0.9 : Restaurer les marges de l'algorithme de positionnement (sauvegard\u00e9es par styleUploadedAd)\r\n        var origMt = $droppable.data('orig-mt');\r\n        $droppable.css({\r\n            'margin-top': (origMt !== undefined) ? origMt + 'px' : '',\r\n            'margin-bottom': '',\r\n            'margin-left': '',\r\n            'margin-right': ''\r\n        });\r\n        \/\/ \u2705 v2.0.11 : Cleanup event namespace docpreview\r\n        $droppable.off('click.docpreview');\r\n        \r\n        \/\/ Reset .OrdiMobileConteneurClass margins\r\n        var $container = $droppable.find('.OrdiMobileConteneurClass');\r\n        var origMb = $container.data('orig-mb');\r\n        $container.css({\r\n            'margin-top': '',\r\n            'margin-bottom': (origMb !== undefined) ? origMb + 'px' : ''\r\n        });\r\n        \r\n        \/\/ Reset .HTMLUploadfileConteneur (set by styleUploadedAd: box-shadow inset, background-color:white)\r\n        $droppable.find('.HTMLUploadfileConteneur').css({\r\n            'border': '',\r\n            'box-shadow': '',\r\n            'outline': '',\r\n            'outline-offset': '',\r\n            'background-color': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': '',\r\n            'overflow': '',\r\n            'padding': '',\r\n            'position': ''\r\n        });\r\n        $droppable.find('.via-green-border-overlay').remove();\r\n        $droppable.find('.via-position-label').remove();\r\n        \/\/ \u2705 v2.7.3 : Nettoyage header\/footer\/wrapper inject\u00e9s par _buildAdOverlay\r\n        var $_ufcReset = $droppable.find('.HTMLUploadfileConteneur');\r\n        \/\/ Sortir HTMLUploadfileConteneur du wrapper avant de supprimer le wrapper\r\n        var $_wrapReset = $droppable.find('.via-ad-wrapper');\r\n        if ($_wrapReset.length) {\r\n            $_wrapReset.before($_ufcReset);\r\n            $_wrapReset.remove();\r\n        }\r\n        $droppable.find('.via-ad-header').remove();\r\n        $droppable.find('.via-ad-footer').remove();\r\n        $_ufcReset.css({'display': '', 'flex-direction': '', 'overflow': '', 'box-shadow': '', 'background-color': '', 'margin': '', 'padding': ''});\r\n        \r\n        \/\/ \u2705 Restaurer pointer-events sur OrdiMobileConteneurClass et ses enfants\r\n        $droppable.find('.OrdiMobileConteneurClass').css('pointer-events', '');\r\n        $droppable.find('#CroixResetAnnonce').css({'pointer-events': '', 'position': '', 'z-index': ''});\r\n        \/\/ \u2705 v2.4.5 : Reset margin-right Ele0A (pos\u00e9 par adjustMobileLayout)\r\n        var _croixContReset = $droppable.find('.CroixResetAnnonceContainer')[0];\r\n        if (_croixContReset) { _croixContReset.style.removeProperty('margin-right'); _croixContReset.style.removeProperty('margin-top'); }\r\n        $droppable.find('#PopUpMessageAchattest').css('pointer-events', '');\r\n        \r\n        \/\/ \u2705 v2.0.9 : Restaurer le scale(1.4) sur .UploadFileConteneur (r\u00e9duit \u00e0 scale(1) par styleUploadedAd sur mobile)\r\n        $droppable.find('.UploadFileConteneur').css({\r\n            'transform': '',\r\n            'transform-origin': ''\r\n        });\r\n        \r\n        \/\/ Reset #UploadFileConteneur - Restaurer le fond bleu #9FC5F3 + retirer liser\u00e9 envoi diff\u00e9r\u00e9\r\n        $droppable.find('#UploadFileConteneur').css({\r\n            'width': '',\r\n            'background-color': '#9FC5F3',\r\n            'border': '',\r\n            'box-sizing': ''\r\n        });\r\n        \r\n        \/\/ Reset .ToBeHidden (set by adjustDesktopLayout: top:105px, min-height:300px + overflow mobile)\r\n        $droppable.closest('.ToBeHidden').css({\r\n            'top': '',\r\n            'min-height': '',\r\n            'overflow': ''\r\n        });\r\n        \r\n        \/\/ Reset overflow sur le parent du droppable (set by adjustDesktopLayout)\r\n        $droppable.parent().css('overflow', '');\r\n        \r\n        \/\/ \u2705 v1.19.5 : Gestion device-specific pour les textes\r\n        var isDesktop = window.outerWidth >= 1000;\r\n        \r\n        if (isDesktop) {\r\n            \/\/ Desktop : cacher les textes mobiles, afficher UploadIci\r\n            $droppable.find('.TexteMobile').hide();\r\n            $droppable.find('.TexteMobileAnnonce').hide();\r\n            $droppable.find('.TexteMobileAjoutAnnonce').hide();\r\n            $droppable.find('.UploadIci').show();\r\n        } else {\r\n            \/\/ Mobile : afficher TexteMobileAnnonce\r\n            $droppable.find('.TexteMobileAnnonce').show();\r\n        }\r\n        \r\n        \/\/ R\u00e9-afficher les \u00e9l\u00e9ments masqu\u00e9s pendant l'upload\r\n        $droppable.find('.PositionEspacePublicitaireContainer').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').show();\r\n        $droppable.find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur > div > .elementor-widget-text-editor').show();\r\n        $droppable.find('.AdDroppedTextNotDisplayed').hide();\r\n        \/\/ \u2705 v2.4.5 : Ele0A + PopUpChoice=Yes \u2192 ne pas r\u00e9-afficher le titre si format d\u00e9j\u00e0 s\u00e9lectionn\u00e9\r\n        \/\/ v4.9ds : condition _skipTitre retir\u00e9e \u00e0 la restauration. Sur les sites pays\r\n        \/\/   (PopUpChoice=Yes) avec Ele0A, le titre Format n'\u00e9tait PAS r\u00e9affich\u00e9 apr\u00e8s\r\n        \/\/   suppression d'annonce \u2192 l'utilisateur ne voyait plus la consigne pour\r\n        \/\/   res\u00e9lectionner un format. \u00c0 la restauration, on force toujours le show().\r\n        $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n        $droppable.find('.SelectionFormatTitre').show();\r\n        $droppable.find('span.ClassHdpCdp, .ClassRefEsp').show();\r\n        $droppable.find('.EspPubFormatMainContainer').show();\r\n        $droppable.find('.EspPubFormatListe').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireClass').show();\r\n        $droppable.find('.GlisserDeposerConteneur').show();\r\n        $droppable.find('.OUClass').show();\r\n        $droppable.find('.PositionReference').show();\r\n        $droppable.find('.ClassHdpCdp').show();\r\n        $droppable.find('.ClassRefEsp').show();\r\n        $droppable.find('.EnvoiUlterieurTexte').show();\r\n        $droppable.find('.EnvoiUlterieurContainer').show();\r\n        \r\n        \/\/ \u2705 v1.19.5 : R\u00e9afficher le .ReserverContainer (bouton Elementor statique)\r\n        $droppable.find('.ReserverContainer').show();\r\n        newElement.find('.ReserverContainer').show();\r\n        \r\n        \/\/ Masquer les \u00e9l\u00e9ments sp\u00e9cifiques \u00e0 l'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle, #CroixResetAnnonce, .CroixResetAnnonceContainer, .DeplaceAnnonce').hide();\r\n        $droppable.find('#CroixResetAnnonce').css({'position': '', 'z-index': ''});\r\n        $droppable.find('.RefEspacePublicitaire').hide();\r\n        \r\n        \/\/ Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 v1.19.6 : Restaurer le format s\u00e9lectionn\u00e9 visuellement\r\n        \/\/ Chercher le format dans plusieurs sources possibles\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') \r\n            || sessionStorage.getItem('Commande_Format_Transmis')\r\n            || $droppable.data('kitFormatSelect')\r\n            || '';\r\n        \r\n        var formatchoisi = sessionStorage.getItem('Formatchoisi');\r\n        console.log('\ud83d\udcd0 Format \u00e0 restaurer:', formatSelect, '| Formatchoisi:', formatchoisi);\r\n        \r\n        \/\/ D'abord reset tous les formats visuellement (sur newElement ET $droppable)\r\n        var $allFormats = newElement.find('.EspPubFormatContainer').add($droppable.find('.EspPubFormatContainer'));\r\n        $allFormats.css({\r\n            'background-color': '',\r\n            'border': ''\r\n        });\r\n        newElement.find('.EspPubFormat').add($droppable.find('.EspPubFormat')).css({\r\n            'color': ''\r\n        });\r\n        \r\n        \/\/ Si un format \u00e9tait s\u00e9lectionn\u00e9 (via sessionStorage OU visuellement avant)\r\n        \/\/ \u2705 v2.1.1 : Sauf popup sur Ele0A (g\u00e9r\u00e9 s\u00e9par\u00e9ment via setTimeout)\r\n        \/\/ \u2705 v2.6 : Pour Ele1A\/2A\/3A, restaurer le format m\u00eame en mode popup (pop-up = format sous-jacent identique)\r\n        var _isEle0APopup = ($droppable.attr('id') === 'Ele0A' ? sessionStorage.getItem('PopUpChoice') === 'Yes' : false);\r\n        if ((formatSelect || formatchoisi === 'Yes') ? !_isEle0APopup : false) {\r\n            var formatFound = false;\r\n            \r\n            if (formatSelect) {\r\n                \/\/ \u2705 v2.1.1 : Normaliser via NFD (plus fiable que les replace manuels)\r\n                var formatLower = formatSelect.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase().replace(\/ \/g, '');\r\n                \r\n                \/\/ Appliquer sur newElement ET $droppable pour \u00eatre s\u00fbr\r\n                var $allContainers = newElement.find('.EspPubFormatContainer').add($droppable.find('.EspPubFormatContainer'));\r\n                \r\n                $allContainers.each(function() {\r\n                    var className = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    \r\n                    if (className.includes(formatLower)) {\r\n                        jQuery(this).css({'background-color': '#ffffff'});\r\n                        jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        formatFound = true;\r\n                        \/\/ \u2705 v2.4.12 : M\u00e9moriser le format restaur\u00e9 (lu par selectEspaceActif pour re-surligner apr\u00e8s reset global)\r\n                        $droppable.attr('data-restored-format', formatSelect);\r\n                        console.log('\u2705 Format restaur\u00e9 visuellement:', formatSelect, '- classe:', this.className);\r\n                    }\r\n                });\r\n            }\r\n            \r\n            \/\/ \u2705 IMPORTANT : Pr\u00e9server Formatchoisi = Yes pour permettre l'upload\r\n            sessionStorage.setItem('Formatchoisi', 'Yes');\r\n            console.log('\u2705 Formatchoisi maintenu \u00e0 Yes');\r\n            \r\n            \/\/ Basculer les titres format (sur newElement ET $droppable)\r\n            newElement.find('.SelectionFormatTitre').hide();\r\n            newElement.find('.SelectionFormatTitreBlanc').show();\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n        }\r\n    }\r\n    \r\n    \/\/ \u2705 v1.19.2 : Relancer InitLoadedPage pour recalculer positions et tailles\r\n    setTimeout(function() {\r\n        if (typeof window.InitLoadedPage === 'function') {\r\n            window.InitLoadedPage();\r\n        }\r\n        \r\n        \/\/ \u2705 v1.19.6 : R\u00e9attacher les MutationObservers sur les formats\r\n        if (typeof FormatUIManager !== 'undefined' ? FormatUIManager.observeFormatChanges : false) {\r\n            FormatUIManager.observeFormatChanges();\r\n        }\r\n    }, 100);\r\n    \r\n    console.log('\u2705 Espace publicitaire r\u00e9initialis\u00e9');\r\n    return true;\r\n}\r\n\r\n\/\/ \u2705 Exposer pour appel depuis yearbook-media.js (suppression popup mode=popup)\r\nwindow.RestoreadSpaceTemplateLocal = RestoreadSpaceTemplateLocal;\r\n\/\/ \u2705 Alias pour appel depuis processVideoDrop (drag d'annonce)\r\nwindow.RestoreadSpaceTemplate = RestoreadSpaceTemplateLocal;\r\n\r\nwindow.verifierAffichageMsgSelectEspaceLocal = function() {\r\n    const formatChoisi = StateManager.get('Formatchoisi') === 'Yes';\r\n    const fileReceived = StateManager.get('FileReceived');\r\n    const envoiUlterieur = StateManager.get('EnvoiUlterieur');\r\n    \r\n    var dateDebut, dateFin, pays;\r\n    try {\r\n        dateDebut = window.parent.$('#form-field-DebutCampagne').val();\r\n        dateFin = window.parent.$('#form-field-FinCampagne').val();\r\n        pays = window.parent.$('#form-field-Nationalite_Societe').val();\r\n    } catch(e) {\r\n        jQuery('#MsgSelectEspace').hide();\r\n        return;\r\n    }\r\n    \r\n    if (!dateDebut || !dateFin || !pays || !formatChoisi) {\r\n        jQuery('#MsgSelectEspace').hide();\r\n        return;\r\n    }\r\n    \r\n    if (fileReceived !== 'Yes') {\r\n        if (envoiUlterieur !== 'true') {\r\n            jQuery('#MsgSelectEspace').show();\r\n        } else {\r\n            jQuery('#MsgSelectEspace').hide();\r\n        }\r\n    } else {\r\n        jQuery('#MsgSelectEspace').hide();\r\n    }\r\n};\r\n\r\n\/\/ Sauvegarder les templates au chargement (apr\u00e8s rendu complet Elementor)\r\njQuery(document).ready(function() {\r\n    \/\/ \u2705 v1.19.3 : D\u00e9lai augment\u00e9 + double sauvegarde pour garantir les bonnes dimensions\r\n    setTimeout(saveAdSpaceTemplateLocal, 3000);\r\n    setTimeout(saveAdSpaceTemplateLocal, 6000);\r\n    \/\/ \u2705 v2.3.4 : Sauvegarde imm\u00e9diate d\u00e8s que la page est pr\u00eate dans l'iframe\r\n    \/\/ (avant toute interaction utilisateur)\r\n    setTimeout(saveAdSpaceTemplateLocal, 100);\r\n    setTimeout(saveAdSpaceTemplateLocal, 500);\r\n});\r\n\r\n\/\/ \u2705 v2.3.4 : Forcer sauvegarde \u00e0 la r\u00e9ception de elementsRemoved (espaces visibles + vierges)\r\nwindow.addEventListener('message', function(e) {\r\n    if (e.data ? e.data.type === 'elementsRemoved' : false) {\r\n        \/\/ Les espaces sont vierges \u00e0 ce stade \u2192 sauvegarder imm\u00e9diatement\r\n        setTimeout(saveAdSpaceTemplateLocal, 50);\r\n        setTimeout(saveAdSpaceTemplateLocal, 300);\r\n    }\r\n});\r\n\r\n\/**\r\n * Fonction helper pour le d\u00e9p\u00f4t r\u00e9dactionnel\r\n *\/\r\nfunction RedactionnelDepose() {\r\n    jQuery('#Tariftobedisplayed').html('-');\r\n    jQuery('#TarifDataStep3').html('\u00e0 choisir').css({'color': '#FB5E2A'});\r\n    jQuery('#FormatDataStep3').html('\u00e0 choisir').css({'color': '#FB5E2A'});\r\n    jQuery('#ListePaysDirect, #ListeThemeDirect, .ListeArticles, #PageAfficheeMessage').hide();\r\n    StateManager.set(\"PositionAnnonceSelection\", 'Yes');\r\n}\r\n\r\n\/**\r\n * Exposition des fonctions globales n\u00e9cessaires\r\n *\/\r\nwindow.uploadFile_achat = (e) => DropHandler.handleDrop(e);\r\nwindow.fileExplorer_achat = (e) => FileExplorer.open(e);\r\nwindow.ajaxFileUpload_achat = (fileObj, dropZone) => UploadManager.handleFileUpload(fileObj, dropZone);\r\nwindow.ActivatesendDataToParent = (dropZone) => UploadManager.activateSendDataToParent(dropZone);\r\n\/\/ \u2705 v2.3.4 : Exposer FormatUIManager pour appel depuis Entete.txt (selectEspaceActif)\r\nwindow.FormatUIManagerRef = FormatUIManager;\r\n\r\n\/\/ \u2705 v2.6 : Pb 12 \u2014 Restaurer l'affichage d'une annonce depuis son URL (sans re-upload)\r\n\/\/ Appel\u00e9 par Entete.txt > selectEspaceActif quand fileReceived=Yes mais annonce non affich\u00e9e\r\nwindow.restoreAdFromUrl = function(rankId, adUrl, formatLabel) {\r\n    console.log('\ud83d\udd04 [restoreAdFromUrl] appel | rank:', rankId, '| url:', adUrl);\r\n    if (!rankId || !adUrl) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] rank ou url manquant'); return; }\r\n    var $droppable = jQuery('#' + rankId);\r\n    if (!$droppable.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] #' + rankId + ' introuvable dans le DOM'); return; }\r\n    if ($droppable.attr('data-via-ad-loaded') === 'true') { console.log('\u23ed\ufe0f [restoreAdFromUrl] d\u00e9j\u00e0 charg\u00e9, skip'); return; }\r\n    \/\/ \u2705 v2.6 : Guard anti-concurrent \u2014 si un probe est d\u00e9j\u00e0 en cours, ne pas relancer\r\n    if ($droppable.attr('data-via-ad-restoring') === 'true') {\r\n        console.log('\u23ed\ufe0f [restoreAdFromUrl] probe en cours pour #' + rankId + ', skip | url demand\u00e9e:', adUrl);\r\n        return;\r\n    }\r\n    $droppable.attr('data-via-ad-restoring', 'true');\r\n    var $dropZone = $droppable.find('#drop_file_zone_achat');\r\n    if (!$dropZone.length) {\r\n        $droppable.removeAttr('data-via-ad-restoring');\r\n        console.warn('\u26a0\ufe0f [restoreAdFromUrl] #drop_file_zone_achat introuvable dans #' + rankId, '| enfants:', jQuery('#' + rankId).children().length);\r\n        return;\r\n    }\r\n    var _ext = (adUrl.split('?')[0].split('.').pop() || '').toLowerCase();\r\n    var _fileType = FileManager.getFileType(_ext);\r\n    var _fmt = (formatLabel || '').trim() || 'Document';\r\n    console.log('\ud83d\udce6 [restoreAdFromUrl] probe d\u00e9marr\u00e9 | ext:', _ext, '| type:', _fileType);\r\n    \/\/ \u2705 Ne pas \u00e9craser Rank_Emplacement_Page_Web \/ FullPathAdFile \/ FileReceived dans StateManager\r\n    \/\/ \u2192 \u00e9vite la race condition avec un upload en cours (finalizeUpload lirait le mauvais rank)\r\n    \/\/ \u2705 _isAdRestoration positionn\u00e9 juste avant styleUploadedAd dans chaque callback probe\r\n    \/\/ \u2192 \u00e9vite qu'un finalizeUpload concurrent ne remette le flag \u00e0 'No' avant le callback\r\n    if (_fileType === 'image') {\r\n        \/\/ \u2705 Probe: v\u00e9rifier que l'image est r\u00e9ellement accessible avant de marquer l'espace\r\n        \/\/ \u2705 _isAdRestoration positionn\u00e9 juste avant styleUploadedAd (pas avant le probe)\r\n        \/\/ \u2192 \u00e9vite qu'un finalizeUpload concurrent ne remette le flag \u00e0 'No' entre-temps\r\n        var _probe = new Image();\r\n        _probe.onload = function() {\r\n            console.log('\u2705 [restoreAdFromUrl] probe1 onload | url:', adUrl);\r\n            jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n            var $_dz = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n            if (!$_dz.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe1 onload: dropZone disparu dans #' + rankId); return; }\r\n            StateManager.set('_isAdRestoration', 'Yes');\r\n            StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n            \/\/ \u2705 Tronc commun : reset DOM propre puis flux standard\r\n            RestoreadSpaceTemplateLocal(jQuery('#' + rankId).find('.OrdiMobileConteneurClass')[0]);\r\n            var $_dz = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n            if (!$_dz.length) { return; }\r\n            PreviewRenderer.renderImage(adUrl, $_dz);\r\n            window._dropFromMiniature = true; \/\/ skip margin-top 150px\r\n            UIManager.updateAfterSuccessfulUpload($_dz);\r\n            window._dropFromMiniature = false;\r\n            UIManager.finalizeAdDisplay($_dz);\r\n            \/\/ v2.9 : si ViaPopupProcessAchat present, repositionner elements hors-lisere dans Ele0A\r\n            if (rankId === 'Ele0A') {\r\n                if ((sessionStorage.getItem('_ViaPopupOpen') === 'Yes')) {\r\n                    setTimeout(function() {\r\n                        var $_drp0A = jQuery('#Ele0A');\r\n                        \/\/ DeplaceAnnonceSubContainer : forcer margin-top positif\r\n                        var $_das = $_drp0A.find('.DeplaceAnnonceSubContainer');\r\n                        if ($_das.length) { $_das[0].style.setProperty('margin-top', '5px', 'important'); }\r\n                        \/\/ PositionEspacePublicitaireDeplacer + RefEspacePublicitaire\r\n                        $_drp0A.find('.PositionEspacePublicitaireDeplacer').each(function() {\r\n                            this.style.setProperty('margin-top',  '5px', 'important');\r\n                        });\r\n                        $_drp0A.find('.RefEspacePublicitaire').each(function() {\r\n                            this.style.setProperty('margin-top',  '5px', 'important');\r\n                            this.style.setProperty('margin-right', '5px', 'important');\r\n                        });\r\n                        \/\/ CroixResetAnnonceContainer : forcer a l'interieur du lisere\r\n                        var $_crc = $_drp0A.find('.CroixResetAnnonceContainer');\r\n                        if ($_crc.length) {\r\n                            $_crc[0].style.setProperty('top',    '5px', 'important');\r\n                            $_crc[0].style.setProperty('right',  '5px', 'important');\r\n                            $_crc[0].style.setProperty('margin', '0px', 'important');\r\n                        }\r\n                        console.log('[restoreAdFromUrl] v2.9 : Ele0A elements repositionnes (ViaPopupProcessAchat)');\r\n                    }, 200);\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.6 : Repositionner Ele0A apr\u00e8s restauration (page peinte, rect valide)\r\n            if (rankId === 'Ele0A') {\r\n                if (typeof positionEle0AOverEle1A === 'function') {\r\n                    requestAnimationFrame(function() {\r\n                        var $_e1W = jQuery('.ToBeHidden').has('[id=\"Ele1A\"]').not(jQuery('.ToBeHidden').has('#Ele0A')).first();\r\n                        var $_e1U = $_e1W.find('#UploadFileConteneur').first();\r\n                        var _lr = $_e1U.length ? $_e1U[0].getBoundingClientRect() : null;\r\n                        window._ele1ASnapRect = (_lr ? (_lr.width > 0 || _lr.height > 0) : false) ? _lr : null;\r\n                        positionEle0AOverEle1A(window._ele1ASnapRect);\r\n                        console.log('[restoreAdFromUrl] repo Ele0A liveRect:', window._ele1ASnapRect ? Math.round(window._ele1ASnapRect.top)+'\/'+Math.round(window._ele1ASnapRect.left) : 'null->fallback');\r\n                    });\r\n                }\r\n            }\r\n            console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e | rank:', rankId, '| type: image | url:', adUrl);\r\n        };\r\n        _probe.onerror = function() {\r\n            console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe1 onerror | url:', adUrl);\r\n            \/\/ \u2705 Fallback : essayer wp-admin\/uploads si wp-content\/uploads \u00e9choue\r\n            \/\/ (fichier physiquement dans wp-admin\/uploads mais URL stock\u00e9e avec wp-content)\r\n            var _altUrl = adUrl.replace('\/wp-content\/uploads\/', '\/wp-admin\/uploads\/');\r\n            if (_altUrl !== adUrl) {\r\n                var _probe2 = new Image();\r\n                _probe2.onload = function() {\r\n                    console.log('\u2705 [restoreAdFromUrl] probe2 onload | alt url:', _altUrl);\r\n                    jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n                    var $_dz2 = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n                    if (!$_dz2.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe2 onload: dropZone disparu dans #' + rankId); return; }\r\n                    StateManager.set('_isAdRestoration', 'Yes');\r\n                    StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n                    \/\/ \u2705 Tronc commun : reset DOM propre puis flux standard\r\n                    RestoreadSpaceTemplateLocal(jQuery('#' + rankId).find('.OrdiMobileConteneurClass')[0]);\r\n                    var $_dz2 = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n                    if (!$_dz2.length) { return; }\r\n                    PreviewRenderer.renderImage(_altUrl, $_dz2);\r\n                    window._dropFromMiniature = true;\r\n                    UIManager.updateAfterSuccessfulUpload($_dz2);\r\n                    window._dropFromMiniature = false;\r\n                    UIManager.finalizeAdDisplay($_dz2);\r\n                    \/\/ \u2705 v2.6 : Repositionner Ele0A apr\u00e8s restauration (page peinte, rect valide)\r\n                    if (rankId === 'Ele0A') {\r\n                        if (typeof positionEle0AOverEle1A === 'function') {\r\n                            requestAnimationFrame(function() {\r\n                                var $_e1W = jQuery('.ToBeHidden').has('[id=\"Ele1A\"]').not(jQuery('.ToBeHidden').has('#Ele0A')).first();\r\n                                var $_e1U = $_e1W.find('#UploadFileConteneur').first();\r\n                                var _lr = $_e1U.length ? $_e1U[0].getBoundingClientRect() : null;\r\n                                window._ele1ASnapRect = (_lr ? (_lr.width > 0 || _lr.height > 0) : false) ? _lr : null;\r\n                                positionEle0AOverEle1A(window._ele1ASnapRect);\r\n                                console.log('[restoreAdFromUrl] repo Ele0A liveRect:', window._ele1ASnapRect ? Math.round(window._ele1ASnapRect.top)+'\/'+Math.round(window._ele1ASnapRect.left) : 'null->fallback');\r\n                    \/\/ Fix fond gris UFC apres restauration\r\n                    var $_ufc0R = jQuery('#Ele0A').find('#UploadFileConteneur');\r\n                    if ($_ufc0R.length) { $_ufc0R.css('background-color', 'white'); console.log('[restoreAdFromUrl] UFC Ele0A -> blanc'); }\r\n                            });\r\n                        }\r\n                    }\r\n                    console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e (alt) | rank:', rankId, '| url:', _altUrl);\r\n                };\r\n                _probe2.onerror = function() {\r\n                    jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n                    console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe2 onerror AUSSI | alt url:', _altUrl, '| rank:', rankId);\r\n                };\r\n                _probe2.src = _altUrl;\r\n            } else {\r\n                console.warn('\u26a0\ufe0f [restoreAdFromUrl] image inaccessible \u2192 skip | rank:', rankId);\r\n            }\r\n        };\r\n        _probe.src = adUrl;\r\n        return;\r\n    } else if (_fileType === 'video') {\r\n        PreviewRenderer.renderVideo(adUrl, adUrl.split('\/').pop(), $dropZone);\r\n    } else {\r\n        PreviewRenderer.renderDocumentPreviewNoImage($dropZone, _fmt, '');\r\n    }\r\n    StateManager.set('_isAdRestoration', 'Yes');\r\n    UIManager.styleUploadedAd($dropZone);\r\n    if (UIManager.isMobile()) { UIManager.adjustMobileLayout($dropZone); }\r\n    console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e | rank:', rankId, '| type:', _fileType, '| url:', adUrl);\r\n};\r\n\r\n\r\n\/**\r\n * Initialisation de l'application\r\n *\/\r\njQuery(document).ready(() => {\r\n    StateManager.init();\r\n    ScrollHelper.init();\r\n    DragDropManager.init();\r\n    FormatUIManager.init();\r\n\r\n    \/\/ v4.9ca : Centrage texte dans EspPubFormatContainer via styles inline\r\n    \/\/ (le CSS inject\u00e9 dans head est \u00e9cras\u00e9 par Elementor \u2014 styles inline = priorit\u00e9 absolue)\r\n    \/\/ v4.9cc : reset complet des variables CSS Elementor + height:100% cha\u00eene compl\u00e8te\r\n    function _centerEspPubFormats() {\r\n        jQuery('.EspPubFormatContainer').each(function() {\r\n            \/\/ Container <a> lui-m\u00eame\r\n            this.style.setProperty('display', 'flex', 'important');\r\n            this.style.setProperty('flex-direction', 'row', 'important');\r\n            this.style.setProperty('align-items', 'center', 'important');\r\n            this.style.setProperty('justify-content', 'center', 'important');\r\n            this.style.setProperty('padding', '0', 'important');\r\n            this.style.setProperty('padding-top', '0', 'important');\r\n            this.style.setProperty('padding-bottom', '0', 'important');\r\n            this.style.setProperty('padding-left', '0', 'important');\r\n            this.style.setProperty('padding-right', '0', 'important');\r\n            \/\/ Variables Elementor (padding-block-*, --padding-*)\r\n            this.style.setProperty('--padding-block-start', '0px', 'important');\r\n            this.style.setProperty('--padding-block-end', '0px', 'important');\r\n            this.style.setProperty('--padding-inline-start', '0px', 'important');\r\n            this.style.setProperty('--padding-inline-end', '0px', 'important');\r\n            this.style.setProperty('--padding-top', '0px', 'important');\r\n            this.style.setProperty('--padding-bottom', '0px', 'important');\r\n            this.style.setProperty('row-gap', '0', 'important');\r\n\r\n            \/\/ Widget .EspPubFormat\r\n            var _widget = this.querySelector('.EspPubFormat');\r\n            if (_widget) {\r\n                _widget.style.setProperty('display', 'flex', 'important');\r\n                _widget.style.setProperty('flex-direction', 'row', 'important');\r\n                _widget.style.setProperty('align-items', 'center', 'important');\r\n                _widget.style.setProperty('justify-content', 'center', 'important');\r\n                _widget.style.setProperty('width', '100%', 'important');\r\n                _widget.style.setProperty('height', '100%', 'important');\r\n                _widget.style.setProperty('margin', '0', 'important');\r\n                _widget.style.setProperty('padding', '0', 'important');\r\n                _widget.style.setProperty('padding-top', '0', 'important');\r\n                _widget.style.setProperty('padding-bottom', '0', 'important');\r\n            }\r\n\r\n            \/\/ .elementor-widget-container\r\n            var _wc = this.querySelector('.elementor-widget-container');\r\n            if (_wc) {\r\n                _wc.style.setProperty('display', 'flex', 'important');\r\n                _wc.style.setProperty('align-items', 'center', 'important');\r\n                _wc.style.setProperty('justify-content', 'center', 'important');\r\n                _wc.style.setProperty('width', '100%', 'important');\r\n                _wc.style.setProperty('height', '100%', 'important');\r\n                \/\/ v4.9dr : padding-top 1px pour centrage vertical\r\n                \/\/ v4.9ds : padding-top 3px sur Communiqu\u00e9\/Interview\/Parrainage (desktop)\r\n                \/\/          (lettres descendantes q\/j abaissent le baseline visuel)\r\n                \/\/          Mobile : 1px partout (centrage flex suffit \u00e0 cette \u00e9chelle)\r\n                var _isMob = UIManager.isMobile();\r\n                var _hasDescender = this.classList.contains('FormatIdCommunique')\r\n                                 || this.classList.contains('FormatIdInterview')\r\n                                 || this.classList.contains('FormatIdParrainage');\r\n                var _padTop = _isMob ? '1px' : (_hasDescender ? '3px' : '1px');\r\n                _wc.style.setProperty('padding-top', _padTop, 'important');\r\n                _wc.style.setProperty('padding-left', '0', 'important');\r\n                _wc.style.setProperty('padding-right', '0', 'important');\r\n                _wc.style.setProperty('padding-bottom', '0px', 'important');\r\n                _wc.style.setProperty('margin', '0', 'important');\r\n                _wc.style.setProperty('margin-top', '0', 'important');\r\n                _wc.style.setProperty('margin-bottom', '0', 'important');\r\n                _wc.style.setProperty('text-align', 'center', 'important');\r\n                _wc.style.setProperty('line-height', '1', 'important');\r\n            }\r\n\r\n            \/\/ Paragraphes \u00e9ventuels g\u00e9n\u00e9r\u00e9s par Elementor text-editor\r\n            jQuery(this).find('p').each(function() {\r\n                this.style.setProperty('margin', '0', 'important');\r\n                this.style.setProperty('padding', '0', 'important');\r\n                this.style.setProperty('line-height', 'inherit', 'important');\r\n            });\r\n        });\r\n    }\r\n    \/\/ Lancer imm\u00e9diatement + apr\u00e8s un d\u00e9lai (Elementor peut appliquer ses styles apr\u00e8s le ready)\r\n    _centerEspPubFormats();\r\n    setTimeout(_centerEspPubFormats, 500);\r\n    setTimeout(_centerEspPubFormats, 1500);\r\n\r\n    \/\/ v4.9cb : FormatIdPopUp ne doit s'afficher QUE sur le site r\u00e9gie (via-regie-iframe).\r\n    \/\/ Sur les sites pays, masquer tous les items .FormatIdPopUp.\r\n    function _hidePopupFormatOnPays() {\r\n        var _isRegie = document.documentElement.classList.contains('via-regie-iframe');\r\n        if (_isRegie) return; \/\/ site r\u00e9gie \u2192 on laisse le Pop-up visible\r\n        jQuery('.FormatIdPopUp').each(function() {\r\n            this.style.setProperty('display', 'none', 'important');\r\n        });\r\n    }\r\n    _hidePopupFormatOnPays();\r\n    setTimeout(_hidePopupFormatOnPays, 500);\r\n    setTimeout(_hidePopupFormatOnPays, 1500);\r\n    \r\n    if (UIManager.isMobile()) {\r\n        UIManager.initMobileUI();\r\n    } else {\r\n        UIManager.initDesktopUI();\r\n    }\r\n    \r\n    jQuery(document).on('click', '#CroixResetAnnonce', (e) => AdResetHandler.handle(e));\r\n\r\n    \/\/ \ud83d\udd27 Fonction globale r\u00e9utilisable : force height:auto + padding\/margin 0 sur le\r\n    \/\/    .elementor-widget-text-editor qui CONTIENT l'OMC. Elementor impose une height\r\n    \/\/    explicite (ex: 297px) \u00e0 certains breakpoints \u00e9troits qui d\u00e9cale le wrapper vers le bas.\r\n    \/\/    Appel\u00e9e au d\u00e9p\u00f4t (depuis _buildAdOverlay) et \u00e0 chaque resize.\r\n    window._viaOverrideTextEditor = function(omcEl) {\r\n        if (!omcEl) return;\r\n        \/\/ Chercher l'anc\u00eatre .elementor-widget-text-editor (pas un enfant)\r\n        var editor = omcEl.closest('.elementor-widget-text-editor');\r\n        if (!editor) return;\r\n        editor.style.setProperty('height', 'auto', 'important');\r\n        editor.style.setProperty('min-height', '0', 'important');\r\n        editor.style.setProperty('padding', '0', 'important');\r\n        editor.style.setProperty('margin', '0', 'important');\r\n        var editorCont = editor.querySelector(':scope > .elementor-widget-container');\r\n        if (editorCont) {\r\n            editorCont.style.setProperty('height', 'auto', 'important');\r\n            editorCont.style.setProperty('min-height', '0', 'important');\r\n            editorCont.style.setProperty('padding', '0', 'important');\r\n            editorCont.style.setProperty('margin', '0', 'important');\r\n        }\r\n    };\r\n\r\n    \/\/ \ud83d\udd0d DIAG : log sur resize pour voir l'\u00e9tat du wrapper quand la fen\u00eatre change\r\n    \/\/ Handler de resize : override text-editor + enforcement + algo + scale\r\n    \/\/ v4.9by : EXCLURE les wrappers \u00e0 l'int\u00e9rieur de #Ele0A (popup fixed) du traitement\r\n    \/\/         Le scroll iOS fire resize \u2192 ce handler retirait position\/left\/transform sur le wrapper\r\n    \/\/         via-ad-wrapper DANS Ele0A \u2192 wrapper r\u00e9tr\u00e9cissait visuellement\r\n    var _diagResizeTimer = null;\r\n    window.addEventListener('resize', function() {\r\n        clearTimeout(_diagResizeTimer);\r\n        _diagResizeTimer = setTimeout(function() {\r\n            var $_wrappers = jQuery('.via-ad-wrapper').filter(function() {\r\n                \/\/ v4.9by : ne pas toucher aux wrappers dans Ele0A (popup fixed, g\u00e9r\u00e9 ailleurs)\r\n                return jQuery(this).closest('#Ele0A').length === 0;\r\n            });\r\n            if (!$_wrappers.length) return;\r\n            var _isRegieIframe = document.documentElement.classList.contains('via-regie-iframe');\r\n            console.log('[RESIZE] handler fire \u2014 inner=' + window.innerWidth + ' outer=' + window.outerWidth + ' isMobile=' + UIManager.isMobile() + ' wrappers=' + $_wrappers.length + ' isRegie=' + _isRegieIframe);\r\n\r\n            \/\/ \u00c9TAPE 1 : enforcement + reset transform\/mb sur tous les wrappers\r\n            $_wrappers.each(function() {\r\n                var $_drop = jQuery(this).closest('.droppable');\r\n                var $_omc = $_drop.find('.OrdiMobileConteneurClass').first();\r\n                var $_ufc = jQuery(this).find('.HTMLUploadfileConteneur').first();\r\n                var _dropEl = $_drop[0], _omcEl = $_omc[0], _ufcEl = $_ufc[0];\r\n\r\n                var _wAvantMt = this.style.marginTop || '(none)';\r\n                var _dAvantMt = _dropEl ? (_dropEl.style.marginTop || '(none)') : '?';\r\n                var _rAvant = _dropEl ? Math.round(_dropEl.getBoundingClientRect().top) : '?';\r\n\r\n                if (_omcEl) { window._viaOverrideTextEditor(_omcEl); }\r\n\r\n                \/\/ \u26a0\ufe0f CLEANUP DVM\/MOBILE : d\u00e8s que inner<1000, nettoyer les paddings\/min-height\r\n                \/\/    pos\u00e9s en plein \u00e9cran sur article\/secteur qui cr\u00e9ent de grands espaces blancs.\r\n                \/\/    Ce bloc s'ex\u00e9cute AVANT le gate UIManager.isMobile() pour couvrir \u00e0 la fois\r\n                \/\/    DVM (outer>=1000, inner<1000) et mobile vrai (outer<1000).\r\n                if (!_isRegieIframe) {\r\n                    if (window.innerWidth < 1000) {\r\n                        \/\/ Reset d'abord les marges n\u00e9gatives pour pouvoir mesurer la VRAIE hauteur\r\n                        this.style.setProperty('margin-top', '0px', 'important');\r\n                        this.style.setProperty('margin-bottom', '0px', 'important');\r\n                        \/\/ Mesure hauteur r\u00e9elle (apr\u00e8s reset)\r\n                        var _dvmWrapperH = Math.round(this.getBoundingClientRect().height);\r\n                        var _dvmWrapperW = Math.round(this.getBoundingClientRect().width);\r\n                        var _dvmDropW = _dropEl ? Math.round(_dropEl.getBoundingClientRect().width) : 0;\r\n                        \/\/ Force min fallback 200px si mesure inf\u00e9rieure\r\n                        if (_dvmWrapperH < 150) { _dvmWrapperH = 200; }\r\n                        \/\/ \u2705 Reset margin-top\/bottom sur OrdiMobileConteneurClass (pose +65px en mobile)\r\n                        if (_omcEl) {\r\n                            _omcEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _omcEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                        }\r\n                        \/\/ \u2705 Reset margin sur HTMLUploadfileConteneur (pose -85px en mobile)\r\n                        if (_ufcEl) {\r\n                            _ufcEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _ufcEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                        }\r\n                        if (_dropEl) {\r\n                            _dropEl.style.setProperty('padding-top', '0px', 'important');\r\n                            _dropEl.style.setProperty('padding-bottom', '0px', 'important');\r\n                            \/\/ PAS de min-height \u2014 laisser la hauteur naturelle du wrapper d\u00e9finir\r\n                            _dropEl.style.removeProperty('min-height');\r\n                            _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _dropEl.style.setProperty('margin-bottom', '40px', 'important');\r\n                            _dropEl.style.setProperty('height', 'auto', 'important');\r\n                            _dropEl.style.removeProperty('box-sizing');\r\n                            \/\/ \u2705 Centrer via flex sur le droppable\r\n                            _dropEl.style.setProperty('display', 'flex', 'important');\r\n                            _dropEl.style.setProperty('flex-direction', 'column', 'important');\r\n                            _dropEl.style.setProperty('justify-content', 'flex-start', 'important');\r\n                            _dropEl.style.setProperty('align-items', 'center', 'important');\r\n                        }\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM DIMS wrapperW=' + _dvmWrapperW + ' dropW=' + _dvmDropW);\r\n                        \/\/ \ud83d\udd0d DIAG chain anc\u00eatres du droppable avec leurs TOP et margins\r\n                        var _chainInfo = [];\r\n                        var _curAnc = _dropEl;\r\n                        var _chainCount = 0;\r\n                        while (_curAnc) {\r\n                            if (_chainCount >= 10) break;\r\n                            var _rCur = _curAnc.getBoundingClientRect();\r\n                            var _csCur = getComputedStyle(_curAnc);\r\n                            var _tagC = _curAnc.tagName + (_curAnc.id ? '#' + _curAnc.id : '') + '.' + ((_curAnc.className || '').toString().split(' ').slice(0, 2).join('.') || '');\r\n                            _chainInfo.push(_tagC + ' top=' + Math.round(_rCur.top) + ' mt=' + _csCur.marginTop + ' pt=' + _csCur.paddingTop);\r\n                            _curAnc = _curAnc.parentElement;\r\n                            _chainCount++;\r\n                        }\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM CHAIN from droppable up: ' + _chainInfo.join(' | '));\r\n                        var _ancD = _dropEl;\r\n                        var _ancDCount = 0;\r\n                        var _ancDDebug = [];\r\n                        while (_ancD) {\r\n                            if (_ancDCount >= 8) break;\r\n                            _ancD = _ancD.parentElement;\r\n                            _ancDCount++;\r\n                            if (!_ancD) break;\r\n                            var _inPt = _ancD.style.paddingTop;\r\n                            var _inPb = _ancD.style.paddingBottom;\r\n                            var _inMh = _ancD.style.minHeight;\r\n                            if (_inPt || _inPb || _inMh) {\r\n                                _ancDDebug.push(_ancD.tagName + '.' + ((_ancD.className || '').toString().split(' ').slice(0, 2).join('.') || '') + ' [pt=' + _inPt + ' pb=' + _inPb + ' mh=' + _inMh + ']');\r\n                            }\r\n                            _ancD.style.setProperty('padding-top', '0px', 'important');\r\n                            _ancD.style.setProperty('padding-bottom', '0px', 'important');\r\n                            _ancD.style.setProperty('min-height', '0px', 'important');\r\n                            if (_ancD.nextElementSibling) { break; }\r\n                        }\r\n                        if (_ancDDebug.length) {\r\n                            console.log('[DIAG resize] \u2699\ufe0f cleanup DVM\/mobile (inner<1000, outer=' + window.outerWidth + ') : wrapper.mt\/mb=25px droppable.min-h=' + (_dvmWrapperH + 60) + ' mb=40 | anc\u00eatres reset \u2192 ' + _ancDDebug.join(' | '));\r\n                        } else {\r\n                            console.log('[DIAG resize] \u2699\ufe0f cleanup DVM\/mobile (inner<1000, outer=' + window.outerWidth + ') : wrapper.mt\/mb=25px droppable.min-h=' + (_dvmWrapperH + 60) + ' mb=40 | wrapperH mesur\u00e9=' + _dvmWrapperH);\r\n                        }\r\n                        \/\/ \u2705 DVM\/mobile : margin-top n\u00e9gatif compense le gros vide entre le contenu\r\n                        \/\/    au-dessus et le droppable (structure Elementor pose ~260px de vide).\r\n                        this.style.setProperty('margin-top', '-120px', 'important');\r\n                        this.style.setProperty('margin-bottom', '25px', 'important');\r\n                        this.style.removeProperty('position');\r\n                        this.style.removeProperty('left');\r\n                        this.style.removeProperty('transform');\r\n                        this.style.setProperty('width', '100%', 'important');\r\n                        this.style.setProperty('max-width', '100%', 'important');\r\n                        this.style.removeProperty('margin-left');\r\n                        this.style.removeProperty('margin-right');\r\n                        this.style.setProperty('display', 'flex', 'important');\r\n                        \/\/ \ud83d\udd0d DIAG POST\r\n                        var _postCS = getComputedStyle(this);\r\n                        var _postRect = this.getBoundingClientRect();\r\n                        var _dropRect2 = _dropEl ? _dropEl.getBoundingClientRect() : null;\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM POST-APPLY | computed w=' + _postCS.width + ' | wrapper RECT left=' + Math.round(_postRect.left) + ' w=' + Math.round(_postRect.width) + ' top=' + Math.round(_postRect.top) + ' | drop RECT left=' + (_dropRect2 ? Math.round(_dropRect2.left) : '?') + ' w=' + (_dropRect2 ? Math.round(_dropRect2.width) : '?') + ' top=' + (_dropRect2 ? Math.round(_dropRect2.top) : '?'));\r\n                    }\r\n                }\r\n\r\n                if (!UIManager.isMobile()) {\r\n                    if (!_isRegieIframe) {\r\n                        \/\/ \ud83d\udd27 ENFORCEMENT JS (version qui marchait) : neutralise marges n\u00e9gatives\r\n                        \/\/    h\u00e9rit\u00e9es du mode mobile + restaure UFC + appelle l'algo inter-espaces.\r\n\r\n                        \/\/ \ud83d\udd0d DIAG : snapshot \u00e9tat AVANT\r\n                        var _cs = getComputedStyle(this);\r\n                        var _rect = this.getBoundingClientRect();\r\n                        var _dropCS = _dropEl ? getComputedStyle(_dropEl) : null;\r\n                        var _dropR = _dropEl ? _dropEl.getBoundingClientRect() : null;\r\n                        var _ufcCS = _ufcEl ? getComputedStyle(_ufcEl) : null;\r\n                        var _ufcR = _ufcEl ? _ufcEl.getBoundingClientRect() : null;\r\n                        console.log('[DIAG resize] inner=' + window.innerWidth + ' outer=' + window.outerWidth\r\n                            + ' | htmlClass=\"' + document.documentElement.className + '\"'\r\n                            + ' | bodyClass=\"' + document.body.className + '\"');\r\n                        console.log('[DIAG resize] WRAPPER inline mt=' + this.style.marginTop + ' mb=' + this.style.marginBottom\r\n                            + ' | computed mt=' + _cs.marginTop + ' mb=' + _cs.marginBottom\r\n                            + ' | rect top=' + Math.round(_rect.top) + ' h=' + Math.round(_rect.height) + ' w=' + Math.round(_rect.width));\r\n                        if (_dropEl) {\r\n                            console.log('[DIAG resize] DROPPABLE#' + _dropEl.id\r\n                                + ' inline mt=' + _dropEl.style.marginTop + ' mb=' + _dropEl.style.marginBottom\r\n                                + ' | computed mt=' + _dropCS.marginTop + ' mb=' + _dropCS.marginBottom\r\n                                + ' | rect top=' + Math.round(_dropR.top) + ' h=' + Math.round(_dropR.height));\r\n                        }\r\n                        if (_ufcEl) {\r\n                            console.log('[DIAG resize] UFC inline h=' + _ufcEl.style.height + ' maxH=' + _ufcEl.style.maxHeight\r\n                                + ' | computed h=' + _ufcCS.height + ' maxH=' + _ufcCS.maxHeight\r\n                                + ' | rect top=' + Math.round(_ufcR.top) + ' h=' + Math.round(_ufcR.height));\r\n                        }\r\n\r\n                        \/\/ 1. Neutraliser les marges n\u00e9gatives h\u00e9rit\u00e9es du mode mobile (wrapper + droppable)\r\n                        var _mt = parseFloat(this.style.marginTop) || 0;\r\n                        if (_mt < 0) {\r\n                            this.style.setProperty('margin-top', '0px', 'important');\r\n                            console.log('[DIAG resize] \u2699\ufe0f wrapper : margin-top forc\u00e9 \u00e0 0 (\u00e9tait ' + _mt + ')');\r\n                        }\r\n                        if (_dropEl) {\r\n                            var _dmt = parseFloat(_dropEl.style.marginTop) || 0;\r\n                            if (_dmt < 0) {\r\n                                _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                                console.log('[DIAG resize] \u2699\ufe0f droppable : margin-top forc\u00e9 \u00e0 0 (\u00e9tait ' + _dmt + ')');\r\n                            }\r\n                        }\r\n\r\n                        \/\/ 2. Restaurer max-height UFC + force height auto\r\n                        \/\/ v4.9ds : Ele0A=260, Ele1A+=280 ; ne pas toucher au dropZone (g\u00e9r\u00e9 par _applyDzMinH)\r\n                        if (_ufcEl) {\r\n                            var _isE0AResize = $_drop.attr('id') === 'Ele0A';\r\n                            _ufcEl.style.removeProperty('min-height');\r\n                            _ufcEl.style.setProperty('max-height', _isE0AResize ? '260px' : '280px', 'important');\r\n                            _ufcEl.style.setProperty('height', 'auto', 'important');\r\n                        }\r\n                        \/\/ v4.9ds : ne PAS toucher au dropZone height\/min-height ici\r\n                        \/\/          (sinon \u00e9crase ce que _applyDzMinH a pos\u00e9 sur Ele1A+)\r\n                        \/\/          Au lieu de \u00e7a, on rejoue _applyDzMinH si elle est expos\u00e9e sur le droppable\r\n                        if (_dropEl ? _dropEl._applyDzMinH : false) {\r\n                            try { _dropEl._applyDzMinH('resize'); } catch(_e) { console.warn('[resize] _applyDzMinH a throw', _e); }\r\n                        }\r\n                        var _imgInner = _ufcEl ? _ufcEl.querySelector('img, video') : null;\r\n                        if (_imgInner) {\r\n                            \/\/ v4.9ds : aligner max-height image sur la hauteur r\u00e9elle du dropZone\r\n                            \/\/   pour \u00e9viter le crop par overflow:hidden du wrapper.\r\n                            \/\/   IMPORTANT : utiliser offsetHeight (qui n'est PAS affect\u00e9 par\r\n                            \/\/   le zoom\/transform\/scale d'un parent) plut\u00f4t que\r\n                            \/\/   getBoundingClientRect().height (qui retourne les pixels \u00e9cran\r\n                            \/\/   r\u00e9els apr\u00e8s transformations). Le rect.h peut \u00eatre r\u00e9duit par\r\n                            \/\/   un zoom:55% appliqu\u00e9 \u00e0 un anc\u00eatre via _viaRunInterEspaces, ce\r\n                            \/\/   qui produirait une max-height absurde.\r\n                            var _dzInnerH = _ufcEl ? _ufcEl.querySelector('#drop_file_zone_achat') : null;\r\n                            var _dzH = _dzInnerH ? _dzInnerH.offsetHeight : 0;\r\n                            var _imgMaxHRsz = _dzH > 50 ? Math.max(0, Math.floor(_dzH - 5)) : 250;\r\n                            _imgInner.style.setProperty('max-height', _imgMaxHRsz + 'px', 'important');\r\n                        }\r\n\r\n                        \/\/ 3. Gater l'algo + scale : n\u00e9cessite inner >= 1000 (viewport CSS desktop).\r\n                        \/\/    En \"desktop version mobile\" (outer>=1000 mais inner<1000), le layout CSS\r\n                        \/\/    est en mode mobile \u2014 l'algo se tromperait.\r\n                        if (window.innerWidth < 1000) {\r\n                            \/\/ \u26a0\ufe0f DVM : nettoyer les paddings\/min-height pos\u00e9s pr\u00e9c\u00e9demment\r\n                            \/\/    (article\/secteur) qui cr\u00e9aient de grands espaces blancs.\r\n                            \/\/    removeProperty ne bat pas un !important inline \u2192 forcer \u00e0 0 !important.\r\n                            if (_dropEl) {\r\n                                _dropEl.style.setProperty('padding-top', '0px', 'important');\r\n                                _dropEl.style.setProperty('padding-bottom', '0px', 'important');\r\n                                _dropEl.style.setProperty('min-height', '0px', 'important');\r\n                                _dropEl.style.removeProperty('box-sizing');\r\n                            }\r\n                            \/\/ Nettoyer TOUS les anc\u00eatres qui ont un padding ou min-height inline\r\n                            \/\/ (pas seulement ceux avec classList e-con-inner\/elementor-element)\r\n                            var _ancC = _dropEl;\r\n                            var _ancCCount = 0;\r\n                            var _ancDebug = [];\r\n                            while (_ancC) {\r\n                                if (_ancCCount >= 8) break;\r\n                                _ancC = _ancC.parentElement;\r\n                                _ancCCount++;\r\n                                if (!_ancC) break;\r\n                                var _ancTag = _ancC.tagName + '.' + ((_ancC.className || '').toString().split(' ').slice(0, 2).join('.') || '');\r\n                                var _hasInlineStyle = false;\r\n                                var _inlinePt = _ancC.style.paddingTop;\r\n                                var _inlinePb = _ancC.style.paddingBottom;\r\n                                var _inlineMh = _ancC.style.minHeight;\r\n                                if (_inlinePt || _inlinePb || _inlineMh) {\r\n                                    _hasInlineStyle = true;\r\n                                    _ancDebug.push(_ancTag + ' [pt=' + _inlinePt + ' pb=' + _inlinePb + ' mh=' + _inlineMh + ']');\r\n                                }\r\n                                \/\/ Reset inline style dans tous les cas\r\n                                _ancC.style.setProperty('padding-top', '0px', 'important');\r\n                                _ancC.style.setProperty('padding-bottom', '0px', 'important');\r\n                                _ancC.style.setProperty('min-height', '0px', 'important');\r\n                                if (_ancC.nextElementSibling) { break; }\r\n                            }\r\n                            \/\/ \u2705 DVM : ~25px d'espace blanc au-dessus et en-dessous de l'annonce.\r\n                            \/\/    Appliqu\u00e9 sur le wrapper (margin-top\/bottom) qui \u00e9tait \u00e0 -150 au d\u00e9p\u00f4t.\r\n                            this.style.setProperty('margin-top', '25px', 'important');\r\n                            this.style.setProperty('margin-bottom', '25px', 'important');\r\n                            console.log('[DIAG resize] \u2699\ufe0f DVM (inner<1000) : wrapper.mt\/mb=25px | anc\u00eatres avec style inline: ' + (_ancDebug.length ? _ancDebug.join(' | ') : 'aucun'));\r\n                        }\r\n\r\n                        if (window.innerWidth >= 1000) {\r\n                            \/\/ 3a. Reset transform AVANT la mesure (hauteur naturelle)\r\n                            this.style.removeProperty('transform');\r\n                            this.style.removeProperty('margin-bottom');\r\n                            if (_dropEl) { _dropEl.style.removeProperty('margin-bottom'); }\r\n\r\n                            \/\/ 3b. Appeler l'algo inter-espaces avec droppable temporairement d\u00e9marqu\u00e9\r\n                            if (_dropEl) { if (typeof window._viaRunInterEspaces === 'function') {\r\n                                var _wasLoaded = _dropEl.getAttribute('data-via-ad-loaded') === 'true';\r\n                                if (_wasLoaded) { _dropEl.removeAttribute('data-via-ad-loaded'); }\r\n                                try {\r\n                                    window._viaInterEspacesRun = false;\r\n                                    window._viaRunInterEspaces();\r\n                                    console.log('[DIAG resize] \u2699\ufe0f _viaRunInterEspaces appel\u00e9 (droppable d\u00e9marqu\u00e9)');\r\n                                } catch (e) {\r\n                                    console.warn('[DIAG resize] \u26a0\ufe0f _viaRunInterEspaces a throw :', e);\r\n                                }\r\n                                if (_wasLoaded) { _dropEl.setAttribute('data-via-ad-loaded', 'true'); }\r\n                            } }\r\n\r\n                            \/\/ 3c. \u2705 Article (body.single) ou page secteur (body.page) plein \u00e9cran :\r\n                            \/\/    80px margin-bottom sur le DROPPABLE pour \u00e9viter chevauchement\r\n                            \/\/    avec le contenu en dessous. Appliqu\u00e9 APR\u00c8S l'algo pour qu'il persiste.\r\n                            var _isArticleOrSecteur = document.body.classList.contains('single') || document.body.classList.contains('page');\r\n                            if (_isArticleOrSecteur) {\r\n                                if (_dropEl) {\r\n                                    \/\/ Force droppable \u00e0 englober wrapper (wrapper=171 > droppable=110)\r\n                                    var _wrH = Math.round(this.getBoundingClientRect().height);\r\n                                    \/\/ \u2705 Padding-top ET padding-bottom : padding fait partie de la hauteur\r\n                                    \/\/    de la bo\u00eete et est respect\u00e9 par les parents flex.\r\n                                    \/\/    50px d'espace au-dessus + wrapperH + 0px en-dessous.\r\n                                    var _pbTotal = _wrH + 0;\r\n                                    _dropEl.style.setProperty('padding-top', '50px', 'important');\r\n                                    _dropEl.style.setProperty('padding-bottom', _pbTotal + 'px', 'important');\r\n                                    _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                                    _dropEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                                    _dropEl.style.setProperty('min-height', (_wrH + 50) + 'px', 'important');\r\n                                    _dropEl.style.setProperty('height', 'auto', 'important');\r\n                                    _dropEl.style.setProperty('overflow', 'visible', 'important');\r\n                                    _dropEl.style.setProperty('box-sizing', 'content-box', 'important');\r\n\r\n                                    \/\/ \u2705 Force height:auto sur tous les conteneurs Elementor entre\r\n                                    \/\/    le droppable et le wrapper, pour que le wrapper ne d\u00e9borde\r\n                                    \/\/    pas de son conteneur vers le contenu du dessous.\r\n                                    var _anc = this; \/\/ wrapper\r\n                                    var _ancCount = 0;\r\n                                    while (_anc) {\r\n                                        if (_ancCount >= 5) break;\r\n                                        _anc = _anc.parentElement;\r\n                                        _ancCount++;\r\n                                        if (!_anc) break;\r\n                                        if (_anc === _dropEl) break;\r\n                                        _anc.style.setProperty('height', 'auto', 'important');\r\n                                        _anc.style.setProperty('min-height', _wrH + 'px', 'important');\r\n                                        _anc.style.setProperty('overflow', 'visible', 'important');\r\n                                    }\r\n\r\n                                    \/\/ Remonter au-del\u00e0 du droppable : padding sur section Elementor\r\n                                    var _anc2 = _dropEl;\r\n                                    var _ancChain = _dropEl.tagName + '#' + _dropEl.id;\r\n                                    var _ancCount2 = 0;\r\n                                    while (_anc2) {\r\n                                        if (_ancCount2 >= 6) break;\r\n                                        _anc2 = _anc2.parentElement;\r\n                                        _ancCount2++;\r\n                                        if (!_anc2) break;\r\n                                        _ancChain += ' > ' + _anc2.tagName + '.' + ((_anc2.className || '').split(' ')[0] || '');\r\n                                        if (_anc2.classList) {\r\n                                            if (_anc2.classList.contains('e-con-inner') || _anc2.classList.contains('elementor-element')) {\r\n                                                _anc2.style.setProperty('padding-top', '50px', 'important');\r\n                                                _anc2.style.setProperty('padding-bottom', '0px', 'important');\r\n                                                _anc2.style.setProperty('min-height', (_wrH + 50) + 'px', 'important');\r\n                                            }\r\n                                        }\r\n                                        if (_anc2.nextElementSibling) { break; }\r\n                                    }\r\n                                    console.log('[DIAG resize] \u2699\ufe0f article\/secteur \u2192 droppable.padding-top=50 padding-bottom=' + _pbTotal + ' (wrapperH=' + _wrH + ') | chain=' + _ancChain);\r\n                                }\r\n                            }\r\n\r\n                            \/\/ 3d. Scale 1.3 conditionnel : uniquement si d\u00e9p\u00f4t mobile\r\n                            var _depositedMode = this.getAttribute('data-deposited-mode') || '';\r\n                            if (_depositedMode === 'mobile') {\r\n                                var _naturalRect = this.getBoundingClientRect();\r\n                                var _naturalH = _naturalRect.height;\r\n                                var _scaleFactor = 1.3;\r\n                                this.style.setProperty('transform', 'scale(' + _scaleFactor + ')', 'important');\r\n                                this.style.setProperty('transform-origin', 'top center', 'important');\r\n                                var _extraH = Math.round(_naturalH * (_scaleFactor - 1));\r\n                                this.style.setProperty('margin-bottom', _extraH + 'px', 'important');\r\n                                console.log('[DIAG resize] \u2699\ufe0f scale ' + _scaleFactor + ' (d\u00e9p\u00f4t mobile) | naturalH=' + Math.round(_naturalH) + ' | wrapper.mb +' + _extraH + 'px');\r\n                            } else {\r\n                                console.log('[DIAG resize] \u2699\ufe0f pas de scale (depositedMode=\"' + _depositedMode + '\")');\r\n                            }\r\n                        } else {\r\n                            console.log('[DIAG resize] \u2699\ufe0f layout \u00e9troit (inner=' + window.innerWidth + '<1000) \u2192 skip algo+scale');\r\n                        }\r\n                        return;\r\n                    }\r\n                    \/\/ R\u00c9GIE IFRAME\r\n                    if (_ufcEl) {\r\n                        _ufcEl.style.setProperty('max-height', '260px', 'important');\r\n                        _ufcEl.style.setProperty('height', 'auto', 'important');\r\n                    }\r\n                    var _dzInner = _ufcEl ? _ufcEl.querySelector('#drop_file_zone_achat') : null;\r\n                    if (_dzInner) {\r\n                        _dzInner.style.setProperty('max-height', '250px', 'important');\r\n                        _dzInner.style.setProperty('height', 'auto', 'important');\r\n                    }\r\n                    var _imgInner = _ufcEl ? _ufcEl.querySelector('img, video') : null;\r\n                    if (_imgInner) {\r\n                        \/\/ v4.9ds : aligner max-height image sur offsetHeight du dropZone\r\n                        \/\/   (cf. commentaire branche desktop ci-dessus \u2014 rect.h fauss\u00e9 par zoom parent)\r\n                        var _dzH2 = _dzInner ? _dzInner.offsetHeight : 0;\r\n                        var _imgMaxHRsz2 = _dzH2 > 50 ? Math.max(0, Math.floor(_dzH2 - 5)) : 250;\r\n                        _imgInner.style.setProperty('max-height', _imgMaxHRsz2 + 'px', 'important');\r\n                    }\r\n                    this.style.removeProperty('transform');\r\n                }\r\n\r\n                var _wApr\u00e8sMt = this.style.marginTop || '(none)';\r\n                var _dApr\u00e8sMt = _dropEl ? (_dropEl.style.marginTop || '(none)') : '?';\r\n                var _rApr\u00e8s = _dropEl ? Math.round(_dropEl.getBoundingClientRect().top) : '?';\r\n                console.log('[RESIZE wrapper] #' + (_dropEl ? _dropEl.id : '?') + ' loaded=' + (_dropEl ? _dropEl.getAttribute('data-via-ad-loaded') : '?') + ' | AVANT wrapper.mt=' + _wAvantMt + ' droppable.mt=' + _dAvantMt + ' rect.top=' + _rAvant + ' | APR\u00c8S wrapper.mt=' + _wApr\u00e8sMt + ' droppable.mt=' + _dApr\u00e8sMt + ' rect.top=' + _rApr\u00e8s);\r\n            });\r\n\r\n            \/\/ \u00c9TAPE 2 : appel de l'algo une seule fois avec vue coh\u00e9rente\r\n            \/\/ \u26a0\ufe0f Sur sites pays, on NE RUN PAS l'algo \u00e0 l'agrandissement : \u00e7a cascade sur tous\r\n            \/\/    les droppables (y compris vides) et fait remonter les ads de plusieurs centaines\r\n            \/\/    de pixels. Layout naturel suffit.\r\n            if (!UIManager.isMobile()) { if (_isRegieIframe) {\r\n                if (typeof window._viaRunInterEspaces === 'function') {\r\n                    try {\r\n                        window._viaInterEspacesRun = false;\r\n                        window._viaRunInterEspaces();\r\n                    } catch (e) { \/* silent *\/ }\r\n                }\r\n            } else {\r\n                \/\/ Sites pays : rien de sp\u00e9cial, on laisse la layout naturelle\r\n            } }\r\n\r\n            \/\/ \u00c9TAPE 3 : scale 1.3 sur les wrappers en d\u00e9p\u00f4t mobile\r\n            if (!UIManager.isMobile()) {\r\n                $_wrappers.each(function() {\r\n                    var _depositedMode = this.getAttribute('data-deposited-mode') || '';\r\n                    if (_depositedMode === 'mobile') {\r\n                        var _naturalRect = this.getBoundingClientRect();\r\n                        var _naturalH = _naturalRect.height;\r\n                        var _scaleFactor = 1.3;\r\n                        this.style.setProperty('transform', 'scale(' + _scaleFactor + ')', 'important');\r\n                        this.style.setProperty('transform-origin', 'top center', 'important');\r\n                        var _extraH = Math.round(_naturalH * (_scaleFactor - 1));\r\n                        this.style.setProperty('margin-bottom', _extraH + 'px', 'important');\r\n                    }\r\n                });\r\n            }\r\n        }, 300);\r\n    });\r\n\r\n    \/\/ \ud83d\udd0d DIAG : fonction globale \u00e0 appeler en console \u2192 diagViaAd()\r\n    window.diagViaAd = function() {\r\n        var out = {\r\n            env: {\r\n                outerWidth: window.outerWidth,\r\n                innerWidth: window.innerWidth,\r\n                isMobileJS: UIManager.isMobile(),\r\n                isDesktopUA: UIManager.isDesktop(),\r\n                isTopWindow: window === window.top,\r\n                htmlClass: document.documentElement.className,\r\n                hasRegieClass: document.documentElement.classList.contains('via-regie-iframe'),\r\n                bodyClass: document.body.className\r\n            },\r\n            wrappers: []\r\n        };\r\n        jQuery('.via-ad-wrapper').each(function() {\r\n            var _cs = getComputedStyle(this);\r\n            var _rect = this.getBoundingClientRect();\r\n            var $_drop = jQuery(this).closest('.droppable');\r\n            var $_omc = $_drop.find('.OrdiMobileConteneurClass').first();\r\n            out.wrappers.push({\r\n                droppableId: $_drop.attr('id'),\r\n                droppableDataLoaded: $_drop.attr('data-via-ad-loaded'),\r\n                wrapperInlineStyle: this.getAttribute('style'),\r\n                wrapperComputedMarginTop: _cs.marginTop,\r\n                wrapperComputedMarginBottom: _cs.marginBottom,\r\n                wrapperRect: { top: Math.round(_rect.top), height: Math.round(_rect.height), width: Math.round(_rect.width) },\r\n                droppableInlineMarginTop: $_drop[0].style.marginTop,\r\n                omcInlineMarginTop: $_omc[0] ? $_omc[0].style.marginTop : '(no omc)',\r\n                omcInlineMarginBottom: $_omc[0] ? $_omc[0].style.marginBottom : '(no omc)'\r\n            });\r\n        });\r\n        console.log('[diagViaAd]', out);\r\n        return out;\r\n    };\r\n    console.log('[DIAG] window.diagViaAd() disponible \u2014 appelle-la en console apr\u00e8s agrandissement pour voir l\\'\u00e9tat');\r\n    \r\n    \/\/ \u2705 Clic sur le texte du label \u2192 toggle manuel de la checkbox du m\u00eame conteneur\r\n    jQuery(document).on('click', '.reserver-dynamic-label', function(e) {\r\n        e.preventDefault();\r\n        e.stopPropagation();\r\n        const $cb = $(this).closest('.reserver-dynamic-option, .reserver-dynamic-container').find('.reserver-dynamic-checkbox');\r\n        if ($cb.length) {\r\n            $cb.prop('checked', !$cb.prop('checked')).trigger('change');\r\n        }\r\n    });\r\n\r\n    \/\/ \u2705 CHECKBOX \"R\u00e9server cet espace publicitaire\" \u2014 VALIDATION ET ENVOI DES DONN\u00c9ES\r\n    \/\/ \u2705 v2.4.13 : iOS touchend\r\n    \/\/ \u2705 v2.4.13 : Mobile \u2014 \u00e9couter touchend sur input ET click\/touchend sur label\r\n    jQuery(document).on('touchend', 'input[name=\"form_fields[ReserverEspacePublicitaire]\"]', function() {\r\n        if (this._viaResTouch) { return; }\r\n        this._viaResTouch = true;\r\n        var _self = this;\r\n        setTimeout(function() { _self._viaResTouch = false; }, 500);\r\n        setTimeout(function() { jQuery(_self).trigger('change'); }, 100);\r\n    });\r\n    jQuery(document).on('touchend click', '.elementor-field-group-ReserverEspacePublicitaire label, .reserver-dynamic-label, .reserver-dynamic-option label', function(e) {\r\n        \/\/ Trouver l'input associ\u00e9\r\n        var $input = jQuery(this).closest('.elementor-field-option, .reserver-dynamic-option').find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n        if (!$input.length) { $input = jQuery(this).siblings('input[name=\"form_fields[ReserverEspacePublicitaire]\"]'); }\r\n        if (!$input.length) { $input = jQuery('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').first(); }\r\n        if ($input.length) {\r\n            if (e.type === 'touchend') { e.preventDefault(); }\r\n            var _wasChecked = $input.prop('checked');\r\n            $input.prop('checked', !_wasChecked);\r\n            setTimeout(function() {\r\n                $input.trigger('change');\r\n            }, 50);\r\n        }\r\n    });\r\n    jQuery(document).on('change', 'input[name=\"form_fields[ReserverEspacePublicitaire]\"]', function(e) {\r\n        const $checkbox = $(this);\r\n        let $droppable = $checkbox.closest('.droppable');\r\n        if (!$droppable.length) {\r\n            $droppable = $checkbox.closest('.reserver-dynamic-container').prev('.droppable');\r\n        }\r\n        \/\/ \u2705 v2.4.13 : Fallback mobile \u2014 checkbox dans body > .reserver-dynamic-container[data-droppable-id]\r\n        if (!$droppable.length) {\r\n            const $dynContainer = $checkbox.closest('.reserver-dynamic-container');\r\n            const _droppableId = $dynContainer.attr('data-droppable-id') || '';\r\n            if (_droppableId) {\r\n                $droppable = $('#' + _droppableId);\r\n            }\r\n        }\r\n        \/\/ Dernier recours : utiliser le rank en sessionStorage\r\n        if (!$droppable.length) {\r\n            const _rankFallback = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            if (_rankFallback) { $droppable = $('#' + _rankFallback); }\r\n        }\r\n        \r\n        \/\/ Si on d\u00e9coche\r\n        if (!$checkbox.is(':checked')) {\r\n            console.log('\u2610 Checkbox \"R\u00e9server\" d\u00e9coch\u00e9e');\r\n            \/\/ \u2705 Mettre \u00e0 jour le label\r\n            const $lbl = $checkbox.closest('.reserver-dynamic-option, .elementor-field-option').find('.reserver-dynamic-label, label').not('input');\r\n            $lbl.text('R\u00e9server cet espace publicitaire').css('color', '');\r\n            \/\/ \u2705 Notifier le parent pour d\u00e9-r\u00e9server l'item dans le r\u00e9cap\r\n            const _rankDecoche = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            const _emplacementDecoche = StateManager.buildEmplacementReference(_rankDecoche);\r\n            MessageManager.sendToParent('annulationReservation', {\r\n                Rank_Emplacement_Page_Web: _rankDecoche,\r\n                Commande_Emplacement_Page_Web: _emplacementDecoche,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n            \/\/ \u2705 v2.4.5 : M\u00e9moriser que ce rank a \u00e9t\u00e9 explicitement d\u00e9coch\u00e9\r\n            StateManager.set('_reserverDecoche_' + _rankDecoche, 'Yes');\r\n            console.log('\ud83d\udce4 annulationReservation envoy\u00e9 \u2192 parent | rank:', _rankDecoche, '| emplacement:', _emplacementDecoche);\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 V\u00e9rifier les conditions : format s\u00e9lectionn\u00e9 ET (fichier d\u00e9pos\u00e9 OU envoi diff\u00e9r\u00e9)\r\n        const hasFormat = FormatUIManager.hasSelectedFormat($droppable);\r\n        \/\/ v2.9 : data-via-ad-loaded est specifique au droppable, plus fiable que FileReceived global\r\n        const hasFile = StateManager.get('FileReceived') === 'Yes'\r\n            || $droppable.attr('data-via-ad-loaded') === 'true';\r\n        const hasEnvoiDiffere = $droppable.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        \r\n        console.log('\ud83d\udd0d Checkbox \"R\u00e9server\" - Validation:', { hasFormat, hasFile, hasEnvoiDiffere });\r\n        \r\n        if (!hasFormat) {\r\n            \/\/ \u2705 Si un fichier est d\u00e9j\u00e0 d\u00e9pos\u00e9 \u2192 d\u00e9duire le format depuis l'extension (ne pas bloquer)\r\n            if (hasFile) {\r\n                const _uploadedName = StateManager.get('Upload_File_Name') || '';\r\n                const _ext = _uploadedName.split('.').pop().toLowerCase();\r\n                const _fileType = FileManager.getFileType(_ext);\r\n                let _deducedFormat = '';\r\n                if (_fileType === 'video') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Video' : 'Vid\u00e9o';\r\n                } else if (_fileType === 'image') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Banner' : 'Banni\u00e8re';\r\n                } else if (_fileType === 'document') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Press release' : 'Communiqu\u00e9';\r\n                }\r\n                if (_deducedFormat) {\r\n                    StateManager.set('Commande_Format_Transmis', _deducedFormat);\r\n                    StateManager.set('FormatSelect', _deducedFormat);\r\n                    StateManager.set('Formatchoisi', 'Yes');\r\n                    console.log('\u2705 Format d\u00e9duit depuis extension (' + _ext + '):', _deducedFormat);\r\n                    \/\/ Continuer vers l'envoi (pas de return false)\r\n                } else {\r\n                    e.preventDefault();\r\n                    $checkbox.prop('checked', false);\r\n                    FormatUIManager.flashTitle($droppable);\r\n                    console.log('\u274c R\u00e9servation bloqu\u00e9e : format non d\u00e9ductible depuis extension:', _ext);\r\n                    return false;\r\n                }\r\n            } else {\r\n                e.preventDefault();\r\n                $checkbox.prop('checked', false);\r\n                FormatUIManager.flashTitle($droppable);\r\n                console.log('\u274c R\u00e9servation bloqu\u00e9e : aucun format s\u00e9lectionn\u00e9');\r\n                return false;\r\n            }\r\n        }\r\n        \r\n        if (!hasFile ? !hasEnvoiDiffere : false) {\r\n            e.preventDefault();\r\n            $checkbox.prop('checked', false);\r\n            console.log('\u274c R\u00e9servation bloqu\u00e9e : ni annonce d\u00e9pos\u00e9e ni envoi diff\u00e9r\u00e9');\r\n            return false;\r\n        }\r\n        \r\n        \/\/ \u2705 Conditions remplies \u2014 Pr\u00e9parer et envoyer les donn\u00e9es\r\n        console.log('\u2705 Checkbox \"R\u00e9server\" valid\u00e9e \u2014 envoi des donn\u00e9es');\r\n        \r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.setMultiple({\r\n            \"sendDataToParentFlag\": \"Yes\",\r\n            \"Formatchoisi\": \"Yes\",\r\n            \"Rank_Emplacement_Page_Web\": rankId,\r\n            \"Commande_Emplacement_Page_Web\": StateManager.buildEmplacementReference(rankId),\r\n            \"LoadedPageUrl\": window.location.href,\r\n            \/\/ \u2705 v2.3.0 : Forcer avant l'envoi \u2014 le setTimeout(4000) dans activateSendDataToParent\r\n            \/\/ arrive trop tard sur le 1er clic \u2192 AddNewRefInVosCampagnes restait null\r\n            \"AddNewRefInVosCampagnes\": \"Yes\"\r\n        });\r\n        \r\n        \/\/ \u2705 D\u00e9clencher l'envoi des donn\u00e9es via activateSendDataToParent\r\n        const $dropZone = $droppable.find('#drop_file_zone_achat');\r\n        UploadManager.activateSendDataToParent($dropZone);\r\n    });\r\n    \r\n    \/\/ G\u00c9RER \"Envoi diff\u00e9r\u00e9\" \u2014 marquer l'\u00e9tat sans envoyer (c'est \"R\u00e9server\" qui envoie)\r\n    \/\/ \u2705 v2.4.13 : iOS touchend\r\n    jQuery(document).on('touchend', 'input[name*=\"EnvoiUlterieur\"]', function() {\r\n        if (this._viaTouch) { return; }\r\n        this._viaTouch = true;\r\n        var _self = this;\r\n        setTimeout(function() { _self._viaTouch = false; }, 500);\r\n        setTimeout(function() { jQuery(_self).trigger('change'); }, 100);\r\n    });\r\n    jQuery(document).on('change', 'input[name*=\"EnvoiUlterieur\"]', function(e) {\r\n        const $checkbox = $(this);\r\n        let $droppable = $checkbox.closest('.droppable');\r\n        \/\/ \u2705 v2.4.13 : Fallback mobile\r\n        if (!$droppable.length) {\r\n            const $dynContainer = $checkbox.closest('.reserver-dynamic-container');\r\n            const _droppableId = $dynContainer.attr('data-droppable-id') || '';\r\n            if (_droppableId) { $droppable = $('#' + _droppableId); }\r\n        }\r\n        if (!$droppable.length) {\r\n            const _rankFallback = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            if (_rankFallback) { $droppable = $('#' + _rankFallback); }\r\n        }\r\n  \r\n        \/\/ Si on d\u00e9coche\r\n        if (!$checkbox.is(':checked')) {\r\n            StateManager.set('EnvoiUlterieur', 'false');\r\n            StateManager.set('sendDataToParentFlag', 'No'); \/\/ v4.9ds\r\n            \r\n            if (typeof window.verifierAffichageMsgSelectEspaceLocal === 'function') {\r\n                window.verifierAffichageMsgSelectEspaceLocal();\r\n            }\r\n            \r\n            \/\/ Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server\r\n            FormatUIManager.updateReserverCheckboxState($droppable);\r\n            return;\r\n        }\r\n        \r\n        \/\/ Si on coche, masquer le message\r\n        jQuery('#MsgSelectEspace').hide();\r\n    \r\n        \/\/ V\u00e9rifier si un format est s\u00e9lectionn\u00e9\r\n        const hasSelectedFormat = FormatUIManager.hasSelectedFormat($droppable);\r\n        \r\n        if (!hasSelectedFormat) {\r\n            e.preventDefault();\r\n            $checkbox.prop('checked', false);\r\n            FormatUIManager.flashTitle($droppable);\r\n            return false;\r\n        }\r\n        \r\n        \/\/ \u2705 FORMAT OK \u2014 Marquer l'\u00e9tat envoi diff\u00e9r\u00e9 (sans envoyer les donn\u00e9es)\r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.setMultiple({\r\n            \"EnvoiUlterieur\": 'true',\r\n            \"Rank_Emplacement_Page_Web\": rankId,\r\n            \"Commande_Emplacement_Page_Web\": StateManager.buildEmplacementReference(rankId),\r\n            \"LoadedPageUrl\": window.location.href,\r\n            \"FileReceived\": \"No\",\r\n            \"FullPathAdFile\": '',\r\n            \"Upload_File_Name\": '',\r\n            \"Formatchoisi\": 'Yes',\r\n            \"sendDataToParentFlag\": 'No' \/\/ v4.9ds : emp\u00eache l'ouverture du popup\r\n        });\r\n        \r\n        console.log('\ud83d\udcdd Envoi diff\u00e9r\u00e9 coch\u00e9 \u2014 en attente de validation via checkbox \"R\u00e9server\"');\r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server (maintenant activable)\r\n        FormatUIManager.updateReserverCheckboxState($droppable);\r\n    });\r\n\r\n    \/\/ \u2705 v2.2.0 : Bouton \"Cr\u00e9ation\" dans espace publicitaire \u2192 Kit Ad Creator dans le parent\r\n    jQuery(document).on('click', '.FormatIdCreation', function(e) {\r\n        e.preventDefault();\r\n        var $btn = jQuery(this);\r\n        var $droppable = $btn.closest('.droppable');\r\n        var isActive = $btn.data('creationActive') === true;\r\n\r\n        if (isActive) {\r\n            \/\/ D\u00e9sactiver\r\n            $btn.data('creationActive', false);\r\n            $btn.find('.EspPubFormat').css({'color': '#ffffff'});\r\n            $btn.css({'background-color': 'transparent'});\r\n            MessageManager.sendToParent('closeAdCreatorFromIframe', {});\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2192 toggle OFF, fermeture popup parent');\r\n            return;\r\n        }\r\n\r\n        \/\/ \u2705 Nouvelle logique sites pays\r\n        \/\/ V\u00e9rifier quel format est s\u00e9lectionn\u00e9 (fond blanc, hors Cr\u00e9ation et PopUp)\r\n        var _selFormat = '';\r\n        var _selIsVideo = false;\r\n        $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').each(function() {\r\n            var _bg = this.style.backgroundColor || '';\r\n            var _isWhite = _bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white';\r\n            if (!_isWhite) {\r\n                var _fEl = this.querySelector('.EspPubFormat');\r\n                if (_fEl) { var _col = _fEl.style.color || ''; _isWhite = _col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900'; }\r\n            }\r\n            if (_isWhite) {\r\n                var _cls = this.className || '';\r\n                _selFormat = _cls.match(\/FormatId(\\w+)\/) ? _cls.match(\/FormatId(\\w+)\/)[1] : 'unknown';\r\n                _selIsVideo = jQuery(this).hasClass('FormatIdVideo');\r\n                return false;\r\n            }\r\n        });\r\n\r\n        \/\/ R\u00e8gle 3 : si Vid\u00e9o est s\u00e9lectionn\u00e9 \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e\r\n        if (_selIsVideo) {\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation bloqu\u00e9e \u2014 format Vid\u00e9o actif');\r\n            UIManager.showFormatError($droppable, 'La cr\u00e9ation n\\'est pas disponible pour le format Vid\u00e9o');\r\n            return;\r\n        }\r\n\r\n        \/\/ R\u00e8gle 2 : aucun format s\u00e9lectionn\u00e9 \u2192 message, mais Cr\u00e9ation reste active visuellement\r\n        if (!_selFormat) {\r\n            \/\/ S\u00e9lectionner Cr\u00e9ation visuellement (fond blanc, texte vert)\r\n            $btn.data('creationActive', true);\r\n            $btn.find('.EspPubFormat').css({'color': '#37D900'});\r\n            $btn.css({'background-color': '#ffffff'});\r\n            \/\/ \u2705 Message persistant \u2014 pas de timeout, supprim\u00e9 au clic format\r\n            var $dropZone2 = $droppable.find('#drop_file_zone_achat, .drop_file_zone_achat_class').first();\r\n            if (!$dropZone2.length) { $dropZone2 = $droppable; }\r\n            var $dropZone2snap = $dropZone2;\r\n            setTimeout(function() {\r\n                jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n                jQuery('#fmt-creation-info').remove();\r\n                var _isMob2 = UIManager.isMobile();\r\n                \/\/ v4.9ds : Attacher le message au BODY (pas dans le drop zone) pour \u00e9chapper\r\n                \/\/          \u00e0 l'opacity 0.4 du format-gate appliqu\u00e9 sur .HTMLUploadfileConteneur.\r\n                \/\/          Position calcul\u00e9e dynamiquement \u00e0 partir du dropZone (au-dessus du curseur).\r\n                \/\/ v4.9ds (suit-drag) : si on est sur Ele0A et qu'un wrapper .ToBeHidden parent\r\n                \/\/          existe (cas espace pop-up draggable), on attache le message au wrapper\r\n                \/\/          au lieu du body. Le wrapper est ce qui se d\u00e9place lors du drag d'Ele0A,\r\n                \/\/          donc le message suit naturellement. L'opacity 0.4 est sur\r\n                \/\/          .HTMLUploadfileConteneur (enfant d'Ele0A) \u2014 le wrapper, parent\r\n                \/\/          d'Ele0A, y \u00e9chappe.\r\n                var _dzEl = $dropZone2snap[0];\r\n                if (!_dzEl) return;\r\n                var _rect = _dzEl.getBoundingClientRect();\r\n                \/\/ v4.9ds : desktop top:50\u219240, mobile top:54 inchang\u00e9\r\n                \/\/   + dans l'iframe r\u00e9gie (window !== window.top) : descendre de 50px\r\n                \/\/   (le message tombait sur les formats au lieu de la dropZone)\r\n                var _isInIframe = (window !== window.top);\r\n                var _extraOffset = _isInIframe ? 50 : 0;\r\n                \/\/ D\u00e9tection wrapper Ele0A pour suivi drag\r\n                var _$ele0A = jQuery(_dzEl).closest('#Ele0A');\r\n                var _$wrapper = _$ele0A.length ? _$ele0A.closest('.ToBeHidden') : jQuery();\r\n                var _useWrapper = _$wrapper.length > 0;\r\n                var _topPx, _leftPx, _$parent, _positionMode;\r\n                if (_useWrapper) {\r\n                    \/\/ Position RELATIVE au wrapper (pas au document) \u2192 suit le drag\r\n                    var _wRect = _$wrapper[0].getBoundingClientRect();\r\n                    _topPx = Math.round((_rect.top - _wRect.top) + (_isMob2 ? 54 : 40) + _extraOffset);\r\n                    _leftPx = Math.round((_rect.left - _wRect.left) + _rect.width \/ 2);\r\n                    _$parent = _$wrapper;\r\n                    _positionMode = 'absolute';\r\n                    \/\/ S'assurer que le wrapper peut h\u00e9berger un absolute (position non-static)\r\n                    var _curPos = window.getComputedStyle(_$wrapper[0]).position;\r\n                    if (_curPos === 'static') { _$wrapper.css('position', 'relative'); }\r\n                    \/\/ Permettre au message de d\u00e9border si le wrapper a overflow:hidden\r\n                    var _curOv = window.getComputedStyle(_$wrapper[0]).overflow;\r\n                    if (_curOv === 'hidden') { _$wrapper.css('overflow', 'visible'); }\r\n                } else {\r\n                    \/\/ Fallback historique : attacher au body (cas miniature, ele1a+, etc.)\r\n                    _topPx = Math.round(_rect.top + (_isMob2 ? 54 : 40) + _extraOffset + (window.scrollY || 0));\r\n                    _leftPx = Math.round(_rect.left + _rect.width \/ 2 + (window.scrollX || 0));\r\n                    _$parent = jQuery('body');\r\n                    _positionMode = 'absolute';\r\n                }\r\n                \/\/ v4.9ds : mobile scale 0.6, desktop scale 0.7\r\n                var _transform = _isMob2\r\n                    ? 'transform:translateX(-50%) scale(0.6);transform-origin:top center;'\r\n                    : 'transform:translateX(-50%) scale(0.7);transform-origin:top center;';\r\n                var _fontSize = _isMob2 ? '13px' : '14px';\r\n                \/\/ v4.9ds (fmt-info-z-fix) : z-index dynamique pour rester au-dessus du\r\n                \/\/   wrapper .ToBeHidden d'Ele0A quand celui-ci est mis en avant-plan par\r\n                \/\/   _bringPopupToFront (Entete.txt). On lit window.popupZIndex (variable\r\n                \/\/   globale du compteur z-index dans Entete.txt) et on prend max+10. Si\r\n                \/\/   absent (cas iframe ou page sans Entete.txt), fallback 99999.\r\n                \/\/   NB : si on est attach\u00e9 au wrapper, le z-index est relatif au stacking\r\n                \/\/   context du wrapper, mais on garde la valeur \u00e9lev\u00e9e par s\u00e9curit\u00e9.\r\n                var _baseZ = (typeof window.popupZIndex === 'number') ? window.popupZIndex : 99999;\r\n                var _msgZ = Math.max(99999, _baseZ + 10);\r\n                _$parent.append('<div id=\"fmt-creation-info\" style=\"'\r\n                    + 'position:' + _positionMode + ';top:' + _topPx + 'px;left:' + _leftPx + 'px;'\r\n                    + _transform\r\n                    + 'width:auto;white-space:nowrap;z-index:' + _msgZ + ';'\r\n                    + 'background:#fff;color:#FB5E2A;font-weight:700;font-size:' + _fontSize + ';'\r\n                    + 'text-align:center;padding:8px 10px;border-radius:6px;'\r\n                    + 'border:2px solid #FB5E2A;box-sizing:border-box;line-height:1.4;'\r\n                    + 'opacity:1;pointer-events:auto;'\r\n                    + '\">Merci de s\u00e9lectionner le format d\\'annonce que vous souhaitez cr\u00e9er<\/div>');\r\n            }, 80);\r\n            \/\/ D\u00e9sactiver visuellement le bouton Vid\u00e9o + changer son texte en \"D\u00e9sactiv\u00e9\"\r\n            $droppable.find('.FormatIdVideo').each(function() {\r\n                jQuery(this).css({'opacity': '0.4', 'pointer-events': 'none'});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                $lbl.attr('data-original-text', $lbl.text());\r\n                $lbl.text('D\u00e9sactiv\u00e9');\r\n            });\r\n            \/\/ NE PAS d\u00e9s\u00e9lectionner Cr\u00e9ation \u2014 laisser l'utilisateur choisir un format\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2014 aucun format s\u00e9lectionn\u00e9 \u2192 message + Vid\u00e9o d\u00e9sactiv\u00e9e');\r\n            return;\r\n        }\r\n\r\n        \/\/ R\u00e8gle 1 : format valide s\u00e9lectionn\u00e9 \u2192 d\u00e9sactiver Vid\u00e9o + ouvrir le popup apr\u00e8s 3 secondes\r\n        $btn.data('creationActive', true);\r\n        $btn.find('.EspPubFormat').css({'color': '#37D900'});\r\n        $btn.css({'background-color': '#ffffff'});\r\n        \/\/ \u2705 D\u00e9sactiver Vid\u00e9o\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '0.4', 'pointer-events': 'none'});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n            $lbl.text('D\u00e9sactiv\u00e9');\r\n        });\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') || '';\r\n        var formatTransmis = sessionStorage.getItem('Commande_Format_Transmis') || '';\r\n        var _rankKit = $droppable.attr('id') || sessionStorage.getItem('Rank_Emplacement_Page_Web') || '';\r\n        var _cSite = sessionStorage.getItem('codeSite') || '';\r\n        var _cPage = sessionStorage.getItem('codePage') || '';\r\n        var _sfxKit = _rankKit.replace('Ele', '');\r\n        var _emplKit = (_cSite ? (_cPage ? _sfxKit : false) : false) ? (_cSite + _cPage + 'L' + _sfxKit) : (sessionStorage.getItem('Commande_Emplacement_Page_Web') || '');\r\n        var _fmtSnap = formatSelect;\r\n        var _fmtTSnap = formatTransmis;\r\n        var _rkSnap = _rankKit;\r\n        var _emplSnap = _emplKit;\r\n        setTimeout(function() {\r\n            MessageManager.sendToParent('openAdCreatorFromIframe', { formatSelect: _fmtSnap, formatTransmis: _fmtTSnap, rankId: _rkSnap, emplacement: _emplSnap });\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2192 toggle ON apr\u00e8s 2s | format:', _selFormat, '| FormatSelect:', _fmtSnap);\r\n        }, 2000);\r\n    });\r\n\r\n    \/\/ \u2705 R\u00e8gle : si Vid\u00e9o est cliqu\u00e9, d\u00e9sactiver Cr\u00e9ation\r\n    jQuery(document).on('click', '.FormatIdVideo', function(e) {\r\n        var $droppable = jQuery(this).closest('.droppable');\r\n        \/\/ R\u00e9activer Vid\u00e9o (peut avoir \u00e9t\u00e9 d\u00e9sactiv\u00e9e par la r\u00e8gle 2) + restaurer texte\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ D\u00e9sactiver visuellement Cr\u00e9ation + changer texte en \"D\u00e9sactiv\u00e9\"\r\n        $droppable.find('.FormatIdCreation').each(function() {\r\n            jQuery(this).data('creationActive', false);\r\n            jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n            jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            $lbl.attr('data-original-text', $lbl.attr('data-original-text') || $lbl.text());\r\n            $lbl.text('D\u00e9sactiv\u00e9');\r\n        });\r\n        \/\/ Fermer le popup si ouvert\r\n        MessageManager.sendToParent('closeAdCreatorFromIframe', {});\r\n        console.log('\ud83c\udfa8 Vid\u00e9o s\u00e9lectionn\u00e9 \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e');\r\n    });\r\n\r\n    \/\/ \u2705 R\u00e8gle : si un autre format est s\u00e9lectionn\u00e9, r\u00e9activer Cr\u00e9ation et Vid\u00e9o + restaurer textes\r\n    jQuery(document).on('click', '.EspPubFormatContainer:not(.FormatIdCreation):not(.FormatIdPopUp):not(.FormatIdVideo)', function(e) {\r\n        var $droppable = jQuery(this).closest('.droppable');\r\n        \/\/ R\u00e9activer Vid\u00e9o + restaurer texte\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ R\u00e9activer Cr\u00e9ation + restaurer texte\r\n        $droppable.find('.FormatIdCreation').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ Supprimer les messages d'erreur\r\n        jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n    });\r\n\r\n    \/\/ \u2705 v2.2.0 : Messages depuis le parent concernant le bouton Cr\u00e9ation\r\n    window.addEventListener('message', function(event) {\r\n        var msg = event.data;\r\n        if (!msg) return;\r\n\r\n        \/\/ \u2705 v2.2.1 : Fournir la position du titre R\u00e9server pour positionner la miniature\r\n        if (msg.type === 'getReserverLabelRect') {\r\n            var $label = jQuery('.reserver-dynamic-label').first();\r\n            if ($label.length) {\r\n                var r = $label[0].getBoundingClientRect();\r\n                var iframeScale = (window.outerWidth > 1000) ? 0.75 : 0.80; \/\/ \u2705 v2.4.12 : 0.85 \u2192 0.75 (scale r\u00e9el du parent)\r\n                event.source.postMessage({\r\n                    type: 'reserVeurLabelRectResult',\r\n                    \/\/ Retourner bottom en coordonn\u00e9es iframe internes (non scal\u00e9es)\r\n                    bottom: r.bottom,\r\n                    left: r.left,\r\n                    right: r.right,\r\n                    iframeScale: iframeScale\r\n                }, '*');\r\n            } else {\r\n                event.source.postMessage({ type: 'reserVeurLabelRectResult', bottom: null }, '*');\r\n            }\r\n        }\r\n\r\n        \/\/ Fermeture du popup \u2192 d\u00e9s\u00e9lectionner Cr\u00e9ation + r\u00e9activer Vid\u00e9o dans tous les espaces\r\n        if (msg.type === 'adCreatorClosedFromParent') {\r\n            jQuery('.FormatIdCreation').each(function() {\r\n                jQuery(this).data('creationActive', false);\r\n                jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                jQuery(this).css({'background-color': 'transparent'});\r\n            });\r\n            \/\/ \u2705 R\u00e9activer Vid\u00e9o dans tous les espaces de la page\r\n            jQuery('.FormatIdVideo').each(function() {\r\n                jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                var _orig = $lbl.attr('data-original-text');\r\n                if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n            });\r\n            \/\/ v4.9ds : r\u00e9-\u00e9valuer SelectionFormatTitre \/ SelectionFormatTitreBlanc sur tous les droppables\r\n            \/\/          (Cr\u00e9ation d\u00e9s\u00e9lectionn\u00e9 \u2192 si aucun autre format actif dans le DOM, le titre rouge doit r\u00e9appara\u00eetre)\r\n            \/\/          Check DOM direct (pas updateTitleColor qui retombe sur sessionStorage Formatchoisi=Yes)\r\n            jQuery('.droppable').not('#Ele0A').each(function() {\r\n                var $_d = jQuery(this);\r\n                var _hasDomFmt = $_d.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').filter(function() {\r\n                    var _bg = jQuery(this).css('background-color') || '';\r\n                    return _bg === 'rgb(255, 255, 255)';\r\n                }).length > 0;\r\n                if (_hasDomFmt) {\r\n                    $_d.find('.SelectionFormatTitre').hide();\r\n                    $_d.find('.SelectionFormatTitreBlanc').show();\r\n                } else {\r\n                    $_d.find('.SelectionFormatTitre').each(function() { this.style.setProperty('display','block','important'); });\r\n                    $_d.find('.SelectionFormatTitreBlanc').hide();\r\n                }\r\n            });\r\n            console.log('\ud83c\udfa8 adCreatorClosedFromParent \u2192 Cr\u00e9ation d\u00e9s\u00e9lectionn\u00e9 + Vid\u00e9o r\u00e9activ\u00e9 + titres r\u00e9-\u00e9valu\u00e9s (DOM direct)');\r\n        }\r\n\r\n        \/\/ Restaurer le style du bouton Cr\u00e9ation apr\u00e8s un reset de removeElements\r\n        if (msg.type === 'restoreCreationButton') {\r\n            jQuery('.FormatIdCreation').each(function() {\r\n                if (jQuery(this).data('creationActive') === true) {\r\n                    jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                    jQuery(this).css({'background-color': '#ffffff'});\r\n                }\r\n            });\r\n            console.log('\ud83c\udfa8 restoreCreationButton re\u00e7u \u2192 style Cr\u00e9ation restaur\u00e9');\r\n        }\r\n    });\r\n\r\n});\r\n\r\n\/\/ =========================================================================\r\n\/\/ \u2705 v4.9ds Pb 9.62 R\u00e8gle 2 : Listener disableEspacesPris \u2014 grise\/d\u00e9sactive les\r\n\/\/   espaces publicitaires d\u00e9j\u00e0 pris par d'autres items du panier sur la m\u00eame page.\r\n\/\/   Re\u00e7u depuis Panier_manager.txt (notifierEspacesPrisIframe).\r\n\/\/   \"1 espace pub = 1 r\u00e9servation\" \u2192 emp\u00eache le user de cliquer \u00e0 nouveau dessus.\r\n\/\/ =========================================================================\r\n(function _viaInitDisableEspacesPris() {\r\n    \/\/ CSS inject\u00e9 une seule fois pour le grisage + overlay \"D\u00e9j\u00e0 r\u00e9serv\u00e9\"\r\n    if (!document.getElementById('via-espace-pris-style')) {\r\n        var _styleEPS = document.createElement('style');\r\n        _styleEPS.id = 'via-espace-pris-style';\r\n        _styleEPS.textContent =\r\n            '.via-espace-pris-greyed { ' +\r\n            '  opacity: 0.45 !important; ' +\r\n            '  pointer-events: none !important; ' +\r\n            '  cursor: not-allowed !important; ' +\r\n            '  position: relative !important; ' +\r\n            '  filter: grayscale(80%) !important; ' +\r\n            '}' +\r\n            '.via-espace-pris-greyed::after { ' +\r\n            '  content: \"D\u00e9j\u00e0 r\u00e9serv\u00e9\"; ' +\r\n            '  position: absolute; ' +\r\n            '  top: 50%; ' +\r\n            '  left: 50%; ' +\r\n            '  transform: translate(-50%, -50%); ' +\r\n            '  background: rgba(34, 93, 169, 0.92); ' +\r\n            '  color: #ffffff; ' +\r\n            '  padding: 5px 12px; ' +\r\n            '  border-radius: 4px; ' +\r\n            '  font-size: 13px; ' +\r\n            '  font-weight: 600; ' +\r\n            '  white-space: nowrap; ' +\r\n            '  pointer-events: none; ' +\r\n            '  z-index: 10; ' +\r\n            '  box-shadow: 0 2px 6px rgba(0,0,0,0.25); ' +\r\n            '}';\r\n        document.head.appendChild(_styleEPS);\r\n    }\r\n\r\n    function _applyEspacesPris(emplacements) {\r\n        \/\/ 1. Retirer le grisage de tous les droppables (reset)\r\n        jQuery('.droppable').removeClass('via-espace-pris-greyed');\r\n        \/\/ 2. Griser ceux dans la liste\r\n        if (!Array.isArray(emplacements)) { return; }\r\n        var _count = 0;\r\n        emplacements.forEach(function(_item) {\r\n            if (!_item) return;\r\n            \/\/ Cibler par rank (Ele1A, Ele2A, etc.) ou par data-empl\r\n            var _rank = _item.rank || '';\r\n            var _empl = _item.emplacement || '';\r\n            var $cible = jQuery();\r\n            if (_rank) {\r\n                $cible = jQuery('#' + _rank);\r\n            }\r\n            if (!$cible.length ? !!_empl : false) {\r\n                \/\/ Fallback : chercher un droppable dont la r\u00e9f\u00e9rence d'emplacement matche\r\n                $cible = jQuery('.droppable').filter(function() {\r\n                    var _t = jQuery(this).find('[data-empl]').attr('data-empl') || '';\r\n                    return _t === _empl;\r\n                });\r\n            }\r\n            if ($cible.length) {\r\n                $cible.addClass('via-espace-pris-greyed');\r\n                _count++;\r\n            }\r\n        });\r\n        console.log('\ud83d\udeab [disableEspacesPris]', _count, '\/', emplacements.length, 'espace(s) gris\u00e9(s)');\r\n    }\r\n\r\n    window.addEventListener('message', function(_eEPS) {\r\n        var _msg = _eEPS.data;\r\n        if (!_msg ? true : _msg.type !== 'disableEspacesPris') return;\r\n        _applyEspacesPris(_msg.emplacements);\r\n    });\r\n\r\n    \/\/ Exposer pour appel manuel \/ test depuis console\r\n    window._viaApplyEspacesPris = _applyEspacesPris;\r\n    console.log('\u2705 [disableEspacesPris] listener install\u00e9');\r\n})();\r\n\r\n\/\/ =========================================================================\r\n\/\/ \u2705 Listener kitAdCreated \u2014 annonce cr\u00e9\u00e9e par le Kit overlay (mode=kit)\r\n\/\/ Injecte directement dans l'espace pub identifi\u00e9 par rankId\r\nwindow.addEventListener('message', function(event) {\r\n    var msg = event.data;\r\n\r\n    if (!msg || msg.action !== 'kitAdCreated') return;\r\n\r\n    console.log('\ud83d\udd0d [DIAG kitAdCreated entr\u00e9e] receveur=' + (window === window.top ? 'PARENT' : 'IFRAME') + ' | location=' + window.location.href.substring(0, 80) + ' | rank=' + (msg.rankId || 'NULL'));\r\n\r\n    var _rankId = msg.rankId || '';\r\n    var _emplacement = msg.emplacement || '';\r\n    console.log('\ud83c\udfa8 [espace_pub] kitAdCreated re\u00e7u | rankId:', _rankId, '| emplacement:', _emplacement);\r\n\r\n    if (!_rankId) { console.warn('\u26a0\ufe0f [kitAdCreated] rankId manquant'); return; }\r\n\r\n    var $droppable = jQuery('#' + _rankId);\r\n    if (!$droppable.length) { console.warn('\u26a0\ufe0f [kitAdCreated] droppable non trouv\u00e9:', _rankId); return; }\r\n    var $dropZone = $droppable.find('.drop_file_zone_achat_class').first();\r\n    if (!$dropZone.length) { $dropZone = $droppable.find('#drop_file_zone_achat').first(); }\r\n    if (!$dropZone.length) { console.warn('\u26a0\ufe0f [kitAdCreated] dropZone non trouv\u00e9e dans', _rankId); return; }\r\n\r\n    \/\/ v4.9ds : d\u00e9verrouiller le format-gate sur le droppable cible\r\n    \/\/   Si l'utilisateur d\u00e9pose une cr\u00e9ation depuis la miniature dans un espace sans format pr\u00e9alable,\r\n    \/\/   data-via-format-gate=\"locked\" reste pos\u00e9 \u2192 opacity 0.4 sur l'annonce d\u00e9pos\u00e9e.\r\n    \/\/   On pose le format de la miniature pour lib\u00e9rer le gate.\r\n    try {\r\n        var _topW = window.top || window;\r\n        var _kitFmt = msg.format || sessionStorage.getItem('FormatSelect') || '';\r\n        if (_kitFmt && typeof _topW._viaSetDroppableFormat === 'function') {\r\n            _topW._viaSetDroppableFormat(_rankId, _kitFmt);\r\n            console.log('\ud83d\udd13 [kitAdCreated] format-gate d\u00e9verrouill\u00e9 sur', _rankId, '| format:', _kitFmt);\r\n        }\r\n    } catch(_eGate) { console.warn('\u26a0\ufe0f [kitAdCreated] _viaSetDroppableFormat error:', _eGate); }\r\n\r\n    \/\/ \u2705 Bug 7 sites pays : stocker pdfImageDataURL pour \"Ouvrir et visualiser\"\r\n    \/\/   (m\u00eame logique que thumbnailDropped qui fonctionne sur la r\u00e9gie)\r\n    if (msg.pdfImageDataURL) {\r\n        $droppable.data('kitPdfImageDataURL', msg.pdfImageDataURL);\r\n        $droppable.data('kitFormatSelect', msg.format || '');\r\n        console.log('\ud83d\udcce [kitAdCreated] pdfImageDataURL stock\u00e9 sur', _rankId, '| format:', msg.format);\r\n    } else {\r\n        $droppable.removeData('kitPdfImageDataURL');\r\n        $droppable.removeData('kitFormatSelect');\r\n    }\r\n    \/\/ v4.9ds : 1\u00e8re image utilisateur (pour preview mobile JPG-in-PDF)\r\n    if (msg.firstImageDataURL) {\r\n        $droppable.data('kitFirstImageDataURL', msg.firstImageDataURL);\r\n        console.log('\ud83d\uddbc\ufe0f [kitAdCreated] kitFirstImageDataURL stock\u00e9 sur', _rankId);\r\n        try { var _dl3 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl3) _dl3('[v4.9ds] kitAdCreated firstImg stock\u00e9 ' + _rankId, 'ok'); } catch(e) {}\r\n    } else {\r\n        $droppable.removeData('kitFirstImageDataURL');\r\n        try { var _dl4 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl4) _dl4('[v4.9ds] kitAdCreated SANS firstImg ' + _rankId, 'warn'); } catch(e) {}\r\n    }\r\n\r\n    var _dataURL = msg.fullResDataURL || msg.pdfDataURL || null;\r\n    var _filename = msg.filename || 'annonce.png';\r\n    var _isPDF = msg.isPDF || false;\r\n    if (!_dataURL) { console.warn('\u26a0\ufe0f [kitAdCreated] aucune donn\u00e9e image'); return; }\r\n\r\n    var _parts = _dataURL.split(',');\r\n    var _mime = (_parts[0].match(\/:(.*?);\/) || [])[1] || (_isPDF ? 'application\/pdf' : 'image\/png');\r\n    var _bStr = atob(_parts[1]);\r\n    var _bytes = new Uint8Array(_bStr.length);\r\n    for (var _i = 0; _i < _bStr.length; _i++) { _bytes[_i] = _bStr.charCodeAt(_i); }\r\n    var _blob = new Blob([_bytes], { type: _mime });\r\n    \/\/ \u2705 Ajouter extension si absente\r\n    var _MIME_EXT = {'image\/png':'.png','image\/jpeg':'.jpg','image\/jpg':'.jpg','image\/webp':'.webp','application\/pdf':'.pdf'};\r\n    if (_filename.indexOf('.') === -1) { _filename = _filename + (_MIME_EXT[_mime] || (_isPDF ? '.pdf' : '.png')); }\r\n    var _file = new File([_blob], _filename, { type: _mime });\r\n\r\n    \/\/ \u2705 Pr\u00e9parer StateManager\r\n    StateManager.set('Rank_Emplacement_Page_Web', _rankId);\r\n    StateManager.set('Commande_Emplacement_Page_Web', _emplacement);\r\n    StateManager.set('Formatchoisi', 'Yes');\r\n    \/\/ \u2705 Marquer le droppable directement en DOM \u2014 r\u00e9siste \u00e0 l'async, lu par styleUploadedAd\r\n    $droppable[0].setAttribute('data-kit-drop', 'true');\r\n    window._dropFromMiniature = true;\r\n\r\n    console.log('\u2705 [kitAdCreated] \u2192 UploadManager.handleFileUpload | rankId:', _rankId);\r\n    \/\/ \u2550\u2550\u2550\u2550 DIAG TEMPORAIRE v4.9ds \u2014 cha\u00eene upload kitAdCreated \u2550\u2550\u2550\u2550\r\n    console.log('\ud83d\udd0d [DIAG kit-upload] AVANT handleFileUpload | StateManager FullPathAdFile:', StateManager.get('FullPathAdFile'), '| FileReceived:', StateManager.get('FileReceived'), '| _file size:', _file.size, '| name:', _file.name);\r\n    \/\/ v4.9ds : exposer une promise globale pour que prepareUploadData puisse attendre\r\n    \/\/   l'upload avant de poster les donn\u00e9es au popup (sinon FullPathAdFile=null en BDD).\r\n    window._viaPendingUpload = window._viaPendingUpload || {};\r\n    window._viaPendingUpload[_rankId] = UploadManager.handleFileUpload(_file, $dropZone).then(function() {\r\n        console.log('\u2705 [kitAdCreated] handleFileUpload termin\u00e9');\r\n        console.log('\ud83d\udd0d [DIAG kit-upload] APR\u00c8S handleFileUpload | StateManager FullPathAdFile:', StateManager.get('FullPathAdFile'), '| FileReceived:', StateManager.get('FileReceived'), '| Upload_File_Name:', StateManager.get('Upload_File_Name'));\r\n        delete window._viaPendingUpload[_rankId];\r\n    }).catch(function(err) {\r\n        window._dropFromMiniature = false;\r\n        $droppable[0].removeAttribute('data-kit-drop');\r\n        console.error('\u274c [kitAdCreated] handleFileUpload erreur:', err);\r\n        delete window._viaPendingUpload[_rankId];\r\n    });\r\n});\r\n\r\n\/\/ \u2705 v1.17.0 : Listener \"thumbnailDropped\" \u2014 d\u00e9p\u00f4t de l'annonce depuis la miniature\r\n\/\/ =========================================================================\r\n\/**\r\n * Re\u00e7oit le drop de l'image-annonce depuis la miniature dans la page parente.\r\n * Identifie l'espace publicitaire sous le curseur, s\u00e9lectionne cet espace,\r\n * et d\u00e9clenche le flux dataFromIframeEspacePub en mode \"envoi diff\u00e9r\u00e9\"\r\n * (l'utilisateur uploadera le vrai fichier depuis le formulaire de commande).\r\n *\r\n * Coordonn\u00e9es re\u00e7ues : viewport de l'iframe (clientX\/clientY relatifs \u00e0 l'iframe)\r\n *\/\r\nwindow.addEventListener('message', function(event) {\r\n    var msg = event.data;\r\n    if (!msg || msg.action !== 'thumbnailDropped') return;\r\n\r\n    console.log('\ud83d\udce8 thumbnailDropped re\u00e7u \u2014 coords iframe:', msg.xRel, msg.yRel);\r\n\r\n    \/\/ \u2705 v1.19.3 : Corriger les coordonn\u00e9es pour le facteur de scale de l'iframe\r\n    \/\/ \u2705 v2.4.10 : 0.75 = scale appliqu\u00e9 par le PARENT sur l'\u00e9l\u00e9ment iframe (et non 0.85 qui est le scale interne OrdiMobileConteneurClass)\r\n    \/\/ Les coords xRel\/yRel viennent de getBoundingClientRect() sur l'iframe scal\u00e9 \u00e0 0.75 dans le parent \u2192 diviser par 0.75\r\n    var iframeScale = (window.outerWidth > 1000) ? 0.75 : 0.80;\r\n    \/\/ \u2705 v2.4.3 : Si Entete a intercept\u00e9 et pos\u00e9 un redirect vers Ele0A, utiliser ces coords\r\n    var _redirect = window._thumbnailDropRedirect || null;\r\n    window._thumbnailDropRedirect = null;\r\n    \/\/ \u2705 v2.4.4 : Les coords de redirect viennent de getBoundingClientRect() dans l'iframe (espace logique)\r\n    \/\/            \u2192 ne pas re-diviser par iframeScale (d\u00e9j\u00e0 en coords iframe-logiques)\r\n    var xAdjusted = _redirect ? _redirect.xRel : (msg.xRel \/ iframeScale);\r\n    var yAdjusted = _redirect ? _redirect.yRel : (msg.yRel \/ iframeScale);\r\n    if (_redirect) { console.log('\ud83d\udd00 [thumbnailDropped] coords redirig\u00e9es vers Ele0A:', Math.round(xAdjusted), Math.round(yAdjusted)); }\r\n    else { console.log('\ud83d\udcd0 Coords ajust\u00e9es (scale', iframeScale, '):', Math.round(xAdjusted), Math.round(yAdjusted)); }\r\n\r\n    \/\/ 1. Identifier l'espace publicitaire le plus proche du point de drop\r\n    \/\/    \u2705 v1.18.1 : Recherche par distance \u2014 coords \u00e9ventuellement redirig\u00e9es par Entete.txt\r\n    var allDroppables = document.querySelectorAll('.droppable');\r\n    var droppableEl = null;\r\n    var closestDist = Infinity;\r\n\r\n    \/\/ \u2705 v2.4.4 : Guard rect Ele0A \u2014 si PopUpChoice=Yes ET curseur dans le rect d'Ele0A \u2192 forcer\r\n    \/\/            (Ele0A est un popup flottant : son centre peut \u00eatre loin du curseur \u2192 algo distance l'ignore)\r\n    \/\/            Ne force PAS si le drop est hors d'Ele0A \u2192 les autres espaces restent accessibles\r\n    if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n        var _ele0ACheck = document.getElementById('Ele0A');\r\n        if (_ele0ACheck) {\r\n            var _r0A = _ele0ACheck.getBoundingClientRect();\r\n            var _margin = 30;\r\n            var _inR0A = (xAdjusted >= _r0A.left - _margin);\r\n            if (_inR0A) { _inR0A = (xAdjusted <= _r0A.right + _margin); }\r\n            if (_inR0A) { _inR0A = (yAdjusted >= _r0A.top - _margin); }\r\n            if (_inR0A) { _inR0A = (yAdjusted <= _r0A.bottom + _margin); }\r\n            if (_inR0A) {\r\n                droppableEl = _ele0ACheck;\r\n                closestDist = 0;\r\n                console.log('\ud83c\udfaf [thumbnailDropped] Ele0A forc\u00e9 (curseur dans rect Ele0A +30px)');\r\n            }\r\n        }\r\n    }\r\n\r\n    if (!droppableEl) {\r\n        allDroppables.forEach(function(d) {\r\n            var r = d.getBoundingClientRect();\r\n            var dCenterX = r.left + r.width  \/ 2;\r\n            var dCenterY = r.top  + r.height \/ 2;\r\n            var dist = Math.abs(dCenterX - xAdjusted) + Math.abs(dCenterY - yAdjusted);\r\n            if (dist < closestDist) { closestDist = dist; droppableEl = d; }\r\n        });\r\n        \/\/ \u2705 v2.4.3 : Si Entete a d\u00e9tect\u00e9 un drop sur Ele0A \u2192 forcer directement\r\n        if (_redirect) { if (_redirect.forceEle0A) {\r\n            var _ele0AForced = document.getElementById('Ele0A');\r\n            if (_ele0AForced) { droppableEl = _ele0AForced; console.log('\ud83c\udfaf [thumbnailDropped] Ele0A forc\u00e9 (redirect Entete)'); }\r\n        } }\r\n    }\r\n\r\n    if (!droppableEl || closestDist > 800) {\r\n        console.warn('thumbnailDropped \u2014 aucun droppable proche (dist min:', closestDist, ')');\r\n        return;\r\n    }\r\n    console.log('\ud83d\udccd Droppable trouv\u00e9:', droppableEl.id, '(dist:', Math.round(closestDist), ')');\r\n\r\n    var $droppable = jQuery(droppableEl);\r\n    var rankId = $droppable.attr('id');\r\n    if (!rankId) { console.warn('thumbnailDropped \u2014 .droppable sans id'); return; }\r\n\r\n    var $dropZone = $droppable.find('#drop_file_zone_achat').first();\r\n    if (!$dropZone.length) { console.warn('thumbnailDropped \u2014 #drop_file_zone_achat introuvable dans', rankId); return; }\r\n\r\n    var emplacementRef = StateManager.buildEmplacementReference(rankId);\r\n    console.log('\u2705 thumbnailDropped \u2014 espace pub:', rankId, '\u2192', emplacementRef);\r\n\r\n    \/\/ 2. Pr\u00e9parer l'\u00e9tat SessionStorage (comme un vrai drop de fichier)\r\n    StateManager.setMultiple({\r\n        'Rank_Emplacement_Page_Web':     rankId,\r\n        'Commande_Emplacement_Page_Web': emplacementRef,\r\n        'FirstUploadFileorMoved':        'FirstUpload',\r\n        'Formatchoisi':                  'Yes',\r\n        'Commande_Format_Transmis':      'Image',\r\n        'EnvoiUlterieur':                'false',\r\n        'FileReceived':                  'No',\r\n        'AdDisplayed':                   'Yes'\r\n    });\r\n\r\n    UIManager.updateEmplacementDisplay();\r\n\r\n    \/\/ 3. Construire le File \u00e0 partir du PDF (communiqu\u00e9\/interview) ou de l'image PNG (autres formats)\r\n    \/\/    puis appeler handleFileUpload \u2192 m\u00eame flux qu'un vrai drag&drop : liser\u00e9 vert, aper\u00e7u, R\u00e9server\r\n    var dataURL, mime, ext;\r\n\r\n    \/\/ \u2705 Stocker le PDF image et le format sur le droppable pour le popup de visualisation inline\r\n    if (msg.pdfImageDataURL) {\r\n        $droppable.data('kitPdfImageDataURL', msg.pdfImageDataURL);\r\n        $droppable.data('kitFormatSelect', msg.FormatSelect || '');\r\n        console.log('\ud83d\udcce pdfImageDataURL stock\u00e9 sur', rankId, '| format:', msg.FormatSelect);\r\n    } else {\r\n        $droppable.removeData('kitPdfImageDataURL');\r\n        $droppable.removeData('kitFormatSelect');\r\n    }\r\n    \/\/ v4.9ds : 1\u00e8re image utilisateur extraite par le kit (pour preview mobile JPG-in-PDF)\r\n    if (msg.firstImageDataURL) {\r\n        $droppable.data('kitFirstImageDataURL', msg.firstImageDataURL);\r\n        console.log('\ud83d\uddbc\ufe0f kitFirstImageDataURL stock\u00e9 sur', rankId);\r\n        try { var _dl1 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl1) _dl1('[v4.9ds] thumbnailDropped firstImg stock\u00e9 ' + rankId, 'ok'); } catch(e) {}\r\n    } else {\r\n        $droppable.removeData('kitFirstImageDataURL');\r\n        try { var _dl2 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl2) _dl2('[v4.9ds] thumbnailDropped SANS firstImg ' + rankId, 'warn'); } catch(e) {}\r\n    }\r\n\r\n    if (msg.isPDF ? msg.pdfDataURL : false) {\r\n        \/\/ \u2500\u2500 Format PDF (communiqu\u00e9 \/ interview) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        dataURL = msg.pdfDataURL;                         \/\/ \"data:application\/pdf;base64,\u2026\"\r\n        mime    = 'application\/pdf';\r\n        ext     = '.pdf';\r\n    } else if (msg.imageDataURL) {\r\n        \/\/ \u2500\u2500 Format image (banni\u00e8re \/ parrainage) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        dataURL = msg.imageDataURL;\r\n        var mimeMatch = dataURL.match(\/^data:([^;]+);base64,\/);\r\n        mime    = mimeMatch ? mimeMatch[1] : 'image\/png';\r\n        ext     = mime === 'image\/jpeg' ? '.jpg' : '.png';\r\n    } else {\r\n        console.warn('thumbnailDropped \u2014 aucune donn\u00e9e image\/pdf disponible');\r\n        return;\r\n    }\r\n\r\n    var baseName = msg.filename ? msg.filename.replace(\/\\.[^.]+$\/, '') : 'annonce-kit';\r\n    var fileName = baseName + ext;\r\n\r\n    \/\/ dataURL \u2192 Uint8Array \u2192 Blob \u2192 File\r\n    var b64  = dataURL.split(',')[1];\r\n    var bstr = atob(b64);\r\n    var u8   = new Uint8Array(bstr.length);\r\n    for (var i = 0; i < bstr.length; i++) { u8[i] = bstr.charCodeAt(i); }\r\n    var blob    = new Blob([u8], { type: mime });\r\n    var fileObj = new File([blob], fileName, { type: mime });\r\n\r\n    console.log('thumbnailDropped \u2192 handleFileUpload:', fileName, Math.round(fileObj.size \/ 1024) + 'KB');\r\n\r\n    \/\/ \u2705 Cacher le File pour r\u00e9utilisation lors des d\u00e9placements (\u00e9vite CORS)\r\n    window._lastRedactionnelFile = fileObj;\r\n\r\n    \/\/ \u2705 v2.4.10 : Signaler \u00e0 adjustDesktopLayout que c'est un drop depuis la miniature\r\n    \/\/ \u2192 applique max-height sur HTMLUploadfileConteneur pour \u00e9viter le d\u00e9bordement vertical (homepage corps de page)\r\n    window._dropFromMiniature = true;\r\n    \/\/ \u2705 v2.6 : Poser data-from-miniature sur le droppable pour que selectEspaceActif ignore le scale(1.35)\r\n    \/\/ Sans ce flag, selectEspaceActif applique scale(1.35) sur .HTMLUploadfileConteneur \u2192 zoom Communiqu\u00e9\/Interview\r\n    if ($droppable[0]) { $droppable[0].setAttribute('data-from-miniature', 'true'); }\r\n    UploadManager.handleFileUpload(fileObj, $dropZone);\r\n\r\n    \/\/ \u2705 v1.19.1 : Positionner l'espace \u00e0 10px du haut du viewport (tous formats)\r\n    \/\/ \u2705 v2.4.13 : Skip si drop depuis miniature \u2014 selectEspaceActif g\u00e8re d\u00e9j\u00e0 le scroll\r\n    if (!window._dropFromMiniature) {\r\n        setTimeout(function() {\r\n            var el = $droppable.find('.HTMLUploadfileConteneur')[0] || $droppable[0];\r\n            if (el) {\r\n                ScrollHelper.scrollElementTo(el, 10);\r\n            }\r\n        }, 400);\r\n    }\r\n});\r\n\r\nconsole.log('This is a ' + (UIManager.isDesktop() ? 'desktop' : 'non-desktop') + ' device.');\r\n\r\n\/\/ v2.9 : Masquer les zones pub en mode previsualisation achat\r\nwindow.addEventListener('message', function(e) {\r\n    if (!e.data) { return; }\r\n    if (e.data.action !== 'removeElements') { return; }\r\n    if (!e.data.viaPurchasePreview) { return; }\r\n    var _sid = 'via-purchase-preview-css';\r\n    if (!document.getElementById(_sid)) {\r\n        var _s = document.createElement('style');\r\n        _s.id = _sid;\r\n        _s.textContent = [\r\n            \/* Masquer le formulaire upload complet en mode previsualisation *\/\r\n            '.droppable .OrdiMobileConteneurClass { display: none !important; }',\r\n            '.droppable .reserver-dynamic-container { display: none !important; }',\r\n            '.droppable .HTMLUploadfileConteneur { display: none !important; }',\r\n            '.droppable .EspPubFormatMainContainer { display: none !important; }',\r\n            '.droppable .GlisserDeposerConteneur { display: none !important; }',\r\n            '.droppable .OUClass { display: none !important; }',\r\n            '.droppable .ChoisirEspacePublicitaireClass { display: none !important; }',\r\n            '.droppable .ChoisirEspacePublicitaire2ndLigne { display: none !important; }',\r\n            '.droppable .UploadIci { display: none !important; }',\r\n            '.droppable .EnvoiUlterieurContainer { display: none !important; }',\r\n            '.droppable .TexteMobileAnnonce { display: none !important; }',\r\n            '.droppable .AdDroppedTextNotDisplayed { display: none !important; }',\r\n            '.droppable .HideFormButton { display: none !important; }'\r\n        ].join('\\n');\r\n        document.head.appendChild(_s);\r\n    }\r\n    window.parent.postMessage({ type: 'elementsRemoved' }, '*');\r\n});\r\n\r\n\/\/ \u2705 Bug 10 : handler DelAdInIframe\r\nwindow.addEventListener('message', function(e) {\r\n    var msg = e.data;\r\n    if (!msg || msg.action !== 'DelAdInIframe') return;\r\n    var rankId = msg.RankId || '';\r\n    if (!rankId) return;\r\n    console.log('[Bug10] DelAdInIframe re\u00e7u | rank:', rankId);\r\n    var $droppable = jQuery('#' + rankId);\r\n    if (!$droppable.length) return;\r\n    var $cont = $droppable.find('.OrdiMobileConteneurClass').first();\r\n    var el = $cont.length ? $cont[0] : $droppable[0];\r\n    if (typeof RestoreadSpaceTemplateLocal === 'function') RestoreadSpaceTemplateLocal(el);\r\n    else if (typeof window.RestoreadSpaceTemplate === 'function') window.RestoreadSpaceTemplate(el);\r\n    console.log('[Bug10] espace r\u00e9initialis\u00e9 | rank:', rankId);\r\n});\r\n\r\n\/\/ ============================================================================\r\n\/\/ v4.9ds : Num\u00e9rotation 1\/2\/3 + croix \"Effacer\" sur les espaces publicitaires\r\n\/\/   - Num\u00e9ro 1 : \u00e0 hauteur de la zone Formats (.EspPubFormatMainContainer)\r\n\/\/   - Num\u00e9ro 2 : \u00e0 hauteur de la zone Glisser-d\u00e9poser (.UploadFileConteneur)\r\n\/\/   - Num\u00e9ro 3 : \u00e0 hauteur du label R\u00e9server (statique ou dynamique)\r\n\/\/   - Croix : haut-droite du wrapper visuel (.OrdiMobileConteneurClass), title=\"Effacer\"\r\n\/\/     simple X blanc sans fond ni bordure.\r\n\/\/   Couleurs num\u00e9ros : dor\u00e9 (#D0C067) Ele0A, bleu clair (#9FC5F3) Ele1A+\r\n\/\/   Tous les num\u00e9ros sont plac\u00e9s sur le .droppable directement (top calcul\u00e9 depuis\r\n\/\/   les ancres) \u2192 align\u00e9s verticalement sur la m\u00eame colonne left:8px.\r\n\/\/   Quand annonce d\u00e9pos\u00e9e (.via-ad-header pr\u00e9sent) \u2192 tous masqu\u00e9s (.via-ad-loaded).\r\n\/\/ ============================================================================\r\n(function() {\r\n    if (window._viaNumCroixReady) return;\r\n    window._viaNumCroixReady = true;\r\n\r\n    function _injectStyles() {\r\n        \/\/ \u2705 v4.9ds : marquer le body si la page est dans une iframe (cas du site r\u00e9gie qui\r\n        \/\/   charge les pages pays). Permet \u00e0 la r\u00e8gle CSS .via-step-num de masquer les\r\n        \/\/   num\u00e9ros 1\/2\/3 dans ce contexte. Demande user.\r\n        try {\r\n            if (window !== window.top) {\r\n                if (document.body) {\r\n                    document.body.classList.add('via-page-in-iframe');\r\n                } else {\r\n                    \/\/ Body pas encore pr\u00eat \u2192 re-tenter d\u00e8s qu'il l'est\r\n                    document.addEventListener('DOMContentLoaded', function() {\r\n                        if (document.body) { document.body.classList.add('via-page-in-iframe'); }\r\n                    });\r\n                }\r\n            }\r\n        } catch(_eIf) { \/* cross-origin silencieux *\/ }\r\n        if (document.getElementById('via-num-croix-styles')) return;\r\n        var s = document.createElement('style');\r\n        s.id = 'via-num-croix-styles';\r\n        s.textContent =\r\n            \/\/ Num\u00e9ros mobile (par d\u00e9faut) : 20\u00d720, font 12, left:4\r\n            '.via-step-num{position:absolute;left:4px;width:20px;height:20px;border-radius:50%;' +\r\n              'background:#ffffff;display:flex;align-items:center;justify-content:center;' +\r\n              'font-family:Roboto,Arial,sans-serif;font-weight:700;font-size:12px;line-height:1;' +\r\n              'box-shadow:0 1px 3px rgba(0,0,0,0.15);z-index:10;pointer-events:none;' +\r\n              'transform:translateY(-50%);transition:opacity 0.2s}' +\r\n            \/\/ Desktop (\u22651000px) : +40% (20\u219228, font 12\u219217), left:13 (mobile +9)\r\n            '@media (min-width:1000px){' +\r\n              '.via-step-num{width:28px;height:28px;font-size:17px;left:13px}' +\r\n            '}' +\r\n            \/\/ Couleurs avec sp\u00e9cificit\u00e9 forte + !important\r\n            'html body .via-step-num.via-num-ele0a{color:#F1C40F !important}' +\r\n            'html body .via-step-num.via-num-ele1a{color:#9FC5F3 !important}' +\r\n            \/\/ v4.9ds : \u00e9tapes valid\u00e9es (num\u00e9ros pr\u00e9c\u00e9dents l'\u00e9tape courante) \u2192 vert\r\n            \/\/   Logique data-via-format-gate (cf _updateStepGate) :\r\n            \/\/     - \"locked\" = \u00e9tape 1 active   \u2192 aucun num\u00e9ro vert\r\n            \/\/     - \"step2\"  = \u00e9tape 2 active   \u2192 num\u00e9ro 1 vert (\u00e9tape 1 valid\u00e9e)\r\n            \/\/     - (absent) = \u00e9tape 3 active   \u2192 num\u00e9ros 1 et 2 verts (\u00e9tapes 1+2 valid\u00e9es)\r\n            \/\/   \u00c9tape 4 (R\u00e9server coch\u00e9) \u2192 num\u00e9ros 1, 2 et 3 verts (les 3 \u00e9tapes valid\u00e9es).\r\n            \/\/   D\u00e9tection R\u00e9server : pr\u00e9sence de input[name=\"form_fields[ReserverEspacePublicitaire]\"]:checked\r\n            \/\/   dans la droppable. Le s\u00e9lecteur :has() est support\u00e9 par tous les navigateurs\r\n            \/\/   modernes (Safari 15.4+, Chrome 105+, Firefox 121+) \u2014 si un user a un\r\n            \/\/   navigateur trop ancien, le num\u00e9ro 3 reste \u00e0 sa couleur d'origine (jaune\/bleu),\r\n            \/\/   les 1 et 2 restent verts par les r\u00e8gles pr\u00e9c\u00e9dentes \u2014 pas de r\u00e9gression.\r\n            \/\/   On surcharge la couleur du chiffre via !important + sp\u00e9cificit\u00e9 combin\u00e9e\r\n            \/\/   pour battre la r\u00e8gle de couleur jaune\/bleu pos\u00e9e juste au-dessus.\r\n            \/\/   Vert utilis\u00e9 : #2ECC71 (coh\u00e9rent avec les liser\u00e9s de s\u00e9lection vifs).\r\n            \/\/ \u00c9tape 2 active \u2192 num\u00e9ro 1 valid\u00e9\r\n            'html body .droppable[data-via-format-gate=\"step2\"] .via-step-num[data-via-num=\"1\"],' +\r\n            \/\/ \u00c9tape 3 active (annonce d\u00e9pos\u00e9e OU envoi diff\u00e9r\u00e9 coch\u00e9 \u2014 pas d'attribut gate) \u2192 num\u00e9ros 1 et 2 valid\u00e9s\r\n            'html body .droppable:not([data-via-format-gate]) .via-step-num[data-via-num=\"1\"],' +\r\n            'html body .droppable:not([data-via-format-gate]) .via-step-num[data-via-num=\"2\"],' +\r\n            \/\/ \u00c9tape 4 : R\u00e9server coch\u00e9 \u2192 num\u00e9ro 3 valid\u00e9 (en plus de 1 et 2 d\u00e9j\u00e0 verts via r\u00e8gles ci-dessus)\r\n            'html body .droppable:has(input[name=\"form_fields[ReserverEspacePublicitaire]\"]:checked) .via-step-num[data-via-num=\"3\"]{' +\r\n              'color:#2ECC71 !important' +\r\n            '}' +\r\n            \/\/ Croix mobile : top:0 right:-2 (d\u00e9calage 2px vers la droite demand\u00e9), font 14\r\n            \/\/   pointer-events:auto !important : override l'h\u00e9ritage de pointer-events:none\r\n            \/\/     pos\u00e9 par le format-gate \"locked\" sur le parent .HTMLUploadfileConteneur\r\n            \/\/   z-index 999 : passe AU-DESSUS de tous les \u00e9l\u00e9ments locaux du droppable\r\n            \/\/     (widgets Elementor, overlays format, etc.) MAIS reste SOUS la pastille\r\n            \/\/     jaune .popupAchatAnnonce qui vit dans EnteteBackground (stacking context\r\n            \/\/     isol\u00e9 z:1000 \u2192 pastille plafonn\u00e9e ~1000 au niveau body).\r\n            \/\/   v4.9ds Fix 29 v2 : z:100 toujours insuffisant \u2014 un \u00e9l\u00e9ment local du\r\n            \/\/     droppable avec z-index entre 100 et la pastille captait encore le clic.\r\n            \/\/     Remont\u00e9 \u00e0 999 pour passer au-dessus de tout sauf la pastille.\r\n            \/\/   padding 6 8 : zone de clic plus large (mobile)\r\n            '.via-erase-btn{position:absolute;top:0px;right:-2px;' +\r\n              'cursor:pointer;z-index:999;pointer-events:auto !important;' +\r\n              'font-family:Roboto,Arial,sans-serif;font-weight:900;font-size:14px;line-height:1;' +\r\n              'color:#ffffff;user-select:none;padding:6px 8px;' +\r\n              'background:transparent !important;border:none !important;border-radius:0 !important;' +\r\n              'box-shadow:none !important;text-shadow:none !important}' +\r\n            \/\/ Desktop : croix top:3 right:3, font 16 (taille originale)\r\n            '@media (min-width:1000px){' +\r\n              '.via-erase-btn{top:3px;right:3px;font-size:16px}' +\r\n            '}' +\r\n            '.via-erase-btn:hover{opacity:0.8}' +\r\n            \/\/ Quand annonce d\u00e9pos\u00e9e \u2192 masquer imm\u00e9diatement num\u00e9ros + croix\r\n            \/\/   D\u00e9tection par 2 signaux pour activation au plus t\u00f4t :\r\n            \/\/   - data-via-ad-loaded=\"true\" (pos\u00e9 par espace_publicitaire.txt:805 d\u00e8s d\u00e9p\u00f4t)\r\n            \/\/   - .via-ad-loaded (classe pos\u00e9e par mon _updateAdLoadedClass via MutationObserver)\r\n            'html body .droppable[data-via-ad-loaded=\"true\"] .via-step-num,' +\r\n            'html body .droppable[data-via-ad-loaded=\"true\"] .via-erase-btn,' +\r\n            'html body .droppable.via-ad-loaded .via-step-num,' +\r\n            'html body .droppable.via-ad-loaded .via-erase-btn{display:none !important}' +\r\n            \/\/ \u2705 v4.9ds : sur le site r\u00e9gie, les pages pays sont charg\u00e9es dans une iframe.\r\n            \/\/   L'utilisateur a demand\u00e9 que les num\u00e9ros 1\/2\/3 ne soient pas affich\u00e9s dans\r\n            \/\/   ce contexte (coh\u00e9rent : sur la r\u00e9gie, le user manipule un panier \u2014 pas un\r\n            \/\/   process d'\u00e9tapes complet, donc les num\u00e9ros n'ont pas de sens).\r\n            \/\/   D\u00e9tection : window !== window.top (la page est dans une iframe). Marqueur\r\n            \/\/   pos\u00e9 en classe sur <body> pour permettre une r\u00e8gle CSS pure dans ce bloc.\r\n            \/\/   Le marqueur est pos\u00e9 via JS plus bas (apr\u00e8s DOM ready).\r\n            'html body.via-page-in-iframe .via-step-num{display:none !important}' +\r\n            \/\/ Conteneur ancr\u00e9 en relative + overflow visible (num\u00e9ros pouvant d\u00e9border)\r\n            '.via-num-anchor{position:relative !important;overflow:visible !important}' +\r\n            \/\/ \u2500\u2500 OPACIT\u00c9S DES NUM\u00c9ROS calqu\u00e9es sur les zones d\u00e9sactiv\u00e9es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/   Num\u00e9ro 2 (zone GD+Envoi diff\u00e9r\u00e9) : opacit\u00e9 0.6 quand \"locked\" (\u00e9tape 1)\r\n            \/\/   Num\u00e9ro 3 (zone R\u00e9server) : opacit\u00e9 0.6 quand \"locked\" OU \"step2\" (\u00e9tapes 1+2)\r\n            'html body .droppable[data-via-format-gate=\"locked\"] .via-step-num[data-via-num=\"2\"],' +\r\n            'html body .droppable[data-via-format-gate=\"locked\"] .via-step-num[data-via-num=\"3\"],' +\r\n            'html body .droppable[data-via-format-gate=\"step2\"] .via-step-num[data-via-num=\"3\"]{' +\r\n              'opacity:0.6}' +\r\n            \/\/ \u2500\u2500 v4.9ds : OVERRIDES SITE R\u00c9GIE (html.via-regie-iframe) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/   Sur la r\u00e9gie (iframe yearbook-iframe sous la page r\u00e9gie), demande user :\r\n            \/\/   - Mobile : 10px en dessous du bouton .HideFormButton.ReserverBouton\r\n            \/\/              (espace pub plus haut visuellement par le bas)\r\n            \/\/   - Le `left` est pos\u00e9 EN INLINE dans _placeNum (CSS pouvait \u00eatre outrepass\u00e9)\r\n            \/\/   - D\u00e9calage vertical N\u00b02\/N\u00b03 desktop+mobile g\u00e9r\u00e9 dans _numTopOffset\r\n            '@media (max-width:999px){' +\r\n              'html.via-regie-iframe .HideFormButton.ReserverBouton{margin-bottom:10px !important}' +\r\n            '}';\r\n        document.head.appendChild(s);\r\n    }\r\n\r\n    function _makeNum(n, isEle0A) {\r\n        var span = document.createElement('span');\r\n        span.className = 'via-step-num ' + (isEle0A ? 'via-num-ele0a' : 'via-num-ele1a');\r\n        span.textContent = String(n);\r\n        span.setAttribute('data-via-num', String(n));\r\n        return span;\r\n    }\r\n\r\n    function _makeEraseBtn() {\r\n        var btn = document.createElement('span');\r\n        btn.className = 'via-erase-btn';\r\n        btn.setAttribute('title', 'Effacer');\r\n        btn.setAttribute('data-via-erase', '1');\r\n        btn.textContent = '\\u2715'; \/\/ \u2715\r\n        return btn;\r\n    }\r\n\r\n    \/\/ Trouver l'ancre pour un num\u00e9ro donn\u00e9\r\n    function _findAnchor(drop, n) {\r\n        var $drop = jQuery(drop);\r\n        \/\/ Pour n=3, .ReserverContainer est un FR\u00c8RE du droppable (pas un descendant) \u2192\r\n        \/\/   \u00e9largir le scope au parent. Pour n=1 et n=2 on reste dans le droppable.\r\n        var $scope = (n === 3) ? $drop.parent() : $drop;\r\n        \/\/ Helper : essayer plusieurs s\u00e9lecteurs dans l'ordre, retourner le premier visible non vide\r\n        function _firstVisible(selectors) {\r\n            for (var i = 0; i < selectors.length; i++) {\r\n                var $set = $scope.find(selectors[i]);\r\n                var $vis = $set.filter(':visible').filter(function() {\r\n                    var r = this.getBoundingClientRect();\r\n                    return r.width > 0 ? r.height > 0 : false;\r\n                });\r\n                if ($vis.length) {\r\n                    return { el: $vis.first()[0], sel: selectors[i] };\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n        \/\/ Variante permissive : ne filtre PAS sur :visible (utile pour R\u00e9server qui peut\r\n        \/\/   \u00eatre en pointer-events:none ou opacity:0 mais avec dimensions r\u00e9elles)\r\n        function _firstWithDims(selectors) {\r\n            for (var i = 0; i < selectors.length; i++) {\r\n                var $set = $scope.find(selectors[i]);\r\n                for (var j = 0; j < $set.length; j++) {\r\n                    var el = $set[j];\r\n                    var r = el.getBoundingClientRect();\r\n                    if (r.width > 0 ? r.height > 0 : false) {\r\n                        return { el: el, sel: selectors[i] + ' (permissif)' };\r\n                    }\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n        if (n === 1) {\r\n            return _firstVisible([\r\n                '.SelectionFormatTitre',\r\n                '.EspPubFormatMainContainer .EspPubFormatContainer'\r\n            ]);\r\n        }\r\n        if (n === 2) {\r\n            return _firstVisible([\r\n                '.UploadIci',\r\n                '#drop_file_zone_achat .UploadIci',\r\n                '#drag_upload_file_achat',\r\n                '#drop_file_zone_achat',\r\n                '.GlisserDeposerConteneur'\r\n            ]);\r\n        }\r\n        if (n === 3) {\r\n            \/\/ Bouton R\u00e9server Elementor : .ReserverContainer \/ .ReserverBouton sont\r\n            \/\/   FR\u00c8RES du droppable (cf HTML user). Le label visible est dans\r\n            \/\/   .elementor-field-subgroup label (le screen-only n'a pas de dimensions).\r\n            return _firstVisible([\r\n                'label[for=\"form-field-ReserverEspacePublicitaire-0\"]',  \/\/ label visible\r\n                '.ReserverBouton .elementor-field-subgroup label',\r\n                '.ReserverContainer .elementor-field-subgroup label',\r\n                '.ReserverBouton',\r\n                '.ReserverContainer',\r\n                '.reserver-dynamic-label',\r\n                '.reserver-dynamic-container'\r\n            ]) || _firstWithDims([\r\n                '.ReserverBouton',\r\n                '.ReserverContainer',\r\n                '.elementor-field-group-ReserverEspacePublicitaire'\r\n            ]) || (function() {\r\n                \/\/ Dernier recours : recherche par TEXTE dans le scope \u00e9largi\r\n                var found = null;\r\n                $scope.find('label, span, p, div').each(function() {\r\n                    if (found) return;\r\n                    var t = (this.textContent || '').replace(\/\\s+\/g, ' ').trim();\r\n                    if (t.indexOf('R\u00e9server cet espace') === -1) return;\r\n                    var hasChildWithText = jQuery(this).children().toArray().some(function(c) {\r\n                        return ((c.textContent || '').replace(\/\\s+\/g, ' ').trim()).indexOf('R\u00e9server cet espace') !== -1;\r\n                    });\r\n                    if (hasChildWithText) return;\r\n                    var r = this.getBoundingClientRect();\r\n                    if (r.width > 0 ? r.height > 0 : false) {\r\n                        found = this;\r\n                    }\r\n                });\r\n                return found ? { el: found, sel: ':contains(R\u00e9server) (texte)' } : null;\r\n            })();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    \/\/ D\u00e9calage vertical (en px) \u00e0 appliquer \u00e0 un num\u00e9ro selon position + plateforme + Ele0A vs Ele1A+\r\n    \/\/   Base = 40\r\n    \/\/   Desktop  : N\u00b01 = 47 \/ N\u00b02 = 70 \/ N\u00b03 = 56 (Ele0A et Ele1A+ identiques)\r\n    \/\/   Mobile Ele1A+ : N\u00b01 = 24 \/ N\u00b02 = 23 \/ N\u00b03 = 1\r\n    \/\/   Mobile Ele0A  : N\u00b01 = 42 \/ N\u00b02 = 32 \/ N\u00b03 = 9\r\n    \/\/   v4.9ds : Compensation Ele0A mobile quand bouton Cr\u00e9ation est actif (creationActive===true).\r\n    \/\/     Le clic sur Cr\u00e9ation d\u00e9clenche un effet de layout (probablement repaint Elementor \/\r\n    \/\/     stacking flexbox \/ re-application de styles inline) qui d\u00e9cale les 3 num\u00e9ros de\r\n    \/\/     +10px vers le bas. On compense ici pour stabilit\u00e9 visuelle. Au toggle OFF\r\n    \/\/     (re-click Cr\u00e9ation), creationActive repasse \u00e0 false \u2192 compensation 0 \u2192 num\u00e9ros\r\n    \/\/     reviennent \u00e0 leur position d'origine.\r\n    \/\/   v4.9ds : d\u00e9calage vertical SITE R\u00c9GIE (Ele1A+) \u2014 demande user :\r\n    \/\/     - Desktop r\u00e9gie Ele1A+ : N\u00b01 = 0, N\u00b02 -10, N\u00b03 -15 (vers le haut)\r\n    \/\/     - Mobile r\u00e9gie Ele1A+  : N\u00b01 = 0, N\u00b02 +10 (bas), N\u00b03 = 0\r\n    \/\/   Le d\u00e9calage horizontal -3px desktop est pos\u00e9 en inline dans _placeNum.\r\n    function _numTopOffset(n, isEle0A, drop) {\r\n        var isMobile = window.innerWidth < 1000;\r\n        var base = 40;\r\n        \/\/ v4.9ds : compensation _creaComp retir\u00e9e \u2014 les num\u00e9ros restent fixes\r\n        \/\/   peu importe le format s\u00e9lectionn\u00e9. ResizeObserver g\u00e8re d\u00e9sormais les\r\n        \/\/   reflows r\u00e9els (cf _viaNumObserve). Les ancres bougent peu au clic\r\n        \/\/   format en r\u00e9alit\u00e9, le d\u00e9calage per\u00e7u \u00e9tait d\u00fb au calcul fait juste\r\n        \/\/   avant que le re-layout Elementor soit appliqu\u00e9.\r\n        \/\/ v4.9ds : d\u00e9calage r\u00e9gie pour Ele1A+ uniquement\r\n        \/\/   Desktop : N\u00b02 -10, N\u00b03 -15 ; Mobile : N\u00b02 +10 ; reste \u00e0 0\r\n        var _regieCompN1 = 0;\r\n        var _regieCompN2 = 0;\r\n        var _regieCompN3 = 0;\r\n        if (!isEle0A) {\r\n            var _isRegie = document.documentElement.classList.contains('via-regie-iframe');\r\n            if (_isRegie) {\r\n                if (isMobile) {\r\n                    _regieCompN2 = 10;\r\n                } else {\r\n                    _regieCompN2 = -10;\r\n                    _regieCompN3 = -15;\r\n                }\r\n            }\r\n        }\r\n        if (isMobile) {\r\n            if (isEle0A) {\r\n                if (n === 1) return base + 2;    \/\/ 42\r\n                if (n === 2) return base - 18;   \/\/ 22\r\n                return base - 41;                 \/\/ -1\r\n            }\r\n            \/\/ Ele1A+ mobile\r\n            if (n === 1) return base - 16 + _regieCompN1;    \/\/ 24\r\n            if (n === 2) return base - 17 + _regieCompN2;    \/\/ 23 \u2192 33 r\u00e9gie\r\n            return base - 39 + _regieCompN3;                  \/\/  1\r\n        } else {\r\n            \/\/ Desktop (Ele0A et Ele1A+ identiques sauf r\u00e9gie sur Ele1A+)\r\n            if (n === 1) return base + 7 + _regieCompN1;     \/\/ 47\r\n            if (n === 2) return base + 30 + _regieCompN2;    \/\/ 70 \u2192 60 r\u00e9gie\r\n            return base + 16 + _regieCompN3;                  \/\/ 56 \u2192 41 r\u00e9gie\r\n        }\r\n    }\r\n\r\n    \/\/ Placer (ou repositionner) un num\u00e9ro dans le container selon son ancre\r\n    \/\/ v4.9ds : ResizeObserver global pour stabiliser les positions des num\u00e9ros\r\n    \/\/   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    \/\/   Probl\u00e8me de fond : les num\u00e9ros 1\/2\/3 sont positionn\u00e9s en `position:absolute;\r\n    \/\/   top:<calcul\u00e9>` \u00e0 partir de `getBoundingClientRect()` sur leur ancre. Ce calcul\r\n    \/\/   est un snapshot \u2014 si l'ancre bouge ensuite (Elementor async, images charg\u00e9es,\r\n    \/\/   fonts custom, transitions, transforms\u2026), le num\u00e9ro reste fig\u00e9.\r\n    \/\/   \r\n    \/\/   Solution : observer les changements de TAILLE\/POSITION des ancres et du\r\n    \/\/   conteneur via ResizeObserver. Quand un changement est d\u00e9tect\u00e9, on relance\r\n    \/\/   le calcul automatiquement. Plus propre que les passes setTimeout (instantan\u00e9,\r\n    \/\/   pas de polling, d\u00e9clench\u00e9 exactement quand c'est n\u00e9cessaire).\r\n    \/\/   \r\n    \/\/   Support : Safari 13.1+, Chrome 64+, Firefox 69+ (>97% du march\u00e9).\r\n    \/\/   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    var _viaNumResizeObs = null;\r\n    var _viaNumObservedEls = (typeof WeakSet !== 'undefined') ? new WeakSet() : null;\r\n    var _viaNumScheduleAfterRO = null; \/\/ inject\u00e9 par _installWatcher au boot\r\n    function _ensureNumResizeObs() {\r\n        if (_viaNumResizeObs) return _viaNumResizeObs;\r\n        if (typeof ResizeObserver === 'undefined') return null; \/\/ navigateur trop ancien\r\n        _viaNumResizeObs = new ResizeObserver(function() {\r\n            \/\/ Replanification debounced via _processAll (passe par _scheduleProcess\r\n            \/\/   du watcher pour rAF debounce, sinon fallback direct)\r\n            if (typeof _viaNumScheduleAfterRO === 'function') {\r\n                _viaNumScheduleAfterRO();\r\n            } else {\r\n                try { _processAll(); } catch (_e) {}\r\n            }\r\n        });\r\n        return _viaNumResizeObs;\r\n    }\r\n    function _viaNumObserve(el) {\r\n        if (!el) return;\r\n        var ro = _ensureNumResizeObs();\r\n        if (!ro) return;\r\n        if (_viaNumObservedEls) {\r\n            if (_viaNumObservedEls.has(el)) return;\r\n            _viaNumObservedEls.add(el);\r\n        }\r\n        try { ro.observe(el); } catch (_e) {}\r\n    }\r\n\r\n    function _placeNum(container, drop, n, isEle0A) {\r\n        var num = container.querySelector(':scope > .via-step-num[data-via-num=\"' + n + '\"]');\r\n        if (!num) {\r\n            num = _makeNum(n, isEle0A);\r\n            container.appendChild(num);\r\n        }\r\n        \/\/ Forcer la bonne classe couleur \u00e0 chaque passe (au cas o\u00f9 isEle0A aurait chang\u00e9,\r\n        \/\/ ou si une classe parasite a \u00e9t\u00e9 pos\u00e9e \u00e0 la cr\u00e9ation)\r\n        var wantedClass = 'via-step-num ' + (isEle0A ? 'via-num-ele0a' : 'via-num-ele1a');\r\n        if (num.className !== wantedClass) { num.className = wantedClass; }\r\n\r\n        \/\/ v4.9ds : forcer le `left` inline (le CSS via-regie-iframe peut \u00eatre outrepass\u00e9\r\n        \/\/   par des r\u00e8gles plus sp\u00e9cifiques dans certains cas). Hors r\u00e9gie : valeurs par\r\n        \/\/   d\u00e9faut (4 mobile \/ 13 desktop). R\u00e9gie : -3px desktop (10), inchang\u00e9 mobile (4).\r\n        var _isRegieLeft = document.documentElement.classList.contains('via-regie-iframe');\r\n        var _isMobLeft = window.innerWidth < 1000;\r\n        var _leftPx;\r\n        if (_isRegieLeft) {\r\n            _leftPx = _isMobLeft ? 4 : 10;\r\n        } else {\r\n            _leftPx = _isMobLeft ? 4 : 13;\r\n        }\r\n        num.style.setProperty('left', _leftPx + 'px', 'important');\r\n\r\n        var anchorInfo = _findAnchor(drop, n);\r\n        var anchor = anchorInfo ? anchorInfo.el : null;\r\n\r\n        if (anchor) {\r\n            \/\/ v4.9ds : observer l'ancre \u2014 son moindre changement de taille\/position\r\n            \/\/   relancera le calcul automatiquement (pas de polling setTimeout).\r\n            _viaNumObserve(anchor);\r\n            var ancRect = anchor.getBoundingClientRect();\r\n            if (ancRect.height > 0 ? ancRect.width > 0 : false) {\r\n                var contRect = container.getBoundingClientRect();\r\n                num.style.display = '';\r\n                num.style.top = (ancRect.top - contRect.top + ancRect.height \/ 2 + _numTopOffset(n, isEle0A, drop)) + 'px';\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ Pas d'ancre exploitable\r\n        if (n === 3) {\r\n            \/\/ Pour le 3, fallback fixe en bas du container \u2014 mais seulement si le container\r\n            \/\/   a une hauteur exploitable (sinon le num\u00e9ro serait \u00e0 top:30 invisible).\r\n            var contRect3 = container.getBoundingClientRect();\r\n            if (contRect3.height > 50) {\r\n                num.style.display = '';\r\n                num.style.top = (contRect3.height + 30) + 'px';\r\n            } else {\r\n                num.style.display = 'none';\r\n            }\r\n            return;\r\n        }\r\n        \/\/ 1 et 2 : masquer si pas d'ancre\r\n        num.style.display = 'none';\r\n    }\r\n\r\n    \/\/ D\u00e9corer un seul espace .droppable\r\n    function _decorateDroppable(drop) {\r\n        if (!drop) return;\r\n        var rankId = drop.id || '';\r\n        var isEle0A = rankId === 'Ele0A';\r\n        var isEleNA = \/^Ele\\d+A$\/.test(rankId);\r\n        if (!isEle0A ? !isEleNA : false) return;\r\n\r\n        var $drop = jQuery(drop);\r\n        \/\/ Conteneur pour num\u00e9ros + croix = #UploadFileConteneur (wrapper visuel color\u00e9)\r\n        var container = drop.querySelector('#UploadFileConteneur')\r\n            || drop.querySelector('.UploadFileConteneur')\r\n            || drop;\r\n        jQuery(container).addClass('via-num-anchor');\r\n\r\n        \/\/ v4.9ds : observer le container et le droppable \u2014 un changement de taille\r\n        \/\/   sur l'un de ces \u00e9l\u00e9ments d\u00e9cale aussi les num\u00e9ros (via contRect dans\r\n        \/\/   le calcul de top). Idempotent gr\u00e2ce au WeakSet anti-doublons.\r\n        _viaNumObserve(container);\r\n        _viaNumObserve(drop);\r\n\r\n        \/\/ \u2500\u2500 Croix Effacer : top-right du #UploadFileConteneur \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/    Le listener click est pos\u00e9 via d\u00e9l\u00e9gation document dans _installStepHooks\r\n        \/\/    (r\u00e9siste aux clones DOM Ele0A). Ici on cr\u00e9e juste l'\u00e9l\u00e9ment.\r\n        if (!container.querySelector(':scope > .via-erase-btn')) {\r\n            container.appendChild(_makeEraseBtn());\r\n        }\r\n\r\n        \/\/ \u2500\u2500 Num\u00e9ros : tous dans #UploadFileConteneur (align\u00e9s left:8) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        _placeNum(container, drop, 1, isEle0A);\r\n        _placeNum(container, drop, 2, isEle0A);\r\n        _placeNum(container, drop, 3, isEle0A);\r\n    }\r\n\r\n    \/\/ Mise \u00e0 jour de la classe .via-ad-loaded selon pr\u00e9sence de .via-ad-header\r\n    function _updateAdLoadedClass(drop) {\r\n        if (!drop ? true : !drop.classList) return;\r\n        \/\/ D\u00e9tecter \"annonce d\u00e9pos\u00e9e OU en cours de d\u00e9p\u00f4t\" via plusieurs signaux \u2014\r\n        \/\/   pris dans l'ordre du flux d'upload (du plus pr\u00e9coce au plus tardif) :\r\n        \/\/   - .via-loading-inline \/ .via-loading-overlay (loading ins\u00e9r\u00e9 d\u00e8s le d\u00e9but upload)\r\n        \/\/   - <img> dans #drop_file_zone_achat ou .HTMLUploadfileConteneur (preview rendue)\r\n        \/\/   - data-via-ad-loaded=\"true\" (attribut pos\u00e9 par espace_publicitaire.txt:805)\r\n        \/\/   - .via-ad-header (header dynamique, pos\u00e9 apr\u00e8s tout)\r\n        var hasLoading = !!drop.querySelector('.via-loading-inline, .via-loading-overlay');\r\n        var hasImg = !!drop.querySelector('#drop_file_zone_achat img, .HTMLUploadfileConteneur img');\r\n        var hasAttr = drop.getAttribute('data-via-ad-loaded') === 'true';\r\n        var hasHeader = !!drop.querySelector('.via-ad-header');\r\n        if (hasLoading ? true : (hasImg ? true : (hasAttr ? true : hasHeader))) {\r\n            drop.classList.add('via-ad-loaded');\r\n        } else {\r\n            drop.classList.remove('via-ad-loaded');\r\n        }\r\n    }\r\n\r\n    \/\/ v4.9ds : m\u00e9canisme \u00e0 3 \u00e9tapes \u2014 pose data-via-format-gate selon l'\u00e9tat r\u00e9el\r\n    \/\/   - \"locked\"  \u2192 \u00e9tape 1 : aucun format \u2192 2 (GD+Envoi diff) ET 3 (R\u00e9server) gris\u00e9es\r\n    \/\/   - \"step2\"   \u2192 \u00e9tape 2 : format choisi \u2192 seul 3 (R\u00e9server) gris\u00e9\r\n    \/\/   - (rien)    \u2192 \u00e9tape 3 : envoi diff\u00e9r\u00e9 coch\u00e9 OU annonce d\u00e9pos\u00e9e \u2192 tout actif\r\n    function _updateStepGate(drop) {\r\n        if (!drop) return;\r\n        var $drop = jQuery(drop);\r\n\r\n        \/\/ Annonce d\u00e9pos\u00e9e \u2192 \u00e9tape 3 (tout actif, pas d'attribut)\r\n        var hasAdHeader = !!drop.querySelector('.via-ad-header');\r\n        if (hasAdHeader) {\r\n            drop.removeAttribute('data-via-format-gate');\r\n            return;\r\n        }\r\n\r\n        \/\/ Envoi diff\u00e9r\u00e9 coch\u00e9 DANS CE droppable \u2192 \u00e9tape 3\r\n        var envoiDiffereCoche = $drop.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        if (envoiDiffereCoche) {\r\n            drop.removeAttribute('data-via-format-gate');\r\n            return;\r\n        }\r\n\r\n        \/\/ Format choisi (un .EspPubFormatContainer hors Cr\u00e9ation\/PopUp avec fond blanc)\r\n        \/\/ OU une vignette Cr\u00e9ation active (data creationActive=true)\r\n        var formatChoisi = $drop.find('.EspPubFormatContainer')\r\n            .not('.FormatIdCreation').not('.FormatIdPopUp')\r\n            .toArray().some(function(el) {\r\n                return jQuery(el).css('background-color') === 'rgb(255, 255, 255)';\r\n            });\r\n        if (!formatChoisi) {\r\n            \/\/ V\u00e9rifier aussi Cr\u00e9ation active\r\n            var $crea = $drop.find('.FormatIdCreation').first();\r\n            if ($crea.length ? $crea.data('creationActive') === true : false) {\r\n                formatChoisi = true;\r\n            }\r\n        }\r\n        if (formatChoisi) {\r\n            drop.setAttribute('data-via-format-gate', 'step2');\r\n            return;\r\n        }\r\n\r\n        \/\/ Sinon \u2192 \u00e9tape 1\r\n        drop.setAttribute('data-via-format-gate', 'locked');\r\n    }\r\n\r\n    \/\/ D\u00e9corer + maintenir l'\u00e9tat pour tous les espaces pr\u00e9sents\r\n    function _processAll() {\r\n        _injectStyles();\r\n        document.querySelectorAll('.droppable').forEach(function(drop) {\r\n            _decorateDroppable(drop);\r\n            _updateAdLoadedClass(drop);\r\n        });\r\n        \/\/ Format-gate : pr\u00e9f\u00e9rer la fonction officielle (couvre tous les droppables)\r\n        try {\r\n            if (typeof window._viaUpdateFormatGate === 'function') {\r\n                window._viaUpdateFormatGate();\r\n            } else {\r\n                document.querySelectorAll('.droppable').forEach(_updateStepGate);\r\n            }\r\n        } catch (_e) {}\r\n    }\r\n\r\n    \/\/ Installer hooks pour r\u00e9agir aux clics format \/ change envoi diff\u00e9r\u00e9\r\n    \/\/   (transition imm\u00e9diate sans attendre MutationObserver)\r\n    function _refreshGate(drop) {\r\n        \/\/ Pr\u00e9f\u00e9rer la fonction officielle qui parcourt tous les droppables\r\n        try {\r\n            if (typeof window._viaUpdateFormatGate === 'function') {\r\n                window._viaUpdateFormatGate();\r\n                return;\r\n            }\r\n        } catch (_e) {}\r\n        \/\/ Fallback local\r\n        try { _updateStepGate(drop); } catch (_e) {}\r\n    }\r\n    function _installStepHooks() {\r\n        try {\r\n            jQuery(document).on('click', '.droppable .EspPubFormatContainer', function() {\r\n                var drop = jQuery(this).closest('.droppable')[0];\r\n                \/\/ D\u00e9lai pour laisser les autres handlers (s\u00e9lection format) finir\r\n                \/\/   v4.9ds : NE PAS appeler _processAll ici \u2014 le ferait dans un \u00e9tat DOM\r\n                \/\/   instable (post-clic, styles Elementor en cours d'application) \u2192 calcul\r\n                \/\/   des rects produit +10px parasite. Le _processAll est triggered ailleurs\r\n                \/\/   (MutationObserver childList sur popup parent, re-passes initiales).\r\n                setTimeout(function() { _refreshGate(drop); }, 100);\r\n                setTimeout(function() { _refreshGate(drop); }, 300);\r\n            });\r\n            jQuery(document).on('change', '.droppable input[name*=\"EnvoiUlterieur\"]', function() {\r\n                var drop = jQuery(this).closest('.droppable')[0];\r\n                setTimeout(function() { _refreshGate(drop); }, 50);\r\n            });\r\n            \/\/ \u2500\u2500 Croix Effacer : d\u00e9l\u00e9gation document \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/    \u00c9vite la perte de listener si le DOM est reclon\u00e9 (popup Ele0A).\r\n            \/\/ \u2705 v4.9ds Fix 28 : le handler appelait window.FonctionCroixResetAnnonce\r\n            \/\/   ou un trigger sur #CroixResetAnnonce \u2014 mais cet \u00e9l\u00e9ment n'existe plus\r\n            \/\/   dans le DOM (remplac\u00e9 par .via-erase-btn). R\u00e9sultat : le clic \u00e9tait\r\n            \/\/   bien d\u00e9tect\u00e9 ([via-erase-btn] click d\u00e9tect\u00e9 logg\u00e9) mais aucune action\r\n            \/\/   ne se d\u00e9clenchait. Solution : appel direct \u00e0 AdResetHandler.handle(e)\r\n            \/\/   d\u00e9fini ligne 5212 \u2014 c'est exactement la m\u00eame fonction qui \u00e9tait\r\n            \/\/   attach\u00e9e \u00e0 #CroixResetAnnonce ligne 5948 (`(e) => AdResetHandler.handle(e)`).\r\n            \/\/   AdResetHandler.handle utilise e.currentTarget pour trouver le droppable\r\n            \/\/   parent \u2014 \u00e7a fonctionne avec .via-erase-btn comme avec #CroixResetAnnonce.\r\n            jQuery(document).on('click', '.via-erase-btn', function(e) {\r\n                console.log('[via-erase-btn] click d\u00e9tect\u00e9 | rank:', jQuery(this).closest('.droppable').attr('id'));\r\n                e.preventDefault();\r\n                e.stopPropagation();\r\n                if (e.stopImmediatePropagation) { e.stopImmediatePropagation(); }\r\n                \/\/ \u2705 Fix 28 : appel direct du handler AdResetHandler (anciennement bind \u00e0\r\n                \/\/   #CroixResetAnnonce qui n'existe plus). Garde un fallback sur\r\n                \/\/   FonctionCroixResetAnnonce si AdResetHandler n'est pas accessible.\r\n                if (typeof AdResetHandler !== 'undefined' ? typeof AdResetHandler.handle === 'function' : false) {\r\n                    AdResetHandler.handle(e);\r\n                    return;\r\n                }\r\n                \/\/ Fallback historique (au cas o\u00f9 AdResetHandler n'est pas dans ce scope)\r\n                var $droppable = jQuery(this).closest('.droppable');\r\n                var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n                if (typeof window.FonctionCroixResetAnnonce === 'function' ? !!_croixEl : false) {\r\n                    window.FonctionCroixResetAnnonce(_croixEl);\r\n                    return;\r\n                }\r\n                if (_croixEl) { jQuery(_croixEl).trigger('click'); }\r\n                console.warn('[via-erase-btn] ni AdResetHandler ni #CroixResetAnnonce trouv\u00e9s \u2014 reset non effectu\u00e9');\r\n            });\r\n        } catch (_e) {}\r\n    }\r\n\r\n    function _installWatcher() {\r\n        try {\r\n            var _scheduled = false;\r\n            var _scheduleProcess = function() {\r\n                if (_scheduled) return;\r\n                _scheduled = true;\r\n                requestAnimationFrame(function() {\r\n                    _scheduled = false;\r\n                    _processAll();\r\n                });\r\n            };\r\n            \/\/ v4.9ds : exposer _scheduleProcess au ResizeObserver pour debounce rAF\r\n            \/\/   commun. Quand une ancre\/container change de taille, le RO d\u00e9clenche\r\n            \/\/   _scheduleProcess qui replanifie un seul _processAll par frame.\r\n            _viaNumScheduleAfterRO = _scheduleProcess;\r\n            var obs = new MutationObserver(function(muts) {\r\n                \/\/ R\u00e9action INSTANTAN\u00c9E (pas via rAF) sur les changements d'attribut\r\n                \/\/   data-via-ad-loaded \u2014 masquage imm\u00e9diat des num\u00e9ros + croix\r\n                for (var i = 0; i < muts.length; i++) {\r\n                    var m = muts[i];\r\n                    if (m.type === 'attributes' ? m.attributeName === 'data-via-ad-loaded' : false) {\r\n                        if (m.target ? m.target.classList : false) {\r\n                            _updateAdLoadedClass(m.target);\r\n                        }\r\n                    }\r\n                }\r\n                \/\/ Replanification standard pour le reste (num\u00e9ros, croix, gate)\r\n                \/\/   Cela couvre l'ajout\/retrait de droppables, changements DOM\r\n                \/\/   (pop-up Ele0A cr\u00e9\u00e9e\/clon\u00e9e, etc.). Le ResizeObserver couvre\r\n                \/\/   les changements de g\u00e9om\u00e9trie sur les ancres existantes.\r\n                _scheduleProcess();\r\n            });\r\n            obs.observe(document.body, {\r\n                childList: true,\r\n                subtree: true,\r\n                attributes: true,\r\n                attributeFilter: ['data-via-ad-loaded']\r\n            });\r\n            \/\/ v4.9ds : resize viewport reste branch\u00e9 (ResizeObserver couvre les\r\n            \/\/   \u00e9l\u00e9ments individuels mais pas les changements globaux de viewport\r\n            \/\/   qui peuvent affecter les valeurs de _numTopOffset bas\u00e9es sur\r\n            \/\/   window.innerWidth).\r\n            window.addEventListener('resize', _scheduleProcess);\r\n        } catch (_e) {}\r\n    }\r\n\r\n    function _boot() {\r\n        _injectStyles();\r\n        _processAll();\r\n        _installWatcher();\r\n        _installStepHooks();\r\n        \/\/ v4.9ds : passes setTimeout supprim\u00e9es \u2014 le ResizeObserver branch\u00e9 sur\r\n        \/\/   chaque ancre\/container d\u00e9tecte automatiquement les changements de\r\n        \/\/   g\u00e9om\u00e9trie (Elementor async, images charg\u00e9es, fonts custom, etc.)\r\n        \/\/   et relance _processAll au bon moment.\r\n        \/\/   On garde une seule passe \u00e0 1000ms pour le cas o\u00f9 le ResizeObserver\r\n        \/\/   ne serait pas support\u00e9 (tr\u00e8s anciens navigateurs) ou pour couvrir\r\n        \/\/   les ancres qui apparaissent en display:none initial puis sont\r\n        \/\/   ajout\u00e9es au DOM (le RO ne les observe pas tant qu'elles n'existent\r\n        \/\/   pas dans la d\u00e9coration \u2192 un retry \u00e0 1s couvre ce cas).\r\n        setTimeout(_processAll, 1000);\r\n    }\r\n\r\n    if (document.readyState === 'loading') {\r\n        document.addEventListener('DOMContentLoaded', _boot);\r\n    } else {\r\n        _boot();\r\n    }\r\n})();\r\n\r\n} \/\/ end _espPubScriptLoaded guard\r\n<\/script>\r\n\r\n\r\n<style>\r\n\/* ============================================================================\r\n   UFC HAUTEUR \u2014 colle \u00e0 l'image, cap via max-height (source unique de v\u00e9rit\u00e9)\r\n   ============================================================================ *\/\r\n.via-ad-wrapper .HTMLUploadfileConteneur,\r\n.via-ad-wrapper .HTMLUploadfileConteneur .elementor-widget-container,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #HTMLUploadfile,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #PopUpMessageAchattest,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n    height: auto !important;\r\n    min-height: 0 !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur {\r\n    max-height: 170px !important;\r\n    overflow: hidden !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n    max-height: 160px !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur img,\r\n.via-ad-wrapper .HTMLUploadfileConteneur video {\r\n    \/* v4.9ds : width\/height 100% + object-fit:contain \u2192 image remplit la box du dropZone\r\n       (m\u00eames contraintes que desktop) avec letterbox automatique selon ratio.\r\n       Avant : height:auto + width:auto \u2192 image gardait son ratio mais pouvait d\u00e9border\r\n       (overflow:hidden du wrapper \u2192 crop). Le max-height 160px est la limite mobile\r\n       (renderImage pose dropZone.height \u00e0 105 sur mobile mais le CSS doit autoriser\r\n       jusqu'\u00e0 160 pour les autres branches qui ne passent pas par renderImage). *\/\r\n    max-height: 160px !important;\r\n    max-width: 100% !important;\r\n    height: 100% !important;\r\n    width: 100% !important;\r\n    object-fit: contain !important;\r\n}\r\n\r\n\/* v4.9ds : doc-preview Communiqu\u00e9\/Interview occupe toute la hauteur du dropZone\r\n   (sinon vide blanc en bas du liser\u00e9 vert) *\/\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container {\r\n    height: 100% !important;\r\n    max-height: 100% !important;\r\n    align-items: stretch !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-thumbnail,\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-info {\r\n    height: 100% !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container.doc-preview-noimage {\r\n    align-items: flex-start !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container.doc-preview-noimage .doc-preview-info-full {\r\n    height: 100% !important;\r\n    max-height: 100% !important;\r\n}\r\n\r\n\/* v4.9ds : r\u00e8gles Ele0A supprim\u00e9es ici, d\u00e9plac\u00e9es en fin de fichier CSS\r\n   (apr\u00e8s les @media min-width:1200px) pour gagner par ordre de d\u00e9claration. *\/\r\n\r\n\/* v4.9ds : normaliser padding-top sur tous les libell\u00e9s de format.\r\n   Elementor pose en inline padding:1px 0 0 sur Cr\u00e9ation\/Pop-up\/Banni\u00e8re\/Vid\u00e9o\r\n   et padding:3px 0 0 sur Communiqu\u00e9\/Interview\/Parrainage (configur\u00e9 c\u00f4t\u00e9 admin\r\n   Elementor) \u2192 d\u00e9calage visuel entre la 1\u00e8re et 2e ligne de la grille de formats.\r\n   On force 1px partout pour alignement vertical homog\u00e8ne. *\/\r\n.EspPubFormatContainer .EspPubFormat .elementor-widget-container {\r\n    padding-top: 1px !important;\r\n}\r\n\r\n@media (max-width: 999px) {\r\n    .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        max-height: 200px !important;\r\n    }\r\n    \/* v4.9ds : Mobile uniquement \u2014 libell\u00e9s des formats en 800 (au lieu de 600) *\/\r\n    .EspPubFormatContainer .EspPubFormat,\r\n    .EspPubFormatContainer .EspPubFormat .elementor-widget-container {\r\n        font-weight: 800 !important;\r\n    }\r\n}\r\n\r\n\/* Les styles CSS restent inchang\u00e9s *\/\r\n\r\n\/* R\u00e9gie iframe : wrapper adaptatif *\/\r\nhtml.via-regie-iframe .via-ad-wrapper {\r\n    width: 100% !important;\r\n    max-width: 100% !important;\r\n}\r\n\/* Padding-bottom sur droppable d\u00e9pos\u00e9 \u2014 DESKTOP uniquement (\u2265600px viewport iframe).\r\n   Sur mobile, le flow naturel suffit : pas de padding-bottom (\u00e9vite gros vide blanc). *\/\r\n@media (min-width: 600px) {\r\n    html.via-regie-iframe body.home .droppable[data-via-ad-loaded=\"true\"] {\r\n        padding-bottom: 240px !important;\r\n    }\r\n    html.via-regie-iframe body:not(.home) .droppable[data-via-ad-loaded=\"true\"] {\r\n        padding-bottom: 130px !important;\r\n    }\r\n}\r\n\r\n\/* R\u00e9gie iframe MOBILE \u2014 annonce charg\u00e9e : r\u00e9duire les margins \u00e9normes pos\u00e9s par\r\n   Entete.txt sur .ToBeHidden (200px+290px calibr\u00e9s pour espaces vides). *\/\r\n@media (max-width: 599px) {\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) {\r\n        margin-top: -100px !important;\r\n        margin-bottom: 5px !important;\r\n    }\r\n    \/* Neutraliser aussi les margins sur l'OrdiMobileConteneurClass et ses descendants\r\n       qui pourraient ajouter du padding\/margin additionnel. *\/\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) .OrdiMobileConteneurClass {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n        padding-bottom: 0 !important;\r\n    }\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) .UploadFileConteneur {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n        padding-bottom: 0 !important;\r\n    }\r\n    \/* Pages secteurs mobile : Entete.txt pose margin-bottom 180\/176px directement\r\n       sur .droppable (pas sur ToBeHidden). R\u00e9duire pour les droppables charg\u00e9s. *\/\r\n    html.via-regie-iframe body.page .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-bottom: 10px !important;\r\n    }\r\n}\r\n\r\n\/* R\u00e9gie iframe desktop (\u2265600px de viewport iframe) : UFC standard + loading spacing *\/\r\n@media (min-width: 600px) {\r\n    \/* UFC : hauteur fixe 170px comme les espaces vides (standard) au lieu de hug auto *\/\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: 170px !important;\r\n        max-height: 170px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: 160px !important;\r\n        display: flex !important;\r\n        align-items: center !important;\r\n        justify-content: center !important;\r\n    }\r\n    \/* Homepages : loading container 30px plus bas *\/\r\n    html.via-regie-iframe body.home .via-loading-inline {\r\n        margin-top: 30px !important;\r\n    }\r\n}\r\n\r\n\/* ============================================================================\r\n   R\u00e9gie iframe desktop plein \u00e9cran (\u22651200px) : neutralise les marges JS\r\n   pos\u00e9es par styleUploadedAd + _buildAdOverlay (calibr\u00e9es pour mode mobile \u00e9troit)\r\n   ============================================================================ *\/\r\n@media (min-width: 1200px) {\r\n    \/* Annule la remont\u00e9e de -55px sur .droppable qui cause le chevauchement *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-top: 0 !important;\r\n    }\r\n    \/* Annule le +130px sur margin-bottom de l'OMC interne *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] .OrdiMobileConteneurClass {\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Annule le -140px (ou -105px) sur .via-ad-wrapper pos\u00e9 par _buildAdOverlay *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] .via-ad-wrapper {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Agrandit l'annonce : UFC 260px max au lieu de 170px *\/\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 260px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        max-height: 250px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        max-height: 250px !important;\r\n    }\r\n}\r\n\r\n\/* ============================================================================\r\n   Sites pays desktop\/tablette (pas dans iframe r\u00e9gie, \u2265768px) : neutralise\r\n   les marges n\u00e9gatives inline h\u00e9rit\u00e9es du mode mobile (margin-top: -75px sur\r\n   droppable, -140px sur wrapper) qui persistent au resize et causent le\r\n   chevauchement avec le contenu du dessus.\r\n   Seuil 768 (Elementor tablette) pour couvrir les cas o\u00f9 innerWidth < 1000\r\n   alors que outerWidth est en mode desktop.\r\n   ============================================================================ *\/\r\n@media (min-width: 768px) {\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] .via-ad-wrapper {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Pas de margin-top: 0 !important sur .droppable \u2014 on laisse l'inline pos\u00e9 par\r\n       _viaRunInterEspaces (algo inter-espaces) appliquer sa valeur n\u00e9gative pour\r\n       standardiser le gap au-dessus \u00e0 20px. *\/\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Annule le margin-bottom sur OMC (pos\u00e9 par styleUploadedAd) *\/\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] .OrdiMobileConteneurClass {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Agrandit l'annonce : UFC 260px max au lieu de 170px *\/\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 260px !important;\r\n    }\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        \/* v4.9ds : aligner sur la hauteur pos\u00e9e par _applyDzMinH (Ele1A+ = 207) *\/\r\n        max-height: 207px !important;\r\n    }\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        \/* v4.9ds : aligner sur la hauteur r\u00e9elle du dropZone (pos\u00e9e par _applyDzMinH).\r\n           Avant : 250px en dur \u2192 image plus haute que dropZone (~207 sur Ele1A+) \u2192 crop\r\n           par overflow:hidden du wrapper. *\/\r\n        max-height: 207px !important;\r\n    }\r\n}\r\n\r\n\/* v4.9cv : Ele0A desktop \u2014 aligner les contraintes de hauteur sur celles d'Ele1A.\r\n   Ces r\u00e8gles arrivent APR\u00c8S les @media min-width:1200px et 768px pour gagner par\r\n   ordre de d\u00e9claration. Pr\u00e9fixe html# boost la sp\u00e9cificit\u00e9 face \u00e0 html.via-regie-iframe. *\/\r\n\/* v4.9cx : force brute + d\u00e9sactiver overflow:hidden sur le wrapper parent *\/\r\n@media (min-width: 768px) {\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 285px !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        max-height: 215px !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        \/* v4.9ds : aligner sur la hauteur r\u00e9elle du dropZone (pos\u00e9e par _applyDzMinH = 215)\r\n           Avant : 275px en dur \u2192 image d\u00e9passait dropZone de 60px \u2192 crop.\r\n           v4.9ds : retir\u00e9 'height: auto !important' pour que le JS (height:100%) puisse\r\n           s'appliquer \u2192 image remplit la box comme Ele1A+ (comportement uniforme).\r\n           object-fit:contain garantit que l'image reste enti\u00e8re (letterbox auto). *\/\r\n        max-height: 215px !important;\r\n    }\r\n    \/* Laisser le contenu d\u00e9border visuellement si jamais une r\u00e8gle cach\u00e9e pose une height trop petite *\/\r\n    html body .ToBeHidden:has(#Ele0A) {\r\n        overflow: visible !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper {\r\n        overflow: visible !important;\r\n    }\r\n    \/* v4.9ds : Ele0A desktop \u2014 d\u00e9caler .EnvoiUlterieurContainer de 17px vers le bas pour\r\n       espacer visuellement le bloc \"ou Envoi diff\u00e9r\u00e9 \/ Envoyer l'annonce \/ Un lien\u2026\" du\r\n       champ hypertext qui le pr\u00e9c\u00e8de. Le margin pousse aussi tout ce qui suit (R\u00e9server). *\/\r\n    html #Ele0A .EnvoiUlterieurContainer {\r\n        margin-top: 17px !important;\r\n    }\r\n}\r\n\r\n\/* \u2705 via-ad-wrapper responsive : s'adapte quand la fen\u00eatre est r\u00e9duite *\/\r\n.via-ad-wrapper {\r\n    min-width: 0;\r\n    overflow: hidden;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: flex-end;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-wrapper .via-ad-header,\r\n.via-ad-wrapper .HTMLUploadfileConteneur,\r\n.via-ad-wrapper .via-ad-footer {\r\n    width: 100%;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-header {\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n}\r\n.via-ah-title {\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n    white-space: nowrap;\r\n    max-width: 65%;\r\n}\r\n\/* desktop mode mobile : reduire les tailles de char du header *\/\r\n@media only screen and (max-width: 999px) {\r\n    .via-ah-pos  { font-size: 8px !important; }\r\n    .via-ah-title { font-size: 9px !important; max-width: 70% !important; }\r\n    .via-ah-ref  { font-size: 8px !important; }\r\n    .via-ad-wrapper .HTMLUploadfileConteneur { max-height: 200px !important; overflow: hidden !important; height: auto !important; }\r\n    \/* v4.9ds : exclure img\/video du s\u00e9lecteur * pour ne pas \u00e9craser leur r\u00e8gle d\u00e9di\u00e9e\r\n       (qui pose width:100%\/height:100%\/object-fit:contain). Sans cette exclusion,\r\n       la max-height:200 inline ici ne pose pas de pb mais le * peut interf\u00e9rer. *\/\r\n    .via-ad-wrapper .HTMLUploadfileConteneur *:not(img):not(video) { max-height: 200px !important; }\r\n    .reserver-dynamic-label { font-size: 12px !important; }\r\n    .via-ad-wrapper .via-af-move { font-size: 8px !important; }\r\n}\r\n.via-ah-pos, .via-ah-ref {\r\n    white-space: nowrap;\r\n    flex-shrink: 1;\r\n    min-width: 0;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n}\r\n\/* v4.9ct : d\u00e9caler la r\u00e9f\u00e9rence MDG... vers la gauche (uniquement desktop).\r\n   - Espaces pub standards : 8px\r\n   - Ele0A (popup) : 5px\r\n   Le margin-right sur .via-ah-ref pousse la ref \u00e0 gauche tandis que la croix reste ancr\u00e9e \u00e0 droite. *\/\r\n\/* v4.9cu : +8\/+5px suppl\u00e9mentaires \u2192 standards 16px, Ele0A 10px *\/\r\n\/* v4.9cv : +8\/+5px suppl\u00e9mentaires \u2192 standards 24px, Ele0A 15px *\/\r\n@media (min-width: 1000px) {\r\n    .via-ad-header .via-ah-ref { margin-right: 24px; }\r\n    #Ele0A .via-ad-header .via-ah-ref { margin-right: 15px; }\r\n    \/* v4.9ds : Ele0A \u2014 d\u00e9caler via-eu-wrapper de +2px vers le bas (cumul avec margin-top:-5px pos\u00e9 par JS Entete) *\/\r\n    \/*          R\u00e9sultat : -5px + 2px = -3px effectif *\/\r\n    #Ele0A .via-eu-wrapper {\r\n        margin-top: -3px !important;\r\n    }\r\n    \/* v4.9ds : Ele1A+ \u2014 d\u00e9caler EnvoiUlterieur de -2px vers le haut *\/\r\n    .droppable:not(#Ele0A) .EnvoiUlterieurContainer .EnvoiUlterieur {\r\n        margin-top: -2px !important;\r\n    }\r\n    \/* v4.9ds : Ele1A+ \u2014 EnvoiUlterieurTexte de -1px (cumul -2px + 1px bas) *\/\r\n    .droppable:not(#Ele0A) .EnvoiUlterieurContainer .EnvoiUlterieurTexte {\r\n        margin-top: -1px !important;\r\n    }\r\n}\r\n\/* HTMLUploadfileConteneur : hauteur auto dans le wrapper *\/\r\n.via-ad-wrapper .HTMLUploadfileConteneur {\r\n    max-height: 200px;\r\n    overflow: hidden !important;\r\n    margin-top: 0 !important;\r\n    margin-bottom: 0 !important;\r\n}\r\n\/* v4.9cs : NE PAS ajouter de zoom suppl\u00e9mentaire sur Ele0A \u2014 #Ele0A a d\u00e9j\u00e0 zoom:55%\r\n   pos\u00e9 par Entete.txt (ligne 5899), donc un second zoom 55% ici donnerait 30% (trop petit). *\/\r\n.via-ad-wrapper #drop_file_zone_achat {\r\n    margin-top: 0 !important;\r\n    padding: 0 2px;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat img,\r\n.via-ad-wrapper #drop_file_zone_achat video {\r\n    \/* v4.9ds : height:100% (au lieu de auto) + object-fit:contain \u2192 image remplit le\r\n       dropZone (coh\u00e9rent avec width:100%) en restant enti\u00e8re. Letterbox auto si ratio\r\n       diff\u00e9rent. Sans !important pour ne pas surclasser les r\u00e8gles plus sp\u00e9cifiques. *\/\r\n    max-width: 100%;\r\n    width: 100%;\r\n    height: 100%;\r\n    object-fit: contain;\r\n    display: block;\r\n}\r\n\/* via-af-move *\/\r\n.via-ad-wrapper .via-af-move {\r\n    white-space: normal;\r\n    color: #67758c;\r\n    overflow: hidden;\r\n    text-align: center;\r\n}\r\n\r\n\r\n#drop_file_zone_achat {\r\n    background-color: #FFFFFF00;\r\n    color: #225DA9;\r\n    font-weight: 500;\r\n    text-align: center;\r\n    border: #999 0px dashed;\r\n    width: 100%;\r\n    height: 180px;\r\n    font-size: 11.5px;\r\n    display: flex;\r\n    justify-content: center; \r\n    align-items: center; \r\n    flex-wrap: wrap;\r\n}\r\n\r\n#drag_upload_file_achat {\r\n    margin: 0 auto;\r\n    width: 90%;\r\n    height: 60px;\r\n}\r\n\r\n#drag_upload_file_achat p {\r\n    text-align: center;\r\n}\r\n\r\n\/* \u2705 v1.16.0 : Liser\u00e9 noir fin autour du texte \"Ici glisser \u2013 d\u00e9poser\" *\/\r\n.GlisserDeposerConteneur .elementor-widget-container p {\r\n    border: 1px solid #000000;\r\n    border-radius: 4px;\r\n    padding: 4px 10px;\r\n    display: inline-block;\r\n}\r\n\r\n#selectfile_achat {\r\n    display: none;\r\n}\r\n\r\n\/* v4.9ds : Ele0A - EnvoiUlterieurTexte +6px bas (cumul 2px + 2px + 2px) *\/\r\n#Ele0A .EnvoiUlterieurTexte {\r\n    margin-top: 6px !important;\r\n}\r\n\r\n.button-2_achat, .button-2_achat-after-reset {\r\n    background-color: #ffffff00!important;\r\n    border: 1px solid white!important;\r\n    border-radius: 8px;\r\n    color: #225DA9!important;\r\n    cursor: pointer;\r\n    display: inline-block;\r\n    font-size: 11px;\r\n    font-weight: 500;\r\n    list-style: none;\r\n    width: 390px;\r\n    height: 62px;\r\n    top: 0px!important; \r\n    margin-top: 0px!important; \r\n    padding-left: 5px!important;\r\n    padding-right: 5px!important;\r\n    margin-bottom: 70px!important;\r\n    margin: 0;\r\n    text-align: center;\r\n    transition: all 200ms;\r\n    vertical-align: baseline;\r\n    white-space: wrap!important;\r\n}\r\n\r\n@media only screen and (max-width: 1000px) {\r\n    .button-2_achat, .button-2_achat-after-reset {\r\n        width: 95%;\r\n        height: 50px;\r\n    }\r\n}\r\n\r\n.newMessageClass {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #56BE50;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 45px;\r\n    bottom: 0px;\r\n    right: 0px;\r\n    left: 0px;\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n    text-align: center;\r\n}\r\n\r\n.MessageClassFormatnonReconnuTitre {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #FB5E2A;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    margin-top: -300px;\r\n    right: 0px;\r\n    text-align: center;\r\n    line-height: 15px;\r\n}\r\n\r\n.MessageClassFormatnonReconnu {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #ffffff;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    margin-top: -280px;\r\n    right: 0px;\r\n    text-align: center;\r\n    line-height: 15px;\r\n}\r\n\r\n.newButtonClass {\r\n    font-size: 12px; \r\n    font-weight: 500;\r\n    background-color: #ffffff00;\r\n    color: #717171;\r\n    height: 10px;\r\n    width: 100%; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 45px;\r\n    bottom: 0px;\r\n    text-align: center;\r\n    right:  50px;\r\n    line-height: 10px;\r\n    border-radius: 3px;\r\n    border: 0px solid #E8ECF1;\r\n}\r\n\r\n.linkClass {\r\n    width: 15px; \r\n    height: 15px;\r\n    margin-top: -310px;\r\n    margin-bottom: -25px;\r\n    margin-right: -50px;\r\n    position: relative;\r\n    top: 0; \r\n    z-index: 99;\r\n    display: block;\r\n    background-image: url('https:\/\/rdc.via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-blanc.jpg');\r\n    background-size: cover;\r\n}\r\n\r\n.newButtonClassVideo {\r\n    font-size: 12px; \r\n    font-weight: 500;\r\n    background-color: #ffffff00;\r\n    color: #717171;\r\n    height: 10px;\r\n    width: 100%; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 0px;\r\n    bottom: 0px;\r\n    right: 10px;\r\n    text-align: center;\r\n    line-height: 10px;\r\n    border-radius: 3px;\r\n    border: 0px solid #E8ECF1;\r\n}\r\n\r\n.linkClassVideo {\r\n    width: 15px; \r\n    height: 15px;\r\n    margin-top: -438px;\r\n    margin-bottom: -25px;\r\n    margin-right: -15px;\r\n    position: relative;\r\n    top: 0; \r\n    z-index: 99;\r\n    display: block;\r\n    background-image: url('https:\/\/rdc.via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-blanc.jpg');\r\n    background-size: cover;\r\n}\r\n\r\n.FinCampagneMobileClass .elementor-button,\r\n.DebutCampagneMobileClass .elementor-button,\r\n.FormSelectDevisesMobile .elementor-button,\r\n.HideFormButton .elementor-button {\r\n    display: none !important;\r\n}\r\n\r\n#drag_upload_file_achat {\r\n    margin: 0;\r\n    padding: 0;\r\n    position: relative;\r\n}\r\n\r\n.dot-container {\r\n    display: inline-block;\r\n}\r\n\r\n.dot {\r\n    opacity: 0;\r\n    transition: opacity 0.3s ease;\r\n}\r\n\r\n@keyframes firstDot {\r\n    0%, 100% { opacity: 0; }\r\n    10%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n@keyframes secondDot {\r\n    0%, 20%, 100% { opacity: 0; }\r\n    30%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n@keyframes thirdDot {\r\n    0%, 40%, 100% { opacity: 0; }\r\n    50%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n.dot:nth-child(1) {\r\n    animation: firstDot 3s infinite;\r\n}\r\n\r\n.dot:nth-child(2) {\r\n    animation: secondDot 3s infinite;\r\n}\r\n\r\n.dot:nth-child(3) {\r\n    animation: thirdDot 3s infinite;\r\n}\r\n\r\n.droppable .elementor-field-type-checkbox .elementor-field-option {\r\n    display: flex;\r\n    flex-direction: row-reverse;\r\n    align-items: center;\r\n    gap: 8px;\r\n}\r\n\r\n.droppable .elementor-field-type-checkbox .elementor-field-option input[type=\"checkbox\"] {\r\n    width: 12px;\r\n    height: 12px;\r\n    min-width: 12px;\r\n    min-height: 12px;\r\n}\r\n\r\n\/* \u2705 Bouton \"R\u00e9server\" dynamique *\/\r\n.reserver-dynamic-container {\r\n    text-align: center;\r\n    margin-top: -7px;\r\n    margin-bottom: 15px;\r\n    padding: 5px 0;\r\n    transform: scale(1.4);\r\n    transform-origin: center top;\r\n    position: relative;\r\n    z-index: 200;\r\n}\r\n\r\n@media only screen and (min-width: 1001px) {\r\n    .reserver-dynamic-container {\r\n        transform: scale(1);\r\n        margin-top: -200px;\r\n        margin-bottom: 0;\r\n    }\r\n}\r\n\r\n.reserver-dynamic-option {\r\n    display: inline-flex;\r\n    flex-direction: row-reverse;\r\n    align-items: center;\r\n    gap: 8px;\r\n    cursor: pointer;\r\n    color: #213864;\r\n    background-color: #ffffff;\r\n    padding: 2px 4px;\r\n    border-radius: 6px;\r\n}\r\n\r\n.reserver-dynamic-checkbox {\r\n    width: 16px;\r\n    height: 16px;\r\n    min-width: 16px;\r\n    min-height: 16px;\r\n    cursor: pointer;\r\n    pointer-events: auto;\r\n}\r\n\r\n.reserver-dynamic-label {\r\n    cursor: pointer;\r\n    color: #213864;\r\n    font-size: 16px;\r\n    font-weight: 600;\r\n    user-select: none;\r\n}\r\n\r\n@media only screen and (max-width: 1000px) {\r\n    .reserver-dynamic-container {\r\n        transform: scale(0.98);\r\n        margin-bottom: 20px;\r\n        white-space: nowrap;\r\n        z-index: 300;\r\n        pointer-events: auto;\r\n    }\r\n    .reserver-dynamic-option {\r\n        padding-top: 0px;\r\n        padding-bottom: 0px;\r\n    }\r\n}\r\n\r\n\/* \u2705 v2.1.3 : DeplaceAnnonceText \/ DeplaceAnnonce \u2014 desktop uniquement *\/\r\n@media only screen and (min-width: 1001px) {\r\n    .DeplaceAnnonceText {\r\n        margin-top: -5px;\r\n        position: relative;\r\n        z-index: 201;\r\n    }\r\n    .DeplaceAnnonce {\r\n        margin-bottom: -150px;\r\n    }\r\n}\r\n<\/style>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-54e4e530 e-con-full MsgFormatIncorrectConteneur elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile e-flex e-con e-child\" data-id=\"54e4e530\" data-element_type=\"container\" id=\"NotusedAnymore\">\n\t\t\t\t<div class=\"elementor-element elementor-element-531f3cb1 MsgFormatIncorrect elementor-widget__width-inherit elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"531f3cb1\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">The file format is not recognized<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3a352c3f elementor-hidden-desktop TexteMobile TexteMobileAnnonce elementor-widget-mobile__width-initial elementor-widget elementor-widget-text-editor\" data-id=\"3a352c3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tDrag and drop or click here<br>\nto download an ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7abb0aba elementor-hidden-desktop TexteMobileAjoutAnnonce elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"7abb0aba\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Click here to download an ad<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-68e2b2ca UploadIci elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"68e2b2ca\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tHere you can drag and drop or upload an ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-8d50d33 e-con-full e-flex e-con e-child\" data-id=\"8d50d33\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6ae6ef83 elementor-button-align-center elementor-widget__width-initial HideFormButton EspPubLienAnnonce elementor-widget-mobile__width-initial elementor-widget elementor-widget-form\" data-id=\"6ae6ef83\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_URL_annonce\" aria-label=\"Form_URL_ad\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353556\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"6ae6ef83\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-text elementor-field-group elementor-column elementor-field-group-LienAnnonce elementor-col-100 elementor-sm-100 elementor-field-required\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-LienAnnonce\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tHere, if necessary, enter the hyperlink to the advertisement.\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<input size=\"1\" type=\"text\" name=\"form_fields[LienAnnonce]\" id=\"form-field-LienAnnonce\" class=\"elementor-field elementor-size-xs  elementor-field-textual\" placeholder=\"Here, if necessary, enter the hyperlink to the advertisement.\" required=\"required\">\n\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidURLAnnonce\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-66fd4d36 e-con-full EnvoiUlterieurContainer e-flex e-con e-child\" data-id=\"66fd4d36\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4ea8e2b4 elementor-button-align-center elementor-widget__width-auto HideFormButton EnvoiUlterieur elementor-widget elementor-widget-form\" data-id=\"4ea8e2b4\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_Envoi_Ulterieur\" aria-label=\"Submit_Form_Later\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353556\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"4ea8e2b4\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-checkbox elementor-field-group elementor-column elementor-field-group-EnvoiUlterieur elementor-col-100 elementor-sm-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-EnvoiUlterieur\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tor Delayed posting of the advertisement\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t<div class=\"elementor-field-subgroup\"><span class=\"elementor-field-option\"><input type=\"checkbox\" value=\"ou Envoi diff\u00e9r\u00e9 de l&#039;annonce\" id=\"form-field-EnvoiUlterieur-0\" name=\"form_fields[EnvoiUlterieur]\"> <label for=\"form-field-EnvoiUlterieur-0\">or Delayed posting of the advertisement<\/label><\/span><\/div>\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidEnvoiUlterieur\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4a59cf88 EnvoiUlterieurTexte elementor-widget elementor-widget-text-editor\" data-id=\"4a59cf88\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Send the ad up to 8 days after payment<br>\nA link will be sent to you by <span style=\"color: #ffffff;\"><a style=\"color: #ffffff;\" href=\"mailto:contact@via-agency.media\">contact@via-agency.media<\/a><\/span><\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-17d3ad0c e-con-full ReserverContainer e-flex e-con e-child\" data-id=\"17d3ad0c\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-177c9b71 elementor-button-align-center elementor-widget__width-auto HideFormButton ReserverBouton elementor-widget elementor-widget-form\" data-id=\"177c9b71\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_Envoi_Ulterieur\" aria-label=\"Submit_Form_Later\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353556\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"177c9b71\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-checkbox elementor-field-group elementor-column elementor-field-group-ReserverEspacePublicitaire elementor-col-100 elementor-sm-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-ReserverEspacePublicitaire\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tReserve this advertising space\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t<div class=\"elementor-field-subgroup\"><span class=\"elementor-field-option\"><input type=\"checkbox\" value=\"R\u00e9server cet espace publicitaire\" id=\"form-field-ReserverEspacePublicitaire-0\" name=\"form_fields[ReserverEspacePublicitaire]\"> <label for=\"form-field-ReserverEspacePublicitaire-0\">Reserve this advertising space<\/label><\/span><\/div>\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidEnvoiUlterieur\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5bafe1f3 e-con-full AdUploadedTitle DeplaceAnnonce elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop e-flex e-con e-child\" data-id=\"5bafe1f3\" data-element_type=\"container\" id=\"DeplaceAnnonceId\">\n\t\t<div class=\"elementor-element elementor-element-432cc2a1 e-con-full DeplaceAnnonceSubContainer e-flex e-con e-child\" data-id=\"432cc2a1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-446e8565 elementor-hidden-tablet elementor-hidden-mobile EspaceReserve elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"446e8565\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reserved space<br \/>Announcement transmitted<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-60df49d0 elementor-hidden-tablet elementor-hidden-mobile DeplaceAnnonceText elementor-widget elementor-widget-text-editor\" data-id=\"60df49d0\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tIf you want another location, you can move this ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-62ef38f0 e-con-full e-flex e-con e-child\" data-id=\"62ef38f0\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f4f51cb elementor-hidden-tablet elementor-hidden-mobile PositionEspacePublicitaireDeplacer elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"f4f51cb\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Position<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f214306 elementor-hidden-tablet elementor-hidden-mobile RefEspacePublicitaire elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"f214306\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reference<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1c2db781 e-con-full e-flex e-con e-child\" data-id=\"1c2db781\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-5bab83ef elementor-grid-3 elementor-grid-tablet-2 elementor-grid-mobile-3 elementor-widget elementor-widget-loop-grid\" data-id=\"5bab83ef\" data-element_type=\"widget\" data-settings=\"{&quot;template_id&quot;:&quot;114614&quot;,&quot;columns&quot;:3,&quot;columns_tablet&quot;:2,&quot;columns_mobile&quot;:3,&quot;_skin&quot;:&quot;post&quot;,&quot;edit_handle_selector&quot;:&quot;[data-elementor-type=\\&quot;loop-item\\&quot;]&quot;,&quot;row_gap&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;row_gap_tablet&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]},&quot;row_gap_mobile&quot;:{&quot;unit&quot;:&quot;px&quot;,&quot;size&quot;:&quot;&quot;,&quot;sizes&quot;:[]}}\" data-widget_type=\"loop-grid.post\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-loop-container elementor-grid\" role=\"list\">\n\t\t<style id=\"loop-dynamic-114614\">.e-loop-item-37515 .elementor-element.elementor-element-bae3280::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .elementor-background-video-container::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .elementor-background-slideshow::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Baie_de_Nyonie_au_Gabon-e1709385883433.jpg\");}@media(max-width:1000px){.e-loop-item-37515 .elementor-element.elementor-element-bae3280::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .elementor-background-video-container::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .elementor-background-slideshow::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-37515 .elementor-element.elementor-element-bae3280 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Baie_de_Nyonie_au_Gabon-e1709385883433.jpg\");}}<\/style><style id=\"loop-114614\">.elementor-114614 .elementor-element.elementor-element-d2b4376{--display:flex;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114614 .elementor-element.elementor-element-bae3280{--display:flex;--min-height:300px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--justify-content:flex-start;--align-items:flex-start;--overlay-opacity:1;--overlay-mix-blend-mode:darken;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114614 .elementor-element.elementor-element-bae3280::before, .elementor-114614 .elementor-element.elementor-element-bae3280 > .elementor-background-video-container::before, .elementor-114614 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-video-container::before, .elementor-114614 .elementor-element.elementor-element-bae3280 > .elementor-background-slideshow::before, .elementor-114614 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-slideshow::before, .elementor-114614 .elementor-element.elementor-element-bae3280 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-color:#FFFFFF;--background-overlay:'';background-position:center center;background-repeat:no-repeat;background-size:cover;}.elementor-114614 .elementor-element.elementor-element-bae3280::before{filter:brightness( 73% ) contrast( 100% ) saturate( 100% ) blur( 0px ) hue-rotate( 0deg );}.elementor-114614 .elementor-element.elementor-element-bae3280.e-con{--align-self:flex-start;}.elementor-widget-theme-post-title .elementor-heading-title{font-family:var( --e-global-typography-primary-font-family ), Sans-serif;font-weight:var( --e-global-typography-primary-font-weight );color:var( --e-global-color-primary );}.elementor-114614 .elementor-element.elementor-element-75eb4b4 > .elementor-widget-container{margin:60px 0px -5px 20px;padding:0px 020px 0px 0px;}.elementor-114614 .elementor-element.elementor-element-75eb4b4 .elementor-heading-title{font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;color:#FFFFFF;}.elementor-widget-theme-post-excerpt .elementor-widget-container{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-114614 .elementor-element.elementor-element-f28c507 > .elementor-widget-container{margin:0px 0px -10px 20px;padding:0px 20px 0px 0px;}.elementor-114614 .elementor-element.elementor-element-f28c507 .elementor-widget-container{font-family:\"Roboto\", Sans-serif;font-size:12px;font-weight:400;line-height:1.3em;color:#FFFFFF;}.elementor-114614 .elementor-element.elementor-element-15d0e12{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-text-editor{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:var( --e-global-color-primary );}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap, .elementor-widget-text-editor.elementor-drop-cap-view-default .elementor-drop-cap{color:var( --e-global-color-primary );border-color:var( --e-global-color-primary );}.elementor-114614 .elementor-element.elementor-element-b9200eb > .elementor-widget-container{margin:0px 0px 0px 20px;padding:0px 20px 0px 0px;}.elementor-114614 .elementor-element.elementor-element-b9200eb{font-family:\"Roboto\", Sans-serif;font-size:12px;font-weight:600;color:#FFFFFF;}@media(max-width:1001px) and (min-width:1001px){.elementor-114614 .elementor-element.elementor-element-bae3280{--width:100%;}}@media(max-width:1001px){.elementor-114614 .elementor-element.elementor-element-d2b4376{--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114614 .elementor-element.elementor-element-bae3280{--min-height:280px;--border-radius:0px 0px 0px 0px;}.elementor-114614 .elementor-element.elementor-element-75eb4b4 > .elementor-widget-container{margin:30px 0px -5px 20px;}.elementor-114614 .elementor-element.elementor-element-75eb4b4 .elementor-heading-title{font-size:14px;}.elementor-114614 .elementor-element.elementor-element-f28c507 > .elementor-widget-container{margin:0px 0px 0px 20px;}.elementor-114614 .elementor-element.elementor-element-f28c507 .elementor-widget-container{font-size:11px;}.elementor-114614 .elementor-element.elementor-element-15d0e12{--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114614 .elementor-element.elementor-element-b9200eb > .elementor-widget-container{margin:0px 0px 0px 20px;padding:0px 0px 0px 0px;}.elementor-114614 .elementor-element.elementor-element-b9200eb{font-size:11px;}}@media(max-width:1000px){.elementor-114614 .elementor-element.elementor-element-d2b4376{--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114614 .elementor-element.elementor-element-bae3280{--width:100%;--min-height:200px;--border-radius:0px 0px 0px 0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-114614 .elementor-element.elementor-element-75eb4b4 > .elementor-widget-container{margin:40px 0px 0px 10px;padding:0px 10px 0px 0px;}.elementor-114614 .elementor-element.elementor-element-75eb4b4{text-align:left;}.elementor-114614 .elementor-element.elementor-element-75eb4b4 .elementor-heading-title{font-size:15px;}.elementor-114614 .elementor-element.elementor-element-f28c507 > .elementor-widget-container{margin:0px 0px 0px 10px;padding:0px 10px 0px 0px;}.elementor-114614 .elementor-element.elementor-element-15d0e12{--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-114614 .elementor-element.elementor-element-b9200eb > .elementor-widget-container{margin:0px 0px 0px 10px;padding:0px 8px 0px 0px;}.elementor-114614 .elementor-element.elementor-element-b9200eb{font-size:12px;}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"114614\" class=\"elementor elementor-114614 e-loop-item e-loop-item-37515 post-37515 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon category-gabon-tourisme category-page-pays-only category-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-d2b4376 e-flex e-con-boxed e-con e-parent\" data-id=\"d2b4376\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-bae3280 e-con-full e-flex e-con e-child\" data-id=\"bae3280\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-75eb4b4 elementor-widget elementor-widget-theme-post-title elementor-page-title elementor-widget-heading\" data-id=\"75eb4b4\" data-element_type=\"widget\" data-widget_type=\"theme-post-title.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h5 class=\"elementor-heading-title elementor-size-default\"><a href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-un-veritable-eden-a-decouvrir\/\">Gabon \u2013 Tourism <br>\u2013 A true Eden to discover<\/a><\/h5>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f28c507 elementor-hidden-mobile VignetteSecteurExtraitPublication elementor-widget elementor-widget-theme-post-excerpt\" data-id=\"f28c507\" data-element_type=\"widget\" data-widget_type=\"theme-post-excerpt.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<p>Thanks in particular to its numerous beaches, the richness of its national parks and its incredible biodiversity, Gabon is featured in the December 2021 issue of Cond\u00e9 Nast Traveler, the magazine of...\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<a class=\"elementor-element elementor-element-15d0e12 e-con-full e-flex e-con e-child\" data-id=\"15d0e12\" data-element_type=\"container\" href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-un-veritable-eden-a-decouvrir\/\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b9200eb elementor-widget elementor-widget-text-editor\" data-id=\"b9200eb\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Read more \u00bb<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<style id=\"loop-dynamic-114614\">.e-loop-item-40151 .elementor-element.elementor-element-bae3280::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .elementor-background-video-container::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .elementor-background-slideshow::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/856261307_d501ff70c4_c-e1710254450210.jpg\");}@media(max-width:1000px){.e-loop-item-40151 .elementor-element.elementor-element-bae3280::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .elementor-background-video-container::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .elementor-background-slideshow::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-40151 .elementor-element.elementor-element-bae3280 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/856261307_d501ff70c4_c-e1710254450210.jpg\");}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"114614\" class=\"elementor elementor-114614 e-loop-item e-loop-item-40151 post-40151 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon-tourisme category-gabon category-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-d2b4376 e-flex e-con-boxed e-con e-parent\" data-id=\"d2b4376\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-bae3280 e-con-full e-flex e-con e-child\" data-id=\"bae3280\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-75eb4b4 elementor-widget elementor-widget-theme-post-title elementor-page-title elementor-widget-heading\" data-id=\"75eb4b4\" data-element_type=\"widget\" data-widget_type=\"theme-post-title.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h5 class=\"elementor-heading-title elementor-size-default\"><a href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-le-plan-de-relance-du-secteur-touristique\/\">Gabon \u2013 Tourism <br>\u2013 The tourism sector recovery plan<\/a><\/h5>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f28c507 elementor-hidden-mobile VignetteSecteurExtraitPublication elementor-widget elementor-widget-theme-post-excerpt\" data-id=\"f28c507\" data-element_type=\"widget\" data-widget_type=\"theme-post-excerpt.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<p>Endowed with 8.15 billion FCFA, the post-Covid-19 recovery plan is structured around three components: creating an innovative and attractive tourist offer, encouraging local tourism and...\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<a class=\"elementor-element elementor-element-15d0e12 e-con-full e-flex e-con e-child\" data-id=\"15d0e12\" data-element_type=\"container\" href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-le-plan-de-relance-du-secteur-touristique\/\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b9200eb elementor-widget elementor-widget-text-editor\" data-id=\"b9200eb\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Read more \u00bb<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<style id=\"loop-dynamic-114614\">.e-loop-item-39045 .elementor-element.elementor-element-bae3280::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .elementor-background-video-container::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .elementor-background-slideshow::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Pool_area_of_the_Le_Meridien_Libreville.jpg\");}@media(max-width:1000px){.e-loop-item-39045 .elementor-element.elementor-element-bae3280::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .elementor-background-video-container::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-video-container::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .elementor-background-slideshow::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .e-con-inner > .elementor-background-slideshow::before, .e-loop-item-39045 .elementor-element.elementor-element-bae3280 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{background-image:url(\"https:\/\/gabon.yearbook-media.com\/wp-content\/uploads\/2024\/03\/Pool_area_of_the_Le_Meridien_Libreville.jpg\");}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"114614\" class=\"elementor elementor-114614 e-loop-item e-loop-item-39045 post-39045 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon-tourisme category-gabon category-page-pays-only category-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-d2b4376 e-flex e-con-boxed e-con e-parent\" data-id=\"d2b4376\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-bae3280 e-con-full e-flex e-con e-child\" data-id=\"bae3280\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-75eb4b4 elementor-widget elementor-widget-theme-post-title elementor-page-title elementor-widget-heading\" data-id=\"75eb4b4\" data-element_type=\"widget\" data-widget_type=\"theme-post-title.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h5 class=\"elementor-heading-title elementor-size-default\"><a href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-lhotellerie-et-la-restauration\/\">Gabon \u2013 Tourism <br>\u2013 Hotel and catering<\/a><\/h5>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f28c507 elementor-hidden-mobile VignetteSecteurExtraitPublication elementor-widget elementor-widget-theme-post-excerpt\" data-id=\"f28c507\" data-element_type=\"widget\" data-widget_type=\"theme-post-excerpt.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<p>The hotel, restaurant, and tourism sector has been the hardest hit by the Covid-19 pandemic. Tourism remains a sector...\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<a class=\"elementor-element elementor-element-15d0e12 e-con-full e-flex e-con e-child\" data-id=\"15d0e12\" data-element_type=\"container\" href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-lhotellerie-et-la-restauration\/\">\n\t\t\t\t<div class=\"elementor-element elementor-element-b9200eb elementor-widget elementor-widget-text-editor\" data-id=\"b9200eb\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Read more \u00bb<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-292dc469 ToBeHidden e-con-full e-flex e-con e-child\" data-id=\"292dc469\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-744c2c7a e-con-full droppable e-flex e-con e-child\" data-id=\"744c2c7a\" data-element_type=\"container\" id=\"Ele4A\">\n\t\t\t\t<div class=\"elementor-element elementor-element-13d076d6 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"13d076d6\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>\t\t<div data-elementor-type=\"page\" data-elementor-id=\"83347\" class=\"elementor elementor-83347\" data-elementor-post-type=\"elementor_library\">\n\t\t\t\t<div class=\"elementor-element elementor-element-46a1d147 e-con-full OrdiMobileConteneurClass e-flex e-con e-child\" data-id=\"46a1d147\" data-element_type=\"container\" id=\"testIdTest\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8a5fac7 elementor-widget__width-inherit elementor-widget elementor-widget-text-editor\" data-id=\"8a5fac7\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p><style>.elementor-353558 .elementor-element.elementor-element-6da64a60{--display:flex;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--justify-content:space-around;--align-items:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:50px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-6da64a60.e-con{--align-self:center;--flex-grow:0;--flex-shrink:0;}.elementor-353558 .elementor-element.elementor-element-45f807e0{--display:flex;--min-height:0px;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-100px;--margin-bottom:62px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:3;}.elementor-353558 .elementor-element.elementor-element-45f807e0.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-61dcced4{--display:flex;--margin-top:0px;--margin-bottom:-11px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-6c65b106{--display:flex;--margin-top:0px;--margin-bottom:-30px;--margin-left:-25px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-eb74aa8{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-eb74aa8:not(.elementor-motion-effects-element-type-background), .elementor-353558 .elementor-element.elementor-element-eb74aa8 > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-1c762c45{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--flex-wrap:nowrap;--margin-top:-3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-5be6a53a{--display:flex;--align-items:flex-start;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-5be6a53a.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-75917e71{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-image .widget-image-caption{color:var( --e-global-color-text );font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-353558 .elementor-element.elementor-element-6be76773 > .elementor-widget-container{margin:38px 5px -38px -40px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-6be76773.elementor-element{--align-self:flex-start;}.elementor-353558 .elementor-element.elementor-element-6be76773{text-align:right;}.elementor-353558 .elementor-element.elementor-element-6be76773 img{width:17px;}.elementor-353558 .elementor-element.elementor-element-5ad25a69{--display:flex;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-5ad25a69.e-con{--align-self:flex-end;}.elementor-353558 .elementor-element.elementor-element-52dec736 > .elementor-widget-container{margin:45px -18px -55px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-52dec736{z-index:101;text-align:right;}.elementor-353558 .elementor-element.elementor-element-469b1a7c{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-25px;--margin-bottom:90px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-469b1a7c:not(.elementor-motion-effects-element-type-background), .elementor-353558 .elementor-element.elementor-element-469b1a7c > .elementor-motion-effects-container > .elementor-motion-effects-layer{background-color:#9FC5F3;}.elementor-353558 .elementor-element.elementor-element-3f5f9124{--display:flex;--min-height:30px;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:1px;--margin-bottom:-6px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:99;}.elementor-353558 .elementor-element.elementor-element-3f5f9124.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-48a37689{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-start;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-widget-text-editor{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );color:var( --e-global-color-text );}.elementor-widget-text-editor.elementor-drop-cap-view-stacked .elementor-drop-cap{background-color:var( --e-global-color-primary );}.elementor-widget-text-editor.elementor-drop-cap-view-framed .elementor-drop-cap, .elementor-widget-text-editor.elementor-drop-cap-view-default .elementor-drop-cap{color:var( --e-global-color-primary );border-color:var( --e-global-color-primary );}.elementor-353558 .elementor-element.elementor-element-4b68287 > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353558 .elementor-element.elementor-element-4b68287.elementor-element{--align-self:flex-start;}.elementor-353558 .elementor-element.elementor-element-4b68287{text-align:start;font-family:\"Roboto\", Sans-serif;font-size:12px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-79a46db6{--display:flex;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-47d25f98 > .elementor-widget-container{margin:10px 0px -10px 0px;}.elementor-353558 .elementor-element.elementor-element-47d25f98{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:18px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-3aa42503{--display:flex;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:flex-end;--align-items:flex-end;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-12d5dc39 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-12d5dc39.elementor-element{--align-self:flex-start;}.elementor-353558 .elementor-element.elementor-element-12d5dc39{text-align:end;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;line-height:1.2em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-21535e15{--display:flex;--justify-content:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--flex-wrap:wrap;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-21535e15.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-de420c1{--display:flex;--min-height:30px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-2f60f3f > .elementor-widget-container{margin:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-2f60f3f.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-2f60f3f{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:500;line-height:1.1em;color:#FB5E2A;}.elementor-353558 .elementor-element.elementor-element-78e1d9f5{--display:flex;--flex-direction:row;--container-widget-width:initial;--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--gap:010px 9px;--row-gap:010px;--column-gap:9px;--flex-wrap:wrap;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:100;}.elementor-353558 .elementor-element.elementor-element-78e1d9f5.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-cf3602e{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-cf3602e.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-7a1bb33 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-7a1bb33.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-7a1bb33{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-6bf72a5{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-1a57dd9 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-1a57dd9.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-1a57dd9{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:500;line-height:1.1em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-34d8bbba{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-32bd35c6 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-32bd35c6.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-32bd35c6{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-5a9252c3{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-8722042 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-8722042.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-8722042{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-19ecc2b3{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-6071d405 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-6071d405.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-6071d405{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-3617569c{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-4b013e71 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-4b013e71.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-4b013e71{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-7450a6f8{--display:flex;--min-height:20px;--justify-content:center;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:4px 4px 4px 4px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-30970f89 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-30970f89.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-30970f89{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-6aac67a6{--display:flex;--margin-top:-46px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:2;}.elementor-353558 .elementor-element.elementor-element-40d299c{width:100%;max-width:100%;}.elementor-353558 .elementor-element.elementor-element-40d299c.elementor-element{--align-self:flex-end;}.elementor-353558 .elementor-element.elementor-element-1fd15d20{width:100%;max-width:100%;}.elementor-353558 .elementor-element.elementor-element-1fd15d20.elementor-element{--align-self:flex-end;}.elementor-353558 .elementor-element.elementor-element-54e4e530{--display:flex;--justify-content:center;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-531f3cb1{width:100%;max-width:100%;font-family:\"Roboto\", Sans-serif;font-size:10px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353558 .elementor-element.elementor-element-531f3cb1 > .elementor-widget-container{margin:-37px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-531f3cb1.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-3a352c3f > .elementor-widget-container{margin:7px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-3a352c3f.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-3a352c3f{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353558 .elementor-element.elementor-element-7abb0aba > .elementor-widget-container{margin:7px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-7abb0aba.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-7abb0aba{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353558 .elementor-element.elementor-element-68e2b2ca > .elementor-widget-container{margin:-110px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-68e2b2ca.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-68e2b2ca{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;color:#FB5E2A;}.elementor-353558 .elementor-element.elementor-element-8d50d33{--display:flex;--margin-top:-85px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-8d50d33.e-con{--align-self:center;}.elementor-widget-form .elementor-field-group > label, .elementor-widget-form .elementor-field-subgroup label{color:var( --e-global-color-text );}.elementor-widget-form .elementor-field-group > label{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-field-type-html{color:var( --e-global-color-text );font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-field-group .elementor-field{color:var( --e-global-color-text );}.elementor-widget-form .elementor-field-group .elementor-field, .elementor-widget-form .elementor-field-subgroup label{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .elementor-button{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-widget-form .e-form__buttons__wrapper__button-next{background-color:var( --e-global-color-accent );}.elementor-widget-form .elementor-button[type=\"submit\"]{background-color:var( --e-global-color-accent );}.elementor-widget-form .e-form__buttons__wrapper__button-previous{background-color:var( --e-global-color-accent );}.elementor-widget-form .elementor-message{font-family:var( --e-global-typography-text-font-family ), Sans-serif;font-weight:var( --e-global-typography-text-font-weight );}.elementor-widget-form .e-form__indicators__indicator, .elementor-widget-form .e-form__indicators__indicator__label{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-widget-form{--e-form-steps-indicator-inactive-primary-color:var( --e-global-color-text );--e-form-steps-indicator-active-primary-color:var( --e-global-color-accent );--e-form-steps-indicator-completed-primary-color:var( --e-global-color-accent );--e-form-steps-indicator-progress-color:var( --e-global-color-accent );--e-form-steps-indicator-progress-background-color:var( --e-global-color-text );--e-form-steps-indicator-progress-meter-color:var( --e-global-color-text );}.elementor-widget-form .e-form__indicators__indicator__progress__meter{font-family:var( --e-global-typography-accent-font-family ), Sans-serif;font-weight:var( --e-global-typography-accent-font-weight );}.elementor-353558 .elementor-element.elementor-element-6ae6ef83{width:var( --container-widget-width, 69.5% );max-width:69.5%;--container-widget-width:69.5%;--container-widget-flex-grow:0;z-index:120;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group{padding-right:calc( 10px\/2 );padding-left:calc( 10px\/2 );margin-bottom:6px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-form-fields-wrapper{margin-left:calc( -10px\/2 );margin-right:calc( -10px\/2 );margin-bottom:-6px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label, .elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{color:#7B88A3;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-type-html{padding-bottom:0px;color:#7A7A7A;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field{color:#484848;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:01px 01px 01px 01px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:01px 01px 01px 01px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353558 .elementor-element.elementor-element-66fd4d36{--display:flex;--min-height:58px;--gap:0px 0px;--row-gap:0px;--column-gap:0px;border-style:solid;--border-style:solid;border-width:1px 1px 1px 1px;--border-top-width:1px;--border-right-width:1px;--border-bottom-width:1px;--border-left-width:1px;border-color:#FFFFFF;--border-color:#FFFFFF;--border-radius:8px 8px 8px 8px;--margin-top:14px;--margin-bottom:10px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353558 .elementor-element.elementor-element-66fd4d36.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4{width:auto;max-width:auto;z-index:120;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 > .elementor-widget-container{margin:2px 0px 2px 1px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group{padding-right:calc( 0px\/2 );padding-left:calc( 0px\/2 );margin-bottom:0px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-form-fields-wrapper{margin-left:calc( -0px\/2 );margin-right:calc( -0px\/2 );margin-bottom:-0px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label, .elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{color:#FB5E2A;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-type-html{padding-bottom:0px;color:#FFFFFF;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field{color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:14px;font-weight:600;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353558 .elementor-element.elementor-element-4a59cf88 > .elementor-widget-container{margin:-7px 0px -24px 0px;}.elementor-353558 .elementor-element.elementor-element-4a59cf88.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-4a59cf88{text-align:center;font-family:\"Roboto\", Sans-serif;font-size:12.5px;font-weight:600;line-height:1.1em;color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-17d3ad0c{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;border-style:none;--border-style:none;--border-radius:8px 8px 8px 8px;--margin-top:-4px;--margin-bottom:05px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353558 .elementor-element.elementor-element-17d3ad0c.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-177c9b71{width:auto;max-width:auto;z-index:5;--e-form-steps-indicators-spacing:20px;--e-form-steps-indicator-padding:30px;--e-form-steps-indicator-inactive-secondary-color:#ffffff;--e-form-steps-indicator-active-secondary-color:#ffffff;--e-form-steps-divider-width:1px;--e-form-steps-divider-gap:10px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 > .elementor-widget-container{margin:2px 0px 2px 1px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-177c9b71.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group{padding-right:calc( 0px\/2 );padding-left:calc( 0px\/2 );margin-bottom:0px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-form-fields-wrapper{margin-left:calc( -0px\/2 );margin-right:calc( -0px\/2 );margin-bottom:-0px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group.recaptcha_v3-bottomleft, .elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group.recaptcha_v3-bottomright{margin-bottom:0;}body.rtl .elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-labels-inline .elementor-field-group > label{padding-left:0px;}body:not(.rtl) .elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-labels-inline .elementor-field-group > label{padding-right:0px;}body .elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-labels-above .elementor-field-group > label{padding-bottom:0px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label, .elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{color:#000000;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label{font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-type-html{padding-bottom:0px;color:#FFFFFF;font-family:\"Roboto\", Sans-serif;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field{color:#000000;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-family:\"Roboto\", Sans-serif;font-size:16px;font-weight:600;line-height:1.1em;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field:not(.elementor-select-wrapper){background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-select-wrapper select{background-color:#FFFFFF;border-color:#E8ECF1;border-width:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-select-wrapper::before{color:#E8ECF1;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-button{font-family:\"Roboto\", Sans-serif;font-size:1px;font-weight:100;border-radius:6px 6px 6px 6px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-next{background-color:#10274200;color:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]{background-color:#10274200;color:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"] svg *{fill:#FFFFFF00;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-previous{color:#ffffff;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-next:hover{color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]:hover{color:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-button[type=\"submit\"]:hover svg *{fill:#FFFFFF;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .e-form__buttons__wrapper__button-previous:hover{color:#ffffff;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-message{font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:400;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-message.elementor-message-success{color:#00FF2700;}.elementor-353558 .elementor-element.elementor-element-5bafe1f3{--display:flex;--min-height:0px;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--gap:0px 0px;--row-gap:0px;--column-gap:0px;--overlay-opacity:1;--margin-top:230px;--margin-bottom:-220px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:10;}.elementor-353558 .elementor-element.elementor-element-5bafe1f3::before, .elementor-353558 .elementor-element.elementor-element-5bafe1f3 > .elementor-background-video-container::before, .elementor-353558 .elementor-element.elementor-element-5bafe1f3 > .e-con-inner > .elementor-background-video-container::before, .elementor-353558 .elementor-element.elementor-element-5bafe1f3 > .elementor-background-slideshow::before, .elementor-353558 .elementor-element.elementor-element-5bafe1f3 > .e-con-inner > .elementor-background-slideshow::before, .elementor-353558 .elementor-element.elementor-element-5bafe1f3 > .elementor-motion-effects-container > .elementor-motion-effects-layer::before{--background-overlay:'';background-size:cover;}.elementor-353558 .elementor-element.elementor-element-5bafe1f3.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-432cc2a1{--display:flex;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-446e8565 > .elementor-widget-container{margin:0px 3px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-446e8565.elementor-element{--align-self:flex-end;}.elementor-353558 .elementor-element.elementor-element-446e8565{z-index:5;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:13px;font-weight:600;line-height:1.1em;color:#213864;}.elementor-353558 .elementor-element.elementor-element-60df49d0 > .elementor-widget-container{margin:35px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-60df49d0.elementor-element{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-60df49d0{z-index:5;text-align:center;font-family:\"Roboto\", Sans-serif;font-size:15px;font-weight:500;line-height:1.1em;color:#6185C0;}.elementor-353558 .elementor-element.elementor-element-62ef38f0{--display:flex;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:space-between;--align-items:flex-start;--gap:0px 0px;--row-gap:0px;--column-gap:0px;--margin-top:-287px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-f4f51cb > .elementor-widget-container{margin:0px 3px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-f4f51cb.elementor-element{--align-self:flex-end;}.elementor-353558 .elementor-element.elementor-element-f4f51cb{z-index:5;text-align:left;font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:600;line-height:1.1em;color:#213864;}.elementor-353558 .elementor-element.elementor-element-f214306 > .elementor-widget-container{margin:0px 0px 0px 3px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-f214306.elementor-element{--align-self:flex-end;}.elementor-353558 .elementor-element.elementor-element-f214306{z-index:5;text-align:start;font-family:\"Roboto\", Sans-serif;font-size:17px;font-weight:600;line-height:1.1em;color:#213864;}@media(max-width:1001px){.elementor-353558 .elementor-element.elementor-element-6ae6ef83{z-index:11;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-size:10px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4{z-index:11;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-size:10px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-177c9b71{z-index:11;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-size:10px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-button{font-size:12px;}}@media(min-width:1001px){.elementor-353558 .elementor-element.elementor-element-6da64a60{--width:500px;}.elementor-353558 .elementor-element.elementor-element-45f807e0{--width:100%;}.elementor-353558 .elementor-element.elementor-element-6c65b106{--width:500px;}.elementor-353558 .elementor-element.elementor-element-75917e71{--width:10px;}.elementor-353558 .elementor-element.elementor-element-5ad25a69{--width:100%;}.elementor-353558 .elementor-element.elementor-element-469b1a7c{--width:500px;}.elementor-353558 .elementor-element.elementor-element-48a37689{--width:22%;}.elementor-353558 .elementor-element.elementor-element-79a46db6{--width:50%;}.elementor-353558 .elementor-element.elementor-element-3aa42503{--width:22%;}.elementor-353558 .elementor-element.elementor-element-78e1d9f5{--width:100%;}.elementor-353558 .elementor-element.elementor-element-cf3602e{--width:103px;}.elementor-353558 .elementor-element.elementor-element-6bf72a5{--width:103px;}.elementor-353558 .elementor-element.elementor-element-34d8bbba{--width:103px;}.elementor-353558 .elementor-element.elementor-element-5a9252c3{--width:103px;}.elementor-353558 .elementor-element.elementor-element-19ecc2b3{--width:103px;}.elementor-353558 .elementor-element.elementor-element-3617569c{--width:103px;}.elementor-353558 .elementor-element.elementor-element-7450a6f8{--width:103px;}.elementor-353558 .elementor-element.elementor-element-8d50d33{--width:100%;}.elementor-353558 .elementor-element.elementor-element-66fd4d36{--width:390px;}.elementor-353558 .elementor-element.elementor-element-17d3ad0c{--width:100%;}.elementor-353558 .elementor-element.elementor-element-5bafe1f3{--width:150%;}.elementor-353558 .elementor-element.elementor-element-62ef38f0{--width:95%;}}@media(max-width:1000px){.elementor-353558 .elementor-element.elementor-element-6da64a60{--width:390px;--margin-top:105px;--margin-bottom:-75px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-45f807e0{--min-height:150px;--flex-direction:column;--container-widget-width:100%;--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--margin-top:-47px;--margin-bottom:-32px;--margin-left:0px;--margin-right:0px;--z-index:3;}.elementor-353558 .elementor-element.elementor-element-61dcced4{--width:25%;}.elementor-353558 .elementor-element.elementor-element-6c65b106{--width:100%;--flex-direction:row;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:100%;--container-widget-flex-grow:1;--container-widget-align-self:stretch;--flex-wrap-mobile:wrap;--justify-content:center;--align-items:center;--flex-wrap:nowrap;--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-6c65b106.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-eb74aa8{--width:75%;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;}.elementor-353558 .elementor-element.elementor-element-eb74aa8.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-1c762c45{--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-6be76773 > .elementor-widget-container{margin:-3px 40px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-5ad25a69{--width:100%;--justify-content:flex-end;--align-items:flex-end;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-52dec736 > .elementor-widget-container{margin:40px 55px -30px 0px;}.elementor-353558 .elementor-element.elementor-element-52dec736{z-index:100;}.elementor-353558 .elementor-element.elementor-element-469b1a7c{--width:78%;--align-items:center;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--flex-wrap:nowrap;--margin-top:-103px;--margin-bottom:-7px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-469b1a7c.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-3f5f9124{--justify-content:center;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-48a37689{--width:25%;--margin-top:2px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-4b68287 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-4b68287{font-size:8px;}.elementor-353558 .elementor-element.elementor-element-79a46db6{--width:46%;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-47d25f98 > .elementor-widget-container{margin:0px -20px 0px -20px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-47d25f98{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-3aa42503{--width:25%;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-12d5dc39 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-12d5dc39{font-size:7px;}.elementor-353558 .elementor-element.elementor-element-21535e15{--margin-top:-15px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-de420c1{--width:83%;--min-height:15px;--margin-top:0px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-de420c1.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-2f60f3f{width:100%;max-width:100%;font-size:10px;}.elementor-353558 .elementor-element.elementor-element-2f60f3f > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-78e1d9f5{--width:100%;--gap:6px 4px;--row-gap:6px;--column-gap:4px;--flex-wrap:wrap;--margin-top:4px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-cf3602e{--width:61px;--min-height:15px;}.elementor-353558 .elementor-element.elementor-element-7a1bb33{font-size:9px;}.elementor-353558 .elementor-element.elementor-element-6bf72a5{--width:61px;--min-height:15px;}.elementor-353558 .elementor-element.elementor-element-1a57dd9{font-size:9px;}.elementor-353558 .elementor-element.elementor-element-34d8bbba{--width:61px;--min-height:15px;}.elementor-353558 .elementor-element.elementor-element-32bd35c6{font-size:9px;}.elementor-353558 .elementor-element.elementor-element-5a9252c3{--width:61px;--min-height:15px;}.elementor-353558 .elementor-element.elementor-element-8722042{font-size:9px;}.elementor-353558 .elementor-element.elementor-element-19ecc2b3{--width:61px;--min-height:15px;}.elementor-353558 .elementor-element.elementor-element-6071d405{font-size:9px;}.elementor-353558 .elementor-element.elementor-element-3617569c{--width:61px;--min-height:15px;}.elementor-353558 .elementor-element.elementor-element-4b013e71{font-size:9px;}.elementor-353558 .elementor-element.elementor-element-7450a6f8{--width:61px;--min-height:15px;}.elementor-353558 .elementor-element.elementor-element-30970f89{font-size:9px;}.elementor-353558 .elementor-element.elementor-element-6aac67a6{--width:96%;--min-height:250px;--margin-top:-56px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;}.elementor-353558 .elementor-element.elementor-element-40d299c > .elementor-widget-container{margin:5px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-1fd15d20 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-54e4e530{--margin-top:3px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-54e4e530.e-con{--order:-99999 \/* order start hack *\/;}.elementor-353558 .elementor-element.elementor-element-531f3cb1 > .elementor-widget-container{margin:-30px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-3a352c3f{width:var( --container-widget-width, 63% );max-width:63%;--container-widget-width:63%;--container-widget-flex-grow:0;text-align:center;font-size:10px;line-height:1.2em;}.elementor-353558 .elementor-element.elementor-element-3a352c3f > .elementor-widget-container{margin:-181px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-7abb0aba > .elementor-widget-container{margin:-180px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-7abb0aba{text-align:center;font-size:14px;line-height:1.2em;}.elementor-353558 .elementor-element.elementor-element-68e2b2ca > .elementor-widget-container{margin:-110px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-68e2b2ca{font-size:10px;}.elementor-353558 .elementor-element.elementor-element-8d50d33{--width:184px;--margin-top:-156px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-8d50d33.e-con{--align-self:center;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83{width:var( --container-widget-width, 100% );max-width:100%;--container-widget-width:100%;--container-widget-flex-grow:0;z-index:120;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 > .elementor-widget-container{margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group > label{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-field-subgroup label{font-size:8px;}.elementor-353558 .elementor-element.elementor-element-6ae6ef83 .elementor-button{font-size:10.5px;}.elementor-353558 .elementor-element.elementor-element-66fd4d36{--width:85%;--min-height:42px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;--margin-top:10px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:151;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4{z-index:120;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group > label{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-type-html{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-field-subgroup label{font-size:10px;}.elementor-353558 .elementor-element.elementor-element-4ea8e2b4 .elementor-button{font-size:10.5px;}.elementor-353558 .elementor-element.elementor-element-4a59cf88 > .elementor-widget-container{margin:2px 0px -10px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-4a59cf88{font-size:7.5px;}.elementor-353558 .elementor-element.elementor-element-17d3ad0c{--width:85%;--min-height:42px;--flex-direction:column;--container-widget-width:calc( ( 1 - var( --container-widget-flex-grow ) ) * 100% );--container-widget-height:initial;--container-widget-flex-grow:0;--container-widget-align-self:initial;--flex-wrap-mobile:wrap;--align-items:center;--margin-top:2px;--margin-bottom:-20px;--margin-left:0px;--margin-right:0px;--padding-top:0px;--padding-bottom:0px;--padding-left:0px;--padding-right:0px;--z-index:251;}.elementor-353558 .elementor-element.elementor-element-177c9b71 > .elementor-widget-container{margin:2px 0px 0px 0px;padding:0px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-177c9b71{z-index:120;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group > label{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-type-html{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-group .elementor-field, .elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-field-subgroup label{font-size:12px;}.elementor-353558 .elementor-element.elementor-element-177c9b71 .elementor-button{font-size:10.5px;}.elementor-353558 .elementor-element.elementor-element-5bafe1f3{--min-height:150px;--margin-top:-149px;--margin-bottom:0px;--margin-left:0px;--margin-right:0px;}.elementor-353558 .elementor-element.elementor-element-446e8565 > .elementor-widget-container{margin:-133px 0px 0px 0px;}.elementor-353558 .elementor-element.elementor-element-446e8565.elementor-element{--align-self:flex-start;}.elementor-353558 .elementor-element.elementor-element-446e8565{text-align:left;font-size:10px;}.elementor-353558 .elementor-element.elementor-element-f4f51cb > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353558 .elementor-element.elementor-element-f4f51cb.elementor-element{--align-self:flex-start;}.elementor-353558 .elementor-element.elementor-element-f4f51cb{text-align:center;font-size:10px;}.elementor-353558 .elementor-element.elementor-element-f214306 > .elementor-widget-container{margin:0px 0px 0px 10px;}.elementor-353558 .elementor-element.elementor-element-f214306.elementor-element{--align-self:flex-start;}.elementor-353558 .elementor-element.elementor-element-f214306{text-align:center;font-size:10px;}}<\/style>\t\t<div data-elementor-type=\"loop-item\" data-elementor-id=\"353558\" class=\"elementor elementor-353558 elementor-bc-flex-widget e-loop-item e-loop-item-39045 post-39045 post type-post status-publish format-standard has-post-thumbnail hentry category-gabon-tourisme category-gabon category-page-pays-only category-tourisme generate-columns tablet-grid-50 mobile-grid-100 grid-parent grid-20\" data-elementor-post-type=\"elementor_library\" data-custom-edit-handle=\"1\">\n\t\t\t<div class=\"elementor-element elementor-element-6da64a60 e-con-full OrdiMobileConteneurClass e-flex e-con e-child\" data-id=\"6da64a60\" data-element_type=\"container\" id=\"testIdTest\">\n\t\t<div class=\"elementor-element elementor-element-45f807e0 e-con-full UploadFileConteneur AdUploadedTitle elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop e-flex e-con e-child\" data-id=\"45f807e0\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-61dcced4 e-con-full e-flex e-con e-child\" data-id=\"61dcced4\" data-element_type=\"container\">\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6c65b106 e-con-full e-flex e-con e-child\" data-id=\"6c65b106\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-eb74aa8 e-con-full e-flex e-con e-child\" data-id=\"eb74aa8\" data-element_type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-1c762c45 e-con-full e-flex e-con e-child\" data-id=\"1c762c45\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-5be6a53a e-con-full e-flex e-con e-child\" data-id=\"5be6a53a\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-75917e71 e-con-full e-flex e-con e-child\" data-id=\"75917e71\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6be76773 AnnonceDragIcone elementor-widget elementor-widget-image\" data-id=\"6be76773\" data-element_type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/via-agency.media\/wp-content\/uploads\/2025\/05\/arrow-drag-64-bleu.png\" title=\"\" alt=\"\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5ad25a69 e-con-full CroixResetAnnonceContainer e-flex e-con e-child\" data-id=\"5ad25a69\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-52dec736 elementor-widget elementor-widget-image\" data-id=\"52dec736\" data-element_type=\"widget\" id=\"CroixResetAnnonce\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<a href=\"#\">\n\t\t\t\t\t\t\t<img decoding=\"async\" src=\"https:\/\/via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-transparent.png\" title=\"\" alt=\"\" loading=\"lazy\" \/>\t\t\t\t\t\t\t\t<\/a>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-469b1a7c e-con-full UploadFileConteneur e-flex e-con e-child\" data-id=\"469b1a7c\" data-element_type=\"container\" id=\"UploadFileConteneur\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;}\">\n\t\t<div class=\"elementor-element elementor-element-3f5f9124 e-con-full ChoisirEspacePublicitaireDisponibiliteConteneur e-flex e-con e-child\" data-id=\"3f5f9124\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-48a37689 e-con-full e-flex e-con e-child\" data-id=\"48a37689\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4b68287 PositionEspacePublicitaire elementor-widget elementor-widget-text-editor\" data-id=\"4b68287\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPosition\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-79a46db6 e-con-full e-flex e-con e-child\" data-id=\"79a46db6\" data-element_type=\"container\" id=\"ChoixEspacePublicitaireTitre\">\n\t\t\t\t<div class=\"elementor-element elementor-element-47d25f98 elementor-widget elementor-widget-text-editor\" data-id=\"47d25f98\" data-element_type=\"widget\" id=\"ChoixEspacePublicitaireTexte\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Fixed advertising space<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3aa42503 e-con-full e-flex e-con e-child\" data-id=\"3aa42503\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-12d5dc39 ReferenceEspacePublicitaire elementor-widget elementor-widget-text-editor\" data-id=\"12d5dc39\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reference<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-21535e15 e-con-full EspPubFormatMainContainer e-flex e-con e-child\" data-id=\"21535e15\" data-element_type=\"container\">\n\t\t<div class=\"elementor-element elementor-element-de420c1 e-con-full e-flex e-con e-child\" data-id=\"de420c1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-2f60f3f elementor-widget-mobile__width-inherit SelectionFormatTitre elementor-hidden-tablet elementor-widget elementor-widget-text-editor\" data-id=\"2f60f3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPlease select or create an ad format\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-78e1d9f5 e-con-full EspPubFormatListe e-flex e-con e-child\" data-id=\"78e1d9f5\" data-element_type=\"container\">\n\t\t<a class=\"elementor-element elementor-element-cf3602e e-con-full EspPubFormatContainer FormatIdCreation e-flex e-con e-child\" data-id=\"cf3602e\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7a1bb33 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"7a1bb33\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tCreation\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-6bf72a5 e-con-full EspPubFormatContainer FormatIdPopUp e-flex e-con e-child\" data-id=\"6bf72a5\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-1a57dd9 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"1a57dd9\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPop-up\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-34d8bbba e-con-full EspPubFormatContainer FormatIdBanniere e-flex e-con e-child\" data-id=\"34d8bbba\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-32bd35c6 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"32bd35c6\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tBanner\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-5a9252c3 e-con-full EspPubFormatContainer FormatIdVideo e-flex e-con e-child\" data-id=\"5a9252c3\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8722042 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"8722042\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tVideo\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-19ecc2b3 e-con-full EspPubFormatContainer FormatIdCommunique e-flex e-con e-child\" data-id=\"19ecc2b3\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6071d405 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"6071d405\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tPress release\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-3617569c e-con-full EspPubFormatContainer FormatIdInterview e-flex e-con e-child\" data-id=\"3617569c\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4b013e71 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"4b013e71\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tInterview\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t<a class=\"elementor-element elementor-element-7450a6f8 e-con-full EspPubFormatContainer FormatIdParrainage e-flex e-con e-child\" data-id=\"7450a6f8\" data-element_type=\"container\" href=\"#\">\n\t\t\t\t<div class=\"elementor-element elementor-element-30970f89 EspPubFormat elementor-widget elementor-widget-text-editor\" data-id=\"30970f89\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tSponsorship\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/a>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-6aac67a6 e-con-full HTMLUploadfileConteneur e-flex e-con e-child\" data-id=\"6aac67a6\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-40d299c elementor-widget__width-inherit HTMLUploadfileClass elementor-widget elementor-widget-html\" data-id=\"40d299c\" data-element_type=\"widget\" id=\"HTMLUploadfile\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <meta name=\"google\" content=\"notranslate\">\r\n    \r\n    <script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" src=\"https:\/\/code.jquery.com\/jquery-3.6.0.min.js\"><\/script>\r\n    <script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" src=\"https:\/\/code.jquery.com\/ui\/1.12.1\/jquery-ui.min.js\"><\/script>\r\n\r\n<script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\">\r\n\/\/ Lazy loading des biblioth\u00e8ques - charg\u00e9es uniquement au besoin\r\nwindow.VIALibraries = {\r\n    mammothLoaded: false,\r\n    pdfLoaded: false,\r\n    \r\n    loadMammoth: function(callback) {\r\n        if (this.mammothLoaded) { callback(); return; }\r\n        var script = document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/mammoth\/1.4.0\/mammoth.browser.min.js';\r\n        script.onload = function() { \r\n            window.VIALibraries.mammothLoaded = true; \r\n            callback(); \r\n        };\r\n        document.head.appendChild(script);\r\n    },\r\n    \r\n    loadPdfJs: function(callback) {\r\n        if (this.pdfLoaded) { callback(); return; }\r\n        var script = document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.min.js';\r\n        script.onload = function() {\r\n            window.VIALibraries.pdfLoaded = true;\r\n            pdfjsLib.GlobalWorkerOptions.workerSrc = '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n            callback();\r\n        };\r\n        document.head.appendChild(script);\r\n    }\r\n};\r\n<\/script>\r\n\r\n<\/head>\r\n<body>\r\n    <div id=\"PopUpMessageAchattest\" class=\"draggable\" draggable=\"true\" style=\"justify-content: center; align-items: flex-start;\">\r\n        <div id=\"drop_file_zone_achat\" class=\"drop_file_zone_achat_class\" ondrop=\"uploadFile_achat(event)\" ondragover=\"return false\">\r\n        <div id=\"drag_upload_file_achat\">\r\n            <p><input class=\"button-2_achat\" type=\"button\" value=\"&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\" onclick=\"fileExplorer_achat(event);\" \/><\/p>\r\n                <input type=\"file\" id=\"selectfile_achat\" \/>\r\n        <\/div>\r\n        <\/div>\r\n        <div class=\"img-content\"><\/div>\r\n    <\/div>\r\n<\/body>\r\n<\/html>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-1fd15d20 elementor-widget__width-inherit HTMLUploadfileClass elementor-widget elementor-widget-html\" data-id=\"1fd15d20\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<script data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\" data-jetpack-boost=\"ignore\">\r\nif (!window._espPubScriptLoaded) {\r\nwindow._espPubScriptLoaded = true;\r\n\/**\r\n * Configuration centralis\u00e9e\r\n *\/\r\nconst CONFIG = {\r\n    iframeId: 'yearbook-iframe',\r\n    ajaxUrl: 'https:\/\/via-agency.media\/wp-admin\/admin-ajax.php',\r\n    apercuUrl: 'https:\/\/rdc.yearbook-media.com\/apercu',\r\n    \r\n    allowedExtensions: {\r\n        video: ['mp4', 'mov', 'wmv', 'avi', 'flv', 'f4v', 'swf', 'mkv', 'webm', 'mpeg'],\r\n        image: ['jpg', 'jpeg', 'png', 'gif', 'tif', 'svg', 'webp'],\r\n        document: ['doc', 'docx', 'ppt', 'pptx', 'pdf']\r\n    },\r\n    \r\n    mimeTypes: {\r\n        docx: 'application\/vnd.openxmlformats-officedocument.wordprocessingml.document',\r\n        pdf: 'application\/pdf'\r\n    },\r\n    \r\n    dragScroll: {\r\n        threshold: 150,\r\n        maxSpeed: 65,\r\n        throttleDelay: 50\r\n    },\r\n    \r\n    breakpoints: {\r\n        mobile: 1000\r\n    },\r\n    \r\n    keywords: [\r\n        { normal: \"parrainage\", display: \"Parrainage\" },\r\n        { normal: \"communique\", display: \"Communiqu\u00e9\" },\r\n        { normal: \"interview\", display: \"Interview\" }\r\n    ]\r\n};\r\n\r\n\/**\r\n * \u2705 v1.19.1 : Module de positionnement dans l'iframe\r\n * \r\n * PROBL\u00c8ME : L'iframe ne scrolle pas en desktop \u2014 c'est le parent (#PageWebTitrePage) qui scrolle.\r\n * L'iframe est scal\u00e9e \u00e0 0.85 \u2192 les coordonn\u00e9es iframe \u2260 coordonn\u00e9es parent.\r\n * position:fixed dans l'iframe = relatif au CONTENU TOTAL, pas au viewport visible.\r\n * \r\n * SOLUTION : Le parent calcule visibleTopIframe (la position iframe du haut du viewport)\r\n * et l'envoie dans le message 'PageWebTitrePage-scroll'. L'iframe n'a qu'\u00e0 l'utiliser.\r\n * \r\n * Pour scroller, l'iframe envoie 'scrollToIframeY' au parent qui fait la conversion.\r\n *\/\r\nconst ScrollHelper = {\r\n    _visibleTopIframe: 0,\r\n    _iframeScale: 0.85,\r\n\r\n    init() {\r\n        \/\/ \u00c9couter les donn\u00e9es de scroll envoy\u00e9es par le parent\r\n        window.addEventListener('message', (event) => {\r\n            var msg = event.data;\r\n            if (msg ? msg.action === 'PageWebTitrePage-scroll' : false) {\r\n                if (typeof msg.visibleTopIframe === 'number') {\r\n                    this._visibleTopIframe = msg.visibleTopIframe;\r\n                }\r\n                if (typeof msg.iframeScale === 'number') {\r\n                    this._iframeScale = msg.iframeScale;\r\n                }\r\n            }\r\n        });\r\n        console.log('\u2705 ScrollHelper initialis\u00e9');\r\n    },\r\n\r\n    \/**\r\n     * Position Y dans le document iframe correspondant au haut du viewport visible\r\n     *\/\r\n    getVisibleTop() {\r\n        if (window === window.top) {\r\n            return window.scrollY || 0;\r\n        }\r\n        return this._visibleTopIframe;\r\n    },\r\n\r\n    \/**\r\n     * Demande au parent de scroller pour positionner `element` \u00e0 `offsetFromTop` px du viewport\r\n     *\/\r\n    scrollElementTo(element, offsetFromTop) {\r\n        if (!element) return;\r\n        \/\/ \u2705 v2.7.3 : Pas de scroll auto sur les sites pays (window.top)\r\n        \/\/ Le scroll apr\u00e8s d\u00e9p\u00f4t d'annonce est ind\u00e9sirable sur desktop et mobile\r\n        if (window === window.top) return;\r\n\r\n        \/\/ getBoundingClientRect().top dans une iframe sans scroll = position absolue Y\r\n        var elementAbsY = element.getBoundingClientRect().top;\r\n\r\n        if (window === window.top) {\r\n            window.scrollTo({ top: elementAbsY - offsetFromTop + (window.scrollY || 0), behavior: 'smooth' });\r\n            return;\r\n        }\r\n\r\n        \/\/ Envoyer au parent \u2014 le parent fait la conversion scale\r\n        window.parent.postMessage({\r\n            type: 'scrollToIframeY',\r\n            iframeId: CONFIG.iframeId,\r\n            iframeY: elementAbsY,\r\n            offsetFromTop: offsetFromTop\r\n        }, '*');\r\n        console.log('\ud83d\udcdc scrollToIframeY:', { iframeY: Math.round(elementAbsY), offsetFromTop });\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'\u00e9tat (SessionStorage)\r\n *\/\r\nconst StateManager = {\r\n    init() {\r\n        if (sessionStorage.getItem(\"AchatEspaceCall\") === 'No') {\r\n            sessionStorage.clear();\r\n        }\r\n        \r\n        this.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        this.set('dragstart_Rank_Emplacement_Page_Web', 'No');\r\n        this.set('dragstart_Commande_Emplacement_Page_Web', 'No');\r\n        \r\n        \/\/ \u2705 v2.1.1 : Popup \u2192 ne jamais pr\u00e9remplir le format\r\n        if (this.get('PopUpChoice') === 'Yes') {\r\n            this.set('Formatchoisi', 'No');\r\n            this.set('FormatSelect', '');\r\n            this.set('Commande_Format_Transmis', '');\r\n        }\r\n    },\r\n    \r\n    get(key) {\r\n        return sessionStorage.getItem(key);\r\n    },\r\n    \r\n    set(key, value) {\r\n        sessionStorage.setItem(key, value);\r\n    },\r\n    \r\n    getMultiple(keys) {\r\n        return keys.reduce((acc, key) => {\r\n            acc[key] = this.get(key);\r\n            return acc;\r\n        }, {});\r\n    },\r\n    \r\n    setMultiple(obj) {\r\n        Object.entries(obj).forEach(([key, value]) => {\r\n            this.set(key, value);\r\n        });\r\n    },\r\n    \r\n    buildEmplacementReference(rank) {\r\n        const codeSite = this.get('codeSite');\r\n        const codePage = this.get('codePage');\r\n        return `${codeSite}${codePage}L${rank.substring(3)}`;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des fichiers\r\n *\/\r\nconst FileManager = {\r\n    getExtension(filename) {\r\n        console.log('filename:', filename);\r\n        const parts = filename.split('.');\r\n        return parts.length > 1 ? parts.pop().toLowerCase() : '';\r\n    },\r\n    \r\n    isAllowedExtension(extension) {\r\n        return Object.values(CONFIG.allowedExtensions)\r\n            .flat()\r\n            .includes(extension);\r\n    },\r\n    \r\n    getFileType(extension) {\r\n        for (const [type, extensions] of Object.entries(CONFIG.allowedExtensions)) {\r\n            if (extensions.includes(extension)) {\r\n                return type;\r\n            }\r\n        }\r\n        return null;\r\n    },\r\n    \r\n    async urlToFile(url, filename) {\r\n        console.log(\"Fetching from URL:\", url);\r\n        \r\n        const mimeType = filename.includes('.docx') \r\n            ? CONFIG.mimeTypes.docx \r\n            : filename.includes('.pdf') \r\n            ? CONFIG.mimeTypes.pdf \r\n            : null;\r\n        \r\n        try {\r\n            const response = await fetch(url);\r\n            \r\n            if (!response.ok) {\r\n                throw new Error(`Network response was not ok: ${response.status} ${response.statusText}`);\r\n            }\r\n            \r\n            const blob = await response.blob();\r\n            return new File([blob], filename, { type: mimeType });\r\n        } catch (error) {\r\n            console.error(\"Error in urlToFile:\", error);\r\n            alert(\"Erreur lors du chargement du fichier. Veuillez r\u00e9essayer.\");\r\n            throw error;\r\n        }\r\n    },\r\n    \r\n    createObjectUrl(file) {\r\n        \/\/ R\u00e9voquer l'ancien URL si existant\r\n        const oldUrl = StateManager.get('objectUrl');\r\n        if (oldUrl ? oldUrl.startsWith('blob:') : false) {\r\n            URL.revokeObjectURL(oldUrl);\r\n            console.log('\ud83d\uddd1\ufe0f Old Object URL revoked');\r\n        }\r\n        \r\n        const url = URL.createObjectURL(file);\r\n        StateManager.set('objectUrl', url);\r\n        return url;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion du Drag & Drop\r\n *\/\r\nconst DragDropManager = {\r\n    state: {\r\n        isFileBeingDragged: false,\r\n        draggedFile: null,\r\n        dragInProgress: false,\r\n        throttleTimeout: null\r\n    },\r\n    \r\n    init() {\r\n        this.attachEventListeners();\r\n    },\r\n    \r\n    attachEventListeners() {\r\n        window.addEventListener('dragstart', (e) => this.handleDragStart(e), true);\r\n        jQuery(document).on('dragover', (e) => this.handleDragOver(e));\r\n        jQuery(document).on('dragleave drop', (e) => this.handleDragEnd(e));\r\n        jQuery(\"#drop_file_zone_achat\").on(\"dragover\", (e) => this.handleDropZoneDragOver(e));\r\n        jQuery(\".draggable\").on(\"dragend\", (e) => this.handleDraggableEnd(e));\r\n    },\r\n    \r\n    handleDragStart(e) {\r\n        console.log('Window-level dragstart', e);\r\n        this.state.dragInProgress = true;\r\n    \r\n        const $target = $(e.target);\r\n        const droppableParent = $target.closest('.droppable');\r\n        const parentId = droppableParent.length ? droppableParent.attr('id') : null;\r\n        \r\n        StateManager.set('dragstart_Rank_Emplacement_Page_Web', parentId);\r\n        console.log('dragstart_Rank_Emplacement_Page_Web', parentId);\r\n        \r\n        \/\/ \u2705 CALCULER IMM\u00c9DIATEMENT dragstart_Commande_Emplacement_Page_Web\r\n        if (parentId) {\r\n            const dragstartRef = StateManager.buildEmplacementReference(parentId);\r\n            StateManager.set('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n            console.log('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n            \/\/ \u2705 v2.4.5 : Capturer l'\u00e9tat checkbox R\u00e9server au moment du dragstart\r\n            \/\/ Si l'utilisateur a explicitement d\u00e9coch\u00e9, forcer \u00e0 No m\u00eame si DOM est coch\u00e9\r\n            var $_dragCb = $('#' + parentId).find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n            var _dragCbChecked = $_dragCb.prop('checked') === true;\r\n            var _expliciteDecocheDrag = StateManager.get('_reserverDecoche_' + parentId) === 'Yes';\r\n            var _reserverDrag = _dragCbChecked ? !_expliciteDecocheDrag : false;\r\n            StateManager.set('dragstart_ReserverChecked', _reserverDrag ? 'Yes' : 'No');\r\n            \/\/ R\u00e9initialiser le flag apr\u00e8s lecture\r\n            StateManager.set('_reserverDecoche_' + parentId, 'No');\r\n            console.log('[dragstart] ReserverChecked dom:', _dragCbChecked, '| d\u00e9coch\u00e9 explicitement:', _expliciteDecocheDrag, '| final:', _reserverDrag);\r\n        }\r\n    \r\n        const dataTransfer = e.dataTransfer || e.originalEvent?.dataTransfer;\r\n        const files = dataTransfer?.files || null;\r\n        \r\n        const imgSrc = $target.closest('.draggable').find('img').attr('src');\r\n        const videoSrc = $target.closest('.draggable').find('video').attr('src') || \r\n                        $target.closest('.draggable').find('video source').attr('src');\r\n        const mediaSrc = imgSrc || videoSrc;\r\n    \r\n        console.log('dragstart target:', e.target);\r\n        console.log('files:', files);\r\n        console.log('mediaSrc:', mediaSrc);\r\n    \r\n        if (!mediaSrc ? (!files || files.length === 0) : false) {\r\n            console.log('No media source or files found, preventing drag');\r\n            e.preventDefault();\r\n            this.state.dragInProgress = false;\r\n            return false;\r\n        }\r\n    \r\n        \/\/ \u2705 v4.9ds Pb 5 : poser une image fant\u00f4me (ghost) sur l'\u00e9v\u00e9nement drag pour que\r\n        \/\/   le navigateur affiche l'image qu'on d\u00e9place au curseur, au lieu de la croix\r\n        \/\/   d'interdiction par d\u00e9faut. Sans setDragImage, certains navigateurs (Chrome,\r\n        \/\/   Firefox) affichent une ic\u00f4ne \"non autoris\u00e9\" si l'\u00e9l\u00e9ment source est complexe\r\n        \/\/   ou si la zone de drop n'est pas imm\u00e9diatement reconnue.\r\n        \/\/   On configure aussi effectAllowed='copy' pour standardiser le comportement.\r\n        if (dataTransfer) {\r\n            try {\r\n                dataTransfer.effectAllowed = 'copy';\r\n                var $_dragImg = $target.closest('.draggable').find('img').first();\r\n                if ($_dragImg.length ? $_dragImg[0].complete : false) {\r\n                    var _imgEl = $_dragImg[0];\r\n                    var _r = _imgEl.getBoundingClientRect();\r\n                    \/\/ D\u00e9calage du ghost : centr\u00e9 sur le curseur\r\n                    var _ox = Math.round((_r.width || 100) \/ 2);\r\n                    var _oy = Math.round((_r.height || 60) \/ 2);\r\n                    dataTransfer.setDragImage(_imgEl, _ox, _oy);\r\n                }\r\n            } catch(_eDI) { console.warn('[dragstart] setDragImage \u00e9chec:', _eDI); }\r\n        }\r\n    \r\n        if (mediaSrc) {\r\n            StateManager.set('objectUrl', mediaSrc);\r\n        }\r\n    \r\n        StateManager.set('Commande_Format_Transmis', '');\r\n        \r\n        if ($target.closest('.droppable').find('.doc-preview-container:visible').length > 0) {\r\n            StateManager.set('Commande_Format_Transmis', 'R\u00e9dactionnel');\r\n            StateManager.set('FullPathAdFile', \r\n                $target.closest('.droppable').find('.doc-preview-FullPathAdFile').text());\r\n        }\r\n    \r\n        if (files ? files.length > 0 : false) {\r\n            this.state.isFileBeingDragged = true;\r\n            this.state.draggedFile = files[0];\r\n        }\r\n        \r\n        \/\/ \u2705 TOUJOURS marquer comme \"Moved\" quand on drag depuis un espace existant\r\n        if (parentId ? mediaSrc : false) {\r\n            StateManager.set('FirstUploadFileorMoved', 'Moved');\r\n            console.log('\u2705 Drag depuis espace existant - Moved');\r\n        }\r\n    \r\n        console.log('Drag Started', {\r\n            file: this.state.draggedFile,\r\n            mediaSrc: mediaSrc,\r\n            dragstartRef: StateManager.get('dragstart_Commande_Emplacement_Page_Web')\r\n        });\r\n    },\r\n    \r\n    handleDragOver(e) {\r\n        e.preventDefault();\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            this.handleIframeDragScroll(e);\r\n        } else {\r\n            this.handleDirectPageScroll(e);\r\n        }\r\n    },\r\n    \r\n    handleIframeDragScroll(e) {\r\n        if (!this.state.throttleTimeout) {\r\n            this.state.throttleTimeout = setTimeout(() => {\r\n                MessageManager.sendToParent('dragScroll', { clientY: e.clientY });\r\n                this.state.throttleTimeout = null;\r\n            }, CONFIG.dragScroll.throttleDelay);\r\n        }\r\n    },\r\n    \r\n    handleDirectPageScroll(e) {\r\n        const { threshold, maxSpeed } = CONFIG.dragScroll;\r\n        const windowHeight = window.innerHeight;\r\n        \r\n        if (e.clientY < threshold) {\r\n            const speed = Math.max(1, maxSpeed * (1 - e.clientY \/ threshold));\r\n            window.scrollBy(0, -speed);\r\n        } else if (e.clientY > windowHeight - threshold) {\r\n            const speed = Math.max(1, maxSpeed * (1 - (windowHeight - e.clientY) \/ threshold));\r\n            window.scrollBy(0, speed);\r\n        }\r\n    },\r\n    \r\n    handleDragEnd(e) {\r\n        e.preventDefault();\r\n        MessageManager.sendToParent('dragEnd', {});\r\n        console.log('dragleave drop');\r\n    },\r\n    \r\n    handleDropZoneDragOver(e) {\r\n        e.preventDefault();\r\n        console.log(\"FirstUploadFileorMoved:\", StateManager.get('FirstUploadFileorMoved'));\r\n    },\r\n    \r\n    handleDraggableEnd(e) {\r\n        e.preventDefault();\r\n        this.resetState();\r\n        console.log('dragend');\r\n    },\r\n    \r\n    resetState() {\r\n        this.state.isFileBeingDragged = false;\r\n        this.state.draggedFile = null;\r\n        this.state.dragInProgress = false;\r\n    },\r\n    \r\n    clearDataTransferFiles(e) {\r\n        e.dataTransfer = new DataTransfer();\r\n        console.log('DataTransfer files cleared');\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'interface utilisateur\r\n *\/\r\nconst UIManager = {\r\n    isMobile() {\r\n        return window.outerWidth < CONFIG.breakpoints.mobile;\r\n    },\r\n    \r\n    isDesktop() {\r\n        const userAgent = navigator.userAgent;\r\n        const isWindows = userAgent.indexOf('Windows') > -1;\r\n        const isMac = userAgent.indexOf('Macintosh') > -1;\r\n        const isLinux = userAgent.indexOf('Linux') > -1 ? userAgent.indexOf('Android') === -1 : false;\r\n        return isWindows || isMac || isLinux;\r\n    },\r\n    \r\n    initMobileUI() {\r\n        $(document).ready(() => {\r\n            $('label[for=\"form-field-LienAnnonce\"]').each(function() {\r\n                $(this).text('Renseigner le lien hypertexte de l\\'annonce');\r\n            });\r\n            \r\n            $('input[name=\"form_fields[LienAnnonce]\"]').each(function() {\r\n                $(this).attr('placeholder', 'Renseigner le lien hypertexte de l\\'annonce');\r\n            });\r\n        });\r\n        \r\n        jQuery('.MsgDatesText').hide();\r\n        this.attachMobileEventListeners();\r\n    },\r\n    \r\n    attachMobileEventListeners() {\r\n        jQuery('.TransmettreFichierAnnonceContainer').on('click', (e) => {\r\n            this.handleMobileSpaceSelection(e);\r\n        });\r\n        \r\n        jQuery('[id=\"form-field-selected_currency_Mobile\"]').off('change').on('change', (e) => {\r\n            this.handleMobileCurrencyChange(e);\r\n        });\r\n    },\r\n    \r\n    handleMobileSpaceSelection(e) {\r\n        e.preventDefault();\r\n        jQuery('#IndisponibilitesMsg').hide();\r\n        \r\n        jQuery('input[name=\"form_fields[SelectEspacePublicitaireMobile]\"]').prop('checked', false);\r\n        jQuery(e.currentTarget).find('input[name=\"form_fields[SelectEspacePublicitaireMobile]\"]').prop('checked', true);\r\n        \r\n        StateManager.set(\"PageWebDisplayed\", 'Yes');\r\n        \r\n        jQuery('.FormSelectDevisesMobile').hide();\r\n        jQuery(e.target).closest('.EspacePublicitaireMobile').find('.FormSelectDevisesMobile').show();\r\n        \r\n        const $droppable = jQuery(e.currentTarget).closest('.droppable');\r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n        StateManager.set('Commande_Emplacement_Page_Web', \r\n            StateManager.buildEmplacementReference(rankId));\r\n        \r\n        this.updateEmplacementDisplay();\r\n        this.handleAvailabilityDisplay(e);\r\n        this.displayUploadedAdIfExists(e);\r\n        this.updateTariffDisplay(e);\r\n    },\r\n    \r\n    updateEmplacementDisplay() {\r\n        const emplacement = StateManager.get('Commande_Emplacement_Page_Web');\r\n        jQuery('#EmplacementAnnonceDataStep3').html(emplacement).css({'color': '#56BE50'});\r\n        console.log(\"Emplacement:\", emplacement);\r\n    },\r\n    \r\n    handleAvailabilityDisplay(e) {\r\n        const $espace = jQuery(e.target).closest('.EspacePublicitaireMobile');\r\n        const dispoText = $espace.find('.ChoisirEspacePublicitaireDisponibilite').text();\r\n        \r\n        if (dispoText.length > 46) {\r\n            const espacePublicitaireDispoFrom = dispoText.slice(-10);\r\n            console.log(\"espacePublicitaireDispoFrom:\", espacePublicitaireDispoFrom);\r\n            console.log(\"Date de debut:\", StateManager.get(\"Debut_de_campagne\"));\r\n        } else {\r\n            jQuery('#form-field-DebutCampagne').val(StateManager.get(\"Debut_de_campagne\"));\r\n        }\r\n    },\r\n    \r\n    displayUploadedAdIfExists(e) {\r\n        if (StateManager.get(\"FileReceived\") === 'Yes') {\r\n            jQuery('.VisualisationAnnonceMobile').hide();\r\n            \r\n            const espaceId = StateManager.get('espaceChoisi');\r\n            const $espace = document.querySelector(`[id=\"${espaceId}\"]`);\r\n            \r\n            jQuery($espace).find('.MessageAnnonceMobileTexte')\r\n                .html('Merci de choisir des dates de campagne<br>afin d\\'obtenir le tarif de cet espace publicitaire');\r\n            jQuery($espace).find('.VisualisationAnnonceMobile').show();\r\n        \r\n            const img = document.createElement('img');\r\n            img.src = StateManager.get('objectUrl');\r\n            img.style.width = 'auto';\r\n            img.style.height = 'auto';\r\n            img.style.maxWidth = '100%';\r\n            img.style.maxHeight = '200px';\r\n            \r\n            jQuery('.VisualisationAnnonceMobile').empty();\r\n            jQuery($espace).find('.VisualisationAnnonceMobile').append(img);\r\n        }\r\n    },\r\n    \r\n    updateTariffDisplay(e) {\r\n        jQuery('.TariftobedisplayedMobile').html('-');\r\n        const newTarif = StateManager.get('NewTarifformatted');\r\n        \r\n        if (newTarif ? newTarif !== '-' : false) {\r\n            jQuery(e.target).closest('.EspacePublicitaireMobile')\r\n                .find('.TariftobedisplayedMobile')\r\n                .html(newTarif);\r\n        }\r\n    },\r\n    \r\n    handleMobileCurrencyChange(e) {\r\n        event.preventDefault();\r\n        StateManager.setMultiple({\r\n            \"SelectionCatalogueOuAchat\": 'Catalogue',\r\n            \"selected_currency\": jQuery(e.currentTarget).val()\r\n        });\r\n        \r\n        jQuery('#form-field-selected_currency_2').val(StateManager.get(\"selected_currency\"));\r\n        console.log(\"selected currency:\", StateManager.get(\"selected_currency\"));\r\n        \r\n        setTimeout(() => {\r\n            jQuery(e.target).closest('.EspacePublicitaireMobile')\r\n                .find('.TariftobedisplayedMobile')\r\n                .html(StateManager.get(\"NewTarifformatted\"));\r\n        }, 500);\r\n    },\r\n    \r\n    initDesktopUI() {\r\n        StateManager.setMultiple({\r\n            \"FileReceived\": \"No\",\r\n            \"AdDisplayed\": \"No\"\r\n        });\r\n        jQuery('.MsgAdNotDisplayed').hide();\r\n    },\r\n    \r\n    showUploadProgress($dropZone) {\r\n        \/\/ Cacher les \u00e9l\u00e9ments\r\n        $dropZone.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .TexteMobileAnnonce')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        \/\/ \u2705 v2.7.3 : Loading dans le dropZone (flux normal, pas de fixed)\r\n        (function() {\r\n            var $_drp    = $dropZone.closest('.droppable');\r\n            var _rankId  = $_drp.attr('id') || '';\r\n            var _isEle0A = _rankId === 'Ele0A';\r\n            var _isMob   = UIManager.isMobile();\r\n            var _posLib  = PreviewRenderer._getPositionLibelle(_rankId) || '';\r\n            var _empl    = StateManager.get('Commande_Emplacement_Page_Web') || '';\r\n            \/\/ \u2705 Bug 11 : -2px sur mobile\r\n            var _fsPos   = _isMob ? '8px'  : '12px';\r\n            var _fsTitle = _isMob ? '9px'  : '14px';\r\n            var _fsRef   = _isMob ? '7px'  : '11px';\r\n\r\n            \/\/ Header identique \u00e0 _buildAdOverlay \u2014 couleurs selon l'espace\r\n            var _hdrBg    = _isEle0A ? '#D0C067' : '#9fc5f3';\r\n            var _titleTxt = _isEle0A ? 'Espace publicitaire Pop-up' : 'Espace publicitaire';\r\n            var _fsRefBold = _isMob ? '8.5px' : '12.5px';\r\n            var _hdrHtml = '<div style=\"display:flex;align-items:center;background:' + _hdrBg + ';padding:4px 8px;box-sizing:border-box;font-family:Roboto,Arial,sans-serif;font-weight:700;color:#ffffff;position:relative;border-radius:4px 4px 0 0;\">'\r\n                + '<div style=\"flex:0 0 auto;z-index:1;\">'\r\n                +   (_posLib ? '<span style=\"font-size:' + _fsPos + ';color:#ffffff;\">' + _posLib + '<\/span>' : '')\r\n                + '<\/div>'\r\n                + '<div style=\"position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;pointer-events:none;\">'\r\n                +   '<span style=\"font-size:' + _fsTitle + ';color:#ffffff;\">' + _titleTxt\r\n                +   (_empl ? ' <span style=\"font-size:' + _fsRefBold + ';font-weight:700;color:#ffffff;\">' + _empl + '<\/span>' : '')\r\n                +   '<\/span>'\r\n                + '<\/div>'\r\n                + '<div style=\"flex:0 0 auto;margin-left:auto;z-index:1;\"><\/div>'\r\n                + '<\/div>';\r\n\r\n            var _bodyHtml = '<div style=\"display:flex;align-items:center;justify-content:center;padding:30px 12px;background:#fff;\">'\r\n                + '<span style=\"color:#00ff19;font-weight:700;font-size:' + (_isMob ? '14px' : '18px') + ';font-family:Roboto,Arial,sans-serif;\">'\r\n                + 'Loading in progress <span style=\"display:inline-block;\"><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><\/span>'\r\n                + '<\/span><\/div>';\r\n\r\n            \/\/ Masquer les \u00e9l\u00e9ments parasites SANS toucher aux dimensions\r\n            $_drp.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne').hide();\r\n            $_drp.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n\r\n            \/\/ Neutraliser les marges n\u00e9gatives Elementor pendant le loading\r\n            \/\/ Sauvegarder sur data() AVANT reset \u2192 styleUploadedAd retrouvera les vraies valeurs\r\n            var _drpOrigMt = parseInt($_drp.css('margin-top')) || 0;\r\n            var _drpOrigMb = parseInt($_drp.css('margin-bottom')) || 0;\r\n            if ($_drp.data('orig-mt') === undefined) { $_drp.data('orig-mt', _drpOrigMt); }\r\n            var $_drpOmc = $_drp.find('.OrdiMobileConteneurClass').first();\r\n            if ($_drpOmc.length) {\r\n                if ($_drpOmc.data('orig-mb') === undefined) { $_drpOmc.data('orig-mb', parseInt($_drpOmc.css('margin-bottom')) || 0); }\r\n            }\r\n            if (_drpOrigMt < 0) { $_drp.css('margin-top', '0px'); }\r\n            if (_drpOrigMb < 0) { $_drp.css('margin-bottom', '0px'); }\r\n\r\n            \/\/ Injecter dans $dropZone sans modifier sa hauteur (height:180px CSS natif)\r\n            \/\/ Forcer le dropZone \u00e0 stretcher son enfant (pas align-items:center)\r\n            $dropZone.css({ 'align-items': 'stretch', 'padding': '0' });\r\n            \/\/ Mobile : 50px de marge en dessous pour \u00e9viter chevauchement avec contenu suivant\r\n            \/\/ Poser sur plusieurs \u00e9l\u00e9ments avec !important + MutationObserver pour r\u00e9sister aux \u00e9crasements\r\n            if (_isMob) {\r\n                var _omcLi = $dropZone.closest('.OrdiMobileConteneurClass')[0];\r\n                if (_omcLi) {\r\n                    _omcLi.style.setProperty('margin-bottom', '50px', 'important');\r\n                    \/\/ Observer : si un autre script \u00e9crase, on restaure\r\n                    try {\r\n                        if (_omcLi._loadingMbObs) { _omcLi._loadingMbObs.disconnect(); }\r\n                        _omcLi._loadingMbObs = new MutationObserver(function(muts) {\r\n                            if (!_omcLi.querySelector('.via-loading-inline')) {\r\n                                \/\/ Loading termin\u00e9, arr\u00eat de l'observer\r\n                                _omcLi._loadingMbObs.disconnect();\r\n                                _omcLi._loadingMbObs = null;\r\n                                return;\r\n                            }\r\n                            var _cur = _omcLi.style.marginBottom;\r\n                            var _needRestore = true;\r\n                            if (_cur === '50px !important') { _needRestore = false; }\r\n                            if (_cur === '50px') { _needRestore = false; }\r\n                            if (_needRestore) {\r\n                                _omcLi.style.setProperty('margin-bottom', '50px', 'important');\r\n                            }\r\n                        });\r\n                        _omcLi._loadingMbObs.observe(_omcLi, { attributes: true, attributeFilter: ['style'] });\r\n                        \/\/ S\u00e9curit\u00e9 : stop au bout de 10 secondes\r\n                        setTimeout(function() {\r\n                            if (_omcLi._loadingMbObs) {\r\n                                _omcLi._loadingMbObs.disconnect();\r\n                                _omcLi._loadingMbObs = null;\r\n                            }\r\n                        }, 10000);\r\n                    } catch(_e) { console.warn('[loading mb observer]', _e); }\r\n                }\r\n            }\r\n            $dropZone.html('<div class=\"via-loading-inline\" style=\"'\r\n                + 'width:100%;height:100%;'\r\n                + 'border:2px solid #00FF19;border-radius:4px;'\r\n                + 'overflow:hidden;background:#fff;box-sizing:border-box;'\r\n                + 'display:flex;flex-direction:column;'\r\n                + (_isMob ? 'margin-top:40px;' : 'margin-top:40px;') + '\">'\r\n                + _hdrHtml\r\n                + '<div style=\"flex:1;display:flex;align-items:' + (_isMob ? 'flex-start' : 'center') + ';justify-content:center;padding-top:' + (_isMob ? '50px' : '0') + ';margin-top:' + (_isMob ? '0' : '-15px') + ';\">'\r\n                + '<span style=\"color:#00ff19;font-weight:700;font-size:' + (_isMob ? '14px' : '18px') + ';font-family:Roboto,Arial,sans-serif;\">'\r\n                + 'Loading in progress <span style=\"display:inline-block;\"><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><span class=\"dot\">.<\/span><\/span>'\r\n                + '<\/span><\/div>'\r\n                + '<\/div>');\r\n        })();\r\n    },\r\n    \r\n    showFormatError($dropZone, message = 'Format de fichier incompatible') {\r\n        \/\/ \u2705 v2.4.6 : M\u00eame rendu overlay que le bloc jpeg \u2014 position absolute sur $dropZone\r\n        \/\/ \u2705 v4.9n  : timeout 5s, mobile r\u00e9duit \u00e0 50% via scale\r\n        var _errId = 'fmt-error-msg-' + Date.now();\r\n        jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n        var _isMob = UIManager.isMobile();\r\n        $dropZone.css('position', 'relative');\r\n        var _errHtml = '<div id=\"' + _errId + '\" style=\"'\r\n            + 'position:absolute;top:' + (_isMob ? '54px' : '50px') + ';left:50%;'\r\n            + (_isMob\r\n                ? 'transform:translateX(-50%) scale(0.604);transform-origin:top center;'\r\n                : 'transform:translateX(-50%);')\r\n            + 'width:auto;white-space:nowrap;'\r\n            + 'background:#fff;color:#FB5E2A;font-weight:700;font-size:' + (_isMob ? '13px' : '14px') + ';'\r\n            + 'text-align:center;padding:8px 10px;border-radius:6px;'\r\n            + 'border:2px solid #FB5E2A;box-sizing:border-box;'\r\n            + 'z-index:99999;line-height:1.4;'\r\n            + '\">' + message + '<\/div>';\r\n        $dropZone.append(_errHtml);\r\n        setTimeout(function() { jQuery('#' + _errId).remove(); }, 5000);\r\n    },\r\n    \r\n    updateAfterSuccessfulUpload($dropZone) {\r\n        jQuery('#errorMessageMobileText').hide();\r\n        \r\n        $dropZone.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 Pas de margin-top:150px si d\u00e9p\u00f4t depuis miniature kit (d\u00e9j\u00e0 positionn\u00e9)\r\n        if (!window._dropFromMiniature) {\r\n            $dropZone.closest('.OrdiMobileConteneurClass')\r\n                .css({'margin-top': '150px', 'margin-bottom': '0px'});\r\n        }\r\n    },\r\n    \r\n    finalizeAdDisplay($dropZone) {\r\n        StateManager.setMultiple({\r\n            \"FileReceived\": \"Yes\",\r\n            \"PageWebDisplayed\": \"Yes\"\r\n        });\r\n        \r\n        jQuery('#MsgChoixPageWeb, #MsgInsererAnnonceConteneur').hide();\r\n        jQuery('#ChoixEspacePublicitaire')\r\n            .html('L\\'annonce est plac\u00e9e dans un espace publicitaire')\r\n            .show()\r\n            .css({'color': '#56BE50'});\r\n        \r\n        jQuery('#MsgPlacerAnnoncePosition').hide();\r\n        jQuery('#FonctionMenu4').show();\r\n        jQuery('.AnnonceData').html(\"Transmis\").css({'color': '#56BE50'});\r\n        \r\n        jQuery('#ProcederPaiementConteneur').hide();\r\n        jQuery('#ProcederPaiementTitreIconeUp').hide();\r\n        jQuery('#ProcederPaiementTitreIconeDown').show();\r\n        \r\n        jQuery('#message').remove();\r\n        \r\n        this.styleUploadedAd($dropZone);\r\n        this.adjustLayoutAfterUpload($dropZone);\r\n        \/\/ \u2705 v2.7.3 : Masquer imm\u00e9diatement apr\u00e8s styleUploadedAd\/adjustLayout\r\n        \/\/ (ces 2 fonctions remontrent des \u00e9l\u00e9ments \u2192 les cacher avant _buildAdOverlay)\r\n        (function(_dz) {\r\n            var $_d = jQuery(_dz).closest('.droppable');\r\n            $_d.find('.DeplaceAnnonce, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .CroixResetAnnonceContainer, .via-ad-header, .via-position-label, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_d.find('.PositionEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })($dropZone[0] || $dropZone);\r\n        \/\/ \u2705 v2.7.3 : Injecter header + footer dans le liser\u00e9 vert\r\n        setTimeout(() => { this._buildAdOverlay($dropZone); }, 250);\r\n        \r\n        \/\/ \u2705 v4.9bf : Figer la largeur du wrapper Ele0A sur mobile site pays apr\u00e8s d\u00e9p\u00f4t\r\n        \/\/            (emp\u00eache le r\u00e9tr\u00e9cissement au scroll caus\u00e9 par width:fit-content)\r\n        \/\/            MutationObserver maintient la valeur en restaurant tout \u00e9crasement\r\n        setTimeout(function() {\r\n            var _droppableId = $dropZone.closest('.droppable').attr('id');\r\n            if (_droppableId !== 'Ele0A') return;\r\n            var _isMobSitePays = (window === window.top);\r\n            if (!_isMobSitePays) return;\r\n            var _isMobCheck = false;\r\n            if (navigator.maxTouchPoints > 0) { _isMobCheck = true; }\r\n            if (window.innerWidth < 1000) { _isMobCheck = true; }\r\n            if (window.outerWidth < 1000) { _isMobCheck = true; }\r\n            if (!_isMobCheck) return;\r\n            var $_wrapE0A = jQuery('.ToBeHidden').has('#Ele0A');\r\n            if (!$_wrapE0A.length) return;\r\n            var _wrapEl = $_wrapE0A[0];\r\n            var _w = _wrapEl.getBoundingClientRect().width;\r\n            if (_w < 50) return;\r\n            var _freezeW = Math.round(_w);\r\n            window._ele0AFrozenWidth = _freezeW;\r\n            \/\/ Applique les 3 propri\u00e9t\u00e9s de largeur\r\n            function _applyFrozenWidth() {\r\n                _wrapEl.style.setProperty('width',     _freezeW + 'px', 'important');\r\n                _wrapEl.style.setProperty('max-width', _freezeW + 'px', 'important');\r\n                _wrapEl.style.setProperty('min-width', _freezeW + 'px', 'important');\r\n            }\r\n            _applyFrozenWidth();\r\n            console.log('[v4.9bf] Ele0A wrapper largeur fig\u00e9e \u00e0 ' + _freezeW + 'px');\r\n            \/\/ Observer : si un autre script change la largeur, on restaure\r\n            try {\r\n                if (_wrapEl._e0AWidthObs) { _wrapEl._e0AWidthObs.disconnect(); }\r\n                _wrapEl._e0AWidthObs = new MutationObserver(function() {\r\n                    var _cur = _wrapEl.getBoundingClientRect().width;\r\n                    if (Math.abs(_cur - _freezeW) > 2) {\r\n                        _applyFrozenWidth();\r\n                    }\r\n                });\r\n                _wrapEl._e0AWidthObs.observe(_wrapEl, { attributes: true, attributeFilter: ['style'] });\r\n                \/\/ Observer aussi #Ele0A (si c'est lui qui change la largeur)\r\n                var _e0AInner = _wrapEl.querySelector('#Ele0A');\r\n                if (_e0AInner) {\r\n                    if (_e0AInner._e0AInnerObs) { _e0AInner._e0AInnerObs.disconnect(); }\r\n                    _e0AInner._e0AInnerObs = new MutationObserver(function() {\r\n                        var _cur = _wrapEl.getBoundingClientRect().width;\r\n                        if (Math.abs(_cur - _freezeW) > 2) {\r\n                            _applyFrozenWidth();\r\n                        }\r\n                    });\r\n                    _e0AInner._e0AInnerObs.observe(_e0AInner, { attributes: true, attributeFilter: ['style'] });\r\n                }\r\n            } catch (_e) { console.warn('[v4.9bf obs]', _e); }\r\n            \/\/ R\u00e9appliquer aussi \u00e0 chaque scroll (backup)\r\n            window.addEventListener('scroll', _applyFrozenWidth, { passive: true });\r\n            \/\/ v4.9bo : badge debug retir\u00e9 (sujet 2 en pause)\r\n        }, 200);\r\n        \r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox \"R\u00e9server\" apr\u00e8s upload\r\n        FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n        \/\/ \u2705 Masquer le bandeau bleu format lors d'une restauration (updateReserverCheckboxState le remet sinon)\r\n        if (StateManager.get('_isAdRestoration') === 'Yes') {\r\n            $dropZone.closest('.droppable').find('.SelectionFormatTitreBlanc').hide();\r\n        }\r\n    },\r\n    \r\n    styleUploadedAd($dropZone) {\r\n        const $container = $dropZone.closest('.OrdiMobileConteneurClass');\r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \r\n        \/\/ \u2705 v1.19.5 : V\u00e9rifier si c'est une restauration via flag d\u00e9di\u00e9\r\n        const isRestoration = StateManager.get('_isAdRestoration') === 'Yes';\r\n        \r\n        $container\r\n            .find('.PositionReference, .TexteMobile, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        $container\r\n            .find('.AdUploadedTitle, .DimensionsMaximales')\r\n            .show();\r\n        \r\n        \/\/ \u2705 Bug 2 fix : montrer la croix (positionnement gere par Pb3 Ele0A)\r\n        $droppable.find('.CroixResetAnnonceContainer').show();\r\n        $droppable.find('#CroixResetAnnonce').show();\r\n        \r\n        \/\/ \u2705 v2.2 : Marquer le droppable comme occup\u00e9 pour qu'Entete le skip\r\n        $droppable.attr('data-via-ad-loaded', 'true');\r\n        \r\n        \/\/ \u2705 v2.0.11 : AdUploadedTitle ne doit pas bloquer le touch sur doc-preview-readmore (mobile)\r\n        $container.find('.AdUploadedTitle').css('pointer-events', 'none');\r\n        \r\n        \/\/ \u2705 #CroixResetAnnonce au-dessus de l'image dans son espace pub\r\n        \/\/ v4.9ds : z-index abaiss\u00e9 \u00e0 5 pour que la pastille jaune .popupAchatAnnonce\r\n        \/\/   passe au-dessus.\r\n        \/\/   Diagnostic stacking context (cf user mobile) :\r\n        \/\/     - Pastille est enfant de EnteteBackground (z-index:6, stacking ctx isol\u00e9)\r\n        \/\/       \u2192 son z-index est PLAFONN\u00c9 \u00e0 6 au niveau body, peu importe la valeur\r\n        \/\/     - Croix vit au niveau body (ses anc\u00eatres jusqu'\u00e0 OrdiMobileConteneurClass\r\n        \/\/       n'ont pas de z-index)\r\n        \/\/   Comp\u00e9tition au niveau body : header z:6 vs croix z:N\r\n        \/\/     \u2192 croix doit avoir z < 6 pour passer sous le header (donc sous la pastille)\r\n        \/\/     \u2192 croix doit avoir z > 3 pour rester au-dessus de l'image (cf z-index:3\r\n        \/\/       sur AdUploadedTitle \/ OrdiMobileConteneurClass parents)\r\n        \/\/   Valeur 5 = compromis : sous header (donc sous pastille) + sur image dans son\r\n        \/\/     droppable.\r\n        $container.find('#CroixResetAnnonce').css({\r\n            'position': 'relative',\r\n            'z-index': '5'\r\n        });\r\n        \r\n        \/\/ \u2705 Desktop : les conteneurs superpos\u00e9s bloquent la croix \u2014 les rendre transparents aux clics\r\n        if (!UIManager.isMobile()) {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css('pointer-events', 'none');\r\n            \/\/ R\u00e9activer les \u00e9l\u00e9ments interactifs\r\n            $container.find('#CroixResetAnnonce').css('pointer-events', 'auto');\r\n            $dropZone.closest('#PopUpMessageAchattest').css('pointer-events', 'auto'); \/\/ drag annonce\r\n        }\r\n        \r\n        \/\/ \u2705 v2.4.3 : Sur mobile, masquer titre et logo drag quand annonce d\u00e9pos\u00e9e dans Ele0A\r\n        if (UIManager.isMobile()) {\r\n            if ($droppable.attr('id') === 'Ele0A') {\r\n                $droppable.find('#ChoixEspacePublicitaireTexte').hide();\r\n                $droppable.find('#Ele0ADragHandle').hide();\r\n            }\r\n            \/\/ v2.9 : forcer pointer-events:auto sur la croix pour tous les espaces (Ele0A + standards)\r\n            var _crcEl = $droppable.find('.CroixResetAnnonceContainer')[0];\r\n            if (_crcEl) { _crcEl.style.setProperty('pointer-events', 'auto', 'important'); }\r\n            var _crEl = $droppable.find('#CroixResetAnnonce')[0];\r\n            if (_crEl) { _crEl.style.setProperty('pointer-events', 'auto', 'important'); }\r\n        }\r\n\r\n        \/\/ v2.6 : Desktop Ele0A - masquer icones drag\/close + elements gris parasites\r\n        if (!UIManager.isMobile()) { if ($droppable.attr('id') === 'Ele0A') {\r\n            $droppable.find('#Ele0ADragHandle').hide();\r\n            $droppable.find('#Ele0ACloseBtn').hide();\r\n            \/\/ v2.6 : Quand annonce chargee dans Ele0A : cacher titre+boutons format (zone grise parasite)\r\n            $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $droppable.find('.EspPubFormatMainContainer').hide();\r\n            $droppable.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n            \/\/ v2.9 : Ele0A desktop - masquer elements parasites (avec ou sans iframe pays)\r\n            $droppable.find('.DeplaceAnnonceText').hide();\r\n            console.log('[styleUploadedAd Ele0A] zone grise masquee | UFC bg -> transparent');\r\n            \/\/ v2.9 : iframe pays (AchatEspaceCall=Yes) -> masquer elements supplementaires\r\n            if (StateManager.get('AchatEspaceCall') === 'Yes') {\r\n                $droppable.find('.PositionEspacePublicitaireDeplacer').hide();\r\n                $droppable.find('.RefEspacePublicitaire').hide();\r\n                console.log('[styleUploadedAd Ele0A] iframe pays: PositionEspacePublicitaireDeplacer masque');\r\n            }\r\n        } }\r\n        \r\n        \/\/ \u2705 v2.6 : Reset uniquement les UFC sans annonce d\u00e9pos\u00e9e (data-via-ad-loaded absent)\r\n        \/\/ Les UFC des espaces d\u00e9j\u00e0 remplis (background:white) ne doivent pas \u00eatre remis \u00e0 transparent\r\n        jQuery(\".HTMLUploadfileConteneur\").each(function() {\r\n            var _droppableEl = jQuery(this).closest('.droppable')[0]\r\n                || jQuery(this).find('#drop_file_zone_achat').closest('.droppable')[0];\r\n            var _hasAd = false;\r\n            if (_droppableEl) {\r\n                if (_droppableEl.getAttribute('data-via-ad-loaded') === 'true') {\r\n                    _hasAd = true;\r\n                }\r\n            }\r\n            if (!_hasAd) {\r\n                jQuery(this).css({ 'border': 'none', 'background-color': 'transparent' });\r\n            }\r\n        });\r\n        \r\n        \/\/ \u2705 v2.4.12 : Retirer le liser\u00e9 envoi diff\u00e9r\u00e9 (#UploadFileConteneur) si pr\u00e9sent\r\n        \/\/ (le d\u00e9p\u00f4t d'une annonce cr\u00e9e son propre liser\u00e9 sur .HTMLUploadfileConteneur)\r\n        var $_ufcEd = $dropZone.closest('.droppable').find('#UploadFileConteneur');\r\n        if ($_ufcEd[0]) {\r\n            $_ufcEd[0].style.removeProperty('box-shadow');\r\n            $_ufcEd[0].style.removeProperty('box-sizing');\r\n            if (UIManager.isMobile()) {\r\n                $_ufcEd[0].style.removeProperty('transform');\r\n                $_ufcEd[0].style.removeProperty('transform-origin');\r\n            }\r\n        }\r\n        \r\n        $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n            \/\/ \u2705 v2.7.3 : box-shadow supprim\u00e9 \u2014 le liser\u00e9 est port\u00e9 par .via-ad-wrapper (outline)\r\n            'box-shadow': 'none',\r\n            'background-color': 'white',\r\n            'box-sizing': 'border-box',\r\n            'overflow': 'hidden'\r\n        });\r\n        \/\/ \u2705 v2.7.3 : Mobile sites pays \u2014 afficher la position en haut \u00e0 gauche dans le liser\u00e9 vert\r\n        if (UIManager.isMobile() ? window === window.top : false) {\r\n            var _rankForPos = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            var _posLabel = PreviewRenderer._getPositionLibelle(_rankForPos);\r\n            if (_posLabel) {\r\n                var $_ufcPos = $dropZone.closest('.HTMLUploadfileConteneur');\r\n                $_ufcPos.css('position', 'relative');\r\n                $_ufcPos.find('.via-position-label').remove();\r\n                $_ufcPos.append('<div class=\"via-position-label\" style=\"position:absolute;top:3px;left:3px;color:#213864;font-size:10px;font-weight:700;font-family:Roboto,sans-serif;padding:2px 7px;z-index:999;pointer-events:none;line-height:1.5;\">' + _posLabel + '<\/div>');\r\n            }\r\n        }\r\n        \r\n        \/\/ \u2705 v2.0.9 : Fix mobile \u2014 r\u00e9duire le scale pour que le liser\u00e9 vert soit visible\r\n        \/\/ \u2705 Pas de scale si annonce d\u00e9pos\u00e9e depuis la miniature kit (d\u00e9j\u00e0 aux bonnes dimensions)\r\n        \/\/ \u2705 v2.4.9 : Pas de scale si AchatEspaceCall=Yes (drop depuis miniature dans iframe)\r\n        \/\/ \u2705 data-kit-drop : marqueur DOM pos\u00e9 par kitAdCreated, r\u00e9siste \u00e0 l'async\r\n        var _fromKitDrop = $droppable[0] ? $droppable[0].getAttribute('data-kit-drop') === 'true' : false;\r\n        if (_fromKitDrop) { window._dropFromMiniature = true; } \/\/ sync le flag window\r\n        var _fromMiniature = window._dropFromMiniature || _fromKitDrop;\r\n        var _fromAchat = StateManager.get('AchatEspaceCall') === 'Yes';\r\n        if (UIManager.isMobile() ? (!_fromMiniature ? !_fromAchat : false) : false) {\r\n            \/\/ \u2705 v2.7.3 : Plus de scale (cause des liser\u00e9s parasites) \u2014 le wrapper overlay g\u00e8re l'apparence\r\n            $dropZone.closest('.UploadFileConteneur').css({\r\n                'transform': 'none',\r\n                'transform-origin': ''\r\n            });\r\n        }\r\n        \r\n        \/\/ \u2705 v2.2 : Sur restauration, Entete a d\u00e9j\u00e0 positionn\u00e9 le droppable correctement\r\n        \/\/ et est emp\u00each\u00e9 de le retoucher (data-via-ad-loaded). On ne touche pas aux marges.\r\n        \/\/ Sur premier upload, on applique les marges normalement.\r\n        \/\/ \u2705 Pas de marges si d\u00e9p\u00f4t depuis miniature kit (dimensions naturelles conserv\u00e9es)\r\n        if (!isRestoration ? !_fromMiniature : false) {\r\n            if ($container.data('orig-mb') === undefined) {\r\n                $container.data('orig-mb', parseInt($container.css('margin-bottom')) || 0);\r\n            }\r\n            if ($droppable.data('orig-mt') === undefined) {\r\n                $droppable.data('orig-mt', parseInt($droppable.css('margin-top')) || 0);\r\n            }\r\n            var _isDocPreview = $dropZone.find('.doc-preview-container').length > 0;\r\n            \/\/ \u2705 Sites pays (window.top) : marges r\u00e9duites (iframe offset non n\u00e9cessaire)\r\n            var _isSitesPays = (window === window.top);\r\n            var _marginAdd    = _isSitesPays ? 0 : (_isDocPreview ? 60 : 130);\r\n            var _marginReduce = _isSitesPays ? 0 : (_isDocPreview ? 60 : 130);\r\n            var _marginTopAdj = _isSitesPays ? 0 : 75;\r\n            $container.css({\r\n                'margin-bottom': ($container.data('orig-mb') + _marginAdd) + 'px'\r\n            });\r\n            $droppable.css({\r\n                'margin-top': ($droppable.data('orig-mt') - _marginReduce + _marginTopAdj) + 'px'\r\n            });\r\n        } else {\r\n            \/\/ \u2705 Sur restauration, ajouter 50px en dessous pour \u00e9viter que le contenu soit trop coll\u00e9\r\n            var _curMb = parseInt($container.css('margin-bottom')) || 0;\r\n            $container.css('margin-bottom', (_curMb + 50) + 'px');\r\n        }\r\n        \r\n        \/\/ \u2705 Masquer texte glisser-d\u00e9poser r\u00e9siduel + position\/r\u00e9server\r\n        $droppable.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne').hide();\r\n        \/\/ \u2705 Masquer .PositionEspacePublicitaireContainer et l'ancien .ReserverContainer\r\n        $droppable.find('.PositionEspacePublicitaireContainer').hide();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \/\/ \u2705 Masquer le checkbox statique Elementor (au cas o\u00f9 .ReserverContainer ne l'englobe pas)\r\n        $droppable.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').hide();\r\n        \/\/ \u2705 Masquer la zone bleue format (SelectionFormatTitreBlanc) et le champ lien hypertexte\r\n        $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n        $droppable.find('.EspPubLienAnnonce').hide();\r\n        \r\n        \/\/ \u2705 Supprimer tout ancien bouton dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 Sur restauration desktop : masquer bandeau bleu + fond #UploadFileConteneur\r\n        \/\/ (ChoisirEspacePublicitaireDisponibiliteConteneur et EspPubFormatMainContainer sont\r\n        \/\/  g\u00e9r\u00e9s par updateAfterSuccessfulUpload + adjustDesktopLayout du flux standard,\r\n        \/\/  mais le background #9FC5F3 du parent #UploadFileConteneur reste \u00e0 neutraliser)\r\n        if (isRestoration) {\r\n            if (!UIManager.isMobile()) {\r\n                $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n                $droppable.find('#UploadFileConteneur').css('background-color', 'transparent');\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v2.1.2 : Cr\u00e9er le bouton R\u00e9server m\u00eame lors d'une restauration\r\n        if (isRestoration) {\r\n            console.log('\ud83d\udd04 Restauration d\u00e9tect\u00e9e - affichage bouton R\u00e9server + DeplaceAnnonce');\r\n            $droppable.find('.DeplaceAnnonce').show();\r\n            \/\/ S'assurer que FileReceived est d\u00e9fini pour la validation\r\n            StateManager.set('FileReceived', 'Yes');\r\n            \/\/ Cocher automatiquement SEULEMENT si c'est une restauration de commande r\u00e9serv\u00e9e (pas pendingAd)\r\n            var _isPendingAdRestore = sessionStorage.getItem('_pendingAdRestoration') === 'Yes';\r\n            if (!_isPendingAdRestore) {\r\n                var _isReservedRestore = sessionStorage.getItem('_isReservedRestoration') === 'Yes';\r\n                setTimeout(function() {\r\n                    var $checkbox = $droppable.find('.reserver-dynamic-checkbox');\r\n                    if ($checkbox.length ? _isReservedRestore : false) {\r\n                        $checkbox.prop('checked', true);\r\n                        $droppable.find('.reserver-dynamic-label').text('Espace publicitaire r\u00e9serv\u00e9').css('color', 'rgb(62, 170, 19)');\r\n                        console.log('\u2705 R\u00e9server coch\u00e9 automatiquement (restauration commande r\u00e9serv\u00e9e)');\r\n                    }\r\n                    sessionStorage.removeItem('_isReservedRestoration');\r\n                    var _wasRestorationA = true; \/\/ \u00e9tait une restauration\r\n                    StateManager.set('_isAdRestoration', 'No');\r\n                    \/\/ \u2705 v2.4.7 : updateReserverCheckboxState APR\u00c8S avoir coch\u00e9 la checkbox\r\n                    \/\/ (l'appel synchrone ligne 614 intervient avant le cocher \u2192 label incorrect)\r\n                    if (typeof FormatUIManager !== 'undefined') {\r\n                        FormatUIManager.updateReserverCheckboxState($droppable);\r\n                        \/\/ \u2705 Masquer bandeau bleu format si restauration (updateReserverCheckboxState le remet)\r\n                        if (_wasRestorationA) { $droppable.find('.SelectionFormatTitreBlanc').hide(); }\r\n                    }\r\n                }, 150);\r\n            } else {\r\n                \/\/ pendingAd : ne pas cocher, ne pas envoyer au parent\r\n                console.log('\u2705 pendingAd restaur\u00e9 \u2014 R\u00e9server NON coch\u00e9');\r\n                sessionStorage.removeItem('_pendingAdRestoration');\r\n                StateManager.set('_isAdRestoration', 'No');\r\n            }\r\n        }\r\n        \r\n        \/\/ \u2705 Supprimer tout bouton R\u00e9server existant avant insertion (\u00e9vite doublons sur restauration)\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 Cr\u00e9er le bouton \"R\u00e9server\" dynamique avec id unique pour \u00e9viter conflit label\/for Elementor\r\n        const _rankId = $droppable.attr('id') || ('drop_' + Date.now());\r\n        const _cbId = 'reserver-cb-' + _rankId;\r\n        const reserverHTML = `\r\n            <div class=\"reserver-dynamic-container\">\r\n                <span class=\"reserver-dynamic-option\">\r\n                    <input type=\"checkbox\" id=\"${_cbId}\" value=\"R\u00e9server cet espace publicitaire\" \r\n                           class=\"reserver-dynamic-checkbox\"\r\n                           name=\"form_fields[ReserverEspacePublicitaire]\">\r\n                    <span class=\"reserver-dynamic-label\">R\u00e9server cet espace publicitaire<\/span>\r\n                <\/span>\r\n            <\/div>\r\n        `;\r\n        \r\n        \/\/ \u2705 Ins\u00e9rer juste avant .DeplaceAnnonceText (position stable)\r\n        const $anchor = $droppable.find('.DeplaceAnnonceText');\r\n        if ($anchor.length) {\r\n            $anchor.before(reserverHTML);\r\n            $anchor.prev('.reserver-dynamic-container').css('margin-bottom', '-40px');\r\n            if (!UIManager.isMobile()) {\r\n                $anchor.prev('.reserver-dynamic-container').find('.reserver-dynamic-label').css({'font-size': '20px', 'font-weight': '700'});\r\n            }\r\n        } else {\r\n            $droppable.after(reserverHTML);\r\n            const $btn = $droppable.next('.reserver-dynamic-container');\r\n            $btn.css('margin-top', UIManager.isMobile() ? '-10px' : '-140px');\r\n        }\r\n        \r\n        console.log('\u2705 Bouton \"R\u00e9server\" dynamique cr\u00e9\u00e9');\r\n    },\r\n    \r\n    \/\/ =========================================================================\r\n    \/\/ \u2705 v2.7.3 : Injection header \/ footer dans le liser\u00e9 vert apr\u00e8s d\u00e9p\u00f4t d'annonce\r\n    \/\/ Structure : .HTMLUploadfileConteneur \u2192 header (.via-ad-header) + body (existant) + footer (.via-ad-footer)\r\n    \/\/ =========================================================================\r\n    _buildAdOverlay($dropZone) {\r\n        const $droppable  = $dropZone.closest('.droppable');\r\n        const $ufc        = $droppable.find('.HTMLUploadfileConteneur').first();\r\n        if (!$ufc.length) return;\r\n\r\n        \/\/ \u2705 Retirer le loading overlay (fixed sur body) + reset styles\r\n        jQuery('.via-loading-overlay-fixed').remove();\r\n        $ufc.find('.via-loading-overlay').remove();\r\n        \/\/ Reset styles pos\u00e9s pendant le loading (UFC + droppable)\r\n        $ufc.css({ 'position': '', 'min-height': '', 'height': '', 'overflow': '' });\r\n        $droppable.css({ 'min-height': '', 'height': '', 'overflow': '' });\r\n        var $_omcReset = $droppable.find('.OrdiMobileConteneurClass').first();\r\n        if ($_omcReset.length) { $_omcReset.css('min-height', ''); }\r\n        $droppable.css('min-height', '');\r\n\r\n        \/\/ Idempotent : ne pas re-injecter si d\u00e9j\u00e0 pr\u00e9sent\r\n        if ($ufc.find('.via-ad-header').length) return;\r\n\r\n        const _isEle0A   = $droppable.attr('id') === 'Ele0A';\r\n        const _isMobile  = UIManager.isMobile();\r\n        const _isDesktop = !_isMobile;\r\n        \/\/ \u2705 v4.9ds Fix 14 (Pb 10) : utiliser l'emplacement du droppable courant, pas\r\n        \/\/   l'emplacement global StateManager. La variable globale refl\u00e8te le DERNIER\r\n        \/\/   emplacement cliqu\u00e9 (par ex L1A si l'user clique sur Banni\u00e8re dans Ele1A\r\n        \/\/   pendant un upload sur Ele0A) \u2014 alors que le header de l'annonce d\u00e9pos\u00e9e\r\n        \/\/   dans Ele0A doit afficher MDG48442L0A. La m\u00e9thode StateManager.buildEmplacementReference\r\n        \/\/   est exactement celle utilis\u00e9e ligne 441-442 pour construire la r\u00e9f\u00e9rence\r\n        \/\/   \u00e0 partir du rankId. Fallback sur l'emplacement global si la m\u00e9thode\r\n        \/\/   n'existe pas (s\u00e9curit\u00e9).\r\n        const _rankId    = $droppable.attr('id') || '';\r\n        var _emplacement = '';\r\n        if (_rankId ? typeof StateManager.buildEmplacementReference === 'function' : false) {\r\n            _emplacement = StateManager.buildEmplacementReference(_rankId) || '';\r\n        }\r\n        if (!_emplacement) {\r\n            _emplacement = StateManager.get('Commande_Emplacement_Page_Web') || '';\r\n        }\r\n        console.log('[Fix 14] _rankId:', _rankId, '| _emplacement r\u00e9solu:', _emplacement, '| global:', StateManager.get('Commande_Emplacement_Page_Web'));\r\n        const _posLib    = PreviewRenderer._getPositionLibelle(_rankId) || '';\r\n\r\n        \/\/ \u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ v4.9ds : Ele0A header +3px (padding 4px\u21925.5px top\/bottom = +3px total)\r\n        const _hdrPadV = _isEle0A ? '5.5px' : '4px';\r\n        const $header = jQuery('<div class=\"via-ad-header\" style=\"' +\r\n            'display:flex;align-items:center;' +\r\n            'background:' + (_isEle0A ? '#D0C067' : '#9FC5F3') + ';padding:' + _hdrPadV + ' 8px;box-sizing:border-box;' +\r\n            'font-family:Roboto,Arial,sans-serif;' +\r\n            'font-size:11px;color:#ffffff;font-weight:700;flex-shrink:0;' +\r\n            'position:relative;' +\r\n        '\"><\/div>');\r\n\r\n        \/\/ \u2500\u2500 Structure header 3 colonnes : [drag] [titre centr\u00e9] [ref + X] \u2500\u2500\u2500\u2500\u2500\r\n        const $colLeft   = jQuery('<div style=\"flex:0 0 auto;display:flex;align-items:center;z-index:1;\"><\/div>');\r\n        const $colCenter = jQuery('<div style=\"position:absolute;left:0;right:0;top:0;bottom:0;display:flex;align-items:center;justify-content:center;pointer-events:none;\"><\/div>');\r\n        const $colRight  = jQuery('<div style=\"flex:0 0 auto;display:flex;align-items:center;gap:8px;margin-left:auto;z-index:1;\"><\/div>');\r\n\r\n        \/\/ Tailles : plein \u00e9cran desktop \/ desktop mode mobile \/ mobile\r\n        var _isFullDesk = !_isMobile ? window.outerWidth >= 1200 : false;\r\n        var _isDeskMob  = !_isMobile ? window.outerWidth < 1200 : false; \/\/ desktop r\u00e9tr\u00e9ci\r\n        var _fsPos   = _isFullDesk ? '13px' : (_isDeskMob ? '6px' : '8px');\r\n        var _fsTitle = _isFullDesk ? '16px' : (_isDeskMob ? (_isEle0A ? '6px' : '8px') : (_isEle0A ? '8px' : '10px'));\r\n        var _fsRef   = _isFullDesk ? '13px' : (_isDeskMob ? '6px' : (_isEle0A ? '6px' : '8px'));\r\n\r\n        if (_isEle0A) {\r\n            \/\/ \u2705 Croix drag 4 fl\u00e8ches SVG \u2014 v4.9ds taille r\u00e9duite (17\u219213) pour aligner sur header standard\r\n            $colLeft.append(jQuery('<span class=\"via-ah-drag\" style=\"cursor:move;opacity:0.8;display:flex;align-items:center;\">' +\r\n                '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"#ffffff\">' +\r\n                '<path d=\"M10 9h4V6h3l-5-5-5 5h3v3zm-1 1H6V7l-5 5 5 5v-3h3v-4zm14 2l-5-5v3h-3v4h3v3l5-5zm-9 3h-4v3H7l5 5 5-5h-3v-3z\"\/>' +\r\n                '<\/svg><\/span>'));\r\n            \/\/ \u2705 via-ah-pos masqu\u00e9 pour Ele0A (position dans le titre suffit)\r\n            \/\/ \u2705 Bug 11 : sur mobile, limiter la largeur pour \u00e9viter chevauchement titre\/r\u00e9f\r\n            \/\/ pas de troncature \u2014 la r\u00e9duction de taille suffit\r\n            $colCenter.append(jQuery('<span class=\"via-ah-title\" style=\"font-size:' + _fsTitle + ';color:#ffffff;\">Espace publicitaire Pop-up' + (_emplacement ? ' <span style=\"font-weight:700;font-size:' + (parseFloat(_fsRef) + 1.5) + 'px;\">' + _emplacement + '<\/span>' : '') + '<\/span>'));\r\n        } else {\r\n            if (_posLib) {\r\n                $colLeft.append(jQuery('<span class=\"via-ah-pos\" style=\"font-size:' + _fsPos + ';color:#ffffff;font-weight:700;\">' + _posLib + '<\/span>'));\r\n            }\r\n            $colCenter.append(jQuery('<span class=\"via-ah-title\" style=\"font-size:' + _fsTitle + ';color:#ffffff;\">Espace publicitaire' + (_emplacement ? ' <span style=\"font-weight:700;font-size:' + (parseFloat(_fsRef) + 1.5) + 'px;\">' + _emplacement + '<\/span>' : '') + '<\/span>'));\r\n        }\r\n\r\n        \/\/ Ref incluse dans le titre ci-dessus\r\n\r\n        \/\/ v4.9ds : croix de fermeture positionn\u00e9e en absolute dans le coin haut-droite\r\n        \/\/   de .HTMLUploadfileConteneur (la zone d'aper\u00e7u blanche), \u00e0 2px du liser\u00e9 vert.\r\n        \/\/   Carr\u00e9 bleu fonc\u00e9 (#5573a8) avec croix blanche centr\u00e9e pour qu'elle se voie\r\n        \/\/   sur les images d'annonce d\u00e9pos\u00e9es (qui peuvent \u00eatre de toute couleur).\r\n        const $closeBtn = jQuery('<span class=\"via-ah-close\" style=\"' +\r\n            'cursor:pointer;font-size:13px;font-weight:900;color:#ffffff;line-height:1;' +\r\n            'pointer-events:auto;' +\r\n            'position:absolute;top:2px;right:4px;' +\r\n            'width:18px;height:18px;' +\r\n            'background:#5573a8;' +\r\n            'display:flex;align-items:center;justify-content:center;' +\r\n            'z-index:5;' +\r\n            '\" title=\"Supprimer l\\'annonce\">\u2715<\/span>');\r\n        $closeBtn.on('click', function(e) {\r\n            e.preventDefault();\r\n            e.stopPropagation();\r\n            \/\/ \u2705 v4.9ds Fix 28 : idem .via-erase-btn (ligne ~7750) \u2014 #CroixResetAnnonce\r\n            \/\/   n'existe plus dans le DOM. Appel direct \u00e0 AdResetHandler.handle.\r\n            if (typeof AdResetHandler !== 'undefined' ? typeof AdResetHandler.handle === 'function' : false) {\r\n                AdResetHandler.handle(e);\r\n                return;\r\n            }\r\n            \/\/ Fallback historique (FonctionCroixResetAnnonce \/ trigger sur #CroixResetAnnonce)\r\n            if (typeof window.FonctionCroixResetAnnonce === 'function') {\r\n                var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n                if (_croixEl) { window.FonctionCroixResetAnnonce(_croixEl); return; }\r\n            }\r\n            var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n            if (_croixEl) { jQuery(_croixEl).trigger('click'); }\r\n        });\r\n        \/\/ $colRight reste vide \u2014 la croix est pos\u00e9e en absolute sur $wrapper plus bas\r\n        \/\/   (juste apr\u00e8s son assemblage, pour rester ancr\u00e9e au liser\u00e9 vert).\r\n\r\n        $header.append($colLeft).append($colCenter).append($colRight);\r\n\r\n        \/\/ \u2500\u2500 Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        const $footer = jQuery('<div class=\"via-ad-footer\" style=\"' +\r\n            'background:' + (_isEle0A ? '#D0C067' : '#9FC5F3') + ';padding:4px 8px;box-sizing:border-box;' +  \/* v4.9ds : jaune Ele0A, bleu Ele1A+ *\/\r\n            'font-family:Roboto,Arial,sans-serif;' +\r\n            'font-size:11px;color:#ffffff;flex-shrink:0;' +\r\n        '\"><\/div>');\r\n\r\n        \/\/ v4.9ds : footer en flex pour aligner \u0153il + R\u00e9server\r\n        \/\/ v4.9ds (eye-pos) : position:relative pour ancrer l'\u0153il en absolute (centr\u00e9 dans la moiti\u00e9 gauche),\r\n        \/\/   le bouton R\u00e9server reste seul dans le flux flex et donc centr\u00e9 dans le footer\r\n        $footer.css({\r\n            'display': 'flex',\r\n            'align-items': 'center',\r\n            'justify-content': 'center',\r\n            'gap': '10px',\r\n            'position': 'relative'\r\n        });\r\n\r\n        \/\/ v4.9ds : si Communiqu\u00e9\/Interview (doc-preview pr\u00e9sent), masquer title + readmore\r\n        \/\/   et ajouter un bouton \u0153il dans le footer (\u00e0 gauche de R\u00e9server) qui ouvre la\r\n        \/\/   m\u00eame popup que le readmore (PDFHandler.showInlineDocPopup)\r\n        const _hasDocPreview = $droppable.find('.doc-preview-container').length > 0;\r\n        if (_hasDocPreview) {\r\n            $droppable.find('.doc-preview-title').hide();\r\n            $droppable.find('.doc-preview-readmore').hide();\r\n            \/\/ \u0152il SVG (Material icon \"visibility\") \u2014 ouvre la popup au clic\r\n            \/\/ v4.9ds (eye-size+pos) : taille +60% (18\u219229 desk, 14\u219222 mob) ;\r\n            \/\/   position:absolute \u00e0 left:12% pour \u00eatre centr\u00e9 entre le bord gauche du footer\r\n            \/\/   et le bouton R\u00e9server (qui, seul dans le flux flex, est centr\u00e9 au milieu du footer).\r\n            \/\/   z-index:2 pour passer au-dessus de tout. pointer-events:auto pour rester cliquable.\r\n            \/\/ v4.9ds (eye-stroke) : liser\u00e9 bleu Material (#1976D2) sur l'amande ext\u00e9rieure ET sur\r\n            \/\/   le rond int\u00e9rieur (iris) pour qu'il ressorte sur les 2 couleurs de footer.\r\n            \/\/   3 paths : path 1 = \u0153il entier rempli blanc (inchang\u00e9) ; path 2 = contour amande\r\n            \/\/   ext\u00e9rieure ; path 3 = contour iris (rond int\u00e9rieur). Tous deux fill:none + stroke,\r\n            \/\/   m\u00eame stroke-width pour coh\u00e9rence visuelle. stroke-linejoin:round pour adoucir.\r\n            const _eyeColor = '#ffffff';\r\n            const $eyeBtn = jQuery('<span class=\"via-af-eye\" title=\"Voir la publication\" style=\"cursor:pointer;display:inline-flex;align-items:center;flex:0 0 auto;position:absolute;left:12%;top:50%;transform:translate(-50%,-50%);z-index:2;pointer-events:auto;\">' +\r\n                '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"' + (_isFullDesk ? '29' : '22') + '\" height=\"' + (_isFullDesk ? '29' : '22') + '\" viewBox=\"0 0 24 24\">' +\r\n                '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z\" fill=\"' + _eyeColor + '\"\/>' +\r\n                '<path d=\"M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5z\" fill=\"none\" stroke=\"#1976D2\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>' +\r\n                '<path d=\"M12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z\" fill=\"none\" stroke=\"#1976D2\" stroke-width=\"2\" stroke-linejoin=\"round\"\/>' +\r\n                '<\/svg><\/span>');\r\n            \/\/ \u2500\u2500 \u2193 click handler (inchang\u00e9) \u2193 \u2500\u2500\r\n            $eyeBtn.on('click', function(e) {\r\n                e.preventDefault();\r\n                e.stopPropagation();\r\n                \/\/ Trouver la popup \u00e0 ouvrir : m\u00eame logique que le clic sur .doc-preview-readmore\r\n                \/\/ Priorit\u00e9 1 : kitPdfImageDataURL (cr\u00e9\u00e9 via Kit miniature)\r\n                var _kitPdf = $droppable.data('kitPdfImageDataURL');\r\n                var _kitFormat = $droppable.data('kitFormatSelect') || '';\r\n                var _isInterviewE = (_kitFormat || '').toLowerCase().indexOf('interview') !== -1;\r\n                if (!_kitFormat) {\r\n                    \/\/ Fallback : chercher le titre dans le DOM\r\n                    var _titleEl = $droppable.find('.doc-preview-title')[0];\r\n                    if (_titleEl) { _isInterviewE = (_titleEl.textContent || '').toLowerCase().indexOf('interview') !== -1; }\r\n                }\r\n                var _popupTitle = _isInterviewE ? 'Interview' : 'Communiqu\u00e9';\r\n                if (_kitPdf) {\r\n                    PDFHandler.showInlineDocPopup($dropZone, { formatTitle: _popupTitle, pdfDataURL: _kitPdf });\r\n                    return;\r\n                }\r\n                \/\/ Priorit\u00e9 2 : PDF upload\u00e9 (PDFHandler.pdfDataForViewer)\r\n                if (PDFHandler.pdfDataForViewer ? PDFHandler.pdfDataForViewer.byteLength > 0 : false) {\r\n                    PDFHandler.showInlineDocPopup($dropZone, { formatTitle: _popupTitle, pdfArrayBuffer: PDFHandler.pdfDataForViewer });\r\n                    return;\r\n                }\r\n                \/\/ Priorit\u00e9 3 : d\u00e9clencher le clic sur readmore (m\u00eame si masqu\u00e9) \u2014 fallback Word\/htmlContent\r\n                var _readmore = $droppable.find('.doc-preview-readmore')[0];\r\n                if (_readmore) {\r\n                    _readmore.style.display = 'block';\r\n                    jQuery(_readmore).trigger('click');\r\n                    _readmore.style.display = 'none';\r\n                }\r\n            });\r\n            $footer.append($eyeBtn);\r\n        }\r\n\r\n        \/\/ \u2500\u2500 D\u00e9placer le reserver-dynamic-container dans le footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ Chercher dans le droppable ET apr\u00e8s le droppable (les 2 positions possibles)\r\n        const $resBtnIn   = $droppable.find('.reserver-dynamic-container').first();\r\n        const $resBtnNext = $droppable.next('.reserver-dynamic-container');\r\n        const $resBtn     = $resBtnIn.length ? $resBtnIn : $resBtnNext;\r\n        if ($resBtn.length) {\r\n            \/\/ \u2705 Reset complet de TOUS les styles inline de positionnement\r\n            $resBtn[0].style.cssText = '';\r\n            $resBtn.css({\r\n                'margin-top': '0',\r\n                'margin-bottom': '0',\r\n                'position': '',\r\n                'top': '',\r\n                'left': '',\r\n                'bottom': ''\r\n            });\r\n            \/\/ Style minimal inline pour le label\r\n            $resBtn.find('.reserver-dynamic-label').css({\r\n                'font-size': _isFullDesk ? '16px' : '10px',\r\n                'font-weight': '700'\r\n            });\r\n            $footer.append($resBtn);\r\n        }\r\n\r\n        \/\/ v4.9ds : phrase \"Si vous souhaitez un autre emplacement...\" retir\u00e9e des espaces standards\r\n        \/\/   (uniformisation visuelle, espace gagn\u00e9 dans le footer)\r\n\r\n        \/\/ \u2500\u2500 Cr\u00e9er un wrapper global qui portera le liser\u00e9 vert \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/ .HTMLUploadfileConteneur garde son box-shadow inset \u2192 overflow:visible\r\n        \/\/ pour que header et footer (qui d\u00e9bordent en haut\/bas) soient dans le liser\u00e9\r\n        \/\/ Solution : envelopper $ufc + $header + $footer dans un wrapper flex\r\n\r\n        \/\/ \u2705 Retirer tous les liser\u00e9s verts r\u00e9siduels \u2014 le wrapper porta le seul outline\r\n        $ufc.css({\r\n            'overflow': 'visible',\r\n            'height': 'auto',\r\n            'min-height': '',\r\n            \/\/ \u2705 Bug 12 : inset gauche\/droite, ext\u00e9rieur haut\/bas\r\n            'box-shadow': 'inset 2px 0 0 #00FF19, inset -2px 0 0 #00FF19, 0 -2px 0 #00FF19, 0 2px 0 #00FF19',\r\n            'border': 'none',\r\n            'background-color': '',\r\n            'display': 'block',\r\n            'margin': '0',\r\n            'padding': '0',\r\n            \/\/ v4.9ds : ancrage absolute pour la croix .via-ah-close\r\n            'position': 'relative'\r\n        });\r\n        \/\/ Nettoyer aussi UploadFileConteneur, UploadFileConteneur, ToBeHidden et dropzone\r\n        $droppable.find('#UploadFileConteneur, .UploadFileConteneur').css({\r\n            'box-shadow': 'none', 'outline': 'none', 'border': 'none'\r\n        });\r\n        $dropZone.css({ 'box-shadow': 'none', 'outline': 'none', 'border': 'none' });\r\n        \/\/ Nettoyer aussi le parent .ToBeHidden qui peut avoir un transform + outline\r\n        $droppable.find('.OrdiMobileConteneurClass').css({ 'box-shadow': 'none', 'outline': 'none', 'border': 'none' });\r\n\r\n\r\n        \/\/ \u2705 Wrapper global lis\u00e9r\u00e9 vert (header + ufc + footer)\r\n        \/\/   v4.9ds : position:relative pour ancrer la croix .via-ah-close dans le coin haut-droite\r\n        const $wrapper = jQuery('<div class=\"via-ad-wrapper\" style=\"' +\r\n            'display:flex;flex-direction:column;box-sizing:border-box;' +\r\n            'background:#fff;position:relative;' +\r\n        '\">' + '<\/div>');\r\n\r\n        $ufc.before($wrapper);\r\n        $wrapper.append($header);\r\n        $wrapper.append($ufc);\r\n        $wrapper.append($footer);\r\n        \/\/ v4.9ds : croix ancr\u00e9e \u00e0 $ufc (zone d'aper\u00e7u blanche), pas au header bleu\/jaune\r\n        $ufc.append($closeBtn);\r\n\r\n        \/\/ max-height + height via setProperty - re-applique apres Elementor (setTimeout)\r\n        \/\/ Desktop (userAgent) \u2192 hauteur fixe 170px (full desktop + desktop reduit)\r\n        \/\/ Mobile device r\u00e9el \u2192 height:auto, plafonn\u00e9 \u00e0 200px\r\n        \/\/ v4.9cy : Ele0A desktop \u2192 260px (comme les espaces agrandis \u22651200px) car les styles\r\n        \/\/          inline pos\u00e9s ici gagnent sur les r\u00e8gles CSS @media.\r\n        (function(_el) {\r\n            var _isDeskUA = UIManager.isDesktop();\r\n            var _hVal1 = 'auto';\r\n            var _maxH1;\r\n            \/\/ v4.9ds : Ele0A align\u00e9 sur standards (280) + 5px suppl\u00e9mentaires sur Ele0A\r\n            if (_isEle0A ? _isDeskUA : false) {\r\n                _maxH1 = '285px';\r\n            } else if (_isDeskUA) {\r\n                _maxH1 = '280px';\r\n            } else {\r\n                _maxH1 = '200px';\r\n            }\r\n            function _applyH() {\r\n                _el.style.setProperty('max-height', _maxH1, 'important');\r\n                _el.style.setProperty('height', _hVal1, 'important');\r\n                _el.style.setProperty('overflow', 'hidden', 'important');\r\n            }\r\n            _applyH();\r\n            setTimeout(_applyH, 100);\r\n            setTimeout(_applyH, 500);\r\n        })($ufc[0]);\r\n\r\n        \/\/ v4.9ds : min-height sur #drop_file_zone_achat = hauteur espace standard - header - footer\r\n        \/\/          \u00c0 chaque dimension correspond la m\u00eame logique que _applyH ci-dessus.\r\n        \/\/          La fonction est stock\u00e9e sur le droppable pour pouvoir \u00eatre rappel\u00e9e au resize.\r\n        (function(_dz, _hdr, _ftr, _drop) {\r\n            if (!_dz) return;\r\n            function _applyDzMinH(_phase) {\r\n                var _isDeskUA = UIManager.isDesktop();\r\n                var _ufcH;\r\n                \/\/ v4.9ds : Ele0A align\u00e9 sur standards (280) + 5px suppl\u00e9mentaires sur Ele0A\r\n                if (_isEle0A ? _isDeskUA : false) { _ufcH = 285; }\r\n                else if (_isDeskUA)               { _ufcH = 280; }\r\n                else                              { _ufcH = 200; }\r\n                var _hdrH = _hdr && _hdr.offsetHeight ? _hdr.offsetHeight : 0;\r\n                var _ftrH = _ftr && _ftr.offsetHeight ? _ftr.offsetHeight : 0;\r\n                var _dzMinH = Math.max(0, _ufcH - _hdrH - _ftrH);\r\n                _dz.style.setProperty('min-height', _dzMinH + 'px', 'important');\r\n                _dz.style.setProperty('height', _dzMinH + 'px', 'important');\r\n                _dz.style.setProperty('flex', '1 1 auto', 'important');\r\n                \/\/ v4.9ds DIAG : mesurer toutes les dimensions cl\u00e9s pour comprendre l'\u00e9cart Ele0A vs standards\r\n                var _ufc = _dz.closest('.HTMLUploadfileConteneur');\r\n                var _wrap = _dz.closest('.via-ad-wrapper');\r\n                var _img = _dz.querySelector('img, video, .doc-preview-container');\r\n                var _ufcCS = _ufc ? window.getComputedStyle(_ufc) : null;\r\n                var _wrapCS = _wrap ? window.getComputedStyle(_wrap) : null;\r\n                var _dzCS = window.getComputedStyle(_dz);\r\n                \/\/ v4.9ds DIAG : aussi rect.height (r\u00e9el \u00e0 l'\u00e9cran apr\u00e8s zoom\/scale) + zoom\/transform sur anc\u00eatres\r\n                var _wrapRect = _wrap ? _wrap.getBoundingClientRect() : null;\r\n                var _ufcRect = _ufc ? _ufc.getBoundingClientRect() : null;\r\n                var _dzRect = _dz.getBoundingClientRect();\r\n                \/\/ Chercher zoom\/transform sur les 5 anc\u00eatres jusqu'au droppable\r\n                var _zoomChain = [];\r\n                var _curEl = _wrap;\r\n                for (var _zi = 0; _zi < 6; _zi++) {\r\n                    if (!_curEl) { break; }\r\n                    var _csEl = window.getComputedStyle(_curEl);\r\n                    var _zm = _csEl.zoom;\r\n                    var _tr = _csEl.transform;\r\n                    var _hasTr = _tr ? (_tr !== 'none') : false;\r\n                    var _hasZoom = (_zm !== '1');\r\n                    if (_hasZoom ? true : _hasTr) {\r\n                        _zoomChain.push((_curEl.tagName + (_curEl.id ? '#' + _curEl.id : '.' + (_curEl.className || '').split(' ')[0])) + ' zoom:' + _zm + ' tr:' + _tr);\r\n                    }\r\n                    _curEl = _curEl.parentElement;\r\n                }\r\n                \/\/ v4.9ds DIAG ULTIME : cha\u00eene compl\u00e8te des anc\u00eatres avec rect.h pour identifier l'\u00e9cart\r\n                var _ancChain = [];\r\n                var _ancEl = _wrap;\r\n                for (var _ai = 0; _ai < 12; _ai++) {\r\n                    if (!_ancEl) { break; }\r\n                    var _aRect = _ancEl.getBoundingClientRect();\r\n                    var _aCS = window.getComputedStyle(_ancEl);\r\n                    var _tag = _ancEl.tagName;\r\n                    var _id = _ancEl.id ? ('#' + _ancEl.id) : '';\r\n                    var _cls = (_ancEl.className || '').split(' ')[0] || '';\r\n                    if (_cls) { _cls = '.' + _cls; }\r\n                    _ancChain.push(_tag + _id + _cls + ' [offH:' + _ancEl.offsetHeight + ' rectH:' + Math.round(_aRect.height) + ' zoom:' + _aCS.zoom + ' tr:' + (_aCS.transform === 'none' ? 'none' : _aCS.transform.substring(0, 30)) + ']');\r\n                    _ancEl = _ancEl.parentElement;\r\n                }\r\n                console.log('[DIAG H ' + _phase + '] '\r\n                    + (_isEle0A ? 'ELE0A' : 'STANDARD ' + (_dz.closest('.droppable') ? _dz.closest('.droppable').id : '?'))\r\n                    + ' | _ufcH:' + _ufcH + ' hdr:' + _hdrH + ' ftr:' + _ftrH + ' dzMinH:' + _dzMinH\r\n                    + '\\n  WRAPPER offsetH:' + (_wrap ? _wrap.offsetHeight : 'null') + ' rect.h:' + (_wrapRect ? Math.round(_wrapRect.height) : 'null')\r\n                    + '\\n  UFC offsetH:' + (_ufc ? _ufc.offsetHeight : 'null') + ' rect.h:' + (_ufcRect ? Math.round(_ufcRect.height) : 'null')\r\n                    + '\\n  DZ offsetH:' + _dz.offsetHeight + ' rect.h:' + Math.round(_dzRect.height)\r\n                    + '\\n  IMG offsetH:' + (_img ? _img.offsetHeight : 'null')\r\n                    + '\\n  CHAIN ancestors:\\n    ' + _ancChain.join('\\n    ')\r\n                );\r\n            }\r\n            _applyDzMinH('T+0');\r\n            setTimeout(function() { _applyDzMinH('T+100'); }, 100);\r\n            setTimeout(function() { _applyDzMinH('T+500'); }, 500);\r\n            \/\/ v4.9ds : exposer pour appel depuis handler resize\r\n            if (_drop) { _drop._applyDzMinH = _applyDzMinH; }\r\n        })($dropZone[0], $header[0], $footer[0], $droppable[0]);\r\n\r\n        \/\/ Sites pays : remonter le wrapper\r\n        if (!_isEle0A ? window === window.top : false) {\r\n            \/\/ \ud83d\udd0d DIAG : loguer la d\u00e9cision de branche + contexte\r\n            console.log('[DIAG _buildAdOverlay] branche sites pays', {\r\n                _isDesktop: _isDesktop,\r\n                isMobile: UIManager.isMobile(),\r\n                outerWidth: window.outerWidth,\r\n                innerWidth: window.innerWidth,\r\n                htmlClass: document.documentElement.className,\r\n                hasRegieClass: document.documentElement.classList.contains('via-regie-iframe'),\r\n                droppableId: $droppable.attr('id')\r\n            });\r\n            if (_isDesktop) {\r\n                \/\/ Kit creation : adjustDesktopLayout modifie deja le droppable margin\r\n                \/\/ -> wrapper margin-top reduit pour eviter le chevauchement\r\n                var _isKitDrop = window._lastDropWasKit === true;\r\n                \/\/ \u2705 DVM (desktop version mobile, inner<1000) : text-editor Elementor pose 297px\r\n                \/\/    \u2192 wrapper trop bas. -150px.\r\n                \/\/    Plein \u00e9cran desktop : -30px (l\u00e9ger d\u00e9calage, coh\u00e9rent avec post-resize).\r\n                var _isDVM = (window.innerWidth < 1000);\r\n                var _mt = _isKitDrop ? '30px' : (_isDVM ? '-150px' : '0px');\r\n                if ($wrapper[0]) {\r\n                    $wrapper[0].style.setProperty('margin-top', _mt, 'important');\r\n                    $wrapper[0].style.setProperty('margin-bottom', _isKitDrop ? '20px' : '15px', 'important');\r\n                }\r\n                \/\/ \u2705 Article (body.single) ou page secteur (body.page) au d\u00e9p\u00f4t plein \u00e9cran :\r\n                \/\/    80px en-dessous du droppable pour \u00e9viter le chevauchement avec le contenu.\r\n                if (!_isDVM) { if (!_isKitDrop) {\r\n                    var _isArticleOrSecteurDep = document.body.classList.contains('single') || document.body.classList.contains('page');\r\n                    if (_isArticleOrSecteurDep) {\r\n                        if ($droppable[0]) {\r\n                            $droppable[0].style.setProperty('margin-bottom', '80px', 'important');\r\n                            console.log('[DIAG _buildAdOverlay] article\/secteur plein \u00e9cran \u2192 droppable.margin-bottom: 80px');\r\n                        }\r\n                    }\r\n                } }\r\n                $wrapper.attr('data-deposited-mode', 'desktop');\r\n                console.log('[DIAG _buildAdOverlay] branche DESKTOP \u2192 margin-top:', _mt, '(DVM=' + _isDVM + ')');\r\n                \/\/ \u2705 D\u00e9clencher un resize simul\u00e9 300ms apr\u00e8s le d\u00e9p\u00f4t pour que le handler resize\r\n                \/\/    applique ses positions (algo inter-espaces, article\/secteur, DVM cleanup, etc.).\r\n                \/\/    Actif aussi en DVM (cas d\u00e9p\u00f4t en version mobile qui chevauche contenu au-dessus).\r\n                \/\/ v4.9ds : retir\u00e9 la condition !_isKitDrop \u2014 les Communiqu\u00e9\/Interview cr\u00e9\u00e9s\r\n                \/\/   depuis le kit ont aussi besoin du resize pour recalculer les espaces en dessous\r\n                setTimeout(function() {\r\n                    try {\r\n                        window.dispatchEvent(new Event('resize'));\r\n                        console.log('[DIAG _buildAdOverlay] resize simul\u00e9 post-d\u00e9p\u00f4t d\u00e9clench\u00e9 (DVM=' + _isDVM + ', kitDrop=' + _isKitDrop + ')');\r\n                    } catch (e) {\r\n                        console.warn('[DIAG _buildAdOverlay] resize simul\u00e9 a throw:', e);\r\n                    }\r\n                }, 300);\r\n                \/\/ \ud83d\udd0d DIAG d\u00e9taill\u00e9 : mesurer la position r\u00e9elle apr\u00e8s application\r\n                setTimeout(function() {\r\n                    var _w = $wrapper[0];\r\n                    var _d = $droppable[0];\r\n                    if (!_w) { return; }\r\n                    var _wRect = _w.getBoundingClientRect();\r\n                    var _dRect = _d ? _d.getBoundingClientRect() : null;\r\n                    var _wCS = getComputedStyle(_w);\r\n                    var _parent = _w.parentElement;\r\n                    var _parentRect = _parent ? _parent.getBoundingClientRect() : null;\r\n                    var _parentCS = _parent ? getComputedStyle(_parent) : null;\r\n                    \/\/ Essayer de trouver l'\u00e9l\u00e9ment juste au-dessus dans la page\r\n                    var _prevEl = null;\r\n                    if (_d) {\r\n                        var _sib = _d.previousElementSibling;\r\n                        while (_sib) {\r\n                            var _sr = _sib.getBoundingClientRect();\r\n                            if (_sr.height > 10) { _prevEl = _sib; break; }\r\n                            _sib = _sib.previousElementSibling;\r\n                        }\r\n                    }\r\n                    var _prevRect = _prevEl ? _prevEl.getBoundingClientRect() : null;\r\n                    console.log('[DIAG DEPOT DVM]', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        wrapperMT_computed: _wCS.marginTop,\r\n                        wrapperRect: { top: Math.round(_wRect.top), bottom: Math.round(_wRect.bottom), height: Math.round(_wRect.height) },\r\n                        droppableId: _d ? _d.id : '?',\r\n                        droppableRect: _dRect ? { top: Math.round(_dRect.top), bottom: Math.round(_dRect.bottom), height: Math.round(_dRect.height) } : null,\r\n                        parentTag: _parent ? (_parent.tagName + '.' + (_parent.className || '').split(' ').slice(0, 3).join('.')) : '?',\r\n                        parentDisplay: _parentCS ? _parentCS.display : '?',\r\n                        parentRect: _parentRect ? { top: Math.round(_parentRect.top), bottom: Math.round(_parentRect.bottom), height: Math.round(_parentRect.height) } : null,\r\n                        prevSibling: _prevEl ? (_prevEl.tagName + '.' + (_prevEl.className || '').split(' ').slice(0, 2).join('.')) : '?',\r\n                        prevSiblingRect: _prevRect ? { top: Math.round(_prevRect.top), bottom: Math.round(_prevRect.bottom) } : null,\r\n                        gapAboveDroppable: (_prevRect ? (_dRect ? Math.round(_dRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        gapAboveWrapper: (_prevRect ? (_wRect ? Math.round(_wRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        inner: window.innerWidth,\r\n                        outer: window.outerWidth\r\n                    });\r\n                }, 150);\r\n            } else {\r\n                \/\/ Sites pays d\u00e9p\u00f4t mobile : -140px (valeur historique qui marche)\r\n                if ($wrapper[0]) {\r\n                    $wrapper[0].style.setProperty('margin-top', '-140px', 'important');\r\n                    $wrapper[0].style.setProperty('margin-bottom', '0px', 'important');\r\n                }\r\n                $wrapper.attr('data-deposited-mode', 'mobile');\r\n                console.log('[DIAG _buildAdOverlay] branche MOBILE \u2192 margin-top: -140px !important');\r\n                \/\/ \u2705 Resize simul\u00e9 300ms apr\u00e8s le d\u00e9p\u00f4t pour que le handler resize applique\r\n                \/\/    ses positions (cleanup DVM, wrapper.mt\/mb=25px, etc.).\r\n                setTimeout(function() {\r\n                    try {\r\n                        window.dispatchEvent(new Event('resize'));\r\n                        console.log('[DIAG _buildAdOverlay] resize simul\u00e9 post-d\u00e9p\u00f4t MOBILE d\u00e9clench\u00e9');\r\n                    } catch (e) {\r\n                        console.warn('[DIAG _buildAdOverlay] resize simul\u00e9 MOBILE a throw:', e);\r\n                    }\r\n                }, 300);\r\n                \/\/ \ud83d\udd0d DIAG d\u00e9taill\u00e9 : mesurer la position r\u00e9elle apr\u00e8s application\r\n                setTimeout(function() {\r\n                    var _w = $wrapper[0];\r\n                    var _d = $droppable[0];\r\n                    if (!_w) { return; }\r\n                    var _wRect = _w.getBoundingClientRect();\r\n                    var _dRect = _d ? _d.getBoundingClientRect() : null;\r\n                    var _wCS = getComputedStyle(_w);\r\n                    var _parent = _w.parentElement;\r\n                    var _parentRect = _parent ? _parent.getBoundingClientRect() : null;\r\n                    var _parentCS = _parent ? getComputedStyle(_parent) : null;\r\n                    var _prevEl = null;\r\n                    if (_d) {\r\n                        var _sib = _d.previousElementSibling;\r\n                        while (_sib) {\r\n                            var _sr = _sib.getBoundingClientRect();\r\n                            if (_sr.height > 10) { _prevEl = _sib; break; }\r\n                            _sib = _sib.previousElementSibling;\r\n                        }\r\n                    }\r\n                    var _prevRect = _prevEl ? _prevEl.getBoundingClientRect() : null;\r\n                    console.log('[DIAG DEPOT MOBILE]', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        wrapperMT_computed: _wCS.marginTop,\r\n                        wrapperRect: { top: Math.round(_wRect.top), bottom: Math.round(_wRect.bottom), height: Math.round(_wRect.height) },\r\n                        droppableId: _d ? _d.id : '?',\r\n                        droppableRect: _dRect ? { top: Math.round(_dRect.top), bottom: Math.round(_dRect.bottom), height: Math.round(_dRect.height) } : null,\r\n                        parentTag: _parent ? (_parent.tagName + '.' + (_parent.className || '').split(' ').slice(0, 3).join('.')) : '?',\r\n                        parentDisplay: _parentCS ? _parentCS.display : '?',\r\n                        parentRect: _parentRect ? { top: Math.round(_parentRect.top), bottom: Math.round(_parentRect.bottom), height: Math.round(_parentRect.height) } : null,\r\n                        prevSibling: _prevEl ? (_prevEl.tagName + '.' + (_prevEl.className || '').split(' ').slice(0, 2).join('.')) : '?',\r\n                        prevSiblingRect: _prevRect ? { top: Math.round(_prevRect.top), bottom: Math.round(_prevRect.bottom) } : null,\r\n                        gapAboveDroppable: (_prevRect ? (_dRect ? Math.round(_dRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        gapAboveWrapper: (_prevRect ? (_wRect ? Math.round(_wRect.top - _prevRect.bottom) : '?') : '?'),\r\n                        inner: window.innerWidth,\r\n                        outer: window.outerWidth\r\n                    });\r\n                }, 150);\r\n            }\r\n            \/\/ \ud83d\udd0d DIAG : v\u00e9rifier l'\u00e9tat effectif apr\u00e8s application\r\n            setTimeout(function() {\r\n                var _w = $wrapper[0];\r\n                if (_w) {\r\n                    var _cs = getComputedStyle(_w);\r\n                    console.log('[DIAG _buildAdOverlay] \u00e9tat wrapper apr\u00e8s 100ms', {\r\n                        inlineStyle: _w.getAttribute('style'),\r\n                        computedMarginTop: _cs.marginTop,\r\n                        computedMarginBottom: _cs.marginBottom,\r\n                        boundingRect: { top: _w.getBoundingClientRect().top, height: _w.getBoundingClientRect().height }\r\n                    });\r\n                }\r\n            }, 100);\r\n        }\r\n\r\n        \/\/ \u2705 Desktop : ajouter margin-bottom pour que le footer ne chevauche pas le contenu\r\n        if (_isDesktop) {\r\n            setTimeout(function() {\r\n                var _footerH = $footer[0] ? $footer[0].getBoundingClientRect().height : 0;\r\n                var _headerH = $header[0] ? $header[0].getBoundingClientRect().height : 0;\r\n                if (_footerH > 0) {\r\n                    $wrapper.css('margin-bottom', (_footerH + _headerH) + 'px');\r\n                }\r\n            }, 50);\r\n        }\r\n\r\n        \/\/ \u2705 body = reset margin du dropzone\r\n        $dropZone.css({ 'margin-top': '0', 'margin-bottom': '0' });\r\n        $dropZone.closest('.UploadFileConteneur').css({ 'margin-top': '0', 'margin-bottom': '0' });\r\n\r\n        \/\/ \u2705 v2.7.3 : Mobile \u2014 largeur = viewport moins marges, hauteur auto\r\n        if (!_isFullDesk) { \/\/ mobile + desktop mode mobile\r\n            \/\/ \u2705 R\u00e9f\u00e9rence CSS : offsetWidth du premier espace standard sans annonce\r\n            \/\/ offsetWidth = largeur CSS pr\u00e9-transform \u2192 m\u00eame base pour tous les espaces\r\n            var _refCssW = 0;\r\n            jQuery('.droppable').not('[id=\"Ele0A\"]').not('[data-via-ad-loaded=\"true\"]').each(function() {\r\n                var $_tbh = jQuery(this).closest('.ToBeHidden');\r\n                if ($_tbh.length) {\r\n                    var _w = $_tbh[0].offsetWidth;\r\n                    if (_w > 50) { _refCssW = _w; return false; }\r\n                }\r\n            });\r\n            if (_refCssW < 50) {\r\n                var $_fbTbh = jQuery('.droppable').not('[id=\"Ele0A\"]').first().closest('.ToBeHidden');\r\n                if ($_fbTbh.length) { _refCssW = $_fbTbh[0].offsetWidth; }\r\n            }\r\n            if (_refCssW < 50) { _refCssW = Math.round(window.innerWidth * 0.90); }\r\n\r\n            \/\/ Largeur CSS du wrapper = m\u00eame offsetWidth CSS que la r\u00e9f\u00e9rence standard\r\n            \/\/ Ele0A : son parent ToBeHidden est plus large \u2192 appliquer ratio rendu vs CSS pour \u00e9quilibrer\r\n            var _wrapCssW = _refCssW;\r\n            if (_isEle0A) {\r\n                var $_e0ATbh = $droppable.closest('.ToBeHidden');\r\n                if ($_e0ATbh.length) {\r\n                    var _e0ARendered = $_e0ATbh[0].getBoundingClientRect().width;\r\n                    var _e0ACss      = $_e0ATbh[0].offsetWidth || _e0ARendered;\r\n                    \/\/ _refCssW * (e0A_rendered \/ e0A_css) = largeur \u00e9cran cible pour Ele0A\r\n                    \/\/ Pour compenser l'exc\u00e9dent de 10% : appliquer le ratio scale Ele0A\r\n                    if (_e0ACss > 0 ? _e0ARendered > 0 : false) {\r\n                        var _e0AScale = _e0ARendered \/ _e0ACss;\r\n                        \/\/ CSS width pour que le rendu = _refRenderedW\r\n                        var $_stdTbhRef = jQuery('.droppable').not('[id=\"Ele0A\"]').not('[data-via-ad-loaded=\"true\"]').first().closest('.ToBeHidden');\r\n                        var _refRendered = $_stdTbhRef.length ? $_stdTbhRef[0].getBoundingClientRect().width : _refCssW;\r\n                        if (_e0AScale > 0.1) { _wrapCssW = Math.round(_refRendered \/ _e0AScale); }\r\n                    }\r\n                }\r\n            }\r\n\r\n            var _wrapPx = Math.round(_wrapCssW) + 'px';\r\n            var $_wrap = $droppable.find('.via-ad-wrapper');\r\n            \/\/ Sites pays : width 100% -> responsive au zoom CSS sans JS au resize\r\n            var _isSitesPays = (window === window.top);\r\n            $_wrap.css({\r\n                'width': _isSitesPays ? '100%' : _wrapPx,\r\n                'max-width': _isSitesPays ? '100%' : _wrapPx,\r\n                'box-sizing': 'border-box',\r\n                'position': 'relative',\r\n                'left': '0',\r\n                'transform': 'none'\r\n            });\r\n            \/\/ \u2705 Laisser les parents en overflow:visible pour ne pas clipper le wrapper\r\n            if (!_isEle0A) {\r\n                $droppable.find('.OrdiMobileConteneurClass').css({ 'overflow': 'visible' });\r\n                $droppable.css({ 'overflow': 'visible' });\r\n            }\r\n            $ufc.css({ 'width': '100%', 'max-width': 'none', 'min-height': '0',\r\n                        'margin': '0', 'padding': '0' });\r\n            (function(_el2) {\r\n                var _omc = _el2.closest ? _el2.closest('.OrdiMobileConteneurClass') : null;\r\n                \/\/ Desktop version mobile (userAgent desktop + fen\u00eatre r\u00e9duite) \u2192 hauteur fixe 170px\r\n                \/\/ Mobile device r\u00e9el \u2192 height:auto, plafonn\u00e9 \u00e0 200px\r\n                var _isDeskMobile = UIManager.isDesktop();\r\n                var _hVal = 'auto';\r\n                var _maxH = _isDeskMobile ? '280px' : '200px';  \/* v4.9ds : 170 \u2192 280 align\u00e9 sur _applyH principal *\/\r\n                function _applyH2() {\r\n                    \/\/ UFC\r\n                    _el2.style.setProperty('height', _hVal, 'important');\r\n                    _el2.style.setProperty('max-height', _maxH, 'important');\r\n                    _el2.style.setProperty('overflow', 'hidden', 'important');\r\n                    \/\/ Parent OMC : hauteur auto, pas de clamp (sinon header+footer clipp\u00e9s)\r\n                    if (_omc) {\r\n                        _omc.style.setProperty('height', 'auto', 'important');\r\n                        _omc.style.removeProperty('max-height');\r\n                        _omc.style.removeProperty('overflow');\r\n                    }\r\n                }\r\n                _applyH2();\r\n                setTimeout(_applyH2, 100);\r\n                setTimeout(_applyH2, 500);\r\n                setTimeout(_applyH2, 1000);\r\n            })($ufc[0]);\r\n            \/\/ Remonter le wrapper : 0 sur sites pays (zoom CSS, pas de scale)\r\n            if (!_isEle0A) {\r\n                var $_wrapStd = $droppable.find('.via-ad-wrapper');\r\n                var _isInIframe = (window !== window.top);\r\n                if (_isInIframe) {\r\n                    \/\/ iframe r\u00e9gie : compensation scale\r\n                    var _ufcMt = parseFloat($ufc.css('margin-top')) || 0;\r\n                    $_wrapStd.css('margin-top', (_ufcMt - 80) + 'px');\r\n                } else {\r\n                    \/\/ sites pays : DVM = -150, plein \u00e9cran = 0\r\n                    if ($_wrapStd[0]) {\r\n                        var _isDVMStd = (window.innerWidth < 1000);\r\n                        $_wrapStd[0].style.setProperty('margin-top', _isDVMStd ? '-150px' : '0px', 'important');\r\n                    }\r\n                }\r\n            }\r\n            \/\/ v4.9ds : dropzone width seulement (height\/min-height g\u00e9r\u00e9s par _applyDzMinH)\r\n            var _imgMaxH = '160px';\r\n            $dropZone.css({ 'width': '100%', 'margin': '0' });\r\n            \/\/ v4.9ds : pour que l'image occupe TOUTE la box du dropZone (comme Ele0A) tout\r\n            \/\/   en restant enti\u00e8re sans crop, on utilise width:100% + height:100% +\r\n            \/\/   object-fit:contain. Le dropZone a sa height pos\u00e9e par _applyDzMinH et le\r\n            \/\/   max-height CSS lignes 7910\/7929 plafonne. object-fit:contain garantit que\r\n            \/\/   l'image enti\u00e8re est visible (letterbox automatique si ratio diff\u00e9rent).\r\n            $dropZone.find('img, video').css({ 'max-width': '100%', 'width': '100%', 'max-height': _imgMaxH, 'height': '100%', 'display': 'block', 'object-fit': 'contain' });\r\n            \/\/ \u2705 Bug 11 : remonter drop_file_zone_achat de 2px sur mobile + margin-bottom 2px\r\n            $dropZone[0].style.setProperty('margin-top', '-2px', 'important');\r\n            $dropZone[0].style.setProperty('margin-bottom', '2px', 'important');\r\n        }\r\n\r\n        \/\/ v4.9ds : Clamp universel image uniquement (le min-height\/height du dropZone\r\n        \/\/          est g\u00e9r\u00e9 par _applyDzMinH ci-dessus). On ne touche plus au dropZone ici.\r\n        (function() {\r\n            var _imgMaxH2 = '160px';\r\n            $dropZone.find('img, video').css({\r\n                'max-height': _imgMaxH2, 'height': '100%', 'object-fit': 'contain'\r\n            });\r\n        })();\r\n\r\n        \/\/ \u2705 Masquer TOUS les anciens \u00e9l\u00e9ments de position\/ref (d\u00e9sormais dans le header)\r\n        $droppable.find('.DeplaceAnnonce, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire').hide();\r\n        $droppable.find('.GlisserDeposerConteneur, .OUClass, .ChoisirEspacePublicitaireClass, .ChoisirEspacePublicitaire2ndLigne, .UploadIci').hide();\r\n        $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        \/\/ setProperty !important pour \u00e9craser les display:block!important d'adjustMobileLayout\r\n        $droppable.find('.PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer').each(function() {\r\n            this.style.setProperty('display', 'none', 'important');\r\n        });\r\n        \/\/ \u2705 Supprimer via-position-label (position maintenant dans le header)\r\n        $droppable.find('.via-position-label').remove();\r\n\r\n        \/\/ \u2699\ufe0f Override text-editor au d\u00e9p\u00f4t : neutralise la height explicite (ex: 297px)\r\n        \/\/    que Elementor pose sur .elementor-widget-text-editor \u00e0 certains breakpoints \u00e9troits.\r\n        \/\/    Sans \u00e7a, le wrapper se retrouve d\u00e9cal\u00e9 vers le bas dans l'OMC et chevauche le\r\n        \/\/    contenu en dessous (pb visible en desktop version mobile).\r\n        if (typeof window._viaOverrideTextEditor === 'function') {\r\n            var _omcForOverride = $droppable.find('.OrdiMobileConteneurClass')[0];\r\n            if (_omcForOverride) {\r\n                window._viaOverrideTextEditor(_omcForOverride);\r\n            }\r\n        }\r\n\r\n        console.log('\u2705 [via-ad-overlay] header+footer+wrapper inject\u00e9s | Ele0A:', _isEle0A, '| ref:', _emplacement);\r\n    },\r\n\r\n    adjustLayoutAfterUpload($dropZone) {\r\n        console.log('\ud83d\udcf1 adjustLayoutAfterUpload \u2014 isMobile():', this.isMobile(), 'outerWidth:', window.outerWidth);\r\n        if (this.isMobile()) {\r\n            this.adjustMobileLayout($dropZone);\r\n            \r\n            \/\/ Repositionner le bouton R\u00e9server APR\u00c8S les ajustements de layout\r\n            setTimeout(() => {\r\n                const $droppable = $dropZone.closest('.droppable');\r\n                \/\/ \u2705 v2.6 : Ele0A popup \u2014 skip repositionnement offset (g\u00e9r\u00e9 dans _isEle0AMobAdj)\r\n                if ($droppable.attr('id') === 'Ele0A') { return; }\r\n                const $btn = $droppable.find('.reserver-dynamic-container').add($droppable.next('.reserver-dynamic-container')).first();\r\n                const $lisere = $droppable.find('.HTMLUploadfileConteneur');\r\n                if ($btn.length ? $lisere.length : false) {\r\n                    const lisereBottom = $lisere[0].getBoundingClientRect().bottom;\r\n                    const btnTop = $btn[0].getBoundingClientRect().top;\r\n                    const offset = lisereBottom - btnTop - 48;\r\n                    \r\n                    \/\/ 15px suppl\u00e9mentaires pour homepage et articles\r\n                    let extraOffset = 0;\r\n                    const isHomepage = window.location.pathname === \"\/\" || window.location.pathname === \"\/en\/\";\r\n                    const isSectorPage = $('body').hasClass('page');\r\n                    if (isHomepage) {\r\n                        extraOffset = 13;\r\n                    }\r\n                    if (!isSectorPage) {\r\n                        extraOffset = 15;\r\n                    }\r\n                    \r\n                    \/\/ \u2705 v2.0.9 : +4px pour documents (communiqu\u00e9\/interview\/PDF) avec pr\u00e9sentation HTML\r\n                    if ($droppable.find('.doc-preview-container:visible').length > 0) {\r\n                        extraOffset += 4;\r\n                    }\r\n                    \r\n                    \/\/ v2.9 : site pays direct \u2014 r\u00e9duire l'offset (layout neutre vs iframe)\r\n                    var _sitePaysAdj = (window === window.top) ? 10 : 0;\r\n                    $btn.css({\r\n                        'margin-top': (offset + extraOffset + 20 - _sitePaysAdj) + 'px',\r\n                        'margin-bottom': (-(offset + extraOffset) - 100 + _sitePaysAdj) + 'px'\r\n                    });\r\n                    console.log('\ud83d\udcd0 Repositionnement mobile R\u00e9server:', { lisereBottom, btnTop, offset });\r\n                }\r\n            }, 50);\r\n        } else {\r\n            this.adjustDesktopLayout($dropZone);\r\n        }\r\n    },\r\n    \r\n    adjustMobileLayout($dropZone) {\r\n        console.log('\ud83d\udcf1 adjustMobileLayout START');\r\n        console.log('\ud83d\udcf1 AchatEspaceCall:', StateManager.get(\"AchatEspaceCall\"));\r\n        console.log('\ud83d\udcf1 PageAjoutModifAnnonce:', StateManager.get(\"PageAjoutModifAnnonce\"));\r\n        console.log('\ud83d\udcf1 window===window.top:', window === window.top);\r\n        console.log('\ud83d\udcf1 pathname:', location.pathname);\r\n        \r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \r\n        \/\/ \u2705 v2.4.3 : Masquer les textes position\/label\/r\u00e9f\u00e9rence (comme sur desktop)\r\n        $droppable\r\n            .find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 v2.6 : Renseigner .PositionEspacePublicitaire sur mobile (manquait vs desktop)\r\n        (function() {\r\n            var _rankPosMob = StateManager.get('Rank_Emplacement_Page_Web') || $droppable.attr('id') || '';\r\n            var _posLibMob = PreviewRenderer._getPositionLibelle(_rankPosMob);\r\n            if (_posLibMob) {\r\n                $droppable\r\n                    .find('.PositionEspacePublicitaire')\r\n                    .text(_posLibMob)\r\n                    .each(function() {\r\n                        this.style.setProperty('display', 'block', 'important');\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        \/\/ \u2705 Scop\u00e9 au $dropZone courant (\u00e9vite d'affecter les autres espaces pub)\r\n        $dropZone.closest('.droppable').find('#CroixResetAnnonce').css({'zoom': '75%'});\r\n\r\n        \/\/ \u2705 v2.4.3 : Remonter la croix reset sur mobile (override margin-top Elementor)\r\n        \/\/ \u2705 v2.4.5 : Poser aussi margin-right ici (Entete.txt ne l'atteint pas pour Ele0A)\r\n        var _croixContainer = $dropZone.closest('.OrdiMobileConteneurClass').find('.CroixResetAnnonceContainer')[0];\r\n        if (_croixContainer) {\r\n            var _isEle0ACroix = ($dropZone.closest('.droppable').attr('id') === 'Ele0A');\r\n            var _isTopWindowCroix = (window === window.top);\r\n            \/\/ v2.9 : site pays (mt=0px sur OrdiMobile) \u2192 compensate +65px vs ancien 65px\r\n            var _mtCroix = StateManager.get(\"PageAjoutModifAnnonce\") === 'Yes' ? '-88px'\r\n                         : (_isTopWindowCroix ? '70px' : '24px');\r\n            _croixContainer.style.setProperty('margin-top', _mtCroix, 'important');\r\n            _croixContainer.style.setProperty('margin-right', _isEle0ACroix ? '17px' : '12px', 'important');\r\n        }\r\n        \r\n        \/\/ \u2705 v2.6 : data-kit-drop = marqueur DOM fiable m\u00eame si _dropFromMiniature d\u00e9j\u00e0 resett\u00e9\r\n        var _droppableMob = $dropZone.closest('.droppable')[0];\r\n        var _isMiniatureAdj = window._dropFromMiniature\r\n            || (_droppableMob ? (_droppableMob.getAttribute('data-kit-drop') === 'true') : false);\r\n        window._dropFromMiniature = false;\r\n        \/\/ Nettoyer data-kit-drop apr\u00e8s lecture (comme le fait adjustDesktopLayout)\r\n        if (_droppableMob) { _droppableMob.removeAttribute('data-kit-drop'); }\r\n\r\n\r\n        if (_isMiniatureAdj ? window.outerWidth <= 1000 : false) {\r\n            \/\/ \u2705 Miniature doc-preview MOBILE : ajuster HTMLUploadfileConteneur \u00e0 la hauteur du contenu\r\n            \/\/ et neutraliser tous les margins Elementor qui d\u00e9calent le $dropZone hors du parent\r\n            var _isDocPreviewMob = $dropZone.find('.doc-preview-container').length > 0;\r\n            var _docH = _isDocPreviewMob ? 144 : 115;\r\n            \/\/ \u2705 v2.6 : Dans l'iframe r\u00e9gie (window !== window.top) \u2192 mt:70px (comme AchatEspaceCall=Yes)\r\n            \/\/ En standalone : mt:-55px (valeur originale)\r\n            \/\/ Raison : UFC top:658 < DROP top:720 avec mt:-55px \u2192 d\u00e9bordement 62px au-dessus du droppable\r\n            var _miniMt = (window !== window.top) ? '70px' : '-55px';\r\n            $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                'height':     _docH + 'px',\r\n                'min-height': _docH + 'px',\r\n                'max-height': _docH + 'px',\r\n                'margin-top': _miniMt,\r\n                'margin-bottom': '0px',\r\n                'overflow': 'hidden'\r\n            });\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px', 'height': (_docH - 6) + 'px'});\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '65px', 'margin-bottom': '40px'});\r\n            \/\/ \u2705 v2.4.9 : Doc-preview \u2014 overflow:visible pour que le liser\u00e9 du haut ne soit pas clipp\u00e9\r\n            if (_isDocPreviewMob) {\r\n                $dropZone.closest('.OrdiMobileConteneurClass').css('overflow', 'visible');\r\n                $dropZone.closest('.OrdiMobileConteneurClass').parent().css('overflow', 'visible');\r\n            }\r\n            \/\/ \u2705 v2.4.9 : Ele0A popup mobile \u2014 neutraliser translateY(-32px) pos\u00e9 par Entete sur EnvoiUlterieurTexte\r\n            var _isEle0AMob = ($dropZone.closest('.droppable').attr('id') === 'Ele0A')\r\n                || ($dropZone.closest('#PopUpMessageAchattest').length > 0);\r\n            if (_isEle0AMob) {\r\n                var $_euTxt = $dropZone.closest('.droppable').find('.EnvoiUlterieurTexte');\r\n                if ($_euTxt[0]) {\r\n                    $_euTxt[0].style.setProperty('transform', 'translateY(0px)', 'important');\r\n                    $_euTxt[0].style.setProperty('margin-top', '0px', 'important');\r\n                }\r\n                \/\/ \u2705 v2.6 : Pb 2 \u2014 centrage vertical doc-preview dans #PopUpMessageAchattest\r\n                var _popupAchat = document.getElementById('PopUpMessageAchattest');\r\n                if (_popupAchat) {\r\n                    _popupAchat.style.setProperty('align-items', 'center', 'important');\r\n                }\r\n                \/\/ Annuler margin-top:23px du media query sur .doc-preview-container\r\n                setTimeout(function() {\r\n                    var _dp = $dropZone.find('.doc-preview-container')[0];\r\n                    if (_dp) { _dp.style.setProperty('margin-top', '0px', 'important'); }\r\n                }, 50);\r\n\r\n                \/\/ \u2705 v2.6 : Pb 8\/9 \u2014 miniature Ele0A : appliquer les m\u00eames fixes que _isEle0AMobAdj\r\n                \/\/ (croix position, masquage \u00e9l\u00e9ments parasites, bouton R\u00e9server)\r\n                var $_dropMini = $dropZone.closest('.droppable');\r\n                if ($_dropMini.attr('id') === 'Ele0A') {\r\n                    $_dropMini.css({'position': 'relative'});\r\n                    \/\/ Masquer EnvoiUlterieur (inutile si fichier d\u00e9pos\u00e9) et R\u00e9server statique\r\n                    $_dropMini.find('.elementor-field-group-EnvoiUlterieur').each(function() {\r\n                        this.style.setProperty('display', 'none', 'important');\r\n                    });\r\n                    $_dropMini.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').each(function() {\r\n                        this.style.setProperty('display', 'none', 'important');\r\n                    });\r\n                    \/\/ Croix en position absolue dans l'UFC\r\n                    var $_ufcMini = $dropZone.closest('.HTMLUploadfileConteneur');\r\n                    $_ufcMini.css('position', 'relative');\r\n                    var _croixMini = $_dropMini.find('.CroixResetAnnonceContainer')[0];\r\n                    if (_croixMini) {\r\n                        if ($_ufcMini[0]) { $_ufcMini[0].appendChild(_croixMini); }\r\n                        _croixMini.style.setProperty('position', 'absolute', 'important');\r\n                        _croixMini.style.setProperty('top', '-31px', 'important');\r\n                        _croixMini.style.setProperty('right', '-36px', 'important');\r\n                        _croixMini.style.setProperty('margin', '0px', 'important');\r\n                        _croixMini.style.setProperty('z-index', '9999', 'important');\r\n                    }\r\n                    \/\/ Bouton R\u00e9server dynamique \u2014 plac\u00e9 apr\u00e8s UFC\r\n                    var $_reserverMini = $_dropMini.find('.reserver-dynamic-container').first();\r\n                    if (!$_reserverMini.length) { $_reserverMini = $_dropMini.next('.reserver-dynamic-container'); }\r\n                    if ($_reserverMini.length) {\r\n                        $_ufcMini.after($_reserverMini);\r\n                        $_reserverMini.css({\r\n                            'margin-top': '-27px',\r\n                            'margin-bottom': '4px',\r\n                            'position': 'relative',\r\n                            'z-index': '10',\r\n                            'text-align': 'center',\r\n                            'transform': 'scale(0.8)',\r\n                            'transform-origin': 'top center'\r\n                        });\r\n                    }\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.4.12 : AchatEspaceCall=Yes \u2192 override margins (m\u00eame correction que pour upload direct)\r\n            if (StateManager.get('AchatEspaceCall') === 'Yes') {\r\n                $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n                var _docHAchatMini = _isDocPreviewMob ? 150 : 112;\r\n                $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                    'margin-top': '70px',\r\n                    'margin-bottom': '35px',\r\n                    'min-height': _docHAchatMini + 'px',\r\n                    'height': _isDocPreviewMob ? _docHAchatMini + 'px' : '',\r\n                    'max-height': _isDocPreviewMob ? _docHAchatMini + 'px' : ''\r\n                });\r\n                console.log('\ud83d\udcf1 [miniature] AchatEspaceCall=Yes OVERRIDE: mt=70px | docPreview:', _isDocPreviewMob);\r\n            }\r\n            return;\r\n        }\r\n        \/\/ \u2705 v2.4.10 : Miniature DESKTOP \u2192 layout normal appliqu\u00e9 ci-dessous\r\n        if (_isMiniatureAdj) {\r\n            \/\/ Pas de overflow:hidden \u2014 le liser\u00e9 box-shadow inset doit rester visible\r\n        }\r\n\r\n        \/\/ \u2705 v2.6 : Ele0A popup mobile \u2014 pas de margins ajust\u00e9s (le droppable a ses propres dimensions)\r\n        \/\/ Le fond blanc doit \u00eatre appliqu\u00e9 au droppable lui-m\u00eame, pas \u00e0 HTMLUploadfileConteneur d\u00e9cal\u00e9\r\n        var _isEle0AMobAdj = ($dropZone.closest('.droppable').attr('id') === 'Ele0A');\r\n        console.log('[Ele0A] _isEle0AMobAdj:', _isEle0AMobAdj, '| ViaPopupProcessAchat:', (sessionStorage.getItem('_ViaPopupOpen') === 'Yes'));\r\n        if (_isEle0AMobAdj) {\r\n            var $_ele0ADrop = $dropZone.closest('.droppable');\r\n            \/\/ v2.9 : si ViaPopupProcessAchat present, repositionner les elements hors-lisere\r\n            var _isVPP = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n            console.log('[Ele0A] _isVPP:', _isVPP);\r\n            if (_isVPP) {\r\n                \/\/ DeplaceAnnonceSubContainer : forcer margin-top positif pour rentrer dans le lisere\r\n                var $_das = $_ele0ADrop.find('.DeplaceAnnonceSubContainer');\r\n                if ($_das.length) {\r\n                    $_das[0].style.setProperty('margin-top', '5px', 'important');\r\n                    console.log('[Ele0A] DeplaceAnnonceSubContainer margin-top: 5px');\r\n                }\r\n                \/\/ CroixResetAnnonceContainer : forcer position interne\r\n                var $_crc = $_ele0ADrop.find('.CroixResetAnnonceContainer');\r\n                if ($_crc.length) {\r\n                    $_crc[0].style.setProperty('position', 'absolute', 'important');\r\n                    $_crc[0].style.setProperty('top',   '5px', 'important');\r\n                    $_crc[0].style.setProperty('right', '5px', 'important');\r\n                    $_crc[0].style.setProperty('margin', '0px', 'important');\r\n                    console.log('[Ele0A] CroixResetAnnonceContainer repositionne: top:5px right:5px');\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.6 : Ele0A \u2014 neutraliser la hauteur des boutons format cach\u00e9s\r\n            \/\/ Ne pas utiliser position:absolute\/overflow:hidden \u2192 cache le bouton R\u00e9server\r\n            $_ele0ADrop.css({'position': 'relative'});\r\n            var $_omcEle0A = $dropZone.closest('.OrdiMobileConteneurClass');\r\n            var $_ufcEle0APre = $dropZone.closest('.HTMLUploadfileConteneur');\r\n            \/\/ \u2705 OrdiMobileConteneurClass : flex parent Elementor \u00e9tire le UFC \u2192 forcer flex-start\r\n            $_omcEle0A.css({\r\n                'padding': '0',\r\n                'min-height': '0',\r\n                'height': 'auto',\r\n                'margin-top': '0px',\r\n                'margin-bottom': '0px',\r\n                'align-items': 'flex-start',\r\n                'justify-content': 'flex-start'\r\n            });\r\n            \/\/ \u2705 UFC : align-self:flex-start \u2192 prend sa hauteur naturelle (pas \u00e9tir\u00e9e par l'OMC)\r\n            $_ufcEle0APre.css({\r\n                'margin': '0',\r\n                'min-height': '0',\r\n                'height': 'auto',\r\n                'width': '100%',\r\n                'align-self': 'flex-start',\r\n                'display': 'flex',\r\n                'align-items': 'center',\r\n                'justify-content': 'center'\r\n            });\r\n            $dropZone.css({\r\n                'margin-top': '0px',\r\n                'margin-bottom': '0px',\r\n                'top': '0px',\r\n                'display': 'flex',\r\n                'align-items': 'center',\r\n                'justify-content': 'center',\r\n                'width': '100%'\r\n            });\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '0px', 'margin-bottom': '0px'});\r\n            \/\/ \u2705 Pb 2 : Centrage vertical du doc-preview \u2014 mesur\u00e9 apr\u00e8s rendu\r\n            \/\/ Le flexbox seul ne suffit pas si le conteneur n'a pas de hauteur propre\r\n            setTimeout(function() {\r\n                var _ele0AEl = $_ele0ADrop[0];\r\n                var _docPrevEl = $dropZone.find('.doc-preview-container')[0];\r\n                if (_ele0AEl) {\r\n                    if (_docPrevEl) {\r\n                        var _ele0AH = _ele0AEl.getBoundingClientRect().height;\r\n                        var _docPrevH = _docPrevEl.getBoundingClientRect().height;\r\n                        var _mt = Math.max(0, Math.round((_ele0AH - _docPrevH) \/ 2) - 8);\r\n                        _docPrevEl.style.setProperty('margin-top', _mt + 'px', 'important');\r\n                        console.log('\ud83d\udcf1 [Ele0A pb2] centrage vertical: ele0AH=' + Math.round(_ele0AH) + ' docH=' + Math.round(_docPrevH) + ' mt=' + _mt + 'px');\r\n                    }\r\n                }\r\n            }, 100);\r\n            \/\/ \u2705 Pb 3 : Croix d\u00e9plac\u00e9e DANS HTMLUploadfileConteneur (position:relative) \u2192 top-right de l'image\r\n            var $_ufcEle0A = $_ufcEle0APre;\r\n            $_ufcEle0A.css('position', 'relative');\r\n            var _croixEle0A = $_ele0ADrop.find('.CroixResetAnnonceContainer')[0];\r\n            if (_croixEle0A) {\r\n                $_ufcEle0A[0].appendChild(_croixEle0A);\r\n                _croixEle0A.style.setProperty('position', 'absolute', 'important');\r\n                var _isViaPopupCroix = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n                if (_isViaPopupCroix) {\r\n                    \/\/ Popup pays site : croix a l'interieur de l'UFC\r\n                    _croixEle0A.style.setProperty('top',   '4px',  'important');\r\n                    _croixEle0A.style.setProperty('right', '4px',  'important');\r\n                } else {\r\n                    _croixEle0A.style.setProperty('top',   '-31px', 'important');\r\n                    _croixEle0A.style.setProperty('right', '-36px', 'important');\r\n                }\r\n                _croixEle0A.style.setProperty('margin', '0px', 'important');\r\n                _croixEle0A.style.setProperty('z-index', '9999', 'important');\r\n            }\r\n            \/\/ \u2705 v2.6 : Masquer les \u00e9l\u00e9ments au-dessus de l'image dans Ele0A\r\n            \/\/ EnvoiUlterieur : inutile quand un fichier est d\u00e9pos\u00e9\r\n            \/\/ ReserverContainer statique : remplac\u00e9 par le bouton dynamique\r\n            $_ele0ADrop.find('.elementor-field-group-EnvoiUlterieur').each(function() {\r\n                this.style.setProperty('display', 'none', 'important');\r\n            });\r\n            $_ele0ADrop.find('.elementor-field-group-ReserverEspacePublicitaire').not('.reserver-dynamic-container .elementor-field-group-ReserverEspacePublicitaire').each(function() {\r\n                this.style.setProperty('display', 'none', 'important');\r\n            });\r\n\r\n            \/\/ \u2705 Pb 4 : Bouton R\u00e9server dynamique \u2014 plac\u00e9 apr\u00e8s UFC\r\n            var $_reserverEle0A = $_ele0ADrop.find('.reserver-dynamic-container').first();\r\n            if (!$_reserverEle0A.length) { $_reserverEle0A = $_ele0ADrop.next('.reserver-dynamic-container'); }\r\n            if ($_reserverEle0A.length) {\r\n                $_ufcEle0A.after($_reserverEle0A);\r\n                $_reserverEle0A.css({\r\n                    'margin-top': '-27px',\r\n                    'margin-bottom': '4px',\r\n                    'position': 'relative',\r\n                    'z-index': '10',\r\n                    'text-align': 'center',\r\n                    'transform': 'scale(0.8)',\r\n                    'transform-origin': 'top center'\r\n                });\r\n            }\r\n            console.log('\ud83d\udcf1 [Ele0A popup] marges neutralis\u00e9es + centrage vertical + croix\/r\u00e9server positionn\u00e9s');\r\n            return;\r\n        }\r\n\r\n        \/\/ v2.9 : site pays direct (window.top) = marges neutres; iframe r\u00e9gie = marges compens\u00e9es\r\n        var _isInIframeNonMini = (window !== window.top);\r\n        var _isOnSitePays = (window === window.top);\r\n        $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n            'min-height': '0',\r\n            'margin-top': _isInIframeNonMini ? '70px' : (_isOnSitePays ? '-50px' : '-85px'),\r\n            'margin-bottom': _isMiniatureAdj ? '0px' : (_isInIframeNonMini ? '35px' : (_isOnSitePays ? '10px' : '20px'))\r\n        });\r\n        console.log('\ud83d\udcf1 HTMLUploadfileConteneur margins set: mt=' + (_isOnSitePays ? '10px' : (_isInIframeNonMini ? '70px' : '-85px')) + ', el found:', $dropZone.closest('.HTMLUploadfileConteneur').length);\r\n        \r\n        if (_isInIframeNonMini || _isOnSitePays) {\r\n            \/\/ iframe ou site pays direct : pas de marges n\u00e9gatives sur dropZone\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n        } else {\r\n            $dropZone.css({\r\n                'margin-top': '-50px',\r\n                'margin-bottom': '-140px'\r\n            });\r\n        }\r\n        \r\n        $dropZone.closest('.OrdiMobileConteneurClass').css({\r\n            'margin-top': _isOnSitePays ? '0px' : '65px',\r\n            'margin-bottom': _isOnSitePays ? '50px' : '-10px'\r\n        });\r\n        console.log('\ud83d\udcf1 OrdiMobile margins set: mt=' + (_isOnSitePays ? '0px' : '65px') + ' mb=' + (_isOnSitePays ? '50px' : '-10px'));\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            \/\/ \u2705 v2.4.12 : Reset $dropZone margins (les -50px\/-140px tirent le contenu vers le haut dans l'iframe)\r\n            \/\/ Pour doc-preview (Communiqu\u00e9\/Interview), la hauteur est plus grande \u2192 ajuster min-height\r\n            var _isDocPreviewAchat = $dropZone.find('.doc-preview-container').length > 0;\r\n            var _docHAchat = _isDocPreviewAchat ? 150 : 112;\r\n            $dropZone.css({'margin-top': '0px', 'margin-bottom': '0px', 'top': '0px'});\r\n            $dropZone.closest('.HTMLUploadfileConteneur').css({\r\n                \/\/ \u2705 v2.4.13 : Miniature desktop \u2192 +40px suppl\u00e9mentaires pour compenser le d\u00e9calage\r\n                'margin-top': _isMiniatureAdj ? '110px' : '70px',\r\n                'margin-bottom': '35px',\r\n                'min-height': _docHAchat + 'px',\r\n                'height': _isDocPreviewAchat ? _docHAchat + 'px' : '',\r\n                'max-height': _isDocPreviewAchat ? _docHAchat + 'px' : ''\r\n            });\r\n            console.log('\ud83d\udcf1 AchatEspaceCall=Yes OVERRIDE: mt=' + (_isMiniatureAdj ? '110px' : '70px') + ' | docPreview:', _isDocPreviewAchat, '| h:', _docHAchat);\r\n        }\r\n        \r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '70px'});\r\n        }\r\n        \r\n        \/\/ v2.9 : site pays mobile \u2014 d\u00e9caler reserver-dynamic-container de 30px vers le bas\r\n        if (_isOnSitePays) {\r\n            setTimeout(function() {\r\n                var $reserverCont = $dropZone.closest('.droppable').find('.reserver-dynamic-container')\r\n                    .add($dropZone.closest('.droppable').next('.reserver-dynamic-container')).first();\r\n                if ($reserverCont.length) {\r\n                    var _curMt = parseInt($reserverCont.css('margin-top')) || 0;\r\n                    $reserverCont.css('margin-top', (_curMt + 30) + 'px');\r\n                    console.log('\ud83d\udcf1 [site pays] reserver-dynamic-container +30px \u2192 mt:', _curMt + 30);\r\n                }\r\n            }, 80);\r\n        }\r\n        \/\/ v2.9 : supprim\u00e9 - margin-top\/bottom -100px causait chevauchement texte sur sites pays\r\n    },\r\n    \r\n    adjustDesktopLayout($dropZone) {\r\n        const emplacement = StateManager.get('Commande_Emplacement_Page_Web');\r\n        const $droppable = $dropZone.closest('.droppable');\r\n        \/\/ \u2705 v2.4.13 : Lire et resetter _dropFromMiniature ici aussi (desktop)\r\n        var _fromMiniatureDesktop = window._dropFromMiniature || ($droppable[0] ? $droppable[0].getAttribute('data-kit-drop') === 'true' : false);\r\n        \/\/ Memoriser pour _buildAdOverlay qui tourne apres\r\n        window._lastDropWasKit = _fromMiniatureDesktop;\r\n        window._dropFromMiniature = false;\r\n        \/\/ \u2705 Nettoyer data-kit-drop apr\u00e8s lecture\r\n        if ($droppable[0]) { $droppable[0].removeAttribute('data-kit-drop'); }\r\n        \r\n        \/\/ \u2705 Masquer les textes enfants (position, label, r\u00e9f\u00e9rence) sans toucher au conteneur\r\n        $droppable\r\n            .find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur > div > .elementor-widget-text-editor')\r\n            .hide();\r\n        \r\n        \/\/ \u2705 +15px sur .droppable \u2014 sauf si drop depuis miniature (dimensions naturelles)\r\n        if (!_fromMiniatureDesktop) {\r\n            const currentMt = parseInt($droppable.css('margin-top')) || 0;\r\n            $droppable.css('margin-top', (currentMt + 15) + 'px');\r\n        } else {\r\n            \/\/ \u2705 v2.4.13 : Drop depuis miniature Kit \u2014 d\u00e9caler de 30px vers le bas\r\n            const currentMt = parseInt($droppable.css('margin-top')) || 0;\r\n            \/\/ \u2705 Homepage : -20px (annonce trop basse sur r\u00e9gie) \u2014 autres pages : +70px\r\n            var _mtOffsetMini = (window.location.pathname === '\/' || window.location.pathname === '\/en\/' || window.location.pathname === '\/fr\/') ? -20 : 70;\r\n            $droppable.css('margin-top', (currentMt + _mtOffsetMini) + 'px');\r\n        }\r\n        \r\n        \/\/ v2.9 : detection popup parent (ViaPopupProcessAchat existe sur site pays)\r\n        var _isViaPopupParent = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n        $dropZone.closest('.OrdiMobileConteneurClass')\r\n            .find('.RefEspacePublicitaire')\r\n            .html(emplacement)\r\n            .attr('id', 'RefEspacePublicitaire')\r\n            .each(function() {\r\n                var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                if (_isViaPopupParent) {\r\n                    \/\/ repositionner a l'interieur du lisere vert\r\n                    this.style.setProperty('display', 'block', 'important');\r\n                    this.style.setProperty('margin-top', '4px', 'important');\r\n                    this.style.setProperty('margin-right', '4px', 'important');\r\n                    if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                } else {\r\n                    if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                    this.style.setProperty('display', 'block', 'important');\r\n                }\r\n            });\r\n\r\n        \/\/ v4.9cp : centrer .RefEspacePublicitaire entre le titre et la croix de fermeture.\r\n        \/\/          Mesure dynamique du bord droit du titre et du bord gauche de la croix,\r\n        \/\/          puis pose position:absolute + left au milieu.\r\n        function _centerRefEspPub() {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').find('.RefEspacePublicitaire').each(function() {\r\n                var _refEl = this;\r\n                var _container = jQuery(_refEl).closest('.droppable')[0] || jQuery(_refEl).closest('.DeplaceAnnonce')[0];\r\n                if (!_container) return;\r\n                \/\/ Bord droit du titre (ChoisirEspacePublicitaireDisponibiliteConteneur ou AdUploadedTitle)\r\n                var _titleEl = _container.querySelector('.ChoisirEspacePublicitaireDisponibiliteConteneur')\r\n                            || _container.querySelector('.AdUploadedTitle');\r\n                \/\/ Bord gauche de la croix\r\n                var _crossEl = _container.querySelector('#CroixResetAnnonce')\r\n                            || _container.querySelector('.CroixResetAnnonceContainer');\r\n                if (!_titleEl || !_crossEl) return;\r\n                var _cRect = _container.getBoundingClientRect();\r\n                var _tRect = _titleEl.getBoundingClientRect();\r\n                var _xRect = _crossEl.getBoundingClientRect();\r\n                if (!_cRect.width || !_tRect.width || !_xRect.width) return;\r\n                \/\/ Milieu entre bord droit titre et bord gauche croix\r\n                var _mid = (_tRect.right + _xRect.left) \/ 2;\r\n                \/\/ Position relative au container (le parent position:relative)\r\n                var _leftPx = Math.round(_mid - _cRect.left);\r\n                _refEl.style.setProperty('position', 'absolute', 'important');\r\n                _refEl.style.setProperty('left', _leftPx + 'px', 'important');\r\n                _refEl.style.setProperty('transform', 'translateX(-50%)', 'important');\r\n                _refEl.style.setProperty('top', (_tRect.top - _cRect.top) + 'px', 'important');\r\n                _refEl.style.removeProperty('margin-right');\r\n                _refEl.style.removeProperty('margin-left');\r\n            });\r\n        }\r\n        \/\/ Apr\u00e8s le rendu (titre + croix visibles)\r\n        setTimeout(_centerRefEspPub, 50);\r\n        setTimeout(_centerRefEspPub, 300);\r\n        setTimeout(_centerRefEspPub, 800);\r\n\r\n        \/\/ \u2705 v2.4.5 : Renseigner et afficher .PositionEspacePublicitaire\r\n        (function() {\r\n            var _rankPos = StateManager.get('Rank_Emplacement_Page_Web') || $droppable.attr('id') || '';\r\n            var _posLib = PreviewRenderer._getPositionLibelle(_rankPos);\r\n            if (_posLib) {\r\n                $dropZone.closest('.OrdiMobileConteneurClass')\r\n                    .find('.PositionEspacePublicitaireDeplacer')\r\n                    .text(_posLib)\r\n                    .each(function() {\r\n                        var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                        if (_isViaPopupParent) {\r\n                            this.style.setProperty('display', 'block', 'important');\r\n                            this.style.setProperty('margin-top', '4px', 'important');\r\n                            if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                        } else {\r\n                            if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                            this.style.setProperty('display', 'block', 'important');\r\n                        }\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        if (window.location.pathname === \"\/\" || window.location.pathname === \"\/en\/\") {\r\n            \/\/ \u2705 v2.4.10 : top selon contexte \u2014 espaces hors .remainingContent d\u00e9calent de 60px, ceux dedans de 20px\r\n            \/\/ \u2705 v2.7.3 : Ele0A est positionn\u00e9 par positionEle0AOverEle1A \u2192 ne pas \u00e9craser son top\r\n            var _isEle0AHP = $droppable.attr('id') === 'Ele0A';\r\n            if (!_isEle0AHP) {\r\n                var _inRemainingContent = $dropZone.closest('.remainingContent').length > 0;\r\n                var _topOffsetHP = _inRemainingContent ? '20px' : '100px';\r\n                $dropZone.closest('.ToBeHidden')\r\n                    .css('top', _topOffsetHP)\r\n                    .css('min-height', '300px');\r\n            }\r\n            \/\/ NB : pas de overflow:hidden ici \u2014 le liser\u00e9 vert (box-shadow inset sur HTMLUploadfileConteneur)\r\n            \/\/ doit rester visible sur les 4 c\u00f4t\u00e9s\r\n        }\r\n        \r\n        \/\/ \u2705 Toutes pages desktop : rendre le parent du droppable non-clippant\r\n        \/\/ Le bouton R\u00e9server est ins\u00e9r\u00e9 apr\u00e8s .droppable avec margin-top n\u00e9gatif \u2014\r\n        \/\/ overflow:hidden sur le parent Elementor le masque sinon.\r\n        $droppable.parent().css('overflow', 'visible');\r\n        \r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            $dropZone.closest('.OrdiMobileConteneurClass').css({'margin-top': '0px'});\r\n            \/\/ \u2705 Page \/annonce : masquer les \u00e9l\u00e9ments de d\u00e9placement inutiles\r\n            $droppable.find('.DeplaceAnnonceText').hide();\r\n            $droppable.find('.PositionEspacePublicitaireDeplacer').hide();\r\n            $droppable.find('.RefEspacePublicitaire').hide();\r\n            $droppable.find('.reserver-dynamic-option').hide();\r\n            $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        }\r\n        \r\n        \/\/ \u2705 v2.7.3 : Ele0A desktop \u2014 ajustements position des labels et de la croix apr\u00e8s d\u00e9p\u00f4t\r\n        if ($droppable.attr('id') === 'Ele0A') {\r\n            \/\/ .PositionEspacePublicitaireDeplacer : +35px bas, +30px droite\r\n            $droppable.find('.PositionEspacePublicitaireDeplacer').each(function() {\r\n                this.style.setProperty('margin-top', '35px', 'important');\r\n                this.style.setProperty('margin-left', '30px', 'important');\r\n            });\r\n            \/\/ .RefEspacePublicitaire : +35px bas, 25px vers la gauche\r\n            $droppable.find('.RefEspacePublicitaire').each(function() {\r\n                this.style.setProperty('margin-top', '35px', 'important');\r\n                this.style.setProperty('margin-right', '25px', 'important');\r\n            });\r\n            \/\/ #CroixResetAnnonce : -30px vers le haut, 22px vers la gauche (+3px droite)\r\n            $droppable.find('#CroixResetAnnonce').each(function() {\r\n                this.style.setProperty('margin-top', '-30px', 'important');\r\n                this.style.setProperty('margin-right', '22px', 'important');\r\n            });\r\n        }\r\n    }\r\n};\r\n\r\n\/**\r\n * \u2705 Module de gestion des formats et du titre .SelectionFormatTitre\r\n *\/\r\nconst FormatUIManager = {\r\n    \r\n    \/**\r\n     * V\u00e9rifie si un format est s\u00e9lectionn\u00e9 dans un espace publicitaire\r\n     * V\u00e9rifie le background-color OU le sessionStorage (format venant de l'accord\u00e9on)\r\n     *\/\r\n    hasSelectedFormat($element) {\r\n        const $droppable = $element.closest('.droppable');\r\n        \/\/ v4.9ds : si une annonce est d\u00e9j\u00e0 d\u00e9pos\u00e9e dans cet espace (data-via-ad-loaded='true'),\r\n        \/\/   le format est implicite (d\u00e9duit par UploadManager.handleFileUpload depuis l'extension\r\n        \/\/   du fichier). Retourner true sans d\u00e9pendre du DOM .EspPubFormatContainer ni du\r\n        \/\/   sessionStorage Upload_File_Name (qui peut \u00eatre nettoy\u00e9 apr\u00e8s une r\u00e9servation\r\n        \/\/   pr\u00e9c\u00e9dente sur un autre espace pub).\r\n        \/\/   Bug constat\u00e9 : 2 annonces d\u00e9pos\u00e9es (Ele0A + Ele1A), Ele0A r\u00e9serv\u00e9e \u2192 popup ouvert,\r\n        \/\/   tentative R\u00e9server Ele1A bloqu\u00e9e car hasFormat=false (sessionStorage vid\u00e9 pour Ele0A,\r\n        \/\/   et le fond blanc EspPubFormatContainer pas pos\u00e9 sur Ele1A en mode popup).\r\n        if ($droppable.attr('data-via-ad-loaded') === 'true') return true;\r\n        \/\/ \u2705 v4.9ds Fix 4 : data-via-current-format prioritaire sur la d\u00e9tection visuelle\r\n        \/\/   \u2192 pos\u00e9 par applyFormatState ; survit aux races qui effacent le background blanc\r\n        \/\/   (un script tiers met temporairement transparent \u2192 l'observer voit \"format perdu\"\r\n        \/\/   \u2192 SelectionFormatTitre s'affiche \u2192 UI cass\u00e9e). L'attribut sur le .droppable\r\n        \/\/   reste pos\u00e9 tant qu'applyFormatState n'a pas \u00e9t\u00e9 appel\u00e9 avec un format diff\u00e9rent.\r\n        if ($droppable.attr('data-via-current-format')) return true;\r\n        const isEle0A = $droppable.attr('id') === 'Ele0A';\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            if (isEle0A) {\r\n                \/\/ \u2705 v2.3.4 : Ele0A popup \u2014 vrai seulement si un format sous-jacent (hors FormatIdPopUp) est s\u00e9lectionn\u00e9\r\n                var _hasDomFmt = $droppable.find('.EspPubFormatContainer').not('.FormatIdPopUp').toArray().some(el => {\r\n                    return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n                });\r\n                if (_hasDomFmt) return true;\r\n                \/\/ \u2705 v2.4.5 : Fallback sessionStorage \u2014 DOM pas encore mis \u00e0 jour (timing MutationObserver)\r\n                return !!sessionStorage.getItem('FormatSelect');\r\n            }\r\n            \/\/ v4.9ds : Autres espaces (Ele1A+) en mode popup \u2014 v\u00e9rifier le DOM r\u00e9el.\r\n            \/\/   AVANT : retournait true syst\u00e9matiquement \u2192 SelectionFormatTitre toujours masqu\u00e9,\r\n            \/\/   m\u00eame apr\u00e8s suppression+restauration d'une annonce dans cet espace.\r\n            \/\/   APR\u00c8S : on cherche un .EspPubFormatContainer (hors Cr\u00e9ation\/PopUp) avec fond\r\n            \/\/   blanc dans CET espace. Si aucun \u2192 return false \u2192 SelectionFormatTitre s'affiche.\r\n            var _hasDomFmtStd = $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').toArray().some(el => {\r\n                return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n            });\r\n            return _hasDomFmtStd;\r\n        }\r\n        \/\/ v4.9ds : exclure FormatIdCreation et FormatIdPopUp \u2014 ce ne sont pas de vrais formats\r\n        const $realFmts = $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp');\r\n        const hasWhiteBg = $realFmts.toArray().some(el => {\r\n            return $(el).css('background-color') === 'rgb(255, 255, 255)';\r\n        });\r\n        if (hasWhiteBg) return true;\r\n        \/\/ v4.9ds : si le DOM contient des containers de format dans CE droppable et qu'AUCUN n'est s\u00e9lectionn\u00e9,\r\n        \/\/   alors retourner false EXPLICITEMENT \u2014 ne pas se rabattre sur des flags sessionStorage globaux\r\n        \/\/   qui peuvent rester pos\u00e9s apr\u00e8s cr\u00e9ation dans un autre espace.\r\n        \/\/   Bug constat\u00e9 : apr\u00e8s fermeture du popup cr\u00e9ation (lanc\u00e9 depuis Ele0A), Formatchoisi\/FormatSelect\r\n        \/\/   restaient pos\u00e9s \u2192 SelectionFormatTitre disparaissait sur Ele1A+ alors qu'aucun format n'y \u00e9tait choisi.\r\n        if ($realFmts.length > 0) return false;\r\n        \/\/ Fallbacks sessionStorage UNIQUEMENT si le DOM n'a pas encore de containers (timing initial \/ pr\u00e9-rendu)\r\n        if (sessionStorage.getItem('Formatchoisi') === 'Yes') return true;\r\n        if (sessionStorage.getItem('FormatSelect')) return true;\r\n        return !!sessionStorage.getItem('_FormatSelectApplied');\r\n    },\r\n    \r\n    \/**\r\n     * Met \u00e0 jour la couleur du titre .SelectionFormatTitre\r\n     * Blanc si un format est s\u00e9lectionn\u00e9, rouge #FB5E2A sinon\r\n     *\/\r\n    updateTitleColor($droppable) {\r\n        \/\/ \u2705 v2.4.5 : Ele0A \u2014 ne jamais afficher SelectionFormatTitre\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes' ? $droppable.attr('id') === 'Ele0A' : false) {\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n            return;\r\n        }\r\n\r\n        if (this.hasSelectedFormat($droppable)) {\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n            console.log('\u2705 SelectionFormatTitreBlanc affich\u00e9 (format s\u00e9lectionn\u00e9)');\r\n        } else {\r\n            $droppable.find('.SelectionFormatTitre').show();\r\n            $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n            console.log('\u26a0\ufe0f SelectionFormatTitre affich\u00e9 (aucun format s\u00e9lectionn\u00e9)');\r\n        }\r\n\r\n        \/\/ \u2705 R\u00e8gle Cr\u00e9ation\/Vid\u00e9o : d\u00e9tecter si Vid\u00e9o est actif et d\u00e9sactiver\/r\u00e9activer Cr\u00e9ation\r\n        var _videoActive = false;\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            var _bg = jQuery(this).css('background-color') || '';\r\n            if (_bg === 'rgb(255, 255, 255)') { _videoActive = true; return false; }\r\n            var _fEl = this.querySelector('.EspPubFormat');\r\n            if (_fEl) { var _col = _fEl.style.color || ''; if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') { _videoActive = true; return false; } }\r\n        });\r\n        if (_videoActive) {\r\n            $droppable.find('.FormatIdCreation').each(function() {\r\n                jQuery(this).data('creationActive', false);\r\n                jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n                if ($lbl.text() !== 'D\u00e9sactiv\u00e9') { $lbl.text('D\u00e9sactiv\u00e9'); }\r\n            });\r\n            console.log('\ud83c\udfa8 updateTitleColor : Vid\u00e9o actif \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e');\r\n        } else {\r\n            $droppable.find('.FormatIdCreation').each(function() {\r\n                var _cur = jQuery(this).css('opacity');\r\n                if (_cur === '0.4') {\r\n                    jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                    var $lbl = jQuery(this).find('.EspPubFormat');\r\n                    var _orig = $lbl.attr('data-original-text');\r\n                    if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                }\r\n            });\r\n        }\r\n    },\r\n    \r\n    \/**\r\n     * Flash visuel sur le titre pour indiquer qu'un format est requis\r\n     *\/\r\n    flashTitle($element) {\r\n        const $droppable = $element.closest('.droppable');\r\n        const $titre = $droppable.find('.SelectionFormatTitre');\r\n        if (!$titre.length) return;\r\n        \r\n        \/\/ v2.6 : En mode popup (Ele0A), SelectionFormatTitre est display:none \u2192 le montrer temporairement\r\n        var _wasHidden = $titre.css('display') === 'none';\r\n        \/\/ v2.6 : Elementor impose display:none !important \u2192 setProperty requis\r\n        if (_wasHidden) { $titre.each(function() { this.style.setProperty('display','block','important'); }); }\r\n        \r\n        \/\/ Flash rouge rapide 3 fois\r\n        let count = 0;\r\n        const originalColor = $titre.css('color');\r\n        \r\n        const flash = setInterval(() => {\r\n            $titre.css('color', count % 2 === 0 ? '#FF0000' : '#FB5E2A');\r\n            count++;\r\n            if (count >= 6) {\r\n                clearInterval(flash);\r\n                $titre.css('color', '#FB5E2A');\r\n                if (_wasHidden) { setTimeout(function() { $titre.each(function() { this.style.setProperty('display','none','important'); }); }, 2000); }\r\n            }\r\n        }, 250);\r\n        \r\n        console.log('\u26a0\ufe0f Flash titre format - action bloqu\u00e9e (aucun format s\u00e9lectionn\u00e9)');\r\n    },\r\n    \r\n    \/**\r\n     * Met \u00e0 jour l'\u00e9tat de la checkbox \"R\u00e9server cet espace publicitaire\"\r\n     * La checkbox est activable si : format s\u00e9lectionn\u00e9 ET (fichier d\u00e9pos\u00e9 OU envoi diff\u00e9r\u00e9)\r\n     *\/\r\n    updateReserverCheckboxState($droppable) {\r\n        \/\/ \u2705 Chercher le label : dynamique ou Elementor statique\r\n        let $label = null;\r\n        let $container = null;\r\n        \r\n        \/\/ 1. Bouton dynamique dans le droppable\r\n        $container = $droppable.find('.reserver-dynamic-container');\r\n        if (!$container.length) {\r\n            $container = $droppable.next('.reserver-dynamic-container');\r\n        }\r\n        if (!$container.length) {\r\n            \/\/ Mobile : checkbox attach\u00e9e au body avec data-droppable-id\r\n            $container = $('body > .reserver-dynamic-container[data-droppable-id=\"' + $droppable.attr('id') + '\"]');\r\n        }\r\n        if (!$container.length) {\r\n            $container = $('body > .reserver-dynamic-container');\r\n        }\r\n        \r\n        if ($container.length) {\r\n            $label = $container.find('.reserver-dynamic-label');\r\n            $container.find('.reserver-dynamic-option, .elementor-field-option').css('opacity', '1');\r\n        }\r\n        \r\n        \/\/ 2. Bouton Elementor statique - chercher dans le droppable puis globalement\r\n        let $reserverStatique = $droppable.find('.elementor-field-group-ReserverEspacePublicitaire label');\r\n        if (!$reserverStatique.length) {\r\n            $reserverStatique = $droppable.find('label[for^=\"form-field-ReserverEspacePublicitaire\"]');\r\n        }\r\n        if (!$reserverStatique.length) {\r\n            \/\/ Fallback global : le formulaire peut \u00eatre en dehors du droppable\r\n            $reserverStatique = $('.elementor-field-group-ReserverEspacePublicitaire .elementor-field-option label');\r\n        }\r\n        \r\n        const hasFormat = this.hasSelectedFormat($droppable);\r\n        \/\/ v2.9 : data-via-ad-loaded est specifique au droppable, plus fiable que FileReceived global\r\n        const hasFile = StateManager.get('FileReceived') === 'Yes'\r\n            || $droppable.attr('data-via-ad-loaded') === 'true';\r\n        const hasEnvoiDiffere = $droppable.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        \r\n        const canReserve = hasFormat ? (hasFile || hasEnvoiDiffere) : false;\r\n        \r\n        \/\/ \u2705 Si la checkbox R\u00e9server est coch\u00e9e \u2192 label vert \"Espace publicitaire r\u00e9serv\u00e9\"\r\n        const isChecked = $droppable.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked') ||\r\n                          ($container.length ? $container.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked') : false);\r\n        \/\/ \u2705 v2.4.7 : Signal de restauration r\u00e9serv\u00e9e \u2014 checkbox pas encore coch\u00e9e (setTimeout 150ms)\r\n        \/\/             mais on sait d\u00e9j\u00e0 que l'espace est r\u00e9serv\u00e9 \u2192 traiter comme coch\u00e9\r\n        const _isReservedRestoration = sessionStorage.getItem('_isReservedRestoration') === 'Yes';\r\n        const isCheckedOrReservedRestore = isChecked || _isReservedRestoration;\r\n        \r\n        if ($label ? $label.length : false) {\r\n            if (isCheckedOrReservedRestore) {\r\n                $label.text('Espace publicitaire r\u00e9serv\u00e9').css('color', 'rgb(62, 170, 19)');\r\n            } else {\r\n                $label.text('R\u00e9server cet espace publicitaire').css('color', canReserve ? '#FB5E2A' : '');\r\n            }\r\n        }\r\n        if ($reserverStatique.length) {\r\n            if (isCheckedOrReservedRestore) {\r\n                $reserverStatique.attr('style', 'color: rgb(62, 170, 19) !important;');\r\n            } else if (canReserve) {\r\n                $reserverStatique.attr('style', 'color: #FB5E2A !important;');\r\n            } else {\r\n                $reserverStatique.removeAttr('style');\r\n            }\r\n        }\r\n        \r\n        console.log('\ud83d\udd04 updateReserverCheckboxState:', { hasFormat, hasFile, hasEnvoiDiffere, canReserve, dynamicLabel: !!($label ? $label.length : false), staticLabel: $reserverStatique.length });\r\n    },\r\n    \r\n    \/**\r\n     * Initialise les listeners pour le changement de format et la checkbox R\u00e9server\r\n     *\/\r\n    init() {\r\n        \/\/ \u2705 \u00c9couter les clics sur les conteneurs de format et leurs parents\r\n        jQuery(document).on('click', '.EspPubFormatContainer', (e) => {\r\n            const $droppable = jQuery(e.target).closest('.droppable');\r\n            if (!$droppable.length) return;\r\n\r\n            \/\/ v4.9ds : stocker le format choisi sur le .droppable (gate ind\u00e9pendant)\r\n            \/\/          Exclusion : Creation et PopUp ne sont pas de vrais formats \u2192 ne pas\r\n            \/\/          d\u00e9verrouiller les zones (glisser-d\u00e9poser, envoi diff\u00e9r\u00e9, r\u00e9server)\r\n            const _$btn = jQuery(e.target).closest('.EspPubFormatContainer');\r\n            const _classes = _$btn.attr('class') || '';\r\n            const _m = _classes.match(\/FormatId(\\w+)\/);\r\n            if (_m && _m[1] && _m[1] !== 'Creation' && _m[1] !== 'PopUp') {\r\n                $droppable.attr('data-via-current-format', _m[1]);\r\n                \/\/ Notifier le parent pour mettre \u00e0 jour le format-gate (CSS top-level)\r\n                try {\r\n                    var _topW = window.top || window;\r\n                    if (_topW && typeof _topW._viaSetDroppableFormat === 'function') {\r\n                        _topW._viaSetDroppableFormat($droppable.attr('id'), _m[1]);\r\n                    }\r\n                } catch(_e) {}\r\n            }\r\n\r\n            \/\/ \u2705 Masquer les messages d'erreur format d\u00e8s la s\u00e9lection d'un format\r\n            jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n            jQuery('#fmt-creation-info').remove();\r\n            \r\n            \/\/ v4.9ds : appeler _viaUpdateFormatGate pour TOUS les clics (incluant Cr\u00e9ation\/PopUp)\r\n            \/\/   afin de mettre \u00e0 jour data-via-title-state=\"chosen\" \u2192 CSS SelectionFormatTitre\r\n            \/\/   (transparent + blanc) prend effet aussi pour Cr\u00e9ation.\r\n            [50, 200, 500].forEach(function(delay) {\r\n                setTimeout(function() {\r\n                    try {\r\n                        var _topW2 = window.top || window;\r\n                        if (_topW2 ? typeof _topW2._viaUpdateFormatGate === 'function' : false) {\r\n                            _topW2._viaUpdateFormatGate();\r\n                        }\r\n                    } catch (_e) {}\r\n                }, delay);\r\n            });\r\n\r\n            \/\/ Multiples d\u00e9lais pour couvrir les traitements asynchrones\r\n            [50, 200, 500].forEach(delay => {\r\n                setTimeout(() => {\r\n                    this.updateTitleColor($droppable);\r\n                    this.updateReserverCheckboxState($droppable);\r\n                }, delay);\r\n            });\r\n\r\n            \/\/ \u2705 v2.2.0 : Si un format (non Cr\u00e9ation) est cliqu\u00e9, notifier le parent pour mettre \u00e0 jour le Kit\r\n            const $btn = jQuery(e.target).closest('.EspPubFormatContainer');\r\n            if ($btn.length ? (!$btn.hasClass('FormatIdCreation') ? !$btn.hasClass('FormatIdPopUp') : false) : false) {\r\n                \/\/ \u2705 v2.3.4 : FormatIdPopUp g\u00e9r\u00e9 par clickFormatFromIframe (Entete.txt) \u2014 ne pas doubler\r\n                var classes = $btn.attr('class') || '';\r\n                var match = classes.match(\/FormatId(\\w+)\/);\r\n                if (match ? match[1] : false) {\r\n                    var _rankEP = $droppable.attr('id') || '';\r\n                    setTimeout(function() {\r\n                        MessageManager.sendToParent('formatChangedInIframe', { formatSelect: match[1], rank: _rankEP });\r\n                        console.log('\ud83d\udd04 formatChangedInIframe envoy\u00e9:', match[1], '| rank:', _rankEP);\r\n                    }, 100);\r\n                }\r\n\r\n                \/\/ \u2705 Si Cr\u00e9ation est d\u00e9j\u00e0 actif ET format valide (non Vid\u00e9o) \u2192 ouvrir le popup\r\n                var $creationBtn = $droppable.find('.FormatIdCreation');\r\n                var _creationActive = $creationBtn.data('creationActive') === true;\r\n                var _isVideoBtn = $btn.hasClass('FormatIdVideo');\r\n                if (_creationActive ? !_isVideoBtn : false) {\r\n                    var _rankKit2 = _rankEP;\r\n                    var _cSite2 = sessionStorage.getItem('codeSite') || '';\r\n                    var _cPage2 = sessionStorage.getItem('codePage') || '';\r\n                    var _sfx2 = _rankKit2.replace('Ele', '');\r\n                    var _empl2 = (_cSite2 ? (_cPage2 ? _sfx2 : false) : false) ? (_cSite2 + _cPage2 + 'L' + _sfx2) : (sessionStorage.getItem('Commande_Emplacement_Page_Web') || '');\r\n                    var _fmt2 = sessionStorage.getItem('FormatSelect') || '';\r\n                    var _fmtT2 = sessionStorage.getItem('Commande_Format_Transmis') || '';\r\n                    \/\/ R\u00e9activer Vid\u00e9o + restaurer texte si d\u00e9sactiv\u00e9e\r\n                    $droppable.find('.FormatIdVideo').each(function() {\r\n                        jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                        var $lbl = jQuery(this).find('.EspPubFormat');\r\n                        var _orig = $lbl.attr('data-original-text');\r\n                        if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                    });\r\n                    setTimeout(function() {\r\n                        MessageManager.sendToParent('openAdCreatorFromIframe', { formatSelect: _fmt2, formatTransmis: _fmtT2, rankId: _rankKit2, emplacement: _empl2 });\r\n                        console.log('\ud83c\udfa8 Cr\u00e9ation d\u00e9j\u00e0 actif + format s\u00e9lectionn\u00e9 \u2192 ouverture popup apr\u00e8s 2s | rank:', _rankKit2);\r\n                    }, 2000);\r\n                }\r\n            }\r\n        });\r\n        \r\n        \/\/ \u2705 MutationObserver sur les conteneurs de format pour d\u00e9tecter les changements de style\r\n        setTimeout(() => {\r\n            this.observeFormatChanges();\r\n            \r\n            \/\/ Initialiser les couleurs des titres au chargement\r\n            jQuery('.droppable').each((i, el) => {\r\n                this.updateTitleColor(jQuery(el));\r\n            });\r\n            \r\n            \/\/ \u2705 v2.3.4 : Popup \u2192 restaurer FormatIdPopUp + format sous-jacent (sans \u00e9craser applyFormatState)\r\n            if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n                jQuery('.FormatIdPopUp').css({'background-color': '#ffffff'});\r\n                jQuery('.FormatIdPopUp').find('.EspPubFormat').css({'color': '#37D900'});\r\n                \/\/ Restaurer aussi le format sous-jacent dans Ele0A\r\n                var _fmtSS = sessionStorage.getItem('FormatSelect') || '';\r\n                if (_fmtSS) {\r\n                    var _fmtSSNorm = _fmtSS.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                    jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdCreation').each(function() {\r\n                        var _cls = this.className.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                        if (_cls.includes(_fmtSSNorm)) {\r\n                            jQuery(this).css({'background-color': '#ffffff'});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        }\r\n                    });\r\n                }\r\n                jQuery('.droppable').each((i, el) => {\r\n                    this.updateTitleColor(jQuery(el));\r\n                });\r\n            }\r\n        }, 500);\r\n        \r\n        \/\/ \u2705 v1.16.0 : Re-v\u00e9rifier apr\u00e8s un d\u00e9lai plus long (format venant de l'accord\u00e9on via postMessage)\r\n        [1500, 3000].forEach(delay => {\r\n            setTimeout(() => {\r\n                \/\/ \u2705 v2.3.4 : Popup \u2192 restaurer FormatIdPopUp + format sous-jacent\r\n                if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n                    jQuery('.FormatIdPopUp').css({'background-color': '#ffffff'});\r\n                    jQuery('.FormatIdPopUp').find('.EspPubFormat').css({'color': '#37D900'});\r\n                    var _fmtSSR = sessionStorage.getItem('FormatSelect') || '';\r\n                    if (_fmtSSR) {\r\n                        var _fmtSSRNorm = _fmtSSR.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                        jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdCreation').each(function() {\r\n                            var _cls = this.className.normalize('NFD').replace(\/[\u0300-\u036f]\/g, '').toLowerCase();\r\n                            if (_cls.includes(_fmtSSRNorm)) {\r\n                                jQuery(this).css({'background-color': '#ffffff'});\r\n                                jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                            }\r\n                        });\r\n                    }\r\n                    jQuery('.droppable').each((i, el) => {\r\n                        this.updateTitleColor(jQuery(el));\r\n                    });\r\n                    return;\r\n                }\r\n                if (sessionStorage.getItem('Formatchoisi') === 'Yes') {\r\n                    jQuery('.droppable').each((i, el) => {\r\n                        this.updateTitleColor(jQuery(el));\r\n                    });\r\n                }\r\n            }, delay);\r\n        });\r\n    },\r\n    \r\n    \/**\r\n     * Attache un MutationObserver sur chaque .EspPubFormatContainer\r\n     * pour d\u00e9tecter tout changement de style (background-color) \r\n     * quel que soit le script qui le d\u00e9clenche\r\n     *\/\r\n    observeFormatChanges() {\r\n        \/\/ \u2705 Guard global anti-boucle \u2014 un seul verrou pour tous les observers\r\n        window._formatObserverLocked = false;\r\n        \r\n        jQuery('.EspPubFormatContainer').each((i, el) => {\r\n            const observer = new MutationObserver(() => {\r\n                if (window._formatObserverLocked) return;\r\n                window._formatObserverLocked = true;\r\n                clearTimeout(window._formatObserverTimer);\r\n                window._formatObserverTimer = setTimeout(() => {\r\n                    const $droppable = jQuery(el).closest('.droppable');\r\n                    this.updateTitleColor($droppable);\r\n                    this.updateReserverCheckboxState($droppable);\r\n\r\n                    \/\/ \u2705 R\u00e8gle Cr\u00e9ation\/Vid\u00e9o : si Vid\u00e9o est actif \u2192 d\u00e9sactiver Cr\u00e9ation\r\n                    var _videoActive = false;\r\n                    $droppable.find('.FormatIdVideo').each(function() {\r\n                        var _bg = this.style.backgroundColor || '';\r\n                        var _isWhite = _bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white';\r\n                        if (!_isWhite) {\r\n                            var _fEl = this.querySelector('.EspPubFormat');\r\n                            if (_fEl) { var _col = _fEl.style.color || ''; _isWhite = _col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900'; }\r\n                        }\r\n                        if (_isWhite) { _videoActive = true; return false; }\r\n                    });\r\n                    if (_videoActive) {\r\n                        $droppable.find('.FormatIdCreation').each(function() {\r\n                            jQuery(this).data('creationActive', false);\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                            jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n                            var $lbl = jQuery(this).find('.EspPubFormat');\r\n                            if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n                            $lbl.text('D\u00e9sactiv\u00e9');\r\n                        });\r\n                    } else {\r\n                        \/\/ R\u00e9activer Cr\u00e9ation si Vid\u00e9o n'est plus actif\r\n                        $droppable.find('.FormatIdCreation').each(function() {\r\n                            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                            var $lbl = jQuery(this).find('.EspPubFormat');\r\n                            var _orig = $lbl.attr('data-original-text');\r\n                            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n                        });\r\n                    }\r\n\r\n                    window._formatObserverLocked = false;\r\n                }, 150);\r\n            });\r\n            observer.observe(el, { \r\n                attributes: true, \r\n                attributeFilter: ['style', 'class'] \r\n            });\r\n        });\r\n        console.log('\u2705 MutationObserver attach\u00e9 aux conteneurs de format');\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de communication avec la page parente\r\n *\/\r\nconst MessageManager = {\r\n    sendToParent(type, data = {}) {\r\n        try {\r\n            window.parent.postMessage({\r\n                type: type,\r\n                iframeId: CONFIG.iframeId,\r\n                data: data\r\n            }, '*');\r\n        } catch (error) {\r\n            console.error('Error sending message to parent:', error);\r\n            window.parent.postMessage({\r\n                type: 'error',\r\n                error: error.message,\r\n                iframeId: CONFIG.iframeId\r\n            }, '*');\r\n        }\r\n    },\r\n    \r\n    sendDataToParent(data) {\r\n        this.sendToParent('dataFromIframeEspacePub', data);\r\n    },\r\n    \r\n    sendDelAdToParent(data) {\r\n        this.sendToParent('dataDelAd', data);\r\n    },\r\n    \r\n    prepareUploadData() {\r\n        const keys = [\r\n            'AddNewRefInVosCampagnes',\r\n            'FirstUploadFileorMoved',\r\n            'LoadedPageUrl',\r\n            'codePage',\r\n            'Rank_Emplacement_Page_Web',\r\n            'Commande_Emplacement_Page_Web',\r\n            'dragstart_Commande_Emplacement_Page_Web',\r\n            'dragstart_Rank_Emplacement_Page_Web',  \r\n            'FullPathAdFile',\r\n            'Upload_File_Name',\r\n            'FileReceived',\r\n            'PageWebDisplayed',\r\n            'Commande_Format_Transmis',\r\n            'EspPubLienAnnonce',\r\n            'EnvoiUlterieur',\r\n            'Formatchoisi'\r\n        ];\r\n        \r\n        const data = StateManager.getMultiple(keys);\r\n        \r\n        \/\/ v4.9ds : r\u00e9cup\u00e9ration FullPathAdFile par rank depuis via_fullpath localStorage.\r\n        \/\/   StateManager.FullPathAdFile est GLOBAL (dernier upload). Dans le sc\u00e9nario\r\n        \/\/   \"drop Ele0A + drop Ele1A + R\u00e9server Ele0A + R\u00e9server Ele1A\", au clic R\u00e9server\r\n        \/\/   Ele1A, StateManager.FullPathAdFile peut contenir la mauvaise valeur (Ele0A\r\n        \/\/   s\u00e9lectionn\u00e9 apr\u00e8s) ou \u00eatre vide. via_fullpath localStorage stocke par rank\r\n        \/\/   {Ele0A: URL_Ele0A, Ele1A: URL_Ele1A} \u2014 source de v\u00e9rit\u00e9 plus fiable.\r\n        try {\r\n            const _rankCur = data.Rank_Emplacement_Page_Web || '';\r\n            if (_rankCur) {\r\n                const _fpMap = JSON.parse(localStorage.getItem('via_fullpath') || '{}');\r\n                const _fpForRank = _fpMap[_rankCur];\r\n                if (_fpForRank) {\r\n                    if (data.FullPathAdFile !== _fpForRank) {\r\n                        console.log('\ud83d\udd27 [prepareUploadData] FullPathAdFile \u00e9cras\u00e9 depuis via_fullpath | rank:', _rankCur,\r\n                            '| ancien:', data.FullPathAdFile || '(vide)', '| nouveau:', _fpForRank);\r\n                    }\r\n                    data.FullPathAdFile = _fpForRank;\r\n                }\r\n            }\r\n        } catch(_eFP) { console.warn('[prepareUploadData] lecture via_fullpath \u00e9chou\u00e9e:', _eFP); }\r\n        \r\n        \/\/ v4.9ds : flag HasAdLoadedDom bas\u00e9 sur l'attribut data-via-ad-loaded du droppable\r\n        \/\/   correspondant \u00e0 l'emplacement courant. Permet \u00e0 Panier_manager.txt de\r\n        \/\/   distinguer \"annonce visuellement d\u00e9pos\u00e9e mais pas upload\u00e9e serveur\" (drop\r\n        \/\/   miniature \u2192 image dans DOM, FileReceived='No') du cas \"rien d\u00e9pos\u00e9\".\r\n        try {\r\n            const _emplCur = data.Commande_Emplacement_Page_Web || '';\r\n            if (_emplCur) {\r\n                const _$dropCur = jQuery('.droppable').filter(function() {\r\n                    return jQuery(this).attr('data-via-empl') === _emplCur\r\n                        || (jQuery(this).attr('id') === 'Ele0A' ? _emplCur.indexOf('L0A') >= 0 : false);\r\n                }).first();\r\n                \/\/ Fallback : matcher par rank si data-via-empl absent\r\n                if (!_$dropCur.length ? data.Rank_Emplacement_Page_Web : false) {\r\n                    const _$byRank = jQuery('.droppable[data-via-rank=\"' + data.Rank_Emplacement_Page_Web + '\"]').first();\r\n                    if (_$byRank.length) {\r\n                        data.HasAdLoadedDom = _$byRank.attr('data-via-ad-loaded') === 'true' ? 'Yes' : 'No';\r\n                    }\r\n                } else if (_$dropCur.length) {\r\n                    data.HasAdLoadedDom = _$dropCur.attr('data-via-ad-loaded') === 'true' ? 'Yes' : 'No';\r\n                }\r\n                \/\/ Dernier fallback : tous les droppables avec data-via-ad-loaded=true \u2192 si un seul, c'est lui\r\n                if (data.HasAdLoadedDom === undefined) {\r\n                    const _$any = jQuery('.droppable[data-via-ad-loaded=\"true\"]');\r\n                    data.HasAdLoadedDom = _$any.length > 0 ? 'Yes' : 'No';\r\n                }\r\n            }\r\n        } catch(_eAd) { data.HasAdLoadedDom = 'No'; }\r\n        \r\n        \/\/ \u2705 v2.3.4 : Popup (Ele0A) \u2014 lire le format sous-jacent depuis le DOM\r\n        \/\/ StateManager contient 'PopUp' (virtuel), mais le vrai format est visuellement s\u00e9lectionn\u00e9 dans #Ele0A\r\n        if (data.Rank_Emplacement_Page_Web === 'Ele0A' || sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            var _ele0AFormatDOM = '';\r\n            jQuery('#Ele0A').find('.EspPubFormatContainer').not('.FormatIdPopUp').each(function() {\r\n                if (jQuery(this).css('background-color') === 'rgb(255, 255, 255)') {\r\n                    var _cls = this.className || '';\r\n                    var _match = _cls.match(\/FormatId(\\S+)\/);\r\n                    if (_match) { _ele0AFormatDOM = _match[1]; return false; }\r\n                }\r\n            });\r\n            if (_ele0AFormatDOM) {\r\n                data.Commande_Format_Transmis = _ele0AFormatDOM;\r\n            }\r\n        }\r\n        \r\n        console.log('\ud83d\udce4 prepareUploadData:', {\r\n            FirstUploadFileorMoved: data.FirstUploadFileorMoved,\r\n            dragstart_Commande_Emplacement_Page_Web: data.dragstart_Commande_Emplacement_Page_Web,\r\n            Commande_Emplacement_Page_Web: data.Commande_Emplacement_Page_Web\r\n        });\r\n    \r\n        return data;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de rendu des aper\u00e7us de fichiers\r\n *\/\r\nconst PreviewRenderer = {\r\n    _getPositionLibelle(rankId) {\r\n        if (!rankId) return '';\r\n        var match = rankId.match(\/Ele(\\d+)[A-Z]\/);\r\n        if (!match) return '';\r\n        var rang = parseInt(match[1]);\r\n        if (rang === 0) return 'Pop-up';\r\n        if (rang === 1 || rang === 2) return 'Haut de page';\r\n        return 'Corps de page';\r\n    },\r\n    renderVideo(objectUrl, fileName, $dropZone) {\r\n        const videoElement = jQuery('<video controls autoplay muted>').attr({\r\n            'src': objectUrl,\r\n            'width': 'auto',\r\n            'height': 'auto',\r\n            'id': fileName\r\n        }).css({\r\n            'max-width': '100%',\r\n            'max-height': '100%'\r\n        });\r\n        \r\n        if (UIManager.isDesktop()) {\r\n            $dropZone.empty().append(videoElement.clone());\r\n            jQuery('#ApercuMobile').css({\r\n                'display': 'flex',\r\n                'justify-content': 'center',\r\n                'align-items': 'center'\r\n            }).append(videoElement);\r\n        } else {\r\n            const img = document.createElement('img');\r\n            img.src = objectUrl;\r\n            img.style.width = 'auto';\r\n            img.style.height = 'auto';\r\n            img.style.maxWidth = 'calc(100% - 4px)';  \/\/ \u2705 v2.4.5 : 2px retrait inset box-shadow\r\n            img.style.maxHeight = '115px';\r\n            \r\n            const container = $dropZone[0];\r\n            while (container.firstChild) {\r\n                container.removeChild(container.firstChild);\r\n            }\r\n            container.appendChild(img);\r\n        }\r\n        \r\n        StateManager.setMultiple({\r\n            'Commande_Format': 'Vid\u00e9o',\r\n            'Commande_Format_Transmis': 'Vid\u00e9o',\r\n            'videoSrc': objectUrl,\r\n            'FormatAnnonceSelection': 'Yes',\r\n            'PositionAnnonceSelection': 'Yes'\r\n        });\r\n        \r\n        jQuery('#FormatDataStep3').html('Vid\u00e9o').css({'color': '#56BE50'});\r\n        jQuery('#TarifDataStep3').css({'color': '#96894D'});\r\n    },\r\n    \r\n    renderImage(objectUrl, $dropZone) {\r\n        \/\/ v4.9ds : utiliser la hauteur du dropZone lui-m\u00eame (pos\u00e9e par _applyDzMinH)\r\n        \/\/   au lieu de la hauteur du HTMLUploadfileConteneur parent.\r\n        \/\/   Le UFC inclut header (~24px) + footer (~46px) en plus du dropZone, donc utiliser\r\n        \/\/   sa hauteur produit une image plus haute que le dropZone \u2192 crop par overflow:hidden\r\n        \/\/   du wrapper. La hauteur du dropZone est la cible exacte de la zone visible.\r\n        let maxHeight;\r\n        if (UIManager.isMobile()) {\r\n            maxHeight = 105;\r\n        } else {\r\n            \/\/ Lire la hauteur effective du dropZone (d\u00e9j\u00e0 pos\u00e9e par _applyDzMinH au moment du d\u00e9p\u00f4t)\r\n            var _dzH = $dropZone.height() || 0;\r\n            if (_dzH > 50) {\r\n                maxHeight = _dzH;\r\n            } else {\r\n                \/\/ Fallback : lire UFC mais retirer estimation header+footer (~70px)\r\n                var _ufcH = $dropZone.closest('.HTMLUploadfileConteneur').height() || 0;\r\n                maxHeight = Math.max(0, _ufcH - 70);\r\n                if (maxHeight < 50) { maxHeight = 200; }  \/\/ dernier recours\r\n            }\r\n        }\r\n    \r\n        $dropZone.css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center',\r\n            'width': '100%',\r\n            'height': maxHeight + 'px',\r\n            'overflow': 'hidden',\r\n            'position': UIManager.isMobile() ? 'relative' : '',\r\n            'top': UIManager.isMobile() ? '45px' : '',\r\n            'padding': UIManager.isMobile() ? '0 2px' : ''  \/\/ \u2705 v2.4.5 : 2px retrait inset box-shadow mobile\r\n        });\r\n    \r\n        \/\/ \u2705 v1.19.2 : Image charg\u00e9e en arri\u00e8re-plan, remplace le message d'attente au onload\r\n        \/\/ \u2705 v2.4.5 : max-width calc(100%-4px) sur mobile pour ne pas recouvrir le box-shadow inset 2px\r\n        \/\/ v4.9ds : width:100% + height:100% + object-fit:contain \u2014 l'image remplit la box du\r\n        \/\/   dropZone (comme Ele0A) tout en restant enti\u00e8re. Letterbox automatique si ratio diff.\r\n        var _mxw = UIManager.isMobile() ? 'calc(100% - 4px)' : '100%';\r\n        var img = new Image();\r\n        img.style.cssText = 'max-width:' + _mxw + '; max-height:' + maxHeight + 'px; width:100%; height:100%; object-fit:contain;';\r\n        img.onload = function() {\r\n            $dropZone.empty().append(img);\r\n        };\r\n        img.onerror = function() {\r\n            $dropZone.html('<img decoding=\"async\" src=\"' + objectUrl + '\" style=\"max-width:' + _mxw + '; max-height:' + maxHeight + 'px; width:100%; height:100%; object-fit:contain;\">');\r\n        };\r\n        img.src = objectUrl;\r\n    \r\n        StateManager.setMultiple({\r\n            'Commande_Format_Transmis': 'Image',\r\n            'FormatAnnonceSelection': 'Yes',\r\n            'PositionAnnonceSelection': 'Yes'\r\n        });\r\n    \r\n        console.log('Image ajout\u00e9e avec succ\u00e8s');\r\n    },\r\n    \r\n    async renderWord(fileObj, $dropZone) {\r\n        return new Promise((resolve, reject) => {\r\n            const reader = new FileReader();\r\n            \r\n            reader.onload = async (e) => {\r\n                try {\r\n                    const arrayBuffer = e.target.result;\r\n                    const result = await mammoth.convertToHtml({arrayBuffer: arrayBuffer});\r\n                    \r\n                    const htmlContent = result.value;\r\n                    const tempContainer = document.createElement('div');\r\n                    tempContainer.innerHTML = htmlContent;\r\n                    \r\n                    const textContent = tempContainer.textContent || \"\";\r\n                    const normalizedText = this.normalizeText(textContent);\r\n                    const firstImage = tempContainer.querySelector('img');\r\n                    \r\n                    const titleText = this.findDocumentTitle(normalizedText);\r\n                    \r\n                    if (firstImage) {\r\n                        this.renderDocumentPreview(\r\n                            $dropZone,\r\n                            firstImage.src,\r\n                            titleText,\r\n                            textContent,\r\n                            htmlContent\r\n                        );\r\n                    } else {\r\n                        \/\/ \u2705 v1.19.0 : Accepter les documents sans image\r\n                        this.renderDocumentPreview(\r\n                            $dropZone,\r\n                            null,\r\n                            titleText,\r\n                            textContent,\r\n                            htmlContent\r\n                        );\r\n                        console.log('\u2139\ufe0f Document Word sans image \u2014 aper\u00e7u texte seul');\r\n                    }\r\n                    resolve();\r\n                } catch (error) {\r\n                    $dropZone.html(`<p style=\"color: #FB5E2A; font-weight: 600; font-family: Roboto, Arial, sans-serif; font-size: 12px; text-align: center; padding: 15px;\">Erreur lors de la lecture du document<\/p>`);\r\n                    reject(error);\r\n                }\r\n            };\r\n            \r\n            reader.readAsArrayBuffer(fileObj);\r\n        });\r\n        \r\n        this.finalizeRedactionnelUpload();\r\n    },\r\n    \r\n    renderDocumentPreview($dropZone, imageSrc, title, text, htmlContent = null) {\r\n        \/\/ \u2705 v1.19.0 : Deux layouts \u2014 avec image ou texte seul\r\n        let previewHtml;\r\n\r\n        if (imageSrc) {\r\n            \/\/ Layout avec miniature image\r\n            previewHtml = `\r\n                <div class=\"doc-preview-container\">\r\n                    <div class=\"doc-preview-thumbnail\">\r\n                        <img decoding=\"async\" src=\"${imageSrc}\" alt=\"Document image\">\r\n                    <\/div>\r\n                    <div class=\"doc-preview-info\">\r\n                        <div class=\"doc-preview-title\" id=\"AdTitle\">${title}<\/div>\r\n                        <div class=\"doc-preview-description\" id=\"AdText\">${this.getTextPreview(text, 13)}<\/div>\r\n                        <div class=\"doc-preview-readmore\" id=\"AdLirelasuite\">Ouvrir et visualiser<\/div>\r\n                        <div class=\"doc-preview-FullPathAdFile\" id=\"AdFullPathAdFile\">${StateManager.get(\"FullPathAdFile\")}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n        } else {\r\n            \/\/ Layout texte seul (sans image)\r\n            previewHtml = `\r\n                <div class=\"doc-preview-container doc-preview-noimage\">\r\n                    <div class=\"doc-preview-icon\">\r\n                        <svg width=\"36\" height=\"36\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"#213864\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\r\n                            <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"\/><polyline points=\"14 2 14 8 20 8\"\/><line x1=\"16\" y1=\"13\" x2=\"8\" y2=\"13\"\/><line x1=\"16\" y1=\"17\" x2=\"8\" y2=\"17\"\/><polyline points=\"10 9 9 9 8 9\"\/>\r\n                        <\/svg>\r\n                    <\/div>\r\n                    <div class=\"doc-preview-info doc-preview-info-full\">\r\n                        <div class=\"doc-preview-title\" id=\"AdTitle\">${title}<\/div>\r\n                        <div class=\"doc-preview-description\" id=\"AdText\">${this.getTextPreview(text, 25)}<\/div>\r\n                        <div class=\"doc-preview-readmore\" id=\"AdLirelasuite\">Ouvrir et visualiser<\/div>\r\n                        <div class=\"doc-preview-FullPathAdFile\" id=\"AdFullPathAdFile\">${StateManager.get(\"FullPathAdFile\")}<\/div>\r\n                    <\/div>\r\n                <\/div>\r\n            `;\r\n        }\r\n        \r\n        $dropZone.html(previewHtml + this.getPreviewStyles());\r\n        \r\n        if (htmlContent) {\r\n            this.attachWordPreviewHandler($dropZone, title, htmlContent);\r\n        }\r\n    },\r\n    \r\n    attachWordPreviewHandler($dropZone, titleText, htmlContent) {\r\n        var $droppable = $dropZone.closest('.droppable');\r\n\r\n        \/\/ \u2705 Adapter le libell\u00e9 selon le type de document\r\n        var isInterview = (titleText || '').toLowerCase().indexOf('interview') !== -1;\r\n        var formatLabel = isInterview ? 'l\\'interview' : 'le communiqu\u00e9';\r\n        $droppable.find('.doc-preview-readmore').text('Ouvrir et visualiser ' + formatLabel);\r\n\r\n        \/\/ \u2705 v2.0.11 : D\u00e9l\u00e9gation d'\u00e9v\u00e9nement \u2014 plus robuste sur mobile (survit aux manipulations DOM)\r\n        $droppable.off('click.docpreview').on('click.docpreview', '.doc-preview-readmore', (event) => {\r\n            event.preventDefault();\r\n            event.stopPropagation();\r\n            \r\n            if (htmlContent) {\r\n                var popupTitle = isInterview ? 'Interview' : 'Communiqu\u00e9';\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    htmlContent: htmlContent\r\n                });\r\n            } else {\r\n                console.warn('Le document est encore en cours de traitement.');\r\n            }\r\n        });\r\n    },\r\n    \r\n    normalizeText(text) {\r\n        return text\r\n            .toLowerCase()\r\n            .normalize(\"NFD\")\r\n            .replace(\/[\\u0300-\\u036f]\/g, \"\");\r\n    },\r\n    \r\n    findDocumentTitle(normalizedText) {\r\n        const foundKeyword = CONFIG.keywords.find(keyword => \r\n            normalizedText.includes(keyword.normal)\r\n        );\r\n        if (foundKeyword) return foundKeyword.display;\r\n        \r\n        \/\/ \u2705 v1.19.1 : Fallback \u2014 utiliser le format s\u00e9lectionn\u00e9 (FormatSelect)\r\n        var formatSelect = (sessionStorage.getItem('FormatSelect') || '').toLowerCase();\r\n        if (formatSelect.indexOf('interview') !== -1) return 'Interview';\r\n        if (formatSelect.indexOf('communiq') !== -1) return 'Communiqu\u00e9';\r\n        \r\n        return \"Document\";\r\n    },\r\n    \r\n    getTextPreview(text, maxWords) {\r\n        const words = text.split(' ');\r\n        if (words.length > maxWords) {\r\n            return words.slice(0, maxWords).join(' ') + ' ...';\r\n        }\r\n        return text;\r\n    },\r\n    \r\n    getPreviewStyles() {\r\n        return `\r\n            <style>\r\n                @import url('https:\/\/fonts.googleapis.com\/css2?family=Roboto:wght@400;600&display=swap');\r\n                \r\n                .doc-preview-container {\r\n                    \/* v4.9ds : -4px largeur + 2px de chaque c\u00f4t\u00e9 pour laisser voir le liser\u00e9 vert *\/\r\n                    margin: 0px 2px;\r\n                    display: flex;\r\n                    width: calc(100% - 4px);\r\n                    max-height: 140px;\r\n                    overflow: hidden;\r\n                    box-sizing: border-box;\r\n                    background: white;\r\n                    background-color: #f8f8f8;\r\n                }\r\n                .doc-preview-thumbnail {\r\n                    width: 50%; \r\n                    height: 130px;\r\n                    overflow: hidden;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                    margin-top: 0px;\r\n                }\r\n                .doc-preview-thumbnail img {\r\n                    max-width: 75%;\r\n                    max-height: 75%;\r\n                    object-position: center;\r\n                    object-fit: fill;\r\n                }\r\n                .doc-preview-info {\r\n                    width: 50%;\r\n                    height: 130px; \r\n                    flex: 1;\r\n                    padding: 10px;\r\n                    display: flex;\r\n                    flex-direction: column; \r\n                }\r\n                .doc-preview-title {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #213864;\r\n                    font-size: 14px;\r\n                    font-weight: 600;\r\n                    text-align: left;\r\n                    margin-bottom: 5px;\r\n                }\r\n                .doc-preview-description {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #213864;\r\n                    font-size: 11px;\r\n                    font-weight: 400;\r\n                    text-align: left;\r\n                    overflow: hidden;\r\n                    margin-bottom: 5px;\r\n                    flex: 1;\r\n                }\r\n                .doc-preview-readmore {\r\n                    font-family: 'Roboto', sans-serif;\r\n                    color: #958848;\r\n                    font-size: 13px;\r\n                    font-weight: 600;\r\n                    text-align: left;\r\n                    cursor: pointer;\r\n                    padding: 4px 0;\r\n                    -webkit-tap-highlight-color: rgba(149,136,72,0.2);\r\n                }\r\n                @media (max-width: 999px) {\r\n                    .doc-preview-readmore { font-size: 12px; }\r\n                    \/* \u2705 Laisser 3px tout autour pour que le liser\u00e9 vert (box-shadow\/outline sur parent) soit visible *\/\r\n                    .doc-preview-container {\r\n                        width: calc(100% - 16px) !important;\r\n                        max-height: 109px !important;\r\n                        margin: 3px !important;\r\n                        margin-top: 23px !important;\r\n                        box-sizing: border-box !important;\r\n                    }\r\n                }\r\n                .doc-preview-FullPathAdFile {\r\n                    display: none;\r\n                }\r\n                \/* \u2705 v1.19.0 : Layout sans image *\/\r\n                .doc-preview-noimage {\r\n                    flex-direction: row;\r\n                    align-items: flex-start;\r\n                    gap: 10px;\r\n                    padding: 8px 10px;\r\n                }\r\n                .doc-preview-icon {\r\n                    flex-shrink: 0;\r\n                    display: flex;\r\n                    align-items: center;\r\n                    justify-content: center;\r\n                    width: 44px;\r\n                    height: 44px;\r\n                    background: #eef2f7;\r\n                    border-radius: 6px;\r\n                    margin-top: 4px;\r\n                }\r\n                .doc-preview-info-full {\r\n                    width: 100%;\r\n                    height: auto;\r\n                    max-height: 130px;\r\n                }\r\n                .doc-preview-noimage .doc-preview-description {\r\n                    font-size: 11px;\r\n                    line-height: 1.35;\r\n                    max-height: 60px;\r\n                    overflow: hidden;\r\n                }\r\n            <\/style>\r\n        `;\r\n    },\r\n    \r\n    finalizeRedactionnelUpload() {\r\n        \/\/ \u2705 v1.19.5 : D\u00e9terminer le format r\u00e9dactionnel correct\r\n        \/\/ Si le format s\u00e9lectionn\u00e9 est \"Interview\", on garde \"Interview\"\r\n        \/\/ Sinon on met \"Communiqu\u00e9\" par d\u00e9faut (au lieu de \"R\u00e9dactionnel\" qui n'est pas un format tarifaire valide)\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') || '';\r\n        var formatTransmis = 'Communiqu\u00e9'; \/\/ Par d\u00e9faut\r\n        \r\n        if (formatSelect.toLowerCase().indexOf('interview') !== -1) {\r\n            formatTransmis = 'Interview';\r\n        } else if (formatSelect.toLowerCase().indexOf('communiq') !== -1) {\r\n            formatTransmis = 'Communiqu\u00e9';\r\n        }\r\n        \r\n        StateManager.set('Commande_Format_Transmis', formatTransmis);\r\n        console.log('\u2705 Format r\u00e9dactionnel d\u00e9termin\u00e9:', formatTransmis, '(FormatSelect:', formatSelect, ')');\r\n        \r\n        if (StateManager.get(\"TarifDirectSelectionne\") === 'Yes') {\r\n            const formatText = jQuery('#FormatDataStep3').text();\r\n            if (formatText !== 'Vid\u00e9o' ? formatText !== 'Banni\u00e8re' : false) {\r\n                jQuery('#form-field-RedactionnelSelection')\r\n                    .val(formatText)\r\n                    .css({'color': '#56BE50'});\r\n            }\r\n        }\r\n        \r\n        RedactionnelDepose();\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'upload\r\n *\/\r\nconst UploadManager = {\r\n    isRunning: false,\r\n    \r\n    \/\/ \u2705 Reset manuel espace publicitaire\r\n    resetEspaceManuel: function($droppable) {\r\n        console.log('\ud83d\udd27 Reset manuel espace publicitaire');\r\n        \r\n        var $dropZone = $droppable.find('#drop_file_zone_achat');\r\n        var $uploadContainer = $droppable.find('.HTMLUploadfileConteneur');\r\n        var $container = $droppable.find('.OrdiMobileConteneurClass');\r\n        \r\n        \/\/ 1. Vider la zone de drop et restaurer le HTML par d\u00e9faut\r\n        $dropZone.empty().html(\r\n            '<div id=\"drag_upload_file_achat\">' +\r\n                '<p class=\"UploadIci\" style=\"color: #FB5E2A; font-weight: 600;\">Ici glisser \u2013 d\u00e9poser ou<br>t\u00e9l\u00e9charger une annonce<\/p>' +\r\n            '<\/div>'\r\n        );\r\n        \r\n        \/\/ 2. Afficher les \u00e9l\u00e9ments cach\u00e9s lors de l'upload\r\n        $droppable.find('.UploadIci').show();\r\n        $droppable.find('.EnvoiUlterieurTexte').show();\r\n        $droppable.find('.EnvoiUlterieurContainer').show();\r\n        $droppable.find('.EspPubFormatMainContainer').show();\r\n        $droppable.find('.EspPubFormatListe').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireClass').show();\r\n        $droppable.find('.GlisserDeposerConteneur').show();\r\n        $droppable.find('.OUClass').show();\r\n        $droppable.find('.TexteMobileAnnonce').show();\r\n        $droppable.find('.PositionReference').show();\r\n        $droppable.find('.ClassHdpCdp').show();\r\n        $droppable.find('.ClassRefEsp').show();\r\n        \r\n        \/\/ 3. Cacher les \u00e9l\u00e9ments d'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle').hide();\r\n        $droppable.find('#CroixResetAnnonce').hide();\r\n        $droppable.find('.CroixResetAnnonceContainer').hide();\r\n        $droppable.find('.DeplaceAnnonce').hide();\r\n        $droppable.find('.RefEspacePublicitaire').hide();\r\n        $droppable.find('.AdDroppedTextNotDisplayed').hide();\r\n        \r\n        \/\/ 4. Reset des styles\r\n        $uploadContainer.css({\r\n            'border': 'none',\r\n            'background-color': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': ''\r\n        });\r\n        \r\n        $container.css({\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'top': '',\r\n            'height': ''\r\n        });\r\n        \r\n        \/\/ \u2705 Reset des positionnements desktop appliqu\u00e9s par adjustDesktopLayout\r\n        $droppable.closest('.ToBeHidden').css({'top': '', 'min-height': ''});\r\n        $droppable.parent().css('overflow', '');\r\n        \/\/ \u2705 v2.0.11 : Cleanup event namespace docpreview\r\n        $droppable.off('click.docpreview');\r\n        \r\n        \/\/ 5. Reset des formats s\u00e9lectionn\u00e9s\r\n        $droppable.find('.EspPubFormatContainer').css({\r\n            'background-color': '',\r\n            'border': ''\r\n        });\r\n        \r\n        \/\/ 6. Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ 7. Restaurer .PositionEspacePublicitaireContainer et .ReserverContainer\r\n        $droppable.find('.PositionEspacePublicitaireContainer').show();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \r\n        console.log('\u2705 Reset manuel termin\u00e9');\r\n    },\r\n    \r\n    async handleFileUpload(fileObj, $dropZone) {\r\n        console.log('fileObj:', fileObj);\r\n        \r\n        \/\/ \u2705 v2.4.6 : Contr\u00f4les d'extension EN PREMIER \u2014 avant tout affichage\r\n        const extension = FileManager.getExtension(fileObj.name);\r\n        StateManager.set('FileExtension', extension);\r\n        console.log('FileExtension:', extension);\r\n        \r\n        \/\/ \u2705 v2.6 : jpg\/jpeg accept\u00e9s sur desktop et mobile\r\n        \r\n        if (!FileManager.isAllowedExtension(extension)) {\r\n            UIManager.showFormatError($dropZone);\r\n            return;\r\n        }\r\n\r\n        \/\/ \u2705 v4.9o : Guard type de fichier vs format s\u00e9lectionn\u00e9\r\n        var _$droppableGuard = $dropZone.closest('.droppable');\r\n        if (!_fromMiniatureGuard) {\r\n            if (!_$droppableGuard.length) { _$droppableGuard = jQuery(); }\r\n            var _fmtChkLabel = '';\r\n            var _fmtClsMap = {\r\n                'formatidbanniere':   'banni\u00e8re',\r\n                'formatidparrainage': 'parrainage',\r\n                'formatidcommunique': 'communiqu\u00e9',\r\n                'formatidinterview':  'interview',\r\n                'formatidvideo':      'vid\u00e9o'\r\n            };\r\n            _$droppableGuard.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').each(function() {\r\n                var _sel = false;\r\n                var _bgV = this.style.backgroundColor || '';\r\n                if (_bgV === 'rgb(255, 255, 255)') { _sel = true; }\r\n                if (!_sel) { if (_bgV === '#ffffff' || _bgV === 'white') { _sel = true; } }\r\n                if (!_sel) {\r\n                    var _fEl = this.querySelector('.EspPubFormat');\r\n                    if (_fEl) {\r\n                        var _col = _fEl.style.color || '';\r\n                        if (_col === 'rgb(55, 217, 0)') { _sel = true; }\r\n                        if (!_sel) { if (_col.toLowerCase() === '#37d900') { _sel = true; } }\r\n                    }\r\n                }\r\n                if (_sel) {\r\n                    var _clsN = this.className.toLowerCase().normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '');\r\n                    var _keys = Object.keys(_fmtClsMap);\r\n                    for (var _ki = 0; _ki < _keys.length; _ki++) {\r\n                        if (_clsN.indexOf(_keys[_ki]) !== -1) { _fmtChkLabel = _fmtClsMap[_keys[_ki]]; return false; }\r\n                    }\r\n                }\r\n            });\r\n            if (_fmtChkLabel) {\r\n                var _extChk  = extension.toLowerCase();\r\n                var _kindChk = CONFIG.allowedExtensions.video.indexOf(_extChk) !== -1 ? 'video'\r\n                             : CONFIG.allowedExtensions.image.indexOf(_extChk) !== -1 ? 'image'\r\n                             : 'document';\r\n                var _imgLbls = ['banni\u00e8re', 'parrainage'];\r\n                var _docLbls = ['communiqu\u00e9', 'interview'];\r\n                var _vidLbls = ['vid\u00e9o'];\r\n                var _compat  = true;\r\n                if (_kindChk === 'image')    { _compat = (_imgLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk === 'document') { _compat = (_docLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk === 'video')    { _compat = (_vidLbls.indexOf(_fmtChkLabel) !== -1); }\r\n                if (_kindChk) { if (!_compat) {\r\n                    UIManager.showFormatError($dropZone, 'Merci de d\u00e9poser un format ' + _fmtChkLabel);\r\n                    console.warn('\ud83d\udeab D\u00e9p\u00f4t refus\u00e9 : fichier ' + _kindChk + ' incompatible avec format ' + _fmtChkLabel);\r\n                    return;\r\n                } }\r\n            }\r\n        }\r\n\r\n        \/\/ v4.9ci : Guard format \u2014 refuser le d\u00e9p\u00f4t si aucun format n'a \u00e9t\u00e9 s\u00e9lectionn\u00e9\r\n        \/\/          DANS LE DROPPABLE cible (pas globalement).\r\n        \/\/ Exception : drop depuis la miniature kit (le format a d\u00e9j\u00e0 \u00e9t\u00e9 choisi dans le kit\r\n        \/\/ juste avant le drop).\r\n        \/\/ v4.9co : D\u00e9tection locale via la classe visuelle \u2014 un EspPubFormatContainer est\r\n        \/\/          \"s\u00e9lectionn\u00e9\" si son EspPubFormat a la couleur verte (#37D900) OU son fond est #ffffff.\r\n        \/\/          On exclut FormatIdCreation et FormatIdPopUp qui ont leur propre \u00e9tat.\r\n        var _fromKitDropGuard = _$droppableGuard.length ? (_$droppableGuard[0].getAttribute('data-kit-drop') === 'true') : false;\r\n        var _fromMiniatureGuard = window._dropFromMiniature || _fromKitDropGuard;\r\n        if (!_fromMiniatureGuard ? _$droppableGuard.length : false) {\r\n            var _hasLocalFmt = false;\r\n            _$droppableGuard.find('.EspPubFormatContainer').each(function() {\r\n                if (jQuery(this).hasClass('FormatIdCreation')) return;\r\n                if (jQuery(this).hasClass('FormatIdPopUp')) return;\r\n                \/\/ Crit\u00e8re 1 : fond blanc inline (pos\u00e9 par handler click)\r\n                var _bg = this.style.backgroundColor || '';\r\n                if (_bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white') {\r\n                    _hasLocalFmt = true;\r\n                    return false; \/\/ break\r\n                }\r\n                \/\/ Crit\u00e8re 2 : texte vert sur .EspPubFormat\r\n                var _fmt = this.querySelector('.EspPubFormat');\r\n                if (_fmt) {\r\n                    var _col = _fmt.style.color || '';\r\n                    if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') {\r\n                        _hasLocalFmt = true;\r\n                        return false;\r\n                    }\r\n                }\r\n            });\r\n            if (!_hasLocalFmt) {\r\n                var _rankId = _$droppableGuard.attr('id') || '?';\r\n                console.warn('\ud83d\udeab D\u00e9p\u00f4t refus\u00e9 : aucun format s\u00e9lectionn\u00e9 dans ce droppable | rank=' + _rankId);\r\n                \/\/ R\u00e9activer le message d'erreur format\r\n                FormatUIManager.flashTitle(_$droppableGuard.find('.HTMLUploadfileConteneur, #UploadFileConteneur').first());\r\n                UIManager.showFormatError($dropZone, 'Merci de s\u00e9lectionner un format d\\'annonce avant de d\u00e9poser votre fichier');\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v1.19.5 : Afficher le message d'attente seulement si le format est valide\r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        this.prepareUIForUpload($dropZone);\r\n        \r\n        const formData = new FormData();\r\n        formData.append('file', fileObj);\r\n        formData.append('action', 'upload_annonce_file_v3');\r\n        \r\n        UIManager.showUploadProgress($dropZone);\r\n        \r\n        try {\r\n            const response = await this.sendUploadRequest(formData);\r\n            await this.handleUploadResponse(response, fileObj, $dropZone, extension);\r\n        } catch (error) {\r\n            console.error('Upload error:', error);\r\n        }\r\n    },\r\n    \r\n    prepareUIForUpload($dropZone) {\r\n        if (!UIManager.isMobile()) {\r\n            jQuery('.ChoisirEspacePublicitaireClass').show();\r\n            jQuery('.ChoisirEspacePublicitaire2ndLigne').show();\r\n            jQuery('.GlisserDeposerConteneur').show();\r\n            jQuery('.OUClass').show();\r\n            \/\/ \u2705 Cibler uniquement le droppable courant \u2014 ne pas r\u00e9afficher sur les espaces d\u00e9j\u00e0 upload\u00e9s\r\n            $dropZone.closest('.droppable').find('.UploadIci').show();\r\n        }\r\n        \r\n        if (StateManager.get(\"PopUpChoice\") === 'Yes') {\r\n            \/\/ \u2705 Ne masquer DeplaceAnnonce QUE sur Ele0A \u2014 pas sur Ele1A qui a deja une annonce chargee\r\n            $dropZone.closest('.OrdiMobileConteneurClass').find('.DeplaceAnnonce').each(function() {\r\n                var _d = jQuery(this).closest('.droppable')[0];\r\n                if (_d ? _d.getAttribute('data-via-ad-loaded') === 'true' : false) { return; }\r\n                jQuery(this).hide();\r\n            });\r\n            $('.AnnonceDragIcone').show().find('img').attr('alt', '');\r\n        } else {\r\n            $('.AnnonceDragIcone').hide();\r\n        }\r\n        \r\n        if (StateManager.get(\"Commande_Page\") === 'Home Page') {\r\n            jQuery('#HPTarifConteneur').css({'background-color': '#B5FFB1'});\r\n        }\r\n        \r\n        jQuery('.ChoisirEspacePublicitaireClass').css({'color': '#ffffff'});\r\n        jQuery('.InterfaceTitreDore').not('#PageWebTitreDore')\r\n            .html(\"Merci de choisir les \u00e9l\u00e9ments de votre campagne publicitaire\");\r\n        jQuery('#ProcessCommande').show();\r\n    },\r\n    \r\n    sendUploadRequest(formData) {\r\n        \/\/ \u2705 v2.6 : dataType 'text' pour eviter parsererror si texte parasite apres le JSON\r\n        return jQuery.ajax({\r\n            url: CONFIG.ajaxUrl,\r\n            type: 'POST',\r\n            data: formData,\r\n            cache: false,\r\n            contentType: false,\r\n            processData: false,\r\n            dataType: 'text'\r\n        }).then(function(text) {\r\n            \/\/ Extraire le JSON meme si du texte parasite suit\r\n            var _json = null;\r\n            try {\r\n                var _match = text.match(\/(\\{[\\s\\S]*?\\})(?:[^{]|$)\/);\r\n                _json = JSON.parse(_match ? _match[1] : text);\r\n            } catch(_e) {\r\n                return jQuery.Deferred().reject({ responseText: text }).promise();\r\n            }\r\n            \/\/ Normaliser le format vers {success, data} attendu par handleUploadResponse\r\n            if (_json.status === 'success') {\r\n                var _fp = _json.file_path || '';\r\n                var _baseUrl = CONFIG.ajaxUrl.replace('\/wp-admin\/admin-ajax.php', '\/wp-admin\/');\r\n                return {\r\n                    success: true,\r\n                    data: {\r\n                        file_url:  _baseUrl + _fp,\r\n                        file_name: _fp.replace(\/^.*\\\/\/, ''),\r\n                        file_path: _fp\r\n                    }\r\n                };\r\n            }\r\n            \/\/ Format WordPress standard {success, data} - retourner tel quel\r\n            return _json;\r\n        });\r\n    },\r\n    \r\n    async handleUploadResponse(response, fileObj, $dropZone, extension) {\r\n        console.log('\u2705 R\u00e9ponse re\u00e7ue');\r\n        \r\n        if (response.responseJSON) {\r\n            response = response.responseJSON;\r\n        }\r\n        \r\n        console.log('\u2705 Response pars\u00e9e:', response);\r\n        \r\n        if (!response.success || !response.data) {\r\n            \/\/ \u2705 v2.1.2 : Si c'est une restauration (_isAdRestoration = 'Yes'),\r\n            \/\/ le fichier existe d\u00e9j\u00e0 sur le serveur \u2192 afficher l'image depuis le fileObj local\r\n            if (StateManager.get('_isAdRestoration') === 'Yes' ? fileObj : false) {\r\n                console.log('\ud83d\udd04 Restauration: upload \u00e9chou\u00e9 (fichier existant) \u2192 rendu local');\r\n                \r\n                FileManager.createObjectUrl(fileObj);\r\n                StateManager.set('FileReceived', 'Yes');\r\n                \r\n                \/\/ \u2705 v2.1.2 : Restaurer l'\u00e9tat EnvoiUlterieur depuis le sessionStorage parent\r\n                var _restoredEnvoi = sessionStorage.getItem('_restoredEnvoiUlterieur');\r\n                if (_restoredEnvoi === 'true') {\r\n                    StateManager.set('EnvoiUlterieur', 'true');\r\n                    StateManager.set('FileReceived', 'No');\r\n                    StateManager.set('FullPathAdFile', '');\r\n                    console.log('\ud83d\udce4 Restauration EnvoiUlterieur = true');\r\n                }\r\n                \r\n                var _objUrl = StateManager.get('objectUrl');\r\n                var _fileType = FileManager.getFileType(extension);\r\n                \r\n                StateManager.set('FormatReconnu', 'No');\r\n                \r\n                switch (_fileType) {\r\n                    case 'video':\r\n                        PreviewRenderer.renderVideo(_objUrl, fileObj.name, $dropZone);\r\n                        StateManager.set('FormatReconnu', 'Yes');\r\n                        break;\r\n                    case 'image':\r\n                        PreviewRenderer.renderImage(_objUrl, $dropZone);\r\n                        StateManager.set('FormatReconnu', 'Yes');\r\n                        break;\r\n                    case 'document':\r\n                        \/\/ \u2705 v2.1.2 : Documents Word\/PDF aussi en restauration\r\n                        await this.handleDocumentUpload(extension, fileObj, $dropZone);\r\n                        break;\r\n                }\r\n                \r\n                UIManager.updateAfterSuccessfulUpload($dropZone);\r\n                \r\n                if (StateManager.get('FormatReconnu') === 'Yes') {\r\n                    this.finalizeUpload($dropZone);\r\n                    $('#MsgSelectEspace').hide();\r\n                }\r\n                \r\n                FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n                console.log('\u2705 Restauration visuelle termin\u00e9e (upload bypass)');\r\n                StateManager.set('_isAdRestoration', 'No');\r\n            }\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 SAUVEGARDER les infos de d\u00e9placement AVANT updateStateAfterUpload\r\n        const isMoved = StateManager.get('FirstUploadFileorMoved') === 'Moved';\r\n        const dragstartRankId = StateManager.get('dragstart_Rank_Emplacement_Page_Web');\r\n        const currentRankId = StateManager.get('Rank_Emplacement_Page_Web');\r\n        const shouldResetOldSpace = isMoved ? (dragstartRankId ? dragstartRankId !== currentRankId : false) : false;\r\n        \/\/ \u2705 Bug 10 : self-drop (m\u00eame espace) \u2014 nettoyer pendingAd du rank source\r\n        \/\/   Le drop sur soi-m\u00eame ne passe pas par dataDelAd, donc pendingAd persiste\r\n        \/\/   et provoque la r\u00e9apparition de l'annonce lors d'un d\u00e9placement ult\u00e9rieur\r\n        if (isMoved ? (dragstartRankId ? (dragstartRankId !== 'No' ? dragstartRankId === currentRankId : false) : false) : false) {\r\n            sessionStorage.removeItem('pendingAd_' + dragstartRankId);\r\n            console.log('[Bug10] self-drop d\u00e9tect\u00e9 \u2014 pendingAd_' + dragstartRankId + ' effac\u00e9');\r\n        }\r\n        \r\n        console.log('\ud83d\udd0d D\u00e9placement?', { isMoved, dragstartRankId, currentRankId, shouldResetOldSpace });\r\n        \r\n        this.updateStateAfterUploadWithoutReset(response, fileObj);\r\n        \r\n        const fileType = FileManager.getFileType(extension);\r\n        \r\n        StateManager.set('FormatReconnu', 'No');\r\n        \r\n        \/\/ \u2705 v1.19.2 : Message d'attente IMM\u00c9DIAT avant le rendu de l'annonce\r\n        \/\/ \u2705 v2.7.3 : Nettoyer l'overlay pr\u00e9c\u00e9dent et masquer avant le loading\r\n        (function() {\r\n            var $_dz2 = $dropZone.closest('.droppable');\r\n            \/\/ Supprimer l'ancien wrapper overlay (re-upload)\r\n            var $_oldWrap = $_dz2.find('.via-ad-wrapper');\r\n            if ($_oldWrap.length) {\r\n                var $_ufcBack = $_dz2.find('.HTMLUploadfileConteneur');\r\n                $_oldWrap.before($_ufcBack);\r\n                $_oldWrap.remove();\r\n            }\r\n            \/\/ Masquer header r\u00e9siduel + anciens \u00e9l\u00e9ments drag\/titre\/position\r\n            $_dz2.find('.via-ad-header, .via-ad-footer, .CroixResetAnnonceContainer, .DeplaceAnnonce, .AdUploadedTitle, .PositionEspacePublicitaire, .PositionEspacePublicitaireDeplacer, .RefEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur').hide();\r\n            $_dz2.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').each(function() { this.style.setProperty('display','none','important'); });\r\n        })();\r\n        switch (fileType) {\r\n            case 'video':\r\n                PreviewRenderer.renderVideo(StateManager.get('objectUrl'), fileObj.name, $dropZone);\r\n                StateManager.set('FormatReconnu', 'Yes');\r\n                break;\r\n                \r\n            case 'image':\r\n                PreviewRenderer.renderImage(StateManager.get('objectUrl'), $dropZone);\r\n                StateManager.set('FormatReconnu', 'Yes');\r\n                break;\r\n                \r\n            case 'document':\r\n                await this.handleDocumentUpload(extension, fileObj, $dropZone);\r\n                break;\r\n        }\r\n        \r\n        UIManager.updateAfterSuccessfulUpload($dropZone);\r\n        \r\n        if (StateManager.get('FormatReconnu') === 'Yes') {\r\n            this.finalizeUpload($dropZone);\r\n            $('#MsgSelectEspace').hide();\r\n        }\r\n        \r\n        \/\/ \u2705 RESET L'ANCIEN ESPACE ICI - APR\u00c8S avoir affich\u00e9 la nouvelle image\r\n        \/\/ \u2705 v2.4.5 : Toujours initialiser \u00e0 false avant le bloc (\u00e9vite la valeur r\u00e9siduelle)\r\n        var _sourceWasReserved = false;\r\n        StateManager.set('_sourceWasReserved', 'No');\r\n        \r\n        if (shouldResetOldSpace) {\r\n            console.log('\ud83d\udd04 Reset ancien espace:', dragstartRankId);\r\n            \r\n            var $oldDroppable = $('#' + dragstartRankId);\r\n            \r\n            if ($oldDroppable.length) {\r\n                var $container = $oldDroppable.find('.OrdiMobileConteneurClass').first();\r\n                \r\n                \/\/ \u2705 v2.4.5 : Utiliser l'\u00e9tat captur\u00e9 au dragstart (fiable vs re-check async)\r\n                if ($container.length) {\r\n                    _sourceWasReserved = StateManager.get('dragstart_ReserverChecked') === 'Yes';\r\n                    console.log('[d\u00e9placement] ReserverChecked au dragstart:', _sourceWasReserved, '| rank:', dragstartRankId);\r\n                    StateManager.set('_sourceWasReserved', _sourceWasReserved ? 'Yes' : 'No');\r\n                    RestoreadSpaceTemplateLocal($container[0]);\r\n                }\r\n            }\r\n        }\r\n        StateManager.set(\"EnvoiUlterieur\", 'false');\r\n        \r\n        \/\/ \u2705 v1.19.1 : Positionner l'espace \u00e0 10px du haut du viewport apr\u00e8s upload\r\n        setTimeout(function() {\r\n            var el = $dropZone.closest('.droppable').find('.HTMLUploadfileConteneur')[0]\r\n                     || $dropZone.closest('.droppable')[0];\r\n            if (el) {\r\n                ScrollHelper.scrollElementTo(el, 10);\r\n            }\r\n        }, 400);\r\n        \r\n        \/\/ \u2705 NE PLUS envoyer automatiquement - c'est la checkbox \"R\u00e9server\" qui d\u00e9clenche\r\n        \/\/ On met \u00e0 jour l'\u00e9tat de la checkbox pour qu'elle devienne activable\r\n        FormatUIManager.updateReserverCheckboxState($dropZone.closest('.droppable'));\r\n        \r\n        \/\/ \u2705 v2.4.5 : Si l'espace source \u00e9tait r\u00e9serv\u00e9, cocher la checkbox de l'espace cible\r\n        if (StateManager.get('_sourceWasReserved') === 'Yes') {\r\n            var $targetCb = $dropZone.closest('.droppable').find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n            if ($targetCb.length ? !$targetCb.prop('checked') : false) {\r\n                $targetCb.prop('checked', true).trigger('change');\r\n                console.log('\u2705 [d\u00e9placement] Checkbox R\u00e9server coch\u00e9e sur espace cible');\r\n            }\r\n            StateManager.set('_sourceWasReserved', 'No');\r\n        }\r\n        \r\n        console.log('\ud83d\udccb Upload termin\u00e9 - en attente de validation via checkbox \"R\u00e9server\"');\r\n    },\r\n    \r\n    updateStateAfterUploadWithoutReset(response, fileObj) {\r\n        StateManager.setMultiple({\r\n            \"PageWhithADType\": StateManager.get('SelectedPageType'),\r\n            \"PageWhithADSecteur\": StateManager.get('SelectedPageSecteur')\r\n        });\r\n        \r\n        $('#BackPageWebWithADCroix').hide();\r\n        jQuery('#ApercuMobile').empty().css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center'\r\n        });\r\n        \r\n        const firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n        \r\n        if (firstUploadOrMoved === 'FirstUpload') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": response.data.file_url,\r\n                \"fileObj\": fileObj,\r\n                \"Upload_File_Name\": fileObj.name\r\n            });\r\n            \r\n            FileManager.createObjectUrl(fileObj);\r\n            \r\n            console.log('\ud83d\udcc1 Fichier upload\u00e9:', {\r\n                fullUrl: response.data.file_url,\r\n                fileName: response.data.file_name,\r\n                filePath: response.data.file_path\r\n            });\r\n        }\r\n    },\r\n    \r\n    updateStateAfterUpload(response, fileObj) {\r\n        StateManager.setMultiple({\r\n            \"PageWhithADType\": StateManager.get('SelectedPageType'),\r\n            \"PageWhithADSecteur\": StateManager.get('SelectedPageSecteur')\r\n        });\r\n        \r\n        $('#BackPageWebWithADCroix').hide();\r\n        jQuery('#ApercuMobile').empty().css({\r\n            'display': 'flex',\r\n            'justify-content': 'center',\r\n            'align-items': 'center'\r\n        });\r\n        \r\n        const firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n        console.log('\ud83d\udd0d FirstUploadFileorMoved:', firstUploadOrMoved);\r\n        \r\n        if (firstUploadOrMoved === 'FirstUpload') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": response.data.file_url,\r\n                \"fileObj\": fileObj,\r\n                \"Upload_File_Name\": fileObj.name\r\n            });\r\n            \r\n            FileManager.createObjectUrl(fileObj);\r\n            \r\n            console.log('\ud83d\udcc1 Fichier upload\u00e9:', {\r\n                fullUrl: response.data.file_url,\r\n                fileName: response.data.file_name,\r\n                filePath: response.data.file_path\r\n            });\r\n        } else if (firstUploadOrMoved === 'Moved') {\r\n            const dragstartRankId = StateManager.get('dragstart_Rank_Emplacement_Page_Web');\r\n            const currentRankId = StateManager.get('Rank_Emplacement_Page_Web');\r\n            \r\n            console.log('\ud83d\udd04 D\u00c9PLACEMENT D\u00c9TECT\u00c9');\r\n            console.log('   Ancien espace (dragstart):', dragstartRankId);\r\n            console.log('   Nouvel espace (current):', currentRankId);\r\n            \r\n            if (dragstartRankId ? dragstartRankId !== currentRankId : false) {\r\n                const $oldDroppable = $('#' + dragstartRankId);\r\n                \r\n                console.log('   $oldDroppable trouv\u00e9:', $oldDroppable.length > 0);\r\n                \r\n                if ($oldDroppable.length) {\r\n                    const $container = $oldDroppable.find('.OrdiMobileConteneurClass');\r\n                    \r\n                    console.log('   $container trouv\u00e9:', $container.length > 0);\r\n                    \r\n                    if ($container.length) {\r\n                        console.log('\ud83d\udd27 Appel RestoreadSpaceTemplate sur:', $container[0]);\r\n                        \r\n                        if (typeof window.RestoreadSpaceTemplate === 'function') {\r\n                            window.RestoreadSpaceTemplate($container[0]);\r\n                            console.log('\u2705 RestoreadSpaceTemplate ex\u00e9cut\u00e9');\r\n                        }\r\n                    } else {\r\n                        console.warn('\u26a0\ufe0f .OrdiMobileConteneurClass non trouv\u00e9 dans', dragstartRankId);\r\n                    }\r\n                }\r\n            } else {\r\n                console.log('\u23ed\ufe0f M\u00eame espace ou dragstartRankId invalide, pas de reset');\r\n            }\r\n        }\r\n    },\r\n        \r\n    async handleDocumentUpload(extension, fileObj, $dropZone) {\r\n        StateManager.set('FormatReconnu', 'Yes');\r\n        \r\n        \/\/ \u2705 Cacher le File r\u00e9dactionnel pour r\u00e9utilisation lors des d\u00e9placements (\u00e9vite CORS)\r\n        window._lastRedactionnelFile = fileObj;\r\n        \r\n        if (extension === 'doc' || extension === 'docx') {\r\n            await new Promise(resolve => window.VIALibraries.loadMammoth(resolve));\r\n            await PreviewRenderer.renderWord(fileObj, $dropZone);\r\n        } else if (extension === 'ppt' || extension === 'pptx') {\r\n            this.renderPowerPoint($dropZone);\r\n            PreviewRenderer.finalizeRedactionnelUpload();\r\n        } else if (extension === 'pdf') {\r\n            await new Promise(resolve => window.VIALibraries.loadPdfJs(resolve));\r\n            await PDFHandler.renderPDF(fileObj, $dropZone);\r\n            PreviewRenderer.finalizeRedactionnelUpload();\r\n        }\r\n    },\r\n    \r\n    renderPowerPoint($dropZone) {\r\n        const img = jQuery('<img \/>', {\r\n            src: '\/wp-content\/uploads\/2024\/06\/microsoft-powerpoint.png',\r\n            css: {\r\n                'width': 'auto',\r\n                'height': 'auto',\r\n                'margin-bottom': '20px',\r\n                'max-width': '150px',\r\n                'max-height': '160px'\r\n            }\r\n        });\r\n        $dropZone.empty().append(img.clone());\r\n        jQuery('#ApercuMobile').append(img);\r\n    },\r\n    \r\n    finalizeUpload($dropZone) {\r\n        \/\/ \u2705 Reset sendDataToParentFlag : nouvel upload = pas encore r\u00e9serv\u00e9\r\n        StateManager.set('sendDataToParentFlag', 'No');\r\n        \r\n        StateManager.setMultiple({\r\n            \"PageAnnonceSelection\": 'Yes',\r\n            \"FormatReconnu\": 'Yes'\r\n        });\r\n        \r\n        jQuery('#MsgChoixPageWeb, #MsgInsererAnnonceConteneur').hide();\r\n        \r\n        if (StateManager.get(\"Commande_Format\") === 'R\u00e9dactionnel') {\r\n            StateManager.set('Commande_Format', '\u00e0 choisir');\r\n            RedactionnelDepose();\r\n        }\r\n        \r\n        if (StateManager.get(\"Commande_Page\") === ' ') {\r\n            jQuery('#HPTarifConteneur').css({'background-color': '#BCFFAD'});\r\n            jQuery('#EmplacementDataStep3').html(\"Home Page\");\r\n            StateManager.setMultiple({\r\n                \"Commande_Page\": 'Home Page',\r\n                \"PageAnnonceSelection\": 'Yes'\r\n            });\r\n        }\r\n        \r\n        $('#PageWeb').css('zoom', '70%');\r\n        $('#PageWebTitreDore').css({'font-size': '14px'});\r\n        \r\n        if (!UIManager.isMobile()) {\r\n            $('#PageWebTitreDore').css({'color': '#213864'});\r\n        }\r\n        \r\n        StateManager.set(\"Page_Web_with_AD_URL\", StateManager.get(\"Page_Web_URL\"));\r\n        \r\n        UIManager.finalizeAdDisplay($dropZone);\r\n        \r\n        \/\/ \u2705 Notifier le parent de l'annonce d\u00e9pos\u00e9e non r\u00e9serv\u00e9e \u2014 pour restauration au retour sur la page\r\n        var _fileRcv = StateManager.get('FileReceived');\r\n        var _isRestoring = StateManager.get('_isAdRestoration') === 'Yes';\r\n        console.log('\ud83d\udfe1 [finalizeUpload] FileReceived:', _fileRcv, '| _isAdRestoration:', _isRestoring);\r\n        if (_fileRcv === 'Yes' ? !_isRestoring : false) {\r\n            var _pendingRank = StateManager.get('Rank_Emplacement_Page_Web') || $dropZone.closest('.droppable').attr('id') || '';\r\n            console.log('\ud83d\udfe1 [finalizeUpload] pendingRank:', _pendingRank, '| FullPathAdFile:', StateManager.get('FullPathAdFile'));\r\n            if (_pendingRank) {\r\n                console.log('\ud83d\udce4 [finalizeUpload] annonceDeposeeSansReservation \u2192 parent rank:', _pendingRank);\r\n                \/\/ \u2705 D\u00e9terminer le vrai format commercial selon le type de fichier d\u00e9pos\u00e9 + format s\u00e9lectionn\u00e9\r\n                var _formatSelect = sessionStorage.getItem('FormatSelect') || StateManager.get('Commande_Format_Transmis') || '';\r\n                var _uploadedExt = (StateManager.get('Upload_File_Name') || '').split('.').pop().toLowerCase();\r\n                var _fileKind = CONFIG.allowedExtensions.video.indexOf(_uploadedExt) !== -1 ? 'video'\r\n                              : CONFIG.allowedExtensions.image.indexOf(_uploadedExt) !== -1 ? 'image'\r\n                              : CONFIG.allowedExtensions.document.indexOf(_uploadedExt) !== -1 ? 'document'\r\n                              : '';\r\n                var _formatPending;\r\n                if (_fileKind === 'video') {\r\n                    \/\/ Vid\u00e9o \u2192 toujours Vid\u00e9o\r\n                    _formatPending = 'Vid\u00e9o';\r\n                } else if (_fileKind === 'image') {\r\n                    \/\/ Image \u2192 Banni\u00e8re ou Parrainage si s\u00e9lectionn\u00e9, sinon Banni\u00e8re par d\u00e9faut\r\n                    var _imgFormats = ['Banni\u00e8re', 'Banniere', 'Parrainage'];\r\n                    _formatPending = (_imgFormats.indexOf(_formatSelect) !== -1) ? _formatSelect : 'Banni\u00e8re';\r\n                } else if (_fileKind === 'document') {\r\n                    \/\/ Document \u2192 Communiqu\u00e9 ou Interview si s\u00e9lectionn\u00e9, sinon Communiqu\u00e9 par d\u00e9faut\r\n                    var _docFormats = ['Communiqu\u00e9', 'Communique', 'Interview'];\r\n                    _formatPending = (_docFormats.indexOf(_formatSelect) !== -1) ? _formatSelect : 'Communiqu\u00e9';\r\n                } else {\r\n                    \/\/ Fallback\r\n                    _formatPending = _formatSelect || StateManager.get('Commande_Format_Transmis') || '';\r\n                }\r\n                \/\/ \u2705 v2.4.9 : Si le format d\u00e9duit diff\u00e8re du format s\u00e9lectionn\u00e9 \u2192 mettre \u00e0 jour vignette + sessionStorage\r\n                if (_formatPending ? _formatPending !== _formatSelect : false) {\r\n                    StateManager.set('FormatSelect', _formatPending);\r\n                    StateManager.set('Commande_Format_Transmis', _formatPending);\r\n                    StateManager.set('Formatchoisi', 'Yes');\r\n                    \/\/ Mettre \u00e0 jour visuellement la vignette dans le droppable courant\r\n                    var _fmtNorm = _formatPending.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    var $_drp = $dropZone.closest('.droppable');\r\n                    $_drp.find('.EspPubFormatContainer').each(function() {\r\n                        var _cls = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                        if (_cls.includes(_fmtNorm)) {\r\n                            jQuery(this).css({'background-color': '#ffffff'});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        } else if (!jQuery(this).hasClass('FormatIdPopUp')) {\r\n                            jQuery(this).css({'background-color': ''});\r\n                            jQuery(this).find('.EspPubFormat').css({'color': ''});\r\n                        }\r\n                    });\r\n                    \/\/ Mettre \u00e0 jour aussi le bandeau parent via postMessage\r\n                    MessageManager.sendToParent('formatChangedInIframe', { formatSelect: _formatPending, rank: _pendingRank });\r\n                    console.log('\ud83d\udd04 [finalizeUpload] Format corrig\u00e9:', _formatSelect, '\u2192', _formatPending);\r\n                }\r\n                var _firstUploadOrMoved = StateManager.get('FirstUploadFileorMoved');\r\n                var _isMoved = _firstUploadOrMoved === 'Moved';\r\n                var _oldRankMoved = _isMoved ? (StateManager.get('dragstart_Rank_Emplacement_Page_Web') || '') : '';\r\n                MessageManager.sendToParent('annonceDeposeeSansReservation', {\r\n                    Rank_Emplacement_Page_Web: _pendingRank,\r\n                    FullPathAdFile: StateManager.get('FullPathAdFile') || '',\r\n                    Upload_File_Name: StateManager.get('Upload_File_Name') || '',\r\n                    LoadedPageUrl: window.location.href,\r\n                    FileReceived: 'Yes',\r\n                    \/\/ \u2705 Lire EnvoiUlterieur depuis la checkbox du droppable courant (pas le StateManager global)\r\n                    \/\/ StateManager.get('EnvoiUlterieur') est global \u2192 peut valoir 'true' si un autre espace a sa checkbox coch\u00e9e\r\n                    EnvoiUlterieur: ($dropZone.closest('.droppable').find('input[name*=\"EnvoiUlterieur\"]').prop('checked') ? 'true' : 'false'),\r\n                    Commande_Format_Transmis: _formatPending,\r\n                    codeSite: StateManager.get('codeSite') || '',\r\n                    codePage: StateManager.get('codePage') || '',\r\n                    Commande_Emplacement_Page_Web: StateManager.buildEmplacementReference(_pendingRank),\r\n                    isMoved: _isMoved,\r\n                    oldRank: _oldRankMoved\r\n                });\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 Page \/annonce : pas de checkbox R\u00e9server \u2192 enregistrer imm\u00e9diatement\r\n        if (location.pathname === '\/annonce' || location.pathname === '\/annonce\/') {\r\n            StateManager.set('sendDataToParentFlag', 'Yes');\r\n            this.activateSendDataToParent($dropZone);\r\n        }\r\n    },\r\n    \r\n    activateSendDataToParent($dropZone) {\r\n        if (StateManager.get(\"sendDataToParentFlag\") !== 'Yes') {\r\n            return;\r\n        }\r\n        \r\n        if (StateManager.get(\"FirstUploadFileorMoved\") === 'Moved') {\r\n            const dragstartRef = StateManager.buildEmplacementReference(\r\n                StateManager.get(\"dragstart_Rank_Emplacement_Page_Web\")\r\n            );\r\n            StateManager.set('dragstart_Commande_Emplacement_Page_Web', dragstartRef);\r\n        }\r\n        \r\n        console.log('Esp Pub Ref FirstUploadFileorMoved:', StateManager.get(\"FirstUploadFileorMoved\"));\r\n        console.log('Esp Pub dragstart_Commande:', StateManager.get(\"dragstart_Commande_Emplacement_Page_Web\"));\r\n        \r\n        \/\/ v4.9ds : si un upload kit (drop miniature) est en cours pour ce rank,\r\n        \/\/   attendre sa fin avant d'envoyer les donn\u00e9es \u2014 sinon FullPathAdFile=null\r\n        \/\/   serait propag\u00e9 \u00e0 l'iframe popup et la BDD serait aliment\u00e9e sans chemin.\r\n        var _self = this;\r\n        var _rankWait = StateManager.get(\"Rank_Emplacement_Page_Web\");\r\n        var _pendingUp = (window._viaPendingUpload ? window._viaPendingUpload[_rankWait] : null);\r\n        if (_pendingUp ? typeof _pendingUp.then === 'function' : false) {\r\n            console.log('\u23f3 [activateSendDataToParent] upload kit en cours sur', _rankWait, '\u2192 wait avant d\\'envoyer');\r\n            _pendingUp.then(function() {\r\n                console.log('\u2705 [activateSendDataToParent] upload termin\u00e9 \u2192 reprise envoi | FullPathAdFile:', StateManager.get('FullPathAdFile'));\r\n                _self._continueActivateSendDataToParent($dropZone);\r\n            }).catch(function(_eUW) {\r\n                console.warn('\u26a0\ufe0f [activateSendDataToParent] upload \u00e9chou\u00e9 \u2192 on continue quand m\u00eame:', _eUW);\r\n                _self._continueActivateSendDataToParent($dropZone);\r\n            });\r\n            return;\r\n        }\r\n        \r\n        this._continueActivateSendDataToParent($dropZone);\r\n    },\r\n    \r\n    _continueActivateSendDataToParent($dropZone) {\r\n        if (StateManager.get(\"PageAjoutModifAnnonce\") === 'Yes') {\r\n            this.handlePageModification($dropZone);\r\n        } else {\r\n            this.handleNormalUpload();\r\n        }\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        setTimeout(() => {\r\n            StateManager.set(\"AddNewRefInVosCampagnes\", 'Yes');\r\n        }, 4000);\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            StateManager.set('Commande_Format_Transmis', '');\r\n        }\r\n    },\r\n    \r\n    handlePageModification($dropZone) {\r\n        $('#CroixResetAnnonce, .DeplaceAnnonce').hide();\r\n        $('.PageAjoutModifAnnonceCroixResetAnnonce').show();\r\n        \r\n        const emplacementRef = $dropZone.closest('.CampagnesTemplateClass')\r\n            .find('.ReferenceEspace').text();\r\n        \r\n        console.log('Commande_Emplacement_Page_Web:', emplacementRef);\r\n        \r\n        jQuery.ajax({\r\n            type: \"POST\",\r\n            url: CONFIG.ajaxUrl,\r\n            data: {\r\n                action: 'via_update_fichier_annonce',\r\n                commande_ref_url: StateManager.get(\"commande_ref_url\"),\r\n                emplacement_page_web: emplacementRef,\r\n                chemin_fichier: StateManager.get(\"FullPathAdFile\")\r\n            },\r\n            xhrFields: { withCredentials: true },\r\n            success: (response) => {\r\n                if (response.success) {\r\n                    console.log('\u2705 Fichier annonce mis \u00e0 jour:', response.data.message);\r\n                    location.reload();\r\n                } else {\r\n                    console.error('\u274c Erreur:', response.data.message);\r\n                }\r\n            },\r\n            error: (xhr, status, error) => {\r\n                console.error('\u274c Erreur AJAX:', error);\r\n            }\r\n        });\r\n    },\r\n    \r\n    handleNormalUpload() {\r\n        if (StateManager.get(\"EnvoiUlterieur\") === 'true') {\r\n            StateManager.setMultiple({\r\n                \"FullPathAdFile\": '',\r\n                \"Upload_File_Name\": ''\r\n            });\r\n        }\r\n        \r\n        StateManager.set(\"LoadedPageUrl\", window.location.href);\r\n        \r\n        console.log('EnvoiUlterieur:', StateManager.get(\"EnvoiUlterieur\"));\r\n        console.log('LoadedPageUrl:', StateManager.get(\"LoadedPageUrl\"));\r\n        console.log('FileReceived:', StateManager.get(\"FileReceived\"));\r\n        \r\n        const data = MessageManager.prepareUploadData();\r\n        \r\n        \/\/ \u2705 Sauvegarder FullPathAdFile dans localStorage pour restauration apr\u00e8s refresh\r\n        \/\/ (ce chemin couvre aussi AchatEspaceCall=Yes o\u00f9 handleEspacePubData d'Entete.txt n'est pas appel\u00e9)\r\n        \/\/ \u2705 Backup : sauver via_fullpath ici aussi (couvre AchatEspaceCall=Yes)\r\n        if (data.FullPathAdFile) {\r\n            var _rankFP = data.Rank_Emplacement_Page_Web;\r\n            if (_rankFP) {\r\n                try {\r\n                    var _fpItemsEP = JSON.parse(localStorage.getItem('via_fullpath') || '{}');\r\n                    _fpItemsEP[_rankFP] = data.FullPathAdFile;\r\n                    localStorage.setItem('via_fullpath', JSON.stringify(_fpItemsEP));\r\n                } catch(e) {}\r\n            }\r\n        }\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            MessageManager.sendDataToParent(data);\r\n        } else {\r\n            const formatChoisi = StateManager.get(\"Formatchoisi\") === 'Yes';\r\n            const fichierDepose = StateManager.get(\"FileReceived\") === 'Yes';\r\n            const envoiDiffere = StateManager.get(\"EnvoiUlterieur\") === 'true';\r\n            \r\n            console.log('\ud83d\udd0d handleNormalUpload - Conditions:', { formatChoisi, fichierDepose, envoiDiffere });\r\n            \r\n            if (formatChoisi ? (fichierDepose || envoiDiffere) : false) {\r\n                if (typeof window.handleEspacePubData === 'function') {\r\n                    window.handleEspacePubData(data);\r\n                } else {\r\n                    console.warn('\u26a0\ufe0f handleEspacePubData non disponible, fallback direct');\r\n                    if (typeof window.executeWithProcessLoaded === 'function') {\r\n                        window.executeWithProcessLoaded(function() {\r\n                            jQuery('#PopupProcessCommandeContainer').show();\r\n                        });\r\n                    } else {\r\n                        jQuery('#PopupProcessCommandeContainer').show();\r\n                    }\r\n                }\r\n            } else if (formatChoisi) {\r\n                console.log('\ud83d\udcdd Mise \u00e0 jour format uniquement');\r\n                \r\n                const borderStyle = {'box-shadow': 'inset 0 0 0 2px #00FF19', 'box-sizing': 'border-box'};  \/\/ \u2705 v2.4.5\r\n                \r\n                jQuery(\".FormatClassique, .FormatPopup\").css({'border': 'none'});\r\n                \r\n                jQuery('.FormatClassique, .FormatPopup').each(function() {\r\n                    \/\/ \u2705 v2.1.1 : NFD normalize both sides\r\n                    var _cn = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    var _cf = (data.Commande_Format_Transmis || '').normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    if (_cn.includes(_cf) ? _cf : false) {\r\n                        jQuery(this).css(borderStyle);\r\n                    }\r\n                });\r\n            }\r\n        }\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        setTimeout(() => {\r\n            StateManager.set(\"AddNewRefInVosCampagnes\", 'Yes');\r\n        }, 4000);\r\n        \r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes') {\r\n            StateManager.set('Commande_Format_Transmis', '');\r\n        }\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des fichiers PDF (s\u00e9par\u00e9 pour \u00e9viter la complexit\u00e9)\r\n *\/\r\nconst PDFHandler = {\r\n    pdfData: null,\r\n    pdfDataForViewer: null,\r\n    \r\n    async renderPDF(fileObj, $dropZone) {\r\n        return new Promise((resolve, reject) => {\r\n            const reader = new FileReader();\r\n            \r\n            reader.onload = async (e) => {\r\n                try {\r\n                    const arrayBuffer = e.target.result;\r\n                    this.pdfData = new Uint8Array(arrayBuffer);\r\n                    this.pdfDataForViewer = arrayBuffer;\r\n                    \r\n                    console.log(\"Initial PDF load, size:\", this.pdfData.byteLength);\r\n                    \r\n                    const pdfjsLib = window['pdfjs-dist\/build\/pdf'];\r\n                    pdfjsLib.GlobalWorkerOptions.workerSrc = \r\n                        '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n                    \r\n                    const pdf = await pdfjsLib.getDocument({ data: new Uint8Array(this.pdfData) }).promise;\r\n                    const page = await pdf.getPage(1);\r\n                    \r\n                    const { titleText, pageText } = await this.extractTextFromPage(page);\r\n                    \/\/ v4.9ds : si une image utilisateur a \u00e9t\u00e9 stock\u00e9e par le kit (mobile PDF JPG-in-PDF),\r\n                    \/\/   l'utiliser directement plut\u00f4t que d'extraire la 1\u00e8re image du PDF\r\n                    \/\/   (qui serait toute la cr\u00e9ation rasteris\u00e9e)\r\n                    const _$drop4ds = $dropZone.closest('.droppable');\r\n                    const _kitFirstImg = _$drop4ds.length ? _$drop4ds.data('kitFirstImageDataURL') : null;\r\n                    let imageData;\r\n                    if (_kitFirstImg) {\r\n                        imageData = { dataUrl: _kitFirstImg };\r\n                        console.log('[v4.9ds] renderPDF \u2192 utilise kitFirstImageDataURL stock\u00e9');\r\n                        try { var _dl5 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl5) _dl5('[v4.9ds] renderPDF utilise firstImg stock\u00e9', 'ok'); } catch(e) {}\r\n                    } else {\r\n                        imageData = await this.extractImageFromPage(page, pdfjsLib);\r\n                        try { var _dl6 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl6) _dl6('[v4.9ds] renderPDF utilise extractImageFromPage (firstImg ABSENT)', 'warn'); } catch(e) {}\r\n                    }\r\n                    \r\n                    if (imageData) {\r\n                        PreviewRenderer.renderDocumentPreview(\r\n                            $dropZone,\r\n                            imageData.dataUrl,\r\n                            titleText,\r\n                            pageText\r\n                        );\r\n                    } else {\r\n                        \/\/ \u2705 v1.19.0 : Accepter les PDF sans image\r\n                        PreviewRenderer.renderDocumentPreview(\r\n                            $dropZone,\r\n                            null,\r\n                            titleText,\r\n                            pageText\r\n                        );\r\n                        console.log('\u2139\ufe0f PDF sans image \u2014 aper\u00e7u texte seul');\r\n                    }\r\n                    \r\n                    this.attachPDFPreviewHandler($dropZone, titleText);\r\n                    resolve();\r\n                } catch (error) {\r\n                    console.error('Error extracting from PDF:', error);\r\n                    \/\/ \u2705 v1.19.0 : Message d'erreur en fran\u00e7ais\r\n                    $dropZone.html(`\r\n                        <div style=\"padding: 15px; background-color: #f8f8f8; border-radius: 4px; text-align: center;\">\r\n                            <p style=\"color: #FB5E2A; font-weight: 600; font-family: Roboto, Arial, sans-serif; font-size: 12px;\">\r\n                                Erreur lors de la lecture du document\r\n                            <\/p>\r\n                        <\/div>\r\n                    `);\r\n                    reject(error);\r\n                }\r\n            };\r\n            \r\n            reader.readAsArrayBuffer(fileObj);\r\n        });\r\n    },\r\n    \r\n    async extractTextFromPage(page) {\r\n        const textContent = await page.getTextContent();\r\n        \r\n        let text = '';\r\n        let lastX = -1;\r\n        let lastY = -1;\r\n        \r\n        for (const item of textContent.items) {\r\n            if (lastY !== -1 ? (Math.abs(lastY - item.transform[5]) > 5) : false) {\r\n                text += '\\n';\r\n            } else if (lastX !== -1 ? (item.transform[4] - lastX > 10) : false) {\r\n                text += ' ';\r\n            }\r\n            \r\n            text += item.str;\r\n            \r\n            lastX = item.transform[4] + (item.width || 0);\r\n            lastY = item.transform[5];\r\n        }\r\n        \r\n        text = text\r\n            .replace(\/(\\w) (\\w)\/g, (match, p1, p2) => {\r\n                if (\/[\u00e9\u00e8\u00ea\u00eb\u00e0\u00e2\u00e4\u00f4\u00f6\u00fb\u00fc\u00ef\u00ee\u00e7]\/i.test(p2)) {\r\n                    return p1 + p2;\r\n                }\r\n                return match;\r\n            })\r\n            .replace(\/ ([.,;:!?])\/g, '$1');\r\n        \r\n        const normalizedText = PreviewRenderer.normalizeText(text);\r\n        const titleText = PreviewRenderer.findDocumentTitle(normalizedText);\r\n        \r\n        return { titleText, pageText: text };\r\n    },\r\n    \r\n    async extractImageFromPage(page, pdfjsLib) {\r\n        const opList = await page.getOperatorList();\r\n        const imageInfo = [];\r\n        let currentTransform = null;\r\n        \r\n        for (let i = 0; i < opList.fnArray.length; i++) {\r\n            const operator = opList.fnArray[i];\r\n            const args = opList.argsArray[i];\r\n            \r\n            if (operator === pdfjsLib.OPS.transform) {\r\n                currentTransform = args;\r\n            } else if (operator === pdfjsLib.OPS.paintImageXObject) {\r\n                const imageName = args[0];\r\n                \r\n                if (!imageInfo.some(info => info.name === imageName)) {\r\n                    const position = currentTransform ? {\r\n                        x: currentTransform[4] || 0,\r\n                        y: currentTransform[5] || 0\r\n                    } : { x: 0, y: 0 };\r\n                    \r\n                    imageInfo.push({ name: imageName, position: position });\r\n                }\r\n            }\r\n        }\r\n        \r\n        if (imageInfo.length === 0) {\r\n            return null;\r\n        }\r\n        \r\n        imageInfo.sort((a, b) => b.position.y - a.position.y);\r\n        \r\n        const targetImageName = imageInfo[0].name;\r\n        const imageObj = page.objs.get(targetImageName);\r\n        \r\n        if (!imageObj) {\r\n            const img = await page.objs.get(targetImageName, true);\r\n            return this.processImageObject(img);\r\n        }\r\n        \r\n        return this.processImageObject(imageObj);\r\n    },\r\n    \r\n    processImageObject(imageObj) {\r\n        if (!imageObj) {\r\n            throw new Error('Image object is null');\r\n        }\r\n        \r\n        if (imageObj.bitmap instanceof ImageBitmap) {\r\n            const width = imageObj.bitmap.width || imageObj.width || imageObj.w;\r\n            const height = imageObj.bitmap.height || imageObj.height || imageObj.h;\r\n            \r\n            const canvas = document.createElement('canvas');\r\n            canvas.width = width;\r\n            canvas.height = height;\r\n            const ctx = canvas.getContext('2d');\r\n            \r\n            ctx.drawImage(imageObj.bitmap, 0, 0);\r\n            \r\n            return {\r\n                canvas: canvas,\r\n                width: width,\r\n                height: height,\r\n                dataUrl: canvas.toDataURL('image\/jpeg', 0.92)\r\n            };\r\n        }\r\n        \r\n        const width = imageObj.width || imageObj.w;\r\n        const height = imageObj.height || imageObj.h;\r\n        \r\n        if (!width || !height) {\r\n            throw new Error('Could not determine image dimensions');\r\n        }\r\n        \r\n        let imgData = imageObj.data || imageObj.bitmap;\r\n        \r\n        if (!imgData) {\r\n            throw new Error('No valid image data found');\r\n        }\r\n        \r\n        const canvas = document.createElement('canvas');\r\n        canvas.width = width;\r\n        canvas.height = height;\r\n        const ctx = canvas.getContext('2d');\r\n        \r\n        ctx.fillStyle = 'white';\r\n        ctx.fillRect(0, 0, width, height);\r\n        \r\n        const imageData = ctx.createImageData(width, height);\r\n        \r\n        this.fillImageData(imageData, imgData, imageObj.kind, width, height);\r\n        \r\n        ctx.putImageData(imageData, 0, 0);\r\n        \r\n        return {\r\n            canvas: canvas,\r\n            width: width,\r\n            height: height,\r\n            dataUrl: canvas.toDataURL('image\/jpeg', 0.92)\r\n        };\r\n    },\r\n    \r\n    fillImageData(imageData, imgData, kind, width, height) {\r\n        if (kind === 'GRAY') {\r\n            for (let i = 0, j = 0; i < imgData.length; i++, j += 4) {\r\n                const value = imgData[i];\r\n                imageData.data[j] = value;\r\n                imageData.data[j + 1] = value;\r\n                imageData.data[j + 2] = value;\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (kind === 'CMYK') {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 4, j += 4) {\r\n                const c = imgData[i] \/ 255;\r\n                const m = imgData[i + 1] \/ 255;\r\n                const y = imgData[i + 2] \/ 255;\r\n                const k = imgData[i + 3] \/ 255;\r\n                \r\n                imageData.data[j] = 255 * (1 - c) * (1 - k);\r\n                imageData.data[j + 1] = 255 * (1 - m) * (1 - k);\r\n                imageData.data[j + 2] = 255 * (1 - y) * (1 - k);\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (kind === 'RGB24') {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 3, j += 4) {\r\n                imageData.data[j] = imgData[i];\r\n                imageData.data[j + 1] = imgData[i + 1];\r\n                imageData.data[j + 2] = imgData[i + 2];\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else if (imgData.length === width * height * 3) {\r\n            for (let i = 0, j = 0; i < imgData.length; i += 3, j += 4) {\r\n                imageData.data[j] = imgData[i];\r\n                imageData.data[j + 1] = imgData[i + 1];\r\n                imageData.data[j + 2] = imgData[i + 2];\r\n                imageData.data[j + 3] = 255;\r\n            }\r\n        } else {\r\n            const tempArray = new Uint8ClampedArray(imgData.length);\r\n            for (let i = 0; i < imgData.length; i++) {\r\n                tempArray[i] = imgData[i];\r\n            }\r\n            \r\n            if (tempArray.length === imageData.data.length \/ 4 * 3) {\r\n                for (let i = 0, j = 0; i < tempArray.length; i += 3, j += 4) {\r\n                    imageData.data[j] = tempArray[i];\r\n                    imageData.data[j + 1] = tempArray[i + 1];\r\n                    imageData.data[j + 2] = tempArray[i + 2];\r\n                    imageData.data[j + 3] = 255;\r\n                }\r\n            } else if (tempArray.length === imageData.data.length) {\r\n                imageData.data.set(tempArray);\r\n            } else {\r\n                console.warn('Unknown image format - creating placeholder');\r\n                for (let i = 0; i < imageData.data.length; i += 4) {\r\n                    const x = (i\/4) % width;\r\n                    const y = Math.floor((i\/4) \/ width);\r\n                    imageData.data[i] = x % 256;\r\n                    imageData.data[i + 1] = y % 256;\r\n                    imageData.data[i + 2] = 100;\r\n                    imageData.data[i + 3] = 255;\r\n                }\r\n            }\r\n        }\r\n    },\r\n    \r\n    attachPDFPreviewHandler($dropZone, titleText) {\r\n        var $droppable = $dropZone.closest('.droppable');\r\n        var self = this;\r\n\r\n        \/\/ \u2705 Adapter le libell\u00e9 selon le format (communiqu\u00e9 \/ interview)\r\n        var kitFormat = $droppable.data('kitFormatSelect') || '';\r\n        var isInterview = (kitFormat || titleText || '').toLowerCase().indexOf('interview') !== -1;\r\n        var formatLabel = isInterview ? 'l\\'interview' : 'le communiqu\u00e9';\r\n        $droppable.find('.doc-preview-readmore').text('Ouvrir et visualiser ' + formatLabel);\r\n\r\n        \/\/ \u2705 v2.0.11 : D\u00e9l\u00e9gation d'\u00e9v\u00e9nement \u2014 plus robuste sur mobile\r\n        $droppable.off('click.docpreview').on('click.docpreview', '.doc-preview-readmore', (event) => {\r\n            event.preventDefault();\r\n            event.stopPropagation();\r\n            var popupTitle = isInterview ? 'Interview' : 'Communiqu\u00e9';\r\n\r\n            \/\/ \u2705 Priorit\u00e9 1 : pdfImageDataURL dispo (Kit)\r\n            var kitPdfImage = $droppable.data('kitPdfImageDataURL');\r\n            if (kitPdfImage) {\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    pdfDataURL: kitPdfImage\r\n                });\r\n                return;\r\n            }\r\n\r\n            \/\/ \u2705 Priorit\u00e9 2 : PDF upload\u00e9 manuellement \u2192 popup inline aussi\r\n            if (self.pdfDataForViewer ? self.pdfDataForViewer.byteLength > 0 : false) {\r\n                PDFHandler.showInlineDocPopup($dropZone, {\r\n                    formatTitle: popupTitle,\r\n                    pdfArrayBuffer: self.pdfDataForViewer\r\n                });\r\n                return;\r\n            }\r\n\r\n            console.error('PDF data is not available');\r\n        });\r\n    },\r\n\r\n    \/\/ \u2705 v1.19.1 : Popup inline document \u2014 position:absolute + ScrollHelper.getVisibleTop()\r\n    \/\/ Le parent envoie visibleTopIframe dans le message scroll \u2192 positionnement fiable.\r\n    showInlineDocPopup($dropZone, options) {\r\n        var existing = document.getElementById('via-pdf-inline-popup');\r\n        if (existing) existing.remove();\r\n\r\n        var $droppable = $dropZone.closest('.droppable');\r\n        var droppableW = $droppable.outerWidth() || 300;\r\n        var popupW = Math.round(droppableW * 1.15);\r\n        \/\/ \u2705 v2.0.11 : Contraindre la largeur au viewport sur mobile (\u00e9vite troncature gauche\/droite)\r\n        var viewportW = document.documentElement.clientWidth || window.innerWidth;\r\n        if (UIManager.isMobile()) {\r\n            popupW = Math.min(popupW, viewportW - 8);\r\n        }\r\n        var popupH = Math.round(popupW * 2.5 * 0.51);\r\n\r\n        \/\/ \u2500\u2500 Position visible r\u00e9elle \u2500\u2500\r\n        var visibleTop = ScrollHelper.getVisibleTop();\r\n        var popupTop = Math.round(visibleTop + 284);\r\n\r\n        \/\/ \u2500\u2500 Popup position:absolute (m\u00eame approche que la r\u00e9gie) \u2500\u2500\r\n        var popup = document.createElement('div');\r\n        popup.id = 'via-pdf-inline-popup';\r\n        \/\/ v4.9ds : initialiser le z-index avec le compteur global window.popupZIndex\r\n        \/\/   pour que le popup PDF s'int\u00e8gre au syst\u00e8me _bringPopupToFront (Entete.txt:921+).\r\n        \/\/   Au prochain mousedown sur ce popup, le s\u00e9lecteur _viaTopMostSel le d\u00e9tecte\r\n        \/\/   et incr\u00e9mente son z-index \u2192 il passe en avant-plan par rapport aux autres\r\n        \/\/   popups (Ele0A, miniature, CGV, etc.). Skip sur mobile (perturberait le rendu).\r\n        var _initZ;\r\n        if (typeof window.popupZIndex === 'number' ? window.innerWidth >= 1000 : false) {\r\n            window.popupZIndex++;\r\n            _initZ = window.popupZIndex;\r\n        } else {\r\n            _initZ = 99999;\r\n        }\r\n        popup.style.cssText =\r\n            'position:absolute;z-index:' + _initZ + ';top:' + popupTop + 'px;' +\r\n            'width:' + popupW + 'px;height:' + popupH + 'px;' +\r\n            'background:#fff;border-radius:8px 8px 0 0;padding:0;' +\r\n            'box-shadow:0 8px 32px rgba(0,0,0,0.35);display:flex;flex-direction:column;' +\r\n            'min-width:200px;min-height:150px;touch-action:none;';\r\n\r\n        \/\/ Centrer horizontalement sur le droppable (coordonn\u00e9es absolues)\r\n        var droppableRect = $droppable[0].getBoundingClientRect();\r\n        var scrollX = window.scrollX || 0;\r\n        var initLeft = Math.round(droppableRect.left + scrollX + (droppableRect.width - popupW) \/ 2);\r\n        initLeft = Math.max(4, initLeft);\r\n        \/\/ \u2705 v2.0.11 : Contraindre \u00e0 droite aussi sur mobile\r\n        if (UIManager.isMobile()) {\r\n            initLeft = Math.min(initLeft, viewportW - popupW - 4);\r\n            initLeft = Math.max(4, initLeft);\r\n        }\r\n        popup.style.left = initLeft + 'px';\r\n\r\n        \/\/ \u2500\u2500 Header \u2500\u2500\r\n        var header = document.createElement('div');\r\n        header.style.cssText =\r\n            'display:flex;align-items:center;justify-content:center;position:relative;' +\r\n            'padding:10px 14px;background:#f0f0f0;color:#494949;flex-shrink:0;' +\r\n            'cursor:grab;user-select:none;border-radius:8px 8px 0 0;';\r\n        var titleEl = document.createElement('span');\r\n        titleEl.style.cssText = 'font-family:Roboto,Arial,sans-serif;font-size:15px;font-weight:600;';\r\n        titleEl.textContent = options.formatTitle || 'Document';\r\n        header.appendChild(titleEl);\r\n\r\n        var closeBtn = document.createElement('button');\r\n        closeBtn.textContent = '\u00d7';\r\n        closeBtn.style.cssText =\r\n            'position:absolute;right:10px;top:50%;transform:translateY(-50%);' +\r\n            'background:transparent;border:none;color:#494949;font-size:20px;' +\r\n            'cursor:pointer;line-height:1;padding:0 4px;';\r\n        closeBtn.addEventListener('click', cleanup);\r\n        header.appendChild(closeBtn);\r\n        popup.appendChild(header);\r\n\r\n        \/\/ \u2500\u2500 Zone scrollable \u2014 pleine largeur \u2500\u2500\r\n        var scrollZone = document.createElement('div');\r\n        scrollZone.style.cssText =\r\n            'flex:1;overflow-y:auto;overflow-x:hidden;padding:0;margin:0;touch-action:pan-y;';\r\n        popup.appendChild(scrollZone);\r\n\r\n        var loader = document.createElement('div');\r\n        loader.style.cssText =\r\n            'text-align:center;padding:30px;font-family:Roboto,Arial,sans-serif;font-size:12px;color:#666;';\r\n        loader.textContent = 'Chargement du document\u2026';\r\n        scrollZone.appendChild(loader);\r\n\r\n        \/\/ \u2500\u2500 8 poign\u00e9es de redimensionnement \u2500\u2500\r\n        var handles = [\r\n            { cursor:'nw-resize', pos:'top:-4px;left:-4px;',       dx:-1, dy:-1 },\r\n            { cursor:'n-resize',  pos:'top:-4px;left:50%;',        dx:0,  dy:-1 },\r\n            { cursor:'ne-resize', pos:'top:-4px;right:-4px;',      dx:1,  dy:-1 },\r\n            { cursor:'w-resize',  pos:'top:50%;left:-4px;',        dx:-1, dy:0  },\r\n            { cursor:'e-resize',  pos:'top:50%;right:-4px;',       dx:1,  dy:0  },\r\n            { cursor:'sw-resize', pos:'bottom:-4px;left:-4px;',    dx:-1, dy:1  },\r\n            { cursor:'s-resize',  pos:'bottom:-4px;left:50%;',     dx:0,  dy:1  },\r\n            { cursor:'se-resize', pos:'bottom:-4px;right:-4px;',   dx:1,  dy:1  }\r\n        ];\r\n        handles.forEach(function(h) {\r\n            var handle = document.createElement('div');\r\n            var isCorner = (h.dx !== 0 ? h.dy !== 0 : false);\r\n            handle.style.cssText =\r\n                'position:absolute;touch-action:none;' + h.pos +\r\n                'width:' + (isCorner ? '12px' : (h.dy === 0 ? '8px' : '30px')) + ';' +\r\n                'height:' + (isCorner ? '12px' : (h.dy === 0 ? '30px' : '8px')) + ';' +\r\n                'cursor:' + h.cursor + ';z-index:10;';\r\n            (function(hInfo) {\r\n                handle.addEventListener('pointerdown', function(e) {\r\n                    e.preventDefault(); e.stopPropagation();\r\n                    handle.setPointerCapture(e.pointerId);\r\n                    var startX = e.clientX, startY = e.clientY;\r\n                    var rect0 = popup.getBoundingClientRect();\r\n                    var sY = window.scrollY || 0, sX = window.scrollX || 0;\r\n                    var startW = rect0.width, startH = rect0.height;\r\n                    var startL = rect0.left + sX, startT = rect0.top + sY;\r\n                    function onResize(ev) {\r\n                        ev.preventDefault();\r\n                        var ddx = ev.clientX - startX, ddy = ev.clientY - startY;\r\n                        var newW = startW, newH = startH, newL = startL, newT = startT;\r\n                        if (hInfo.dx === 1)  newW = Math.max(200, startW + ddx);\r\n                        if (hInfo.dx === -1) { newW = Math.max(200, startW - ddx); newL = startL + ddx; }\r\n                        if (hInfo.dy === 1)  newH = Math.max(150, startH + ddy);\r\n                        if (hInfo.dy === -1) { newH = Math.max(150, startH - ddy); newT = startT + ddy; }\r\n                        popup.style.width  = newW + 'px';\r\n                        popup.style.height = newH + 'px';\r\n                        popup.style.left   = newL + 'px';\r\n                        popup.style.top    = newT + 'px';\r\n                    }\r\n                    function onResizeEnd(ev) {\r\n                        handle.releasePointerCapture(ev.pointerId);\r\n                        handle.removeEventListener('pointermove', onResize);\r\n                        handle.removeEventListener('pointerup', onResizeEnd);\r\n                    }\r\n                    handle.addEventListener('pointermove', onResize);\r\n                    handle.addEventListener('pointerup', onResizeEnd);\r\n                });\r\n            })(h);\r\n            popup.appendChild(handle);\r\n        });\r\n\r\n        \/\/ \u2500\u2500 Drag via header \u2500\u2500\r\n        header.style.touchAction = 'none';\r\n        header.addEventListener('pointerdown', function(e) {\r\n            if (e.target === closeBtn) return;\r\n            e.preventDefault();\r\n            header.setPointerCapture(e.pointerId);\r\n            var mx = e.clientX, my = e.clientY;\r\n            var popupRect = popup.getBoundingClientRect();\r\n            var scrollY = window.scrollY || 0;\r\n            var scrollX2 = window.scrollX || 0;\r\n            var curLeft = popupRect.left + scrollX2;\r\n            var curTop  = popupRect.top  + scrollY;\r\n            header.style.cursor = 'grabbing';\r\n            function onMove(ev) {\r\n                ev.preventDefault();\r\n                var dx = ev.clientX - mx;\r\n                var dy = ev.clientY - my;\r\n                curLeft += dx;\r\n                curTop  += dy;\r\n                popup.style.left = curLeft + 'px';\r\n                popup.style.top  = curTop  + 'px';\r\n                mx = ev.clientX;\r\n                my = ev.clientY;\r\n            }\r\n            function onUp(ev) {\r\n                header.releasePointerCapture(ev.pointerId);\r\n                header.style.cursor = 'grab';\r\n                header.removeEventListener('pointermove', onMove);\r\n                header.removeEventListener('pointerup', onUp);\r\n            }\r\n            header.addEventListener('pointermove', onMove);\r\n            header.addEventListener('pointerup', onUp);\r\n        });\r\n\r\n        \/\/ \u2500\u2500 Cleanup \u2500\u2500\r\n        function cleanup() {\r\n            popup.remove();\r\n            document.removeEventListener('keydown', escHandler);\r\n        }\r\n        var escHandler = function(e) { if (e.key === 'Escape') cleanup(); };\r\n        document.addEventListener('keydown', escHandler);\r\n\r\n        document.body.appendChild(popup);\r\n\r\n        \/\/ \u2500\u2500 Rendre le contenu \u2500\u2500\r\n        if (options.pdfDataURL)          this.renderPdfInPopup(scrollZone, options.pdfDataURL, popupW, 'dataurl');\r\n        else if (options.pdfArrayBuffer) this.renderPdfInPopup(scrollZone, options.pdfArrayBuffer, popupW, 'arraybuffer');\r\n        else if (options.htmlContent)    this.renderHtmlInPopup(scrollZone, options.htmlContent);\r\n    },\r\n\r\n    \/\/ Alias pour compatibilit\u00e9\r\n    showInlinePdfPopup($dropZone, pdfImageDataURL, formatTitle) {\r\n        this.showInlineDocPopup($dropZone, { formatTitle: formatTitle, pdfDataURL: pdfImageDataURL });\r\n    },\r\n\r\n    \/\/ \u2705 Rendu PDF dans popup (dataurl ou arraybuffer) \u2014 avec crop des marges blanches\r\n    async renderPdfInPopup(container, pdfData, popupWidth, mode) {\r\n        try {\r\n            var u8;\r\n            if (mode === 'dataurl') {\r\n                var b64 = pdfData.split(',')[1];\r\n                var bstr = atob(b64);\r\n                u8 = new Uint8Array(bstr.length);\r\n                for (var i = 0; i < bstr.length; i++) { u8[i] = bstr.charCodeAt(i); }\r\n            } else {\r\n                u8 = new Uint8Array(pdfData);\r\n            }\r\n\r\n            var pdfjsLib = window['pdfjs-dist\/build\/pdf'];\r\n            if (!pdfjsLib) {\r\n                container.innerHTML = '<div style=\"padding:20px;text-align:center;color:#c00;\">pdf.js non charg\u00e9<\/div>';\r\n                return;\r\n            }\r\n            pdfjsLib.GlobalWorkerOptions.workerSrc =\r\n                '\/\/cdn.jsdelivr.net\/npm\/pdfjs-dist@latest\/build\/pdf.worker.min.js';\r\n\r\n            var pdf = await pdfjsLib.getDocument({ data: u8 }).promise;\r\n            console.log('\ud83d\udcc4 PDF popup :', pdf.numPages, 'pages');\r\n            container.innerHTML = '';\r\n\r\n            var firstPage = await pdf.getPage(1);\r\n            var vpNative = firstPage.getViewport({ scale: 1 });\r\n            var scale = popupWidth \/ vpNative.width;\r\n\r\n            \/\/ \u2705 R\u00e9f\u00e9rence au popup pour ajuster sa hauteur apr\u00e8s rendu\r\n            var _popup = container.closest('#via-pdf-inline-popup');\r\n            var _headerH = _popup ? (_popup.querySelector('div[style*=\"grab\"]') || {offsetHeight: 44}).offsetHeight : 44;\r\n\r\n            \/\/ \u2705 D\u00e9tecte les marges blanches haut\/bas\/gauche\/droite d'un canvas rendu\r\n            \/\/ v4.9ds (pdf-popup-fullwidth) : scan des 4 c\u00f4t\u00e9s (avant : top\/bottom seulement),\r\n            \/\/   pour que le contenu PDF prenne toute la largeur de la popup m\u00eame si le PDF\r\n            \/\/   d'origine a des marges lat\u00e9rales blanches importantes (cas PDFs kit Interview\/\r\n            \/\/   Communiqu\u00e9 portrait avec contenu centr\u00e9 ~40% de la largeur).\r\n            \/\/   Pour left\/right, on scanne UNIQUEMENT entre topRow et bottomRow d\u00e9j\u00e0 d\u00e9termin\u00e9s\r\n            \/\/   afin de ne pas \u00eatre tromp\u00e9 par du blanc d\u00e9j\u00e0 sur le point d'\u00eatre crop\u00e9.\r\n            function detectWhiteMargins(canvas) {\r\n                var ctx = canvas.getContext('2d');\r\n                var data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;\r\n                var W = canvas.width, H = canvas.height;\r\n                var topRow = 0, bottomRow = H - 1, leftCol = 0, rightCol = W - 1;\r\n                \/\/ Top : premi\u00e8re ligne non-blanche (tol\u00e9rance 252 pour anti-aliasing)\r\n                outer: for (var y = 0; y < H; y++) {\r\n                    for (var x = 0; x < W; x++) {\r\n                        var i = (y * W + x) * 4;\r\n                        if (data[i] < 252 || data[i+1] < 252 || data[i+2] < 252) { topRow = y; break outer; }\r\n                    }\r\n                }\r\n                \/\/ Bottom : derni\u00e8re ligne non-blanche\r\n                outer2: for (var y2 = H - 1; y2 >= topRow; y2--) {\r\n                    for (var x2 = 0; x2 < W; x2++) {\r\n                        var i2 = (y2 * W + x2) * 4;\r\n                        if (data[i2] < 252 || data[i2+1] < 252 || data[i2+2] < 252) { bottomRow = y2; break outer2; }\r\n                    }\r\n                }\r\n                \/\/ Left : premi\u00e8re colonne non-blanche (scan limit\u00e9 \u00e0 la zone [topRow..bottomRow])\r\n                outer3: for (var x3 = 0; x3 < W; x3++) {\r\n                    for (var y3 = topRow; y3 <= bottomRow; y3++) {\r\n                        var i3 = (y3 * W + x3) * 4;\r\n                        if (data[i3] < 252 || data[i3+1] < 252 || data[i3+2] < 252) { leftCol = x3; break outer3; }\r\n                    }\r\n                }\r\n                \/\/ Right : derni\u00e8re colonne non-blanche\r\n                outer4: for (var x4 = W - 1; x4 >= leftCol; x4--) {\r\n                    for (var y4 = topRow; y4 <= bottomRow; y4++) {\r\n                        var i4 = (y4 * W + x4) * 4;\r\n                        if (data[i4] < 252 || data[i4+1] < 252 || data[i4+2] < 252) { rightCol = x4; break outer4; }\r\n                    }\r\n                }\r\n                return { top: topRow, bottom: bottomRow, left: leftCol, right: rightCol };\r\n            }\r\n\r\n            for (var pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\r\n                var page = await pdf.getPage(pageNum);\r\n                var vp = page.getViewport({ scale: scale });\r\n                \/\/ Rendre dans un canvas temporaire\r\n                var tmpCanvas = document.createElement('canvas');\r\n                tmpCanvas.width = vp.width;\r\n                tmpCanvas.height = vp.height;\r\n                await page.render({ canvasContext: tmpCanvas.getContext('2d'), viewport: vp }).promise;\r\n\r\n                \/\/ \u2705 Crop marges blanches sur les 4 c\u00f4t\u00e9s (avec petite marge de s\u00e9curit\u00e9 de 4px)\r\n                var margins = detectWhiteMargins(tmpCanvas);\r\n                var cropTop    = Math.max(0, margins.top - 4);\r\n                var cropBottom = Math.min(tmpCanvas.height - 1, margins.bottom + 4);\r\n                var cropLeft   = Math.max(0, margins.left - 4);\r\n                var cropRight  = Math.min(tmpCanvas.width - 1, margins.right + 4);\r\n                var croppedH = cropBottom - cropTop + 1;\r\n                var croppedW = cropRight - cropLeft + 1;\r\n\r\n                \/\/ Canvas final crop\u00e9 sur les 4 c\u00f4t\u00e9s\r\n                var canvas = document.createElement('canvas');\r\n                canvas.width = croppedW;\r\n                canvas.height = croppedH;\r\n                canvas.getContext('2d').drawImage(tmpCanvas, cropLeft, cropTop, croppedW, croppedH, 0, 0, croppedW, croppedH);\r\n                canvas.style.cssText = 'display:block;width:100%;height:auto;margin:0;padding:0;';\r\n                container.appendChild(canvas);\r\n                console.log('\ud83d\udcc4 Page', pageNum, '- crop H:', cropTop, '\u2192', cropBottom, '\/ W:', cropLeft, '\u2192', cropRight, '(' + Math.round((cropLeft + (tmpCanvas.width - cropRight - 1)) \/ tmpCanvas.width * 100) + '% width trimmed)');\r\n            }\r\n\r\n            \/\/ \u2705 Ajuster la hauteur du popup \u00e0 la hauteur r\u00e9elle du contenu rendu\r\n            \/\/ (popup \u00e9tait calcul\u00e9 sur popupW*2.5*0.51 qui peut \u00eatre trop grand ou trop petit)\r\n            \/\/ v4.9ds (pdf-popup-fullwidth) : _maxPopupH calcul\u00e9 en fonction de la position\r\n            \/\/   r\u00e9elle de la popup (popupTop dans le viewport) + 20px de marge en bas, pour\r\n            \/\/   garantir que la popup ne d\u00e9passe jamais le viewport. Si le contenu est plus\r\n            \/\/   haut, le overflow-y:auto de scrollZone affiche la barre de d\u00e9filement.\r\n            if (_popup) {\r\n                var _totalH = 0;\r\n                container.querySelectorAll('canvas').forEach(function(c) { _totalH += c.height * (popupWidth \/ c.width); });\r\n                var _viewportH = window.innerHeight || document.documentElement.clientHeight || 600;\r\n                var _popupRectNow = _popup.getBoundingClientRect();\r\n                var _popupTopVisible = _popupRectNow.top;\r\n                \/\/ Plafond strict : viewport - top de la popup - 20px de marge en bas\r\n                var _maxPopupH = Math.max(200, _viewportH - _popupTopVisible - 20);\r\n                var _idealH = Math.min(_totalH + _headerH + 8, _maxPopupH);\r\n                _popup.style.height = _idealH + 'px';\r\n                console.log('\ud83d\udcd0 Popup redimensionn\u00e9:', Math.round(_idealH), 'px (contenu:', Math.round(_totalH), ', max:', Math.round(_maxPopupH), ')');\r\n            }\r\n        } catch (err) {\r\n            console.error('\u274c Erreur rendu PDF popup:', err);\r\n            container.innerHTML =\r\n                '<div style=\"padding:20px;text-align:center;color:#c00;font-size:12px;\">Erreur lors du chargement du document<\/div>';\r\n        }\r\n    },\r\n\r\n    \/\/ \u2705 Rendu HTML (Word) dans popup \u2014 avec CSS complet pour mammoth\r\n    renderHtmlInPopup(container, htmlContent) {\r\n        container.innerHTML = '';\r\n        \/\/ \u2705 Nettoyer les <p> vides en d\u00e9but\/fin produits par mammoth (marges parasites)\r\n        htmlContent = htmlContent\r\n            .replace(\/^(\\s*<p[^>]*>\\s*(<br\\s*\\\/?>\\s*)*<\\\/p>\\s*)+\/i, '')\r\n            .replace(\/(\\s*<p[^>]*>\\s*(<br\\s*\\\/?>\\s*)*<\\\/p>\\s*)+$\/i, '');\r\n        \/\/ \u2500\u2500 Styles pour le HTML g\u00e9n\u00e9r\u00e9 par mammoth (titres, paragraphes, listes, images) \u2500\u2500\r\n        var style = document.createElement('style');\r\n        style.textContent = [\r\n            '.via-html-popup-body { padding:20px 24px; background:#fff; color:#222; font-family:Georgia,\"Times New Roman\",serif; font-size:15px; line-height:1.7; }',\r\n            '.via-html-popup-body h1 { font-size:22px; font-weight:700; margin:0 0 12px; line-height:1.3; }',\r\n            '.via-html-popup-body h2 { font-size:18px; font-weight:700; margin:18px 0 8px; line-height:1.3; }',\r\n            '.via-html-popup-body h3 { font-size:15px; font-weight:700; margin:14px 0 6px; }',\r\n            '.via-html-popup-body p  { margin:0 0 10px; }',\r\n            '.via-html-popup-body p:first-child { margin-top:0; }',\r\n            '.via-html-popup-body strong, .via-html-popup-body b { font-weight:700; }',\r\n            '.via-html-popup-body em, .via-html-popup-body i { font-style:italic; }',\r\n            '.via-html-popup-body ul, .via-html-popup-body ol { margin:6px 0 10px 22px; padding:0; }',\r\n            '.via-html-popup-body li { margin-bottom:4px; }',\r\n            '.via-html-popup-body img { max-width:100%; height:auto; display:block; margin:10px auto; border-radius:4px; }',\r\n            '.via-html-popup-body table { width:100%; border-collapse:collapse; margin:10px 0; font-size:13px; }',\r\n            '.via-html-popup-body td, .via-html-popup-body th { border:1px solid #ddd; padding:6px 8px; vertical-align:top; }',\r\n            '.via-html-popup-body th { background:#f0f4f8; font-weight:700; }',\r\n            '.via-html-popup-body a { color:#225da9; text-decoration:underline; }'\r\n        ].join('');\r\n        container.appendChild(style);\r\n        var wrapper = document.createElement('div');\r\n        wrapper.className = 'via-html-popup-body';\r\n        wrapper.innerHTML = htmlContent;\r\n        container.appendChild(wrapper);\r\n    },\r\n    \r\n    async renderPDFInWindow(childWindow, container) {\r\n        container.innerHTML = '';\r\n        \r\n        const script = childWindow.document.createElement('script');\r\n        script.src = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.4.120\/pdf.min.js';\r\n        childWindow.document.head.appendChild(script);\r\n        \r\n        script.onload = async () => {\r\n            const pdfjsLib = childWindow.pdfjsLib;\r\n            const viewerContainer = childWindow.document.createElement('div');\r\n            viewerContainer.style.width = '100%';\r\n            viewerContainer.style.backgroundColor = 'white';\r\n            viewerContainer.style.position = 'relative';\r\n            container.appendChild(viewerContainer);\r\n            \r\n            const loadingTask = pdfjsLib.getDocument({data: window.pdfDataToTransfer});\r\n            const pdf = await loadingTask.promise;\r\n            \r\n            const firstPage = await pdf.getPage(1);\r\n            const viewport = firstPage.getViewport({scale: 1.5});\r\n            const pageHeight = viewport.height;\r\n            \r\n            const spacing = -15;\r\n            const totalHeight = (pageHeight * pdf.numPages) + (spacing * (pdf.numPages - 1));\r\n            viewerContainer.style.height = `${totalHeight}px`;\r\n            \r\n            for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\r\n                const page = await pdf.getPage(pageNum);\r\n                const canvas = childWindow.document.createElement('canvas');\r\n                const context = canvas.getContext('2d');\r\n                \r\n                canvas.width = viewport.width;\r\n                canvas.height = viewport.height;\r\n                \r\n                canvas.style.position = 'absolute';\r\n                canvas.style.left = '50%';\r\n                canvas.style.transform = 'translateX(-50%)';\r\n                canvas.style.top = `${(pageNum - 1) * (pageHeight + spacing)}px`;\r\n                \r\n                await page.render({\r\n                    canvasContext: context,\r\n                    viewport: viewport\r\n                }).promise;\r\n                \r\n                viewerContainer.appendChild(canvas);\r\n            }\r\n        };\r\n    },\r\n    \r\n    arrayBufferToBase64(buffer) {\r\n        let binary = '';\r\n        const bytes = new Uint8Array(buffer);\r\n        const len = bytes.byteLength;\r\n        for (let i = 0; i < len; i++) {\r\n            binary += String.fromCharCode(bytes[i]);\r\n        }\r\n        return window.btoa(binary);\r\n    },\r\n    \r\n    base64ToArrayBuffer(base64) {\r\n        const binaryString = window.atob(base64);\r\n        const len = binaryString.length;\r\n        const bytes = new Uint8Array(len);\r\n        for (let i = 0; i < len; i++) {\r\n            bytes[i] = binaryString.charCodeAt(i);\r\n        }\r\n        return bytes.buffer;\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion des \u00e9v\u00e9nements de drop et d'upload\r\n *\/\r\nconst DropHandler = {\r\n    async handleDrop(e) {\r\n        e.preventDefault();\r\n        \r\n        const $currentTarget = this.findDropTarget(e);\r\n        \r\n        if (!$currentTarget) {\r\n            console.log('No valid drop target found');\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 V\u00c9RIFIER SI C'EST UN D\u00c9PLACEMENT (pas besoin de v\u00e9rifier le format)\r\n        const isMoved = StateManager.get('FirstUploadFileorMoved') === 'Moved';\r\n        const dragstartRef = StateManager.get('dragstart_Commande_Emplacement_Page_Web');\r\n        let hasDragstartRef = false;\r\n        \r\n        if (dragstartRef) {\r\n            if (dragstartRef !== 'No') {\r\n                hasDragstartRef = true;\r\n            }\r\n        }\r\n        \r\n        let isDeplacementAnnonce = false;\r\n        if (isMoved) {\r\n            if (hasDragstartRef) {\r\n                isDeplacementAnnonce = true;\r\n            }\r\n        }\r\n        \r\n        if (isDeplacementAnnonce) {\r\n            console.log('\ud83d\udd04 D\u00e9placement d\u00e9tect\u00e9 - contr\u00f4le format ignor\u00e9');\r\n        } else {\r\n            \/\/ \u2705 v2.7.3 : Le format est d\u00e9duit de l'extension du fichier d\u00e9pos\u00e9\r\n            \/\/ \u2192 ne plus bloquer le d\u00e9p\u00f4t si aucun format s\u00e9lectionn\u00e9\r\n            console.log('\ud83d\udd04 Nouveau d\u00e9p\u00f4t \u2014 format sera d\u00e9duit du fichier, contr\u00f4le format ignor\u00e9');\r\n        }\r\n    \r\n        console.log(\"Drop at:\", $currentTarget);\r\n        \r\n        this.updateEmplacementState($currentTarget);\r\n        \r\n        const shouldProcess = this.shouldProcessDrop(e, $currentTarget);\r\n        \r\n        if (shouldProcess) {\r\n            await this.processFileDrop(e, $currentTarget);\r\n        } else {\r\n            this.processVideoDrop($currentTarget);\r\n        }\r\n        \r\n        DragDropManager.clearDataTransferFiles(e);\r\n    },\r\n    \r\n    findDropTarget(e) {\r\n        \/\/ \u2705 v2.4.3 : Si Ele0A est actif et le drop arrive sur Ele1A \u2192 rediriger vers Ele0A\r\n        if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n            var _ele0ADrop = document.querySelector('#Ele0A #drop_file_zone_achat');\r\n            if (_ele0ADrop) {\r\n                console.log('\ud83c\udfaf findDropTarget \u2014 PopUpChoice=Yes \u2192 cible forc\u00e9e: Ele0A');\r\n                return jQuery(_ele0ADrop);\r\n            }\r\n        }\r\n\r\n        let target = e.target.closest('#drop_file_zone_achat');\r\n        \r\n        if (!target) {\r\n            target = e.currentTarget.closest('#drop_file_zone_achat');\r\n            console.log('Drop target found 1');\r\n        }\r\n        \r\n        if (!target) {\r\n            target = jQuery(e.target).closest('#drop_file_zone_achat')[0];\r\n            console.log('Drop target found 2');\r\n        }\r\n        \r\n        return target ? jQuery(target) : null;\r\n    },\r\n    \r\n    updateEmplacementState($target) {\r\n        const espaceId = $target.closest('.droppable').attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', espaceId);\r\n        StateManager.set('Commande_Emplacement_Page_Web',\r\n            StateManager.buildEmplacementReference(espaceId));\r\n        \r\n        UIManager.updateEmplacementDisplay();\r\n        \r\n        console.log(\"Rank_Emplacement_Page_Web:\", StateManager.get('Rank_Emplacement_Page_Web'));\r\n        console.log(\"Droppable at:\", StateManager.get('Commande_Emplacement_Page_Web'));\r\n    },\r\n    \r\n    shouldProcessDrop(e, $target) {\r\n        const hasFiles = e.dataTransfer.files.length > 0;\r\n        const isRedactionnel = StateManager.get('Commande_Format_Transmis') === 'R\u00e9dactionnel';\r\n        const isValidBackground = window.getComputedStyle(\r\n            $target.closest('#UploadFileConteneur')[0]\r\n        ).backgroundColor !== 'rgba(0, 0, 0, 0)';\r\n        \r\n        return (hasFiles || isRedactionnel) ? isValidBackground : false;\r\n    },\r\n    \r\n    async processFileDrop(e, $target) {\r\n        jQuery('.MsgAdNotDisplayed').hide();\r\n        StateManager.setMultiple({\r\n            \"AdDisplayed\": 'Yes',\r\n            \/\/ \u2705 NE PLUS mettre sendDataToParentFlag ici - c'est la checkbox \"R\u00e9server\" qui le fera\r\n            \/\/ \"sendDataToParentFlag\": 'Yes'\r\n        });\r\n        \r\n        console.log('ajaxFileUpload_achat launched');\r\n        console.log(\"FirstUploadFileorMoved:\", StateManager.get('FirstUploadFileorMoved'));\r\n        \r\n        if (UIManager.isMobile()) {\r\n            $target = jQuery('#Ele1A').find('#drop_file_zone_achat');\r\n        }\r\n        \r\n        if (StateManager.get('Commande_Format_Transmis') === 'R\u00e9dactionnel') {\r\n            const fileURL = StateManager.get('FullPathAdFile');\r\n            const filename = fileURL.substring(fileURL.indexOf('_') + 1);\r\n            \r\n            try {\r\n                \/\/ \u2705 R\u00e9utiliser le File cach\u00e9 (\u00e9vite CORS cross-domain)\r\n                let file;\r\n                if (window._lastRedactionnelFile) {\r\n                    file = window._lastRedactionnelFile;\r\n                    console.log('\u267b\ufe0f R\u00e9utilisation du File cach\u00e9:', file.name, Math.round(file.size \/ 1024) + 'KB');\r\n                } else {\r\n                    file = await FileManager.urlToFile(fileURL, filename);\r\n                }\r\n                await UploadManager.handleFileUpload(file, $target);\r\n            } catch (error) {\r\n                console.error('Error:', error);\r\n            }\r\n        } else {\r\n            await UploadManager.handleFileUpload(e.dataTransfer.files[0], $target);\r\n        }\r\n    },\r\n    \r\n    processVideoDrop($target) {\r\n        const isValidBackground = window.getComputedStyle(\r\n            $target.closest('#UploadFileConteneur')[0]\r\n        ).backgroundColor !== 'rgba(0, 0, 0, 0)';\r\n        \r\n        if (!isValidBackground) {\r\n            jQuery('.MsgAdNotDisplayed').show();\r\n            StateManager.set(\"AdDisplayed\", 'No');\r\n            return;\r\n        }\r\n        \r\n        StateManager.set(\"AdDisplayed\", 'Yes');\r\n        \r\n        const objectUrl = StateManager.get('videoSrc');\r\n        const $previousDropZone = $('.drop_file_zone_achat_class').has(`video[src=\"${objectUrl}\"]`);\r\n        window.RestoreadSpaceTemplate($previousDropZone);\r\n        \r\n        const videoElement = jQuery('<video controls autoplay muted>').attr({\r\n            'src': objectUrl,\r\n            'max-width': '100%',\r\n            'max-height': '100%',\r\n            'draggable': 'true'\r\n        }).css({\r\n            'max-width': '100%',\r\n            'max-height': '100%'\r\n        });\r\n        \r\n        $target.empty().append(videoElement);\r\n        \r\n        $target.closest('#UploadFileConteneur').css({'background-color': '#FFFFFF00'});\r\n        \r\n        var _isViaPopupParentV = (sessionStorage.getItem('_ViaPopupOpen') === 'Yes');\r\n        $target.closest('.OrdiMobileConteneurClass')\r\n            .find('.RefEspacePublicitaire')\r\n            .html('R\u00e9f\u00e9rence de l\\'espace : ' + StateManager.get('Commande_Emplacement_Page_Web'))\r\n            .attr('id', 'RefEspacePublicitaire')\r\n            .each(function() {\r\n                var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                this.style.setProperty('display', 'block', 'important');\r\n                if (_isViaPopupParentV) { this.style.setProperty('margin-top', '4px', 'important'); }\r\n            });\r\n        (function() {\r\n            var _rankPosV = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            var _posLibV = PreviewRenderer._getPositionLibelle(_rankPosV);\r\n            if (_posLibV) {\r\n                $target.closest('.OrdiMobileConteneurClass')\r\n                    .find('.PositionEspacePublicitaireDeplacer')\r\n                    .text(_posLibV)\r\n                    .each(function() {\r\n                        var _parent = jQuery(this).closest('.DeplaceAnnonce')[0];\r\n                        if (_parent) { _parent.style.setProperty('display', 'flex', 'important'); }\r\n                        this.style.setProperty('display', 'block', 'important');\r\n                        if (_isViaPopupParentV) { this.style.setProperty('margin-top', '4px', 'important'); }\r\n                    });\r\n            }\r\n        })();\r\n        \r\n        $target.closest('.OrdiMobileConteneurClass').find('.AdUploadedTitle').show();\r\n        \r\n        $target.closest('.OrdiMobileConteneurClass')\r\n            .css({'top': '60px', 'margin-bottom': '80px'});\r\n        \r\n        $target.closest('.HTMLUploadfileConteneur')\r\n            .not('.AdUploadedTitle')\r\n            .css({\r\n                'top': '0px',\r\n                'margin-bottom': '0px',\r\n                'box-shadow': 'inset 0 0 0 2px #00FF19',  \/\/ \u2705 v2.4.5\r\n                'background-color': 'white'\r\n            });\r\n        \r\n        $target.closest('.droppable')\r\n            .find('.AdDroppedTextNotDisplayed, .ChoisirEspacePublicitaire2ndLigne, span.ClassHdpCdp, .ClassRefEsp, .HideFormButton, .EspPubFormatMainContainer, .EnvoiUlterieurContainer')\r\n            .hide();\r\n        \r\n        jQuery('#MsgElementsCommandeValides, #MessageOptionsacompleterConteneur').hide();\r\n        \r\n        StateManager.set(\"FirstUploadFileorMoved\", 'Moved');\r\n        \r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server (au lieu d'envoyer automatiquement)\r\n        FormatUIManager.updateReserverCheckboxState($target.closest('.droppable'));\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion de l'explorateur de fichiers\r\n *\/\r\nconst FileExplorer = {\r\n    isRunning: false,\r\n\r\n    open(e) {\r\n        if (this.isRunning) {\r\n            return;\r\n        }\r\n        \r\n        const $element = jQuery(e.target).closest('.droppable');\r\n        \r\n        \/\/ \u2705 v2.7.3 : Le format sera d\u00e9duit de l'extension du fichier s\u00e9lectionn\u00e9\r\n        \/\/ \u2192 ne plus bloquer l'ouverture du s\u00e9lecteur si aucun format n'est encore choisi\r\n        this.isRunning = true;\r\n        \r\n        const rankId = $element.attr('id');\r\n        \r\n        StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n        StateManager.set('Commande_Emplacement_Page_Web',\r\n            StateManager.buildEmplacementReference(rankId));\r\n        \r\n        UIManager.updateEmplacementDisplay();\r\n        \r\n        console.log(\"Drop at:\", StateManager.get('Commande_Emplacement_Page_Web'));\r\n        \r\n        StateManager.set('FirstUploadFileorMoved', 'FirstUpload');\r\n        \r\n        const $currentTarget = jQuery(e.target).closest('#drop_file_zone_achat');\r\n        const $fileInput = jQuery('#selectfile_achat');\r\n        \r\n        console.log(\"currentTarget\", $currentTarget);\r\n        \r\n        $fileInput.off('change');\r\n        $fileInput.off('click');\r\n        \r\n        const onChange = (event) => {\r\n            this.isRunning = false;\r\n            $fileInput.off('change', onChange);\r\n            \r\n            \/\/ \u2705 NE PLUS mettre sendDataToParentFlag ici\r\n            \/\/ StateManager.set(\"sendDataToParentFlag\", 'Yes');\r\n            \r\n            if ($fileInput[0].files.length > 0) {\r\n                UploadManager.handleFileUpload($fileInput[0].files[0], $currentTarget);\r\n            }\r\n        };\r\n        \r\n        const onClick = () => {\r\n            $fileInput.off('click', onClick);\r\n            $fileInput.on('focus', function onFocus() {\r\n                setTimeout(() => {\r\n                    if (!$fileInput.val()) {\r\n                        FileExplorer.isRunning = false;\r\n                    }\r\n                }, 200);\r\n                $fileInput.off('focus', onFocus);\r\n            });\r\n        };\r\n        \r\n        $fileInput.on('change', onChange);\r\n        $fileInput.on('click', onClick);\r\n        \r\n        \/\/ \u2705 Contr\u00f4le format imm\u00e9diat \u2014 avant d'ouvrir le s\u00e9lecteur de fichiers\r\n        var _$dropGuard = $currentTarget.closest('.droppable');\r\n        if (_$dropGuard.length) {\r\n            var _hasFmtNow = false;\r\n            _$dropGuard.find('.EspPubFormatContainer').each(function() {\r\n                if (jQuery(this).hasClass('FormatIdCreation')) return;\r\n                if (jQuery(this).hasClass('FormatIdPopUp')) return;\r\n                var _bg = this.style.backgroundColor || '';\r\n                if (_bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white') { _hasFmtNow = true; return false; }\r\n                var _fmtEl = this.querySelector('.EspPubFormat');\r\n                if (_fmtEl) { var _col = _fmtEl.style.color || ''; if (_col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900') { _hasFmtNow = true; return false; } }\r\n            });\r\n            if (!_hasFmtNow) {\r\n                UIManager.showFormatError($currentTarget, 'Merci de s\u00e9lectionner un format d\\'annonce avant de t\u00e9l\u00e9charger votre fichier');\r\n                FormatUIManager.flashTitle($currentTarget);\r\n                FileExplorer.isRunning = false;\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ \u2705 v2.7.3 : R\u00e9initialiser la valeur avant d'ouvrir le s\u00e9lecteur\r\n        \/\/ Sans ce reset, certains navigateurs (Safari mobile) ne d\u00e9clenchent pas\r\n        \/\/ l'\u00e9v\u00e9nement 'change' si un fichier avait d\u00e9j\u00e0 \u00e9t\u00e9 s\u00e9lectionn\u00e9 pr\u00e9c\u00e9demment\r\n        $fileInput.val('');\r\n        $fileInput.click();\r\n        \r\n        setTimeout(() => {\r\n            this.isRunning = false;\r\n        }, 300);\r\n    }\r\n};\r\n\r\n\/**\r\n * Module de gestion du reset d'annonce\r\n *\/\r\nconst AdResetHandler = {\r\n    handle(e) {\r\n        e.preventDefault();\r\n        console.log(\"CroixResetAnnonce click\");\r\n        \r\n        \/\/ Reset l'\u00e9tat EnvoiUlterieur\r\n        StateManager.set('EnvoiUlterieur', 'false');\r\n        \r\n        \/\/ \u2705 v1.19.6 : Reset FileReceived mais garder le format s\u00e9lectionn\u00e9\r\n        StateManager.set('FileReceived', 'No');\r\n        \r\n        const $element = jQuery(e.currentTarget);\r\n        const $droppable = $element.closest('.droppable');\r\n        const resetRef = StateManager.buildEmplacementReference($droppable.attr('id'));\r\n        \r\n        console.log(\"Reset_Commande_Emplacement_Page_Web:\", resetRef);\r\n        \r\n        \/\/ \u2705 Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        $droppable.find('.ReserverContainer').hide();\r\n        \r\n        var _rankForDel = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n        \/\/ \u2705 Bug 10 : envoyer via postMessage si dans une iframe (pas seulement mode=popup)\r\n        \/\/   AchatEspaceCall=Yes uniquement pour mode=popup \u2014 pour les sites pays standards\r\n        \/\/   (iframe r\u00e9gie, non-popup), window.processdataDelAd n'existe pas dans le contexte iframe\r\n        if (StateManager.get(\"AchatEspaceCall\") === 'Yes' || window.self !== window.top) {\r\n            MessageManager.sendDelAdToParent({\r\n                Commande_Emplacement_Page_Web: resetRef,\r\n                Rank_Emplacement_Page_Web: _rankForDel,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n        } else {\r\n            window.processdataDelAd({\r\n                Commande_Emplacement_Page_Web: resetRef,\r\n                Rank_Emplacement_Page_Web: _rankForDel,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n        }\r\n        \r\n        \/\/ \u2705 v1.16.0 : Appeler RestoreadSpaceTemplateLocal directement (accessible dans ce scope)\r\n        RestoreadSpaceTemplateLocal(e.currentTarget);\r\n        \r\n        window.FonctionCroixResetAnnonce(e.currentTarget);\r\n        \r\n        \/\/ \u2705 v2.1.1 : Sauf popup\r\n        var formatWasSelected = sessionStorage.getItem('FormatSelect') \r\n            || sessionStorage.getItem('Commande_Format_Transmis')\r\n            || $droppable.data('kitFormatSelect');\r\n        \r\n        if (sessionStorage.getItem('PopUpChoice') !== 'Yes' ? (formatWasSelected || sessionStorage.getItem('Formatchoisi') === 'Yes') : false) {\r\n            sessionStorage.setItem('Formatchoisi', 'Yes');\r\n            console.log('\u2705 Formatchoisi forc\u00e9 \u00e0 Yes apr\u00e8s reset');\r\n        }\r\n        \r\n        \/\/ \u2705 v1.19.6 : Remettre \u00e0 jour le titre format et r\u00e9afficher la checkbox R\u00e9server\r\n        setTimeout(() => {\r\n            FormatUIManager.updateTitleColor($droppable);\r\n            FormatUIManager.updateReserverCheckboxState($droppable);\r\n            \r\n            \/\/ \u2705 R\u00e9afficher le .ReserverContainer statique\r\n            $droppable.find('.ReserverContainer').show();\r\n        }, 150);\r\n    }\r\n};\r\n\r\n\/**\r\n * \u2705 Template pour r\u00e9initialisation des espaces publicitaires (IFRAME)\r\n *\/\r\nvar adSpaceTemplatesLocal = {};\r\n\r\nfunction saveAdSpaceTemplateLocal() {\r\n    var saved = 0;\r\n    jQuery('.droppable').each(function() {\r\n        var droppableId = jQuery(this).attr('id');\r\n        if (!droppableId) return;\r\n        \r\n        \/\/ \u2705 Ne jamais sauvegarder Ele0A (clone temporaire popup, pas un template d'origine)\r\n        if (droppableId === 'Ele0A') return;\r\n        \r\n        \/\/ \u2705 Ne pas r\u00e9-\u00e9craser un template d\u00e9j\u00e0 sauvegard\u00e9\r\n        if (adSpaceTemplatesLocal[droppableId]) return;\r\n        \r\n        \/\/ \u2705 v2.3.4 : Ne pas sauvegarder si template d\u00e9j\u00e0 connu (\u00e9tat propre sauvegard\u00e9)\r\n        \/\/ Le guard hasAd est supprim\u00e9 \u2014 on veut capturer le template le plus t\u00f4t possible\r\n        \/\/ Si le template existe d\u00e9j\u00e0, on ne le r\u00e9\u00e9crase pas\r\n        \/\/ (le guard anti-\u00e9crasement if (adSpaceTemplatesLocal[droppableId]) return; suffit)\r\n        \r\n        var $content = jQuery(this).find('.OrdiMobileConteneurClass').first();\r\n        if ($content.length > 0) {\r\n            adSpaceTemplatesLocal[droppableId] = $content.clone(true, true);\r\n            saved++;\r\n        }\r\n    });\r\n    if (saved > 0) {\r\n        console.log('\u2705 Templates espaces pub sauvegard\u00e9s:', saved, 'espaces -', Object.keys(adSpaceTemplatesLocal));\r\n        return true;\r\n    }\r\n    return false;\r\n}\r\n\r\nfunction RestoreadSpaceTemplateLocal(element) {\r\n    console.log('\ud83d\udd04 RestoreadSpaceTemplateLocal', element);\r\n    \r\n    var $element = jQuery(element);\r\n    var $droppable = $element.closest('.droppable');\r\n    var droppableId = $droppable.attr('id');\r\n    \r\n    \/\/ \u2705 FIX Ele0A : pas de template sauvegard\u00e9 (clone temporaire popup)\r\n    \/\/ \u2192 pas de remplacement DOM, reset visuel direct sur le contenu existant\r\n    var _isEle0A = (droppableId === 'Ele0A');\r\n\r\n    \/\/ \u2705 v1.19.3 : Chercher le template sp\u00e9cifique \u00e0 CET espace (sauf Ele0A)\r\n    if (!_isEle0A) {\r\n        if (!droppableId || !adSpaceTemplatesLocal[droppableId]) {\r\n            if (!saveAdSpaceTemplateLocal()) {\r\n                console.error('\u274c Template non disponible');\r\n                return false;\r\n            }\r\n            if (!adSpaceTemplatesLocal[droppableId]) {\r\n                console.error('\u274c Template non trouv\u00e9 pour', droppableId);\r\n                return false;\r\n            }\r\n        }\r\n    }\r\n    \r\n    var adSpaceElement = $droppable.find('.OrdiMobileConteneurClass').first();\r\n    \r\n    if (!adSpaceElement || !adSpaceElement.length) {\r\n        console.error('\u274c OrdiMobileConteneurClass non trouv\u00e9');\r\n        return false;\r\n    }\r\n    \r\n    var newElement;\r\n    if (_isEle0A) {\r\n        \/\/ Ele0A : pas de clone \u2014 on remet en \u00e9tat le contenu existant directement\r\n        newElement = adSpaceElement;\r\n        console.log('\u2705 [Ele0A] reset visuel direct (pas de template clone)');\r\n        \/\/ \u2705 Vider le contenu du dropzone et restaurer le HTML par d\u00e9faut\r\n        \/\/ (le replaceWith n'ayant pas lieu, l'image d\u00e9pos\u00e9e et le fond blanc restent sinon)\r\n        var $dz0A = $droppable.find('#drop_file_zone_achat');\r\n        $dz0A.empty().html(\r\n            '<div id=\"drag_upload_file_achat\">' +\r\n                '<p class=\"UploadIci\" style=\"color:#FB5E2A;font-weight:600;\">Ici glisser \u2013 d\u00e9poser ou<br>t\u00e9l\u00e9charger une annonce<\/p>' +\r\n            '<\/div>'\r\n        );\r\n        \/\/ Restaurer le fond bleu #9FC5F3 et retirer les styles d'annonce upload\u00e9e\r\n        $droppable.find('#UploadFileConteneur').css({\r\n            'background-color': '#9FC5F3',\r\n            'border': '',\r\n            'box-sizing': ''\r\n        });\r\n        $droppable.find('.HTMLUploadfileConteneur').css({\r\n            'box-shadow': '',\r\n            'background-color': '',\r\n            'border': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': '',\r\n            'overflow': '',\r\n            'height': '',\r\n            'max-height': ''\r\n        });\r\n        \/\/ Masquer la croix et le titre d'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle, #CroixResetAnnonce, .CroixResetAnnonceContainer, .DeplaceAnnonce').hide();\r\n        $droppable.removeAttr('data-via-ad-loaded').removeAttr('data-from-miniature');\r\n        console.log('\u2705 [Ele0A] dropzone vid\u00e9 + fond restaur\u00e9');\r\n    } else {\r\n        \/\/ \u2705 v1.19.3 : Cloner le template SP\u00c9CIFIQUE \u00e0 cet espace (pas le premier)\r\n        newElement = adSpaceTemplatesLocal[droppableId].clone(true, true);\r\n        console.log('\u2705 Template utilis\u00e9 pour', droppableId);\r\n        adSpaceElement.replaceWith(newElement);\r\n        \/\/ \u2705 v2.4.12 : Effacer data-via-ad-loaded (sinon selectEspaceActif masque .ReserverContainer)\r\n        $droppable.removeAttr('data-via-ad-loaded').removeAttr('data-from-miniature');\r\n        if (window.outerWidth < 1000) {\r\n            newElement.css({'margin-top': '-25px', 'bottom': '0px'});\r\n        }\r\n    }\r\n    \r\n    \/\/ D\u00e9cocher la case Envoi diff\u00e9r\u00e9 si elle existe\r\n    newElement.find('input[name*=\"EnvoiUlterieur\"]').prop('checked', false);\r\n    \r\n    \/\/ \u2705 D\u00e9cocher la checkbox \"R\u00e9server\"\r\n    newElement.find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').prop('checked', false);\r\n    \r\n    \/\/ Reset l'\u00e9tat EnvoiUlterieur\r\n    StateManager.set('EnvoiUlterieur', 'false');\r\n    \r\n    \/\/ \u2705 v1.19.3 : R\u00e9initialiser TOUS les styles inline des conteneurs parents modifi\u00e9s pendant le d\u00e9p\u00f4t\r\n    if ($droppable.length) {\r\n        \/\/ \u2705 v2.0.9 : Restaurer les marges de l'algorithme de positionnement (sauvegard\u00e9es par styleUploadedAd)\r\n        var origMt = $droppable.data('orig-mt');\r\n        $droppable.css({\r\n            'margin-top': (origMt !== undefined) ? origMt + 'px' : '',\r\n            'margin-bottom': '',\r\n            'margin-left': '',\r\n            'margin-right': ''\r\n        });\r\n        \/\/ \u2705 v2.0.11 : Cleanup event namespace docpreview\r\n        $droppable.off('click.docpreview');\r\n        \r\n        \/\/ Reset .OrdiMobileConteneurClass margins\r\n        var $container = $droppable.find('.OrdiMobileConteneurClass');\r\n        var origMb = $container.data('orig-mb');\r\n        $container.css({\r\n            'margin-top': '',\r\n            'margin-bottom': (origMb !== undefined) ? origMb + 'px' : ''\r\n        });\r\n        \r\n        \/\/ Reset .HTMLUploadfileConteneur (set by styleUploadedAd: box-shadow inset, background-color:white)\r\n        $droppable.find('.HTMLUploadfileConteneur').css({\r\n            'border': '',\r\n            'box-shadow': '',\r\n            'outline': '',\r\n            'outline-offset': '',\r\n            'background-color': '',\r\n            'margin-top': '',\r\n            'margin-bottom': '',\r\n            'min-height': '',\r\n            'overflow': '',\r\n            'padding': '',\r\n            'position': ''\r\n        });\r\n        $droppable.find('.via-green-border-overlay').remove();\r\n        $droppable.find('.via-position-label').remove();\r\n        \/\/ \u2705 v2.7.3 : Nettoyage header\/footer\/wrapper inject\u00e9s par _buildAdOverlay\r\n        var $_ufcReset = $droppable.find('.HTMLUploadfileConteneur');\r\n        \/\/ Sortir HTMLUploadfileConteneur du wrapper avant de supprimer le wrapper\r\n        var $_wrapReset = $droppable.find('.via-ad-wrapper');\r\n        if ($_wrapReset.length) {\r\n            $_wrapReset.before($_ufcReset);\r\n            $_wrapReset.remove();\r\n        }\r\n        $droppable.find('.via-ad-header').remove();\r\n        $droppable.find('.via-ad-footer').remove();\r\n        $_ufcReset.css({'display': '', 'flex-direction': '', 'overflow': '', 'box-shadow': '', 'background-color': '', 'margin': '', 'padding': ''});\r\n        \r\n        \/\/ \u2705 Restaurer pointer-events sur OrdiMobileConteneurClass et ses enfants\r\n        $droppable.find('.OrdiMobileConteneurClass').css('pointer-events', '');\r\n        $droppable.find('#CroixResetAnnonce').css({'pointer-events': '', 'position': '', 'z-index': ''});\r\n        \/\/ \u2705 v2.4.5 : Reset margin-right Ele0A (pos\u00e9 par adjustMobileLayout)\r\n        var _croixContReset = $droppable.find('.CroixResetAnnonceContainer')[0];\r\n        if (_croixContReset) { _croixContReset.style.removeProperty('margin-right'); _croixContReset.style.removeProperty('margin-top'); }\r\n        $droppable.find('#PopUpMessageAchattest').css('pointer-events', '');\r\n        \r\n        \/\/ \u2705 v2.0.9 : Restaurer le scale(1.4) sur .UploadFileConteneur (r\u00e9duit \u00e0 scale(1) par styleUploadedAd sur mobile)\r\n        $droppable.find('.UploadFileConteneur').css({\r\n            'transform': '',\r\n            'transform-origin': ''\r\n        });\r\n        \r\n        \/\/ Reset #UploadFileConteneur - Restaurer le fond bleu #9FC5F3 + retirer liser\u00e9 envoi diff\u00e9r\u00e9\r\n        $droppable.find('#UploadFileConteneur').css({\r\n            'width': '',\r\n            'background-color': '#9FC5F3',\r\n            'border': '',\r\n            'box-sizing': ''\r\n        });\r\n        \r\n        \/\/ Reset .ToBeHidden (set by adjustDesktopLayout: top:105px, min-height:300px + overflow mobile)\r\n        $droppable.closest('.ToBeHidden').css({\r\n            'top': '',\r\n            'min-height': '',\r\n            'overflow': ''\r\n        });\r\n        \r\n        \/\/ Reset overflow sur le parent du droppable (set by adjustDesktopLayout)\r\n        $droppable.parent().css('overflow', '');\r\n        \r\n        \/\/ \u2705 v1.19.5 : Gestion device-specific pour les textes\r\n        var isDesktop = window.outerWidth >= 1000;\r\n        \r\n        if (isDesktop) {\r\n            \/\/ Desktop : cacher les textes mobiles, afficher UploadIci\r\n            $droppable.find('.TexteMobile').hide();\r\n            $droppable.find('.TexteMobileAnnonce').hide();\r\n            $droppable.find('.TexteMobileAjoutAnnonce').hide();\r\n            $droppable.find('.UploadIci').show();\r\n        } else {\r\n            \/\/ Mobile : afficher TexteMobileAnnonce\r\n            $droppable.find('.TexteMobileAnnonce').show();\r\n        }\r\n        \r\n        \/\/ R\u00e9-afficher les \u00e9l\u00e9ments masqu\u00e9s pendant l'upload\r\n        $droppable.find('.PositionEspacePublicitaireContainer').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireDisponibiliteConteneur').show();\r\n        $droppable.find('.PositionEspacePublicitaire, .ReferenceEspacePublicitaire, .ChoisirEspacePublicitaireDisponibiliteConteneur > div > .elementor-widget-text-editor').show();\r\n        $droppable.find('.AdDroppedTextNotDisplayed').hide();\r\n        \/\/ \u2705 v2.4.5 : Ele0A + PopUpChoice=Yes \u2192 ne pas r\u00e9-afficher le titre si format d\u00e9j\u00e0 s\u00e9lectionn\u00e9\r\n        \/\/ v4.9ds : condition _skipTitre retir\u00e9e \u00e0 la restauration. Sur les sites pays\r\n        \/\/   (PopUpChoice=Yes) avec Ele0A, le titre Format n'\u00e9tait PAS r\u00e9affich\u00e9 apr\u00e8s\r\n        \/\/   suppression d'annonce \u2192 l'utilisateur ne voyait plus la consigne pour\r\n        \/\/   res\u00e9lectionner un format. \u00c0 la restauration, on force toujours le show().\r\n        $droppable.find('.SelectionFormatTitreBlanc').hide();\r\n        $droppable.find('.SelectionFormatTitre').show();\r\n        $droppable.find('span.ClassHdpCdp, .ClassRefEsp').show();\r\n        $droppable.find('.EspPubFormatMainContainer').show();\r\n        $droppable.find('.EspPubFormatListe').show();\r\n        $droppable.find('.ChoisirEspacePublicitaireClass').show();\r\n        $droppable.find('.GlisserDeposerConteneur').show();\r\n        $droppable.find('.OUClass').show();\r\n        $droppable.find('.PositionReference').show();\r\n        $droppable.find('.ClassHdpCdp').show();\r\n        $droppable.find('.ClassRefEsp').show();\r\n        $droppable.find('.EnvoiUlterieurTexte').show();\r\n        $droppable.find('.EnvoiUlterieurContainer').show();\r\n        \r\n        \/\/ \u2705 v1.19.5 : R\u00e9afficher le .ReserverContainer (bouton Elementor statique)\r\n        $droppable.find('.ReserverContainer').show();\r\n        newElement.find('.ReserverContainer').show();\r\n        \r\n        \/\/ Masquer les \u00e9l\u00e9ments sp\u00e9cifiques \u00e0 l'annonce upload\u00e9e\r\n        $droppable.find('.AdUploadedTitle, #CroixResetAnnonce, .CroixResetAnnonceContainer, .DeplaceAnnonce').hide();\r\n        $droppable.find('#CroixResetAnnonce').css({'position': '', 'z-index': ''});\r\n        $droppable.find('.RefEspacePublicitaire').hide();\r\n        \r\n        \/\/ Supprimer le bouton \"R\u00e9server\" dynamique\r\n        $droppable.find('.reserver-dynamic-container').remove();\r\n        $droppable.next('.reserver-dynamic-container').remove();\r\n        \r\n        \/\/ \u2705 v1.19.6 : Restaurer le format s\u00e9lectionn\u00e9 visuellement\r\n        \/\/ Chercher le format dans plusieurs sources possibles\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') \r\n            || sessionStorage.getItem('Commande_Format_Transmis')\r\n            || $droppable.data('kitFormatSelect')\r\n            || '';\r\n        \r\n        var formatchoisi = sessionStorage.getItem('Formatchoisi');\r\n        console.log('\ud83d\udcd0 Format \u00e0 restaurer:', formatSelect, '| Formatchoisi:', formatchoisi);\r\n        \r\n        \/\/ D'abord reset tous les formats visuellement (sur newElement ET $droppable)\r\n        var $allFormats = newElement.find('.EspPubFormatContainer').add($droppable.find('.EspPubFormatContainer'));\r\n        $allFormats.css({\r\n            'background-color': '',\r\n            'border': ''\r\n        });\r\n        newElement.find('.EspPubFormat').add($droppable.find('.EspPubFormat')).css({\r\n            'color': ''\r\n        });\r\n        \r\n        \/\/ Si un format \u00e9tait s\u00e9lectionn\u00e9 (via sessionStorage OU visuellement avant)\r\n        \/\/ \u2705 v2.1.1 : Sauf popup sur Ele0A (g\u00e9r\u00e9 s\u00e9par\u00e9ment via setTimeout)\r\n        \/\/ \u2705 v2.6 : Pour Ele1A\/2A\/3A, restaurer le format m\u00eame en mode popup (pop-up = format sous-jacent identique)\r\n        var _isEle0APopup = ($droppable.attr('id') === 'Ele0A' ? sessionStorage.getItem('PopUpChoice') === 'Yes' : false);\r\n        if ((formatSelect || formatchoisi === 'Yes') ? !_isEle0APopup : false) {\r\n            var formatFound = false;\r\n            \r\n            if (formatSelect) {\r\n                \/\/ \u2705 v2.1.1 : Normaliser via NFD (plus fiable que les replace manuels)\r\n                var formatLower = formatSelect.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase().replace(\/ \/g, '');\r\n                \r\n                \/\/ Appliquer sur newElement ET $droppable pour \u00eatre s\u00fbr\r\n                var $allContainers = newElement.find('.EspPubFormatContainer').add($droppable.find('.EspPubFormatContainer'));\r\n                \r\n                $allContainers.each(function() {\r\n                    var className = this.className.normalize('NFD').replace(\/[\\u0300-\\u036f]\/g, '').toLowerCase();\r\n                    \r\n                    if (className.includes(formatLower)) {\r\n                        jQuery(this).css({'background-color': '#ffffff'});\r\n                        jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                        formatFound = true;\r\n                        \/\/ \u2705 v2.4.12 : M\u00e9moriser le format restaur\u00e9 (lu par selectEspaceActif pour re-surligner apr\u00e8s reset global)\r\n                        $droppable.attr('data-restored-format', formatSelect);\r\n                        console.log('\u2705 Format restaur\u00e9 visuellement:', formatSelect, '- classe:', this.className);\r\n                    }\r\n                });\r\n            }\r\n            \r\n            \/\/ \u2705 IMPORTANT : Pr\u00e9server Formatchoisi = Yes pour permettre l'upload\r\n            sessionStorage.setItem('Formatchoisi', 'Yes');\r\n            console.log('\u2705 Formatchoisi maintenu \u00e0 Yes');\r\n            \r\n            \/\/ Basculer les titres format (sur newElement ET $droppable)\r\n            newElement.find('.SelectionFormatTitre').hide();\r\n            newElement.find('.SelectionFormatTitreBlanc').show();\r\n            $droppable.find('.SelectionFormatTitre').hide();\r\n            $droppable.find('.SelectionFormatTitreBlanc').show();\r\n        }\r\n    }\r\n    \r\n    \/\/ \u2705 v1.19.2 : Relancer InitLoadedPage pour recalculer positions et tailles\r\n    setTimeout(function() {\r\n        if (typeof window.InitLoadedPage === 'function') {\r\n            window.InitLoadedPage();\r\n        }\r\n        \r\n        \/\/ \u2705 v1.19.6 : R\u00e9attacher les MutationObservers sur les formats\r\n        if (typeof FormatUIManager !== 'undefined' ? FormatUIManager.observeFormatChanges : false) {\r\n            FormatUIManager.observeFormatChanges();\r\n        }\r\n    }, 100);\r\n    \r\n    console.log('\u2705 Espace publicitaire r\u00e9initialis\u00e9');\r\n    return true;\r\n}\r\n\r\n\/\/ \u2705 Exposer pour appel depuis yearbook-media.js (suppression popup mode=popup)\r\nwindow.RestoreadSpaceTemplateLocal = RestoreadSpaceTemplateLocal;\r\n\/\/ \u2705 Alias pour appel depuis processVideoDrop (drag d'annonce)\r\nwindow.RestoreadSpaceTemplate = RestoreadSpaceTemplateLocal;\r\n\r\nwindow.verifierAffichageMsgSelectEspaceLocal = function() {\r\n    const formatChoisi = StateManager.get('Formatchoisi') === 'Yes';\r\n    const fileReceived = StateManager.get('FileReceived');\r\n    const envoiUlterieur = StateManager.get('EnvoiUlterieur');\r\n    \r\n    var dateDebut, dateFin, pays;\r\n    try {\r\n        dateDebut = window.parent.$('#form-field-DebutCampagne').val();\r\n        dateFin = window.parent.$('#form-field-FinCampagne').val();\r\n        pays = window.parent.$('#form-field-Nationalite_Societe').val();\r\n    } catch(e) {\r\n        jQuery('#MsgSelectEspace').hide();\r\n        return;\r\n    }\r\n    \r\n    if (!dateDebut || !dateFin || !pays || !formatChoisi) {\r\n        jQuery('#MsgSelectEspace').hide();\r\n        return;\r\n    }\r\n    \r\n    if (fileReceived !== 'Yes') {\r\n        if (envoiUlterieur !== 'true') {\r\n            jQuery('#MsgSelectEspace').show();\r\n        } else {\r\n            jQuery('#MsgSelectEspace').hide();\r\n        }\r\n    } else {\r\n        jQuery('#MsgSelectEspace').hide();\r\n    }\r\n};\r\n\r\n\/\/ Sauvegarder les templates au chargement (apr\u00e8s rendu complet Elementor)\r\njQuery(document).ready(function() {\r\n    \/\/ \u2705 v1.19.3 : D\u00e9lai augment\u00e9 + double sauvegarde pour garantir les bonnes dimensions\r\n    setTimeout(saveAdSpaceTemplateLocal, 3000);\r\n    setTimeout(saveAdSpaceTemplateLocal, 6000);\r\n    \/\/ \u2705 v2.3.4 : Sauvegarde imm\u00e9diate d\u00e8s que la page est pr\u00eate dans l'iframe\r\n    \/\/ (avant toute interaction utilisateur)\r\n    setTimeout(saveAdSpaceTemplateLocal, 100);\r\n    setTimeout(saveAdSpaceTemplateLocal, 500);\r\n});\r\n\r\n\/\/ \u2705 v2.3.4 : Forcer sauvegarde \u00e0 la r\u00e9ception de elementsRemoved (espaces visibles + vierges)\r\nwindow.addEventListener('message', function(e) {\r\n    if (e.data ? e.data.type === 'elementsRemoved' : false) {\r\n        \/\/ Les espaces sont vierges \u00e0 ce stade \u2192 sauvegarder imm\u00e9diatement\r\n        setTimeout(saveAdSpaceTemplateLocal, 50);\r\n        setTimeout(saveAdSpaceTemplateLocal, 300);\r\n    }\r\n});\r\n\r\n\/**\r\n * Fonction helper pour le d\u00e9p\u00f4t r\u00e9dactionnel\r\n *\/\r\nfunction RedactionnelDepose() {\r\n    jQuery('#Tariftobedisplayed').html('-');\r\n    jQuery('#TarifDataStep3').html('\u00e0 choisir').css({'color': '#FB5E2A'});\r\n    jQuery('#FormatDataStep3').html('\u00e0 choisir').css({'color': '#FB5E2A'});\r\n    jQuery('#ListePaysDirect, #ListeThemeDirect, .ListeArticles, #PageAfficheeMessage').hide();\r\n    StateManager.set(\"PositionAnnonceSelection\", 'Yes');\r\n}\r\n\r\n\/**\r\n * Exposition des fonctions globales n\u00e9cessaires\r\n *\/\r\nwindow.uploadFile_achat = (e) => DropHandler.handleDrop(e);\r\nwindow.fileExplorer_achat = (e) => FileExplorer.open(e);\r\nwindow.ajaxFileUpload_achat = (fileObj, dropZone) => UploadManager.handleFileUpload(fileObj, dropZone);\r\nwindow.ActivatesendDataToParent = (dropZone) => UploadManager.activateSendDataToParent(dropZone);\r\n\/\/ \u2705 v2.3.4 : Exposer FormatUIManager pour appel depuis Entete.txt (selectEspaceActif)\r\nwindow.FormatUIManagerRef = FormatUIManager;\r\n\r\n\/\/ \u2705 v2.6 : Pb 12 \u2014 Restaurer l'affichage d'une annonce depuis son URL (sans re-upload)\r\n\/\/ Appel\u00e9 par Entete.txt > selectEspaceActif quand fileReceived=Yes mais annonce non affich\u00e9e\r\nwindow.restoreAdFromUrl = function(rankId, adUrl, formatLabel) {\r\n    console.log('\ud83d\udd04 [restoreAdFromUrl] appel | rank:', rankId, '| url:', adUrl);\r\n    if (!rankId || !adUrl) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] rank ou url manquant'); return; }\r\n    var $droppable = jQuery('#' + rankId);\r\n    if (!$droppable.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] #' + rankId + ' introuvable dans le DOM'); return; }\r\n    if ($droppable.attr('data-via-ad-loaded') === 'true') { console.log('\u23ed\ufe0f [restoreAdFromUrl] d\u00e9j\u00e0 charg\u00e9, skip'); return; }\r\n    \/\/ \u2705 v2.6 : Guard anti-concurrent \u2014 si un probe est d\u00e9j\u00e0 en cours, ne pas relancer\r\n    if ($droppable.attr('data-via-ad-restoring') === 'true') {\r\n        console.log('\u23ed\ufe0f [restoreAdFromUrl] probe en cours pour #' + rankId + ', skip | url demand\u00e9e:', adUrl);\r\n        return;\r\n    }\r\n    $droppable.attr('data-via-ad-restoring', 'true');\r\n    var $dropZone = $droppable.find('#drop_file_zone_achat');\r\n    if (!$dropZone.length) {\r\n        $droppable.removeAttr('data-via-ad-restoring');\r\n        console.warn('\u26a0\ufe0f [restoreAdFromUrl] #drop_file_zone_achat introuvable dans #' + rankId, '| enfants:', jQuery('#' + rankId).children().length);\r\n        return;\r\n    }\r\n    var _ext = (adUrl.split('?')[0].split('.').pop() || '').toLowerCase();\r\n    var _fileType = FileManager.getFileType(_ext);\r\n    var _fmt = (formatLabel || '').trim() || 'Document';\r\n    console.log('\ud83d\udce6 [restoreAdFromUrl] probe d\u00e9marr\u00e9 | ext:', _ext, '| type:', _fileType);\r\n    \/\/ \u2705 Ne pas \u00e9craser Rank_Emplacement_Page_Web \/ FullPathAdFile \/ FileReceived dans StateManager\r\n    \/\/ \u2192 \u00e9vite la race condition avec un upload en cours (finalizeUpload lirait le mauvais rank)\r\n    \/\/ \u2705 _isAdRestoration positionn\u00e9 juste avant styleUploadedAd dans chaque callback probe\r\n    \/\/ \u2192 \u00e9vite qu'un finalizeUpload concurrent ne remette le flag \u00e0 'No' avant le callback\r\n    if (_fileType === 'image') {\r\n        \/\/ \u2705 Probe: v\u00e9rifier que l'image est r\u00e9ellement accessible avant de marquer l'espace\r\n        \/\/ \u2705 _isAdRestoration positionn\u00e9 juste avant styleUploadedAd (pas avant le probe)\r\n        \/\/ \u2192 \u00e9vite qu'un finalizeUpload concurrent ne remette le flag \u00e0 'No' entre-temps\r\n        var _probe = new Image();\r\n        _probe.onload = function() {\r\n            console.log('\u2705 [restoreAdFromUrl] probe1 onload | url:', adUrl);\r\n            jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n            var $_dz = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n            if (!$_dz.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe1 onload: dropZone disparu dans #' + rankId); return; }\r\n            StateManager.set('_isAdRestoration', 'Yes');\r\n            StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n            \/\/ \u2705 Tronc commun : reset DOM propre puis flux standard\r\n            RestoreadSpaceTemplateLocal(jQuery('#' + rankId).find('.OrdiMobileConteneurClass')[0]);\r\n            var $_dz = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n            if (!$_dz.length) { return; }\r\n            PreviewRenderer.renderImage(adUrl, $_dz);\r\n            window._dropFromMiniature = true; \/\/ skip margin-top 150px\r\n            UIManager.updateAfterSuccessfulUpload($_dz);\r\n            window._dropFromMiniature = false;\r\n            UIManager.finalizeAdDisplay($_dz);\r\n            \/\/ v2.9 : si ViaPopupProcessAchat present, repositionner elements hors-lisere dans Ele0A\r\n            if (rankId === 'Ele0A') {\r\n                if ((sessionStorage.getItem('_ViaPopupOpen') === 'Yes')) {\r\n                    setTimeout(function() {\r\n                        var $_drp0A = jQuery('#Ele0A');\r\n                        \/\/ DeplaceAnnonceSubContainer : forcer margin-top positif\r\n                        var $_das = $_drp0A.find('.DeplaceAnnonceSubContainer');\r\n                        if ($_das.length) { $_das[0].style.setProperty('margin-top', '5px', 'important'); }\r\n                        \/\/ PositionEspacePublicitaireDeplacer + RefEspacePublicitaire\r\n                        $_drp0A.find('.PositionEspacePublicitaireDeplacer').each(function() {\r\n                            this.style.setProperty('margin-top',  '5px', 'important');\r\n                        });\r\n                        $_drp0A.find('.RefEspacePublicitaire').each(function() {\r\n                            this.style.setProperty('margin-top',  '5px', 'important');\r\n                            this.style.setProperty('margin-right', '5px', 'important');\r\n                        });\r\n                        \/\/ CroixResetAnnonceContainer : forcer a l'interieur du lisere\r\n                        var $_crc = $_drp0A.find('.CroixResetAnnonceContainer');\r\n                        if ($_crc.length) {\r\n                            $_crc[0].style.setProperty('top',    '5px', 'important');\r\n                            $_crc[0].style.setProperty('right',  '5px', 'important');\r\n                            $_crc[0].style.setProperty('margin', '0px', 'important');\r\n                        }\r\n                        console.log('[restoreAdFromUrl] v2.9 : Ele0A elements repositionnes (ViaPopupProcessAchat)');\r\n                    }, 200);\r\n                }\r\n            }\r\n            \/\/ \u2705 v2.6 : Repositionner Ele0A apr\u00e8s restauration (page peinte, rect valide)\r\n            if (rankId === 'Ele0A') {\r\n                if (typeof positionEle0AOverEle1A === 'function') {\r\n                    requestAnimationFrame(function() {\r\n                        var $_e1W = jQuery('.ToBeHidden').has('[id=\"Ele1A\"]').not(jQuery('.ToBeHidden').has('#Ele0A')).first();\r\n                        var $_e1U = $_e1W.find('#UploadFileConteneur').first();\r\n                        var _lr = $_e1U.length ? $_e1U[0].getBoundingClientRect() : null;\r\n                        window._ele1ASnapRect = (_lr ? (_lr.width > 0 || _lr.height > 0) : false) ? _lr : null;\r\n                        positionEle0AOverEle1A(window._ele1ASnapRect);\r\n                        console.log('[restoreAdFromUrl] repo Ele0A liveRect:', window._ele1ASnapRect ? Math.round(window._ele1ASnapRect.top)+'\/'+Math.round(window._ele1ASnapRect.left) : 'null->fallback');\r\n                    });\r\n                }\r\n            }\r\n            console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e | rank:', rankId, '| type: image | url:', adUrl);\r\n        };\r\n        _probe.onerror = function() {\r\n            console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe1 onerror | url:', adUrl);\r\n            \/\/ \u2705 Fallback : essayer wp-admin\/uploads si wp-content\/uploads \u00e9choue\r\n            \/\/ (fichier physiquement dans wp-admin\/uploads mais URL stock\u00e9e avec wp-content)\r\n            var _altUrl = adUrl.replace('\/wp-content\/uploads\/', '\/wp-admin\/uploads\/');\r\n            if (_altUrl !== adUrl) {\r\n                var _probe2 = new Image();\r\n                _probe2.onload = function() {\r\n                    console.log('\u2705 [restoreAdFromUrl] probe2 onload | alt url:', _altUrl);\r\n                    jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n                    var $_dz2 = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n                    if (!$_dz2.length) { console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe2 onload: dropZone disparu dans #' + rankId); return; }\r\n                    StateManager.set('_isAdRestoration', 'Yes');\r\n                    StateManager.set('Rank_Emplacement_Page_Web', rankId);\r\n                    \/\/ \u2705 Tronc commun : reset DOM propre puis flux standard\r\n                    RestoreadSpaceTemplateLocal(jQuery('#' + rankId).find('.OrdiMobileConteneurClass')[0]);\r\n                    var $_dz2 = jQuery('#' + rankId).find('#drop_file_zone_achat');\r\n                    if (!$_dz2.length) { return; }\r\n                    PreviewRenderer.renderImage(_altUrl, $_dz2);\r\n                    window._dropFromMiniature = true;\r\n                    UIManager.updateAfterSuccessfulUpload($_dz2);\r\n                    window._dropFromMiniature = false;\r\n                    UIManager.finalizeAdDisplay($_dz2);\r\n                    \/\/ \u2705 v2.6 : Repositionner Ele0A apr\u00e8s restauration (page peinte, rect valide)\r\n                    if (rankId === 'Ele0A') {\r\n                        if (typeof positionEle0AOverEle1A === 'function') {\r\n                            requestAnimationFrame(function() {\r\n                                var $_e1W = jQuery('.ToBeHidden').has('[id=\"Ele1A\"]').not(jQuery('.ToBeHidden').has('#Ele0A')).first();\r\n                                var $_e1U = $_e1W.find('#UploadFileConteneur').first();\r\n                                var _lr = $_e1U.length ? $_e1U[0].getBoundingClientRect() : null;\r\n                                window._ele1ASnapRect = (_lr ? (_lr.width > 0 || _lr.height > 0) : false) ? _lr : null;\r\n                                positionEle0AOverEle1A(window._ele1ASnapRect);\r\n                                console.log('[restoreAdFromUrl] repo Ele0A liveRect:', window._ele1ASnapRect ? Math.round(window._ele1ASnapRect.top)+'\/'+Math.round(window._ele1ASnapRect.left) : 'null->fallback');\r\n                    \/\/ Fix fond gris UFC apres restauration\r\n                    var $_ufc0R = jQuery('#Ele0A').find('#UploadFileConteneur');\r\n                    if ($_ufc0R.length) { $_ufc0R.css('background-color', 'white'); console.log('[restoreAdFromUrl] UFC Ele0A -> blanc'); }\r\n                            });\r\n                        }\r\n                    }\r\n                    console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e (alt) | rank:', rankId, '| url:', _altUrl);\r\n                };\r\n                _probe2.onerror = function() {\r\n                    jQuery('#' + rankId).removeAttr('data-via-ad-restoring');\r\n                    console.warn('\u26a0\ufe0f [restoreAdFromUrl] probe2 onerror AUSSI | alt url:', _altUrl, '| rank:', rankId);\r\n                };\r\n                _probe2.src = _altUrl;\r\n            } else {\r\n                console.warn('\u26a0\ufe0f [restoreAdFromUrl] image inaccessible \u2192 skip | rank:', rankId);\r\n            }\r\n        };\r\n        _probe.src = adUrl;\r\n        return;\r\n    } else if (_fileType === 'video') {\r\n        PreviewRenderer.renderVideo(adUrl, adUrl.split('\/').pop(), $dropZone);\r\n    } else {\r\n        PreviewRenderer.renderDocumentPreviewNoImage($dropZone, _fmt, '');\r\n    }\r\n    StateManager.set('_isAdRestoration', 'Yes');\r\n    UIManager.styleUploadedAd($dropZone);\r\n    if (UIManager.isMobile()) { UIManager.adjustMobileLayout($dropZone); }\r\n    console.log('\u2705 [restoreAdFromUrl] annonce restaur\u00e9e | rank:', rankId, '| type:', _fileType, '| url:', adUrl);\r\n};\r\n\r\n\r\n\/**\r\n * Initialisation de l'application\r\n *\/\r\njQuery(document).ready(() => {\r\n    StateManager.init();\r\n    ScrollHelper.init();\r\n    DragDropManager.init();\r\n    FormatUIManager.init();\r\n\r\n    \/\/ v4.9ca : Centrage texte dans EspPubFormatContainer via styles inline\r\n    \/\/ (le CSS inject\u00e9 dans head est \u00e9cras\u00e9 par Elementor \u2014 styles inline = priorit\u00e9 absolue)\r\n    \/\/ v4.9cc : reset complet des variables CSS Elementor + height:100% cha\u00eene compl\u00e8te\r\n    function _centerEspPubFormats() {\r\n        jQuery('.EspPubFormatContainer').each(function() {\r\n            \/\/ Container <a> lui-m\u00eame\r\n            this.style.setProperty('display', 'flex', 'important');\r\n            this.style.setProperty('flex-direction', 'row', 'important');\r\n            this.style.setProperty('align-items', 'center', 'important');\r\n            this.style.setProperty('justify-content', 'center', 'important');\r\n            this.style.setProperty('padding', '0', 'important');\r\n            this.style.setProperty('padding-top', '0', 'important');\r\n            this.style.setProperty('padding-bottom', '0', 'important');\r\n            this.style.setProperty('padding-left', '0', 'important');\r\n            this.style.setProperty('padding-right', '0', 'important');\r\n            \/\/ Variables Elementor (padding-block-*, --padding-*)\r\n            this.style.setProperty('--padding-block-start', '0px', 'important');\r\n            this.style.setProperty('--padding-block-end', '0px', 'important');\r\n            this.style.setProperty('--padding-inline-start', '0px', 'important');\r\n            this.style.setProperty('--padding-inline-end', '0px', 'important');\r\n            this.style.setProperty('--padding-top', '0px', 'important');\r\n            this.style.setProperty('--padding-bottom', '0px', 'important');\r\n            this.style.setProperty('row-gap', '0', 'important');\r\n\r\n            \/\/ Widget .EspPubFormat\r\n            var _widget = this.querySelector('.EspPubFormat');\r\n            if (_widget) {\r\n                _widget.style.setProperty('display', 'flex', 'important');\r\n                _widget.style.setProperty('flex-direction', 'row', 'important');\r\n                _widget.style.setProperty('align-items', 'center', 'important');\r\n                _widget.style.setProperty('justify-content', 'center', 'important');\r\n                _widget.style.setProperty('width', '100%', 'important');\r\n                _widget.style.setProperty('height', '100%', 'important');\r\n                _widget.style.setProperty('margin', '0', 'important');\r\n                _widget.style.setProperty('padding', '0', 'important');\r\n                _widget.style.setProperty('padding-top', '0', 'important');\r\n                _widget.style.setProperty('padding-bottom', '0', 'important');\r\n            }\r\n\r\n            \/\/ .elementor-widget-container\r\n            var _wc = this.querySelector('.elementor-widget-container');\r\n            if (_wc) {\r\n                _wc.style.setProperty('display', 'flex', 'important');\r\n                _wc.style.setProperty('align-items', 'center', 'important');\r\n                _wc.style.setProperty('justify-content', 'center', 'important');\r\n                _wc.style.setProperty('width', '100%', 'important');\r\n                _wc.style.setProperty('height', '100%', 'important');\r\n                \/\/ v4.9dr : padding-top 1px pour centrage vertical\r\n                \/\/ v4.9ds : padding-top 3px sur Communiqu\u00e9\/Interview\/Parrainage (desktop)\r\n                \/\/          (lettres descendantes q\/j abaissent le baseline visuel)\r\n                \/\/          Mobile : 1px partout (centrage flex suffit \u00e0 cette \u00e9chelle)\r\n                var _isMob = UIManager.isMobile();\r\n                var _hasDescender = this.classList.contains('FormatIdCommunique')\r\n                                 || this.classList.contains('FormatIdInterview')\r\n                                 || this.classList.contains('FormatIdParrainage');\r\n                var _padTop = _isMob ? '1px' : (_hasDescender ? '3px' : '1px');\r\n                _wc.style.setProperty('padding-top', _padTop, 'important');\r\n                _wc.style.setProperty('padding-left', '0', 'important');\r\n                _wc.style.setProperty('padding-right', '0', 'important');\r\n                _wc.style.setProperty('padding-bottom', '0px', 'important');\r\n                _wc.style.setProperty('margin', '0', 'important');\r\n                _wc.style.setProperty('margin-top', '0', 'important');\r\n                _wc.style.setProperty('margin-bottom', '0', 'important');\r\n                _wc.style.setProperty('text-align', 'center', 'important');\r\n                _wc.style.setProperty('line-height', '1', 'important');\r\n            }\r\n\r\n            \/\/ Paragraphes \u00e9ventuels g\u00e9n\u00e9r\u00e9s par Elementor text-editor\r\n            jQuery(this).find('p').each(function() {\r\n                this.style.setProperty('margin', '0', 'important');\r\n                this.style.setProperty('padding', '0', 'important');\r\n                this.style.setProperty('line-height', 'inherit', 'important');\r\n            });\r\n        });\r\n    }\r\n    \/\/ Lancer imm\u00e9diatement + apr\u00e8s un d\u00e9lai (Elementor peut appliquer ses styles apr\u00e8s le ready)\r\n    _centerEspPubFormats();\r\n    setTimeout(_centerEspPubFormats, 500);\r\n    setTimeout(_centerEspPubFormats, 1500);\r\n\r\n    \/\/ v4.9cb : FormatIdPopUp ne doit s'afficher QUE sur le site r\u00e9gie (via-regie-iframe).\r\n    \/\/ Sur les sites pays, masquer tous les items .FormatIdPopUp.\r\n    function _hidePopupFormatOnPays() {\r\n        var _isRegie = document.documentElement.classList.contains('via-regie-iframe');\r\n        if (_isRegie) return; \/\/ site r\u00e9gie \u2192 on laisse le Pop-up visible\r\n        jQuery('.FormatIdPopUp').each(function() {\r\n            this.style.setProperty('display', 'none', 'important');\r\n        });\r\n    }\r\n    _hidePopupFormatOnPays();\r\n    setTimeout(_hidePopupFormatOnPays, 500);\r\n    setTimeout(_hidePopupFormatOnPays, 1500);\r\n    \r\n    if (UIManager.isMobile()) {\r\n        UIManager.initMobileUI();\r\n    } else {\r\n        UIManager.initDesktopUI();\r\n    }\r\n    \r\n    jQuery(document).on('click', '#CroixResetAnnonce', (e) => AdResetHandler.handle(e));\r\n\r\n    \/\/ \ud83d\udd27 Fonction globale r\u00e9utilisable : force height:auto + padding\/margin 0 sur le\r\n    \/\/    .elementor-widget-text-editor qui CONTIENT l'OMC. Elementor impose une height\r\n    \/\/    explicite (ex: 297px) \u00e0 certains breakpoints \u00e9troits qui d\u00e9cale le wrapper vers le bas.\r\n    \/\/    Appel\u00e9e au d\u00e9p\u00f4t (depuis _buildAdOverlay) et \u00e0 chaque resize.\r\n    window._viaOverrideTextEditor = function(omcEl) {\r\n        if (!omcEl) return;\r\n        \/\/ Chercher l'anc\u00eatre .elementor-widget-text-editor (pas un enfant)\r\n        var editor = omcEl.closest('.elementor-widget-text-editor');\r\n        if (!editor) return;\r\n        editor.style.setProperty('height', 'auto', 'important');\r\n        editor.style.setProperty('min-height', '0', 'important');\r\n        editor.style.setProperty('padding', '0', 'important');\r\n        editor.style.setProperty('margin', '0', 'important');\r\n        var editorCont = editor.querySelector(':scope > .elementor-widget-container');\r\n        if (editorCont) {\r\n            editorCont.style.setProperty('height', 'auto', 'important');\r\n            editorCont.style.setProperty('min-height', '0', 'important');\r\n            editorCont.style.setProperty('padding', '0', 'important');\r\n            editorCont.style.setProperty('margin', '0', 'important');\r\n        }\r\n    };\r\n\r\n    \/\/ \ud83d\udd0d DIAG : log sur resize pour voir l'\u00e9tat du wrapper quand la fen\u00eatre change\r\n    \/\/ Handler de resize : override text-editor + enforcement + algo + scale\r\n    \/\/ v4.9by : EXCLURE les wrappers \u00e0 l'int\u00e9rieur de #Ele0A (popup fixed) du traitement\r\n    \/\/         Le scroll iOS fire resize \u2192 ce handler retirait position\/left\/transform sur le wrapper\r\n    \/\/         via-ad-wrapper DANS Ele0A \u2192 wrapper r\u00e9tr\u00e9cissait visuellement\r\n    var _diagResizeTimer = null;\r\n    window.addEventListener('resize', function() {\r\n        clearTimeout(_diagResizeTimer);\r\n        _diagResizeTimer = setTimeout(function() {\r\n            var $_wrappers = jQuery('.via-ad-wrapper').filter(function() {\r\n                \/\/ v4.9by : ne pas toucher aux wrappers dans Ele0A (popup fixed, g\u00e9r\u00e9 ailleurs)\r\n                return jQuery(this).closest('#Ele0A').length === 0;\r\n            });\r\n            if (!$_wrappers.length) return;\r\n            var _isRegieIframe = document.documentElement.classList.contains('via-regie-iframe');\r\n            console.log('[RESIZE] handler fire \u2014 inner=' + window.innerWidth + ' outer=' + window.outerWidth + ' isMobile=' + UIManager.isMobile() + ' wrappers=' + $_wrappers.length + ' isRegie=' + _isRegieIframe);\r\n\r\n            \/\/ \u00c9TAPE 1 : enforcement + reset transform\/mb sur tous les wrappers\r\n            $_wrappers.each(function() {\r\n                var $_drop = jQuery(this).closest('.droppable');\r\n                var $_omc = $_drop.find('.OrdiMobileConteneurClass').first();\r\n                var $_ufc = jQuery(this).find('.HTMLUploadfileConteneur').first();\r\n                var _dropEl = $_drop[0], _omcEl = $_omc[0], _ufcEl = $_ufc[0];\r\n\r\n                var _wAvantMt = this.style.marginTop || '(none)';\r\n                var _dAvantMt = _dropEl ? (_dropEl.style.marginTop || '(none)') : '?';\r\n                var _rAvant = _dropEl ? Math.round(_dropEl.getBoundingClientRect().top) : '?';\r\n\r\n                if (_omcEl) { window._viaOverrideTextEditor(_omcEl); }\r\n\r\n                \/\/ \u26a0\ufe0f CLEANUP DVM\/MOBILE : d\u00e8s que inner<1000, nettoyer les paddings\/min-height\r\n                \/\/    pos\u00e9s en plein \u00e9cran sur article\/secteur qui cr\u00e9ent de grands espaces blancs.\r\n                \/\/    Ce bloc s'ex\u00e9cute AVANT le gate UIManager.isMobile() pour couvrir \u00e0 la fois\r\n                \/\/    DVM (outer>=1000, inner<1000) et mobile vrai (outer<1000).\r\n                if (!_isRegieIframe) {\r\n                    if (window.innerWidth < 1000) {\r\n                        \/\/ Reset d'abord les marges n\u00e9gatives pour pouvoir mesurer la VRAIE hauteur\r\n                        this.style.setProperty('margin-top', '0px', 'important');\r\n                        this.style.setProperty('margin-bottom', '0px', 'important');\r\n                        \/\/ Mesure hauteur r\u00e9elle (apr\u00e8s reset)\r\n                        var _dvmWrapperH = Math.round(this.getBoundingClientRect().height);\r\n                        var _dvmWrapperW = Math.round(this.getBoundingClientRect().width);\r\n                        var _dvmDropW = _dropEl ? Math.round(_dropEl.getBoundingClientRect().width) : 0;\r\n                        \/\/ Force min fallback 200px si mesure inf\u00e9rieure\r\n                        if (_dvmWrapperH < 150) { _dvmWrapperH = 200; }\r\n                        \/\/ \u2705 Reset margin-top\/bottom sur OrdiMobileConteneurClass (pose +65px en mobile)\r\n                        if (_omcEl) {\r\n                            _omcEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _omcEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                        }\r\n                        \/\/ \u2705 Reset margin sur HTMLUploadfileConteneur (pose -85px en mobile)\r\n                        if (_ufcEl) {\r\n                            _ufcEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _ufcEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                        }\r\n                        if (_dropEl) {\r\n                            _dropEl.style.setProperty('padding-top', '0px', 'important');\r\n                            _dropEl.style.setProperty('padding-bottom', '0px', 'important');\r\n                            \/\/ PAS de min-height \u2014 laisser la hauteur naturelle du wrapper d\u00e9finir\r\n                            _dropEl.style.removeProperty('min-height');\r\n                            _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                            _dropEl.style.setProperty('margin-bottom', '40px', 'important');\r\n                            _dropEl.style.setProperty('height', 'auto', 'important');\r\n                            _dropEl.style.removeProperty('box-sizing');\r\n                            \/\/ \u2705 Centrer via flex sur le droppable\r\n                            _dropEl.style.setProperty('display', 'flex', 'important');\r\n                            _dropEl.style.setProperty('flex-direction', 'column', 'important');\r\n                            _dropEl.style.setProperty('justify-content', 'flex-start', 'important');\r\n                            _dropEl.style.setProperty('align-items', 'center', 'important');\r\n                        }\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM DIMS wrapperW=' + _dvmWrapperW + ' dropW=' + _dvmDropW);\r\n                        \/\/ \ud83d\udd0d DIAG chain anc\u00eatres du droppable avec leurs TOP et margins\r\n                        var _chainInfo = [];\r\n                        var _curAnc = _dropEl;\r\n                        var _chainCount = 0;\r\n                        while (_curAnc) {\r\n                            if (_chainCount >= 10) break;\r\n                            var _rCur = _curAnc.getBoundingClientRect();\r\n                            var _csCur = getComputedStyle(_curAnc);\r\n                            var _tagC = _curAnc.tagName + (_curAnc.id ? '#' + _curAnc.id : '') + '.' + ((_curAnc.className || '').toString().split(' ').slice(0, 2).join('.') || '');\r\n                            _chainInfo.push(_tagC + ' top=' + Math.round(_rCur.top) + ' mt=' + _csCur.marginTop + ' pt=' + _csCur.paddingTop);\r\n                            _curAnc = _curAnc.parentElement;\r\n                            _chainCount++;\r\n                        }\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM CHAIN from droppable up: ' + _chainInfo.join(' | '));\r\n                        var _ancD = _dropEl;\r\n                        var _ancDCount = 0;\r\n                        var _ancDDebug = [];\r\n                        while (_ancD) {\r\n                            if (_ancDCount >= 8) break;\r\n                            _ancD = _ancD.parentElement;\r\n                            _ancDCount++;\r\n                            if (!_ancD) break;\r\n                            var _inPt = _ancD.style.paddingTop;\r\n                            var _inPb = _ancD.style.paddingBottom;\r\n                            var _inMh = _ancD.style.minHeight;\r\n                            if (_inPt || _inPb || _inMh) {\r\n                                _ancDDebug.push(_ancD.tagName + '.' + ((_ancD.className || '').toString().split(' ').slice(0, 2).join('.') || '') + ' [pt=' + _inPt + ' pb=' + _inPb + ' mh=' + _inMh + ']');\r\n                            }\r\n                            _ancD.style.setProperty('padding-top', '0px', 'important');\r\n                            _ancD.style.setProperty('padding-bottom', '0px', 'important');\r\n                            _ancD.style.setProperty('min-height', '0px', 'important');\r\n                            if (_ancD.nextElementSibling) { break; }\r\n                        }\r\n                        if (_ancDDebug.length) {\r\n                            console.log('[DIAG resize] \u2699\ufe0f cleanup DVM\/mobile (inner<1000, outer=' + window.outerWidth + ') : wrapper.mt\/mb=25px droppable.min-h=' + (_dvmWrapperH + 60) + ' mb=40 | anc\u00eatres reset \u2192 ' + _ancDDebug.join(' | '));\r\n                        } else {\r\n                            console.log('[DIAG resize] \u2699\ufe0f cleanup DVM\/mobile (inner<1000, outer=' + window.outerWidth + ') : wrapper.mt\/mb=25px droppable.min-h=' + (_dvmWrapperH + 60) + ' mb=40 | wrapperH mesur\u00e9=' + _dvmWrapperH);\r\n                        }\r\n                        \/\/ \u2705 DVM\/mobile : margin-top n\u00e9gatif compense le gros vide entre le contenu\r\n                        \/\/    au-dessus et le droppable (structure Elementor pose ~260px de vide).\r\n                        this.style.setProperty('margin-top', '-120px', 'important');\r\n                        this.style.setProperty('margin-bottom', '25px', 'important');\r\n                        this.style.removeProperty('position');\r\n                        this.style.removeProperty('left');\r\n                        this.style.removeProperty('transform');\r\n                        this.style.setProperty('width', '100%', 'important');\r\n                        this.style.setProperty('max-width', '100%', 'important');\r\n                        this.style.removeProperty('margin-left');\r\n                        this.style.removeProperty('margin-right');\r\n                        this.style.setProperty('display', 'flex', 'important');\r\n                        \/\/ \ud83d\udd0d DIAG POST\r\n                        var _postCS = getComputedStyle(this);\r\n                        var _postRect = this.getBoundingClientRect();\r\n                        var _dropRect2 = _dropEl ? _dropEl.getBoundingClientRect() : null;\r\n                        console.log('[DIAG resize] \u2699\ufe0f DVM POST-APPLY | computed w=' + _postCS.width + ' | wrapper RECT left=' + Math.round(_postRect.left) + ' w=' + Math.round(_postRect.width) + ' top=' + Math.round(_postRect.top) + ' | drop RECT left=' + (_dropRect2 ? Math.round(_dropRect2.left) : '?') + ' w=' + (_dropRect2 ? Math.round(_dropRect2.width) : '?') + ' top=' + (_dropRect2 ? Math.round(_dropRect2.top) : '?'));\r\n                    }\r\n                }\r\n\r\n                if (!UIManager.isMobile()) {\r\n                    if (!_isRegieIframe) {\r\n                        \/\/ \ud83d\udd27 ENFORCEMENT JS (version qui marchait) : neutralise marges n\u00e9gatives\r\n                        \/\/    h\u00e9rit\u00e9es du mode mobile + restaure UFC + appelle l'algo inter-espaces.\r\n\r\n                        \/\/ \ud83d\udd0d DIAG : snapshot \u00e9tat AVANT\r\n                        var _cs = getComputedStyle(this);\r\n                        var _rect = this.getBoundingClientRect();\r\n                        var _dropCS = _dropEl ? getComputedStyle(_dropEl) : null;\r\n                        var _dropR = _dropEl ? _dropEl.getBoundingClientRect() : null;\r\n                        var _ufcCS = _ufcEl ? getComputedStyle(_ufcEl) : null;\r\n                        var _ufcR = _ufcEl ? _ufcEl.getBoundingClientRect() : null;\r\n                        console.log('[DIAG resize] inner=' + window.innerWidth + ' outer=' + window.outerWidth\r\n                            + ' | htmlClass=\"' + document.documentElement.className + '\"'\r\n                            + ' | bodyClass=\"' + document.body.className + '\"');\r\n                        console.log('[DIAG resize] WRAPPER inline mt=' + this.style.marginTop + ' mb=' + this.style.marginBottom\r\n                            + ' | computed mt=' + _cs.marginTop + ' mb=' + _cs.marginBottom\r\n                            + ' | rect top=' + Math.round(_rect.top) + ' h=' + Math.round(_rect.height) + ' w=' + Math.round(_rect.width));\r\n                        if (_dropEl) {\r\n                            console.log('[DIAG resize] DROPPABLE#' + _dropEl.id\r\n                                + ' inline mt=' + _dropEl.style.marginTop + ' mb=' + _dropEl.style.marginBottom\r\n                                + ' | computed mt=' + _dropCS.marginTop + ' mb=' + _dropCS.marginBottom\r\n                                + ' | rect top=' + Math.round(_dropR.top) + ' h=' + Math.round(_dropR.height));\r\n                        }\r\n                        if (_ufcEl) {\r\n                            console.log('[DIAG resize] UFC inline h=' + _ufcEl.style.height + ' maxH=' + _ufcEl.style.maxHeight\r\n                                + ' | computed h=' + _ufcCS.height + ' maxH=' + _ufcCS.maxHeight\r\n                                + ' | rect top=' + Math.round(_ufcR.top) + ' h=' + Math.round(_ufcR.height));\r\n                        }\r\n\r\n                        \/\/ 1. Neutraliser les marges n\u00e9gatives h\u00e9rit\u00e9es du mode mobile (wrapper + droppable)\r\n                        var _mt = parseFloat(this.style.marginTop) || 0;\r\n                        if (_mt < 0) {\r\n                            this.style.setProperty('margin-top', '0px', 'important');\r\n                            console.log('[DIAG resize] \u2699\ufe0f wrapper : margin-top forc\u00e9 \u00e0 0 (\u00e9tait ' + _mt + ')');\r\n                        }\r\n                        if (_dropEl) {\r\n                            var _dmt = parseFloat(_dropEl.style.marginTop) || 0;\r\n                            if (_dmt < 0) {\r\n                                _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                                console.log('[DIAG resize] \u2699\ufe0f droppable : margin-top forc\u00e9 \u00e0 0 (\u00e9tait ' + _dmt + ')');\r\n                            }\r\n                        }\r\n\r\n                        \/\/ 2. Restaurer max-height UFC + force height auto\r\n                        \/\/ v4.9ds : Ele0A=260, Ele1A+=280 ; ne pas toucher au dropZone (g\u00e9r\u00e9 par _applyDzMinH)\r\n                        if (_ufcEl) {\r\n                            var _isE0AResize = $_drop.attr('id') === 'Ele0A';\r\n                            _ufcEl.style.removeProperty('min-height');\r\n                            _ufcEl.style.setProperty('max-height', _isE0AResize ? '260px' : '280px', 'important');\r\n                            _ufcEl.style.setProperty('height', 'auto', 'important');\r\n                        }\r\n                        \/\/ v4.9ds : ne PAS toucher au dropZone height\/min-height ici\r\n                        \/\/          (sinon \u00e9crase ce que _applyDzMinH a pos\u00e9 sur Ele1A+)\r\n                        \/\/          Au lieu de \u00e7a, on rejoue _applyDzMinH si elle est expos\u00e9e sur le droppable\r\n                        if (_dropEl ? _dropEl._applyDzMinH : false) {\r\n                            try { _dropEl._applyDzMinH('resize'); } catch(_e) { console.warn('[resize] _applyDzMinH a throw', _e); }\r\n                        }\r\n                        var _imgInner = _ufcEl ? _ufcEl.querySelector('img, video') : null;\r\n                        if (_imgInner) {\r\n                            \/\/ v4.9ds : aligner max-height image sur la hauteur r\u00e9elle du dropZone\r\n                            \/\/   pour \u00e9viter le crop par overflow:hidden du wrapper.\r\n                            \/\/   IMPORTANT : utiliser offsetHeight (qui n'est PAS affect\u00e9 par\r\n                            \/\/   le zoom\/transform\/scale d'un parent) plut\u00f4t que\r\n                            \/\/   getBoundingClientRect().height (qui retourne les pixels \u00e9cran\r\n                            \/\/   r\u00e9els apr\u00e8s transformations). Le rect.h peut \u00eatre r\u00e9duit par\r\n                            \/\/   un zoom:55% appliqu\u00e9 \u00e0 un anc\u00eatre via _viaRunInterEspaces, ce\r\n                            \/\/   qui produirait une max-height absurde.\r\n                            var _dzInnerH = _ufcEl ? _ufcEl.querySelector('#drop_file_zone_achat') : null;\r\n                            var _dzH = _dzInnerH ? _dzInnerH.offsetHeight : 0;\r\n                            var _imgMaxHRsz = _dzH > 50 ? Math.max(0, Math.floor(_dzH - 5)) : 250;\r\n                            _imgInner.style.setProperty('max-height', _imgMaxHRsz + 'px', 'important');\r\n                        }\r\n\r\n                        \/\/ 3. Gater l'algo + scale : n\u00e9cessite inner >= 1000 (viewport CSS desktop).\r\n                        \/\/    En \"desktop version mobile\" (outer>=1000 mais inner<1000), le layout CSS\r\n                        \/\/    est en mode mobile \u2014 l'algo se tromperait.\r\n                        if (window.innerWidth < 1000) {\r\n                            \/\/ \u26a0\ufe0f DVM : nettoyer les paddings\/min-height pos\u00e9s pr\u00e9c\u00e9demment\r\n                            \/\/    (article\/secteur) qui cr\u00e9aient de grands espaces blancs.\r\n                            \/\/    removeProperty ne bat pas un !important inline \u2192 forcer \u00e0 0 !important.\r\n                            if (_dropEl) {\r\n                                _dropEl.style.setProperty('padding-top', '0px', 'important');\r\n                                _dropEl.style.setProperty('padding-bottom', '0px', 'important');\r\n                                _dropEl.style.setProperty('min-height', '0px', 'important');\r\n                                _dropEl.style.removeProperty('box-sizing');\r\n                            }\r\n                            \/\/ Nettoyer TOUS les anc\u00eatres qui ont un padding ou min-height inline\r\n                            \/\/ (pas seulement ceux avec classList e-con-inner\/elementor-element)\r\n                            var _ancC = _dropEl;\r\n                            var _ancCCount = 0;\r\n                            var _ancDebug = [];\r\n                            while (_ancC) {\r\n                                if (_ancCCount >= 8) break;\r\n                                _ancC = _ancC.parentElement;\r\n                                _ancCCount++;\r\n                                if (!_ancC) break;\r\n                                var _ancTag = _ancC.tagName + '.' + ((_ancC.className || '').toString().split(' ').slice(0, 2).join('.') || '');\r\n                                var _hasInlineStyle = false;\r\n                                var _inlinePt = _ancC.style.paddingTop;\r\n                                var _inlinePb = _ancC.style.paddingBottom;\r\n                                var _inlineMh = _ancC.style.minHeight;\r\n                                if (_inlinePt || _inlinePb || _inlineMh) {\r\n                                    _hasInlineStyle = true;\r\n                                    _ancDebug.push(_ancTag + ' [pt=' + _inlinePt + ' pb=' + _inlinePb + ' mh=' + _inlineMh + ']');\r\n                                }\r\n                                \/\/ Reset inline style dans tous les cas\r\n                                _ancC.style.setProperty('padding-top', '0px', 'important');\r\n                                _ancC.style.setProperty('padding-bottom', '0px', 'important');\r\n                                _ancC.style.setProperty('min-height', '0px', 'important');\r\n                                if (_ancC.nextElementSibling) { break; }\r\n                            }\r\n                            \/\/ \u2705 DVM : ~25px d'espace blanc au-dessus et en-dessous de l'annonce.\r\n                            \/\/    Appliqu\u00e9 sur le wrapper (margin-top\/bottom) qui \u00e9tait \u00e0 -150 au d\u00e9p\u00f4t.\r\n                            this.style.setProperty('margin-top', '25px', 'important');\r\n                            this.style.setProperty('margin-bottom', '25px', 'important');\r\n                            console.log('[DIAG resize] \u2699\ufe0f DVM (inner<1000) : wrapper.mt\/mb=25px | anc\u00eatres avec style inline: ' + (_ancDebug.length ? _ancDebug.join(' | ') : 'aucun'));\r\n                        }\r\n\r\n                        if (window.innerWidth >= 1000) {\r\n                            \/\/ 3a. Reset transform AVANT la mesure (hauteur naturelle)\r\n                            this.style.removeProperty('transform');\r\n                            this.style.removeProperty('margin-bottom');\r\n                            if (_dropEl) { _dropEl.style.removeProperty('margin-bottom'); }\r\n\r\n                            \/\/ 3b. Appeler l'algo inter-espaces avec droppable temporairement d\u00e9marqu\u00e9\r\n                            if (_dropEl) { if (typeof window._viaRunInterEspaces === 'function') {\r\n                                var _wasLoaded = _dropEl.getAttribute('data-via-ad-loaded') === 'true';\r\n                                if (_wasLoaded) { _dropEl.removeAttribute('data-via-ad-loaded'); }\r\n                                try {\r\n                                    window._viaInterEspacesRun = false;\r\n                                    window._viaRunInterEspaces();\r\n                                    console.log('[DIAG resize] \u2699\ufe0f _viaRunInterEspaces appel\u00e9 (droppable d\u00e9marqu\u00e9)');\r\n                                } catch (e) {\r\n                                    console.warn('[DIAG resize] \u26a0\ufe0f _viaRunInterEspaces a throw :', e);\r\n                                }\r\n                                if (_wasLoaded) { _dropEl.setAttribute('data-via-ad-loaded', 'true'); }\r\n                            } }\r\n\r\n                            \/\/ 3c. \u2705 Article (body.single) ou page secteur (body.page) plein \u00e9cran :\r\n                            \/\/    80px margin-bottom sur le DROPPABLE pour \u00e9viter chevauchement\r\n                            \/\/    avec le contenu en dessous. Appliqu\u00e9 APR\u00c8S l'algo pour qu'il persiste.\r\n                            var _isArticleOrSecteur = document.body.classList.contains('single') || document.body.classList.contains('page');\r\n                            if (_isArticleOrSecteur) {\r\n                                if (_dropEl) {\r\n                                    \/\/ Force droppable \u00e0 englober wrapper (wrapper=171 > droppable=110)\r\n                                    var _wrH = Math.round(this.getBoundingClientRect().height);\r\n                                    \/\/ \u2705 Padding-top ET padding-bottom : padding fait partie de la hauteur\r\n                                    \/\/    de la bo\u00eete et est respect\u00e9 par les parents flex.\r\n                                    \/\/    50px d'espace au-dessus + wrapperH + 0px en-dessous.\r\n                                    var _pbTotal = _wrH + 0;\r\n                                    _dropEl.style.setProperty('padding-top', '50px', 'important');\r\n                                    _dropEl.style.setProperty('padding-bottom', _pbTotal + 'px', 'important');\r\n                                    _dropEl.style.setProperty('margin-top', '0px', 'important');\r\n                                    _dropEl.style.setProperty('margin-bottom', '0px', 'important');\r\n                                    _dropEl.style.setProperty('min-height', (_wrH + 50) + 'px', 'important');\r\n                                    _dropEl.style.setProperty('height', 'auto', 'important');\r\n                                    _dropEl.style.setProperty('overflow', 'visible', 'important');\r\n                                    _dropEl.style.setProperty('box-sizing', 'content-box', 'important');\r\n\r\n                                    \/\/ \u2705 Force height:auto sur tous les conteneurs Elementor entre\r\n                                    \/\/    le droppable et le wrapper, pour que le wrapper ne d\u00e9borde\r\n                                    \/\/    pas de son conteneur vers le contenu du dessous.\r\n                                    var _anc = this; \/\/ wrapper\r\n                                    var _ancCount = 0;\r\n                                    while (_anc) {\r\n                                        if (_ancCount >= 5) break;\r\n                                        _anc = _anc.parentElement;\r\n                                        _ancCount++;\r\n                                        if (!_anc) break;\r\n                                        if (_anc === _dropEl) break;\r\n                                        _anc.style.setProperty('height', 'auto', 'important');\r\n                                        _anc.style.setProperty('min-height', _wrH + 'px', 'important');\r\n                                        _anc.style.setProperty('overflow', 'visible', 'important');\r\n                                    }\r\n\r\n                                    \/\/ Remonter au-del\u00e0 du droppable : padding sur section Elementor\r\n                                    var _anc2 = _dropEl;\r\n                                    var _ancChain = _dropEl.tagName + '#' + _dropEl.id;\r\n                                    var _ancCount2 = 0;\r\n                                    while (_anc2) {\r\n                                        if (_ancCount2 >= 6) break;\r\n                                        _anc2 = _anc2.parentElement;\r\n                                        _ancCount2++;\r\n                                        if (!_anc2) break;\r\n                                        _ancChain += ' > ' + _anc2.tagName + '.' + ((_anc2.className || '').split(' ')[0] || '');\r\n                                        if (_anc2.classList) {\r\n                                            if (_anc2.classList.contains('e-con-inner') || _anc2.classList.contains('elementor-element')) {\r\n                                                _anc2.style.setProperty('padding-top', '50px', 'important');\r\n                                                _anc2.style.setProperty('padding-bottom', '0px', 'important');\r\n                                                _anc2.style.setProperty('min-height', (_wrH + 50) + 'px', 'important');\r\n                                            }\r\n                                        }\r\n                                        if (_anc2.nextElementSibling) { break; }\r\n                                    }\r\n                                    console.log('[DIAG resize] \u2699\ufe0f article\/secteur \u2192 droppable.padding-top=50 padding-bottom=' + _pbTotal + ' (wrapperH=' + _wrH + ') | chain=' + _ancChain);\r\n                                }\r\n                            }\r\n\r\n                            \/\/ 3d. Scale 1.3 conditionnel : uniquement si d\u00e9p\u00f4t mobile\r\n                            var _depositedMode = this.getAttribute('data-deposited-mode') || '';\r\n                            if (_depositedMode === 'mobile') {\r\n                                var _naturalRect = this.getBoundingClientRect();\r\n                                var _naturalH = _naturalRect.height;\r\n                                var _scaleFactor = 1.3;\r\n                                this.style.setProperty('transform', 'scale(' + _scaleFactor + ')', 'important');\r\n                                this.style.setProperty('transform-origin', 'top center', 'important');\r\n                                var _extraH = Math.round(_naturalH * (_scaleFactor - 1));\r\n                                this.style.setProperty('margin-bottom', _extraH + 'px', 'important');\r\n                                console.log('[DIAG resize] \u2699\ufe0f scale ' + _scaleFactor + ' (d\u00e9p\u00f4t mobile) | naturalH=' + Math.round(_naturalH) + ' | wrapper.mb +' + _extraH + 'px');\r\n                            } else {\r\n                                console.log('[DIAG resize] \u2699\ufe0f pas de scale (depositedMode=\"' + _depositedMode + '\")');\r\n                            }\r\n                        } else {\r\n                            console.log('[DIAG resize] \u2699\ufe0f layout \u00e9troit (inner=' + window.innerWidth + '<1000) \u2192 skip algo+scale');\r\n                        }\r\n                        return;\r\n                    }\r\n                    \/\/ R\u00c9GIE IFRAME\r\n                    if (_ufcEl) {\r\n                        _ufcEl.style.setProperty('max-height', '260px', 'important');\r\n                        _ufcEl.style.setProperty('height', 'auto', 'important');\r\n                    }\r\n                    var _dzInner = _ufcEl ? _ufcEl.querySelector('#drop_file_zone_achat') : null;\r\n                    if (_dzInner) {\r\n                        _dzInner.style.setProperty('max-height', '250px', 'important');\r\n                        _dzInner.style.setProperty('height', 'auto', 'important');\r\n                    }\r\n                    var _imgInner = _ufcEl ? _ufcEl.querySelector('img, video') : null;\r\n                    if (_imgInner) {\r\n                        \/\/ v4.9ds : aligner max-height image sur offsetHeight du dropZone\r\n                        \/\/   (cf. commentaire branche desktop ci-dessus \u2014 rect.h fauss\u00e9 par zoom parent)\r\n                        var _dzH2 = _dzInner ? _dzInner.offsetHeight : 0;\r\n                        var _imgMaxHRsz2 = _dzH2 > 50 ? Math.max(0, Math.floor(_dzH2 - 5)) : 250;\r\n                        _imgInner.style.setProperty('max-height', _imgMaxHRsz2 + 'px', 'important');\r\n                    }\r\n                    this.style.removeProperty('transform');\r\n                }\r\n\r\n                var _wApr\u00e8sMt = this.style.marginTop || '(none)';\r\n                var _dApr\u00e8sMt = _dropEl ? (_dropEl.style.marginTop || '(none)') : '?';\r\n                var _rApr\u00e8s = _dropEl ? Math.round(_dropEl.getBoundingClientRect().top) : '?';\r\n                console.log('[RESIZE wrapper] #' + (_dropEl ? _dropEl.id : '?') + ' loaded=' + (_dropEl ? _dropEl.getAttribute('data-via-ad-loaded') : '?') + ' | AVANT wrapper.mt=' + _wAvantMt + ' droppable.mt=' + _dAvantMt + ' rect.top=' + _rAvant + ' | APR\u00c8S wrapper.mt=' + _wApr\u00e8sMt + ' droppable.mt=' + _dApr\u00e8sMt + ' rect.top=' + _rApr\u00e8s);\r\n            });\r\n\r\n            \/\/ \u00c9TAPE 2 : appel de l'algo une seule fois avec vue coh\u00e9rente\r\n            \/\/ \u26a0\ufe0f Sur sites pays, on NE RUN PAS l'algo \u00e0 l'agrandissement : \u00e7a cascade sur tous\r\n            \/\/    les droppables (y compris vides) et fait remonter les ads de plusieurs centaines\r\n            \/\/    de pixels. Layout naturel suffit.\r\n            if (!UIManager.isMobile()) { if (_isRegieIframe) {\r\n                if (typeof window._viaRunInterEspaces === 'function') {\r\n                    try {\r\n                        window._viaInterEspacesRun = false;\r\n                        window._viaRunInterEspaces();\r\n                    } catch (e) { \/* silent *\/ }\r\n                }\r\n            } else {\r\n                \/\/ Sites pays : rien de sp\u00e9cial, on laisse la layout naturelle\r\n            } }\r\n\r\n            \/\/ \u00c9TAPE 3 : scale 1.3 sur les wrappers en d\u00e9p\u00f4t mobile\r\n            if (!UIManager.isMobile()) {\r\n                $_wrappers.each(function() {\r\n                    var _depositedMode = this.getAttribute('data-deposited-mode') || '';\r\n                    if (_depositedMode === 'mobile') {\r\n                        var _naturalRect = this.getBoundingClientRect();\r\n                        var _naturalH = _naturalRect.height;\r\n                        var _scaleFactor = 1.3;\r\n                        this.style.setProperty('transform', 'scale(' + _scaleFactor + ')', 'important');\r\n                        this.style.setProperty('transform-origin', 'top center', 'important');\r\n                        var _extraH = Math.round(_naturalH * (_scaleFactor - 1));\r\n                        this.style.setProperty('margin-bottom', _extraH + 'px', 'important');\r\n                    }\r\n                });\r\n            }\r\n        }, 300);\r\n    });\r\n\r\n    \/\/ \ud83d\udd0d DIAG : fonction globale \u00e0 appeler en console \u2192 diagViaAd()\r\n    window.diagViaAd = function() {\r\n        var out = {\r\n            env: {\r\n                outerWidth: window.outerWidth,\r\n                innerWidth: window.innerWidth,\r\n                isMobileJS: UIManager.isMobile(),\r\n                isDesktopUA: UIManager.isDesktop(),\r\n                isTopWindow: window === window.top,\r\n                htmlClass: document.documentElement.className,\r\n                hasRegieClass: document.documentElement.classList.contains('via-regie-iframe'),\r\n                bodyClass: document.body.className\r\n            },\r\n            wrappers: []\r\n        };\r\n        jQuery('.via-ad-wrapper').each(function() {\r\n            var _cs = getComputedStyle(this);\r\n            var _rect = this.getBoundingClientRect();\r\n            var $_drop = jQuery(this).closest('.droppable');\r\n            var $_omc = $_drop.find('.OrdiMobileConteneurClass').first();\r\n            out.wrappers.push({\r\n                droppableId: $_drop.attr('id'),\r\n                droppableDataLoaded: $_drop.attr('data-via-ad-loaded'),\r\n                wrapperInlineStyle: this.getAttribute('style'),\r\n                wrapperComputedMarginTop: _cs.marginTop,\r\n                wrapperComputedMarginBottom: _cs.marginBottom,\r\n                wrapperRect: { top: Math.round(_rect.top), height: Math.round(_rect.height), width: Math.round(_rect.width) },\r\n                droppableInlineMarginTop: $_drop[0].style.marginTop,\r\n                omcInlineMarginTop: $_omc[0] ? $_omc[0].style.marginTop : '(no omc)',\r\n                omcInlineMarginBottom: $_omc[0] ? $_omc[0].style.marginBottom : '(no omc)'\r\n            });\r\n        });\r\n        console.log('[diagViaAd]', out);\r\n        return out;\r\n    };\r\n    console.log('[DIAG] window.diagViaAd() disponible \u2014 appelle-la en console apr\u00e8s agrandissement pour voir l\\'\u00e9tat');\r\n    \r\n    \/\/ \u2705 Clic sur le texte du label \u2192 toggle manuel de la checkbox du m\u00eame conteneur\r\n    jQuery(document).on('click', '.reserver-dynamic-label', function(e) {\r\n        e.preventDefault();\r\n        e.stopPropagation();\r\n        const $cb = $(this).closest('.reserver-dynamic-option, .reserver-dynamic-container').find('.reserver-dynamic-checkbox');\r\n        if ($cb.length) {\r\n            $cb.prop('checked', !$cb.prop('checked')).trigger('change');\r\n        }\r\n    });\r\n\r\n    \/\/ \u2705 CHECKBOX \"R\u00e9server cet espace publicitaire\" \u2014 VALIDATION ET ENVOI DES DONN\u00c9ES\r\n    \/\/ \u2705 v2.4.13 : iOS touchend\r\n    \/\/ \u2705 v2.4.13 : Mobile \u2014 \u00e9couter touchend sur input ET click\/touchend sur label\r\n    jQuery(document).on('touchend', 'input[name=\"form_fields[ReserverEspacePublicitaire]\"]', function() {\r\n        if (this._viaResTouch) { return; }\r\n        this._viaResTouch = true;\r\n        var _self = this;\r\n        setTimeout(function() { _self._viaResTouch = false; }, 500);\r\n        setTimeout(function() { jQuery(_self).trigger('change'); }, 100);\r\n    });\r\n    jQuery(document).on('touchend click', '.elementor-field-group-ReserverEspacePublicitaire label, .reserver-dynamic-label, .reserver-dynamic-option label', function(e) {\r\n        \/\/ Trouver l'input associ\u00e9\r\n        var $input = jQuery(this).closest('.elementor-field-option, .reserver-dynamic-option').find('input[name=\"form_fields[ReserverEspacePublicitaire]\"]');\r\n        if (!$input.length) { $input = jQuery(this).siblings('input[name=\"form_fields[ReserverEspacePublicitaire]\"]'); }\r\n        if (!$input.length) { $input = jQuery('input[name=\"form_fields[ReserverEspacePublicitaire]\"]').first(); }\r\n        if ($input.length) {\r\n            if (e.type === 'touchend') { e.preventDefault(); }\r\n            var _wasChecked = $input.prop('checked');\r\n            $input.prop('checked', !_wasChecked);\r\n            setTimeout(function() {\r\n                $input.trigger('change');\r\n            }, 50);\r\n        }\r\n    });\r\n    jQuery(document).on('change', 'input[name=\"form_fields[ReserverEspacePublicitaire]\"]', function(e) {\r\n        const $checkbox = $(this);\r\n        let $droppable = $checkbox.closest('.droppable');\r\n        if (!$droppable.length) {\r\n            $droppable = $checkbox.closest('.reserver-dynamic-container').prev('.droppable');\r\n        }\r\n        \/\/ \u2705 v2.4.13 : Fallback mobile \u2014 checkbox dans body > .reserver-dynamic-container[data-droppable-id]\r\n        if (!$droppable.length) {\r\n            const $dynContainer = $checkbox.closest('.reserver-dynamic-container');\r\n            const _droppableId = $dynContainer.attr('data-droppable-id') || '';\r\n            if (_droppableId) {\r\n                $droppable = $('#' + _droppableId);\r\n            }\r\n        }\r\n        \/\/ Dernier recours : utiliser le rank en sessionStorage\r\n        if (!$droppable.length) {\r\n            const _rankFallback = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            if (_rankFallback) { $droppable = $('#' + _rankFallback); }\r\n        }\r\n        \r\n        \/\/ Si on d\u00e9coche\r\n        if (!$checkbox.is(':checked')) {\r\n            console.log('\u2610 Checkbox \"R\u00e9server\" d\u00e9coch\u00e9e');\r\n            \/\/ \u2705 Mettre \u00e0 jour le label\r\n            const $lbl = $checkbox.closest('.reserver-dynamic-option, .elementor-field-option').find('.reserver-dynamic-label, label').not('input');\r\n            $lbl.text('R\u00e9server cet espace publicitaire').css('color', '');\r\n            \/\/ \u2705 Notifier le parent pour d\u00e9-r\u00e9server l'item dans le r\u00e9cap\r\n            const _rankDecoche = $droppable.attr('id') || StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            const _emplacementDecoche = StateManager.buildEmplacementReference(_rankDecoche);\r\n            MessageManager.sendToParent('annulationReservation', {\r\n                Rank_Emplacement_Page_Web: _rankDecoche,\r\n                Commande_Emplacement_Page_Web: _emplacementDecoche,\r\n                LoadedPageUrl: window.location.href\r\n            });\r\n            \/\/ \u2705 v2.4.5 : M\u00e9moriser que ce rank a \u00e9t\u00e9 explicitement d\u00e9coch\u00e9\r\n            StateManager.set('_reserverDecoche_' + _rankDecoche, 'Yes');\r\n            console.log('\ud83d\udce4 annulationReservation envoy\u00e9 \u2192 parent | rank:', _rankDecoche, '| emplacement:', _emplacementDecoche);\r\n            return;\r\n        }\r\n        \r\n        \/\/ \u2705 V\u00e9rifier les conditions : format s\u00e9lectionn\u00e9 ET (fichier d\u00e9pos\u00e9 OU envoi diff\u00e9r\u00e9)\r\n        const hasFormat = FormatUIManager.hasSelectedFormat($droppable);\r\n        \/\/ v2.9 : data-via-ad-loaded est specifique au droppable, plus fiable que FileReceived global\r\n        const hasFile = StateManager.get('FileReceived') === 'Yes'\r\n            || $droppable.attr('data-via-ad-loaded') === 'true';\r\n        const hasEnvoiDiffere = $droppable.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        \r\n        console.log('\ud83d\udd0d Checkbox \"R\u00e9server\" - Validation:', { hasFormat, hasFile, hasEnvoiDiffere });\r\n        \r\n        if (!hasFormat) {\r\n            \/\/ \u2705 Si un fichier est d\u00e9j\u00e0 d\u00e9pos\u00e9 \u2192 d\u00e9duire le format depuis l'extension (ne pas bloquer)\r\n            if (hasFile) {\r\n                const _uploadedName = StateManager.get('Upload_File_Name') || '';\r\n                const _ext = _uploadedName.split('.').pop().toLowerCase();\r\n                const _fileType = FileManager.getFileType(_ext);\r\n                let _deducedFormat = '';\r\n                if (_fileType === 'video') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Video' : 'Vid\u00e9o';\r\n                } else if (_fileType === 'image') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Banner' : 'Banni\u00e8re';\r\n                } else if (_fileType === 'document') {\r\n                    _deducedFormat = sessionStorage.getItem('SiteLangue') === 'EN' ? 'Press release' : 'Communiqu\u00e9';\r\n                }\r\n                if (_deducedFormat) {\r\n                    StateManager.set('Commande_Format_Transmis', _deducedFormat);\r\n                    StateManager.set('FormatSelect', _deducedFormat);\r\n                    StateManager.set('Formatchoisi', 'Yes');\r\n                    console.log('\u2705 Format d\u00e9duit depuis extension (' + _ext + '):', _deducedFormat);\r\n                    \/\/ Continuer vers l'envoi (pas de return false)\r\n                } else {\r\n                    e.preventDefault();\r\n                    $checkbox.prop('checked', false);\r\n                    FormatUIManager.flashTitle($droppable);\r\n                    console.log('\u274c R\u00e9servation bloqu\u00e9e : format non d\u00e9ductible depuis extension:', _ext);\r\n                    return false;\r\n                }\r\n            } else {\r\n                e.preventDefault();\r\n                $checkbox.prop('checked', false);\r\n                FormatUIManager.flashTitle($droppable);\r\n                console.log('\u274c R\u00e9servation bloqu\u00e9e : aucun format s\u00e9lectionn\u00e9');\r\n                return false;\r\n            }\r\n        }\r\n        \r\n        if (!hasFile ? !hasEnvoiDiffere : false) {\r\n            e.preventDefault();\r\n            $checkbox.prop('checked', false);\r\n            console.log('\u274c R\u00e9servation bloqu\u00e9e : ni annonce d\u00e9pos\u00e9e ni envoi diff\u00e9r\u00e9');\r\n            return false;\r\n        }\r\n        \r\n        \/\/ \u2705 Conditions remplies \u2014 Pr\u00e9parer et envoyer les donn\u00e9es\r\n        console.log('\u2705 Checkbox \"R\u00e9server\" valid\u00e9e \u2014 envoi des donn\u00e9es');\r\n        \r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.setMultiple({\r\n            \"sendDataToParentFlag\": \"Yes\",\r\n            \"Formatchoisi\": \"Yes\",\r\n            \"Rank_Emplacement_Page_Web\": rankId,\r\n            \"Commande_Emplacement_Page_Web\": StateManager.buildEmplacementReference(rankId),\r\n            \"LoadedPageUrl\": window.location.href,\r\n            \/\/ \u2705 v2.3.0 : Forcer avant l'envoi \u2014 le setTimeout(4000) dans activateSendDataToParent\r\n            \/\/ arrive trop tard sur le 1er clic \u2192 AddNewRefInVosCampagnes restait null\r\n            \"AddNewRefInVosCampagnes\": \"Yes\"\r\n        });\r\n        \r\n        \/\/ \u2705 D\u00e9clencher l'envoi des donn\u00e9es via activateSendDataToParent\r\n        const $dropZone = $droppable.find('#drop_file_zone_achat');\r\n        UploadManager.activateSendDataToParent($dropZone);\r\n    });\r\n    \r\n    \/\/ G\u00c9RER \"Envoi diff\u00e9r\u00e9\" \u2014 marquer l'\u00e9tat sans envoyer (c'est \"R\u00e9server\" qui envoie)\r\n    \/\/ \u2705 v2.4.13 : iOS touchend\r\n    jQuery(document).on('touchend', 'input[name*=\"EnvoiUlterieur\"]', function() {\r\n        if (this._viaTouch) { return; }\r\n        this._viaTouch = true;\r\n        var _self = this;\r\n        setTimeout(function() { _self._viaTouch = false; }, 500);\r\n        setTimeout(function() { jQuery(_self).trigger('change'); }, 100);\r\n    });\r\n    jQuery(document).on('change', 'input[name*=\"EnvoiUlterieur\"]', function(e) {\r\n        const $checkbox = $(this);\r\n        let $droppable = $checkbox.closest('.droppable');\r\n        \/\/ \u2705 v2.4.13 : Fallback mobile\r\n        if (!$droppable.length) {\r\n            const $dynContainer = $checkbox.closest('.reserver-dynamic-container');\r\n            const _droppableId = $dynContainer.attr('data-droppable-id') || '';\r\n            if (_droppableId) { $droppable = $('#' + _droppableId); }\r\n        }\r\n        if (!$droppable.length) {\r\n            const _rankFallback = StateManager.get('Rank_Emplacement_Page_Web') || '';\r\n            if (_rankFallback) { $droppable = $('#' + _rankFallback); }\r\n        }\r\n  \r\n        \/\/ Si on d\u00e9coche\r\n        if (!$checkbox.is(':checked')) {\r\n            StateManager.set('EnvoiUlterieur', 'false');\r\n            StateManager.set('sendDataToParentFlag', 'No'); \/\/ v4.9ds\r\n            \r\n            if (typeof window.verifierAffichageMsgSelectEspaceLocal === 'function') {\r\n                window.verifierAffichageMsgSelectEspaceLocal();\r\n            }\r\n            \r\n            \/\/ Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server\r\n            FormatUIManager.updateReserverCheckboxState($droppable);\r\n            return;\r\n        }\r\n        \r\n        \/\/ Si on coche, masquer le message\r\n        jQuery('#MsgSelectEspace').hide();\r\n    \r\n        \/\/ V\u00e9rifier si un format est s\u00e9lectionn\u00e9\r\n        const hasSelectedFormat = FormatUIManager.hasSelectedFormat($droppable);\r\n        \r\n        if (!hasSelectedFormat) {\r\n            e.preventDefault();\r\n            $checkbox.prop('checked', false);\r\n            FormatUIManager.flashTitle($droppable);\r\n            return false;\r\n        }\r\n        \r\n        \/\/ \u2705 FORMAT OK \u2014 Marquer l'\u00e9tat envoi diff\u00e9r\u00e9 (sans envoyer les donn\u00e9es)\r\n        const rankId = $droppable.attr('id');\r\n        \r\n        StateManager.setMultiple({\r\n            \"EnvoiUlterieur\": 'true',\r\n            \"Rank_Emplacement_Page_Web\": rankId,\r\n            \"Commande_Emplacement_Page_Web\": StateManager.buildEmplacementReference(rankId),\r\n            \"LoadedPageUrl\": window.location.href,\r\n            \"FileReceived\": \"No\",\r\n            \"FullPathAdFile\": '',\r\n            \"Upload_File_Name\": '',\r\n            \"Formatchoisi\": 'Yes',\r\n            \"sendDataToParentFlag\": 'No' \/\/ v4.9ds : emp\u00eache l'ouverture du popup\r\n        });\r\n        \r\n        console.log('\ud83d\udcdd Envoi diff\u00e9r\u00e9 coch\u00e9 \u2014 en attente de validation via checkbox \"R\u00e9server\"');\r\n        \/\/ \u2705 Mettre \u00e0 jour l'\u00e9tat de la checkbox R\u00e9server (maintenant activable)\r\n        FormatUIManager.updateReserverCheckboxState($droppable);\r\n    });\r\n\r\n    \/\/ \u2705 v2.2.0 : Bouton \"Cr\u00e9ation\" dans espace publicitaire \u2192 Kit Ad Creator dans le parent\r\n    jQuery(document).on('click', '.FormatIdCreation', function(e) {\r\n        e.preventDefault();\r\n        var $btn = jQuery(this);\r\n        var $droppable = $btn.closest('.droppable');\r\n        var isActive = $btn.data('creationActive') === true;\r\n\r\n        if (isActive) {\r\n            \/\/ D\u00e9sactiver\r\n            $btn.data('creationActive', false);\r\n            $btn.find('.EspPubFormat').css({'color': '#ffffff'});\r\n            $btn.css({'background-color': 'transparent'});\r\n            MessageManager.sendToParent('closeAdCreatorFromIframe', {});\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2192 toggle OFF, fermeture popup parent');\r\n            return;\r\n        }\r\n\r\n        \/\/ \u2705 Nouvelle logique sites pays\r\n        \/\/ V\u00e9rifier quel format est s\u00e9lectionn\u00e9 (fond blanc, hors Cr\u00e9ation et PopUp)\r\n        var _selFormat = '';\r\n        var _selIsVideo = false;\r\n        $droppable.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').each(function() {\r\n            var _bg = this.style.backgroundColor || '';\r\n            var _isWhite = _bg === 'rgb(255, 255, 255)' || _bg === '#ffffff' || _bg === 'white';\r\n            if (!_isWhite) {\r\n                var _fEl = this.querySelector('.EspPubFormat');\r\n                if (_fEl) { var _col = _fEl.style.color || ''; _isWhite = _col === 'rgb(55, 217, 0)' || _col.toLowerCase() === '#37d900'; }\r\n            }\r\n            if (_isWhite) {\r\n                var _cls = this.className || '';\r\n                _selFormat = _cls.match(\/FormatId(\\w+)\/) ? _cls.match(\/FormatId(\\w+)\/)[1] : 'unknown';\r\n                _selIsVideo = jQuery(this).hasClass('FormatIdVideo');\r\n                return false;\r\n            }\r\n        });\r\n\r\n        \/\/ R\u00e8gle 3 : si Vid\u00e9o est s\u00e9lectionn\u00e9 \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e\r\n        if (_selIsVideo) {\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation bloqu\u00e9e \u2014 format Vid\u00e9o actif');\r\n            UIManager.showFormatError($droppable, 'La cr\u00e9ation n\\'est pas disponible pour le format Vid\u00e9o');\r\n            return;\r\n        }\r\n\r\n        \/\/ R\u00e8gle 2 : aucun format s\u00e9lectionn\u00e9 \u2192 message, mais Cr\u00e9ation reste active visuellement\r\n        if (!_selFormat) {\r\n            \/\/ S\u00e9lectionner Cr\u00e9ation visuellement (fond blanc, texte vert)\r\n            $btn.data('creationActive', true);\r\n            $btn.find('.EspPubFormat').css({'color': '#37D900'});\r\n            $btn.css({'background-color': '#ffffff'});\r\n            \/\/ \u2705 Message persistant \u2014 pas de timeout, supprim\u00e9 au clic format\r\n            var $dropZone2 = $droppable.find('#drop_file_zone_achat, .drop_file_zone_achat_class').first();\r\n            if (!$dropZone2.length) { $dropZone2 = $droppable; }\r\n            var $dropZone2snap = $dropZone2;\r\n            setTimeout(function() {\r\n                jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n                jQuery('#fmt-creation-info').remove();\r\n                var _isMob2 = UIManager.isMobile();\r\n                \/\/ v4.9ds : Attacher le message au BODY (pas dans le drop zone) pour \u00e9chapper\r\n                \/\/          \u00e0 l'opacity 0.4 du format-gate appliqu\u00e9 sur .HTMLUploadfileConteneur.\r\n                \/\/          Position calcul\u00e9e dynamiquement \u00e0 partir du dropZone (au-dessus du curseur).\r\n                \/\/ v4.9ds (suit-drag) : si on est sur Ele0A et qu'un wrapper .ToBeHidden parent\r\n                \/\/          existe (cas espace pop-up draggable), on attache le message au wrapper\r\n                \/\/          au lieu du body. Le wrapper est ce qui se d\u00e9place lors du drag d'Ele0A,\r\n                \/\/          donc le message suit naturellement. L'opacity 0.4 est sur\r\n                \/\/          .HTMLUploadfileConteneur (enfant d'Ele0A) \u2014 le wrapper, parent\r\n                \/\/          d'Ele0A, y \u00e9chappe.\r\n                var _dzEl = $dropZone2snap[0];\r\n                if (!_dzEl) return;\r\n                var _rect = _dzEl.getBoundingClientRect();\r\n                \/\/ v4.9ds : desktop top:50\u219240, mobile top:54 inchang\u00e9\r\n                \/\/   + dans l'iframe r\u00e9gie (window !== window.top) : descendre de 50px\r\n                \/\/   (le message tombait sur les formats au lieu de la dropZone)\r\n                var _isInIframe = (window !== window.top);\r\n                var _extraOffset = _isInIframe ? 50 : 0;\r\n                \/\/ D\u00e9tection wrapper Ele0A pour suivi drag\r\n                var _$ele0A = jQuery(_dzEl).closest('#Ele0A');\r\n                var _$wrapper = _$ele0A.length ? _$ele0A.closest('.ToBeHidden') : jQuery();\r\n                var _useWrapper = _$wrapper.length > 0;\r\n                var _topPx, _leftPx, _$parent, _positionMode;\r\n                if (_useWrapper) {\r\n                    \/\/ Position RELATIVE au wrapper (pas au document) \u2192 suit le drag\r\n                    var _wRect = _$wrapper[0].getBoundingClientRect();\r\n                    _topPx = Math.round((_rect.top - _wRect.top) + (_isMob2 ? 54 : 40) + _extraOffset);\r\n                    _leftPx = Math.round((_rect.left - _wRect.left) + _rect.width \/ 2);\r\n                    _$parent = _$wrapper;\r\n                    _positionMode = 'absolute';\r\n                    \/\/ S'assurer que le wrapper peut h\u00e9berger un absolute (position non-static)\r\n                    var _curPos = window.getComputedStyle(_$wrapper[0]).position;\r\n                    if (_curPos === 'static') { _$wrapper.css('position', 'relative'); }\r\n                    \/\/ Permettre au message de d\u00e9border si le wrapper a overflow:hidden\r\n                    var _curOv = window.getComputedStyle(_$wrapper[0]).overflow;\r\n                    if (_curOv === 'hidden') { _$wrapper.css('overflow', 'visible'); }\r\n                } else {\r\n                    \/\/ Fallback historique : attacher au body (cas miniature, ele1a+, etc.)\r\n                    _topPx = Math.round(_rect.top + (_isMob2 ? 54 : 40) + _extraOffset + (window.scrollY || 0));\r\n                    _leftPx = Math.round(_rect.left + _rect.width \/ 2 + (window.scrollX || 0));\r\n                    _$parent = jQuery('body');\r\n                    _positionMode = 'absolute';\r\n                }\r\n                \/\/ v4.9ds : mobile scale 0.6, desktop scale 0.7\r\n                var _transform = _isMob2\r\n                    ? 'transform:translateX(-50%) scale(0.6);transform-origin:top center;'\r\n                    : 'transform:translateX(-50%) scale(0.7);transform-origin:top center;';\r\n                var _fontSize = _isMob2 ? '13px' : '14px';\r\n                \/\/ v4.9ds (fmt-info-z-fix) : z-index dynamique pour rester au-dessus du\r\n                \/\/   wrapper .ToBeHidden d'Ele0A quand celui-ci est mis en avant-plan par\r\n                \/\/   _bringPopupToFront (Entete.txt). On lit window.popupZIndex (variable\r\n                \/\/   globale du compteur z-index dans Entete.txt) et on prend max+10. Si\r\n                \/\/   absent (cas iframe ou page sans Entete.txt), fallback 99999.\r\n                \/\/   NB : si on est attach\u00e9 au wrapper, le z-index est relatif au stacking\r\n                \/\/   context du wrapper, mais on garde la valeur \u00e9lev\u00e9e par s\u00e9curit\u00e9.\r\n                var _baseZ = (typeof window.popupZIndex === 'number') ? window.popupZIndex : 99999;\r\n                var _msgZ = Math.max(99999, _baseZ + 10);\r\n                _$parent.append('<div id=\"fmt-creation-info\" style=\"'\r\n                    + 'position:' + _positionMode + ';top:' + _topPx + 'px;left:' + _leftPx + 'px;'\r\n                    + _transform\r\n                    + 'width:auto;white-space:nowrap;z-index:' + _msgZ + ';'\r\n                    + 'background:#fff;color:#FB5E2A;font-weight:700;font-size:' + _fontSize + ';'\r\n                    + 'text-align:center;padding:8px 10px;border-radius:6px;'\r\n                    + 'border:2px solid #FB5E2A;box-sizing:border-box;line-height:1.4;'\r\n                    + 'opacity:1;pointer-events:auto;'\r\n                    + '\">Merci de s\u00e9lectionner le format d\\'annonce que vous souhaitez cr\u00e9er<\/div>');\r\n            }, 80);\r\n            \/\/ D\u00e9sactiver visuellement le bouton Vid\u00e9o + changer son texte en \"D\u00e9sactiv\u00e9\"\r\n            $droppable.find('.FormatIdVideo').each(function() {\r\n                jQuery(this).css({'opacity': '0.4', 'pointer-events': 'none'});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                $lbl.attr('data-original-text', $lbl.text());\r\n                $lbl.text('D\u00e9sactiv\u00e9');\r\n            });\r\n            \/\/ NE PAS d\u00e9s\u00e9lectionner Cr\u00e9ation \u2014 laisser l'utilisateur choisir un format\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2014 aucun format s\u00e9lectionn\u00e9 \u2192 message + Vid\u00e9o d\u00e9sactiv\u00e9e');\r\n            return;\r\n        }\r\n\r\n        \/\/ R\u00e8gle 1 : format valide s\u00e9lectionn\u00e9 \u2192 d\u00e9sactiver Vid\u00e9o + ouvrir le popup apr\u00e8s 3 secondes\r\n        $btn.data('creationActive', true);\r\n        $btn.find('.EspPubFormat').css({'color': '#37D900'});\r\n        $btn.css({'background-color': '#ffffff'});\r\n        \/\/ \u2705 D\u00e9sactiver Vid\u00e9o\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '0.4', 'pointer-events': 'none'});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            if (!$lbl.attr('data-original-text')) { $lbl.attr('data-original-text', $lbl.text()); }\r\n            $lbl.text('D\u00e9sactiv\u00e9');\r\n        });\r\n        var formatSelect = sessionStorage.getItem('FormatSelect') || '';\r\n        var formatTransmis = sessionStorage.getItem('Commande_Format_Transmis') || '';\r\n        var _rankKit = $droppable.attr('id') || sessionStorage.getItem('Rank_Emplacement_Page_Web') || '';\r\n        var _cSite = sessionStorage.getItem('codeSite') || '';\r\n        var _cPage = sessionStorage.getItem('codePage') || '';\r\n        var _sfxKit = _rankKit.replace('Ele', '');\r\n        var _emplKit = (_cSite ? (_cPage ? _sfxKit : false) : false) ? (_cSite + _cPage + 'L' + _sfxKit) : (sessionStorage.getItem('Commande_Emplacement_Page_Web') || '');\r\n        var _fmtSnap = formatSelect;\r\n        var _fmtTSnap = formatTransmis;\r\n        var _rkSnap = _rankKit;\r\n        var _emplSnap = _emplKit;\r\n        setTimeout(function() {\r\n            MessageManager.sendToParent('openAdCreatorFromIframe', { formatSelect: _fmtSnap, formatTransmis: _fmtTSnap, rankId: _rkSnap, emplacement: _emplSnap });\r\n            console.log('\ud83c\udfa8 Cr\u00e9ation \u2192 toggle ON apr\u00e8s 2s | format:', _selFormat, '| FormatSelect:', _fmtSnap);\r\n        }, 2000);\r\n    });\r\n\r\n    \/\/ \u2705 R\u00e8gle : si Vid\u00e9o est cliqu\u00e9, d\u00e9sactiver Cr\u00e9ation\r\n    jQuery(document).on('click', '.FormatIdVideo', function(e) {\r\n        var $droppable = jQuery(this).closest('.droppable');\r\n        \/\/ R\u00e9activer Vid\u00e9o (peut avoir \u00e9t\u00e9 d\u00e9sactiv\u00e9e par la r\u00e8gle 2) + restaurer texte\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ D\u00e9sactiver visuellement Cr\u00e9ation + changer texte en \"D\u00e9sactiv\u00e9\"\r\n        $droppable.find('.FormatIdCreation').each(function() {\r\n            jQuery(this).data('creationActive', false);\r\n            jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n            jQuery(this).css({'background-color': 'transparent', 'opacity': '0.4', 'pointer-events': 'none'});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            $lbl.attr('data-original-text', $lbl.attr('data-original-text') || $lbl.text());\r\n            $lbl.text('D\u00e9sactiv\u00e9');\r\n        });\r\n        \/\/ Fermer le popup si ouvert\r\n        MessageManager.sendToParent('closeAdCreatorFromIframe', {});\r\n        console.log('\ud83c\udfa8 Vid\u00e9o s\u00e9lectionn\u00e9 \u2192 Cr\u00e9ation d\u00e9sactiv\u00e9e');\r\n    });\r\n\r\n    \/\/ \u2705 R\u00e8gle : si un autre format est s\u00e9lectionn\u00e9, r\u00e9activer Cr\u00e9ation et Vid\u00e9o + restaurer textes\r\n    jQuery(document).on('click', '.EspPubFormatContainer:not(.FormatIdCreation):not(.FormatIdPopUp):not(.FormatIdVideo)', function(e) {\r\n        var $droppable = jQuery(this).closest('.droppable');\r\n        \/\/ R\u00e9activer Vid\u00e9o + restaurer texte\r\n        $droppable.find('.FormatIdVideo').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ R\u00e9activer Cr\u00e9ation + restaurer texte\r\n        $droppable.find('.FormatIdCreation').each(function() {\r\n            jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n            var $lbl = jQuery(this).find('.EspPubFormat');\r\n            var _orig = $lbl.attr('data-original-text');\r\n            if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n        });\r\n        \/\/ Supprimer les messages d'erreur\r\n        jQuery('[id^=\"fmt-error-msg-\"]').remove();\r\n    });\r\n\r\n    \/\/ \u2705 v2.2.0 : Messages depuis le parent concernant le bouton Cr\u00e9ation\r\n    window.addEventListener('message', function(event) {\r\n        var msg = event.data;\r\n        if (!msg) return;\r\n\r\n        \/\/ \u2705 v2.2.1 : Fournir la position du titre R\u00e9server pour positionner la miniature\r\n        if (msg.type === 'getReserverLabelRect') {\r\n            var $label = jQuery('.reserver-dynamic-label').first();\r\n            if ($label.length) {\r\n                var r = $label[0].getBoundingClientRect();\r\n                var iframeScale = (window.outerWidth > 1000) ? 0.75 : 0.80; \/\/ \u2705 v2.4.12 : 0.85 \u2192 0.75 (scale r\u00e9el du parent)\r\n                event.source.postMessage({\r\n                    type: 'reserVeurLabelRectResult',\r\n                    \/\/ Retourner bottom en coordonn\u00e9es iframe internes (non scal\u00e9es)\r\n                    bottom: r.bottom,\r\n                    left: r.left,\r\n                    right: r.right,\r\n                    iframeScale: iframeScale\r\n                }, '*');\r\n            } else {\r\n                event.source.postMessage({ type: 'reserVeurLabelRectResult', bottom: null }, '*');\r\n            }\r\n        }\r\n\r\n        \/\/ Fermeture du popup \u2192 d\u00e9s\u00e9lectionner Cr\u00e9ation + r\u00e9activer Vid\u00e9o dans tous les espaces\r\n        if (msg.type === 'adCreatorClosedFromParent') {\r\n            jQuery('.FormatIdCreation').each(function() {\r\n                jQuery(this).data('creationActive', false);\r\n                jQuery(this).find('.EspPubFormat').css({'color': '#ffffff'});\r\n                jQuery(this).css({'background-color': 'transparent'});\r\n            });\r\n            \/\/ \u2705 R\u00e9activer Vid\u00e9o dans tous les espaces de la page\r\n            jQuery('.FormatIdVideo').each(function() {\r\n                jQuery(this).css({'opacity': '', 'pointer-events': ''});\r\n                var $lbl = jQuery(this).find('.EspPubFormat');\r\n                var _orig = $lbl.attr('data-original-text');\r\n                if (_orig) { $lbl.text(_orig); $lbl.removeAttr('data-original-text'); }\r\n            });\r\n            \/\/ v4.9ds : r\u00e9-\u00e9valuer SelectionFormatTitre \/ SelectionFormatTitreBlanc sur tous les droppables\r\n            \/\/          (Cr\u00e9ation d\u00e9s\u00e9lectionn\u00e9 \u2192 si aucun autre format actif dans le DOM, le titre rouge doit r\u00e9appara\u00eetre)\r\n            \/\/          Check DOM direct (pas updateTitleColor qui retombe sur sessionStorage Formatchoisi=Yes)\r\n            jQuery('.droppable').not('#Ele0A').each(function() {\r\n                var $_d = jQuery(this);\r\n                var _hasDomFmt = $_d.find('.EspPubFormatContainer').not('.FormatIdCreation').not('.FormatIdPopUp').filter(function() {\r\n                    var _bg = jQuery(this).css('background-color') || '';\r\n                    return _bg === 'rgb(255, 255, 255)';\r\n                }).length > 0;\r\n                if (_hasDomFmt) {\r\n                    $_d.find('.SelectionFormatTitre').hide();\r\n                    $_d.find('.SelectionFormatTitreBlanc').show();\r\n                } else {\r\n                    $_d.find('.SelectionFormatTitre').each(function() { this.style.setProperty('display','block','important'); });\r\n                    $_d.find('.SelectionFormatTitreBlanc').hide();\r\n                }\r\n            });\r\n            console.log('\ud83c\udfa8 adCreatorClosedFromParent \u2192 Cr\u00e9ation d\u00e9s\u00e9lectionn\u00e9 + Vid\u00e9o r\u00e9activ\u00e9 + titres r\u00e9-\u00e9valu\u00e9s (DOM direct)');\r\n        }\r\n\r\n        \/\/ Restaurer le style du bouton Cr\u00e9ation apr\u00e8s un reset de removeElements\r\n        if (msg.type === 'restoreCreationButton') {\r\n            jQuery('.FormatIdCreation').each(function() {\r\n                if (jQuery(this).data('creationActive') === true) {\r\n                    jQuery(this).find('.EspPubFormat').css({'color': '#37D900'});\r\n                    jQuery(this).css({'background-color': '#ffffff'});\r\n                }\r\n            });\r\n            console.log('\ud83c\udfa8 restoreCreationButton re\u00e7u \u2192 style Cr\u00e9ation restaur\u00e9');\r\n        }\r\n    });\r\n\r\n});\r\n\r\n\/\/ =========================================================================\r\n\/\/ \u2705 v4.9ds Pb 9.62 R\u00e8gle 2 : Listener disableEspacesPris \u2014 grise\/d\u00e9sactive les\r\n\/\/   espaces publicitaires d\u00e9j\u00e0 pris par d'autres items du panier sur la m\u00eame page.\r\n\/\/   Re\u00e7u depuis Panier_manager.txt (notifierEspacesPrisIframe).\r\n\/\/   \"1 espace pub = 1 r\u00e9servation\" \u2192 emp\u00eache le user de cliquer \u00e0 nouveau dessus.\r\n\/\/ =========================================================================\r\n(function _viaInitDisableEspacesPris() {\r\n    \/\/ CSS inject\u00e9 une seule fois pour le grisage + overlay \"D\u00e9j\u00e0 r\u00e9serv\u00e9\"\r\n    if (!document.getElementById('via-espace-pris-style')) {\r\n        var _styleEPS = document.createElement('style');\r\n        _styleEPS.id = 'via-espace-pris-style';\r\n        _styleEPS.textContent =\r\n            '.via-espace-pris-greyed { ' +\r\n            '  opacity: 0.45 !important; ' +\r\n            '  pointer-events: none !important; ' +\r\n            '  cursor: not-allowed !important; ' +\r\n            '  position: relative !important; ' +\r\n            '  filter: grayscale(80%) !important; ' +\r\n            '}' +\r\n            '.via-espace-pris-greyed::after { ' +\r\n            '  content: \"D\u00e9j\u00e0 r\u00e9serv\u00e9\"; ' +\r\n            '  position: absolute; ' +\r\n            '  top: 50%; ' +\r\n            '  left: 50%; ' +\r\n            '  transform: translate(-50%, -50%); ' +\r\n            '  background: rgba(34, 93, 169, 0.92); ' +\r\n            '  color: #ffffff; ' +\r\n            '  padding: 5px 12px; ' +\r\n            '  border-radius: 4px; ' +\r\n            '  font-size: 13px; ' +\r\n            '  font-weight: 600; ' +\r\n            '  white-space: nowrap; ' +\r\n            '  pointer-events: none; ' +\r\n            '  z-index: 10; ' +\r\n            '  box-shadow: 0 2px 6px rgba(0,0,0,0.25); ' +\r\n            '}';\r\n        document.head.appendChild(_styleEPS);\r\n    }\r\n\r\n    function _applyEspacesPris(emplacements) {\r\n        \/\/ 1. Retirer le grisage de tous les droppables (reset)\r\n        jQuery('.droppable').removeClass('via-espace-pris-greyed');\r\n        \/\/ 2. Griser ceux dans la liste\r\n        if (!Array.isArray(emplacements)) { return; }\r\n        var _count = 0;\r\n        emplacements.forEach(function(_item) {\r\n            if (!_item) return;\r\n            \/\/ Cibler par rank (Ele1A, Ele2A, etc.) ou par data-empl\r\n            var _rank = _item.rank || '';\r\n            var _empl = _item.emplacement || '';\r\n            var $cible = jQuery();\r\n            if (_rank) {\r\n                $cible = jQuery('#' + _rank);\r\n            }\r\n            if (!$cible.length ? !!_empl : false) {\r\n                \/\/ Fallback : chercher un droppable dont la r\u00e9f\u00e9rence d'emplacement matche\r\n                $cible = jQuery('.droppable').filter(function() {\r\n                    var _t = jQuery(this).find('[data-empl]').attr('data-empl') || '';\r\n                    return _t === _empl;\r\n                });\r\n            }\r\n            if ($cible.length) {\r\n                $cible.addClass('via-espace-pris-greyed');\r\n                _count++;\r\n            }\r\n        });\r\n        console.log('\ud83d\udeab [disableEspacesPris]', _count, '\/', emplacements.length, 'espace(s) gris\u00e9(s)');\r\n    }\r\n\r\n    window.addEventListener('message', function(_eEPS) {\r\n        var _msg = _eEPS.data;\r\n        if (!_msg ? true : _msg.type !== 'disableEspacesPris') return;\r\n        _applyEspacesPris(_msg.emplacements);\r\n    });\r\n\r\n    \/\/ Exposer pour appel manuel \/ test depuis console\r\n    window._viaApplyEspacesPris = _applyEspacesPris;\r\n    console.log('\u2705 [disableEspacesPris] listener install\u00e9');\r\n})();\r\n\r\n\/\/ =========================================================================\r\n\/\/ \u2705 Listener kitAdCreated \u2014 annonce cr\u00e9\u00e9e par le Kit overlay (mode=kit)\r\n\/\/ Injecte directement dans l'espace pub identifi\u00e9 par rankId\r\nwindow.addEventListener('message', function(event) {\r\n    var msg = event.data;\r\n\r\n    if (!msg || msg.action !== 'kitAdCreated') return;\r\n\r\n    console.log('\ud83d\udd0d [DIAG kitAdCreated entr\u00e9e] receveur=' + (window === window.top ? 'PARENT' : 'IFRAME') + ' | location=' + window.location.href.substring(0, 80) + ' | rank=' + (msg.rankId || 'NULL'));\r\n\r\n    var _rankId = msg.rankId || '';\r\n    var _emplacement = msg.emplacement || '';\r\n    console.log('\ud83c\udfa8 [espace_pub] kitAdCreated re\u00e7u | rankId:', _rankId, '| emplacement:', _emplacement);\r\n\r\n    if (!_rankId) { console.warn('\u26a0\ufe0f [kitAdCreated] rankId manquant'); return; }\r\n\r\n    var $droppable = jQuery('#' + _rankId);\r\n    if (!$droppable.length) { console.warn('\u26a0\ufe0f [kitAdCreated] droppable non trouv\u00e9:', _rankId); return; }\r\n    var $dropZone = $droppable.find('.drop_file_zone_achat_class').first();\r\n    if (!$dropZone.length) { $dropZone = $droppable.find('#drop_file_zone_achat').first(); }\r\n    if (!$dropZone.length) { console.warn('\u26a0\ufe0f [kitAdCreated] dropZone non trouv\u00e9e dans', _rankId); return; }\r\n\r\n    \/\/ v4.9ds : d\u00e9verrouiller le format-gate sur le droppable cible\r\n    \/\/   Si l'utilisateur d\u00e9pose une cr\u00e9ation depuis la miniature dans un espace sans format pr\u00e9alable,\r\n    \/\/   data-via-format-gate=\"locked\" reste pos\u00e9 \u2192 opacity 0.4 sur l'annonce d\u00e9pos\u00e9e.\r\n    \/\/   On pose le format de la miniature pour lib\u00e9rer le gate.\r\n    try {\r\n        var _topW = window.top || window;\r\n        var _kitFmt = msg.format || sessionStorage.getItem('FormatSelect') || '';\r\n        if (_kitFmt && typeof _topW._viaSetDroppableFormat === 'function') {\r\n            _topW._viaSetDroppableFormat(_rankId, _kitFmt);\r\n            console.log('\ud83d\udd13 [kitAdCreated] format-gate d\u00e9verrouill\u00e9 sur', _rankId, '| format:', _kitFmt);\r\n        }\r\n    } catch(_eGate) { console.warn('\u26a0\ufe0f [kitAdCreated] _viaSetDroppableFormat error:', _eGate); }\r\n\r\n    \/\/ \u2705 Bug 7 sites pays : stocker pdfImageDataURL pour \"Ouvrir et visualiser\"\r\n    \/\/   (m\u00eame logique que thumbnailDropped qui fonctionne sur la r\u00e9gie)\r\n    if (msg.pdfImageDataURL) {\r\n        $droppable.data('kitPdfImageDataURL', msg.pdfImageDataURL);\r\n        $droppable.data('kitFormatSelect', msg.format || '');\r\n        console.log('\ud83d\udcce [kitAdCreated] pdfImageDataURL stock\u00e9 sur', _rankId, '| format:', msg.format);\r\n    } else {\r\n        $droppable.removeData('kitPdfImageDataURL');\r\n        $droppable.removeData('kitFormatSelect');\r\n    }\r\n    \/\/ v4.9ds : 1\u00e8re image utilisateur (pour preview mobile JPG-in-PDF)\r\n    if (msg.firstImageDataURL) {\r\n        $droppable.data('kitFirstImageDataURL', msg.firstImageDataURL);\r\n        console.log('\ud83d\uddbc\ufe0f [kitAdCreated] kitFirstImageDataURL stock\u00e9 sur', _rankId);\r\n        try { var _dl3 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl3) _dl3('[v4.9ds] kitAdCreated firstImg stock\u00e9 ' + _rankId, 'ok'); } catch(e) {}\r\n    } else {\r\n        $droppable.removeData('kitFirstImageDataURL');\r\n        try { var _dl4 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl4) _dl4('[v4.9ds] kitAdCreated SANS firstImg ' + _rankId, 'warn'); } catch(e) {}\r\n    }\r\n\r\n    var _dataURL = msg.fullResDataURL || msg.pdfDataURL || null;\r\n    var _filename = msg.filename || 'annonce.png';\r\n    var _isPDF = msg.isPDF || false;\r\n    if (!_dataURL) { console.warn('\u26a0\ufe0f [kitAdCreated] aucune donn\u00e9e image'); return; }\r\n\r\n    var _parts = _dataURL.split(',');\r\n    var _mime = (_parts[0].match(\/:(.*?);\/) || [])[1] || (_isPDF ? 'application\/pdf' : 'image\/png');\r\n    var _bStr = atob(_parts[1]);\r\n    var _bytes = new Uint8Array(_bStr.length);\r\n    for (var _i = 0; _i < _bStr.length; _i++) { _bytes[_i] = _bStr.charCodeAt(_i); }\r\n    var _blob = new Blob([_bytes], { type: _mime });\r\n    \/\/ \u2705 Ajouter extension si absente\r\n    var _MIME_EXT = {'image\/png':'.png','image\/jpeg':'.jpg','image\/jpg':'.jpg','image\/webp':'.webp','application\/pdf':'.pdf'};\r\n    if (_filename.indexOf('.') === -1) { _filename = _filename + (_MIME_EXT[_mime] || (_isPDF ? '.pdf' : '.png')); }\r\n    var _file = new File([_blob], _filename, { type: _mime });\r\n\r\n    \/\/ \u2705 Pr\u00e9parer StateManager\r\n    StateManager.set('Rank_Emplacement_Page_Web', _rankId);\r\n    StateManager.set('Commande_Emplacement_Page_Web', _emplacement);\r\n    StateManager.set('Formatchoisi', 'Yes');\r\n    \/\/ \u2705 Marquer le droppable directement en DOM \u2014 r\u00e9siste \u00e0 l'async, lu par styleUploadedAd\r\n    $droppable[0].setAttribute('data-kit-drop', 'true');\r\n    window._dropFromMiniature = true;\r\n\r\n    console.log('\u2705 [kitAdCreated] \u2192 UploadManager.handleFileUpload | rankId:', _rankId);\r\n    \/\/ \u2550\u2550\u2550\u2550 DIAG TEMPORAIRE v4.9ds \u2014 cha\u00eene upload kitAdCreated \u2550\u2550\u2550\u2550\r\n    console.log('\ud83d\udd0d [DIAG kit-upload] AVANT handleFileUpload | StateManager FullPathAdFile:', StateManager.get('FullPathAdFile'), '| FileReceived:', StateManager.get('FileReceived'), '| _file size:', _file.size, '| name:', _file.name);\r\n    \/\/ v4.9ds : exposer une promise globale pour que prepareUploadData puisse attendre\r\n    \/\/   l'upload avant de poster les donn\u00e9es au popup (sinon FullPathAdFile=null en BDD).\r\n    window._viaPendingUpload = window._viaPendingUpload || {};\r\n    window._viaPendingUpload[_rankId] = UploadManager.handleFileUpload(_file, $dropZone).then(function() {\r\n        console.log('\u2705 [kitAdCreated] handleFileUpload termin\u00e9');\r\n        console.log('\ud83d\udd0d [DIAG kit-upload] APR\u00c8S handleFileUpload | StateManager FullPathAdFile:', StateManager.get('FullPathAdFile'), '| FileReceived:', StateManager.get('FileReceived'), '| Upload_File_Name:', StateManager.get('Upload_File_Name'));\r\n        delete window._viaPendingUpload[_rankId];\r\n    }).catch(function(err) {\r\n        window._dropFromMiniature = false;\r\n        $droppable[0].removeAttribute('data-kit-drop');\r\n        console.error('\u274c [kitAdCreated] handleFileUpload erreur:', err);\r\n        delete window._viaPendingUpload[_rankId];\r\n    });\r\n});\r\n\r\n\/\/ \u2705 v1.17.0 : Listener \"thumbnailDropped\" \u2014 d\u00e9p\u00f4t de l'annonce depuis la miniature\r\n\/\/ =========================================================================\r\n\/**\r\n * Re\u00e7oit le drop de l'image-annonce depuis la miniature dans la page parente.\r\n * Identifie l'espace publicitaire sous le curseur, s\u00e9lectionne cet espace,\r\n * et d\u00e9clenche le flux dataFromIframeEspacePub en mode \"envoi diff\u00e9r\u00e9\"\r\n * (l'utilisateur uploadera le vrai fichier depuis le formulaire de commande).\r\n *\r\n * Coordonn\u00e9es re\u00e7ues : viewport de l'iframe (clientX\/clientY relatifs \u00e0 l'iframe)\r\n *\/\r\nwindow.addEventListener('message', function(event) {\r\n    var msg = event.data;\r\n    if (!msg || msg.action !== 'thumbnailDropped') return;\r\n\r\n    console.log('\ud83d\udce8 thumbnailDropped re\u00e7u \u2014 coords iframe:', msg.xRel, msg.yRel);\r\n\r\n    \/\/ \u2705 v1.19.3 : Corriger les coordonn\u00e9es pour le facteur de scale de l'iframe\r\n    \/\/ \u2705 v2.4.10 : 0.75 = scale appliqu\u00e9 par le PARENT sur l'\u00e9l\u00e9ment iframe (et non 0.85 qui est le scale interne OrdiMobileConteneurClass)\r\n    \/\/ Les coords xRel\/yRel viennent de getBoundingClientRect() sur l'iframe scal\u00e9 \u00e0 0.75 dans le parent \u2192 diviser par 0.75\r\n    var iframeScale = (window.outerWidth > 1000) ? 0.75 : 0.80;\r\n    \/\/ \u2705 v2.4.3 : Si Entete a intercept\u00e9 et pos\u00e9 un redirect vers Ele0A, utiliser ces coords\r\n    var _redirect = window._thumbnailDropRedirect || null;\r\n    window._thumbnailDropRedirect = null;\r\n    \/\/ \u2705 v2.4.4 : Les coords de redirect viennent de getBoundingClientRect() dans l'iframe (espace logique)\r\n    \/\/            \u2192 ne pas re-diviser par iframeScale (d\u00e9j\u00e0 en coords iframe-logiques)\r\n    var xAdjusted = _redirect ? _redirect.xRel : (msg.xRel \/ iframeScale);\r\n    var yAdjusted = _redirect ? _redirect.yRel : (msg.yRel \/ iframeScale);\r\n    if (_redirect) { console.log('\ud83d\udd00 [thumbnailDropped] coords redirig\u00e9es vers Ele0A:', Math.round(xAdjusted), Math.round(yAdjusted)); }\r\n    else { console.log('\ud83d\udcd0 Coords ajust\u00e9es (scale', iframeScale, '):', Math.round(xAdjusted), Math.round(yAdjusted)); }\r\n\r\n    \/\/ 1. Identifier l'espace publicitaire le plus proche du point de drop\r\n    \/\/    \u2705 v1.18.1 : Recherche par distance \u2014 coords \u00e9ventuellement redirig\u00e9es par Entete.txt\r\n    var allDroppables = document.querySelectorAll('.droppable');\r\n    var droppableEl = null;\r\n    var closestDist = Infinity;\r\n\r\n    \/\/ \u2705 v2.4.4 : Guard rect Ele0A \u2014 si PopUpChoice=Yes ET curseur dans le rect d'Ele0A \u2192 forcer\r\n    \/\/            (Ele0A est un popup flottant : son centre peut \u00eatre loin du curseur \u2192 algo distance l'ignore)\r\n    \/\/            Ne force PAS si le drop est hors d'Ele0A \u2192 les autres espaces restent accessibles\r\n    if (sessionStorage.getItem('PopUpChoice') === 'Yes') {\r\n        var _ele0ACheck = document.getElementById('Ele0A');\r\n        if (_ele0ACheck) {\r\n            var _r0A = _ele0ACheck.getBoundingClientRect();\r\n            var _margin = 30;\r\n            var _inR0A = (xAdjusted >= _r0A.left - _margin);\r\n            if (_inR0A) { _inR0A = (xAdjusted <= _r0A.right + _margin); }\r\n            if (_inR0A) { _inR0A = (yAdjusted >= _r0A.top - _margin); }\r\n            if (_inR0A) { _inR0A = (yAdjusted <= _r0A.bottom + _margin); }\r\n            if (_inR0A) {\r\n                droppableEl = _ele0ACheck;\r\n                closestDist = 0;\r\n                console.log('\ud83c\udfaf [thumbnailDropped] Ele0A forc\u00e9 (curseur dans rect Ele0A +30px)');\r\n            }\r\n        }\r\n    }\r\n\r\n    if (!droppableEl) {\r\n        allDroppables.forEach(function(d) {\r\n            var r = d.getBoundingClientRect();\r\n            var dCenterX = r.left + r.width  \/ 2;\r\n            var dCenterY = r.top  + r.height \/ 2;\r\n            var dist = Math.abs(dCenterX - xAdjusted) + Math.abs(dCenterY - yAdjusted);\r\n            if (dist < closestDist) { closestDist = dist; droppableEl = d; }\r\n        });\r\n        \/\/ \u2705 v2.4.3 : Si Entete a d\u00e9tect\u00e9 un drop sur Ele0A \u2192 forcer directement\r\n        if (_redirect) { if (_redirect.forceEle0A) {\r\n            var _ele0AForced = document.getElementById('Ele0A');\r\n            if (_ele0AForced) { droppableEl = _ele0AForced; console.log('\ud83c\udfaf [thumbnailDropped] Ele0A forc\u00e9 (redirect Entete)'); }\r\n        } }\r\n    }\r\n\r\n    if (!droppableEl || closestDist > 800) {\r\n        console.warn('thumbnailDropped \u2014 aucun droppable proche (dist min:', closestDist, ')');\r\n        return;\r\n    }\r\n    console.log('\ud83d\udccd Droppable trouv\u00e9:', droppableEl.id, '(dist:', Math.round(closestDist), ')');\r\n\r\n    var $droppable = jQuery(droppableEl);\r\n    var rankId = $droppable.attr('id');\r\n    if (!rankId) { console.warn('thumbnailDropped \u2014 .droppable sans id'); return; }\r\n\r\n    var $dropZone = $droppable.find('#drop_file_zone_achat').first();\r\n    if (!$dropZone.length) { console.warn('thumbnailDropped \u2014 #drop_file_zone_achat introuvable dans', rankId); return; }\r\n\r\n    var emplacementRef = StateManager.buildEmplacementReference(rankId);\r\n    console.log('\u2705 thumbnailDropped \u2014 espace pub:', rankId, '\u2192', emplacementRef);\r\n\r\n    \/\/ 2. Pr\u00e9parer l'\u00e9tat SessionStorage (comme un vrai drop de fichier)\r\n    StateManager.setMultiple({\r\n        'Rank_Emplacement_Page_Web':     rankId,\r\n        'Commande_Emplacement_Page_Web': emplacementRef,\r\n        'FirstUploadFileorMoved':        'FirstUpload',\r\n        'Formatchoisi':                  'Yes',\r\n        'Commande_Format_Transmis':      'Image',\r\n        'EnvoiUlterieur':                'false',\r\n        'FileReceived':                  'No',\r\n        'AdDisplayed':                   'Yes'\r\n    });\r\n\r\n    UIManager.updateEmplacementDisplay();\r\n\r\n    \/\/ 3. Construire le File \u00e0 partir du PDF (communiqu\u00e9\/interview) ou de l'image PNG (autres formats)\r\n    \/\/    puis appeler handleFileUpload \u2192 m\u00eame flux qu'un vrai drag&drop : liser\u00e9 vert, aper\u00e7u, R\u00e9server\r\n    var dataURL, mime, ext;\r\n\r\n    \/\/ \u2705 Stocker le PDF image et le format sur le droppable pour le popup de visualisation inline\r\n    if (msg.pdfImageDataURL) {\r\n        $droppable.data('kitPdfImageDataURL', msg.pdfImageDataURL);\r\n        $droppable.data('kitFormatSelect', msg.FormatSelect || '');\r\n        console.log('\ud83d\udcce pdfImageDataURL stock\u00e9 sur', rankId, '| format:', msg.FormatSelect);\r\n    } else {\r\n        $droppable.removeData('kitPdfImageDataURL');\r\n        $droppable.removeData('kitFormatSelect');\r\n    }\r\n    \/\/ v4.9ds : 1\u00e8re image utilisateur extraite par le kit (pour preview mobile JPG-in-PDF)\r\n    if (msg.firstImageDataURL) {\r\n        $droppable.data('kitFirstImageDataURL', msg.firstImageDataURL);\r\n        console.log('\ud83d\uddbc\ufe0f kitFirstImageDataURL stock\u00e9 sur', rankId);\r\n        try { var _dl1 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl1) _dl1('[v4.9ds] thumbnailDropped firstImg stock\u00e9 ' + rankId, 'ok'); } catch(e) {}\r\n    } else {\r\n        $droppable.removeData('kitFirstImageDataURL');\r\n        try { var _dl2 = (window.top || window).top._viaDebugLog || (window.top || window)._viaDebugLog; if (_dl2) _dl2('[v4.9ds] thumbnailDropped SANS firstImg ' + rankId, 'warn'); } catch(e) {}\r\n    }\r\n\r\n    if (msg.isPDF ? msg.pdfDataURL : false) {\r\n        \/\/ \u2500\u2500 Format PDF (communiqu\u00e9 \/ interview) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        dataURL = msg.pdfDataURL;                         \/\/ \"data:application\/pdf;base64,\u2026\"\r\n        mime    = 'application\/pdf';\r\n        ext     = '.pdf';\r\n    } else if (msg.imageDataURL) {\r\n        \/\/ \u2500\u2500 Format image (banni\u00e8re \/ parrainage) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        dataURL = msg.imageDataURL;\r\n        var mimeMatch = dataURL.match(\/^data:([^;]+);base64,\/);\r\n        mime    = mimeMatch ? mimeMatch[1] : 'image\/png';\r\n        ext     = mime === 'image\/jpeg' ? '.jpg' : '.png';\r\n    } else {\r\n        console.warn('thumbnailDropped \u2014 aucune donn\u00e9e image\/pdf disponible');\r\n        return;\r\n    }\r\n\r\n    var baseName = msg.filename ? msg.filename.replace(\/\\.[^.]+$\/, '') : 'annonce-kit';\r\n    var fileName = baseName + ext;\r\n\r\n    \/\/ dataURL \u2192 Uint8Array \u2192 Blob \u2192 File\r\n    var b64  = dataURL.split(',')[1];\r\n    var bstr = atob(b64);\r\n    var u8   = new Uint8Array(bstr.length);\r\n    for (var i = 0; i < bstr.length; i++) { u8[i] = bstr.charCodeAt(i); }\r\n    var blob    = new Blob([u8], { type: mime });\r\n    var fileObj = new File([blob], fileName, { type: mime });\r\n\r\n    console.log('thumbnailDropped \u2192 handleFileUpload:', fileName, Math.round(fileObj.size \/ 1024) + 'KB');\r\n\r\n    \/\/ \u2705 Cacher le File pour r\u00e9utilisation lors des d\u00e9placements (\u00e9vite CORS)\r\n    window._lastRedactionnelFile = fileObj;\r\n\r\n    \/\/ \u2705 v2.4.10 : Signaler \u00e0 adjustDesktopLayout que c'est un drop depuis la miniature\r\n    \/\/ \u2192 applique max-height sur HTMLUploadfileConteneur pour \u00e9viter le d\u00e9bordement vertical (homepage corps de page)\r\n    window._dropFromMiniature = true;\r\n    \/\/ \u2705 v2.6 : Poser data-from-miniature sur le droppable pour que selectEspaceActif ignore le scale(1.35)\r\n    \/\/ Sans ce flag, selectEspaceActif applique scale(1.35) sur .HTMLUploadfileConteneur \u2192 zoom Communiqu\u00e9\/Interview\r\n    if ($droppable[0]) { $droppable[0].setAttribute('data-from-miniature', 'true'); }\r\n    UploadManager.handleFileUpload(fileObj, $dropZone);\r\n\r\n    \/\/ \u2705 v1.19.1 : Positionner l'espace \u00e0 10px du haut du viewport (tous formats)\r\n    \/\/ \u2705 v2.4.13 : Skip si drop depuis miniature \u2014 selectEspaceActif g\u00e8re d\u00e9j\u00e0 le scroll\r\n    if (!window._dropFromMiniature) {\r\n        setTimeout(function() {\r\n            var el = $droppable.find('.HTMLUploadfileConteneur')[0] || $droppable[0];\r\n            if (el) {\r\n                ScrollHelper.scrollElementTo(el, 10);\r\n            }\r\n        }, 400);\r\n    }\r\n});\r\n\r\nconsole.log('This is a ' + (UIManager.isDesktop() ? 'desktop' : 'non-desktop') + ' device.');\r\n\r\n\/\/ v2.9 : Masquer les zones pub en mode previsualisation achat\r\nwindow.addEventListener('message', function(e) {\r\n    if (!e.data) { return; }\r\n    if (e.data.action !== 'removeElements') { return; }\r\n    if (!e.data.viaPurchasePreview) { return; }\r\n    var _sid = 'via-purchase-preview-css';\r\n    if (!document.getElementById(_sid)) {\r\n        var _s = document.createElement('style');\r\n        _s.id = _sid;\r\n        _s.textContent = [\r\n            \/* Masquer le formulaire upload complet en mode previsualisation *\/\r\n            '.droppable .OrdiMobileConteneurClass { display: none !important; }',\r\n            '.droppable .reserver-dynamic-container { display: none !important; }',\r\n            '.droppable .HTMLUploadfileConteneur { display: none !important; }',\r\n            '.droppable .EspPubFormatMainContainer { display: none !important; }',\r\n            '.droppable .GlisserDeposerConteneur { display: none !important; }',\r\n            '.droppable .OUClass { display: none !important; }',\r\n            '.droppable .ChoisirEspacePublicitaireClass { display: none !important; }',\r\n            '.droppable .ChoisirEspacePublicitaire2ndLigne { display: none !important; }',\r\n            '.droppable .UploadIci { display: none !important; }',\r\n            '.droppable .EnvoiUlterieurContainer { display: none !important; }',\r\n            '.droppable .TexteMobileAnnonce { display: none !important; }',\r\n            '.droppable .AdDroppedTextNotDisplayed { display: none !important; }',\r\n            '.droppable .HideFormButton { display: none !important; }'\r\n        ].join('\\n');\r\n        document.head.appendChild(_s);\r\n    }\r\n    window.parent.postMessage({ type: 'elementsRemoved' }, '*');\r\n});\r\n\r\n\/\/ \u2705 Bug 10 : handler DelAdInIframe\r\nwindow.addEventListener('message', function(e) {\r\n    var msg = e.data;\r\n    if (!msg || msg.action !== 'DelAdInIframe') return;\r\n    var rankId = msg.RankId || '';\r\n    if (!rankId) return;\r\n    console.log('[Bug10] DelAdInIframe re\u00e7u | rank:', rankId);\r\n    var $droppable = jQuery('#' + rankId);\r\n    if (!$droppable.length) return;\r\n    var $cont = $droppable.find('.OrdiMobileConteneurClass').first();\r\n    var el = $cont.length ? $cont[0] : $droppable[0];\r\n    if (typeof RestoreadSpaceTemplateLocal === 'function') RestoreadSpaceTemplateLocal(el);\r\n    else if (typeof window.RestoreadSpaceTemplate === 'function') window.RestoreadSpaceTemplate(el);\r\n    console.log('[Bug10] espace r\u00e9initialis\u00e9 | rank:', rankId);\r\n});\r\n\r\n\/\/ ============================================================================\r\n\/\/ v4.9ds : Num\u00e9rotation 1\/2\/3 + croix \"Effacer\" sur les espaces publicitaires\r\n\/\/   - Num\u00e9ro 1 : \u00e0 hauteur de la zone Formats (.EspPubFormatMainContainer)\r\n\/\/   - Num\u00e9ro 2 : \u00e0 hauteur de la zone Glisser-d\u00e9poser (.UploadFileConteneur)\r\n\/\/   - Num\u00e9ro 3 : \u00e0 hauteur du label R\u00e9server (statique ou dynamique)\r\n\/\/   - Croix : haut-droite du wrapper visuel (.OrdiMobileConteneurClass), title=\"Effacer\"\r\n\/\/     simple X blanc sans fond ni bordure.\r\n\/\/   Couleurs num\u00e9ros : dor\u00e9 (#D0C067) Ele0A, bleu clair (#9FC5F3) Ele1A+\r\n\/\/   Tous les num\u00e9ros sont plac\u00e9s sur le .droppable directement (top calcul\u00e9 depuis\r\n\/\/   les ancres) \u2192 align\u00e9s verticalement sur la m\u00eame colonne left:8px.\r\n\/\/   Quand annonce d\u00e9pos\u00e9e (.via-ad-header pr\u00e9sent) \u2192 tous masqu\u00e9s (.via-ad-loaded).\r\n\/\/ ============================================================================\r\n(function() {\r\n    if (window._viaNumCroixReady) return;\r\n    window._viaNumCroixReady = true;\r\n\r\n    function _injectStyles() {\r\n        \/\/ \u2705 v4.9ds : marquer le body si la page est dans une iframe (cas du site r\u00e9gie qui\r\n        \/\/   charge les pages pays). Permet \u00e0 la r\u00e8gle CSS .via-step-num de masquer les\r\n        \/\/   num\u00e9ros 1\/2\/3 dans ce contexte. Demande user.\r\n        try {\r\n            if (window !== window.top) {\r\n                if (document.body) {\r\n                    document.body.classList.add('via-page-in-iframe');\r\n                } else {\r\n                    \/\/ Body pas encore pr\u00eat \u2192 re-tenter d\u00e8s qu'il l'est\r\n                    document.addEventListener('DOMContentLoaded', function() {\r\n                        if (document.body) { document.body.classList.add('via-page-in-iframe'); }\r\n                    });\r\n                }\r\n            }\r\n        } catch(_eIf) { \/* cross-origin silencieux *\/ }\r\n        if (document.getElementById('via-num-croix-styles')) return;\r\n        var s = document.createElement('style');\r\n        s.id = 'via-num-croix-styles';\r\n        s.textContent =\r\n            \/\/ Num\u00e9ros mobile (par d\u00e9faut) : 20\u00d720, font 12, left:4\r\n            '.via-step-num{position:absolute;left:4px;width:20px;height:20px;border-radius:50%;' +\r\n              'background:#ffffff;display:flex;align-items:center;justify-content:center;' +\r\n              'font-family:Roboto,Arial,sans-serif;font-weight:700;font-size:12px;line-height:1;' +\r\n              'box-shadow:0 1px 3px rgba(0,0,0,0.15);z-index:10;pointer-events:none;' +\r\n              'transform:translateY(-50%);transition:opacity 0.2s}' +\r\n            \/\/ Desktop (\u22651000px) : +40% (20\u219228, font 12\u219217), left:13 (mobile +9)\r\n            '@media (min-width:1000px){' +\r\n              '.via-step-num{width:28px;height:28px;font-size:17px;left:13px}' +\r\n            '}' +\r\n            \/\/ Couleurs avec sp\u00e9cificit\u00e9 forte + !important\r\n            'html body .via-step-num.via-num-ele0a{color:#F1C40F !important}' +\r\n            'html body .via-step-num.via-num-ele1a{color:#9FC5F3 !important}' +\r\n            \/\/ v4.9ds : \u00e9tapes valid\u00e9es (num\u00e9ros pr\u00e9c\u00e9dents l'\u00e9tape courante) \u2192 vert\r\n            \/\/   Logique data-via-format-gate (cf _updateStepGate) :\r\n            \/\/     - \"locked\" = \u00e9tape 1 active   \u2192 aucun num\u00e9ro vert\r\n            \/\/     - \"step2\"  = \u00e9tape 2 active   \u2192 num\u00e9ro 1 vert (\u00e9tape 1 valid\u00e9e)\r\n            \/\/     - (absent) = \u00e9tape 3 active   \u2192 num\u00e9ros 1 et 2 verts (\u00e9tapes 1+2 valid\u00e9es)\r\n            \/\/   \u00c9tape 4 (R\u00e9server coch\u00e9) \u2192 num\u00e9ros 1, 2 et 3 verts (les 3 \u00e9tapes valid\u00e9es).\r\n            \/\/   D\u00e9tection R\u00e9server : pr\u00e9sence de input[name=\"form_fields[ReserverEspacePublicitaire]\"]:checked\r\n            \/\/   dans la droppable. Le s\u00e9lecteur :has() est support\u00e9 par tous les navigateurs\r\n            \/\/   modernes (Safari 15.4+, Chrome 105+, Firefox 121+) \u2014 si un user a un\r\n            \/\/   navigateur trop ancien, le num\u00e9ro 3 reste \u00e0 sa couleur d'origine (jaune\/bleu),\r\n            \/\/   les 1 et 2 restent verts par les r\u00e8gles pr\u00e9c\u00e9dentes \u2014 pas de r\u00e9gression.\r\n            \/\/   On surcharge la couleur du chiffre via !important + sp\u00e9cificit\u00e9 combin\u00e9e\r\n            \/\/   pour battre la r\u00e8gle de couleur jaune\/bleu pos\u00e9e juste au-dessus.\r\n            \/\/   Vert utilis\u00e9 : #2ECC71 (coh\u00e9rent avec les liser\u00e9s de s\u00e9lection vifs).\r\n            \/\/ \u00c9tape 2 active \u2192 num\u00e9ro 1 valid\u00e9\r\n            'html body .droppable[data-via-format-gate=\"step2\"] .via-step-num[data-via-num=\"1\"],' +\r\n            \/\/ \u00c9tape 3 active (annonce d\u00e9pos\u00e9e OU envoi diff\u00e9r\u00e9 coch\u00e9 \u2014 pas d'attribut gate) \u2192 num\u00e9ros 1 et 2 valid\u00e9s\r\n            'html body .droppable:not([data-via-format-gate]) .via-step-num[data-via-num=\"1\"],' +\r\n            'html body .droppable:not([data-via-format-gate]) .via-step-num[data-via-num=\"2\"],' +\r\n            \/\/ \u00c9tape 4 : R\u00e9server coch\u00e9 \u2192 num\u00e9ro 3 valid\u00e9 (en plus de 1 et 2 d\u00e9j\u00e0 verts via r\u00e8gles ci-dessus)\r\n            'html body .droppable:has(input[name=\"form_fields[ReserverEspacePublicitaire]\"]:checked) .via-step-num[data-via-num=\"3\"]{' +\r\n              'color:#2ECC71 !important' +\r\n            '}' +\r\n            \/\/ Croix mobile : top:0 right:-2 (d\u00e9calage 2px vers la droite demand\u00e9), font 14\r\n            \/\/   pointer-events:auto !important : override l'h\u00e9ritage de pointer-events:none\r\n            \/\/     pos\u00e9 par le format-gate \"locked\" sur le parent .HTMLUploadfileConteneur\r\n            \/\/   z-index 999 : passe AU-DESSUS de tous les \u00e9l\u00e9ments locaux du droppable\r\n            \/\/     (widgets Elementor, overlays format, etc.) MAIS reste SOUS la pastille\r\n            \/\/     jaune .popupAchatAnnonce qui vit dans EnteteBackground (stacking context\r\n            \/\/     isol\u00e9 z:1000 \u2192 pastille plafonn\u00e9e ~1000 au niveau body).\r\n            \/\/   v4.9ds Fix 29 v2 : z:100 toujours insuffisant \u2014 un \u00e9l\u00e9ment local du\r\n            \/\/     droppable avec z-index entre 100 et la pastille captait encore le clic.\r\n            \/\/     Remont\u00e9 \u00e0 999 pour passer au-dessus de tout sauf la pastille.\r\n            \/\/   padding 6 8 : zone de clic plus large (mobile)\r\n            '.via-erase-btn{position:absolute;top:0px;right:-2px;' +\r\n              'cursor:pointer;z-index:999;pointer-events:auto !important;' +\r\n              'font-family:Roboto,Arial,sans-serif;font-weight:900;font-size:14px;line-height:1;' +\r\n              'color:#ffffff;user-select:none;padding:6px 8px;' +\r\n              'background:transparent !important;border:none !important;border-radius:0 !important;' +\r\n              'box-shadow:none !important;text-shadow:none !important}' +\r\n            \/\/ Desktop : croix top:3 right:3, font 16 (taille originale)\r\n            '@media (min-width:1000px){' +\r\n              '.via-erase-btn{top:3px;right:3px;font-size:16px}' +\r\n            '}' +\r\n            '.via-erase-btn:hover{opacity:0.8}' +\r\n            \/\/ Quand annonce d\u00e9pos\u00e9e \u2192 masquer imm\u00e9diatement num\u00e9ros + croix\r\n            \/\/   D\u00e9tection par 2 signaux pour activation au plus t\u00f4t :\r\n            \/\/   - data-via-ad-loaded=\"true\" (pos\u00e9 par espace_publicitaire.txt:805 d\u00e8s d\u00e9p\u00f4t)\r\n            \/\/   - .via-ad-loaded (classe pos\u00e9e par mon _updateAdLoadedClass via MutationObserver)\r\n            'html body .droppable[data-via-ad-loaded=\"true\"] .via-step-num,' +\r\n            'html body .droppable[data-via-ad-loaded=\"true\"] .via-erase-btn,' +\r\n            'html body .droppable.via-ad-loaded .via-step-num,' +\r\n            'html body .droppable.via-ad-loaded .via-erase-btn{display:none !important}' +\r\n            \/\/ \u2705 v4.9ds : sur le site r\u00e9gie, les pages pays sont charg\u00e9es dans une iframe.\r\n            \/\/   L'utilisateur a demand\u00e9 que les num\u00e9ros 1\/2\/3 ne soient pas affich\u00e9s dans\r\n            \/\/   ce contexte (coh\u00e9rent : sur la r\u00e9gie, le user manipule un panier \u2014 pas un\r\n            \/\/   process d'\u00e9tapes complet, donc les num\u00e9ros n'ont pas de sens).\r\n            \/\/   D\u00e9tection : window !== window.top (la page est dans une iframe). Marqueur\r\n            \/\/   pos\u00e9 en classe sur <body> pour permettre une r\u00e8gle CSS pure dans ce bloc.\r\n            \/\/   Le marqueur est pos\u00e9 via JS plus bas (apr\u00e8s DOM ready).\r\n            'html body.via-page-in-iframe .via-step-num{display:none !important}' +\r\n            \/\/ Conteneur ancr\u00e9 en relative + overflow visible (num\u00e9ros pouvant d\u00e9border)\r\n            '.via-num-anchor{position:relative !important;overflow:visible !important}' +\r\n            \/\/ \u2500\u2500 OPACIT\u00c9S DES NUM\u00c9ROS calqu\u00e9es sur les zones d\u00e9sactiv\u00e9es \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/   Num\u00e9ro 2 (zone GD+Envoi diff\u00e9r\u00e9) : opacit\u00e9 0.6 quand \"locked\" (\u00e9tape 1)\r\n            \/\/   Num\u00e9ro 3 (zone R\u00e9server) : opacit\u00e9 0.6 quand \"locked\" OU \"step2\" (\u00e9tapes 1+2)\r\n            'html body .droppable[data-via-format-gate=\"locked\"] .via-step-num[data-via-num=\"2\"],' +\r\n            'html body .droppable[data-via-format-gate=\"locked\"] .via-step-num[data-via-num=\"3\"],' +\r\n            'html body .droppable[data-via-format-gate=\"step2\"] .via-step-num[data-via-num=\"3\"]{' +\r\n              'opacity:0.6}' +\r\n            \/\/ \u2500\u2500 v4.9ds : OVERRIDES SITE R\u00c9GIE (html.via-regie-iframe) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/   Sur la r\u00e9gie (iframe yearbook-iframe sous la page r\u00e9gie), demande user :\r\n            \/\/   - Mobile : 10px en dessous du bouton .HideFormButton.ReserverBouton\r\n            \/\/              (espace pub plus haut visuellement par le bas)\r\n            \/\/   - Le `left` est pos\u00e9 EN INLINE dans _placeNum (CSS pouvait \u00eatre outrepass\u00e9)\r\n            \/\/   - D\u00e9calage vertical N\u00b02\/N\u00b03 desktop+mobile g\u00e9r\u00e9 dans _numTopOffset\r\n            '@media (max-width:999px){' +\r\n              'html.via-regie-iframe .HideFormButton.ReserverBouton{margin-bottom:10px !important}' +\r\n            '}';\r\n        document.head.appendChild(s);\r\n    }\r\n\r\n    function _makeNum(n, isEle0A) {\r\n        var span = document.createElement('span');\r\n        span.className = 'via-step-num ' + (isEle0A ? 'via-num-ele0a' : 'via-num-ele1a');\r\n        span.textContent = String(n);\r\n        span.setAttribute('data-via-num', String(n));\r\n        return span;\r\n    }\r\n\r\n    function _makeEraseBtn() {\r\n        var btn = document.createElement('span');\r\n        btn.className = 'via-erase-btn';\r\n        btn.setAttribute('title', 'Effacer');\r\n        btn.setAttribute('data-via-erase', '1');\r\n        btn.textContent = '\\u2715'; \/\/ \u2715\r\n        return btn;\r\n    }\r\n\r\n    \/\/ Trouver l'ancre pour un num\u00e9ro donn\u00e9\r\n    function _findAnchor(drop, n) {\r\n        var $drop = jQuery(drop);\r\n        \/\/ Pour n=3, .ReserverContainer est un FR\u00c8RE du droppable (pas un descendant) \u2192\r\n        \/\/   \u00e9largir le scope au parent. Pour n=1 et n=2 on reste dans le droppable.\r\n        var $scope = (n === 3) ? $drop.parent() : $drop;\r\n        \/\/ Helper : essayer plusieurs s\u00e9lecteurs dans l'ordre, retourner le premier visible non vide\r\n        function _firstVisible(selectors) {\r\n            for (var i = 0; i < selectors.length; i++) {\r\n                var $set = $scope.find(selectors[i]);\r\n                var $vis = $set.filter(':visible').filter(function() {\r\n                    var r = this.getBoundingClientRect();\r\n                    return r.width > 0 ? r.height > 0 : false;\r\n                });\r\n                if ($vis.length) {\r\n                    return { el: $vis.first()[0], sel: selectors[i] };\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n        \/\/ Variante permissive : ne filtre PAS sur :visible (utile pour R\u00e9server qui peut\r\n        \/\/   \u00eatre en pointer-events:none ou opacity:0 mais avec dimensions r\u00e9elles)\r\n        function _firstWithDims(selectors) {\r\n            for (var i = 0; i < selectors.length; i++) {\r\n                var $set = $scope.find(selectors[i]);\r\n                for (var j = 0; j < $set.length; j++) {\r\n                    var el = $set[j];\r\n                    var r = el.getBoundingClientRect();\r\n                    if (r.width > 0 ? r.height > 0 : false) {\r\n                        return { el: el, sel: selectors[i] + ' (permissif)' };\r\n                    }\r\n                }\r\n            }\r\n            return null;\r\n        }\r\n        if (n === 1) {\r\n            return _firstVisible([\r\n                '.SelectionFormatTitre',\r\n                '.EspPubFormatMainContainer .EspPubFormatContainer'\r\n            ]);\r\n        }\r\n        if (n === 2) {\r\n            return _firstVisible([\r\n                '.UploadIci',\r\n                '#drop_file_zone_achat .UploadIci',\r\n                '#drag_upload_file_achat',\r\n                '#drop_file_zone_achat',\r\n                '.GlisserDeposerConteneur'\r\n            ]);\r\n        }\r\n        if (n === 3) {\r\n            \/\/ Bouton R\u00e9server Elementor : .ReserverContainer \/ .ReserverBouton sont\r\n            \/\/   FR\u00c8RES du droppable (cf HTML user). Le label visible est dans\r\n            \/\/   .elementor-field-subgroup label (le screen-only n'a pas de dimensions).\r\n            return _firstVisible([\r\n                'label[for=\"form-field-ReserverEspacePublicitaire-0\"]',  \/\/ label visible\r\n                '.ReserverBouton .elementor-field-subgroup label',\r\n                '.ReserverContainer .elementor-field-subgroup label',\r\n                '.ReserverBouton',\r\n                '.ReserverContainer',\r\n                '.reserver-dynamic-label',\r\n                '.reserver-dynamic-container'\r\n            ]) || _firstWithDims([\r\n                '.ReserverBouton',\r\n                '.ReserverContainer',\r\n                '.elementor-field-group-ReserverEspacePublicitaire'\r\n            ]) || (function() {\r\n                \/\/ Dernier recours : recherche par TEXTE dans le scope \u00e9largi\r\n                var found = null;\r\n                $scope.find('label, span, p, div').each(function() {\r\n                    if (found) return;\r\n                    var t = (this.textContent || '').replace(\/\\s+\/g, ' ').trim();\r\n                    if (t.indexOf('R\u00e9server cet espace') === -1) return;\r\n                    var hasChildWithText = jQuery(this).children().toArray().some(function(c) {\r\n                        return ((c.textContent || '').replace(\/\\s+\/g, ' ').trim()).indexOf('R\u00e9server cet espace') !== -1;\r\n                    });\r\n                    if (hasChildWithText) return;\r\n                    var r = this.getBoundingClientRect();\r\n                    if (r.width > 0 ? r.height > 0 : false) {\r\n                        found = this;\r\n                    }\r\n                });\r\n                return found ? { el: found, sel: ':contains(R\u00e9server) (texte)' } : null;\r\n            })();\r\n        }\r\n        return null;\r\n    }\r\n\r\n    \/\/ D\u00e9calage vertical (en px) \u00e0 appliquer \u00e0 un num\u00e9ro selon position + plateforme + Ele0A vs Ele1A+\r\n    \/\/   Base = 40\r\n    \/\/   Desktop  : N\u00b01 = 47 \/ N\u00b02 = 70 \/ N\u00b03 = 56 (Ele0A et Ele1A+ identiques)\r\n    \/\/   Mobile Ele1A+ : N\u00b01 = 24 \/ N\u00b02 = 23 \/ N\u00b03 = 1\r\n    \/\/   Mobile Ele0A  : N\u00b01 = 42 \/ N\u00b02 = 32 \/ N\u00b03 = 9\r\n    \/\/   v4.9ds : Compensation Ele0A mobile quand bouton Cr\u00e9ation est actif (creationActive===true).\r\n    \/\/     Le clic sur Cr\u00e9ation d\u00e9clenche un effet de layout (probablement repaint Elementor \/\r\n    \/\/     stacking flexbox \/ re-application de styles inline) qui d\u00e9cale les 3 num\u00e9ros de\r\n    \/\/     +10px vers le bas. On compense ici pour stabilit\u00e9 visuelle. Au toggle OFF\r\n    \/\/     (re-click Cr\u00e9ation), creationActive repasse \u00e0 false \u2192 compensation 0 \u2192 num\u00e9ros\r\n    \/\/     reviennent \u00e0 leur position d'origine.\r\n    \/\/   v4.9ds : d\u00e9calage vertical SITE R\u00c9GIE (Ele1A+) \u2014 demande user :\r\n    \/\/     - Desktop r\u00e9gie Ele1A+ : N\u00b01 = 0, N\u00b02 -10, N\u00b03 -15 (vers le haut)\r\n    \/\/     - Mobile r\u00e9gie Ele1A+  : N\u00b01 = 0, N\u00b02 +10 (bas), N\u00b03 = 0\r\n    \/\/   Le d\u00e9calage horizontal -3px desktop est pos\u00e9 en inline dans _placeNum.\r\n    function _numTopOffset(n, isEle0A, drop) {\r\n        var isMobile = window.innerWidth < 1000;\r\n        var base = 40;\r\n        \/\/ v4.9ds : compensation _creaComp retir\u00e9e \u2014 les num\u00e9ros restent fixes\r\n        \/\/   peu importe le format s\u00e9lectionn\u00e9. ResizeObserver g\u00e8re d\u00e9sormais les\r\n        \/\/   reflows r\u00e9els (cf _viaNumObserve). Les ancres bougent peu au clic\r\n        \/\/   format en r\u00e9alit\u00e9, le d\u00e9calage per\u00e7u \u00e9tait d\u00fb au calcul fait juste\r\n        \/\/   avant que le re-layout Elementor soit appliqu\u00e9.\r\n        \/\/ v4.9ds : d\u00e9calage r\u00e9gie pour Ele1A+ uniquement\r\n        \/\/   Desktop : N\u00b02 -10, N\u00b03 -15 ; Mobile : N\u00b02 +10 ; reste \u00e0 0\r\n        var _regieCompN1 = 0;\r\n        var _regieCompN2 = 0;\r\n        var _regieCompN3 = 0;\r\n        if (!isEle0A) {\r\n            var _isRegie = document.documentElement.classList.contains('via-regie-iframe');\r\n            if (_isRegie) {\r\n                if (isMobile) {\r\n                    _regieCompN2 = 10;\r\n                } else {\r\n                    _regieCompN2 = -10;\r\n                    _regieCompN3 = -15;\r\n                }\r\n            }\r\n        }\r\n        if (isMobile) {\r\n            if (isEle0A) {\r\n                if (n === 1) return base + 2;    \/\/ 42\r\n                if (n === 2) return base - 18;   \/\/ 22\r\n                return base - 41;                 \/\/ -1\r\n            }\r\n            \/\/ Ele1A+ mobile\r\n            if (n === 1) return base - 16 + _regieCompN1;    \/\/ 24\r\n            if (n === 2) return base - 17 + _regieCompN2;    \/\/ 23 \u2192 33 r\u00e9gie\r\n            return base - 39 + _regieCompN3;                  \/\/  1\r\n        } else {\r\n            \/\/ Desktop (Ele0A et Ele1A+ identiques sauf r\u00e9gie sur Ele1A+)\r\n            if (n === 1) return base + 7 + _regieCompN1;     \/\/ 47\r\n            if (n === 2) return base + 30 + _regieCompN2;    \/\/ 70 \u2192 60 r\u00e9gie\r\n            return base + 16 + _regieCompN3;                  \/\/ 56 \u2192 41 r\u00e9gie\r\n        }\r\n    }\r\n\r\n    \/\/ Placer (ou repositionner) un num\u00e9ro dans le container selon son ancre\r\n    \/\/ v4.9ds : ResizeObserver global pour stabiliser les positions des num\u00e9ros\r\n    \/\/   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    \/\/   Probl\u00e8me de fond : les num\u00e9ros 1\/2\/3 sont positionn\u00e9s en `position:absolute;\r\n    \/\/   top:<calcul\u00e9>` \u00e0 partir de `getBoundingClientRect()` sur leur ancre. Ce calcul\r\n    \/\/   est un snapshot \u2014 si l'ancre bouge ensuite (Elementor async, images charg\u00e9es,\r\n    \/\/   fonts custom, transitions, transforms\u2026), le num\u00e9ro reste fig\u00e9.\r\n    \/\/   \r\n    \/\/   Solution : observer les changements de TAILLE\/POSITION des ancres et du\r\n    \/\/   conteneur via ResizeObserver. Quand un changement est d\u00e9tect\u00e9, on relance\r\n    \/\/   le calcul automatiquement. Plus propre que les passes setTimeout (instantan\u00e9,\r\n    \/\/   pas de polling, d\u00e9clench\u00e9 exactement quand c'est n\u00e9cessaire).\r\n    \/\/   \r\n    \/\/   Support : Safari 13.1+, Chrome 64+, Firefox 69+ (>97% du march\u00e9).\r\n    \/\/   \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n    var _viaNumResizeObs = null;\r\n    var _viaNumObservedEls = (typeof WeakSet !== 'undefined') ? new WeakSet() : null;\r\n    var _viaNumScheduleAfterRO = null; \/\/ inject\u00e9 par _installWatcher au boot\r\n    function _ensureNumResizeObs() {\r\n        if (_viaNumResizeObs) return _viaNumResizeObs;\r\n        if (typeof ResizeObserver === 'undefined') return null; \/\/ navigateur trop ancien\r\n        _viaNumResizeObs = new ResizeObserver(function() {\r\n            \/\/ Replanification debounced via _processAll (passe par _scheduleProcess\r\n            \/\/   du watcher pour rAF debounce, sinon fallback direct)\r\n            if (typeof _viaNumScheduleAfterRO === 'function') {\r\n                _viaNumScheduleAfterRO();\r\n            } else {\r\n                try { _processAll(); } catch (_e) {}\r\n            }\r\n        });\r\n        return _viaNumResizeObs;\r\n    }\r\n    function _viaNumObserve(el) {\r\n        if (!el) return;\r\n        var ro = _ensureNumResizeObs();\r\n        if (!ro) return;\r\n        if (_viaNumObservedEls) {\r\n            if (_viaNumObservedEls.has(el)) return;\r\n            _viaNumObservedEls.add(el);\r\n        }\r\n        try { ro.observe(el); } catch (_e) {}\r\n    }\r\n\r\n    function _placeNum(container, drop, n, isEle0A) {\r\n        var num = container.querySelector(':scope > .via-step-num[data-via-num=\"' + n + '\"]');\r\n        if (!num) {\r\n            num = _makeNum(n, isEle0A);\r\n            container.appendChild(num);\r\n        }\r\n        \/\/ Forcer la bonne classe couleur \u00e0 chaque passe (au cas o\u00f9 isEle0A aurait chang\u00e9,\r\n        \/\/ ou si une classe parasite a \u00e9t\u00e9 pos\u00e9e \u00e0 la cr\u00e9ation)\r\n        var wantedClass = 'via-step-num ' + (isEle0A ? 'via-num-ele0a' : 'via-num-ele1a');\r\n        if (num.className !== wantedClass) { num.className = wantedClass; }\r\n\r\n        \/\/ v4.9ds : forcer le `left` inline (le CSS via-regie-iframe peut \u00eatre outrepass\u00e9\r\n        \/\/   par des r\u00e8gles plus sp\u00e9cifiques dans certains cas). Hors r\u00e9gie : valeurs par\r\n        \/\/   d\u00e9faut (4 mobile \/ 13 desktop). R\u00e9gie : -3px desktop (10), inchang\u00e9 mobile (4).\r\n        var _isRegieLeft = document.documentElement.classList.contains('via-regie-iframe');\r\n        var _isMobLeft = window.innerWidth < 1000;\r\n        var _leftPx;\r\n        if (_isRegieLeft) {\r\n            _leftPx = _isMobLeft ? 4 : 10;\r\n        } else {\r\n            _leftPx = _isMobLeft ? 4 : 13;\r\n        }\r\n        num.style.setProperty('left', _leftPx + 'px', 'important');\r\n\r\n        var anchorInfo = _findAnchor(drop, n);\r\n        var anchor = anchorInfo ? anchorInfo.el : null;\r\n\r\n        if (anchor) {\r\n            \/\/ v4.9ds : observer l'ancre \u2014 son moindre changement de taille\/position\r\n            \/\/   relancera le calcul automatiquement (pas de polling setTimeout).\r\n            _viaNumObserve(anchor);\r\n            var ancRect = anchor.getBoundingClientRect();\r\n            if (ancRect.height > 0 ? ancRect.width > 0 : false) {\r\n                var contRect = container.getBoundingClientRect();\r\n                num.style.display = '';\r\n                num.style.top = (ancRect.top - contRect.top + ancRect.height \/ 2 + _numTopOffset(n, isEle0A, drop)) + 'px';\r\n                return;\r\n            }\r\n        }\r\n\r\n        \/\/ Pas d'ancre exploitable\r\n        if (n === 3) {\r\n            \/\/ Pour le 3, fallback fixe en bas du container \u2014 mais seulement si le container\r\n            \/\/   a une hauteur exploitable (sinon le num\u00e9ro serait \u00e0 top:30 invisible).\r\n            var contRect3 = container.getBoundingClientRect();\r\n            if (contRect3.height > 50) {\r\n                num.style.display = '';\r\n                num.style.top = (contRect3.height + 30) + 'px';\r\n            } else {\r\n                num.style.display = 'none';\r\n            }\r\n            return;\r\n        }\r\n        \/\/ 1 et 2 : masquer si pas d'ancre\r\n        num.style.display = 'none';\r\n    }\r\n\r\n    \/\/ D\u00e9corer un seul espace .droppable\r\n    function _decorateDroppable(drop) {\r\n        if (!drop) return;\r\n        var rankId = drop.id || '';\r\n        var isEle0A = rankId === 'Ele0A';\r\n        var isEleNA = \/^Ele\\d+A$\/.test(rankId);\r\n        if (!isEle0A ? !isEleNA : false) return;\r\n\r\n        var $drop = jQuery(drop);\r\n        \/\/ Conteneur pour num\u00e9ros + croix = #UploadFileConteneur (wrapper visuel color\u00e9)\r\n        var container = drop.querySelector('#UploadFileConteneur')\r\n            || drop.querySelector('.UploadFileConteneur')\r\n            || drop;\r\n        jQuery(container).addClass('via-num-anchor');\r\n\r\n        \/\/ v4.9ds : observer le container et le droppable \u2014 un changement de taille\r\n        \/\/   sur l'un de ces \u00e9l\u00e9ments d\u00e9cale aussi les num\u00e9ros (via contRect dans\r\n        \/\/   le calcul de top). Idempotent gr\u00e2ce au WeakSet anti-doublons.\r\n        _viaNumObserve(container);\r\n        _viaNumObserve(drop);\r\n\r\n        \/\/ \u2500\u2500 Croix Effacer : top-right du #UploadFileConteneur \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        \/\/    Le listener click est pos\u00e9 via d\u00e9l\u00e9gation document dans _installStepHooks\r\n        \/\/    (r\u00e9siste aux clones DOM Ele0A). Ici on cr\u00e9e juste l'\u00e9l\u00e9ment.\r\n        if (!container.querySelector(':scope > .via-erase-btn')) {\r\n            container.appendChild(_makeEraseBtn());\r\n        }\r\n\r\n        \/\/ \u2500\u2500 Num\u00e9ros : tous dans #UploadFileConteneur (align\u00e9s left:8) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n        _placeNum(container, drop, 1, isEle0A);\r\n        _placeNum(container, drop, 2, isEle0A);\r\n        _placeNum(container, drop, 3, isEle0A);\r\n    }\r\n\r\n    \/\/ Mise \u00e0 jour de la classe .via-ad-loaded selon pr\u00e9sence de .via-ad-header\r\n    function _updateAdLoadedClass(drop) {\r\n        if (!drop ? true : !drop.classList) return;\r\n        \/\/ D\u00e9tecter \"annonce d\u00e9pos\u00e9e OU en cours de d\u00e9p\u00f4t\" via plusieurs signaux \u2014\r\n        \/\/   pris dans l'ordre du flux d'upload (du plus pr\u00e9coce au plus tardif) :\r\n        \/\/   - .via-loading-inline \/ .via-loading-overlay (loading ins\u00e9r\u00e9 d\u00e8s le d\u00e9but upload)\r\n        \/\/   - <img> dans #drop_file_zone_achat ou .HTMLUploadfileConteneur (preview rendue)\r\n        \/\/   - data-via-ad-loaded=\"true\" (attribut pos\u00e9 par espace_publicitaire.txt:805)\r\n        \/\/   - .via-ad-header (header dynamique, pos\u00e9 apr\u00e8s tout)\r\n        var hasLoading = !!drop.querySelector('.via-loading-inline, .via-loading-overlay');\r\n        var hasImg = !!drop.querySelector('#drop_file_zone_achat img, .HTMLUploadfileConteneur img');\r\n        var hasAttr = drop.getAttribute('data-via-ad-loaded') === 'true';\r\n        var hasHeader = !!drop.querySelector('.via-ad-header');\r\n        if (hasLoading ? true : (hasImg ? true : (hasAttr ? true : hasHeader))) {\r\n            drop.classList.add('via-ad-loaded');\r\n        } else {\r\n            drop.classList.remove('via-ad-loaded');\r\n        }\r\n    }\r\n\r\n    \/\/ v4.9ds : m\u00e9canisme \u00e0 3 \u00e9tapes \u2014 pose data-via-format-gate selon l'\u00e9tat r\u00e9el\r\n    \/\/   - \"locked\"  \u2192 \u00e9tape 1 : aucun format \u2192 2 (GD+Envoi diff) ET 3 (R\u00e9server) gris\u00e9es\r\n    \/\/   - \"step2\"   \u2192 \u00e9tape 2 : format choisi \u2192 seul 3 (R\u00e9server) gris\u00e9\r\n    \/\/   - (rien)    \u2192 \u00e9tape 3 : envoi diff\u00e9r\u00e9 coch\u00e9 OU annonce d\u00e9pos\u00e9e \u2192 tout actif\r\n    function _updateStepGate(drop) {\r\n        if (!drop) return;\r\n        var $drop = jQuery(drop);\r\n\r\n        \/\/ Annonce d\u00e9pos\u00e9e \u2192 \u00e9tape 3 (tout actif, pas d'attribut)\r\n        var hasAdHeader = !!drop.querySelector('.via-ad-header');\r\n        if (hasAdHeader) {\r\n            drop.removeAttribute('data-via-format-gate');\r\n            return;\r\n        }\r\n\r\n        \/\/ Envoi diff\u00e9r\u00e9 coch\u00e9 DANS CE droppable \u2192 \u00e9tape 3\r\n        var envoiDiffereCoche = $drop.find('input[name*=\"EnvoiUlterieur\"]:checked').length > 0;\r\n        if (envoiDiffereCoche) {\r\n            drop.removeAttribute('data-via-format-gate');\r\n            return;\r\n        }\r\n\r\n        \/\/ Format choisi (un .EspPubFormatContainer hors Cr\u00e9ation\/PopUp avec fond blanc)\r\n        \/\/ OU une vignette Cr\u00e9ation active (data creationActive=true)\r\n        var formatChoisi = $drop.find('.EspPubFormatContainer')\r\n            .not('.FormatIdCreation').not('.FormatIdPopUp')\r\n            .toArray().some(function(el) {\r\n                return jQuery(el).css('background-color') === 'rgb(255, 255, 255)';\r\n            });\r\n        if (!formatChoisi) {\r\n            \/\/ V\u00e9rifier aussi Cr\u00e9ation active\r\n            var $crea = $drop.find('.FormatIdCreation').first();\r\n            if ($crea.length ? $crea.data('creationActive') === true : false) {\r\n                formatChoisi = true;\r\n            }\r\n        }\r\n        if (formatChoisi) {\r\n            drop.setAttribute('data-via-format-gate', 'step2');\r\n            return;\r\n        }\r\n\r\n        \/\/ Sinon \u2192 \u00e9tape 1\r\n        drop.setAttribute('data-via-format-gate', 'locked');\r\n    }\r\n\r\n    \/\/ D\u00e9corer + maintenir l'\u00e9tat pour tous les espaces pr\u00e9sents\r\n    function _processAll() {\r\n        _injectStyles();\r\n        document.querySelectorAll('.droppable').forEach(function(drop) {\r\n            _decorateDroppable(drop);\r\n            _updateAdLoadedClass(drop);\r\n        });\r\n        \/\/ Format-gate : pr\u00e9f\u00e9rer la fonction officielle (couvre tous les droppables)\r\n        try {\r\n            if (typeof window._viaUpdateFormatGate === 'function') {\r\n                window._viaUpdateFormatGate();\r\n            } else {\r\n                document.querySelectorAll('.droppable').forEach(_updateStepGate);\r\n            }\r\n        } catch (_e) {}\r\n    }\r\n\r\n    \/\/ Installer hooks pour r\u00e9agir aux clics format \/ change envoi diff\u00e9r\u00e9\r\n    \/\/   (transition imm\u00e9diate sans attendre MutationObserver)\r\n    function _refreshGate(drop) {\r\n        \/\/ Pr\u00e9f\u00e9rer la fonction officielle qui parcourt tous les droppables\r\n        try {\r\n            if (typeof window._viaUpdateFormatGate === 'function') {\r\n                window._viaUpdateFormatGate();\r\n                return;\r\n            }\r\n        } catch (_e) {}\r\n        \/\/ Fallback local\r\n        try { _updateStepGate(drop); } catch (_e) {}\r\n    }\r\n    function _installStepHooks() {\r\n        try {\r\n            jQuery(document).on('click', '.droppable .EspPubFormatContainer', function() {\r\n                var drop = jQuery(this).closest('.droppable')[0];\r\n                \/\/ D\u00e9lai pour laisser les autres handlers (s\u00e9lection format) finir\r\n                \/\/   v4.9ds : NE PAS appeler _processAll ici \u2014 le ferait dans un \u00e9tat DOM\r\n                \/\/   instable (post-clic, styles Elementor en cours d'application) \u2192 calcul\r\n                \/\/   des rects produit +10px parasite. Le _processAll est triggered ailleurs\r\n                \/\/   (MutationObserver childList sur popup parent, re-passes initiales).\r\n                setTimeout(function() { _refreshGate(drop); }, 100);\r\n                setTimeout(function() { _refreshGate(drop); }, 300);\r\n            });\r\n            jQuery(document).on('change', '.droppable input[name*=\"EnvoiUlterieur\"]', function() {\r\n                var drop = jQuery(this).closest('.droppable')[0];\r\n                setTimeout(function() { _refreshGate(drop); }, 50);\r\n            });\r\n            \/\/ \u2500\u2500 Croix Effacer : d\u00e9l\u00e9gation document \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\r\n            \/\/    \u00c9vite la perte de listener si le DOM est reclon\u00e9 (popup Ele0A).\r\n            \/\/ \u2705 v4.9ds Fix 28 : le handler appelait window.FonctionCroixResetAnnonce\r\n            \/\/   ou un trigger sur #CroixResetAnnonce \u2014 mais cet \u00e9l\u00e9ment n'existe plus\r\n            \/\/   dans le DOM (remplac\u00e9 par .via-erase-btn). R\u00e9sultat : le clic \u00e9tait\r\n            \/\/   bien d\u00e9tect\u00e9 ([via-erase-btn] click d\u00e9tect\u00e9 logg\u00e9) mais aucune action\r\n            \/\/   ne se d\u00e9clenchait. Solution : appel direct \u00e0 AdResetHandler.handle(e)\r\n            \/\/   d\u00e9fini ligne 5212 \u2014 c'est exactement la m\u00eame fonction qui \u00e9tait\r\n            \/\/   attach\u00e9e \u00e0 #CroixResetAnnonce ligne 5948 (`(e) => AdResetHandler.handle(e)`).\r\n            \/\/   AdResetHandler.handle utilise e.currentTarget pour trouver le droppable\r\n            \/\/   parent \u2014 \u00e7a fonctionne avec .via-erase-btn comme avec #CroixResetAnnonce.\r\n            jQuery(document).on('click', '.via-erase-btn', function(e) {\r\n                console.log('[via-erase-btn] click d\u00e9tect\u00e9 | rank:', jQuery(this).closest('.droppable').attr('id'));\r\n                e.preventDefault();\r\n                e.stopPropagation();\r\n                if (e.stopImmediatePropagation) { e.stopImmediatePropagation(); }\r\n                \/\/ \u2705 Fix 28 : appel direct du handler AdResetHandler (anciennement bind \u00e0\r\n                \/\/   #CroixResetAnnonce qui n'existe plus). Garde un fallback sur\r\n                \/\/   FonctionCroixResetAnnonce si AdResetHandler n'est pas accessible.\r\n                if (typeof AdResetHandler !== 'undefined' ? typeof AdResetHandler.handle === 'function' : false) {\r\n                    AdResetHandler.handle(e);\r\n                    return;\r\n                }\r\n                \/\/ Fallback historique (au cas o\u00f9 AdResetHandler n'est pas dans ce scope)\r\n                var $droppable = jQuery(this).closest('.droppable');\r\n                var _croixEl = $droppable.find('#CroixResetAnnonce')[0];\r\n                if (typeof window.FonctionCroixResetAnnonce === 'function' ? !!_croixEl : false) {\r\n                    window.FonctionCroixResetAnnonce(_croixEl);\r\n                    return;\r\n                }\r\n                if (_croixEl) { jQuery(_croixEl).trigger('click'); }\r\n                console.warn('[via-erase-btn] ni AdResetHandler ni #CroixResetAnnonce trouv\u00e9s \u2014 reset non effectu\u00e9');\r\n            });\r\n        } catch (_e) {}\r\n    }\r\n\r\n    function _installWatcher() {\r\n        try {\r\n            var _scheduled = false;\r\n            var _scheduleProcess = function() {\r\n                if (_scheduled) return;\r\n                _scheduled = true;\r\n                requestAnimationFrame(function() {\r\n                    _scheduled = false;\r\n                    _processAll();\r\n                });\r\n            };\r\n            \/\/ v4.9ds : exposer _scheduleProcess au ResizeObserver pour debounce rAF\r\n            \/\/   commun. Quand une ancre\/container change de taille, le RO d\u00e9clenche\r\n            \/\/   _scheduleProcess qui replanifie un seul _processAll par frame.\r\n            _viaNumScheduleAfterRO = _scheduleProcess;\r\n            var obs = new MutationObserver(function(muts) {\r\n                \/\/ R\u00e9action INSTANTAN\u00c9E (pas via rAF) sur les changements d'attribut\r\n                \/\/   data-via-ad-loaded \u2014 masquage imm\u00e9diat des num\u00e9ros + croix\r\n                for (var i = 0; i < muts.length; i++) {\r\n                    var m = muts[i];\r\n                    if (m.type === 'attributes' ? m.attributeName === 'data-via-ad-loaded' : false) {\r\n                        if (m.target ? m.target.classList : false) {\r\n                            _updateAdLoadedClass(m.target);\r\n                        }\r\n                    }\r\n                }\r\n                \/\/ Replanification standard pour le reste (num\u00e9ros, croix, gate)\r\n                \/\/   Cela couvre l'ajout\/retrait de droppables, changements DOM\r\n                \/\/   (pop-up Ele0A cr\u00e9\u00e9e\/clon\u00e9e, etc.). Le ResizeObserver couvre\r\n                \/\/   les changements de g\u00e9om\u00e9trie sur les ancres existantes.\r\n                _scheduleProcess();\r\n            });\r\n            obs.observe(document.body, {\r\n                childList: true,\r\n                subtree: true,\r\n                attributes: true,\r\n                attributeFilter: ['data-via-ad-loaded']\r\n            });\r\n            \/\/ v4.9ds : resize viewport reste branch\u00e9 (ResizeObserver couvre les\r\n            \/\/   \u00e9l\u00e9ments individuels mais pas les changements globaux de viewport\r\n            \/\/   qui peuvent affecter les valeurs de _numTopOffset bas\u00e9es sur\r\n            \/\/   window.innerWidth).\r\n            window.addEventListener('resize', _scheduleProcess);\r\n        } catch (_e) {}\r\n    }\r\n\r\n    function _boot() {\r\n        _injectStyles();\r\n        _processAll();\r\n        _installWatcher();\r\n        _installStepHooks();\r\n        \/\/ v4.9ds : passes setTimeout supprim\u00e9es \u2014 le ResizeObserver branch\u00e9 sur\r\n        \/\/   chaque ancre\/container d\u00e9tecte automatiquement les changements de\r\n        \/\/   g\u00e9om\u00e9trie (Elementor async, images charg\u00e9es, fonts custom, etc.)\r\n        \/\/   et relance _processAll au bon moment.\r\n        \/\/   On garde une seule passe \u00e0 1000ms pour le cas o\u00f9 le ResizeObserver\r\n        \/\/   ne serait pas support\u00e9 (tr\u00e8s anciens navigateurs) ou pour couvrir\r\n        \/\/   les ancres qui apparaissent en display:none initial puis sont\r\n        \/\/   ajout\u00e9es au DOM (le RO ne les observe pas tant qu'elles n'existent\r\n        \/\/   pas dans la d\u00e9coration \u2192 un retry \u00e0 1s couvre ce cas).\r\n        setTimeout(_processAll, 1000);\r\n    }\r\n\r\n    if (document.readyState === 'loading') {\r\n        document.addEventListener('DOMContentLoaded', _boot);\r\n    } else {\r\n        _boot();\r\n    }\r\n})();\r\n\r\n} \/\/ end _espPubScriptLoaded guard\r\n<\/script>\r\n\r\n\r\n<style>\r\n\/* ============================================================================\r\n   UFC HAUTEUR \u2014 colle \u00e0 l'image, cap via max-height (source unique de v\u00e9rit\u00e9)\r\n   ============================================================================ *\/\r\n.via-ad-wrapper .HTMLUploadfileConteneur,\r\n.via-ad-wrapper .HTMLUploadfileConteneur .elementor-widget-container,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #HTMLUploadfile,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #PopUpMessageAchattest,\r\n.via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n    height: auto !important;\r\n    min-height: 0 !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur {\r\n    max-height: 170px !important;\r\n    overflow: hidden !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n    max-height: 160px !important;\r\n}\r\n\r\n.via-ad-wrapper .HTMLUploadfileConteneur img,\r\n.via-ad-wrapper .HTMLUploadfileConteneur video {\r\n    \/* v4.9ds : width\/height 100% + object-fit:contain \u2192 image remplit la box du dropZone\r\n       (m\u00eames contraintes que desktop) avec letterbox automatique selon ratio.\r\n       Avant : height:auto + width:auto \u2192 image gardait son ratio mais pouvait d\u00e9border\r\n       (overflow:hidden du wrapper \u2192 crop). Le max-height 160px est la limite mobile\r\n       (renderImage pose dropZone.height \u00e0 105 sur mobile mais le CSS doit autoriser\r\n       jusqu'\u00e0 160 pour les autres branches qui ne passent pas par renderImage). *\/\r\n    max-height: 160px !important;\r\n    max-width: 100% !important;\r\n    height: 100% !important;\r\n    width: 100% !important;\r\n    object-fit: contain !important;\r\n}\r\n\r\n\/* v4.9ds : doc-preview Communiqu\u00e9\/Interview occupe toute la hauteur du dropZone\r\n   (sinon vide blanc en bas du liser\u00e9 vert) *\/\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container {\r\n    height: 100% !important;\r\n    max-height: 100% !important;\r\n    align-items: stretch !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-thumbnail,\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-info {\r\n    height: 100% !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container.doc-preview-noimage {\r\n    align-items: flex-start !important;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat .doc-preview-container.doc-preview-noimage .doc-preview-info-full {\r\n    height: 100% !important;\r\n    max-height: 100% !important;\r\n}\r\n\r\n\/* v4.9ds : r\u00e8gles Ele0A supprim\u00e9es ici, d\u00e9plac\u00e9es en fin de fichier CSS\r\n   (apr\u00e8s les @media min-width:1200px) pour gagner par ordre de d\u00e9claration. *\/\r\n\r\n\/* v4.9ds : normaliser padding-top sur tous les libell\u00e9s de format.\r\n   Elementor pose en inline padding:1px 0 0 sur Cr\u00e9ation\/Pop-up\/Banni\u00e8re\/Vid\u00e9o\r\n   et padding:3px 0 0 sur Communiqu\u00e9\/Interview\/Parrainage (configur\u00e9 c\u00f4t\u00e9 admin\r\n   Elementor) \u2192 d\u00e9calage visuel entre la 1\u00e8re et 2e ligne de la grille de formats.\r\n   On force 1px partout pour alignement vertical homog\u00e8ne. *\/\r\n.EspPubFormatContainer .EspPubFormat .elementor-widget-container {\r\n    padding-top: 1px !important;\r\n}\r\n\r\n@media (max-width: 999px) {\r\n    .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        max-height: 200px !important;\r\n    }\r\n    \/* v4.9ds : Mobile uniquement \u2014 libell\u00e9s des formats en 800 (au lieu de 600) *\/\r\n    .EspPubFormatContainer .EspPubFormat,\r\n    .EspPubFormatContainer .EspPubFormat .elementor-widget-container {\r\n        font-weight: 800 !important;\r\n    }\r\n}\r\n\r\n\/* Les styles CSS restent inchang\u00e9s *\/\r\n\r\n\/* R\u00e9gie iframe : wrapper adaptatif *\/\r\nhtml.via-regie-iframe .via-ad-wrapper {\r\n    width: 100% !important;\r\n    max-width: 100% !important;\r\n}\r\n\/* Padding-bottom sur droppable d\u00e9pos\u00e9 \u2014 DESKTOP uniquement (\u2265600px viewport iframe).\r\n   Sur mobile, le flow naturel suffit : pas de padding-bottom (\u00e9vite gros vide blanc). *\/\r\n@media (min-width: 600px) {\r\n    html.via-regie-iframe body.home .droppable[data-via-ad-loaded=\"true\"] {\r\n        padding-bottom: 240px !important;\r\n    }\r\n    html.via-regie-iframe body:not(.home) .droppable[data-via-ad-loaded=\"true\"] {\r\n        padding-bottom: 130px !important;\r\n    }\r\n}\r\n\r\n\/* R\u00e9gie iframe MOBILE \u2014 annonce charg\u00e9e : r\u00e9duire les margins \u00e9normes pos\u00e9s par\r\n   Entete.txt sur .ToBeHidden (200px+290px calibr\u00e9s pour espaces vides). *\/\r\n@media (max-width: 599px) {\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) {\r\n        margin-top: -100px !important;\r\n        margin-bottom: 5px !important;\r\n    }\r\n    \/* Neutraliser aussi les margins sur l'OrdiMobileConteneurClass et ses descendants\r\n       qui pourraient ajouter du padding\/margin additionnel. *\/\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) .OrdiMobileConteneurClass {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n        padding-bottom: 0 !important;\r\n    }\r\n    html.via-regie-iframe .ToBeHidden:has(.droppable[data-via-ad-loaded=\"true\"]) .UploadFileConteneur {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n        padding-bottom: 0 !important;\r\n    }\r\n    \/* Pages secteurs mobile : Entete.txt pose margin-bottom 180\/176px directement\r\n       sur .droppable (pas sur ToBeHidden). R\u00e9duire pour les droppables charg\u00e9s. *\/\r\n    html.via-regie-iframe body.page .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-bottom: 10px !important;\r\n    }\r\n}\r\n\r\n\/* R\u00e9gie iframe desktop (\u2265600px de viewport iframe) : UFC standard + loading spacing *\/\r\n@media (min-width: 600px) {\r\n    \/* UFC : hauteur fixe 170px comme les espaces vides (standard) au lieu de hug auto *\/\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: 170px !important;\r\n        max-height: 170px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: 160px !important;\r\n        display: flex !important;\r\n        align-items: center !important;\r\n        justify-content: center !important;\r\n    }\r\n    \/* Homepages : loading container 30px plus bas *\/\r\n    html.via-regie-iframe body.home .via-loading-inline {\r\n        margin-top: 30px !important;\r\n    }\r\n}\r\n\r\n\/* ============================================================================\r\n   R\u00e9gie iframe desktop plein \u00e9cran (\u22651200px) : neutralise les marges JS\r\n   pos\u00e9es par styleUploadedAd + _buildAdOverlay (calibr\u00e9es pour mode mobile \u00e9troit)\r\n   ============================================================================ *\/\r\n@media (min-width: 1200px) {\r\n    \/* Annule la remont\u00e9e de -55px sur .droppable qui cause le chevauchement *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-top: 0 !important;\r\n    }\r\n    \/* Annule le +130px sur margin-bottom de l'OMC interne *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] .OrdiMobileConteneurClass {\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Annule le -140px (ou -105px) sur .via-ad-wrapper pos\u00e9 par _buildAdOverlay *\/\r\n    html.via-regie-iframe .droppable[data-via-ad-loaded=\"true\"] .via-ad-wrapper {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Agrandit l'annonce : UFC 260px max au lieu de 170px *\/\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 260px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        max-height: 250px !important;\r\n    }\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html.via-regie-iframe .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        max-height: 250px !important;\r\n    }\r\n}\r\n\r\n\/* ============================================================================\r\n   Sites pays desktop\/tablette (pas dans iframe r\u00e9gie, \u2265768px) : neutralise\r\n   les marges n\u00e9gatives inline h\u00e9rit\u00e9es du mode mobile (margin-top: -75px sur\r\n   droppable, -140px sur wrapper) qui persistent au resize et causent le\r\n   chevauchement avec le contenu du dessus.\r\n   Seuil 768 (Elementor tablette) pour couvrir les cas o\u00f9 innerWidth < 1000\r\n   alors que outerWidth est en mode desktop.\r\n   ============================================================================ *\/\r\n@media (min-width: 768px) {\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] .via-ad-wrapper {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Pas de margin-top: 0 !important sur .droppable \u2014 on laisse l'inline pos\u00e9 par\r\n       _viaRunInterEspaces (algo inter-espaces) appliquer sa valeur n\u00e9gative pour\r\n       standardiser le gap au-dessus \u00e0 20px. *\/\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] {\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Annule le margin-bottom sur OMC (pos\u00e9 par styleUploadedAd) *\/\r\n    html:not(.via-regie-iframe) .droppable[data-via-ad-loaded=\"true\"] .OrdiMobileConteneurClass {\r\n        margin-top: 0 !important;\r\n        margin-bottom: 0 !important;\r\n    }\r\n    \/* Agrandit l'annonce : UFC 260px max au lieu de 170px *\/\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 260px !important;\r\n    }\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        \/* v4.9ds : aligner sur la hauteur pos\u00e9e par _applyDzMinH (Ele1A+ = 207) *\/\r\n        max-height: 207px !important;\r\n    }\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html:not(.via-regie-iframe) .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        \/* v4.9ds : aligner sur la hauteur r\u00e9elle du dropZone (pos\u00e9e par _applyDzMinH).\r\n           Avant : 250px en dur \u2192 image plus haute que dropZone (~207 sur Ele1A+) \u2192 crop\r\n           par overflow:hidden du wrapper. *\/\r\n        max-height: 207px !important;\r\n    }\r\n}\r\n\r\n\/* v4.9cv : Ele0A desktop \u2014 aligner les contraintes de hauteur sur celles d'Ele1A.\r\n   Ces r\u00e8gles arrivent APR\u00c8S les @media min-width:1200px et 768px pour gagner par\r\n   ordre de d\u00e9claration. Pr\u00e9fixe html# boost la sp\u00e9cificit\u00e9 face \u00e0 html.via-regie-iframe. *\/\r\n\/* v4.9cx : force brute + d\u00e9sactiver overflow:hidden sur le wrapper parent *\/\r\n@media (min-width: 768px) {\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur {\r\n        height: auto !important;\r\n        max-height: 285px !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur #drop_file_zone_achat {\r\n        height: auto !important;\r\n        max-height: 215px !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur img,\r\n    html #Ele0A .via-ad-wrapper .HTMLUploadfileConteneur video {\r\n        \/* v4.9ds : aligner sur la hauteur r\u00e9elle du dropZone (pos\u00e9e par _applyDzMinH = 215)\r\n           Avant : 275px en dur \u2192 image d\u00e9passait dropZone de 60px \u2192 crop.\r\n           v4.9ds : retir\u00e9 'height: auto !important' pour que le JS (height:100%) puisse\r\n           s'appliquer \u2192 image remplit la box comme Ele1A+ (comportement uniforme).\r\n           object-fit:contain garantit que l'image reste enti\u00e8re (letterbox auto). *\/\r\n        max-height: 215px !important;\r\n    }\r\n    \/* Laisser le contenu d\u00e9border visuellement si jamais une r\u00e8gle cach\u00e9e pose une height trop petite *\/\r\n    html body .ToBeHidden:has(#Ele0A) {\r\n        overflow: visible !important;\r\n    }\r\n    html #Ele0A .via-ad-wrapper {\r\n        overflow: visible !important;\r\n    }\r\n    \/* v4.9ds : Ele0A desktop \u2014 d\u00e9caler .EnvoiUlterieurContainer de 17px vers le bas pour\r\n       espacer visuellement le bloc \"ou Envoi diff\u00e9r\u00e9 \/ Envoyer l'annonce \/ Un lien\u2026\" du\r\n       champ hypertext qui le pr\u00e9c\u00e8de. Le margin pousse aussi tout ce qui suit (R\u00e9server). *\/\r\n    html #Ele0A .EnvoiUlterieurContainer {\r\n        margin-top: 17px !important;\r\n    }\r\n}\r\n\r\n\/* \u2705 via-ad-wrapper responsive : s'adapte quand la fen\u00eatre est r\u00e9duite *\/\r\n.via-ad-wrapper {\r\n    min-width: 0;\r\n    overflow: hidden;\r\n    display: flex;\r\n    flex-direction: column;\r\n    align-items: flex-end;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-wrapper .via-ad-header,\r\n.via-ad-wrapper .HTMLUploadfileConteneur,\r\n.via-ad-wrapper .via-ad-footer {\r\n    width: 100%;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-header {\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n}\r\n.via-ah-title {\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n    white-space: nowrap;\r\n    max-width: 65%;\r\n}\r\n\/* desktop mode mobile : reduire les tailles de char du header *\/\r\n@media only screen and (max-width: 999px) {\r\n    .via-ah-pos  { font-size: 8px !important; }\r\n    .via-ah-title { font-size: 9px !important; max-width: 70% !important; }\r\n    .via-ah-ref  { font-size: 8px !important; }\r\n    .via-ad-wrapper .HTMLUploadfileConteneur { max-height: 200px !important; overflow: hidden !important; height: auto !important; }\r\n    \/* v4.9ds : exclure img\/video du s\u00e9lecteur * pour ne pas \u00e9craser leur r\u00e8gle d\u00e9di\u00e9e\r\n       (qui pose width:100%\/height:100%\/object-fit:contain). Sans cette exclusion,\r\n       la max-height:200 inline ici ne pose pas de pb mais le * peut interf\u00e9rer. *\/\r\n    .via-ad-wrapper .HTMLUploadfileConteneur *:not(img):not(video) { max-height: 200px !important; }\r\n    .reserver-dynamic-label { font-size: 12px !important; }\r\n    .via-ad-wrapper .via-af-move { font-size: 8px !important; }\r\n}\r\n.via-ah-pos, .via-ah-ref {\r\n    white-space: nowrap;\r\n    flex-shrink: 1;\r\n    min-width: 0;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n}\r\n\/* v4.9ct : d\u00e9caler la r\u00e9f\u00e9rence MDG... vers la gauche (uniquement desktop).\r\n   - Espaces pub standards : 8px\r\n   - Ele0A (popup) : 5px\r\n   Le margin-right sur .via-ah-ref pousse la ref \u00e0 gauche tandis que la croix reste ancr\u00e9e \u00e0 droite. *\/\r\n\/* v4.9cu : +8\/+5px suppl\u00e9mentaires \u2192 standards 16px, Ele0A 10px *\/\r\n\/* v4.9cv : +8\/+5px suppl\u00e9mentaires \u2192 standards 24px, Ele0A 15px *\/\r\n@media (min-width: 1000px) {\r\n    .via-ad-header .via-ah-ref { margin-right: 24px; }\r\n    #Ele0A .via-ad-header .via-ah-ref { margin-right: 15px; }\r\n    \/* v4.9ds : Ele0A \u2014 d\u00e9caler via-eu-wrapper de +2px vers le bas (cumul avec margin-top:-5px pos\u00e9 par JS Entete) *\/\r\n    \/*          R\u00e9sultat : -5px + 2px = -3px effectif *\/\r\n    #Ele0A .via-eu-wrapper {\r\n        margin-top: -3px !important;\r\n    }\r\n    \/* v4.9ds : Ele1A+ \u2014 d\u00e9caler EnvoiUlterieur de -2px vers le haut *\/\r\n    .droppable:not(#Ele0A) .EnvoiUlterieurContainer .EnvoiUlterieur {\r\n        margin-top: -2px !important;\r\n    }\r\n    \/* v4.9ds : Ele1A+ \u2014 EnvoiUlterieurTexte de -1px (cumul -2px + 1px bas) *\/\r\n    .droppable:not(#Ele0A) .EnvoiUlterieurContainer .EnvoiUlterieurTexte {\r\n        margin-top: -1px !important;\r\n    }\r\n}\r\n\/* HTMLUploadfileConteneur : hauteur auto dans le wrapper *\/\r\n.via-ad-wrapper .HTMLUploadfileConteneur {\r\n    max-height: 200px;\r\n    overflow: hidden !important;\r\n    margin-top: 0 !important;\r\n    margin-bottom: 0 !important;\r\n}\r\n\/* v4.9cs : NE PAS ajouter de zoom suppl\u00e9mentaire sur Ele0A \u2014 #Ele0A a d\u00e9j\u00e0 zoom:55%\r\n   pos\u00e9 par Entete.txt (ligne 5899), donc un second zoom 55% ici donnerait 30% (trop petit). *\/\r\n.via-ad-wrapper #drop_file_zone_achat {\r\n    margin-top: 0 !important;\r\n    padding: 0 2px;\r\n    box-sizing: border-box;\r\n}\r\n.via-ad-wrapper #drop_file_zone_achat img,\r\n.via-ad-wrapper #drop_file_zone_achat video {\r\n    \/* v4.9ds : height:100% (au lieu de auto) + object-fit:contain \u2192 image remplit le\r\n       dropZone (coh\u00e9rent avec width:100%) en restant enti\u00e8re. Letterbox auto si ratio\r\n       diff\u00e9rent. Sans !important pour ne pas surclasser les r\u00e8gles plus sp\u00e9cifiques. *\/\r\n    max-width: 100%;\r\n    width: 100%;\r\n    height: 100%;\r\n    object-fit: contain;\r\n    display: block;\r\n}\r\n\/* via-af-move *\/\r\n.via-ad-wrapper .via-af-move {\r\n    white-space: normal;\r\n    color: #67758c;\r\n    overflow: hidden;\r\n    text-align: center;\r\n}\r\n\r\n\r\n#drop_file_zone_achat {\r\n    background-color: #FFFFFF00;\r\n    color: #225DA9;\r\n    font-weight: 500;\r\n    text-align: center;\r\n    border: #999 0px dashed;\r\n    width: 100%;\r\n    height: 180px;\r\n    font-size: 11.5px;\r\n    display: flex;\r\n    justify-content: center; \r\n    align-items: center; \r\n    flex-wrap: wrap;\r\n}\r\n\r\n#drag_upload_file_achat {\r\n    margin: 0 auto;\r\n    width: 90%;\r\n    height: 60px;\r\n}\r\n\r\n#drag_upload_file_achat p {\r\n    text-align: center;\r\n}\r\n\r\n\/* \u2705 v1.16.0 : Liser\u00e9 noir fin autour du texte \"Ici glisser \u2013 d\u00e9poser\" *\/\r\n.GlisserDeposerConteneur .elementor-widget-container p {\r\n    border: 1px solid #000000;\r\n    border-radius: 4px;\r\n    padding: 4px 10px;\r\n    display: inline-block;\r\n}\r\n\r\n#selectfile_achat {\r\n    display: none;\r\n}\r\n\r\n\/* v4.9ds : Ele0A - EnvoiUlterieurTexte +6px bas (cumul 2px + 2px + 2px) *\/\r\n#Ele0A .EnvoiUlterieurTexte {\r\n    margin-top: 6px !important;\r\n}\r\n\r\n.button-2_achat, .button-2_achat-after-reset {\r\n    background-color: #ffffff00!important;\r\n    border: 1px solid white!important;\r\n    border-radius: 8px;\r\n    color: #225DA9!important;\r\n    cursor: pointer;\r\n    display: inline-block;\r\n    font-size: 11px;\r\n    font-weight: 500;\r\n    list-style: none;\r\n    width: 390px;\r\n    height: 62px;\r\n    top: 0px!important; \r\n    margin-top: 0px!important; \r\n    padding-left: 5px!important;\r\n    padding-right: 5px!important;\r\n    margin-bottom: 70px!important;\r\n    margin: 0;\r\n    text-align: center;\r\n    transition: all 200ms;\r\n    vertical-align: baseline;\r\n    white-space: wrap!important;\r\n}\r\n\r\n@media only screen and (max-width: 1000px) {\r\n    .button-2_achat, .button-2_achat-after-reset {\r\n        width: 95%;\r\n        height: 50px;\r\n    }\r\n}\r\n\r\n.newMessageClass {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #56BE50;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 45px;\r\n    bottom: 0px;\r\n    right: 0px;\r\n    left: 0px;\r\n    display: flex;\r\n    justify-content: center;\r\n    align-items: center;\r\n    text-align: center;\r\n}\r\n\r\n.MessageClassFormatnonReconnuTitre {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #FB5E2A;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    margin-top: -300px;\r\n    right: 0px;\r\n    text-align: center;\r\n    line-height: 15px;\r\n}\r\n\r\n.MessageClassFormatnonReconnu {\r\n    background-color: #FFFFFF00!important;\r\n    font-size: 13px; \r\n    font-weight: 600;\r\n    color: #ffffff;\r\n    height: 35px;\r\n    width: 550px; \r\n    position: relative;\r\n    z-index: 99;\r\n    margin-top: -280px;\r\n    right: 0px;\r\n    text-align: center;\r\n    line-height: 15px;\r\n}\r\n\r\n.newButtonClass {\r\n    font-size: 12px; \r\n    font-weight: 500;\r\n    background-color: #ffffff00;\r\n    color: #717171;\r\n    height: 10px;\r\n    width: 100%; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 45px;\r\n    bottom: 0px;\r\n    text-align: center;\r\n    right:  50px;\r\n    line-height: 10px;\r\n    border-radius: 3px;\r\n    border: 0px solid #E8ECF1;\r\n}\r\n\r\n.linkClass {\r\n    width: 15px; \r\n    height: 15px;\r\n    margin-top: -310px;\r\n    margin-bottom: -25px;\r\n    margin-right: -50px;\r\n    position: relative;\r\n    top: 0; \r\n    z-index: 99;\r\n    display: block;\r\n    background-image: url('https:\/\/rdc.via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-blanc.jpg');\r\n    background-size: cover;\r\n}\r\n\r\n.newButtonClassVideo {\r\n    font-size: 12px; \r\n    font-weight: 500;\r\n    background-color: #ffffff00;\r\n    color: #717171;\r\n    height: 10px;\r\n    width: 100%; \r\n    position: relative;\r\n    z-index: 99;\r\n    top: 0px;\r\n    bottom: 0px;\r\n    right: 10px;\r\n    text-align: center;\r\n    line-height: 10px;\r\n    border-radius: 3px;\r\n    border: 0px solid #E8ECF1;\r\n}\r\n\r\n.linkClassVideo {\r\n    width: 15px; \r\n    height: 15px;\r\n    margin-top: -438px;\r\n    margin-bottom: -25px;\r\n    margin-right: -15px;\r\n    position: relative;\r\n    top: 0; \r\n    z-index: 99;\r\n    display: block;\r\n    background-image: url('https:\/\/rdc.via-agency.media\/wp-content\/uploads\/2024\/06\/Croix-retour-HP-fond-blanc.jpg');\r\n    background-size: cover;\r\n}\r\n\r\n.FinCampagneMobileClass .elementor-button,\r\n.DebutCampagneMobileClass .elementor-button,\r\n.FormSelectDevisesMobile .elementor-button,\r\n.HideFormButton .elementor-button {\r\n    display: none !important;\r\n}\r\n\r\n#drag_upload_file_achat {\r\n    margin: 0;\r\n    padding: 0;\r\n    position: relative;\r\n}\r\n\r\n.dot-container {\r\n    display: inline-block;\r\n}\r\n\r\n.dot {\r\n    opacity: 0;\r\n    transition: opacity 0.3s ease;\r\n}\r\n\r\n@keyframes firstDot {\r\n    0%, 100% { opacity: 0; }\r\n    10%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n@keyframes secondDot {\r\n    0%, 20%, 100% { opacity: 0; }\r\n    30%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n@keyframes thirdDot {\r\n    0%, 40%, 100% { opacity: 0; }\r\n    50%, 70% { opacity: 1; }\r\n    90% { opacity: 0; }\r\n}\r\n\r\n.dot:nth-child(1) {\r\n    animation: firstDot 3s infinite;\r\n}\r\n\r\n.dot:nth-child(2) {\r\n    animation: secondDot 3s infinite;\r\n}\r\n\r\n.dot:nth-child(3) {\r\n    animation: thirdDot 3s infinite;\r\n}\r\n\r\n.droppable .elementor-field-type-checkbox .elementor-field-option {\r\n    display: flex;\r\n    flex-direction: row-reverse;\r\n    align-items: center;\r\n    gap: 8px;\r\n}\r\n\r\n.droppable .elementor-field-type-checkbox .elementor-field-option input[type=\"checkbox\"] {\r\n    width: 12px;\r\n    height: 12px;\r\n    min-width: 12px;\r\n    min-height: 12px;\r\n}\r\n\r\n\/* \u2705 Bouton \"R\u00e9server\" dynamique *\/\r\n.reserver-dynamic-container {\r\n    text-align: center;\r\n    margin-top: -7px;\r\n    margin-bottom: 15px;\r\n    padding: 5px 0;\r\n    transform: scale(1.4);\r\n    transform-origin: center top;\r\n    position: relative;\r\n    z-index: 200;\r\n}\r\n\r\n@media only screen and (min-width: 1001px) {\r\n    .reserver-dynamic-container {\r\n        transform: scale(1);\r\n        margin-top: -200px;\r\n        margin-bottom: 0;\r\n    }\r\n}\r\n\r\n.reserver-dynamic-option {\r\n    display: inline-flex;\r\n    flex-direction: row-reverse;\r\n    align-items: center;\r\n    gap: 8px;\r\n    cursor: pointer;\r\n    color: #213864;\r\n    background-color: #ffffff;\r\n    padding: 2px 4px;\r\n    border-radius: 6px;\r\n}\r\n\r\n.reserver-dynamic-checkbox {\r\n    width: 16px;\r\n    height: 16px;\r\n    min-width: 16px;\r\n    min-height: 16px;\r\n    cursor: pointer;\r\n    pointer-events: auto;\r\n}\r\n\r\n.reserver-dynamic-label {\r\n    cursor: pointer;\r\n    color: #213864;\r\n    font-size: 16px;\r\n    font-weight: 600;\r\n    user-select: none;\r\n}\r\n\r\n@media only screen and (max-width: 1000px) {\r\n    .reserver-dynamic-container {\r\n        transform: scale(0.98);\r\n        margin-bottom: 20px;\r\n        white-space: nowrap;\r\n        z-index: 300;\r\n        pointer-events: auto;\r\n    }\r\n    .reserver-dynamic-option {\r\n        padding-top: 0px;\r\n        padding-bottom: 0px;\r\n    }\r\n}\r\n\r\n\/* \u2705 v2.1.3 : DeplaceAnnonceText \/ DeplaceAnnonce \u2014 desktop uniquement *\/\r\n@media only screen and (min-width: 1001px) {\r\n    .DeplaceAnnonceText {\r\n        margin-top: -5px;\r\n        position: relative;\r\n        z-index: 201;\r\n    }\r\n    .DeplaceAnnonce {\r\n        margin-bottom: -150px;\r\n    }\r\n}\r\n<\/style>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-54e4e530 e-con-full MsgFormatIncorrectConteneur elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile e-flex e-con e-child\" data-id=\"54e4e530\" data-element_type=\"container\" id=\"NotusedAnymore\">\n\t\t\t\t<div class=\"elementor-element elementor-element-531f3cb1 MsgFormatIncorrect elementor-widget__width-inherit elementor-hidden-desktop elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"531f3cb1\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">The file format is not recognized<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-3a352c3f elementor-hidden-desktop TexteMobile TexteMobileAnnonce elementor-widget-mobile__width-initial elementor-widget elementor-widget-text-editor\" data-id=\"3a352c3f\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tDrag and drop or click here<br>\nto download an ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-7abb0aba elementor-hidden-desktop TexteMobileAjoutAnnonce elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"7abb0aba\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Click here to download an ad<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-68e2b2ca UploadIci elementor-hidden-tablet elementor-hidden-mobile elementor-widget elementor-widget-text-editor\" data-id=\"68e2b2ca\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tHere you can drag and drop or upload an ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-8d50d33 e-con-full e-flex e-con e-child\" data-id=\"8d50d33\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6ae6ef83 elementor-button-align-center elementor-widget__width-initial HideFormButton EspPubLienAnnonce elementor-widget-mobile__width-initial elementor-widget elementor-widget-form\" data-id=\"6ae6ef83\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_URL_annonce\" aria-label=\"Form_URL_ad\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353558\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"6ae6ef83\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-text elementor-field-group elementor-column elementor-field-group-LienAnnonce elementor-col-100 elementor-sm-100 elementor-field-required\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-LienAnnonce\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tHere, if necessary, enter the hyperlink to the advertisement.\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<input size=\"1\" type=\"text\" name=\"form_fields[LienAnnonce]\" id=\"form-field-LienAnnonce\" class=\"elementor-field elementor-size-xs  elementor-field-textual\" placeholder=\"Here, if necessary, enter the hyperlink to the advertisement.\" required=\"required\">\n\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidURLAnnonce\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-66fd4d36 e-con-full EnvoiUlterieurContainer e-flex e-con e-child\" data-id=\"66fd4d36\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4ea8e2b4 elementor-button-align-center elementor-widget__width-auto HideFormButton EnvoiUlterieur elementor-widget elementor-widget-form\" data-id=\"4ea8e2b4\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_Envoi_Ulterieur\" aria-label=\"Submit_Form_Later\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353558\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"4ea8e2b4\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-checkbox elementor-field-group elementor-column elementor-field-group-EnvoiUlterieur elementor-col-100 elementor-sm-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-EnvoiUlterieur\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tor Delayed posting of the advertisement\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t<div class=\"elementor-field-subgroup\"><span class=\"elementor-field-option\"><input type=\"checkbox\" value=\"ou Envoi diff\u00e9r\u00e9 de l&#039;annonce\" id=\"form-field-EnvoiUlterieur-0\" name=\"form_fields[EnvoiUlterieur]\"> <label for=\"form-field-EnvoiUlterieur-0\">or Delayed posting of the advertisement<\/label><\/span><\/div>\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidEnvoiUlterieur\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4a59cf88 EnvoiUlterieurTexte elementor-widget elementor-widget-text-editor\" data-id=\"4a59cf88\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p style=\"text-align: center;\">Send the ad up to 8 days after payment<br>\nA link will be sent to you by <span style=\"color: #ffffff;\"><a style=\"color: #ffffff;\" href=\"mailto:contact@via-agency.media\">contact@via-agency.media<\/a><\/span><\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-17d3ad0c e-con-full ReserverContainer e-flex e-con e-child\" data-id=\"17d3ad0c\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-177c9b71 elementor-button-align-center elementor-widget__width-auto HideFormButton ReserverBouton elementor-widget elementor-widget-form\" data-id=\"177c9b71\" data-element_type=\"widget\" data-settings=\"{&quot;step_next_label&quot;:&quot;Suivant&quot;,&quot;step_previous_label&quot;:&quot;Pr\\u00e9c\\u00e9dent&quot;,&quot;step_type&quot;:&quot;none&quot;,&quot;step_icon_shape&quot;:&quot;none&quot;,&quot;button_width&quot;:&quot;100&quot;}\" data-widget_type=\"form.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<form class=\"elementor-form\" method=\"post\" id=\"Formulaire_Annonceur_donnees_notconnected2\" name=\"Formulaire_Envoi_Ulterieur\" aria-label=\"Submit_Form_Later\" action=\"\">\n\t\t\t<input type=\"hidden\" name=\"post_id\" value=\"353558\"\/>\n\t\t\t<input type=\"hidden\" name=\"form_id\" value=\"177c9b71\"\/>\n\t\t\t<input type=\"hidden\" name=\"referer_title\" value=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n\n\t\t\t\n\t\t\t<div class=\"elementor-form-fields-wrapper elementor-labels-\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-type-checkbox elementor-field-group elementor-column elementor-field-group-ReserverEspacePublicitaire elementor-col-100 elementor-sm-100\">\n\t\t\t\t\t\t\t\t\t\t\t\t<label for=\"form-field-ReserverEspacePublicitaire\" class=\"elementor-field-label elementor-screen-only\">\n\t\t\t\t\t\t\t\tReserve this advertising space\t\t\t\t\t\t\t<\/label>\n\t\t\t\t\t\t<div class=\"elementor-field-subgroup\"><span class=\"elementor-field-option\"><input type=\"checkbox\" value=\"R\u00e9server cet espace publicitaire\" id=\"form-field-ReserverEspacePublicitaire-0\" name=\"form_fields[ReserverEspacePublicitaire]\"> <label for=\"form-field-ReserverEspacePublicitaire-0\">Reserve this advertising space<\/label><\/span><\/div>\t\t\t\t<\/div>\n\t\t\t\t\t\t\t\t<div class=\"elementor-field-group elementor-column elementor-field-type-submit elementor-col-100 e-form__buttons\">\n\t\t\t\t\t<button class=\"elementor-button elementor-size-xs\" type=\"submit\" id=\"ButtonValidEnvoiUlterieur\">\n\t\t\t\t\t\t<span class=\"elementor-button-content-wrapper\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<span class=\"elementor-button-text\"> <\/span>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<\/span>\n\t\t\t\t\t<\/button>\n\t\t\t\t<\/div>\n\t\t\t<\/div>\n\t\t<input type=\"hidden\" name=\"trp-form-language\" value=\"en\"\/><\/form>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-5bafe1f3 e-con-full AdUploadedTitle DeplaceAnnonce elementor-hidden-tablet elementor-hidden-mobile elementor-hidden-desktop e-flex e-con e-child\" data-id=\"5bafe1f3\" data-element_type=\"container\" id=\"DeplaceAnnonceId\">\n\t\t<div class=\"elementor-element elementor-element-432cc2a1 e-con-full DeplaceAnnonceSubContainer e-flex e-con e-child\" data-id=\"432cc2a1\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-446e8565 elementor-hidden-tablet elementor-hidden-mobile EspaceReserve elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"446e8565\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reserved space<br \/>Announcement transmitted<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-60df49d0 elementor-hidden-tablet elementor-hidden-mobile DeplaceAnnonceText elementor-widget elementor-widget-text-editor\" data-id=\"60df49d0\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\tIf you want another location, you can move this ad\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-62ef38f0 e-con-full e-flex e-con e-child\" data-id=\"62ef38f0\" data-element_type=\"container\">\n\t\t\t\t<div class=\"elementor-element elementor-element-f4f51cb elementor-hidden-tablet elementor-hidden-mobile PositionEspacePublicitaireDeplacer elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"f4f51cb\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Position<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-f214306 elementor-hidden-tablet elementor-hidden-mobile RefEspacePublicitaire elementor-hidden-desktop elementor-widget elementor-widget-text-editor\" data-id=\"f214306\" data-element_type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t<p>Reference<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<\/p>\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-63c9f13b e-flex e-con-boxed e-con e-parent\" data-id=\"63c9f13b\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"<p>Le secteur du tourisme au Gabon Le secteur du tourisme gabonais valorise un \u00e9cosyst\u00e8me naturel exceptionnel, avec treize parcs nationaux pr\u00e9serv\u00e9s et une biodiversit\u00e9 marine unique sur la c\u00f4te atlantique. Gr\u00e2ce \u00e0 son \u00e9cotourisme de qualit\u00e9, ses plages vierges et sa faune sauvage diversifi\u00e9e, le Gabon d\u00e9veloppe une destination touristique haut de gamme respectueuse de &#8230; <a title=\"Gabon &#8211; Tourisme &#8211; L\u2019h\u00f4tellerie et la restauration\" class=\"read-more\" href=\"https:\/\/gabon.yearbook-media.com\/en\/gabon-tourisme-lhotellerie-et-la-restauration\/\" aria-label=\"More on Gabon &#8211; Tourisme &#8211; L\u2019h\u00f4tellerie et la restauration\">Read more<\/a><\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-113276","page","type-page","status-publish","hentry","generate-columns","tablet-grid-50","mobile-grid-100","grid-parent","grid-20"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v24.8.1 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Gabon - Tourism<\/title>\n<meta name=\"description\" content=\"Gabon, nicknamed &quot;the last Eden&quot;, offers unique biodiversity with its 13 national parks covering 10% of the territory.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/gabon.yearbook-media.com\/en\/tourisme\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Gabon - Tourisme\" \/>\n<meta property=\"og:description\" content=\"Gabon, nicknamed &quot;the last Eden&quot;, offers unique biodiversity with its 13 national parks covering 10% of the territory.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/gabon.yearbook-media.com\/en\/tourisme\/\" \/>\n<meta property=\"og:site_name\" content=\"GABON YEARBOOK RAPPORT ECONOMIQUE\" \/>\n<meta property=\"article:modified_time\" content=\"2025-06-06T18:04:06+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data1\" content=\"20 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/gabon.yearbook-media.com\/tourisme\/\",\"url\":\"https:\/\/gabon.yearbook-media.com\/tourisme\/\",\"name\":\"Gabon - Tourisme\",\"isPartOf\":{\"@id\":\"https:\/\/gabon.yearbook-media.com\/#website\"},\"datePublished\":\"2025-05-28T08:58:56+00:00\",\"dateModified\":\"2025-06-06T18:04:06+00:00\",\"description\":\"Le Gabon, surnomm\u00e9 \u00ab le dernier Eden \u00bb, offre une biodiversit\u00e9 unique avec ses 13 parcs nationaux couvrant 10\u202f% du territoire.\",\"breadcrumb\":{\"@id\":\"https:\/\/gabon.yearbook-media.com\/tourisme\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/gabon.yearbook-media.com\/tourisme\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/gabon.yearbook-media.com\/tourisme\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Accueil\",\"item\":\"https:\/\/gabon.yearbook-media.com\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Gabon &#8211; Tourisme\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/gabon.yearbook-media.com\/#website\",\"url\":\"https:\/\/gabon.yearbook-media.com\/\",\"name\":\"GABON YEARBOOK RAPPORT ECONOMIQUE\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/gabon.yearbook-media.com\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Gabon - Tourism","description":"Gabon, nicknamed &quot;the last Eden&quot;, offers unique biodiversity with its 13 national parks covering 10% of the territory.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/gabon.yearbook-media.com\/en\/tourisme\/","og_locale":"en_US","og_type":"article","og_title":"Gabon - Tourisme","og_description":"Gabon, nicknamed &quot;the last Eden&quot;, offers unique biodiversity with its 13 national parks covering 10% of the territory.","og_url":"https:\/\/gabon.yearbook-media.com\/en\/tourisme\/","og_site_name":"GABON YEARBOOK RAPPORT ECONOMIQUE","article_modified_time":"2025-06-06T18:04:06+00:00","twitter_card":"summary_large_image","twitter_misc":{"Est. reading time":"20 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/gabon.yearbook-media.com\/tourisme\/","url":"https:\/\/gabon.yearbook-media.com\/tourisme\/","name":"Gabon - Tourisme","isPartOf":{"@id":"https:\/\/gabon.yearbook-media.com\/#website"},"datePublished":"2025-05-28T08:58:56+00:00","dateModified":"2025-06-06T18:04:06+00:00","description":"Le Gabon, surnomm\u00e9 \u00ab le dernier Eden \u00bb, offre une biodiversit\u00e9 unique avec ses 13 parcs nationaux couvrant 10\u202f% du territoire.","breadcrumb":{"@id":"https:\/\/gabon.yearbook-media.com\/tourisme\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/gabon.yearbook-media.com\/tourisme\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/gabon.yearbook-media.com\/tourisme\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Accueil","item":"https:\/\/gabon.yearbook-media.com\/"},{"@type":"ListItem","position":2,"name":"Gabon &#8211; Tourisme"}]},{"@type":"WebSite","@id":"https:\/\/gabon.yearbook-media.com\/#website","url":"https:\/\/gabon.yearbook-media.com\/","name":"GABON YEARBOOK ECONOMIC REPORT","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/gabon.yearbook-media.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"}]}},"_links":{"self":[{"href":"https:\/\/gabon.yearbook-media.com\/en\/wp-json\/wp\/v2\/pages\/113276"}],"collection":[{"href":"https:\/\/gabon.yearbook-media.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/gabon.yearbook-media.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/gabon.yearbook-media.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/gabon.yearbook-media.com\/en\/wp-json\/wp\/v2\/comments?post=113276"}],"version-history":[{"count":2,"href":"https:\/\/gabon.yearbook-media.com\/en\/wp-json\/wp\/v2\/pages\/113276\/revisions"}],"predecessor-version":[{"id":205760,"href":"https:\/\/gabon.yearbook-media.com\/en\/wp-json\/wp\/v2\/pages\/113276\/revisions\/205760"}],"wp:attachment":[{"href":"https:\/\/gabon.yearbook-media.com\/en\/wp-json\/wp\/v2\/media?parent=113276"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}