diff --git a/src/pages/index.js b/src/pages/index.js index 5fe7fe8..fceb3a1 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,118 +1,410 @@ +import clsx from 'clsx'; import Link from '@docusaurus/Link'; -import Translate from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; -import clsx from 'clsx'; -import React, { useEffect } from 'react'; -import Slider from "react-slick"; -import RecruitPhone from "../../static/img/index-recruit-phone.jpg"; -import Recruit from "../../static/img/index-recruit.jpg"; -import indexbug from '../../static/img/indexbug.png'; -import indexbugphone from '../../static/img/indexbugphone.png'; -import Elephant from '../../svg/img-elephant-balloon.svg'; -import HOW from '../../static/img/ivorysql-how.jpg'; -import HomepageFeatures from '../components/HomepageFeatures'; +import React, { useEffect, useState } from 'react'; import styles from './index.module.css'; -import SliderIndex from './slider'; -import SliderBug from './slider-bug'; -import SliderBugPhone from './slider-bug-phone'; -import SliderPhoneIndex from './slider-phone'; -import { customFields } from '../../docusaurus.config'; -function HomepageHeader() { - const {siteConfig} = useDocusaurusContext(); - const settings = { - autoplay: true, - autoplaySpeed: 5000, - dots: true, - infinite: true, - slidesToShow: 1, - slidesToScroll: 1, - arrows: false, - }; - const { i18n } = useDocusaurusContext(); - const isEnglish = i18n.currentLocale === 'en'; + +const Icon01 = require('../../svg/icon-01.svg').default; +const Icon02 = require('../../svg/icon-02.svg').default; +const Icon03 = require('../../svg/icon-03.svg').default; +const Icon04 = require('../../svg/icon-04.svg').default; +const Icon05 = require('../../svg/icon-05.svg').default; +const Icon06 = require('../../svg/icon-06.svg').default; +const HeroElephant = require('../../svg/img-elephant-balloon.svg').default; + +const CoreAdvantageIcon = Icon01; +const ScenarioIcon = Icon03; +const EcosystemIcon = Icon02; +const CertificateIcon = Icon05; +const InstallDeployIcon = Icon04; + +const CORE_CARD_ICONS = [Icon01, Icon03, Icon04, Icon02, Icon05, Icon06, Icon01, Icon03]; +const SCENARIO_CARD_ICONS = [Icon04, Icon02, Icon06, Icon01, Icon05]; +const INSTALL_CARD_ICONS = [Icon04, Icon01, Icon06]; + +const RELEASES_URL = 'https://github.com/IvorySQL/IvorySQL/releases'; +const ONLINE_TRIAL_URL = 'https://trial.ivorysql.org/'; +const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/IvorySQL/IvorySQL/releases/latest'; +const LATEST_VERSION_CACHE_KEY = 'ivorysql_latest_release_label'; +const LATEST_VERSION_CACHE_TTL = 6 * 60 * 60 * 1000; + +const ECOSYSTEM_TOOL_STATUS = { + progress: new Set(['citus', 'pg_ai_query', 'stackgres', 'databene', 'madlib']), + planned: new Set(['shardingsphere', 'pacemaker corosync', 'postgresql age', 'yukon', 'powa']), + proprietary: new Set(['ivymigration', 'ivyevaluation', 'ivorysql serverless']), +}; + +function normalizeToolName(toolName) { + return toolName.replace(/\u200c/g, '').toLowerCase().trim(); +} + +function getEcosystemToolTone(toolName) { + const normalizedName = normalizeToolName(toolName); + if (ECOSYSTEM_TOOL_STATUS.proprietary.has(normalizedName)) { + return 'proprietary'; + } + if (ECOSYSTEM_TOOL_STATUS.progress.has(normalizedName)) { + return 'progress'; + } + if (ECOSYSTEM_TOOL_STATUS.planned.has(normalizedName)) { + return 'planned'; + } + return 'supported'; +} + +function formatLatestReleaseLabel(release, fallbackLabel) { + const source = `${release?.name || ''} ${release?.tag_name || ''}`; + const versionMatch = source.match(/(\d+(?:\.\d+){1,2})/); + if (versionMatch) { + return `IvorySQL ${versionMatch[1]}`; + } + const cleaned = (release?.name || release?.tag_name || '').trim(); + return cleaned || fallbackLabel; +} + +function readLatestVersionFromCache() { + if (typeof window === 'undefined') { + return null; + } + + try { + const rawCache = window.localStorage.getItem(LATEST_VERSION_CACHE_KEY); + if (!rawCache) { + return null; + } + const parsedCache = JSON.parse(rawCache); + if (!parsedCache?.label || !parsedCache?.time) { + return null; + } + if (Date.now() - parsedCache.time > LATEST_VERSION_CACHE_TTL) { + return null; + } + return parsedCache.label; + } catch { + return null; + } +} + +function writeLatestVersionToCache(label) { + if (typeof window === 'undefined') { + return; + } + + try { + window.localStorage.setItem( + LATEST_VERSION_CACHE_KEY, + JSON.stringify({ + label, + time: Date.now(), + }), + ); + } catch { + // Ignore storage failures in private mode / restricted environments. + } +} + +const CONTENT = { + zh: { + slogan: '一款开源的兼容 Oracle 的 PostgreSQL', + intro: + 'IvorySQL 是一款先进、功能齐全的开源 Oracle 兼容 PostgreSQL,致力于保持高兼容性,并可作为最新 PostgreSQL 的完全替代品。通过 compatible_mode 开关可在 Oracle 与 PostgreSQL 兼容模式间切换,PL/iSQL 支持 Oracle PL/SQL 语法及 Oracle 风格包(Packages)。', + heroBadges: ['Oracle 兼容', 'Apache 2.0 开源', '基于 PostgreSQL 内核'], + latestVersionPrefix: '最新版本', + latestVersionLabel: 'IvorySQL 5.1', + actions: [ + { label: '免费下载', to: '/releases-page' }, + { label: '在线体验', href: ONLINE_TRIAL_URL }, + { label: '最新活动', to: '/webinars-page' }, + ], + coreTitle: 'IvorySQL 核心优势', + coreDesc: '从内核兼容到生态扩展,提供面向企业生产环境的数据库能力。', + coreItems: [ + { + title: '核心开源', + description: '采用 Apache 2.0 协议开源,无厂商限制,代码透明且支持定制化开发。', + }, + { + title: '深度 Oracle 兼容', + description: '通过 PL/iSQL 过程语言和 ivorysql_ora 插件实现 PL/SQL 语法兼容,支持 Oracle 数据库迁移。', + }, + { + title: '国产化全平台兼容', + description: '全面兼容国内外主流软硬件,兼容国产芯片架构和操作系统,提供全平台介质包确保便捷部署。', + }, + { + title: '云原生支持', + description: '容器化方案覆盖 Docker Compose/Swarm、K8S Operator 及云服务平台。', + }, + { + title: '企业级支持', + description: '由瀚高股份提供技术支持,并在多个企业生产环境落地。', + }, + { + title: '生态融合', + description: '继承 PostgreSQL 完整 SQL 能力、可靠性和丰富生态组件。', + }, + { + title: '场景覆盖广', + description: '覆盖企业数据库、LBS、数据仓库、建站开发、数据库迁移等核心场景。', + }, + { + title: '易用性强', + description: '降低系统管理成本,提供开发者友好接口和第三方工具集成能力。', + }, + ], + scenariosTitle: 'IvorySQL 应用场景', + scenariosDesc: '覆盖从交易系统到分析平台的典型数据库工作负载。', + scenarioItems: [ + { + title: '企业数据库', + description: '适用于 ERP、交易系统、财务系统等对高可用和复杂业务逻辑有要求的场景。', + }, + { + title: 'LBS 应用', + description: '支持地理空间查询(如 O2O、游戏地图),通过 PostGIS 实现位置服务。', + }, + { + title: '数据仓库 / 大数据', + description: '利用丰富数据类型和计算能力搭建分析平台。', + }, + { + title: '建站 / App 开发', + description: '依托高性能能力提升网站与应用效率。', + }, + { + title: '数据库迁移', + description: '支持将 Oracle 数据库迁移到 IvorySQL。', + }, + ], + installTitle: 'IvorySQL 安装部署', + installDesc: + '结合官网文档与版本发布信息,提供从快速上手到生产部署的清晰路径,可按环境选择包安装、源码构建或容器化部署。', + installItems: [ + { + title: '快速安装(推荐)', + description: '通过官方安装文档完成依赖准备、实例初始化和服务启动,适合首次体验与标准化部署。', + action: { label: '查看安装文档', to: '/docs-installation' }, + }, + { + title: '版本与介质包安装', + description: '在 Releases 页面查看当前稳定版本与历史版本,并根据系统环境选择对应安装介质包。', + action: { label: '查看 Releases', to: '/releases-page' }, + }, + { + title: '容器化部署', + description: '基于官方 Docker 仓库进行镜像部署,便于在开发测试、CI/CD 与云原生环境中快速落地。', + action: { label: '查看 Docker 仓库', href: 'https://github.com/IvorySQL/docker_library' }, + }, + ], + ecosystemTitle: 'IvorySQL 及周边工具生态', + ecosystemDesc: + '社区提供丰富的生态工具:客户端工具、高可用工具、云原生工具、监控运维工具、备份恢复工具、地理信息工具等。', + ecosystemGroups: [ + { title: '数据访问中间件', items: ['pgpool-II', 'pgBouncer', 'odyssey', 'HAProxy', 'ShardingSphere', 'Citus', 'vip-manager'], wide: true }, + { title: 'ORM', items: ['Go', 'NodeJS', 'MyBatis', 'Hibernate'] }, + { title: '标准 SQL 及驱动', items: ['libpq', 'JDBC', 'ODBC', 'NodeJS', 'psycopg2', 'Go', 'Python', 'Rust', 'Ruby', 'ADO.NET', 'lib/pq', 'pgx'], wide: true }, + { title: '客户端工具', items: ['DBeaver', 'pgAdmin', 'Navicat', 'Navicat Premium'] }, + { title: '内核扩展', items: ['Oracle 兼容', 'PG 兼容及跟进'] }, + { title: '高可靠', items: ['pg_rman', 'WAL-G', 'pg_probackup', 'pgBackRest'] }, + { title: '集群工具', items: ['Patroni', 'repmgr', 'Pacemaker Corosync'] }, + { title: '监控运维', items: ['Prometheus', 'Alertmanager', 'pgMonitor', 'Grafana', 'PoWA'] }, + { title: '异构数据库访问工具', items: ['Debezium', 'pglogical', 'mysql_fdw', 'oracle_fdw'] }, + { title: '多模数据库', items: ['TimescaleDB', 'DocumentDB', 'PostgreSQL AGE', 'FerretDB'] }, + { title: '地理信息', items: ['PostGIS', 'pgRouting'] }, + { title: '机器学习及 AI', items: ['pgvector', 'MADlib', 'pg_ai_query'] }, + { title: 'DDL 及数据加载工具', items: ['pg_bulkload', 'ddlx'] }, + { title: '在线体验平台', items: ['postgres-wasm', 'IVYOnlineTrial'] }, + { title: '定时任务工具', items: ['pg_cron', 'pgAgent', 'pg_jobs'] }, + { title: '生态合作', items: ['Yukon', 'StackGres', 'Databene', 'WhaleOps'] }, + { title: '迁移/评估工具', items: ['Ora2Pg', 'ivyMigration', 'ivyEvaluation'] }, + { + title: '云生态', + items: ['Docker Compose', 'Podman', 'Docker Swarm', 'IvorySQL Cloud', 'IvorySQL Operator', 'IvorySQL Serverless'], + }, + ], + ecosystemFooters: [ + '操作系统(windows / CentOS / Redhat / ubuntu / openEuler / 银河麒麟 / 统信 UOS 等)', + 'x86、鲲鹏、龙芯、兆芯、申威、海光、飞腾、MIPS、RISC-V', + ], + ecosystemLegend: [ + { label: '已支持', tone: 'supported' }, + { label: '正在支持', tone: 'progress' }, + { label: '未来支持', tone: 'planned' }, + { label: '闭源产品', tone: 'proprietary' }, + ], + compatibilityTitle: 'IvorySQL 兼容认证', + compatibilityDesc: '更多兼容认证与生态合作信息,请查看合作伙伴页面。', + }, + en: { + slogan: 'An open source PostgreSQL with Oracle compatibility', + intro: + 'IvorySQL is an advanced open source Oracle-compatible PostgreSQL distribution built for strong compatibility and smooth replacement of the latest PostgreSQL. The compatible_mode switch lets you move between Oracle and PostgreSQL modes, while PL/iSQL supports Oracle PL/SQL syntax and package-style development.', + heroBadges: ['Oracle compatibility', 'Apache 2.0 open source', 'Built on PostgreSQL kernel'], + latestVersionPrefix: 'Latest Version', + latestVersionLabel: 'IvorySQL 5.1', + actions: [ + { label: 'Free Download', to: '/releases-page' }, + { label: 'Online Trial', href: ONLINE_TRIAL_URL }, + { label: 'Latest Webinars', to: '/webinars-page' }, + ], + coreTitle: 'Core Advantages', + coreDesc: 'From kernel compatibility to ecosystem integration, built for production workloads.', + coreItems: [ + { + title: 'Open Source Core', + description: 'Apache 2.0 licensed with no vendor lock-in, transparent code, and easy customization.', + }, + { + title: 'Deep Oracle Compatibility', + description: 'PL/iSQL and the ivorysql_ora extension provide strong PL/SQL compatibility for Oracle migrations.', + }, + { + title: 'Full-Platform Compatibility', + description: 'Compatible with mainstream hardware and operating systems, including domestic chip architectures.', + }, + { + title: 'Cloud-Native Support', + description: 'Container-ready support across Docker Compose/Swarm, K8S Operator, and cloud platforms.', + }, + { + title: 'Enterprise Support', + description: 'Backed by HighGo and validated in production-grade enterprise deployments.', + }, + { + title: 'Ecosystem Integration', + description: 'Inherits PostgreSQL SQL completeness, reliability, and ecosystem extensibility.', + }, + { + title: 'Broad Scenario Coverage', + description: 'Covers enterprise workloads, LBS, data warehousing, web/app development, and migrations.', + }, + { + title: 'Easy to Use', + description: 'Reduces management overhead with developer-friendly interfaces and third-party integration.', + }, + ], + scenariosTitle: 'Application Scenarios', + scenariosDesc: 'Supports mainstream database workloads from OLTP systems to analytical platforms.', + scenarioItems: [ + { + title: 'Enterprise Databases', + description: 'Fits ERP, transaction systems, and finance systems requiring high availability and complex logic.', + }, + { + title: 'LBS Applications', + description: 'Supports geospatial workloads such as O2O and game maps through PostGIS.', + }, + { + title: 'Data Warehouse / Big Data', + description: 'Build analytical platforms with rich data types and robust processing capabilities.', + }, + { + title: 'Websites / App Development', + description: 'Improves website and application efficiency with high-performance database capabilities.', + }, + { + title: 'Database Migration', + description: 'Enables direct migration paths from Oracle databases to IvorySQL.', + }, + ], + installTitle: 'IvorySQL Installation & Deployment', + installDesc: + 'Based on official docs and release resources, choose package installation, source build, or container deployment to match your environment.', + installItems: [ + { + title: 'Quick Installation (Recommended)', + description: + 'Follow the official installation guide to prepare dependencies, initialize clusters, and start services quickly.', + action: { label: 'Installation Guide', to: '/docs-installation' }, + }, + { + title: 'Packages & Releases', + description: + 'Use the Releases page to check stable/historical versions and pick proper installation packages for your platform.', + action: { label: 'View Releases', to: '/releases-page' }, + }, + { + title: 'Container Deployment', + description: + 'Use the official Docker repository for fast setup in development, CI/CD pipelines, and cloud-native environments.', + action: { label: 'Docker Repository', href: 'https://github.com/IvorySQL/docker_library' }, + }, + ], + ecosystemTitle: 'IvorySQL Ecosystem & Tools', + ecosystemDesc: + 'The community provides a rich ecosystem of tools, including client tools, high availability tools, cloud-native tools, monitoring and operations tools, backup and recovery tools, and geospatial tools.', + ecosystemGroups: [ + { + title: 'Data Access Middleware', + items: ['pgpool-II', 'pgBouncer', 'odyssey', 'HAProxy', 'ShardingSphere', 'Citus', 'vip-manager'], + wide: true, + }, + { title: 'ORM', items: ['Go', 'NodeJS', 'MyBatis', 'Hibernate'] }, + { + title: 'Standard SQL & Drivers', + items: ['libpq', 'JDBC', 'ODBC', 'NodeJS', 'psycopg2', 'Go', 'Python', 'Rust', 'Ruby', 'ADO.NET', 'lib/pq', 'pgx'], + wide: true, + }, + { title: 'Client Tools', items: ['DBeaver', 'pgAdmin', 'Navicat', 'Navicat Premium'] }, + { title: 'Core Extensions', items: ['Oracle Compatibility', 'PG Compatibility & Upstream Tracking'] }, + { title: 'High Availability', items: ['pg_rman', 'WAL-G', 'pg_probackup', 'pgBackRest'] }, + { title: 'Cluster Management Tools', items: ['Patroni', 'repmgr', 'Pacemaker Corosync'] }, + { title: 'Monitoring & Operations', items: ['Prometheus', 'Alertmanager', 'pgMonitor', 'Grafana', 'PoWA'] }, + { title: 'Heterogeneous Access Tools', items: ['Debezium', 'pglogical', 'mysql_fdw', 'oracle_fdw'] }, + { title: 'Multi-Model Database', items: ['TimescaleDB', 'DocumentDB', 'PostgreSQL AGE', 'FerretDB'] }, + { title: 'Geospatial', items: ['PostGIS', 'pgRouting'] }, + { title: 'Machine Learning & AI', items: ['pgvector', 'MADlib', 'pg_ai_query'] }, + { title: 'DDL & Data Loading Tools', items: ['pg_bulkload', 'ddlx'] }, + { title: 'Online Demo Platform', items: ['postgres-wasm', 'IVYOnlineTrial'] }, + { title: 'Job Scheduling Tools', items: ['pg_cron', 'pgAgent', 'pg_jobs'] }, + { title: 'Ecosystem Partnerships', items: ['Yukon', 'StackGres', 'Databene', 'WhaleOps'] }, + { title: 'Migration & Assessment Tools', items: ['Ora2Pg', 'ivyMigration', 'ivyEvaluation'] }, + { + title: 'Cloud Ecosystem', + items: ['Docker Compose', 'Podman', 'Docker Swarm', 'IvorySQL Cloud', 'IvorySQL Operator', 'IvorySQL Serverless'], + }, + ], + ecosystemFooters: [ + 'Operating System (windows / CentOS / Redhat / ubuntu / openEuler / kylin OS / UnionTech OS)', + 'x86 / Kunpeng / LoongArch / Zhaoxin / Sunway / Hygon / Phytium / MIPS / RISC-V', + ], + ecosystemLegend: [ + { label: 'Supported', tone: 'supported' }, + { label: 'Support In Progress', tone: 'progress' }, + { label: 'Support Planned', tone: 'planned' }, + { label: 'Proprietary Software', tone: 'proprietary' }, + ], + compatibilityTitle: 'IvorySQL Compatibility Certificates', + compatibilityDesc: 'See more compatibility certificates and ecosystem partnerships on the partners page.', + }, +}; + +function ActionLink({ action, className }) { + if (action.href) { + return ( + + {action.label} + + ); + } return ( -
- - {/* HOW 大会宣传,根据中英版显示不同图片及链接*/} -
- - IvorySQL Banner - -
- {/* 页面一 */} -
-
-
-
- {/* */} -
-
-
-

IvorySQL

-

Open Source Oracle Compatible PostgreSQL

-
- - Online Trial - - - Learn More - -
-
-
- - {/* */} -
-
-
-
-
- {/* 页面二 */} -
-
- - -
-
- - -
-
-
-
+ + {action.label} + ); } function ChatWidget() { - const {siteConfig:{customFields}} = useDocusaurusContext(); - + const { + siteConfig: { customFields }, + } = useDocusaurusContext(); + useEffect(() => { - // 创建外部 script 标签加载 SDK const script = document.createElement('script'); - script.src = "https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/1.1.0-beta.3/libs/cn/index.js"; + script.src = 'https://lf-cdn.coze.cn/obj/unpkg/flow-platform/chat-app-sdk/1.1.0-beta.3/libs/cn/index.js'; script.async = true; - // 当脚本加载完成后调用初始化代码 script.onload = () => { if (window.CozeWebSDK && window.CozeWebSDK.WebChatClient) { new window.CozeWebSDK.WebChatClient({ @@ -122,43 +414,278 @@ function ChatWidget() { componentProps: { title: 'IvorySQL Chatroom', icon: 'https://raw.githubusercontent.com/IvorySQL/Ivory-www/main/static/img/ivory-black.png', - }, auth: { - type: "token", + type: 'token', token: customFields.patToken, - onRefreshToken: function () { + onRefreshToken() { return customFields.patToken; - } - } + }, + }, }); } else { - console.error('CozeWebSDK 未加载成功!'); + console.error('Failed to load CozeWebSDK.'); } }; - // 将 script 标签添加到 body 中 document.body.appendChild(script); - - // 可选:组件卸载时清除 script 标签 return () => { document.body.removeChild(script); }; - }, []); + }, [customFields.botId, customFields.patToken]); + + return null; +} - return null; // 该组件不需要渲染任何内容 +function SectionTitle({ title, Icon }) { + return ( +
+
+ {Icon ? ( + + ) : null} +

{title}

+
+
+ ); } export default function Home() { - const {siteConfig} = useDocusaurusContext(); + const { siteConfig, i18n } = useDocusaurusContext(); + const isZh = i18n.currentLocale.toLowerCase().startsWith('zh'); + const content = isZh ? CONTENT.zh : CONTENT.en; + const [latestVersionLabel, setLatestVersionLabel] = useState(content.latestVersionLabel); + const certImages = [ + '/img/partners/cert1.jpg', + '/img/partners/cert2.jpg', + '/img/partners/cert3.jpg', + '/img/partners/cert4.jpg', + '/img/partners/cert5.png', + ]; + const certCarouselImages = [...certImages, ...certImages]; + + useEffect(() => { + let cancelled = false; + + setLatestVersionLabel(content.latestVersionLabel); + + const cacheLabel = readLatestVersionFromCache(); + if (cacheLabel) { + setLatestVersionLabel(cacheLabel); + return () => { + cancelled = true; + }; + } + + const updateLatestVersion = async () => { + try { + const response = await fetch(LATEST_RELEASE_API_URL, { + headers: { + Accept: 'application/vnd.github+json', + }, + }); + + if (!response.ok) { + return; + } + + const release = await response.json(); + const label = formatLatestReleaseLabel(release, content.latestVersionLabel); + + if (!cancelled && label) { + setLatestVersionLabel(label); + writeLatestVersionToCache(label); + } + } catch { + // Keep default label when request fails. + } + }; + + updateLatestVersion(); + + return () => { + cancelled = true; + }; + }, [content.latestVersionLabel]); + return ( - + - -
- +
+
+
+
+

IvorySQL

+

{content.slogan}

+
+ {content.heroBadges.map((badge) => ( + + {badge} + + ))} +
+

{content.intro}

+
+ {content.latestVersionPrefix} + + {latestVersionLabel} + +
+
+ {content.actions.map((action) => ( + + ))} +
+
+ +
+
+ +
+
+ +

{content.coreDesc}

+
+ {content.coreItems.map((item, index) => { + const CardIcon = CORE_CARD_ICONS[index % CORE_CARD_ICONS.length]; + return ( +
+
+ + + +

{item.title}

+
+

{item.description}

+
+ ); + })} +
+
+
+ +
+
+ +

{content.scenariosDesc}

+
+ {content.scenarioItems.map((item, index) => { + const CardIcon = SCENARIO_CARD_ICONS[index % SCENARIO_CARD_ICONS.length]; + return ( +
+
+ + + +

{item.title}

+
+

{item.description}

+
+ ); + })} +
+
+
+ +
+
+ +

{content.installDesc}

+
+ {content.installItems.map((item, index) => { + const CardIcon = INSTALL_CARD_ICONS[index % INSTALL_CARD_ICONS.length]; + return ( +
+
+ + + +

{item.title}

+
+

{item.description}

+ +
+ ); + })} +
+
+
+ +
+
+ +

{content.ecosystemDesc}

+
+
+ {content.ecosystemGroups.map((group) => ( +
+

{group.title}

+
+ {group.items.map((item) => { + const tone = getEcosystemToolTone(item); + return ( + + {item} + + ); + })} +
+
+ ))} +
+ +
+ {content.ecosystemFooters.map((line) => ( +

{line}

+ ))} +
+ +
+ {content.ecosystemLegend.map((legend) => ( + +