Compare commits

..

31 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
6a9bd7adcb feat: add GitHub project management infrastructure (RACI, issue templates, workflows)
Co-authored-by: amarantha-k <13425257+amarantha-k@users.noreply.github.com>
2026-03-02 21:44:33 +00:00
copilot-swe-agent[bot]
2680344850 Initial plan 2026-03-02 21:38:00 +00:00
oeggert
16364ea2d5 Merge pull request #3527 from XRPLF/update-lending-spec
Add DestinationTag and update links to lending spec
2026-02-27 15:55:25 -08:00
Oliver Eggert
27c25dbb12 add DestinationTag and update links to lending spec 2026-02-27 15:43:40 -08:00
Amarantha Kulkarni
61fc160523 Merge pull request #3521 from XRPLF/vulnerability-disclosure-report-feb2026
Vulnerability disclosure report February 2026
2026-02-26 14:13:20 -08:00
Amarantha Kulkarni
dc3db2c4f2 Apply suggestions from code review
Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com>
2026-02-26 14:06:37 -08:00
Amarantha Kulkarni
3ca8de9646 Apply suggestions from code review
Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com>
2026-02-26 13:54:57 -08:00
Maria Shodunke
2ca8706e41 Merge pull request #3519 from XRPLF/remove-batch-sav-setup
Don't use Batch for SAV tutorials setup
2026-02-26 18:05:43 +00:00
oeggert
dc0778b666 Merge pull request #3522 from XRPLF/update-lending-setup
Update lending tutorial setup scripts
2026-02-26 09:50:00 -08:00
Oliver Eggert
7268a0e52e add warnings for obsolete amendments 2026-02-26 09:45:35 -08:00
Amarantha Kulkarni
025c262f3e Merge pull request #3489 from XRPLF/add-bitget-wallet
Add Bitget wallet to XRP and Uses pages
2026-02-26 09:43:02 -08:00
Maria Shodunke
f26c5921b8 Apply review comments 2026-02-26 15:11:26 +00:00
amarantha-k
4094d1f76a Fix syntax for links 2026-02-25 13:33:53 -08:00
Oliver Eggert
62288d6c3b mark batch and fixbatchinnersigs as obsolete 2026-02-25 13:29:39 -08:00
amarantha-k
f5f08d8690 Incorporate review feedback 2026-02-25 13:14:46 -08:00
Oliver Eggert
88c28dfd05 update lending_setup.py to use tickets instead of batch 2026-02-25 12:56:32 -08:00
amarantha-k
c297fc5960 Fix typo in attribution 2026-02-25 12:33:58 -08:00
amarantha-k
fa9ddb8d67 Add attribution to post 2026-02-25 12:32:14 -08:00
amarantha-k
3c14520ad0 Add disclosure report for Batch 2026-02-25 12:08:39 -08:00
Oliver Eggert
a83e6e3a1a update lendingSetup.js to use tickets instead of batch 2026-02-25 11:40:52 -08:00
Maria Shodunke
79e800baae Add amendment badge to Batch concept doc 2026-02-25 18:00:51 +00:00
Maria Shodunke
646d4a7381 Don't use Batch for SAV tutorials setup 2026-02-25 17:29:21 +00:00
oeggert
62238e2ba8 Merge pull request #3511 from XRPLF/blog-3.1.1
Add 3.1.1 blog
2026-02-23 16:50:50 -08:00
Oliver Eggert
f211351f4c add reviewer suggestions 2026-02-23 16:47:53 -08:00
Oliver Eggert
7c41d016db add 3.1.1 blog 2026-02-23 15:49:01 -08:00
amarantha-k
9af8e7849e Update logo image for dark mode 2026-02-12 11:18:09 -08:00
mDuo13
c62d5090d0 Fix typo causing Bitget logo to not apear 2026-02-11 04:03:45 -08:00
amarantha-k
19ec38e7d8 Update number of wallets to 7 2026-02-10 20:34:49 -08:00
amarantha-k
f119214cea replace corrupted image files 2026-02-10 20:24:23 -08:00
amarantha-k
69d4113536 Add missing image files 2026-02-10 16:37:14 -08:00
amarantha-k
f5988554db Add Bitget wallet to XRP and Uses pages 2026-02-10 16:23:43 -08:00
422 changed files with 6355 additions and 83063 deletions

38
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,38 @@
# CODEOWNERS
# Defines code ownership for automatic review assignment.
# The last matching pattern takes precedence.
# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
# Global default — core maintainers review everything
* @XRPLF/docs-maintainers
# Concepts and narrative docs
/docs/concepts/ @XRPLF/docs-maintainers
# Tutorials
/docs/tutorials/ @XRPLF/docs-maintainers
# API reference
/docs/references/ @XRPLF/docs-maintainers
# Infrastructure / node docs
/docs/infrastructure/ @XRPLF/docs-maintainers
# Code samples — require technical review
/_code-samples/ @XRPLF/docs-maintainers
# API examples
/_api-examples/ @XRPLF/docs-maintainers
# Site theme and web tooling
/@theme/ @XRPLF/docs-maintainers
/styles/ @XRPLF/docs-maintainers
# Localization files
/@l10n/ @XRPLF/docs-maintainers
# CI/CD and project configuration — maintainer-only
/.github/ @XRPLF/docs-maintainers
/redocly.yaml @XRPLF/docs-maintainers
/package.json @XRPLF/docs-maintainers
/package-lock.json @XRPLF/docs-maintainers

67
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,67 @@
name: "Bug Report"
description: Report a typo, broken link, incorrect information, or other documentation error
title: "[Bug]: "
labels: ["bug"]
body:
- type: markdown
attributes:
value: |
Thanks for helping improve the XRP Ledger documentation! Please fill out this form as completely as possible.
- type: input
id: page-url
attributes:
label: Affected Page URL
description: The URL of the page with the issue (e.g. https://xrpl.org/docs/...)
placeholder: "https://xrpl.org/docs/..."
validations:
required: true
- type: dropdown
id: bug-type
attributes:
label: Type of Issue
description: What kind of problem did you find?
options:
- Typo or grammar error
- Broken or incorrect link
- Incorrect technical information
- Outdated content
- Missing content
- Formatting or display issue
- Other
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Describe the problem clearly. Include what is wrong and what it should say instead.
placeholder: "The page states X, but it should say Y because..."
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Content
description: What should the correct content say?
validations:
required: false
- type: input
id: source-reference
attributes:
label: Source Reference (optional)
description: Link to source code, specification, or other reference that confirms the correct information
placeholder: "https://github.com/XRPLF/rippled/..."
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I have searched existing issues to confirm this has not already been reported
required: true
- label: I have verified this issue exists on the live site (not just locally)

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: XRP Ledger Developer Discord
url: https://discord.gg/xrpl
about: Ask questions and discuss XRP Ledger development with the community
- name: XRPL.org Feedback
url: https://xrpl.org/about/contact
about: General feedback about the XRP Ledger portal

View File

@@ -0,0 +1,86 @@
name: "Content Update"
description: Request an update, addition, or improvement to existing documentation
title: "[Content]: "
labels: ["content updates"]
body:
- type: markdown
attributes:
value: |
Use this template to request changes to existing documentation content — for example, adding new details, updating outdated information, or improving clarity.
- type: input
id: page-url
attributes:
label: Affected Page URL
description: URL of the page that needs updating (if applicable)
placeholder: "https://xrpl.org/docs/..."
- type: dropdown
id: update-type
attributes:
label: Type of Update
options:
- Add missing information
- Update outdated content
- Improve clarity or readability
- Reorganize or restructure content
- Add examples or code samples
- Other
validations:
required: true
- type: dropdown
id: content-area
attributes:
label: Content Area
description: Which area of the documentation does this affect?
options:
- Concepts
- Tutorials
- References / API
- Infrastructure / Nodes
- Tokenization / NFTs
- DEX / AMM
- Payment Channels / Escrow
- Accounts / Wallets
- Localization / Translation
- Site tooling
- Other
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: Describe what needs to be changed and why. Be as specific as possible.
placeholder: "The documentation for X needs to be updated because..."
validations:
required: true
- type: input
id: source-reference
attributes:
label: Source / Reference
description: Link to the rippled source, amendment details, XLS proposal, or other authoritative reference
placeholder: "https://github.com/XRPLF/rippled/..."
- type: dropdown
id: amendment-status
attributes:
label: Amendment Status (if applicable)
description: If this relates to a protocol amendment, what is its status?
options:
- "N/A"
- In development (not yet proposed)
- Proposed (in open voting)
- Enabled on Mainnet
- Enabled on Devnet/Testnet only
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I have searched existing issues to avoid duplicates
required: true

View File

@@ -0,0 +1,66 @@
name: "Feature Request"
description: Suggest a new page, section, tool, or site feature
title: "[Feature]: "
labels: ["enhancement"]
body:
- type: markdown
attributes:
value: |
Use this template to suggest a new documentation page, site feature, or tooling improvement for the XRP Ledger Developer Portal.
- type: dropdown
id: feature-type
attributes:
label: Feature Type
options:
- New documentation page or section
- New interactive tool or code sample
- Site navigation or UX improvement
- Developer tooling or CI/CD improvement
- Localization support
- Other
validations:
required: true
- type: textarea
id: problem
attributes:
label: Problem to Solve
description: Describe the problem or gap this feature would address. Who would benefit?
placeholder: "As a developer building on the XRP Ledger, I need..."
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: Describe what you'd like to see added or changed.
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Have you considered any alternative approaches?
- type: dropdown
id: priority
attributes:
label: Priority (your assessment)
options:
- Low nice to have
- Medium would significantly improve the docs
- High blocks common developer workflows
validations:
required: true
- type: checkboxes
id: checklist
attributes:
label: Checklist
options:
- label: I have searched existing issues to avoid duplicates
required: true
- label: I am willing to contribute a PR for this feature

52
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,52 @@
## Description
<!-- Provide a clear, concise description of what this PR does. -->
Fixes #<!-- issue number -->
## Type of Change
<!-- Check all that apply -->
- [ ] Bug fix (documentation typo, broken link, incorrect information)
- [ ] Content update (new information, updated existing docs)
- [ ] New page or section
- [ ] Site feature / tooling change
- [ ] Localization / translation
- [ ] Refactoring (no functional change)
## RACI Confirmation
This project follows a RACI model for contributions. Please confirm the following:
| Role | Description | Confirmed |
|------|-------------|-----------|
| **R Responsible** | I am the author of these changes and have completed the work described above | - [ ] |
| **A Accountable** | I understand that a maintainer must approve this PR before it can be merged | - [ ] |
| **C Consulted** | I have tagged relevant subject-matter experts as reviewers (if applicable) | - [ ] |
| **I Informed** | The linked issue keeps stakeholders informed of this change | - [ ] |
> See [RACI Model](.github/project-management/RACI.md) for full role definitions.
## Review Checklist
**Author (Responsible)**
- [ ] PR is linked to an open issue
- [ ] Changes are accurate and technically correct
- [ ] Content follows the [style guide](https://xrpl.org/resources/contribute-documentation/)
- [ ] All links work and point to correct targets
- [ ] Code samples (if any) have been tested
- [ ] Localization impact considered (does this need translation updates?)
**Reviewer (Accountable / Consulted)**
- [ ] Technical accuracy verified
- [ ] Writing is clear and follows style guidelines
- [ ] No broken links or missing references
- [ ] No security-sensitive information inadvertently exposed
## Screenshots (if applicable)
<!-- Add before/after screenshots for UI or formatting changes -->
## Additional Notes
<!-- Any context, decisions made, or follow-up items -->

80
.github/labeler.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
# Auto-labeler configuration for pull_request_target events.
# Labels are applied based on which files are changed in the PR.
# See: https://github.com/actions/labeler
"content updates":
- changed-files:
- any-glob-to-any-file:
- "docs/**/*.md"
- "docs/**/*.mdx"
- "docs/**/*.page.tsx"
"web dev":
- changed-files:
- any-glob-to-any-file:
- "@theme/**"
- "styles/**"
- "*.tsx"
- "*.ts"
- "shared/**"
"docs-tooling":
- changed-files:
- any-glob-to-any-file:
- ".github/**"
- "redocly.yaml"
- "package.json"
- "package-lock.json"
- "tsconfig.json"
- "sidebars.yaml"
- "redirects.yaml"
"localization":
- changed-files:
- any-glob-to-any-file:
- "@l10n/**"
- "translations.yaml"
- "**/*.ja.md"
- "**/*.es-ES.md"
"javascript":
- changed-files:
- any-glob-to-any-file:
- "_code-samples/**/*.js"
- "_code-samples/**/*.mjs"
- "_code-samples/**/*.cjs"
- "docs/tutorials/**/*javascript*"
- "docs/tutorials/**/*js*"
"python":
- changed-files:
- any-glob-to-any-file:
- "_code-samples/**/*.py"
- "docs/tutorials/**/*python*"
"java":
- changed-files:
- any-glob-to-any-file:
- "_code-samples/**/*.java"
- "docs/tutorials/**/*java*"
"tokenization":
- changed-files:
- any-glob-to-any-file:
- "docs/**/*nft*"
- "docs/**/*token*"
- "docs/**/*fungible*"
- "docs/tutorials/nfts/**"
- "docs/concepts/tokens/**"
"infra":
- changed-files:
- any-glob-to-any-file:
- "docs/infrastructure/**"
"organization":
- changed-files:
- any-glob-to-any-file:
- "sidebars.yaml"
- "top-nav.yaml"
- "redirects.yaml"

179
.github/project-management/RACI.md vendored Normal file
View File

@@ -0,0 +1,179 @@
# RACI Model — XRP Ledger Developer Portal
This document defines the **RACI model** used to manage contributions to the XRP Ledger Developer Portal. RACI ensures every task has clear ownership, appropriate oversight, and proper communication for all stakeholders.
---
## What is RACI?
| Letter | Role | Description |
|--------|------|-------------|
| **R** | **Responsible** | The person who does the work. Every issue and PR must have exactly one responsible party. |
| **A** | **Accountable** | The person ultimately answerable for the outcome. Must approve work before it is merged. There is always exactly one accountable person per change. |
| **C** | **Consulted** | Subject-matter experts whose input is needed before or during the work. Two-way communication. |
| **I** | **Informed** | Stakeholders kept up to date on decisions and outcomes. One-way communication. |
---
## Role Mapping for This Project
### R Responsible (Contributors)
- **Community contributors** — anyone opening issues or submitting PRs
- **Assigned issue owners** — maintainers or contributors assigned to an issue
- Responsible parties create PRs, write content, and address review feedback
- A single contributor should be assigned `Responsible` per issue/PR
### A Accountable (Maintainers / CODEOWNERS)
- The **`@XRPLF/docs-maintainers`** team is accountable for all merged changes
- Enforced via [CODEOWNERS](../CODEOWNERS): every PR requires at least **one approval** from a code owner before it can be merged
- Maintainers may delegate review to subject-matter experts (Consulted) but remain accountable for the final merge decision
- Branch protection rules require:
- ✅ At least **1 approving review** from a CODEOWNER
- ✅ All **status checks pass** (CI workflows)
-**No unresolved conversations**
### C Consulted (Subject-Matter Experts)
Depending on the content area, the relevant experts should be tagged as reviewers:
| Content Area | Who to Consult |
|---|---|
| Protocol / core XRP Ledger mechanics | Protocol engineers (`@XRPLF/rippled` team members) |
| Smart contracts / Hooks | Hooks developers |
| NFT / tokenization | Tokenization working group |
| AMM / DeFi | DeFi working group |
| Client libraries (xrpl.js, xrpl-py, xrpl4j) | Respective library maintainers |
| Localization | Translation contributors for the target language |
| Site tooling / Redocly | Redocly configuration owners |
Tag SMEs using `@username` in PR descriptions or inline review comments.
### I Informed (Stakeholders)
The following parties are automatically informed via GitHub notifications:
- **Issue reporters** — notified of comments and resolution
- **PR watchers** — subscribed to PR updates
- **Repository watchers** — receive notifications for all activity
- **Linked issues** — cross-referenced via `Fixes #<number>` in PR descriptions
---
## RACI by Activity Type
| Activity | R (Does the Work) | A (Approves) | C (Consulted) | I (Informed) |
|---|---|---|---|---|
| Report a bug or issue | Community contributor | Maintainer (triage) | — | Repo watchers |
| Fix a documentation bug | Community contributor / Maintainer | CODEOWNER | SME if technical | Issue reporter |
| Write a new doc page | Assigned contributor | CODEOWNER | Protocol SME + Tech writer | Community |
| Update API reference | Assigned contributor | CODEOWNER | Protocol engineer | Developers |
| Add/update code sample | Contributor | CODEOWNER | Library maintainer | Developers |
| Merge a PR | — | CODEOWNER | — | PR author, watchers |
| Triage issues | Maintainer | Lead maintainer | — | Issue reporter |
| Release / deploy | CI/CD (automated) | Lead maintainer | — | Stakeholders |
| Update site tooling | Maintainer | CODEOWNER | Redocly config owner | All contributors |
---
## Workflow Summary
```
Community Contributor (R)
├─ Opens Issue ──────────────────────► Auto-triage labels applied
│ Maintainer triages & prioritizes
└─ Opens Pull Request ────────────────► PR Validation check runs
│ Auto-labels applied
│ Greeting for new contributors
├─ Tags SME reviewers (C) ───────► SMEs leave review comments
└─ Maintainer reviews (A) ───────► Approves or requests changes
└─ All checks pass ─────────► PR merged by Accountable maintainer
```
---
## Project Board
The XRP Ledger Developer Portal uses a **GitHub Project board** to track issue and PR status. The board has the following columns:
| Column | Description |
|---|---|
| **📋 Backlog** | Issues that are accepted but not yet scheduled |
| **🔍 Needs Triage** | New issues awaiting maintainer review |
| **📅 Planned** | Issues scheduled for an upcoming milestone |
| **🚧 In Progress** | Issues with a linked open PR or active assignee |
| **👀 In Review** | PRs awaiting review or with pending change requests |
| **✅ Done** | Merged PRs and closed issues |
> **Setup**: Maintainers can create and configure the board at:
> [organization-level project](https://github.com/orgs/XRPLF/projects)
> or
> [repository-level project](https://github.com/XRPLF/xrpl-dev-portal/projects)
### Automation Rules (configure in GitHub Projects)
- **Issue opened** → Move to _Needs Triage_
- **Label `stale` added** → Move to _Backlog_
- **PR opened / linked to issue** → Move issue to _In Progress_
- **PR review requested** → Move PR to _In Review_
- **PR merged** → Move to _Done_ and close linked issue
- **Issue closed** → Move to _Done_
---
## Branch Protection Requirements
To enforce the "at least one human review" requirement, configure branch protection on `master`:
1. Go to **Settings → Branches → Branch protection rules**
2. Add a rule for `master` (or your default branch)
3. Enable:
-**Require a pull request before merging**
-**Require approvals** — set to **1** (minimum)
-**Require review from Code Owners** (enforces CODEOWNERS file)
-**Require status checks to pass before merging**
- Add: `validate-pr` (from `pr-check.yml`)
-**Require conversation resolution before merging**
-**Do not allow bypassing the above settings** (for non-admins)
---
## Labels Reference
| Label | Description | Who Applies |
|---|---|---|
| `bug` | Typo, broken link, or incorrect info | Auto-triage / contributor |
| `content updates` | Updates to existing documentation | Auto-triage / maintainer |
| `enhancement` | New pages or features | Auto-triage / maintainer |
| `good first issue` | Suitable for new contributors | Maintainer |
| `help wanted` | Community contributions welcome | Maintainer |
| `needs triage` | Awaiting maintainer review | Auto-triage |
| `stale` | No activity for 90+ days | Stale bot |
| `wip` | Work in progress, not ready for review | Contributor |
| `tokenization` | NFT/token-related content | Auto-triage |
| `infra` | Validator/node/server content | Auto-triage |
| `web dev` | Site tooling, styles, templates | Auto-triage |
| `organization` | Navigation, structure, architecture | Auto-triage |
| `docs-tooling` | CI/CD, build tooling | Auto-triage |
| `localization` | Translation and i18n | Auto-triage |
| `javascript` | JavaScript code samples/tutorials | Auto-triage |
| `python` | Python code samples/tutorials | Auto-triage |
---
## Contributing Quick Reference
1. **Find an issue** — Browse the [project board](https://github.com/XRPLF/xrpl-dev-portal/issues) or open a new one using a template
2. **Assign yourself** — Comment "I'll work on this" or ask a maintainer to assign you
3. **Fork & branch** — Create a branch from `master`
4. **Make changes** — Follow the [style guide](https://xrpl.org/resources/contribute-documentation/)
5. **Open a PR** — Fill out the PR template completely, including RACI confirmation
6. **Tag reviewers** — Add SMEs as reviewers for technical accuracy
7. **Address feedback** — Respond to review comments and push updates
8. **Merge** — A maintainer (Accountable) will merge once all checks pass

19
.github/workflows/auto-label.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Auto-Label Pull Requests
on:
pull_request_target:
types: [opened, synchronize, reopened]
permissions:
contents: read
pull-requests: write
jobs:
label:
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
configuration-path: .github/labeler.yml
sync-labels: true

92
.github/workflows/greet-contributor.yml vendored Normal file
View File

@@ -0,0 +1,92 @@
name: Greet New Contributors
on:
issues:
types: [opened]
pull_request_target:
types: [opened]
permissions:
issues: write
pull-requests: write
jobs:
greet:
runs-on: ubuntu-latest
steps:
- name: Greet first-time issue reporter
if: github.event_name == 'issues'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const creator = context.payload.issue.user.login;
// Check if this is their first issue in the repo
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
creator: creator,
state: 'all',
});
// If only 1 issue exists, it's the one they just opened
if (issues.length <= 1) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: [
`👋 Welcome to the XRP Ledger Developer Portal, @${creator}! Thanks for opening your first issue.`,
'',
'Here are a few things to know:',
'- A maintainer will triage this issue and add appropriate labels',
'- If you\'d like to fix this yourself, check out our [Contributing Guide](https://xrpl.org/resources/contribute-documentation/)',
'- Join the conversation on [XRPL Discord](https://discord.gg/xrpl) if you have questions',
'',
'We appreciate your contribution to the XRP Ledger community! 🚀',
].join('\n'),
});
}
- name: Greet first-time PR contributor
if: github.event_name == 'pull_request_target'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const creator = context.payload.pull_request.user.login;
// Check previous merged PRs from this user
const { data: prs } = await github.rest.pulls.list({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'all',
});
const userPRs = prs.filter(pr => pr.user.login === creator);
// If this is their first PR
if (userPRs.length <= 1) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: [
`🎉 Welcome, @${creator}! Thanks for submitting your first pull request to the XRP Ledger Developer Portal.`,
'',
'Here\'s what happens next:',
'1. 🔍 Automated checks will run on your PR',
'2. 👀 A maintainer will review your changes (this may take a few days)',
'3. 💬 You may receive feedback or requests for changes',
'4. ✅ Once approved by a maintainer, your PR will be merged',
'',
'Please make sure your PR:',
'- Has a clear description of what was changed and why',
'- Is linked to an open issue (e.g. `Fixes #123`)',
'- Follows our [style guide](https://xrpl.org/resources/contribute-documentation/)',
'',
'Thank you for contributing to the XRP Ledger community! 🙏',
].join('\n'),
});
}

97
.github/workflows/issue-triage.yml vendored Normal file
View File

@@ -0,0 +1,97 @@
name: Issue Triage
on:
issues:
types: [opened, reopened]
permissions:
issues: write
jobs:
triage:
runs-on: ubuntu-latest
steps:
- name: Auto-label based on issue title and body
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issue = context.payload.issue;
const title = (issue.title || '').toLowerCase();
const body = (issue.body || '').toLowerCase();
const text = title + ' ' + body;
const labelsToAdd = new Set();
// --- keyword → label mapping ---
const rules = [
// Content areas
{ patterns: ['nft', 'nonfungible', 'non-fungible', 'token', 'mpt', 'fungible'], label: 'tokenization' },
{ patterns: ['amm', 'dex', 'offer', 'liquidity', 'pool'], label: 'defi' },
{ patterns: ['escrow', 'payment channel', 'check'], label: 'content updates' },
{ patterns: ['tutorial', 'how to', 'how-to', 'walkthrough', 'guide'], label: 'content updates' },
{ patterns: ['api', 'rpc', 'websocket', 'method', 'command', 'response field'], label: 'content updates' },
{ patterns: ['amendment', 'feature flag'], label: 'content updates' },
{ patterns: ['rippled', 'validator', 'node', 'server', 'peer'], label: 'infra' },
{ patterns: ['javascript', 'js ', 'node.js', 'typescript'], label: 'javascript' },
{ patterns: ['python'], label: 'python' },
{ patterns: ['java', 'xrpl4j'], label: 'java' },
{ patterns: ['navigate', 'menu', 'sidebar', 'landing page', 'homepage'], label: 'organization' },
{ patterns: ['style', 'css', 'layout', 'theme', 'mobile', 'responsive'], label: 'web dev' },
{ patterns: ['translate', 'translation', 'locali', 'japanese', 'spanish'], label: 'localization' },
{ patterns: ['broken link', 'dead link', '404 error', 'page not found', 'typo', 'grammar', 'spelling'], label: 'bug' },
{ patterns: ['redocly', 'build', 'ci ', 'ci/', 'workflow', 'lint', 'tooling'], label: 'docs-tooling' },
];
for (const rule of rules) {
if (rule.patterns.some(p => text.includes(p))) {
labelsToAdd.add(rule.label);
}
}
// If no labels matched, add 'needs triage' so maintainers notice it
if (labelsToAdd.size === 0) {
labelsToAdd.add('needs triage');
}
const existingLabels = (issue.labels || []).map(l => l.name);
const newLabels = [...labelsToAdd].filter(l => !existingLabels.includes(l));
if (newLabels.length > 0) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
labels: newLabels,
});
console.log(`Added labels: ${newLabels.join(', ')}`);
} else {
console.log('No new labels to add.');
}
- name: Request triage for unlabeled issues
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const issue = context.payload.issue;
const labels = (issue.labels || []).map(l => l.name);
// If the only label is 'needs triage', add a comment directing maintainers
if (labels.length === 1 && labels[0] === 'needs triage') {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue.number,
body: [
'👋 Thanks for opening this issue!',
'',
'This issue has been flagged for **maintainer triage** because it could not be automatically categorized.',
'A maintainer will review it shortly to add labels and assess priority.',
'',
'In the meantime, you can help by:',
'- Making sure the issue title clearly describes the problem',
'- Adding a link to the affected page (for documentation issues)',
'- Providing a reference to source code or specs (for technical accuracy issues)',
].join('\n'),
});
}

95
.github/workflows/pr-check.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
name: PR Validation
on:
pull_request_target:
types: [opened, edited, synchronize, reopened]
permissions:
pull-requests: write
issues: read
jobs:
validate-pr:
runs-on: ubuntu-latest
steps:
- name: Validate PR description and linked issue
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
const body = pr.body || '';
const title = pr.title || '';
const warnings = [];
const errors = [];
// 1. PR must have a non-trivial description
const descriptionSection = body.replace(/<!--[\s\S]*?-->/g, '').trim();
if (descriptionSection.length < 30) {
errors.push('❌ **Missing description**: Please provide a clear description of what this PR changes and why.');
}
// 2. PR should reference an issue (Fixes/Closes/Resolves #N or mention #N)
const issueRef = /(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s+#\d+|#\d+/i.test(body);
if (!issueRef) {
warnings.push('⚠️ **No linked issue detected**: Consider linking this PR to an issue using `Fixes #<number>` so progress is tracked on the project board.');
}
// 3. RACI table must have at least Responsible checked
// Simple check: if the RACI section exists, at least one checkbox should be filled
if (body.includes('RACI') && !/- \[x\]/i.test(body)) {
warnings.push('⚠️ **RACI checklist incomplete**: Please tick the checkboxes in the RACI Confirmation table.');
}
// 4. Draft PRs skip hard errors
const isDraft = pr.draft;
// Post a review comment only if there are issues
if (errors.length > 0 || warnings.length > 0) {
const lines = [
'## 🤖 PR Validation Report',
'',
...errors,
...warnings,
'',
isDraft ? '_This is a draft PR — errors above will need to be resolved before requesting review._'
: '_Please address the items above before requesting a maintainer review._',
'',
'---',
'_This comment is generated automatically. See [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines._',
];
// Check if we already posted a validation comment so we update rather than duplicate
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
});
const botComment = comments.find(c =>
c.user.type === 'Bot' && c.body.includes('PR Validation Report')
);
const commentBody = lines.join('\n');
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: commentBody,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: commentBody,
});
}
// Fail the check if there are hard errors and this is not a draft
if (errors.length > 0 && !isDraft) {
core.setFailed(`PR validation failed: ${errors.join(' | ')}`);
}
}

73
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,73 @@
name: Stale Issue & PR Management
on:
schedule:
# Runs daily at 01:00 UTC
- cron: "0 1 * * *"
workflow_dispatch:
permissions:
issues: write
pull-requests: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# ── Issues ────────────────────────────────────────────────────────
days-before-issue-stale: 90
days-before-issue-close: 14
stale-issue-label: "stale"
stale-issue-message: >
This issue has been open for 90 days with no recent activity.
It will be closed in 14 days unless someone comments or removes
the `stale` label.
If this issue is still relevant, please:
- Leave a comment with an update
- Remove the `stale` label if you plan to work on it
- Link it to an open PR if work is already in progress
close-issue-message: >
This issue was closed due to inactivity (90 + 14 days).
If you believe it is still relevant, please open a new issue
and reference this one.
# Issues with these labels are never marked stale
exempt-issue-labels: >
pinned,
security,
good first issue,
help wanted
# ── Pull Requests ─────────────────────────────────────────────────
days-before-pr-stale: 30
days-before-pr-close: 7
stale-pr-label: "stale"
stale-pr-message: >
This pull request has been open for 30 days with no recent activity.
It will be closed in 7 days unless there is new activity.
If this PR is still in progress:
- Push an update or leave a comment
- Remove the `stale` label to reset the timer
close-pr-message: >
This pull request was closed due to inactivity (30 + 7 days).
The branch has been preserved. Please open a new PR if you
wish to continue this work.
exempt-pr-labels: >
pinned,
security,
work in progress,
wip
# Keep the stale label when the issue/PR becomes active again
remove-stale-when-updated: true
# Only count non-bot comments as activity
operations-per-run: 100

3
.gitignore vendored
View File

@@ -8,10 +8,7 @@ yarn-error.log
*.iml
.venv/
_code-samples/*/js/package-lock.json
*.css.map
_code-samples/*/*/*[Ss]etup.json
.claude/
# PHP
composer.lock
.cursor/

View File

@@ -2,7 +2,6 @@ navbar.about: Acerca de
navbar.docs: Docs
navbar.resources: Recursos
navbar.community: Comunidad
navbar.showcase: Escaparate
footer.about: Acerca de
footer.docs: Docs
footer.resources: Recursos

View File

@@ -16,7 +16,6 @@ navbar.about: 概要
navbar.docs: ドキュメント
navbar.resources: リソース
navbar.community: コミュニティ
navbar.showcase: ショーケース
footer.about: 概要
footer.docs: ドキュメント
footer.resources: リソース

View File

@@ -1,152 +1,449 @@
import * as React from "react";
import { useSearchDialog } from "@redocly/theme/core/hooks";
import { SearchDialog } from "@redocly/theme/components/Search/SearchDialog";
import { useThemeConfig, useThemeHooks } from "@redocly/theme/core/hooks";
import { LanguagePicker } from "@redocly/theme/components/LanguagePicker/LanguagePicker";
import { slugify } from "../../helpers";
import { Link } from "@redocly/theme/components/Link/Link";
import { ColorModeSwitcher } from "@redocly/theme/components/ColorModeSwitcher/ColorModeSwitcher";
import { Search } from "@redocly/theme/components/Search/Search";
import arrowUpRight from "../../../static/img/icons/arrow-up-right-custom.svg";
import moment from "moment-timezone";
// Import from modular components
import { AlertBanner } from "./components/AlertBanner";
import { NavLogo } from "./components/NavLogo";
import { NavItems } from "./components/NavItems";
import { NavControls, HamburgerButton } from "./controls";
import { DevelopSubmenu, UseCasesSubmenu, CommunitySubmenu, NetworkSubmenu } from "./submenus";
import { MobileMenu } from "./mobile-menu";
import { alertBanner } from "./constants/navigation";
// @ts-ignore
// Re-export AlertBanner for backwards compatibility
export { AlertBanner } from "./components/AlertBanner";
const alertBanner = {
show: false,
message: "APEX 2025",
button: "REGISTER",
link: "https://www.xrpledgerapex.com/?utm_source=xrplwebsite&utm_medium=direct&utm_campaign=xrpl-event-ho-xrplapex-glb-2025-q1_xrplwebsite_ari_arp_bf_rsvp&utm_content=cta_btn_english_pencilbanner"
};
// Props interface for Navbar (extensible for future use)
interface NavbarProps {
className?: string;
}
export function AlertBanner({ message, button, link, show }) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const bannerRef = React.useRef(null);
const [displayDate, setDisplayDate] = React.useState("JUNE 10-12");
/**
* Main Navbar Component.
* Renders the complete navigation bar including:
* - Alert banner (when enabled)
* - Logo
* - Navigation items with desktop submenus
* - Control buttons (search, theme toggle, language)
* - Mobile menu
*/
export function Navbar(_props: NavbarProps = {}) {
const [mobileMenuOpen, setMobileMenuOpen] = React.useState(false);
const [activeSubmenu, setActiveSubmenu] = React.useState<string | null>(null);
const [closingSubmenu, setClosingSubmenu] = React.useState<string | null>(null);
const submenuTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
const closingTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);
// Use Redocly's search dialog hook - shared across navbar and mobile menu
const { isOpen: isSearchOpen, onOpen: onSearchOpen, onClose: onSearchClose } = useSearchDialog();
const handleHamburgerClick = () => {
setMobileMenuOpen(true);
};
const handleMobileMenuClose = () => {
setMobileMenuOpen(false);
};
const handleSubmenuMouseEnter = (itemLabel: string) => {
// Clear any pending close/closing timeouts
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
submenuTimeoutRef.current = null;
}
if (closingTimeoutRef.current) {
clearTimeout(closingTimeoutRef.current);
closingTimeoutRef.current = null;
}
// Cancel closing state and activate the new submenu
setClosingSubmenu(null);
setActiveSubmenu(itemLabel);
};
const handleSubmenuMouseLeave = () => {
submenuTimeoutRef.current = setTimeout(() => {
// Start closing animation
const currentSubmenu = activeSubmenu;
if (currentSubmenu) {
setClosingSubmenu(currentSubmenu);
setActiveSubmenu(null);
// After animation completes (300ms), clear closing state
closingTimeoutRef.current = setTimeout(() => {
setClosingSubmenu(null);
}, 350); // Slightly longer than animation to ensure completion
}
}, 150);
};
const handleSubmenuClose = () => {
// Close submenu immediately (for keyboard navigation)
if (activeSubmenu) {
setClosingSubmenu(activeSubmenu);
setActiveSubmenu(null);
// After animation completes, clear closing state
closingTimeoutRef.current = setTimeout(() => {
setClosingSubmenu(null);
}, 350);
}
};
// Handle scroll lock when submenu is open or closing
React.useEffect(() => {
if (activeSubmenu || closingSubmenu) {
document.body.classList.add('bds-submenu-open');
} else {
document.body.classList.remove('bds-submenu-open');
}
return () => {
document.body.classList.remove('bds-submenu-open');
const calculateCountdown = () => {
// Calculate days until June 11, 2025 8AM Singapore time
// This will automatically adjust for the user's timezone
const target = moment.tz('2025-06-11 08:00:00', 'Asia/Singapore');
const now = moment();
const daysUntil = target.diff(now, 'days');
// Show countdown if event is in the future, otherwise show the provided date
let newDisplayDate = "JUNE 10-12";
if (daysUntil > 0) {
newDisplayDate = daysUntil === 1 ? 'IN 1 DAY' : `IN ${daysUntil} DAYS`;
} else if (daysUntil === 0) {
// Check if it's today
const hoursUntil = target.diff(now, 'hours');
newDisplayDate = hoursUntil > 0 ? 'TODAY' : "JUNE 10-12";
}
setDisplayDate(newDisplayDate);
};
}, [activeSubmenu, closingSubmenu]);
// Calculate immediately
calculateCountdown();
// Update every hour
const interval = setInterval(calculateCountdown, 60 * 60 * 1000);
return () => clearInterval(interval);
}, []);
React.useEffect(() => {
const banner = bannerRef.current;
if (!banner) return;
const handleMouseEnter = () => {
banner.classList.add("has-hover");
};
// Attach the event listener
banner.addEventListener("mouseenter", handleMouseEnter);
// Clean up the event listener on unmount
return () => {
if (submenuTimeoutRef.current) {
clearTimeout(submenuTimeoutRef.current);
}
if (closingTimeoutRef.current) {
clearTimeout(closingTimeoutRef.current);
}
banner.removeEventListener("mouseenter", handleMouseEnter);
};
}, []);
const navbarClasses = [
"bds-navbar",
alertBanner.show ? "bds-navbar--with-banner" : ""
].filter(Boolean).join(" ");
if (show) {
return (
<a
href={link}
target="_blank"
ref={bannerRef}
className="top-banner fixed-top web-banner"
rel="noopener noreferrer"
aria-label="Get Tickets for the APEX 2025 Event"
>
<div className="banner-event-details">
<div className="event-info">{translate(message)}</div>
<div className="event-date">{displayDate}</div>
</div>
<div className="banner-button">
<div className="button-text">{translate(button)}</div>
<img
className="button-icon"
src={arrowUpRight}
alt="Get Tickets Icon"
/>
</div>
</a>
);
}
return null;
}
export function Navbar(props) {
// const [isOpen, setIsOpen] = useMobileMenu(false);
const themeConfig = useThemeConfig();
const { useL10n } = useThemeHooks();
const { changeLanguage } = useL10n();
const menu = themeConfig.navbar?.items;
const logo = themeConfig.logo;
const { href, altText, items } = props;
const pathPrefix = "";
const navItems = menu.map((item, index) => {
if (item.type === "group") {
return (
<NavDropdown
key={index}
label={item.label}
labelTranslationKey={item.labelTranslationKey}
items={item.items}
pathPrefix={pathPrefix}
/>
);
} else {
return (
<NavItem key={index}>
<Link to={item.link} className="nav-link">
{item.label}
</Link>
</NavItem>
);
}
});
React.useEffect(() => {
// Turns out jQuery is necessary for firing events on Bootstrap v4
// dropdowns. These events set classes so that the search bar and other
// submenus collapse on mobile when you expand one submenu.
const dds = $("#topnav-pages .dropdown");
const top_main_nav = document.querySelector("#top-main-nav");
dds.on("show.bs.dropdown", (evt) => {
top_main_nav.classList.add("submenu-expanded");
});
dds.on("hidden.bs.dropdown", (evt) => {
top_main_nav.classList.remove("submenu-expanded");
});
// Close navbar on .dropdown-item click
const toggleNavbar = () => {
const navbarToggler = document.querySelector(".navbar-toggler");
const isNavbarCollapsed =
navbarToggler.getAttribute("aria-expanded") === "true";
if (isNavbarCollapsed) {
navbarToggler?.click(); // Simulate click to toggle navbar
}
};
const dropdownItems = document.querySelectorAll(".dropdown-item");
dropdownItems.forEach((item) => {
item.addEventListener("click", toggleNavbar);
});
// Cleanup function to remove event listeners
return () => {
dropdownItems.forEach((item) => {
item.removeEventListener("click", toggleNavbar);
});
};
}, []);
return (
<>
<AlertBanner {...alertBanner} />
{/* Backdrop blur overlay when submenu is open or closing */}
<div
className={`bds-submenu-backdrop ${activeSubmenu || closingSubmenu ? 'bds-submenu-backdrop--active' : ''}`}
onClick={() => setActiveSubmenu(null)}
/>
<header
className={navbarClasses}
onMouseLeave={handleSubmenuMouseLeave}
>
<div className="bds-navbar__content">
<NavLogo />
<NavItems activeSubmenu={activeSubmenu} onSubmenuEnter={handleSubmenuMouseEnter} onSubmenuClose={handleSubmenuClose} />
<NavControls onSearch={onSearchOpen} />
<HamburgerButton onClick={handleHamburgerClick} />
</div>
{/* Submenus positioned relative to navbar */}
<div onMouseEnter={() => activeSubmenu && handleSubmenuMouseEnter(activeSubmenu)}>
<DevelopSubmenu isActive={activeSubmenu === 'Develop'} isClosing={closingSubmenu === 'Develop'} onClose={handleSubmenuClose} />
<UseCasesSubmenu isActive={activeSubmenu === 'Use Cases'} isClosing={closingSubmenu === 'Use Cases'} onClose={handleSubmenuClose} />
<CommunitySubmenu isActive={activeSubmenu === 'Community'} isClosing={closingSubmenu === 'Community'} onClose={handleSubmenuClose} />
<NetworkSubmenu isActive={activeSubmenu === 'Network'} isClosing={closingSubmenu === 'Network'} onClose={handleSubmenuClose} />
</div>
</header>
<MobileMenu isOpen={mobileMenuOpen} onClose={handleMobileMenuClose} onSearch={onSearchOpen} />
{/* Render SearchDialog when open - this is the actual search modal */}
{isSearchOpen && <SearchDialog onClose={onSearchClose} />}
<NavWrapper belowAlertBanner={alertBanner.show}>
<LogoBlock to={href} img={logo} alt={altText} />
<NavControls>
<MobileMenuIcon />
</NavControls>
<TopNavCollapsible>
<NavItems>
{navItems}
<div id="topnav-search" className="nav-item search">
<Search className="topnav-search" />
</div>
<div id="topnav-language" className="nav-item">
<LanguagePicker
onChangeLanguage={changeLanguage}
onlyIcon
alignment="end"
/>
</div>
<div id="topnav-theme" className="nav-item">
<ColorModeSwitcher />
</div>
</NavItems>
</TopNavCollapsible>
</NavWrapper>
</>
);
}
export function TopNavCollapsible({ children }) {
return (
<div
className="collapse navbar-collapse justify-content-between"
id="top-main-nav"
>
{children}
</div>
);
}
export function NavDropdown(props) {
const { label, items, pathPrefix, labelTranslationKey } = props;
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const dropdownGroups = items.map((item, index) => {
if (item.items) {
const groupLinks = item.items.map((item2, index2) => {
const cls2 = item2.external
? "dropdown-item external-link"
: "dropdown-item";
let item2_href = item2.link;
if (item2_href && !item2_href.match(/^https?:/)) {
item2_href = pathPrefix + item2_href;
}
//conditional specific for brand kit
if (item2.link === "/XRPL_Brand_Kit.zip") {
return (
<a target="_blank" key={index2} href="/XRPL_Brand_Kit.zip" className={cls2}>
{translate(item2.labelTranslationKey, item2.label)}
</a>
);
}
return (
<Link key={index2} className={cls2} to={item2_href}>
{translate(item2.labelTranslationKey, item2.label)}
</Link>
);
});
const clnm = "navcol col-for-" + slugify(item.label);
return (
<div key={index} className={clnm}>
<h5 className="dropdown-item">
{translate(item.labelTranslationKey, item.label)}
</h5>
{groupLinks}
</div>
);
} else if (item.icon) {
const hero_id = "dropdown-hero-for-" + slugify(label);
const img_alt = item.label + " icon";
let hero_href = item.link;
if (hero_href && !hero_href.match(/^https?:/)) {
hero_href = pathPrefix + hero_href;
}
const splitlabel = item.label.split(" || ");
let splittranslationkey = ["", ""];
if (item.labelTranslationKey) {
splittranslationkey = item.labelTranslationKey.split(" || ");
}
const newlabel = translate(splittranslationkey[0], splitlabel[0]);
const description = translate(splittranslationkey[1], splitlabel[1]); // splitlabel[1] might be undefined, that's ok
return (
<Link
key={index}
className="dropdown-item dropdown-hero"
id={hero_id}
to={hero_href}
>
<img id={item.hero} alt={img_alt} src={item.icon} />
<div className="dropdown-hero-text">
<h4>{newlabel}</h4>
<p>{description}</p>
</div>
</Link>
);
} else {
const cls = item.external
? "dropdown-item ungrouped external-link"
: "dropdown-item ungrouped";
let item_href = item.link;
if (item_href && !item_href.match(/^https?:/)) {
item_href = pathPrefix + item_href;
}
return (
<Link key={index} className={cls} to={item_href}>
{translate(item.labelTranslationKey, item.label)}
</Link>
);
}
});
const toggler_id = "topnav_" + slugify(label);
const dd_id = "topnav_dd_" + slugify(label);
return (
<li className="nav-item dropdown">
<a
className="nav-link dropdown-toggle"
href="#"
id={toggler_id}
role="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
>
<span>{translate(labelTranslationKey, label)}</span>
</a>
<div className="dropdown-menu" aria-labelledby={toggler_id} id={dd_id}>
{dropdownGroups}
</div>
</li>
);
}
export function NavWrapper(props) {
return (
<nav
className="top-nav navbar navbar-expand-lg navbar-dark fixed-top"
style={props.belowAlertBanner ? { marginTop: "52px" } : {}}
>
{props.children}
</nav>
);
}
export function NavControls(props) {
return (
<button
className="navbar-toggler collapsed"
type="button"
data-toggle="collapse"
data-target="#top-main-nav"
aria-controls="navbarHolder"
aria-expanded="false"
aria-label="Toggle navigation"
>
{props.children}
</button>
);
}
export function MobileMenuIcon() {
return (
<span className="navbar-toggler-icon">
<div></div>
</span>
);
}
export function GetStartedButton() {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<Link
className="btn btn-primary"
to={"/docs/tutorials"}
style={{ height: "38px", paddingTop: "11px" }}
>
{translate("Get Started")}
</Link>
);
}
export function NavItems(props) {
return (
<ul className="nav navbar-nav" id="topnav-pages">
{props.children}
</ul>
);
}
export function NavItem(props) {
return <li className="nav-item">{props.children}</li>;
}
export function LogoBlock(props) {
const { to, img, altText } = props;
return (
<Link className="navbar-brand" to="/">
<img className="logo" alt={"XRP LEDGER"} height="40" src="data:," />
</Link>
);
}
export class ThemeToggle extends React.Component {
auto_update_theme() {
const upc = window.localStorage.getItem("user-prefers-color");
let theme = "dark"; // Default to dark theme
if (!upc) {
// User hasn't saved a preference specifically for this site; check
// the browser-level preferences.
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: light)").matches
) {
theme = "light";
}
} else {
// Follow user's saved setting.
theme = upc == "light" ? "light" : "dark";
}
const disable_theme = theme == "dark" ? "light" : "dark";
document.documentElement.classList.add(theme);
document.documentElement.classList.remove(disable_theme);
}
user_choose_theme() {
const new_theme = document.documentElement.classList.contains("dark")
? "light"
: "dark";
window.localStorage.setItem("user-prefers-color", new_theme);
document.body.style.transition = "background-color .2s ease";
const disable_theme = new_theme == "dark" ? "light" : "dark";
document.documentElement.classList.add(new_theme);
document.documentElement.classList.remove(disable_theme);
}
render() {
return (
<div className="nav-item" id="topnav-theme">
<form className="form-inline">
<div
className="custom-control custom-theme-toggle form-inline-item"
title=""
data-toggle="tooltip"
data-placement="left"
data-original-title="Toggle Dark Mode"
>
<input
type="checkbox"
className="custom-control-input"
id="css-toggle-btn"
onClick={this.user_choose_theme}
/>
<label className="custom-control-label" htmlFor="css-toggle-btn">
<span className="d-lg-none">Light/Dark Theme</span>
</label>
</div>
</form>
</div>
);
}
componentDidMount() {
this.auto_update_theme();
}
}

View File

@@ -1,82 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import moment from "moment-timezone";
import { arrowUpRight } from "../constants/icons";
interface AlertBannerProps {
message: string;
button: string;
link: string;
show: boolean;
}
/**
* Alert Banner Component.
* Displays a promotional banner at the top of the page.
*/
export function AlertBanner({ message, button, link, show }: AlertBannerProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const bannerRef = React.useRef<HTMLAnchorElement>(null);
// Use null initial state to avoid hydration mismatch - server and client both render null initially
const [displayDate, setDisplayDate] = React.useState<string | null>(null);
React.useEffect(() => {
const calculateCountdown = () => {
const target = moment.tz('2025-06-11 08:00:00', 'Asia/Singapore');
const now = moment();
const daysUntil = target.diff(now, 'days');
let newDisplayDate = "JUNE 10-12";
if (daysUntil > 0) {
newDisplayDate = daysUntil === 1 ? 'IN 1 DAY' : `IN ${daysUntil} DAYS`;
} else if (daysUntil === 0) {
const hoursUntil = target.diff(now, 'hours');
newDisplayDate = hoursUntil > 0 ? 'TODAY' : "JUNE 10-12";
}
setDisplayDate(newDisplayDate);
};
calculateCountdown();
const interval = setInterval(calculateCountdown, 60 * 60 * 1000);
return () => clearInterval(interval);
}, []);
React.useEffect(() => {
const banner = bannerRef.current;
if (!banner) return;
const handleMouseEnter = () => {
banner.classList.add("has-hover");
};
banner.addEventListener("mouseenter", handleMouseEnter);
return () => {
banner.removeEventListener("mouseenter", handleMouseEnter);
};
}, []);
if (!show) return null;
return (
<a
href={link}
target="_blank"
ref={bannerRef}
className="top-banner fixed-top web-banner"
rel="noopener noreferrer"
aria-label={translate("Get Tickets for the APEX 2025 Event")}
>
<div className="banner-event-details">
<div className="event-info">{translate(message)}</div>
<div className="event-date">{displayDate ?? translate("JUNE 10-12")}</div>
</div>
<div className="banner-button">
<div className="button-text">{translate(button)}</div>
<img className="button-icon" src={arrowUpRight} alt="" />
</div>
</a>
);
}

View File

@@ -1,114 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { navItems } from "../constants/navigation";
interface NavItemsProps {
activeSubmenu: string | null;
onSubmenuEnter: (itemLabel: string) => void;
onSubmenuClose?: () => void;
}
/**
* Nav Items Component.
* Centered navigation links with submenu support.
* ARIA compliant with full keyboard navigation support.
*/
export function NavItems({ activeSubmenu, onSubmenuEnter, onSubmenuClose }: NavItemsProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const [activeItem, setActiveItem] = React.useState<string | null>(null);
const handleMouseEnter = (itemLabel: string, hasSubmenu: boolean) => {
setActiveItem(itemLabel);
if (hasSubmenu) {
onSubmenuEnter(itemLabel);
}
};
const handleMouseLeave = (hasSubmenu: boolean) => {
if (!hasSubmenu) {
setActiveItem(null);
}
// Don't close submenu on leave - let the parent Navbar handle that
};
const handleKeyDown = (event: React.KeyboardEvent, itemLabel: string) => {
switch (event.key) {
case 'Enter':
case ' ':
event.preventDefault();
// Toggle submenu on Enter/Space
if (activeSubmenu === itemLabel) {
onSubmenuClose?.();
} else {
onSubmenuEnter(itemLabel);
}
break;
case 'Escape':
event.preventDefault();
onSubmenuClose?.();
break;
case 'Tab':
// If submenu is open and Tab is pressed (without Shift), move focus into submenu
if (activeSubmenu === itemLabel && !event.shiftKey) {
event.preventDefault();
// Focus first focusable element in submenu
const submenu = document.querySelector('.bds-submenu--active');
const firstFocusable = submenu?.querySelector<HTMLElement>('a, button');
firstFocusable?.focus();
}
break;
case 'ArrowDown':
// If submenu is open, move focus into submenu
if (activeSubmenu === itemLabel) {
event.preventDefault();
// Focus first focusable element in submenu
const submenu = document.querySelector('.bds-submenu--active');
const firstFocusable = submenu?.querySelector<HTMLElement>('a, button');
firstFocusable?.focus();
}
break;
}
};
// Sync activeItem with activeSubmenu state
React.useEffect(() => {
if (!activeSubmenu) {
setActiveItem(null);
}
}, [activeSubmenu]);
return (
<nav className="bds-navbar__items" aria-label={translate("Main navigation")}>
{navItems.map((item) => (
item.hasSubmenu ? (
<button
key={item.label}
type="button"
className={`bds-navbar__item ${activeItem === item.label || activeSubmenu === item.label ? 'bds-navbar__item--active' : ''}`}
onMouseEnter={() => handleMouseEnter(item.label, true)}
onMouseLeave={() => handleMouseLeave(true)}
onKeyDown={(e) => handleKeyDown(e, item.label)}
aria-expanded={activeSubmenu === item.label}
aria-haspopup="menu"
>
{translate(item.labelTranslationKey, item.label)}
</button>
) : (
<BdsLink
key={item.label}
href={item.href}
className={`bds-navbar__item ${activeItem === item.label ? 'bds-navbar__item--active' : ''}`}
onMouseEnter={() => handleMouseEnter(item.label, false)}
onMouseLeave={() => handleMouseLeave(false)}
variant="inline"
>
{translate(item.labelTranslationKey, item.label)}
</BdsLink>
)
))}
</nav>
);
}

View File

@@ -1,34 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { xrpSymbolBlack, xrpLogotypeBlack, xrpLedgerNav } from "../constants/icons";
/**
* Nav Logo Component.
* Shows symbol on desktop/mobile, full logotype on tablet.
* On desktop hover, the "XRP LEDGER" text animates out to the right.
*/
export function NavLogo() {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<BdsLink href="/" className="bds-navbar__logo" aria-label={translate("XRP Ledger Home")} variant="inline">
<img
src={xrpSymbolBlack}
alt={translate("XRP Ledger")}
className="bds-navbar__logo-symbol"
/>
<img
src={xrpLedgerNav}
alt=""
className="bds-navbar__logo-text"
/>
<img
src={xrpLogotypeBlack}
alt={translate("XRP Ledger")}
className="bds-navbar__logo-full"
/>
</BdsLink>
);
}

View File

@@ -1,5 +0,0 @@
// Re-export navbar components
export { AlertBanner } from './AlertBanner';
export { NavLogo } from './NavLogo';
export { NavItems } from './NavItems';

View File

@@ -1,71 +0,0 @@
// Navbar icon imports
// Main navbar icons
export { default as xrpSymbolBlack } from "../../../../static/img/navbar/xrp-symbol-black.svg";
export { default as xrpLogotypeBlack } from "../../../../static/img/navbar/xrp-logotype-black.svg";
export { default as xrpLedgerNav } from "../../../../static/img/navbar/xrp-ledger-nav.svg";
export { default as searchIcon } from "../../../../static/img/navbar/search-icon.svg";
export { default as modeToggleIcon } from "../../../../static/img/navbar/mode-toggle.svg";
export { default as globeIcon } from "../../../../static/img/navbar/globe-icon.svg";
export { default as chevronDown } from "../../../../static/img/navbar/chevron-down.svg";
export { default as hamburgerIcon } from "../../../../static/img/navbar/hamburger-icon.svg";
export { default as arrowUpRight } from "../../../../static/img/icons/arrow-up-right-custom.svg";
// Network submenu pattern images (used for both light and dark mode)
export { default as resourcesIconPattern } from "../../../../static/img/navbar/resources-icon.svg";
export { default as insightsIconPattern } from "../../../../static/img/navbar/insights-icon.svg";
// Submenu icons - imported once, exported individually and used in navIcons mapping
import devHomeIcon from "../../../../static/img/navbar/dev_home.svg";
import learnIcon from "../../../../static/img/navbar/learn.svg";
import codeSamplesIcon from "../../../../static/img/navbar/code_samples.svg";
import docsIcon from "../../../../static/img/navbar/docs.svg";
import clientLibIcon from "../../../../static/img/navbar/client_lib.svg";
import paymentsIcon from "../../../../static/img/navbar/payments.svg";
import tokenizationIcon from "../../../../static/img/navbar/tokenization.svg";
import creditIcon from "../../../../static/img/navbar/credit.svg";
import tradingIcon from "../../../../static/img/navbar/trading.svg";
import communityIcon from "../../../../static/img/navbar/community.svg";
import fundingIcon from "../../../../static/img/navbar/funding.svg";
import contributeIcon from "../../../../static/img/navbar/contribute.svg";
import ecosystemIcon from "../../../../static/img/navbar/ecosystem.svg";
import insightsIcon from "../../../../static/img/navbar/insights.svg";
import resourcesIcon from "../../../../static/img/navbar/resources.svg";
// Re-export submenu icons for direct imports
export default {
devHomeIcon,
learnIcon,
codeSamplesIcon,
docsIcon,
clientLibIcon,
paymentsIcon,
tokenizationIcon,
creditIcon,
tradingIcon,
communityIcon,
fundingIcon,
contributeIcon,
ecosystemIcon,
insightsIcon,
resourcesIcon,
};
// Dynamic icon lookup for navbar submenus
export const navIcons: Record<string, string> = {
dev_home: devHomeIcon,
learn: learnIcon,
code_samples: codeSamplesIcon,
docs: docsIcon,
client_lib: clientLibIcon,
payments: paymentsIcon,
tokenization: tokenizationIcon,
credit: creditIcon,
trading: tradingIcon,
community: communityIcon,
contribute: contributeIcon,
insights: insightsIcon,
resources: resourcesIcon,
ecosystem: ecosystemIcon,
funding: fundingIcon,
};

View File

@@ -1,4 +0,0 @@
// Re-export all constants
export * from './icons';
export * from './navigation';

View File

@@ -1,161 +0,0 @@
import type { NavItem, SubmenuItemBase, SubmenuItemWithChildren, SubmenuItem, NetworkSubmenuSection } from '../types';
// Alert Banner Configuration
export const alertBanner = {
show: false,
message: "APEX 2025",
button: "REGISTER",
link: "https://www.xrpledgerapex.com/?utm_source=xrplwebsite&utm_medium=direct&utm_campaign=xrpl-event-ho-xrplapex-glb-2025-q1_xrplwebsite_ari_arp_bf_rsvp&utm_content=cta_btn_english_pencilbanner"
};
// Main navigation items
export const navItems: NavItem[] = [
{ label: "Develop", labelTranslationKey: "navbar.develop", href: "/develop", hasSubmenu: true },
{ label: "Use Cases", labelTranslationKey: "navbar.usecases", href: "/use-cases", hasSubmenu: true },
{ label: "Community", labelTranslationKey: "navbar.community", href: "/community", hasSubmenu: true },
{ label: "Network", labelTranslationKey: "navbar.network", href: "/resources", hasSubmenu: true },
{ label: "Showcase (Temporary)", labelTranslationKey: "navbar.showcase", href: "/showcase", hasSubmenu: false },
];
// Develop submenu data structure
export const developSubmenuData: {
left: SubmenuItemBase[];
right: SubmenuItemWithChildren[];
} = {
left: [
{ label: "Developer's Home", href: "/develop", icon: "dev_home" },
{ label: "Learn", href: "https://learn.xrpl.org", icon: "learn" },
{ label: "Code Samples", href: "/resources/code-samples", icon: "code_samples" },
],
right: [
{
label: "Docs",
href: "/docs",
icon: "docs",
children: [
{ label: "API Reference", href: "/references" },
{ label: "Tutorials", href: "/docs/tutorials" },
{ label: "Concepts", href: "/concepts" },
{ label: "Infrastructure", href: "/docs/infrastructure" },
],
},
{
label: "Client Libraries",
href: "#",
icon: "client_lib",
children: [
{ label: "JavaScript", href: "#" },
{ label: "Python", href: "#" },
{ label: "PHP", href: "#" },
{ label: "Go", href: "#" },
],
},
],
};
// Use Cases submenu data structure
export const useCasesSubmenuData: {
left: SubmenuItemWithChildren[];
right: SubmenuItemWithChildren[];
} = {
left: [
{
label: "Payments",
href: "/use-cases/payments",
icon: "payments",
children: [
{ label: "Direct XRP Payments", href: "/use-cases/payments/direct-xrp-payments" },
{ label: "Cross-currency Payments", href: "/use-cases/payments/cross-currency-payments" },
{ label: "Escrow", href: "/use-cases/payments/escrow" },
{ label: "Checks", href: "/use-cases/payments/checks" },
],
},
{
label: "Tokenization",
href: "/use-cases/tokenization",
icon: "tokenization",
children: [
{ label: "Stablecoin", href: "/use-cases/tokenization/stablecoin" },
{ label: "NFT", href: "/use-cases/tokenization/nft" },
],
},
],
right: [
{
label: "Credit",
href: "/use-cases/credit",
icon: "credit",
children: [
{ label: "Lending", href: "/use-cases/credit/lending" },
{ label: "Collateralization", href: "/use-cases/credit/collateralization" },
{ label: "Sustainability", href: "/use-cases/credit/sustainability" },
],
},
{
label: "Trading",
href: "/use-cases/trading",
icon: "trading",
children: [
{ label: "DEX", href: "/use-cases/trading/dex" },
{ label: "Permissioned Trading", href: "/use-cases/trading/permissioned-trading" },
{ label: "AMM", href: "/use-cases/trading/amm" },
],
},
],
};
// Community submenu data structure
export const communitySubmenuData: {
left: SubmenuItem[];
right: SubmenuItem[];
} = {
left: [
{
label: "Community",
href: "/community",
icon: "community",
children: [
{ label: "Events", href: "/community/events" },
{ label: "Blog", href: "/blog" },
],
},
{ label: "Funding", href: "/community/developer-funding", icon: "funding" },
],
right: [
{
label: "Contribute",
href: "/community/contribute",
icon: "contribute",
children: [
{ label: "Bug Bounty", href: "/blog/2020/rippled-1.5.0#bug-bounties-and-responsible-disclosures" },
{ label: "Research", href: "https://xls.xrpl.org/" },
],
},
{ label: "Ecosystem Map", href: "/about/uses", icon: "ecosystem" },
],
};
// Network submenu data
export const networkSubmenuData: NetworkSubmenuSection[] = [
{
label: "Resources",
href: "/resources",
icon: "resources",
children: [
{ label: "About", href: "/about/history" },
{ label: "XRPL Brand Kit", href: "/community/brand-kit" },
],
patternColor: 'lilac',
},
{
label: "Insights",
href: "/insights",
icon: "insights",
children: [
{ label: "Explorer", href: "https://livenet.xrpl.org" },
{ label: "Amendment Voting Status", href: "https://xrpl.org/resources/known-amendments" },
],
patternColor: 'green',
},
];

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { hamburgerIcon } from "../constants/icons";
interface HamburgerButtonProps {
onClick?: () => void;
}
/**
* Hamburger Menu Button Component.
* Mobile-only button that opens the mobile menu.
*/
export function HamburgerButton({ onClick }: HamburgerButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={hamburgerIcon} ariaLabel={translate("Open menu")} className="bds-navbar__hamburger" onClick={onClick} />;
}

View File

@@ -1,28 +0,0 @@
interface IconButtonProps {
/** The icon image source */
icon: string;
/** Accessible label for the button */
ariaLabel: string;
/** Optional click handler */
onClick?: () => void;
/** CSS class name for styling variants */
className?: string;
}
/**
* Unified Icon Button component.
* Used for search, mode toggle, hamburger menu, and other icon-only buttons.
*/
export function IconButton({ icon, ariaLabel, onClick, className = "bds-navbar__icon" }: IconButtonProps) {
return (
<button
type="button"
className={className}
aria-label={ariaLabel}
onClick={onClick}
>
<img src={icon} alt="" />
</button>
);
}

View File

@@ -1,85 +0,0 @@
import * as React from "react";
import { useLanguagePicker, useThemeHooks } from "@redocly/theme/core/hooks";
interface LanguageDropdownProps {
isOpen: boolean;
onClose: () => void;
}
/**
* Language Dropdown Component.
* Displays available language options in a dropdown menu.
* Based on Figma design: dark background with rounded corners.
*/
export function LanguageDropdown({ isOpen, onClose }: LanguageDropdownProps) {
const { currentLocale, locales, setLocale } = useLanguagePicker();
const { useL10n, useTranslate } = useThemeHooks();
const { changeLanguage } = useL10n();
const { translate } = useTranslate();
const dropdownRef = React.useRef<HTMLDivElement>(null);
// Handle clicking outside to close
React.useEffect(() => {
if (!isOpen) return;
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
// Check if click was on the language pill (parent trigger)
const target = event.target as HTMLElement;
if (!target.closest('.bds-navbar__lang-pill')) {
onClose();
}
}
};
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleEscape);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleEscape);
};
}, [isOpen, onClose]);
if (!isOpen || locales.length < 2) {
return null;
}
const handleLanguageSelect = (localeCode: string) => {
setLocale(localeCode);
changeLanguage(localeCode);
onClose();
};
return (
<div
ref={dropdownRef}
className="bds-lang-dropdown"
role="menu"
aria-label={translate("Language selection")}
>
{locales.map((locale) => {
const isActive = locale.code === currentLocale?.code;
return (
<button
key={locale.code}
type="button"
role="menuitem"
className={`bds-lang-dropdown__item ${isActive ? 'bds-lang-dropdown__item--active' : ''}`}
onClick={() => handleLanguageSelect(locale.code)}
aria-current={isActive ? 'true' : undefined}
>
{locale.name || locale.code}
</button>
);
})}
</div>
);
}

View File

@@ -1,53 +0,0 @@
import { useLanguagePicker, useThemeHooks } from "@redocly/theme/core/hooks";
import { globeIcon, chevronDown } from "../constants/icons";
interface LanguagePillProps {
onClick?: () => void;
isOpen?: boolean;
}
/**
* Get short display name for a locale code.
* e.g., "en-US" -> "En", "ja" -> "日本語"
*/
function getLocaleShortName(code: string | undefined): string {
if (!code) return "En";
// Map locale codes to short display names
const shortNames: Record<string, string> = {
"en-US": "En",
"en": "En",
"ja": "日本語",
};
return shortNames[code] || code.substring(0, 2).toUpperCase();
}
/**
* Language Pill Button Component.
* Shows current language and opens language selector.
*/
export function LanguagePill({ onClick, isOpen }: LanguagePillProps) {
const { currentLocale } = useLanguagePicker();
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const displayName = getLocaleShortName(currentLocale?.code);
return (
<button
type="button"
className={`bds-navbar__lang-pill ${isOpen ? 'bds-navbar__lang-pill--open' : ''}`}
aria-label={translate("Select language")}
aria-expanded={isOpen}
aria-haspopup="menu"
onClick={onClick}
>
<img src={globeIcon} alt="" className="bds-navbar__lang-pill-icon" />
<span className="bds-navbar__lang-pill-text">
<span>{displayName}</span>
<img src={chevronDown} alt="" className="bds-navbar__lang-pill-chevron" />
</span>
</button>
);
}

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { modeToggleIcon } from "../constants/icons";
interface ModeToggleButtonProps {
onClick?: () => void;
}
/**
* Mode Toggle Button Component.
* Icon button that toggles between light and dark mode.
*/
export function ModeToggleButton({ onClick }: ModeToggleButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={modeToggleIcon} ariaLabel={translate("Toggle color mode")} onClick={onClick} />;
}

View File

@@ -1,46 +0,0 @@
import * as React from "react";
import { SearchButton } from "./SearchButton";
import { ModeToggleButton } from "./ModeToggleButton";
import { LanguagePill } from "./LanguagePill";
import { LanguageDropdown } from "./LanguageDropdown";
interface NavControlsProps {
onSearch?: () => void;
}
/**
* Nav Controls Component.
* Right side of the navbar containing search, mode toggle, and language selector.
*/
export function NavControls({ onSearch }: NavControlsProps) {
const [isLanguageDropdownOpen, setIsLanguageDropdownOpen] = React.useState(false);
const handleModeToggle = () => {
// Toggle between light and dark theme
const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark";
window.localStorage.setItem("user-prefers-color", newTheme);
document.body.style.transition = "background-color .2s ease";
document.documentElement.classList.remove("dark", "light");
document.documentElement.classList.add(newTheme);
};
const handleLanguageClick = () => {
setIsLanguageDropdownOpen(!isLanguageDropdownOpen);
};
const handleLanguageDropdownClose = () => {
setIsLanguageDropdownOpen(false);
};
return (
<div className="bds-navbar__controls">
<SearchButton onClick={onSearch} />
<ModeToggleButton onClick={handleModeToggle} />
<div className="bds-navbar__lang-wrapper">
<LanguagePill onClick={handleLanguageClick} isOpen={isLanguageDropdownOpen} />
<LanguageDropdown isOpen={isLanguageDropdownOpen} onClose={handleLanguageDropdownClose} />
</div>
</div>
);
}

View File

@@ -1,19 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { IconButton } from "./IconButton";
import { searchIcon } from "../constants/icons";
interface SearchButtonProps {
onClick?: () => void;
}
/**
* Search Button Component.
* Icon button that triggers the search modal.
*/
export function SearchButton({ onClick }: SearchButtonProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return <IconButton icon={searchIcon} ariaLabel={translate("Search")} onClick={onClick} />;
}

View File

@@ -1,11 +0,0 @@
// Unified base component
export { IconButton } from './IconButton';
// Specific button implementations (use IconButton internally)
export { NavControls } from './NavControls';
export { SearchButton } from './SearchButton';
export { ModeToggleButton } from './ModeToggleButton';
export { LanguagePill } from './LanguagePill';
export { LanguageDropdown } from './LanguageDropdown';
export { HamburgerButton } from './HamburgerButton';

View File

@@ -1,30 +0,0 @@
import * as React from "react";
interface ChevronIconProps {
expanded: boolean;
}
/**
* Chevron Icon Component for Mobile Accordion
*/
export function ChevronIcon({ expanded }: ChevronIconProps) {
return (
<svg
className={`bds-mobile-menu__chevron ${expanded ? 'bds-mobile-menu__chevron--expanded' : ''}`}
width="13"
height="8"
viewBox="0 0 13 8"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M1 1L6.5 6.5L12 1"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}

View File

@@ -1,14 +0,0 @@
import * as React from "react";
/**
* Close Icon Component for Mobile Menu
*/
export function CloseIcon() {
return (
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
<line x1="7" y1="7" x2="21" y2="21" stroke="#141414" strokeWidth="2" strokeLinecap="round" />
<line x1="21" y1="7" x2="7" y2="21" stroke="#141414" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}

View File

@@ -1,56 +0,0 @@
interface ArrowIconProps {
className?: string;
color?: string;
/**
* When true, the horizontal line has a class for CSS animation (parent links).
* When false, the full arrow is shown without animation class (child links).
*/
animated?: boolean;
}
/**
* Unified Arrow Icon component.
* - For parent links (animated=true): horizontal line animates away on hover
* - For child links (animated=false): full static arrow
*/
export function ArrowIcon({ className, color = "currentColor", animated = true }: ArrowIconProps) {
return (
<svg
className={className}
width="15"
height="14"
viewBox="0 0 26 22"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
{/* Chevron part */}
<path
d="M14.0019 1.00191L24.0015 11.0015L14.0019 21.001"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
strokeLinecap="round"
/>
{/* Horizontal line */}
<path
d="M23.999 10.999H0"
stroke={color}
strokeWidth="2"
strokeMiterlimit="10"
strokeLinecap="round"
className={animated ? "arrow-horizontal" : undefined}
/>
</svg>
);
}
// Backwards-compatible aliases
export const SubmenuArrow = (props: Omit<ArrowIconProps, 'animated'>) => (
<ArrowIcon {...props} animated={true} />
);
export const SubmenuChildArrow = (props: Omit<ArrowIconProps, 'animated'>) => (
<ArrowIcon {...props} animated={false} />
);

View File

@@ -1,6 +0,0 @@
// Re-export all icon components
// Unified arrow icon with backwards-compatible aliases
export { ArrowIcon, SubmenuArrow, SubmenuChildArrow } from './SubmenuArrow';
export { CloseIcon } from './CloseIcon';
export { ChevronIcon } from './ChevronIcon';

View File

@@ -1,13 +0,0 @@
// Main Navbar component
export { Navbar } from './Navbar';
// Re-export types
export * from './types';
// Re-export components for advanced usage
export * from './components';
export * from './controls';
export * from './submenus';
export * from './mobile-menu';
export * from './icons';

View File

@@ -1,215 +0,0 @@
import * as React from "react";
import { useThemeHooks, useLanguagePicker } from "@redocly/theme/core/hooks";
import { BdsLink } from "../../../../shared/components/Link/Link";
import { CloseIcon, ChevronIcon } from "../icons";
import { xrpSymbolBlack, globeIcon, chevronDown, modeToggleIcon, searchIcon } from "../constants/icons";
import { navItems } from "../constants/navigation";
import { MobileMenuContent, type MobileMenuKey } from "./MobileMenuContent";
interface MobileMenuProps {
isOpen: boolean;
onClose: () => void;
onSearch?: () => void;
}
/**
* Mobile Menu Component.
* Full-screen slide-out menu for mobile devices.
*/
export function MobileMenu({ isOpen, onClose, onSearch }: MobileMenuProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const [expandedItem, setExpandedItem] = React.useState<string | null>("Develop");
// Handle body scroll lock
React.useEffect(() => {
if (isOpen) {
document.body.classList.add('bds-mobile-menu-open');
} else {
document.body.classList.remove('bds-mobile-menu-open');
}
return () => {
document.body.classList.remove('bds-mobile-menu-open');
};
}, [isOpen]);
const toggleAccordion = (item: string) => {
setExpandedItem(expandedItem === item ? null : item);
};
const handleSearch = () => {
if (onSearch) {
onSearch();
}
onClose();
};
const handleModeToggle = () => {
const newTheme = document.documentElement.classList.contains("dark") ? "light" : "dark";
window.localStorage.setItem("user-prefers-color", newTheme);
document.body.style.transition = "background-color .2s ease";
document.documentElement.classList.remove("dark", "light");
document.documentElement.classList.add(newTheme);
};
const renderAccordionContent = (label: string) => {
// All nav items with submenus use the unified MobileMenuContent
const validKeys: MobileMenuKey[] = ['Develop', 'Use Cases', 'Community', 'Network'];
if (validKeys.includes(label as MobileMenuKey)) {
return <MobileMenuContent menuKey={label as MobileMenuKey} />;
}
return null;
};
return (
<div className={`bds-mobile-menu ${isOpen ? 'bds-mobile-menu--open' : ''}`}>
{/* Header */}
<div className="bds-mobile-menu__header">
<BdsLink href="/" className="bds-navbar__logo" aria-label={translate("XRP Ledger Home")} onClick={onClose} variant="inline">
<img src={xrpSymbolBlack} alt={translate("XRP Ledger")} className="bds-navbar__logo-symbol" style={{ width: 33, height: 28 }} />
</BdsLink>
<button
type="button"
className="bds-mobile-menu__close"
aria-label={translate("Close menu")}
onClick={onClose}
>
<CloseIcon />
</button>
</div>
{/* Content */}
<div className="bds-mobile-menu__content">
<div className="bds-mobile-menu__accordion">
{navItems.map((item) => (
<React.Fragment key={item.label}>
<button
type="button"
className="bds-mobile-menu__accordion-header"
onClick={() => item.hasSubmenu ? toggleAccordion(item.label) : null}
aria-expanded={expandedItem === item.label}
>
{item.hasSubmenu ? (
<>
<span>{translate(item.labelTranslationKey, item.label)}</span>
<ChevronIcon expanded={expandedItem === item.label} />
</>
) : (
<BdsLink
href={item.href}
onClick={onClose}
variant="inline"
style={{
display: 'flex',
width: '100%',
justifyContent: 'space-between',
alignItems: 'center',
color: 'inherit',
textDecoration: 'none'
}}
>
<span>{translate(item.labelTranslationKey, item.label)}</span>
<ChevronIcon expanded={false} />
</BdsLink>
)}
</button>
{item.hasSubmenu && (
<div
className={`bds-mobile-menu__accordion-content ${
expandedItem === item.label ? 'bds-mobile-menu__accordion-content--expanded' : ''
}`}
>
{renderAccordionContent(item.label)}
</div>
)}
</React.Fragment>
))}
</div>
</div>
{/* Footer */}
<MobileMenuFooter
onModeToggle={handleModeToggle}
onSearch={handleSearch}
/>
</div>
);
}
interface MobileMenuFooterProps {
onModeToggle: () => void;
onSearch: () => void;
}
/**
* Get short display name for a locale code.
*/
function getLocaleShortName(code: string | undefined): string {
if (!code) return "En";
const shortNames: Record<string, string> = {
"en-US": "En",
"en": "En",
"ja": "日本語",
};
return shortNames[code] || code.substring(0, 2).toUpperCase();
}
function MobileMenuFooter({ onModeToggle, onSearch }: MobileMenuFooterProps) {
const { currentLocale, locales, setLocale } = useLanguagePicker();
const { useL10n, useTranslate } = useThemeHooks();
const { changeLanguage } = useL10n();
const { translate } = useTranslate();
const [isLangOpen, setIsLangOpen] = React.useState(false);
const displayName = getLocaleShortName(currentLocale?.code);
const handleLanguageSelect = (localeCode: string) => {
setLocale(localeCode);
changeLanguage(localeCode);
setIsLangOpen(false);
};
return (
<div className="bds-mobile-menu__footer">
<div className="bds-mobile-menu__lang-wrapper">
<button
type="button"
className={`bds-mobile-menu__lang-pill ${isLangOpen ? 'bds-mobile-menu__lang-pill--open' : ''}`}
aria-label={translate("Select language")}
aria-expanded={isLangOpen}
onClick={() => setIsLangOpen(!isLangOpen)}
>
<img src={globeIcon} alt="" className="bds-mobile-menu__lang-pill-icon" />
<span className="bds-mobile-menu__lang-pill-text">
<span>{displayName}</span>
<img src={chevronDown} alt="" className="bds-mobile-menu__lang-pill-chevron" />
</span>
</button>
{isLangOpen && locales.length >= 2 && (
<div className="bds-lang-dropdown bds-lang-dropdown--mobile" role="menu">
{locales.map((locale) => {
const isActive = locale.code === currentLocale?.code;
return (
<button
key={locale.code}
type="button"
role="menuitem"
className={`bds-lang-dropdown__item ${isActive ? 'bds-lang-dropdown__item--active' : ''}`}
onClick={() => handleLanguageSelect(locale.code)}
>
{locale.name || locale.code}
</button>
);
})}
</div>
)}
</div>
<button type="button" className="bds-mobile-menu__footer-icon" aria-label={translate("Toggle color mode")} onClick={onModeToggle}>
<img src={modeToggleIcon} alt="" />
</button>
<button type="button" className="bds-mobile-menu__footer-icon" aria-label={translate("Search")} onClick={onSearch}>
<img src={searchIcon} alt="" />
</button>
</div>
);
}

View File

@@ -1,47 +0,0 @@
import { MobileMenuSection } from "./MobileMenuSection";
import { developSubmenuData, useCasesSubmenuData, communitySubmenuData, networkSubmenuData } from "../constants/navigation";
import type { SubmenuItem, SubmenuItemWithChildren, NetworkSubmenuSection } from "../types";
export type MobileMenuKey = 'Develop' | 'Use Cases' | 'Community' | 'Network';
interface MobileMenuContentProps {
/** Which menu section to render */
menuKey: MobileMenuKey;
}
/** Get flattened menu items based on menu key */
function getMenuItems(menuKey: MobileMenuKey): (SubmenuItem | SubmenuItemWithChildren | NetworkSubmenuSection)[] {
switch (menuKey) {
case 'Develop':
return [...developSubmenuData.left, ...developSubmenuData.right];
case 'Use Cases':
return [...useCasesSubmenuData.left, ...useCasesSubmenuData.right];
case 'Community':
return [...communitySubmenuData.left, ...communitySubmenuData.right];
case 'Network':
return networkSubmenuData;
}
}
/**
* Unified Mobile Menu Content component.
* Renders accordion content for any menu section.
*/
export function MobileMenuContent({ menuKey }: MobileMenuContentProps) {
const items = getMenuItems(menuKey);
return (
<div className="bds-mobile-menu__tier-list">
{items.map((item) => (
<MobileMenuSection key={item.label} item={item} />
))}
</div>
);
}
// Backwards-compatible named exports
export const MobileMenuDevelopContent = () => <MobileMenuContent menuKey="Develop" />;
export const MobileMenuUseCasesContent = () => <MobileMenuContent menuKey="Use Cases" />;
export const MobileMenuCommunityContent = () => <MobileMenuContent menuKey="Community" />;
export const MobileMenuNetworkContent = () => <MobileMenuContent menuKey="Network" />;

View File

@@ -1,54 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { SubmenuArrow, SubmenuChildArrow } from "../icons";
import { navIcons } from "../constants/icons";
import { hasChildren, type SubmenuItem } from "../types";
interface MobileMenuSectionProps {
item: SubmenuItem;
}
/**
* Reusable mobile menu section component.
* Renders a parent link with icon, and optionally child links.
*/
export function MobileMenuSection({ item }: MobileMenuSectionProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const itemHasChildren = hasChildren(item);
return (
<React.Fragment>
<a href={item.href} className="bds-mobile-menu__tier1 bds-mobile-menu__parent-link">
<span className="bds-mobile-menu__icon">
<img src={navIcons[item.icon]} alt={translate(item.label)} />
</span>
<span className="bds-mobile-menu__link bds-mobile-menu__link--bold">
{translate(item.label)}
<span className="bds-mobile-menu__arrow">
<SubmenuArrow />
</span>
</span>
</a>
{itemHasChildren && (
<div className="bds-mobile-menu__tier2">
{item.children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-mobile-menu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-mobile-menu__sublink-arrow">
<SubmenuChildArrow />
</span>
</a>
))}
</div>
)}
</React.Fragment>
);
}

View File

@@ -1,16 +0,0 @@
// Main mobile menu component
export { MobileMenu } from './MobileMenu';
// Unified content component with backwards-compatible aliases
export {
MobileMenuContent,
MobileMenuDevelopContent,
MobileMenuUseCasesContent,
MobileMenuCommunityContent,
MobileMenuNetworkContent,
type MobileMenuKey
} from './MobileMenuContent';
// Reusable section component
export { MobileMenuSection } from './MobileMenuSection';

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface CommunitySubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Community Submenu Component.
* Wrapper for unified Submenu component with 'community' variant.
*/
export function CommunitySubmenu({ isActive, isClosing, onClose }: CommunitySubmenuProps) {
return <Submenu variant="community" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface DevelopSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Develop Submenu Component.
* Wrapper for unified Submenu component with 'develop' variant.
*/
export function DevelopSubmenu({ isActive, isClosing, onClose }: DevelopSubmenuProps) {
return <Submenu variant="develop" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface NetworkSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Network Submenu Component.
* Wrapper for unified Submenu component with 'network' variant.
*/
export function NetworkSubmenu({ isActive, isClosing, onClose }: NetworkSubmenuProps) {
return <Submenu variant="network" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,277 +0,0 @@
import * as React from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { SubmenuSection } from "./SubmenuSection";
import { ArrowIcon } from "../icons";
import { navIcons, resourcesIconPattern, insightsIconPattern } from "../constants/icons";
import { developSubmenuData, useCasesSubmenuData, communitySubmenuData, networkSubmenuData } from "../constants/navigation";
import type { SubmenuItem, SubmenuItemWithChildren, NetworkSubmenuSection } from "../types";
export type SubmenuVariant = 'develop' | 'use-cases' | 'community' | 'network';
interface SubmenuProps {
/** Which submenu variant to render */
variant: SubmenuVariant;
/** Whether this submenu is currently active (visible) */
isActive: boolean;
/** Whether this submenu is in closing animation */
isClosing: boolean;
/** Callback when submenu should close (e.g., Escape key) */
onClose?: () => void;
}
/** Get submenu data based on variant */
function getSubmenuData(variant: SubmenuVariant) {
switch (variant) {
case 'develop': return developSubmenuData;
case 'use-cases': return useCasesSubmenuData;
case 'community': return communitySubmenuData;
case 'network': return networkSubmenuData;
}
}
/** Get CSS modifier class for variant */
function getVariantClass(variant: SubmenuVariant): string {
if (variant === 'develop') return '';
return `bds-submenu--${variant}`;
}
/**
* Get all focusable elements within a container
*/
function getFocusableElements(container: HTMLElement | null): HTMLElement[] {
if (!container) return [];
return Array.from(
container.querySelectorAll<HTMLElement>('a[href], button:not([disabled]), [tabindex]:not([tabindex="-1"])')
);
}
/**
* Find the next nav item button after the current expanded one
*/
function getNextNavItem(): HTMLElement | null {
const navItems = document.querySelectorAll<HTMLElement>('.bds-navbar__item');
const currentIndex = Array.from(navItems).findIndex(item =>
item.getAttribute('aria-expanded') === 'true'
);
if (currentIndex >= 0 && currentIndex < navItems.length - 1) {
return navItems[currentIndex + 1];
}
// If at the last nav item, go to the first control button (search, etc.)
const controls = document.querySelector<HTMLElement>('.bds-navbar__controls button, .bds-navbar__controls a');
return controls;
}
/**
* Unified Submenu component.
* Handles all submenu variants (develop, use-cases, community, network).
* ARIA compliant with full keyboard navigation support.
*/
export function Submenu({ variant, isActive, isClosing, onClose }: SubmenuProps) {
const submenuRef = React.useRef<HTMLDivElement>(null);
// Handle keyboard events for accessibility
const handleKeyDown = React.useCallback((event: KeyboardEvent) => {
if (!isActive) return;
if (event.key === 'Escape') {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
// Handle Tab at end of submenu - move to next nav item
if (event.key === 'Tab' && !event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const lastFocusable = focusableElements[focusableElements.length - 1];
if (document.activeElement === lastFocusable) {
event.preventDefault();
onClose?.();
const nextItem = getNextNavItem();
nextItem?.focus();
}
}
// Handle Shift+Tab at start of submenu - move back to trigger button
if (event.key === 'Tab' && event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const firstFocusable = focusableElements[0];
if (document.activeElement === firstFocusable) {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
}
}, [isActive, onClose]);
// Add keyboard event listener when submenu is active
React.useEffect(() => {
if (isActive) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isActive, handleKeyDown]);
// Network submenu needs special handling for theme-aware patterns
if (variant === 'network') {
return <NetworkSubmenuContent isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}
const data = getSubmenuData(variant);
const classNames = [
'bds-submenu',
getVariantClass(variant),
isActive ? 'bds-submenu--active' : '',
isClosing ? 'bds-submenu--closing' : '',
].filter(Boolean).join(' ');
// Standard two-column layout
const leftItems = 'left' in data ? data.left : [];
const rightItems = 'right' in data ? data.right : [];
return (
<div
ref={submenuRef}
className={classNames}
role="menu"
aria-hidden={!isActive}
>
<div className="bds-submenu__left">
{leftItems.map((item: SubmenuItem | SubmenuItemWithChildren) => (
<SubmenuSection key={item.label} item={item} />
))}
</div>
<div className="bds-submenu__right">
{rightItems.map((item: SubmenuItem | SubmenuItemWithChildren) => (
<SubmenuSection key={item.label} item={item} />
))}
</div>
</div>
);
}
/** Network submenu with pattern images (same for light and dark mode) */
function NetworkSubmenuContent({ isActive, isClosing, onClose }: { isActive: boolean; isClosing: boolean; onClose?: () => void }) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
// Handle keyboard events for accessibility
const handleKeyDown = React.useCallback((event: KeyboardEvent) => {
if (!isActive) return;
if (event.key === 'Escape') {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
// Handle Tab at end of submenu - move to next nav item
if (event.key === 'Tab' && !event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const lastFocusable = focusableElements[focusableElements.length - 1];
if (document.activeElement === lastFocusable) {
event.preventDefault();
onClose?.();
const nextItem = getNextNavItem();
nextItem?.focus();
}
}
// Handle Shift+Tab at start of submenu - move back to trigger button
if (event.key === 'Tab' && event.shiftKey) {
const activeSubmenu = document.querySelector<HTMLElement>('.bds-submenu--active');
const focusableElements = getFocusableElements(activeSubmenu);
const firstFocusable = focusableElements[0];
if (document.activeElement === firstFocusable) {
event.preventDefault();
onClose?.();
// Return focus to the trigger button
const triggerButton = document.querySelector<HTMLButtonElement>(
`.bds-navbar__item[aria-expanded="true"]`
);
triggerButton?.focus();
}
}
}, [isActive, onClose]);
// Add keyboard event listener when submenu is active
React.useEffect(() => {
if (isActive) {
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}
}, [isActive, handleKeyDown]);
// Use same pattern images for both light and dark mode
const patternImages = {
lilac: resourcesIconPattern,
green: insightsIconPattern,
};
const classNames = [
'bds-submenu',
'bds-submenu--network',
isActive ? 'bds-submenu--active' : '',
isClosing ? 'bds-submenu--closing' : '',
].filter(Boolean).join(' ');
return (
<div className={classNames} role="menu" aria-hidden={!isActive}>
{networkSubmenuData.map((section: NetworkSubmenuSection) => (
<div key={section.label} className="bds-submenu__section">
<a href={section.href} className="bds-submenu__tier1 bds-submenu__parent-link">
<span className="bds-submenu__icon">
<img src={navIcons[section.icon]} alt={translate(section.label)} />
</span>
<span className="bds-submenu__link bds-submenu__link--bold">
{translate(section.label)}
<span className="bds-submenu__arrow">
<ArrowIcon animated />
</span>
</span>
</a>
<div className="bds-submenu__network-content">
<div className="bds-submenu__tier2">
{section.children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-submenu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-submenu__sublink-arrow">
<ArrowIcon animated={false} />
</span>
</a>
))}
</div>
<div className="bds-submenu__pattern-container">
<img src={patternImages[section.patternColor]} alt="" className="bds-submenu__pattern" />
</div>
</div>
</div>
))}
</div>
);
}

View File

@@ -1,70 +0,0 @@
import { useThemeHooks } from "@redocly/theme/core/hooks";
import { ArrowIcon } from "../icons";
import { navIcons } from "../constants/icons";
import { hasChildren, type SubmenuItem, type SubmenuItemWithChildren, type SubmenuItemBase } from "../types";
interface SubmenuSectionProps {
/** The menu item data */
item: SubmenuItem | SubmenuItemWithChildren | SubmenuItemBase;
/** Whether to render children links (default: true) */
showChildren?: boolean;
}
/**
* Unified submenu section component.
* Renders a parent link with icon, and optionally child links if the item has them.
*
* Usage:
* - For items that may or may not have children: <SubmenuSection item={item} />
* - For parent-only rendering: <SubmenuSection item={item} showChildren={false} />
*/
export function SubmenuSection({ item, showChildren = true }: SubmenuSectionProps) {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
const itemHasChildren = hasChildren(item as SubmenuItem);
const shouldShowChildren = showChildren && itemHasChildren;
return (
<div className="bds-submenu__section">
<a href={item.href} className="bds-submenu__tier1 bds-submenu__parent-link">
<span className="bds-submenu__icon">
<img src={navIcons[item.icon]} alt={translate(item.label)} />
</span>
<span className="bds-submenu__link bds-submenu__link--bold">
{translate(item.label)}
<span className="bds-submenu__arrow">
<ArrowIcon animated />
</span>
</span>
</a>
{shouldShowChildren && (
<div className="bds-submenu__tier2">
{(item as SubmenuItemWithChildren).children.map((child) => (
<a
key={child.label}
href={child.href}
className="bds-submenu__sublink"
target={child.href.startsWith('http') ? '_blank' : undefined}
rel={child.href.startsWith('http') ? 'noopener noreferrer' : undefined}
>
{translate(child.label)}
<span className="bds-submenu__sublink-arrow">
<ArrowIcon animated={false} />
</span>
</a>
))}
</div>
)}
</div>
);
}
// Backwards-compatible aliases (all use the unified SubmenuSection)
export const SubmenuParentOnly = ({ item }: { item: SubmenuItemBase }) => (
<SubmenuSection item={item} showChildren={false} />
);
export const SubmenuWithChildren = ({ item }: { item: SubmenuItemWithChildren }) => (
<SubmenuSection item={item} showChildren={true} />
);

View File

@@ -1,16 +0,0 @@
import { Submenu } from "./Submenu";
interface UseCasesSubmenuProps {
isActive: boolean;
isClosing: boolean;
onClose?: () => void;
}
/**
* Desktop Use Cases Submenu Component.
* Wrapper for unified Submenu component with 'use-cases' variant.
*/
export function UseCasesSubmenu({ isActive, isClosing, onClose }: UseCasesSubmenuProps) {
return <Submenu variant="use-cases" isActive={isActive} isClosing={isClosing} onClose={onClose} />;
}

View File

@@ -1,12 +0,0 @@
// Unified submenu component
export { Submenu, type SubmenuVariant } from './Submenu';
// Variant-specific wrappers (all use Submenu internally)
export { DevelopSubmenu } from './DevelopSubmenu';
export { UseCasesSubmenu } from './UseCasesSubmenu';
export { CommunitySubmenu } from './CommunitySubmenu';
export { NetworkSubmenu } from './NetworkSubmenu';
// Reusable submenu section component
export { SubmenuSection, SubmenuParentOnly, SubmenuWithChildren } from './SubmenuSection';

View File

@@ -1,42 +0,0 @@
// Types for submenu data structures
export interface SubmenuChild {
label: string;
href: string;
active?: boolean;
}
export interface SubmenuItemBase {
label: string;
href: string;
icon: string;
}
export interface SubmenuItemWithChildren extends SubmenuItemBase {
children: SubmenuChild[];
}
export type SubmenuItem = SubmenuItemBase | SubmenuItemWithChildren;
// Network submenu section with decorative images
export interface NetworkSubmenuSection {
label: string;
href: string;
icon: string;
children: SubmenuChild[];
patternColor: 'lilac' | 'green';
}
// Nav item type
export interface NavItem {
label: string;
labelTranslationKey: string;
href: string;
hasSubmenu: boolean;
}
// Type guard to check if item has children
export function hasChildren(item: SubmenuItem): item is SubmenuItemWithChildren {
return 'children' in item && Array.isArray((item as SubmenuItemWithChildren).children);
}

View File

@@ -25,7 +25,7 @@ export function XRPLCard(props: XRPLCardProps) {
<p className="card-text">{props.body}</p>
)}
</div>
{/* <div className="card-footer">&nbsp;</div> */}
<div className="card-footer">&nbsp;</div>
</Link>
)
}

View File

@@ -1,178 +0,0 @@
# Color System Migration Summary
**Date:** October 21, 2025
**Source:** [XRPL.org Design Tokens - Figma](https://figma.com/design/zRyhXG4hRP3Lk3B6Owr3eo/XRPL.org-Design-Tokens)
## Migration Strategy: Clean Migration
The old 10-level color scale (100-1000) has been completely migrated to a new 5-level scale (100-500). All references in the codebase have been updated, and backward compatibility aliases have been removed for a clean implementation.
**Mapping Applied:**
```
Old System → New System
100 → 100 (lightest)
200 → 100
300 → 200
400 → 200
500 → 300 (mid-tone, default)
600 → 300
700 → 400
800 → 400
900 → 500 (darkest)
1000 → 500
```
**Migration Approach:**
1. All color usages (600-1000) were found and replaced with their new equivalents (300-500)
2. Backward compatibility aliases were removed from `_colors.scss`
3. Only 100-500 design tokens remain for each color family
## Color Families Updated
### Primary Colors
#### Gray (Neutral) ⏸️ NOT UPDATED
- **Status:** Original values retained - design tokens not ready
- **Current values:** #FCFCFD, #F5F5F7, #E0E0E1, #C1C1C2, #A2A2A4, #838386, #454549, #343437, #232325, #111112
- **Note:** Gray/Neutral design tokens will be migrated in a future update
#### Green ✅
- **New Design Tokens:** #EAFCF1, #70EE97, #21E46B, #0DAA3E, #078139
- **Variables:** `$green-100` through `$green-500` only
- **Migrated:** 14 file references updated
- **Special:** `$apex-2023-green` (#00FF76) retained
#### Lilac (Primary) ✅ *replaces blue-purple*
- **New Design Tokens:** #F2EDFF, #D9CAFF, #C0A7FF, #7649E3, #5429A1
- **Variables:** `$lilac-100` through `$lilac-500` only
- **Legacy aliases:** `$blue-purple-100` through `$blue-purple-500` map to lilac (600-900 removed)
- **Migrated:** 16 file references updated
- **Note:** This is a new color name in the design system
### Secondary Colors
#### Red ✅ *NEW*
- **New Design Tokens:** #FDECE7, #F27A66, #F0643A, #DA4518, #A22514
- **Variables:** `$red-100` through `$red-500` only
- **Note:** This is a completely new color family added to the design system
#### Pink ✅ *replaces magenta*
- **New Design Tokens:** #FDF1F4, #F2B5C3, #F18DA5, #FF577F, #DC466F
- **Variables:** `$pink-100` through `$pink-500` only
- **Legacy aliases:** `$magenta-100` through `$magenta-500` map to pink (600-900 removed)
- **Migrated:** 7 file references updated
#### Blue ✅
- **New Design Tokens:** #EDF4FF, #93BFF1, #428CFF, #0179E7, #0A4DC0
- **Variables:** `$blue-100` through `$blue-500` only
- **Migrated:** 8 file references updated
- **Special:** `$accent-blue-90` (#001133) retained
#### Yellow ✅
- **New Design Tokens:** #F3F1EB, #E6F1A7, #DBF15E, #E1DB26, #D4C02D
- **Variables:** `$yellow-100` through `$yellow-500` only
- **Migrated:** 11 file references updated
## Colors Retained (No Design Token Replacement)
### Orange
- **Status:** Legacy values retained
- **Values:** #FFEEE5, #FFCCB2, #FFAA80, #FF884B, #FF6719, #E54D00, #B23C00, #802B00, #4C1A00
- **Reason:** No corresponding design token in new system
### Red-purple
- **Status:** Legacy values retained
- **Values:** #FBE5FF, #F2B2FF, #EA80FF, #E24CFF, #D919FF, #C000E5, #9500B2, #6B0080, #40004C
- **Reason:** No corresponding design token in new system
### Special Event Colors
- `$apex-2023-green: #00FF76`
- `$token-2049-purple: #410bb9`
- `$accent-blue-90: #001133`
## Bootstrap & Component Colors
All Bootstrap theme variables remain functional:
- `$primary``$purple` (now `$lilac-400`)
- `$secondary``$gray-200`
- `$success``$green-500`
- `$info``$blue-500`
- `$warning``$yellow-500`
- `$danger``$magenta-500` (now `$pink-500`)
## Breaking Changes
**Removed Variables:**
- All color variables from 600-1000 have been removed for: Green, Blue, Lilac, Pink, Red, Yellow
- `$blue-purple-600` through `$blue-purple-900` removed (use 100-500)
- `$magenta-600` through `$magenta-900` removed (use 100-500)
**No Impact:**
- All usages in the codebase have been updated
- Legacy color name aliases maintained (100-500 only):
- `$blue-purple-100` through `$blue-purple-500` → maps to `$lilac-*`
- `$magenta-100` through `$magenta-500` → maps to `$pink-*`
## Color Name Changes
| Old Name | New Name | Reason |
|----------|----------|--------|
| `blue-purple-*` | `lilac-*` | Design system rebranding |
| `magenta-*` | `pink-*` | Design system rebranding |
| N/A | `red-*` | New color family added |
## Usage Recommendations
### Current Best Practices
Use the new 5-level design tokens (100-500):
```scss
// Primary colors
color: $gray-300; // Gray (not yet migrated - still uses old values)
color: $green-300; // Default green
color: $lilac-400; // Primary purple
// Secondary colors
color: $red-300; // Default red
color: $pink-300; // Default pink
color: $blue-300; // Default blue
color: $yellow-300; // Default yellow
```
### Legacy Aliases Still Available
```scss
// These legacy names work (100-500 only)
color: $blue-purple-400; // Same as $lilac-400
color: $magenta-300; // Same as $pink-300
```
## Files Modified
- `styles/_colors.scss` - Complete color system update
## Validation Status
✅ All SCSS variables resolve correctly
✅ No linter errors
✅ Bootstrap theme colors functional
✅ All old color references (600-1000) removed from codebase
✅ Special event colors preserved
⏸️ Gray/Neutral colors - pending future update
## Migration Statistics
**Files Updated:** 11 SCSS files
- `styles/_colors.scss` - Color definitions cleaned up
- `styles/light/_light-theme.scss` - 11 color references updated
- `styles/_status-labels.scss` - 39 color references updated
- `styles/_diagrams.scss` - 6 color references updated
- `styles/_code-tabs.scss` - 2 color references updated
- `styles/_content.scss` - 1 color reference updated
- `styles/_buttons.scss` - 7 color references updated
- `styles/_pages.scss` - 3 color references updated
- `styles/_blog.scss` - 2 color references updated
- `styles/_feedback.scss` - 2 color references updated
- `styles/_rpc-tool.scss` - 1 color reference updated
- `styles/_landings.scss` - 1 color reference updated
**Total Color References Updated:** 75+ instances

View File

@@ -1,3 +1,32 @@
# Contributing
For information about how to contribute to this repository, see [Contribute Documentation (XRPL.org)](https://xrpl.org/resources/contribute-documentation/).
For information about how to contribute to this repository, see [Contribute Documentation (XRPL.org)](https://xrpl.org/resources/contribute-documentation/).
## Quick Start
1. **Report an issue** — Use one of the [issue templates](.github/ISSUE_TEMPLATE/) to report bugs, request content updates, or suggest new features.
2. **Work on an issue** — Browse [open issues](https://github.com/XRPLF/xrpl-dev-portal/issues) with the `good first issue` or `help wanted` labels.
3. **Submit a pull request** — Fill out the [PR template](.github/PULL_REQUEST_TEMPLATE.md) completely, including linking to the issue you are addressing.
## Review Process (RACI Model)
This project follows a [RACI model](.github/project-management/RACI.md) to manage contributions:
- **R Responsible**: The contributor who authors the change
- **A Accountable**: A maintainer from `@XRPLF/docs-maintainers` who approves and merges
- **C Consulted**: Subject-matter experts tagged as reviewers for technical accuracy
- **I Informed**: Stakeholders notified via linked issues and PR comments
**At least one maintainer approval is required before any PR can be merged.**
## Automated Workflows
The following GitHub Actions workflows run automatically:
| Workflow | Trigger | Purpose |
|---|---|---|
| Issue Triage | New issue opened | Applies labels based on content |
| PR Validation | PR opened/updated | Validates description and linked issue |
| Auto-Label | PR opened/updated | Labels PRs based on changed file paths |
| Stale Management | Daily | Marks and closes inactive issues/PRs |
| Greet Contributor | First issue/PR | Welcomes new contributors |

View File

@@ -1,154 +0,0 @@
# CSS Optimization - Implementation Summary
## ✅ Successfully Completed
The CSS build pipeline has been modernized with industry-standard optimization tools, resulting in significant performance improvements.
## Results
### Bundle Size Improvements
\`\`\`
=== CSS Bundle Comparison ===
Master (Bootstrap 4):
Uncompressed: 405.09 KB
Gzipped: 63.44 KB
This Branch BEFORE Optimization (Bootstrap 5):
Uncompressed: 486.64 KB
Gzipped: 71.14 KB
This Branch AFTER Optimization (Bootstrap 5 + PurgeCSS):
Uncompressed: 280.92 KB ✅ 42% smaller
Gzipped: 43.32 KB ✅ 39% smaller (network transfer)
\`\`\`
### Key Improvements
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| **Network Transfer (Gzipped)** | 71.14 KB | 43.32 KB | **39% smaller** |
| **Uncompressed Size** | 486.64 KB | 280.92 KB | **42% smaller** |
| **CSS Selectors** | 5,423 | 2,681 | **51% fewer** |
| **DevTools Filter Performance** | ~60 seconds | <1 second | **98% faster** |
### Real-World Impact
- **Page Load:** 40% faster CSS download on 3G connections
- **Developer Experience:** DevTools CSS filtering is now instant (<1s vs 60s)
- **Bandwidth Savings:** ~28 KB less per page load
- **Maintainability:** Modern tooling with source maps in development
## What Was Implemented
### 1. Modern Build Pipeline
- **Upgraded Sass** from 1.26.10 (2020) 1.93.2 (latest)
- **Added PostCSS** with optimization plugins:
- **PurgeCSS** - Removes unused CSS selectors
- **Autoprefixer** - Browser compatibility
- **cssnano** - Advanced minification
### 2. Build Scripts
```json
{
"scripts": {
"build-css": "Production build with full optimization",
"build-css-dev": "Development build with source maps",
"build-css-watch": "Watch mode for continuous compilation",
"analyze-css": "Bundle analysis tool"
}
}
```
### 3. PurgeCSS Configuration
- Scans all `.tsx`, `.md`, `.yaml`, `.html` files for class names
- Intelligent safelist for dynamically-added classes
- Preserves Bootstrap JS components, CodeMirror, custom tools
- Only runs in production (dev builds are fast)
### 4. CSS Analysis Tool
Created `scripts/analyze-css.js` to monitor:
- Bundle size and composition
- Bootstrap component usage
- Optimization opportunities
- Before/after comparisons
## Files Created/Modified
### New Files
- `postcss.config.cjs` - PostCSS and PurgeCSS configuration
- `scripts/analyze-css.js` - CSS bundle analysis tool
- `CSS-OPTIMIZATION.md` - Comprehensive optimization guide
- `CSS-OPTIMIZATION-SUMMARY.md` - This summary
### Modified Files
- `package.json` - Updated dependencies and build scripts
- `styles/README.md` - Updated build documentation
### Configuration Files
All configuration files include extensive inline documentation explaining decisions and patterns.
## Usage
### For Production
```bash
npm run build-css # Full optimization
npm run analyze-css # Check results
```
### For Development
```bash
npm run build-css:dev # Fast build with source maps
npm run build-css:watch # Auto-rebuild on changes
```
## Backward Compatibility
**No breaking changes** - All existing styles are preserved
Visual appearance is identical
All Bootstrap components still work
Dynamic classes are safelisted
## Documentation
- **`styles/README.md`** - Build process and troubleshooting
- **`CSS-OPTIMIZATION.md`** - Detailed implementation guide
- **`postcss.config.cjs`** - Inline configuration documentation
## Maintenance
### Adding New Styles
1. Create `_component.scss` file
2. Import in `xrpl.scss`
3. Add dynamic classes to safelist if needed
4. Test: `npm run build-css:dev` and `npm run build-css`
5. Analyze: `npm run analyze-css`
### Troubleshooting Missing Styles
If styles are missing in production:
1. Check if class is added dynamically
2. Add pattern to safelist in `postcss.config.cjs`
3. Rebuild with `npm run build-css`
## Next Steps (Optional Future Optimizations)
1. **Code Splitting** - Separate vendor CSS from custom styles
2. **Critical CSS** - Extract above-the-fold styles
3. **Bootstrap Customization** - Import only needed components
4. **CSS Modules** - Component-scoped styles for React pages
## Conclusion
The CSS optimization is complete and working perfectly. The bundle size has been reduced by 42% (uncompressed) and 39% (gzipped), resulting in faster page loads and dramatically improved developer experience.
**Status: ✅ Ready for Production**
---
*Last Updated: October 2025*

View File

@@ -1,381 +0,0 @@
# CSS Optimization Guide
## Overview
This document describes the CSS optimization implementation for the XRPL Dev Portal, including the rationale, implementation details, performance improvements, and maintenance guidelines.
## The Problem
### Before Optimization
The dev portal was serving a **486 KB** minified CSS bundle that included:
- **Entire Bootstrap 5.3.8 framework** (~200+ KB)
- Thousands of unused CSS selectors
- No tree-shaking or dead code elimination
- All styles loaded on every page, regardless of usage
- **1-minute lag** in Chrome DevTools when filtering CSS
#### Impact
- **Developer Experience:** DevTools filter took 60+ seconds to respond
- **Page Performance:** 486 KB CSS downloaded on every page load
- **Build Process:** Outdated Sass 1.26.10 (from 2020)
- **Debugging:** No source maps, even in development
### Analysis Results
Initial analysis showed:
```
Bundle Size: 486.64 KB
Total Selectors: 5,423
Unique Selectors: 4,678
Bootstrap Component Usage:
- Pagination: 998 usages
- Cards: 428 usages
- Grid System: 253 usages
- ...but also...
- Toast: 8 usages
- Spinner: 8 usages
- Accordion: 0 usages (unused!)
```
## The Solution
### Modern Build Pipeline
Implemented a three-stage optimization pipeline:
```
SCSS → Sass Compiler → PostCSS → Optimized CSS
├─ PurgeCSS (removes unused)
├─ Autoprefixer (adds vendor prefixes)
└─ cssnano (minifies)
```
### Key Technologies
1. **Sass (latest)** - Modern SCSS compilation with better performance
2. **PostCSS** - Industry-standard CSS processing
3. **PurgeCSS** - Intelligent unused CSS removal
4. **Autoprefixer** - Browser compatibility
5. **cssnano** - Advanced minification
## Implementation
### 1. Dependency Upgrades
```json
{
"devDependencies": {
"sass": "^1.93.2", // was 1.26.10
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"@fullhuman/postcss-purgecss": "^7.0.2",
"autoprefixer": "^10.4.21",
"cssnano": "^7.1.1"
}
}
```
### 2. Build Scripts
Created separate development and production builds:
```json
{
"scripts": {
"build-css": "Production build with full optimization",
"build-css:dev": "Development build with source maps",
"build-css:watch": "Watch mode for continuous compilation",
"analyze-css": "node scripts/analyze-css.js"
}
}
```
**Production Build:**
- ✅ Full PurgeCSS optimization
- ✅ Minified and compressed
- ✅ Autoprefixed
- ❌ No source maps
**Development Build:**
- ✅ Source maps for debugging
- ✅ Autoprefixed
- ❌ No PurgeCSS (faster builds)
- ❌ Not minified (readable)
### 3. PurgeCSS Configuration
Created `postcss.config.cjs` with intelligent safelist:
```javascript
// Content paths - scan these for class names
content: [
'./**/*.tsx',
'./**/*.md',
'./**/*.yaml',
'./**/*.html',
'./static/js/**/*.js',
]
// Safelist - preserve these classes
safelist: {
standard: [
'html', 'body', 'light', 'dark',
/^show$/, /^active$/, /^disabled$/,
],
deep: [
/dropdown-menu/, /modal-backdrop/,
/cm-/, /CodeMirror/, // Third-party
/rpc-tool/, /websocket/, // Custom components
],
}
```
**Safelist Strategy:**
- **Standard:** State classes added by JavaScript
- **Deep:** Component patterns (keeps parent and children)
- **Greedy:** Attribute-based matching
### 4. Analysis Tool
Created `scripts/analyze-css.js` to track optimization:
- Bundle size metrics
- Selector counts
- Bootstrap component usage
- Custom pattern detection
- Optimization recommendations
## Results
### Performance Improvements
| Metric | Before | After | Improvement |
|--------|--------|-------|-------------|
| **Bundle Size (Uncompressed)** | 486.64 KB | 280.92 KB | **42% smaller** |
| **Bundle Size (Gzipped)** | 71.14 KB | 43.32 KB | **39% smaller** |
| **Total Selectors** | 5,423 | 2,681 | **51% fewer** |
| **Unique Selectors** | 4,678 | 2,167 | **54% fewer** |
| **DevTools Filter** | ~60 seconds | <1 second | **98% faster** |
| **Download Time (3G)** | ~2.0s | ~1.2s | **40% faster** |
**Note:** Gzipped size is what actually gets transmitted over the network, representing the real-world bandwidth savings.
### Bootstrap Component Optimization
| Component | Before | After | Reduction |
|-----------|--------|-------|-----------|
| Pagination | 998 | 831 | 17% |
| Cards | 428 | 306 | 29% |
| Grid System | 253 | 94 | 63% |
| Badge | 253 | 0 | 100% (unused) |
| Navbar | 171 | 78 | 54% |
| Buttons | 145 | 77 | 47% |
| Forms | 179 | 70 | 61% |
### Developer Experience
**Before:**
```
Build time: 5-10 seconds
DevTools CSS filter: 60 seconds
Debugging: No source maps
```
**After:**
```
Production build: 8-12 seconds
Development build: 3-5 seconds (no PurgeCSS)
DevTools CSS filter: <1 second
Debugging: Source maps in dev mode
```
## Maintenance
### Adding New Styles
When adding new component styles:
1. **Create the SCSS file:**
```scss
// styles/_my-component.scss
.my-component {
// styles here
}
```
2. **Import in xrpl.scss:**
```scss
@import "_my-component.scss";
```
3. **If using dynamic classes, update safelist:**
```javascript
// postcss.config.cjs
deep: [
/my-component/, // Keeps all .my-component-* classes
]
```
4. **Test both builds:**
```bash
npm run build-css:dev # Test development build
npm run build-css # Test production build
npm run analyze-css # Check bundle size impact
```
### Troubleshooting Missing Styles
If styles are missing after a production build:
1. **Identify the missing class:**
```bash
# Search for class usage in codebase
grep -r "missing-class" .
```
2. **Check if it's dynamically added:**
- Bootstrap JavaScript components
- React state-based classes
- Third-party library classes
3. **Add to PurgeCSS safelist:**
```javascript
// postcss.config.cjs
safelist: {
deep: [
/missing-class/, // Preserve this pattern
],
}
```
4. **Rebuild and verify:**
```bash
npm run build-css
npm run analyze-css
```
### Monitoring Bundle Size
Run the analysis tool regularly:
```bash
npm run analyze-css
```
**Watch for:**
- Bundle size > 350 KB (indicates regression)
- Components with 0 usages (can be removed from Bootstrap import)
- Significant selector count increases
### Future Optimizations
Potential next steps for further optimization:
1. **Code Splitting**
- Split vendor CSS (Bootstrap) from custom styles
- Lazy-load page-specific styles
- Critical CSS extraction
2. **Bootstrap Customization**
- Import only needed Bootstrap components
- Remove unused variables and mixins
- Custom Bootstrap build
3. **Component-Level CSS**
- CSS Modules for page components
- CSS-in-JS for dynamic styles
- Scoped styles per route
4. **Advanced Compression**
- Brotli compression (88% ratio vs 76% gzip)
- CSS splitting by media queries
- HTTP/2 server push for critical CSS
## Migration Notes
### Breaking Changes
**None** - This optimization is backward-compatible. All existing classes and styles are preserved.
### Testing Checklist
When testing the optimization:
- [ ] Homepage loads correctly
- [ ] Documentation pages display properly
- [ ] Blog posts render correctly
- [ ] Dev tools (RPC tool, WebSocket tool) function
- [ ] Navigation menus work
- [ ] Dropdowns and modals open correctly
- [ ] Forms are styled properly
- [ ] Code syntax highlighting works
- [ ] Print styles work
- [ ] Light/dark theme switching works
### Rollback Procedure
If issues are found:
1. **Temporarily revert to old build:**
```bash
# In package.json, change build-css to:
"build-css": "sass --load-path styles/scss styles/xrpl.scss ./static/css/devportal2024-v1.css --style compressed --no-source-map"
```
2. **Rebuild:**
```bash
npm run build-css
```
3. **Report the issue** with:
- Missing class names
- Page where issue appears
- Expected vs actual behavior
## Resources
### Documentation
- [PurgeCSS Documentation](https://purgecss.com/)
- [PostCSS Documentation](https://postcss.org/)
- [Sass Documentation](https://sass-lang.com/)
- [Bootstrap Customization](https://getbootstrap.com/docs/5.3/customize/sass/)
### Tools
- `npm run build-css` - Production build
- `npm run build-css:dev` - Development build
- `npm run build-css:watch` - Watch mode
- `npm run analyze-css` - Bundle analysis
### Files
- `styles/README.md` - Build process documentation
- `postcss.config.cjs` - PostCSS and PurgeCSS configuration
- `scripts/analyze-css.js` - Bundle analysis tool
- `package.json` - Build scripts
## Conclusion
This optimization reduces the CSS bundle by 42% (486 KB 281 KB), dramatically improving both developer experience and end-user performance. The implementation uses industry-standard tools and maintains full backward compatibility while providing a foundation for future optimizations.
**Key Takeaways:**
- 42% smaller uncompressed CSS bundle (486 KB 281 KB)
- 39% smaller gzipped bundle (71 KB 43 KB network transfer)
- 98% faster DevTools filtering (60s <1s)
- Modern build tooling (Sass + PostCSS + PurgeCSS)
- Source maps in development mode
- Backward compatible - no breaking changes
- Well documented and maintainable
---
*Last updated: October 2025*
*Contributors: CSS Optimization Initiative*

View File

@@ -92,7 +92,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title subhead-sm-r" id="send-xrp-modal-label">Send XRP</h1>
<h1 class="modal-title fs-5" id="send-xrp-modal-label">Send XRP</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">

View File

@@ -92,7 +92,7 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title subhead-sm-r" id="send-xrp-modal-label">Send XRP</h1>
<h1 class="modal-title fs-5" id="send-xrp-modal-label">Send XRP</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">

View File

@@ -3,7 +3,7 @@
import xrpl from 'xrpl'
import fs from 'fs'
process.stdout.write('Setting up tutorial: 0/6\r')
process.stdout.write('Setting up tutorial: 0/7\r')
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
@@ -21,10 +21,45 @@ const [
client.fundWallet()
])
process.stdout.write('Setting up tutorial: 1/6\r')
process.stdout.write('Setting up tutorial: 1/7\r')
// Create tickets for parallel transactions
const extractTickets = (response) =>
response.result.meta.AffectedNodes
.filter(node => node.CreatedNode?.LedgerEntryType === 'Ticket')
.map(node => node.CreatedNode.NewFields.TicketSequence)
const [ciTicketResponse, lbTicketResponse, brTicketResponse, dpTicketResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'TicketCreate',
Account: credentialIssuer.address,
TicketCount: 4
}, { wallet: credentialIssuer, autofill: true }),
client.submitAndWait({
TransactionType: 'TicketCreate',
Account: loanBroker.address,
TicketCount: 4
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'TicketCreate',
Account: borrower.address,
TicketCount: 2
}, { wallet: borrower, autofill: true }),
client.submitAndWait({
TransactionType: 'TicketCreate',
Account: depositor.address,
TicketCount: 2
}, { wallet: depositor, autofill: true })
])
const ciTickets = extractTickets(ciTicketResponse)
const lbTickets = extractTickets(lbTicketResponse)
const brTickets = extractTickets(brTicketResponse)
const dpTickets = extractTickets(dpTicketResponse)
process.stdout.write('Setting up tutorial: 2/7\r')
// Issue MPT with depositor
// Create tickets for later use with loanBroker
// Set up credentials and domain with credentialIssuer
const credentialType = xrpl.convertStringToHex('KYC-Verified')
const mptData = {
@@ -56,12 +91,7 @@ const mptData = {
}
}
const [ticketCreateResponse, mptIssuanceResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'TicketCreate',
Account: loanBroker.address,
TicketCount: 2
}, { wallet: loanBroker, autofill: true }),
const [mptIssuanceResponse, domainSetResponse] = await Promise.all([
client.submitAndWait({
TransactionType: 'MPTokenIssuanceCreate',
Account: depositor.address,
@@ -74,104 +104,87 @@ const [ticketCreateResponse, mptIssuanceResponse] = await Promise.all([
MPTokenMetadata: xrpl.encodeMPTokenMetadata(mptData)
}, { wallet: depositor, autofill: true }),
client.submitAndWait({
TransactionType: 'Batch',
TransactionType: 'PermissionedDomainSet',
Account: credentialIssuer.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
AcceptedCredentials: [
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: loanBroker.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: borrower.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: depositor.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'PermissionedDomainSet',
Account: credentialIssuer.address,
AcceptedCredentials: [
{
Credential: {
Issuer: credentialIssuer.address,
CredentialType: credentialType
}
}
],
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
Credential: {
Issuer: credentialIssuer.address,
CredentialType: credentialType
}
}
]
],
Sequence: 0,
TicketSequence: ciTickets[0]
}, { wallet: credentialIssuer, autofill: true }),
client.submitAndWait({
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: loanBroker.address,
CredentialType: credentialType,
Sequence: 0,
TicketSequence: ciTickets[1]
}, { wallet: credentialIssuer, autofill: true }),
client.submitAndWait({
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: borrower.address,
CredentialType: credentialType,
Sequence: 0,
TicketSequence: ciTickets[2]
}, { wallet: credentialIssuer, autofill: true }),
client.submitAndWait({
TransactionType: 'CredentialCreate',
Account: credentialIssuer.address,
Subject: depositor.address,
CredentialType: credentialType,
Sequence: 0,
TicketSequence: ciTickets[3]
}, { wallet: credentialIssuer, autofill: true })
])
// Extract ticket sequence numbers
const tickets = ticketCreateResponse.result.meta.AffectedNodes
.filter(node => node.CreatedNode?.LedgerEntryType === 'Ticket')
.map(node => node.CreatedNode.NewFields.TicketSequence)
// Extract MPT issuance ID
const mptID = mptIssuanceResponse.result.meta.mpt_issuance_id
// Get domain ID
const credentialIssuerObjects = await client.request({
command: 'account_objects',
account: credentialIssuer.address,
ledger_index: 'validated'
})
const domainID = credentialIssuerObjects.result.account_objects.find(node =>
node.LedgerEntryType === 'PermissionedDomain'
).index
// Extract domain ID
const domainID = domainSetResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'PermissionedDomain'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 2/6\r')
process.stdout.write('Setting up tutorial: 3/7\r')
// Accept credentials and authorize MPT for each account
await Promise.all([
...([loanBroker, borrower].map(wallet =>
client.submitAndWait({
TransactionType: 'Batch',
Account: wallet.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'CredentialAccept',
Account: wallet.address,
Issuer: credentialIssuer.address,
CredentialType: credentialType,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'MPTokenAuthorize',
Account: wallet.address,
MPTokenIssuanceID: mptID,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
}, { wallet, autofill: true })
)),
client.submitAndWait({
TransactionType: 'CredentialAccept',
Account: loanBroker.address,
Issuer: credentialIssuer.address,
CredentialType: credentialType,
Sequence: 0,
TicketSequence: lbTickets[0]
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'MPTokenAuthorize',
Account: loanBroker.address,
MPTokenIssuanceID: mptID,
Sequence: 0,
TicketSequence: lbTickets[1]
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'CredentialAccept',
Account: borrower.address,
Issuer: credentialIssuer.address,
CredentialType: credentialType,
Sequence: 0,
TicketSequence: brTickets[0]
}, { wallet: borrower, autofill: true }),
client.submitAndWait({
TransactionType: 'MPTokenAuthorize',
Account: borrower.address,
MPTokenIssuanceID: mptID,
Sequence: 0,
TicketSequence: brTickets[1]
}, { wallet: borrower, autofill: true }),
// Depositor only needs to accept credentials
client.submitAndWait({
TransactionType: 'CredentialAccept',
@@ -181,7 +194,7 @@ await Promise.all([
}, { wallet: depositor, autofill: true })
])
process.stdout.write('Setting up tutorial: 3/6\r')
process.stdout.write('Setting up tutorial: 4/7\r')
// Create private vault and distribute MPT to accounts
const [vaultCreateResponse] = await Promise.all([
@@ -195,35 +208,26 @@ const [vaultCreateResponse] = await Promise.all([
DomainID: domainID
}, { wallet: loanBroker, autofill: true }),
client.submitAndWait({
TransactionType: 'Batch',
TransactionType: 'Payment',
Account: depositor.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'Payment',
Account: depositor.address,
Destination: loanBroker.address,
Amount: {
mpt_issuance_id: mptID,
value: '5000'
},
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'Payment',
Account: depositor.address,
Destination: borrower.address,
Amount: {
mpt_issuance_id: mptID,
value: '2500'
},
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
Destination: loanBroker.address,
Amount: {
mpt_issuance_id: mptID,
value: '5000'
},
Sequence: 0,
TicketSequence: dpTickets[0]
}, { wallet: depositor, autofill: true }),
client.submitAndWait({
TransactionType: 'Payment',
Account: depositor.address,
Destination: borrower.address,
Amount: {
mpt_issuance_id: mptID,
value: '2500'
},
Sequence: 0,
TicketSequence: dpTickets[1]
}, { wallet: depositor, autofill: true })
])
@@ -231,7 +235,7 @@ const vaultID = vaultCreateResponse.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Vault'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 4/6\r')
process.stdout.write('Setting up tutorial: 5/7\r')
// Create LoanBroker and deposit MPT into vault
const [loanBrokerSetResponse] = await Promise.all([
@@ -255,7 +259,7 @@ const loanBrokerID = loanBrokerSetResponse.result.meta.AffectedNodes.find(node =
node.CreatedNode?.LedgerEntryType === 'LoanBroker'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 5/6\r')
process.stdout.write('Setting up tutorial: 6/7\r')
// Create 2 identical loans with complete repayment due in 30 days
@@ -289,8 +293,8 @@ async function createLoan (ticketSequence) {
}
const [submitResponse1, submitResponse2] = await Promise.all([
createLoan(tickets[0]),
createLoan(tickets[1])
createLoan(lbTickets[2]),
createLoan(lbTickets[3])
])
const loanID1 = submitResponse1.result.meta.AffectedNodes.find(node =>
@@ -301,7 +305,7 @@ const loanID2 = submitResponse2.result.meta.AffectedNodes.find(node =>
node.CreatedNode?.LedgerEntryType === 'Loan'
).CreatedNode.LedgerIndex
process.stdout.write('Setting up tutorial: 6/6\r')
process.stdout.write('Setting up tutorial: 7/7\r')
// Write setup data to JSON file
const setupData = {

View File

@@ -8,9 +8,6 @@ from xrpl.asyncio.wallet import generate_faucet_wallet
from xrpl.asyncio.transaction import submit_and_wait, autofill, sign
from xrpl.transaction import sign_loan_set_by_counterparty
from xrpl.models import (
AccountObjects,
Batch,
BatchFlag,
CredentialAccept,
CredentialCreate,
LoanBrokerSet,
@@ -34,7 +31,7 @@ from xrpl.utils import encode_mptoken_metadata, str_to_hex
async def main():
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
print("Setting up tutorial: 0/6", end="\r")
print("Setting up tutorial: 0/7", end="\r")
# Create and fund wallets
loan_broker, borrower, depositor, credential_issuer = await asyncio.gather(
@@ -44,10 +41,61 @@ async def main():
generate_faucet_wallet(client),
)
print("Setting up tutorial: 1/6", end="\r")
print("Setting up tutorial: 1/7", end="\r")
# Create tickets for parallel transactions
def extract_tickets(response):
return [
node["CreatedNode"]["NewFields"]["TicketSequence"]
for node in response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Ticket"
]
ci_ticket_response, lb_ticket_response, br_ticket_response, dp_ticket_response = (
await asyncio.gather(
submit_and_wait(
TicketCreate(
account=credential_issuer.address,
ticket_count=4,
),
client,
credential_issuer,
),
submit_and_wait(
TicketCreate(
account=loan_broker.address,
ticket_count=4,
),
client,
loan_broker,
),
submit_and_wait(
TicketCreate(
account=borrower.address,
ticket_count=2,
),
client,
borrower,
),
submit_and_wait(
TicketCreate(
account=depositor.address,
ticket_count=2,
),
client,
depositor,
),
)
)
ci_tickets = extract_tickets(ci_ticket_response)
lb_tickets = extract_tickets(lb_ticket_response)
br_tickets = extract_tickets(br_ticket_response)
dp_tickets = extract_tickets(dp_ticket_response)
print("Setting up tutorial: 2/7", end="\r")
# Issue MPT with depositor
# Create tickets for later use with loan_broker
# Set up credentials and domain with credential_issuer
credential_type = str_to_hex("KYC-Verified")
@@ -80,15 +128,7 @@ async def main():
},
}
ticket_create_response, mpt_issuance_response, _ = await asyncio.gather(
submit_and_wait(
TicketCreate(
account=loan_broker.address,
ticket_count=2,
),
client,
loan_broker,
),
mpt_issuance_response, domain_set_response, *_ = await asyncio.gather(
submit_and_wait(
MPTokenIssuanceCreate(
account=depositor.address,
@@ -105,104 +145,112 @@ async def main():
depositor,
),
submit_and_wait(
Batch(
PermissionedDomainSet(
account=credential_issuer.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialCreate(
account=credential_issuer.address,
subject=loan_broker.address,
accepted_credentials=[
Credential(
issuer=credential_issuer.address,
credential_type=credential_type,
),
CredentialCreate(
account=credential_issuer.address,
subject=borrower.address,
credential_type=credential_type,
),
CredentialCreate(
account=credential_issuer.address,
subject=depositor.address,
credential_type=credential_type,
),
PermissionedDomainSet(
account=credential_issuer.address,
accepted_credentials=[
Credential(
issuer=credential_issuer.address,
credential_type=credential_type,
),
],
),
],
sequence=0,
ticket_sequence=ci_tickets[0],
),
client,
credential_issuer,
),
submit_and_wait(
CredentialCreate(
account=credential_issuer.address,
subject=loan_broker.address,
credential_type=credential_type,
sequence=0,
ticket_sequence=ci_tickets[1],
),
client,
credential_issuer,
),
submit_and_wait(
CredentialCreate(
account=credential_issuer.address,
subject=borrower.address,
credential_type=credential_type,
sequence=0,
ticket_sequence=ci_tickets[2],
),
client,
credential_issuer,
),
submit_and_wait(
CredentialCreate(
account=credential_issuer.address,
subject=depositor.address,
credential_type=credential_type,
sequence=0,
ticket_sequence=ci_tickets[3],
),
client,
credential_issuer,
),
)
# Extract ticket sequence numbers
tickets = [
node["CreatedNode"]["NewFields"]["TicketSequence"]
for node in ticket_create_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Ticket"
]
# Extract MPT issuance ID
mpt_id = mpt_issuance_response.result["meta"]["mpt_issuance_id"]
# Get domain ID
credential_issuer_objects = await client.request(AccountObjects(
account=credential_issuer.address,
ledger_index="validated",
))
# Extract domain ID
domain_id = next(
node["index"]
for node in credential_issuer_objects.result["account_objects"]
if node["LedgerEntryType"] == "PermissionedDomain"
node["CreatedNode"]["LedgerIndex"]
for node in domain_set_response.result["meta"]["AffectedNodes"]
if node.get("CreatedNode", {}).get("LedgerEntryType") == "PermissionedDomain"
)
print("Setting up tutorial: 2/6", end="\r")
print("Setting up tutorial: 3/7", end="\r")
# Accept credentials and authorize MPT for each account
await asyncio.gather(
submit_and_wait(
Batch(
CredentialAccept(
account=loan_broker.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=loan_broker.address,
issuer=credential_issuer.address,
credential_type=credential_type,
),
MPTokenAuthorize(
account=loan_broker.address,
mptoken_issuance_id=mpt_id,
),
],
issuer=credential_issuer.address,
credential_type=credential_type,
sequence=0,
ticket_sequence=lb_tickets[0],
),
client,
loan_broker,
),
submit_and_wait(
Batch(
MPTokenAuthorize(
account=loan_broker.address,
mptoken_issuance_id=mpt_id,
sequence=0,
ticket_sequence=lb_tickets[1],
),
client,
loan_broker,
),
submit_and_wait(
CredentialAccept(
account=borrower.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=borrower.address,
issuer=credential_issuer.address,
credential_type=credential_type,
),
MPTokenAuthorize(
account=borrower.address,
mptoken_issuance_id=mpt_id,
),
],
issuer=credential_issuer.address,
credential_type=credential_type,
sequence=0,
ticket_sequence=br_tickets[0],
),
client,
borrower,
),
submit_and_wait(
MPTokenAuthorize(
account=borrower.address,
mptoken_issuance_id=mpt_id,
sequence=0,
ticket_sequence=br_tickets[1],
),
client,
borrower,
),
# Depositor only needs to accept credentials
submit_and_wait(
CredentialAccept(
account=depositor.address,
@@ -214,10 +262,10 @@ async def main():
),
)
print("Setting up tutorial: 3/6", end="\r")
print("Setting up tutorial: 4/7", end="\r")
# Create private vault and distribute MPT to accounts
vault_create_response, _ = await asyncio.gather(
vault_create_response, *_ = await asyncio.gather(
submit_and_wait(
VaultCreate(
account=loan_broker.address,
@@ -229,21 +277,23 @@ async def main():
loan_broker,
),
submit_and_wait(
Batch(
Payment(
account=depositor.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
Payment(
account=depositor.address,
destination=loan_broker.address,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="5000"),
),
Payment(
account=depositor.address,
destination=borrower.address,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2500"),
),
],
destination=loan_broker.address,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="5000"),
sequence=0,
ticket_sequence=dp_tickets[0],
),
client,
depositor,
),
submit_and_wait(
Payment(
account=depositor.address,
destination=borrower.address,
amount=MPTAmount(mpt_issuance_id=mpt_id, value="2500"),
sequence=0,
ticket_sequence=dp_tickets[1],
),
client,
depositor,
@@ -256,7 +306,7 @@ async def main():
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Vault"
)
print("Setting up tutorial: 4/6", end="\r")
print("Setting up tutorial: 5/7", end="\r")
# Create LoanBroker and deposit MPT into vault
loan_broker_set_response, _ = await asyncio.gather(
@@ -285,10 +335,10 @@ async def main():
if node.get("CreatedNode", {}).get("LedgerEntryType") == "LoanBroker"
)
print("Setting up tutorial: 5/6", end="\r")
print("Setting up tutorial: 6/7", end="\r")
# Create 2 identical loans with complete repayment due in 30 days
# Helper function to create, sign, and submit a LoanSet transaction
async def create_loan(ticket_sequence):
loan_set_tx = await autofill(LoanSet(
@@ -312,8 +362,8 @@ async def main():
return submit_response
submit_response_1, submit_response_2 = await asyncio.gather(
create_loan(tickets[0]),
create_loan(tickets[1]),
create_loan(lb_tickets[2]),
create_loan(lb_tickets[3]),
)
loan_id_1 = next(
@@ -328,7 +378,7 @@ async def main():
if node.get("CreatedNode", {}).get("LedgerEntryType") == "Loan"
)
print("Setting up tutorial: 6/6", end="\r")
print("Setting up tutorial: 7/7", end="\r")
# Write setup data to JSON file
setup_data = {

View File

@@ -1,9 +1,16 @@
import xrpl from 'xrpl'
import fs from 'fs'
// Helper function to extract ticket sequences from a TicketCreate transaction result
function getTicketSequences(ticketCreateResult) {
return ticketCreateResult.result.meta.AffectedNodes
.filter(node => node.CreatedNode?.LedgerEntryType === 'Ticket')
.map(node => node.CreatedNode.NewFields.TicketSequence)
}
// Setup script for vault tutorials
process.stdout.write('Setting up tutorial: 0/5\r')
process.stdout.write('Setting up tutorial: 0/6\r')
const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
@@ -21,11 +28,37 @@ const [
client.fundWallet()
])
// Step 1: Create MPT issuance, permissioned domain, and credentials in parallel
process.stdout.write('Setting up tutorial: 1/5\r')
// Step 1: Create tickets for domain owner and depositor to submit transactions concurrently
process.stdout.write('Setting up tutorial: 1/6\r')
const credType = 'VaultAccess'
const [mptCreateResult] = await Promise.all([
const [domainOwnerTicketCreateResult, depositorTicketCreateResult] = await Promise.all([
client.submitAndWait(
{
TransactionType: 'TicketCreate',
Account: domainOwner.address,
TicketCount: 2
},
{ wallet: domainOwner, autofill: true }
),
client.submitAndWait(
{
TransactionType: 'TicketCreate',
Account: depositor.address,
TicketCount: 2
},
{ wallet: depositor, autofill: true }
)
])
// Get the ticket sequences from transaction results
const domainOwnerTicketSequences = getTicketSequences(domainOwnerTicketCreateResult)
const depositorTicketSequences = getTicketSequences(depositorTicketCreateResult)
// Step 2: Create MPT issuance, permissioned domain, and credentials in parallel
process.stdout.write('Setting up tutorial: 2/6\r')
const [mptCreateResult, domainSetResult] = await Promise.all([
client.submitAndWait(
{
TransactionType: "MPTokenIssuanceCreate",
@@ -71,35 +104,29 @@ const [mptCreateResult] = await Promise.all([
),
client.submitAndWait(
{
TransactionType: "Batch",
TransactionType: "PermissionedDomainSet",
Account: domainOwner.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
AcceptedCredentials: [
{
RawTransaction: {
TransactionType: "PermissionedDomainSet",
Account: domainOwner.address,
AcceptedCredentials: [
{
Credential: {
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType),
},
},
],
Flags: xrpl.GlobalFlags.tfInnerBatchTxn,
},
},
{
RawTransaction: {
TransactionType: "CredentialCreate",
Account: domainOwner.address,
Subject: depositor.address,
Credential: {
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType),
Flags: xrpl.GlobalFlags.tfInnerBatchTxn,
},
},
],
TicketSequence: domainOwnerTicketSequences[0],
Sequence: 0
},
{ wallet: domainOwner, autofill: true },
),
client.submitAndWait(
{
TransactionType: "CredentialCreate",
Account: domainOwner.address,
Subject: depositor.address,
CredentialType: xrpl.convertStringToHex(credType),
TicketSequence: domainOwnerTicketSequences[1],
Sequence: 0
},
{ wallet: domainOwner, autofill: true },
),
@@ -107,44 +134,34 @@ const [mptCreateResult] = await Promise.all([
const mptIssuanceId = mptCreateResult.result.meta.mpt_issuance_id
// Get domain ID
const domainOwnerObjects = await client.request({
command: 'account_objects',
account: domainOwner.address,
ledger_index: 'validated'
})
const domainId = domainOwnerObjects.result.account_objects.find(
(node) => node.LedgerEntryType === 'PermissionedDomain'
).index
// Get domain ID from transaction result
const domainNode = domainSetResult.result.meta.AffectedNodes.find(
(node) => node.CreatedNode?.LedgerEntryType === 'PermissionedDomain'
)
const domainId = domainNode.CreatedNode.LedgerIndex
// Step 2: Depositor accepts credential, authorizes MPT, and creates vault in parallel
process.stdout.write('Setting up tutorial: 2/5\r')
// Step 3: Depositor accepts credential, authorizes MPT, and creates vault in parallel
process.stdout.write('Setting up tutorial: 3/6\r')
const [, vaultCreateResult] = await Promise.all([
const [, , vaultCreateResult] = await Promise.all([
client.submitAndWait(
{
TransactionType: 'Batch',
TransactionType: 'CredentialAccept',
Account: depositor.address,
Flags: xrpl.BatchFlags.tfAllOrNothing,
RawTransactions: [
{
RawTransaction: {
TransactionType: 'CredentialAccept',
Account: depositor.address,
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType),
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
},
{
RawTransaction: {
TransactionType: 'MPTokenAuthorize',
Account: depositor.address,
MPTokenIssuanceID: mptIssuanceId,
Flags: xrpl.GlobalFlags.tfInnerBatchTxn
}
}
]
Issuer: domainOwner.address,
CredentialType: xrpl.convertStringToHex(credType),
TicketSequence: depositorTicketSequences[0],
Sequence: 0
},
{ wallet: depositor, autofill: true }
),
client.submitAndWait(
{
TransactionType: 'MPTokenAuthorize',
Account: depositor.address,
MPTokenIssuanceID: mptIssuanceId,
TicketSequence: depositorTicketSequences[1],
Sequence: 0
},
{ wallet: depositor, autofill: true }
),
@@ -196,8 +213,8 @@ const vaultNode = vaultCreateResult.result.meta.AffectedNodes.find(
const vaultID = vaultNode.CreatedNode.LedgerIndex
const vaultShareMPTIssuanceId = vaultNode.CreatedNode.NewFields.ShareMPTID
// Step 3: Issuer sends payment to depositor
process.stdout.write('Setting up tutorial: 3/5\r')
// Step 4: Issuer sends payment to depositor
process.stdout.write('Setting up tutorial: 4/6\r')
const paymentResult = await client.submitAndWait(
{
@@ -218,8 +235,8 @@ if (paymentResult.result.meta.TransactionResult !== 'tesSUCCESS') {
process.exit(1)
}
// Step 4: Make an initial deposit so withdraw example has shares to work with
process.stdout.write('Setting up tutorial: 4/5\r')
// Step 5: Make an initial deposit so withdraw example has shares to work with
process.stdout.write('Setting up tutorial: 5/6\r')
const initialDepositResult = await client.submitAndWait(
{
@@ -240,8 +257,8 @@ if (initialDepositResult.result.meta.TransactionResult !== 'tesSUCCESS') {
process.exit(1)
}
// Step 5: Save setup data to file
process.stdout.write('Setting up tutorial: 5/5\r')
// Step 6: Save setup data to file
process.stdout.write('Setting up tutorial: 6/6\r')
const setupData = {
mptIssuer: {

View File

@@ -6,11 +6,10 @@ from xrpl.asyncio.clients import AsyncWebsocketClient
from xrpl.asyncio.transaction import submit_and_wait
from xrpl.asyncio.wallet import generate_faucet_wallet
from xrpl.models import (
Batch, BatchFlag, CredentialAccept, CredentialCreate, MPTokenAuthorize,
CredentialAccept, CredentialCreate, MPTokenAuthorize,
MPTokenIssuanceCreate, MPTokenIssuanceCreateFlag, Payment,
PermissionedDomainSet, VaultDeposit
PermissionedDomainSet, TicketCreate, VaultDeposit
)
from xrpl.models.requests import AccountObjects
from xrpl.models.transactions.deposit_preauth import Credential
from xrpl.models.transactions.vault_create import (
VaultCreate, VaultCreateFlag, WithdrawalPolicy
@@ -18,9 +17,17 @@ from xrpl.models.transactions.vault_create import (
from xrpl.utils import encode_mptoken_metadata, str_to_hex
def get_ticket_sequences(ticket_create_result):
"""Extract ticket sequences from a TicketCreate transaction result."""
return [
node["CreatedNode"]["NewFields"]["TicketSequence"]
for node in ticket_create_result.result["meta"]["AffectedNodes"]
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "Ticket"
]
async def main():
# Setup script for vault tutorials
print("Setting up tutorial: 0/5", end="\r")
print("Setting up tutorial: 0/6", end="\r")
async with AsyncWebsocketClient("wss://s.devnet.rippletest.net:51233") as client:
# Create and fund all wallets concurrently
@@ -31,11 +38,39 @@ async def main():
generate_faucet_wallet(client),
)
# Step 1: Create MPT issuance, permissioned domain, and credentials in parallel
print("Setting up tutorial: 1/5", end="\r")
# Step 1: Create tickets for domain owner and depositor to submit transactions concurrently
print("Setting up tutorial: 1/6", end="\r")
cred_type = "VaultAccess"
mpt_create_result, _ = await asyncio.gather(
domain_owner_ticket_create_result, depositor_ticket_create_result = await asyncio.gather(
submit_and_wait(
TicketCreate(
account=domain_owner.address,
ticket_count=2
),
client,
domain_owner,
autofill=True
),
submit_and_wait(
TicketCreate(
account=depositor.address,
ticket_count=2
),
client,
depositor,
autofill=True
)
)
# Get the ticket sequences from transaction results
domain_owner_ticket_sequences = get_ticket_sequences(domain_owner_ticket_create_result)
depositor_ticket_sequences = get_ticket_sequences(depositor_ticket_create_result)
# Step 2: Create MPT issuance, permissioned domain, and credentials in parallel
print("Setting up tutorial: 2/6", end="\r")
mpt_create_result, domain_set_result, _ = await asyncio.gather(
submit_and_wait(
MPTokenIssuanceCreate(
account=mpt_issuer.address,
@@ -82,25 +117,28 @@ async def main():
autofill=True
),
submit_and_wait(
Batch(
PermissionedDomainSet(
account=domain_owner.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
PermissionedDomainSet(
account=domain_owner.address,
accepted_credentials=[
Credential(
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type)
)
],
),
CredentialCreate(
account=domain_owner.address,
subject=depositor.address,
credential_type=str_to_hex(cred_type),
),
accepted_credentials=[
Credential(
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type)
)
],
ticket_sequence=domain_owner_ticket_sequences[0],
sequence=0
),
client,
domain_owner,
autofill=True
),
submit_and_wait(
CredentialCreate(
account=domain_owner.address,
subject=depositor.address,
credential_type=str_to_hex(cred_type),
ticket_sequence=domain_owner_ticket_sequences[1],
sequence=0
),
client,
domain_owner,
@@ -110,36 +148,35 @@ async def main():
mpt_issuance_id = mpt_create_result.result["meta"]["mpt_issuance_id"]
# Get domain ID
domain_owner_objects = await client.request(AccountObjects(
account=domain_owner.address,
ledger_index="validated",
))
domain_id = next(
node["index"]
for node in domain_owner_objects.result["account_objects"]
if node["LedgerEntryType"] == "PermissionedDomain"
# Get domain ID from transaction result
domain_node = next(
node for node in domain_set_result.result["meta"]["AffectedNodes"]
if "CreatedNode" in node and node["CreatedNode"].get("LedgerEntryType") == "PermissionedDomain"
)
domain_id = domain_node["CreatedNode"]["LedgerIndex"]
# Step 2: Depositor accepts credential, authorizes MPT, and creates vault in parallel
print("Setting up tutorial: 2/5", end="\r")
# Step 3: Depositor accepts credential, authorizes MPT, and creates vault in parallel
print("Setting up tutorial: 3/6", end="\r")
_, vault_create_result = await asyncio.gather(
_, _, vault_create_result = await asyncio.gather(
submit_and_wait(
Batch(
CredentialAccept(
account=depositor.address,
flags=BatchFlag.TF_ALL_OR_NOTHING,
raw_transactions=[
CredentialAccept(
account=depositor.address,
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type),
),
MPTokenAuthorize(
account=depositor.address,
mptoken_issuance_id=mpt_issuance_id,
),
],
issuer=domain_owner.address,
credential_type=str_to_hex(cred_type),
ticket_sequence=depositor_ticket_sequences[0],
sequence=0
),
client,
depositor,
autofill=True
),
submit_and_wait(
MPTokenAuthorize(
account=depositor.address,
mptoken_issuance_id=mpt_issuance_id,
ticket_sequence=depositor_ticket_sequences[1],
sequence=0
),
client,
depositor,
@@ -194,8 +231,8 @@ async def main():
vault_id = vault_node["CreatedNode"]["LedgerIndex"]
vault_share_mpt_issuance_id = vault_node["CreatedNode"]["NewFields"]["ShareMPTID"]
# Step 3: Issuer sends payment to depositor
print("Setting up tutorial: 3/5", end="\r")
# Step 4: Issuer sends payment to depositor
print("Setting up tutorial: 4/6", end="\r")
payment_result = await submit_and_wait(
Payment(
@@ -215,8 +252,8 @@ async def main():
print(f"\nPayment failed: {payment_result.result['meta']['TransactionResult']}", file=sys.stderr)
sys.exit(1)
# Step 4: Make an initial deposit so withdraw example has shares to work with
print("Setting up tutorial: 4/5", end="\r")
# Step 5: Make an initial deposit so withdraw example has shares to work with
print("Setting up tutorial: 5/6", end="\r")
initial_deposit_result = await submit_and_wait(
VaultDeposit(
@@ -236,8 +273,8 @@ async def main():
print(f"\nInitial deposit failed: {initial_deposit_result.result['meta']['TransactionResult']}", file=sys.stderr)
sys.exit(1)
# Step 5: Save setup data to file
print("Setting up tutorial: 5/5", end="\r")
# Step 6: Save setup data to file
print("Setting up tutorial: 6/6", end="\r")
setup_data = {
"mpt_issuer": {

View File

@@ -24,6 +24,14 @@ export default function History() {
return (
<div className="landing">
<div className="overflow-hidden">
<div className="position-relative">
<img
alt="background orange waves"
src={require("../static/img/backgrounds/history-orange.svg")}
className="landing-bg"
id="history-orange"
/>
</div>
<section className="py-26 text-center">
<div className="col-lg-5 mx-auto text-center">
<div className="d-flex flex-column-reverse">
@@ -53,6 +61,13 @@ export default function History() {
</p>
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="background purple waves"
src={require("../static/img/backgrounds/history-purple.svg")}
id="history-purple"
/>
</div>
<div className="container-new marketing-wrapper">
<section className="row mb-60">
<div className="timeline">

View File

@@ -32,6 +32,14 @@ export default function Impact() {
return (
<div className="landing page-impact">
<div className="overflow-hidden">
<div className="position-relative d-none-sm">
<img
alt="purple waves"
src={require("../static/img/backgrounds/community-purple.svg")}
className="landing-bg"
id="impact-purple"
/>
</div>
<section className="container-new py-26 text-lg-center">
<div className="p-0 col-lg-8 mx-lg-auto">
<div className="d-flex flex-column-reverse">
@@ -44,6 +52,13 @@ export default function Impact() {
</div>
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="green waves"
src={require("../static/img/backgrounds/home-green.svg")}
id="impact-green"
/>
</div>
{/* World map */}
<section className="container-new py-10">
<div className="col-sm-10 col-lg-6 offset-md-3 p-10-until-sm pl-0-sm pr-0-sm">
@@ -118,6 +133,16 @@ export default function Impact() {
{/* Card */}
<section className="container-new py-26">
<div className="col-md-6 offset-md-3 p-6-sm p-10-until-sm br-8 cta-card">
<img
alt="purple waves"
src={require("../static/img/backgrounds/cta-community-purple.svg")}
className="cta cta-top-left"
/>
<img
alt="green waves"
src={require("../static/img/backgrounds/cta-calculator-green.svg")}
className="cta cta-bottom-right"
/>
<div className="z-index-1 position-relative">
<div className="d-flex flex-column-reverse">
<h2 className="h4 h2-sm mb-10-until-sm mb-8-sm">

View File

@@ -1,7 +1,6 @@
import * as React from "react";
import { useThemeHooks } from '@redocly/theme/core/hooks';
import { Link } from '@redocly/theme/components/Link/Link';
import { PageGrid, PageGridCol, PageGridRow } from "shared/components/PageGrid/page-grid";
export const frontmatter = {
seo: {
@@ -79,6 +78,14 @@ export default function XrplOverview() {
/>
</div>
</div>
<div className="position-relative">
<img
alt="purple waves"
src={require("../static/img/backgrounds/xrpl-overview-purple.svg")}
className="landing-bg"
id="xrpl-overview-purple"
/>
</div>
<section className="py-26 text-center">
<div className="col-lg-5 mx-auto text-center">
<div className="d-flex flex-column-reverse">
@@ -93,6 +100,13 @@ export default function XrplOverview() {
</div>
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="orange waves"
src={require("../static/img/backgrounds/xrpl-overview-orange.svg")}
id="xrpl-overview-orange"
/>
</div>
<section className="container-new py-26">
<div className="card-grid card-grid-2xN">
<div className="col">
@@ -119,7 +133,7 @@ export default function XrplOverview() {
{translate("Read Technical Docs")}
</Link>{" "}
<a
className="ms-4 video-external-link"
className="ml-4 video-external-link"
target="_blank"
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
>
@@ -154,7 +168,7 @@ export default function XrplOverview() {
{translate("Read Technical Docs")}
</Link>{" "}
<a
className="ms-4 video-external-link"
className="ml-4 video-external-link"
target="_blank"
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
>
@@ -164,9 +178,9 @@ export default function XrplOverview() {
</div>
</div>
</section>
<PageGrid className="py-26">
<PageGridRow>
<PageGrid.Col span={{ base: 4, lg: 6 }}>
<section className="container-new py-26">
<div className="card-grid card-grid-2xN">
<div className="col">
<div className="d-flex flex-column-reverse">
<h2 className="h4 h2-sm mb-8">
{translate("How the Consensus Protocol works")}
@@ -193,23 +207,25 @@ export default function XrplOverview() {
<p className="mb-0">
{translate('about.index.consensus.ppart1', 'Currently, over 120 ')}
<a href="https://livenet.xrpl.org/network/validators" target="_blank">{translate('about.index.consensus.ppart2', 'validators')}</a>
{translate('about.index.consensus.ppart3', ' are active on the ledger, operated by universities, exchanges, businesses, and individuals. As the validator pool grows, the consensus protocol ensures decentralization of the blockchain over time.')}
{translate('about.index.consensus.ppart3', ' are active on the ledger, operated by universities, exchanges, businesses, and individuals. As the validator pool grows, the consensus protocol ensures decentralization of the blockchain over time.')}
</p>
</PageGrid.Col>
<PageGrid.Col span={{ base: 4, lg: 6 }}>
<div className="col mb-16-sm">
<img
className="mw-100"
id="validator-graphic"
alt="(Graphic: Validators in Consensus)"
/>
</div>
</PageGrid.Col>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGrid.Col span={{ base: 4, lg: 6 }} offset={{ lg: 3 }} className="p-6-sm p-10-until-sm br-8 cta-card">
</div>
<div className="col mb-16-sm">
<img
className="mw-100"
id="validator-graphic"
alt="(Graphic: Validators in Consensus)"
/>
</div>
</div>
</section>
<section className="container-new py-26">
<div className="col-md-6 offset-md-3 p-6-sm p-10-until-sm br-8 cta-card">
<img
alt="green waves"
src={require("../static/img/backgrounds/cta-xrpl-overview-green.svg")}
className="cta cta-bottom-right"
/>
<div className="z-index-1 position-relative">
<h2 className="h4 mb-10-until-sm mb-8-sm">
{translate("A Sustainable Blockchain")}
@@ -223,13 +239,11 @@ export default function XrplOverview() {
{translate("Learn More")}
</a>
</div>
</PageGrid.Col>
</PageGridRow>
</PageGrid>
<PageGrid className="py-26">
<PageGridRow>
<PageGrid.Col span={{ base: 4, lg: 6 }}>
</div>
</section>
<section className="container-new py-26">
<div className="card-grid card-grid-2xN">
<div className="col">
<div className="d-flex flex-column-reverse">
<h4 className="h4 h2-sm mb-8">
{translate("Building with confidence on ")}
@@ -251,8 +265,8 @@ export default function XrplOverview() {
<a className="btn btn-primary btn-arrow mb-10-sm" href="/about/uses">
{translate("Explore More")}
</a>
</PageGrid.Col>
<PageGrid.Col span={{ base: 4, lg: 6 }}>
</div>
<div className="col mb-0">
<div className="d-flex flex-column-reverse">
<h4 className="h4 h2-sm mb-8">
{translate("Creating new value for long-term growth")}
@@ -269,11 +283,11 @@ export default function XrplOverview() {
"Significant investment in development, along with low transaction costs and energy usage, is fueling growth and opening up a wide variety of use cases at scale."
)}
</p>
</PageGrid.Col>
</PageGridRow>
</PageGrid>
</div>
</div>
</section>
<section className="container-new py-26">
<div className="d-flex flex-column-reverse col-xl-6 mb-lg-4 ps-0 ">
<div className="d-flex flex-column-reverse col-xl-6 mb-lg-4 pl-0 ">
<h2 className="h4 h2-sm">
{translate(
"Watch the explainer video series to learn more about the XRP Ledger"
@@ -361,6 +375,11 @@ export default function XrplOverview() {
</section>
<section className="container-new py-26">
<div className="col-md-6 offset-md-3 p-6-sm p-10-until-sm br-8 cta-card">
<img
alt="orange waves"
src={require("../static/img/backgrounds/cta-xrpl-overview-orange.svg")}
className="cta cta-bottom-right"
/>
<div className="z-index-1 position-relative">
<h4 className="h4 mb-10-until-sm mb-8-sm">
{translate("Tomorrows Blockchain Starts With You")}
@@ -388,7 +407,7 @@ export default function XrplOverview() {
</section>
<section className="container-new py-26">
<div
className="col-md-10 offset-md-1 col-lg-8 offset-lg-2 ps-0 pe-0 mini-faq"
className="col-md-6 offset-md-3 w-100 pl-0 pr-0 mini-faq"
id="minifaq-accordion"
>
{faqs.map((faq, index) => (
@@ -396,8 +415,8 @@ export default function XrplOverview() {
<a
href={`#heading${index + 1}`}
className="expander collapsed"
data-bs-toggle="collapse"
data-bs-target={`#answer${index + 1}`}
data-toggle="collapse"
data-target={`#answer${index + 1}`}
aria-expanded="false"
aria-controls={`answer${index + 1}`}
>

View File

@@ -26,7 +26,7 @@ const logos = {
],
developer_tooling: ["cryptum", "evernode", "threezy", "tokenize"],
interoperability: ["multichain"],
wallet: ["crossmark", "edge", "gem-wallet", "xumm", "joey-wallet", "bifrost-wallet"],
wallet: ["crossmark", "edge", "gem-wallet", "xumm", "joey-wallet", "bifrost-wallet", "bitget-wallet"],
nfts: [
"aesthetes",
"audiotarky",
@@ -422,6 +422,15 @@ const cardsData = [
category_name: "Wallet",
link: "https://bifrostwallet.com/",
},
{
id: "bitget-wallet",
title: "Bitget Wallet",
description:
"Bitget Wallet is a non-custodial wallet designed to make crypto simple and secure for everyone.",
category_id: "wallet",
category_name: "Wallet",
link: "https://web3.bitget.com/",
},
];
const featured_categories = {
@@ -468,7 +477,7 @@ const uses = [
{
id: "wallet",
title: "Wallet",
number: 6,
number: 7,
description:
"Build digital wallets to store passwords and interact with various blockchains to send and receive digital assets, including XRP."
},
@@ -859,17 +868,17 @@ export default function Uses() {
</div>
<a
className="btn d-block d-lg-none"
data-bs-toggle="modal"
data-bs-target="#categoryFilterModal"
data-toggle="modal"
data-target="#categoryFilterModal"
>
<span className="me-3">
<span className="mr-3">
<img
src={require("../static/img/uses/usecase-filter.svg")}
alt="Filter button"
/>
</span>
{translate("Filter by Categories")}
<span className="ms-3 total_count category_count">2</span>
<span className="ml-3 total_count category_count">2</span>
</a>
{/* Start company cards */}
<div className="row col-12 m-0 p-0 mt-4 pt-2">

View File

@@ -19,18 +19,14 @@ const links = [
const softwallets = [
{ href: "https://bifrostwallet.com/", id: "wallet-bifrost", alt: "Bifrost Wallet" },
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" },
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
{
href: "https://gatehub.net/",
id: "wallet-gatehub",
alt: "Gatehub",
imgclasses: "invertible-img",
},
{ href: "https://gemwallet.app/", id: "wallet-gem", alt: "Gem Wallet" },
{ href: "https://web3.bitget.com/", id: "wallet-bitget", alt: "Bitget Wallet" },
{ href: "https://coin.space/", id: "wallet-coin", alt: "Coin Space" },
{ href: "https://crossmark.io/", id: "wallet-crossmark", alt: "Crossmark Wallet" },
{ href: "https://gatehub.net/", id: "wallet-gatehub", alt: "Gatehub", imgclasses: "invertible-img" },
{ href: "https://gemwallet.app/", id: "wallet-gem", alt: "Gem Wallet" },
{ href: "https://joeywallet.xyz/", id: "wallet-joey", alt: "Joey Wallet" },
{ href: "https://trustwallet.com/", id: "wallet-trust", alt: "Trust Wallet" },
{ href: "https://xaman.app/", id: "wallet-xumm", alt: "Xaman" }
];
const hardwallets = [
@@ -61,16 +57,8 @@ const exchanges = [
{ href: "https://www.liquid.com/", id: "exch-liquid", alt: "Liquid" },
{ href: "https://www.lmax.com/", id: "exch-lmax", alt: "LMAX" },
{ href: "https://www.bitfinex.com/", id: "exch-bitfinex", alt: "Bitfinex" },
{
href: "https://www.etoro.com/crypto/exchange/",
id: "exch-etoro",
alt: "eToro",
},
{
href: "https://currency.com",
id: "exch-currency-com",
alt: "Currency.com",
},
{ href: "https://www.etoro.com/crypto/exchange/", id: "exch-etoro", alt: "eToro"},
{ href: "https://currency.com", id: "exch-currency-com", alt: "Currency.com"},
{ href: "https://bittrex.com/", id: "exch-bittrex", alt: "Bittrex" },
];
@@ -116,380 +104,400 @@ export default function XrpOverview() {
const totalCols = Math.max(softwallets.length, hardwallets.length) + 1;
return (
<div className="landing">
<section className="py-26 text-center">
<div className="col-lg-5 mx-auto text-center">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("Your Questions About XRP, Answered")}
</h1>
<h6 className="eyebrow mb-3">{translate("XRP Overview")}</h6>
</div>
<div>
<div className="position-relative">
<img
alt="blue waves"
src={require("../static/img/backgrounds/xrp-overview-blue.svg")}
className="landing-bg"
id="xrp-overview-blue"
/>
</div>
</section>
<section className="container-new my-20">
<div className="card-grid card-grid-1x2">
<div className="d-none-sm mt-lg-0">
<ul className="page-toc no-sideline p-0 sticky-top floating-nav">
{links.map((link) => (
<li
key={link.hash}
className={`nav-item ${
activeSection === link.hash.substring(1) ? "active" : ""
}`}
>
<a
className={`sidelinks nav-link ${
<section className="py-26 text-center">
<div className="col-lg-5 mx-auto text-center">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("Your Questions About XRP, Answered")}
</h1>
<h6 className="eyebrow mb-3">{translate("XRP Overview")}</h6>
</div>
</div>
</section>
<section className="container-new my-20">
<div className="card-grid card-grid-1x2">
<div className="d-none-sm mt-lg-0">
<ul className="page-toc no-sideline p-0 sticky-top floating-nav">
{links.map((link) => (
<li
key={link.hash}
className={`nav-item ${
activeSection === link.hash.substring(1) ? "active" : ""
}`}
href={link.hash}
>
{translate(link.text)}
<a
className={`sidelinks nav-link ${
activeSection === link.hash.substring(1) ? "active" : ""
}`}
href={link.hash}
>
{translate(link.text)}
</a>
</li>
))}
</ul>
</div>
<div className="col mt-lg-0">
<div className="link-section pb-26" id="about-xrp">
<h2 className="h4 h2-sm mb-8">{translate("What Is XRP?")}</h2>
<h5 className="longform mb-10">
{translate(
"about.xrp.what-is-xrp.ppart1",
"XRP is a digital asset thats native to the XRP Ledger—an open-source, permissionless and decentralized ",
)}
<a
href="https://www.distributedagreement.com/2018/09/24/what-is-a-blockchain/"
target="_blank"
rel="noopener noreferrer"
>
{translate("about.xrp.what-is-xrp.ppart2", "blockchain technology.")}
</a>
</li>
))}
</ul>
</div>
<div className="col mt-lg-0">
<div className="link-section pb-26" id="about-xrp">
<h2 className="h4 h2-sm mb-8">{translate("What Is XRP?")}</h2>
<h5 className="longform mb-10">
{translate(
"about.xrp.what-is-xrp.ppart1",
"XRP is a digital asset thats native to the XRP Ledger—an open-source, permissionless and decentralized ",
)}
<a
href="https://www.distributedagreement.com/2018/09/24/what-is-a-blockchain/"
target="_blank"
rel="noopener noreferrer"
>
{translate("about.xrp.what-is-xrp.ppart2", "blockchain technology.")}
</a>
{translate("about.xrp.what-is-xrp.ppart3", " ")}
</h5>
{translate("about.xrp.what-is-xrp.ppart3", " ")}
</h5>
<p className="mb-6">
{translate(
"Created in 2012 specifically for payments, XRP can settle transactions on the ledger in 3-5 seconds. It was built to be a better Bitcoin—faster, cheaper and greener than any other digital asset."
)}
</p>
<div className="overflow-x-xs">
<table className="mb-10 landing-table">
<thead>
<tr>
<th>
<h6>{translate("Benefits")}</h6>
</th>
<th>
<h6>{translate("XRP")}</h6>
</th>
<th>
<h6>{translate("Bitcoin")}</h6>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate("Fast")}</td>
<td>{translate("3-5 seconds to settle")}</td>
<td>{translate("500 seconds to settle")}</td>
</tr>
<tr>
<td>{translate("Low-Cost")}</td>
<td>{translate("$0.0002/tx")}</td>
<td>{translate("$0.50/tx")}</td>
</tr>
<tr>
<td>{translate("Scalable")}</td>
<td>{translate("1,500 tx per second")}</td>
<td>{translate("3 tx per second")}</td>
</tr>
<tr>
<td>{translate("Sustainable")}</td>
<td>
{translate(
"Environmentally sustainable (negligible energy consumption)"
)}
</td>
<td>
{translate("0.3% of global energy consumption")}
</td>
</tr>
</tbody>
</table>
</div>
<p className="mb-10">
{translate(
"XRP can be sent directly without needing a central intermediary, making it a convenient instrument in bridging two different currencies quickly and efficiently. It is freely exchanged on the open market and used in the real world for enabling cross-border payments and microtransactions."
)}
</p>
<div className="card-grid card-grid-2xN mb-10">
<div>
<p className="mb-6">
{translate(
"Created in 2012 specifically for payments, XRP can settle transactions on the ledger in 3-5 seconds. It was built to be a better Bitcoin—faster, cheaper and greener than any other digital asset."
)}
</p>
<div className="overflow-x-xs">
<table className="mb-10 landing-table">
<thead>
<tr>
<th>
<h6>{translate("Benefits")}</h6>
</th>
<th>
<h6>{translate("XRP")}</h6>
</th>
<th>
<h6>{translate("Bitcoin")}</h6>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate("Fast")}</td>
<td>{translate("3-5 seconds to settle")}</td>
<td>{translate("500 seconds to settle")}</td>
</tr>
<tr>
<td>{translate("Low-Cost")}</td>
<td>{translate("$0.0002/tx")}</td>
<td>{translate("$0.50/tx")}</td>
</tr>
<tr>
<td>{translate("Scalable")}</td>
<td>{translate("1,500 tx per second")}</td>
<td>{translate("3 tx per second")}</td>
</tr>
<tr>
<td>{translate("Sustainable")}</td>
<td>
{translate(
"Environmentally sustainable (negligible energy consumption)"
)}
</td>
<td>
{translate("0.3% of global energy consumption")}
</td>
</tr>
</tbody>
</table>
</div>
<p className="mb-10">
{translate(
"XRP can be sent directly without needing a central intermediary, making it a convenient instrument in bridging two different currencies quickly and efficiently. It is freely exchanged on the open market and used in the real world for enabling cross-border payments and microtransactions."
)}
</p>
<div className="card-grid card-grid-2xN mb-10">
<div>
<img
alt="briefcase"
className="mw-100 mb-2 invertible-img"
src={briefcaseIcon}
/>
<h6 className="fs-4-5">
{translate("Financial Institutions")}
</h6>
<p className="">
{translate(
"Leverage XRP as a bridge currency to facilitate faster, more affordable cross-border payments around the world."
)}
</p>
</div>
<div>
<img
alt="user"
className="mw-100 mb-2 invertible-img"
src={userIcon}
/>
<h6 className="fs-4-5">
{translate("Individual Consumers")}
</h6>
<p>
{translate(
"Use XRP to move different currencies around the world."
)}
</p>
</div>
</div>
<div className="mt-10 p-10 br-8 cta-card position-relative">
<img
alt="briefcase"
className="mw-100 mb-2 invertible-img"
src={briefcaseIcon}
alt="magenta waves"
src={require("../static/img/backgrounds/cta-xrp-overview-magenta.svg")}
className="cta cta-bottom-right"
/>
<h6 className="subhead-sm-r">
{translate("Financial Institutions")}
</h6>
<p className="">
{translate(
"Leverage XRP as a bridge currency to facilitate faster, more affordable cross-border payments around the world."
)}
</p>
<div className="z-index-1 position-relative">
<h2 className="h4 mb-10-until-sm mb-8-sm">
{translate(
"The XRP Ledger is built for business."
)}
</h2>
<p className="mb-10">
{translate(
"The only major L-1 blockchain thats built for business and designed specifically to power finance use cases and applications at scale. Powerful enough to bootstrap a new economy, the XRP Ledger (XRPL) is fast, scalable, and sustainable."
)}
</p>
</div>
</div>
<div>
</div>
<div className="py-26 link-section" id="xrp-trading">
<h2 className="h4 h2-sm mb-8">
{translate("How Is XRP Used in Trading?")}
</h2>
<h5 className="longform mb-10">
{translate(
"XRP is traded on more than 100 markets and exchanges worldwide."
)}
</h5>
<p className="mb-6">
{translate(
"about.xrp.xrp-in-trading.ppart1",
"XRPs low transaction fees, reliability and high-speed enable traders to use the digital asset as high-speed, cost-efficient and reliable collateral across trading venues—"
)}
<a
href="https://ripple.com/insights/xrp-a-preferred-base-currency-for-arbitrage-trading/"
target="_blank"
>
{translate("about.xrp.xrp-in-trading.ppart2","seizing arbitrage opportunities")}
</a>
{translate(
"about.xrp.xrp-in-trading.ppart3",
", servicing margin calls and managing general trading inventory in real time."
)}
</p>
<p>
{translate(
"Because of the properties inherent to XRP and the ecosystem around it, traders worldwide are able to shift collateral, bridge currencies and switch from one crypto into another nearly instantly, across any exchange on the planet."
)}
</p>
</div>
<div className="py-26 link-section" id="ripple">
<h2 className="h4 h2-sm mb-8">
{translate(
"What Is the Relationship Between Ripple and XRP?"
)}
</h2>
<h5 className="longform mb-10">
<a href="https://ripple.com" target="_blank">
{translate("Ripple")}
</a>
{translate(
" is a technology company that makes it easier to build a high-performance, global payments business. XRP is a digital asset independent of this."
)}
</h5>
<p>
{translate(
"There is a finite amount of XRP. All XRP is already in existence today—no more than the original 100 billion can be created. The XRPL founders gifted 80 billion XRP, the platforms native currency, to Ripple. To provide predictability to the XRP supply, Ripple has locked 55 billion XRP (55% of the total possible supply) into a series of escrows using the XRP Ledger itself. The XRPL's transaction processing rules, enforced by the consensus protocol, control the release of the XRP."
)}
</p>
<div className="mt-10 p-10 br-8 cta-card position-relative">
<img
alt="user"
className="mw-100 mb-2 invertible-img"
src={userIcon}
alt="green waves"
src={require("../static/img/backgrounds/cta-xrp-overview-green-2.svg")}
className="landing-bg cta cta-bottom-right"
/>
<h6 className="subhead-sm-r">
{translate("Individual Consumers")}
</h6>
<p>
{translate(
"Use XRP to move different currencies around the world."
)}
</p>
</div>
</div>
<div className="mt-10 p-10 br-8 cta-card position-relative">
<div className="z-index-1 position-relative">
<h2 className="h4 mb-10-until-sm mb-8-sm">
{translate(
"The XRP Ledger is built for business."
)}
</h2>
<p className="mb-10">
{translate(
"The only major L-1 blockchain thats built for business and designed specifically to power finance use cases and applications at scale. Powerful enough to bootstrap a new economy, the XRP Ledger (XRPL) is fast, scalable, and sustainable."
)}
</p>
</div>
</div>
</div>
<div className="py-26 link-section" id="xrp-trading">
<h2 className="h4 h2-sm mb-8">
{translate("How Is XRP Used in Trading?")}
</h2>
<h5 className="longform mb-10">
{translate(
"XRP is traded on more than 100 markets and exchanges worldwide."
)}
</h5>
<p className="mb-6">
{translate(
"about.xrp.xrp-in-trading.ppart1",
"XRPs low transaction fees, reliability and high-speed enable traders to use the digital asset as high-speed, cost-efficient and reliable collateral across trading venues—"
)}
<a
href="https://ripple.com/insights/xrp-a-preferred-base-currency-for-arbitrage-trading/"
target="_blank"
>
{translate("about.xrp.xrp-in-trading.ppart2","seizing arbitrage opportunities")}
</a>
{translate(
"about.xrp.xrp-in-trading.ppart3",
", servicing margin calls and managing general trading inventory in real time."
)}
</p>
<p>
{translate(
"Because of the properties inherent to XRP and the ecosystem around it, traders worldwide are able to shift collateral, bridge currencies and switch from one crypto into another nearly instantly, across any exchange on the planet."
)}
</p>
</div>
<div className="py-26 link-section" id="ripple">
<h2 className="h4 h2-sm mb-8">
{translate(
"What Is the Relationship Between Ripple and XRP?"
)}
</h2>
<h5 className="longform mb-10">
<a href="https://ripple.com" target="_blank">
{translate("Ripple")}
</a>
{translate(
" is a technology company that makes it easier to build a high-performance, global payments business. XRP is a digital asset independent of this."
)}
</h5>
<p>
{translate(
"There is a finite amount of XRP. All XRP is already in existence today—no more than the original 100 billion can be created. The XRPL founders gifted 80 billion XRP, the platforms native currency, to Ripple. To provide predictability to the XRP supply, Ripple has locked 55 billion XRP (55% of the total possible supply) into a series of escrows using the XRP Ledger itself. The XRPL's transaction processing rules, enforced by the consensus protocol, control the release of the XRP."
)}
</p>
<div className="mt-10 p-10 br-8 cta-card position-relative">
<div className="z-index-1 position-relative">
<h3 className="h4">
{translate("about.xrp.ripple-escrow.ppart1","As of ")}
<span className="stat-highlight" id="ripple-escrow-as-of">
{translate("about.xrp.ripple-escrow.ppart2","October 2024")}
</span>
{translate("about.xrp.ripple-escrow.ppart3"," ")}
<br />
<span className="d-inline-flex">
<img
id="xrp-mark-overview"
className="mw-100 invertible-img me-2"
src={require("../static/img/logos/xrp-mark.svg")}
alt="XRP Logo Mark"
/>
<span
className="numbers stat-highlight"
id="ripple-escrow-amount"
>
{translate("38B")}
<div className="z-index-1 position-relative">
<h3 className="h4">
{translate("about.xrp.ripple-escrow.ppart1","As of ")}
<span className="stat-highlight" id="ripple-escrow-as-of">
{translate("about.xrp.ripple-escrow.ppart2","October 2024")}
</span>
</span>
<br />
{translate("XRP remains in escrow")}
</h3>
{translate("about.xrp.ripple-escrow.ppart3"," ")}
<br />
<span className="d-inline-flex">
<img
id="xrp-mark-overview"
className="mw-100 invertible-img mr-2"
src={require("../static/img/logos/xrp-mark.svg")}
alt="XRP Logo Mark"
/>
<span
className="numbers stat-highlight"
id="ripple-escrow-amount"
>
{translate("38B")}
</span>
</span>
<br />
{translate("XRP remains in escrow")}
</h3>
</div>
</div>
</div>
</div>
<div className="link-section py-26" id="wallets">
<h2 className="h4 h2-sm mb-8">
{translate("What Wallets Support XRP?")}
</h2>
<h5 className="longform mb-10">
{translate(
"Digital wallets are pieces of software that allow people to send, receive, and store cryptocurrencies, including XRP. There are two types of digital wallets: hardware and software."
)}
</h5>
<ul className={`nav nav-grid-lg cols-of-${totalCols}`} id="wallets">
<li className="nav-item nav-grid-head">
<h6 className="subhead-sm-r">{translate("Software Wallets")}</h6>
</li>
{softwallets.map((wallet) => (
<li key={wallet.id} className="nav-item">
<a
className="nav-link external-link"
href={wallet.href}
target="_blank"
>
<img
className={`mw-100 ${
!!wallet?.imgclasses && wallet.imgclasses
}`}
id={wallet.id}
alt={wallet.alt}
/>
</a>
<div className="link-section py-26" id="wallets">
<h2 className="h4 h2-sm mb-8">
{translate("What Wallets Support XRP?")}
</h2>
<h5 className="longform mb-10">
{translate(
"Digital wallets are pieces of software that allow people to send, receive, and store cryptocurrencies, including XRP. There are two types of digital wallets: hardware and software."
)}
</h5>
<ul className={`nav nav-grid-lg cols-of-${totalCols}`} id="wallets">
<li className="nav-item nav-grid-head">
<h6 className="fs-4-5">{translate("Software Wallets")}</h6>
</li>
))}
<li className="nav-item nav-grid-head">
<h6 className="subhead-sm-r">{translate("Hardware Wallets")}</h6>
</li>
{hardwallets.map((wallet) => (
<li className="nav-item" key={wallet.id}>
<a
className="nav-link external-link"
href={wallet.href}
target="_blank"
>
<img
className={`mw-100 ${
!!wallet.imgclasses && wallet.imgclasses
}`}
id={wallet.id}
alt={wallet.alt}
/>
</a>
{softwallets.map((wallet) => (
<li key={wallet.id} className="nav-item">
<a
className="nav-link external-link"
href={wallet.href}
target="_blank"
>
<img
className={`mw-100 ${
!!wallet?.imgclasses && wallet.imgclasses
}`}
id={wallet.id}
alt={wallet.alt}
/>
</a>
</li>
))}
<li className="nav-item nav-grid-head">
<h6 className="fs-4-5">{translate("Hardware Wallets")}</h6>
</li>
))}
</ul>
<p className="label-l mt-10">
{translate(
"Disclaimer: This information is drawn from other sources on the internet. XRPL.org does not endorse or recommend any exchanges or make any representations with respect to exchanges or the purchase or sale of digital assets more generally. Its advisable to conduct your own due diligence before relying on any third party or third-party technology, and providers may vary significantly in their compliance, data security, and privacy practices."
)}
</p>
</div>
<div className="py-26 link-section" id="exchanges">
<h2 className="h4 h2-sm mb-8">
{translate("What Exchanges Support XRP?")}
</h2>
<h5 className="longform mb-10">
{translate(
"Exchanges are where people trade currencies. XRP is traded on more than 100 markets and exchanges worldwide."
)}
</h5>
<p className="mb-10">
{translate(
"There are different types of exchanges that vary depending on the type of market (spot, futures, options, swaps), and the type of security model (custodial, non-custodial)."
)}
</p>
<div className="card-grid card-grid-2xN mb-10">
<div>
<h6 className="subhead-sm-r">{translate("Spot Exchanges")}</h6>
<p className="mb-0">
{translate(
"Spot exchanges allow people to buy and sell cryptocurrencies at current (spot) market rates."
)}
</p>
</div>
<div>
<h6 className="subhead-sm-r">
{translate("Futures, Options and Swap Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Futures, options and swap exchanges allow people to buy and sell standardized contracts of cryptocurrency market rates in the future."
)}
</p>
</div>
<div>
<h6 className="subhead-sm-r">
{translate("Custodial Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Custodial exchanges manage a users private keys, and publish centralized order books of buyers and sellers."
)}
</p>
</div>
<div>
<h6 className="subhead-sm-r">
{translate("Non-Custodial Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Non-custodial exchanges, also known as decentralized exchanges, do not manage a users private keys, and publish decentralized order books of buyers and sellers on a blockchain."
)}
</p>
</div>
{hardwallets.map((wallet) => (
<li className="nav-item" key={wallet.id}>
<a
className="nav-link external-link"
href={wallet.href}
target="_blank"
>
<img
className={`mw-100 ${
!!wallet.imgclasses && wallet.imgclasses
}`}
id={wallet.id}
alt={wallet.alt}
/>
</a>
</li>
))}
</ul>
<p className="fs-3 mt-10">
{translate(
"Disclaimer: This information is drawn from other sources on the internet. XRPL.org does not endorse or recommend any exchanges or make any representations with respect to exchanges or the purchase or sale of digital assets more generally. Its advisable to conduct your own due diligence before relying on any third party or third-party technology, and providers may vary significantly in their compliance, data security, and privacy practices."
)}
</p>
</div>
<div className="py-26 link-section" id="exchanges">
<h2 className="h4 h2-sm mb-8">
{translate("What Exchanges Support XRP?")}
</h2>
<h5 className="longform mb-10">
{translate(
"Exchanges are where people trade currencies. XRP is traded on more than 100 markets and exchanges worldwide."
)}
</h5>
<p className="mb-10">
{translate(
"There are different types of exchanges that vary depending on the type of market (spot, futures, options, swaps), and the type of security model (custodial, non-custodial)."
)}
</p>
<div className="card-grid card-grid-2xN mb-10">
<div>
<h6 className="fs-4-5">{translate("Spot Exchanges")}</h6>
<p className="mb-0">
{translate(
"Spot exchanges allow people to buy and sell cryptocurrencies at current (spot) market rates."
)}
</p>
</div>
<div>
<h6 className="fs-4-5">
{translate("Futures, Options and Swap Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Futures, options and swap exchanges allow people to buy and sell standardized contracts of cryptocurrency market rates in the future."
)}
</p>
</div>
<div>
<h6 className="fs-4-5">
{translate("Custodial Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Custodial exchanges manage a users private keys, and publish centralized order books of buyers and sellers."
)}
</p>
</div>
<div>
<h6 className="fs-4-5">
{translate("Non-Custodial Exchanges")}
</h6>
<p className="mb-0">
{translate(
"Non-custodial exchanges, also known as decentralized exchanges, do not manage a users private keys, and publish decentralized order books of buyers and sellers on a blockchain."
)}
</p>
</div>
</div>
<h6>
{translate("Top Exchanges, according to CryptoCompare")}
</h6>
<ul
className="nav nav-grid-lg cols-of-5 mb-10"
id="top-exchanges"
>
{exchanges.map((exch, i) => (
<li className="nav-item" key={exch.id}>
<a
className="nav-link external-link"
href={exch.href}
target="_blank"
>
<span className="longform mr-3">{i+1}</span>
<img className="mw-100" id={exch.id} alt={exch.alt} />
</a>
</li>
))}
</ul>
<p className="fs-3 mt-10 mb-0">
{translate(
"Disclaimer: This information is drawn from other sources on the internet. XRPL.org does not endorse or recommend any exchanges or make any representations with respect to exchanges or the purchase or sale of digital assets more generally. Its advisable to conduct your own due diligence before relying on any third party or third-party technology, and providers may vary significantly in their compliance, data security, and privacy practices."
)}
</p>
</div>
<h6>
{translate("Top Exchanges, according to CryptoCompare")}
</h6>
<ul
className="nav nav-grid-lg cols-of-5 mb-10"
id="top-exchanges"
>
{exchanges.map((exch, i) => (
<li className="nav-item" key={exch.id}>
<a
className="nav-link external-link"
href={exch.href}
target="_blank"
>
<span className="longform me-3">{i+1}</span>
<img className="mw-100" id={exch.id} alt={exch.alt} />
</a>
</li>
))}
</ul>
<p className="label-l mt-10 mb-0">
{translate(
"Disclaimer: This information is drawn from other sources on the internet. XRPL.org does not endorse or recommend any exchanges or make any representations with respect to exchanges or the purchase or sale of digital assets more generally. Its advisable to conduct your own due diligence before relying on any third party or third-party technology, and providers may vary significantly in their compliance, data security, and privacy practices."
)}
</p>
</div>
</div>
</div>
</section>
</section>
</div>
</div>
);
}

142
blog/2026/rippled-3.1.1.md Normal file
View File

@@ -0,0 +1,142 @@
---
category: 2026
date: "2026-02-23"
template: '../../@theme/templates/blogpost'
seo:
title: Introducing XRP Ledger version 3.1.1 and upcoming Devnet reset
description: rippled version 3.1.1 is now available. This version disables the Batch and fixBatchInnerSigs amendments. Devnet is also scheduled to reset on Tuesday, March 3, 2026 to prevent validators from becoming amendment blocked.
labels:
- rippled Release Notes
markdown:
editPage:
hide: true
---
# Introducing XRP Ledger version 3.1.1 and Upcoming Devnet Reset
Version 3.1.1 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release supersedes version 3.1.0, disabling the `Batch` and `fixBatchInnerSigs` amendments due to a severe bug.
## Upcoming Devnet Reset
Devnet is scheduled for a reset on **Tuesday, March 3, 2026**. The `Batch` amendment requires more development and is now set to unsupported in version 3.1.1. To prevent validators that upgrade to this version from becoming amendment blocked, Devnet must be reset.
### Impact
This reset affects Devnet only. Other networks will continue to operate as usual, including XRPL Mainnet, XRPL Testnet, Xahau, and the Hooks Testnet.
The reset will delete all ledger data in Devnet, including all accounts, transactions, balances, settings, offers, AMMs, escrows, and other data. This means all balances will be reset to zero and the block number will start at one again. No changes are anticipated to services such as Devnet APIs, faucets, Explorers, access rights, and wallet integrations; these services usually manage resets without issues.
Any existing accounts or other data will need new test XRP from the faucet and will need to be re-created.
If code relies on specific addresses, a request to the faucet can fund the same address again. However, any AMMs or Vaults that are re-created after the reset will have different pseudo-account addresses. As a reminder, it's best not to use the same addresses or key pairs on Mainnet and any developer networks.
## Action Required
If you run an XRP Ledger server, upgrade to version 3.1.1 as soon as possible to ensure service continuity.
### Add New GPG Key
As a reminder, [Ripple rotated the GPG key](./gpg-key-rotation.md) used to sign `rippled` packages. You must download and trust the new key before upgrading to version 3.1.1.
{% tabs %}
{% tab label="Red Hat / CentOS" %}
```bash
sudo rpm --import https://repos.ripple.com/repos/rippled-rpm/stable/repodata/repomd.xml.key
rpm -qi gpg-pubkey-ab06faa6 | gpg --show-keys --fingerprint
```
{% /tab %}
{% tab label="Ubuntu / Debian" %}
```bash
sudo install -d -m 0755 /etc/apt/keyrings && \
curl -fsSL https://repos.ripple.com/repos/api/gpg/key/public \
| gpg --dearmor \
| sudo tee /etc/apt/keyrings/ripple.gpg > /dev/null
gpg --show-keys --fingerprint /etc/apt/keyrings/ripple.gpg
```
Ensure the `signed-by` path in your Ripple source list refers to the location the key was downloaded. For example, on an Ubuntu 22.04 Jammy installation, `/etc/apt/sources.list.d/ripple.list` would contain:
```
deb [signed-by=/etc/apt/keyrings/ripple.gpg] https://repos.ripple.com/repos/rippled-deb jammy stable
```
{% /tab %}
{% /tabs %}
The output should include an entry for Ripple such as the following:
```
pub ed25519 2026-02-16 [SC] [expires: 2033-02-14]
E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6
uid TechOps Team at Ripple <techops+rippled@ripple.com>
sub ed25519 2026-02-16 [S] [expires: 2029-02-15]
```
{% admonition type="danger" name="Warning" %}
Only trust this key if its fingerprint exactly matches: `E057 C1CF 72B0 DF1A 4559 E857 7DEE 9236 AB06 FAA6`.
{% /admonition %}
### Install / Upgrade
On supported platforms, see the [instructions on installing or updating `rippled`](../../docs/infrastructure/installation/index.md).
| Package | SHA-256 |
|:--------|:--------|
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-3.1.1-1.el9.x86_64.rpm) | `c6d028db1e2a4da898df68e5a92a893bebf1d167a0539d15ae27435f2155ccb2` |
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_3.1.1-1_amd64.deb) | `cc30c33012bd83ed793b38738870cf931a96ae106fde60b71685c766da1d22e3` |
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/master/BUILD.md). The most recent commit in the git log should be the change setting the version:
```text
commit c5988233d05bedddac28866ed37607f4869855f9
Author: Ed Hennis <ed@ripple.com>
Date: Mon Feb 23 16:47:09 2026 -0400
Set version to 3.1.1 (#6410)
```
### Delete Devnet Database
If you run a `rippled` server that is connected to Devnet, after the reset you should delete your database data and restart the server. Database files and folders are defined in your config file in the `[database_path]` and `[node_db]` stanzas. If you use the default config, you can run the following commands:
```sh
rm -r /var/lib/rippled/db/*
systemctl restart rippled.service
```
## Full Changelog
### Amendments
- **Batch** - A bug was discovered in `Batch`, and the amendment was disabled. The fix for this feature will be included in a future release as `BatchV1_1`. ([#6402](https://github.com/XRPLF/rippled/pull/6402))
- **fixBatchInnerSigs** - A bug was discovered in `Batch`, so this amendment was also disabled. This fix will be included in a future release as part of `BatchV1_1`. ([#6402](https://github.com/XRPLF/rippled/pull/6402))
### CI/Build
- CI: Update `prepare-runner` action to fix macOS build environment. ([#6402](https://github.com/XRPLF/rippled/pull/6402))
## Credits
Thanks to Pranamya Keshkamat and Cantina AI for discovering and responsibly disclosing the `Batch` issue.
The following RippleX teams contributed to this release:
- RippleX Engineering
- RippleX Docs
- RippleX Product
## Bug Bounties and Responsible Disclosures
We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find.
For more information, see:
- [Ripple's Bug Bounty Program](https://ripple.com/legal/bug-bounty/)
- [rippled Security Policy](https://github.com/XRPLF/rippled/blob/develop/SECURITY.md)

View File

@@ -0,0 +1,113 @@
---
category: 2026
date: "2026-02-26"
template: '../../@theme/templates/blogpost'
seo:
description: This vulnerability disclosure report contains technical details of the XRP Ledger bug reported on February 19, 2026.
labels:
- Advisories
markdown:
editPage:
hide: true
---
# Vulnerability Disclosure Report: XRPL Batch Amendment Unauthorized Inner Transaction Execution
_By XRPL Labs_
This vulnerability disclosure report contains technical details of the XRPL `Batch` amendment bug reported on February 19, 2026.
**Date Reported:** February 19, 2026
**Affected Version(s):** rippled 3.1.0 (with the `Batch` amendment enabled)
## Summary of Vulnerability
On February 19, 2026, **Pranamya Keshkamat** and **Cantina AI** identified a critical logic flaw in the signature-validation logic of the XRPL `Batch` amendment. The bug allowed an attacker to execute inner transactions on behalf of arbitrary victim accounts without their private keys, enabling unauthorized fund transfers and ledger state changes. The amendment was in its voting phase and had not been activated on mainnet; no funds were at risk. Note that even before this bug was reported, the `Batch` amendment was disabled due to extra caution around ensuring the `fixBatchInnerSigs` amendment activated first.
UNL validators were immediately advised to vote "No" on the amendment. The emergency release **rippled 3.1.1** marks both `Batch` and `fixBatchInnerSigs` as unsupported, preventing activation. A corrected replacement, `BatchV1_1`, has been implemented and is currently under review; no release date has been set.
## Impact
If the `Batch` amendment had activated before this bug was caught, an attacker could have:
- **Stolen funds:** executed inner `Payment` transactions draining victim accounts down to the reserve, without access to victim private keys.
- **Modified ledger state:** submitted `AccountSet`, `TrustSet`, and potentially `AccountDelete` transactions from victim accounts without authorization.
- **Destabilized the ecosystem:** a successful large-scale exploit could have caused substantial loss of confidence in XRPL, with potentially significant disruption for the broader ecosystem.
## Technical Details
### Discovery
**Pranamya Keshkamat** and the autonomous AI security tool **Apex** (developed by **Cantina AI**) identified the vulnerability via static analysis of the rippled codebase and submitted a responsible disclosure report. Ripple engineering teams promptly validated the report with an independent proof-of-concept and a full unit-test reproduction. Remediation began the same evening.
### Root Cause
When the `Batch` amendment is enabled, inner transactions in a batch are intentionally unsigned; authorization is delegated entirely to the outer batch's list of batch signers. The function responsible for validating those signers contained a critical loop error: when it encountered a signer whose account did not yet exist on the ledger and whose signing key matched their own account (the normal case for a brand-new account), it immediately declared success and exited, skipping validation of all remaining signers entirely.
### Exploit Path
1. The attacker constructs a batch transaction containing three inner transactions: one that creates a new account they control (account B), one simple transaction from that new account (making it a required signer), and one payment from the victim account to the attacker.
2. The attacker provides two batch signer entries: a legitimate one for account B signed with B's own key, and a forged one claiming to authorize the victim account but signed with the attacker's own key.
3. Because account B does not yet exist at validation time, the signer check exits successfully after the first entry and never validates the second.
4. The victim's payment executes without the victim's keys ever being involved.
## Remediation
- UNL validators were contacted and advised to vote "No" on the `Batch` amendment.
- **rippled 3.1.1** was published on February 23, 2026. This release marks `Batch` and `fixBatchInnerSigs` as unsupported, preventing them from receiving validator votes or being activated on the network. This is the immediate remediation and does not contain the underlying logic fix.
- The full logic fix removing the early-exit, adding additional authorization guards, and tightening the scope of the signing check has been implemented and is undergoing a thorough review process prior to release under the `BatchV1_1` amendment. Given the sensitivity of the changes, no timeline has been set.
## Security Enhancements Roadmap
- Adding AI-assisted code audit pipelines as a standard step in the review process.
- Extending static analysis coverage to flag premature success returns inside signer-iteration loops.
- Adding explicit comments and invariant assertions documenting expected behavior for uncreated accounts at validation time.
- Reviewing all other locations in the codebase where early success returns occur inside loops to confirm no similar patterns exist.
## Steps to Reproduce
1. Fund two attacker-controlled accounts and leave a third unfunded. Construct a batch transaction containing a payment that creates the unfunded account, a simple transaction from that account, and a payment from the victim account to the attacker.
2. Sign the batch message with the unfunded account's master key for its signer entry, and with the attacker's own key for a second signer entry that falsely claims to authorize the victim account.
3. Submit the batch transaction to the network.
**Pre-fix behavior:**
- The batch transaction succeeds.
- The victim account's balance is reduced by the payment amount without the victim's keys being used.
- The attacker's balance increases by the net amount minus the batch fee.
**Expected post-fix behavior:**
- The batch transaction is rejected with an authorization error.
- No account balances change.
## Fixes / Patches Available
[**rippled 3.1.1**](https://xrpl.org/blog/2026/rippled-3.1.1) is available now. The corrected `BatchV1_1` amendment will be included in a future release following completion of its review; no date has been set.
## Acknowledgements
Thanks to **Pranamya Keshkamat** and **Cantina AI** (whose autonomous security tool *Apex* identified the vulnerability) for responsible and thorough disclosure. Their detailed bug report, proof-of-concept, and constructive collaboration throughout the remediation process were invaluable.
Thanks also to the UNL validators who moved swiftly to vote against the affected amendment, the Ripple and XRPL Foundation engineering teams for the expedited review and emergency release, and the broader community of validators, developers, and contributors who keep the XRP Ledger safe and secure.
## References
- [rippled 3.1.1 release](https://xrpl.org/blog/2026/rippled-3.1.1)
- [Community post on X](https://x.com/hrkrshnn/status/2025336360010387613)
## Contact
To report security issues, contact [security@ripple.com](mailto:security@ripple.com).
## Incident Response Timeline
| Key Action | Timestamp | Description |
|---|---|---|
| Initial Discovery | February 19, 2026 | Bug reported by Pranamya Keshkamat & Cantina AI. Engineering teams validated the report and opened an emergency response channel. |
| Validator Notification | February 19, 2026 | UNL validators advised to vote "No" on `Batch`. Several validators applied vetoes the same evening. |
| Unit Test Reproduction | February 19, 2026 | Independent unit-test reproduction confirmed. Proof-of-concept script validated by multiple engineers. |
| Patch In Review | February 21, 2026 | Fix created in a private repository and currently under review. |
| Emergency Release | February 23, 2026 | rippled 3.1.1 published. Operators advised to upgrade. |
| Report Published | February 25, 2026 | Public vulnerability disclosure report published. |

View File

@@ -57,6 +57,13 @@ export default function Index() {
return (
<div className="landing dev-blog">
<div className="justify-content-center align-items-lg-center">
<div className="position-relative d-none-sm">
<img
alt="background purple waves"
src={require("../static/img/backgrounds/home-purple.svg")}
id="blog-purple"
/>
</div>
<section className="py-lg-5 text-center mt-lg-5">
<div className="mx-auto text-center col-lg-5">
<div className="d-flex flex-column">

View File

@@ -10,6 +10,8 @@
- group: '2026'
expanded: false
items:
- page: 2026/vulnerabilitydisclosurereport-bug-feb2026.md
- page: 2026/rippled-3.1.1.md
- page: 2026/gpg-key-rotation.md
- page: 2026/rippled-3.1.0.md
- page: 2026/clio-2.7.0.md

2025
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,8 @@ import { useThemeHooks } from '@redocly/theme/core/hooks';
export const frontmatter = {
seo: {
title: 'Ambassadors',
description: "The XRPL Campus Ambassador program connects and empowers student champions of the XRPL.",
title: 'Ambassadors',
description: "The XRPL Campus Ambassador program connects and empowers student champions of the XRPL.",
}
};
@@ -17,403 +17,409 @@ export default function Ambassadors() {
const { translate } = useTranslate();
return (
<div className="landing page-ambassadors">
<div className="landing page-ambassadors">
<div>
<div className="position-relative d-none-sm">
<img alt="background purple waves" src={require("../static/img/backgrounds/ambassador-purple.svg")} className="position-absolute" style={{top: 0, right: 0}} />
</div>
<section className="container-new py-26 text-lg-center">
{/* For translater: This section could change dynamically based on the time of year */}
<div className="p-0 col-lg-8 mx-lg-auto">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">{translate("Become an XRP Ledger Campus Ambassador")}</h1>
<h6 className="eyebrow mb-3">{translate("Join the Student Cohort")}</h6>
{/* For translater: This section could change dynamically based on the time of year */}
<div className="p-0 col-lg-8 mx-lg-auto">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">{translate("Become an XRP Ledger Campus Ambassador")}</h1>
<h6 className="eyebrow mb-3">{translate("Join the Student Cohort")}</h6>
</div>
<p className="mt-3 pt-3 col-lg-8 mx-lg-auto p-0">{translate("This fall, the ")} <b>{translate("XRPL Student Builder Residency ")}</b> {translate("offers top technical students a 3-week online program (Oct 21 - Nov 13) to develop XRPL projects with expert mentorship. Apply by Oct 14, 2024")}</p>
<p className=" col-lg-8 mx-lg-auto p-0">{translate("This program will run from October 21 - November 13 and will be conducted entirely online. ")}</p>
<p className="pb-3 col-lg-8 mx-lg-auto p-0"><b>{translate("Applications due October 14, 2024")}</b>{translate(" @ 11:59pm PDT")}</p>
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
<p className="mt-3 pt-3 col-lg-8 mx-lg-auto p-0">{translate("This fall, the ")} <b>{translate("XRPL Student Builder Residency ")}</b> {translate("offers top technical students a 3-week online program (Oct 21 - Nov 13) to develop XRPL projects with expert mentorship. Apply by Oct 14, 2024")}</p>
<p className=" col-lg-8 mx-lg-auto p-0">{translate("This program will run from October 21 - November 13 and will be conducted entirely online. ")}</p>
<p className="pb-3 col-lg-8 mx-lg-auto p-0"><b>{translate("Applications due October 14, 2024")}</b>{translate(" @ 11:59pm PDT")}</p>
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</section>
{/* Current Students */}
<section className="container-new py-26">
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-lg-2 mx-lg-4 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 pr-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Campus Ambassadors")}</h3>
<h6 className="eyebrow mb-3">{translate("Empowering Students")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("The XRPL Campus Ambassador program aims to elevate the impact of college students who are passionate about blockchain technology.")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="order-lg-1 col-lg-6 px-0 mr-lg-4">
<div className="row m-0">
<img alt="Person speaking and person taking photo" src={require("../static/img/ambassadors/developer-hero@2x.png")} className="w-100" />
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3 p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-lg-2 mx-lg-4 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 pr-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Campus Ambassadors")}</h3>
<h6 className="eyebrow mb-3">{translate("Empowering Students")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("The XRPL Campus Ambassador program aims to elevate the impact of college students who are passionate about blockchain technology.")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="order-lg-1 col-lg-6 px-0 mr-lg-4">
<div className="row m-0">
<img alt="Person speaking and person taking photo" src={require("../static/img/ambassadors/developer-hero@2x.png")} className="w-100" />
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3 p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
</section>
{/* Benefits */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Why become an XRPL Campus Ambassador?")}</h3>
<h6 className="eyebrow mb-3">{translate("Benefits")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Join a global cohort of students empowering others to build on the XRPL.")}</p>
</div>
<div className="order-2 col-lg-6 px-0 mr-lg-5">
<div className="row align-items-center m-0" id="benefits-list">
{/* benefitslist */}
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img alt="Smiley face" id="benefits-01" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Exclusive Opportunities")}</h6>
<p>{translate("Get access and invitations to Ambassador-only events and opportunities")}</p>
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Why become an XRPL Campus Ambassador?")}</h3>
<h6 className="eyebrow mb-3">{translate("Benefits")}</h6>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img alt="Book" id="benefits-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Education")}</h6>
<p>{translate("Tutorials and workshops from leading XRPL and blockchain developers")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Gift" id="benefits-03" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Swag")}</h6>
<p>{translate("New XRPL swag for Ambassadors and swag to share with other students")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Medallion" id="benefits-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Mentorship")}</h6>
<p>{translate("Meet with and learn from influential builders and leaders across the XRPL community")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Up Arrow" id="benefits-05" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Career Acceleration")}</h6>
<p className="pb-lg-0">{translate("Gain hands-on experience building communities and grow your professional network in the blockchain industry")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Dollar Sign" id="benefits-06" className="pl-lg-3" />
<div className="pb-lg-0">
<h6 className="mb-3">{translate("Stipend")}</h6>
<p className="pb-lg-0">{translate("Receive a stipend to fund your ideas and initiatives that fuel XRPL growth in your community")}</p>
</div>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Join a global cohort of students empowering others to build on the XRPL.")}</p>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<img alt="Book" id="benefits-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Education")}</h6>
<p>{translate("Tutorials and workshops from leading XRPL and blockchain developers")}</p>
<div className="order-2 col-lg-6 px-0 mr-lg-5">
<div className="row align-items-center m-0" id="benefits-list">
{/* benefitslist */}
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img alt="Smiley face" id="benefits-01" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Exclusive Opportunities")}</h6>
<p>{translate("Get access and invitations to Ambassador-only events and opportunities")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img alt="Book" id="benefits-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Education")}</h6>
<p>{translate("Tutorials and workshops from leading XRPL and blockchain developers")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Gift" id="benefits-03" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Swag")}</h6>
<p>{translate("New XRPL swag for Ambassadors and swag to share with other students")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Medallion" id="benefits-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Mentorship")}</h6>
<p>{translate("Meet with and learn from influential builders and leaders across the XRPL community")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Up Arrow" id="benefits-05" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Career Acceleration")}</h6>
<p className="pb-lg-0">{translate("Gain hands-on experience building communities and grow your professional network in the blockchain industry")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Dollar Sign" id="benefits-06" className="pl-lg-3" />
<div className="pb-lg-0">
<h6 className="mb-3">{translate("Stipend")}</h6>
<p className="pb-lg-0">{translate("Receive a stipend to fund your ideas and initiatives that fuel XRPL growth in your community")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<img alt="Book" id="benefits-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Education")}</h6>
<p>{translate("Tutorials and workshops from leading XRPL and blockchain developers")}</p>
</div>
</div>
<div className="px-lg-3 pb-3 ">
<img alt="Medallion" id="benefits-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Mentorship")}</h6>
<p>{translate("Meet with and learn from influential builders and leaders across the XRPL community")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Dollar Sign" id="benefits-06" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Stipend")}</h6>
<p className="pb-lg-0">{translate("Receive a stipend to fund your ideas and initiatives that fuel XRPL growth in your community")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="px-lg-3 pb-3 ">
<img alt="Medallion" id="benefits-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Mentorship")}</h6>
<p>{translate("Meet with and learn from influential builders and leaders across the XRPL community")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="Dollar Sign" id="benefits-06" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Stipend")}</h6>
<p className="pb-lg-0">{translate("Receive a stipend to fund your ideas and initiatives that fuel XRPL growth in your community")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
</div>
</section>
{/* Eligibility */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-4">
<div className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 mr-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Should You Apply?")}</h3>
<h6 className="eyebrow mb-3">{translate("Eligibility for XRPL Campus Ambassadors")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Students currently enrolled in an undergraduate or postgraduate program at an accredited college or university are eligible to apply.")}</p>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0" id="eligibility-list">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img alt="Calendar" id="eligibility-01" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("A Leader")}</h6>
<p>{translate("Interested in leading meetups and workshops for your local campus community")}</p>
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-4">
<div className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 mr-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Should You Apply?")}</h3>
<h6 className="eyebrow mb-3">{translate("Eligibility for XRPL Campus Ambassadors")}</h6>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img alt="Book" id="eligibility-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Active")}</h6>
<p>{translate("An active participant in the XRPL community or interested in blockchain and crypto technologies")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="CPU" id="eligibility-03" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Curious")}</h6>
<p>{translate("Eager to learn more about technical blockchain topics and the XRPL")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Quote Bubble" id="eligibility-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Passionate")}</h6>
<p>{translate("Passionate about increasing XRPL education and awareness through events, content, and classroom engagement")}</p>
</div>
</div>
<div className="p-lg-3 pb-3">
<img alt="People" id="eligibility-05" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Creative")}</h6>
<p className="pb-lg-0 mb-0">{translate("Ability to think outside the box and have an impact in the XRPL student community")}</p>
</div>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Students currently enrolled in an undergraduate or postgraduate program at an accredited college or university are eligible to apply.")}</p>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block">
<div className="px-lg-3 pb-3 ">
<img alt="Book" id="eligibility-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Active")}</h6>
<p>{translate("An active participant in the XRPL community or interested in blockchain and crypto technologies")} </p>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0" id="eligibility-list">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img alt="Calendar" id="eligibility-01" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("A Leader")}</h6>
<p>{translate("Interested in leading meetups and workshops for your local campus community")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img alt="Book" id="eligibility-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Active")}</h6>
<p>{translate("An active participant in the XRPL community or interested in blockchain and crypto technologies")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img alt="CPU" id="eligibility-03" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Curious")}</h6>
<p>{translate("Eager to learn more about technical blockchain topics and the XRPL")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="Quote Bubble" id="eligibility-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Passionate")}</h6>
<p>{translate("Passionate about increasing XRPL education and awareness through events, content, and classroom engagement")}</p>
</div>
</div>
<div className="p-lg-3 pb-3">
<img alt="People" id="eligibility-05" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Creative")}</h6>
<p className="pb-lg-0 mb-0">{translate("Ability to think outside the box and have an impact in the XRPL student community")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block">
<div className="px-lg-3 pb-3 ">
<img alt="Book" id="eligibility-02" className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Active")}</h6>
<p>{translate("An active participant in the XRPL community or interested in blockchain and crypto technologies")} </p>
</div>
</div>
<div className="px-lg-3 pb-3 ">
<img alt="Quote Bubble" id="eligibility-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Passionate")}</h6>
<p> {translate("Passionate about increasing XRPL education and awareness through events, content, and classroom engagement")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="px-lg-3 pb-3 ">
<img alt="Quote Bubble" id="eligibility-04" className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Passionate")}</h6>
<p> {translate("Passionate about increasing XRPL education and awareness through events, content, and classroom engagement")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
</div>
</section>
{/* Current Students */}
<section className="container-new py-26">
{/* Quotes */}
<div id="carouselSlidesOnly" className="carousel slide col-lg-10 mx-auto px-0" data-ride="carousel">
<div className="carousel-inner">
<div className="carousel-item active">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="I have learned so much through creating programs and connecting with the XRPL community. Im truly grateful for everyone's support along the way and for the opportunity to gain so much knowledge from this expierence" src={require("../static/img/ambassadors/quote1-small.svg")} className="h-100 d-lg-none mb-4" />
<img alt="I have learned so much through creating programs and connecting with the XRPL community. Im truly grateful for everyone's support along the way and for the opportunity to gain so much knowledge from this expierence" src={require("../static/img/ambassadors/quote1.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Derrick N.</strong><br />
Toronto Metropolitan University<br />
Spring 2023 XRPL Campus Ambassador</p>
{/* Quotes */}
<div id="carouselSlidesOnly" className="carousel slide col-lg-10 mx-auto px-0" data-ride="carousel">
<div className="carousel-inner">
<div className="carousel-item active">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="I have learned so much through creating programs and connecting with the XRPL community. Im truly grateful for everyone's support along the way and for the opportunity to gain so much knowledge from this expierence" src={require("../static/img/ambassadors/quote1-small.svg")} className="h-100 d-lg-none mb-4" />
<img alt="I have learned so much through creating programs and connecting with the XRPL community. Im truly grateful for everyone's support along the way and for the opportunity to gain so much knowledge from this expierence" src={require("../static/img/ambassadors/quote1.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Derrick N.</strong><br />
Toronto Metropolitan University<br />
Spring 2023 XRPL Campus Ambassador</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="carousel-item mb-20">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="The XRPL Campus Ambassador program really helped broaden my view of the blockchain industry with their learning resource and virtual community. Being an ambassador allowed me to meet industry professionals and likeminded peers which have given me invaluable experiences and insights." src={require("../static/img/ambassadors/quote2-small.svg")} className="h-150 d-lg-none mb-4" />
<img alt="The XRPL Campus Ambassador program really helped broaden my view of the blockchain industry with their learning resource and virtual community. Being an ambassador allowed me to meet industry professionals and likeminded peers which have given me invaluable experiences and insights." src={require("../static/img/ambassadors/quote2.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Sally Z.</strong><br />
Toronto Metropolitan University<br />
Spring 2023 XRPL Campus Ambassador</p>
<div className="carousel-item mb-20">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="The XRPL Campus Ambassador program really helped broaden my view of the blockchain industry with their learning resource and virtual community. Being an ambassador allowed me to meet industry professionals and likeminded peers which have given me invaluable experiences and insights." src={require("../static/img/ambassadors/quote2-small.svg")} className="h-150 d-lg-none mb-4" />
<img alt="The XRPL Campus Ambassador program really helped broaden my view of the blockchain industry with their learning resource and virtual community. Being an ambassador allowed me to meet industry professionals and likeminded peers which have given me invaluable experiences and insights." src={require("../static/img/ambassadors/quote2.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Sally Z.</strong><br />
Toronto Metropolitan University<br />
Spring 2023 XRPL Campus Ambassador</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="carousel-item mb-40">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="Ive had the pleasure over the course of this program to speak with amazing individuals, I encourage you all to reach out to other people in this program and make as many connections as you can. You will quickly find out that by speaking with other people in this cohort you can learn just about anything if you ask the right people." src={require("../static/img/ambassadors/quote3-small.svg")} className="h-150 d-lg-none mb-4" />
<img alt="Ive had the pleasure over the course of this program to speak with amazing individuals, I encourage you all to reach out to other people in this program and make as many connections as you can. You will quickly find out that by speaking with other people in this cohort you can learn just about anything if you ask the right people." src={require("../static/img/ambassadors/quote3.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Nick D.</strong><br />
Miami University<br />
Spring 2023 XRPL Campus Ambassador</p>
<div className="carousel-item mb-40">
<div className="p-0">
<div className="mb-4 p-lg-3">
<img alt="Ive had the pleasure over the course of this program to speak with amazing individuals, I encourage you all to reach out to other people in this program and make as many connections as you can. You will quickly find out that by speaking with other people in this cohort you can learn just about anything if you ask the right people." src={require("../static/img/ambassadors/quote3-small.svg")} className="h-150 d-lg-none mb-4" />
<img alt="Ive had the pleasure over the course of this program to speak with amazing individuals, I encourage you all to reach out to other people in this program and make as many connections as you can. You will quickly find out that by speaking with other people in this cohort you can learn just about anything if you ask the right people." src={require("../static/img/ambassadors/quote3.svg")} className="h-100 d-none d-lg-block" />
<div className="p-0 col-lg-7 mx-lg-auto">
<p className="p-lg-3 mb-2"><strong>Nick D.</strong><br />
Miami University<br />
Spring 2023 XRPL Campus Ambassador</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
{/* How it Works */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mr-lg-4 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Process to become a Campus Ambassador")}</h3>
<h6 className="eyebrow mb-3">{translate("How it Works")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("Apply now to become an XRPL Campus Ambassador.")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="order-2 col-lg-6 px-0 ml-lg-2">
<div className="row m-0">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img src={require("../static/img/ambassadors/01.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Apply")}</h6>
<p>{translate("Submit an application to be considered for the Campus Ambassador program.")}</p>
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mr-lg-4 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Process to become a Campus Ambassador")}</h3>
<h6 className="eyebrow mb-3">{translate("How it Works")}</h6>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img src={require("../static/img/ambassadors/02.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Interview")}</h6>
<p>{translate("Tell the XRPL community-led panel more about yourself and your interest in the program during an interview.")}</p>
<p className="p-lg-3 mb-2 longform">{translate("Apply now to become an XRPL Campus Ambassador.")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="px-lg-3 pb-3">
<img src={require("../static/img/ambassadors/03.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Join")}</h6>
<p>{translate("Congrats on your new role! Join the global cohort of Ambassadors and meet with community participants during onboarding.")}</p>
</div>
</div>
{/* Hide on large */}
<div className="p-lg-3 pb-3 d-lg-none">
<img src={require("../static/img/ambassadors/04.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Learn")}</h6>
<p> {translate("Participate in personalized learning and training sessions for Ambassadors on the XRPL and blockchain technology.")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block mt-5">
<div className="px-lg-3 pb-3 mt-5">
<img src={require("../static/img/ambassadors/02.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Interview")}</h6>
<p>{translate("Tell the XRPL community-led panel more about yourself and your interest in the program during an interview.")}</p>
<div className="order-2 col-lg-6 px-0 ml-lg-2">
<div className="row m-0">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="px-lg-3 pb-3">
<img src={require("../static/img/ambassadors/01.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Apply")}</h6>
<p>{translate("Submit an application to be considered for the Campus Ambassador program.")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none ">
<img src={require("../static/img/ambassadors/02.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Interview")}</h6>
<p>{translate("Tell the XRPL community-led panel more about yourself and your interest in the program during an interview.")}</p>
</div>
</div>
<div className="px-lg-3 pb-3">
<img src={require("../static/img/ambassadors/03.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Join")}</h6>
<p>{translate("Congrats on your new role! Join the global cohort of Ambassadors and meet with community participants during onboarding.")}</p>
</div>
</div>
{/* Hide on large */}
<div className="p-lg-3 pb-3 d-lg-none">
<img src={require("../static/img/ambassadors/04.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Learn")}</h6>
<p> {translate("Participate in personalized learning and training sessions for Ambassadors on the XRPL and blockchain technology.")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 pl-lg-4 d-none d-lg-block mt-5">
<div className="px-lg-3 pb-3 mt-5">
<img src={require("../static/img/ambassadors/02.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3">
<h6 className="mb-3">{translate("Interview")}</h6>
<p>{translate("Tell the XRPL community-led panel more about yourself and your interest in the program during an interview.")}</p>
</div>
</div>
<div className="p-lg-3 pb-3 ">
<img src={require("../static/img/ambassadors/04.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Learn")}</h6>
<p className="pb-lg-0">{translate("Participate in personalized learning and training sessions for Ambassadors on the XRPL and blockchain technology.")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="p-lg-3 pb-3 ">
<img src={require("../static/img/ambassadors/04.svg")} className="pl-lg-3" />
<div className="p-lg-3 pt-3 pb-lg-0">
<h6 className="mb-3">{translate("Learn")}</h6>
<p className="pb-lg-0">{translate("Participate in personalized learning and training sessions for Ambassadors on the XRPL and blockchain technology.")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
</section>
{/* Image Block */}
<div>
<img alt="Ripple Conferences and two people Sitting" src={require("../static/img/ambassadors/students-large.png")} className="w-100" />
<img alt="Ripple Conferences and two people Sitting" src={require("../static/img/ambassadors/students-large.png")} className="w-100" />
</div>
{/* Global Community Carousel */}
<section className="container-new pt-26">
<div className="p-0 col-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Join a global cohort of Student Ambassadors")}</h3>
<h6 className="eyebrow mb-3">{translate("Global Community")}</h6>
<div className="p-0 col-lg-5">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Join a global cohort of Student Ambassadors")}</h3>
<h6 className="eyebrow mb-3">{translate("Global Community")}</h6>
</div>
</div>
</div>
</section>
<div id="container-scroll">
<div className="photobanner">
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
</div>
<div className="photobanner photobanner-bottom">
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
</div>
<div className="photobanner">
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-1.png")} alt="Ambassador locations" height="48px" className="px-5" />
</div>
<div className="photobanner photobanner-bottom">
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
<img src={require("../static/img/ambassadors/locations-row-2.png")} alt="Ambassador locations" height="48px" className="px-5" />
</div>
</div>
{/* Connect */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Stay connected to the XRPL Community")}</h3>
<h6 className="eyebrow mb-3">{translate("Connect")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">{translate("To stay up-to-date on the latest activity, meetups, and events of the XRPL Community be sure to follow these channels:")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="order-2 col-lg-6 px-0 ml-lg-5">
<div className="row align-items-center m-0">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="p-lg-3 mb-3 pb-3">
<img alt="meetup" src={require("../static/img/ambassadors/icon_meetup.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://www.meetup.com/pro/xrpl-community/">{translate("MeetUp")}</a></h6>
<p>{translate("Attend an XRPL Meetup in your local area")}</p>
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0">
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("Stay connected to the XRPL Community")}</h3>
<h6 className="eyebrow mb-3">{translate("Connect")}</h6>
</div>
</div>
<div className="p-lg-3 mb-3 pb-3">
<img alt="devto" src={require("../static/img/ambassadors/icon_devto.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://dev.to/t/xrpl">{translate("Dev.to Blog")}</a></h6>
<p>{translate("Read more about the activity of the XRPL Ambassadors")}</p>
<p className="p-lg-3 mb-2 longform">{translate("To stay up-to-date on the latest activity, meetups, and events of the XRPL Community be sure to follow these channels:")}</p>
<div className="d-none d-lg-block p-lg-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
</div>
<div className="col-12 col-lg-6 p-0 pl-lg-4">
<div className="p-lg-3 mb-3 pb-3 ">
<img alt="discord" src={require("../static/img/ambassadors/icon_discord.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://xrpldevs.org">{translate("Discord")}</a></h6>
<p>{translate("Join the conversation on the XRPL Developer Discord")}</p>
<div className="order-2 col-lg-6 px-0 ml-lg-5">
<div className="row align-items-center m-0">
<div className="col-12 col-lg-6 p-0 pr-lg-4">
<div className="p-lg-3 mb-3 pb-3">
<img alt="meetup" src={require("../static/img/ambassadors/icon_meetup.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://www.meetup.com/pro/xrpl-community/">{translate("MeetUp")}</a></h6>
<p>{translate("Attend an XRPL Meetup in your local area")}</p>
</div>
</div>
<div className="p-lg-3 mb-3 pb-3">
<img alt="devto" src={require("../static/img/ambassadors/icon_devto.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://dev.to/t/xrpl">{translate("Dev.to Blog")}</a></h6>
<p>{translate("Read more about the activity of the XRPL Ambassadors")}</p>
</div>
</div>
</div>
<div className="col-12 col-lg-6 p-0 pl-lg-4">
<div className="p-lg-3 mb-3 pb-3 ">
<img alt="discord" src={require("../static/img/ambassadors/icon_discord.svg")} className="mb-3" />
<div>
<h6 className="mb-3"><a className="btn-arrow" href="https://xrpldevs.org">{translate("Discord")}</a></h6>
<p>{translate("Join the conversation on the XRPL Developer Discord")}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<button className="btn btn-primary btn-arrow-out" onClick={() => window.open('https://share.hsforms.com/1k47bfuX2SL2DKZtZoJzArg4vgrs', "_blank")} >{translate("Apply for Fall 2024")}</button>
</div>
</div>
</section>
</div>
</div>
</div>
)
}

View File

@@ -19,148 +19,261 @@ export default function Funding() {
return (
<div className="landing page-funding">
<section className="container-new py-26 text-lg-center">
<div className="p-0 col-lg-6 mx-lg-auto">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("XRPL Developer Funding Programs")}
</h1>
<h6 className="eyebrow mb-3">{translate("Project Resources")}</h6>
</div>
<div>
<div className="position-relative d-none-sm">
<img
alt="purple waves"
src={require("../static/img/backgrounds/funding-purple.svg")}
className="position-absolute"
style={{ top: 0, right: 0 }}
/>
</div>
</section>
<section className="container-new py-26">
<div className="p-0 col-lg-6 mx-lg-auto" style={{ maxWidth: 520 }}>
<div className="d-flex flex-column-reverse">
<h1 className="mb-0 h4 h2-sm">
{translate(
"Explore funding opportunities for developers and teams"
)}
</h1>
<h6 className="eyebrow mb-3">{translate("Funding Overview")}</h6>
</div>
<p className="mt-3 py-3 p-0 longform">
{translate(
"If youre a software developer or team looking to build your next project or venture on the XRP Ledger (XRPL), there are a number of opportunities to fund your next innovation."
)}
</p>
</div>
</section>
{/* Hackathons */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div
className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0"
style={{ maxWidth: 520 }}
>
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Hackathons")}</h3>
<h6 className="eyebrow mb-3">{translate("Join an Event")}</h6>
<section className="container-new py-26 text-lg-center">
<div className="p-0 col-lg-6 mx-lg-auto">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("XRPL Developer Funding Programs")}
</h1>
<h6 className="eyebrow mb-3">{translate("Project Resources")}</h6>
</div>
<p className="p-lg-3 mb-2 longform">
</div>
</section>
<section className="container-new py-26">
<div className="p-0 col-lg-6 mx-lg-auto" style={{ maxWidth: 520 }}>
<div className="d-flex flex-column-reverse">
<h1 className="mb-0 h4 h2-sm">
{translate(
"Explore funding opportunities for developers and teams"
)}
</h1>
<h6 className="eyebrow mb-3">{translate("Funding Overview")}</h6>
</div>
<p className="mt-3 py-3 p-0 longform">
{translate(
"Hackathons are open to all developers to explore and invent a project on the XRP Ledger. Visit the events page for updates on upcoming hackathons."
"If youre a software developer or team looking to build your next project or venture on the XRP Ledger (XRPL), there are a number of opportunities to fund your next innovation."
)}
</p>
<div className="d-none d-lg-block p-lg-3">
<Link className="btn btn-primary btn-arrow" to="/community/events">
{translate("See Upcoming Events")}
</Link>
</div>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Software developers and teams building directly on the XRP Ledger"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>{translate("Some coding experience")}</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>{translate("XRPL beginner to advanced developers")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("Prize money and awards")}</p>
</div>
</div>
</section>
{/* Hackathons */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div
className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0"
style={{ maxWidth: 520 }}
>
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Hackathons")}</h3>
<h6 className="eyebrow mb-3">{translate("Join an Event")}</h6>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<p className="p-lg-3 mb-2 longform">
{translate(
"Hackathons are open to all developers to explore and invent a project on the XRP Ledger. Visit the events page for updates on upcoming hackathons."
)}
</p>
<div className="d-none d-lg-block p-lg-3">
<Link className="btn btn-primary btn-arrow" to="/community/events">
{translate("See Upcoming Events")}
</Link>
</div>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Software developers and teams building directly on the XRP Ledger"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>{translate("Some coding experience")}</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("Prize money and awards")}</p>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>{translate("XRPL beginner to advanced developers")}</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("Prize money and awards")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>{translate("Some coding experience")}</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("Prize money and awards")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
{/* end col 2 */}
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<Link className="btn btn-primary btn-arrow" to="/community/events">
{translate("See Upcoming Events")}
</Link>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<Link className="btn btn-primary btn-arrow" to="/community/events">
{translate("See Upcoming Events")}
</Link>
</div>
</div>
</section>
{/* Eligibility */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-4">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 p-lg-3">
<div className="d-flex flex-column-reverse py-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Grants")}</h3>
<h6 className="eyebrow mb-3">
{translate("Fund Your Project")}
</h6>
</section>
{/* Eligibility */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center mr-lg-4">
<div className="order-1 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0 p-lg-3">
<div className="d-flex flex-column-reverse py-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Grants")}</h3>
<h6 className="eyebrow mb-3">
{translate("Fund Your Project")}
</h6>
</div>
<p className="py-lg-3 mb-2 longform" style={{ maxWidth: 520 }}>
{translate(
"Developer grants for projects that contribute to the growing XRP Ledger community."
)}
</p>
<div className="mt-4 pt-3" style={{ maxWidth: 520 }}>
<span className="h6" style={{ fontSize: "1rem" }}>
{translate("Past awardees include:")}
</span>
<div className="mb-4 py-3" id="xrplGrantsDark" />
</div>
<div className="d-none d-lg-block py-lg-3">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://xrplgrants.org/"
>
{translate("Visit XRPL Grants")}
</a>
</div>
</div>
<p className="py-lg-3 mb-2 longform" style={{ maxWidth: 520 }}>
{translate(
"Developer grants for projects that contribute to the growing XRP Ledger community."
)}
</p>
<div className="mt-4 pt-3" style={{ maxWidth: 520 }}>
<span className="h6" style={{ fontSize: "1rem" }}>
{translate("Past awardees include:")}
</span>
<div className="mb-4 py-3" id="xrplGrantsDark" />
<div className="order-2 col-lg-6 px-0 pl-lg-3">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Software developers, teams, and start-ups building directly on the XRP Ledger"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Coding experience")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Github repository")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Project narrative/description")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("At least one developer on the core team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Budget and milestones")}
</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>
{translate("XRPL intermediate to advanced developers")}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("$10,000 - $200,000")}</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Coding experience")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Github repository")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Project narrative/description")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("At least one developer on the core team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Budget and milestones")}
</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("$10,000 - $200,000")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="d-none d-lg-block py-lg-3">
<div className="d-lg-none order-3 mt-4 pt-3">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
@@ -170,202 +283,52 @@ export default function Funding() {
</a>
</div>
</div>
<div className="order-2 col-lg-6 px-0 pl-lg-3">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Software developers, teams, and start-ups building directly on the XRP Ledger"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Coding experience")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Github repository")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Project narrative/description")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("At least one developer on the core team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Budget and milestones")}
</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>
{translate("XRPL intermediate to advanced developers")}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("$10,000 - $200,000")}</p>
</div>
</div>
</section>
{/* Accelerator */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div
className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0"
style={{ maxWidth: 520 }}
>
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Accelerator")}</h3>
<h6 className="eyebrow mb-3">
{translate("Advance your project")}
</h6>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<img alt="book" id="funding-02" />
<p className="p-lg-3 mb-2 longform">
{translate(
"12-week program for entrepreneurs building on the XRP Ledger to scale their projects into thriving businesses."
)}
</p>
<div className="d-none d-lg-block p-lg-3">
<a
className="btn btn-primary btn-arrow"
href="https://xrplaccelerator.org/"
>
{translate("View XRPL Accelerator")}
</a>
</div>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Coding experience")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Github repository")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Project narrative/description")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("At least one developer on the core team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Budget and milestones")}
{translate(
"Start-ups building scalable products on XRPL that can capture a large market opportunity"
)}
</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>{translate("$10,000 - $200,000")}</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://xrplgrants.org/"
>
{translate("Visit XRPL Grants")}
</a>
</div>
</div>
</section>
{/* Accelerator */}
<section className="container-new py-26">
{/* flex. Col for mobile. Row for large. on large align content to the center */}
<div className="d-flex flex-column flex-lg-row align-items-lg-center">
<div
className="order-1 order-lg-2 mb-4 pb-3 mb-lg-0 pb-lg-0 col-lg-6 px-0"
style={{ maxWidth: 520 }}
>
<div className="d-flex flex-column-reverse p-lg-3">
<h3 className="h4 h2-sm">{translate("XRPL Accelerator")}</h3>
<h6 className="eyebrow mb-3">
{translate("Advance your project")}
</h6>
</div>
<p className="p-lg-3 mb-2 longform">
{translate(
"12-week program for entrepreneurs building on the XRP Ledger to scale their projects into thriving businesses."
)}
</p>
<div className="d-none d-lg-block p-lg-3">
<a
className="btn btn-primary btn-arrow"
href="https://xrplaccelerator.org/"
>
{translate("View XRPL Accelerator")}
</a>
</div>
</div>
<div className="order-2 order-lg-1 col-lg-6 px-0">
<div className="row align-items-center m-0 funding-list">
{/* funding list */}
<div className="col-12 col-lg-6 p-0">
<div className="px-lg-3 pb-3">
<img alt="user" id="funding-01" />
<div className="pt-3">
<h6 className="mb-3">{translate("Best for")}</h6>
<p>
{translate(
"Start-ups building scalable products on XRPL that can capture a large market opportunity"
)}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Strong founding team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Bold, ambitious vision")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Ideally an MVP and monetization strategy")}
</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("XRPL advanced developers")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Business acumen")}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>
{translate(
"$50,000 (grant) + pitch for venture funding"
)}
</p>
</div>
</div>
</div>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
@@ -377,38 +340,92 @@ export default function Funding() {
{translate("Bold, ambitious vision")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Ideally an MVP and monetization strategy")}
</p>
</div>
</div>
<div className="px-lg-3 pb-3 pt-lg-5">
<img alt="arrow" id="funding-03" />
<div className="pt-3">
<h6 className="mb-3">{translate("Level")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("XRPL advanced developers")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Business acumen")}
</p>
</div>
</div>
{/* Hide on large */}
<div className="px-lg-3 pb-3 d-lg-none">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>
{translate(
"Ideally an MVP and monetization strategy"
"$50,000 (grant) + pitch for venture funding"
)}
</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>
{translate(
"$50,000 (grant) + pitch for venture funding"
)}
</p>
{/* end col 1 */}
{/* Show on large */}
<div className="col-12 col-lg-6 p-0 d-none d-lg-block">
<div className="px-lg-3 pb-3 pt-5 mt-5">
<div className="pt-1 mt-3">
<img alt="book" id="funding-02" />
<div className="pt-3">
<h6 className="mb-3">{translate("Required")}</h6>
<p>
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Strong founding team")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate("Bold, ambitious vision")}
<br />
<span style={{ color: "#7919FF" }}></span>{" "}
{translate(
"Ideally an MVP and monetization strategy"
)}
</p>
</div>
</div>
</div>
<div className="px-lg-3 pb-3 pt-5">
<img alt="dollar sign" id="funding-04" />
<div className="pt-3">
<h6 className="mb-3">{translate("Funding Levels")}</h6>
<p>
{translate(
"$50,000 (grant) + pitch for venture funding"
)}
</p>
</div>
</div>
</div>
{/* end col 2 */}
</div>
{/* end col 2 */}
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<a
className="btn btn-primary btn-arrow"
href="https://xrplaccelerator.org/"
>
{translate("View XRPL Accelerator")}
</a>
</div>
</div>
<div className="d-lg-none order-3 mt-4 pt-3">
<a
className="btn btn-primary btn-arrow"
href="https://xrplaccelerator.org/"
>
{translate("View XRPL Accelerator")}
</a>
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="orange waves"
src={require("../static/img/backgrounds/funding-orange.svg")}
id="funding-orange"
/>
</div>
</section>
</div>
</div>
);
}

View File

@@ -1430,342 +1430,311 @@ export default function Events() {
return (
<div className="landing page-events">
<section className="text-center py-26">
<div className="mx-auto text-center col-lg-5">
<div className="d-flex flex-column-reverse">
<h1 className="mb-0">
{translate("Find the XRPL Community Around the World")}
</h1>
<h6 className="mb-3 eyebrow">{translate("Events")}</h6>
</div>
<div>
<div className="position-relative d-none-sm">
<img
alt="orange waves"
src={require("../static/img/backgrounds/events-orange.svg")}
id="events-orange"
/>
</div>
</section>
<section className="container-new py-26">
<div className="event-hero card-grid card-grid-2xN">
<div className="pe-2 col">
<img
alt="xrp ledger events hero"
src={require("../static/img/events/xrp-community-night.png")}
className="w-100"
/>
</div>
<div className="pt-5 pe-2 col">
<section className="text-center py-26">
<div className="mx-auto text-center col-lg-5">
<div className="d-flex flex-column-reverse">
<h2 className="mb-8 h4 h2-sm">
{translate("XRP Community Night NYC")}
</h2>
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
<h1 className="mb-0">
{translate("Find the XRPL Community Around the World")}
</h1>
<h6 className="mb-3 eyebrow">{translate("Events")}</h6>
</div>
<p className="mb-4">
</div>
</section>
<section className="container-new py-26">
<div className="event-hero card-grid card-grid-2xN">
<div className="pr-2 col">
<img
alt="xrp ledger events hero"
src={require("../static/img/events/xrpl-hero.png")}
className="w-100"
/>
</div>
<div className="pt-5 pr-2 col">
<div className="d-flex flex-column-reverse">
<h2 className="mb-8 h4 h2-sm">
{translate("XRP Community Night Denver")}
</h2>
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
</div>
<p className="mb-4">
{translate(
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP."
)}
</p>
<div className=" my-3 event-small-gray">
{translate("Location: Denver, CO")}
</div>
<div className="py-2 my-3 event-small-gray">
{translate("February 18, 2026")}
</div>
<div className="d-lg-block">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://luma.com/chz145tf?utm_source=xprlorg"
>
{translate("Register Now")}
</a>
</div>
</div>
</div>
</section>
{/* Upcoming Events */}
<section className="container-new py-26" id="upcoming-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">
<h3 className="h4 h2-sm">
{translate(
"Join the XRP community in NYC—meet builders, users, and projects innovating on the XRP Ledger."
"Check out meetups, hackathons, and other events hosted by the XRPL Community"
)}
</p>
<div className=" my-3 event-small-gray">
{translate("Location: New York, NY")}
</h3>
<h6 className="mb-3 eyebrow">{translate("Upcoming Events")}</h6>
</div>
<div className="filter row col-12 mt-lg-5 d-flex flex-column">
<h6 className="mb-3">{translate("Filter By:")}</h6>
<div>
<div className="form-check form-check-inline">
<input
defaultValue="conference"
id="conference-upcoming"
name="conference-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.conference}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="conference-upcoming">
{translate("Conference")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="meetup"
id="meetup-upcoming"
name="meetup-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.meetup}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="meetup-upcoming">{translate("Meetups")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="hackathon"
id="hackathon-upcoming"
name="hackathon-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.hackathon}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="hackathon-upcoming">
{translate("Hackathons")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="ama"
id="ama-upcoming"
name="ama-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.ama}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="ama-upcoming">{translate("AMAs")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="cc"
id="cc-upcoming"
name="cc-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.cc}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="cc-upcoming">
{translate("Community Calls")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="zone"
id="zone-upcoming"
name="zone-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.zone}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="zone-upcoming">{translate("XRPL Zone")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="info"
id="info-upcoming"
name="info-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters["info"]}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="info-upcoming">
{translate("Info Session")}
</label>
</div>
</div>
<div className="py-2 my-3 event-small-gray">
{translate("November 5, 2025")}
</div>
<div className="d-lg-block">
</div>
{/* # Available Types - conference, hackathon, ama, cc, zone, meetup, info */}
<div className="mt-2 row row-cols-1 row-cols-lg-3 card-deck">
{filteredUpcoming.map((event, i) => (
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://lu.ma/g5uja58m?utm_source=xrpleventspage"
>
{translate("Register Now")}
</a>
</div>
</div>
</div>
</section>
<section className="container-new py-26">
<div className="event-hero card-grid card-grid-2xN">
<div className="pr-2 col">
<img
alt="xrp ledger events hero"
src={require("../static/img/events/xrpl-hero.png")}
className="w-100"
/>
</div>
<div className="pt-5 pr-2 col">
<div className="d-flex flex-column-reverse">
<h2 className="mb-8 h4 h2-sm">
{translate("XRP Community Night Denver")}
</h2>
<h6 className="mb-3 eyebrow">{translate("Save the Date")}</h6>
</div>
<p className="mb-4">
{translate(
"Attending ETHDenver? Join us for an evening with the XRP community in Denver. Connect with the users, builders and projects innovating with and utilizing XRP."
)}
</p>
<div className=" my-3 event-small-gray">
{translate("Location: Denver, CO")}
</div>
<div className="py-2 my-3 event-small-gray">
{translate("February 18, 2026")}
</div>
<div className="d-lg-block">
<a
className="btn btn-primary btn-arrow-out"
target="_blank"
href="https://luma.com/chz145tf?utm_source=xprlorg"
>
{translate("Register Now")}
</a>
</div>
</div>
</div>
</section>
{/* Upcoming Events */}
<section className="container-new py-26" id="upcoming-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">
<h3 className="h4 h2-sm">
{translate(
"Check out meetups, hackathons, and other events hosted by the XRPL Community"
)}
</h3>
<h6 className="mb-3 eyebrow">{translate("Upcoming Events")}</h6>
</div>
<div className="filter row col-12 mt-lg-5 d-flex flex-column">
<h6 className="mb-3">{translate("Filter By:")}</h6>
<div>
<div className="form-check form-check-inline">
<input
defaultValue="conference"
id="conference-upcoming"
name="conference-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.conference}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="conference-upcoming">
{translate("Conference")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="meetup"
id="meetup-upcoming"
name="meetup-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.meetup}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="meetup-upcoming">{translate("Meetups")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="hackathon"
id="hackathon-upcoming"
name="hackathon-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.hackathon}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="hackathon-upcoming">
{translate("Hackathons")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="ama"
id="ama-upcoming"
name="ama-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.ama}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="ama-upcoming">{translate("AMAs")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="cc"
id="cc-upcoming"
name="cc-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.cc}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="cc-upcoming">
{translate("Community Calls")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="zone"
id="zone-upcoming"
name="zone-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters.zone}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="zone-upcoming">{translate("XRPL Zone")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="info"
id="info-upcoming"
name="info-upcoming"
type="checkbox"
className="events-filter"
checked={upcomingFilters["info"]}
onChange={handleUpcomingFilterChange}
/>
<label htmlFor="info-upcoming">
{translate("Info Session")}
</label>
</div>
</div>
</div>
{/* # Available Types - conference, hackathon, ama, cc, zone, meetup, info */}
<div className="row row-cols-1 row-cols-lg-3 g-4 mt-2">
{filteredUpcoming.map((event, i) => (
<div key={event.name + i} className="col">
<a
className={`event-card ${event.type} h-100`}
key={event.name + i}
className={`event-card ${event.type}`}
href={event.link}
style={{}}
target="_blank"
>
<div
className="event-card-header"
style={{
background: `url(${event.image}) no-repeat`,
}}
>
<div className="event-card-title">
{translate(event.name)}
<div
className="event-card-header"
style={{
background: `url(${event.image}) no-repeat`,
}}
>
<div className="event-card-title">
{translate(event.name)}
</div>
</div>
</div>
<div className="event-card-body">
<p>{translate(event.description)}</p>
</div>
<div className="mt-lg-auto event-card-footer d-flex flex-column">
<span className="mb-2 d-flex icon icon-location">
{event.location}
</span>
<span className="d-flex icon icon-date">{event.date}</span>
</div>
</a>
<div className="event-card-body">
<p>{translate(event.description)}</p>
</div>
<div className="mt-lg-auto event-card-footer d-flex flex-column">
<span className="mb-2 d-flex icon icon-location">
{event.location}
</span>
<span className="d-flex icon icon-date">{event.date}</span>
</div>
</a>
))}
</div>
))}
</div>
</section>
{/* Past Events */}
<section className="container-new pt-26" id="past-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">
<h3 className="h4 h2-sm">
{translate("Explore past community-hosted events")}
</h3>
<h6 className="mb-3 eyebrow">{translate("Past Events")}</h6>
</div>
<div className="filter row col-12 mt-lg-5 d-flex flex-column">
<h6 className="mb-3">{translate("Filter By:")}</h6>
<div>
<div className="form-check form-check-inline">
<input
defaultValue="conference"
id="conference-past"
name="conference-past"
type="checkbox"
className="events-filter"
checked={pastFilters.conference}
onChange={handlePastFilterChange}
/>
<label htmlFor="conference-past">
{translate("Conference")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="meetup"
id="meetup-past"
name="meetup-past"
type="checkbox"
className="events-filter"
checked={pastFilters.meetup}
onChange={handlePastFilterChange}
/>
<label htmlFor="meetup-past">{translate("Meetups")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="hackathon"
id="hackathon-past"
name="hackathon-past"
type="checkbox"
className="events-filter"
checked={pastFilters.hackathon}
onChange={handlePastFilterChange}
/>
<label htmlFor="hackathon-past">
{translate("Hackathons")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="ama"
id="ama-past"
name="ama-past"
type="checkbox"
className="events-filter"
checked={pastFilters.ama}
onChange={handlePastFilterChange}
/>
<label htmlFor="ama-past">{translate("AMAs")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="cc"
id="cc-past"
name="cc-past"
type="checkbox"
className="events-filter"
checked={pastFilters.cc}
onChange={handlePastFilterChange}
/>
<label htmlFor="cc-past">{translate("Community Calls")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="zone"
id="zone-past"
name="zone-past"
type="checkbox"
className="events-filter"
checked={pastFilters.zone}
onChange={handlePastFilterChange}
/>
<label htmlFor="zone-past">{translate("XRPL Zone")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="info"
id="info-past"
name="info-past"
type="checkbox"
className="events-filter"
checked={pastFilters["info"]}
onChange={handlePastFilterChange}
/>
<label htmlFor="info-past">
{translate("Info Session")}
</label>
</section>
{/* Past Events */}
<section className="container-new pt-26" id="past-events">
<div className="p-0 pb-2 mb-4 d-flex flex-column-reverse col-lg-6 pr-lg-5">
<h3 className="h4 h2-sm">
{translate("Explore past community-hosted events")}
</h3>
<h6 className="mb-3 eyebrow">{translate("Past Events")}</h6>
</div>
<div className="filter row col-12 mt-lg-5 d-flex flex-column">
<h6 className="mb-3">{translate("Filter By:")}</h6>
<div>
<div className="form-check form-check-inline">
<input
defaultValue="conference"
id="conference-past"
name="conference-past"
type="checkbox"
className="events-filter"
checked={pastFilters.conference}
onChange={handlePastFilterChange}
/>
<label htmlFor="conference-past">
{translate("Conference")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="meetup"
id="meetup-past"
name="meetup-past"
type="checkbox"
className="events-filter"
checked={pastFilters.meetup}
onChange={handlePastFilterChange}
/>
<label htmlFor="meetup-past">{translate("Meetups")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="hackathon"
id="hackathon-past"
name="hackathon-past"
type="checkbox"
className="events-filter"
checked={pastFilters.hackathon}
onChange={handlePastFilterChange}
/>
<label htmlFor="hackathon-past">
{translate("Hackathons")}
</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="ama"
id="ama-past"
name="ama-past"
type="checkbox"
className="events-filter"
checked={pastFilters.ama}
onChange={handlePastFilterChange}
/>
<label htmlFor="ama-past">{translate("AMAs")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="cc"
id="cc-past"
name="cc-past"
type="checkbox"
className="events-filter"
checked={pastFilters.cc}
onChange={handlePastFilterChange}
/>
<label htmlFor="cc-past">{translate("Community Calls")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="zone"
id="zone-past"
name="zone-past"
type="checkbox"
className="events-filter"
checked={pastFilters.zone}
onChange={handlePastFilterChange}
/>
<label htmlFor="zone-past">{translate("XRPL Zone")}</label>
</div>
<div className="form-check form-check-inline">
<input
defaultValue="info"
id="info-past"
name="info-past"
type="checkbox"
className="events-filter"
checked={pastFilters["info"]}
onChange={handlePastFilterChange}
/>
<label htmlFor="info-past">
{translate("Info Session")}
</label>
</div>
</div>
</div>
</div>
<div className="row row-cols-1 row-cols-lg-3 g-4 mt-2 mb-0">
{filteredPast.map((event, i) => (
<div key={event.name + i} className="col">
<div className="mt-2 mb-0 row row-cols-1 row-cols-lg-3 card-deck ">
{filteredPast.map((event, i) => (
<a
className={`event-card ${event.type} h-100`}
key={event.name + i}
className="event-card {event.type}"
href={event.link}
target="_blank"
>
@@ -1786,13 +1755,13 @@ export default function Events() {
<span className="mb-2 d-flex icon icon-location">
{event.location}
</span>
<span className="d-flex icon icon-date">{event.date}</span>
</div>
</a>
</div>
))}
</div>
</section>
<span className="d-flex icon icon-date">{event.date}</span>
</div>
</a>
))}
</div>
</section>
</div>
</div>
);
}

View File

@@ -702,7 +702,7 @@ const CommunityPage: React.FC = () => {
/>
</div>
<div className="mx-auto text-start col-lg-6 text-md-center hero-title">
<div className="mx-auto text-left col-lg-6 text-md-center hero-title">
<div className="d-flex flex-column-reverse align-items-center sm-align-items-start">
<img
src={require("../static/img/icons/arrow-down.svg")}
@@ -954,6 +954,7 @@ const CommunityPage: React.FC = () => {
{/* Bottom Cards Section 2 cards */}
<section className="bottom-cards-section bug-bounty">
<div className="com-card ripplex-bug-bounty">
<img className="top-right-img bug-bounty-card-bg" alt="Top Right Image" />
<div className="card-content">
<h6 className="card-title">
{translate("RippleX Bug Bounty Program")}
@@ -993,6 +994,7 @@ const CommunityPage: React.FC = () => {
</div>
</div>
<div className="com-card">
<img className="bottom-right-img bug-bounty-card-bg-2" alt="Bottom Right Image" />
<div className="card-content">
<h6 className="card-title">{translate("Report a Scam")}</h6>
<h6 className="card-subtitle pr-bt28">
@@ -1020,6 +1022,7 @@ const CommunityPage: React.FC = () => {
{/* Bottom Cards Section */}
<section className="bottom-cards-section">
<div className="com-card">
<img className="top-left-img" alt="Top Left Image" />
<div className="card-content">
<h6 className="card-title">
{translate("Contribute to Consensus")}
@@ -1065,6 +1068,7 @@ const CommunityPage: React.FC = () => {
</div>
</div>
<div className="com-card">
<img className="bottom-right-img" alt="Bottom Right Image" />
<div className="card-content">
<h6 className="card-title">{translate("XRPL Careers")}</h6>
<h6 className="card-subtitle pr-bt16">
@@ -1089,6 +1093,7 @@ const CommunityPage: React.FC = () => {
</div>
</div>
<div className="com-card">
<img className="top-right-img" alt="Top Right Image" />
<div className="card-content">
<h6 className="card-title">
{translate("Contribute to XRPL.org")}

View File

@@ -10,6 +10,8 @@ status: not_enabled
XRPL Batch Transactions let you package multiple [transactions](/docs/concepts/transactions) together and execute them as a single unit. It eliminates the risk of partial completion and unexpected outcomes, giving you a more reliable and predictable experience for complex operations. Up to eight transactions can be submitted in a single batch.
{% amendment-disclaimer name="Batch" /%}
## XRPL Batch Use Cases
Some potential uses for `Batch` include the following.

View File

@@ -1,470 +1,426 @@
import * as React from 'react';
import HeaderHeroPrimaryMedia from 'shared/sections/HeaderHeroPrimaryMedia/HeaderHeroPrimaryMedia';
import { CarouselCardList } from 'shared/sections/CarouselCardList/CarouselCardList';
import { CardsTextGrid } from 'shared/sections/CardsTextGrid/CardsTextGrid';
import FeaturedVideoHero from 'shared/sections/FeaturedVideoHero/FeaturedVideoHero';
import { LinkSmallGrid } from 'shared/sections/LinkSmallGrid/LinkSmallGrid';
import { LinkTextDirectory } from 'shared/sections/LinkTextDirectory/LinkTextDirectory';
import { FeatureTwoColumn } from 'shared/sections/FeatureTwoColumn/FeatureTwoColumn';
import { SmallTilesSection } from 'shared/sections/SmallTilesSection/SmallTilesSection';
import { CardsIconGrid } from 'shared/sections/CardsIconGrid/CardsIconGrid';
import { StandardCardGroupSection } from 'shared/sections/StandardCardGroupSection/StandardCardGroupSection';
import { useThemeHooks } from '@redocly/theme/core/hooks';
import { NavList } from "shared/components/nav-list";
import { Link } from "@redocly/theme/components/Link/Link";
export const frontmatter = {
seo: {
title: 'XRP Ledger Documentation & Developer Resources',
description:
'Explore XRP Ledger documentation and other blockchain developer resources needed to start building and integrating with the ledger.',
},
description: "Explore XRP Ledger documentation and other blockchain developer resources needed to start building and integrating with the ledger.",
}
};
export default function Docs() {
const recommendedPages = [
{
description: 'Public API Methods',
link: '/docs/references/http-websocket-apis/public-api-methods/',
},
{
description: 'Run a Validator',
link: '/docs/infrastructure/configuration/server-modes/run-rippled-as-a-validator/',
},
{
description: 'Reserves',
link: '/docs/concepts/accounts/reserves/',
},
{
description: 'Transaction Types',
link: '/docs/references/protocol/transactions/types/',
}
];
const useCases = [
{
title: 'On-Chain Finance',
id: 'on-chain-finance-use-cases',
imgClass: 'wallet-illustration',
subItems: [
{
description: 'Algorithmic Trading',
link: '/docs/use-cases/defi/algorithmic-trading/',
},
{
description: 'List XRP as an Exchange',
link: '/docs/use-cases/defi/list-xrp-as-an-exchange/',
},
{
description: 'Payment Types',
link: '/docs/concepts/payment-types/',
},
],
},
{
title: 'Tokens',
id: 'token-use-cases',
imgClass: 'token-illustration',
subItems: [
{
description: 'Stablecoin Issuer',
link: '/docs/use-cases/tokenization/stablecoin-issuer/',
},
{
description: 'NFT Marketplace',
link: '/docs/use-cases/tokenization/nft-mkt-overview/',
},
{
description: 'Digital Artist',
link: '/docs/use-cases/tokenization/digital-artist/',
},
],
},
{
title: 'Payments',
id: 'payments-use-cases',
imgClass: 'connections-illustration',
subItems: [
{
description: 'Peer-to-Peer Payments',
link: '/docs/use-cases/payments/peer-to-peer-payments-uc/',
},
{
description: 'Cross-Currency Payments',
link: '/docs/concepts/payment-types/cross-currency-payments/',
},
{
description: 'Smart Contracts',
link: '/docs/use-cases/payments/smart-contracts-uc/',
},
],
},
];
const intermediateVideos = [
{
src: require('../static/img/backgrounds/docs-advanced-payment-features@2x.png'),
title: 'Advanced Payment Features',
url: 'https://www.youtube.com/embed/e2Iwsk37LMk?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-governance@2x.png'),
title: 'Governance and the Amendment Process',
url: 'https://www.youtube.com/embed/4GbRdanHoR4?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-sidechains@2x.png'),
title: 'Federated Sidechains',
url: 'https://www.youtube.com/embed/NhH4LM8NxgY?rel=0&amp;showinfo=0&amp;autoplay=1',
},
];
const getStartedVideos = [
{
src: require('../static/img/backgrounds/docs-intro-to-XRP-ledger@2x.png'),
title: 'Intro to XRP Ledger',
url: 'https://www.youtube.com/embed/sVTybJ3cNyo?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-accounts@2x.png'),
title: 'Accounts',
url: 'https://www.youtube.com/embed/eO8jE6PftX8?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-decentralized-exchange@2x.png'),
title: 'Decentralized Exchange',
url: 'https://www.youtube.com/embed/VWNrHBDfXvA?rel=0&amp;showinfo=0&amp;autoplay=1',
},
{
src: require('../static/img/backgrounds/docs-tokenization@2x.png'),
title: 'Tokenization',
url: 'https://www.youtube.com/embed/Oj4cWOiWf4A?rel=0&amp;showinfo=0&amp;autoplay=1',
},
];
const devTools = [
{
title: 'XRP Faucets',
link: '/resources/dev-tools/xrp-faucets',
description: 'Get credentials and test-XRP for XRP Ledger Testnet or Devnet.',
},
{
title: 'WebSocket Tool',
link: '/resources/dev-tools/websocket-api-tool',
description: 'Send sample requests and get responses from the rippled API.',
},
{
title: 'XRP Ledger Explorer',
link: 'https://livenet.xrpl.org',
description:
'View validations of new ledger versions in real-time, chart the location of servers in the XRP Ledger.',
},
{
title: 'Transaction Sender',
link: '/resources/dev-tools/tx-sender',
description:
'Test how your code handles various XRP Ledger transactions by sending them over the Testnet to the address.',
},
];
function UseCasesCard(props: {
useCase: {
id: string;
title: string;
imgClass: string;
subItems: { description: string; link: string }[];
};
}) {
const { useCase } = props;
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<div className="landing page-docs page-docs-index">
{/* 1. Hero */}
<HeaderHeroPrimaryMedia
headline="XRP Ledger (XRPL) Documentation"
subtitle="Explore XRPL documentation with our essential guide for developers and admins who want to start building and integrating with the XRP Ledger."
media={{ type: 'image', src: require('../static/img/bds-2026/HeroMedia.jpg'), alt: 'XRPL Documentation' }}
/>
{/* 2. Get Started Carousel */}
<CarouselCardList
variant="green"
buttonVariant="green"
heading="Get Started: XRPL Developer & Admin Resources"
description="Learn your way. Read docs, watch videos, or get hands-on with code samples. Explore different ways to learn on the XRP Ledger."
cards={[
{
icon: '',
title: 'Ready-to-Use Code Samples',
description:
'Run complete code snippets to understand XRPL integration in seconds.',
href: '/docs/tutorials/',
},
{
icon: '',
title: 'Launch Your First Project',
description:
'Explore funding and development opportunities for your project on the XRPL.',
href: '/resources/grant-funding/',
},
{
icon: '',
title: 'Step-by-Step Tutorials',
description:
'Follow guided walkthroughs to master XRPL fundamentals and industry best practices.',
href: '/docs/tutorials/',
},
]}
/>
{/* 3. Core Concepts Text Grid */}
<CardsTextGrid
heading="Dig Into Core Concepts & Tools"
cards={[
{
heading: 'XRPL Fundamentals',
description: (
<>
Discover the basics of the XRPL by learning about accounts,
transactions, and the ledger structure.{' '}<br/><br/>
<a href="/docs/concepts/">Read More</a>
</>
),
},
{
heading: 'Advanced XRPL Topics: Go Deeper',
description: (
<>
Implement real-world solutions by combining XRPL primitives for
lending, token issuance, DEX trading, and more.{' '}<br/><br/>
<a href="/docs/use-cases/">Read More</a>
</>
),
},
]}
/>
{/* 4. Featured Video — Advanced Payment Features */}
<FeaturedVideoHero
headline="Advanced Payment Features"
subtitle="Master sophisticated movement of value through features such as escrows, checks, or payment channels."
video={{
source: {
type: 'embed',
embedUrl: 'https://www.youtube.com/embed/e2Iwsk37LMk',
},
}}
links={[
{
label: 'Watch Now',
href: 'https://youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi',
},
]}
/>
{/* 5. Featured Video — Governance */}
<FeaturedVideoHero
headline="Governance and the Amendment Process"
subtitle="Understand how the decentralized network evolves through validator voting and how new features are activated."
video={{
source: {
type: 'embed',
embedUrl: 'https://www.youtube.com/embed/4GbRdanHoR4',
},
}}
links={[
{
label: 'Watch Now',
href: 'https://youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi',
},
]}
/>
{/* 6. Developer Reference Links */}
<LinkSmallGrid
variant="gray"
heading="Developers"
description="Master the Protocol: Essential References"
links={[
{
label: 'Transaction Types',
href: '/docs/references/protocol/transactions/types',
},
{
label: 'Account Methods',
href: '/docs/references/http-websocket-apis/public-api-methods/account-methods',
},
{
label: 'Ledger Entry Types',
href: '/docs/references/protocol/ledger-data/ledger-entry-types',
},
{
label: 'Transaction Methods',
href: '/docs/references/http-websocket-apis/public-api-methods/transaction-methods',
},
{
label: 'Basic Data Types',
href: '/docs/references/protocol/data-types/basic-data-types',
},
{
label: 'Path and Orderbook Methods',
href: '/docs/references/http-websocket-apis/public-api-methods/path-and-order-book-methods',
},
]}
/>
{/* 7. Server Admin Reference Links */}
<LinkSmallGrid
variant="gray"
heading="Server Admins"
description="Master the Protocol: Essential References"
links={[
{
label: 'Commandline Usage',
href: '/docs/infrastructure/commandline-usage',
},
{
label: 'Admin API Methods',
href: '/docs/references/http-websocket-apis/admin-api-methods',
},
]}
/>
{/* 8. Dev Tools Directory */}
<LinkTextDirectory
heading="Tools to Test & Deploy"
cards={[
{
heading: 'Get Test XRP (Faucets)',
description:
'Get credentials and test-XRP for XRP Ledger Testnet or Devnet.',
buttons: [
{ label: 'Access Here', href: '/resources/dev-tools/xrp-faucets' },
],
},
{
heading: 'Send Test Transactions',
description:
'Test how your code handles various XRP Ledger transactions by sending them over the Testnet to the address.',
buttons: [
{ label: 'Access Here', href: '/resources/dev-tools/tx-sender' },
],
},
{
heading: 'Explore WebSocket API',
description:
'Send sample requests and get responses from the rippled API.',
buttons: [
{
label: 'Access Here',
href: '/resources/dev-tools/websocket-api-tool',
},
],
},
{
heading: 'Monitor the XRP Ledger',
description:
'View validations of new ledger versions in real-time, chart the location of servers in the XRP Ledger.',
buttons: [
{ label: 'Access Here', href: 'https://livenet.xrpl.org/' },
],
},
]}
/>
{/* 9a. Use Cases — Payments */}
<FeatureTwoColumn
color="lilac"
arrange="left"
title="Payments"
description=""
media={{ src: require('../static/img/bds-2026/FeatureMedia-1.jpg'), alt: 'Payments' }}
links={[
{
label: 'Peer-to-Peer Payments',
href: '/docs/use-cases/payments/peer-to-peer-payments-uc',
},
{
label: 'Cross-Currency Payments',
href: '/docs/concepts/payment-types/cross-currency-payments',
},
{
label: 'Smart Contracts',
href: '/docs/use-cases/payments/smart-contracts-uc',
},
]}
/>
{/* 9b. Use Cases — Tokens */}
<FeatureTwoColumn
color="lilac"
arrange="right"
title="Tokens"
description=""
media={{ src: require('../static/img/bds-2026/FeatureMedia-2.jpg'), alt: 'Tokens' }}
links={[
{
label: 'Stablecoin Issuer',
href: '/docs/use-cases/tokenization/stablecoin-issuer',
},
{
label: 'NFT Marketplace',
href: '/docs/use-cases/tokenization/nft-mkt-overview',
},
{
label: 'Digital Artist',
href: '/docs/use-cases/tokenization/digital-artist',
},
]}
/>
{/* 9c. Use Cases — On-Chain Finance */}
<FeatureTwoColumn
color="lilac"
arrange="left"
title="On-Chain Finance"
description=""
media={{ src: require('../static/img/bds-2026/FeatureMedia-3.jpg'), alt: 'On-Chain Finance' }}
links={[
{
label: 'List XRP as an Exchange',
href: '/docs/use-cases/defi/list-xrp-as-an-exchange',
},
{
label: 'Trade with an Auction Slot',
href: '/docs/tutorials/javascript/amm/trade-with-auction-slot',
},
]}
/>
{/* 9d. Use Cases — Compliance Features */}
<FeatureTwoColumn
color="lilac"
arrange="right"
title="Compliance Features"
description=""
media={{ src: require('../static/img/bds-2026/FeatureMedia.jpg'), alt: 'Compliance Features' }}
links={[
{
label: 'Build a Credential Issuing Service',
href: '/docs/tutorials/javascript/build-apps/credential-issuing-service',
},
{
label: 'Create a Permissioned Domain',
href: '/docs/tutorials/javascript/compliance/create-permissioned-domains',
},
]}
/>
{/* 10. SDK Tiles */}
<SmallTilesSection
headline="Build with SDKs"
cardVariant="neutral"
cards={[
{
icon: require('../static/img/logos/javascript.svg'),
iconAlt: 'JavaScript',
label: 'Get Started with Javascript',
href: '/docs/tutorials/javascript',
},
{
icon: require('../static/img/logos/python.svg'),
iconAlt: 'Python',
label: 'Python',
href: '/docs/tutorials/python',
},
{
icon: require('../static/img/logos/java.svg'),
iconAlt: 'Java',
label: 'Java',
href: '/docs/tutorials/java/build-apps/get-started',
},
{
icon: require('../static/img/logos/golang.svg'),
iconAlt: 'Go',
label: 'Go',
href: '/docs/tutorials/go',
},
{
icon: '',
iconAlt: 'PHP',
label: 'PHP',
href: '/docs/tutorials/php',
},
]}
/>
{/* 11. Infrastructure Cards */}
<CardsIconGrid
heading="XRPL Infrastructure: Running a Server"
cards={[
{
icon: '',
iconAlt: 'Server',
heading: 'Install Your XRPL Server: Rippled & Clio',
description: (
<>
Take ownership of your connection to the blockchain with a core
server that can submit transactions, read balances, and store a
complete copy of the ledger data.{' '}
<a href="/docs/infrastructure/installation">Learn More</a>
</>
),
},
{
icon: '',
iconAlt: 'Network',
heading: 'Node Configuration',
description: (
<>
Customize your server configuration for your use case, including
data retention, network connectivity, and performance tuning.{' '}
<a href="/docs/infrastructure/configuration">Learn More</a>
</>
),
},
{
icon: '',
iconAlt: 'Tools',
heading: 'Troubleshooting Your Node',
description: (
<>
Diagnose and solve problems with your server to maximize uptime
and reliability.{' '}
<a href="/docs/infrastructure/troubleshooting">Learn More</a>
</>
),
},
]}
/>
{/* 12. Contribute Cards */}
<StandardCardGroupSection
headline="Get Involved: Contribute to the XRP Ledger"
description=""
variant="neutral"
cards={[
{
headline: 'Contribute to the Protocol',
callsToAction: [
{ children: 'Contribute Now', href: '/resources/contribute-code' },
],
children:
'Have you identified gaps, edge cases, or improvements through real-world use? Contribute code or proposals and help shape the future of XRPL at the protocol level.',
},
{
headline: 'Contribute to Docs',
callsToAction: [
{
children: 'Contribute Now',
href: '/resources/contribute-documentation',
},
],
children: (
<>
Contribute to{' '}
<a href="https://xrpl.org/" target="_blank" rel="noreferrer">
XRPL.org
</a>
, the go-to resource for all things XRP Ledger.
</>
),
},
{
headline: 'Contribute a Blog Post',
callsToAction: [
{
children: 'Contribute Now',
href: '/resources/contribute-blog',
},
],
children:
'Share how you solved a real-world problem using the XRP Ledger, including your architecture decisions, workflows, tradeoffs, and lessons learned. Contribute a blog post to help other developers build faster.',
},
]}
/>
{/* 13. Continuous Learning Cards */}
<StandardCardGroupSection
headline="Continuous Learning: Additional XRPL Resources"
description=""
variant="neutral"
cards={[
{
headline: 'XRPL Learning Portal',
callsToAction: [
{ children: 'Learn More', href: 'https://learn.xrpl.org/' },
],
children:
'From blockchain fundamentals to building on the XRPL, create your own learning journey and progress at your own pace.',
},
{
headline: 'Aquarium Residency Program',
callsToAction: [
{
children: 'Learn More',
href: 'https://www.xrpl-commons.org/residency',
},
],
children:
'A hands-on residency program helping teams build, test, and launch real-world solutions on the XRP Ledger.',
},
{
headline: 'Developer Community Forum',
callsToAction: [
{
children: 'Learn More',
href: 'https://discord.gg/sfX3ERAMjH',
},
],
children:
'Join the XRPL developer community on Discord! Ask questions, share tips, and collaborate with builders turning ideas into real-world projects.',
},
]}
/>
<div className="col">
<img className={'use-cases-img img-fluid mb-2 shadow ' + useCase.imgClass} alt={useCase.title} id={useCase.id} />
<h5 className="mt-4">{translate(useCase.title)}</h5>
<NavList pages={useCase.subItems} />
</div>
);
}
function FlatCard(props: { href: string; title: string; description: string; linkText: string; imgClass }) {
const { title, description, linkText, href, imgClass } = props;
return (
<Link to={href} className="card flat-card float-up-on-hover">
<img className={'mb-2 ' + imgClass} alt={title} />
<h5 className="row">
<div className="nav-link">{title}</div>
</h5>
<p className="row faded-text flat-card-padding">{description}</p>
<div className="col align-button-on-bottom">
<div className="btn btn-primary btn-arrow" id={href + '-button'}>
{linkText}
</div>
</div>
</Link>
);
}
function VideoCard(props: { url: string; title: string; src: string }) {
const { url, title, src } = props;
return (
<div className="col float-up-on-hover">
<a href={url} id="playvideo" className="btn1" data-url={url}>
<img alt={title} className="get-started-img video-image" id={title} src={src} />
<h6 className="pt-3">{title}</h6>
</a>
</div>
);
}
function DevToolCard(props: { link: string; title: string; description: string }) {
const { link, title, description } = props;
return (
<Link to={link} className="col dev-tools-link">
<h6 className="btn-arrow">{title}</h6>
<p> {description}</p>
</Link>
);
}
function PrimaryButton(props: { href: string; text: string; isArrowUp: boolean }) {
const { href, text, isArrowUp } = props;
return (
<Link className={`btn btn-primary ${isArrowUp ? 'btn-arrow-out' : 'btn-arrow'}`} id={href + '-button'} to={href}>
{text}
</Link>
);
}
export default function Docs() {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<div className="landing page-docs page-docs-index landing-builtin-bg overflow-hidden styled-page">
<div>
<section className="text-center title-space">
<div className="col-lg-9 mx-auto text-center">
<div className="d-flex flex-column-reverse">
<h1>{translate('Documentation')}</h1>
<h6 className="eyebrow mb-3">{translate('XRP Ledger Developer Resources')}</h6>
</div>
</div>
</section>
<section className="container-new ">
<div className="nav card-grid flat-card-grid card-grid-3xN">
<div className="col">
<FlatCard
href="/docs/concepts/"
title={translate('Concepts')}
description={translate('Learn the "what" and the "why" behind fundamental aspects of the XRP Ledger.')}
linkText={translate('Read the Docs')}
imgClass="concepts-doc-illustration"
/>
</div>
<div className="col">
<FlatCard
href="/docs/tutorials/"
title={translate('Tutorials')}
description={translate('Get step-by-step guidance to perform common tasks with the XRP Ledger.')}
linkText={translate('View Tutorials')}
imgClass="tutorial-illustration"
/>
</div>
<div className="col">
<FlatCard
href="/docs/references/"
title={translate('References')}
description={translate(
'Look up reference documentation for the XRP Ledger protocol, API methods, and more.'
)}
linkText={translate('View References')}
imgClass="ref-book-illustration"
/>
</div>
</div>
</section>
<section className="container-new">
<h4 className="pb-4">{translate('Use Cases')}</h4>
<div className="card-grid card-grid-3xN use-cases">
{useCases.map(useCase => (
<UseCasesCard useCase={useCase} key={useCase.id} />
))}
</div>
</section>
<section className="container-new ">
<h4 className="pb-4">{translate('Getting Started')}</h4>
<div className="card-grid card-grid-2xN quickstart-card">
<div className="col">
<Link to="/docs/introduction/" className="card float-up-on-hover">
<h5 className="mt-7">{translate('Introduction to the XRP Ledger')}</h5>
<p className="mb-8 mt-4">{translate('An introduction to fundamental aspects of the XRP Ledger.')}</p>
<div className="dg-lg-block mb-3">
<div className="btn btn-primary btn-arrow get-started-button">{translate('Introduction')}</div>
</div>
<img alt="quick-start" id="quick-start-img" className="quickstart-image" />
</Link>
</div>
<div className="col">
<div className="card-grid card-grid-2xN video-grid">
{getStartedVideos.map(video => (
<VideoCard url={video.url} title={translate(video.title)} src={video.src} key={video.url} />
))}
</div>
<div className="align-button-on-bottom">
<PrimaryButton
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
text={translate('Watch Full Series')}
isArrowUp={true}
/>
</div>
</div>
</div>
</section>
<section className="container-new ">
<div className="d-flex flex-column-reverse col-sm-8 p-0">
<h3 className="h4 h2-sm">{translate('Interact with the XRP Ledger in a language of your choice')}</h3>
<h6 className="eyebrow mb-3">{translate('Explore SDKs')}</h6>
</div>
<div className="card-grid card-grid-2xN">
<div className="col">
<div className="card-grid langs-cards card-grid-2xN mt-10" id="langs-cards">
<div className="col langs">
<Link to="/docs/tutorials/javascript/">
<img alt="Javascript Logo" src={require('../static/img/logos/javascript.svg')} className="circled-logo" />
<h5 className="btn-arrow">{translate('Javascript')}</h5>
</Link>
</div>
<div className="col langs">
<Link to="/docs/tutorials/python/">
<img alt="Python Logo" src={require('../static/img/logos/python.svg')} className="circled-logo" />
<h5 className="btn-arrow">{translate('Python')}</h5>
</Link>
</div>
<div className="col langs">
<Link to="/docs/tutorials/java/build-apps/get-started/">
<img alt="Java Logo" src={require('../static/img/logos/java.svg')} className="circled-logo" />
<h5 className="btn-arrow">{translate('Java')}</h5>
</Link>
</div>
<div className="col langs">
<Link to="/docs/tutorials/go/">
<img
alt="Go Logo"
src={require("../static/img/logos/golang.svg")}
className="circled-logo"
/>
<h5 className="btn-arrow">{translate("GoLang")}</h5>
</Link>
</div>
</div>
</div>
<div className="col center-image">
<img className="img-fluid sdk-img" />
</div>
</div>
</section>
<section className="container-new ">
<h4 className="pb-4">{translate('Intermediate Learning Sources')}</h4>
<div className="card-grid card-grid-3xN">
{intermediateVideos.map(video => (
<VideoCard url={video.url} title={translate(video.title)} src={video.src} key={video.url} />
))}
</div>
</section>
<section className="container-new ">
<div className="card-grid card-grid-2xN">
<div className="col d-flex align-items-center justify-content-center">
<img className="dev-tools-img" />
</div>
<div className="col explore-links">
<div className="d-flex flex-column-reverse w-100">
<h4 className="mb-10">{translate('Explore, Test, Verify')}</h4>
<h6 className="mb-3">{translate('Explore Dev Tools')}</h6>
</div>
<p className="mb-20">
{translate(
'Use these web-based tools to assist during all stages of development, from getting your first payment to testing your implementation for best practices.'
)}
</p>
<div className="card-grid card-grid-2xN">
{devTools.map(card => (
<DevToolCard
link={card.link}
title={translate(card.title)}
description={translate(card.description)}
key={card.link}
/>
))}
</div>
<PrimaryButton href="/resources/dev-tools" text={translate('View All tools')} isArrowUp={false} />
</div>
</div>
</section>
<section className="container-new " id="docs-browse-by">
<div className="row card-grid card-grid-2xN">
<div className="col" id="popular-topics">
<h2 className="h4">{translate('Browse By Recommended Pages')}</h2>
<NavList pages={recommendedPages} />
</div>
<div className="col">
<div className="card cta-card p-8-sm p-10-until-sm br-8">
<img src={require('../static/img/backgrounds/cta-home-purple.svg')} className="d-none-sm cta cta-top-left" />
<img src={require('../static/img/backgrounds/cta-home-green.svg')} className="cta cta-bottom-right" />
<div className="z-index-1 position-relative">
<h2 className="h4 mb-8-sm mb-10-until-sm">{translate('Get Free Test XRP')}</h2>
<p className="mb-10">
{translate(
'Connect to the XRP Ledger Testnet network to develop and test your apps built on the XRP Ledger, without risking real money or impacting production XRP Ledger users.'
)}
</p>
<Link className="btn btn-primary btn-arrow" to="/resources/dev-tools/xrp-faucets/">
{translate('Generate Testnet Credentials')}
</Link>
</div>
</div>
</div>
</div>
</section>
{/* full docs index isn't ported to Redocly
<section className="container-new">
<a href="/docs/full-index" className="btn-arrow arrow-purple documentation-index mr-auto">
{translate('See full documentation index')}
</a>
</section>
*/}
</div>
</div>
);
}

View File

@@ -40,11 +40,12 @@ Only the owner of the associated `LoanBroker` entry can initiate this transactio
In addition to the [common fields][], {% code-page-name /%} transactions use the following fields:
| Field Name | JSON Type | Internal Type | Required? | Description |
|:-------------- |:--------------------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to withdraw from. |
| `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to withdraw. |
| `Destination` | String | AccountID | No | An account to receive the assets. |
| Field Name | JSON Type | Internal Type | Required? | Description |
|:---------------- |:--------------------|:--------------|:----------|:------------|
| `LoanBrokerID` | String | Hash256 | Yes | The ID of the `LoanBroker` ledger entry to withdraw from. |
| `Amount` | [Currency Amount][] | Amount | Yes | The amount of first-loss capital to withdraw. |
| `Destination` | String | AccountID | No | An account to receive the assets. |
| `DestinationTag` | Number | UInt32 | No | An arbitrary tag identifying the reason for the transaction to the destination. |
## Error Cases

View File

@@ -20,7 +20,7 @@ A loan payment has four types, depending on the amount and timing of the payment
- **Early Full Payment**: A payment that covers the outstanding principal of the loan. A `CloseInterestRate` is charged on the outstanding principal.
- **Overpayment**: A payment that exceeds the required minimum payment amount.
To see how loan payment transactions are calculated, see [transaction pseudo-code](https://github.com/Tapanito/XRPL-Standards/tree/xls-66-lending-protocol/XLS-0066-lending-protocol#a-3-loanpay-implementation-reference).
To see how loan payment transactions are calculated, see [LoanPay Implementation Reference](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0066-lending-protocol#a-3-loanpay-implementation-reference).
## Example {% $frontmatter.seo.title %} JSON

View File

@@ -1106,7 +1106,7 @@ const { sendXrp } = require('./library/7_helpers')
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title subhead-sm-r" id="send-xrp-modal-label">Send XRP</h1>
<h1 class="modal-title fs-5" id="send-xrp-modal-label">Send XRP</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">

View File

@@ -221,7 +221,7 @@ const PaymentsPage: React.FC = () => {
</div>
<div className="text-content">
<h6 className="eyebrow mb-3 subhead-sm-r">
<h6 className="eyebrow mb-3 text-large">
{translate("Payments")}
</h6>
<h2 className="h4 h2-sm mb-10">

View File

@@ -199,7 +199,7 @@ export default function Tokenization() {
"Work with a variety of tokens supported by the XRP Ledger."
)}
</h2>
<h6 className="eyebrow mb-3 subhead-sm-r">
<h6 className="eyebrow mb-3 text-large">
{translate("Tokenization")}
</h6>
</div>
@@ -220,7 +220,7 @@ export default function Tokenization() {
{translate("Quick Start")}
</Link>{" "}
<a
className="ms-4 video-external-link btn-none"
className="ml-4 video-external-link btn-none"
target="_blank"
href="https://www.youtube.com/playlist?list=PLJQ55Tj1hIVZtJ_JdTvSum2qMTsedWkNi"
>
@@ -273,7 +273,7 @@ export default function Tokenization() {
target="_blank"
href={article.url}
>
<div className="time h4 mb-8">
<div className="time h4 normal mb-8">
{translate(article.time)}
</div>
<div className="h5 mb-4">{translate(article.title)}</div>

View File

@@ -399,6 +399,13 @@ function TokenHeroSection() {
const { translate } = useTranslate();
return (
<section className="token-hero-section">
<div className="position-relative d-none-sm">
<img
alt="orange waves"
src={require("./../../../static/img/backgrounds/events-orange.svg")}
id="events-orange"
/>
</div>
<div className="token-title-container">
<h1 className="token-title">
{translate("Real-World Asset (RWA) Tokenization")}

View File

@@ -1,7 +1,6 @@
import { useThemeHooks } from '@redocly/theme/core/hooks';
import { Link } from '@redocly/theme/components/Link/Link';
import { BenefitsSection } from 'shared/components/benefits-section';
import { PageGrid, PageGridCol, PageGridRow } from 'shared/components/PageGrid/page-grid';
export const frontmatter = {
seo: {
@@ -118,7 +117,7 @@ export default function Index() {
</div>
<div className="col-lg-6 mx-auto text-center pl-0 pr-0">
<div className="d-flex flex-column-reverse">
<h1 className="display-lg mb-10">
<h1 className="mb-10">
{translate('home.hero.h1part1', 'The Blockchain')}
<br className="until-sm" />
{translate('home.hero.h1part2', 'Built for Business')}
@@ -130,8 +129,12 @@ export default function Index() {
</Link>
</div>
</section>
<PageGrid className="py-26">
<PageGrid.Col span={{ base: 12, lg: 6 }} offset={{ lg: 3 }}>
<div className="position-relative d-none-sm">
<img src={require('./static/img/backgrounds/home-purple.svg')} id="home-purple" loading="lazy" />
<img src={require('./static/img/backgrounds/home-green.svg')} id="home-green" loading="lazy" />
</div>
<section className="container-new py-26">
<div className="col-lg-6 offset-lg-3 pl-0-sm pr-0-sm p-8-sm p-10-until-sm">
<h2 className="h4 mb-8 h2-sm">{translate('The XRP Ledger: The Blockchain Built for Business')}</h2>
<h6 className="longform mb-10">
{translate(
@@ -143,8 +146,8 @@ export default function Index() {
'Proven reliable over more than a decade of error-free functioning, the XRPL offers streamlined development, low transaction costs, high performance, and sustainability. So you can build with confidenceand move your most critical projects forward.'
)}
</p>
</PageGrid.Col>
</PageGrid>
</div>
</section>
<BenefitsSection
eyebrow="Benefits"
title="Why developers choose the XRP Ledger"
@@ -191,6 +194,8 @@ export default function Index() {
</section>
<section className="container-new py-26">
<div className="col-lg-6 offset-lg-3 p-6-sm p-10-until-sm br-8 cta-card">
<img src={require('./static/img/backgrounds/cta-home-purple.svg')} className="d-none-sm cta cta-top-left" />
<img src={require('./static/img/backgrounds/cta-home-green.svg')} className="cta cta-bottom-right" />
<div className="z-index-1 position-relative">
<h2 className="h4 mb-8-sm mb-10-until-sm">{translate('Our Shared Vision for XRPLs Future')}</h2>
<p className="mb-10">
@@ -227,6 +232,7 @@ export default function Index() {
</section>
<section className="container-new py-26">
<div className="col-md-6 offset-md-3 p-8-sm p-10-until-sm br-8 cta-card">
<img alt="" src={require('./static/img/backgrounds/cta-home-magenta.svg')} className="cta cta-bottom-right" />
<div className="z-index-1 position-relative">
<div className="d-flex flex-column-reverse">
<h2 className="h4 mb-8-sm mb-10-until-sm">

2909
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,10 +5,8 @@
"type": "module",
"description": "The XRP Ledger Dev Portal is the authoritative source for XRP Ledger documentation, including the `rippled` server, client libraries, and other open-source XRP Ledger software.",
"scripts": {
"analyze-css": "node scripts/analyze-css.js",
"build-css": "sass --load-path styles/scss --load-path . styles/xrpl.scss ./static/css/devportal2024-v1.tmp.css && NODE_ENV=production postcss ./static/css/devportal2024-v1.tmp.css -o ./static/css/devportal2024-v1.css && rm -f ./static/css/devportal2024-v1.tmp.css",
"build-css:dev": "sass --load-path styles/scss --load-path . styles/xrpl.scss ./static/css/devportal2024-v1.tmp.css --source-map && postcss ./static/css/devportal2024-v1.tmp.css -o ./static/css/devportal2024-v1.css && rm -f ./static/css/devportal2024-v1.tmp.css",
"build-css:watch": "sass --watch --load-path styles/scss --load-path . styles/xrpl.scss:static/css/devportal2024-v1.css --source-map",
"build-css": "sass --load-path styles/scss styles/xrpl.scss ./static/css/devportal2024-v1.css --style compressed --no-source-map",
"build-css-watch": "sass --watch --load-path styles/scss styles/xrpl.scss ./static/css/devportal2024-v1.css --style compressed --no-source-map",
"start": "realm develop"
},
"keywords": [],
@@ -36,19 +34,11 @@
},
"overrides": {
"react": "^19.1.0",
"react-dom": "^19.1.0",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
"react-dom": "^19.1.0"
},
"devDependencies": {
"@fullhuman/postcss-purgecss": "^7.0.2",
"autoprefixer": "^10.4.21",
"bootstrap": "^5.3.3",
"cssnano": "^7.1.1",
"bootstrap": "^4.6.2",
"htmltojsx": "^0.3.0",
"postcss": "^8.5.6",
"postcss-cli": "^11.0.1",
"sass": "^1.93.2"
"sass": "1.26.10"
}
}

View File

@@ -1,161 +0,0 @@
/**
* PostCSS Configuration
*
* Processes compiled Sass output through:
* 1. PurgeCSS - Removes unused CSS selectors
* 2. Autoprefixer - Adds vendor prefixes for browser compatibility
* 3. cssnano - Minifies and optimizes CSS (production only)
*/
const purgecss = require('@fullhuman/postcss-purgecss').default;
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
const isProduction = process.env.NODE_ENV === 'production';
module.exports = {
plugins: [
// Only run PurgeCSS in production or when explicitly enabled
...(isProduction || process.env.PURGECSS === 'true'
? [
purgecss({
// Scan all content files for class names (TSX/TS/HTML = usage; SCSS = BEM modifier definitions)
content: [
'./**/*.tsx',
'./**/*.ts',
'./**/*.md',
'./**/*.yaml',
'./**/*.html',
'./**/*.scss',
'./static/js/**/*.js',
'./static/vendor/**/*.js',
// Ignore node_modules except for specific libraries that inject classes
'!./node_modules/**/*',
],
// Default extractor - looks for class names in content
defaultExtractor: content => {
// Match all words, including those with dashes and numbers
const broadMatches = content.match(/[^<>"'`\s]*[^<>"'`\s:]/g) || [];
// Match class names in className="..." or class="..."
const classMatches = content.match(/(?:class|className)=["']([^"']*)["']/g) || [];
const classes = classMatches.flatMap(match => {
const m = match.match(/["']([^"']*)["']/);
return m ? m[1].split(/\s+/) : [];
});
// Extract BEM modifier classes from SCSS (e.g. .bds-callout-media-banner--green)
// These are dynamically applied in TSX via template literals so PurgeCSS won't find them as literals
const scssBemMatches = content.match(/\.(bds-[a-z0-9-]+--[a-z0-9-]+)/g) || [];
const bemModifiers = [...new Set(scssBemMatches.map(m => m.replace(/^\./, '')))];
return [...new Set([...broadMatches, ...classes, ...bemModifiers])];
},
// Safelist - classes that should never be removed
safelist: {
// Standard safelist - dynamic state classes
standard: [
'html',
'body',
'light',
'dark',
'show',
'hide',
'active',
'disabled',
'open',
'collapsed',
'collapsing',
'lang-ja', // Japanese language class
/^lang-/, // All language classes
// Common Bootstrap utility patterns that should always be kept
/^container/, // All container classes
/^row$/, // Row class
/^col-/, // Column classes
/^bds-grid__col/, // PageGrid column classes (dynamic span values)
/^bds-grid__offset/, // PageGrid offset classes
// BDS BEM modifier classes - applied dynamically via template literals (e.g. bds-callout-media-banner--green)
// Required: PurgeCSS cannot find these in TSX/SCSS due to interpolation
/^bds-[a-z0-9-]+--/,
/^g-/, // Gap utilities
/^p-/, // Padding utilities
/^m-/, // Margin utilities
/^px-/, /^py-/, /^pt-/, /^pb-/, /^ps-/, /^pe-/, // Directional padding
/^mx-/, /^my-/, /^mt-/, /^mb-/, /^ms-/, /^me-/, // Directional margin
/^d-/, // Display utilities
/^flex-/, // Flexbox utilities
/^justify-/, // Justify content
/^align-/, // Align items
/^w-/, // Width utilities
/^h-/, // Height utilities
/^text-/, // Text utilities
/^bg-/, // Background utilities
/^border/, // Border utilities
/^rounded/, // Border radius
],
// Deep safelist - MINIMAL - only truly dynamic components
deep: [
// Bootstrap JS components (only if actually used with JS)
/dropdown-menu/,
/dropdown-item/,
/modal-backdrop/,
/fade/,
// Third-party libraries
/cm-/,
/CodeMirror/,
/lottie/,
],
// Greedy safelist - VERY MINIMAL
greedy: [
/data-theme/, // Theme switching
],
},
// Reject specific patterns - don't remove these even if not found
rejected: [],
// Variables - keep CSS custom properties
variables: true,
// Keyframes - keep animation keyframes
keyframes: true,
// Font-face rules
fontFace: true,
}),
]
: []),
// Autoprefixer - adds vendor prefixes
autoprefixer({
overrideBrowserslist: [
'>0.2%',
'not dead',
'not op_mini all',
'last 2 versions',
],
}),
// cssnano - minification (production only)
...(isProduction
? [
cssnano({
preset: [
'default',
{
discardComments: {
removeAll: true,
},
normalizeWhitespace: true,
colormin: true,
minifySelectors: true,
},
],
}),
]
: []),
],
};

View File

@@ -54,12 +54,16 @@ scripts:
head:
- src: https://cmp.osano.com/AzyjT6TIZMlgyLyy8/f11f7772-8ed5-4b73-bd17-c0814edcc440/osano.js
- src: ./static/js/xrpl-2.11.0.min.js
# - src: ./static/vendor/jquery-3.7.1.min.js
- src: ./static/vendor/jquery-3.7.1.min.js
- src: ./static/vendor/bootstrap.min.js
- src: ./static/js/osano.js
type: text/javascript
links:
- href: https://fonts.googleapis.com/css2?family=Noto+Sans:wght@300;400;500;600;700&family=Noto+Serif:wght@400;500;600;700&family=Noto+Sans+JP:wght@300;400;500;600;700&display=swap
- href: https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700&display=swap
rel: stylesheet
- href: https://fonts.googleapis.com/css?family=Source+Code+Pro:300,400,600,700&display=swap
rel: stylesheet
- href: https://fonts.googleapis.com/css?family=Space+Grotesk:300,400,600,700&display=swap
rel: stylesheet
- href: ./static/css/devportal2024-v1.css
rel: stylesheet

View File

@@ -40,17 +40,25 @@ export default function CodeSamples() {
{/* <a className="mt-12 btn btn-primary btn-arrow">Submit Code Samples</a> */}
</div>
</section>
<div className="position-relative d-none-sm">
<img
alt="orange waves"
src={require('../static/img/backgrounds/xrpl-overview-orange.svg')}
id="xrpl-overview-orange"
/>
</div>
<section className="container-new py-26">
<div className="d-flex flex-column col-sm-8 p-0">
<h3 className="h4 h2-sm">
{translate('Browse sample code for building common use cases on the XRP Ledger')}
</h3>
</div>
<div className="row gx-4 gy-5 mt-10 mb-20" id="code-samples-deck">
{codeSamples.map(card => (
<div key={card.href} className="col-12 col-lg-6 mb-4">
<div className="row col-12 card-deck mt-10" id="code-samples-deck">
<div className="row col-md-12 px-0" id="code_samples_list">
{codeSamples.map(card => (
<a
className={`card cardtest h-100 ${card.langs.join(' ')}`}
key={card.href}
className={`card cardtest col-12 col-lg-5 ${card.langs.join(' ')}`}
href={target.github_forkurl + `/tree/${target.github_branch}/${card.href}`.replace('/content','')}
>
<div className="card-header">
@@ -64,9 +72,10 @@ export default function CodeSamples() {
<h4 className="card-title h5">{card.title}</h4>
<p className="card-text">{card.description}</p>
</div>
<div className="card-footer">&nbsp;</div>
</a>
</div>
))}
))}
</div>
</div>
</section>
<section className="container-new py-26">
@@ -77,8 +86,8 @@ export default function CodeSamples() {
{translate('Help the XRPL community by submitting your own code samples')}
</h6>
</div>
<div className="row ps-4">
<div className=" col-lg-3 ps-4 ps-lg-0 pe-4 contribute dot contribute_1">
<div className="row pl-4">
<div className=" col-lg-3 pl-4 pl-lg-0 pr-4 contribute dot contribute_1">
<span className="dot" />
<h5 className="pb-4 pt-md-5">{translate('Fork and clone')}</h5>
<p className="pb-4">
@@ -89,7 +98,7 @@ export default function CodeSamples() {
{translate('resources.contribute.1.part3', '. Using git, clone the fork to your computer.')}
</p>
</div>
<div className=" col-lg-3 ps-4 ps-lg-0 pe-4 contribute dot contribute_2">
<div className=" col-lg-3 pl-4 pl-lg-0 pr-4 contribute dot contribute_2">
<span className="dot" />
<h5 className="pb-4 pt-md-5">{translate('Add to folder')}</h5>
<p className="pb-4">

View File

@@ -62,8 +62,8 @@ export function CurlButton ({selectedConnection, currentBody}: CurlButtonProps)
return <>
<button
className="btn btn-outline-secondary curl"
data-bs-toggle="modal"
data-bs-target="#wstool-1-curl"
data-toggle="modal"
data-target="#wstool-1-curl"
title={translate("cURL Syntax")}
onClick={() => setShowCurlModal(true)}
>

View File

@@ -62,8 +62,8 @@ export function PermalinkButton ({currentBody, selectedConnection}: PermaLinkBut
return <>
<button
className="btn btn-outline-secondary permalink"
data-bs-toggle="modal"
data-bs-target="#wstool-1-permalink"
data-toggle="modal"
data-target="#wstool-1-permalink"
title={translate("Permalink")}
onClick={() => setShowPermalinkModal(true)}
>

View File

@@ -146,8 +146,8 @@ export default function DevTools() {
<button
className="nav-link active dev-tools-tab"
id="explorers-tab"
data-bs-toggle="tab"
data-bs-target="#explorers"
data-toggle="tab"
data-target="#explorers"
role="tab"
aria-controls="explorers"
aria-selected="true"
@@ -159,8 +159,8 @@ export default function DevTools() {
<button
className="nav-link dev-tools-tab"
id="api-access-tab"
data-bs-toggle="tab"
data-bs-target="#api-access"
data-toggle="tab"
data-target="#api-access"
role="tab"
aria-controls="api-access"
aria-selected="false"
@@ -172,8 +172,8 @@ export default function DevTools() {
<button
className="nav-link dev-tools-tab"
id="other-tab"
data-bs-toggle="tab"
data-bs-target="#other"
data-toggle="tab"
data-target="#other"
role="tab"
aria-controls="other"
aria-selected="false"
@@ -278,6 +278,16 @@ export default function DevTools() {
</section>
<section className="container-new py-10 px-0">
<div className="col-lg-12 p-6-sm p-10-until-sm br-8 cta-card">
<img
alt="purple waves"
src={require("../../static/img/backgrounds/cta-home-purple.svg")}
className="d-none-sm cta cta-top-left"
/>
<img
alt="green waves"
src={require("../../static/img/backgrounds/cta-home-green.svg")}
className="cta cta-bottom-right"
/>
<div className="z-index-1 position-relative">
<h2 className="h4 mb-8-sm mb-10-until-sm">
{translate("Have an Idea For a Tool?")}

View File

@@ -251,7 +251,7 @@ export function WebsocketApiTool() {
className="btn-toolbar justify-content-between pt-4"
role="toolbar"
>
<div className="btn-group me-3" role="group">
<div className="btn-group mr-3" role="group">
<button
className="btn btn-outline-secondary send-request"
onClick={() => sendWebSocketMessage(currentBody)}
@@ -272,8 +272,8 @@ export function WebsocketApiTool() {
connected ? "btn-success" : "btn-outline-secondary"
} ${connectionError ?? "btn-danger"}`}
onClick={openConnectionModal}
data-bs-toggle="modal"
data-bs-target="#wstool-1-connection-settings"
data-toggle="modal"
data-target="#wstool-1-connection-settings"
>
{`${selectedConnection.shortname}${
connected ? ` (${translate('Connected')})` : ` (${translate('Not Connected')})`

View File

@@ -184,7 +184,7 @@ function TestCredentials({selectedFaucet, translate}) {
setBalance,
setSequence,
translate)
} className="btn btn-primary me-2 mb-2">
} className="btn btn-primary mr-2 mb-2">
{`${translate('resources.dev-tools.faucet.cred-btn.part1', 'Generate ')}${selectedFaucet.shortName}${translate('resources.dev-tools.faucet.cred-btn.part2', ' credentials')}`}
</button>
</div>

View File

@@ -35,6 +35,8 @@ The following is a list of known [amendments](../docs/concepts/networks-and-serv
| Name | Introduced | Status |
|:----------------------------------|:-----------|:------------------------------|
| [fixBatchInnerSigs] | v3.1.0 | {% badge href="https://xrpl.org/blog/2026/rippled-3.1.1" %}Obsolete: Removed in v3.1.1{% /badge %} |
| [Batch] | v2.5.0 | {% badge href="https://xrpl.org/blog/2026/rippled-3.1.1" %}Obsolete: Removed in v3.1.1{% /badge %} |
| [PermissionDelegation] | v2.5.0 | {% badge href="https://xrpl.org/blog/2025/rippled-2.6.1" %}Obsolete: Removed in v2.6.1{% /badge %} |
| [fixNFTokenNegOffer][] | v1.9.2 | {% badge %}Obsolete: To Be Removed{% /badge %} |
| [fixNFTokenDirV1][] | v1.9.1 | {% badge %}Obsolete: To Be Removed{% /badge %} |
@@ -105,12 +107,16 @@ For details, see the [XLS-73: AMMClawback specification](https://github.com/XRPL
| Amendment | Batch |
|:-------------|:------|
| Amendment ID | 894646DD5284E97DECFE6674A6D6152686791C4A95F8C132CCA9BAF9E5812FB6 |
| Status | Open for Voting |
| Status | Obsolete |
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
Allows multiple transactions to be bundled into a batch that's processed all together. Standard: [XLS-56](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0056-batch)
{% admonition type="danger" name="Warning" %}
This amendment was disabled in v3.1.1 due to a bug. It will be replaced by `BatchV1_1` in a future release.
{% /admonition %}
### CheckCashMakesTrustLine
[CheckCashMakesTrustLine]: #checkcashmakestrustline
@@ -746,12 +752,16 @@ Adds several fixes to Automated Market Maker code, specifically:
| Amendment | fixBatchInnerSigs |
|:-------------|:----------------|
| Amendment ID | 267624F8F744C4A4F1B5821A7D54410BCEBABE987F0172EE89E5FC4B6EDBC18A |
| Status | Open for Voting |
| Status | Obsolete |
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
This amendment fixes an issue where inner transactions of a `Batch` transaction would be flagged as having valid signatures. Since inner transactions aren't signed directly, they should never have valid signatures.
{% admonition type="danger" name="Warning" %}
This amendment was disabled in v3.1.1 due to a bug. It will be replaced by `BatchV1_1` in a future release.
{% /admonition %}
### fixCheckThreading
[fixCheckThreading]: #fixcheckthreading
@@ -1487,7 +1497,7 @@ This amendment adds several new invariants to protect the ledger against bugs in
The Lending Protocol enables on-chain, fixed-term, uncollateralized loans using pooled funds from a Single Asset Vault. This implementation relies on off-chain underwriting and risk management to assess the creditworthiness of borrowers, but offers configurable, peer-to-peer loans.
Specification: [XLS-66](https://github.com/Tapanito/XRPL-Standards/tree/xls-66-lending-protocol/XLS-0066-lending-protocol).
Specification: [XLS-66](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0066-lending-protocol).
### MPTokensV1
@@ -1699,9 +1709,11 @@ For more information, see the [Payment Channels Tutorial](../docs/tutorials/how-
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
Allows accounts to delegate some permissions to other accounts. This amendment was disabled in v2.6.1 due to a bug. It will be replaced by `PermissionDelegationV1_1` in a future release.
Allows accounts to delegate some permissions to other accounts. Specification: [XLS-75](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0075-permission-delegation).
Specification: [XLS-75](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0075-permission-delegation).
{% admonition type="danger" name="Warning" %}
This amendment was disabled in v2.6.1 due to a bug. It will be replaced by `PermissionDelegationV1_1` in a future release.
{% /admonition %}
### PermissionedDEX

View File

@@ -1,166 +0,0 @@
#!/usr/bin/env node
/**
* CSS Analysis Script
*
* Analyzes the compiled CSS bundle to identify:
* - Total size and line count
* - Bootstrap vs custom CSS breakdown
* - Most common selectors and patterns
* - Potential optimization opportunities
*/
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const CSS_FILE = path.join(__dirname, '../static/css/devportal2024-v1.css');
function analyzeCSS() {
console.log('🔍 Analyzing CSS Bundle...\n');
if (!fs.existsSync(CSS_FILE)) {
console.error(`❌ CSS file not found: ${CSS_FILE}`);
console.log('💡 Run "npm run build-css" first to generate the CSS bundle.');
process.exit(1);
}
const css = fs.readFileSync(CSS_FILE, 'utf-8');
const stats = fs.statSync(CSS_FILE);
// Basic stats
const lines = css.split('\n').length;
const sizeKB = (stats.size / 1024).toFixed(2);
const selectors = css.match(/[^{}]+(?=\{)/g) || [];
const uniqueSelectors = new Set(selectors.map(s => s.trim())).size;
console.log('📊 Bundle Statistics:');
console.log('━'.repeat(50));
console.log(` Size: ${sizeKB} KB`);
console.log(` Lines: ${lines.toLocaleString()}`);
console.log(` Total Selectors: ${selectors.length.toLocaleString()}`);
console.log(` Unique Selectors: ${uniqueSelectors.toLocaleString()}`);
console.log('');
// Bootstrap component detection
const bootstrapPatterns = {
'Grid System': /\.(container|row|col-)/g,
'Buttons': /\.btn-/g,
'Forms': /\.(form-control|form-select|form-check)/g,
'Cards': /\.card-/g,
'Navbar': /\.navbar-/g,
'Dropdown': /\.dropdown-/g,
'Modal': /\.modal-/g,
'Alert': /\.alert-/g,
'Badge': /\.badge-/g,
'Breadcrumb': /\.breadcrumb/g,
'Pagination': /\.page-/g,
'Accordion': /\.accordion/g,
'Carousel': /\.carousel/g,
'Tooltip': /\.tooltip/g,
'Popover': /\.popover/g,
'Toast': /\.toast/g,
'Spinner': /\.spinner-/g,
};
console.log('🎨 Bootstrap Component Usage:');
console.log('━'.repeat(50));
const componentUsage = [];
for (const [component, pattern] of Object.entries(bootstrapPatterns)) {
const matches = css.match(pattern);
const count = matches ? matches.length : 0;
componentUsage.push({ component, count });
}
componentUsage.sort((a, b) => b.count - a.count);
componentUsage.forEach(({ component, count }) => {
const bar = '█'.repeat(Math.min(Math.floor(count / 10), 40));
console.log(` ${component.padEnd(20)} ${count.toString().padStart(4)} ${bar}`);
});
console.log('');
// Custom classes analysis
const customPatterns = [
{ name: 'Dev Tools', pattern: /\.(rpc-tool|websocket|code-tab)/g },
{ name: 'Navigation', pattern: /\.(top-nav|side-nav|breadcrumb)/g },
{ name: 'Content', pattern: /\.(content-|landing-|page-)/g },
{ name: 'Cards', pattern: /\.(card-deck|project-card)/g },
{ name: 'Video', pattern: /\.video-/g },
{ name: 'Blog', pattern: /\.blog-/g },
];
console.log('🎯 Custom Component Patterns:');
console.log('━'.repeat(50));
customPatterns.forEach(({ name, pattern }) => {
const matches = css.match(pattern);
const count = matches ? matches.length : 0;
if (count > 0) {
console.log(` ${name.padEnd(20)} ${count.toString().padStart(4)}`);
}
});
console.log('');
// Optimization recommendations
console.log('💡 Optimization Recommendations:');
console.log('━'.repeat(50));
const recommendations = [];
// Check for unused Bootstrap components
const lowUsageComponents = componentUsage.filter(c => c.count < 5 && c.count > 0);
if (lowUsageComponents.length > 0) {
recommendations.push({
priority: 'HIGH',
message: `${lowUsageComponents.length} Bootstrap components with <5 usages detected`,
action: 'Consider manually importing only needed Bootstrap modules'
});
}
const noUsageComponents = componentUsage.filter(c => c.count === 0);
if (noUsageComponents.length > 0) {
recommendations.push({
priority: 'HIGH',
message: `${noUsageComponents.length} Bootstrap components with 0 usages detected`,
action: 'Remove unused components from Bootstrap import'
});
}
if (sizeKB > 200) {
recommendations.push({
priority: 'CRITICAL',
message: 'Bundle size exceeds 200KB',
action: 'Implement PurgeCSS to remove unused styles'
});
}
recommendations.push({
priority: 'MEDIUM',
message: 'No code splitting detected',
action: 'Consider splitting vendor CSS from custom styles'
});
recommendations.forEach(({ priority, message, action }) => {
const emoji = priority === 'CRITICAL' ? '🔴' : priority === 'HIGH' ? '🟡' : '🔵';
console.log(` ${emoji} [${priority}] ${message}`);
console.log(`${action}`);
console.log('');
});
// Estimate potential savings
const estimatedReduction = Math.round(parseFloat(sizeKB) * 0.75);
const estimatedFinal = Math.round(parseFloat(sizeKB) * 0.25);
console.log('📈 Estimated Optimization Potential:');
console.log('━'.repeat(50));
console.log(` Current Size: ${sizeKB} KB`);
console.log(` Potential Savings: ~${estimatedReduction} KB (75%)`);
console.log(` Expected Size: ~${estimatedFinal} KB`);
console.log('');
}
analyzeCSS();

View File

@@ -1,652 +0,0 @@
# Button Component Documentation
## Overview
The Button component is a scalable, accessible button implementation following the XRPL Brand Design System (BDS). It supports three visual variants (Primary, Secondary, Tertiary) and two color themes (Green, Black), with comprehensive state management and smooth animations.
## Features
- **Three Variants**: Primary (solid), Secondary (outline), Tertiary (text-only)
- **Two Color Themes**: Green (default) and Black
- **Link Support**: Can render as anchor elements for navigation via `href` prop
- **Animated Arrow Icon**: Optional icon with smooth hover animations
- **Full State Support**: Enabled, Hover, Focus, Active, and Disabled states
- **Responsive Design**: Adaptive padding and spacing across breakpoints
- **Accessibility**: WCAG compliant with keyboard navigation and screen reader support
- **Smooth Animations**: 150ms transitions with custom bezier timing
## Props API
```typescript
interface ButtonProps {
/** Button variant - determines visual style */
variant?: 'primary' | 'secondary' | 'tertiary';
/** Color theme - green (default) or black */
color?: 'green' | 'black';
/**
* Force the color to remain constant regardless of theme mode.
* When true, the button color will not change between light/dark modes.
* Use this for buttons on colored backgrounds where black should stay black.
*/
forceColor?: boolean;
/** Button content/label */
children: React.ReactNode;
/** Click handler */
onClick?: () => void;
/** Disabled state */
disabled?: boolean;
/** Button type attribute */
type?: 'button' | 'submit' | 'reset';
/** Additional CSS classes */
className?: string;
/** Whether to show the arrow icon */
showIcon?: boolean;
/** Accessibility label - defaults to button text if not provided */
ariaLabel?: string;
/** URL to navigate to - renders as a Link instead of button */
href?: string;
/** Link target - only applies when href is provided */
target?: '_self' | '_blank';
}
```
### Default Values
- `variant`: `'primary'`
- `color`: `'green'`
- `forceColor`: `false`
- `disabled`: `false`
- `type`: `'button'`
- `className`: `''`
- `showIcon`: `true`
- `ariaLabel`: (derived from children text)
- `href`: `undefined`
- `target`: `'_self'`
## Variants
### Primary Button
The Primary button is used for the main call-to-action on a page. It features a solid background that fills from bottom-to-top on hover.
**Visual Characteristics:**
- Solid background (Green 300 / Black)
- High visual emphasis
- Background color transitions on hover
- Black text on green background, white text on black background
**Usage:**
```tsx
<Button variant="primary" onClick={handleClick}>
Get Started
</Button>
```
### Secondary Button
The Secondary button is used for supporting actions. It features an outline style with a transparent background that fills on hover.
**Visual Characteristics:**
- Transparent background with 2px border
- Medium visual emphasis
- Background fills from bottom-to-top on hover
- Green/Black text and border
**Usage:**
```tsx
<Button variant="secondary" onClick={handleClick}>
Learn More
</Button>
```
### Tertiary Button
The Tertiary button is used for low-emphasis or contextual actions. It appears as text-only with optional underline on hover.
**Visual Characteristics:**
- Text-only, no background or border
- Lowest visual emphasis
- Underline appears on hover/focus
- Different typography (Body R token vs Label R)
**Usage:**
```tsx
<Button variant="tertiary" onClick={handleClick}>
View Details
</Button>
```
## Color Themes
### Green Theme (Default)
The green theme uses the XRPL brand green colors:
- **Primary**: Green 300 background (#21E46B), Green 200 hover (#70EE97)
- **Secondary**: Green 400 text/border (#0DAA3E), Green 500 hover (#078139)
- **Tertiary**: Green 400 text (#0DAA3E), Green 500 hover (#078139)
### Black Theme
The black theme provides an alternative color scheme:
- **Primary**: Black background (#141414), 80% black hover
- **Secondary**: Black text/border (#141414), 15% black hover fill
- **Tertiary**: Black text (#141414)
**Usage:**
```tsx
<Button variant="primary" color="black" onClick={handleClick}>
Dark Button
</Button>
```
### Force Color (Theme-Independent)
By default, black buttons automatically switch to green in dark mode for better visibility. However, when placing buttons on colored backgrounds (e.g., lilac, yellow, green), you may want black buttons to remain black regardless of theme mode.
Use the `forceColor` prop to prevent automatic color switching:
**Usage:**
```tsx
{/* Black button that stays black in both light and dark modes */}
<Button variant="primary" color="black" forceColor onClick={handleClick}>
Always Black
</Button>
{/* Useful for colored backgrounds like in FeatureTwoColumn pattern */}
<FeatureTwoColumn color="lilac">
<Button variant="primary" color="black" forceColor href="/get-started">
Get Started
</Button>
<Button variant="tertiary" color="black" forceColor href="/learn-more">
Learn More
</Button>
</FeatureTwoColumn>
```
**When to use `forceColor`:**
- Buttons on colored backgrounds (lilac, yellow, green variants)
- When you need consistent button colors regardless of user's theme preference
- Pattern components like `FeatureTwoColumn` where black text is required for readability
**Note:** The `forceColor` prop only affects the color behavior; all other button functionality (hover animations, focus states, etc.) remains the same.
## Link Buttons
The Button component can render as an anchor element for navigation by passing the `href` prop. When `href` is provided, the button is wrapped in a Redocly `Link` component for proper routing support within the application.
### How It Works
- When `href` is provided and button is not disabled, renders as `<Link>` (anchor element)
- When `href` is not provided or button is disabled, renders as `<button>` element
- All visual styles and animations remain identical regardless of element type
- The Redocly Link component handles internal routing and external link handling
### Internal Navigation
For navigating within the application:
```tsx
<Button variant="primary" href="/docs">
View Documentation
</Button>
<Button variant="secondary" href="/about">
About Us
</Button>
```
### External Links
For external URLs, use `target="_blank"` to open in a new tab:
```tsx
<Button variant="primary" href="https://xrpl.org" target="_blank">
Visit XRPL.org
</Button>
```
### Link with Click Handler
You can combine `href` with `onClick` for tracking or additional logic:
```tsx
<Button
variant="primary"
href="/signup"
onClick={() => trackEvent('signup_click')}
>
Sign Up
</Button>
```
### Disabled Link Buttons
When `disabled={true}` and `href` is provided, the component renders as a `<button>` element instead of a link to prevent navigation:
```tsx
<Button variant="primary" href="/checkout" disabled>
Checkout (Unavailable)
</Button>
```
### All Variants as Links
All button variants support link functionality:
```tsx
{/* Primary link button */}
<Button variant="primary" href="/get-started">
Get Started
</Button>
{/* Secondary link button */}
<Button variant="secondary" href="/learn-more">
Learn More
</Button>
{/* Tertiary link button */}
<Button variant="tertiary" href="/view-details">
View Details
</Button>
{/* Black theme link button */}
<Button variant="primary" color="black" href="/dashboard">
Dashboard
</Button>
```
## States
### Enabled State
The default interactive state of the button. All variants display their base styling.
### Hover State
Triggered when the user hovers over the button with a mouse:
- **Primary/Secondary**: Background fills from bottom-to-top
- **Tertiary**: Underline appears, text color darkens
- **All Variants**: Arrow icon line shrinks, gap increases (with icon)
### Focus State
Triggered when the button receives keyboard focus (Tab key):
- Similar visual changes to hover state
- Additional focus outline (2px border/outline)
- Ensures keyboard accessibility
### Active State
Triggered when the button is being pressed:
- Returns to default padding/gap
- Background resets (for Primary/Secondary)
- Maintains visual feedback during press
### Disabled State
When `disabled={true}`:
- Icon is automatically hidden
- Gray text and background (Primary) or border (Secondary)
- Cursor changes to `not-allowed`
- `pointer-events: none` prevents interaction
- `aria-disabled` attribute set for screen readers
**Usage:**
```tsx
<Button variant="primary" disabled>
Unavailable
</Button>
```
## How It Works
### Component Structure
The Button component uses BEM (Block Element Modifier) naming convention with the `bds` namespace:
- `.bds-btn` - Base button class
- `.bds-btn--primary` - Primary variant modifier
- `.bds-btn--secondary` - Secondary variant modifier
- `.bds-btn--tertiary` - Tertiary variant modifier
- `.bds-btn--green` - Green color theme (default)
- `.bds-btn--black` - Black color theme
- `.bds-btn--disabled` - Disabled state modifier
- `.bds-btn--no-icon` - No icon modifier
- `.bds-btn__label` - Label element
- `.bds-btn__icon` - Icon container
- `.bds-btn__icon-line` - Arrow horizontal line
- `.bds-btn__icon-chevron` - Arrow chevron
### Background Animation
Primary and Secondary variants use a shared animation pattern:
1. **Pseudo-element (`::before`)**: Creates the hover background fill
2. **Transform Origin**: Set to `bottom center` for bottom-to-top fill
3. **Initial State**: `scaleY(0)` - background hidden
4. **Hover/Focus**: `scaleY(1)` - background fills from bottom
5. **Active**: `scaleY(0)` - background resets during press
This creates a smooth, directional fill animation that feels natural and responsive.
### Arrow Icon Animation
The arrow icon consists of two parts:
1. **Horizontal Line**: Shrinks from right to left (`scaleX(0)`) on hover/focus
2. **Chevron**: Stays visible, shifts right via increased gap
The gap between label and icon increases on hover/focus:
- **Default**: 16px (desktop), 16px (mobile)
- **Hover/Focus**: 22px (desktop), 21px (mobile)
This creates the illusion of the arrow "moving forward" as the line disappears.
### Padding Adjustments
On hover/focus, padding adjusts to accommodate the increased gap:
- **Primary**: `8px 19px 8px 20px``8px 13px 8px 20px` (desktop)
- **Secondary**: `6px 17px 6px 18px``6px 11px 6px 18px` (desktop)
- **Tertiary**: `8px 20px``8px 14px 8px 20px` (desktop)
These adjustments maintain visual balance while allowing the icon animation to work smoothly.
### Responsive Behavior
The component adapts to screen size at the `1023px` breakpoint:
**Desktop (≥1024px):**
- Larger padding values
- 22px gap on hover/focus
**Tablet/Mobile (≤1023px):**
- Reduced padding values
- 21px gap on hover/focus
All transitions remain smooth across breakpoints.
## Typography
### Primary & Secondary Variants
- **Font**: Booton, sans-serif
- **Size**: 16px (Label R token)
- **Weight**: 400
- **Line Height**: 23.2px
- **Letter Spacing**: 0px
### Tertiary Variant
- **Font**: Booton, sans-serif
- **Size**: 18px (Body R token)
- **Weight**: 400
- **Line Height**: 26.1px
- **Letter Spacing**: -0.5px
## Spacing & Layout
- **Border Radius**: 100px (fully rounded)
- **Max Height**: 40px
- **Icon Size**: 15px × 14px
- **Transitions**: 150ms with `cubic-bezier(0.98, 0.12, 0.12, 0.98)`
## Usage Examples
### Basic Usage
```tsx
import { Button } from 'shared/components/Button';
// Primary button (default)
<Button onClick={handleClick}>
Get Started
</Button>
// Secondary button
<Button variant="secondary" onClick={handleClick}>
Learn More
</Button>
// Tertiary button
<Button variant="tertiary" onClick={handleClick}>
View Details
</Button>
```
### Form Integration
```tsx
<form onSubmit={handleSubmit}>
<Button variant="primary" type="submit">
Submit
</Button>
<Button variant="tertiary" type="reset">
Reset
</Button>
<Button variant="tertiary" type="button" onClick={handleCancel}>
Cancel
</Button>
</form>
```
### Without Icon
```tsx
<Button variant="primary" showIcon={false} onClick={handleClick}>
No Arrow
</Button>
```
### Disabled State
```tsx
<Button variant="primary" disabled>
Unavailable
</Button>
```
### Color Themes
```tsx
{/* Green theme (default) */}
<Button variant="primary" color="green" onClick={handleClick}>
Green Button
</Button>
{/* Black theme */}
<Button variant="primary" color="black" onClick={handleClick}>
Black Button
</Button>
```
### Link Buttons
```tsx
{/* Internal navigation */}
<Button variant="primary" href="/docs">
View Documentation
</Button>
{/* External link (opens in new tab) */}
<Button variant="primary" href="https://xrpl.org" target="_blank">
Visit XRPL.org
</Button>
{/* Link with click tracking */}
<Button
variant="secondary"
href="/signup"
onClick={() => analytics.track('signup_button')}
>
Sign Up
</Button>
{/* Black theme link button */}
<Button variant="primary" color="black" href="/dashboard">
Go to Dashboard
</Button>
```
### Visual Hierarchy
```tsx
{/* Use variants to create clear visual hierarchy */}
<Button variant="primary" onClick={handlePrimaryAction}>
Main Action
</Button>
<Button variant="secondary" onClick={handleSecondaryAction}>
Secondary Action
</Button>
<Button variant="tertiary" onClick={handleTertiaryAction}>
Tertiary Action
</Button>
```
## Accessibility
### Keyboard Navigation
- **Tab**: Focus next button
- **Shift+Tab**: Focus previous button
- **Enter/Space**: Activate button
- **Focus Indicator**: Visible outline/border (2px)
- **Disabled buttons**: Not focusable
### Screen Reader Support
- Semantic `<button>` element (or `<a>` for link buttons)
- Button label announced via `aria-label` attribute
- `aria-disabled` attribute for disabled state
- Icon marked with `aria-hidden="true"`
- Link buttons use proper anchor semantics for navigation
### Color Contrast
All variants meet WCAG AA standards:
- **Primary**: Black on Green 300 = sufficient contrast
- **Secondary/Tertiary**: Green 400/500 on White = 4.52:1 / 5.12:1
- **Disabled**: Gray 400/500 indicates non-interactive state
### Focus Management
- Focus outline appears on keyboard navigation (`:focus-visible`)
- Focus styles match hover styles for consistency
- Square corners on Tertiary focus outline for better visibility
## Design Tokens
The component uses design tokens from the XRPL Brand Design System:
### Colors
- `$green-100` through `$green-500`
- `$gray-200`, `$gray-400`, `$gray-500`
- `$white`
- Neutral black (`#141414`)
### Spacing
- Border radius: `100px`
- Focus border width: `2px`
- Responsive breakpoint: `1023px`
### Motion
- Transition duration: `150ms`
- Timing function: `cubic-bezier(0.98, 0.12, 0.12, 0.98)`
## Best Practices
1. **Use Primary for main actions**: Reserve primary buttons for the most important action on a page
2. **Use Secondary for supporting actions**: Use secondary buttons for actions that support the primary action
3. **Use Tertiary for low-emphasis actions**: Use tertiary buttons for cancel, skip, or less important actions
4. **Maintain visual hierarchy**: Don't use multiple primary buttons on the same page
5. **Provide clear labels**: Button text should clearly indicate the action
6. **Handle disabled states**: Always provide feedback when actions are unavailable
7. **Test keyboard navigation**: Ensure all buttons are accessible via keyboard
8. **Consider context**: Choose color theme based on background and design context
9. **Use `href` for navigation**: When the button navigates to a new page, use the `href` prop instead of `onClick` with router navigation
10. **Use `target="_blank"` for external links**: Always open external URLs in a new tab for better UX
11. **Combine `href` with `onClick` for tracking**: When you need both navigation and analytics tracking
## Implementation Details
### Class Name Generation
The component builds class names dynamically:
```typescript
const classNames = [
'bds-btn',
`bds-btn--${variant}`,
`bds-btn--${color}`,
disabled ? 'bds-btn--disabled' : '',
!shouldShowIcon ? 'bds-btn--no-icon' : '',
className,
]
.filter(Boolean)
.join(' ');
```
### Icon Visibility Logic
The icon is automatically hidden when:
- `showIcon={false}` is passed
- `disabled={true}` is set
This ensures disabled buttons don't show interactive elements.
### Link Rendering Logic
The component conditionally renders as different elements:
```typescript
// Render as Link when href is provided and not disabled
if (href && !disabled) {
return (
<Link to={href} target={target} className={classNames}>
{content}
</Link>
);
}
// Otherwise render as button
return (
<button type={type} className={classNames} disabled={disabled}>
{content}
</button>
);
```
This ensures:
- Link buttons use proper anchor semantics for navigation
- Disabled state always renders as a button to prevent navigation
- Visual styles remain consistent across both element types
### State Management
The component manages states through CSS classes and props:
- **Disabled**: Controlled via `disabled` prop and `aria-disabled` attribute
- **Hover/Focus**: Handled by CSS `:hover` and `:focus-visible` pseudo-classes
- **Active**: Handled by CSS `:active` pseudo-class
- **Link vs Button**: Determined by presence of `href` prop
## Browser Support
The component uses modern CSS features:
- CSS Grid/Flexbox (widely supported)
- `:focus-visible` (supported in modern browsers)
- CSS transforms and transitions (widely supported)
- CSS custom properties (supported in modern browsers)
For older browser support, consider polyfills or fallbacks as needed.
## Related Components
- See showcase pages for interactive examples:
- `about/button-showcase-tertiary.page.tsx`
- Other variant showcase pages
## File Structure
```
shared/components/Button/
├── Button.tsx # Component implementation
├── Button.scss # Component styles
├── Button.md # This documentation
└── index.ts # Exports
```

File diff suppressed because it is too large Load Diff

View File

@@ -1,183 +0,0 @@
import React from 'react';
import clsx from 'clsx';
import { Link } from '@redocly/theme/components/Link/Link';
export interface ButtonProps {
/** Button variant - determines visual style */
variant?: 'primary' | 'secondary' | 'tertiary';
/** Color theme - green (default) or black */
color?: 'green' | 'black';
/**
* Force the color to remain constant regardless of theme mode.
* When true, the button color will not change between light/dark modes.
* Use this for buttons on colored backgrounds where black should stay black.
*/
forceColor?: boolean;
/** Button content/label */
children: React.ReactNode;
/** Click handler */
onClick?: () => void;
/** Disabled state */
disabled?: boolean;
/** Button type attribute */
type?: 'button' | 'submit' | 'reset';
/** Additional CSS classes */
className?: string;
/** Whether to show the arrow icon */
showIcon?: boolean;
/** Accessibility label - defaults to button text if not provided */
ariaLabel?: string;
/** URL to navigate to - renders as a Link instead of button */
href?: string;
/** Link target - only applies when href is provided */
target?: '_self' | '_blank';
/**
* Force no padding and left-align text.
* When true, removes all padding and aligns content to the left.
* Useful for tertiary buttons in block layouts where left alignment is needed.
*/
forceNoPadding?: boolean;
}
/**
* Animated Arrow Icon Component
* The horizontal line shrinks from left to right on hover/focus,
* while the chevron shifts right via the gap change.
*/
const ArrowIcon: React.FC = () => (
<svg
className="bds-btn__icon"
width="15"
height="14"
viewBox="0 0 15 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
{/* Horizontal line - shrinks on hover */}
<line
className="bds-btn__icon-line"
x1="0"
y1="7"
x2="14"
y2="7"
stroke="currentColor"
strokeWidth="1.3"
strokeMiterlimit="10"
/>
{/* Chevron - stays visible */}
<path
className="bds-btn__icon-chevron"
d="M8.16755 1.16743L14.0005 7.00038L8.16755 12.8333"
stroke="currentColor"
strokeWidth="1.3"
strokeMiterlimit="10"
fill="none"
/>
</svg>
);
/**
* BDS Button Component
*
* A scalable button component following the XRPL Brand Design System.
* Supports Primary, Secondary, and Tertiary variants with green (default) or black color themes.
*
* @example
* <Button variant="primary" onClick={handleClick}>Get Started</Button>
* <Button variant="secondary" onClick={handleClick}>Learn More</Button>
* <Button variant="tertiary" onClick={handleClick}>View Details</Button>
* <Button variant="primary" color="black" onClick={handleClick}>Dark Button</Button>
*/
/**
* Helper function to extract text content from ReactNode
*/
const getTextFromChildren = (children: React.ReactNode): string => {
if (typeof children === 'string' || typeof children === 'number') {
return String(children);
}
if (Array.isArray(children)) {
return children.map(getTextFromChildren).join('');
}
if (React.isValidElement(children)) {
const props = children.props as { children?: React.ReactNode };
if (props.children) {
return getTextFromChildren(props.children);
}
}
return '';
};
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
color = 'green',
forceColor = false,
children,
onClick,
disabled = false,
type = 'button',
className = '',
showIcon = true,
ariaLabel,
href,
target = '_self',
forceNoPadding = false,
}) => {
// Hide icon when disabled (per design spec)
const shouldShowIcon = showIcon && !disabled;
// Default ariaLabel to button text if not provided
const buttonAriaLabel = ariaLabel || getTextFromChildren(children);
// Build class names using BEM with bds namespace
const classNames = clsx(
'bds-btn',
`bds-btn--${variant}`,
`bds-btn--${color}`,
{
'bds-btn--disabled': disabled,
'bds-btn--no-icon': !shouldShowIcon,
'bds-btn--force-color': forceColor,
'bds-btn--no-padding': forceNoPadding,
},
className
);
// Inner content shared between button and link
const content = (
<>
<span className="bds-btn__label">{children}</span>
{shouldShowIcon && <ArrowIcon />}
</>
);
// Render as Link when href is provided
if (href && !disabled) {
return (
<Link
to={href}
target={target}
className={classNames}
onClick={onClick}
aria-label={buttonAriaLabel}
>
{content}
</Link>
);
}
return (
<button
type={type}
className={classNames}
onClick={onClick}
disabled={disabled}
aria-disabled={disabled}
aria-label={buttonAriaLabel}
>
{content}
</button>
);
};
export default Button;

View File

@@ -1,2 +0,0 @@
export { Button, type ButtonProps } from './Button';
export { default } from './Button';

View File

@@ -1,167 +0,0 @@
# CardIcon Component
A clickable card component featuring an icon (top-left) and label text with arrow (bottom). Supports two color variants with responsive sizing that adapts at breakpoints.
## Features
- **Two Color Variants**: Neutral (gray) and Green
- **Five Interaction States**: Default, Hover, Focused, Pressed, Disabled
- **Responsive Sizing**: Automatically adapts at SM/MD/LG breakpoints
- **Window Shade Animation**: Smooth hover effect with color wipe
- **Accessible**: Full keyboard navigation and ARIA support
- **Flexible Rendering**: Renders as `<a>` or `<button>` based on props
## Responsive Sizes
The component automatically adapts its dimensions based on viewport width:
| Breakpoint | Height | Icon Size | Padding |
|------------|--------|-----------|---------|
| SM (< 576px) | 136px | 56x56 | 8px |
| MD (576px - 991px) | 140px | 60x60 | 12px |
| LG (>= 992px) | 144px | 64x64 | 16px |
## Color States
### Light Mode
#### Neutral Variant
- **Default**: Gray 200 (#E6EAF0) - black text
- **Hover**: Gray 300 (#CAD4DF) - black text
- **Focused**: Gray 300 + 2px black border - black text
- **Pressed**: Gray 400 (#8A919A) - black text
- **Disabled**: Gray 100 (#F0F3F7) - muted text (Gray 400), 50% icon opacity
#### Green Variant
- **Default**: Green 200 (#70EE97) - black text
- **Hover**: Green 300 (#21E46B) - black text
- **Focused**: Green 300 + 2px black border - black text
- **Pressed**: Green 400 (#0DAA3E) - black text
- **Disabled**: Green 100 (#EAFCF1) - muted text (Gray 400), 50% icon opacity
### Dark Mode
#### Neutral Variant
- **Default**: Gray 500 (#72777E) - white text; icons forced white (`filter`)
- **Hover**: Gray 400 (#8A919A) - white text
- **Focused**: Gray 400 overlay + **2px white** focus outline - white text
- **Pressed**: **`$gray-500-pressed-dark` (#56595E)** — neutral/500 (#72777E) with 70% black tint
- **Disabled**: Gray 500 at 30% opacity - white text; icons stay white (same filter)
#### Green Variant
- **Default**: Green 300 (#21E46B) - black text
- **Hover**: Green 200 (#70EE97) - black text
- **Focused**: Green 200 overlay + **2px white** focus outline - black text
- **Pressed**: Green 400 (#0DAA3E) - black text
- **Disabled**: Gray 500 at 30% opacity - white text; **icon forced white** for contrast
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `variant` | `'neutral' \| 'green'` | `'neutral'` | Color variant |
| `icon` | `string` | *required* | Icon image source (URL or path) |
| `iconAlt` | `string` | `''` | Alt text for the icon image |
| `label` | `string` | *required* | Card label text |
| `href` | `string` | - | Link destination (renders as `<a>`) |
| `onClick` | `() => void` | - | Click handler (renders as `<button>`) |
| `disabled` | `boolean` | `false` | Disabled state |
| `className` | `string` | `''` | Additional CSS classes |
## Usage Examples
### Basic Link Card
```tsx
import { CardIcon } from '@/shared/components/CardIcon';
<CardIcon
variant="neutral"
icon="/icons/javascript.svg"
iconAlt="JavaScript logo"
label="Get Started with Javascript"
href="/docs/tutorials/javascript"
/>
```
### Green Variant with Click Handler
```tsx
<CardIcon
variant="green"
icon="/icons/python.svg"
iconAlt="Python logo"
label="Python Tutorial"
onClick={() => openTutorial('python')}
/>
```
### Disabled State
```tsx
<CardIcon
variant="neutral"
icon="/icons/coming-soon.svg"
label="Coming Soon"
disabled
/>
```
### Within a Grid Layout
```tsx
import { PageGrid, PageGridItem } from '@/shared/components/PageGrid';
import { CardIcon } from '@/shared/components/CardIcon';
<PageGrid>
<PageGridItem colSpan={{ sm: 2, md: 4, lg: 3 }}>
<CardIcon
variant="neutral"
icon="/icons/javascript.svg"
label="JavaScript"
href="/docs/javascript"
/>
</PageGridItem>
<PageGridItem colSpan={{ sm: 2, md: 4, lg: 3 }}>
<CardIcon
variant="green"
icon="/icons/python.svg"
label="Python"
href="/docs/python"
/>
</PageGridItem>
</PageGrid>
```
## Accessibility
- Uses semantic `<a>` or `<button>` elements based on interaction type
- Includes `aria-label` for screen readers
- Supports keyboard navigation (Tab, Enter, Space)
- Focus states meet WCAG 2.1 AA contrast requirements
- Disabled state includes `aria-disabled` attribute
## CSS Classes (BEM)
| Class | Description |
|-------|-------------|
| `.bds-card-icon` | Base card styles |
| `.bds-card-icon--neutral` | Neutral color variant |
| `.bds-card-icon--green` | Green color variant |
| `.bds-card-icon--hovered` | Hover state (JS-controlled) |
| `.bds-card-icon--disabled` | Disabled state |
| `.bds-card-icon__overlay` | Hover animation overlay |
| `.bds-card-icon__icon` | Icon container |
| `.bds-card-icon__icon-img` | Icon image |
| `.bds-card-icon__content` | Bottom content row |
| `.bds-card-icon__label` | Text label |
| `.bds-card-icon__arrow` | Arrow icon wrapper |
## Design Tokens
The component uses these design tokens from the style system:
- **Colors**: `$gray-100` through `$gray-400`, `$green-100` through `$green-400`
- **Typography**: `body-r` token from `_font.scss`
- **Breakpoints**: `$grid-breakpoints` from `_breakpoints.scss`
- **Animation**: `cubic-bezier(0.98, 0.12, 0.12, 0.98)` timing function

Some files were not shown because too many files have changed in this diff Show More