{"id":14787,"date":"2026-08-01T14:19:25","date_gmt":"2026-08-01T14:19:25","guid":{"rendered":"https:\/\/www.agence-exoa.com\/?page_id=14787"},"modified":"2026-08-03T16:27:15","modified_gmt":"2026-08-03T16:27:15","slug":"marketing-delegue","status":"publish","type":"page","link":"https:\/\/www.agence-exoa.com\/de\/marketing-delegue\/","title":{"rendered":"Marketing d\u00e9l\u00e9gu\u00e9 \u2014 Votre \u00e9quipe marketing externalis\u00e9e | Exoa"},"content":{"rendered":"\n<style>:root{--navy:#060039;--navy2:#0C0A4A;--lime:#CDF980;--lav:#808CF9;--lav2:#5159A4;--violet:#7670E8;--ink:#060039;--body:#565967;--line:#E4E6FF;--bg:#F8FAFB}.exoa-marketing-delegue-page *{margin:0;padding:0;box-sizing:border-box}html{scroll-behavior:smooth}.exoa-marketing-delegue-page{font-family:'DM Sans',sans-serif;color:var(--ink);background:#fff;-webkit-font-smoothing:antialiased}.exoa-marketing-delegue-page a{color:var(--lav2);text-decoration:none}.exoa-marketing-delegue-page a:hover{color:var(--violet)}.exoa-marketing-delegue-page .wrap{max-width:1200px;margin:0 auto;padding:0 32px}.exoa-marketing-delegue-page .mono{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700}.exoa-marketing-delegue-page h1, .exoa-marketing-delegue-page h2, .exoa-marketing-delegue-page h3, .exoa-marketing-delegue-page .disp{font-family:'Bricolage Grotesque',sans-serif;font-weight:800;letter-spacing:-.02em;line-height:1.02}.exoa-marketing-delegue-page .nav{position:sticky;top:0;z-index:60;background:rgba(6,0,57,.88);backdrop-filter:blur(14px);border-bottom:1px solid rgba(255,255,255,.1)}.exoa-marketing-delegue-page .nav-in{max-width:1200px;margin:0 auto;padding:14px 32px;display:flex;align-items:center;justify-content:space-between;gap:20px}.exoa-marketing-delegue-page .logo{display:flex;align-items:center;gap:9px;color:#fff;font-weight:700;font-size:15px;letter-spacing:.02em}.exoa-marketing-delegue-page .logo svg{width:17px;height:17px}.exoa-marketing-delegue-page .nav-mid{display:flex;gap:26px}.exoa-marketing-delegue-page .nav-mid a{color:#C6C9E0;font-size:14px;font-weight:500}.exoa-marketing-delegue-page .nav-mid a:hover{color:#fff}.exoa-marketing-delegue-page .btn{display:inline-flex;align-items:center;gap:8px;padding:13px 24px;border-radius:99px;font-size:14.5px;font-weight:600;font-family:inherit;border:0;cursor:pointer;transition:all .2s;white-space:nowrap}.exoa-marketing-delegue-page .btn-lime{background:var(--lime);color:var(--navy)}.exoa-marketing-delegue-page .btn-lime:hover{background:#dcff9c;color:var(--navy);transform:translateY(-1px)}.exoa-marketing-delegue-page .btn-ghost{background:rgba(255,255,255,.08);color:#fff;border:1px solid rgba(255,255,255,.2)}.exoa-marketing-delegue-page .btn-ghost:hover{background:rgba(255,255,255,.16);color:#fff}.exoa-marketing-delegue-page .btn-navy{background:var(--navy);color:#fff}.exoa-marketing-delegue-page .btn-navy:hover{background:var(--navy2);color:#fff;transform:translateY(-1px)}.exoa-marketing-delegue-page .btn-out{background:#fff;color:var(--navy);border:1.5px solid var(--line)}.exoa-marketing-delegue-page .btn-out:hover{border-color:var(--lav);color:var(--navy)}.exoa-marketing-delegue-page .hero{background:var(--navy);color:#fff;padding:88px 0 0;position:relative;overflow:hidden}.exoa-marketing-delegue-page .hero::before{content:'';position:absolute;top:-260px;right:-180px;width:640px;height:640px;border-radius:50%;background:radial-gradient(circle,rgba(118,112,232,.42),transparent 68%);pointer-events:none}.exoa-marketing-delegue-page .hero::after{content:'';position:absolute;bottom:-200px;left:-160px;width:520px;height:520px;border-radius:50%;background:radial-gradient(circle,rgba(205,249,128,.14),transparent 70%);pointer-events:none}.exoa-marketing-delegue-page .hero-in{position:relative;z-index:2}.exoa-marketing-delegue-page .pill{display:inline-flex;align-items:center;gap:9px;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.16);border-radius:99px;padding:8px 16px;margin-bottom:26px}.exoa-marketing-delegue-page .pill .dot{width:7px;height:7px;border-radius:50%;background:var(--lime);flex-shrink:0;box-shadow:0 0 0 4px rgba(205,249,128,.18)}.exoa-marketing-delegue-page .pill span{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;font-weight:700;color:#E8EAF6}.exoa-marketing-delegue-page .hero h1{font-size:clamp(40px,5.6vw,76px);max-width:15ch}.exoa-marketing-delegue-page .hero h1 em{font-style:normal;color:var(--lime)}.exoa-marketing-delegue-page .hero h1 .v{font-style:normal;color:var(--violet)}.exoa-marketing-delegue-page .hero-sub{font-size:clamp(16px,1.35vw,19px);color:#C6C9E0;line-height:1.6;max-width:60ch;margin:24px 0 32px;text-wrap:pretty}.exoa-marketing-delegue-page .hero-cta{display:flex;gap:12px;flex-wrap:wrap;align-items:center}.exoa-marketing-delegue-page .hero-note{font-size:13px;color:#9498b8;margin-top:18px;display:flex;gap:18px;flex-wrap:wrap}.exoa-marketing-delegue-page .hero-note b{color:#E8EAF6;font-weight:600}.exoa-marketing-delegue-page .reel{margin-top:56px;position:relative;z-index:2}.exoa-marketing-delegue-page .reel-frame{position:relative;border-radius:20px 20px 0 0;overflow:hidden;border:1px solid rgba(255,255,255,.16);border-bottom:0;background:#0C0A4A;aspect-ratio:16\/7}.exoa-marketing-delegue-page .reel-bar{position:absolute;top:0;left:0;right:0;height:38px;display:flex;align-items:center;gap:7px;padding:0 14px;background:rgba(6,0,57,.72);backdrop-filter:blur(8px);z-index:3;border-bottom:1px solid rgba(255,255,255,.1)}.exoa-marketing-delegue-page .reel-bar i{width:9px;height:9px;border-radius:50%;background:rgba(255,255,255,.22)}.exoa-marketing-delegue-page .reel-bar em{font-style:normal;font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.1em;color:#9498b8;margin-left:8px}.exoa-marketing-delegue-page .marq{background:var(--lime);color:var(--navy);padding:15px 0;overflow:hidden;white-space:nowrap;border-top:1px solid rgba(6,0,57,.1)}.exoa-marketing-delegue-page .marq-t{display:inline-flex;gap:44px;animation:sc 26s linear infinite;padding-right:44px}.exoa-marketing-delegue-page .marq-t span{font-family:'JetBrains Mono',monospace;font-size:12.5px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;display:inline-flex;align-items:center;gap:44px}.exoa-marketing-delegue-page .marq-t span::after{content:'\u2726';opacity:.45}@keyframes sc{to{transform:translateX(-50%)}}.exoa-marketing-delegue-page .sec{padding:104px 0}.exoa-marketing-delegue-page .sec-lt{background:var(--bg)}.exoa-marketing-delegue-page .sec-dk{background:var(--navy);color:#fff}.exoa-marketing-delegue-page .tag{display:inline-flex;align-items:center;gap:8px;margin-bottom:16px}.exoa-marketing-delegue-page .tag i{width:22px;height:2px;background:var(--lav);border-radius:2px}.exoa-marketing-delegue-page .tag span{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:var(--lav2)}.exoa-marketing-delegue-page .sec-dk .tag span{color:var(--lime)}.exoa-marketing-delegue-page .sec-dk .tag i{background:var(--lime)}.exoa-marketing-delegue-page .h2{font-size:clamp(30px,3.6vw,50px);max-width:22ch}.exoa-marketing-delegue-page .h2 em{font-style:normal;color:var(--lav2)}.exoa-marketing-delegue-page .sec-dk .h2 em{color:var(--lime)}.exoa-marketing-delegue-page .lead{font-size:17px;color:var(--body);line-height:1.65;max-width:64ch;margin-top:18px;text-wrap:pretty}.exoa-marketing-delegue-page .sec-dk .lead{color:#C6C9E0}.exoa-marketing-delegue-page .head-row{display:flex;justify-content:space-between;align-items:flex-end;gap:40px;margin-bottom:52px;flex-wrap:wrap}.exoa-marketing-delegue-page .stats{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:52px}.exoa-marketing-delegue-page .stat{background:rgba(255,255,255,.045);border:1px solid rgba(255,255,255,.13);border-radius:18px;padding:30px}.exoa-marketing-delegue-page .stat-n{font-family:'Bricolage Grotesque',sans-serif;font-size:46px;font-weight:800;color:var(--lime);letter-spacing:-.03em;line-height:1}.exoa-marketing-delegue-page .stat-l{font-size:14.5px;color:#C6C9E0;line-height:1.55;margin-top:12px}.exoa-marketing-delegue-page .bricks{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}.exoa-marketing-delegue-page .brick{background:#fff;border:1px solid var(--line);border-radius:20px;overflow:hidden;display:flex;flex-direction:column;transition:all .22s}.exoa-marketing-delegue-page .brick:hover{border-color:var(--lav);transform:translateY(-3px);box-shadow:0 16px 40px -20px rgba(6,0,57,.22)}.exoa-marketing-delegue-page .brick-vis{width:100%;min-width:0;aspect-ratio:4\/3;background:var(--bg);border-bottom:1px solid var(--line);position:relative}.exoa-marketing-delegue-page .brick>*{min-width:0}.exoa-marketing-delegue-page .brick-b{padding:26px;display:flex;flex-direction:column;gap:11px;flex:1}.exoa-marketing-delegue-page .brick-n{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.12em;font-weight:700;color:var(--lav2)}.exoa-marketing-delegue-page .brick h3{font-size:21px;letter-spacing:-.01em}.exoa-marketing-delegue-page .brick p{font-size:14.5px;color:var(--body);line-height:1.62}.exoa-marketing-delegue-page .brick ul{list-style:none;display:flex;flex-direction:column;gap:8px;margin-top:4px}.exoa-marketing-delegue-page .brick li{font-size:13.5px;color:var(--body);padding-left:20px;position:relative;line-height:1.5}.exoa-marketing-delegue-page .brick li::before{content:'';position:absolute;left:0;top:7px;width:8px;height:8px;border-radius:2px;background:var(--lime);border:1px solid rgba(6,0,57,.15)}.exoa-marketing-delegue-page .packs{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;align-items:start}.exoa-marketing-delegue-page .pack{background:#fff;border:1px solid var(--line);border-radius:22px;padding:32px;display:flex;flex-direction:column;gap:18px;position:relative}.exoa-marketing-delegue-page .pack.hot{background:var(--navy);border-color:var(--navy);color:#fff;box-shadow:0 24px 60px -28px rgba(6,0,57,.5)}.exoa-marketing-delegue-page .pack-bdg{position:absolute;top:-12px;left:32px;background:var(--lime);color:var(--navy);font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;padding:6px 14px;border-radius:99px}.exoa-marketing-delegue-page .pack-n{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.13em;text-transform:uppercase;font-weight:700;color:var(--lav2)}.exoa-marketing-delegue-page .pack.hot .pack-n{color:var(--lime)}.exoa-marketing-delegue-page .pack h3{font-size:25px}.exoa-marketing-delegue-page .pack-for{font-size:13.5px;color:var(--body);line-height:1.55}.exoa-marketing-delegue-page .pack.hot .pack-for{color:#C6C9E0}.exoa-marketing-delegue-page .pack-p{display:flex;align-items:baseline;gap:7px;padding-top:6px;border-top:1px solid var(--line)}.exoa-marketing-delegue-page .pack.hot .pack-p{border-color:rgba(255,255,255,.15)}.exoa-marketing-delegue-page .pack-p b{font-family:'Bricolage Grotesque',sans-serif;font-size:40px;font-weight:800;letter-spacing:-.03em}.exoa-marketing-delegue-page .pack.hot .pack-p b{color:var(--lime)}.exoa-marketing-delegue-page .pack-p i{font-style:normal;font-size:13px;color:var(--body);font-weight:500}.exoa-marketing-delegue-page .pack.hot .pack-p i{color:#9CA0C4}.exoa-marketing-delegue-page .pack-l{list-style:none;display:flex;flex-direction:column;gap:11px;flex:1}.exoa-marketing-delegue-page .pack-l li{font-size:14px;line-height:1.5;padding-left:24px;position:relative;color:var(--body)}.exoa-marketing-delegue-page .pack.hot .pack-l li{color:#DDE0F0}.exoa-marketing-delegue-page .pack-l li::before{content:'';position:absolute;left:0;top:5px;width:13px;height:13px;border-radius:50%;background:var(--lime);box-shadow:inset 0 0 0 1px rgba(6,0,57,.2)}.exoa-marketing-delegue-page .pack-l li b{color:var(--ink);font-weight:700}.exoa-marketing-delegue-page .pack.hot .pack-l li b{color:#fff}.exoa-marketing-delegue-page .pack-l li.plus::before{background:var(--violet);box-shadow:none}.exoa-marketing-delegue-page .pack .btn{justify-content:center;width:100%}.exoa-marketing-delegue-page .sub{margin-top:26px;background:linear-gradient(100deg,var(--violet),var(--lav2));border-radius:22px;padding:36px 40px;color:#fff;display:flex;gap:32px;align-items:center;flex-wrap:wrap}.exoa-marketing-delegue-page .sub-l{flex:1;min-width:280px}.exoa-marketing-delegue-page .sub .mono{color:rgba(255,255,255,.75);display:block;margin-bottom:9px}.exoa-marketing-delegue-page .sub h3{font-size:26px;margin-bottom:10px}.exoa-marketing-delegue-page .sub p{font-size:14.5px;color:rgba(255,255,255,.9);line-height:1.6;max-width:58ch}.exoa-marketing-delegue-page .sub-r{text-align:right}.exoa-marketing-delegue-page .sub-r b{font-family:'Bricolage Grotesque',sans-serif;font-size:40px;font-weight:800;display:block;letter-spacing:-.03em}.exoa-marketing-delegue-page .sub-r i{font-style:normal;font-size:13px;color:rgba(255,255,255,.8)}.exoa-marketing-delegue-page .fmts{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-top:12px}.exoa-marketing-delegue-page .fmt{background:rgba(255,255,255,.045);border:1px solid rgba(255,255,255,.13);border-radius:16px;padding:22px;display:flex;flex-direction:column;gap:12px}.exoa-marketing-delegue-page .fmt-r{border:1.5px dashed rgba(205,249,128,.5);border-radius:7px;background:rgba(205,249,128,.07)}.exoa-marketing-delegue-page .fmt b{font-size:15px;color:#fff}.exoa-marketing-delegue-page .fmt small{font-size:12.5px;color:#9CA0C4;line-height:1.5;display:block}.exoa-marketing-delegue-page .steps{display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin-top:12px;border-top:1px solid var(--line)}.exoa-marketing-delegue-page .step{padding:30px 26px 30px 0;border-right:1px solid var(--line);position:relative}.exoa-marketing-delegue-page .step:last-child{border-right:0;padding-right:0}.exoa-marketing-delegue-page .step::before{content:'';position:absolute;top:-1px;left:0;width:38%;height:3px;background:var(--lime)}.exoa-marketing-delegue-page .step-n{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.12em;font-weight:700;color:var(--lav2);margin-bottom:12px;display:block}.exoa-marketing-delegue-page .step h3{font-size:18px;margin-bottom:9px;letter-spacing:-.01em}.exoa-marketing-delegue-page .step p{font-size:14px;color:var(--body);line-height:1.6}.exoa-marketing-delegue-page .step em{font-style:normal;display:inline-block;margin-top:10px;font-family:'JetBrains Mono',monospace;font-size:10.5px;font-weight:700;letter-spacing:.1em;color:var(--violet);background:rgba(118,112,232,.1);padding:4px 10px;border-radius:99px}.exoa-marketing-delegue-page .adds{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:14px}.exoa-marketing-delegue-page .add{background:#fff;border:1px solid var(--line);border-radius:14px;padding:20px 22px;display:flex;justify-content:space-between;align-items:center;gap:14px;transition:all .2s}.exoa-marketing-delegue-page .add:hover{border-color:var(--lav)}.exoa-marketing-delegue-page .add b{font-size:14.5px;font-weight:600;display:block;margin-bottom:3px}.exoa-marketing-delegue-page .add small{font-size:12.5px;color:var(--body)}.exoa-marketing-delegue-page .add i{font-style:normal;font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;color:var(--lav2);white-space:nowrap}.exoa-marketing-delegue-page .gal{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:12px}.exoa-marketing-delegue-page .gal-i{aspect-ratio:9\/16;border-radius:16px;overflow:hidden;border:1px solid rgba(255,255,255,.14);background:rgba(255,255,255,.04)}.exoa-marketing-delegue-page .gal-i.wide{grid-column:span 2;aspect-ratio:16\/10}.exoa-marketing-delegue-page .faq{max-width:840px;margin:0 auto;display:flex;flex-direction:column;gap:10px}.exoa-marketing-delegue-page details{background:#fff;border:1px solid var(--line);border-radius:14px;overflow:hidden;transition:border-color .2s}.exoa-marketing-delegue-page details[open]{border-color:var(--lav)}.exoa-marketing-delegue-page summary{padding:20px 24px;font-size:16px;font-weight:600;cursor:pointer;list-style:none;display:flex;justify-content:space-between;gap:18px;align-items:center}.exoa-marketing-delegue-page summary::-webkit-details-marker{display:none}.exoa-marketing-delegue-page summary::after{content:'+';font-family:'Bricolage Grotesque',sans-serif;font-size:22px;color:var(--lav2);flex-shrink:0;line-height:1}.exoa-marketing-delegue-page details[open] summary::after{content:'\u2212'}.exoa-marketing-delegue-page details p{padding:0 24px 22px;font-size:14.5px;color:var(--body);line-height:1.7}.exoa-marketing-delegue-page details p b{color:var(--ink)}.exoa-marketing-delegue-page .fin{background:var(--navy);color:#fff;padding:104px 0;position:relative;overflow:hidden}.exoa-marketing-delegue-page .fin::before{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:900px;height:900px;border-radius:50%;background:radial-gradient(circle,rgba(118,112,232,.3),transparent 65%)}.exoa-marketing-delegue-page .fin-in{position:relative;z-index:2;display:grid;grid-template-columns:1fr .9fr;gap:56px;align-items:start}.exoa-marketing-delegue-page .fin h2{font-size:clamp(32px,4vw,54px);max-width:16ch}.exoa-marketing-delegue-page .fin h2 em{font-style:normal;color:var(--lime)}.exoa-marketing-delegue-page .fin p{font-size:16.5px;color:#C6C9E0;line-height:1.65;margin-top:20px;max-width:50ch}.exoa-marketing-delegue-page .fin-tr{display:flex;flex-direction:column;gap:12px;margin-top:30px}.exoa-marketing-delegue-page .fin-tr div{display:flex;gap:11px;align-items:flex-start;font-size:14.5px;color:#DDE0F0;line-height:1.5}.exoa-marketing-delegue-page .fin-tr b{color:var(--lime);flex-shrink:0}.exoa-marketing-delegue-page .form{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.14);border-radius:22px;padding:32px;backdrop-filter:blur(10px)}.exoa-marketing-delegue-page .form h3{font-size:20px;margin-bottom:6px}.exoa-marketing-delegue-page .form>p{font-size:13.5px;color:#9CA0C4;margin-bottom:22px}.exoa-marketing-delegue-page .fg{display:grid;grid-template-columns:1fr 1fr;gap:13px}.exoa-marketing-delegue-page .fld{display:flex;flex-direction:column;gap:6px}.exoa-marketing-delegue-page .fld.full{grid-column:1\/-1}.exoa-marketing-delegue-page .fld label{font-size:12.5px;font-weight:600;color:#DDE0F0}.exoa-marketing-delegue-page .fld input, .exoa-marketing-delegue-page .fld select, .exoa-marketing-delegue-page .fld textarea{padding:11px 14px;border-radius:9px;border:1px solid rgba(255,255,255,.18);background:rgba(255,255,255,.07);color:#fff;font-size:14px;font-family:inherit}.exoa-marketing-delegue-page .fld input::placeholder, .exoa-marketing-delegue-page .fld textarea::placeholder{color:#7C81A6}.exoa-marketing-delegue-page .fld select option{background:var(--navy);color:#fff}.exoa-marketing-delegue-page .fld input:focus, .exoa-marketing-delegue-page .fld select:focus, .exoa-marketing-delegue-page .fld textarea:focus{outline:0;border-color:var(--lime)}.exoa-marketing-delegue-page .chk{display:flex;gap:9px;align-items:flex-start;font-size:12px;color:#9CA0C4;line-height:1.5;margin-top:14px}.exoa-marketing-delegue-page .chk input{margin-top:2px;accent-color:var(--lime)}.exoa-marketing-delegue-page .ok{display:none;text-align:center;padding:36px 12px}.exoa-marketing-delegue-page .ok.on{display:block}.exoa-marketing-delegue-page .ok h3{font-size:23px;color:var(--lime);margin-bottom:10px}.exoa-marketing-delegue-page .ok p{font-size:14.5px;color:#C6C9E0;line-height:1.6}.exoa-marketing-delegue-page .foot{background:#04002B;color:#7C81A6;padding:34px 0;font-size:13px}.exoa-marketing-delegue-page .foot-in{max-width:1200px;margin:0 auto;padding:0 32px;display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;align-items:center}.exoa-marketing-delegue-page .foot a{color:#9CA0C4}.exoa-marketing-delegue-page .foot a:hover{color:#fff}@media(max-width:1000px){.exoa-marketing-delegue-page .bricks, .exoa-marketing-delegue-page .packs, .exoa-marketing-delegue-page .stats{grid-template-columns:1fr 1fr}.exoa-marketing-delegue-page .fmts{grid-template-columns:1fr 1fr}.exoa-marketing-delegue-page .steps{grid-template-columns:1fr 1fr}.exoa-marketing-delegue-page .step{border-right:0;border-bottom:1px solid var(--line);padding-right:26px}.exoa-marketing-delegue-page .fin-in{grid-template-columns:1fr}.exoa-marketing-delegue-page .gal{grid-template-columns:1fr 1fr}.exoa-marketing-delegue-page .gal-i.wide{grid-column:span 2}}@media(max-width:700px){.exoa-marketing-delegue-page .wrap{padding:0 20px}.exoa-marketing-delegue-page .nav-mid{display:none}.exoa-marketing-delegue-page .sec{padding:64px 0}.exoa-marketing-delegue-page .bricks, .exoa-marketing-delegue-page .packs, .exoa-marketing-delegue-page .stats, .exoa-marketing-delegue-page .fmts, .exoa-marketing-delegue-page .steps, .exoa-marketing-delegue-page .gal, .exoa-marketing-delegue-page .fg{grid-template-columns:1fr}.exoa-marketing-delegue-page .gal-i.wide{grid-column:span 1}.exoa-marketing-delegue-page .hero{padding-top:56px}.exoa-marketing-delegue-page .hero-cta{flex-direction:column;align-items:stretch}.exoa-marketing-delegue-page .hero-cta .btn{justify-content:center}.exoa-marketing-delegue-page .sub{padding:28px 24px}.exoa-marketing-delegue-page .sub-r{text-align:left}.exoa-marketing-delegue-page .head-row{margin-bottom:36px}.exoa-marketing-delegue-page .foot-in{flex-direction:column;align-items:flex-start}}.exoa-marketing-delegue-page .pack-l li.neu::before{background:var(--lav);box-shadow:none}.exoa-marketing-delegue-page .grid2{display:grid;grid-template-columns:repeat(2,1fr);gap:22px}.exoa-marketing-delegue-page .lev{background:rgba(255,255,255,.045);border:1px solid rgba(255,255,255,.13);border-radius:16px;padding:24px}.exoa-marketing-delegue-page .lev b{font-size:16px;color:#fff;display:block;margin-bottom:7px}.exoa-marketing-delegue-page .lev small{font-size:13.5px;color:#9CA0C4;line-height:1.6;display:block}.exoa-marketing-delegue-page .lev-t{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.12em;font-weight:700;color:var(--lime);display:block;margin-bottom:10px}.exoa-marketing-delegue-page .cmp{width:100%;border-collapse:collapse;font-size:14.5px;background:#fff;border:1px solid var(--line);border-radius:16px;overflow:hidden}.exoa-marketing-delegue-page .cmp th{padding:16px 18px;text-align:left;font-weight:700;font-size:13.5px;color:var(--ink);background:var(--bg);border-bottom:1px solid var(--line)}.exoa-marketing-delegue-page .cmp td{padding:15px 18px;color:var(--body);border-bottom:1px solid var(--line);line-height:1.5}.exoa-marketing-delegue-page .cmp tr:last-child td{border-bottom:0}.exoa-marketing-delegue-page .cmp td:first-child{font-weight:600;color:var(--ink)}.exoa-marketing-delegue-page .cmp .yes{color:var(--lav2);font-weight:600}.exoa-marketing-delegue-page .cmp-w{overflow-x:auto;border-radius:16px}@media(max-width:1000px){.exoa-marketing-delegue-page .grid2{grid-template-columns:1fr}}\n.exoa-marketing-delegue-page{margin-top:0 !important;padding-top:0 !important}\n.exoa-marketing-delegue-page .btn-primary,.exoa-marketing-delegue-page .btn.btn-primary{color:#060039 !important;background:#CDF980 !important;font-weight:700}\n\nbody .site-content,body #content,body #primary,body .site-main,body .ast-container,body .content-area,body .ast-plain-container,body .ast-separate-container,body .ast-separate-container .ast-article-single{padding-top:0 !important;margin-top:0 !important;background:#060039 !important}\nbody.ast-separate-container{background-color:#060039 !important}\nbody .entry-content,body .entry-header{margin:0 !important;padding:0 !important}\nbody .ast-article-single{padding:0 !important;margin:0 !important;border:none !important;background:transparent !important}\n\n.exoa-marketing-delegue-page .resx-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:8px}\n.exoa-marketing-delegue-page .resx-card{background:#fff;border:1px solid rgba(6,0,57,.1);border-radius:16px;padding:30px 26px}\n.exoa-marketing-delegue-page .resx-num{font-size:34px;font-weight:800;color:#060039;line-height:1.1}\n.exoa-marketing-delegue-page .resx-card h3{font-size:17px;font-weight:700;color:#060039;margin:6px 0 8px}\n.exoa-marketing-delegue-page .resx-card p{font-size:14px;color:#5A5E80;line-height:1.6;margin:0}\n.exoa-marketing-delegue-page .livr-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:8px}\n.exoa-marketing-delegue-page .livr-card{background:#F7F7FB;border:1px solid rgba(6,0,57,.08);border-radius:14px;padding:24px 22px}\n.exoa-marketing-delegue-page .livr-card h3{font-size:15.5px;font-weight:700;color:#060039;margin:0 0 6px}\n.exoa-marketing-delegue-page .livr-card h3 span{color:#8CC63F;font-weight:800;margin-right:6px}\n.exoa-marketing-delegue-page .livr-card p{font-size:13.5px;color:#5A5E80;line-height:1.6;margin:0}\n.exoa-marketing-delegue-page .expx-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:8px}\n.exoa-marketing-delegue-page .expx-card{display:block;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.14);border-radius:14px;padding:22px;text-decoration:none}\n.exoa-marketing-delegue-page .expx-card:hover{background:rgba(255,255,255,.1);border-color:rgba(205,249,128,.5)}\n.exoa-marketing-delegue-page .expx-card strong{display:block;color:#fff;font-size:15.5px;font-weight:700;margin-bottom:4px}\n.exoa-marketing-delegue-page .expx-card span{display:block;color:#C6C9E0;font-size:13px;line-height:1.5}\n.exoa-marketing-delegue-page .expx-card em{display:inline-block;margin-top:10px;font-style:normal;color:#CDF980;font-size:12px;font-weight:700}\n.exoa-marketing-delegue-page .hero h1{color:#fff !important}\n@media(max-width:900px){.exoa-marketing-delegue-page .resx-grid,.exoa-marketing-delegue-page .livr-grid,.exoa-marketing-delegue-page .expx-grid{grid-template-columns:1fr 1fr}}\n@media(max-width:600px){.exoa-marketing-delegue-page .resx-grid,.exoa-marketing-delegue-page .livr-grid,.exoa-marketing-delegue-page .expx-grid{grid-template-columns:1fr}}\n\/*exoa-landing-fix*\/\n.exoa-marketing-delegue-page .sec-dk h2,.exoa-marketing-delegue-page .sec-dk .h2,.exoa-marketing-delegue-page .sec-dk h3,.exoa-marketing-delegue-page .fin h2,.exoa-marketing-delegue-page .fin h3{color:#fff !important}\n.exoa-marketing-delegue-page .sec-dk .h2 em,.exoa-marketing-delegue-page .fin h2 em{color:#CDF980 !important}\n.exoa-marketing-delegue-page .sec:not(.sec-dk) h2,.exoa-marketing-delegue-page .sec:not(.sec-dk) .h2{color:#060039}\n.exoa-marketing-delegue-page .steps{border-top:0 !important;gap:16px !important}\n.exoa-marketing-delegue-page .step{border-right:0 !important;background:#fff;border:1px solid #E4E6FF !important;border-radius:14px;padding:26px 22px !important;box-shadow:0 6px 18px rgba(6,0,57,.05)}\n.exoa-marketing-delegue-page .step::before{position:static !important;display:block;width:44px;height:3px;border-radius:99px;margin-bottom:14px}\n@media(max-width:768px){\n  .exoa-marketing-delegue-page .reel{display:none !important}\n  .exoa-marketing-delegue-page .hero{padding-bottom:40px !important}\n  .exoa-marketing-delegue-page .hero-note{margin-bottom:0 !important}\n}\n\/*exoa-width-fix*\/\n.exoa-marketing-delegue-page .hero h1{max-width:24ch !important}\n.exoa-marketing-delegue-page .h2{max-width:38ch !important}\n.exoa-marketing-delegue-page .fin h2{max-width:28ch !important}\n.exoa-marketing-delegue-page .sec .wrap > p,.exoa-marketing-delegue-page .h2 + p,.exoa-marketing-delegue-page .tag ~ p,.exoa-marketing-delegue-page .fin p,.exoa-marketing-delegue-page .sub p{max-width:76ch !important}\n.exoa-marketing-delegue-page .hero-sub{max-width:70ch !important}\n\/*exoa-pack-align*\/\n.exoa-marketing-delegue-page .packs{align-items:stretch !important}\n.exoa-marketing-delegue-page .pack{height:100%}\n.exoa-marketing-delegue-page .pack > p{min-height:3.4em}\n.exoa-marketing-delegue-page .pack ul{flex:1}\n.exoa-marketing-delegue-page .pack .btn,.exoa-marketing-delegue-page .pack a[class*=\"btn\"]{margin-top:auto}\n@media(max-width:900px){.exoa-marketing-delegue-page .pack > p{min-height:0}}\n<\/style>\n<script type=\"application\/ld+json\">\n{\"@context\":\"https:\/\/schema.org\",\"@type\":\"Service\",\"name\":\"Marketing d\u00e9l\u00e9gu\u00e9 \u2014 \u00e9quipe marketing externalis\u00e9e Exoa\",\"description\":\"Une \u00e9quipe marketing compl\u00e8te \u00e0 disposition : strat\u00e9gie, contenu, publicit\u00e9, e-mailing, analyse. Sans recruter, sans charges, d\u00e8s 1 490\u20ac\/mois. France & Luxembourg.\",\"provider\":{\"@type\":\"Organization\",\"name\":\"Exoa\",\"url\":\"https:\/\/www.agence-exoa.com\"},\"areaServed\":[{\"@type\":\"Country\",\"name\":\"France\"},{\"@type\":\"Country\",\"name\":\"Luxembourg\"}],\"serviceType\":\"Marketing externalis\u00e9\",\"offers\":[{\"@type\":\"Offer\",\"name\":\"Renfort\",\"price\":\"1490\",\"priceCurrency\":\"EUR\"},{\"@type\":\"Offer\",\"name\":\"Pilotage\",\"price\":\"2490\",\"priceCurrency\":\"EUR\"},{\"@type\":\"Offer\",\"name\":\"Direction\",\"price\":\"3900\",\"priceCurrency\":\"EUR\"}]}\n<\/script>\n<script type=\"application\/ld+json\">\n{\"@context\":\"https:\/\/schema.org\",\"@type\":\"FAQPage\",\"mainEntity\":[{\"@type\":\"Question\",\"name\":\"En quoi est-ce diff\u00e9rent d'une agence classique ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Une agence vend des prestations ponctuelles. Nous prenons en charge la fonction marketing avec un plan, des priorit\u00e9s et une obligation de r\u00e9sultat sur la dur\u00e9e.\"}},{\"@type\":\"Question\",\"name\":\"Est-ce vraiment moins cher qu'un recrutement ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"La formule Pilotage revient \u00e0 environ 30 000\u20ac par an pour cinq comp\u00e9tences, outils compris \u2014 moins qu'un seul profil confirm\u00e9 charg\u00e9.\"}},{\"@type\":\"Question\",\"name\":\"Qui est mon interlocuteur au quotidien ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Un responsable de compte unique conna\u00eet votre dossier, coordonne l'\u00e9quipe et vous r\u00e9pond.\"}},{\"@type\":\"Question\",\"name\":\"Puis-je garder mes prestataires actuels ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Oui. Nous travaillons r\u00e9guli\u00e8rement aux c\u00f4t\u00e9s d'un d\u00e9veloppeur, d'un graphiste ou d'une agence d\u00e9j\u00e0 en place.\"}},{\"@type\":\"Question\",\"name\":\"Que se passe-t-il si j'arr\u00eate ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Vous conservez comptes, contenus, fichiers sources, audiences et acc\u00e8s, avec une documentation de reprise.\"}},{\"@type\":\"Question\",\"name\":\"Faut-il un budget publicitaire en plus ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Pour la publicit\u00e9, comptez au minimum 800\u20ac \u00e0 1 000\u20ac par mois de budget m\u00e9dia r\u00e9gl\u00e9 depuis votre propre compte. Le reste ne demande aucun budget additionnel.\"}},{\"@type\":\"Question\",\"name\":\"Travaillez-vous avec des concurrents directs ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Non. Sur un m\u00eame march\u00e9 et une m\u00eame zone g\u00e9ographique, nous n'accompagnons qu'une seule entreprise.\"}}]}\n<\/script>\n<div class=\"exoa-marketing-delegue-page\">\n<header class=\"hero\"><div class=\"wrap hero-in\">\n<div class=\"pill\"><span class=\"dot\"><\/span><span>Strat\u00e9gie \u00b7 Contenu \u00b7 Publicit\u00e9 \u00b7 Analyse<\/span><\/div>\n<h1>Une <em>\u00e9quipe marketing compl\u00e8te<\/em>, sans une seule <span class=\"v\">embauche<\/span>.<\/h1>\n<p class=\"hero-sub\">Un directeur marketing, un r\u00e9dacteur, un graphiste, un sp\u00e9cialiste publicit\u00e9 et un analyste : le dispositif dont vous auriez besoin, mutualis\u00e9 et disponible d\u00e8s le mois prochain. Vous pilotez votre activit\u00e9, nous pilotons votre visibilit\u00e9.<\/p>\n<div class=\"hero-cta\">\n<a href=\"#packs\" class=\"btn btn-lime\">Voir les formules \u2014 d\u00e8s 1 490\u20ac\/mois \u2192<\/a>\n<a href=\"#devis\" class=\"btn btn-ghost\">Obtenir une proposition chiffr\u00e9e<\/a>\n<\/div>\n<div class=\"hero-note\"><span><b>Op\u00e9rationnel<\/b> en 15 jours<\/span><span><b>5 m\u00e9tiers<\/b> pour le prix d&rsquo;un poste<\/span><span><b>Sans engagement<\/b> de dur\u00e9e<\/span><\/div>\n<div class=\"reel\">\n<div class=\"reel-frame\">\n<div class=\"reel-bar\"><i><\/i><i><\/i><i><\/i><em>PLAN MARKETING \u2014 TRIMESTRE EN COURS<\/em><\/div>\n<img decoding=\"async\" src=\"https:\/\/www.agence-exoa.com\/wp-content\/uploads\/2026\/08\/marketing-delegue-reporting.webp\" alt=\"Pilotage et diffusion : campagnes, conversions et rapport mensuel en euros\" loading=\"lazy\" style=\"width:100%;height:100%;object-fit:cover;object-position:top center;display:block;border-radius:inherit\">\n<\/div>\n<\/div>\n<\/div><\/header>\n\n<div class=\"marq\"><div class=\"marq-t\">\n<span>Strat\u00e9gie<\/span><span>Contenu<\/span><span>Publicit\u00e9<\/span><span>E-mailing<\/span><span>R\u00e9seaux sociaux<\/span><span>R\u00e9f\u00e9rencement<\/span><span>Analyse<\/span><span>Design<\/span><span>Automatisation<\/span>\n<span>Strat\u00e9gie<\/span><span>Contenu<\/span><span>Publicit\u00e9<\/span><span>E-mailing<\/span><span>R\u00e9seaux sociaux<\/span><span>R\u00e9f\u00e9rencement<\/span><span>Analyse<\/span><span>Design<\/span><span>Automatisation<\/span>\n<\/div><\/div>\n\n<section class=\"sec sec-dk\">\n<div class=\"wrap\">\n<div class=\"tag\"><i><\/i><span>Le constat<\/span><\/div>\n<h2 class=\"h2\">Recruter un profil marketing, <em>c&rsquo;est parier gros<\/em>.<\/h2>\n<p class=\"lead\">Un poste marketing co\u00fbte cher, met des mois \u00e0 se pourvoir, et un seul profil ne peut pas \u00eatre \u00e0 la fois strat\u00e8ge, r\u00e9dacteur, graphiste, publicitaire et analyste. La plupart des entreprises finissent avec un g\u00e9n\u00e9raliste d\u00e9bord\u00e9 \u2014 ou avec personne.<\/p>\n<div class=\"stats\">\n<div class=\"stat\"><div class=\"stat-n\">\u2248 55 k\u20ac<\/div><div class=\"stat-l\">Le co\u00fbt annuel charg\u00e9 d&rsquo;un profil marketing confirm\u00e9, avant m\u00eame de compter les outils, la formation et le temps de management.<\/div><\/div>\n<div class=\"stat\"><div class=\"stat-n\">4 mois<\/div><div class=\"stat-l\">Le d\u00e9lai moyen entre la d\u00e9cision de recruter et le premier jour r\u00e9ellement productif d&rsquo;un nouveau poste marketing.<\/div><\/div>\n<div class=\"stat\"><div class=\"stat-n\">5 m\u00e9tiers<\/div><div class=\"stat-l\">Le nombre de comp\u00e9tences distinctes qu&rsquo;un marketing efficace mobilise aujourd&rsquo;hui \u2014 impossible \u00e0 r\u00e9unir dans une seule personne.<\/div><\/div>\n<\/div>\n<\/div>\n<\/section>\n\n<section class=\"sec\" id=\"offre\">\n<div class=\"wrap\">\n<div class=\"head-row\">\n<div>\n<div class=\"tag\"><i><\/i><span>Votre \u00e9quipe<\/span><\/div>\n<h2 class=\"h2\">Cinq m\u00e9tiers, <em>un seul interlocuteur<\/em>.<\/h2>\n<\/div>\n<p class=\"lead\" style=\"margin:0;max-width:38ch\">Vous ne coordonnez rien : un responsable de compte unique orchestre l&rsquo;\u00e9quipe et vous rend des comptes.<\/p>\n<\/div>\n<div class=\"bricks\">\n<article class=\"brick\">\n<div class=\"brick-vis\"><img decoding=\"async\" src=\"https:\/\/www.agence-exoa.com\/wp-content\/uploads\/2026\/08\/marketing-delegue-strategie.webp\" alt=\"S\u00e9ance de strat\u00e9gie marketing \u2014 cadrage des objectifs et du plan d'action\" loading=\"lazy\" style=\"width:100%;height:100%;object-fit:cover;display:block;border-radius:inherit\"><\/div>\n<div class=\"brick-b\">\n<span class=\"brick-n\">R\u00d4LE 01<\/span>\n<h3>Direction marketing<\/h3>\n<p>Quelqu&rsquo;un qui d\u00e9cide quoi faire, dans quel ordre et pourquoi \u2014 puis qui l&rsquo;assume devant vous.<\/p>\n<ul><li>Plan marketing trimestriel \u00e9crit et prioris\u00e9<\/li><li>Positionnement, messages, offres<\/li><li>Arbitrage des budgets entre leviers<\/li><li>Point mensuel de pilotage avec vous<\/li><\/ul>\n<\/div>\n<\/article>\n<article class=\"brick\">\n<div class=\"brick-vis\"><img decoding=\"async\" src=\"https:\/\/www.agence-exoa.com\/wp-content\/uploads\/2026\/08\/marketing-delegue-creation.webp\" alt=\"Production cr\u00e9ative : articles, visuels, vid\u00e9os, publications et emails\" loading=\"lazy\" style=\"width:100%;height:100%;object-fit:cover;display:block;border-radius:inherit\"><\/div>\n<div class=\"brick-b\">\n<span class=\"brick-n\">R\u00d4LE 02<\/span>\n<h3>Production &#038; cr\u00e9ation<\/h3>\n<p>Les contenus qui alimentent tous vos canaux : \u00e9crits, visuels, anim\u00e9s. Produits, pas seulement recommand\u00e9s.<\/p>\n<ul><li>Articles, pages, fiches, argumentaires<\/li><li>Publications et carrousels pour les r\u00e9seaux<\/li><li>Vid\u00e9os motion et visuels publicitaires<\/li><li>Mod\u00e8les d&rsquo;e-mails et de documents commerciaux<\/li><\/ul>\n<\/div>\n<\/article>\n<article class=\"brick\">\n<div class=\"brick-vis\"><img decoding=\"async\" src=\"https:\/\/www.agence-exoa.com\/wp-content\/uploads\/2026\/08\/marketing-delegue-equipe.webp\" alt=\"Votre \u00e9quipe marketing d\u00e9di\u00e9e : les 5 p\u00f4les orchestr\u00e9s\" loading=\"lazy\" style=\"width:100%;height:100%;object-fit:cover;display:block;border-radius:inherit\"><\/div>\n<div class=\"brick-b\">\n<span class=\"brick-n\">R\u00d4LE 03<\/span>\n<h3>Diffusion &#038; mesure<\/h3>\n<p>Un contenu qui n&rsquo;est pas diffus\u00e9 et mesur\u00e9 n&rsquo;existe pas. Nous nous chargeons des deux.<\/p>\n<ul><li>Campagnes publicitaires et r\u00e9f\u00e9rencement<\/li><li>S\u00e9quences d&rsquo;e-mails et automatisations<\/li><li>Suivi des conversions et tableaux de bord<\/li><li>Rapport mensuel en euros, pas en vues<\/li><\/ul>\n<\/div>\n<\/article>\n<\/div>\n<\/div>\n<\/section>\n\n<section class=\"sec sec-lt\" id=\"packs\">\n<div class=\"wrap\">\n<div class=\"head-row\">\n<div>\n<div class=\"tag\"><i><\/i><span>Les formules<\/span><\/div>\n<h2 class=\"h2\">Trois niveaux <em>d&rsquo;implication<\/em>.<\/h2>\n<\/div>\n<p class=\"lead\" style=\"margin:0;max-width:38ch\">Du renfort ponctuel \u00e0 la direction marketing compl\u00e8te. Vous montez ou descendez de formule \u00e0 tout moment.<\/p>\n<\/div>\n<div class=\"packs\">\n<div class=\"pack\">\n\n<span class=\"pack-n\">Formule 01<\/span>\n<h3>Renfort<\/h3>\n<p class=\"pack-for\">Pour une entreprise qui sait o\u00f9 elle va mais n&rsquo;a personne pour ex\u00e9cuter.<\/p>\n<div class=\"pack-p\"><b>1 490\u20ac<\/b><i>HT \/ mois<\/i><\/div>\n<ul class=\"pack-l\"><li><b>Plan trimestriel<\/b> \u00e9crit et prioris\u00e9<\/li><li><b>8 publications<\/b> par mois, visuels inclus<\/li><li><b>2 articles<\/b> optimis\u00e9s par mois<\/li><li>Gestion d&rsquo;un levier publicitaire<\/li><li>Rapport mensuel comment\u00e9<\/li><\/ul>\n<a href=\"#devis\" class=\"btn btn-out\">Choisir Renfort<\/a>\n<\/div>\n<div class=\"pack hot\">\n<span class=\"pack-bdg\">Le plus choisi<\/span>\n<span class=\"pack-n\">Formule 02<\/span>\n<h3>Pilotage<\/h3>\n<p class=\"pack-for\">Pour confier l&rsquo;ensemble du marketing op\u00e9rationnel, du contenu \u00e0 la publicit\u00e9.<\/p>\n<div class=\"pack-p\"><b>2 490\u20ac<\/b><i>HT \/ mois<\/i><\/div>\n<ul class=\"pack-l\"><li><b>Tout le renfort<\/b>, avec un volume doubl\u00e9<\/li><li><b>16 publications<\/b> et <b>4 articles<\/b> par mois<\/li><li><b>2 vid\u00e9os motion<\/b> par mois<\/li><li>Publicit\u00e9 multi-levier et retargeting<\/li><li>S\u00e9quences d&rsquo;e-mails et automatisations<\/li><li>Point mensuel de 1 heure \u00b7 responsable d\u00e9di\u00e9<\/li><\/ul>\n<a href=\"#devis\" class=\"btn btn-lime\">Choisir Pilotage<\/a>\n<\/div>\n<div class=\"pack\">\n\n<span class=\"pack-n\">Formule 03<\/span>\n<h3>Direction<\/h3>\n<p class=\"pack-for\">Pour remplacer une direction marketing interne, avec la production qui va avec.<\/p>\n<div class=\"pack-p\"><b>3 900\u20ac<\/b><i>HT \/ mois<\/i><\/div>\n<ul class=\"pack-l\"><li><b>Tout le pilotage<\/b>, sans limite de volume raisonnable<\/li><li class=\"plus\"><b>Direction marketing<\/b> : plan annuel, budgets, arbitrages<\/li><li class=\"plus\"><b>Refonte des pages<\/b> et du tunnel de conversion<\/li><li class=\"plus\"><b>Comit\u00e9 mensuel<\/b> avec votre direction<\/li><li class=\"plus\">Pr\u00e9sence aux r\u00e9unions commerciales si utile<\/li><li class=\"plus\">Acc\u00e8s prioritaire \u00e0 toute l&rsquo;\u00e9quipe<\/li><\/ul>\n<a href=\"#devis\" class=\"btn btn-out\">Choisir Direction<\/a>\n<\/div>\n<\/div>\n\n<div class=\"sub\">\n<div class=\"sub-l\">\n<span class=\"mono\">Pour commencer<\/span>\n<h3>Diagnostic marketing 360\u00b0<\/h3>\n<p>Trois semaines pour cartographier votre situation : march\u00e9, concurrence, canaux existants, tunnel de conversion, contenus, outils. Vous recevez un plan d&rsquo;action prioris\u00e9 sur douze mois, chiffr\u00e9 et s\u00e9quenc\u00e9 \u2014 exploitable par nous, par votre \u00e9quipe ou par un autre prestataire. D\u00e9duit de la premi\u00e8re mensualit\u00e9 si vous poursuivez avec nous.<\/p>\n<\/div>\n<div class=\"sub-r\">\n<b>2 500\u20ac<\/b>\n<i>HT \u00b7 une seule fois<\/i>\n<\/div>\n<\/div>\n<\/div>\n<\/section>\n\n<section class=\"sec sec-dk\">\n<div class=\"wrap\">\n<div class=\"tag\"><i><\/i><span>Ce que couvre l&rsquo;\u00e9quipe<\/span><\/div>\n<h2 class=\"h2\">Le p\u00e9rim\u00e8tre <em>complet<\/em>.<\/h2>\n<p class=\"lead\">Tout ne se d\u00e9clenche pas en m\u00eame temps : le plan trimestriel d\u00e9cide de ce qui compte maintenant, et de ce qui attendra.<\/p>\n<div class=\"grid2\" style=\"margin-top:44px\">\n<div class=\"lev\"><span class=\"lev-t\">FONDATIONS<\/span><b>Positionnement &#038; messages<\/b><small>Ce que vous vendez, \u00e0 qui, et pourquoi vous plut\u00f4t qu&rsquo;un autre. Sans cette base \u00e9crite, tout le reste part dans toutes les directions.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">ATTRACTION<\/span><b>Contenu &#038; r\u00e9f\u00e9rencement<\/b><small>Articles, pages et fiches con\u00e7us pour \u00eatre trouv\u00e9s sur les moteurs de recherche et repris par les assistants conversationnels.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">VISIBILIT\u00c9<\/span><b>R\u00e9seaux sociaux<\/b><small>Publications r\u00e9guli\u00e8res, carrousels, vid\u00e9os courtes. Une pr\u00e9sence tenue plut\u00f4t qu&rsquo;une avalanche suivie de six mois de silence.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">ACC\u00c9L\u00c9RATION<\/span><b>Publicit\u00e9 digitale<\/b><small>Google, Meta, LinkedIn, TikTok, programmatique. Le levier qui donne du volume imm\u00e9diat quand les fondations sont pos\u00e9es.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">RELATION<\/span><b>E-mailing &#038; automatisation<\/b><small>S\u00e9quences de bienvenue, r\u00e9activation, relances commerciales, newsletters. Le canal le moins co\u00fbteux et le plus n\u00e9glig\u00e9.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">PILOTAGE<\/span><b>Mesure &#038; reporting<\/b><small>Suivi des conversions, tableaux de bord, rapport mensuel. Savoir ce qui rapporte pour arr\u00eater de financer ce qui ne rapporte pas.<\/small><\/div>\n<\/div>\n<\/div>\n<\/section>\n\n<section class=\"sec\" id=\"process\">\n<div class=\"wrap\">\n<div class=\"tag\"><i><\/i><span>Comment \u00e7a se passe<\/span><\/div>\n<h2 class=\"h2\">Op\u00e9rationnel en <em>quinze jours<\/em>.<\/h2>\n<div class=\"steps\">\n<div class=\"step\">\n<span class=\"step-n\">\u00c9TAPE 01<\/span>\n<h3>Immersion<\/h3>\n<p>Deux ateliers pour comprendre votre activit\u00e9, vos clients, vos marges et vos contraintes. Nous r\u00e9cup\u00e9rons les acc\u00e8s et l&rsquo;existant.<\/p>\n<em>JOURS 1 \u00c0 5<\/em>\n<\/div>\n<div class=\"step\">\n<span class=\"step-n\">\u00c9TAPE 02<\/span>\n<h3>Plan trimestriel<\/h3>\n<p>Un document \u00e9crit : les priorit\u00e9s, les livrables, le calendrier et les indicateurs de succ\u00e8s. Valid\u00e9 par vous avant ex\u00e9cution.<\/p>\n<em>JOURS 6 \u00c0 15<\/em>\n<\/div>\n<div class=\"step\">\n<span class=\"step-n\">\u00c9TAPE 03<\/span>\n<h3>Production<\/h3>\n<p>L&rsquo;\u00e9quipe entre en action. Contenus, campagnes, s\u00e9quences : les premiers livrables arrivent d\u00e8s la troisi\u00e8me semaine.<\/p>\n<em>\u00c0 PARTIR DU JOUR 15<\/em>\n<\/div>\n<div class=\"step\">\n<span class=\"step-n\">\u00c9TAPE 04<\/span>\n<h3>Pilotage<\/h3>\n<p>Point mensuel, rapport chiffr\u00e9, r\u00e9ajustement du plan. Chaque trimestre, nous r\u00e9\u00e9crivons les priorit\u00e9s avec vous.<\/p>\n<em>EN CONTINU<\/em>\n<\/div>\n<\/div>\n<\/div>\n<\/section>\n\n<section class=\"sec sec-lt\">\n<div class=\"wrap\">\n<div class=\"head-row\">\n<div>\n<div class=\"tag\"><i><\/i><span>\u00c0 la carte<\/span><\/div>\n<h2 class=\"h2\">Besoin d&rsquo;aller <em>plus loin<\/em> ?<\/h2>\n<\/div>\n<p class=\"lead\" style=\"margin:0;max-width:38ch\">Chaque option s&rsquo;ajoute \u00e0 une formule ou se commande seule. Tarifs HT.<\/p>\n<\/div>\n<div class=\"adds\">\n<div class=\"add\"><div><b>Diagnostic marketing 360\u00b0<\/b><small>Plan prioris\u00e9 sur 12 mois<\/small><\/div><i>2 500\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Refonte de site web<\/b><small>Site vitrine ou e-commerce<\/small><\/div><i>d\u00e8s 1 250\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Identit\u00e9 graphique<\/b><small>Logo, univers, d\u00e9clinaisons<\/small><\/div><i>d\u00e8s 500\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Vid\u00e9o motion additionnelle<\/b><small>15 secondes, 4 formats<\/small><\/div><i>390\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Atelier positionnement<\/b><small>Demi-journ\u00e9e avec votre direction<\/small><\/div><i>890\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Suivi &#038; conformit\u00e9<\/b><small>GA4, GTM, consentement RGPD<\/small><\/div><i>850\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Formation de votre \u00e9quipe<\/b><small>Transfert de comp\u00e9tences, demi-journ\u00e9e<\/small><\/div><i>590\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Landing page d\u00e9di\u00e9e<\/b><small>Page de campagne ou de lancement<\/small><\/div><i>750\u20ac<\/i><\/div>\n<\/div>\n<\/div>\n<\/section>\n\n<section class=\"sec\" id=\"faq\">\n<div class=\"wrap\">\n<div style=\"text-align:center;margin-bottom:44px\">\n<div class=\"tag\" style=\"justify-content:center\"><i><\/i><span>Questions fr\u00e9quentes<\/span><\/div>\n<h2 class=\"h2\" style=\"margin:0 auto\">Ce qu&rsquo;on nous demande <em>avant de signer<\/em>.<\/h2>\n<\/div>\n<div class=\"faq\">\n<details open><summary>En quoi est-ce diff\u00e9rent d&rsquo;une agence classique ?<\/summary><p>Une agence vend des prestations : un site, une campagne, dix articles. Nous prenons en charge <b>une fonction<\/b> \u2014 le marketing \u2014 avec un plan, des priorit\u00e9s et une obligation de r\u00e9sultat sur la dur\u00e9e. La diff\u00e9rence se voit surtout au bout de six mois.<\/p><\/details>\n<details><summary>Est-ce vraiment moins cher qu&rsquo;un recrutement ?<\/summary><p>\u00c0 2 490\u20ac par mois, la formule Pilotage revient \u00e0 environ <b>30 000\u20ac par an<\/b> pour cinq comp\u00e9tences, outils compris. Un seul profil confirm\u00e9 co\u00fbte davantage, charg\u00e9, sans les outils et sans la polyvalence.<\/p><\/details>\n<details><summary>Qui est mon interlocuteur au quotidien ?<\/summary><p>Un <b>responsable de compte unique<\/b>. C&rsquo;est lui qui conna\u00eet votre dossier, coordonne l&rsquo;\u00e9quipe et vous r\u00e9pond. Vous n&rsquo;avez jamais \u00e0 r\u00e9expliquer votre activit\u00e9 \u00e0 quelqu&rsquo;un de nouveau.<\/p><\/details>\n<details><summary>Puis-je garder mes prestataires actuels ?<\/summary><p>Oui. Nous travaillons r\u00e9guli\u00e8rement <b>aux c\u00f4t\u00e9s<\/b> d&rsquo;un d\u00e9veloppeur, d&rsquo;un graphiste ou d&rsquo;une agence d\u00e9j\u00e0 en place. Nous coordonnons plut\u00f4t que de tout reprendre.<\/p><\/details>\n<details><summary>Que se passe-t-il si j&rsquo;arr\u00eate ?<\/summary><p>Vous conservez tout : comptes, contenus, fichiers sources, audiences, acc\u00e8s. Nous fournissons une <b>documentation de reprise<\/b> pour qu&rsquo;un successeur, interne ou externe, puisse encha\u00eener sans repartir de z\u00e9ro.<\/p><\/details>\n<details><summary>Faut-il un budget publicitaire en plus ?<\/summary><p>Pour la partie publicit\u00e9, oui \u2014 comptez au minimum <b>800\u20ac \u00e0 1 000\u20ac par mois<\/b> de budget m\u00e9dia, r\u00e9gl\u00e9 depuis votre propre compte. Le reste du dispositif (contenu, r\u00e9f\u00e9rencement, e-mailing) ne demande aucun budget additionnel.<\/p><\/details>\n<details><summary>Travaillez-vous avec des concurrents directs ?<\/summary><p>Non. Sur un m\u00eame march\u00e9 et une m\u00eame zone g\u00e9ographique, nous <b>n&rsquo;accompagnons qu&rsquo;une seule entreprise<\/b>.<\/p><\/details>\n<\/div>\n<\/div>\n<\/section>\n\n\n<section style=\"background:#F7F7FB;padding:90px 20px\"><div style=\"max-width:1240px;margin:0 auto\">\n<div style=\"text-align:center;margin-bottom:48px\"><h2 style=\"color:#060039\">Des r\u00e9sultats <span style=\"color:#7B87E8\">mesurables<\/span><\/h2><\/div>\n<div class=\"resx-grid\"><div class=\"resx-card\"><div class=\"resx-num\">-50%<\/div><h3>vs un recrutement<\/h3><p>Le co\u00fbt d&rsquo;une \u00e9quipe marketing compl\u00e8te, pour moins qu&rsquo;un salaire charg\u00e9.<\/p><\/div><div class=\"resx-card\"><div class=\"resx-num\">5+<\/div><h3>experts mobilis\u00e9s<\/h3><p>SEO, Ads, social, cr\u00e9ation, data : les comp\u00e9tences selon vos besoins du mois.<\/p><\/div><div class=\"resx-card\"><div class=\"resx-num\">0<\/div><h3>engagement long terme<\/h3><p>Un service qui se juge sur ses r\u00e9sultats, pas sur son contrat.<\/p><\/div><\/div><\/div><\/section>\n<section style=\"background:#fff;padding:90px 20px\"><div style=\"max-width:1240px;margin:0 auto\">\n<div style=\"text-align:center;margin-bottom:48px\"><h2 style=\"color:#060039\">Ce que nous livrons <span style=\"color:#7B87E8\">concr\u00e8tement<\/span><\/h2><p style=\"color:#5A5E80;max-width:640px;margin:12px auto 0\">Chaque accompagnement marketing d\u00e9l\u00e9gu\u00e9 s&rsquo;appuie sur des livrables tangibles.<\/p><\/div>\n<div class=\"livr-grid\"><div class=\"livr-card\"><h3><span>\u2713<\/span>Direction marketing externalis\u00e9e<\/h3><p>Un responsable qui pilote votre marketing comme un interne.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Plan d&rsquo;action mensuel<\/h3><p>Priorit\u00e9s, budgets et livrables valid\u00e9s ensemble chaque mois.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Ex\u00e9cution compl\u00e8te<\/h3><p>Campagnes, contenus, site, emailing : notre \u00e9quipe produit.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Coordination prestataires<\/h3><p>Nous orchestrons vos prestataires existants si besoin.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Outils et reporting<\/h3><p>Stack marketing, dashboards et rituels de pilotage install\u00e9s.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Transfert de comp\u00e9tences<\/h3><p>Votre \u00e9quipe monte en comp\u00e9tence \u00e0 mesure que nous avan\u00e7ons.<\/p><\/div><\/div><\/div><\/section>\n<section style=\"background:#060039;padding:90px 20px\"><div style=\"max-width:1240px;margin:0 auto\">\n<div style=\"text-align:center;margin-bottom:48px\"><h2 style=\"color:#fff\">Un dispositif appuy\u00e9 sur <span style=\"color:#CDF980\">toutes nos expertises<\/span><\/h2><\/div>\n<div class=\"expx-grid\"><a class=\"expx-card\" href=\"\/audit-strategique\/\"><strong>Audit strat\u00e9gique<\/strong><span>\u00c9tat des lieux complet de votre acquisition.<\/span><em>D\u00c9COUVRIR \u2192<\/em><\/a><a class=\"expx-card\" href=\"\/agence-seo\/\"><strong>SEO<\/strong><span>Le canal au meilleur ROI long terme.<\/span><em>D\u00c9COUVRIR \u2192<\/em><\/a><a class=\"expx-card\" href=\"\/agence-sea\/\"><strong>SEA &#038; Google Ads<\/strong><span>Des r\u00e9sultats imm\u00e9diats et pilot\u00e9s.<\/span><em>D\u00c9COUVRIR \u2192<\/em><\/a><a class=\"expx-card\" href=\"\/generation-de-leads\/\"><strong>G\u00e9n\u00e9ration de leads<\/strong><span>Un flux de prospects qualifi\u00e9s et mesurable.<\/span><em>D\u00c9COUVRIR \u2192<\/em><\/a><a class=\"expx-card\" href=\"\/automatisation-marketing\/\"><strong>Automatisation<\/strong><span>Nurturing, scoring et sc\u00e9narios qui travaillent 24h\/24.<\/span><em>D\u00c9COUVRIR \u2192<\/em><\/a><a class=\"expx-card\" href=\"\/strategie-360\/\"><strong>Strat\u00e9gie 360<\/strong><span>L&rsquo;orchestration compl\u00e8te de vos canaux.<\/span><em>D\u00c9COUVRIR \u2192<\/em><\/a><\/div><\/div><\/section>\n<section class=\"fin\" id=\"devis\">\n<div class=\"wrap fin-in\">\n<div>\n<div class=\"tag\"><i><\/i><span>D\u00e9marrer<\/span><\/div>\n<h2>Confiez-nous le marketing. <em>Gardez votre m\u00e9tier.<\/em><\/h2>\n<p>D\u00e9crivez votre situation en quelques lignes : nous revenons sous vingt-quatre heures avec la formule adapt\u00e9e et les premi\u00e8res priorit\u00e9s que nous voyons.<\/p>\n<div class=\"fin-tr\">\n<div><b>\u2713<\/b><span>Proposition chiffr\u00e9e sous 24 h<\/span><\/div>\n<div><b>\u2713<\/b><span>Un responsable de compte unique<\/span><\/div>\n<div><b>\u2713<\/b><span>Vous restez propri\u00e9taire de tout<\/span><\/div>\n<div><b>\u2713<\/b><span>Une seule entreprise par march\u00e9 et par zone<\/span><\/div>\n<\/div>\n<\/div>\n<div class=\"exoa-contact-redirect\" style=\"background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.16);border-radius:18px;padding:38px 32px;text-align:center\">\n<h3 style=\"color:#fff;font-size:22px;margin:0 0 10px\">Parlons de votre projet<\/h3>\n<p style=\"color:#C6C9E0;margin:0 0 22px;line-height:1.6\">R\u00e9ponse sous 24 heures ouvr\u00e9es \u2014 audit et premi\u00e8res recommandations offerts.<\/p>\n<a href=\"\/contact\/?sujet=Marketing+d\u00e9l\u00e9gu\u00e9\" style=\"display:inline-block;background:#CDF980;color:#060039;font-weight:700;font-size:16px;border-radius:999px;padding:15px 32px;text-decoration:none\">Demander un devis gratuit \u2192<\/a>\n<p style=\"color:#9CA3C8;font-size:12.5px;margin:14px 0 0\">Vous serez redirig\u00e9 vers notre formulaire de contact s\u00e9curis\u00e9.<\/p>\n<\/div>\n<\/div>\n<\/section>\n\n<footer class=\"foot\"><div class=\"foot-in\">\n<span>\u00a9 2026 Exoa \u2014 Marketing d\u00e9l\u00e9gu\u00e9 \u00b7 Tarifs HT, France et Luxembourg<\/span>\n<span><a href=\"\/\">Accueil<\/a> \u00b7 <a href=\"\/pack-motion-ads\/\">Motion &amp; Ads<\/a> \u00b7 <a href=\"\/regie-publicitaire-deleguee\/\">Publicit\u00e9<\/a> \u00b7 <a href=\"\/marketing-delegue\/\">Marketing d\u00e9l\u00e9gu\u00e9<\/a> \u00b7 <a href=\"\/expertise-acquisition\/\">Acquisition<\/a> \u00b7 <a href=\"\/expertise-conversion\/\">Conversion<\/a><\/span>\n<\/div><\/footer>\n\n\n<script>\nfunction mSend(e){e.preventDefault();document.getElementById('mFields').style.display='none';document.getElementById('mOk').classList.add('on');return false}\n<\/script>\n<\/div>\n<script>\/\/ @ds-adherence-ignore -- omelette starter scaffold (raw elements\/hex\/px by design)\n\/\/ Copied omelette starter. Re-running copy_starter_component with this kind overwrites this file with the latest version (page content is unaffected).\n\/* BEGIN USAGE *\/\n\/**\n * <image-slot> \u2014 user-fillable image placeholder.\n *\n * Drop this into a deck, mockup, or page wherever a design needs an image.\n * You control the slot's shape; it sizes to its container by default. When the search_stock_photos tool\n * is available, prefill the slot by default \u2014 write the photo's URL into\n * src (with credit\/credit-href); the user can still fill or replace it\n * by dragging an image file onto it (or clicking to browse). The dropped\n * image persists across reloads via a .image-slots.state.json sidecar \u2014\n * same read-via-fetch \/ write-via-window.omelette pattern as\n * design_canvas.jsx, so the filled slot shows on share links, downloaded\n * zips, and PPTX export. Outside the omelette runtime the slot is read-only.\n *\n * The sidecar is a SIBLING of the HTML file that uses this component: the\n * read is a document-relative fetch, and the host resolves the bridge's\n * sidecar writes into the previewed file's directory to match (same\n * contract as design_canvas.jsx). Pages in the same directory share one\n * sidecar; keep slot ids distinct across them.\n *\n * Attributes:\n *   id           Persistence key. REQUIRED for the drop to survive reload \u2014\n *                every slot on the page needs a distinct id.\n *   shape        'rect' | 'rounded' | 'circle' | 'pill'   (default 'rounded')\n *                'circle' applies 50% border-radius; on a non-square slot\n *                that's an ellipse \u2014 set equal width and height for a true\n *                circle.\n *   radius       Corner radius in px for 'rounded'.       (default 12)\n *   mask         Any CSS clip-path value. Overrides `shape` \u2014 use this for\n *                hexagons, blobs, arbitrary polygons.\n *   fit          Initial framing baseline: cover | contain.   (default 'cover')\n *                cover starts the image filling the frame (overflow cropped);\n *                contain starts it fully visible (letterboxed). Either way the\n *                user can always pan\/scale from there \u2014 double-click, or the\n *                Edit control, enters reframe mode (drag to move, scroll or\n *                corner-handles to scale; Escape \/ click-out commits). The\n *                crop persists alongside the image in the sidecar.\n *   placeholder  Empty-state caption.                      (default 'Drop an image')\n *   src          Optional initial\/fallback image URL. Prefill it with a real\n *                photo via search_stock_photos when that tool is available\n *                (set credit\/credit-href from the result). A user drop\n *                overrides it; clearing the drop reveals src again.\n *   credit       Attribution text shown as a small overlay at the\n *                bottom-left of the filled slot. REQUIRED whenever src\n *                points at any Unsplash host (images.unsplash.com,\n *                plus.unsplash.com, \u2026): an Unsplash src with no credit\n *                renders an error tile INSTEAD of the photo (Unsplash\n *                terms forbid showing their photos unattributed). Use the\n *                exact form 'Photo by {photographer name} on Unsplash' \u2014\n *                the overlay then links the name to credit-href and\n *                'Unsplash' to the Unsplash homepage, and links back to\n *                unsplash.com automatically get the required utm referral\n *                params appended at render time. The credit belongs to\n *                the src image, so it only shows while src is what's\n *                displayed \u2014 a user-dropped image hides it.\n *   credit-href  Link for the photographer's name in the credit overlay\n *                (their Unsplash profile URL from the stock-photo search\n *                results). http(s) URLs only \u2014 anything else renders the\n *                name as plain text.\n *\n * Sizing: the slot fills its container by default (width\/height 100%).\n * Put it in a sized wrapper \u2014 absolutely positioned, a grid cell, a fixed\n * frame \u2014 and it takes exactly that box. When the parent's height is\n * indefinite (ordinary flow), it falls back to full width at a 3:2 aspect\n * ratio instead of collapsing. In a shrink-to-fit parent (a float,\n * width:max-content, an unsized absolute wrapper), percentages have\n * nothing to resolve against \u2014 size the slot or its wrapper explicitly\n * there. For a fixed-size slot, set\n * width\/height on the element itself (inline style), which overrides the\n * default. When\n * layering content above a slot (full-bleed layouts), make the overlay\n * click-through \u2014 pointer-events: none on scrims\/text plates, re-enabled\n * on interactive children \u2014 so the slot's hover controls stay reachable.\n * Keep the slot's bottom-left corner visually clear as well: the credit\n * overlay renders there, and a dark fade or text plate covering it hides\n * the attribution Unsplash's terms require \u2014 end the fade above that\n * corner, or keep it nearly transparent where the credit sits.\n *\n * Usage:\n *   <div style=\"position:relative;width:100%;height:100%\">      <!-- full-bleed: -->\n *     <image-slot id=\"bg\" shape=\"rect\"><\/image-slot>            <!-- fills the wrapper -->\n *   <\/div>\n *   <image-slot id=\"hero\"   style=\"width:800px;height:450px\" shape=\"rounded\" radius=\"20\"\n *               placeholder=\"Drop a hero image\"><\/image-slot>\n *   <image-slot id=\"avatar\" style=\"width:120px;height:120px\" shape=\"circle\"><\/image-slot>\n *   <image-slot id=\"kite\"   style=\"width:300px;height:300px\"\n *               mask=\"polygon(50% 0, 100% 50%, 50% 100%, 0 50%)\"><\/image-slot>\n *\/\n\/* END USAGE *\/\n\n(() => {\n  const STATE_FILE = '.image-slots.state.json';\n\n  \/\/ Unsplash terms require visible attribution wherever their photos\n  \/\/ display, and every link back to unsplash.com must carry utm referral\n  \/\/ params. Two render-time rules enforce that here:\n  \/\/  - an Unsplash-src slot with NO credit attribute renders an error\n  \/\/    tile INSTEAD of the photo (an uncredited Unsplash photo on screen\n  \/\/    is itself the terms violation, so it never renders bare);\n  \/\/  - rendered credit links pointing at unsplash.com get the referral\n  \/\/    params appended when absent (credit-href values live in page\n  \/\/    content that can't be edited after the fact).\n  \/\/ Keep the utm_source value in sync with UTM_SOURCE in\n  \/\/ platform\/web-agent\/unsplash.ts \u2014 this file is a project-local\n  \/\/ artifact and cannot import it (equality is pinned by tests).\n  const UNSPLASH_HOMEPAGE_HREF =\n    'https:\/\/unsplash.com\/?utm_source=claude_design&utm_medium=referral';\n  \/\/ Host rule mirrors the hotlink validator that admits Unsplash srcs into\n  \/\/ pages in the first place (cdn$ in unsplash.ts: apex or any subdomain)\n  \/\/ \u2014 Unsplash+ results serve from plus.unsplash.com, not just images.*,\n  \/\/ and an admitted-but-uncredited photo must error whatever unsplash\n  \/\/ host it rides on.\n  \/\/ Trailing-dot FQDNs (images.unsplash.com.) are the same host to the\n  \/\/ browser but would miss the regex \u2014 strip one dot so the check fails\n  \/\/ CLOSED (unrecognized-but-real Unsplash srcs must error, not render).\n  const isUnsplashHost = (u) => {\n    try {\n      return \/(^|\\.)unsplash\\.com$\/.test(\n        new URL(u, document.baseURI).hostname.replace(\/\\.$\/, '')\n      );\n    } catch {\n      return false;\n    }\n  };\n  \/\/ Render-time referral normalization for links back to Unsplash:\n  \/\/ appends utm_source\/utm_medium when absent, preserves every existing\n  \/\/ query param, never overwrites an existing utm_source, and passes\n  \/\/ non-Unsplash URLs through untouched. Input is an ABSOLUTE validated\n  \/\/ http(s) URL (the credit render funnel resolves + validates first).\n  const withReferral = (href) => {\n    try {\n      const u = new URL(href);\n      if (!\/(^|\\.)unsplash\\.com$\/.test(u.hostname.replace(\/\\.$\/, ''))) {\n        return href;\n      }\n      if (!u.searchParams.has('utm_source')) {\n        u.searchParams.set('utm_source', 'claude_design');\n      }\n      if (!u.searchParams.has('utm_medium')) {\n        u.searchParams.set('utm_medium', 'referral');\n      }\n      return u.toString();\n    } catch (e) {\n      return href;\n    }\n  };\n  \/\/ 2\u00d7 a ~600px slot in a 1920-wide deck \u2014 retina-sharp without making the\n  \/\/ sidecar enormous. A 1200px WebP at q=0.85 is ~150-300KB.\n  const MAX_DIM = 1200;\n  \/\/ Raster formats only. SVG is excluded (can carry script; createImageBitmap\n  \/\/ on SVG blobs is inconsistent). GIF is excluded because the canvas\n  \/\/ re-encode keeps only the first frame, so an animated GIF would silently\n  \/\/ go still \u2014 better to reject than surprise.\n  const ACCEPT = ['image\/png', 'image\/jpeg', 'image\/webp', 'image\/avif'];\n\n  \/\/ \u2500\u2500 Shared sidecar store \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  \/\/ One fetch + immediate write-on-change for every <image-slot> on the\n  \/\/ page. Reads via fetch() so viewing works anywhere the HTML and sidecar\n  \/\/ are served together; writes go through window.omelette.writeFile, which\n  \/\/ the host allowlists to *.state.json basenames only.\n  const subs = new Set();\n  let slots = {};\n  \/\/ ids explicitly cleared before the sidecar fetch resolved \u2014 otherwise\n  \/\/ the merge below can't tell \"never set\" from \"just deleted\" and would\n  \/\/ resurrect the sidecar's stale value.\n  const tombstones = new Set();\n  let loaded = false;\n  let loadP = null;\n\n  function load() {\n    if (loadP) return loadP;\n    loadP = fetch(STATE_FILE)\n      .then((r) => (r.ok ? r.json() : null))\n      .then((j) => {\n        \/\/ Merge: sidecar loses to any in-memory change that raced ahead of\n        \/\/ the fetch (drop or clear) so neither is clobbered by hydration.\n        if (j && typeof j === 'object') {\n          const merged = Object.assign({}, j, slots);\n          \/\/ A framing-only write that raced ahead of hydration must not\n          \/\/ drop a user image that's only on disk \u2014 inherit u from the\n          \/\/ sidecar for any in-memory entry that lacks one.\n          for (const k in slots) {\n            if (merged[k] && !merged[k].u && j[k]) {\n              merged[k].u = typeof j[k] === 'string' ? j[k] : j[k].u;\n            }\n          }\n          for (const id of tombstones) delete merged[id];\n          slots = merged;\n        }\n        tombstones.clear();\n      })\n      .catch(() => {})\n      .then(() => { loaded = true; subs.forEach((fn) => fn()); });\n    return loadP;\n  }\n\n  \/\/ Serialize writes so two near-simultaneous drops on different slots\n  \/\/ can't reorder at the backend and leave the sidecar with only the\n  \/\/ first. A save requested mid-flight just marks dirty and re-fires on\n  \/\/ completion with the then-current slots.\n  let saving = false;\n  let saveDirty = false;\n  \/\/ Unload-time flush: save()'s serialization defers a mid-RTT re-fire to a\n  \/\/ .then that never runs in an unloading document, silently dropping a\n  \/\/ pagehide commit. Post the current slots immediately instead \u2014 content\n  \/\/ is a superset snapshot of any in-flight save's, the write is a\n  \/\/ whole-file last-writer-wins replace, and postMessage FIFO delivers it\n  \/\/ to the host after the in-flight one, so a backend-side reorder at\n  \/\/ worst reproduces the dropped-commit outcome this flush improves on.\n  \/\/ Guarded on the initial sidecar read: pre-hydration slots can miss\n  \/\/ other slots' persisted entries, and flushing it would clobber them \u2014\n  \/\/ that narrow case stays best-effort (the in-memory merge in load()\n  \/\/ cannot happen in an unloading document anyway).\n  function flushNow() {\n    if (!loaded) return;\n    const w = window.omelette && window.omelette.writeFile;\n    if (!w) return;\n    try { Promise.resolve(w(STATE_FILE, JSON.stringify(slots))).catch(() => {}); } catch (e) {}\n  }\n  function save() {\n    if (saving) { saveDirty = true; return; }\n    const w = window.omelette && window.omelette.writeFile;\n    if (!w) return;\n    saving = true;\n    Promise.resolve(w(STATE_FILE, JSON.stringify(slots)))\n      .catch(() => {})\n      .then(() => { saving = false; if (saveDirty) { saveDirty = false; save(); } });\n  }\n\n  const S_MAX = 5;\n  const clampS = (s) => Math.max(1, Math.min(S_MAX, s));\n\n  \/\/ Normalize a stored slot value. Pre-reframe sidecars stored a bare\n  \/\/ data-URL string; newer ones store {u, s, x, y}. Either shape is valid.\n  function getSlot(id) {\n    const v = slots[id];\n    if (!v) return null;\n    return typeof v === 'string' ? { u: v, s: 1, x: 0, y: 0 } : v;\n  }\n\n  function setSlot(id, val) {\n    if (!id) return;\n    if (val) { slots[id] = val; tombstones.delete(id); }\n    else { delete slots[id]; if (!loaded) tombstones.add(id); }\n    subs.forEach((fn) => fn());\n    \/\/ A drop is rare + high-value \u2014 write immediately so nav-away can't lose\n    \/\/ it. Gate on the initial read so we don't overwrite a sidecar we haven't\n    \/\/ merged yet; the merge in load() keeps this change once the read lands.\n    if (loaded) save(); else load().then(save);\n  }\n\n  \/\/ \u2500\u2500 Image downscale \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  \/\/ Encode through a canvas so the sidecar carries resized bytes, not the\n  \/\/ raw upload. Longest side is capped at 2\u00d7 the slot's rendered width\n  \/\/ (retina) and at MAX_DIM. WebP keeps alpha and is ~10\u00d7 smaller than PNG\n  \/\/ for photos, so there's no need for per-image format picking.\n  async function toDataUrl(file, targetW) {\n    const bitmap = await createImageBitmap(file);\n    try {\n      const cap = Math.min(MAX_DIM, Math.max(1, Math.round(targetW * 2)) || MAX_DIM);\n      const scale = Math.min(1, cap \/ Math.max(bitmap.width, bitmap.height));\n      const w = Math.max(1, Math.round(bitmap.width * scale));\n      const h = Math.max(1, Math.round(bitmap.height * scale));\n      const canvas = document.createElement('canvas');\n      canvas.width = w; canvas.height = h;\n      canvas.getContext('2d').drawImage(bitmap, 0, 0, w, h);\n      return canvas.toDataURL('image\/webp', 0.85);\n    } finally {\n      bitmap.close && bitmap.close();\n    }\n  }\n\n  \/\/ \u2500\u2500 Custom element \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  const stylesheet =\n    \/\/ Fill the container by default: slots are usually placed inside a\n    \/\/ sized wrapper (a hero frame, a grid cell, an inset:0 layer) and are\n    \/\/ expected to take that box \u2014 a fixed intrinsic size would render as\n    \/\/ a small tile in the corner of a full-bleed wrapper instead.\n    \/\/ aspect-ratio is the companion fallback that keeps a bare slot\n    \/\/ visible when the parent's height is indefinite: height:100%\n    \/\/ resolves to auto there, and the ratio then derives height from\n    \/\/ width instead of letting the slot collapse to zero height.\n    \/\/ Explicit width\/height on the element override all of this.\n    \/\/ color:inherit (not a fixed near-black): the placeholder chrome \u2014\n    \/\/ empty-state icon\/caption (currentColor) and the dashed ring \u2014 must\n    \/\/ read on dark decks too, and the slide's own text color is the one\n    \/\/ color guaranteed to contrast with the slide background. The soft\n    \/\/ look comes from opacity on those parts, not from a baked-in alpha.\n    ':host{display:block;position:relative;' +\n    '  font:13px\/1.3 system-ui,-apple-system,sans-serif;' +\n    '  width:100%;height:100%;aspect-ratio:3\/2}' +\n    '.empty .cap,.empty .sub{opacity:.75}' +\n    '.frame{position:absolute;inset:0;overflow:hidden;background:rgba(127,127,127,.08)}' +\n    \/\/ .frame img (clipped) and .spill (unclipped ghost + handles) share the\n    \/\/ same left\/top\/width\/height in frame-%, computed by _applyView(), so the\n    \/\/ inside-mask crop and the outside-mask spill stay pixel-aligned.\n    '.frame img{position:absolute;max-width:none;transform:translate(-50%,-50%);' +\n    '  -webkit-user-drag:none;user-select:none;touch-action:none}' +\n    \/\/ Reframe mode (double-click): the full image spills past the mask. The\n    \/\/ spill layer is sized to the IMAGE bounds so its corners are where the\n    \/\/ resize handles belong. The ghost <img> inside is translucent; the real\n    \/\/ clipped <img> underneath shows the opaque in-mask crop.\n    \/\/ popover=manual promotes the spill to the top layer on reframe, so it is\n    \/\/ not clipped by any overflow:hidden \/ clip-path \/ scroll-container\n    \/\/ ancestor (a plain z-index can't escape overflow clipping). UA popover\n    \/\/ defaults (inset:0;margin:auto) are reset; _applyView sets viewport px.\n    '.spill{position:fixed;margin:0;inset:auto;border:0;padding:0;background:transparent;' +\n    '  overflow:visible;transform:translate(-50%,-50%);z-index:1;cursor:grab;touch-action:none}' +\n    ':host([data-panning]) .spill{cursor:grabbing}' +\n    '.spill .ghost{position:absolute;inset:0;width:100%;height:100%;opacity:.35;' +\n    '  pointer-events:none;-webkit-user-drag:none;user-select:none;' +\n    '  box-shadow:0 0 0 1px rgba(0,0,0,.2),0 12px 32px rgba(0,0,0,.2)}' +\n    '.spill .handle{position:absolute;width:12px;height:12px;border-radius:50%;' +\n    '  background:#fff;box-shadow:0 0 0 1.5px #c96442,0 1px 3px rgba(0,0,0,.3);' +\n    '  transform:translate(-50%,-50%)}' +\n    '.spill .handle[data-c=nw]{left:0;top:0;cursor:nwse-resize}' +\n    '.spill .handle[data-c=ne]{left:100%;top:0;cursor:nesw-resize}' +\n    '.spill .handle[data-c=sw]{left:0;top:100%;cursor:nesw-resize}' +\n    '.spill .handle[data-c=se]{left:100%;top:100%;cursor:nwse-resize}' +\n    ':host([data-reframe]){z-index:10}' +\n    ':host([data-reframe]) .frame{box-shadow:0 0 0 2px #c96442}' +\n    '.empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;' +\n    '  justify-content:center;gap:6px;text-align:center;padding:12px;box-sizing:border-box;' +\n    '  cursor:pointer;user-select:none}' +\n    '.empty svg{opacity:.45}' +\n    '.empty .cap{max-width:90%;font-weight:500;letter-spacing:.01em}' +\n    '.empty .sub{font-size:11px}' +\n    '.empty .sub u{text-underline-offset:2px}' +\n    '.empty:hover .sub{opacity:1}' +\n    ':host([data-over]) .frame{outline:2px solid #c96442;outline-offset:-2px;' +\n    '  background:rgba(201,100,66,.10)}' +\n    '.ring{position:absolute;inset:0;pointer-events:none;border:1.5px dashed currentColor;' +\n    '  opacity:.35;transition:border-color .12s,opacity .12s}' +\n    ':host([data-over]) .ring{border-color:#c96442;opacity:1}' +\n    ':host([data-filled]) .ring{display:none}' +\n    \/\/ Controls overlay INSIDE the frame, pinned to the top-right corner, so\n    \/\/ a full-bleed slot in an overflow:hidden container still shows them\n    \/\/ (the old below-mask placement got clipped). Credit sits bottom-left,\n    \/\/ so top-right avoids collision. The blurred pill background keeps them\n    \/\/ legible over the image.\n    \/\/ The UA [popover] base rule styles the element in EVERY state (only\n    \/\/ display:none is gated on :not(:popover-open), and the display:flex\n    \/\/ below overrides that) \u2014 so the UA resets live HERE, like .spill's,\n    \/\/ or the ordinary hover-state strip renders as a bordered Canvas box\n    \/\/ centered by margin:auto. inset:auto precedes top\/right (shorthand).\n    '.ctl{position:absolute;inset:auto;top:8px;right:8px;margin:0;border:0;padding:0;' +\n    '  background:transparent;overflow:visible;' +\n    '  display:flex;gap:6px;opacity:0;pointer-events:none;transition:opacity .12s;z-index:2;' +\n    '  white-space:nowrap}' +\n    \/\/ While reframing, the spill owns the top layer and would swallow every\n    \/\/ click on the in-frame controls. Promoting .ctl into the top layer\n    \/\/ ABOVE the spill (shown after it \u2014 later popovers stack higher) keeps\n    \/\/ Edit-as-toggle and Replace clickable mid-reframe. _applyView pins it\n    \/\/ to the frame's top-right in viewport px (translateX(-100%)\n    \/\/ right-aligns against the computed left edge); inset:auto clears the\n    \/\/ base rule's top\/right so the inline left\/top position it alone.\n    '.ctl:popover-open{position:fixed;inset:auto;transform:translateX(-100%)}' +\n    ':host([data-filled][data-editable]:hover) .ctl,:host([data-reframe]) .ctl' +\n    '  {opacity:1;pointer-events:auto}' +\n    '.ctl button{appearance:none;border:0;border-radius:6px;padding:5px 10px;cursor:pointer;' +\n    '  background:rgba(0,0,0,.65);color:#fff;font:11px\/1 system-ui,-apple-system,sans-serif;' +\n    '  backdrop-filter:blur(6px)}' +\n    '.ctl button:hover{background:rgba(0,0,0,.8)}' +\n    '.err{position:absolute;left:8px;bottom:8px;right:8px;color:#b3261e;font-size:11px;' +\n    '  background:rgba(255,255,255,.85);padding:4px 6px;border-radius:5px;pointer-events:none}' +\n    \/\/ Replacement in flight: after a src swap the browser keeps painting\n    \/\/ the PREVIOUS image until the new one decodes, so a Replace would\n    \/\/ flash the old photo and then pop. Hide the stale frame (visibility,\n    \/\/ not display \u2014 _applyView geometry still applies) and spin until the\n    \/\/ new image reports in (load\/error clears data-swapping).\n    ':host([data-swapping]) .frame img{visibility:hidden}' +\n    '.loading{position:absolute;inset:0;display:none;align-items:center;' +\n    '  justify-content:center;pointer-events:none}' +\n    ':host([data-swapping]) .loading{display:flex}' +\n    '.loading::after{content:\"\";width:22px;height:22px;border-radius:50%;' +\n    '  border:2px solid rgba(127,127,127,.25);border-top-color:currentColor;' +\n    '  animation:om-slot-spin .7s linear infinite}' +\n    '@keyframes om-slot-spin{to{transform:rotate(360deg)}}' +\n    \/\/ Reduced motion: the static two-tone ring still reads as \"working\".\n    '@media (prefers-reduced-motion:reduce){.loading::after{animation:none}}' +\n    '.credit{position:absolute;left:6px;bottom:6px;max-width:calc(100% - 12px);display:none;' +\n    '  padding:3px 7px;border-radius:5px;background:rgba(0,0,0,.55);color:#fff;' +\n    '  font:10px\/1.2 system-ui,-apple-system,sans-serif;text-decoration:none;' +\n    '  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;backdrop-filter:blur(6px)}' +\n    \/\/ The credit is a SPAN holding one or two <a>s (Unsplash's prescribed\n    \/\/ form links the photographer AND Unsplash) \u2014 anchors style inline so\n    \/\/ the overlay reads as one line of text.\n    '.credit a{color:inherit;text-decoration:none}' +\n    '.credit a:hover,.credit a:focus-visible{text-decoration:underline}' +\n    ':host([data-filled][data-credit]) .credit{display:block}' +\n    \/\/ Exports must ship JUST the image \u2014 no hover controls, no credit chip\n    \/\/ (the host marks <html data-om-exporting> for the capture window; the\n    \/\/ page-level hide script can't reach shadow DOM, this rule can).\n    ':host-context([data-om-exporting]) .ctl,' +\n    ':host-context([data-om-exporting]) .credit{display:none !important}' +\n    \/\/ Print must ship just the image too: the hover-gated controls can be\n    \/\/ mid-hover when print() fires, and the credit chip is screen chrome \u2014\n    \/\/ the same rule the capture window gets, keyed on print media instead\n    \/\/ of the host's data-om-exporting mark (the print path sets no mark).\n    '@media print{.ctl,.credit{display:none !important}}' +\n    \/\/ No export-window mask rules here on purpose: the export capture\n    \/\/ releases the replacement mask by REMOVING data-swapping (the\n    \/\/ shadow-root pass in pages\/export\/shared.ts HIDE_EXPORT_CHROME_SCRIPT)\n    \/\/ \u2014 attribute removal works in every engine (:host-context is\n    \/\/ Chromium-only), is scoped by construction to slots actually\n    \/\/ mid-swap, and hides the spinner through the same gate. A masked img\n    \/\/ would otherwise be silently dropped from PPTX decks (the capture\n    \/\/ walk skips visibility:hidden imgs).\n    \/\/ Attribution error tile: REPLACES the photo when an Unsplash src has\n    \/\/ no credit attribute \u2014 rendering the photo uncredited is the terms\n    \/\/ violation, so the photo must not appear at all.\n    \/\/ Calm and neutral on purpose (review feedback): the tile informs the\n    \/\/ user; the fix instructions are machine-facing (usage docblock, tool\n    \/\/ description, and the turn-end scan's bounce copy name the attributes\n    \/\/ for the agent).\n    '.attr-error{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;' +\n    '  justify-content:center;gap:6px;text-align:center;padding:12px;box-sizing:border-box;' +\n    '  background:#f2f1ef;color:#6e6c66;user-select:none;' +\n    '  font:13px\/1.45 system-ui,-apple-system,sans-serif}' +\n    '.attr-error svg{opacity:.55}' +\n    '.attr-error .cap{max-width:92%;font-weight:500;letter-spacing:.01em}' +\n    ':host([data-attribution-error]) .attr-error{display:flex}' +\n    ':host([data-attribution-error]) .ring{display:none}';\n\n  const icon =\n    '<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" ' +\n    'stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' +\n    '<rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"\/><circle cx=\"8.5\" cy=\"8.5\" r=\"1.5\"\/>' +\n    '<path d=\"m21 15-5-5L5 21\"\/><\/svg>';\n\n  const warnIcon =\n    '<svg width=\"28\" height=\"28\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" ' +\n    'stroke-width=\"1.6\" stroke-linecap=\"round\" stroke-linejoin=\"round\">' +\n    '<path d=\"m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"\/>' +\n    '<path d=\"M12 9v4\"\/><path d=\"M12 17h.01\"\/><\/svg>';\n\n  class ImageSlot extends HTMLElement {\n    static get observedAttributes() {\n      return ['shape', 'radius', 'mask', 'fit', 'placeholder', 'src', 'id', 'credit', 'credit-href'];\n    }\n\n    \/** Duplicate-slide hook (called by deck-stage, see its\n     *  _remintDuplicateIds): copy this id's stored image, if any, under a\n     *  freshly minted key and return that key \u2014 so a duplicated slide's\n     *  slot keeps its dropped photo instead of reverting to the\n     *  placeholder. 'isFree' is the caller's uniqueness check (document\n     *  ids); candidates must ALSO be unused in the sidecar, which can\n     *  hold keys from other pages sharing the project root. (An EMPTY\n     *  slot on another page leaves no sidecar entry, so its id is not\n     *  detectable here \u2014 a minted key can collide with it and that slot\n     *  would show this photo. Same blast radius as two pages reusing an\n     *  id by hand, which the shared sidecar already permits.) Returns null\n     *  when no id could be minted (caller strips the id, today's\n     *  behavior). *\/\n    static cloneSlot(fromId, isFree) {\n      if (typeof fromId !== 'string' || !fromId) return null;\n      \/\/ Pre-hydration the store can't veto candidates or source the copy\n      \/\/ \u2014 degrade to the strip (today's behavior) rather than mint\n      \/\/ against keys we can't see yet. Any rendered (= droppable) slot\n      \/\/ means load() has already settled.\n      if (!loaded) return null;\n      const stem = fromId.replace(\/-\\d+$\/, '') || fromId;\n      for (let n = 2; n < 100; n++) {\n        const toId = stem + '-' + n;\n        if (toId === fromId) continue;\n        if (slots[toId] !== undefined) {\n          \/\/ Reuse a key holding this exact value (bytes AND crop) if no\n          \/\/ live element here owns it \u2014 a duplicate op the host refused\n          \/\/ after minting leaves such a key behind, and reusing keeps\n          \/\/ refused retries from accumulating one orphaned copy per\n          \/\/ attempt. Full equality (not just bytes) so a byte-identical\n          \/\/ key another PAGE owns with its own crop is stepped past, not\n          \/\/ adopted or rewritten. (Entries without .u never match.)\n          const prev = getSlot(toId);\n          const cur = getSlot(fromId);\n          if (!(prev &#038;&#038; cur &#038;&#038; prev.u &#038;&#038; prev.u === cur.u &#038;&#038;\n                prev.s === cur.s &#038;&#038; prev.x === cur.x &#038;&#038; prev.y === cur.y &#038;&#038;\n                (typeof isFree !== 'function' || isFree(toId)))) continue;\n          return toId;\n        }\n        if (typeof isFree === 'function' &#038;&#038; !isFree(toId)) continue;\n        const v = getSlot(fromId);\n        if (v) setSlot(toId, Object.assign({}, v));\n        return toId;\n      }\n      return null;\n    }\n\n    constructor() {\n      super();\n      \/\/ clonable: rail thumbnails deep-clone slides and carry this shadow\n      \/\/ along; reuse an already-cloned root so upgrade-after-clone works.\n      \/\/ (Deliberately NOT serializable \u2014 a getHTML consumer would embed\n      \/\/ multi-MB sidecar data-URLs into serialized page HTML.)\n      const root = this.shadowRoot ||\n        this.attachShadow({ mode: 'open', clonable: true });\n      \/\/ .spill and .ctl sit OUTSIDE .frame so overflow:hidden + border-radius\n      \/\/ on the frame (circle, pill, rounded) can't clip them.\n      root.innerHTML =\n        '<style>' + stylesheet + '<\/style>' +\n        '<div class=\"frame\" part=\"frame\">' +\n        '  <img part=\"image\" alt=\"\" draggable=\"false\" style=\"display:none\">' +\n        '  <div class=\"empty\" part=\"empty\">' + icon +\n        '    <div class=\"cap\"><\/div>' +\n        '    <div class=\"sub\">or <u>browse files<\/u><\/div><\/div>' +\n        '  <div class=\"attr-error\" part=\"attribution-error\">' + warnIcon +\n        '    <div class=\"cap\">This photo needs attribution<\/div><\/div>' +\n        '  <div class=\"loading\" part=\"loading\"><\/div>' +\n        '  <div class=\"ring\" part=\"ring\"><\/div>' +\n        '<\/div>' +\n        \/\/ Outside .frame, like .spill\/.ctl \u2014 the frame's overflow:hidden +\n        \/\/ border-radius\/clip-path would cut the credit off on circle\/pill\/mask.\n        \/\/ A SPAN, not an <a>: the prescribed Unsplash credit holds two links\n        \/\/ (photographer + Unsplash), built per-render in _render().\n        '<span class=\"credit\" part=\"credit\"><\/span>' +\n        '<div class=\"spill\" popover=\"manual\" data-dc-edit-transparent>' +\n        '  <img class=\"ghost\" alt=\"\" draggable=\"false\">' +\n        '  <div class=\"handle\" data-c=\"nw\"><\/div><div class=\"handle\" data-c=\"ne\"><\/div>' +\n        '  <div class=\"handle\" data-c=\"sw\"><\/div><div class=\"handle\" data-c=\"se\"><\/div>' +\n        '<\/div>' +\n        \/\/ data-dc-edit-transparent: the DC editor's edit-mode picker lets\n        \/\/ clicks through for chrome marked with it (EDIT_TRANSPARENT_SEL)\n        \/\/ \u2014 without it, Replace\/Edit clicks in Edit mode are swallowed by\n        \/\/ element selection and the controls look dead.\n        '<div class=\"ctl\" popover=\"manual\" data-dc-edit-transparent><button data-act=\"replace\" title=\"Replace image\">Replace<\/button>' +\n        '  <button data-act=\"edit\" title=\"Reframe image\">Edit<\/button><\/div>' +\n        '<input type=\"file\" accept=\"' + ACCEPT.join(',') + '\" hidden>';\n      this._frame = root.querySelector('.frame');\n      this._ring = root.querySelector('.ring');\n      this._img = root.querySelector('.frame img');\n      this._empty = root.querySelector('.empty');\n      this._cap = root.querySelector('.cap');\n      this._sub = root.querySelector('.sub');\n      this._spill = root.querySelector('.spill');\n      this._ctl = root.querySelector('.ctl');\n      this._credit = root.querySelector('.credit');\n      this._attrError = root.querySelector('.attr-error');\n      \/\/ Credit clicks open the link, not browse\/reframe.\n      this._credit.addEventListener('click', (e) => e.stopPropagation());\n      this._credit.addEventListener('dblclick', (e) => e.stopPropagation());\n      this._ghost = root.querySelector('.ghost');\n      this._err = null;\n      this._input = root.querySelector('input');\n      this._depth = 0;\n      this._gen = 0;\n      \/\/ Encode-in-flight marker (the owning _ingest generation): while set,\n      \/\/ the same-src \"nothing in flight\" clear in _render must not fire \u2014\n      \/\/ the stored value still points at the OLD image until the encode\n      \/\/ lands, so that clear would unmask the stale image mid-replace.\n      this._swapGen = 0;\n      \/\/ Render-owned swap in flight: set when _render assigns a new src,\n      \/\/ cleared only by the img's own load\/error (or the empty branch).\n      \/\/ img.complete CANNOT stand in for this \u2014 setting src only QUEUES\n      \/\/ the current-request swap (a microtask), so synchronously after an\n      \/\/ assignment, complete still reports the OLD settled request. The\n      \/\/ pick path does exactly that: the host sets src, credit, and\n      \/\/ credit-href back-to-back in one task, and renders #2\/#3 would\n      \/\/ read the stale complete === true and drop the mask one render\n      \/\/ after it was set.\n      this._loadPending = false;\n      \/\/ See _render's empty branch: a transient attribution-error wipe of a\n      \/\/ showing image must make the follow-up render a replacement (spinner),\n      \/\/ not a first fill (blank frame).\n      this._hidShowing = false;\n      this._view = { s: 1, x: 0, y: 0 };\n      this._subFn = () => this._render();\n      \/\/ Shadow-DOM listeners live with the shadow DOM \u2014 bound once here so\n      \/\/ disconnect\/reconnect (e.g. React remount) doesn't stack handlers.\n      this._empty.addEventListener('click', () => this._input.click());\n      root.addEventListener('click', (e) => {\n        const act = e.target && e.target.getAttribute && e.target.getAttribute('data-act');\n        if (!act) return;\n        \/\/ The hidden controls are opacity-0 but still tabbable \u2014 without\n        \/\/ this gate a keyboard user could drive them on a read-only share\n        \/\/ link (mirrors the dblclick handler's editable gate).\n        if (!this.hasAttribute('data-editable')) return;\n        if (act === 'replace') {\n          this._exitReframe(true);\n          \/\/ Host-owned picker (Unsplash modal; it also offers local import).\n          this.dispatchEvent(new CustomEvent('image-slot:pick', {\n            bubbles: true, composed: true, detail: { id: this.id || null }\n          }));\n        }\n        if (act === 'edit') {\n          if (!this._reframes()) return;\n          if (this.hasAttribute('data-reframe')) this._exitReframe(true);\n          else this._enterReframe();\n        }\n      });\n      this._input.addEventListener('change', () => {\n        const f = this._input.files && this._input.files[0];\n        if (f) this._ingest(f);\n        this._input.value = '';\n      });\n      \/\/ naturalWidth\/Height aren't known until load \u2014 re-apply so the cover\n      \/\/ baseline is computed from real dimensions, not the 100%\u00d7100% fallback.\n      \/\/ load\/error also release the replacement-in-flight mask (via the\n      \/\/ single discipline in _releaseMask): the swap is only revealed once\n      \/\/ the new image can actually paint (on error the frame shows its\n      \/\/ background, same as a fresh slot with a broken src).\n      this._img.addEventListener('load', () => {\n        this._loadPending = false;\n        this._releaseMask(true);\n        this._applyView();\n      });\n      this._img.addEventListener('error', () => {\n        this._loadPending = false;\n        this._releaseMask(true);\n      });\n      \/\/ Gated only on editable \u2014 any filled slot can be repositioned\/scaled,\n      \/\/ regardless of fit. Share links (no writeFile) stay static.\n      this.addEventListener('dblclick', (e) => {\n        if (!this.hasAttribute('data-editable') || !this._reframes()) return;\n        e.preventDefault();\n        if (this.hasAttribute('data-reframe')) this._exitReframe(true);\n        else this._enterReframe();\n      });\n      \/\/ Pan + resize both originate on the spill layer. A handle pointerdown\n      \/\/ drives an aspect-locked resize anchored at the opposite corner; any\n      \/\/ other pointerdown on the spill pans. Offsets are frame-% so a\n      \/\/ reframed slot survives responsive resize \/ PPTX export.\n      this._spill.addEventListener('pointerdown', (e) => {\n        if (e.button !== 0 || !this.hasAttribute('data-reframe')) return;\n        e.preventDefault();\n        e.stopPropagation();\n        this._spill.setPointerCapture(e.pointerId);\n        const rect = this.getBoundingClientRect();\n        const fw = rect.width || 1, fh = rect.height || 1;\n        const corner = e.target.getAttribute && e.target.getAttribute('data-c');\n        let move;\n        if (corner) {\n          \/\/ Resize about the OPPOSITE corner. Viewport-px throughout (rect\n          \/\/ fw\/fh, not clientWidth) so the math survives a transform:scale()\n          \/\/ ancestor \u2014 deck_stage renders slides scaled-to-fit.\n          const iw = this._img.naturalWidth || 1, ih = this._img.naturalHeight || 1;\n          const contain = (this.getAttribute('fit') || 'cover').toLowerCase() === 'contain';\n          const base = contain ? Math.min(fw \/ iw, fh \/ ih) : Math.max(fw \/ iw, fh \/ ih);\n          const sx = corner.includes('e') ? 1 : -1;\n          const sy = corner.includes('s') ? 1 : -1;\n          const s0 = this._view.s;\n          const w0 = iw * base * s0, h0 = ih * base * s0;\n          const cx0 = (50 + this._view.x) \/ 100 * fw;\n          const cy0 = (50 + this._view.y) \/ 100 * fh;\n          const ox = cx0 - sx * w0 \/ 2, oy = cy0 - sy * h0 \/ 2;\n          const diag0 = Math.hypot(w0, h0);\n          const ux = sx * w0 \/ diag0, uy = sy * h0 \/ diag0;\n          move = (ev) => {\n            const proj = (ev.clientX - rect.left - ox) * ux +\n                         (ev.clientY - rect.top - oy) * uy;\n            const s = clampS(s0 * proj \/ diag0);\n            const d = diag0 * s \/ s0;\n            this._view.s = s;\n            this._view.x = (ox + ux * d \/ 2) \/ fw * 100 - 50;\n            this._view.y = (oy + uy * d \/ 2) \/ fh * 100 - 50;\n            this._clampView();\n            this._applyView();\n          };\n        } else {\n          this.setAttribute('data-panning', '');\n          const start = { px: e.clientX, py: e.clientY, x: this._view.x, y: this._view.y };\n          move = (ev) => {\n            this._view.x = start.x + (ev.clientX - start.px) \/ fw * 100;\n            this._view.y = start.y + (ev.clientY - start.py) \/ fh * 100;\n            this._clampView();\n            this._applyView();\n          };\n        }\n        const up = () => {\n          try { this._spill.releasePointerCapture(e.pointerId); } catch {}\n          this._spill.removeEventListener('pointermove', move);\n          this._spill.removeEventListener('pointerup', up);\n          this._spill.removeEventListener('pointercancel', up);\n          this.removeAttribute('data-panning');\n          this._dragUp = null;\n        };\n        \/\/ Stashed so _exitReframe (Escape \/ outside-click mid-drag) can\n        \/\/ tear the capture + listeners down synchronously.\n        this._dragUp = up;\n        this._spill.addEventListener('pointermove', move);\n        this._spill.addEventListener('pointerup', up);\n        this._spill.addEventListener('pointercancel', up);\n      });\n      \/\/ Wheel zoom stays available inside reframe mode as a trackpad nicety \u2014\n      \/\/ zooms toward the cursor (offset' = cursor\u00b7(1-k) + offset\u00b7k).\n      this.addEventListener('wheel', (e) => {\n        if (!this.hasAttribute('data-reframe')) return;\n        e.preventDefault();\n        const r = this.getBoundingClientRect();\n        const cx = (e.clientX - r.left) \/ r.width * 100 - 50;\n        const cy = (e.clientY - r.top) \/ r.height * 100 - 50;\n        const prev = this._view.s;\n        const next = clampS(prev * Math.pow(1.0015, -e.deltaY));\n        if (next === prev) return;\n        const k = next \/ prev;\n        this._view.s = next;\n        this._view.x = cx * (1 - k) + this._view.x * k;\n        this._view.y = cy * (1 - k) + this._view.y * k;\n        this._clampView();\n        this._applyView();\n      }, { passive: false });\n    }\n\n    connectedCallback() {\n      \/\/ Warn once per page \u2014 an id-less slot works for the session but\n      \/\/ cannot persist, and two id-less slots would share nothing.\n      if (!this.id && !ImageSlot._warned) {\n        ImageSlot._warned = true;\n        console.warn('<image-slot> without an id will not persist its dropped image.');\n      }\n      this.addEventListener('dragenter', this);\n      this.addEventListener('dragover', this);\n      this.addEventListener('dragleave', this);\n      this.addEventListener('drop', this);\n      subs.add(this._subFn);\n      \/\/ The host may inject window.omelette.writeFile AFTER the first render;\n      \/\/ re-render on hover so the editable-gated controls reliably appear.\n      this.addEventListener('pointerenter', this._subFn);\n      \/\/ width%\/height% in _applyView encode the frame aspect at call time \u2014\n      \/\/ a host resize (responsive grid, pane divider) would stretch the\n      \/\/ image until the next _render. Re-render on size change: _render()\n      \/\/ re-seeds _view from stored before clamp\/apply, so a shrink\u2192grow\n      \/\/ cycle round-trips instead of ratcheting x\/y toward the narrower\n      \/\/ frame's clamp range.\n      this._ro = new ResizeObserver(() => this._render());\n      this._ro.observe(this);\n      load();\n      this._render();\n    }\n\n    disconnectedCallback() {\n      subs.delete(this._subFn);\n      this.removeEventListener('pointerenter', this._subFn);\n      this.removeEventListener('dragenter', this);\n      this.removeEventListener('dragover', this);\n      this.removeEventListener('dragleave', this);\n      this.removeEventListener('drop', this);\n      if (this._ro) { this._ro.disconnect(); this._ro = null; }\n      \/\/ commit=false: a disconnect is not a user intent \u2014 committing here\n      \/\/ would persist whatever half-finished drag a React remount or DOM\n      \/\/ splice happened to interrupt. Deliberate exits commit on their own\n      \/\/ paths (Escape\/click-out\/toggle), and unloads commit via pagehide.\n      this._exitReframe(false);\n    }\n\n    _enterReframe() {\n      if (this.hasAttribute('data-reframe')) return;\n      this.setAttribute('data-reframe', '');\n      this._signalReframe(true);\n      \/\/ Best-effort commit when the document unloads mid-reframe (a host\n      \/\/ navigation racing the enter signal, a manual reload, tab close):\n      \/\/ the sidecar write rides the host bridge, which outlives this\n      \/\/ document, so the crop survives even though the mode dies with the\n      \/\/ DOM. Held on the instance so _exitReframe detaches exactly what\n      \/\/ was attached.\n      this._pagehide = () => { this._exitReframe(true); flushNow(); };\n      window.addEventListener('pagehide', this._pagehide);\n      \/\/ Promote spill to the top layer, then keep it pinned over the frame:\n      \/\/ scroll\/resize cover the common cases, and a per-frame rect check\n      \/\/ catches layout shifts that fire neither (an image above finishing\n      \/\/ load, streamed DOM pushing the slot down, an ancestor transform\n      \/\/ change) so the overlay can't detach from the frame.\n      try { this._spill.showPopover(); } catch {}\n      \/\/ After the spill, so the controls stack above it in the top layer.\n      try { this._ctl.showPopover(); } catch {}\n      this._reposition = () => { if (this.hasAttribute('data-reframe')) this._applyView(); };\n      window.addEventListener('scroll', this._reposition, true);\n      window.addEventListener('resize', this._reposition);\n      this._lastRect = '';\n      this._watch = () => {\n        if (!this.hasAttribute('data-reframe')) return;\n        const r = this.getBoundingClientRect();\n        const key = r.left + ',' + r.top + ',' + r.width + ',' + r.height;\n        if (key !== this._lastRect) { this._lastRect = key; this._applyView(); }\n        this._watchId = requestAnimationFrame(this._watch);\n      };\n      this._watchId = requestAnimationFrame(this._watch);\n      this._applyView();\n      \/\/ Close on click outside (the spill handler stopPropagation()s so\n      \/\/ in-image drags don't reach this) and on Escape. Listeners are held\n      \/\/ on the instance so _exitReframe \/ disconnectedCallback can detach\n      \/\/ exactly what was attached.\n      this._outside = (e) => {\n        if (e.composedPath && e.composedPath().includes(this)) return;\n        this._exitReframe(true);\n      };\n      this._esc = (e) => { if (e.key === 'Escape') this._exitReframe(true); };\n      document.addEventListener('pointerdown', this._outside, true);\n      document.addEventListener('keydown', this._esc, true);\n    }\n\n    _exitReframe(commit) {\n      if (!this.hasAttribute('data-reframe')) return;\n      if (this._dragUp) this._dragUp();\n      this.removeAttribute('data-reframe');\n      this.removeAttribute('data-panning');\n      if (this._outside) document.removeEventListener('pointerdown', this._outside, true);\n      if (this._esc) document.removeEventListener('keydown', this._esc, true);\n      this._outside = this._esc = null;\n      if (this._reposition) {\n        window.removeEventListener('scroll', this._reposition, true);\n        window.removeEventListener('resize', this._reposition);\n        this._reposition = null;\n      }\n      if (this._watchId) { cancelAnimationFrame(this._watchId); this._watchId = 0; }\n      if (this._pagehide) {\n        window.removeEventListener('pagehide', this._pagehide);\n        this._pagehide = null;\n      }\n      try { this._spill.hidePopover(); } catch {}\n      try { this._ctl.hidePopover(); } catch {}\n      this._ctl.style.left = ''; this._ctl.style.top = '';\n      if (commit) this._commitView();\n      this._signalReframe(false);\n    }\n\n    \/\/ Reframe state lives only in this DOM until commit, invisible to the\n    \/\/ host's dirty signals \u2014 announce enter\/exit so the host can hold\n    \/\/ auto-reloads for exactly the gesture (the guest bundle forwards\n    \/\/ image-slot:reframe to the host as imageSlotReframe). Dispatched on\n    \/\/ the element (composed, so it escapes shadow roots) while connected;\n    \/\/ a disconnected exit (disconnectedCallback) falls back to document so\n    \/\/ the host still hears it.\n    _signalReframe(active) {\n      const target = this.isConnected ? this : document;\n      target.dispatchEvent(new CustomEvent('image-slot:reframe', {\n        bubbles: true, composed: true,\n        detail: { active: active, id: this.id || null }\n      }));\n    }\n\n    \/\/ Public: host's \"Import from computer\" calls this to run local browse.\n    openFilePicker() { this._exitReframe(true); this._input.click(); }\n\n    \/\/ A src write is a newer intent for this slot's content \u2014 the host\n    \/\/ pick path (setImageSlotImage) or an agent edit \u2014 so it must win\n    \/\/ over any encode still in flight from an earlier drop: left live,\n    \/\/ that encode lands later, passes _ingest's gen guard, and its\n    \/\/ setSlot silently overwrites the pick (the stored value shadows\n    \/\/ src in _render). Bumping _gen kills the encode before its own\n    \/\/ _swapGen clear runs, so clear the dead claim here too \u2014 otherwise\n    \/\/ _releaseMask (gated on !_swapGen) never fires and the pick's\n    \/\/ spinner is stranded. src ONLY: the pick sets credit\/credit-href\n    \/\/ in the same task, and clearing _swapGen on those would let the\n    \/\/ same-src branch unmask the old image mid-encode.\n    attributeChangedCallback(name, oldVal, newVal) {\n      if (name === 'src' && oldVal !== newVal) {\n        this._gen++;\n        this._swapGen = 0;\n      }\n      if (this.shadowRoot) this._render();\n    }\n\n    \/\/ handleEvent \u2014 one listener object for all four drag events keeps the\n    \/\/ add\/remove symmetric and the depth counter correct.\n    handleEvent(e) {\n      if (e.type === 'dragenter' || e.type === 'dragover') {\n        \/\/ Without preventDefault the browser never fires 'drop'.\n        e.preventDefault();\n        e.stopPropagation();\n        if (e.dataTransfer) e.dataTransfer.dropEffect = 'copy';\n        if (e.type === 'dragenter') this._depth++;\n        this.setAttribute('data-over', '');\n      } else if (e.type === 'dragleave') {\n        \/\/ dragenter\/leave fire for every descendant crossing \u2014 count depth\n        \/\/ so hovering the icon inside the empty state doesn't flicker.\n        if (--this._depth <= 0) { this._depth = 0; this.removeAttribute('data-over'); }\n      } else if (e.type === 'drop') {\n        e.preventDefault();\n        e.stopPropagation();\n        this._depth = 0;\n        this.removeAttribute('data-over');\n        const f = e.dataTransfer &#038;&#038; e.dataTransfer.files &#038;&#038; e.dataTransfer.files[0];\n        if (f) this._ingest(f);\n      }\n    }\n\n    async _ingest(file) {\n      this._setError(null);\n      if (!file || ACCEPT.indexOf(file.type) < 0) {\n        this._setError('Drop a PNG, JPEG, WebP, or AVIF image.');\n        return;\n      }\n      \/\/ toDataUrl can take hundreds of ms on a large photo. A Clear or a\n      \/\/ newer drop during that window would be clobbered when this await\n      \/\/ resumes \u2014 bump + capture a generation so stale encodes bail.\n      const gen = ++this._gen;\n      \/\/ Replacing a shown image: surface the swap through the encode too,\n      \/\/ not just the decode \u2014 otherwise the old photo sits there with no\n      \/\/ feedback while the canvas re-encode runs. An empty slot keeps its\n      \/\/ placeholder (no spinner) until the encode lands, as before.\n      \/\/ _swapGen guards the mask against re-renders DURING the encode\n      \/\/ (pointerenter, ResizeObserver, another slot's store write): the\n      \/\/ stored value still resolves to the old image there, so _render's\n      \/\/ same-src clear would otherwise unmask it mid-replace.\n      if (this.hasAttribute('data-filled')) {\n        this.setAttribute('data-swapping', '');\n        this._swapGen = gen;\n      }\n      try {\n        const w = this.clientWidth || this.offsetWidth || MAX_DIM;\n        const url = await toDataUrl(file, w);\n        if (gen !== this._gen) return;\n        \/\/ Only exit reframe once the new image is in hand \u2014 a rejected type\n        \/\/ or decode failure leaves the in-progress crop untouched.\n        this._exitReframe(false);\n        \/\/ Clear BEFORE setSlot: its synchronous re-render must see no\n        \/\/ pending encode, so a byte-identical re-upload (same data URL, no\n        \/\/ load event coming) still clears the mask via the complete branch.\n        this._swapGen = 0;\n        const val = { u: url, s: 1, x: 0, y: 0 };\n        setSlot(this.id || '', val);\n        \/\/ Keep a session-local copy for id-less slots so the drop still\n        \/\/ shows, even though it cannot persist.\n        if (!this.id) { this._local = val; this._render(); }\n      } catch (err) {\n        if (gen !== this._gen) return;\n        this._swapGen = 0;\n        \/\/ Reveal the kept old image \u2014 unless another replacement (a\n        \/\/ remote pick's src swap) is still in flight, in which case the\n        \/\/ mask stays until THAT image settles (its load\/error releases).\n        this._releaseMask();\n        this._setError('Could not read that image.');\n        console.warn('<image-slot> ingest failed:', err);\n      }\n    }\n\n    _setError(msg) {\n      if (this._err) { this._err.remove(); this._err = null; }\n      if (!msg) return;\n      const d = document.createElement('div');\n      d.className = 'err'; d.textContent = msg;\n      this.shadowRoot.appendChild(d);\n      this._err = d;\n      setTimeout(() => { if (this._err === d) { d.remove(); this._err = null; } }, 3000);\n    }\n\n    \/\/ Reframing (pan\/resize) is available on any filled slot \u2014 the user can\n    \/\/ always reposition\/scale. `fit` only sets the initial baseline (see\n    \/\/ _geom): contain starts fully-visible, cover starts frame-filling.\n    _reframes() {\n      return this.hasAttribute('data-filled');\n    }\n\n    \/\/ The single release discipline for the replacement-in-flight mask\n    \/\/ (data-swapping). The mask comes off only when BOTH hold:\n    \/\/  - no encode is pending (_swapGen) \u2014 mid-encode the stored value\n    \/\/    still resolves to the old image, so any reveal paints it;\n    \/\/  - the frame img has settled on its current src \u2014 an unsettled src\n    \/\/    means some replacement is still in flight (e.g. a remote pick),\n    \/\/    whoever started it, and revealing would paint the previous\n    \/\/    frame. The load\/error listeners pass settled=true (the event IS\n    \/\/    the settlement signal, per spec complete is true by then);\n    \/\/    other callers rely on the complete flag (covers loaded AND\n    \/\/    failed).\n    \/\/ Every release path funnels through here EXCEPT _render's empty\n    \/\/ branch (the img is being cleared \u2014 nothing will ever settle).\n    _releaseMask(settled) {\n      if (\n        !this._swapGen &&\n        !this._loadPending &&\n        (settled || this._img.complete)\n      ) {\n        this.removeAttribute('data-swapping');\n      }\n    }\n\n    \/\/ Baseline geometry, shared by clamp\/apply\/resize. `base` is the scale at\n    \/\/ view-scale s=1: cover = fill the frame (overflow on the looser axis),\n    \/\/ contain = fit fully inside (letterboxed). Zooming a contain image past\n    \/\/ s where it overflows naturally becomes a crop. Null until the img has\n    \/\/ loaded (naturalWidth is 0 before that) or when the slot has no layout\n    \/\/ box \u2014 ResizeObserver fires with a 0\u00d70 rect under display:none, and\n    \/\/ clamping against a degenerate 1\u00d71 frame would silently pull the stored\n    \/\/ pan toward zero.\n    _geom() {\n      const iw = this._img.naturalWidth, ih = this._img.naturalHeight;\n      const fw = this.clientWidth, fh = this.clientHeight;\n      if (!iw || !ih || !fw || !fh) return null;\n      const contain = (this.getAttribute('fit') || 'cover').toLowerCase() === 'contain';\n      const base = contain\n        ? Math.min(fw \/ iw, fh \/ ih)\n        : Math.max(fw \/ iw, fh \/ ih);\n      return { iw, ih, fw, fh, base };\n    }\n\n    _clampView() {\n      \/\/ Pan range on each axis is half the overflow past the frame edge.\n      const g = this._geom();\n      if (!g) return;\n      const mx = Math.max(0, (g.iw * g.base * this._view.s \/ g.fw - 1) * 50);\n      const my = Math.max(0, (g.ih * g.base * this._view.s \/ g.fh - 1) * 50);\n      this._view.x = Math.max(-mx, Math.min(mx, this._view.x));\n      this._view.y = Math.max(-my, Math.min(my, this._view.y));\n    }\n\n    _applyView() {\n      const g = this._geom();\n      \/\/ Top-layer controls: pin to the frame's top-right in viewport px\n      \/\/ (the same 8px inset as the in-frame layout; unscaled \u2014 top-layer UI\n      \/\/ reads as chrome, not page content). BEFORE the geometry branch:\n      \/\/ placement needs only the frame rect, and a not-yet-loaded or broken\n      \/\/ src must not leave the promoted strip floating unpositioned. Gated\n      \/\/ on the popover actually being open: without the Popover API,\n      \/\/ showPopover() threw (swallowed in _enterReframe), .ctl stays in\n      \/\/ its in-frame absolute layout, and viewport-px coordinates would\n      \/\/ shove it off-frame \u2014 and matches(':popover-open') itself throws\n      \/\/ there (unknown pseudo-class), hence the try\/catch.\n      if (this.hasAttribute('data-reframe')) {\n        let onTop = false;\n        try { onTop = this._ctl.matches(':popover-open'); } catch {}\n        if (onTop) {\n          const r = this.getBoundingClientRect();\n          this._ctl.style.left = (r.right - 8) + 'px';\n          this._ctl.style.top = (r.top + 8) + 'px';\n        }\n      }\n      if (!g) {\n        \/\/ Dimensions not known yet (before img load) \u2014 centered fit so there\n        \/\/ is no flash of an unpositioned image before the geometry lands.\n        const contain = (this.getAttribute('fit') || 'cover').toLowerCase() === 'contain';\n        this._img.style.width = '100%';\n        this._img.style.height = '100%';\n        this._img.style.left = '50%';\n        this._img.style.top = '50%';\n        this._img.style.objectFit = contain ? 'contain' : 'cover';\n        return;\n      }\n      \/\/ Baseline (cover-fill or contain-fit) \u00d7 view scale. Width\/height and\n      \/\/ left\/top are all frame-% \u2014 depends only on the frame aspect ratio, so\n      \/\/ a responsive resize keeps the same crop. The spill layer mirrors the\n      \/\/ same box so its corners = image corners.\n      const k = g.base * this._view.s;\n      const w = (g.iw * k \/ g.fw * 100) + '%';\n      const h = (g.ih * k \/ g.fh * 100) + '%';\n      const l = (50 + this._view.x) + '%';\n      const t = (50 + this._view.y) + '%';\n      this._img.style.width = w; this._img.style.height = h;\n      this._img.style.left = l; this._img.style.top = t;\n      this._img.style.objectFit = '';\n      if (this.hasAttribute('data-reframe')) {\n        \/\/ Top-layer spill: position in viewport px over the frame. The top\n        \/\/ layer escapes ancestor transforms entirely, so EVERY term must be\n        \/\/ in viewport units: getBoundingClientRect gives the frame's scaled\n        \/\/ origin AND size, and the rect\/layout ratio rescales the ghost \u2014\n        \/\/ sizing from layout px alone renders it 1\/scale too large under a\n        \/\/ scaled deck slide. Inner ghost + handles stay box-relative.\n        const r = this.getBoundingClientRect();\n        const sx = g.fw ? r.width \/ g.fw : 1;\n        const sy = g.fh ? r.height \/ g.fh : 1;\n        this._spill.style.width = (g.iw * k * sx) + 'px';\n        this._spill.style.height = (g.ih * k * sy) + 'px';\n        this._spill.style.left = (r.left + (50 + this._view.x) \/ 100 * r.width) + 'px';\n        this._spill.style.top = (r.top + (50 + this._view.y) \/ 100 * r.height) + 'px';\n      }\n    }\n\n    _commitView() {\n      const v = { s: this._view.s, x: this._view.x, y: this._view.y };\n      if (this._userUrl) v.u = this._userUrl;\n      \/\/ Framing-only (no u) persists too so an author-src slot remembers its\n      \/\/ crop; clearing the sidecar still falls through to src=.\n      if (this.id) setSlot(this.id, v);\n      else { this._local = v; }\n    }\n\n    _render() {\n      \/\/ Shape \/ mask. Presets use border-radius so the dashed ring can\n      \/\/ follow the rounded outline; clip-path is only applied for an\n      \/\/ explicit `mask` (the ring is hidden there since a rectangle\n      \/\/ dashed border chopped by an arbitrary polygon looks broken).\n      const mask = this.getAttribute('mask');\n      const shape = (this.getAttribute('shape') || 'rounded').toLowerCase();\n      let radius = '';\n      if (shape === 'circle') radius = '50%';\n      else if (shape === 'pill') radius = '9999px';\n      else if (shape === 'rounded') {\n        const n = parseFloat(this.getAttribute('radius'));\n        radius = (Number.isFinite(n) ? n : 12) + 'px';\n      }\n      this._frame.style.borderRadius = mask ? '' : radius;\n      this._frame.style.clipPath = mask || '';\n      this._ring.style.borderRadius = mask ? '' : radius;\n      this._ring.style.display = mask ? 'none' : '';\n\n      \/\/ Controls and reframe entry gate on this so share links stay read-only.\n      const editable = !!(window.omelette && window.omelette.writeFile);\n      this.toggleAttribute('data-editable', editable);\n      this._sub.style.display = editable ? '' : 'none';\n\n      \/\/ Content. The sidecar is also writable by the agent's write_file\n      \/\/ tool, so its value isn't guaranteed canvas-originated \u2014 only accept\n      \/\/ data:image\/ URLs from it. The `src` attribute is author-controlled\n      \/\/ (Claude wrote it into the HTML) so it passes through unchanged.\n      let stored = this.id ? getSlot(this.id) : this._local;\n      if (stored && stored.u && !\/^data:image\\\/\/i.test(stored.u)) stored = null;\n      const srcAttr = this.getAttribute('src') || '';\n      this._userUrl = (stored && stored.u) || null;\n      const url = this._userUrl || srcAttr;\n      \/\/ Don't clobber an in-flight reframe with a store-triggered re-render.\n      if (!this.hasAttribute('data-reframe')) {\n        this._view = {\n          s: stored && Number.isFinite(stored.s) ? clampS(stored.s) : 1,\n          x: stored && Number.isFinite(stored.x) ? stored.x : 0,\n          y: stored && Number.isFinite(stored.y) ? stored.y : 0,\n        };\n      }\n      this._cap.textContent = this.getAttribute('placeholder') || 'Drop an image';\n      \/\/ Toggle via style.display \u2014 the [hidden] attribute alone loses to\n      \/\/ the display:flex \/ display:block rules in the stylesheet above.\n      \/\/ An Unsplash src with no credit attribute must NOT render \u2014 showing\n      \/\/ the photo uncredited is the Unsplash-terms violation itself. The\n      \/\/ error tile replaces the photo until the credit is written. A\n      \/\/ user-dropped image is the user's own content and always renders.\n      \/\/ Trimmed: credit is agent\/user-editable content, and a whitespace-\n      \/\/ only value must count as missing \u2014 otherwise it would suppress the\n      \/\/ error tile AND render an empty credit box (no text, no links),\n      \/\/ exactly the unattributed state this gate exists to prevent.\n      const credit = (this.getAttribute('credit') || '').trim();\n      const attrError = !!(\n        !credit && !this._userUrl && srcAttr && isUnsplashHost(srcAttr)\n      );\n      this.toggleAttribute('data-attribution-error', attrError);\n      if (url && !attrError) {\n        const prev = this._img.getAttribute('src');\n        if (prev !== url) {\n          \/\/ Replacing an already-shown image: mark the swap BEFORE setting\n          \/\/ src so the stale frame is never revealed (see the data-swapping\n          \/\/ stylesheet rules). First fill (prev empty) keeps the existing\n          \/\/ placeholder-until-load behavior \u2014 no spinner. _hidShowing\n          \/\/ covers the pick path's transient attribution-error wipe: prev\n          \/\/ is gone, but an image WAS showing, so this is a replacement.\n          if (prev || this._hidShowing) this.setAttribute('data-swapping', '');\n          \/\/ Mark the swap BEFORE assigning src: complete keeps reporting\n          \/\/ the old settled request until the browser's\n          \/\/ update-the-image-data microtask runs, so same-task re-renders\n          \/\/ (the pick path's credit\/credit-href setAttributes) need this\n          \/\/ flag, not complete, to know a load is in flight.\n          this._loadPending = true;\n          this._img.src = url;\n          this._ghost.src = url;\n        } else {\n          \/\/ Same-src re-render \u2014 release if settled, so an ingest-set\n          \/\/ spinner can't stick after a byte-identical re-upload (same\n          \/\/ data URL, no further load event ever fires).\n          this._releaseMask();\n        }\n        this._hidShowing = false;\n        this._img.style.display = 'block';\n        this._empty.style.display = 'none';\n        this.setAttribute('data-filled', '');\n        this._clampView();\n        this._applyView();\n      } else {\n        this.removeAttribute('data-swapping');\n        \/\/ The src is being removed \u2014 no load\/error will ever fire for it.\n        this._loadPending = false;\n        \/\/ A transient attribution-error wipe of a showing image happens on\n        \/\/ the pick path: the host sets src one setAttribute before credit,\n        \/\/ so render N hides the old image (attrError) and render N+1\n        \/\/ restores a URL. Remember the wipe so that restore renders as a\n        \/\/ replacement (spinner), not a first fill (blank frame).\n        this._hidShowing = attrError && !!this._img.getAttribute('src');\n        this._img.style.display = 'none';\n        this._img.removeAttribute('src');\n        this._ghost.removeAttribute('src');\n        \/\/ The error tile owns the blocked-photo state; .empty stays for\n        \/\/ the genuinely-empty slot.\n        this._empty.style.display = attrError ? 'none' : 'flex';\n        this.removeAttribute('data-filled');\n      }\n\n      \/\/ Credit belongs to the author src, so a user drop hides it.\n      \/\/ textContent + the http(s)-only funnel keep external strings inert.\n      const showCredit = !!(url && credit && !this._userUrl && !attrError);\n      this._credit.textContent = '';\n      if (showCredit) {\n        \/\/ Validate once (resolved against the document, http(s) only),\n        \/\/ then append the terms-required utm referral params to links\n        \/\/ that point back at unsplash.com.\n        let href = '';\n        const rawHref = this.getAttribute('credit-href') || '';\n        if (rawHref) {\n          try {\n            const u = new URL(rawHref, document.baseURI);\n            if (u.protocol === 'http:' || u.protocol === 'https:') {\n              href = withReferral(u.href);\n            }\n          } catch {}\n        }\n        const mkLink = (text, linkHref) => {\n          const a = document.createElement('a');\n          a.setAttribute('target', '_blank');\n          a.setAttribute('rel', 'noopener noreferrer');\n          a.setAttribute('href', linkHref);\n          a.textContent = text;\n          return a;\n        };\n        \/\/ Unsplash's prescribed credit is TWO links \u2014 the photographer's\n        \/\/ name to their profile (credit-href) and 'Unsplash' to the\n        \/\/ homepage. Render that split whenever the text has the canonical\n        \/\/ shape; other text keeps the legacy single-link rendering.\n        const m = \/^Photo by (.+) on Unsplash$\/.exec(credit);\n        if (m) {\n          this._credit.appendChild(document.createTextNode('Photo by '));\n          this._credit.appendChild(\n            href ? mkLink(m[1], href) : document.createTextNode(m[1])\n          );\n          this._credit.appendChild(document.createTextNode(' on '));\n          this._credit.appendChild(mkLink('Unsplash', UNSPLASH_HOMEPAGE_HREF));\n        } else if (href) {\n          this._credit.appendChild(mkLink(credit, href));\n        } else {\n          this._credit.textContent = credit;\n        }\n      }\n      this.toggleAttribute('data-credit', showCredit);\n    }\n  }\n\n  if (!customElements.get('image-slot')) {\n    customElements.define('image-slot', ImageSlot);\n  }\n})();\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>Strat\u00e9gie \u00b7 Contenu \u00b7 Publicit\u00e9 \u00b7 Analyse Une \u00e9quipe marketing compl\u00e8te, sans une seule embauche. Un directeur marketing, un r\u00e9dacteur, un graphiste, un sp\u00e9cialiste publicit\u00e9 et un analyste : le dispositif dont vous auriez besoin, mutualis\u00e9 et disponible d\u00e8s le mois prochain. Vous pilotez votre activit\u00e9, nous pilotons votre visibilit\u00e9. Voir les formules \u2014 d\u00e8s 1 490\u20ac\/mois \u2192 Obtenir une proposition chiffr\u00e9e Op\u00e9rationnel en 15 jours5 m\u00e9tiers pour le prix d&rsquo;un posteSans engagement de dur\u00e9e PLAN MARKETING \u2014 TRIMESTRE EN COURS Strat\u00e9gieContenuPublicit\u00e9E-mailingR\u00e9seaux sociauxR\u00e9f\u00e9rencementAnalyseDesignAutomatisation Strat\u00e9gieContenuPublicit\u00e9E-mailingR\u00e9seaux sociauxR\u00e9f\u00e9rencementAnalyseDesignAutomatisation Le constat Recruter un profil marketing, c&rsquo;est parier gros. Un poste marketing co\u00fbte cher, met des mois \u00e0 se pourvoir, et un seul profil ne peut pas \u00eatre \u00e0 la fois strat\u00e8ge, r\u00e9dacteur, graphiste, publicitaire et analyste. La plupart des entreprises finissent avec un g\u00e9n\u00e9raliste d\u00e9bord\u00e9 \u2014 ou avec personne. \u2248 55 k\u20ac Le co\u00fbt annuel charg\u00e9 d&rsquo;un profil marketing confirm\u00e9, avant m\u00eame de compter les outils, la formation et le temps de management. 4 mois Le d\u00e9lai moyen entre la d\u00e9cision de recruter et le premier jour r\u00e9ellement productif d&rsquo;un nouveau poste marketing. 5 m\u00e9tiers Le nombre de comp\u00e9tences distinctes qu&rsquo;un marketing efficace mobilise aujourd&rsquo;hui \u2014 impossible \u00e0 r\u00e9unir dans une seule personne. Votre \u00e9quipe Cinq m\u00e9tiers, un seul interlocuteur. Vous ne coordonnez rien : un responsable de compte unique orchestre l&rsquo;\u00e9quipe et vous rend des comptes. R\u00d4LE 01 Direction marketing Quelqu&rsquo;un qui d\u00e9cide quoi faire, dans quel ordre et pourquoi \u2014 puis qui l&rsquo;assume devant vous. Plan marketing trimestriel \u00e9crit et prioris\u00e9 Positionnement, messages, offres Arbitrage des budgets entre leviers Point mensuel de pilotage avec vous R\u00d4LE 02 Production &#038; cr\u00e9ation Les contenus qui alimentent tous vos canaux : \u00e9crits, visuels, anim\u00e9s. Produits, pas seulement recommand\u00e9s. Articles, pages, fiches, argumentaires Publications et carrousels pour les r\u00e9seaux Vid\u00e9os motion et visuels publicitaires Mod\u00e8les d&rsquo;e-mails et de documents commerciaux R\u00d4LE 03 Diffusion &#038; mesure Un contenu qui n&rsquo;est pas diffus\u00e9 et mesur\u00e9 n&rsquo;existe pas. Nous nous chargeons des deux. Campagnes publicitaires et r\u00e9f\u00e9rencement S\u00e9quences d&rsquo;e-mails et automatisations Suivi des conversions et tableaux de bord Rapport mensuel en euros, pas en vues Les formules Trois niveaux d&rsquo;implication. Du renfort ponctuel \u00e0 la direction marketing compl\u00e8te. Vous montez ou descendez de formule \u00e0 tout moment. Formule 01 Renfort Pour une entreprise qui sait o\u00f9 elle va mais n&rsquo;a personne pour ex\u00e9cuter. 1 490\u20acHT \/ mois Plan trimestriel \u00e9crit et prioris\u00e9 8 publications par mois, visuels inclus 2 articles optimis\u00e9s par mois Gestion d&rsquo;un levier publicitaire Rapport mensuel comment\u00e9 Choisir Renfort Le plus choisi Formule 02 Pilotage Pour confier l&rsquo;ensemble du marketing op\u00e9rationnel, du contenu \u00e0 la publicit\u00e9. 2 490\u20acHT \/ mois Tout le renfort, avec un volume doubl\u00e9 16 publications et 4 articles par mois 2 vid\u00e9os motion par mois Publicit\u00e9 multi-levier et retargeting S\u00e9quences d&rsquo;e-mails et automatisations Point mensuel de 1 heure \u00b7 responsable d\u00e9di\u00e9 Choisir Pilotage Formule 03 Direction Pour remplacer une direction marketing interne, avec la production qui va avec. 3 900\u20acHT \/ mois Tout le pilotage, sans limite de volume raisonnable Direction marketing : plan annuel, budgets, arbitrages Refonte des pages et du tunnel de conversion Comit\u00e9 mensuel avec votre direction Pr\u00e9sence aux r\u00e9unions commerciales si utile Acc\u00e8s prioritaire \u00e0 toute l&rsquo;\u00e9quipe Choisir Direction Pour commencer Diagnostic marketing 360\u00b0 Trois semaines pour cartographier votre situation : march\u00e9, concurrence, canaux existants, tunnel de conversion, contenus, outils. Vous recevez un plan d&rsquo;action prioris\u00e9 sur douze mois, chiffr\u00e9 et s\u00e9quenc\u00e9 \u2014 exploitable par nous, par votre \u00e9quipe ou par un autre prestataire. D\u00e9duit de la premi\u00e8re mensualit\u00e9 si vous poursuivez avec nous. 2 500\u20ac HT \u00b7 une seule fois Ce que couvre l&rsquo;\u00e9quipe Le p\u00e9rim\u00e8tre complet. Tout ne se d\u00e9clenche pas en m\u00eame temps : le plan trimestriel d\u00e9cide de ce qui compte maintenant, et de ce qui attendra. FONDATIONSPositionnement &#038; messagesCe que vous vendez, \u00e0 qui, et pourquoi vous plut\u00f4t qu&rsquo;un autre. Sans cette base \u00e9crite, tout le reste part dans toutes les directions. ATTRACTIONContenu &#038; r\u00e9f\u00e9rencementArticles, pages et fiches con\u00e7us pour \u00eatre trouv\u00e9s sur les moteurs de recherche et repris par les assistants conversationnels. VISIBILIT\u00c9R\u00e9seaux sociauxPublications r\u00e9guli\u00e8res, carrousels, vid\u00e9os courtes. Une pr\u00e9sence tenue plut\u00f4t qu&rsquo;une avalanche suivie de six mois de silence. ACC\u00c9L\u00c9RATIONPublicit\u00e9 digitaleGoogle, Meta, LinkedIn, TikTok, programmatique. Le levier qui donne du volume imm\u00e9diat quand les fondations sont pos\u00e9es. RELATIONE-mailing &#038; automatisationS\u00e9quences de bienvenue, r\u00e9activation, relances commerciales, newsletters. Le canal le moins co\u00fbteux et le plus n\u00e9glig\u00e9. PILOTAGEMesure &#038; reportingSuivi des conversions, tableaux de bord, rapport mensuel. Savoir ce qui rapporte pour arr\u00eater de financer ce qui ne rapporte pas. Comment \u00e7a se passe Op\u00e9rationnel en quinze jours. \u00c9TAPE 01 Immersion Deux ateliers pour comprendre votre activit\u00e9, vos clients, vos marges et vos contraintes. Nous r\u00e9cup\u00e9rons les acc\u00e8s et l&rsquo;existant. JOURS 1 \u00c0 5 \u00c9TAPE 02 Plan trimestriel Un document \u00e9crit : les priorit\u00e9s, les livrables, le calendrier et les indicateurs de succ\u00e8s. Valid\u00e9 par vous avant ex\u00e9cution. JOURS 6 \u00c0 15 \u00c9TAPE 03 Production L&rsquo;\u00e9quipe entre en action. Contenus, campagnes, s\u00e9quences : les premiers livrables arrivent d\u00e8s la troisi\u00e8me semaine. \u00c0 PARTIR DU JOUR 15 \u00c9TAPE 04 Pilotage Point mensuel, rapport chiffr\u00e9, r\u00e9ajustement du plan. Chaque trimestre, nous r\u00e9\u00e9crivons les priorit\u00e9s avec vous. EN CONTINU \u00c0 la carte Besoin d&rsquo;aller plus loin ? Chaque option s&rsquo;ajoute \u00e0 une formule ou se commande seule. Tarifs HT. Diagnostic marketing 360\u00b0Plan prioris\u00e9 sur 12 mois 2 500\u20ac Refonte de site webSite vitrine ou e-commerce d\u00e8s 1 250\u20ac Identit\u00e9 graphiqueLogo, univers, d\u00e9clinaisons d\u00e8s 500\u20ac Vid\u00e9o motion additionnelle15 secondes, 4 formats 390\u20ac Atelier positionnementDemi-journ\u00e9e avec votre direction 890\u20ac Suivi &#038; conformit\u00e9GA4, GTM, consentement RGPD 850\u20ac Formation de votre \u00e9quipeTransfert de comp\u00e9tences, demi-journ\u00e9e 590\u20ac Landing page d\u00e9di\u00e9ePage de campagne ou de lancement 750\u20ac Questions fr\u00e9quentes Ce qu&rsquo;on nous demande avant de signer. En quoi est-ce diff\u00e9rent d&rsquo;une agence classique ? Une agence vend des prestations : un site, une campagne, dix articles. Nous prenons en charge une fonction \u2014 le marketing \u2014 avec un plan, des priorit\u00e9s et une obligation de r\u00e9sultat sur la dur\u00e9e. La diff\u00e9rence se voit surtout au bout de six mois. Est-ce vraiment<\/p>","protected":false},"author":4,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"_sitemap_exclude":false,"_sitemap_priority":"","_sitemap_frequency":"","rank_math_title":"Marketing D\u00e9l\u00e9gu\u00e9 \u2014 \u00c9quipe Externalis\u00e9e | Exoa","rank_math_description":"Marketing d\u00e9l\u00e9gu\u00e9 : une \u00e9quipe marketing compl\u00e8te (SEO, ads, social, cr\u00e9ation) pour moins qu'un salaire charg\u00e9. Sans engagement long terme.","rank_math_focus_keyword":"marketing d\u00e9l\u00e9gu\u00e9","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_robots":["index"],"rank_math_advanced_robots":[],"site-post-title":"disabled","site-content-layout":"page-builder","site-sidebar-layout":"no-sidebar","ast-featured-img":"disabled","ast-breadcrumbs-content":"","footnotes":""},"class_list":["post-14787","page","type-page","status-publish","hentry"],"_yoast_wpseo_focuskw":null,"_yoast_wpseo_metadesc":null,"_links":{"self":[{"href":"https:\/\/www.agence-exoa.com\/de\/wp-json\/wp\/v2\/pages\/14787","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.agence-exoa.com\/de\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.agence-exoa.com\/de\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.agence-exoa.com\/de\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.agence-exoa.com\/de\/wp-json\/wp\/v2\/comments?post=14787"}],"version-history":[{"count":8,"href":"https:\/\/www.agence-exoa.com\/de\/wp-json\/wp\/v2\/pages\/14787\/revisions"}],"predecessor-version":[{"id":16594,"href":"https:\/\/www.agence-exoa.com\/de\/wp-json\/wp\/v2\/pages\/14787\/revisions\/16594"}],"wp:attachment":[{"href":"https:\/\/www.agence-exoa.com\/de\/wp-json\/wp\/v2\/media?parent=14787"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}