import re, base64 import glob, os def _find_charte(): env=os.environ.get("ADALAN_CHARTE") if env: return env.rstrip("/")+"/" pats=[os.path.expanduser("~/.claude/skills/**/charte-graphique-adalan/assets/modeles/presentation.html"), "/root/.claude/skills/**/charte-graphique-adalan/assets/modeles/presentation.html", "./charte/modeles/presentation.html"] for p in pats: hits=glob.glob(p,recursive=True) if hits: return os.path.dirname(os.path.dirname(hits[0]))+"/" raise SystemExit("charte-graphique-adalan introuvable : définir ADALAN_CHARTE=") A=_find_charte() tpl=open(A+"modeles/presentation.html").read() css=open(A+"adalan.css").read() def b64(p): return "data:image/svg+xml;base64,"+base64.b64encode(open(A+p,"rb").read()).decode() TE,TC,LOGO=b64("motifs/topo-sur-encre.svg"),b64("motifs/topo-sur-clair.svg"),b64("logos/logo.svg") css=css.replace("url('motifs/topo-sur-encre.svg')",f"url('{TE}')").replace("url('motifs/topo-sur-clair.svg')",f"url('{TC}')") style=re.search(r"",tpl,re.S).group(1) style=style.replace("url('../motifs/topo-sur-clair.svg')",f"url('{TC}')").replace("url('../motifs/topo-sur-encre.svg')",f"url('{TE}')") symbols=re.search(r'()\n\n
',tpl,re.S).group(1) script=re.search(r"()",tpl,re.S).group(1) script=script.replace(" const fit = ()", """ // bouton de retour à l'accueil sur chaque slide slides.forEach(s => s.insertAdjacentHTML('beforeend', 'Accueil')); addEventListener('keydown', e => { if (e.key === 'h' || e.key === 'H' || e.key === 'Escape') { if (!document.fullscreenElement) location.href = 'index.html'; } }); const fit = ()""",1) ICONS={ "check":'', "clock":'', "mail":'', "shield":'', "users":'', "mic":'', "database":'', "target":'', "link":'', "alert":'', "file":'', "chart":'', "lock":'', "layers":'', "coins":'', "phone":'', "gift":'', "cpu":'', "home":'', "arrow":'', } iconsyms="".join(f'{v}' for k,v in ICONS.items()) symbols=symbols[:symbols.rfind("")]+iconsyms+"" EXTRA=""" .s-title--s { font-size: 54px; } .s-cover-body { width: 1560px; bottom: 150px; } .s-cover { font-size: 132px; line-height: .98; } .s-cover-sub { max-width: 1200px; } .s-grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; } .s-cards--2 { grid-template-columns: repeat(2, 1fr); } .s-cards--4 { grid-template-columns: repeat(4, 1fr); gap: 28px; } .s-cards--4 .card { padding: 36px 32px 40px; } .s-cards--4 h3 { font-size: 28px; } .s-cards .card--ink p { color: var(--ink-200); } .s-cards h3 small { display:block; margin-bottom: 10px; font: 500 16px/1 var(--mono); letter-spacing:.16em; text-transform: uppercase; color: var(--kicker, var(--peach-700)); } .s-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 18px; } .s-list li { position: relative; padding-left: 36px; font-size: 24px; line-height: 1.42; color: var(--text-2); } .s-list li::before { content: ''; position: absolute; left: 4px; top: 13px; width: 13px; height: 13px; background: var(--peach); border-radius: 6px 1px 6px 1px; } .s-list li b { color: var(--text); font-weight: 650; } .s-cards .s-list li { font-size: 21px; padding-left: 30px; } .s-cards .s-list li::before { top: 11px; width: 11px; height: 11px; } .s-cards .s-list { gap: 10px; margin-top: 18px; } .s-table { width: 100%; border-collapse: separate; border-spacing: 0; background: var(--paper); border-radius: var(--shape); box-shadow: inset 0 0 0 1px var(--line), var(--shadow-1); overflow: hidden; } .s-table th { text-align: left; padding: 22px 30px; font: 500 16px/1.2 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--peach-700); background: var(--ink-50); } .s-table td { padding: 18px 30px; font-size: 22px; line-height: 1.4; color: var(--text-2); vertical-align: top; box-shadow: inset 0 1px 0 var(--line); } .s-table td:first-child { color: var(--ink); font-weight: 640; width: 26%; } .s-table td.price { font-family: var(--mono); font-size: 20px; color: var(--peach-700); white-space: nowrap; width: 30%; } .s-steps { display: grid; align-items: stretch; grid-template-columns: repeat(var(--n, 4), 1fr); gap: 24px; } .s-step { position: relative; padding: 36px 32px 40px; border-radius: var(--shape); background: var(--paper); box-shadow: inset 0 0 0 1px var(--line), var(--shadow-1); } .s-step.is-hl { background: var(--ink); --text: #fff; --text-2: var(--ink-200); color:#fff; } .s-step__when { font: 500 18px/1 var(--mono); letter-spacing: .16em; text-transform: uppercase; color: var(--peach-700); } .s-step.is-hl .s-step__when { color: var(--peach); } .s-step h3 { margin-top: 18px; font-size: 30px; line-height: 1.15; font-weight: 700; font-stretch: 106%; color: var(--text); } .s-step p { margin-top: 12px; font-size: 21px; line-height: 1.45; color: var(--text-2); } .s-step::after { content: ''; position: absolute; right: -19px; top: 44px; width: 14px; height: 14px; background: var(--peach); border-radius: 6px 1px 6px 1px; z-index: 2; } .s-step:last-child::after { display: none; } .s-flow { display: flex; align-items: stretch; gap: 0; } .s-flow__box { flex: 1; padding: 30px 28px; border-radius: var(--shape-md); background: var(--paper); box-shadow: inset 0 0 0 1px var(--line), var(--shadow-1); } .s-flow__box.is-ink { background: var(--ink); color: #fff; } .s-flow__box h4 { font-size: 26px; font-weight: 700; font-stretch: 106%; line-height: 1.15; } .s-flow__box p { margin-top: 10px; font-size: 19px; line-height: 1.42; color: var(--text-2); } .s-flow__box.is-ink p { color: var(--ink-200); } .s-flow__box .tag { margin-bottom: 14px; font-size: 14px; } .s-flow__arrow { flex: none; width: 58px; display: grid; place-items: center; color: var(--peach-600); } .s-flow__arrow svg { width: 34px; height: 34px; } .s-kpi { display: flex; align-items: flex-end; gap: 70px; } .s-kpi__val { font-size: 220px; line-height: .85; font-weight: 800; font-stretch: 112%; letter-spacing: -.045em; color: var(--peach); } .s-kpi__val small { font-size: .42em; margin-left: 10px; } .s-kpi__txt { padding-bottom: 20px; font-size: 34px; line-height: 1.3; font-weight: 440; color: var(--ink-200); max-width: 820px; } .s-kpi__txt b { color: #fff; font-weight: 700; } .s-cards .card--ink h3 { color: #fff; } .s-refs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 24px; } .s-ref { padding: 30px 32px 34px; border-radius: var(--shape-md); background: var(--paper); box-shadow: inset 0 0 0 1px var(--line), var(--shadow-1); } .s-ref__sector { font: 500 16px/1.3 var(--mono); letter-spacing: .14em; text-transform: uppercase; color: var(--peach-700); } .s-ref__names { margin-top: 16px; font-size: 32px; line-height: 1.2; font-weight: 720; font-stretch: 108%; letter-spacing: -.015em; color: var(--ink); } .home-btn { position: absolute; top: 34px; right: 40px; z-index: 9; display: inline-flex; align-items: center; gap: 10px; padding: 10px 16px 10px 14px; border-radius: var(--shape-s); font: 500 15px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; text-decoration: none; color: var(--ink); background: rgb(255 255 255 / .7); box-shadow: inset 0 0 0 1px var(--line-2); transition: transform .2s var(--ease), box-shadow .2s var(--ease), background-color .2s ease; } .home-btn svg { width: 20px; height: 20px; color: var(--peach-700); } .home-btn:hover { transform: translateY(-1px); background: #fff; box-shadow: inset 0 0 0 1px var(--line-2), var(--shadow-1); } .home-btn:focus-visible { outline: 2px solid var(--peach-600); outline-offset: 3px; } .slide--ink .home-btn, .slide--photo .home-btn { color: #fff; background: rgb(255 255 255 / .08); box-shadow: inset 0 0 0 1px rgb(255 255 255 / .22); } .slide--ink .home-btn svg, .slide--photo .home-btn svg { color: var(--peach); } .slide--ink .home-btn:hover, .slide--photo .home-btn:hover { background: rgb(255 255 255 / .16); } @media print { .home-btn { display: none !important; } } .slide .s-merci { bottom: 300px; } .slide .s-merci-sub { bottom: 110px; display: flex; flex-direction: column; gap: 10px; line-height: 1.25; } .s-merci-cta { font: 500 20px/1 var(--mono); letter-spacing: .2em; text-transform: uppercase; color: var(--peach); margin-bottom: 8px; } .s-merci-sub a { color: #fff; text-decoration: none; box-shadow: inset 0 -2px 0 rgb(249 166 107 / .7); } .s-merci-sub a:hover { color: var(--peach); } .todo { background: var(--peach-200); color: var(--peach-700); font-family: var(--mono); font-size: .8em; padding: 0 .3em; border-radius: 5px 1px 5px 1px; } .slide--ink .todo { background: rgb(249 166 107 / .25); color: var(--peach); } .s-fine { margin-top: 26px; font: 400 17px/1.4 var(--mono); letter-spacing: .04em; color: var(--text-3); } """ style=style+EXTRA def typo(s): NB, FINE = "\u00a0", "\u202f" s=re.sub(r" ([:;!?»])(?=[\s<]|$)",lambda m:(FINE if m.group(1) in ";!?" else NB)+m.group(1),s) s=s.replace("« ","«"+NB) s=re.sub(r"(\d) (%|€|k€|M€|h|j|mois|semaines|jours|min|lignes|sessions|commerciaux)(?!\w)",lambda m:m.group(1)+NB+m.group(2),s) s=re.sub(r"(?<=[A-Za-zÀ-ÿ])'(?=[A-Za-zÀ-ÿ])","\u2019",s) return s def build(title,desc,slides_html,out): body=typo(slides_html) html=f""" {title} {symbols}
{body}
{script} """ open(out,"w").write(html) PH_COVER='Un sommet émerge d’une mer de nuages au crépuscule' CR_COVER='

Photo : Michele Bergami / Unsplash

' PH_SPLIT='Une cordée de quatre alpinistes gravit un glacier' CR_SPLIT='Photo : Jackman Chiu / Unsplash' PH_END='Six amis lèvent les bras au sommet, au soleil couchant' CR_END='

Photo : Chang Duong / Unsplash

' def cover(kicker,title,sub): return f'''
{PH_COVER}
{kicker}

{title}

{sub}

{CR_COVER}
''' def section(sec,num,title): return f'''
{num}

{title}

''' def light(sec,kicker,title,sub,body,small=False): subh=f'

{sub}

' if sub else '' return f'''
{kicker}

{title}

{subh}
{body}
''' def split(sec,paras): ps="".join(f'

{p}

' for i,p in enumerate(paras)) return f'''
{ps}
{PH_SPLIT}
{CR_SPLIT}
''' CONTACTS=[] # signataires de l'affaire, à remplir dans decks.py : CONTACTS[:] = [("Prénom Nom", "adresse@adalan.fr")] GENERIC=("adalan","contact@adalan.fr") def merci(contacts=None): contacts=contacts or CONTACTS or [GENERIC] contact='Parlons de votre projet'+"".join(f'{n} · {m}' for n,m in contacts) return f'''
{PH_END}

Merci.

{contact}

{CR_END}
''' def card(icon,title,text,i,cls="",lst=None): t=f'

{text}

' if text else '' l='
    '+"".join(f"
  • {x}
  • " for x in lst)+'
' if lst else '' return f'

{title}

{t}{l}
' def cards(items,n=3,start=3): cls={2:" s-cards--2",3:"",4:" s-cards--4"}[n] return f'
'+"".join(card(*it[:3],start+k,*(it[3:] or [])) for k,it in enumerate(items))+'
' def note(text,i,icon="flag",peach=False): return f'

{text}

' def steps(items,start=3,hl=None): return f'
'+"".join(f'
{w}

{t}

{p}

' for k,(w,t,p) in enumerate(items))+'
' def table(head,rows,i=3,price_col=None): h="".join(f"{x}" for x in head) r="".join(""+"".join(('' if price_col==j else '')+f'{c}' for j,c in enumerate(row))+"" for row in rows) return f'{h}{r}
' def ulist(items,i=3): return f'
    '+"".join(f"
  • {x}
  • " for x in items)+'
' def flow(boxes,i=3): out=[] for k,(tag,t,p,ink) in enumerate(boxes): if k: out.append('
'%(i+k)) out.append(f'
{tag}

{t}

{p}

') return '
'+"".join(out)+'
' REFS=[("Banque et gestion d'actifs","Natixis
Amundi"),("Assurance","Groupama"),("Énergie","TotalEnergies"),("Aéronautique","Airbus"),("Arômes et parfums","Mane"),("Aménagement d'espaces","Kardham"),("Investissement","Carmine Capital
50 Partners")] def refs(): items="".join(f'
{s}
{n}
' for k,(s,n) in enumerate(REFS)) items+='
Et demain
Votre projet
' return light("adalan","Ils nous font confiance","Des grands comptes, dans sept secteurs.","Merci à eux.",'
'+items+'
') def kpi(sec,kicker,val,unit,text,fine=""): f=f'

{fine}

' if fine else '' return f'''
{kicker}
{val}{unit}

{text}

{f}
''' def qsn_general(): return light("adalan","Qui sommes-nous","Des experts augmentés. Des outils qui tournent.","adalan, né à Paris le 1er octobre 2023 : deux métiers qui se répondent, et un parcours IA ouvert à vos équipes.", cards([("users","Consulting","Des seniors qui arrivent outillés : une séniorité revendiquée, une verticale assumée, quatre compétences IA."), ("hammer","Factory","De l'idée à l'outil qui tourne : applications, agents et workflows IA, jusqu'à la mise en production."), ("cap","Parcours IA","Trois terrains. Quatre grades. Un parcours que nous suivons nous-mêmes, ouvert à vos équipes.")])+note("Factory vous rend autonome. Consulting vous rassure et accélère le déploiement.",6)) def qsn_formation(): return light("adalan","Qui sommes-nous","Les métiers ne disparaissent pas. Ils s'augmentent.","L'IA prend la production. Il reste le meilleur : penser, convaincre, construire.", cards([("cap","Un parcours éprouvé","Trois terrains. Quatre grades. Un parcours que nous suivons nous-mêmes, et que nous ouvrons à vos équipes."), ("users","Nous le vivons","Nous formons nos consultant(e)s avant de former vos équipes : une formation par mois, un abonnement Claude pour chacun(e)."), ("shield","Sans shadow AI","Des outils sûrs et des règles claires, pour que personne n'ait à contourner.")])+note("adalan : des experts augmentés, et une Factory qui construit des outils qui tournent.",6)) def qsn_factory(stage=1): st=[("01","Qualifier","Valeur, faisabilité, données, coût."),("02","Prototyper","Un POC sur vos vraies données."),("03","Construire","Un MVP utilisé par de vrais utilisateurs."),("04","Déployer","Mise en production et maintien."),("05","Transmettre","Vos équipes autonomes sur l'outil.")] w,t,p=st[stage]; st[stage]=(w+" · cette étape",t,p) return light("adalan","Qui sommes-nous","De l'idée à l'outil qui tourne.","adalan Factory conçoit, développe et déploie applications, agents et workflows IA, intégrés à votre SI. Par étapes courtes, chacune avec son livrable.", steps(st,3,stage)+note("La conformité est intégrée dès le cadrage : AI Act, RGPD, données sensibles, shadow AI.",8,"shield")) def two_col(left,right,i=3): return f'
{left}
{right}
' def ink_box(kicker,items): return '
'+kicker+'
    '+"".join(f"
  • {x}
  • " for x in items)+'
' def box(icon,title,items,fine=""): f=f'

{fine}

' if fine else '' return f'

{title}

    '+"".join(f"
  • {x}
  • " for x in items)+f'
{f}
' def todo(label): return f'[à compléter : {label}]' # ---------------- page d'accueil ---------------- INDEX_STYLE=""" body { background: var(--ground); } .topbar { position: sticky; top: 0; z-index: 10; background: rgb(246 245 251 / .82); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); box-shadow: inset 0 -1px 0 var(--line); } .topbar .container { display: flex; align-items: center; gap: 1.5rem; min-height: 4rem; } .topbar svg.logo { width: 7.25rem; height: auto; color: var(--ink); display: block; } .topbar .meta { margin-left: auto; } .hero-band { padding-block: clamp(3.5rem, 2rem + 6vw, 7.5rem); } .hero-band .hero { margin-top: 1.2rem; max-width: 16ch; } .hero-band .lead { margin-top: 1.4rem; } .facts { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 2rem; } .facts .tag { background: rgb(255 255 255 / .06); color: var(--ink-200); box-shadow: inset 0 0 0 1px rgb(255 255 255 / .16); padding: .45em .75em; } .list { display: grid; gap: 1.1rem; margin-top: 2rem; } .deck { display: grid; grid-template-columns: 4.5rem 1fr auto; gap: 1.5rem; align-items: center; text-decoration: none; color: var(--text); padding: clamp(1.25rem, 1rem + 1vw, 2rem); border-radius: var(--shape); background: var(--paper); box-shadow: inset 0 0 0 1px var(--line), var(--shadow-1); transition: transform .25s var(--ease), box-shadow .25s var(--ease); } .deck:hover { transform: translateY(-2px); box-shadow: inset 0 0 0 1px var(--line-2), var(--shadow-2); } .deck:focus-visible { outline: 2px solid var(--peach-600); outline-offset: 3px; } .deck__num { font: 500 2.4rem/1 var(--mono); color: var(--peach-700); letter-spacing: -.02em; } .deck .title { margin-top: .5rem; } .deck .body { margin-top: .5rem; font-size: 1rem; } .deck__meta { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .9rem; } .deck__go { display: grid; place-items: center; width: 3rem; height: 3rem; border-radius: 16px 4px 16px 4px; background: var(--ink); color: #fff; transition: background-color .2s ease, transform .2s var(--ease); } .deck__go svg { width: 1.3rem; height: 1.3rem; } .deck:hover .deck__go { background: var(--peach); color: var(--ink); transform: translateX(3px); } .deck--ink { background: var(--ink); border-radius: var(--shape-ink); box-shadow: var(--shadow-2); position: relative; isolation: isolate; overflow: hidden; --text: #fff; --text-2: var(--ink-200); --text-3: var(--ink-300); --line: rgb(255 255 255 / .12); color: #fff; } .deck--ink::before { content: ''; position: absolute; inset: -8% -6%; z-index: -1; background: var(--topo) center / cover no-repeat; opacity: .9; } .deck--ink .deck__num { color: var(--peach); } .deck--ink .deck__go { background: var(--peach); color: var(--ink); } .deck--ink:hover .deck__go { background: #fff; } .deck--ink .tag { color: var(--ink-200); } .deck--ink .kicker { --kicker: var(--peach); } .contacts { list-style: none; margin: 1.6rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 1rem 3rem; } .contacts li { display: flex; flex-direction: column; gap: .35rem; } .contacts b { font-size: 1.25rem; font-weight: 680; font-stretch: 106%; color: #fff; } .contacts a { font: 400 .95rem/1.3 var(--mono); color: var(--peach); text-decoration: none; } .contacts a:hover { text-decoration: underline; text-underline-offset: 3px; } .section-title { margin-top: var(--section); } .help { margin-top: 2rem; display: flex; flex-wrap: wrap; gap: .5rem 1.5rem; } kbd { font: 500 .78rem/1 var(--mono); padding: .3em .5em; border-radius: 6px 2px 6px 2px; background: var(--paper); box-shadow: inset 0 0 0 1px var(--line-2); color: var(--ink); } .end { margin-top: var(--section); } .end .display { margin-top: 1rem; } footer { padding-block: 2.5rem; box-shadow: inset 0 1px 0 var(--line); margin-top: var(--section); } footer .container { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem 2rem; } footer svg.logo { width: 5.5rem; color: var(--ink); } footer .meta:last-child { margin-left: auto; } @media (max-width: 640px) { .deck { grid-template-columns: 1fr; gap: .8rem; } .deck__go { justify-self: start; } .topbar .meta { display: none; } } [data-r] { animation: rise .9s var(--ease) both; animation-delay: calc(var(--i, 0) * 70ms); } @keyframes rise { from { opacity: 0; transform: translateY(24px); } } @media (prefers-reduced-motion: reduce) { [data-r] { animation: none; } } """ def build_index(out,client_kicker,title_html,lead,groups,contacts=None,facts=None): """groups : liste de (titre_h2_html_ou_None, lead_ou_None, [ (num, fichier, sur-titre, titre, description, [tags], mis_en_avant) ])""" contacts=contacts or CONTACTS or [GENERIC] topo=re.search(r"url\('(data:image/svg\+xml;base64,[^']+)'\)",css).group(1) i=[2] def deck(n,href,k,t,d,tags,ink): i[0]+=1 tg="".join(f'{x}' for j,x in enumerate(tags)) return (f'{n}' f'{k}

{t}

{d}

{tg}
' '
') sec="" for h2,ld,items in groups: if h2: sec+=f'

{h2}

' if ld: sec+=f'

{ld}

' sec+='
'+"".join(deck(*it) for it in items)+'
' fa="".join(f'{x}' for x in (facts or [])) LOGO_SVG='' cts="".join(f'
  • {n}{m}
  • ' for n,m in contacts) body=f"""
    {LOGO_SVG}AI Driven Insight
    {client_kicker}

    {title_html}

    {lead}

    {fa}
    {sec}
    Dans une présentation : ← → pour naviguerF plein écranH ou Échap retour à l'accueilImpression : une slide par page
    Parlons de votre projet

    Des experts augmentés. Des outils qui tournent.

      {cts}
    {LOGO_SVG}14 avenue de l'Opéra, 75001 Paris · contact@adalan.fr© {__import__('datetime').date.today().year} adalan · AI Driven Insight
    """ html=f""" Propositions · adalan {symbols}{typo(body)}""" open(out,"w").write(html)