import cairosvg,math
def build(mode):
    W,H=1700,980
    k=2.55
    cx=640
    y0=800
    def Y(m): return y0-m*k
    def X(m): return cx+m*k
    def txt(x,y,s,size=15,anchor="start",weight="normal",fill="#111"):
        return f'<text x="{x}" y="{y}" font-family="Arial,Helvetica,sans-serif" font-size="{size}" text-anchor="{anchor}" font-weight="{weight}" fill="{fill}">{s}</text>'
    def leader(x1,y1,x2,y2):
        return f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="#333" stroke-width="1.5"/><circle cx="{x2}" cy="{y2}" r="3" fill="#333"/>'
    paris=mode!="cote"
    if mode=="cote":
        T=38;N=11;R=50;RB=54;A=234;top=262;eol=(238,256);JT=30;S=0
        rtr=lambda h:30
    elif mode=="cone":
        T=164;N=4;R=40;RB=164;A=230;top=260;eol=(236,254);JT=None;S=25
        rtr=lambda h:75-51*h/164
    elif mode=="evase":
        T=164;N=4;R=40;RB=164;A=230;top=260;eol=(236,254);JT=40;S=30
        rtr=lambda h:40-16*max(h-30,0)/134
    else:
        T=164;N=4;R=40;RB=164;A=230;top=260;eol=(236,254);JT=30;S=30
        rtr=lambda h:30-6*h/164
    s=[f'<svg xmlns="http://www.w3.org/2000/svg" width="{W}" height="{H}" viewBox="0 0 {W} {H}">']
    s.append(f'<rect width="{W}" height="{H}" fill="#eef3f7"/>')
    if paris:
        for bx,bw,bh in ((1120,60,30),(1190,45,45),(1245,70,25),(1325,40,50),(1375,80,35),(1465,50,48),(1525,65,28),(1600,45,42),(1655,40,32)):
            s.append(f'<rect x="{bx}" y="{y0-bh}" width="{bw}" height="{bh}" fill="#c3ccd6" stroke="#9aa6b2" stroke-width="1"/>')
        s.append(f'<rect x="0" y="{y0}" width="{W}" height="{H-y0}" fill="#b8b0a0"/>')
        s.append(f'<rect x="1110" y="{y0+18}" width="600" height="34" fill="#5b8fb5"/>')
        s.append(txt(1410,y0+41,"La Seine : source de froid, comme le réseau Fraîcheur de Paris",15,"middle","normal","#fff"))
    else:
        s.append(f'<rect x="0" y="{y0}" width="1110" height="{H-y0}" fill="#b8b0a0"/>')
        s.append(f'<rect x="1110" y="{y0+10}" width="600" height="{H-y0-10}" fill="#4a90c2"/>')
        s.append(f'<rect x="1100" y="{y0}" width="30" height="50" fill="#777"/>')
        s.append(txt(1410,y0+42,"Mer ou bassin portuaire : source froide, option Shore Power à étudier",15,"middle","normal","#fff"))
    s.append(f'<rect x="{X(-30)}" y="{Y(0)}" width="{60*k}" height="{10*k}" fill="#1b6ca8" stroke="#0a3d62" stroke-width="2"/>')
    s.append(txt(cx,Y(0)+18,"Bassin 8 500 m³",12,"middle","bold","#fff"))
    if mode in ("cote","style"):
        for side in (-1,1):
            x1,x2=X(side*80),X(side*110)
            s.append(f'<rect x="{min(x1,x2)}" y="{Y(5)}" width="{30*k}" height="5" fill="#1d4e89"/>')
            for p in (82,95,108):
                s.append(f'<line x1="{X(side*p)}" y1="{Y(0)}" x2="{X(side*p)}" y2="{Y(5)+5}" stroke="#666" stroke-width="2"/>')
            for c in (87,102):
                s.append(f'<rect x="{X(side*c)-6}" y="{Y(1.6)}" width="12" height="{1.6*k}" fill="#2e8b57"/>')
    if mode=="cote":
        for m in (122,140):
            s.append(f'<line x1="{X(m)}" y1="{Y(0)}" x2="{X(m)}" y2="{Y(20)}" stroke="#555" stroke-width="3"/>')
            s.append(f'<rect x="{X(m)-9*k}" y="{Y(26)}" width="{18*k}" height="{6*k}" rx="6" fill="#2c3e50" stroke="#1d4e89" stroke-width="2"/>')
            s.append(f'<rect x="{X(m)-9*k}" y="{Y(1.2)}" width="{18*k}" height="4" fill="#1d4e89"/>')
    rb=rtr(0)
    s.append(f'<polygon points="{X(-rb)},{Y(0)} {X(rb)},{Y(0)} {X(rtr(T))},{Y(T)} {X(-rtr(T))},{Y(T)}" fill="#dfe7ee" stroke="#4a5a6a" stroke-width="3"/>')
    if JT is not None:
        s.append(f'<polygon points="{X(-75)},{Y(0)} {X(-JT)},{Y(30)} {X(JT)},{Y(30)} {X(75)},{Y(0)}" fill="#e3ddd0" stroke="#4a5a6a" stroke-width="2"/>')
        for side in (-1,1):
            s.append(f'<line x1="{X(side*75)}" y1="{Y(0)}" x2="{X(side*JT)}" y2="{Y(30)}" stroke="#1d4e89" stroke-width="7"/>')
            for z in (6,12,18,24):
                ri=rtr(z);ro=75-(z+3.5)*(75-JT)/30
                s.append(f'<line x1="{X(side*ri)}" y1="{Y(z)}" x2="{X(side*ro)}" y2="{Y(z)}" stroke="#8a97a3" stroke-width="1.2"/>')
            for z in (1.5,7.5,13.5,19.5):
                xx=X(side*(rtr(z)+3))
                s.append(f'<rect x="{min(xx,xx+side*12)}" y="{Y(z+2.5)}" width="12" height="{2.5*k}" fill="#34495e"/>')
    else:
        s.append(f'<polygon points="{X(-75)},{Y(0)} {X(75)},{Y(0)} {X(rtr(S))},{Y(S)} {X(-rtr(S))},{Y(S)}" fill="#e3ddd0" stroke="#4a5a6a" stroke-width="2"/>')
        s.append(f'<line x1="{X(75)}" y1="{Y(0)}" x2="{X(rtr(S))}" y2="{Y(S)}" stroke="#1d4e89" stroke-width="8"/>')
        for z in (6,12,18,24):
            s.append(f'<line x1="{X(-rtr(z)+1)}" y1="{Y(z)}" x2="{X(rtr(z)-1)}" y2="{Y(z)}" stroke="#8a97a3" stroke-width="1.2"/>')
        for z in (3,15):
            for xx in (-55,-35,-15,5,25,45):
                if abs(xx)<rtr(z)-6: s.append(f'<rect x="{X(xx)}" y="{Y(z+2.5)}" width="12" height="{2.5*k}" fill="#34495e"/>')
    atr=mode in ("cone","evase")
    if atr:
        s.append(f'<rect x="{X(-15)}" y="{Y(T-2)}" width="{30*k}" height="{(T-2-S)*k}" fill="#f6f9fb" stroke="#8a97a3" stroke-width="1" stroke-dasharray="5,4"/>')
        s.append(f'<path d="M {X(-15)} {Y(T-2)} Q {cx} {Y(T+4)} {X(15)} {Y(T-2)}" fill="#dff1fb" stroke="#4a90c2" stroke-width="2"/>')
        for h in [S+1.9+3.8*i for i in range(int((T-4-S)/3.8))]:
            hw=rtr(h)-0.8
            s.append(f'<line x1="{X(-hw)}" y1="{Y(h)}" x2="{X(-15)}" y2="{Y(h)}" stroke="#8a97a3" stroke-width="1" stroke-dasharray="4,3"/>')
            s.append(f'<line x1="{X(15)}" y1="{Y(h)}" x2="{X(hw)}" y2="{Y(h)}" stroke="#8a97a3" stroke-width="1" stroke-dasharray="4,3"/>')
            for d,col in ((15,"#c47a12"),(23,"#c47a12"),(30,"#2e8b57")):
                if hw-d>15.5 and (mode=="cone" or d==15):
                    s.append(f'<line x1="{X(-(hw-d))}" y1="{Y(h)}" x2="{X(-(hw-d))}" y2="{Y(h+3.8)}" stroke="{col}" stroke-width="1.2"/>')
                    s.append(f'<line x1="{X(hw-d)}" y1="{Y(h)}" x2="{X(hw-d)}" y2="{Y(h+3.8)}" stroke="{col}" stroke-width="1.2"/>')
    elif mode=="style":
        for h in [11+3.8*i for i in range(39)]+[T-7]:
            if h<31: continue
            hw=rtr(h)-0.8
            s.append(f'<line x1="{X(-hw)}" y1="{Y(h)}" x2="{X(hw)}" y2="{Y(h)}" stroke="#8a97a3" stroke-width="1" stroke-dasharray="4,3"/>')
    else:
        for h in (6,12,18,24,31):
            hw=rtr(h)-0.8
            s.append(f'<line x1="{X(-hw)}" y1="{Y(h)}" x2="{X(hw)}" y2="{Y(h)}" stroke="#8a97a3" stroke-width="1" stroke-dasharray="4,3"/>')
    if paris:
        r=5
        for row in range(0,int(T*k/(r*1.75))+1):
            yy=Y(0)-r-row*r*1.75;hm=(Y(0)-yy)/k
            if hm<S+1 or hm>T-3: continue
            half=rtr(hm)*k;cols=int(half*2/(r*1.9));off=(row%2)*r*0.95
            for c in range(cols):
                xx=cx-half+r+off+c*r*1.9
                if xx>cx+half-r: continue
                if atr and abs(xx-cx)<(half-15*k): continue
                pts=" ".join(f"{xx+r*math.cos(math.radians(60*a+30)):.1f},{yy+r*math.sin(math.radians(60*a+30)):.1f}" for a in range(6))
                s.append(f'<polygon points="{pts}" fill="none" stroke="#1d4e89" stroke-width="1"/>')
    rT=rtr(T)
    s.append(f'<path d="M {X(-rT)} {Y(T)} Q {cx} {Y(T+12)} {X(rT)} {Y(T)} Z" fill="#c9d6e3" stroke="#1d4e89" stroke-width="5"/>')
    if mode=="cote":
        s.append(f'<rect x="{X(-3)}" y="{Y(A)}" width="{6*k}" height="{A*k}" fill="#f4f4f4" stroke="#555" stroke-width="2"/>')
    elif mode=="style":
        s.append(f'<rect x="{X(-3)}" y="{Y(T)}" width="{6*k}" height="{T*k}" fill="#f4f4f4" stroke="#555" stroke-width="2"/>')
    sh=1.6 if mode=="cote" else 3.2
    s.append(f'<rect x="{X(-sh)}" y="{Y(RB+16*N+2)}" width="{2*sh*k}" height="{(RB+16*N+2-T)*k}" fill="#1d4e89"/>')
    s.append(f'<rect x="{X(-9)}" y="{Y(T+1)-9}" width="{7*k}" height="9" fill="#333"/>')
    s.append(f'<rect x="{X(2)}" y="{Y(T+1)-9}" width="{7*k}" height="9" fill="#333"/>')
    for e in range(N):
        yb=Y(RB+e*16+1.5);yt=Y(RB+e*16+14.5);ym=(yb+yt)/2
        for side in (-1,1):
            xb=X(side*R)
            s.append(f'<line x1="{X(side*3)}" y1="{ym}" x2="{xb}" y2="{ym}" stroke="#555" stroke-width="4"/>')
            s.append(f'<line x1="{X(side*3)}" y1="{ym-1}" x2="{xb}" y2="{ym-1}" stroke="#1d4e89" stroke-width="2"/>')
            s.append(f'<rect x="{xb-4}" y="{yt}" width="8" height="{yb-yt}" rx="3" fill="#2c3e50"/>')
    if mode=="cote":
        F=60
        for side in (-1,1):
            s.append(f'<line x1="{X(side*F)}" y1="{Y(0)}" x2="{X(side*F)}" y2="{Y(A)}" stroke="#7a1f1f" stroke-width="7"/>')
            for i in range(0,int(A/10)):
                h1=i*10;h2=h1+10
                s.append(f'<line x1="{X(side*(F-1.6))}" y1="{Y(h1)}" x2="{X(side*(F+1.6))}" y2="{Y(h2)}" stroke="#b04a4a" stroke-width="1.2"/>')
            s.append(f'<line x1="{X(side*F)}" y1="{Y(A)}" x2="{X(0)}" y2="{Y(A)}" stroke="#7a1f1f" stroke-width="6"/>')
            s.append(f'<rect x="{X(side*F)-12}" y="{Y(0)}" width="24" height="10" fill="#7a1f1f"/>')
    s.append(f'<circle cx="{cx}" cy="{Y(A)}" r="7" fill="#222"/>')
    s.append(f'<path d="M {X(-15)} {Y(A+1)} Q {cx} {Y(A+5.5)} {X(15)} {Y(A+1)} Z" fill="#2e8b57" stroke="#1e5e3a" stroke-width="2"/>')
    s.append(f'<line x1="{cx}" y1="{Y(A+3)}" x2="{cx}" y2="{Y(top)}" stroke="#444" stroke-width="3"/>')
    for side in (-1,1):
        for hh in (eol[0]+3,eol[1]-3):
            s.append(f'<line x1="{cx}" y1="{Y(hh)}" x2="{X(side*15)}" y2="{Y(hh)}" stroke="#555" stroke-width="2"/>')
        s.append(f'<rect x="{X(side*15)-3}" y="{Y(eol[1])}" width="6" height="{(eol[1]-eol[0])*k}" rx="2" fill="#2c3e50"/>')
        s.append(f'<rect x="{cx+side*6-3}" y="{Y(top)}" width="6" height="{3.5*k}" fill="#c0392b"/>')
    rx=1060 if mode=="cote" else 965
    s.append(f'<line x1="{rx}" y1="{Y(0)}" x2="{rx}" y2="{Y(top)}" stroke="#333" stroke-width="2"/>')
    for m in range(0,top+1,10):
        L_=10 if m%50==0 else 5
        s.append(f'<line x1="{rx}" y1="{Y(m)}" x2="{rx+L_}" y2="{Y(m)}" stroke="#333" stroke-width="1.5"/>')
        if m%50==0: s.append(txt(rx+14,Y(m)+5,f"{m} m",13,"start","normal","#333"))
    s.append(txt(rx+14,Y(top)+5,f"{top} m",13,"start","bold","#333"))
    s.append(f'<line x1="{X(-75)}" y1="{Y(0)+36}" x2="{X(75)}" y2="{Y(0)+36}" stroke="#333" stroke-width="2"/>')
    for m in range(-75,76,25):
        s.append(f'<line x1="{X(m)}" y1="{Y(0)+31}" x2="{X(m)}" y2="{Y(0)+41}" stroke="#333" stroke-width="1.5"/>')
    s.append(txt(cx,Y(0)+56,"150 m à la base, graduations de 25 m ; échelle verticale à droite, 10 m par trait",13,"middle","normal","#333"))
    L=30
    s.append(txt(L,Y(top-1),f"Antennes télécom sur poteau fixe, sommet à {top} m"))
    s.append(leader(400,Y(top-1.5),cx-10,Y(top-1)))
    s.append(txt(L,Y(eol[0]+9),"Éolienne verticale 400 kW, 30 m de diamètre, 20 m de haut"))
    s.append(leader(455,Y(eol[0]+8.5),X(-15)-5,Y(eol[0]+8.5)))
    s.append(txt(L,Y(A+2),"Chapeau de 30 m couvert de photovoltaïque"))
    s.append(leader(345,Y(A+1.5),X(-12),Y(A+3)))
    mid=RB+8*N+4
    Rx=1160
    if mode=="cote":
        s.append(txt(L,Y(mid+5),"11 étages de 4 pales verticales de 16 m,"))
        s.append(txt(L,Y(mid),"pas cyclique, 100 m × 176 m, 11 MW,"))
        s.append(txt(L,Y(mid-5),"de 54 à 230 m : un étage de plus à la place des bureaux"))
        s.append(leader(330,Y(mid+2.5),X(-R)-5,Y(mid+2.5)))
        s.append(txt(L,Y(66),"Plus de bureaux ni d'alvéoles : le tronc s'arrête à 38 m"))
        s.append(txt(L,Y(61),"(rez-de-chaussée, 2 niveaux de centre de données, machinerie)"))
        s.append(leader(420,Y(63.5),X(-rtr(35)),Y(35)))
        s.append(txt(L,Y(48),"Jupe élargie : 30 m de haut jusqu'à 75 m de l'axe,"))
        s.append(txt(L,Y(43),"3,8 MWc de panneaux pleins inclinés à 32°"))
        s.append(leader(395,Y(45.5),X(-52),Y(14.5)))
        s.append(txt(L,Y(29),"Sous la jupe : centre de données, 24 200 m²"))
        s.append(txt(L,Y(24),"sur 5 niveaux, sans fenêtres, les pieds du trépied la traversent"))
        s.append(leader(300,Y(26.5),X(-40),Y(8)))
        s.append(txt(Rx,Y(A+4),"Roulement haut au moyeu du trépied :"))
        s.append(txt(Rx,Y(A-1),"le mât est tenu aux deux bouts"))
        s.append(leader(Rx-5,Y(A+1.5),cx+8,Y(A)))
        s.append(txt(Rx,Y(175),"Trépied : 3 pieds droits en treillis à 120°,"))
        s.append(txt(Rx,Y(170),"à 60 m de l'axe, 10 m de dégagement du rotor"))
        s.append(leader(Rx-5,Y(172.5),X(F)+4,Y(172)))
        s.append(txt(Rx,Y(140),"Rotor 11 MW nominaux, 5,6 tr/min, mât de 176 m"))
        s.append(txt(Rx,Y(135),"tenu aux deux bouts, résonance à calculer"))
        s.append(leader(Rx-5,Y(137.5),X(R)+5,Y(137.5)))
        s.append(txt(Rx,Y(84),"Toit bombé à 38 m : couvre les deux génératrices"))
        s.append(txt(Rx,Y(79),"de 5,5 MW, couvert de photovoltaïque"))
        s.append(leader(Rx-5,Y(81.5),X(12),Y(T+4)))
        s.append(txt(Rx,Y(60),"Ombrières de 80 à 110 m de l'axe :"))
        s.append(txt(Rx,Y(55),"240 places, 48 bornes de recharge, vélos"))
        s.append(leader(Rx-5,Y(57.5),X(105),Y(5)))
        s.append(txt(Rx,Y(36),"Au-delà des ombrières, au vent de la tour :"))
        s.append(txt(Rx,Y(31),"20 panneaux solaires hybrides Muhleisen industriels"))
        s.append(leader(Rx-5,Y(33.5),X(140)+9*k+2,Y(29)))
        s.append(txt(Rx,Y(16),"Bassin : chaleur des serveurs, froid de la mer,"))
        s.append(txt(Rx,Y(11),"eau de pluie, vidange d'urgence vers la mer"))
        s.append(leader(Rx-5,Y(13.5),X(30),Y(0)+16))
    else:
        s.append(txt(L,Y(mid+5),"4 étages de 4 pales verticales de 16 m,"))
        s.append(txt(L,Y(mid),"pas cyclique, 80 m × 64 m, 3,2 MW, de 164 à 228 m"))
        s.append(leader(330,Y(mid+2.5),X(-R)-5,Y(mid+2.5)))
        s.append(txt(Rx,Y(A+4),"Plus de trépied : l'arbre de 64 m est tenu par le bas,"))
        s.append(txt(Rx,Y(A-1),"chapeau, éolienne et antennes posés sur le moyeu"))
        s.append(leader(Rx-5,Y(A+1.5),cx+8,Y(A)))
        s.append(txt(Rx,Y(T+18),"Toit bombé : couvre les deux génératrices de 1,6 MW,"))
        s.append(txt(Rx,Y(T+13),"l'arbre de 6 à 8 m et son palier" if not atr else "la verrière de l'atrium, le palier de l'arbre"))
        s.append(leader(Rx-5,Y(T+15.5),X(12),Y(T+4)))
        s.append(txt(Rx,Y(212),"Rotor 3,2 MW nominaux, 7 tr/min,"))
        s.append(txt(Rx,Y(207),"vent urbain de 7 à 7,5 m/s à cette hauteur"))
        s.append(leader(Rx-5,Y(209.5),X(R)+5,Y(209.5)))
        if mode=="cone":
            s.append(txt(L,Y(140),"Une seule pente de 73° du sol (150 m) au toit bombé (48 m)"))
            s.append(txt(L,Y(135),"De 25 à 164 m : 36 niveaux de bureaux en alvéoles"))
            s.append(txt(L,Y(130),"Chaque niveau : façade, bureaux 15 m, services 8 m,"))
            s.append(txt(L,Y(125),"bureaux 15 m, atrium ; sous 87 m, le coeur (trait vert)"))
            s.append(txt(L,Y(120),"est en locaux de service, 158 000 m² de bureaux"))
            s.append(leader(300,Y(122.5),X(-rtr(115)),Y(115)))
            s.append(txt(L,Y(60),"Socle de 0 à 25 m : 4 niveaux de 6 m, 66 000 m² bruts :"))
            s.append(txt(L,Y(55),"centre de données 28 000 m², parking et bornes, stockage,"))
            s.append(txt(L,Y(50),"face sud en photovoltaïque"))
            s.append(leader(440,Y(52.5),X(-rtr(15)+8),Y(15)))
            s.append(txt(Rx,Y(110),"Atrium : colonne de 30 m de diamètre, du socle"))
            s.append(txt(Rx,Y(105),"à la verrière, ascenseurs dans l'anneau de services"))
            s.append(leader(Rx-5,Y(107.5),X(14),Y(100)))
            s.append(txt(Rx,Y(40),"Plus de jupe ni d'ombrières : la tour tient"))
            s.append(txt(Rx,Y(35),"sur son cercle de 150 m, 1,8 ha au lieu de 3,8"))
            s.append(leader(Rx-5,Y(37.5),X(rtr(12)),Y(12)))
        elif mode=="evase":
            s.append(txt(L,Y(140),"Tronc évasé de 30 à 164 m : 80 m de diamètre en bas,"))
            s.append(txt(L,Y(135),"48 m en haut, une seule pente, 34 niveaux"))
            s.append(txt(L,Y(130),"Chaque niveau : façade, bureaux 15 m, services,"))
            s.append(txt(L,Y(125),"atrium ; 65 600 m² de bureaux derrière les alvéoles"))
            s.append(leader(300,Y(127.5),X(-rtr(115)),Y(115)))
            s.append(txt(L,Y(48),"Jupe : 30 m de haut jusqu'à 75 m de l'axe, pente 40°,"))
            s.append(txt(L,Y(43),"3,5 MWc de panneaux pleins"))
            s.append(leader(395,Y(45.5),X(-55),Y(14.5)))
            s.append(txt(L,Y(29),"Sous la jupe : centre de données 28 000 m² avec le tronc,"))
            s.append(txt(L,Y(24),"parking et bornes au premier niveau, sans fenêtres"))
            s.append(leader(300,Y(26.5),X(-45),Y(8)))
            s.append(txt(Rx,Y(110),"Atrium : colonne de 30 m de diamètre, de la jupe"))
            s.append(txt(Rx,Y(105),"à la verrière, ascenseurs dans l'anneau de services"))
            s.append(leader(Rx-5,Y(107.5),X(14),Y(100)))
            s.append(txt(Rx,Y(40),"Plus d'ombrières : la tour tient sur"))
            s.append(txt(Rx,Y(35),"son cercle de 150 m, 1,8 ha au lieu de 3,8"))
            s.append(leader(Rx-5,Y(37.5),X(70),Y(2)))
        else:
            s.append(txt(L,Y(120),"Tronc de 164 m : 39 niveaux de bureaux"))
            s.append(txt(L,Y(115),"au-dessus de la jupe, 56 500 m² louables"))
            s.append(leader(330,Y(117.5),X(-rtr(110)),Y(110)))
            s.append(txt(L,Y(48),"Jupe élargie : 30 m de haut jusqu'à 75 m de l'axe,"))
            s.append(txt(L,Y(43),"3,8 MWc de panneaux pleins inclinés à 32°"))
            s.append(leader(395,Y(45.5),X(-52),Y(14.5)))
            s.append(txt(L,Y(29),"Sous la jupe : centre de données, 24 200 m²"))
            s.append(txt(L,Y(24),"sur 5 niveaux, sans fenêtres"))
            s.append(leader(300,Y(26.5),X(-40),Y(8)))
            s.append(txt(Rx,Y(60),"Ombrières de 80 à 110 m de l'axe :"))
            s.append(txt(Rx,Y(55),"240 places, 48 bornes de recharge, vélos"))
            s.append(leader(Rx-5,Y(57.5),X(105),Y(5)))
            s.append(txt(Rx,Y(36),"Bassin : chaleur des serveurs vers le réseau,"))
            s.append(txt(Rx,Y(31),"froid tiré de la Seine, eau de pluie"))
            s.append(leader(Rx-5,Y(33.5),X(30),Y(0)+16))
    s.append(f'<rect x="0" y="{H-70}" width="{W}" height="70" fill="#0a3d62"/>')
    foot={"cote":("Titan Claudia 5 : tour côtière de 262 m sans bureaux, rotor de 100 m sur 11 étages, trépied à pieds droits, jupe solaire et centre de données, 39 GWh par an","Coupe schématique à l'échelle. 11 MW de rotor + 400 kW, 7,6 MWc, 20 modules hybrides, 67 M€ en série de 10, 56 M€ en série de 1 000 (97 €/MWh), remboursée en 7,9 ans, 6,6 en grande série. www.mulo.world, octobre 2026, open source","schema_titan_claudia_5"),
    "cone":("Titan Claudia 5 Paris Cône : 150 m de base, 36 niveaux de bureaux en deux anneaux autour d'un atrium de 30 m, sans trépied, 158 000 m² de bureaux","Coupe schématique à l'échelle. 322 000 m² de plancher, centre de données et parking dans le socle, rotor de 4 étages, 4,9 GWh par an, 228 M€ en série, remboursée en 4,0 ans terrain offert. www.mulo.world, octobre 2026, open source","schema_titan_claudia_5_paris"),
    "evase":("Titan Claudia 5 Paris 80 : tronc évasé de 80 à 48 m, 34 niveaux autour d'un atrium de 30 m, sans trépied ni ombrières, 65 600 m² de bureaux","Coupe schématique à l'échelle. Jupe à 40° de 3,5 MWc, centre de données de 28 000 m², rotor de 4 étages, 6,9 GWh par an, 110 M€ en série, remboursée en 3,9 ans terrain offert. www.mulo.world, octobre 2026, open source","schema_titan_claudia_5_paris_80"),
    "style":("Titan Claudia 4 Paris Style : la V4 Paris sans trépied, arbre tenu par le bas, 39 niveaux de bureaux, 86 600 m² louables, 8 GWh par an","Coupe schématique à l'échelle. La Défense, terrain offert (hypothèse), 107 M€ en série au lieu de 112, remboursée en 4,3 ans, 8,3 ans à 1 000 €/m² de plancher. www.mulo.world, octobre 2026, open source","schema_titan_claudia_4_paris_style")}[mode]
    s.append(txt(W/2,H-40,foot[0],22,"middle","bold","#fff"))
    s.append(txt(W/2,H-14,foot[1],15,"middle","normal","#cfe3f5"))
    s.append('</svg>')
    svg="\n".join(s)
    cairosvg.svg2png(bytestring=svg.encode("utf-8"),write_to=foot[2]+".png",output_width=W,output_height=H)
for m in ("cote","cone","evase","style"):
    build(m)
print("ok")
