From cbf3dbd8b66547c24e420653a825a8e382c55110 Mon Sep 17 00:00:00 2001 From: Edmond Date: Wed, 26 Aug 2026 09:50:05 +0100 Subject: [PATCH 1/4] feat: Add Sponsors page showcasing sponsors by tier - New /sponsors page (linked from the navbar) presenting sponsors with logo and description: Diamond card, Bronze and Community logo tiles with description on hover (shown inline on mobile) - Sponsor data in src/data/sponsorsList.js, logos in public/sponsors/ - Existing /sponsor page (become a sponsor) left unchanged Co-Authored-By: Claude Fable 5 --- public/sponsors/.gitkeep | 0 public/sponsors/apify.svg | 12 + public/sponsors/bpd.webp | Bin 0 -> 7002 bytes public/sponsors/canonical.webp | Bin 0 -> 7600 bytes public/sponsors/chaoss.webp | Bin 0 -> 8554 bytes public/sponsors/dsf.webp | Bin 0 -> 7278 bytes public/sponsors/jetbrains.webp | Bin 0 -> 11902 bytes src/App.jsx | 2 + src/components/Navbar.jsx | 4 +- src/components/SponsorShowcase.jsx | 187 +++++++++++++++ src/data/sponsorsList.js | 95 ++++++++ src/i18n/en.json | 26 ++- src/i18n/fr.json | 26 ++- src/index.css | 354 +++++++++++++++++++++++++++++ src/pages/Sponsors.jsx | 24 ++ 15 files changed, 726 insertions(+), 4 deletions(-) create mode 100644 public/sponsors/.gitkeep create mode 100644 public/sponsors/apify.svg create mode 100644 public/sponsors/bpd.webp create mode 100644 public/sponsors/canonical.webp create mode 100644 public/sponsors/chaoss.webp create mode 100644 public/sponsors/dsf.webp create mode 100644 public/sponsors/jetbrains.webp create mode 100644 src/components/SponsorShowcase.jsx create mode 100644 src/data/sponsorsList.js create mode 100644 src/pages/Sponsors.jsx diff --git a/public/sponsors/.gitkeep b/public/sponsors/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/sponsors/apify.svg b/public/sponsors/apify.svg new file mode 100644 index 0000000..c8894c8 --- /dev/null +++ b/public/sponsors/apify.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/sponsors/bpd.webp b/public/sponsors/bpd.webp new file mode 100644 index 0000000000000000000000000000000000000000..3b4bfa3996a8c8db7ecf2e731221a5f806db0e39 GIT binary patch literal 7002 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 literal 0 HcmV?d00001 diff --git a/public/sponsors/dsf.webp b/public/sponsors/dsf.webp new file mode 100644 index 0000000000000000000000000000000000000000..e8036e3e4a5dd26f66d544ab07387baeda711672 GIT binary patch literal 7278 zcmbW5RZJa@vhEk|PO*i%7Kc^b-HJODcXusPDDLj=?ykkXxO;JjqHCT1-sj}rWam8G z+GnhyegCNih{o6%e`ZKY4v4AP0((9L5ex=qr7KWKm&3GT{Rz z2oAQXEr4|NVcLkrYx)?hkn+E$CB!9ep6Y2mYZb z`_Ur_W}=xL3*31V0{{LHQ6Edn3D=Jqp|oJb8@}@@K)Xae<%}|BX4FqCo{*BE7eQg3;Zu5a_DMBk;hATz>2RL)BOL- zj= zuSv%y$e;0}NkOtD558=FQ`K2`Gj5H}ocY~^>aOYC5CS84t`I&+hN>fc+j3jHMa%lQ zyGsiUS;CoVUG^IWQs>wxl(F@Nde!`*st=g(Y5CLMeOFSZKKnU+O_z<>R`(hC>^6t= zluZfq=+%8eyM@3?S(Q( zD3(8vO)VyZBz~@_-wYmj*JC3}G{NlBf>a~<##t(JuBXYbL-@%fY>q7{O%p$z)mDy& z>_0vLQX;)6b$NkOZR5f6tKpZw*>@abBG~@|iZ3Srf?c0m%c30kHjsjb^ za|%{+#?J?f<%#866T!ZEPJGtfmKtNaY9;7^b{7IW{q`-;)Yiw3TKf+1Y(eGWYP04I zR|bx57|vE`8pPCvB4JwSQ0#FMR)qw`ti~En zBUf;hrNxp4-uQAE<3ON{5iLn0TBG_Ux6TAY`;8KK~)|I=i|nlu>Ul^cJKy6I*#D zAT#+V{15WzLIllDD&dk7M_Pgc|Ct)YTN|D7zg63O=CKGDM5sn98W04l#@Sur!5q8` zp@b_Wmi5CUy<(=zVo~)>>gmG!(eeFXTECLz7f?=^{R168+zU-dF_`{u#UC$U+J(_P zU`+b5`2+G1JWImz{|^{c)j+WK8kXSa;ir!LW&xFd@uT=VL-2guOAQ7u$63o6y~zJ1 ziKi9WCO->`E8vRJOVPi!}9-!qvr1b@XImew6(m` z-3i?Z0A*fo4ET3YTDsNUnmpYoTLqSK_YOxqEBJ?hibkerz-kABRp;*I`x%&r99SOx=rnDIvrZCPX+*>ni9{-_gV_6NxfCXmaiUf;^o$Z6Ni05 zB>MfVd5QOdwzZSwMNKb+Ea&ysnO15*70s2S5?2qBf;?0 z%?=ET25FY#d%@0ut@cZ57CoSJ-85K6R6r}-I>cV`T3i0%I|<&5N%JqAg>XY!Km^Oy zdX9NE1qq-!!(BEjoM_hH0*4ui%`EaZBqWCwN*~gL%kv3s~; zgeeaHr(UN4{%kWpV>H-Hmal%w2tKj%Z`IZ>SOS8VZ#Dq)vU?@A7A~RaV8KN`sVBPe z=)=mG@3?|trM0uhmM;uov(feFMRUrRgfocSX4ez4M4)OxRD5d4Xw<7xvBn_p3B@)1FnwX(VU=Sg>#ITA7R`GX9|lAVAbci#j6 z;w}kT-$Wa1H}b#gi^DzzeA>P}3AXmdNyyT(_^Qz7Dr{aPJ}WFB?V$; zT!Z9xmG{p?Ivsoq(as96=7H}ts;@5tXECc}mA*0p34PQu+B9nQq>lVpl%;x!!%{39 z2F>k|u#`bxCmytFXe~nfjbR#Is#6f8-(qTpI zJM*zqkJBtazTF`@Eiqy`8t2ucMUf;_(0tAb%|MRh`Toh}{^Wf_p;=?e)a7sp+Yk z`eeE7!63z*WF>fP99lyR0xiD061Oq*dD>Lg8&#w;_VNvN!Dr#)*vJ@-d zzYZ)t`F%|F;uz~09vxgcM}|#lO`RKqNl6t-J}GV^RwvY9aGvR+W=zz2KdLOagaikqzLvo_Nz_6oDL&eJVS`|@7Ks?rUwCxDU8)n2F5F9=Fuau&w_KXo z7Uay*y0!t?mysZ`Q180Mu~&?UDWUtzb=E`TB0S5xL}B<{sHRLj!D#KN-4X6V;*Wn& zz+~bIzj_?0n7$gKDezdrAmv5v*Nc&|sS~x_6py=O(kF1aGjqX!S2)?IrgPeBxHvow z(3NXw`_J9@z+FHRjfj6QL+LNvHrI-Wg43tSs~fWcGmUDgstL@x06@AgnD%Z?R6#tG zrcoVrFXDto&=a)05>CraO38|7F16nm|Lnf(8IkHW@-Bkb`6N8C^zf2BVlr*~`TZt_ z^ziWKp$3-poV(;VlJ1tLQlTI1SQfgvePD`b{T$sMhQI4`^~E81+|3C-dEOL63#Ge$ z#QHVk>E$*uac(wkW%;6Xy^03Jr4>aLwC=ASWUzQEkl+#Glv-1EpFOR7A9;4Wkl^~} zFJ-4DR>&W5wPAU%zuNAD7K9#rZ_nUv0Kegc^?IuXUd+``?x3a3s!e}2jd$>;d)(ZM z4?&CMZsEFW-a_trtY_D|*!Rp0ACv}$QCCZsBsS|Evx9Gr?<1<2qb?}xSMrmR7NW$m zFUcajj`sj{ORmVOX+hygLxDQabRg`~5KOY1E4k2wbP3spmmT?8 zf{ksCH%0RWHJB_!+9U$gwVV5mcBTiKX3&jKX9E5}z~L}_38vFVT+U{^v=<9Ka2xo+ zYdH_KB7u-4{)*fEyCPySdpC*?xn{}F-I|X@xl)=&nJf1GZkvAnn>AG6V3{#tuGojD zx5DRX{k3LFXYLk6*hV{TwO7?w&VHwxrU6ki5QFhG1t3OfVG$_q&&pHuzcB}Q5s zB3epbjP{%+KOSo`w@gZ)12l;}-$eyRWDDJFs1+Cou#;&uW7F8{w zb?b%PV!MbRDrAX&-joPS_Zav1rGWSV@u#E?USj*JPRNuLd%R)h8_KzOK$AEWK4mKf zX#6(ah2!`nW@1IzP$pbMLgX`+5>Q{sN}dJpB$sXJ*>Ts`8V2=yvA2g*6rNq-dCfF^ z>lds(o(JjPs^Dss4cxbp5r{JU&`?ljx#@iqnlSQ)q4$N$Q*qzNtmbcYiiPj|hhmS{ zJr~u4)B*nhGl&~{;-w*_jdmK`v14 zP*)|gnR%bKPUg3%@9jfFt(8`)f+an0q9k|^NlKVzuAlh*T;D)elMm(>qD!ZoWef~f z0|2&vA5OYOdU~FmFG(Q#lU``!fv&|WX|uIKnp-@slR=ynZ%Ko!kYbxK-q7l7_Bib*C@&8n#i6+L}8-xq=@a*Nf!_mzho34geq-Z^kN4d>&kD%OcN{uWSgUN_{pW zk^X_0;Y0{hFPkmHBW<(RTe2zvaDBM)oV&=g7l`K*Ky*O3wQV~j{*6hryBZIjzr>)< zG^vWuPdu3TRF!nDrw;tE@Cdup?ppZ3S|(ijyCQc{Fqp^4JCbrfKI2JPd!DQB7A6J- zX75cN2H9ji8SH+i0Jhp5^G_8g*p}OOW2AvRnZ^@86-PZRE*BlVYUe8~;ky=K3m5D; zcr=_`hZsFLU1Spy#!YqhdI$m8t+SR)D}|WXGRd;F@!S=f^_qKe4>CYz$3{vur8Bw3 z8E15LNF5IELf8`?u^Pfxy+yFU)ukn`AeTg^-*|{#Z`+3C0^7N93l^k@TBQkiIOF&J zpkE>yn`bD&Gs{uw%eXhxpmVfiA1-6e*{quS-bD}#SY=e%)nq=|G7U`VLs{icJ4?q> zMw2O`AeUZBeU6cS*b_j@^5C^nEQT8Aq3gu3IH`I+>ytAbPoM%@w?adj(x-GvOk97WYIHFLh>OKB3XGgzvEhY&PsF%;InVL?R~@^HmNY<1inJ zdtTtYMubkUI^GxxXqj1m8f2I(bSnSLZfv|&>t1UKVw^YBvY>p;^QO67*A;X`BI(4|%J9K~z8+e!w@Z zu|19VzTE1@Y4v z{Ui`5{rMuvpkuj-=_pa~+YsH5;~A?Pxebewu5>K2@>;hf>u)Z|)62)12u-tb7imSi zvE}MyyfpG~rO8-=>t698$BEDj-CtbrUyn4Vk=OyVbp!GW?89!E%fa|nxv8woaf9>0 z#_zP`UAR>|eVf8_U~0@n=dTw5mW_bgq!+!&QI@g+&X+6+gGS%J0Z;1BB+vrVY0~Zd z!X;@HrTT4!G5j)vJo1ih0RfH~RBKN7uriuWdJJEL zs-;1d$u_S9o1)^^MK94%9fvtF3>1psM}@9K3Lj9jNM7*b zg9hO1FIr6U?tHCewSWJQKYW(V&)G;%U5LASPRh)FjNV)bt+=ga$^3g@U6zd!EN}(w z*m-EX(SmA?vhgE)F7V2F5~aZK%Qm{5hAms`bk5LYrpybzpiV>veEzoMX9O1Ohp{Ep zP-ntxhRJsJEInMxoNh7+qK;z2Kl%o>bwJf_$lM&a{GahRT!gAZEYzICdA?a=mJvrr z^o}G}98QSm@<1#X4oB0>UwUwFD`3tlzE9oG8)w@;IxOJr?-df8dxyB;uXyhz;QXw% zw=uovYNf9_!fiT=y>jI+d4*snkq(@L9}Q=qsvVlR5QR$%9cyMjDb)_e7#+0x z(mZp&GE!nbL`3w=Oo!1UzW-gT@qsU)SF4Zdb3l| z&zdl_l8(gN;T1W7*z;~pk0=@dZP&ipQ-x){ON~6u1@EJvflbRz(00?tHm6qFKsdkO zeM-7^@C{xz6X|K5*T}KvdR{hC^$mf+DM|X?p#@f`=p#ML_lnc6`eRT`Kbu{Sl;gET zpxj2~QWD>B{&yTUxQ95ytjRmem&M;WJ%ToV@}o+=WBPzL*@n_2HDyZI&0aRpB*Fze z#8-Sg;n9aWoAF1AJkr>6biw!9Ah`s*^9jc-)I|ZmzNShxU$OXlS4D(r)RZy{LDH*Z zOlq6jAij?*G8fcokGT@`JHk`Q(M1a_EOCqM+;}z3IN0p7>$~{PqO3o*j;zRW40)v#^8#Qg>Cr=A4`>yn3k?m-WS7*4~$cUPjoyij)pYa?Z| zjCrWP=#`xIj;&ii%N_gc>H_Vr zJKOx928aQ{N;8X|8FLjIk{z3;CnsWxbJyce`MP56Wv-w0WbVI8CLk{le(_|KCoEDP z8o!T?%JDv;$~bhB>t*q9YobR-TcKx`l1vUDjEAAb-ToTBrBau7%}(0X#HC^cX3qRB){dGlt6?x7=(O}3{CN`nIUUjN09Fhj%ii$S1pvXi#R}4zQeM3y8 zWRRb(5;Vkv2AmS1=rU?#coU$>NYJ8iXa{S?X^jdalyNfP;^)!@Jd z7U_37sLoJc7hCa#LZ{*lF7|)gd%(*z+)ISt)OOi<%>EdSI7j49g7jTcC z_bw%7R|0CBzLX5`B$R=Nr@axYDK3ae3~T|6YJRH9QO}ihQ8u4tHnqFK*s}?p35)yb z5N4&pkD_;ycA}iZ#Dy3mUGG|TXPPO}_^YO8Q*z7jPepgD0LvNYhGrbLs!RW)$|`Md zB%5)ER^vp|pNGpaFdrx|!Y(_O=w0M2vB_9cHiZ*W4UgI}+k5<(mM3n?OH2C_PTaaL z!^#X(F~}Jp``>)xsv*2c#vm+h(_}N? z)!0HRwS%Z;X!whx?v}33i^YO!=JcBu(Pa6O^y&M{gWtoN;2=5>UI2 z3)P*N;I4k?x)RtKtV%b=IHvac$pM9Onv6; zO+QNOLRm{XJKjdRocwb5t=>MpywXnjy_PDF0j77API>-@TPr-OClQ_?StbhnIStL5 zh)wrvFYG*kQVi`^7>(mz-4F!pijI&xrI+Wt*Kb(5-~6P+AqUnh`DgVihQ?)@Ld)2B z0Fj#HqV_f+ae@6^kguBcpeg?QA^xrt5sg*kcyohK0bcAb-H2)d{cQL}|48zNkAd50 z7EH1=H+rSd6M{QB06Nqd(F=W41e42}(w+ihQZr}KGi2N=hI;f()9*FTKbf z2$3pn>6(wQbgB;hjG${qD%~0{eDt$#dpS%`{Ri{&B>8>spQ9D?T>VA}*3i7!YvCcD h=_6a-;@8lbv;lc3lyvSXM-8*v|M^Y-_lFt)_%DT|I|={* literal 0 HcmV?d00001 diff --git a/public/sponsors/jetbrains.webp b/public/sponsors/jetbrains.webp new file mode 100644 index 0000000000000000000000000000000000000000..c5c5de50514e1cd590966fecd20aeae34683b145 GIT binary patch literal 11902 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 import('./pages/Home')); const About = lazy(() => import('./pages/About')); const Speakers = lazy(() => import('./pages/Speakers')); const Sponsor = lazy(() => import('./pages/Sponsor')); +const Sponsors = lazy(() => import('./pages/Sponsors')); const TicketSponsor = lazy(() => import('./pages/TicketSponsor')); const Grants = lazy(() => import('./pages/Grants')); const Attend = lazy(() => import('./pages/Attend')); @@ -81,6 +82,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/Navbar.jsx b/src/components/Navbar.jsx index f5c9c20..b1a32bd 100644 --- a/src/components/Navbar.jsx +++ b/src/components/Navbar.jsx @@ -54,7 +54,7 @@ const Navbar = () => { isActive ? "active" : ""}>{t('nav.about')} isActive ? "active" : ""}>{t('nav.agenda')} isActive ? "active" : ""}>{t('nav.speakers')} - isActive ? "active" : ""}>{t('nav.sponsor')} + isActive ? "active" : ""}>{t('nav.sponsor')} isActive ? "active" : ""}>{t('nav.attend')} isActive ? "active" : ""}>{t('nav.venue')} isActive ? "active" : ""} style={{ display: 'flex', alignItems: 'center', gap: '4px' }}> @@ -91,7 +91,7 @@ const Navbar = () => { isActive ? "active" : ""}>{t('nav.about')} isActive ? "active" : ""}>{t('nav.agenda')} isActive ? "active" : ""}>{t('nav.speakers')} - isActive ? "active" : ""}>{t('nav.sponsor')} + isActive ? "active" : ""}>{t('nav.sponsor')} isActive ? "active" : ""}>{t('nav.attend')} isActive ? "active" : ""}>{t('nav.venue')} isActive ? "active" : ""} style={{ display: 'flex', alignItems: 'center', gap: '6px' }}> diff --git a/src/components/SponsorShowcase.jsx b/src/components/SponsorShowcase.jsx new file mode 100644 index 0000000..bcbaf22 --- /dev/null +++ b/src/components/SponsorShowcase.jsx @@ -0,0 +1,187 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { ArrowUpRight } from 'lucide-react'; +import { sponsorsByTier, sponsorsList as defaultSponsorsList } from '../data/sponsorsList'; +import { useLocalizedPath } from '../hooks/useLocalizedPath'; + +const getDescription = (sponsor, lang) => + sponsor.description?.[lang] ?? sponsor.description?.en ?? ''; + +const DiamondCard = ({ sponsor, t, lang }) => ( + +); + +const GoldCard = ({ sponsor, t, lang }) => ( +
+
+
+ {sponsor.name} +
+
+

{sponsor.name}

+ {t('sponsors.tierBadge.gold')} +
+
+

{getDescription(sponsor, lang)}

+ {sponsor.website && ( + + {t('sponsors.visitWebsite')} + + )} +
+); + +const SilverCard = ({ sponsor, lang }) => ( +
+
+ {sponsor.name} +
+ {sponsor.website ? ( + + {sponsor.name} + + ) : ( +

{sponsor.name}

+ )} +

{getDescription(sponsor, lang)}

+
+); + +// Shared logo-tile layout for the "bronze" and "community" tiers. +const LogoTile = ({ sponsor, tier, lang }) => { + const Tag = sponsor.website ? 'a' : 'div'; + const linkProps = sponsor.website + ? { href: sponsor.website, target: '_blank', rel: 'noopener noreferrer' } + : {}; + const description = getDescription(sponsor, lang); + + return ( + +
+ {sponsor.name} +
+ {sponsor.name} + {description && ( + {description} + )} +
+ ); +}; + +const SponsorShowcase = ({ sponsors = defaultSponsorsList }) => { + const { t, i18n } = useTranslation(); + const { l } = useLocalizedPath(); + const groups = sponsorsByTier(sponsors); + + if (groups.length === 0) { + return ( +
+
+
+

{t('sponsors.emptyTitle')}

+

{t('sponsors.emptyText')}

+ + {t('sponsors.becomeSponsor')} + +
+
+
+ ); + } + + return ( +
+
+ {groups.map(({ tier, sponsors: tierSponsors }) => ( +
+
+ + {t(`sponsors.tierLabels.${tier}`)} + + +
+ + {tier === 'diamond' && ( +
+ {tierSponsors.map((sponsor) => ( + + ))} +
+ )} + + {tier === 'gold' && ( +
+ {tierSponsors.map((sponsor) => ( + + ))} +
+ )} + + {tier === 'silver' && ( +
+ {tierSponsors.map((sponsor) => ( + + ))} +
+ )} + + {tier === 'bronze' && ( +
+ {tierSponsors.map((sponsor) => ( + + ))} +
+ )} + + {tier === 'community' && ( +
+ {tierSponsors.map((sponsor) => ( + + ))} +
+ )} +
+ ))} + +
+

{t('sponsors.joinList')}

+ + {t('sponsors.becomeSponsor')} + +
+
+
+ ); +}; + +export default SponsorShowcase; diff --git a/src/data/sponsorsList.js b/src/data/sponsorsList.js new file mode 100644 index 0000000..4843cef --- /dev/null +++ b/src/data/sponsorsList.js @@ -0,0 +1,95 @@ +export const TIERS = ['diamond', 'gold', 'silver', 'bronze', 'community']; + +/** + * @typedef {Object} Sponsor + * @property {string} id - unique, slug + * @property {string} name + * @property {'diamond'|'gold'|'silver'|'bronze'|'community'} tier + * @property {string} logo - square, ~400x400, transparent or white background + * @property {string} [website] - optional + * @property {{ fr: string, en: string }} description - 1 to 3 sentences + */ + +// { +// id: 'acme', // unique, slug +// name: 'ACME Corp', +// tier: 'gold', // 'diamond' | 'gold' | 'silver' | 'bronze' | 'community' +// logo: '/sponsors/acme.webp', // carré, ~400×400, fond transparent ou blanc +// website: 'https://acme.example', // optionnel +// description: { fr: '…', en: '…' } // 1 à 3 phrases +// } + +/** @type {Sponsor[]} */ +export const sponsorsList = [ + { + id: 'canonical', + name: 'Canonical', + tier: 'diamond', + logo: '/sponsors/canonical.webp', + website: 'https://canonical.com', + description: { + fr: "Canonical est l'éditeur d'Ubuntu, le système d'exploitation open source le plus utilisé pour le cloud, les serveurs et le poste de travail. Canonical soutient les communautés open source à travers le monde.", + en: 'Canonical is the publisher of Ubuntu, the most widely used open source operating system for cloud, servers and desktops. Canonical supports open source communities around the world.', + }, + }, + { + id: 'apify', + name: 'Apify', + tier: 'bronze', + logo: '/sponsors/apify.svg', + website: 'https://apify.com', + description: { + fr: "Plateforme de web scraping et d'automatisation, avec un SDK Python.", + en: 'Web scraping and automation platform with a Python SDK.', + }, + }, + { + id: 'black-python-devs', + name: 'Black Python Devs', + tier: 'community', + logo: '/sponsors/bpd.webp', + website: 'https://blackpythondevs.com', + description: { + fr: 'Communauté mondiale qui accompagne les développeurs Python noirs et de couleur.', + en: 'A global community supporting Black and people of colour Python developers.', + }, + }, + { + id: 'chaoss', + name: 'CHAOSS', + tier: 'community', + logo: '/sponsors/chaoss.webp', + website: 'https://chaoss.community', + description: { + fr: 'Projet de la Linux Foundation dédié à la santé et à la pérennité des communautés open source.', + en: 'A Linux Foundation project focused on the health and sustainability of open source communities.', + }, + }, + { + id: 'dsf', + name: 'Django Software Foundation', + tier: 'community', + logo: '/sponsors/dsf.webp', + website: 'https://www.djangoproject.com/foundation/', + description: { + fr: 'La Django Software Foundation soutient le développement et la communauté du framework Django.', + en: 'The Django Software Foundation supports the development and community of the Django web framework.', + }, + }, + { + id: 'jetbrains', + name: 'JetBrains', + tier: 'community', + logo: '/sponsors/jetbrains.webp', + website: 'https://www.jetbrains.com', + description: { + fr: "JetBrains crée des outils de développement logiciel intelligents utilisés par plus de 11,4 millions de professionnels et 88 entreprises du Fortune Global Top 100. Notre gamme de plus de 30 produits comprend des IDE primés, dont PyCharm — l'IDE de référence pour un développement Python productif et efficace. Nous proposons également l'assistant de codage JetBrains AI ainsi que l'agent de codage Junie.", + en: 'JetBrains creates intelligent software development tools used by over 11.4 million professionals and 88 Fortune Global Top 100 companies. Our lineup of more than 30 products includes award-winning IDEs, including PyCharm -- the go-to IDE for productive and efficient Python development. We also offer the JetBrains AI-powered coding assistant, and coding agent Junie.', + }, + }, +]; + +export const sponsorsByTier = (list) => + TIERS.map((tier) => ({ tier, sponsors: list.filter((s) => s.tier === tier) })).filter( + (g) => g.sponsors.length + ); diff --git a/src/i18n/en.json b/src/i18n/en.json index 36121af..f36ae58 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -3,7 +3,7 @@ "about": "About", "agenda": "Agenda", "speakers": "Speakers", - "sponsor": "Sponsor", + "sponsor": "Sponsors", "attend": "Attend", "venue": "Venue", "ubucon": "UbuCon" @@ -240,6 +240,30 @@ "getInvolvedText": "Join companies that are leading the way in supporting the Python community in Africa.", "contactSponsor": "Contact Us for Sponsorship" }, + "sponsors": { + "title": "Our", + "titleHighlight": "Sponsors", + "subtitle": "They make PyCon Cameroon 2026 possible. Thank you!", + "becomeSponsor": "Become a sponsor", + "tierLabels": { + "diamond": "Diamond Sponsor", + "gold": "Gold Sponsors", + "silver": "Silver Sponsors", + "bronze": "Bronze Sponsors", + "community": "Community Sponsors" + }, + "tierBadge": { + "diamond": "Diamond", + "gold": "Gold", + "silver": "Silver", + "bronze": "Bronze", + "community": "Community" + }, + "visitWebsite": "Visit website", + "joinList": "Want to join this list?", + "emptyTitle": "2026 sponsors coming soon", + "emptyText": "Your company could be the first one featured here." + }, "attend": { "title": "Attend", "titleHighlight": "PyCon", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index cf3462c..db1abae 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -3,7 +3,7 @@ "about": "\u00c0 propos", "agenda": "Agenda", "speakers": "Intervenants", - "sponsor": "Sponsoriser", + "sponsor": "Sponsors", "attend": "Participer", "venue": "Lieu", "ubucon": "UbuCon" @@ -240,6 +240,30 @@ "getInvolvedText": "Rejoignez les entreprises qui ouvrent la voie en soutenant la communaut\u00e9 Python en Afrique.", "contactSponsor": "Contactez-nous pour le sponsoring" }, + "sponsors": { + "title": "Nos", + "titleHighlight": "Sponsors", + "subtitle": "Ils rendent PyCon Cameroun 2026 possible. Merci !", + "becomeSponsor": "Devenir sponsor", + "tierLabels": { + "diamond": "Sponsor Diamond", + "gold": "Sponsors Gold", + "silver": "Sponsors Silver", + "bronze": "Sponsors Bronze", + "community": "Sponsors Communaut\u00e9" + }, + "tierBadge": { + "diamond": "Diamond", + "gold": "Gold", + "silver": "Silver", + "bronze": "Bronze", + "community": "Community" + }, + "visitWebsite": "Visiter le site", + "joinList": "Vous souhaitez rejoindre cette liste ?", + "emptyTitle": "Les sponsors 2026 arrivent", + "emptyText": "Votre entreprise pourrait \u00eatre la premi\u00e8re affich\u00e9e ici." + }, "attend": { "title": "Participer \u00e0", "titleHighlight": "PyCon", diff --git a/src/index.css b/src/index.css index a783369..04c17ae 100644 --- a/src/index.css +++ b/src/index.css @@ -295,6 +295,42 @@ color: #8b5a2b; } +[data-theme="light"] .sponsor-showcase-card.diamond { + border-color: #00b8d4; +} + +[data-theme="light"] .sponsor-showcase-badge.diamond, +[data-theme="light"] .sponsor-tier-heading-label.diamond { + color: #00838f; +} + +[data-theme="light"] .sponsor-showcase-card.gold { + border-color: #c9a800; +} + +[data-theme="light"] .sponsor-showcase-badge.gold, +[data-theme="light"] .sponsor-tier-heading-label.gold { + color: #b8860b; +} + +[data-theme="light"] .sponsor-showcase-card.silver { + border-color: #909090; +} + +[data-theme="light"] .sponsor-showcase-badge.silver, +[data-theme="light"] .sponsor-tier-heading-label.silver { + color: #666666; +} + +[data-theme="light"] .sponsor-showcase-badge.bronze, +[data-theme="light"] .sponsor-tier-heading-label.bronze { + color: #8b5a2b; +} + +[data-theme="light"] .sponsor-showcase-tile-desc { + background: #f5f5f5; +} + [data-theme="light"] .btn-tier.btn-diamond { background: #00b8d4; color: #ffffff; @@ -3003,6 +3039,324 @@ img { font-weight: bold; } +/* =================================== + Sponsor showcase + =================================== */ +.sponsor-tier-block { + margin-bottom: var(--spacing-lg); +} + +.sponsor-tier-block:last-of-type { + margin-bottom: var(--spacing-md); +} + +.sponsor-tier-heading { + display: flex; + align-items: center; + gap: var(--spacing-sm); + margin-bottom: var(--spacing-md); +} + +.sponsor-tier-heading-label { + font-size: 0.875rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 2px; + white-space: nowrap; +} + +.sponsor-tier-heading-label.diamond { + color: #b9f2ff; +} + +.sponsor-tier-heading-label.gold { + color: #ffd700; +} + +.sponsor-tier-heading-label.silver { + color: #c0c0c0; +} + +.sponsor-tier-heading-label.bronze { + color: #cd7f32; +} + +.sponsor-tier-heading-label.community { + color: var(--color-green); +} + +.sponsor-tier-heading-line { + flex: 1; + height: 1px; + background: var(--color-border); +} + +.sponsor-showcase-badge { + display: inline-block; + border: 1px solid currentColor; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 2px; + padding: 4px 12px; + border-radius: 50px; + margin-bottom: var(--spacing-xs); +} + +.sponsor-showcase-badge.diamond { + color: #b9f2ff; +} + +.sponsor-showcase-badge.gold { + color: #ffd700; +} + +.sponsor-showcase-badge.silver { + color: #c0c0c0; +} + +.sponsor-showcase-badge.bronze { + color: #cd7f32; +} + +.sponsor-showcase-badge.community { + color: var(--color-green); +} + +.sponsor-showcase-name { + margin-bottom: var(--spacing-xs); +} + +.sponsor-showcase-desc { + color: var(--color-text-secondary); + margin-bottom: var(--spacing-sm); +} + +.sponsor-showcase-link { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--color-orange); + font-weight: 600; +} + +.sponsor-showcase-link:hover { + text-decoration: underline; +} + +/* Diamond */ +.sponsor-showcase-diamond-list { + display: flex; + flex-direction: column; + gap: var(--spacing-md); +} + +.sponsor-showcase-card.diamond { + display: flex; + align-items: center; + gap: var(--spacing-lg); + background: linear-gradient(145deg, var(--color-dark) 0%, var(--color-dark-alt) 100%); + border: 2px solid #b9f2ff; + padding: var(--spacing-lg); +} + +.sponsor-showcase-logo.diamond { + width: 220px; + height: 220px; + background: #ffffff; + border-radius: 20px; + padding: 12px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.sponsor-showcase-logo img { + width: 100%; + height: 100%; + object-fit: contain; +} + +@media (max-width: 768px) { + .sponsor-showcase-card.diamond { + flex-direction: column; + text-align: center; + } + + .sponsor-showcase-logo.diamond { + width: 160px; + height: 160px; + margin: 0 auto; + } +} + +/* Gold */ +.sponsor-showcase-card.gold { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); + border: 2px solid #ffd700; +} + +.sponsor-showcase-gold-head { + display: flex; + align-items: center; + gap: var(--spacing-sm); +} + +.sponsor-showcase-logo.gold { + width: 96px; + height: 96px; + background: #ffffff; + border-radius: 16px; + padding: 10px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.sponsor-showcase-card.gold .sponsor-showcase-name { + font-size: 1.5rem; +} + +.sponsor-showcase-card.gold .sponsor-showcase-desc { + font-size: 0.95rem; +} + +/* Silver */ +.sponsor-showcase-card.silver { + border: 1px solid #c0c0c0; + padding: 1.5rem; + text-align: center; +} + +.sponsor-showcase-logo.silver { + width: 72px; + height: 72px; + background: #ffffff; + border-radius: 12px; + padding: 8px; + margin: 0 auto var(--spacing-sm); + display: flex; + align-items: center; + justify-content: center; +} + +.sponsor-showcase-card.silver .sponsor-showcase-name { + display: block; + font-size: 1.1rem; + font-weight: 600; +} + +a.sponsor-showcase-name.silver-link:hover { + color: var(--color-orange); +} + +.sponsor-showcase-card.silver .sponsor-showcase-desc { + font-size: 0.9rem; + color: var(--color-text-muted); + margin-bottom: 0; +} + +/* Bronze & Community (shared logo-tile layout) */ +.sponsor-showcase-tile { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; + padding: var(--spacing-md); + text-decoration: none; + position: relative; + overflow: hidden; + min-height: 220px; +} + +.sponsor-showcase-tile-logo { + width: 96px; + height: 64px; + background: #ffffff; + border-radius: 12px; + padding: 8px; + border: 2px solid var(--color-border); + margin-bottom: var(--spacing-sm); + display: flex; + align-items: center; + justify-content: center; +} + +.sponsor-showcase-tile-logo img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.sponsor-showcase-tile-name { + font-size: 0.9rem; + font-weight: 600; +} + +.sponsor-showcase-tile-desc { + position: absolute; + inset: 0; + display: flex; + align-items: flex-start; + justify-content: center; + padding: 1rem var(--spacing-sm); + background: var(--color-dark-alt); + color: var(--color-text-secondary); + font-size: 0.8rem; + line-height: 1.45; + text-align: center; + border-radius: inherit; + opacity: 0; + transition: opacity var(--transition-normal); + overflow-y: auto; + pointer-events: none; +} + +.sponsor-showcase-tile:hover .sponsor-showcase-tile-desc, +.sponsor-showcase-tile:focus-visible .sponsor-showcase-tile-desc { + opacity: 1; +} + +@media (hover: none), (max-width: 768px) { + .sponsor-showcase-tile { + min-height: 0; + } + + .sponsor-showcase-tile-desc { + position: static; + inset: auto; + opacity: 1; + padding: 0; + margin-top: var(--spacing-xs); + background: transparent; + border-radius: 0; + overflow: visible; + pointer-events: auto; + } +} + +@media (prefers-reduced-motion: reduce) { + .sponsor-showcase-tile-desc { + transition: none; + } +} + +/* Empty state */ +.sponsor-showcase-empty { + max-width: 720px; + margin: 0 auto; + padding: var(--spacing-lg); + text-align: center; +} + +.sponsor-showcase-empty .grid { + margin: var(--spacing-md) 0 var(--spacing-lg); +} + /* =================================== Venue Page Specific Styles =================================== */ diff --git a/src/pages/Sponsors.jsx b/src/pages/Sponsors.jsx new file mode 100644 index 0000000..d5e2bab --- /dev/null +++ b/src/pages/Sponsors.jsx @@ -0,0 +1,24 @@ +import { useTranslation } from 'react-i18next'; +import useScrollAnimation from '../hooks/useScrollAnimation'; +import SponsorShowcase from '../components/SponsorShowcase'; + +const Sponsors = () => { + const { t } = useTranslation(); + useScrollAnimation(); + + return ( + <> + {/* Page Header */} +
+
+

{t('sponsors.title')} {t('sponsors.titleHighlight')}

+

{t('sponsors.subtitle')}

+
+
+ + + + ); +}; + +export default Sponsors; From 9ced7c8369009040fdd197950ab93052a97ea586 Mon Sep 17 00:00:00 2001 From: Edmond Date: Wed, 26 Aug 2026 10:43:10 +0100 Subject: [PATCH 2/4] feat: Add scrolling sponsor logo marquee to the home page Shown below the keynote speakers section, fed by sponsorsList; logos are not clickable, a "See all our sponsors" button links to the Sponsors page. Co-Authored-By: Claude Fable 5 --- src/components/SponsorMarquee.jsx | 52 ++++++++++++++++++ src/i18n/en.json | 6 ++- src/i18n/fr.json | 6 ++- src/index.css | 90 ++++++++++++++++++++++++++++++- src/pages/Home.jsx | 4 ++ 5 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/components/SponsorMarquee.jsx diff --git a/src/components/SponsorMarquee.jsx b/src/components/SponsorMarquee.jsx new file mode 100644 index 0000000..91b46b5 --- /dev/null +++ b/src/components/SponsorMarquee.jsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { sponsorsList } from '../data/sponsorsList'; +import { useLocalizedPath } from '../hooks/useLocalizedPath'; + +const SponsorMarquee = () => { + const { t } = useTranslation(); + const { l } = useLocalizedPath(); + + if (sponsorsList.length === 0) return null; + + const repeats = Math.max(1, Math.ceil(12 / sponsorsList.length)); + const logos = Array.from({ length: repeats }).flatMap((_, i) => + sponsorsList.map((sponsor) => ({ ...sponsor, key: `${sponsor.id}-${i}` })) + ); + + return ( +
+
+
+

{t('home.sponsorsTitle')} {t('home.sponsorsHighlight')}

+
+
+
+
+
+ {logos.map((sponsor) => ( +
+ {sponsor.name} +
+ ))} +
+ +
+
+
+ + {t('home.sponsorsAll')} + +
+
+ ); +}; + +export default SponsorMarquee; diff --git a/src/i18n/en.json b/src/i18n/en.json index f36ae58..31cd549 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -75,7 +75,11 @@ "readyToJoin": "Ready to Join Us?", "readyToJoinText": "Be part of Cameroon's first PyCon and help shape the future of Python in Africa.", "submitATalk": "Submit a Talk", - "sponsorshipDeck": "Sponsorship Deck" + "sponsorshipDeck": "Sponsorship Deck", + "sponsorsTitle": "Our", + "sponsorsHighlight": "Sponsors", + "sponsorsAll": "See all our sponsors", + "sponsorsAria": "Our sponsors' logos" }, "about": { "title": "About", diff --git a/src/i18n/fr.json b/src/i18n/fr.json index db1abae..a3dd4d9 100644 --- a/src/i18n/fr.json +++ b/src/i18n/fr.json @@ -75,7 +75,11 @@ "readyToJoin": "Pr\u00eat \u00e0 nous rejoindre ?", "readyToJoinText": "Faites partie du premier PyCon du Cameroun et contribuez \u00e0 fa\u00e7onner l'avenir de Python en Afrique.", "submitATalk": "Soumettre une conf\u00e9rence", - "sponsorshipDeck": "Dossier de sponsoring" + "sponsorshipDeck": "Dossier de sponsoring", + "sponsorsTitle": "Nos", + "sponsorsHighlight": "Sponsors", + "sponsorsAll": "Voir tous nos sponsors", + "sponsorsAria": "Logos de nos sponsors" }, "about": { "title": "\u00c0 propos de", diff --git a/src/index.css b/src/index.css index 04c17ae..a882886 100644 --- a/src/index.css +++ b/src/index.css @@ -3554,4 +3554,92 @@ a.sponsor-showcase-name.silver-link:hover { top: var(--spacing-xs); right: var(--spacing-xs); } -} \ No newline at end of file +} +/* =================================== + Sponsor marquee (home) + =================================== */ +.sponsor-marquee { + overflow: hidden; + width: 100%; + padding: var(--spacing-sm) 0; + -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); + mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent); +} + +.sponsor-marquee-track { + display: flex; + width: max-content; + animation: sponsor-marquee 40s linear infinite; +} + +.sponsor-marquee:hover .sponsor-marquee-track { + animation-play-state: paused; +} + +.sponsor-marquee-group { + display: flex; + align-items: center; + gap: var(--spacing-lg); + padding-right: var(--spacing-lg); + flex-shrink: 0; +} + +.sponsor-marquee-logo { + height: 72px; + width: 150px; + background: #ffffff; + border-radius: 12px; + padding: 10px; + border: 2px solid var(--color-border); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} + +.sponsor-marquee-logo img { + max-width: 100%; + max-height: 100%; + width: auto; + height: auto; + object-fit: contain; +} + +@keyframes sponsor-marquee { + from { + transform: translateX(0); + } + to { + transform: translateX(-50%); + } +} + + +@media (prefers-reduced-motion: reduce) { + .sponsor-marquee-track { + animation: none; + flex-wrap: wrap; + justify-content: center; + width: 100%; + } + + .sponsor-marquee-group[aria-hidden="true"] { + display: none; + } + + .sponsor-marquee { + mask-image: none; + -webkit-mask-image: none; + } +} + +@media (max-width: 768px) { + .sponsor-marquee-logo { + height: 56px; + width: 120px; + } + + .sponsor-marquee-group { + gap: var(--spacing-md); + } +} diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index 21791a6..c028250 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -7,6 +7,7 @@ import useScrollAnimation from '../hooks/useScrollAnimation'; import LazyImage from '../components/LazyImage'; import TrackSection from '../components/TrackSection'; import KeynoteCard from '../components/KeynoteCard'; +import SponsorMarquee from '../components/SponsorMarquee'; import { sponsorshipDeckUrl } from '../data/sponsors'; import { speakers } from '../data/speakers'; import { keynoteSessions } from '../data/agenda'; @@ -117,6 +118,9 @@ const Home = () => { )} + {/* Sponsors marquee */} + + {/* Features Section */}
From 53bfc051c8d15c98c560af36cc9b1df171af0374 Mon Sep 17 00:00:00 2001 From: Edmond Date: Wed, 26 Aug 2026 10:47:08 +0100 Subject: [PATCH 3/4] feat: Show sponsor descriptions in a floating popover instead of a full-tile overlay Bronze and Community tiles used to reveal the description in an overlay covering the whole tile (logo included), with an inner scrollbar for long texts. They now show a floating card above the tile on hover/focus (tap on touch devices), flipping below when there is no room at the top. Tile state is carried by data attributes rather than className so the scroll-in animation class added by useScrollAnimation is never wiped by a re-render. Co-Authored-By: Claude Fable 5 --- src/components/SponsorShowcase.jsx | 107 +++++++++++++++-- src/index.css | 178 +++++++++++++++++++++++------ 2 files changed, 237 insertions(+), 48 deletions(-) diff --git a/src/components/SponsorShowcase.jsx b/src/components/SponsorShowcase.jsx index bcbaf22..05f7ca1 100644 --- a/src/components/SponsorShowcase.jsx +++ b/src/components/SponsorShowcase.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useEffect, useId, useRef, useState } from 'react'; import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { ArrowUpRight } from 'lucide-react'; @@ -78,23 +78,106 @@ const SilverCard = ({ sponsor, lang }) => ( ); // Shared logo-tile layout for the "bronze" and "community" tiers. -const LogoTile = ({ sponsor, tier, lang }) => { - const Tag = sponsor.website ? 'a' : 'div'; - const linkProps = sponsor.website - ? { href: sponsor.website, target: '_blank', rel: 'noopener noreferrer' } - : {}; +// The description lives in a floating popover shown on hover/focus +// (desktop) or tap (touch devices) rather than inline. +const LogoTile = ({ sponsor, tier, lang, t }) => { const description = getDescription(sponsor, lang); + const popoverId = useId(); + const tileRef = useRef(null); + const [open, setOpen] = useState(false); + const [isBelow, setIsBelow] = useState(false); + + const isTouchDevice = () => + typeof window !== 'undefined' && window.matchMedia?.('(hover: none)').matches; + + const updatePlacement = () => { + const top = tileRef.current?.getBoundingClientRect().top ?? Infinity; + setIsBelow(top < 300); + }; + + const handleTileClick = (e) => { + if (e.target.closest('a')) return; // let real links navigate normally + if (!isTouchDevice()) return; + updatePlacement(); + setOpen((v) => !v); + }; + + useEffect(() => { + if (!open) return undefined; + + const handlePointerDown = (e) => { + if (!tileRef.current?.contains(e.target)) setOpen(false); + }; + const handleKeyDown = (e) => { + if (e.key === 'Escape') setOpen(false); + }; + document.addEventListener('pointerdown', handlePointerDown); + document.addEventListener('keydown', handleKeyDown); + return () => { + document.removeEventListener('pointerdown', handlePointerDown); + document.removeEventListener('keydown', handleKeyDown); + }; + }, [open]); return ( - +
{sponsor.name}
- {sponsor.name} + {sponsor.website ? ( + + {sponsor.name} + + ) : ( + {sponsor.name} + )} {description && ( - {description} +
+
+
+ +
+
+
{sponsor.name}
+ + {t(`sponsors.tierBadge.${tier}`)} + +
+
+

{description}

+ {sponsor.website && ( + + {t('sponsors.visitWebsite')} + + )} +
)} - +
); }; @@ -158,7 +241,7 @@ const SponsorShowcase = ({ sponsors = defaultSponsorsList }) => { {tier === 'bronze' && (
{tierSponsors.map((sponsor) => ( - + ))}
)} @@ -166,7 +249,7 @@ const SponsorShowcase = ({ sponsors = defaultSponsorsList }) => { {tier === 'community' && (
{tierSponsors.map((sponsor) => ( - + ))}
)} diff --git a/src/index.css b/src/index.css index a882886..7e686a3 100644 --- a/src/index.css +++ b/src/index.css @@ -327,10 +327,6 @@ color: #8b5a2b; } -[data-theme="light"] .sponsor-showcase-tile-desc { - background: #f5f5f5; -} - [data-theme="light"] .btn-tier.btn-diamond { background: #00b8d4; color: #ffffff; @@ -3267,10 +3263,21 @@ a.sponsor-showcase-name.silver-link:hover { justify-content: center; text-align: center; padding: var(--spacing-md); - text-decoration: none; position: relative; - overflow: hidden; - min-height: 220px; + overflow: visible; + z-index: 1; +} + +@media (hover: hover) { + .sponsor-showcase-tile:hover { + z-index: 30; + } +} + +.sponsor-showcase-tile:focus-within, +.sponsor-showcase-tile:focus-visible, +.sponsor-showcase-tile[data-open="true"] { + z-index: 30; } .sponsor-showcase-tile-logo { @@ -3297,50 +3304,149 @@ a.sponsor-showcase-name.silver-link:hover { font-weight: 600; } -.sponsor-showcase-tile-desc { +a.sponsor-showcase-tile-name:hover { + color: var(--color-orange); +} + +/* Floating popover with the sponsor's full description */ +.sponsor-showcase-popover { position: absolute; - inset: 0; - display: flex; - align-items: flex-start; - justify-content: center; - padding: 1rem var(--spacing-sm); + bottom: calc(100% + 12px); + left: 50%; + width: min(340px, 90vw); + max-width: calc(100vw - 2rem); background: var(--color-dark-alt); - color: var(--color-text-secondary); - font-size: 0.8rem; - line-height: 1.45; - text-align: center; - border-radius: inherit; + border: 1px solid var(--color-orange); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + padding: var(--spacing-sm); + text-align: left; opacity: 0; - transition: opacity var(--transition-normal); - overflow-y: auto; pointer-events: none; + transform: translateX(-50%) translateY(6px) scale(0.98); + transition: opacity var(--transition-normal), transform var(--transition-normal); + z-index: 40; } -.sponsor-showcase-tile:hover .sponsor-showcase-tile-desc, -.sponsor-showcase-tile:focus-visible .sponsor-showcase-tile-desc { - opacity: 1; +/* Invisible bridge so the pointer doesn't lose :hover crossing the gap */ +.sponsor-showcase-popover::before { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: -12px; + height: 12px; } -@media (hover: none), (max-width: 768px) { - .sponsor-showcase-tile { - min-height: 0; - } +.sponsor-showcase-popover::after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + width: 10px; + height: 10px; + margin-top: -5px; + background: var(--color-dark-alt); + border-right: 1px solid var(--color-orange); + border-bottom: 1px solid var(--color-orange); + transform: translateX(-50%) rotate(45deg); +} - .sponsor-showcase-tile-desc { - position: static; - inset: auto; +@media (hover: hover) { + .sponsor-showcase-tile:hover .sponsor-showcase-popover { opacity: 1; - padding: 0; - margin-top: var(--spacing-xs); - background: transparent; - border-radius: 0; - overflow: visible; pointer-events: auto; + transform: translateX(-50%) translateY(0) scale(1); } } +.sponsor-showcase-tile:focus-within .sponsor-showcase-popover, +.sponsor-showcase-tile:focus-visible .sponsor-showcase-popover, +.sponsor-showcase-tile[data-open="true"] .sponsor-showcase-popover { + opacity: 1; + pointer-events: auto; + transform: translateX(-50%) translateY(0) scale(1); +} + +/* Flip below the tile when there isn't enough room above (set via JS) */ +.sponsor-showcase-tile[data-placement="below"] .sponsor-showcase-popover { + bottom: auto; + top: calc(100% + 12px); + transform: translateX(-50%) translateY(-6px) scale(0.98); +} + +.sponsor-showcase-tile[data-placement="below"] .sponsor-showcase-popover::before { + top: -12px; + bottom: auto; +} + +.sponsor-showcase-tile[data-placement="below"] .sponsor-showcase-popover::after { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: -5px; + border-right: none; + border-bottom: none; + border-left: 1px solid var(--color-orange); + border-top: 1px solid var(--color-orange); +} + +.sponsor-showcase-popover-head { + display: flex; + align-items: center; + gap: 0.6rem; + margin-bottom: 0.6rem; +} + +.sponsor-showcase-popover-logo { + width: 40px; + height: 40px; + background: #ffffff; + border-radius: 8px; + padding: 4px; + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: center; +} + +.sponsor-showcase-popover-logo img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.sponsor-showcase-popover-name { + font-family: var(--font-ui); + font-weight: 600; + font-size: 0.95rem; + margin-bottom: 2px; +} + +.sponsor-showcase-popover-desc { + font-size: 0.85rem; + line-height: 1.55; + color: var(--color-text-secondary); + max-height: 240px; + overflow-y: auto; + margin: 0 0 0.6rem; +} + +.sponsor-showcase-popover-link { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--color-orange); + font-weight: 600; + font-size: 0.85rem; +} + +.sponsor-showcase-popover-link:hover { + text-decoration: underline; +} + @media (prefers-reduced-motion: reduce) { - .sponsor-showcase-tile-desc { + .sponsor-showcase-popover { transition: none; } } From adfc3079efa3488cae5700ba96888b3be43eadf9 Mon Sep 17 00:00:00 2001 From: Edmond Date: Wed, 26 Aug 2026 11:34:45 +0100 Subject: [PATCH 4/4] feat: Update sponsors descriptions --- src/data/sponsorsList.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/data/sponsorsList.js b/src/data/sponsorsList.js index 4843cef..f92d0c8 100644 --- a/src/data/sponsorsList.js +++ b/src/data/sponsorsList.js @@ -28,8 +28,8 @@ export const sponsorsList = [ logo: '/sponsors/canonical.webp', website: 'https://canonical.com', description: { - fr: "Canonical est l'éditeur d'Ubuntu, le système d'exploitation open source le plus utilisé pour le cloud, les serveurs et le poste de travail. Canonical soutient les communautés open source à travers le monde.", - en: 'Canonical is the publisher of Ubuntu, the most widely used open source operating system for cloud, servers and desktops. Canonical supports open source communities around the world.', + fr: "Canonical fournit des solutions open source sécurisées de niveau entreprise, auxquelles font confiance des millions de développeurs et les plus grandes organisations mondiales. Sa vaste gamme de produits s'articule autour d'Ubuntu — le système d'exploitation open source le plus utilisé au monde et la plateforme de référence pour l'infrastructure cloud et le développement Python. L'entreprise propose également des technologies cloud-native avancées, telles que MicroK8s, LXD, ainsi que la couverture de sécurité complète d'Ubuntu Pro.", + en: "Canonical delivers secure, enterprise-grade open-source solutions trusted by millions of developers and leading global organizations. Its extensive portfolio is anchored by Ubuntu — the world's most widely used open-source operating system and the go-to platform for cloud infrastructure and seamless Python development. Canonical also offers advanced cloud-native technologies, including MicroK8s, LXD, and the comprehensive enterprise security coverage of Ubuntu Pro.", }, }, { @@ -39,8 +39,8 @@ export const sponsorsList = [ logo: '/sponsors/apify.svg', website: 'https://apify.com', description: { - fr: "Plateforme de web scraping et d'automatisation, avec un SDK Python.", - en: 'Web scraping and automation platform with a Python SDK.', + fr: "Apify fournit une plateforme avancée de web scraping et d'automatisation à laquelle font confiance des milliers de développeurs et d'organisations orientées données à travers le monde. Son vaste écosystème propose plus d'un millier de programmes cloud prêts à l'emploi appelés Actors — les solutions de référence pour l'extraction de données à grande échelle et l'automatisation des processus. L'entreprise offre également de puissants outils de développement, notamment le framework open source Crawlee et un SDK Python dédié pour une intégration fluide.", + en: 'Apify provides an advanced web scraping and automation platform trusted by thousands of developers and data-driven organizations worldwide. Its extensive ecosystem features over a thousand ready-to-use cloud programs called Actors — the go-to solutions for scalable web data extraction and workflow automation. The company also offers robust developer tools, including the open-source Crawlee framework and a dedicated Python SDK for seamless integration into modern tech stacks.', }, }, { @@ -50,8 +50,8 @@ export const sponsorsList = [ logo: '/sponsors/bpd.webp', website: 'https://blackpythondevs.com', description: { - fr: 'Communauté mondiale qui accompagne les développeurs Python noirs et de couleur.', - en: 'A global community supporting Black and people of colour Python developers.', + fr: "Black Python Devs cultive une communauté mondiale dynamique dédiée au soutien des développeurs noirs et de couleur à travers l'industrie technologique. Son vaste réseau fournit des ressources essentielles, notamment du mentorat et des opportunités de développement professionnel — l'écosystème de référence pour autonomiser les voix sous-représentées dans le domaine de Python. L'organisation milite également pour la diversité et l'inclusion, favorisant un espace collaboratif pour l'évolution de carrière et l'excellence technique.", + en: "Black Python Devs cultivates a thriving, global community dedicated to supporting Black and people of color developers across the tech industry. Its extensive network provides vital resources, including mentorship and professional development opportunities — the go-to ecosystem for empowering underrepresented voices in Python. The organization also advocates for diversity and inclusion, fostering a collaborative space for career growth and technical excellence.", }, }, { @@ -61,8 +61,8 @@ export const sponsorsList = [ logo: '/sponsors/chaoss.webp', website: 'https://chaoss.community', description: { - fr: 'Projet de la Linux Foundation dédié à la santé et à la pérennité des communautés open source.', - en: 'A Linux Foundation project focused on the health and sustainability of open source communities.', + fr: "CHAOSS crée des métriques standardisées et des logiciels d'analyse utilisés par les gestionnaires de communautés, les équipes d'ingénierie et les écosystèmes open source du monde entier pour évaluer la santé et la pérennité des communautés. Son écosystème d'outils comprend GrimoireLab et Augur — les plateformes de référence pour collecter et visualiser les données des projets open source. Le projet de la Linux Foundation propose également le tableau de bord 8Knot, ainsi que des méthodologies complètes pour analyser l'engagement des contributeurs.", + en: "CHAOSS creates standard metrics and analytics software used by community managers, engineering teams, and open source ecosystems worldwide to evaluate community health and sustainability. Its ecosystem of tools includes GrimoireLab and Augur — the go-to platforms for collecting and visualizing open source project data. The Linux Foundation project also offers the 8Knot dashboard, alongside comprehensive methodologies for understanding contributor engagement.", }, }, { @@ -72,8 +72,8 @@ export const sponsorsList = [ logo: '/sponsors/dsf.webp', website: 'https://www.djangoproject.com/foundation/', description: { - fr: 'La Django Software Foundation soutient le développement et la communauté du framework Django.', - en: 'The Django Software Foundation supports the development and community of the Django web framework.', + fr: "La Django Software Foundation (DSF) protège et fait progresser l'écosystème open source Django, soutenant des millions de développeurs et d'organisations qui conçoivent des applications web modernes à travers le monde. Elle pilote Django — le framework web Python de référence pour créer des applications sécurisées, évolutives et maintenables, conçu pour les perfectionnistes sous pression de délais. La fondation finance également le développement communautaire, octroie des bourses pour les événements et anime une communauté mondiale dynamique et inclusive.", + en: "The Django Software Foundation (DSF) protects and advances the open-source Django ecosystem, supporting millions of developers and organizations powering modern web applications globally. It stewards Django — the go-to Python web framework for building secure, scalable, and maintainable web applications with perfectionists under tight deadlines. The foundation also funds community development, provides event grants, and nurtures a vibrant, inclusive global developer community.", }, }, { @@ -83,8 +83,8 @@ export const sponsorsList = [ logo: '/sponsors/jetbrains.webp', website: 'https://www.jetbrains.com', description: { - fr: "JetBrains crée des outils de développement logiciel intelligents utilisés par plus de 11,4 millions de professionnels et 88 entreprises du Fortune Global Top 100. Notre gamme de plus de 30 produits comprend des IDE primés, dont PyCharm — l'IDE de référence pour un développement Python productif et efficace. Nous proposons également l'assistant de codage JetBrains AI ainsi que l'agent de codage Junie.", - en: 'JetBrains creates intelligent software development tools used by over 11.4 million professionals and 88 Fortune Global Top 100 companies. Our lineup of more than 30 products includes award-winning IDEs, including PyCharm -- the go-to IDE for productive and efficient Python development. We also offer the JetBrains AI-powered coding assistant, and coding agent Junie.', + fr: "JetBrains crée des outils de développement logiciel intelligents utilisés par plus de 11,4 millions de professionnels et 88 des 100 plus grandes entreprises du classement Fortune Global. Sa gamme de plus de 30 produits comprend des IDE primés, dont PyCharm — l'IDE de référence pour un développement Python productif et efficace. L'entreprise propose également l'assistant de codage basé sur l'IA de JetBrains, ainsi que l'agent de codage Junie.", + en: "JetBrains creates intelligent software development tools used by over 11.4 million professionals and 88 Fortune Global Top 100 companies. Its lineup of more than 30 products includes award-winning IDEs, including PyCharm — the go-to IDE for productive and efficient Python development. The company also offers the JetBrains AI-powered coding assistant, and coding agent Junie.", }, }, ];