{"id":14785,"date":"2026-08-01T14:19:29","date_gmt":"2026-08-01T14:19:29","guid":{"rendered":"https:\/\/www.agence-exoa.com\/?page_id=14785"},"modified":"2026-08-03T16:41:10","modified_gmt":"2026-08-03T16:41:10","slug":"acquisition-expertise","status":"publish","type":"page","link":"https:\/\/www.agence-exoa.com\/en\/expertise-acquisition\/","title":{"rendered":"Expertise acquisition \u2014 G\u00e9n\u00e9rer un flux de demandes qualifi\u00e9es | 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-expertise-acquisition-page *{margin:0;padding:0;box-sizing:border-box}html{scroll-behavior:smooth}.exoa-expertise-acquisition-page{font-family:'DM Sans',sans-serif;color:var(--ink);background:#fff;-webkit-font-smoothing:antialiased}.exoa-expertise-acquisition-page a{color:var(--lav2);text-decoration:none}.exoa-expertise-acquisition-page a:hover{color:var(--violet)}.exoa-expertise-acquisition-page .wrap{max-width:1200px;margin:0 auto;padding:0 32px}.exoa-expertise-acquisition-page .mono{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700}.exoa-expertise-acquisition-page h1, .exoa-expertise-acquisition-page h2, .exoa-expertise-acquisition-page h3, .exoa-expertise-acquisition-page .disp{font-family:'Bricolage Grotesque',sans-serif;font-weight:800;letter-spacing:-.02em;line-height:1.02}.exoa-expertise-acquisition-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-expertise-acquisition-page .nav-in{max-width:1200px;margin:0 auto;padding:14px 32px;display:flex;align-items:center;justify-content:space-between;gap:20px}.exoa-expertise-acquisition-page .logo{display:flex;align-items:center;gap:9px;color:#fff;font-weight:700;font-size:15px;letter-spacing:.02em}.exoa-expertise-acquisition-page .logo svg{width:17px;height:17px}.exoa-expertise-acquisition-page .nav-mid{display:flex;gap:26px}.exoa-expertise-acquisition-page .nav-mid a{color:#C6C9E0;font-size:14px;font-weight:500}.exoa-expertise-acquisition-page .nav-mid a:hover{color:#fff}.exoa-expertise-acquisition-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-expertise-acquisition-page .btn-lime{background:var(--lime);color:var(--navy)}.exoa-expertise-acquisition-page .btn-lime:hover{background:#dcff9c;color:var(--navy);transform:translateY(-1px)}.exoa-expertise-acquisition-page .btn-ghost{background:rgba(255,255,255,.08);color:#fff;border:1px solid rgba(255,255,255,.2)}.exoa-expertise-acquisition-page .btn-ghost:hover{background:rgba(255,255,255,.16);color:#fff}.exoa-expertise-acquisition-page .btn-navy{background:var(--navy);color:#fff}.exoa-expertise-acquisition-page .btn-navy:hover{background:var(--navy2);color:#fff;transform:translateY(-1px)}.exoa-expertise-acquisition-page .btn-out{background:#fff;color:var(--navy);border:1.5px solid var(--line)}.exoa-expertise-acquisition-page .btn-out:hover{border-color:var(--lav);color:var(--navy)}.exoa-expertise-acquisition-page .hero{background:var(--navy);color:#fff;padding:88px 0 0;position:relative;overflow:hidden}.exoa-expertise-acquisition-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-expertise-acquisition-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-expertise-acquisition-page .hero-in{position:relative;z-index:2}.exoa-expertise-acquisition-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-expertise-acquisition-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-expertise-acquisition-page .pill span{font-family:'JetBrains Mono',monospace;font-size:10.5px;letter-spacing:.13em;text-transform:uppercase;font-weight:700;color:#E8EAF6}.exoa-expertise-acquisition-page .hero h1{font-size:clamp(40px,5.6vw,76px);max-width:15ch}.exoa-expertise-acquisition-page .hero h1 em{font-style:normal;color:var(--lime)}.exoa-expertise-acquisition-page .hero h1 .v{font-style:normal;color:var(--violet)}.exoa-expertise-acquisition-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-expertise-acquisition-page .hero-cta{display:flex;gap:12px;flex-wrap:wrap;align-items:center}.exoa-expertise-acquisition-page .hero-note{font-size:13px;color:#9498b8;margin-top:18px;display:flex;gap:18px;flex-wrap:wrap}.exoa-expertise-acquisition-page .hero-note b{color:#E8EAF6;font-weight:600}.exoa-expertise-acquisition-page .reel{margin-top:56px;position:relative;z-index:2}.exoa-expertise-acquisition-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-expertise-acquisition-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-expertise-acquisition-page .reel-bar i{width:9px;height:9px;border-radius:50%;background:rgba(255,255,255,.22)}.exoa-expertise-acquisition-page .reel-bar em{font-style:normal;font-family:'JetBrains Mono',monospace;font-size:10px;letter-spacing:.1em;color:#9498b8;margin-left:8px}.exoa-expertise-acquisition-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-expertise-acquisition-page .marq-t{display:inline-flex;gap:44px;animation:sc 26s linear infinite;padding-right:44px}.exoa-expertise-acquisition-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-expertise-acquisition-page .marq-t span::after{content:'\u2726';opacity:.45}@keyframes sc{to{transform:translateX(-50%)}}.exoa-expertise-acquisition-page .sec{padding:104px 0}.exoa-expertise-acquisition-page .sec-lt{background:var(--bg)}.exoa-expertise-acquisition-page .sec-dk{background:var(--navy);color:#fff}.exoa-expertise-acquisition-page .tag{display:inline-flex;align-items:center;gap:8px;margin-bottom:16px}.exoa-expertise-acquisition-page .tag i{width:22px;height:2px;background:var(--lav);border-radius:2px}.exoa-expertise-acquisition-page .tag span{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.14em;text-transform:uppercase;font-weight:700;color:var(--lav2)}.exoa-expertise-acquisition-page .sec-dk .tag span{color:var(--lime)}.exoa-expertise-acquisition-page .sec-dk .tag i{background:var(--lime)}.exoa-expertise-acquisition-page .h2{font-size:clamp(30px,3.6vw,50px);max-width:22ch}.exoa-expertise-acquisition-page .h2 em{font-style:normal;color:var(--lav2)}.exoa-expertise-acquisition-page .sec-dk .h2 em{color:var(--lime)}.exoa-expertise-acquisition-page .lead{font-size:17px;color:var(--body);line-height:1.65;max-width:64ch;margin-top:18px;text-wrap:pretty}.exoa-expertise-acquisition-page .sec-dk .lead{color:#C6C9E0}.exoa-expertise-acquisition-page .head-row{display:flex;justify-content:space-between;align-items:flex-end;gap:40px;margin-bottom:52px;flex-wrap:wrap}.exoa-expertise-acquisition-page .stats{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;margin-top:52px}.exoa-expertise-acquisition-page .stat{background:rgba(255,255,255,.045);border:1px solid rgba(255,255,255,.13);border-radius:18px;padding:30px}.exoa-expertise-acquisition-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-expertise-acquisition-page .stat-l{font-size:14.5px;color:#C6C9E0;line-height:1.55;margin-top:12px}.exoa-expertise-acquisition-page .bricks{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}.exoa-expertise-acquisition-page .brick{background:#fff;border:1px solid var(--line);border-radius:20px;overflow:hidden;display:flex;flex-direction:column;transition:all .22s}.exoa-expertise-acquisition-page .brick:hover{border-color:var(--lav);transform:translateY(-3px);box-shadow:0 16px 40px -20px rgba(6,0,57,.22)}.exoa-expertise-acquisition-page .brick-vis{width:100%;min-width:0;aspect-ratio:4\/3;background:var(--bg);border-bottom:1px solid var(--line);position:relative}.exoa-expertise-acquisition-page .brick>*{min-width:0}.exoa-expertise-acquisition-page .brick-b{padding:26px;display:flex;flex-direction:column;gap:11px;flex:1}.exoa-expertise-acquisition-page .brick-n{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.12em;font-weight:700;color:var(--lav2)}.exoa-expertise-acquisition-page .brick h3{font-size:21px;letter-spacing:-.01em}.exoa-expertise-acquisition-page .brick p{font-size:14.5px;color:var(--body);line-height:1.62}.exoa-expertise-acquisition-page .brick ul{list-style:none;display:flex;flex-direction:column;gap:8px;margin-top:4px}.exoa-expertise-acquisition-page .brick li{font-size:13.5px;color:var(--body);padding-left:20px;position:relative;line-height:1.5}.exoa-expertise-acquisition-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-expertise-acquisition-page .packs{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;align-items:start}.exoa-expertise-acquisition-page .pack{background:#fff;border:1px solid var(--line);border-radius:22px;padding:32px;display:flex;flex-direction:column;gap:18px;position:relative}.exoa-expertise-acquisition-page .pack.hot{background:var(--navy);border-color:var(--navy);color:#fff;box-shadow:0 24px 60px -28px rgba(6,0,57,.5)}.exoa-expertise-acquisition-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-expertise-acquisition-page .pack-n{font-family:'JetBrains Mono',monospace;font-size:11px;letter-spacing:.13em;text-transform:uppercase;font-weight:700;color:var(--lav2)}.exoa-expertise-acquisition-page .pack.hot .pack-n{color:var(--lime)}.exoa-expertise-acquisition-page .pack h3{font-size:25px}.exoa-expertise-acquisition-page .pack-for{font-size:13.5px;color:var(--body);line-height:1.55}.exoa-expertise-acquisition-page .pack.hot .pack-for{color:#C6C9E0}.exoa-expertise-acquisition-page .pack-p{display:flex;align-items:baseline;gap:7px;padding-top:6px;border-top:1px solid var(--line)}.exoa-expertise-acquisition-page .pack.hot .pack-p{border-color:rgba(255,255,255,.15)}.exoa-expertise-acquisition-page .pack-p b{font-family:'Bricolage Grotesque',sans-serif;font-size:40px;font-weight:800;letter-spacing:-.03em}.exoa-expertise-acquisition-page .pack.hot .pack-p b{color:var(--lime)}.exoa-expertise-acquisition-page .pack-p i{font-style:normal;font-size:13px;color:var(--body);font-weight:500}.exoa-expertise-acquisition-page .pack.hot .pack-p i{color:#9CA0C4}.exoa-expertise-acquisition-page .pack-l{list-style:none;display:flex;flex-direction:column;gap:11px;flex:1}.exoa-expertise-acquisition-page .pack-l li{font-size:14px;line-height:1.5;padding-left:24px;position:relative;color:var(--body)}.exoa-expertise-acquisition-page .pack.hot .pack-l li{color:#DDE0F0}.exoa-expertise-acquisition-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-expertise-acquisition-page .pack-l li b{color:var(--ink);font-weight:700}.exoa-expertise-acquisition-page .pack.hot .pack-l li b{color:#fff}.exoa-expertise-acquisition-page .pack-l li.plus::before{background:var(--violet);box-shadow:none}.exoa-expertise-acquisition-page .pack .btn{justify-content:center;width:100%}.exoa-expertise-acquisition-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-expertise-acquisition-page .sub-l{flex:1;min-width:280px}.exoa-expertise-acquisition-page .sub .mono{color:rgba(255,255,255,.75);display:block;margin-bottom:9px}.exoa-expertise-acquisition-page .sub h3{font-size:26px;margin-bottom:10px}.exoa-expertise-acquisition-page .sub p{font-size:14.5px;color:rgba(255,255,255,.9);line-height:1.6;max-width:58ch}.exoa-expertise-acquisition-page .sub-r{text-align:right}.exoa-expertise-acquisition-page .sub-r b{font-family:'Bricolage Grotesque',sans-serif;font-size:40px;font-weight:800;display:block;letter-spacing:-.03em}.exoa-expertise-acquisition-page .sub-r i{font-style:normal;font-size:13px;color:rgba(255,255,255,.8)}.exoa-expertise-acquisition-page .fmts{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-top:12px}.exoa-expertise-acquisition-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-expertise-acquisition-page .fmt-r{border:1.5px dashed rgba(205,249,128,.5);border-radius:7px;background:rgba(205,249,128,.07)}.exoa-expertise-acquisition-page .fmt b{font-size:15px;color:#fff}.exoa-expertise-acquisition-page .fmt small{font-size:12.5px;color:#9CA0C4;line-height:1.5;display:block}.exoa-expertise-acquisition-page .steps{display:grid;grid-template-columns:repeat(4,1fr);gap:0;margin-top:12px;border-top:1px solid var(--line)}.exoa-expertise-acquisition-page .step{padding:30px 26px 30px 0;border-right:1px solid var(--line);position:relative}.exoa-expertise-acquisition-page .step:last-child{border-right:0;padding-right:0}.exoa-expertise-acquisition-page .step::before{content:'';position:absolute;top:-1px;left:0;width:38%;height:3px;background:var(--lime)}.exoa-expertise-acquisition-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-expertise-acquisition-page .step h3{font-size:18px;margin-bottom:9px;letter-spacing:-.01em}.exoa-expertise-acquisition-page .step p{font-size:14px;color:var(--body);line-height:1.6}.exoa-expertise-acquisition-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-expertise-acquisition-page .adds{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:14px}.exoa-expertise-acquisition-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-expertise-acquisition-page .add:hover{border-color:var(--lav)}.exoa-expertise-acquisition-page .add b{font-size:14.5px;font-weight:600;display:block;margin-bottom:3px}.exoa-expertise-acquisition-page .add small{font-size:12.5px;color:var(--body)}.exoa-expertise-acquisition-page .add i{font-style:normal;font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;color:var(--lav2);white-space:nowrap}.exoa-expertise-acquisition-page .gal{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:12px}.exoa-expertise-acquisition-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-expertise-acquisition-page .gal-i.wide{grid-column:span 2;aspect-ratio:16\/10}.exoa-expertise-acquisition-page .faq{max-width:840px;margin:0 auto;display:flex;flex-direction:column;gap:10px}.exoa-expertise-acquisition-page details{background:#fff;border:1px solid var(--line);border-radius:14px;overflow:hidden;transition:border-color .2s}.exoa-expertise-acquisition-page details[open]{border-color:var(--lav)}.exoa-expertise-acquisition-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-expertise-acquisition-page summary::-webkit-details-marker{display:none}.exoa-expertise-acquisition-page summary::after{content:'+';font-family:'Bricolage Grotesque',sans-serif;font-size:22px;color:var(--lav2);flex-shrink:0;line-height:1}.exoa-expertise-acquisition-page details[open] summary::after{content:'\u2212'}.exoa-expertise-acquisition-page details p{padding:0 24px 22px;font-size:14.5px;color:var(--body);line-height:1.7}.exoa-expertise-acquisition-page details p b{color:var(--ink)}.exoa-expertise-acquisition-page .fin{background:var(--navy);color:#fff;padding:104px 0;position:relative;overflow:hidden}.exoa-expertise-acquisition-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-expertise-acquisition-page .fin-in{position:relative;z-index:2;display:grid;grid-template-columns:1fr .9fr;gap:56px;align-items:start}.exoa-expertise-acquisition-page .fin h2{font-size:clamp(32px,4vw,54px);max-width:16ch}.exoa-expertise-acquisition-page .fin h2 em{font-style:normal;color:var(--lime)}.exoa-expertise-acquisition-page .fin p{font-size:16.5px;color:#C6C9E0;line-height:1.65;margin-top:20px;max-width:50ch}.exoa-expertise-acquisition-page .fin-tr{display:flex;flex-direction:column;gap:12px;margin-top:30px}.exoa-expertise-acquisition-page .fin-tr div{display:flex;gap:11px;align-items:flex-start;font-size:14.5px;color:#DDE0F0;line-height:1.5}.exoa-expertise-acquisition-page .fin-tr b{color:var(--lime);flex-shrink:0}.exoa-expertise-acquisition-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-expertise-acquisition-page .form h3{font-size:20px;margin-bottom:6px}.exoa-expertise-acquisition-page .form>p{font-size:13.5px;color:#9CA0C4;margin-bottom:22px}.exoa-expertise-acquisition-page .fg{display:grid;grid-template-columns:1fr 1fr;gap:13px}.exoa-expertise-acquisition-page .fld{display:flex;flex-direction:column;gap:6px}.exoa-expertise-acquisition-page .fld.full{grid-column:1\/-1}.exoa-expertise-acquisition-page .fld label{font-size:12.5px;font-weight:600;color:#DDE0F0}.exoa-expertise-acquisition-page .fld input, .exoa-expertise-acquisition-page .fld select, .exoa-expertise-acquisition-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-expertise-acquisition-page .fld input::placeholder, .exoa-expertise-acquisition-page .fld textarea::placeholder{color:#7C81A6}.exoa-expertise-acquisition-page .fld select option{background:var(--navy);color:#fff}.exoa-expertise-acquisition-page .fld input:focus, .exoa-expertise-acquisition-page .fld select:focus, .exoa-expertise-acquisition-page .fld textarea:focus{outline:0;border-color:var(--lime)}.exoa-expertise-acquisition-page .chk{display:flex;gap:9px;align-items:flex-start;font-size:12px;color:#9CA0C4;line-height:1.5;margin-top:14px}.exoa-expertise-acquisition-page .chk input{margin-top:2px;accent-color:var(--lime)}.exoa-expertise-acquisition-page .ok{display:none;text-align:center;padding:36px 12px}.exoa-expertise-acquisition-page .ok.on{display:block}.exoa-expertise-acquisition-page .ok h3{font-size:23px;color:var(--lime);margin-bottom:10px}.exoa-expertise-acquisition-page .ok p{font-size:14.5px;color:#C6C9E0;line-height:1.6}.exoa-expertise-acquisition-page .foot{background:#04002B;color:#7C81A6;padding:34px 0;font-size:13px}.exoa-expertise-acquisition-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-expertise-acquisition-page .foot a{color:#9CA0C4}.exoa-expertise-acquisition-page .foot a:hover{color:#fff}@media(max-width:1000px){.exoa-expertise-acquisition-page .bricks, .exoa-expertise-acquisition-page .packs, .exoa-expertise-acquisition-page .stats{grid-template-columns:1fr 1fr}.exoa-expertise-acquisition-page .fmts{grid-template-columns:1fr 1fr}.exoa-expertise-acquisition-page .steps{grid-template-columns:1fr 1fr}.exoa-expertise-acquisition-page .step{border-right:0;border-bottom:1px solid var(--line);padding-right:26px}.exoa-expertise-acquisition-page .fin-in{grid-template-columns:1fr}.exoa-expertise-acquisition-page .gal{grid-template-columns:1fr 1fr}.exoa-expertise-acquisition-page .gal-i.wide{grid-column:span 2}}@media(max-width:700px){.exoa-expertise-acquisition-page .wrap{padding:0 20px}.exoa-expertise-acquisition-page .nav-mid{display:none}.exoa-expertise-acquisition-page .sec{padding:64px 0}.exoa-expertise-acquisition-page .bricks, .exoa-expertise-acquisition-page .packs, .exoa-expertise-acquisition-page .stats, .exoa-expertise-acquisition-page .fmts, .exoa-expertise-acquisition-page .steps, .exoa-expertise-acquisition-page .gal, .exoa-expertise-acquisition-page .fg{grid-template-columns:1fr}.exoa-expertise-acquisition-page .gal-i.wide{grid-column:span 1}.exoa-expertise-acquisition-page .hero{padding-top:56px}.exoa-expertise-acquisition-page .hero-cta{flex-direction:column;align-items:stretch}.exoa-expertise-acquisition-page .hero-cta .btn{justify-content:center}.exoa-expertise-acquisition-page .sub{padding:28px 24px}.exoa-expertise-acquisition-page .sub-r{text-align:left}.exoa-expertise-acquisition-page .head-row{margin-bottom:36px}.exoa-expertise-acquisition-page .foot-in{flex-direction:column;align-items:flex-start}}.exoa-expertise-acquisition-page .pack-l li.neu::before{background:var(--lav);box-shadow:none}.exoa-expertise-acquisition-page .grid2{display:grid;grid-template-columns:repeat(2,1fr);gap:22px}.exoa-expertise-acquisition-page .lev{background:rgba(255,255,255,.045);border:1px solid rgba(255,255,255,.13);border-radius:16px;padding:24px}.exoa-expertise-acquisition-page .lev b{font-size:16px;color:#fff;display:block;margin-bottom:7px}.exoa-expertise-acquisition-page .lev small{font-size:13.5px;color:#9CA0C4;line-height:1.6;display:block}.exoa-expertise-acquisition-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-expertise-acquisition-page .cmp{width:100%;border-collapse:collapse;font-size:14.5px;background:#fff;border:1px solid var(--line);border-radius:16px;overflow:hidden}.exoa-expertise-acquisition-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-expertise-acquisition-page .cmp td{padding:15px 18px;color:var(--body);border-bottom:1px solid var(--line);line-height:1.5}.exoa-expertise-acquisition-page .cmp tr:last-child td{border-bottom:0}.exoa-expertise-acquisition-page .cmp td:first-child{font-weight:600;color:var(--ink)}.exoa-expertise-acquisition-page .cmp .yes{color:var(--lav2);font-weight:600}.exoa-expertise-acquisition-page .cmp-w{overflow-x:auto;border-radius:16px}@media(max-width:1000px){.exoa-expertise-acquisition-page .grid2{grid-template-columns:1fr}}\n.exoa-expertise-acquisition-page{margin-top:0 !important;padding-top:0 !important}\n.exoa-expertise-acquisition-page .btn-primary,.exoa-expertise-acquisition-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-expertise-acquisition-page .resx-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-top:8px}\n.exoa-expertise-acquisition-page .resx-card{background:#fff;border:1px solid rgba(6,0,57,.1);border-radius:16px;padding:30px 26px}\n.exoa-expertise-acquisition-page .resx-num{font-size:34px;font-weight:800;color:#060039;line-height:1.1}\n.exoa-expertise-acquisition-page .resx-card h3{font-size:17px;font-weight:700;color:#060039;margin:6px 0 8px}\n.exoa-expertise-acquisition-page .resx-card p{font-size:14px;color:#5A5E80;line-height:1.6;margin:0}\n.exoa-expertise-acquisition-page .livr-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:8px}\n.exoa-expertise-acquisition-page .livr-card{background:#F7F7FB;border:1px solid rgba(6,0,57,.08);border-radius:14px;padding:24px 22px}\n.exoa-expertise-acquisition-page .livr-card h3{font-size:15.5px;font-weight:700;color:#060039;margin:0 0 6px}\n.exoa-expertise-acquisition-page .livr-card h3 span{color:#8CC63F;font-weight:800;margin-right:6px}\n.exoa-expertise-acquisition-page .livr-card p{font-size:13.5px;color:#5A5E80;line-height:1.6;margin:0}\n.exoa-expertise-acquisition-page .expx-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:8px}\n.exoa-expertise-acquisition-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-expertise-acquisition-page .expx-card:hover{background:rgba(255,255,255,.1);border-color:rgba(205,249,128,.5)}\n.exoa-expertise-acquisition-page .expx-card strong{display:block;color:#fff;font-size:15.5px;font-weight:700;margin-bottom:4px}\n.exoa-expertise-acquisition-page .expx-card span{display:block;color:#C6C9E0;font-size:13px;line-height:1.5}\n.exoa-expertise-acquisition-page .expx-card em{display:inline-block;margin-top:10px;font-style:normal;color:#CDF980;font-size:12px;font-weight:700}\n.exoa-expertise-acquisition-page .hero h1{color:#fff !important}\n@media(max-width:900px){.exoa-expertise-acquisition-page .resx-grid,.exoa-expertise-acquisition-page .livr-grid,.exoa-expertise-acquisition-page .expx-grid{grid-template-columns:1fr 1fr}}\n@media(max-width:600px){.exoa-expertise-acquisition-page .resx-grid,.exoa-expertise-acquisition-page .livr-grid,.exoa-expertise-acquisition-page .expx-grid{grid-template-columns:1fr}}\n\/*exoa-landing-fix*\/\n.exoa-expertise-acquisition-page .sec-dk h2,.exoa-expertise-acquisition-page .sec-dk .h2,.exoa-expertise-acquisition-page .sec-dk h3,.exoa-expertise-acquisition-page .fin h2,.exoa-expertise-acquisition-page .fin h3{color:#fff !important}\n.exoa-expertise-acquisition-page .sec-dk .h2 em,.exoa-expertise-acquisition-page .fin h2 em{color:#CDF980 !important}\n.exoa-expertise-acquisition-page .sec:not(.sec-dk) h2,.exoa-expertise-acquisition-page .sec:not(.sec-dk) .h2{color:#060039}\n.exoa-expertise-acquisition-page .steps{border-top:0 !important;gap:16px !important}\n.exoa-expertise-acquisition-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-expertise-acquisition-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-expertise-acquisition-page .reel{display:none !important}\n  .exoa-expertise-acquisition-page .hero{padding-bottom:40px !important}\n  .exoa-expertise-acquisition-page .hero-note{margin-bottom:0 !important}\n}\n\/*exoa-width-fix*\/\n.exoa-expertise-acquisition-page .hero h1{max-width:24ch !important}\n.exoa-expertise-acquisition-page .h2{max-width:38ch !important}\n.exoa-expertise-acquisition-page .fin h2{max-width:28ch !important}\n.exoa-expertise-acquisition-page .sec .wrap > p,.exoa-expertise-acquisition-page .h2 + p,.exoa-expertise-acquisition-page .tag ~ p,.exoa-expertise-acquisition-page .fin p,.exoa-expertise-acquisition-page .sub p{max-width:76ch !important}\n.exoa-expertise-acquisition-page .hero-sub{max-width:70ch !important}\n\/*exoa-pack-align*\/\n.exoa-expertise-acquisition-page .packs{align-items:stretch !important}\n.exoa-expertise-acquisition-page .pack{height:100%}\n.exoa-expertise-acquisition-page .pack > p,.exoa-expertise-acquisition-page .pack .pack-for{min-height:3.4em}\n.exoa-expertise-acquisition-page .pack ul,.exoa-expertise-acquisition-page .pack .pack-l{flex:1}\n.exoa-expertise-acquisition-page .pack .btn,.exoa-expertise-acquisition-page .pack a[class*=\"btn\"]{margin-top:auto}\n@media(max-width:900px){.exoa-expertise-acquisition-page .pack > p,.exoa-expertise-acquisition-page .pack .pack-for{min-height:0}}\n<\/style>\n<script type=\"application\/ld+json\">\n{\"@context\":\"https:\/\/schema.org\",\"@type\":\"Service\",\"name\":\"Expertise acquisition \u2014 Exoa\",\"description\":\"Syst\u00e8me d'acquisition complet : r\u00e9f\u00e9rencement, contenu, publicit\u00e9, ressources gratuites. Un flux de demandes r\u00e9gulier plut\u00f4t que du bouche-\u00e0-oreille. D\u00e8s 990\u20ac\/mois.\",\"provider\":{\"@type\":\"Organization\",\"name\":\"Exoa\",\"url\":\"https:\/\/www.agence-exoa.com\"},\"areaServed\":[{\"@type\":\"Country\",\"name\":\"France\"},{\"@type\":\"Country\",\"name\":\"Luxembourg\"}],\"serviceType\":\"Acquisition de clients\",\"offers\":[{\"@type\":\"Offer\",\"name\":\"Socle\",\"price\":\"990\",\"priceCurrency\":\"EUR\"},{\"@type\":\"Offer\",\"name\":\"Croissance\",\"price\":\"1790\",\"priceCurrency\":\"EUR\"},{\"@type\":\"Offer\",\"name\":\"Expansion\",\"price\":\"2900\",\"priceCurrency\":\"EUR\"}]}\n<\/script>\n<script type=\"application\/ld+json\">\n{\"@context\":\"https:\/\/schema.org\",\"@type\":\"FAQPage\",\"mainEntity\":[{\"@type\":\"Question\",\"name\":\"En combien de temps voit-on arriver des demandes ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"La publicit\u00e9 produit ses premi\u00e8res demandes en une \u00e0 deux semaines. Le r\u00e9f\u00e9rencement et le contenu demandent trois \u00e0 six mois.\"}},{\"@type\":\"Question\",\"name\":\"Faut-il tout activer en m\u00eame temps ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Non. Nous s\u00e9quen\u00e7ons les canaux selon votre cycle de vente et votre panier moyen.\"}},{\"@type\":\"Question\",\"name\":\"Le budget publicitaire est-il compris ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Non. Le budget m\u00e9dia est r\u00e9gl\u00e9 depuis votre propre compte publicitaire, avec un minimum conseill\u00e9 de 800\u20ac par mois.\"}},{\"@type\":\"Question\",\"name\":\"Comment mesurez-vous le r\u00e9sultat ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"En demandes qualifi\u00e9es et en co\u00fbt par demande, pas en positions ni en impressions.\"}},{\"@type\":\"Question\",\"name\":\"Que devient le travail si j'arr\u00eate ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Le contenu et le r\u00e9f\u00e9rencement vous restent et continuent de produire. Vous conservez pages, contenus, comptes et audiences.\"}},{\"@type\":\"Question\",\"name\":\"Travaillez-vous le r\u00e9f\u00e9rencement pour les assistants IA ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Oui. Structuration des donn\u00e9es, formulations citables et autorit\u00e9 th\u00e9matique pour \u00eatre la source cit\u00e9e par les assistants conversationnels.\"}},{\"@type\":\"Question\",\"name\":\"Y a-t-il un engagement de dur\u00e9e ?\",\"acceptedAnswer\":{\"@type\":\"Answer\",\"text\":\"Aucun engagement, mais moins de six mois ne permet de juger que la publicit\u00e9.\"}}]}\n<\/script>\n<div class=\"exoa-expertise-acquisition-page\">\n<header class=\"hero\"><div class=\"wrap hero-in\">\n<div class=\"pill\"><span class=\"dot\"><\/span><span>R\u00e9f\u00e9rencement \u00b7 Contenu \u00b7 Publicit\u00e9 \u00b7 Ressources<\/span><\/div>\n<h1>Arr\u00eatez de d\u00e9pendre du <em>bouche-\u00e0-oreille<\/em> et des mois <span class=\"v\">creux<\/span>.<\/h1>\n<p class=\"hero-sub\">Un syst\u00e8me d&rsquo;acquisition, ce n&rsquo;est pas une campagne : c&rsquo;est un ensemble de canaux qui se renforcent et produisent des demandes qualifi\u00e9es chaque semaine, m\u00eame quand vous ne faites rien. Nous le construisons, puis nous l&rsquo;alimentons.<\/p>\n<div class=\"hero-cta\">\n<a href=\"#packs\" class=\"btn btn-lime\">Voir les formules \u2014 d\u00e8s 990\u20ac\/mois \u2192<\/a>\n<a href=\"#devis\" class=\"btn btn-ghost\">\u00c9valuer mon acquisition actuelle<\/a>\n<\/div>\n<div class=\"hero-note\"><span><b>Premiers r\u00e9sultats<\/b> en 6 \u00e0 10 semaines<\/span><span><b>Canaux cumul\u00e9s<\/b> pas remplac\u00e9s<\/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>FLUX D&rsquo;ACQUISITION \u2014 90 DERNIERS JOURS<\/em><\/div>\n<img decoding=\"async\" src=\"https:\/\/www.agence-exoa.com\/wp-content\/uploads\/2026\/08\/acquisition-demande-immediate.webp\" alt=\"Tableau de bord acquisition : co\u00fbt par conversion, volume et r\u00e9partition par canal\" 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>R\u00e9f\u00e9rencement naturel<\/span><span>Contenu<\/span><span>Google Ads<\/span><span>R\u00e9seaux sociaux<\/span><span>Ressources gratuites<\/span><span>R\u00e9f\u00e9rencement local<\/span><span>Assistants IA<\/span><span>Partenariats<\/span><span>Retargeting<\/span>\n<span>R\u00e9f\u00e9rencement naturel<\/span><span>Contenu<\/span><span>Google Ads<\/span><span>R\u00e9seaux sociaux<\/span><span>Ressources gratuites<\/span><span>R\u00e9f\u00e9rencement local<\/span><span>Assistants IA<\/span><span>Partenariats<\/span><span>Retargeting<\/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\">Un canal unique, <em>c&rsquo;est une d\u00e9pendance<\/em>.<\/h2>\n<p class=\"lead\">Beaucoup d&rsquo;entreprises tiennent gr\u00e2ce \u00e0 un seul canal : la recommandation, un annuaire, une campagne qui marche. Le jour o\u00f9 il se tarit \u2014 algorithme, concurrent, saison \u2014 il n&rsquo;y a pas de plan B. L&rsquo;acquisition consiste pr\u00e9cis\u00e9ment \u00e0 ne plus d\u00e9pendre d&rsquo;une seule source.<\/p>\n<div class=\"stats\">\n<div class=\"stat\"><div class=\"stat-n\">3 canaux<\/div><div class=\"stat-l\">Le minimum pour qu&rsquo;un flux de demandes reste stable quand l&rsquo;un des canaux se d\u00e9grade. En dessous, chaque variation se paie cash.<\/div><\/div>\n<div class=\"stat\"><div class=\"stat-n\">6 mois<\/div><div class=\"stat-l\">Le temps qu&rsquo;il faut au r\u00e9f\u00e9rencement naturel pour produire des r\u00e9sultats durables \u2014 raison pour laquelle il doit d\u00e9marrer avant qu&rsquo;on en ait besoin.<\/div><\/div>\n<div class=\"stat\"><div class=\"stat-n\">\u00d7 2<\/div><div class=\"stat-l\">Le trafic de recherche qu&rsquo;un contenu bien positionn\u00e9 continue de g\u00e9n\u00e9rer un an apr\u00e8s sa publication, sans d\u00e9pense suppl\u00e9mentaire.<\/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>Le syst\u00e8me<\/span><\/div>\n<h2 class=\"h2\">Trois temporalit\u00e9s, <em>un m\u00eame syst\u00e8me<\/em>.<\/h2>\n<\/div>\n<p class=\"lead\" style=\"margin:0;max-width:38ch\">Le payant amorce, le contenu construit, les ressources capturent. Sans les trois, il n&rsquo;y a pas de flux r\u00e9gulier.<\/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\/acquisition-publicite.webp\" alt=\"Campagnes sur les requ\u00eates \u00e0 forte intention : annonces Google Search et Maps, r\u00e9sultats mesur\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\">BRIQUE 01<\/span>\n<h3>Demande imm\u00e9diate<\/h3>\n<p>La publicit\u00e9 et le r\u00e9f\u00e9rencement local pour capter tout de suite les personnes qui cherchent d\u00e9j\u00e0 votre solution.<\/p>\n<ul><li>Campagnes sur les requ\u00eates \u00e0 forte intention<\/li><li>Fiche d&rsquo;\u00e9tablissement et r\u00e9f\u00e9rencement local<\/li><li>Retargeting des visiteurs non convertis<\/li><li>Cr\u00e9ations publicitaires incluses<\/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\/acquisition-contenu.webp\" alt=\"Production de contenu SEO : articles, arborescence, avis et suivi de croissance\" 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\">BRIQUE 02<\/span>\n<h3>Demande construite<\/h3>\n<p>Le contenu qui vous rend trouvable durablement \u2014 sur les moteurs de recherche comme dans les r\u00e9ponses des assistants conversationnels.<\/p>\n<ul><li>Arborescence de contenus par th\u00e9matique<\/li><li>Articles et pages optimis\u00e9s<\/li><li>Structuration des donn\u00e9es pour les moteurs<\/li><li>Optimisation pour les moteurs conversationnels<\/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\/acquisition-ressources.webp\" alt=\"Ressources t\u00e9l\u00e9chargeables, pages de capture, s\u00e9quences email et qualification des contacts\" 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\">BRIQUE 03<\/span>\n<h3>Capture &#038; qualification<\/h3>\n<p>Un visiteur qui repart sans laisser de trace est un budget perdu. Les ressources gratuites transforment l&rsquo;audience en contacts.<\/p>\n<ul><li>Ressources t\u00e9l\u00e9chargeables ou outils en ligne<\/li><li>Formulaires courts et pages de capture<\/li><li>S\u00e9quences d&rsquo;e-mails de mise en relation<\/li><li>Qualification et transmission des contacts<\/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>de couverture<\/em>.<\/h2>\n<\/div>\n<p class=\"lead\" style=\"margin:0;max-width:38ch\">Chaque formule ajoute des canaux au syst\u00e8me existant. Rien n&rsquo;est jamais remplac\u00e9, tout se cumule.<\/p>\n<\/div>\n<div class=\"packs\">\n<div class=\"pack\">\n\n<span class=\"pack-n\">Formule 01<\/span>\n<h3>Socle<\/h3>\n<p class=\"pack-for\">Pour poser les fondations et sortir de la d\u00e9pendance \u00e0 un canal unique.<\/p>\n<div class=\"pack-p\"><b>990\u20ac<\/b><i>HT \/ mois<\/i><\/div>\n<ul class=\"pack-l\"><li><b>R\u00e9f\u00e9rencement<\/b> : technique, pages, local<\/li><li><b>2 articles<\/b> optimis\u00e9s par mois<\/li><li><b>1 levier publicitaire<\/b> pilot\u00e9<\/li><li>Suivi des conversions install\u00e9<\/li><li>Rapport mensuel comment\u00e9<\/li><\/ul>\n<a href=\"#devis\" class=\"btn btn-out\">Choisir Socle<\/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>Croissance<\/h3>\n<p class=\"pack-for\">Pour faire tourner plusieurs canaux ensemble et cr\u00e9er un vrai flux.<\/p>\n<div class=\"pack-p\"><b>1 790\u20ac<\/b><i>HT \/ mois<\/i><\/div>\n<ul class=\"pack-l\"><li><b>Tout le socle<\/b>, volume doubl\u00e9<\/li><li><b>4 articles<\/b> et une arborescence th\u00e9matique<\/li><li><b>2 \u00e0 3 leviers<\/b> publicitaires<\/li><li><b>1 ressource gratuite<\/b> par trimestre<\/li><li>S\u00e9quences d&rsquo;e-mails de qualification<\/li><li>Point mensuel de 45 minutes<\/li><\/ul>\n<a href=\"#devis\" class=\"btn btn-lime\">Choisir Croissance<\/a>\n<\/div>\n<div class=\"pack\">\n\n<span class=\"pack-n\">Formule 03<\/span>\n<h3>Expansion<\/h3>\n<p class=\"pack-for\">Pour occuper le terrain sur toute votre th\u00e9matique et toutes vos zones.<\/p>\n<div class=\"pack-p\"><b>2 900\u20ac<\/b><i>HT \/ mois<\/i><\/div>\n<ul class=\"pack-l\"><li><b>Toute la croissance<\/b>, sans plafond raisonnable<\/li><li class=\"plus\"><b>8 articles<\/b> par mois, contenus longs inclus<\/li><li class=\"plus\"><b>Tous les leviers<\/b>, y compris programmatique<\/li><li class=\"plus\"><b>Pages par ville ou par m\u00e9tier<\/b><\/li><li class=\"plus\"><b>Netlinking<\/b> et relations \u00e9ditoriales<\/li><li class=\"plus\">Bilan trimestriel et plan \u00e0 6 mois<\/li><\/ul>\n<a href=\"#devis\" class=\"btn btn-out\">Choisir Expansion<\/a>\n<\/div>\n<\/div>\n\n<div class=\"sub\">\n<div class=\"sub-l\">\n<span class=\"mono\">Pour savoir o\u00f9 vous en \u00eates<\/span>\n<h3>Diagnostic acquisition<\/h3>\n<p>Deux semaines pour mesurer d&rsquo;o\u00f9 viennent r\u00e9ellement vos demandes, ce que vous captez et ce que vous laissez \u00e0 vos concurrents. Vous recevez la liste des requ\u00eates que vous devriez occuper, les fuites de votre tunnel et un plan prioris\u00e9 sur six mois. D\u00e9duit de la premi\u00e8re mensualit\u00e9 si vous poursuivez.<\/p>\n<\/div>\n<div class=\"sub-r\">\n<b>1 750\u20ac<\/b>\n<i>HT \u00b7 une seule fois<\/i>\n<\/div>\n<\/div>\n<\/div>\n<div class=\"exoa-alacarte-note\" style=\"max-width:900px;margin:26px auto 0;background:#fff;border:1.5px solid #CDF980;border-radius:12px;padding:16px 22px;text-align:center;box-shadow:0 6px 18px rgba(6,0,57,.06)\">\n<p style=\"color:#3A3E5C;margin:0;font-size:14px;line-height:1.6\">&#x1f4a1; Ces formules <strong style=\"color:#060039\">r\u00e9partissent l&rsquo;effort entre plusieurs canaux<\/strong> (r\u00e9f\u00e9rencement + contenu + publicit\u00e9). Pour concentrer tout le budget sur un seul levier \u2014 SEO d\u00e8s 900 \u20ac\/mois, gestion Ads d\u00e8s 500 \u20ac\/mois \u2014 <a href=\"\/offres\/#tarifs\" style=\"color:#5159A4;font-weight:700\">voyez nos services \u00e0 la carte \u2192<\/a><\/p><\/div><\/section>\n\n<section class=\"sec sec-dk\">\n<div class=\"wrap\">\n<div class=\"tag\"><i><\/i><span>Les canaux<\/span><\/div>\n<h2 class=\"h2\">Tout ce qui peut <em>amener une demande<\/em>.<\/h2>\n<p class=\"lead\">Nous n&rsquo;activons pas tout d&rsquo;un coup. L&rsquo;ordre d\u00e9pend de votre cycle de vente, de votre panier moyen et de votre concurrence.<\/p>\n<div class=\"grid2\" style=\"margin-top:44px\">\n<div class=\"lev\"><span class=\"lev-t\">DURABLE<\/span><b>R\u00e9f\u00e9rencement naturel<\/b><small>Le canal le plus rentable \u00e0 long terme, et le plus lent \u00e0 produire. Il doit d\u00e9marrer bien avant que vous en ayez besoin.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">IMM\u00c9DIAT<\/span><b>Publicit\u00e9 sur les moteurs<\/b><small>Capter les recherches \u00e0 forte intention d\u00e8s la premi\u00e8re semaine. Le compl\u00e9ment indispensable au r\u00e9f\u00e9rencement pendant sa mont\u00e9e en charge.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">PROXIMIT\u00c9<\/span><b>R\u00e9f\u00e9rencement local<\/b><small>Fiche d&rsquo;\u00e9tablissement, avis, citations, pages par zone. D\u00e9cisif d\u00e8s que vos clients vous cherchent pr\u00e8s de chez eux.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">AUTORIT\u00c9<\/span><b>Contenu &#038; assistants IA<\/b><small>\u00catre la source cit\u00e9e quand quelqu&rsquo;un pose une question \u00e0 un assistant conversationnel. Le r\u00e9f\u00e9rencement des prochaines ann\u00e9es se joue l\u00e0.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">CAPTURE<\/span><b>Ressources &#038; outils gratuits<\/b><small>Un mod\u00e8le, un calculateur, un guide : de quoi obtenir un contact avant m\u00eame que la personne soit pr\u00eate \u00e0 acheter.<\/small><\/div>\n<div class=\"lev\"><span class=\"lev-t\">REPRISE<\/span><b>Retargeting &#038; e-mailing<\/b><small>La grande majorit\u00e9 des visiteurs ne convertissent pas au premier passage. Les recontacter co\u00fbte une fraction du co\u00fbt d&rsquo;une nouvelle visite.<\/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\">Six semaines pour <em>amorcer le flux<\/em>.<\/h2>\n<div class=\"steps\">\n<div class=\"step\">\n<span class=\"step-n\">\u00c9TAPE 01<\/span>\n<h3>Diagnostic<\/h3>\n<p>D&rsquo;o\u00f9 viennent vos demandes aujourd&rsquo;hui, ce que cherchent vos clients, ce que captent vos concurrents. Chiffres avant opinions.<\/p>\n<em>SEMAINES 1 \u00c0 2<\/em>\n<\/div>\n<div class=\"step\">\n<span class=\"step-n\">\u00c9TAPE 02<\/span>\n<h3>Fondations<\/h3>\n<p>Corrections techniques, suivi des conversions, arborescence de contenus, premi\u00e8res campagnes. Le socle qui rend tout le reste mesurable.<\/p>\n<em>SEMAINES 3 \u00c0 4<\/em>\n<\/div>\n<div class=\"step\">\n<span class=\"step-n\">\u00c9TAPE 03<\/span>\n<h3>Amor\u00e7age<\/h3>\n<p>La publicit\u00e9 apporte les premi\u00e8res demandes pendant que le contenu et le r\u00e9f\u00e9rencement montent en puissance en arri\u00e8re-plan.<\/p>\n<em>SEMAINES 5 \u00c0 10<\/em>\n<\/div>\n<div class=\"step\">\n<span class=\"step-n\">\u00c9TAPE 04<\/span>\n<h3>Alimentation<\/h3>\n<p>Chaque mois : nouveaux contenus, nouvelles cr\u00e9ations, arbitrage des budgets. Le syst\u00e8me grossit au lieu de stagner.<\/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;un <em>canal en plus<\/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 acquisition<\/b><small>Plan prioris\u00e9 sur 6 mois<\/small><\/div><i>1 750\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Ressource gratuite<\/b><small>Guide, mod\u00e8le ou outil en ligne<\/small><\/div><i>d\u00e8s 890\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Pages par ville ou m\u00e9tier<\/b><small>Lot de 5 pages optimis\u00e9es<\/small><\/div><i>990\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Netlinking<\/b><small>Acquisition de liens qualitatifs<\/small><\/div><i>d\u00e8s 450\u20ac\/mois<\/i><\/div>\n<div class=\"add\"><div><b>Landing page d\u00e9di\u00e9e<\/b><small>Page de campagne ou de capture<\/small><\/div><i>750\u20ac<\/i><\/div>\n<div class=\"add\"><div><b>Vid\u00e9o motion<\/b><small>15 secondes, 4 formats<\/small><\/div><i>390\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>Contenu additionnel<\/b><small>Article optimis\u00e9, \u00e0 l&rsquo;unit\u00e9<\/small><\/div><i>d\u00e8s 190\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 combien de temps voit-on arriver des demandes ?<\/summary><p>La publicit\u00e9 produit ses premi\u00e8res demandes en <b>une \u00e0 deux semaines<\/b>. Le r\u00e9f\u00e9rencement et le contenu demandent <b>trois \u00e0 six mois<\/b> pour peser r\u00e9ellement. C&rsquo;est pourquoi nous lan\u00e7ons les deux en parall\u00e8le : le payant finance l&rsquo;attente.<\/p><\/details>\n<details><summary>Faut-il tout activer en m\u00eame temps ?<\/summary><p>Non, et ce serait une erreur. Nous <b>s\u00e9quen\u00e7ons<\/b> selon votre cycle de vente et votre panier moyen : un canal mal pr\u00e9par\u00e9 co\u00fbte plus qu&rsquo;il ne rapporte.<\/p><\/details>\n<details><summary>Le budget publicitaire est-il compris ?<\/summary><p>Non. Les tarifs couvrent la strat\u00e9gie, la production et le pilotage. Le <b>budget m\u00e9dia<\/b> est r\u00e9gl\u00e9 depuis votre propre compte publicitaire \u2014 comptez au minimum 800\u20ac par mois pour que les plateformes optimisent correctement.<\/p><\/details>\n<details><summary>Comment mesurez-vous le r\u00e9sultat ?<\/summary><p>En <b>demandes qualifi\u00e9es et en co\u00fbt par demande<\/b>, pas en positions ni en impressions. Le rapport mensuel part toujours du nombre de contacts obtenus et de ce qu&rsquo;ils ont co\u00fbt\u00e9.<\/p><\/details>\n<details><summary>Que devient le travail si j&rsquo;arr\u00eate ?<\/summary><p>Le contenu et le r\u00e9f\u00e9rencement <b>vous restent et continuent de produire<\/b> : c&rsquo;est la diff\u00e9rence avec la publicit\u00e9, qui s&rsquo;arr\u00eate net. Vous conservez les pages, les contenus, les comptes et les audiences.<\/p><\/details>\n<details><summary>Travaillez-vous le r\u00e9f\u00e9rencement pour les assistants IA ?<\/summary><p>Oui, syst\u00e9matiquement. Structuration des donn\u00e9es, formulations directement citables, autorit\u00e9 th\u00e9matique : <b>\u00eatre la source d&rsquo;une r\u00e9ponse d&rsquo;assistant<\/b> devient aussi important qu&rsquo;un bon classement sur un moteur de recherche.<\/p><\/details>\n<details><summary>Y a-t-il un engagement de dur\u00e9e ?<\/summary><p>Aucun, mais soyons francs : sur de l&rsquo;acquisition, <b>moins de six mois ne permet pas de juger<\/b> autre chose que la publicit\u00e9. Nous vous le dirons avant de commencer.<\/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\">x2,5<\/div><h3>de leads qualifi\u00e9s<\/h3><p>Multiplication moyenne du volume de leads sur 12 mois de pilotage.<\/p><\/div><div class=\"resx-card\"><div class=\"resx-num\">-30%<\/div><h3>de co\u00fbt d&rsquo;acquisition<\/h3><p>Baisse moyenne du CAC apr\u00e8s optimisation du mix canaux.<\/p><\/div><div class=\"resx-card\"><div class=\"resx-num\">360\u00b0<\/div><h3>tous canaux couverts<\/h3><p>SEO, Ads, social, emailing : orchestr\u00e9s ensemble, pas en silos.<\/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 acquisition s&rsquo;appuie sur des livrables tangibles.<\/p><\/div>\n<div class=\"livr-grid\"><div class=\"livr-card\"><h3><span>\u2713<\/span>Diagnostic acquisition<\/h3><p>Forces, fuites et opportunit\u00e9s de votre tunnel actuel.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Mix canaux optimis\u00e9<\/h3><p>Les budgets allou\u00e9s aux canaux qui performent, r\u00e9\u00e9valu\u00e9s chaque mois.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Campagnes pilot\u00e9es<\/h3><p>Google, Meta, LinkedIn : cr\u00e9ation, optimisation et tests en continu.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Landing pages<\/h3><p>Des pages d\u00e9di\u00e9es par offre et par audience.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Tracking complet<\/h3><p>Chaque lead trac\u00e9 de la source au CRM.<\/p><\/div><div class=\"livr-card\"><h3><span>\u2713<\/span>Reporting ROI<\/h3><p>Co\u00fbt par lead et par client, par canal.<\/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>D\u00e9crivez votre march\u00e9. <em>On construit le flux.<\/em><\/h2>\n<p>Dites-nous ce que vous vendez et d&rsquo;o\u00f9 viennent vos clients aujourd&rsquo;hui : nous revenons sous vingt-quatre heures avec les canaux \u00e0 activer en priorit\u00e9.<\/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>Mesure en demandes, pas en impressions<\/span><\/div>\n<div><b>\u2713<\/b><span>Le contenu produit vous reste acquis<\/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=Acquisition\" 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 Expertise acquisition \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>R\u00e9f\u00e9rencement \u00b7 Contenu \u00b7 Publicit\u00e9 \u00b7 Ressources Arr\u00eatez de d\u00e9pendre du bouche-\u00e0-oreille et des mois creux. Un syst\u00e8me d&rsquo;acquisition, ce n&rsquo;est pas une campagne : c&rsquo;est un ensemble de canaux qui se renforcent et produisent des demandes qualifi\u00e9es chaque semaine, m\u00eame quand vous ne faites rien. Nous le construisons, puis nous l&rsquo;alimentons. Voir les formules \u2014 d\u00e8s 990\u20ac\/mois \u2192 \u00c9valuer mon acquisition actuelle Premiers r\u00e9sultats en 6 \u00e0 10 semainesCanaux cumul\u00e9s pas remplac\u00e9sSans engagement de dur\u00e9e FLUX D&rsquo;ACQUISITION \u2014 90 DERNIERS JOURS R\u00e9f\u00e9rencement naturelContenuGoogle AdsR\u00e9seaux sociauxRessources gratuitesR\u00e9f\u00e9rencement localAssistants IAPartenariatsRetargeting R\u00e9f\u00e9rencement naturelContenuGoogle AdsR\u00e9seaux sociauxRessources gratuitesR\u00e9f\u00e9rencement localAssistants IAPartenariatsRetargeting Le constat Un canal unique, c&rsquo;est une d\u00e9pendance. Beaucoup d&rsquo;entreprises tiennent gr\u00e2ce \u00e0 un seul canal : la recommandation, un annuaire, une campagne qui marche. Le jour o\u00f9 il se tarit \u2014 algorithme, concurrent, saison \u2014 il n&rsquo;y a pas de plan B. L&rsquo;acquisition consiste pr\u00e9cis\u00e9ment \u00e0 ne plus d\u00e9pendre d&rsquo;une seule source. 3 canaux Le minimum pour qu&rsquo;un flux de demandes reste stable quand l&rsquo;un des canaux se d\u00e9grade. En dessous, chaque variation se paie cash. 6 mois Le temps qu&rsquo;il faut au r\u00e9f\u00e9rencement naturel pour produire des r\u00e9sultats durables \u2014 raison pour laquelle il doit d\u00e9marrer avant qu&rsquo;on en ait besoin. \u00d7 2 Le trafic de recherche qu&rsquo;un contenu bien positionn\u00e9 continue de g\u00e9n\u00e9rer un an apr\u00e8s sa publication, sans d\u00e9pense suppl\u00e9mentaire. Le syst\u00e8me Trois temporalit\u00e9s, un m\u00eame syst\u00e8me. Le payant amorce, le contenu construit, les ressources capturent. Sans les trois, il n&rsquo;y a pas de flux r\u00e9gulier. BRIQUE 01 Demande imm\u00e9diate La publicit\u00e9 et le r\u00e9f\u00e9rencement local pour capter tout de suite les personnes qui cherchent d\u00e9j\u00e0 votre solution. Campagnes sur les requ\u00eates \u00e0 forte intention Fiche d&rsquo;\u00e9tablissement et r\u00e9f\u00e9rencement local Retargeting des visiteurs non convertis Cr\u00e9ations publicitaires incluses BRIQUE 02 Demande construite Le contenu qui vous rend trouvable durablement \u2014 sur les moteurs de recherche comme dans les r\u00e9ponses des assistants conversationnels. Arborescence de contenus par th\u00e9matique Articles et pages optimis\u00e9s Structuration des donn\u00e9es pour les moteurs Optimisation pour les moteurs conversationnels BRIQUE 03 Capture &#038; qualification Un visiteur qui repart sans laisser de trace est un budget perdu. Les ressources gratuites transforment l&rsquo;audience en contacts. Ressources t\u00e9l\u00e9chargeables ou outils en ligne Formulaires courts et pages de capture S\u00e9quences d&rsquo;e-mails de mise en relation Qualification et transmission des contacts Les formules Trois niveaux de couverture. Chaque formule ajoute des canaux au syst\u00e8me existant. Rien n&rsquo;est jamais remplac\u00e9, tout se cumule. Formule 01 Socle Pour poser les fondations et sortir de la d\u00e9pendance \u00e0 un canal unique. 990\u20acHT \/ mois R\u00e9f\u00e9rencement : technique, pages, local 2 articles optimis\u00e9s par mois 1 levier publicitaire pilot\u00e9 Suivi des conversions install\u00e9 Rapport mensuel comment\u00e9 Choisir Socle Le plus choisi Formule 02 Croissance Pour faire tourner plusieurs canaux ensemble et cr\u00e9er un vrai flux. 1 790\u20acHT \/ mois Tout le socle, volume doubl\u00e9 4 articles et une arborescence th\u00e9matique 2 \u00e0 3 leviers publicitaires 1 ressource gratuite par trimestre S\u00e9quences d&rsquo;e-mails de qualification Point mensuel de 45 minutes Choisir Croissance Formule 03 Expansion Pour occuper le terrain sur toute votre th\u00e9matique et toutes vos zones. 2 900\u20acHT \/ mois Toute la croissance, sans plafond raisonnable 8 articles par mois, contenus longs inclus Tous les leviers, y compris programmatique Pages par ville ou par m\u00e9tier Netlinking et relations \u00e9ditoriales Bilan trimestriel et plan \u00e0 6 mois Choisir Expansion Pour savoir o\u00f9 vous en \u00eates Diagnostic acquisition Deux semaines pour mesurer d&rsquo;o\u00f9 viennent r\u00e9ellement vos demandes, ce que vous captez et ce que vous laissez \u00e0 vos concurrents. Vous recevez la liste des requ\u00eates que vous devriez occuper, les fuites de votre tunnel et un plan prioris\u00e9 sur six mois. D\u00e9duit de la premi\u00e8re mensualit\u00e9 si vous poursuivez. 1 750\u20ac HT \u00b7 une seule fois &#x1f4a1; Ces formules r\u00e9partissent l&rsquo;effort entre plusieurs canaux (r\u00e9f\u00e9rencement + contenu + publicit\u00e9). Pour concentrer tout le budget sur un seul levier \u2014 SEO d\u00e8s 900 \u20ac\/mois, gestion Ads d\u00e8s 500 \u20ac\/mois \u2014 voyez nos services \u00e0 la carte \u2192 Les canaux Tout ce qui peut amener une demande. Nous n&rsquo;activons pas tout d&rsquo;un coup. L&rsquo;ordre d\u00e9pend de votre cycle de vente, de votre panier moyen et de votre concurrence. DURABLER\u00e9f\u00e9rencement naturelLe canal le plus rentable \u00e0 long terme, et le plus lent \u00e0 produire. Il doit d\u00e9marrer bien avant que vous en ayez besoin. IMM\u00c9DIATPublicit\u00e9 sur les moteursCapter les recherches \u00e0 forte intention d\u00e8s la premi\u00e8re semaine. Le compl\u00e9ment indispensable au r\u00e9f\u00e9rencement pendant sa mont\u00e9e en charge. PROXIMIT\u00c9R\u00e9f\u00e9rencement localFiche d&rsquo;\u00e9tablissement, avis, citations, pages par zone. D\u00e9cisif d\u00e8s que vos clients vous cherchent pr\u00e8s de chez eux. AUTORIT\u00c9Contenu &#038; assistants IA\u00catre la source cit\u00e9e quand quelqu&rsquo;un pose une question \u00e0 un assistant conversationnel. Le r\u00e9f\u00e9rencement des prochaines ann\u00e9es se joue l\u00e0. CAPTURERessources &#038; outils gratuitsUn mod\u00e8le, un calculateur, un guide : de quoi obtenir un contact avant m\u00eame que la personne soit pr\u00eate \u00e0 acheter. REPRISERetargeting &#038; e-mailingLa grande majorit\u00e9 des visiteurs ne convertissent pas au premier passage. Les recontacter co\u00fbte une fraction du co\u00fbt d&rsquo;une nouvelle visite. Comment \u00e7a se passe Six semaines pour amorcer le flux. \u00c9TAPE 01 Diagnostic D&rsquo;o\u00f9 viennent vos demandes aujourd&rsquo;hui, ce que cherchent vos clients, ce que captent vos concurrents. Chiffres avant opinions. SEMAINES 1 \u00c0 2 \u00c9TAPE 02 Fondations Corrections techniques, suivi des conversions, arborescence de contenus, premi\u00e8res campagnes. Le socle qui rend tout le reste mesurable. SEMAINES 3 \u00c0 4 \u00c9TAPE 03 Amor\u00e7age La publicit\u00e9 apporte les premi\u00e8res demandes pendant que le contenu et le r\u00e9f\u00e9rencement montent en puissance en arri\u00e8re-plan. SEMAINES 5 \u00c0 10 \u00c9TAPE 04 Alimentation Chaque mois : nouveaux contenus, nouvelles cr\u00e9ations, arbitrage des budgets. Le syst\u00e8me grossit au lieu de stagner. EN CONTINU \u00c0 la carte Besoin d&rsquo;un canal en plus ? Chaque option s&rsquo;ajoute \u00e0 une formule ou se commande seule. Tarifs HT. Diagnostic acquisitionPlan prioris\u00e9 sur 6 mois 1 750\u20ac Ressource gratuiteGuide, mod\u00e8le ou outil en ligne d\u00e8s 890\u20ac Pages par ville ou m\u00e9tierLot de 5 pages optimis\u00e9es 990\u20ac NetlinkingAcquisition de liens qualitatifs d\u00e8s 450\u20ac\/mois Landing page d\u00e9di\u00e9ePage de campagne ou de capture 750\u20ac Vid\u00e9o motion15 secondes,<\/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":"Expertise Acquisition \u2014 Flux de Demandes Qualifi\u00e9es | Exoa","rank_math_description":"Expertise acquisition Exoa : mix canaux optimis\u00e9, campagnes pilot\u00e9es et tracking complet. Un flux de leads qualifi\u00e9s et mesurable. Diagnostic gratuit.","rank_math_focus_keyword":"expertise acquisition","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-14785","page","type-page","status-publish","hentry"],"_yoast_wpseo_focuskw":null,"_yoast_wpseo_metadesc":null,"_links":{"self":[{"href":"https:\/\/www.agence-exoa.com\/en\/wp-json\/wp\/v2\/pages\/14785","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.agence-exoa.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.agence-exoa.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.agence-exoa.com\/en\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/www.agence-exoa.com\/en\/wp-json\/wp\/v2\/comments?post=14785"}],"version-history":[{"count":10,"href":"https:\/\/www.agence-exoa.com\/en\/wp-json\/wp\/v2\/pages\/14785\/revisions"}],"predecessor-version":[{"id":16641,"href":"https:\/\/www.agence-exoa.com\/en\/wp-json\/wp\/v2\/pages\/14785\/revisions\/16641"}],"wp:attachment":[{"href":"https:\/\/www.agence-exoa.com\/en\/wp-json\/wp\/v2\/media?parent=14785"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}