diff --git a/docusaurus.config.js b/docusaurus.config.js index eb3a1a3..b10fb6e 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -143,6 +143,10 @@ const config = { label: 'Events', to: '/events', }, + { + label: 'Expert Advisory Committee', + to: '/expert-advisory-committee', + }, { label: 'Partners', to: '/partners-page', diff --git a/i18n/zh-CN/docusaurus-theme-classic/navbar.json b/i18n/zh-CN/docusaurus-theme-classic/navbar.json index 9cef7ac..04606df 100644 --- a/i18n/zh-CN/docusaurus-theme-classic/navbar.json +++ b/i18n/zh-CN/docusaurus-theme-classic/navbar.json @@ -39,6 +39,10 @@ "message": "资源", "description": "Navbar item with label Resources" }, + "item.label.Expert Advisory Committee": { + "message": "专家顾问委员会", + "description": "Navbar item with label Expert Advisory Committee" + }, "item.label.Online Trial": { "message": "在线试用", "description": "Navbar item with label Online Trial" diff --git a/src/data/expertCommittee.json b/src/data/expertCommittee.json new file mode 100644 index 0000000..7825ce6 --- /dev/null +++ b/src/data/expertCommittee.json @@ -0,0 +1,473 @@ +[ + { + "id": "alvaro-hernandez", + "name": { + "en": "Álvaro Hernández", + "zh": "Alvaro Hernandez" + }, + "title": { + "en": "Founder of OnGres · PostgreSQL Developer", + "zh": "OnGres 创始人 · PostgreSQL 开发者" + }, + "bio": { + "en": "Founder of OnGres and a database developer with more than 20 years of PostgreSQL experience. He created projects including StackGres and ToroDB and is a frequent international speaker.", + "zh": "OnGres 创始人,拥有 20 余年 PostgreSQL 与数据库研发经验,创建 StackGres、ToroDB 等项目,并长期参与国际技术交流。" + }, + "avatar": "/img/expert-committee/alvaro-hernandez.webp?v=20260809", + "avatarFraming": { "scale": 1.5, "position": "50% 30%" } + }, + { + "id": "cedric-villemain", + "name": { + "en": "Cédric Villemain", + "zh": "Cédric Villemain" + }, + "title": { + "en": "CEO of Data Bene", + "zh": "Data Bene 首席执行官" + }, + "bio": { + "en": "Founder and CEO of Data Bene, formerly 2ndQuadrant France. He has worked with PostgreSQL since 2004 and provides open-source PostgreSQL services and solutions to global customers.", + "zh": "Data Bene(前 2ndQuadrant France)创始人兼首席执行官,自 2004 年起深耕 PostgreSQL,为全球客户提供开源数据库服务与解决方案。" + }, + "avatar": "/img/expert-committee/cedric-villemain.webp" + }, + { + "id": "cui-peng", + "name": { + "en": "Cui Peng", + "zh": "崔鹏" + }, + "title": { + "en": "DBA Director at Hytera Communications · PostgreSQL ACE", + "zh": "海能达通信 DBA 总监 · PostgreSQL ACE" + }, + "bio": { + "en": "Computer science PhD with more than 10 years of database experience. Oracle OCM and PostgreSQL ACE certified, and editor of the WeChat account CP's PostgreSQL Kitchen.", + "zh": "计算机博士,拥有 10 年以上数据库经验,获得 Oracle OCM 与 PostgreSQL ACE 双认证,现任海能达通信 DBA 总监,主理公众号“CP 的 PostgreSQL 厨房”。" + }, + "avatar": "/img/expert-committee/cui-peng.webp" + }, + { + "id": "denis-lussier", + "name": { + "en": "Denis Lussier", + "zh": "Denis Lussier" + }, + "title": { + "en": "Co-founder and CTO of pgEdge", + "zh": "pgEdge 联合创始人兼 CTO" + }, + "bio": { + "en": "A software developer, architect, and serial entrepreneur with decades of PostgreSQL experience. He co-founded EDB, later founded OpenSCG, and has helped advance enterprise PostgreSQL products and communities.", + "zh": "拥有数十年 PostgreSQL 经验的软件开发者、架构师和连续创业者,曾联合创立 EDB、创办 OpenSCG,长期推动企业级 PostgreSQL 产品与社区发展。" + }, + "avatar": "/img/expert-committee/denis-lussier.webp" + }, + { + "id": "feng-ruohang", + "name": { + "en": "Feng Ruohang", + "zh": "冯若航" + }, + "title": { + "en": "Founder of Pigsty · PostgreSQL Expert", + "zh": "Pigsty 作者 · PostgreSQL 专家" + }, + "bio": { + "en": "Creator of Pigsty, PostgreSQL specialist, and full-stack developer. He has worked at Alibaba, Apple, and Tantan and has translated major database engineering books into Chinese.", + "zh": "Pigsty 作者、PostgreSQL 专家与全栈开发者,曾任职于阿里巴巴、苹果和探探,并译有多部数据库工程著作。" + }, + "avatar": "/img/expert-committee/feng-ruohang.webp" + }, + { + "id": "fu-chao", + "name": { + "en": "Fu Chao", + "zh": "付超" + }, + "title": { + "en": "Oracle ACE · PostgreSQL ACE", + "zh": "Oracle ACE · PostgreSQL ACE" + }, + "bio": { + "en": "A dual-certified Oracle ACE and PostgreSQL ACE and a core member of the PG Xi'an User Group. He focuses on database operations and technology advocacy and holds Oracle OCM and Kubernetes CKS certifications.", + "zh": "Oracle ACE 与 PostgreSQL ACE 双认证专家、PG 分会西安用户组核心成员,长期从事数据库运维与技术布道,持有 Oracle OCM、Kubernetes CKS 等认证。" + }, + "avatar": "/img/expert-committee/fu-chao.webp" + }, + { + "id": "lei-yanliang", + "name": { + "en": "Lei Yanliang", + "zh": "类延良" + }, + "title": { + "en": "PostgreSQL Technology Enthusiast · Oracle OCM", + "zh": "PostgreSQL 数据库技术爱好者 · Oracle OCM" + }, + "bio": { + "en": "A PostgreSQL database technology enthusiast, Oracle Database 10g and 11g OCM, and Oracle GoldenGate certified specialist.", + "zh": "PostgreSQL 数据库技术爱好者,持有 Oracle Database 10g 与 11g OCM,并通过 OGG 专家认证。" + }, + "avatar": "/img/expert-committee/lei-yanliang.webp" + }, + { + "id": "leng-bo", + "name": { + "en": "Leng Bo", + "zh": "冷菠" + }, + "title": { + "en": "PostgreSQL ACE Partner · Database Architect", + "zh": "PostgreSQL ACE Partner · 数据库架构师" + }, + "bio": { + "en": "A senior DBA and database architect with more than 10 years of experience. He works on open-source database adoption, architecture design, performance tuning, and domestic-platform migration.", + "zh": "拥有 10 年以上 DBA 与数据库架构经验,专注开源数据库推广、信创适配、架构设计、性能调优与架构重构。" + }, + "avatar": "/img/expert-committee/leng-bo.webp" + }, + { + "id": "li-chuancheng", + "name": { + "en": "Li Chuancheng", + "zh": "李传成" + }, + "title": { + "en": "WALMiner Author · Database Kernel Engineer", + "zh": "WALMiner 作者 · 数据库内核研发工程师" + }, + "bio": { + "en": "A senior kernel engineer focused on database backup and recovery. He created the WAL parsing tool WALMiner and the PostgreSQL block recovery tool pg_lightool.", + "zh": "资深数据库内核研发工程师,主要研究数据库备份与恢复,自研 WAL 日志解析工具 WALMiner 和 PG 块恢复工具 pg_lightool。" + }, + "avatar": "/img/expert-committee/li-chuancheng.webp" + }, + { + "id": "liu-huayang", + "name": { + "en": "Liu Huayang", + "zh": "刘华阳" + }, + "title": { + "en": "PostgreSQL ACE · Beijing-Tianjin User Group Chair", + "zh": "PostgreSQL ACE · 中国 PG 分会京津用户组主席" + }, + "bio": { + "en": "A database professional with 20 years of experience in internet and financial organizations. He specializes in heterogeneous migration and the operation of large PostgreSQL estates.", + "zh": "拥有 20 年数据库从业经验,曾任职于大型互联网和金融企业,擅长异构数据库迁移及超大规模 PostgreSQL 数据库管理。" + }, + "avatar": "/img/expert-committee/liu-huayang.webp?v=20260809", + "avatarFraming": { "scale": 1.8, "position": "55% 27%" } + }, + { + "id": "luo-min", + "name": { + "en": "Luo Min", + "zh": "罗敏" + }, + "title": { + "en": "Former Senior Technical Consultant at Oracle", + "zh": "原 Oracle 公司资深技术顾问" + }, + "bio": { + "en": "A database specialist with 36 years of industry experience. He led database delivery and performance optimization for major national projects and has published several books on Oracle technology.", + "zh": "拥有 36 年数据库行业经验,曾主导多个国家级关键项目的数据库实施与性能优化,并出版多部 Oracle 技术与性能优化专著。" + }, + "avatar": "/img/expert-committee/luo-min.webp" + }, + { + "id": "michael-meskes", + "name": { + "en": "Michael Meskes", + "zh": "Michael Meskes" + }, + "title": { + "en": "Independent Consultant and Angel Investor", + "zh": "独立顾问与天使投资人" + }, + "bio": { + "en": "An open-source developer, entrepreneur, and advocate active since 1993. He founded credativ and brings deep experience in open-source commercialization, technology, and community building.", + "zh": "自 1993 年起活跃于开源领域,曾创办 credativ,在开源商业化、技术发展与社区建设方面拥有丰富经验。" + }, + "avatar": "/img/expert-committee/michael-meskes.webp" + }, + { + "id": "technical-expert", + "name": { + "en": "NkYoung", + "zh": "NkYoung" + }, + "title": { + "en": "PostgreSQL ACE · Head of the PG Xi'an User Group", + "zh": "PostgreSQL ACE · PG 分会西安用户组负责人" + }, + "bio": { + "en": "A PostgreSQL ACE, head of the PG Xi'an User Group, and editor of the WeChat account PostgreSQL Operations Guide.", + "zh": "PostgreSQL ACE,PG 分会西安用户组负责人,公众号“PostgreSQL 运维之道”主理人。" + }, + "avatar": null + }, + { + "id": "peng-chong", + "name": { + "en": "Peng Chong", + "zh": "彭冲" + }, + "title": { + "en": "PostgreSQL ACE · Database Researcher and Author", + "zh": "PostgreSQL ACE · 数据库研究者与作者" + }, + "bio": { + "en": "Chief editor of Mastering New Features of PostgreSQL Versions Quickly. He focuses on practical research into PostgreSQL releases, open-source databases, and domestic database technologies.", + "zh": "《快速掌握 PostgreSQL 版本新特性》主编,长期从事 PostgreSQL 版本新特性、开源数据库及国产数据库的实践研究。" + }, + "avatar": "/img/expert-committee/peng-chong.webp", + "avatarFraming": { "scale": 2.4, "position": "19% 15%" } + }, + { + "id": "shang-lei", + "name": { + "en": "Shang Lei", + "zh": "尚雷" + }, + "title": { + "en": "Founder of TechTalk · Senior Database Engineer", + "zh": "TechTalk 技术交流社区创始人 · 高级数据库工程师" + }, + "bio": { + "en": "Founder of the TechTalk technical exchange community and a senior database engineer. He actively supports database knowledge sharing and community collaboration.", + "zh": "TechTalk 技术交流社区创始人、高级数据库工程师,长期推动数据库技术交流与社区协作。" + }, + "avatar": "/img/expert-committee/shang-lei.webp" + }, + { + "id": "shi-jiawei", + "name": { + "en": "Shi Jiawei", + "zh": "施嘉伟" + }, + "title": { + "en": "Oracle ACE Pro · PostgreSQL ACE", + "zh": "Oracle ACE Pro · PostgreSQL ACE" + }, + "bio": { + "en": "A database professional with 12 years of experience and OCM, PGCM, and KCM certifications. He advises multiple database communities, serves as a TiDB technology evangelist, and writes the WeChat account Digital Observer.", + "zh": "拥有 12 年数据库从业经验,持有 OCM、PGCM、KCM 认证;担任多个数据库社区技术顾问、TiDB 社区技术布道师,并主理微信公众号《Digital Observer》。" + }, + "avatar": "/img/expert-committee/shi-jiawei.webp" + }, + { + "id": "tang-cheng", + "name": { + "en": "Tang Cheng", + "zh": "唐成" + }, + "title": { + "en": "PostgreSQL ACE Director · Founder of Zhongqi Chengshu", + "zh": "PostgreSQL ACE Director · 中启乘数创始人" + }, + "bio": { + "en": "A database architect with more than 20 years across operating systems, storage, high availability, performance, and cloud databases. He authored PostgreSQL Practicing Road and created ZQPool.", + "zh": "拥有 20 余年数据库、操作系统与存储经验,长期研究高可用、高性能与数据库云化,著有《PostgreSQL 修炼之道:从小工到专家》并开发 ZQPool。" + }, + "avatar": "/img/expert-committee/tang-cheng.webp" + }, + { + "id": "wei-bo", + "name": { + "en": "Wei Bo", + "zh": "魏波" + }, + "title": { + "en": "Technical Advisor at Beijing Shengshu · PG Branch Deputy Secretary-General", + "zh": "北京晟数科技技术顾问 · PG 分会副秘书长" + }, + "bio": { + "en": "Technical advisor at Beijing Shengshu Technology and deputy secretary-general of the PostgreSQL China branch. He actively supports PostgreSQL technology exchange and community development.", + "zh": "北京晟数科技技术顾问、PG 分会副秘书长,长期参与 PostgreSQL 技术交流与社区建设。" + }, + "avatar": "/img/expert-committee/wei-bo.webp" + }, + { + "id": "wu-yang", + "name": { + "en": "Wu Yang", + "zh": "吴洋" + }, + "title": { + "en": "PostgreSQL ACE Partner · Healthcare IT Practitioner", + "zh": "PostgreSQL ACE Partner · 医疗信息化从业者" + }, + "bio": { + "en": "A database technology practitioner in healthcare IT with certifications including 12c OCM, PGCE, OBCP, and KCP. He focuses on database optimization and incident handling.", + "zh": "医疗信息化行业数据库技术从业者,持有 12c OCM、PGCE、OBCP、KCP 等认证,专注数据库优化与故障处理。" + }, + "avatar": "/img/expert-committee/wu-yang.webp" + }, + { + "id": "xiao-shaocong", + "name": { + "en": "Xiao Shaocong", + "zh": "萧少聪" + }, + "title": { + "en": "Independent Technical Consultant", + "zh": "独立技术顾问" + }, + "bio": { + "en": "An independent technical consultant and co-founder of the PostgreSQL Chinese community. He has long participated in PostgreSQL technology and community ecosystem development.", + "zh": "独立技术顾问、PG 中文社区联合发起人,长期参与 PostgreSQL 技术与社区生态建设。" + }, + "avatar": "/img/expert-committee/xiao-shaocong.webp" + }, + { + "id": "xiong-cancan", + "name": { + "en": "Xiong Cancan", + "zh": "熊灿灿" + }, + "title": { + "en": "PostgreSQL ACE · Chengdu User Group Chair", + "zh": "PostgreSQL ACE · 中国 PG 分会成都用户组主席" + }, + "bio": { + "en": "A PostgreSQL architecture, tuning, and troubleshooting specialist with substantial production experience. He writes the PostgreSQL Apprentice WeChat account and helps organize regional PostgreSQL events.", + "zh": "深入理解 PostgreSQL 体系架构与运行原理,拥有重大项目保障、调优和排障经验,主理公众号“PostgreSQL 学徒”并积极组织社区活动。" + }, + "avatar": "/img/expert-committee/xiong-cancan.webp", + "avatarFraming": { "scale": 2.2, "position": "70% 32%" } + }, + { + "id": "xu-ji", + "name": { + "en": "Xu Ji (Baishan)", + "zh": "徐戟(白鳝)" + }, + "title": { + "en": "PostgreSQL ACE Director · Systems Optimization Expert", + "zh": "PostgreSQL ACE Director · 系统优化专家" + }, + "bio": { + "en": "A noted systems optimization expert with more than 20 years in IT. He has led large information-system projects and provides senior technical consulting for finance, telecom, energy, and public-sector organizations.", + "zh": "国内知名系统优化专家,拥有 20 余年 IT 行业经验,长期参与大型信息系统建设,并为金融、运营商、能源及政企客户提供高级技术咨询。" + }, + "avatar": "/img/expert-committee/xu-ji.webp?v=20260809", + "avatarFraming": { "scale": 1.6, "position": "56% 31%" } + }, + { + "id": "xu-xiaoqiang", + "name": { + "en": "Xu Xiaoqiang", + "zh": "徐小强" + }, + "title": { + "en": "Database Administrator · Oracle OCP and OCM", + "zh": "专职 DBA · Oracle OCP 与 OCM" + }, + "bio": { + "en": "A full-time database administrator with Oracle 11g OCP and OCM certifications. He writes the JiekeXu DBA Journey WeChat account.", + "zh": "专职 DBA,持有 Oracle 11g OCP 与 OCM 认证,主理公众号“JiekeXu DBA 之路”。" + }, + "avatar": "/img/expert-committee/xu-xiaoqiang.webp", + "avatarFraming": { "scale": 3, "position": "50% 50%" } + }, + { + "id": "xue-xiaogang", + "name": { + "en": "Xue Xiaogang", + "zh": "薛晓刚" + }, + "title": { + "en": "PostgreSQL ACE · Shanghai User Group Chair", + "zh": "PostgreSQL ACE · 中国 PG 分会上海用户组主席" + }, + "bio": { + "en": "Chair of the PostgreSQL China Shanghai User Group and a long-time database community contributor. He holds Oracle OCP, MySQL OCP, PGCP, and PCP certifications.", + "zh": "中国 PG 分会上海用户组主席,长期参与数据库社区建设,持有 Oracle OCP、MySQL OCP、PGCP 和 PCP 等认证。" + }, + "avatar": "/img/expert-committee/xue-xiaogang.webp" + }, + { + "id": "yin-haiwen", + "name": { + "en": "Yin Haiwen", + "zh": "尹海文" + }, + "title": { + "en": "PostgreSQL ACE · Oracle ACE · OCM", + "zh": "PostgreSQL ACE · Oracle ACE · OCM" + }, + "bio": { + "en": "A database services specialist familiar with Oracle, PostgreSQL, and MySQL. He is a PolarDB open-source community technical advisor and an active promoter of the PostgreSQL ecosystem.", + "zh": "熟悉 Oracle、PostgreSQL、MySQL 等数据库,主要从事数据库服务工作,并担任 PolarDB 开源社区技术顾问,积极推广 PG 生态。" + }, + "avatar": "/img/expert-committee/yin-haiwen.webp", + "avatarFraming": { "scale": 1.9, "position": "50% 0%" } + }, + { + "id": "yu-zixuan", + "name": { + "en": "Yu Zixuan", + "zh": "虞梓轩" + }, + "title": { + "en": "Technical Lead at Guangzhou Huizhi · IvorySQL Contributor", + "zh": "广州汇智技术负责人 · IvorySQL 贡献者" + }, + "bio": { + "en": "Technical lead at Guangzhou Huizhi and an IvorySQL contributor. He supports database technology practice and contributes to the IvorySQL open-source ecosystem.", + "zh": "广州汇智技术负责人、IvorySQL 贡献者,持续参与数据库技术实践与 IvorySQL 开源生态建设。" + }, + "avatar": null + }, + { + "id": "zhang-chen", + "name": { + "en": "Zhang Chen", + "zh": "张晨" + }, + "title": { + "en": "Author of PostgreSQL Data Unloader", + "zh": "PostgreSQL Data Unloader 作者" + }, + "bio": { + "en": "Creator of PostgreSQL Data Unloader, a widely known PostgreSQL data rescue tool designed to recover data from damaged systems.", + "zh": "PostgreSQL Data Unloader(PDU)作者,该工具用于 PostgreSQL 数据抢救与恢复。" + }, + "avatar": "/img/expert-committee/zhang-chen.webp" + }, + { + "id": "zhang-dagang", + "name": { + "en": "Zhang Dagang", + "zh": "张达刚" + }, + "title": { + "en": "Founder of DESKUI · Author of DBCS", + "zh": "DESKUI 社区创始人 · DBCS 作者" + }, + "bio": { + "en": "Founder of the DESKUI community and creator of DBCS, DESK, and XGuard. His tools support database administration and operations across multiple architectures and database products.", + "zh": "DESKUI 社区创始人,自主研发 DBCS、DESK、XGuard 等软件,覆盖多架构、多数据库的管理、运维与安全管控场景。" + }, + "avatar": "/img/expert-committee/zhang-dagang.webp" + }, + { + "id": "zhou-zhengzhong", + "name": { + "en": "Zhou Zhengzhong (Digoal)", + "zh": "周正中(德哥)" + }, + "title": { + "en": "PostgreSQL ACE Director · Open Source Community Leader", + "zh": "PostgreSQL ACE Director · 开源社区负责人" + }, + "bio": { + "en": "One of the founders of the PostgreSQL China community and a prolific technical author. He has long advanced open-source database technology, ecosystems, and talent development in China.", + "zh": "PostgreSQL 中国社区发起人之一,长期推动国内开源数据库技术、生态发展与人才培养,发表了大量覆盖数据库全生命周期的技术文章。" + }, + "avatar": "/img/expert-committee/zhou-zhengzhong.webp" + } +] diff --git a/src/pages/expert-advisory-committee.js b/src/pages/expert-advisory-committee.js new file mode 100644 index 0000000..bc93204 --- /dev/null +++ b/src/pages/expert-advisory-committee.js @@ -0,0 +1,327 @@ +import Layout from '@theme/Layout'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import experts from '../data/expertCommittee.json'; +import { + getAvatarFramingStyle, + getFocusTrapTarget, + isDialogMode, +} from '../utils/expertCommitteeInteraction.cjs'; +import getExpertPopoverPosition from '../utils/getExpertPopoverPosition.cjs'; +import styles from './expert-advisory-committee.module.css'; + +const COPY = { + en: { + pageTitle: 'Expert Advisory Committee', + pageDescription: 'Meet the experts guiding IvorySQL technology and community development.', + eyebrow: 'Community Leadership', + heroTitle: 'Expert Advisory Committee', + heroDescription: + 'Bringing together experienced database specialists and open-source leaders from around the world to provide long-term guidance for IvorySQL technology, ecosystem, and community development.', + expertCount: 'experts', + globalPerspective: 'Global perspective', + unordered: 'No ranking implied', + sectionTitle: 'Committee Members', + sectionDescription: 'Hover over or select a portrait or name to read a short biography.', + orderNote: 'Experts are listed alphabetically by English name, with no ranking implied.', + joinTitle: 'Join the IvorySQL Expert Advisory Committee', + joinDescription: + 'We welcome more database experts to help advance the open-source database ecosystem with us.', + joinAction: 'Contact us by email', + hoverHint: 'Hover or select to view profile', + profileLabel: 'Expert profile', + close: 'Close expert profile', + emptyAvatar: 'Portrait coming soon', + }, + zh: { + pageTitle: '专家顾问委员会', + pageDescription: '了解为 IvorySQL 产品技术与社区发展提供指导的专家委员。', + eyebrow: 'Community Leadership', + heroTitle: '专家顾问委员会', + heroDescription: + '汇聚全球数据库领域的资深专家与开源领袖,为 IvorySQL 的产品技术、生态建设与社区发展提供长期、专业的战略指导。', + expertCount: '位专家', + globalPerspective: '全球技术视野', + unordered: '排名不分先后', + sectionTitle: '委员会成员', + sectionDescription: '将鼠标移至或选择头像、姓名,即可查看专家简介。', + orderNote: '专家按英文姓名首字母顺序排列,排名不分先后。', + joinTitle: '欢迎加入 IvorySQL 专家顾问委员会', + joinDescription: '欢迎更多数据库专家加入 IvorySQL 专家顾问委员会,与我们共同推动开源数据库生态发展。', + joinAction: '发送邮件联系我们', + hoverHint: '悬停或选择查看简介', + profileLabel: '专家简介', + close: '关闭专家简介', + emptyAvatar: '头像待补充', + }, +}; + +function EmptyAvatar() { + return ( + + ); +} + +function ExpertCard({ expert, locale, copy, onOpen }) { + const popoverRef = useRef(null); + const [popoverPosition, setPopoverPosition] = useState(null); + const resolvedAvatar = useBaseUrl(expert.avatar || '/'); + const name = expert.name[locale]; + const title = expert.title[locale]; + const biography = expert.bio[locale]; + const popoverId = `${expert.id}-bio`; + + const openInDialogMode = (event) => { + if (typeof window !== 'undefined' && isDialogMode(window.matchMedia.bind(window))) { + onOpen(expert, event.currentTarget); + } + }; + + const positionPopover = (event) => { + if (!popoverRef.current) return; + + const trigger = event.currentTarget.getBoundingClientRect(); + const popover = popoverRef.current.getBoundingClientRect(); + setPopoverPosition( + getExpertPopoverPosition(trigger, popover, { + width: window.innerWidth, + height: window.innerHeight, + }), + ); + }; + + return ( +
+ + + {title} + + +
+ ); +} + +function MobileBioDialog({ expert, locale, copy, onClose, returnFocus }) { + const dialogRef = useRef(null); + const closeButtonRef = useRef(null); + + useEffect(() => { + if (!expert || !dialogRef.current) return undefined; + + const dialog = dialogRef.current; + const previousFocus = returnFocus.current || document.activeElement; + const backgroundElements = [...document.querySelectorAll('main, nav, footer')] + .filter((element) => !element.contains(dialog)); + const backgroundState = backgroundElements.map((element) => ({ + element, + inert: element.getAttribute('inert'), + ariaHidden: element.getAttribute('aria-hidden'), + })); + const previousOverflow = document.body.style.overflow; + + backgroundElements.forEach((element) => { + element.setAttribute('inert', ''); + element.setAttribute('aria-hidden', 'true'); + }); + document.body.style.overflow = 'hidden'; + + const closeOnEscapeOrTrapFocus = (event) => { + if (event.key === 'Escape') { + event.preventDefault(); + onClose(); + return; + } + if (event.key !== 'Tab') return; + + const focusableElements = [...dialog.querySelectorAll( + 'a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])', + )]; + const targetIndex = getFocusTrapTarget( + focusableElements.indexOf(document.activeElement), + focusableElements.length, + event.shiftKey, + ); + if (targetIndex === null) return; + + event.preventDefault(); + focusableElements[targetIndex].focus(); + }; + const containFocus = (event) => { + if (!dialog.contains(event.target)) closeButtonRef.current?.focus(); + }; + + document.addEventListener('keydown', closeOnEscapeOrTrapFocus); + document.addEventListener('focusin', containFocus); + closeButtonRef.current?.focus(); + + return () => { + document.removeEventListener('keydown', closeOnEscapeOrTrapFocus); + document.removeEventListener('focusin', containFocus); + backgroundState.forEach(({ element, inert, ariaHidden }) => { + if (inert === null) element.removeAttribute('inert'); + else element.setAttribute('inert', inert); + if (ariaHidden === null) element.removeAttribute('aria-hidden'); + else element.setAttribute('aria-hidden', ariaHidden); + }); + document.body.style.overflow = previousOverflow; + if (previousFocus && document.contains(previousFocus)) previousFocus.focus(); + }; + }, [expert, onClose, returnFocus]); + + if (!expert) return null; + + return ( +
+
event.stopPropagation()} + > + + {copy.profileLabel} +

{expert.name[locale]}

+ {expert.title[locale]} +

{expert.bio[locale]}

+
+
+ ); +} + +export default function ExpertAdvisoryCommitteePage() { + const { i18n } = useDocusaurusContext(); + const locale = i18n.currentLocale.toLowerCase().startsWith('zh') ? 'zh' : 'en'; + const copy = COPY[locale]; + const [selectedExpert, setSelectedExpert] = useState(null); + const returnFocusRef = useRef(null); + const openExpert = useCallback((expert, trigger) => { + returnFocusRef.current = trigger; + setSelectedExpert(expert); + }, []); + const closeExpert = useCallback(() => setSelectedExpert(null), []); + + return ( + +
+
+
+
+

{copy.eyebrow}

+

{copy.heroTitle}

+

{copy.heroDescription}

+
+ {experts.length} {copy.expertCount} + {copy.globalPerspective} + {copy.unordered} +
+
+
+
+ +
+
+
+
+

{copy.sectionTitle}

+

+ {copy.orderNote} {copy.sectionDescription} +

+
+ {copy.hoverHint} +
+ +
+ {experts.map((expert) => ( + + ))} +
+ + +
+
+
+ + +
+ ); +} diff --git a/src/pages/expert-advisory-committee.module.css b/src/pages/expert-advisory-committee.module.css new file mode 100644 index 0000000..0e933e8 --- /dev/null +++ b/src/pages/expert-advisory-committee.module.css @@ -0,0 +1,567 @@ +.page { + --committee-accent: #2f74ff; + --committee-ink: #14213d; + --committee-muted: #6f7e98; + --committee-line: #e1e8f3; + min-height: 100vh; + background: linear-gradient(180deg, #f4f8ff 0%, #ffffff 44%, #f6f9ff 100%); +} + +.hero { + position: relative; + overflow: hidden; + padding: 72px 0 76px; + color: #fff; + background: + radial-gradient(circle at 78% 18%, rgba(77, 155, 255, 0.28), transparent 28%), + radial-gradient(circle at 8% 105%, rgba(119, 83, 232, 0.2), transparent 34%), + linear-gradient(145deg, #060d2a 0%, #0b1e56 54%, #10487e 100%); +} + +.hero::before, +.hero::after { + content: ''; + position: absolute; + pointer-events: none; + border-radius: 50%; + border: 1px solid rgba(144, 190, 255, 0.12); +} + +.hero::before { + width: 330px; + height: 330px; + right: 7%; + top: -170px; + box-shadow: + 0 0 0 32px rgba(136, 181, 255, 0.025), + 0 0 0 64px rgba(136, 181, 255, 0.018); +} + +.hero::after { + width: 190px; + height: 190px; + left: -92px; + bottom: -128px; +} + +.heroContent { + position: relative; + z-index: 1; + max-width: 820px; +} + +.eyebrow { + display: flex; + align-items: center; + gap: 12px; + margin: 0 0 16px; + color: #91baff; + font-size: 0.76rem; + font-weight: 800; + letter-spacing: 0.18em; + text-transform: uppercase; +} + +.eyebrow::before { + content: ''; + width: 34px; + height: 2px; + border-radius: 2px; + background: var(--committee-accent); +} + +.hero h1 { + margin: 0 0 18px; + color: #fff; + font-size: clamp(2.5rem, 5vw, 4rem); + line-height: 1.05; + letter-spacing: -0.035em; +} + +.heroDescription { + max-width: 760px; + margin: 0; + color: #c7d5f1; + font-size: 1.03rem; + line-height: 1.85; +} + +.heroMeta { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 26px; +} + +.heroMeta span { + padding: 8px 12px; + border: 1px solid rgba(157, 193, 255, 0.18); + border-radius: 999px; + color: #d9e5fb; + background: rgba(255, 255, 255, 0.045); + font-size: 0.82rem; + backdrop-filter: blur(8px); +} + +.membersSection { + padding: 54px 0 84px; +} + +.sectionHeader { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 24px; + margin-bottom: 26px; +} + +.sectionHeader h2 { + margin: 0 0 8px; + color: var(--committee-ink); + font-size: clamp(1.75rem, 3vw, 2.25rem); +} + +.sectionHeader p { + margin: 0; + color: var(--committee-muted); + line-height: 1.65; +} + +.hoverHint { + flex-shrink: 0; + padding: 8px 12px; + border-radius: 999px; + color: #245fd4; + background: #e9f1ff; + font-size: 0.82rem; + font-weight: 700; +} + +.grid { + display: grid; + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 18px; +} + +.joinCommittee { + display: flex; + align-items: center; + justify-content: space-between; + gap: 28px; + margin-top: 34px; + padding: 28px 30px; + border: 1px solid rgba(47, 116, 255, 0.18); + border-radius: 20px; + background: linear-gradient(135deg, #eef4ff 0%, #f8fbff 100%); +} + +.joinCommittee h2 { + margin: 0 0 8px; + font-size: 1.35rem; +} + +.joinCommittee p { + margin: 0; + color: var(--committee-muted); +} + +.joinAction { + display: inline-flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + padding: 11px 16px; + border-radius: 999px; + color: #fff; + background: var(--committee-accent); + font-weight: 800; + text-decoration: none; +} + +.joinAction:hover { + color: #fff; + background: #1f63eb; + text-decoration: none; +} + +.card { + position: relative; + display: flex; + min-width: 0; + min-height: 208px; + flex-direction: column; + align-items: center; + justify-content: flex-start; + padding: 22px 14px 18px; + border: 1px solid var(--committee-line); + border-radius: 20px; + background: linear-gradient(180deg, #fff 0%, #fbfcff 100%); + box-shadow: 0 10px 26px rgba(21, 43, 84, 0.055); + transition: + border-color 0.18s ease, + box-shadow 0.18s ease, + transform 0.18s ease; +} + +.card:hover, +.card:focus-within { + z-index: 100; +} + +.profileTrigger { + display: inline-flex; + min-width: 0; + flex-direction: column; + align-items: center; + padding: 0; + border: 0; + color: inherit; + background: transparent; + font: inherit; + cursor: help; +} + +.profileTrigger:focus { + outline: none; +} + +.avatarFrame { + position: relative; + display: grid; + width: 94px; + height: 94px; + place-items: center; + flex-shrink: 0; + overflow: hidden; + padding: 3px; + border-radius: 50%; + background: linear-gradient(145deg, #dce8ff 0%, #83aaff 100%); + box-shadow: 0 9px 20px rgba(47, 116, 255, 0.17); + transition: + box-shadow 0.18s ease, + transform 0.18s ease; +} + +.avatarFrame img, +.emptyAvatar { + display: block; + width: 100%; + height: 100%; + border-radius: 50%; +} + +.avatarFrame::after { + content: ''; + position: absolute; + inset: 3px; + border: 3px solid #fff; + border-radius: 50%; + pointer-events: none; +} + +.avatarFrame img { + object-fit: cover; + object-position: var(--avatar-position, center); + transform: scale(var(--avatar-scale, 1)); + transform-origin: var(--avatar-position, center); +} + +.emptyAvatar { + display: grid; + place-items: center; + color: #8190aa; + background: linear-gradient(145deg, #eef3fb 0%, #dfe7f3 100%); +} + +.emptyAvatar svg { + width: 44px; + height: 44px; + fill: none; + stroke: currentColor; + stroke-linecap: round; + stroke-linejoin: round; + stroke-width: 2.6; +} + +.name { + max-width: 100%; + margin-top: 14px; + padding: 0 1px 3px; + border-bottom: 1px dashed transparent; + color: var(--committee-ink); + font-size: 1rem; + font-weight: 800; + line-height: 1.3; + text-align: center; + transition: + border-color 0.18s ease, + color 0.18s ease; +} + +.title { + display: -webkit-box; + min-height: 2.7em; + margin-top: 7px; + overflow: hidden; + color: var(--committee-muted); + font-size: 0.78rem; + line-height: 1.4; + text-align: center; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.profileTrigger:hover .avatarFrame, +.profileTrigger:focus-visible .avatarFrame { + transform: translateY(-2px); + box-shadow: 0 13px 26px rgba(47, 116, 255, 0.23); +} + +.profileTrigger:hover .name, +.profileTrigger:focus-visible .name { + border-color: #80aaff; + color: var(--committee-accent); +} + +.profileTrigger:focus-visible { + border-radius: 16px; + outline: 3px solid rgba(47, 116, 255, 0.3); + outline-offset: 5px; +} + +.bioPopover { + position: fixed; + z-index: 1000; + box-sizing: border-box; + width: min(292px, calc(100vw - 24px)); + max-height: calc(100vh - 24px); + overflow-y: auto; + padding: 20px; + border: 1px solid rgba(122, 166, 246, 0.2); + border-radius: 18px; + color: #fff; + background: linear-gradient(145deg, #081535 0%, #10295f 100%); + box-shadow: 0 24px 54px rgba(7, 19, 53, 0.3); + opacity: 0; + pointer-events: auto; + text-align: left; + transform: translateX(-5px) scale(0.98); + transform-origin: left center; + visibility: hidden; + transition: + opacity 0.15s ease, + transform 0.15s ease, + visibility 0s linear 0.15s; +} + +.bioPopover::after { + content: ''; + position: absolute; + top: 0; + bottom: 0; + left: -16px; + width: 16px; +} + +.bioPopover::before { + content: ''; + position: absolute; + top: 44px; + left: -7px; + width: 14px; + height: 14px; + background: #0a1a40; + transform: rotate(45deg); +} + +.profileLabel { + display: block; + color: #80b0ff; + font-size: 0.69rem; + font-weight: 800; + letter-spacing: 0.12em; + text-transform: uppercase; +} + +.bioPopover strong { + display: block; + margin: 7px 0 4px; + color: #fff; + font-size: 1.08rem; +} + +.popoverTitle { + display: block; + color: #aec8f5; + font-size: 0.78rem; + font-weight: 700; + line-height: 1.45; +} + +.bioPopover p { + margin: 12px 0 0; + color: #d6e1f4; + font-size: 0.84rem; + line-height: 1.72; +} + +.bioPopover[data-side='left']::before { + right: -7px; + left: auto; +} + +.bioPopover[data-side='left']::after { + right: -16px; + left: auto; +} + +.dialogBackdrop { + position: fixed; + z-index: 1000; + inset: 0; + display: flex; + align-items: flex-end; + justify-content: center; + padding: 16px; + background: rgba(3, 10, 28, 0.58); + backdrop-filter: blur(5px); +} + +.mobileDialog { + position: relative; + width: min(100%, 560px); + padding: 24px 22px 26px; + border: 1px solid rgba(122, 166, 246, 0.22); + border-radius: 22px; + color: #fff; + background: linear-gradient(145deg, #081535 0%, #10295f 100%); + box-shadow: 0 24px 60px rgba(3, 10, 30, 0.42); +} + +.mobileDialog h2 { + margin: 8px 42px 4px 0; + color: #fff; + font-size: 1.4rem; +} + +.mobileDialog strong { + display: block; + color: #aec8f5; + font-size: 0.88rem; + line-height: 1.5; +} + +.mobileDialog p { + margin: 14px 0 0; + color: #d6e1f4; + line-height: 1.75; +} + +.dialogClose { + position: absolute; + top: 14px; + right: 14px; + display: grid; + width: 34px; + height: 34px; + place-items: center; + padding: 0; + border: 1px solid rgba(255, 255, 255, 0.14); + border-radius: 50%; + color: #fff; + background: rgba(255, 255, 255, 0.08); + font-size: 1.3rem; + line-height: 1; + cursor: pointer; +} + +@media (hover: hover) and (pointer: fine) { + .profileTrigger:hover ~ .bioPopover, + .profileTrigger:focus-visible ~ .bioPopover, + .bioPopover:hover { + opacity: 1; + transform: none; + visibility: visible; + transition-delay: 0s; + } +} + +@media (min-width: 700px) and (max-width: 1099px) { + .grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + +} + +@media (max-width: 699px) { + .hero { + padding: 52px 0 56px; + } + + .hero h1 { + font-size: clamp(2.25rem, 11vw, 3rem); + } + + .heroDescription { + font-size: 0.95rem; + } + + .membersSection { + padding: 40px 0 60px; + } + + .sectionHeader { + align-items: flex-start; + flex-direction: column; + gap: 14px; + margin-bottom: 22px; + } + + .hoverHint { + display: none; + } + + .grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 12px; + } + + .joinCommittee { + align-items: flex-start; + flex-direction: column; + padding: 24px 20px; + } + + .card { + min-height: 190px; + padding: 18px 10px 14px; + border-radius: 17px; + } + + .avatarFrame { + width: 82px; + height: 82px; + } + + .name { + margin-top: 11px; + font-size: 0.92rem; + } + + .title { + font-size: 0.72rem; + } + + .bioPopover { + display: none; + } +} + +@media (prefers-reduced-motion: reduce) { + .card, + .avatarFrame, + .name, + .bioPopover { + transition: none; + } +} diff --git a/src/utils/expertCommitteeInteraction.cjs b/src/utils/expertCommitteeInteraction.cjs new file mode 100644 index 0000000..e03b3d8 --- /dev/null +++ b/src/utils/expertCommitteeInteraction.cjs @@ -0,0 +1,28 @@ +const DIALOG_MODE_QUERY = '(max-width: 699px), (hover: none), (pointer: coarse)'; + +function isDialogMode(matchMedia) { + return matchMedia(DIALOG_MODE_QUERY).matches; +} + +function getAvatarFramingStyle(expert) { + if (!expert.avatarFraming) return undefined; + return { + '--avatar-scale': expert.avatarFraming.scale, + '--avatar-position': expert.avatarFraming.position, + }; +} + +function getFocusTrapTarget(activeIndex, focusableCount, shiftKey) { + if (focusableCount === 0) return null; + if (activeIndex === -1) return shiftKey ? focusableCount - 1 : 0; + if (shiftKey && activeIndex === 0) return focusableCount - 1; + if (!shiftKey && activeIndex === focusableCount - 1) return 0; + return null; +} + +module.exports = { + DIALOG_MODE_QUERY, + getAvatarFramingStyle, + getFocusTrapTarget, + isDialogMode, +}; diff --git a/src/utils/getExpertPopoverPosition.cjs b/src/utils/getExpertPopoverPosition.cjs new file mode 100644 index 0000000..740fcee --- /dev/null +++ b/src/utils/getExpertPopoverPosition.cjs @@ -0,0 +1,36 @@ +const GAP = 14; +const VIEWPORT_MARGIN = 12; + +function clamp(value, minimum, maximum) { + return Math.min(Math.max(value, minimum), maximum); +} + +function getExpertPopoverPosition(trigger, popover, viewport) { + const availableWidth = Math.max(0, viewport.width - (VIEWPORT_MARGIN * 2)); + const popoverWidth = Math.min(popover.width, availableWidth); + const rightSpace = viewport.width - VIEWPORT_MARGIN - trigger.right - GAP; + const leftSpace = trigger.left - GAP - VIEWPORT_MARGIN; + const fitsRight = rightSpace >= popoverWidth; + const fitsLeft = leftSpace >= popoverWidth; + const side = fitsRight || (!fitsLeft && rightSpace >= leftSpace) ? 'right' : 'left'; + const requestedLeft = side === 'right' + ? trigger.right + GAP + : trigger.left - GAP - popoverWidth; + const left = clamp( + requestedLeft, + VIEWPORT_MARGIN, + Math.max(VIEWPORT_MARGIN, viewport.width - popoverWidth - VIEWPORT_MARGIN), + ); + const availableHeight = Math.max(0, viewport.height - (VIEWPORT_MARGIN * 2)); + const popoverHeight = Math.min(popover.height, availableHeight); + const centeredTop = trigger.top + (trigger.height - popoverHeight) / 2; + const top = clamp( + centeredTop, + VIEWPORT_MARGIN, + Math.max(VIEWPORT_MARGIN, viewport.height - popoverHeight - VIEWPORT_MARGIN), + ); + + return { left: Math.round(left), top: Math.round(top), side }; +} + +module.exports = getExpertPopoverPosition; diff --git a/static/img/expert-committee/alvaro-hernandez.webp b/static/img/expert-committee/alvaro-hernandez.webp new file mode 100644 index 0000000..4114a84 Binary files /dev/null and b/static/img/expert-committee/alvaro-hernandez.webp differ diff --git a/static/img/expert-committee/cedric-villemain.webp b/static/img/expert-committee/cedric-villemain.webp new file mode 100644 index 0000000..0a4ab8a Binary files /dev/null and b/static/img/expert-committee/cedric-villemain.webp differ diff --git a/static/img/expert-committee/cui-peng.webp b/static/img/expert-committee/cui-peng.webp new file mode 100644 index 0000000..e24ed18 Binary files /dev/null and b/static/img/expert-committee/cui-peng.webp differ diff --git a/static/img/expert-committee/denis-lussier.webp b/static/img/expert-committee/denis-lussier.webp new file mode 100644 index 0000000..435bed6 Binary files /dev/null and b/static/img/expert-committee/denis-lussier.webp differ diff --git a/static/img/expert-committee/feng-ruohang.webp b/static/img/expert-committee/feng-ruohang.webp new file mode 100644 index 0000000..936a451 Binary files /dev/null and b/static/img/expert-committee/feng-ruohang.webp differ diff --git a/static/img/expert-committee/fu-chao.webp b/static/img/expert-committee/fu-chao.webp new file mode 100644 index 0000000..7e39eb1 Binary files /dev/null and b/static/img/expert-committee/fu-chao.webp differ diff --git a/static/img/expert-committee/lei-yanliang.webp b/static/img/expert-committee/lei-yanliang.webp new file mode 100644 index 0000000..e2c2248 Binary files /dev/null and b/static/img/expert-committee/lei-yanliang.webp differ diff --git a/static/img/expert-committee/leng-bo.webp b/static/img/expert-committee/leng-bo.webp new file mode 100644 index 0000000..1d75a53 Binary files /dev/null and b/static/img/expert-committee/leng-bo.webp differ diff --git a/static/img/expert-committee/li-chuancheng.webp b/static/img/expert-committee/li-chuancheng.webp new file mode 100644 index 0000000..61e12ee Binary files /dev/null and b/static/img/expert-committee/li-chuancheng.webp differ diff --git a/static/img/expert-committee/liu-huayang.webp b/static/img/expert-committee/liu-huayang.webp new file mode 100644 index 0000000..b004c2f Binary files /dev/null and b/static/img/expert-committee/liu-huayang.webp differ diff --git a/static/img/expert-committee/luo-min.webp b/static/img/expert-committee/luo-min.webp new file mode 100644 index 0000000..40f7e33 Binary files /dev/null and b/static/img/expert-committee/luo-min.webp differ diff --git a/static/img/expert-committee/michael-meskes.webp b/static/img/expert-committee/michael-meskes.webp new file mode 100644 index 0000000..e754b3f Binary files /dev/null and b/static/img/expert-committee/michael-meskes.webp differ diff --git a/static/img/expert-committee/peng-chong.webp b/static/img/expert-committee/peng-chong.webp new file mode 100644 index 0000000..1d8e68c Binary files /dev/null and b/static/img/expert-committee/peng-chong.webp differ diff --git a/static/img/expert-committee/shang-lei.webp b/static/img/expert-committee/shang-lei.webp new file mode 100644 index 0000000..d8993eb Binary files /dev/null and b/static/img/expert-committee/shang-lei.webp differ diff --git a/static/img/expert-committee/shi-jiawei.webp b/static/img/expert-committee/shi-jiawei.webp new file mode 100644 index 0000000..baacd9f Binary files /dev/null and b/static/img/expert-committee/shi-jiawei.webp differ diff --git a/static/img/expert-committee/tang-cheng.webp b/static/img/expert-committee/tang-cheng.webp new file mode 100644 index 0000000..f7317ac Binary files /dev/null and b/static/img/expert-committee/tang-cheng.webp differ diff --git a/static/img/expert-committee/wei-bo.webp b/static/img/expert-committee/wei-bo.webp new file mode 100644 index 0000000..3cd47ad Binary files /dev/null and b/static/img/expert-committee/wei-bo.webp differ diff --git a/static/img/expert-committee/wu-yang.webp b/static/img/expert-committee/wu-yang.webp new file mode 100644 index 0000000..1cad0fe Binary files /dev/null and b/static/img/expert-committee/wu-yang.webp differ diff --git a/static/img/expert-committee/xiao-shaocong.webp b/static/img/expert-committee/xiao-shaocong.webp new file mode 100644 index 0000000..719bc09 Binary files /dev/null and b/static/img/expert-committee/xiao-shaocong.webp differ diff --git a/static/img/expert-committee/xiong-cancan.webp b/static/img/expert-committee/xiong-cancan.webp new file mode 100644 index 0000000..88e4ae1 Binary files /dev/null and b/static/img/expert-committee/xiong-cancan.webp differ diff --git a/static/img/expert-committee/xu-ji.webp b/static/img/expert-committee/xu-ji.webp new file mode 100644 index 0000000..0b9f309 Binary files /dev/null and b/static/img/expert-committee/xu-ji.webp differ diff --git a/static/img/expert-committee/xu-xiaoqiang.webp b/static/img/expert-committee/xu-xiaoqiang.webp new file mode 100644 index 0000000..9603267 Binary files /dev/null and b/static/img/expert-committee/xu-xiaoqiang.webp differ diff --git a/static/img/expert-committee/xue-xiaogang.webp b/static/img/expert-committee/xue-xiaogang.webp new file mode 100644 index 0000000..1762123 Binary files /dev/null and b/static/img/expert-committee/xue-xiaogang.webp differ diff --git a/static/img/expert-committee/yin-haiwen.webp b/static/img/expert-committee/yin-haiwen.webp new file mode 100644 index 0000000..8d629e4 Binary files /dev/null and b/static/img/expert-committee/yin-haiwen.webp differ diff --git a/static/img/expert-committee/zhang-chen.webp b/static/img/expert-committee/zhang-chen.webp new file mode 100644 index 0000000..648ce17 Binary files /dev/null and b/static/img/expert-committee/zhang-chen.webp differ diff --git a/static/img/expert-committee/zhang-dagang.webp b/static/img/expert-committee/zhang-dagang.webp new file mode 100644 index 0000000..cc0dcdd Binary files /dev/null and b/static/img/expert-committee/zhang-dagang.webp differ diff --git a/static/img/expert-committee/zhou-zhengzhong.webp b/static/img/expert-committee/zhou-zhengzhong.webp new file mode 100644 index 0000000..24f660f Binary files /dev/null and b/static/img/expert-committee/zhou-zhengzhong.webp differ