diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..9679b4a --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,42 @@ +name: CodeQL Security Analysis + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Run weekly on Mondays at 08:00 UTC + - cron: '0 8 * * 1' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [javascript-typescript] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: security-extended + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: '/language:${{ matrix.language }}' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 76e8144..53d1e32 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,6 +8,32 @@ on: workflow_dispatch: jobs: + lint: + name: Lint & Format Check + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: website/package-lock.json + + - name: Install dependencies + working-directory: ./website + run: npm ci + + - name: Run ESLint + working-directory: ./website + run: npm run lint + + - name: Check Prettier formatting + working-directory: ./website + run: npm run format:check + e2e-tests: name: E2E Tests runs-on: ubuntu-latest @@ -83,3 +109,25 @@ jobs: name: lighthouse-report path: website/.lighthouseci/ retention-days: 7 + + dependency-check: + name: Dependency Security Audit + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: website/package-lock.json + + - name: Install dependencies + working-directory: ./website + run: npm ci + + - name: Run security audit + working-directory: ./website + run: npm audit --audit-level=high diff --git a/PROJECT_STATUS.md b/PROJECT_STATUS.md index 75b5f7b..58f97be 100644 --- a/PROJECT_STATUS.md +++ b/PROJECT_STATUS.md @@ -1,82 +1,84 @@ -# Project Status: Semantic Anchors Website Redesign +# Project Status: Semantic Anchors Website -**Last Updated:** 2025-02-13 -**Current Phase:** Planning Complete ✅ → Ready for Phase 1 +**Last Updated:** 2026-02-20 +**Current Phase:** All Phases Complete - Website Live -## 📊 Quick Stats +## Quick Stats -- **19 GitHub Issues** created and ready -- **3 Epics** for organized work (Phase 1, 2, 3) +- **Website Live**: https://llm-coding.github.io/Semantic-Anchors/ +- **28 E2E Tests** all passing +- **5 ADRs** with Pugh matrices (ADR-001 to ADR-005) +- **8 open Risk Radar issues** (#81-88) being actively worked on - **2,693 lines** of specifications - **2,804 lines** of arc42 architecture -- **4 ADRs** with Pugh matrices -## 📋 Documentation Status +## Documentation Status | Document | Status | Location | |----------|--------|----------| -| PRD | ✅ Complete | `docs/PRD.md` | -| Use Cases | ✅ Complete | `docs/specs/01_use_cases.adoc` | -| API Specification | ✅ Complete | `docs/specs/02_api_specification.adoc` | -| Acceptance Criteria | ✅ Complete | `docs/specs/03_acceptance_criteria.adoc` | -| ADRs | ✅ Complete | `docs/specs/adrs/` | -| arc42 Architecture | ✅ Complete | `docs/arc42/` | -| CLAUDE.md | ✅ Complete | `CLAUDE.md` | +| PRD | Complete | `docs/PRD.md` | +| Use Cases | Complete | `docs/specs/01_use_cases.adoc` | +| API Specification | Complete | `docs/specs/02_api_specification.adoc` | +| Acceptance Criteria | Complete | `docs/specs/03_acceptance_criteria.adoc` | +| ADRs | Complete | `docs/specs/adrs/` | +| arc42 Architecture | Complete | `docs/arc42/` | +| CLAUDE.md | Complete | `CLAUDE.md` | -## 🎯 Implementation Roadmap +## Implementation Status -### ✅ Phase 0: Planning (COMPLETE) +### Phase 0: Planning (COMPLETE) - [x] Create PRD - [x] Write specifications - [x] Create arc42 architecture -- [x] Write ADRs +- [x] Write ADRs (ADR-001 to ADR-005) - [x] Create GitHub Issues -### 🔄 Phase 1: Foundation (Ready to Start) -**Timeline:** Week 1-2 +### Phase 1: Foundation (COMPLETE) **Epic:** [#35](https://github.com/LLM-Coding/Semantic-Anchors/issues/35) -**Issues:** -- [ ] [#36](https://github.com/LLM-Coding/Semantic-Anchors/issues/36) MECE analysis -- [ ] [#37](https://github.com/LLM-Coding/Semantic-Anchors/issues/37) Role mapping -- [ ] [#38](https://github.com/LLM-Coding/Semantic-Anchors/issues/38) Split README -- [ ] [#39](https://github.com/LLM-Coding/Semantic-Anchors/issues/39) Metadata script -- [ ] [#40](https://github.com/LLM-Coding/Semantic-Anchors/issues/40) Generate includes +- [x] [#36](https://github.com/LLM-Coding/Semantic-Anchors/issues/36) MECE analysis +- [x] [#37](https://github.com/LLM-Coding/Semantic-Anchors/issues/37) Role mapping +- [x] [#38](https://github.com/LLM-Coding/Semantic-Anchors/issues/38) Split README into individual anchor files +- [x] [#39](https://github.com/LLM-Coding/Semantic-Anchors/issues/39) Metadata extraction script +- [x] [#40](https://github.com/LLM-Coding/Semantic-Anchors/issues/40) Generate category/role include files -### ⏳ Phase 2: Website Development (Blocked) -**Timeline:** Week 3-5 +### Phase 2: Website Development (COMPLETE) **Epic:** [#41](https://github.com/LLM-Coding/Semantic-Anchors/issues/41) -**Dependencies:** Phase 1 must complete - -**Issues:** -- [ ] [#42](https://github.com/LLM-Coding/Semantic-Anchors/issues/42) Vite setup -- [ ] [#43](https://github.com/LLM-Coding/Semantic-Anchors/issues/43) Treemap -- [ ] [#44](https://github.com/LLM-Coding/Semantic-Anchors/issues/44) Role filter -- [ ] [#45](https://github.com/LLM-Coding/Semantic-Anchors/issues/45) Search -- [ ] [#46](https://github.com/LLM-Coding/Semantic-Anchors/issues/46) AsciiDoc rendering -- [ ] [#47](https://github.com/LLM-Coding/Semantic-Anchors/issues/47) i18n -- [ ] [#48](https://github.com/LLM-Coding/Semantic-Anchors/issues/48) Theming - -### ⏳ Phase 3: Automation & Deployment (Blocked) -**Timeline:** Week 6-7 -**Epic:** [#49](https://github.com/LLM-Coding/Semantic-Anchors/issues/49) -**Dependencies:** Phase 2 must complete - -**Issues:** -- [ ] [#50](https://github.com/LLM-Coding/Semantic-Anchors/issues/50) GitHub Actions -- [ ] [#51](https://github.com/LLM-Coding/Semantic-Anchors/issues/51) Issue templates -- [ ] [#52](https://github.com/LLM-Coding/Semantic-Anchors/issues/52) CONTRIBUTING.md -- [ ] [#53](https://github.com/LLM-Coding/Semantic-Anchors/issues/53) Update README -### 🔮 Phase 4: Enhancement (Future) -**Timeline:** Week 8+ +- [x] [#42](https://github.com/LLM-Coding/Semantic-Anchors/issues/42) Vite setup +- [x] [#43](https://github.com/LLM-Coding/Semantic-Anchors/issues/43) Card grid visualization (superseded treemap per ADR-005) +- [x] [#44](https://github.com/LLM-Coding/Semantic-Anchors/issues/44) Role filter +- [x] [#45](https://github.com/LLM-Coding/Semantic-Anchors/issues/45) Search functionality +- [x] [#46](https://github.com/LLM-Coding/Semantic-Anchors/issues/46) AsciiDoc rendering +- [x] [#47](https://github.com/LLM-Coding/Semantic-Anchors/issues/47) i18n (EN/DE) +- [x] [#48](https://github.com/LLM-Coding/Semantic-Anchors/issues/48) Dark/Light theming -- GitHub Copilot validation workflow -- Advanced search features -- Privacy-first analytics -- Service Worker for offline support +### Phase 3: Automation & Deployment (COMPLETE) +**Epic:** [#49](https://github.com/LLM-Coding/Semantic-Anchors/issues/49) -## 🏗️ Tech Stack Decisions +- [x] [#50](https://github.com/LLM-Coding/Semantic-Anchors/issues/50) GitHub Actions deployment +- [x] [#51](https://github.com/LLM-Coding/Semantic-Anchors/issues/51) Issue templates +- [x] [#52](https://github.com/LLM-Coding/Semantic-Anchors/issues/52) CONTRIBUTING.md +- [x] [#53](https://github.com/LLM-Coding/Semantic-Anchors/issues/53) Updated README +- [x] E2E tests with Playwright (28 tests, all passing) +- [x] Lighthouse CI integration + +### Phase 4: Enhancement (In Progress) +**Risk Radar issues** tracked in https://github.com/LLM-Coding/Semantic-Anchors/labels/risk-radar + +- [ ] [#81](https://github.com/LLM-Coding/Semantic-Anchors/issues/81) ESLint / Prettier configuration +- [ ] [#82](https://github.com/LLM-Coding/Semantic-Anchors/issues/82) Pre-commit hooks (husky) +- [ ] [#83](https://github.com/LLM-Coding/Semantic-Anchors/issues/83) npm audit in CI +- [ ] [#84](https://github.com/LLM-Coding/Semantic-Anchors/issues/84) SAST (Semgrep / CodeQL) +- [ ] [#85](https://github.com/LLM-Coding/Semantic-Anchors/issues/85) Property-based tests +- [ ] [#86](https://github.com/LLM-Coding/Semantic-Anchors/issues/86) AI code review (CodeRabbit / Copilot Review) +- [ ] [#87](https://github.com/LLM-Coding/Semantic-Anchors/issues/87) SonarQube quality gate +- [ ] [#88](https://github.com/LLM-Coding/Semantic-Anchors/issues/88) Sampling review (~20%) +- [ ] GitHub Copilot validation workflow +- [ ] Advanced search features +- [ ] Privacy-first analytics + +## Tech Stack Decisions All decisions are documented with Pugh matrices: @@ -84,49 +86,48 @@ All decisions are documented with Pugh matrices: |----------|--------|-------|-----| | Static Site Generator | Vite | +88 | [ADR-001](docs/specs/adrs/adr-001-static-site-generator.adoc) | | Metadata Storage | AsciiDoc Attributes | +51 | [ADR-002](docs/specs/adrs/adr-002-metadata-storage.adoc) | -| Treemap Library | Apache ECharts | +77 | [ADR-003](docs/specs/adrs/adr-003-treemap-library.adoc) | +| Visualization Library (superseded) | Apache ECharts for Treemap | +77 | [ADR-003](docs/specs/adrs/adr-003-treemap-library.adoc) | | File Structure | One File per Anchor | +105 | [ADR-004](docs/specs/adrs/adr-004-one-file-per-anchor.adoc) | +| Visualization (current) | Card Grid | +137 | [ADR-005](docs/specs/adrs/adr-005-card-grid-visualization.adoc) | -## 📈 Success Criteria +**Note:** ADR-003 (Apache ECharts for Treemap) was superseded by ADR-005 (Card Grid) due to fundamental usability issues with the treemap (text truncation, poor contrast, viewport cut-off). -### Phase 1 Success -- ✅ MECE-compliant categories -- ✅ All 60+ anchors in separate files -- ✅ Metadata extracted and validated -- ✅ Includes working +## Success Criteria -### Phase 2 Success (MVP) -- ✅ Website runs locally -- ✅ All core features working -- ✅ Responsive design -- ✅ Dark/Light mode -- ✅ EN/DE language switching +### Phase 1 Success (Achieved) +- [x] MECE-compliant categories +- [x] All 60+ anchors in separate files +- [x] Metadata extracted and validated +- [x] Includes working -### Phase 3 Success (Launch) -- ✅ Auto-deployment working -- ✅ Issue templates functional -- ✅ CONTRIBUTING.md clear -- ✅ Website live on GitHub Pages +### Phase 2 Success / MVP (Achieved) +- [x] Website runs locally and on GitHub Pages +- [x] All core features working +- [x] Responsive design +- [x] Dark/Light mode +- [x] EN/DE language switching +- [x] Card grid visualization (replaced original treemap plan) + +### Phase 3 Success / Launch (Achieved) +- [x] Auto-deployment working +- [x] Issue templates functional +- [x] CONTRIBUTING.md clear +- [x] Website live: https://llm-coding.github.io/Semantic-Anchors/ +- [x] 28 E2E tests passing ### Overall Success Metrics - Lighthouse Performance > 90 - WCAG 2.1 AA compliance - Load time < 2s on 3G -- 50% increase in contributions (3 months post-launch) - -## 🎯 Next Steps - -1. **Review Issue #54** (Project Overview) - Pin it! -2. **Start Phase 1** with Issue #36 (MECE analysis) -3. **Sequential execution**: Complete Phase 1 before starting Phase 2 -4. **Regular check-ins**: Update this file as phases complete +- 50% increase in contributions target (3 months post-launch) -## 📞 Contact +## Contact **Maintainer:** @rdmueller -**Architecture:** Claude Sonnet 4.5 (AI-assisted design) +**Architecture:** Claude Sonnet (AI-assisted design) **Repository:** https://github.com/LLM-Coding/Semantic-Anchors +**Live Website:** https://llm-coding.github.io/Semantic-Anchors/ --- -💡 **Tip:** See [Issue #54](https://github.com/LLM-Coding/Semantic-Anchors/issues/54) for the complete project overview. +See [Issue #54](https://github.com/LLM-Coding/Semantic-Anchors/issues/54) for the complete project overview. diff --git a/docs/PRD.md b/docs/PRD.md index eb50475..78f4fa6 100644 --- a/docs/PRD.md +++ b/docs/PRD.md @@ -1,9 +1,9 @@ # Product Requirements Document (PRD) ## Semantic Anchors for LLMs - Website & Repository Redesign -**Version:** 1.0 -**Date:** 2025-02-13 -**Status:** Draft +**Version:** 2.0 +**Date:** 2026-02-20 +**Status:** Implemented --- @@ -12,7 +12,7 @@ The Semantic Anchors repository has grown from a simple README to a comprehensive catalog of 60+ semantic anchors across 10 categories. To better serve its growing user base and enable easier contributions, the project requires: 1. **Website**: A modern, bilingual (EN/DE), responsive website with dark/light mode -2. **Visualization**: Interactive treemap showing MECE-compliant categories and role-based filtering +2. **Visualization**: Card grid with category sections showing MECE-compliant categories and role-based filtering (originally planned as treemap, changed per ADR-005) 3. **Repository Structure**: Modular AsciiDoc files replacing the monolithic README 4. **Contribution Workflow**: Automated issue templates and GitHub Actions/Copilot integration for proposing and adding new anchors @@ -83,7 +83,7 @@ The Semantic Anchors repository has grown from a simple README to a comprehensiv ### Discovery & Navigation - **US-01**: As a software developer, I want to filter anchors by my role so that I see only relevant methodologies -- **US-02**: As a user, I want to visualize anchor categories in a treemap so that I understand the knowledge domain structure +- **US-02**: As a user, I want to browse anchor categories in a structured card grid so that I understand the knowledge domain structure - **US-03**: As a user, I want to search anchors by keyword so that I can quickly find what I need - **US-04**: As a user, I want to see related anchors so that I can explore connected concepts - **US-05**: As a German speaker, I want to view the site in German so that I can understand content in my native language @@ -193,11 +193,11 @@ Based on the current 60+ semantic anchors, the following roles provide meaningfu - **FR-1.8**: Search functionality (client-side) ### FR-2: Visualization -- **FR-2.1**: Interactive treemap of categories (MECE-compliant) +- **FR-2.1**: Card grid with category sections (MECE-compliant, originally planned as treemap, changed per ADR-005) - **FR-2.2**: Role-based filtering (multi-select) -- **FR-2.3**: Click on treemap node to navigate to anchor details -- **FR-2.4**: Visual indication of anchor density per category -- **FR-2.5**: Responsive treemap (adapts to screen size) +- **FR-2.3**: Click on anchor card to open anchor details +- **FR-2.4**: Visual indication of category through color-coded section headers +- **FR-2.5**: Responsive card grid (CSS Grid, adapts to screen size) ### FR-3: Content Structure - **FR-3.1**: Split README.adoc into modular files: @@ -244,7 +244,7 @@ Based on the current 60+ semantic anchors, the following roles provide meaningfu ### NFR-1: Performance - **NFR-1.1**: Page load time < 2 seconds on 3G connection - **NFR-1.2**: Lighthouse score > 90 (Performance, Accessibility, Best Practices, SEO) -- **NFR-1.3**: Treemap rendering < 500ms for 100 anchors +- **NFR-1.3**: Card grid rendering < 500ms for 100 anchors ### NFR-2: Accessibility - **NFR-2.1**: WCAG 2.1 Level AA compliance @@ -294,7 +294,7 @@ Based on the current 60+ semantic anchors, the following roles provide meaningfu ### TR-1: Frontend - Static Site Generator: **Astro** (pending confirmation) - AsciiDoc rendering: **asciidoctor.js** -- Visualization: **D3.js** or **Apache ECharts** for treemap +- Visualization: **CSS Grid** (card grid with category sections, per ADR-005; Apache ECharts treemap was originally planned per ADR-003 but replaced) - Styling: **Tailwind CSS** (responsive, dark mode support) - i18n: **i18next** or framework-native solution - Build: **Vite** (included with Astro) @@ -335,7 +335,7 @@ semantic-anchors/ │ ├── src/ │ │ ├── pages/ │ │ ├── components/ -│ │ │ ├── Treemap.astro +│ │ │ ├── CardGrid.js │ │ │ ├── RoleFilter.astro │ │ │ └── AnchorCard.astro │ │ ├── layouts/ @@ -361,7 +361,7 @@ semantic-anchors/ - [ ] Dark/Light mode - [ ] Responsive design - [ ] All existing anchors migrated to modular structure -- [ ] Basic treemap visualization +- [x] Card grid visualization (replaced treemap per ADR-005) - [ ] Role-based filtering (top 5 roles) - [ ] One issue template ("Propose New Anchor") - [ ] Updated README and CONTRIBUTING guide @@ -408,7 +408,7 @@ The following are explicitly **not** included in this project phase: - [ ] Implement i18n (EN/DE) - [ ] Integrate asciidoctor.js - [ ] Build category/anchor browsing pages -- [ ] Implement treemap visualization +- [x] Implement card grid visualization (replaced treemap per ADR-005) - [ ] Implement role filtering ### Phase 3: Automation & Deployment (Week 6-7) @@ -435,8 +435,8 @@ The following are explicitly **not** included in this project phase: 2. **Anchor Granularity**: One file per anchor or one file per category? - **Recommendation**: Start with one file per category, split to individual files if needed for scalability -3. **Treemap Library**: D3.js (flexible, complex) vs. Apache ECharts (easier, less flexible) vs. Plotly (balanced)? - - **Recommendation**: Apache ECharts for ease + good-enough flexibility +3. **Visualization Library**: D3.js / Apache ECharts treemap was originally planned (ADR-003), but replaced by CSS Grid card grid (ADR-005) due to text truncation, contrast issues, and viewport problems. + - **Decision**: CSS Grid card grid (no external library needed) 4. **Translation Approach**: Human translation or machine translation with review? - **Recommendation**: Human translation for quality, start with UI elements, anchor content can remain EN-only initially diff --git a/docs/arc42/chapters/01_introduction_and_goals.adoc b/docs/arc42/chapters/01_introduction_and_goals.adoc index f131e9a..3fab09b 100644 --- a/docs/arc42/chapters/01_introduction_and_goals.adoc +++ b/docs/arc42/chapters/01_introduction_and_goals.adoc @@ -8,13 +8,13 @@ Das Semantic Anchors Repository ist ein kuratierter Katalog von semantischen Ank **Das Projekt benötigt:** * **Moderne Website**: Interaktive, zweisprachige (EN/DE) Website mit Dark/Light Mode -* **Visualisierung**: Treemap-basierte Darstellung der Kategorien nach MECE-Prinzip +* **Visualisierung**: Card Grid-basierte Darstellung der Kategorien nach MECE-Prinzip (abgelöst ADR-003 Treemap durch ADR-005 Card Grid) * **Skalierbare Struktur**: Aufteilung des monolithischen README.adoc in modulare Files * **Contribution Workflow**: Automatisierte Validierung und Erstellung neuer Anker via GitHub Actions **Kern-Features:** -* Interaktive Treemap-Visualisierung der Kategorien +* Interaktive Card Grid-Visualisierung der Kategorien * Filterung nach Rollen (Software Developer, Architect, Product Owner, etc.) * Volltextsuche über alle Anker * Detailansicht mit AsciiDoc-Rendering @@ -35,7 +35,7 @@ Das Semantic Anchors Repository ist ein kuratierter Katalog von semantischen Ank |2 |**Performance** |ISO 25010 -|Page Load < 2s auf 3G, Treemap-Rendering < 500ms. Langsame Seiten reduzieren Nutzung drastisch. +|Page Load < 2s auf 3G, Card Grid-Rendering < 500ms. Langsame Seiten reduzieren Nutzung drastisch. |3 |**Maintainability** diff --git a/docs/arc42/chapters/03_context_and_scope.adoc b/docs/arc42/chapters/03_context_and_scope.adoc index 29ed398..dc2fd87 100644 --- a/docs/arc42/chapters/03_context_and_scope.adoc +++ b/docs/arc42/chapters/03_context_and_scope.adoc @@ -45,7 +45,7 @@ Rel(browser, website, "Rendert", "") |**Website → Browser** |Ausgabe -|Rendert Treemap, Anker-Details, Navigation +|Rendert Card Grid, Anker-Details, Navigation |HTML, JSON, AsciiDoc (gerendert) |**Contributor → GitHub** @@ -79,7 +79,7 @@ Rel(browser, website, "Rendert", "") Person(user, "Benutzer") Container(spa, "Single Page Application", "Vite, Vanilla JS/Alpine.js", "Statische Website mit Client-Side Routing") -Container(treemap, "Treemap Component", "Apache ECharts", "Interaktive Visualisierung") +Container(cardgrid, "Card Grid Component", "Vanilla JS / CSS Grid", "Interaktive Visualisierung") Container(asciidoc, "AsciiDoc Renderer", "asciidoctor.js", "Rendert .adoc zu HTML") Container(i18n, "i18n Module", "JSON-based", "EN/DE Übersetzungen") @@ -89,7 +89,7 @@ System_Ext(cdn, "CDN (optional)", "jsDelivr, unpkg", "ECharts, Fonts") System_Ext(ghpages, "GitHub Pages", "Static Hosting") Rel(user, spa, "Verwendet", "HTTPS") -Rel(spa, treemap, "Verwendet für Visualisierung", "JavaScript") +Rel(spa, cardgrid, "Verwendet für Visualisierung", "JavaScript") Rel(spa, asciidoc, "Rendert Content", "JavaScript") Rel(spa, i18n, "Übersetzt UI", "JSON") Rel(spa, static, "Lädt Daten", "fetch API") @@ -115,7 +115,7 @@ Rel(ghpages, spa, "Hostet", "") |Main Application, Routing, State Management |JavaScript Modules -|**Treemap Component** +|**Card Grid Component** |Apache ECharts |Kategorien-Visualisierung |JavaScript Object (ECharts Config) @@ -170,7 +170,7 @@ Browser lädt SPA ↓ SPA lädt JSON-Daten (fetch) ↓ -Treemap rendert Visualisierung +Card Grid rendert Visualisierung ↓ User interagiert (Filter, Search, Click) ↓ diff --git a/docs/arc42/chapters/04_solution_strategy.adoc b/docs/arc42/chapters/04_solution_strategy.adoc index b51d224..3bc51e4 100644 --- a/docs/arc42/chapters/04_solution_strategy.adoc +++ b/docs/arc42/chapters/04_solution_strategy.adoc @@ -28,12 +28,12 @@ ✅ Einfaches Refactoring + ❌ Mehr Files zu verwalten -|**Apache ECharts für Treemap** -|Native Treemap-Support. Einfache API. Built-in Theming. Open Source (Apache 2.0). -|✅ Schnelle Implementation + -✅ Professionelle Visualisierung + -✅ Responsive + -❌ ~100KB Bundle-Size +|**Card Grid Visualisierung** _(ersetzt Apache ECharts Treemap, siehe ADR-005)_ +|CSS Grid, keine externe Library. Klare Hierarchie. WCAG-konformer Kontrast. Kein Text-Truncation. +|✅ Kein Text-Truncation + +✅ WCAG-konformer Kontrast + +✅ Responsive Grid + +✅ Kleinere Bundle-Size (kein ECharts) |**Client-Side Rendering** |GitHub Pages ist statisch. Alle Daten als JSON. Browser rendert mit JavaScript. @@ -59,7 +59,7 @@ Vite (Build Tool & Dev Server) ↓ Vanilla JavaScript / Alpine.js (3KB) ↓ -Apache ECharts (Treemap) +CSS Grid (Card Grid Visualisierung) ↓ asciidoctor.js (AsciiDoc → HTML) ↓ @@ -101,7 +101,7 @@ Domain: github.io (oder Custom Domain) |**Usability** |User-Centered Design, Mobile-First, Accessibility -|Responsive Treemap, Keyboard-Navigation, ARIA-Labels, High Contrast +|Responsive Card Grid, Keyboard-Navigation, ARIA-Labels, High Contrast |**Maintainability** |Modular Code, Clear Separation of Concerns, Documentation @@ -126,19 +126,19 @@ Domain: github.io (oder Custom Domain) 1. Metadata (`roles`) ist in JSON verfügbar 2. Filter-UI (Multi-Select Dropdown) 3. Client-Side Filtering: `anchors.filter(a => selectedRoles.some(r => a.roles.includes(r)))` -4. Treemap wird mit gefilterten Daten neu generiert +4. Card Grid wird mit gefilterten Daten aktualisiert 5. localStorage persistiert Filter-Auswahl -==== UC-02: Treemap-Visualisierung +==== UC-02: Card Grid-Visualisierung **Problem:** Nutzer verliert Überblick bei 60+ Ankern. **Lösung:** -1. Kategorien werden als Treemap visualisiert (Apache ECharts) -2. Größe = Anzahl Anker pro Kategorie -3. Farbe = Kategorie-Typ (aus Metadata) -4. Interaktiv: Hover (Tooltip), Click (Navigation) -5. Responsive: Anpassung an Viewport-Größe +1. Kategorien werden als Card Grid mit Category Sections visualisiert +2. Category Headings strukturieren die Übersicht +3. Farbe = Kategorie-Typ (aus Metadata, via CSS Variable) +4. Interaktiv: Click auf Card öffnet Details-Modal +5. Responsive: CSS Grid passt sich an Viewport-Größe an ==== UC-03: Suche @@ -221,7 +221,7 @@ graph LR |Fast Loading, SEO-friendly |**Component-Based Architecture** -|Wiederverwendbare UI-Komponenten (Treemap, Filter, Search) +|Wiederverwendbare UI-Komponenten (Card Grid, Filter, Search) |Maintainability, Testability |**Docs-as-Code** diff --git a/docs/arc42/chapters/05_building_block_view.adoc b/docs/arc42/chapters/05_building_block_view.adoc index 5ecada7..13b86a3 100644 --- a/docs/arc42/chapters/05_building_block_view.adoc +++ b/docs/arc42/chapters/05_building_block_view.adoc @@ -56,7 +56,7 @@ package "Semantic Anchors Website" { [State Manager] as state package "UI Components" { - [Treemap Visualizer] as treemap + [Card Grid Visualizer] as cardgrid [Role Filter] as rolefilter [Search Bar] as search [Anchor Details View] as anchorview @@ -82,12 +82,12 @@ package "Semantic Anchors Website" { app --> router app --> state -router --> treemap +router --> cardgrid router --> anchorview state --> rolefilter state --> search -treemap --> api +cardgrid --> api rolefilter --> api search --> api anchorview --> asciidoc @@ -157,7 +157,7 @@ class App { * Output: Component Rendering **Routes:** -* `/` → Home (Treemap) +* `/` → Home (Card Grid) * `/anchor/:id` → Anchor Details * `/category/:id` → Category View * `/role/:id` → Role View @@ -191,50 +191,45 @@ class App { } ``` -==== Treemap Visualizer +==== Card Grid Visualizer **Verantwortung:** -* Rendert Kategorien als Treemap -* Interaktive Tooltips -* Click-Navigation +* Rendert Kategorien als Card Grid mit Category Sections +* Responsive Layout via CSS Grid (Karte öffnet Details-Modal) +* Responsive Layout via CSS Grid -**Technologie:** Apache ECharts +**Technologie:** Vanilla JavaScript, CSS Grid (keine externe Library) + +**Anmerkung:** Ersetzt die ursprünglich geplante Apache ECharts Treemap (ADR-003) aufgrund fundamentaler Usability-Probleme (Text-Truncation, schlechter Kontrast, Viewport-Cut-off). Entscheidung dokumentiert in ADR-005. **Implementierung:** ```javascript -// src/components/Treemap.js -import * as echarts from 'echarts/core'; -import { TreemapChart } from 'echarts/charts'; -import { CanvasRenderer } from 'echarts/renderers'; - -echarts.use([TreemapChart, CanvasRenderer]); +// src/components/card-grid.js +export class CardGrid { + constructor(containerEl, data) { + this.container = containerEl; + this.render(data); + } -export class Treemap { - constructor(elementId, data) { - this.chart = echarts.init(document.getElementById(elementId)); - this.setData(data); + render(categories) { + this.container.innerHTML = categories + .map(cat => this.renderSection(cat)) + .join(''); } - setData(data) { - const option = { - series: [{ - type: 'treemap', - data: this.transformData(data) - }] - }; - this.chart.setOption(option); + renderSection(category) { + const cards = category.anchors.map(a => this.renderCard(a)).join(''); + return `
+

${category.name}

+
${cards}
+
`; } - transformData(categories) { - return categories.map(cat => ({ - name: cat.name, - value: cat.anchorCount, - children: cat.anchors.map(anchor => ({ - name: anchor.name, - value: 1, - anchorId: anchor.id - })) - })); + renderCard(anchor) { + return `
+

${anchor.name}

+

${anchor.description || ''}

+
`; } } ``` diff --git a/docs/arc42/chapters/06_runtime_view.adoc b/docs/arc42/chapters/06_runtime_view.adoc index 069ebfb..c0c7fcd 100644 --- a/docs/arc42/chapters/06_runtime_view.adoc +++ b/docs/arc42/chapters/06_runtime_view.adoc @@ -25,8 +25,8 @@ JSON --> API: Roles API -> JSON: GET /data/anchors.json JSON --> API: Anchors (Metadata only) API --> App: Daten geladen -App -> App: Rendert Treemap -App --> User: Zeigt Homepage mit Treemap +App -> App: Rendert Card Grid +App --> User: Zeigt Homepage mit Card Grid @enduml ---- @@ -40,15 +40,15 @@ App --> User: Zeigt Homepage mit Treemap actor User participant "Role Filter UI" as Filter participant "State Manager" as State -participant "Treemap Component" as Treemap +participant "Card Grid Component" as CardGrid participant "LocalStorage" as LS User -> Filter: Wählt "Software Developer" Filter -> State: updateFilters({roles: ['software-developer']}) State -> State: Filtert Anchors -State -> Treemap: update(filteredAnchors) -Treemap -> Treemap: Generiert neue Treemap-Daten -Treemap --> User: Zeigt gefilterte Treemap +State -> CardGrid: update(filteredAnchors) +CardGrid -> CardGrid: Filtert und blendet Cards/Sections +CardGrid --> User: Zeigt gefilterte Card Grid State -> LS: Speichert Filter LS --> State: Gespeichert @enduml @@ -86,7 +86,7 @@ Results --> User: Zeigt Suchergebnisse ---- @startuml actor User -participant "Treemap/Search" as UI +participant "Card Grid/Search" as UI participant Router participant "Anchor View" as View participant "AsciiDoc Renderer" as Renderer @@ -115,7 +115,7 @@ View --> User: Zeigt Anker-Details actor User participant "Theme Toggle" as Toggle participant "Theme Manager" as Manager -participant "Treemap" as Chart +participant "Card Grid" as Chart participant LocalStorage as LS User -> Toggle: Klick auf Theme Button @@ -125,7 +125,7 @@ Manager -> Manager: document.body.className = currentTheme Manager -> Chart: updateTheme(currentTheme) Chart -> Chart: Dispose old chart Chart -> Chart: Init with new theme -Chart --> User: Treemap mit neuem Theme +Chart --> User: Card Grid mit neuem Theme Manager -> LS: Save theme preference LS --> Manager: Gespeichert @enduml diff --git a/docs/arc42/chapters/08_crosscutting_concepts.adoc b/docs/arc42/chapters/08_crosscutting_concepts.adoc index 05ff117..27256a8 100644 --- a/docs/arc42/chapters/08_crosscutting_concepts.adoc +++ b/docs/arc42/chapters/08_crosscutting_concepts.adoc @@ -16,7 +16,7 @@ Siehe Kapitel 5 (Bausteinsicht) - Datenmodell mit Anchor, Category, Role, Propon **Interaction Patterns:** -* **Hover**: Tooltips in Treemap +* **Hover**: Hover-Effekt auf Anchor Cards (Elevation, Border-Highlight) * **Click**: Navigation zu Details * **Keyboard**: Tab-Navigation, Enter für Aktivierung, Escape für Schließen * **Touch**: Tap-friendly Buttons (min. 44x44px) @@ -100,7 +100,7 @@ Content-Security-Policy: **Optimierungen:** -* **Code-Splitting**: Lazy Load Treemap, AsciiDoc Renderer +* **Code-Splitting**: Lazy Load AsciiDoc Renderer * **Tree-Shaking**: Nur benötigte ECharts-Module * **Image Optimization**: WebP, Responsive Images * **Font Optimization**: Subset Fonts, font-display: swap @@ -112,7 +112,7 @@ Content-Security-Policy: * API Fetch Errors → Retry mit Exponential Backoff * AsciiDoc Render Errors → Fallback zu Plain Text -* Treemap Render Errors → Fallback zu Liste +* Card Grid Render Errors → Fallback zu einfacher Liste **User-Facing Errors:** @@ -167,7 +167,7 @@ const logger = { **Test-Pyramide:** * **Unit Tests**: Utils, State Manager, Search Engine (Vitest) -* **Component Tests**: Treemap, Filter, Search Bar (Vitest + @testing-library) +* **Component Tests**: Card Grid, Filter, Search Bar (Vitest + @testing-library) * **E2E Tests**: User Flows (Playwright) * **Accessibility Tests**: axe-core, Lighthouse CI @@ -220,8 +220,8 @@ VITE_ENABLE_ANALYTICS=true -
- +
+
``` diff --git a/docs/arc42/chapters/09_architecture_decisions.adoc b/docs/arc42/chapters/09_architecture_decisions.adoc index 0c454b3..ad60f58 100644 --- a/docs/arc42/chapters/09_architecture_decisions.adoc +++ b/docs/arc42/chapters/09_architecture_decisions.adoc @@ -22,12 +22,17 @@ Dieses Kapitel referenziert die detaillierten Architecture Decision Records (ADR |link:../specs/adrs/adr-003-treemap-library.adoc[ADR-003] |**Apache ECharts** für Treemap |Native Treemap-Unterstützung, exzellente DX, Built-in Theming, Open Source -|Accepted +|Superseded by ADR-005 |link:../specs/adrs/adr-004-one-file-per-anchor.adoc[ADR-004] |**Ein File pro Anchor** + Includes |Minimale Merge-Konflikte, klare Git-History, skaliert perfekt |Accepted + +|link:../specs/adrs/adr-005-card-grid-visualization.adoc[ADR-005] +|**Card Grid** statt Treemap für Visualisierung +|Löst fundamentale Usability-Probleme (Text-Truncation, Kontrast, Viewport), größerer Pugh-Score (+137) +|Accepted |=== === ADR-001: Vite als Static Site Generator @@ -154,14 +159,42 @@ docs/ **Details:** link:../specs/adrs/adr-004-one-file-per-anchor.adoc[ADR-004] +=== ADR-005: Card Grid statt Treemap für Visualisierung + +**Alternative Optionen:** Treemap verbessern (Baseline), Tabs+Liste, Tag Cloud, Table View + +**Pugh-Matrix Ergebnis:** + +* Card Grid: **+137 Punkte** +* Treemap verbessern: 0 (Baseline) +* Table View: +100 +* Tag Cloud: +81 +* Tabs+Liste: +51 + +**Kern-Argumente:** + +* ✅ Kein Text-Truncation (voller Platz für Labels) +* ✅ WCAG-konformer Kontrast (Tailwind colors, kontrollierbar) +* ✅ Responsive Layout (kein Viewport-Cut-off) +* ✅ Kleinere Bundle-Size (kein ECharts ~300KB gespart) +* ✅ Semantic HTML, bessere Accessibility +* ❌ Mehr vertikaler Platz, Scrolling erforderlich +* ❌ Weniger visuell beeindruckend als Treemap + +**Hintergrund:** Playwright-Testing zeigte fundamentale Usability-Probleme bei der Treemap: +Issue #59 (Text-Truncation), Issue #60 (Map-Cut-off), Issue #61 (Poor Contrast). + +**Details:** link:../specs/adrs/adr-005-card-grid-visualization.adoc[ADR-005] + === Zukünftige Entscheidungen **Offene Punkte:** -* **ADR-005**: Suchindex-Implementierung (Client-Side vs. Build-Time) -* **ADR-006**: Analytics-Lösung (Plausible vs. Simple Analytics vs. Keine) -* **ADR-007**: Service Worker für Offline-Support (Ja/Nein) -* **ADR-008**: Testing-Framework für E2E (Playwright vs. Cypress) +* **ADR-005**: Card Grid Visualisierung - Accepted (ersetzt ADR-003 Treemap) +* **ADR-006**: Suchindex-Implementierung (Client-Side vs. Build-Time) +* **ADR-007**: Analytics-Lösung (Plausible vs. Simple Analytics vs. Keine) +* **ADR-008**: Service Worker für Offline-Support (Ja/Nein) +* **ADR-009**: Testing-Framework für E2E (Playwright vs. Cypress) === Entscheidungsprozess diff --git a/docs/arc42/chapters/10_quality_requirements.adoc b/docs/arc42/chapters/10_quality_requirements.adoc index 2c8639a..cac3c0c 100644 --- a/docs/arc42/chapters/10_quality_requirements.adoc +++ b/docs/arc42/chapters/10_quality_requirements.adoc @@ -13,7 +13,7 @@ Qualität ├── Performance (Prio 2) │ ├── Load Time: < 2s on 3G │ ├── Time to Interactive: < 3.5s -│ └── Treemap Rendering: < 500ms +│ └── Card Grid Rendering: < 500ms │ ├── Maintainability (Prio 3) │ ├── Modularity: Neue Anker hinzufügen in < 10 Min @@ -83,7 +83,7 @@ Qualität |Antwort |1. Dropdown öffnet mit allen Rollen + 2. Wählt "Architect" + -3. Treemap aktualisiert sich < 100ms + +3. Card Grid aktualisiert sich < 100ms + 4. Zeigt nur relevante Anker |Metrik @@ -113,14 +113,14 @@ Qualität |Antwort |1. HTML lädt in < 500ms + 2. Critical CSS inline + -3. Treemap lädt lazy in < 2s total + +3. Card Grid lädt in < 2s total + 4. Seite ist interaktiv |Metrik |Time to Interactive < 2s |=== -**QS-P2: Responsive Treemap** +**QS-P2: Responsive Card Grid** [cols="1,3", options="header"] |=== @@ -133,19 +133,19 @@ Qualität |Wählt 2 zusätzliche Rollen |Artefakt -|Treemap Component +|Card Grid Component |Umgebung |Desktop, normale Hardware |Antwort |1. Filter-State aktualisiert + -2. Treemap-Daten neu berechnet + -3. ECharts rendert + -4. Neue Treemap sichtbar +2. Card Grid-Daten neu gefiltert + +3. CSS Grid rendert + +4. Neues Card Grid sichtbar |Metrik -|< 500ms vom Klick bis neue Treemap +|< 500ms vom Klick bis neue Card Grid |=== ==== Maintainability @@ -247,18 +247,18 @@ Qualität |Blinde Person nutzt Screen Reader (NVDA, JAWS) |Stimulus -|Navigiert zu Treemap +|Navigiert zu Card Grid |Artefakt -|Treemap Component +|Card Grid Component |Umgebung |Windows, NVDA Screen Reader |Antwort -|1. Screen Reader liest "Treemap visualization of categories" + -2. Kategorien haben ARIA-Labels mit Namen und Anzahl + -3. Navigation zu Details ist verständlich angekündigt +|1. Screen Reader liest "Card grid visualization of categories" + +2. Category Sections haben ARIA-Labels mit Namen + +3. Anchor Cards sind mit Namen und Beschreibung erreichbar |Metrik |Alle Informationen für Screen Reader zugänglich @@ -310,7 +310,7 @@ Qualität |> 90 |Lighthouse CI -|**Performance - Treemap** +|**Performance - Card Grid** |Rendering Time |< 500ms |Performance.measure() diff --git a/docs/arc42/chapters/11_risks_and_technical_debt.adoc b/docs/arc42/chapters/11_risks_and_technical_debt.adoc index aae9339..1f90492 100644 --- a/docs/arc42/chapters/11_risks_and_technical_debt.adoc +++ b/docs/arc42/chapters/11_risks_and_technical_debt.adoc @@ -11,8 +11,8 @@ |Bundle-Size zu groß |Mittel |Mittel -|ECharts + asciidoctor.js könnten Bundle > 500KB machen, langsamer Load auf Mobile -|Tree-Shaking, Code-Splitting, CDN für ECharts, Lazy Loading +|asciidoctor.js könnte Bundle > 500KB machen, langsamer Load auf Mobile (ECharts wurde durch Card Grid ersetzt und entfernt) +|Tree-Shaking, Code-Splitting, Lazy Loading AsciiDoc Renderer |**R-02** |SEO-Probleme (Client-Side Rendering) @@ -32,8 +32,8 @@ |Skalierung >200 Anker |Niedrig |Hoch -|Treemap könnte bei sehr vielen Ankern unleserlich werden -|Pagination, Collapsible Categories, Zoom-Feature in ECharts +|Card Grid könnte bei sehr vielen Ankern sehr lang werden +|Pagination, Collapsible Category Sections, Lazy Loading |**R-05** |GitHub Pages Downtime diff --git a/docs/arc42/chapters/12_glossary.adoc b/docs/arc42/chapters/12_glossary.adoc index ceb9216..8aff8d0 100644 --- a/docs/arc42/chapters/12_glossary.adoc +++ b/docs/arc42/chapters/12_glossary.adoc @@ -15,7 +15,7 @@ |Etablierter Begriff, Methodologie oder Framework, der als Referenzpunkt in der LLM-Kommunikation dient (z.B. "TDD, London School", "SOLID Principles") |**Apache ECharts** -|Open-Source Visualisierungs-Library (Apache 2.0) für interaktive Charts, inkl. Treemap +|Open-Source Visualisierungs-Library (Apache 2.0) für interaktive Charts, inkl. Treemap. Wurde ursprünglich für dieses Projekt geplant (ADR-003), aber durch Card Grid ersetzt (ADR-005). |**arc42** |Standardisiertes Template für Software-Architekturdokumentation mit 12 Kapiteln @@ -129,7 +129,10 @@ |Build-Optimierung: Entfernung von ungenutztem Code aus Bundle |**Treemap** -|Hierarchische Visualisierung als verschachtelte Rechtecke, Größe = Wert +|Hierarchische Visualisierung als verschachtelte Rechtecke, Größe = Wert. In diesem Projekt ursprünglich geplant (ADR-003), aber aufgrund von Usability-Problemen (Text-Truncation, Kontrast, Viewport-Cut-off) durch ein Card Grid ersetzt (ADR-005). + +|**Card Grid** +|Visualisierungsform mit Category Sections und Anchor Cards in einem CSS Grid-Layout. In diesem Projekt als Ersatz für die Treemap implementiert (ADR-005). Bietet bessere Lesbarkeit, WCAG-konformen Kontrast und responsives Layout. |**Vite** |Moderner Build-Tool und Dev-Server für Frontend-Projekte, sehr schnell durch native ES Modules diff --git a/website/.prettierrc b/website/.prettierrc new file mode 100644 index 0000000..238d4d9 --- /dev/null +++ b/website/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "printWidth": 100 +} diff --git a/website/eslint.config.js b/website/eslint.config.js new file mode 100644 index 0000000..4cf55bf --- /dev/null +++ b/website/eslint.config.js @@ -0,0 +1,73 @@ +import js from '@eslint/js' +import prettierConfig from 'eslint-config-prettier' + +const browserGlobals = { + window: 'readonly', + document: 'readonly', + console: 'readonly', + navigator: 'readonly', + fetch: 'readonly', + URL: 'readonly', + URLSearchParams: 'readonly', + history: 'readonly', + location: 'readonly', + localStorage: 'readonly', + sessionStorage: 'readonly', + setTimeout: 'readonly', + clearTimeout: 'readonly', + setInterval: 'readonly', + clearInterval: 'readonly', + HTMLElement: 'readonly', + Event: 'readonly', + CustomEvent: 'readonly', + KeyboardEvent: 'readonly', + HashChangeEvent: 'readonly', + MutationObserver: 'readonly', + IntersectionObserver: 'readonly', + requestAnimationFrame: 'readonly', + performance: 'readonly', +} + +export default [ + js.configs.recommended, + prettierConfig, + { + languageOptions: { + ecmaVersion: 2024, + sourceType: 'module', + globals: browserGlobals, + }, + rules: { + 'no-unused-vars': ['warn', { argsIgnorePattern: '^_' }], + 'no-console': ['warn', { allow: ['warn', 'error'] }], + }, + ignores: [ + 'node_modules/**', + 'dist/**', + 'playwright-report/**', + '.lighthouseci/**', + 'test-results/**', + ], + }, + { + files: ['**/*.test.js', 'tests/**/*.js', 'playwright.config.js'], + languageOptions: { + globals: { + ...browserGlobals, + global: 'writable', + describe: 'readonly', + it: 'readonly', + test: 'readonly', + expect: 'readonly', + beforeEach: 'readonly', + afterEach: 'readonly', + beforeAll: 'readonly', + afterAll: 'readonly', + vi: 'readonly', + }, + }, + rules: { + 'no-console': 'off', + }, + }, +] diff --git a/website/package-lock.json b/website/package-lock.json index d637a76..ac73540 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -13,10 +13,14 @@ "echarts": "^6.0.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@lhci/cli": "^0.14.0", "@playwright/test": "^1.58.2", "@tailwindcss/vite": "^4.1.18", + "eslint": "^10.0.0", + "eslint-config-prettier": "^10.1.8", "jsdom": "^28.0.0", + "prettier": "^3.8.1", "tailwindcss": "^4.1.18", "vite": "^7.3.1", "vitest": "^4.0.18" @@ -179,7 +183,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" }, @@ -220,7 +223,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=20.19.0" } @@ -667,6 +669,134 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.1.tgz", + "integrity": "sha512-uVSdg/V4dfQmTjJzR0szNczjOH/J+FyUMMjYtr07xFRXR7EDf9i1qdxrD0VusZH9knj1/ecxzCQQxyic5NzAiA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.1", + "debug": "^4.3.1", + "minimatch": "^10.1.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.2.tgz", + "integrity": "sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz", + "integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.1.tgz", + "integrity": "sha512-P9cq2dpr+LU8j3qbLygLcSZrl2/ds/pUpfnHNNuk5HW7mnngHs+6WSq5C9mO3rqRX8A1poxqLTC9cu0KOyJlBg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.6.0.tgz", + "integrity": "sha512-bIZEUzOI1jkhviX2cp5vNyXQc6olzb2ohewQubuYlMXZ2Q/XjBO0x0XhGPvc9fjSIiUN0vw+0hq53BJ4eQSJKQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.1.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@exodus/bytes": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.14.1.tgz", @@ -776,6 +906,58 @@ "dev": true, "license": "0BSD" }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -1533,16 +1715,6 @@ "node": ">= 6.0.0" } }, - "node_modules/@sentry/node/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/@sentry/node/node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -1911,6 +2083,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -1918,6 +2097,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "25.2.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", @@ -2074,6 +2260,29 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -2084,6 +2293,23 @@ "node": ">= 14" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -2193,10 +2419,13 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.3.tgz", + "integrity": "sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } }, "node_modules/bare-events": { "version": "2.8.2", @@ -2379,12 +2608,15 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.2.tgz", + "integrity": "sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" } }, "node_modules/buffer": { @@ -2681,13 +2913,6 @@ "dev": true, "license": "MIT" }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, "node_modules/configstore": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", @@ -2730,13 +2955,17 @@ } }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cookie-signature": { @@ -2746,6 +2975,21 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -2864,6 +3108,13 @@ "dev": true, "license": "MIT" }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/define-lazy-prop": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", @@ -2925,8 +3176,7 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/dot-prop": { "version": "5.3.0", @@ -3170,29 +3420,229 @@ "dev": true, "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.0.0.tgz", + "integrity": "sha512-O0piBKY36YSJhlFSG8p9VUdPV/SxxS4FYDWVpr/9GJuMaepzwlf4J8I4ov1b+ySQfDTPhc3DtLaxcT1fN0yqCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.0", + "@eslint/config-helpers": "^0.5.2", + "@eslint/core": "^1.1.0", + "@eslint/plugin-kit": "^0.6.0", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.0", + "eslint-visitor-keys": "^5.0.0", + "espree": "^11.1.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.1.1", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.0.tgz", + "integrity": "sha512-CkWE42hOJsNj9FJRaoMX9waUFYhqY4jmyLFdAdzZr6VaCg3ynLYx4WnOdkaIifGfH4gsUcBTn4OZbHXkpLD0FQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.0.tgz", + "integrity": "sha512-A0XeIi7CXU7nPlfHS9loMYEKxUaONu/hTEzHTGba9Huu94Cq1hPivf+DE5erJozZOky0LfvXAyrV/tcswpLI0Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "node_modules/espree": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.0.tgz", + "integrity": "sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": ">=6.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, - "optionalDependencies": { - "source-map": "~0.6.1" + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -3209,6 +3659,32 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estraverse": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", @@ -3382,6 +3858,13 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-fifo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", @@ -3389,6 +3872,20 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -3430,6 +3927,19 @@ "node": ">=4" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -3480,6 +3990,27 @@ "node": ">=8" } }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3631,6 +4162,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3793,6 +4337,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/image-ssim": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/image-ssim/-/image-ssim-0.2.0.tgz", @@ -3908,6 +4462,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", @@ -3918,6 +4482,19 @@ "node": ">=4" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -3955,6 +4532,13 @@ "node": ">=8" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/isomorphic-fetch": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", @@ -4013,7 +4597,6 @@ "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.7.6", @@ -4048,6 +4631,51 @@ } } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lighthouse": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/lighthouse/-/lighthouse-12.1.0.tgz", @@ -4804,15 +5432,18 @@ } }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "license": "ISC", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.2.tgz", + "integrity": "sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^5.0.2" }, "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { @@ -4878,6 +5509,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/negotiator": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", @@ -5030,6 +5668,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -5162,6 +5818,16 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -5196,7 +5862,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5293,6 +5958,32 @@ "node": ">=4" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -5514,17 +6205,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/rimraf/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5547,19 +6227,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/robots-parser": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/robots-parser/-/robots-parser-3.0.1.tgz", @@ -5768,6 +6435,29 @@ "dev": true, "license": "ISC" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -6052,8 +6742,7 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tapable": { "version": "2.3.0", @@ -6214,17 +6903,6 @@ "node": ">=6" } }, - "node_modules/tmp/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/tmp/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -6247,19 +6925,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/tmp/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/tmp/node_modules/rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -6326,6 +6991,19 @@ "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "license": "0BSD" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -6410,6 +7088,16 @@ "node": ">=8.11" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/urlpattern-polyfill": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", @@ -6459,7 +7147,6 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -6662,6 +7349,22 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", @@ -6686,6 +7389,16 @@ "node": ">=8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -6967,6 +7680,19 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zod": { "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", diff --git a/website/package.json b/website/package.json index 960a866..66a80dc 100644 --- a/website/package.json +++ b/website/package.json @@ -14,13 +14,21 @@ "test:e2e:ui": "playwright test --ui", "test:e2e:headed": "playwright test --headed", "test:lighthouse": "lhci autorun", - "test:all": "npm run test && npm run test:e2e" + "test:all": "npm run test && npm run test:e2e", + "lint": "eslint src/", + "lint:fix": "eslint src/ --fix", + "format": "prettier --write src/", + "format:check": "prettier --check src/" }, "devDependencies": { + "@eslint/js": "^10.0.1", "@lhci/cli": "^0.14.0", "@playwright/test": "^1.58.2", "@tailwindcss/vite": "^4.1.18", + "eslint": "^10.0.0", + "eslint-config-prettier": "^10.1.8", "jsdom": "^28.0.0", + "prettier": "^3.8.1", "tailwindcss": "^4.1.18", "vite": "^7.3.1", "vitest": "^4.0.18" @@ -29,5 +37,9 @@ "@asciidoctor/core": "^3.0.4", "@tailwindcss/typography": "^0.5.19", "echarts": "^6.0.0" + }, + "overrides": { + "minimatch": ">=10.2.1", + "cookie": ">=0.7.0" } } diff --git a/website/src/components/anchor-modal.js b/website/src/components/anchor-modal.js index 1345953..8fdc8e4 100644 --- a/website/src/components/anchor-modal.js +++ b/website/src/components/anchor-modal.js @@ -26,7 +26,8 @@ export function createModal() { const modal = document.createElement('div') modal.id = 'anchor-modal' - modal.className = 'fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50 p-4' + modal.className = + 'fixed inset-0 bg-black bg-opacity-50 hidden items-center justify-center z-50 p-4' modal.innerHTML = `
@@ -117,7 +118,9 @@ export async function loadAnchorContent(anchorId) { if (currentLang !== 'en') { // Try fetching language-specific anchor file - response = await fetch(`${import.meta.env.BASE_URL}docs/anchors/${anchorId}.${currentLang}.adoc`) + response = await fetch( + `${import.meta.env.BASE_URL}docs/anchors/${anchorId}.${currentLang}.adoc` + ) // If language-specific file not found, fallback to English if (!response.ok) { @@ -139,8 +142,8 @@ export async function loadAnchorContent(anchorId) { safe: 'secure', attributes: { showtitle: true, - sectanchors: true - } + sectanchors: true, + }, }) // Extract title from HTML or use anchor ID @@ -151,12 +154,12 @@ export async function loadAnchorContent(anchorId) { contentEl.innerHTML = String(htmlContent) // Auto-expand all collapsible sections - contentEl.querySelectorAll('details').forEach(details => { + contentEl.querySelectorAll('details').forEach((details) => { details.setAttribute('open', '') }) // Convert internal AsciiDoc cross-reference links to router navigation - contentEl.querySelectorAll('a[href^="#"]').forEach(link => { + contentEl.querySelectorAll('a[href^="#"]').forEach((link) => { const href = link.getAttribute('href') // Only process simple hash links (cross-references), not hash routes if (href && href.startsWith('#') && !href.startsWith('#/')) { @@ -168,7 +171,6 @@ export async function loadAnchorContent(anchorId) { }) } }) - } catch (error) { console.error('Error loading anchor content:', error) titleEl.textContent = 'Error' @@ -203,7 +205,7 @@ async function shareAnchor(anchorId, title) { try { await navigator.share({ title: text, - url: url + url: url, }) return } catch (err) { diff --git a/website/src/components/anchor-modal.test.js b/website/src/components/anchor-modal.test.js index b1a99ed..4eaa818 100644 --- a/website/src/components/anchor-modal.test.js +++ b/website/src/components/anchor-modal.test.js @@ -9,7 +9,7 @@ describe('anchor-modal', () => { beforeEach(() => { dom = new JSDOM('', { - url: 'http://localhost' + url: 'http://localhost', }) document = dom.window.document window = dom.window @@ -103,7 +103,7 @@ describe('anchor-modal', () => { it('should open modal when called', async () => { global.fetch.mockResolvedValue({ ok: true, - text: async () => '= Test Anchor\n\nTest content' + text: async () => '= Test Anchor\n\nTest content', }) await showAnchorDetails('test-anchor') @@ -114,14 +114,12 @@ describe('anchor-modal', () => { it('should fetch anchor content', async () => { global.fetch.mockResolvedValue({ ok: true, - text: async () => '= Test Anchor\n\nTest content' + text: async () => '= Test Anchor\n\nTest content', }) showAnchorDetails('test-anchor') - expect(global.fetch).toHaveBeenCalledWith( - expect.stringContaining('test-anchor.adoc') - ) + expect(global.fetch).toHaveBeenCalledWith(expect.stringContaining('test-anchor.adoc')) }) it('should handle fetch errors gracefully', async () => { @@ -142,7 +140,7 @@ describe('anchor-modal', () => { global.fetch.mockResolvedValue({ ok: false, status: 404, - text: async () => 'Not found' + text: async () => 'Not found', }) await showAnchorDetails('nonexistent-anchor') diff --git a/website/src/components/card-grid.js b/website/src/components/card-grid.js index 21d0b1b..1026c79 100644 --- a/website/src/components/card-grid.js +++ b/website/src/components/card-grid.js @@ -18,14 +18,14 @@ const CATEGORY_COLORS = { 'design-principles': '#91cc75', 'development-workflow': '#fac858', 'dialogue-interaction': '#ee6666', - 'documentation': '#73c0de', - 'meta': '#3ba272', + documentation: '#73c0de', + meta: '#3ba272', 'problem-solving': '#fc8452', 'requirements-engineering': '#9a60b4', 'software-architecture': '#ea7ccc', 'statistical-methods': '#48b8d0', 'strategic-planning': '#c23531', - 'testing-quality': '#5470c6' + 'testing-quality': '#5470c6', } /** @@ -36,14 +36,14 @@ const CATEGORY_ICONS = { 'design-principles': '🎯', 'development-workflow': '⚙️', 'dialogue-interaction': '🤝', - 'documentation': '📚', - 'meta': '🔍', + documentation: '📚', + meta: '🔍', 'problem-solving': '💡', 'requirements-engineering': '📋', 'software-architecture': '🏗️', 'statistical-methods': '📊', 'strategic-planning': '🎯', - 'testing-quality': '🧪' + 'testing-quality': '🧪', } /** @@ -54,7 +54,7 @@ export function renderCardGrid(categories, anchors) { return `
- ${categories.map(category => renderCategorySection(category, anchors)).join('')} + ${categories.map((category) => renderCategorySection(category, anchors)).join('')}
` } @@ -63,8 +63,8 @@ export function renderCardGrid(categories, anchors) { * Render a single category section with its anchors */ function renderCategorySection(category, allAnchors) { - const categoryAnchors = allAnchors.filter(anchor => - anchor.categories && anchor.categories.includes(category.id) + const categoryAnchors = allAnchors.filter( + (anchor) => anchor.categories && anchor.categories.includes(category.id) ) if (categoryAnchors.length === 0) return '' @@ -81,7 +81,7 @@ function renderCategorySection(category, allAnchors) {
- ${categoryAnchors.map(anchor => renderAnchorCard(anchor, color)).join('')} + ${categoryAnchors.map((anchor) => renderAnchorCard(anchor, color)).join('')}
` @@ -138,28 +138,40 @@ function renderAnchorCard(anchor, categoryColor) {
- ${anchor.proponents && anchor.proponents.length > 0 ? ` + ${ + anchor.proponents && anchor.proponents.length > 0 + ? `

${escapeHtml(anchor.proponents.slice(0, 2).join(', '))}

- ` : ''} + ` + : '' + }
- ${rolesCount > 0 ? ` + ${ + rolesCount > 0 + ? ` ${rolesCount} ${roleText} - ` : ''} + ` + : '' + } - ${anchor.tags && anchor.tags.length > 0 ? ` + ${ + anchor.tags && anchor.tags.length > 0 + ? ` ${anchor.tags.length} ${tagsText} - ` : ''} + ` + : '' + }
@@ -207,7 +219,7 @@ export function initCardGrid() { if (card) { const anchorId = card.dataset.anchor const event = new CustomEvent('anchor-selected', { - detail: { anchorId } + detail: { anchorId }, }) document.dispatchEvent(event) } @@ -222,7 +234,7 @@ export function initCardGrid() { e.preventDefault() const anchorId = card.dataset.anchor const event = new CustomEvent('anchor-selected', { - detail: { anchorId } + detail: { anchorId }, }) document.dispatchEvent(event) } @@ -238,16 +250,17 @@ export function filterCardsByRole(roleId) { const cards = document.querySelectorAll('.anchor-card') const sections = document.querySelectorAll('.category-section') - cards.forEach(card => { + cards.forEach((card) => { const roles = card.dataset.roles.split(',').filter(Boolean) const matches = !roleId || roles.includes(roleId) card.style.display = matches ? 'block' : 'none' }) // Hide empty sections - sections.forEach(section => { - const visibleCards = Array.from(section.querySelectorAll('.anchor-card')) - .filter(card => card.style.display !== 'none') + sections.forEach((section) => { + const visibleCards = Array.from(section.querySelectorAll('.anchor-card')).filter( + (card) => card.style.display !== 'none' + ) section.style.display = visibleCards.length > 0 ? 'block' : 'none' }) } @@ -258,7 +271,7 @@ export function filterCardsByRole(roleId) { export function filterCardsBySearch(query) { if (!query || query.trim() === '') { // Show all - document.querySelectorAll('.anchor-card, .category-section').forEach(el => { + document.querySelectorAll('.anchor-card, .category-section').forEach((el) => { el.style.display = 'block' }) return @@ -268,22 +281,22 @@ export function filterCardsBySearch(query) { const cards = document.querySelectorAll('.anchor-card') const sections = document.querySelectorAll('.category-section') - cards.forEach(card => { + cards.forEach((card) => { const title = card.querySelector('.anchor-card-title').textContent.toLowerCase() const tags = card.dataset.tags.toLowerCase() const anchorId = card.dataset.anchor.toLowerCase() - const matches = title.includes(lowerQuery) || - tags.includes(lowerQuery) || - anchorId.includes(lowerQuery) + const matches = + title.includes(lowerQuery) || tags.includes(lowerQuery) || anchorId.includes(lowerQuery) card.style.display = matches ? 'block' : 'none' }) // Hide empty sections - sections.forEach(section => { - const visibleCards = Array.from(section.querySelectorAll('.anchor-card')) - .filter(card => card.style.display !== 'none') + sections.forEach((section) => { + const visibleCards = Array.from(section.querySelectorAll('.anchor-card')).filter( + (card) => card.style.display !== 'none' + ) section.style.display = visibleCards.length > 0 ? 'block' : 'none' }) } @@ -305,7 +318,7 @@ export function applyCardFilters(roleId, searchQuery) { let visibleCount = 0 - cards.forEach(card => { + cards.forEach((card) => { // Role filter const roles = card.dataset.roles.split(',').filter(Boolean) const roleMatch = !roleId || roles.includes(roleId) @@ -321,9 +334,8 @@ export function applyCardFilters(roleId, searchQuery) { const title = card.querySelector('.anchor-card-title').textContent.toLowerCase() const tags = card.dataset.tags.toLowerCase() const anchorId = card.dataset.anchor.toLowerCase() - searchMatch = title.includes(lowerQuery) || - tags.includes(lowerQuery) || - anchorId.includes(lowerQuery) + searchMatch = + title.includes(lowerQuery) || tags.includes(lowerQuery) || anchorId.includes(lowerQuery) } } @@ -333,9 +345,10 @@ export function applyCardFilters(roleId, searchQuery) { }) // Hide empty sections - sections.forEach(section => { - const visibleCards = Array.from(section.querySelectorAll('.anchor-card')) - .filter(card => card.style.display !== 'none') + sections.forEach((section) => { + const visibleCards = Array.from(section.querySelectorAll('.anchor-card')).filter( + (card) => card.style.display !== 'none' + ) section.style.display = visibleCards.length > 0 ? 'block' : 'none' }) diff --git a/website/src/components/doc-page.js b/website/src/components/doc-page.js index fb4d607..28cdc2a 100644 --- a/website/src/components/doc-page.js +++ b/website/src/components/doc-page.js @@ -15,7 +15,7 @@ async function getAsciidoctor() { * @param {string} title - Page title for header * @returns {string} HTML string */ -export function renderDocPage(title) { +export function renderDocPage(_title) { return `
@@ -64,24 +64,24 @@ export async function loadDocContent(docPath) { const htmlContent = asciidocEngine.convert(adocContent, { safe: 'secure', attributes: { - 'showtitle': true, + showtitle: true, 'source-highlighter': 'highlight.js', - 'icons': 'font', - 'sectanchors': true, - 'idprefix': '', - 'idseparator': '-' - } + icons: 'font', + sectanchors: true, + idprefix: '', + idseparator: '-', + }, }) contentEl.innerHTML = String(htmlContent) // Auto-expand collapsible sections - contentEl.querySelectorAll('details').forEach(details => { + contentEl.querySelectorAll('details').forEach((details) => { details.setAttribute('open', '') }) // Make external links open in new tab - contentEl.querySelectorAll('a[href^="http"]').forEach(link => { + contentEl.querySelectorAll('a[href^="http"]').forEach((link) => { link.setAttribute('target', '_blank') link.setAttribute('rel', 'noopener noreferrer') }) diff --git a/website/src/components/doc-page.test.js b/website/src/components/doc-page.test.js index 74a603c..7b86b6d 100644 --- a/website/src/components/doc-page.test.js +++ b/website/src/components/doc-page.test.js @@ -17,9 +17,10 @@ describe('doc-page', () => { it('falls back to English when localized document is missing', async () => { i18n.setLang('de') - global.fetch - .mockResolvedValueOnce({ ok: false, status: 404 }) - .mockResolvedValueOnce({ ok: true, text: async () => '= About\n\nlink:https://example.com[Example]' }) + global.fetch.mockResolvedValueOnce({ ok: false, status: 404 }).mockResolvedValueOnce({ + ok: true, + text: async () => '= About\n\nlink:https://example.com[Example]', + }) await loadDocContent('docs/about.adoc') @@ -37,6 +38,8 @@ describe('doc-page', () => { await loadDocContent('docs/about.adoc') - expect(document.getElementById('doc-content').textContent).toContain('Failed to Load Documentation') + expect(document.getElementById('doc-content').textContent).toContain( + 'Failed to Load Documentation' + ) }) }) diff --git a/website/src/i18n.js b/website/src/i18n.js index 3f57a1e..b936865 100644 --- a/website/src/i18n.js +++ b/website/src/i18n.js @@ -8,16 +8,16 @@ const DEFAULT_LANG = 'en' let currentLang = DEFAULT_LANG export function applyTranslations() { - document.querySelectorAll('[data-i18n]').forEach(el => { + document.querySelectorAll('[data-i18n]').forEach((el) => { el.textContent = i18n.t(el.dataset.i18n) }) - document.querySelectorAll('[data-i18n-placeholder]').forEach(el => { + document.querySelectorAll('[data-i18n-placeholder]').forEach((el) => { el.placeholder = i18n.t(el.dataset.i18nPlaceholder) }) - document.querySelectorAll('[data-i18n-aria]').forEach(el => { + document.querySelectorAll('[data-i18n-aria]').forEach((el) => { el.setAttribute('aria-label', i18n.t(el.dataset.i18nAria)) }) - document.querySelectorAll('[data-i18n-title]').forEach(el => { + document.querySelectorAll('[data-i18n-title]').forEach((el) => { el.setAttribute('title', i18n.t(el.dataset.i18nTitle)) }) } diff --git a/website/src/main.js b/website/src/main.js index 0255224..c0a2444 100644 --- a/website/src/main.js +++ b/website/src/main.js @@ -4,7 +4,12 @@ import { initTheme, toggleTheme, currentTheme } from './theme.js' import { renderHeader } from './components/header.js' import { renderMain } from './components/main-content.js' import { renderFooter } from './components/footer.js' -import { renderCardGrid, initCardGrid, applyCardFilters, updateAnchorCount } from './components/card-grid.js' +import { + renderCardGrid, + initCardGrid, + applyCardFilters, + updateAnchorCount, +} from './components/card-grid.js' import { fetchData } from './utils/data-loader.js' import { buildSearchIndex, isIndexReady, isIndexBuilding } from './utils/search-index.js' import { initRouter, addRoute } from './utils/router.js' @@ -25,7 +30,8 @@ window.copyAnchorLink = async function copyAnchorLink(anchorId) { window.showToast = function showToast(message) { const toast = document.createElement('div') - toast.className = 'fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg z-50 animate-fade-in' + toast.className = + 'fixed bottom-4 right-4 bg-green-600 text-white px-4 py-2 rounded-lg shadow-lg z-50 animate-fade-in' toast.textContent = message document.body.appendChild(toast) @@ -131,7 +137,8 @@ function renderHomePage() { console.error('Failed to initialize home page:', err) const container = document.getElementById('main-content') if (container) { - container.innerHTML = '
Failed to load anchors. Please try again later.
' + container.innerHTML = + '
Failed to load anchors. Please try again later.
' } }) } diff --git a/website/src/styles/asciidoctor-scoped.css b/website/src/styles/asciidoctor-scoped.css index 7b7c624..8285537 100644 --- a/website/src/styles/asciidoctor-scoped.css +++ b/website/src/styles/asciidoctor-scoped.css @@ -10,7 +10,7 @@ /* Base typography */ background: #fff; color: rgba(0, 0, 0, 0.8); - font-family: "Noto Serif", "DejaVu Serif", serif; + font-family: 'Noto Serif', 'DejaVu Serif', serif; line-height: 1.6; & * { @@ -35,7 +35,7 @@ & h4, & h5, & h6 { - font-family: "Open Sans", "DejaVu Sans", sans-serif; + font-family: 'Open Sans', 'DejaVu Sans', sans-serif; font-weight: 300; color: #ba3925; text-rendering: optimizeLegibility; @@ -44,12 +44,24 @@ line-height: 1.2; } - & h1 { font-size: 2.125em; } - & h2 { font-size: 1.6875em; } - & h3 { font-size: 1.375em; } - & h4 { font-size: 1.125em; } - & h5 { font-size: 1.0625em; } - & h6 { font-size: 1em; } + & h1 { + font-size: 2.125em; + } + & h2 { + font-size: 1.6875em; + } + & h3 { + font-size: 1.375em; + } + & h4 { + font-size: 1.125em; + } + & h5 { + font-size: 1.0625em; + } + & h6 { + font-size: 1em; + } & p { line-height: 1.6; @@ -60,7 +72,7 @@ & code, & kbd, & pre { - font-family: "Noto Sans Mono", "Droid Sans Mono", monospace; + font-family: 'Noto Sans Mono', 'Droid Sans Mono', monospace; font-size: 1em; } diff --git a/website/src/styles/asciidoctor.css b/website/src/styles/asciidoctor.css index 677db7c..ecb16a9 100644 --- a/website/src/styles/asciidoctor.css +++ b/website/src/styles/asciidoctor.css @@ -1,393 +1,1877 @@ /*! Asciidoctor default stylesheet | MIT License | https://asciidoctor.org */ /* Uncomment the following line when using as a custom stylesheet */ /* @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CNoto+Sans+Mono:400,700"; */ -*,::before,::after{box-sizing:border-box} -html{font-size:100%;-webkit-text-size-adjust:100%} -body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Noto Serif","DejaVu Serif",serif;font-size:inherit;line-height:1;position:relative;cursor:auto;-moz-tab-size:4;-o-tab-size:4;tab-size:4;word-wrap:anywhere;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased} -dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,p,blockquote,th,td{margin:0;padding:0} -a{background:none;color:#2156a5;text-decoration:underline;line-height:inherit} -a:active,a:hover{cursor:pointer;outline:0} -a:focus{outline:thin dotted} -a:hover,a:focus{color:#1d4b8f} -abbr{font-size:.9em} -abbr[title]{cursor:help;border-bottom:1px dotted #dddddf;text-decoration:none} -b,strong{font-weight:bold;line-height:inherit} -strong strong{font-weight:400} -code,kbd,pre{font-family:"Noto Sans Mono","Droid Sans Mono",monospace;font-size:1em} -code{font-weight:400;color:rgba(0,0,0,.9)} -pre{color:rgba(0,0,0,.9);line-height:1.45;text-rendering:optimizeSpeed;white-space:pre-wrap} -dfn{font-style:italic} -em,i{font-style:italic;line-height:inherit} -em em{font-style:normal} -hr{border:solid #dddddf;border-width:1px 0 0;clear:both;height:0;margin:1.25em 0 1.1875em} -mark{background:#ff0;color:#000} -p{line-height:1.6;margin-bottom:1.25rem;text-rendering:optimizeLegibility} -q{quotes:"\201C" "\201D" "\2018" "\2019"} -small{font-size:60%;line-height:inherit} -sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline} -sup{top:-.5em} -sub{bottom:-.25em} -img,object[type^="image/"],svg{display:inline-block;height:auto;max-width:100%;vertical-align:middle} -img{border:0;-ms-interpolation-mode:bicubic} -object{max-width:100%} -svg:not(:root){overflow:hidden} -figure{margin:0} -audio,video{display:inline-block} -audio:not([controls]){display:none;height:0} -.left{float:left!important} -.right{float:right!important} -.text-left,div.text-left>*{text-align:left!important} -.text-right,div.text-right>*{text-align:right!important} -.text-center,div.text-center>*{text-align:center!important} -.text-justify,div.text-justify>*{text-align:justify!important} -.hide{display:none} -.subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em} -p aside{font-size:.875em;line-height:1.35;font-style:italic} -h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.2;word-spacing:-.05em} -h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{color:#e99b8f;line-height:0} -h1{font-size:2.125em} -h2{font-size:1.6875em} -h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em} -h4,h5{font-size:1.125em} -h6{font-size:1em} -ul,ol,dl{line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit} -ul,ol{margin-left:1.5em} -ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0} -ul.circle{list-style-type:circle} -ul.disc{list-style-type:disc} -ul.square{list-style-type:square} -ul.circle ul:not([class]),ul.disc ul:not([class]),ul.square ul:not([class]){list-style:inherit} -ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0} -dl dt{margin-bottom:.3125em;font-weight:bold} -dl dd{margin-bottom:1.25em;margin-left:1.125em} -blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd} -blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)} -@media screen and (min-width:768px){h1{font-size:2.75em} -h2{font-size:2.3125em} -h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em} -h4{font-size:1.4375em}} -table{background:#fff;border:1px solid #dedede;border-collapse:collapse;border-spacing:0;margin-bottom:1.25em;word-wrap:normal} -table thead,table tfoot{background:#f7f8f7} -table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left} -table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)} -table tr.even,table tr.alt{background:#f8f8f7} -table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{line-height:1.6} -h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400} -.center{margin-left:auto;margin-right:auto} -.stretch{width:100%} -.clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:"";display:table} -.clearfix::after,.float-group::after{clear:both} -:not(pre).nobreak{word-wrap:normal} -:not(pre).nowrap{white-space:nowrap} -:not(pre).pre-wrap{white-space:pre-wrap} -:not(pre):not([class^=L])>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background:#f7f7f8;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed} -pre code,pre pre{color:inherit;font-size:inherit;line-height:inherit} -pre.nowrap,pre.nowrap pre{white-space:pre;word-wrap:normal} -.keyseq{color:rgba(51,51,51,.8)} -kbd{display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background:#f7f7f7;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 0 rgba(0,0,0,.2),inset 0 0 0 .1em #fff;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap} -.keyseq kbd:first-child{margin-left:0} -.keyseq kbd:last-child{margin-right:0} -.menuseq,.menuref{color:#000} -.menuseq b:not(.caret),.menuref{font-weight:inherit} -.menuseq{word-spacing:-.02em} -.menuseq b.caret{font-size:1.25em;line-height:.8} -.menuseq i.caret{font-weight:bold;text-align:center;width:.45em} -b.button::before,b.button::after{position:relative;top:-1px;font-weight:400} -b.button::before{content:"[";padding:0 3px 0 2px} -b.button::after{content:"]";padding:0 2px 0 3px} -p a>code:hover{color:rgba(0,0,0,.9)} -body>div[id]{margin:0 auto;max-width:62.5em;position:relative;padding-left:.9375em;padding-right:.9375em;width:100%} -body>div[id]::before,body>div[id]::after,#content #footnotes::before{content:"";display:table;clear:both} -#content{margin-top:1.25em;margin-bottom:.625em} -#content::before{content:none} -#header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0} -#header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #dddddf} -#header>h1:only-child{border-bottom:1px solid #dddddf;padding-bottom:8px} -#header .details{border-bottom:1px solid #dddddf;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:flex;flex-flow:row wrap} -#header .details span:first-child{margin-left:-.125em} -#header .details span.email a{color:rgba(0,0,0,.85)} -#header .details br{display:none} -#header .details br+span::before{content:"\00a0\2013\00a0"} -#header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)} -#header .details br+span#revremark::before{content:"\00a0|\00a0"} -#header #revnumber{text-transform:capitalize} -#header #revnumber::after{content:"\00a0"} -#content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #dddddf;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem} -#toc{border-bottom:1px solid #e7e7e9;padding-bottom:.5em} -#toc>ul{margin-left:.125em} -#toc ul.sectlevel0>li:not([class])>a{font-style:italic} -#toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0} -#toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none} -#toc li{line-height:1.3334;margin-top:.3334em} -#toc a{text-decoration:none} -#toc a:active{text-decoration:underline} -#toctitle{color:#7a2518;font-size:1.2em} -@media screen and (min-width:768px){#toctitle{font-size:1.375em} -body.toc2{padding-left:15em;padding-right:0} -body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #dddddf;padding-bottom:8px} -#toc.toc2{margin-top:0!important;background:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #e7e7e9;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto} -#toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em} -#toc.toc2>ul{font-size:.9em;margin-bottom:0} -#toc.toc2 ul ul{margin-left:0;padding-left:1em} -#toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em} -body.toc2.toc-right{padding-left:0;padding-right:15em} -body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #e7e7e9;left:auto;right:0}} -@media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0} -#toc.toc2{width:20em} -#toc.toc2 #toctitle{font-size:1.375em} -#toc.toc2>ul{font-size:.95em} -#toc.toc2 ul ul{padding-left:1.25em} -body.toc2.toc-right{padding-left:0;padding-right:20em}} -#content #toc{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;border-radius:4px} -#footer{max-width:none;background:rgba(0,0,0,.8);padding:1.25em} -#footer-text{color:hsla(0,0%,100%,.8);line-height:1.44} -.sect1{padding-bottom:.625em} -@media screen and (min-width:768px){#content{margin-bottom:1.25em} -.sect1{padding-bottom:1.25em}} -.sect1:last-child{padding-bottom:0} -.sect1+.sect1{border-top:1px solid #e7e7e9} -#content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400} -#content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em} -#content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible} -#content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none} -#content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221} -details,.audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em} -details{margin-left:1.25rem} -details>summary{cursor:pointer;display:block;position:relative;line-height:1.6;margin-bottom:.625rem;outline:none;-webkit-tap-highlight-color:transparent} -details>summary::-webkit-details-marker{display:none} -details>summary::before{content:"";border:solid transparent;border-left:solid;border-width:.3em 0 .3em .5em;position:absolute;top:.5em;left:-1.25rem;transform:translateX(15%)} -details[open]>summary::before{border:solid transparent;border-top:solid;border-width:.5em .3em 0;transform:translateY(15%)} -details>summary::after{content:"";width:1.25rem;height:1em;position:absolute;top:.3em;left:-1.25rem} -.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic} -table.tableblock.fit-content>caption.title{white-space:nowrap;width:0} -.paragraph.lead>p,#preamble>.sectionbody>[class=paragraph]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)} -.admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%} -.admonitionblock>table td.icon{text-align:center;width:80px} -.admonitionblock>table td.icon img{max-width:none} -.admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase} -.admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #dddddf;color:rgba(0,0,0,.6);word-wrap:anywhere} -.admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0} -.exampleblock>.content{border:1px solid #e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#fffef7;border-radius:4px;box-shadow:0 1px 4px #e0e0dc} -.sidebarblock{border:1px solid #dbdbd6;margin-bottom:1.25em;padding:1.25em;background:#f3f3f2;border-radius:4px} -.sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center} -#content #toc>:first-child,.exampleblock>.content>:first-child,.sidebarblock>.content>:first-child{margin-top:0} -#content #toc>:last-child,.exampleblock>.content>:last-child,.exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0} -.literalblock pre,.listingblock>.content>pre{border-radius:4px;overflow-x:auto;padding:1em;font-size:.8125em} -@media screen and (min-width:768px){.literalblock pre,.listingblock>.content>pre{font-size:.90625em}} -@media screen and (min-width:1280px){.literalblock pre,.listingblock>.content>pre{font-size:1em}} -.literalblock pre,.listingblock>.content>pre:not(.highlight),.listingblock>.content>pre[class=highlight],.listingblock>.content>pre[class^="highlight "]{background:#f7f7f8} -.literalblock.output pre{color:#f7f7f8;background:rgba(0,0,0,.9)} -.listingblock>.content{position:relative} -.listingblock pre>code{display:block} -.listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:inherit;opacity:.5} -.listingblock:hover code[data-lang]::before{display:block} -.listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:inherit;opacity:.5} -.listingblock.terminal pre .command:not([data-prompt])::before{content:"$"} -.listingblock pre.highlightjs{padding:0} -.listingblock pre.highlightjs>code{padding:1em;border-radius:4px} -.listingblock pre.prettyprint{border-width:0} -.prettyprint{background:#f7f7f8} -pre.prettyprint .linenums{line-height:1.45;margin-left:2em} -pre.prettyprint li{background:none;list-style-type:inherit;padding-left:0} -pre.prettyprint li code[data-lang]::before{opacity:1} -pre.prettyprint li:not(:first-child) code[data-lang]::before{display:none} -table.linenotable{border-collapse:separate;border:0;margin-bottom:0;background:none} -table.linenotable td[class]{color:inherit;vertical-align:top;padding:0;line-height:inherit;white-space:normal} -table.linenotable td.code{padding-left:.75em} -table.linenotable td.linenos{width:.01%} -table.linenotable td.linenos,pre.pygments .linenos,pre.rouge .linenos{border-right:1px solid;opacity:.35;padding-right:.5em;-webkit-user-select:none;-moz-user-select:none;user-select:none} -pre.pygments span.linenos,pre.rouge span.linenos{display:inline-block;margin-right:.75em} -.quoteblock{margin:0 1em 1.25em 1.5em;display:table} -.quoteblock:not(.excerpt)>.title{margin-left:-1.5em;margin-bottom:.75em} -.quoteblock blockquote,.quoteblock p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify} -.quoteblock blockquote{margin:0;padding:0;border:0} -.quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)} -.quoteblock blockquote>.paragraph:last-child p{margin-bottom:0} -.quoteblock .attribution{margin-top:.75em;margin-right:.5ex;text-align:right} -.verseblock{margin:0 1em 1.25em} -.verseblock pre{font-family:"Open Sans","DejaVu Sans",sans-serif;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility} -.verseblock pre strong{font-weight:400} -.verseblock .attribution{margin-top:1.25rem;margin-left:.5ex} -.quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic} -.quoteblock .attribution br,.verseblock .attribution br{display:none} -.quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)} -.quoteblock.abstract blockquote::before,.quoteblock.excerpt blockquote::before,.quoteblock .quoteblock blockquote::before{display:none} -.quoteblock.abstract blockquote,.quoteblock.abstract p,.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{line-height:1.6;word-spacing:0} -.quoteblock.abstract{margin:0 1em 1.25em;display:block} -.quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center} -.quoteblock.excerpt>blockquote,.quoteblock .quoteblock{padding:0 0 .25em 1em;border-left:.25em solid #dddddf} -.quoteblock.excerpt,.quoteblock .quoteblock{margin-left:0} -.quoteblock.excerpt blockquote,.quoteblock.excerpt p,.quoteblock .quoteblock blockquote,.quoteblock .quoteblock p{color:inherit;font-size:1.0625rem} -.quoteblock.excerpt .attribution,.quoteblock .quoteblock .attribution{color:inherit;font-size:.85rem;text-align:left;margin-right:0} -p.tableblock:last-child{margin-bottom:0} -td.tableblock>.content{margin-bottom:1.25em;word-wrap:anywhere} -td.tableblock>.content>:last-child{margin-bottom:-1.25em} -table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede} -table.grid-all>*>tr>*{border-width:1px} -table.grid-cols>*>tr>*{border-width:0 1px} -table.grid-rows>*>tr>*{border-width:1px 0} -table.frame-all{border-width:1px} -table.frame-ends{border-width:1px 0} -table.frame-sides{border-width:0 1px} -table.frame-none>colgroup+*>:first-child>*,table.frame-sides>colgroup+*>:first-child>*{border-top-width:0} -table.frame-none>:last-child>:last-child>*,table.frame-sides>:last-child>:last-child>*{border-bottom-width:0} -table.frame-none>*>tr>:first-child,table.frame-ends>*>tr>:first-child{border-left-width:0} -table.frame-none>*>tr>:last-child,table.frame-ends>*>tr>:last-child{border-right-width:0} -table.stripes-all>tbody>tr,table.stripes-odd>tbody>tr:nth-of-type(odd),table.stripes-even>tbody>tr:nth-of-type(even),table.stripes-hover>tbody>tr:hover{background:#f8f8f7} -th.halign-left,td.halign-left{text-align:left} -th.halign-right,td.halign-right{text-align:right} -th.halign-center,td.halign-center{text-align:center} -th.valign-top,td.valign-top{vertical-align:top} -th.valign-bottom,td.valign-bottom{vertical-align:bottom} -th.valign-middle,td.valign-middle{vertical-align:middle} -table thead th,table tfoot th{font-weight:bold} -tbody tr th{background:#f7f8f7} -tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold} -p.tableblock>code:only-child{background:none;padding:0} -p.tableblock{font-size:1em} -ol{margin-left:1.75em} -ul li ol{margin-left:1.5em} -dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0} -li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em} -ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none} -ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em} -ul.unstyled,ol.unstyled{margin-left:0} -li>p:empty:only-child::before{content:"";display:inline-block} -ul.checklist>li>p:first-child{margin-left:-1em} -ul.checklist>li>p:first-child>.fa-square-o:first-child,ul.checklist>li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em} -ul.checklist>li>p:first-child>input[type=checkbox]:first-child{font:inherit;margin:0 .25em 0 0;padding:0} -ul.inline{display:flex;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em} -ul.inline>li{margin-left:1.25em} -.unstyled dl dt{font-weight:400;font-style:normal} -ol.arabic{list-style-type:decimal} -ol.decimal{list-style-type:decimal-leading-zero} -ol.loweralpha{list-style-type:lower-alpha} -ol.upperalpha{list-style-type:upper-alpha} -ol.lowerroman{list-style-type:lower-roman} -ol.upperroman{list-style-type:upper-roman} -ol.lowergreek{list-style-type:lower-greek} -.hdlist>table,.colist>table{border:0;background:none} -.hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none} -td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em} -td.hdlist1{font-weight:bold;padding-bottom:1.25em} -td.hdlist2{word-wrap:anywhere} -.literalblock+.colist,.listingblock+.colist{margin-top:-.5em} -.colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top} -.colist td:not([class]):first-child img{max-width:none} -.colist td:not([class]):last-child{padding:.25em 0} -.thumb,.th{line-height:0;display:inline-block;border:4px solid #fff;box-shadow:0 0 0 1px #ddd} -.imageblock.left{margin:.25em .625em 1.25em 0} -.imageblock.right{margin:.25em 0 1.25em .625em} -.imageblock>.title{margin-bottom:0} -.imageblock.thumb,.imageblock.th{border-width:6px} -.imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em} -.image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0} -.image.left{margin-right:.625em} -.image.right{margin-left:.625em} -a.image{text-decoration:none;display:inline-block} -a.image object{pointer-events:none} -sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super} -sup.footnote a,sup.footnoteref a{text-decoration:none} -sup.footnote a:active,sup.footnoteref a:active,#footnotes .footnote a:first-of-type:active{text-decoration:underline} -#footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em} -#footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0} -#footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em} -#footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em} -#footnotes .footnote:last-of-type{margin-bottom:0} -#content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0} -div.page-break{display:none} -div.unbreakable{-moz-column-break-inside:avoid;break-inside:avoid} -.big{font-size:larger} -.small{font-size:smaller} -.underline{text-decoration:underline} -.overline{text-decoration:overline} -.line-through{text-decoration:line-through} -.aqua{color:#00bfbf} -.aqua-background{background:#00fafa} -.black{color:#000} -.black-background{background:#000} -.blue{color:#0000bf} -.blue-background{background:#0000fa} -.fuchsia{color:#bf00bf} -.fuchsia-background{background:#fa00fa} -.gray{color:#606060} -.gray-background{background:#7d7d7d} -.green{color:#006000} -.green-background{background:#007d00} -.lime{color:#00bf00} -.lime-background{background:#00fa00} -.maroon{color:#600000} -.maroon-background{background:#7d0000} -.navy{color:#000060} -.navy-background{background:#00007d} -.olive{color:#606000} -.olive-background{background:#7d7d00} -.purple{color:#600060} -.purple-background{background:#7d007d} -.red{color:#bf0000} -.red-background{background:#fa0000} -.silver{color:#909090} -.silver-background{background:#bcbcbc} -.teal{color:#006060} -.teal-background{background:#007d7d} -.white{color:#bfbfbf} -.white-background{background:#fafafa} -.yellow{color:#bfbf00} -.yellow-background{background:#fafa00} -span.icon>.fa{cursor:default} -a span.icon>.fa{cursor:inherit} -.admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default} -.admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c} -.admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111} -.admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900} -.admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400} -.admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000} -.conum[data-value]{display:inline-block;color:#fff!important;background:rgba(0,0,0,.8);border-radius:50%;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold} -.conum[data-value] *{color:#fff!important} -.conum[data-value]+b{display:none} -.conum[data-value]::after{content:attr(data-value)} -pre .conum[data-value]{position:relative;top:-.125em} -b.conum *{color:inherit!important} -.conum:not([data-value]):empty{display:none} -dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility} -h1,h2,p,td.content,span.alt,summary{letter-spacing:-.01em} -p strong,td.content strong,div.footnote strong{letter-spacing:-.005em} -p,blockquote,dt,td.content,td.hdlist1,span.alt,summary{font-size:1.0625rem} -.sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em} -.print-only{display:none!important} -@page{margin:1.25cm .75cm} -@media print{*{box-shadow:none!important;text-shadow:none!important} -html{font-size:80%} -a{color:inherit!important;text-decoration:underline!important} -a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important} -a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em} -abbr[title]{border-bottom:1px dotted} -abbr[title]::after{content:" (" attr(title) ")"} -pre,blockquote,tr,img,object,svg{-moz-column-break-inside:avoid;break-inside:avoid} -thead{display:table-header-group} -p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3} -h2,h3,#toctitle,.sidebarblock>.content>.title{-moz-column-break-after:avoid;break-after:avoid} -body>div[id]{max-width:none} -#toc,.sidebarblock,.exampleblock>.content{background:none!important} -#toc{border-bottom:1px solid #dddddf!important;padding-bottom:0!important} -body.book #header{text-align:center} -body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em} -body.book #header .details{border:0!important;display:block;padding:0!important} -body.book #header .details span:first-child{margin-left:0!important} -body.book #header .details br{display:block} -body.book #header .details br+span::before{content:none!important} -body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important} -body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{-moz-column-break-before:page;break-before:page} -.listingblock code[data-lang]::before{display:block} -div.page-break{display:block;-moz-column-break-after:page;break-after:page} -#footer{padding:0 .9375em} -.hide-on-print{display:none!important} -.print-only{display:block!important} -.hide-for-print{display:none!important} -.show-for-print{display:inherit!important}} -@media amzn-kf8,print{#header>h1:first-child{margin-top:1.25rem} -.sect1{padding:0!important} -.sect1+.sect1{border:0} -#footer{background:none} -#footer-text{color:rgba(0,0,0,.6);font-size:.9em}} -@media amzn-kf8{body>div[id]{padding:0}} +*, +::before, +::after { + box-sizing: border-box; +} +html { + font-size: 100%; + -webkit-text-size-adjust: 100%; +} +body { + background: #fff; + color: rgba(0, 0, 0, 0.8); + padding: 0; + margin: 0; + font-family: 'Noto Serif', 'DejaVu Serif', serif; + font-size: inherit; + line-height: 1; + position: relative; + cursor: auto; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + word-wrap: anywhere; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; +} +dl, +dt, +dd, +ul, +ol, +li, +h1, +h2, +h3, +h4, +h5, +h6, +pre, +p, +blockquote, +th, +td { + margin: 0; + padding: 0; +} +a { + background: none; + color: #2156a5; + text-decoration: underline; + line-height: inherit; +} +a:active, +a:hover { + cursor: pointer; + outline: 0; +} +a:focus { + outline: thin dotted; +} +a:hover, +a:focus { + color: #1d4b8f; +} +abbr { + font-size: 0.9em; +} +abbr[title] { + cursor: help; + border-bottom: 1px dotted #dddddf; + text-decoration: none; +} +b, +strong { + font-weight: bold; + line-height: inherit; +} +strong strong { + font-weight: 400; +} +code, +kbd, +pre { + font-family: 'Noto Sans Mono', 'Droid Sans Mono', monospace; + font-size: 1em; +} +code { + font-weight: 400; + color: rgba(0, 0, 0, 0.9); +} +pre { + color: rgba(0, 0, 0, 0.9); + line-height: 1.45; + text-rendering: optimizeSpeed; + white-space: pre-wrap; +} +dfn { + font-style: italic; +} +em, +i { + font-style: italic; + line-height: inherit; +} +em em { + font-style: normal; +} +hr { + border: solid #dddddf; + border-width: 1px 0 0; + clear: both; + height: 0; + margin: 1.25em 0 1.1875em; +} +mark { + background: #ff0; + color: #000; +} +p { + line-height: 1.6; + margin-bottom: 1.25rem; + text-rendering: optimizeLegibility; +} +q { + quotes: '\201C' '\201D' '\2018' '\2019'; +} +small { + font-size: 60%; + line-height: inherit; +} +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { + top: -0.5em; +} +sub { + bottom: -0.25em; +} +img, +object[type^='image/'], +svg { + display: inline-block; + height: auto; + max-width: 100%; + vertical-align: middle; +} +img { + border: 0; + -ms-interpolation-mode: bicubic; +} +object { + max-width: 100%; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 0; +} +audio, +video { + display: inline-block; +} +audio:not([controls]) { + display: none; + height: 0; +} +.left { + float: left !important; +} +.right { + float: right !important; +} +.text-left, +div.text-left > * { + text-align: left !important; +} +.text-right, +div.text-right > * { + text-align: right !important; +} +.text-center, +div.text-center > * { + text-align: center !important; +} +.text-justify, +div.text-justify > * { + text-align: justify !important; +} +.hide { + display: none; +} +.subheader, +.admonitionblock td.content > .title, +.audioblock > .title, +.exampleblock > .title, +.imageblock > .title, +.listingblock > .title, +.literalblock > .title, +.stemblock > .title, +.openblock > .title, +.paragraph > .title, +.quoteblock > .title, +table.tableblock > .title, +.verseblock > .title, +.videoblock > .title, +.dlist > .title, +.olist > .title, +.ulist > .title, +.qlist > .title, +.hdlist > .title { + line-height: 1.45; + color: #7a2518; + font-weight: 400; + margin-top: 0; + margin-bottom: 0.25em; +} +p aside { + font-size: 0.875em; + line-height: 1.35; + font-style: italic; +} +h1, +h2, +h3, +#toctitle, +.sidebarblock > .content > .title, +h4, +h5, +h6 { + font-family: 'Open Sans', 'DejaVu Sans', sans-serif; + font-weight: 300; + font-style: normal; + color: #ba3925; + text-rendering: optimizeLegibility; + margin-top: 1em; + margin-bottom: 0.5em; + line-height: 1.2; + word-spacing: -0.05em; +} +h1 small, +h2 small, +h3 small, +#toctitle small, +.sidebarblock > .content > .title small, +h4 small, +h5 small, +h6 small { + color: #e99b8f; + line-height: 0; +} +h1 { + font-size: 2.125em; +} +h2 { + font-size: 1.6875em; +} +h3, +#toctitle, +.sidebarblock > .content > .title { + font-size: 1.375em; +} +h4, +h5 { + font-size: 1.125em; +} +h6 { + font-size: 1em; +} +ul, +ol, +dl { + line-height: 1.6; + margin-bottom: 1.25em; + list-style-position: outside; + font-family: inherit; +} +ul, +ol { + margin-left: 1.5em; +} +ul li ul, +ul li ol { + margin-left: 1.25em; + margin-bottom: 0; +} +ul.circle { + list-style-type: circle; +} +ul.disc { + list-style-type: disc; +} +ul.square { + list-style-type: square; +} +ul.circle ul:not([class]), +ul.disc ul:not([class]), +ul.square ul:not([class]) { + list-style: inherit; +} +ol li ul, +ol li ol { + margin-left: 1.25em; + margin-bottom: 0; +} +dl dt { + margin-bottom: 0.3125em; + font-weight: bold; +} +dl dd { + margin-bottom: 1.25em; + margin-left: 1.125em; +} +blockquote { + margin: 0 0 1.25em; + padding: 0.5625em 1.25em 0 1.1875em; + border-left: 1px solid #ddd; +} +blockquote, +blockquote p { + line-height: 1.6; + color: rgba(0, 0, 0, 0.85); +} +@media screen and (min-width: 768px) { + h1 { + font-size: 2.75em; + } + h2 { + font-size: 2.3125em; + } + h3, + #toctitle, + .sidebarblock > .content > .title { + font-size: 1.6875em; + } + h4 { + font-size: 1.4375em; + } +} +table { + background: #fff; + border: 1px solid #dedede; + border-collapse: collapse; + border-spacing: 0; + margin-bottom: 1.25em; + word-wrap: normal; +} +table thead, +table tfoot { + background: #f7f8f7; +} +table thead tr th, +table thead tr td, +table tfoot tr th, +table tfoot tr td { + padding: 0.5em 0.625em 0.625em; + font-size: inherit; + color: rgba(0, 0, 0, 0.8); + text-align: left; +} +table tr th, +table tr td { + padding: 0.5625em 0.625em; + font-size: inherit; + color: rgba(0, 0, 0, 0.8); +} +table tr.even, +table tr.alt { + background: #f8f8f7; +} +table thead tr th, +table tfoot tr th, +table tbody tr td, +table tr td, +table tfoot tr td { + line-height: 1.6; +} +h1 strong, +h2 strong, +h3 strong, +#toctitle strong, +.sidebarblock > .content > .title strong, +h4 strong, +h5 strong, +h6 strong { + font-weight: 400; +} +.center { + margin-left: auto; + margin-right: auto; +} +.stretch { + width: 100%; +} +.clearfix::before, +.clearfix::after, +.float-group::before, +.float-group::after { + content: ''; + display: table; +} +.clearfix::after, +.float-group::after { + clear: both; +} +:not(pre).nobreak { + word-wrap: normal; +} +:not(pre).nowrap { + white-space: nowrap; +} +:not(pre).pre-wrap { + white-space: pre-wrap; +} +:not(pre):not([class^='L']) > code { + font-size: 0.9375em; + font-style: normal !important; + letter-spacing: 0; + padding: 0.1em 0.5ex; + word-spacing: -0.15em; + background: #f7f7f8; + border-radius: 4px; + line-height: 1.45; + text-rendering: optimizeSpeed; +} +pre code, +pre pre { + color: inherit; + font-size: inherit; + line-height: inherit; +} +pre.nowrap, +pre.nowrap pre { + white-space: pre; + word-wrap: normal; +} +.keyseq { + color: rgba(51, 51, 51, 0.8); +} +kbd { + display: inline-block; + color: rgba(0, 0, 0, 0.8); + font-size: 0.65em; + line-height: 1.45; + background: #f7f7f7; + border: 1px solid #ccc; + border-radius: 3px; + box-shadow: + 0 1px 0 rgba(0, 0, 0, 0.2), + inset 0 0 0 0.1em #fff; + margin: 0 0.15em; + padding: 0.2em 0.5em; + vertical-align: middle; + position: relative; + top: -0.1em; + white-space: nowrap; +} +.keyseq kbd:first-child { + margin-left: 0; +} +.keyseq kbd:last-child { + margin-right: 0; +} +.menuseq, +.menuref { + color: #000; +} +.menuseq b:not(.caret), +.menuref { + font-weight: inherit; +} +.menuseq { + word-spacing: -0.02em; +} +.menuseq b.caret { + font-size: 1.25em; + line-height: 0.8; +} +.menuseq i.caret { + font-weight: bold; + text-align: center; + width: 0.45em; +} +b.button::before, +b.button::after { + position: relative; + top: -1px; + font-weight: 400; +} +b.button::before { + content: '['; + padding: 0 3px 0 2px; +} +b.button::after { + content: ']'; + padding: 0 2px 0 3px; +} +p a > code:hover { + color: rgba(0, 0, 0, 0.9); +} +body > div[id] { + margin: 0 auto; + max-width: 62.5em; + position: relative; + padding-left: 0.9375em; + padding-right: 0.9375em; + width: 100%; +} +body > div[id]::before, +body > div[id]::after, +#content #footnotes::before { + content: ''; + display: table; + clear: both; +} +#content { + margin-top: 1.25em; + margin-bottom: 0.625em; +} +#content::before { + content: none; +} +#header > h1:first-child { + color: rgba(0, 0, 0, 0.85); + margin-top: 2.25rem; + margin-bottom: 0; +} +#header > h1:first-child + #toc { + margin-top: 8px; + border-top: 1px solid #dddddf; +} +#header > h1:only-child { + border-bottom: 1px solid #dddddf; + padding-bottom: 8px; +} +#header .details { + border-bottom: 1px solid #dddddf; + line-height: 1.45; + padding-top: 0.25em; + padding-bottom: 0.25em; + padding-left: 0.25em; + color: rgba(0, 0, 0, 0.6); + display: flex; + flex-flow: row wrap; +} +#header .details span:first-child { + margin-left: -0.125em; +} +#header .details span.email a { + color: rgba(0, 0, 0, 0.85); +} +#header .details br { + display: none; +} +#header .details br + span::before { + content: '\00a0\2013\00a0'; +} +#header .details br + span.author::before { + content: '\00a0\22c5\00a0'; + color: rgba(0, 0, 0, 0.85); +} +#header .details br + span#revremark::before { + content: '\00a0|\00a0'; +} +#header #revnumber { + text-transform: capitalize; +} +#header #revnumber::after { + content: '\00a0'; +} +#content > h1:first-child:not([class]) { + color: rgba(0, 0, 0, 0.85); + border-bottom: 1px solid #dddddf; + padding-bottom: 8px; + margin-top: 0; + padding-top: 1rem; + margin-bottom: 1.25rem; +} +#toc { + border-bottom: 1px solid #e7e7e9; + padding-bottom: 0.5em; +} +#toc > ul { + margin-left: 0.125em; +} +#toc ul.sectlevel0 > li:not([class]) > a { + font-style: italic; +} +#toc ul.sectlevel0 ul.sectlevel1 { + margin: 0.5em 0; +} +#toc ul { + font-family: 'Open Sans', 'DejaVu Sans', sans-serif; + list-style-type: none; +} +#toc li { + line-height: 1.3334; + margin-top: 0.3334em; +} +#toc a { + text-decoration: none; +} +#toc a:active { + text-decoration: underline; +} +#toctitle { + color: #7a2518; + font-size: 1.2em; +} +@media screen and (min-width: 768px) { + #toctitle { + font-size: 1.375em; + } + body.toc2 { + padding-left: 15em; + padding-right: 0; + } + body.toc2 #header > h1:nth-last-child(2) { + border-bottom: 1px solid #dddddf; + padding-bottom: 8px; + } + #toc.toc2 { + margin-top: 0 !important; + background: #f8f8f7; + position: fixed; + width: 15em; + left: 0; + top: 0; + border-right: 1px solid #e7e7e9; + border-top-width: 0 !important; + border-bottom-width: 0 !important; + z-index: 1000; + padding: 1.25em 1em; + height: 100%; + overflow: auto; + } + #toc.toc2 #toctitle { + margin-top: 0; + margin-bottom: 0.8rem; + font-size: 1.2em; + } + #toc.toc2 > ul { + font-size: 0.9em; + margin-bottom: 0; + } + #toc.toc2 ul ul { + margin-left: 0; + padding-left: 1em; + } + #toc.toc2 ul.sectlevel0 ul.sectlevel1 { + padding-left: 0; + margin-top: 0.5em; + margin-bottom: 0.5em; + } + body.toc2.toc-right { + padding-left: 0; + padding-right: 15em; + } + body.toc2.toc-right #toc.toc2 { + border-right-width: 0; + border-left: 1px solid #e7e7e9; + left: auto; + right: 0; + } +} +@media screen and (min-width: 1280px) { + body.toc2 { + padding-left: 20em; + padding-right: 0; + } + #toc.toc2 { + width: 20em; + } + #toc.toc2 #toctitle { + font-size: 1.375em; + } + #toc.toc2 > ul { + font-size: 0.95em; + } + #toc.toc2 ul ul { + padding-left: 1.25em; + } + body.toc2.toc-right { + padding-left: 0; + padding-right: 20em; + } +} +#content #toc { + border: 1px solid #e0e0dc; + margin-bottom: 1.25em; + padding: 1.25em; + background: #f8f8f7; + border-radius: 4px; +} +#footer { + max-width: none; + background: rgba(0, 0, 0, 0.8); + padding: 1.25em; +} +#footer-text { + color: hsla(0, 0%, 100%, 0.8); + line-height: 1.44; +} +.sect1 { + padding-bottom: 0.625em; +} +@media screen and (min-width: 768px) { + #content { + margin-bottom: 1.25em; + } + .sect1 { + padding-bottom: 1.25em; + } +} +.sect1:last-child { + padding-bottom: 0; +} +.sect1 + .sect1 { + border-top: 1px solid #e7e7e9; +} +#content h1 > a.anchor, +h2 > a.anchor, +h3 > a.anchor, +#toctitle > a.anchor, +.sidebarblock > .content > .title > a.anchor, +h4 > a.anchor, +h5 > a.anchor, +h6 > a.anchor { + position: absolute; + z-index: 1001; + width: 1.5ex; + margin-left: -1.5ex; + display: block; + text-decoration: none !important; + visibility: hidden; + text-align: center; + font-weight: 400; +} +#content h1 > a.anchor::before, +h2 > a.anchor::before, +h3 > a.anchor::before, +#toctitle > a.anchor::before, +.sidebarblock > .content > .title > a.anchor::before, +h4 > a.anchor::before, +h5 > a.anchor::before, +h6 > a.anchor::before { + content: '\00A7'; + font-size: 0.85em; + display: block; + padding-top: 0.1em; +} +#content h1:hover > a.anchor, +#content h1 > a.anchor:hover, +h2:hover > a.anchor, +h2 > a.anchor:hover, +h3:hover > a.anchor, +#toctitle:hover > a.anchor, +.sidebarblock > .content > .title:hover > a.anchor, +h3 > a.anchor:hover, +#toctitle > a.anchor:hover, +.sidebarblock > .content > .title > a.anchor:hover, +h4:hover > a.anchor, +h4 > a.anchor:hover, +h5:hover > a.anchor, +h5 > a.anchor:hover, +h6:hover > a.anchor, +h6 > a.anchor:hover { + visibility: visible; +} +#content h1 > a.link, +h2 > a.link, +h3 > a.link, +#toctitle > a.link, +.sidebarblock > .content > .title > a.link, +h4 > a.link, +h5 > a.link, +h6 > a.link { + color: #ba3925; + text-decoration: none; +} +#content h1 > a.link:hover, +h2 > a.link:hover, +h3 > a.link:hover, +#toctitle > a.link:hover, +.sidebarblock > .content > .title > a.link:hover, +h4 > a.link:hover, +h5 > a.link:hover, +h6 > a.link:hover { + color: #a53221; +} +details, +.audioblock, +.imageblock, +.literalblock, +.listingblock, +.stemblock, +.videoblock { + margin-bottom: 1.25em; +} +details { + margin-left: 1.25rem; +} +details > summary { + cursor: pointer; + display: block; + position: relative; + line-height: 1.6; + margin-bottom: 0.625rem; + outline: none; + -webkit-tap-highlight-color: transparent; +} +details > summary::-webkit-details-marker { + display: none; +} +details > summary::before { + content: ''; + border: solid transparent; + border-left: solid; + border-width: 0.3em 0 0.3em 0.5em; + position: absolute; + top: 0.5em; + left: -1.25rem; + transform: translateX(15%); +} +details[open] > summary::before { + border: solid transparent; + border-top: solid; + border-width: 0.5em 0.3em 0; + transform: translateY(15%); +} +details > summary::after { + content: ''; + width: 1.25rem; + height: 1em; + position: absolute; + top: 0.3em; + left: -1.25rem; +} +.admonitionblock td.content > .title, +.audioblock > .title, +.exampleblock > .title, +.imageblock > .title, +.listingblock > .title, +.literalblock > .title, +.stemblock > .title, +.openblock > .title, +.paragraph > .title, +.quoteblock > .title, +table.tableblock > .title, +.verseblock > .title, +.videoblock > .title, +.dlist > .title, +.olist > .title, +.ulist > .title, +.qlist > .title, +.hdlist > .title { + text-rendering: optimizeLegibility; + text-align: left; + font-family: 'Noto Serif', 'DejaVu Serif', serif; + font-size: 1rem; + font-style: italic; +} +table.tableblock.fit-content > caption.title { + white-space: nowrap; + width: 0; +} +.paragraph.lead > p, +#preamble > .sectionbody > [class='paragraph']:first-of-type p { + font-size: 1.21875em; + line-height: 1.6; + color: rgba(0, 0, 0, 0.85); +} +.admonitionblock > table { + border-collapse: separate; + border: 0; + background: none; + width: 100%; +} +.admonitionblock > table td.icon { + text-align: center; + width: 80px; +} +.admonitionblock > table td.icon img { + max-width: none; +} +.admonitionblock > table td.icon .title { + font-weight: bold; + font-family: 'Open Sans', 'DejaVu Sans', sans-serif; + text-transform: uppercase; +} +.admonitionblock > table td.content { + padding-left: 1.125em; + padding-right: 1.25em; + border-left: 1px solid #dddddf; + color: rgba(0, 0, 0, 0.6); + word-wrap: anywhere; +} +.admonitionblock > table td.content > :last-child > :last-child { + margin-bottom: 0; +} +.exampleblock > .content { + border: 1px solid #e0e0dc; + margin-bottom: 1.25em; + padding: 1.25em; + background: #fffef7; + border-radius: 4px; + box-shadow: 0 1px 4px #e0e0dc; +} +.sidebarblock { + border: 1px solid #dbdbd6; + margin-bottom: 1.25em; + padding: 1.25em; + background: #f3f3f2; + border-radius: 4px; +} +.sidebarblock > .content > .title { + color: #7a2518; + margin-top: 0; + text-align: center; +} +#content #toc > :first-child, +.exampleblock > .content > :first-child, +.sidebarblock > .content > :first-child { + margin-top: 0; +} +#content #toc > :last-child, +.exampleblock > .content > :last-child, +.exampleblock > .content > :last-child > :last-child, +.exampleblock > .content .olist > ol > li:last-child > :last-child, +.exampleblock > .content .ulist > ul > li:last-child > :last-child, +.exampleblock > .content .qlist > ol > li:last-child > :last-child, +.sidebarblock > .content > :last-child, +.sidebarblock > .content > :last-child > :last-child, +.sidebarblock > .content .olist > ol > li:last-child > :last-child, +.sidebarblock > .content .ulist > ul > li:last-child > :last-child, +.sidebarblock > .content .qlist > ol > li:last-child > :last-child { + margin-bottom: 0; +} +.literalblock pre, +.listingblock > .content > pre { + border-radius: 4px; + overflow-x: auto; + padding: 1em; + font-size: 0.8125em; +} +@media screen and (min-width: 768px) { + .literalblock pre, + .listingblock > .content > pre { + font-size: 0.90625em; + } +} +@media screen and (min-width: 1280px) { + .literalblock pre, + .listingblock > .content > pre { + font-size: 1em; + } +} +.literalblock pre, +.listingblock > .content > pre:not(.highlight), +.listingblock > .content > pre[class='highlight'], +.listingblock > .content > pre[class^='highlight '] { + background: #f7f7f8; +} +.literalblock.output pre { + color: #f7f7f8; + background: rgba(0, 0, 0, 0.9); +} +.listingblock > .content { + position: relative; +} +.listingblock pre > code { + display: block; +} +.listingblock code[data-lang]::before { + display: none; + content: attr(data-lang); + position: absolute; + font-size: 0.75em; + top: 0.425rem; + right: 0.5rem; + line-height: 1; + text-transform: uppercase; + color: inherit; + opacity: 0.5; +} +.listingblock:hover code[data-lang]::before { + display: block; +} +.listingblock.terminal pre .command::before { + content: attr(data-prompt); + padding-right: 0.5em; + color: inherit; + opacity: 0.5; +} +.listingblock.terminal pre .command:not([data-prompt])::before { + content: '$'; +} +.listingblock pre.highlightjs { + padding: 0; +} +.listingblock pre.highlightjs > code { + padding: 1em; + border-radius: 4px; +} +.listingblock pre.prettyprint { + border-width: 0; +} +.prettyprint { + background: #f7f7f8; +} +pre.prettyprint .linenums { + line-height: 1.45; + margin-left: 2em; +} +pre.prettyprint li { + background: none; + list-style-type: inherit; + padding-left: 0; +} +pre.prettyprint li code[data-lang]::before { + opacity: 1; +} +pre.prettyprint li:not(:first-child) code[data-lang]::before { + display: none; +} +table.linenotable { + border-collapse: separate; + border: 0; + margin-bottom: 0; + background: none; +} +table.linenotable td[class] { + color: inherit; + vertical-align: top; + padding: 0; + line-height: inherit; + white-space: normal; +} +table.linenotable td.code { + padding-left: 0.75em; +} +table.linenotable td.linenos { + width: 0.01%; +} +table.linenotable td.linenos, +pre.pygments .linenos, +pre.rouge .linenos { + border-right: 1px solid; + opacity: 0.35; + padding-right: 0.5em; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; +} +pre.pygments span.linenos, +pre.rouge span.linenos { + display: inline-block; + margin-right: 0.75em; +} +.quoteblock { + margin: 0 1em 1.25em 1.5em; + display: table; +} +.quoteblock:not(.excerpt) > .title { + margin-left: -1.5em; + margin-bottom: 0.75em; +} +.quoteblock blockquote, +.quoteblock p { + color: rgba(0, 0, 0, 0.85); + font-size: 1.15rem; + line-height: 1.75; + word-spacing: 0.1em; + letter-spacing: 0; + font-style: italic; + text-align: justify; +} +.quoteblock blockquote { + margin: 0; + padding: 0; + border: 0; +} +.quoteblock blockquote::before { + content: '\201c'; + float: left; + font-size: 2.75em; + font-weight: bold; + line-height: 0.6em; + margin-left: -0.6em; + color: #7a2518; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); +} +.quoteblock blockquote > .paragraph:last-child p { + margin-bottom: 0; +} +.quoteblock .attribution { + margin-top: 0.75em; + margin-right: 0.5ex; + text-align: right; +} +.verseblock { + margin: 0 1em 1.25em; +} +.verseblock pre { + font-family: 'Open Sans', 'DejaVu Sans', sans-serif; + font-size: 1.15rem; + color: rgba(0, 0, 0, 0.85); + font-weight: 300; + text-rendering: optimizeLegibility; +} +.verseblock pre strong { + font-weight: 400; +} +.verseblock .attribution { + margin-top: 1.25rem; + margin-left: 0.5ex; +} +.quoteblock .attribution, +.verseblock .attribution { + font-size: 0.9375em; + line-height: 1.45; + font-style: italic; +} +.quoteblock .attribution br, +.verseblock .attribution br { + display: none; +} +.quoteblock .attribution cite, +.verseblock .attribution cite { + display: block; + letter-spacing: -0.025em; + color: rgba(0, 0, 0, 0.6); +} +.quoteblock.abstract blockquote::before, +.quoteblock.excerpt blockquote::before, +.quoteblock .quoteblock blockquote::before { + display: none; +} +.quoteblock.abstract blockquote, +.quoteblock.abstract p, +.quoteblock.excerpt blockquote, +.quoteblock.excerpt p, +.quoteblock .quoteblock blockquote, +.quoteblock .quoteblock p { + line-height: 1.6; + word-spacing: 0; +} +.quoteblock.abstract { + margin: 0 1em 1.25em; + display: block; +} +.quoteblock.abstract > .title { + margin: 0 0 0.375em; + font-size: 1.15em; + text-align: center; +} +.quoteblock.excerpt > blockquote, +.quoteblock .quoteblock { + padding: 0 0 0.25em 1em; + border-left: 0.25em solid #dddddf; +} +.quoteblock.excerpt, +.quoteblock .quoteblock { + margin-left: 0; +} +.quoteblock.excerpt blockquote, +.quoteblock.excerpt p, +.quoteblock .quoteblock blockquote, +.quoteblock .quoteblock p { + color: inherit; + font-size: 1.0625rem; +} +.quoteblock.excerpt .attribution, +.quoteblock .quoteblock .attribution { + color: inherit; + font-size: 0.85rem; + text-align: left; + margin-right: 0; +} +p.tableblock:last-child { + margin-bottom: 0; +} +td.tableblock > .content { + margin-bottom: 1.25em; + word-wrap: anywhere; +} +td.tableblock > .content > :last-child { + margin-bottom: -1.25em; +} +table.tableblock, +th.tableblock, +td.tableblock { + border: 0 solid #dedede; +} +table.grid-all > * > tr > * { + border-width: 1px; +} +table.grid-cols > * > tr > * { + border-width: 0 1px; +} +table.grid-rows > * > tr > * { + border-width: 1px 0; +} +table.frame-all { + border-width: 1px; +} +table.frame-ends { + border-width: 1px 0; +} +table.frame-sides { + border-width: 0 1px; +} +table.frame-none > colgroup + * > :first-child > *, +table.frame-sides > colgroup + * > :first-child > * { + border-top-width: 0; +} +table.frame-none > :last-child > :last-child > *, +table.frame-sides > :last-child > :last-child > * { + border-bottom-width: 0; +} +table.frame-none > * > tr > :first-child, +table.frame-ends > * > tr > :first-child { + border-left-width: 0; +} +table.frame-none > * > tr > :last-child, +table.frame-ends > * > tr > :last-child { + border-right-width: 0; +} +table.stripes-all > tbody > tr, +table.stripes-odd > tbody > tr:nth-of-type(odd), +table.stripes-even > tbody > tr:nth-of-type(even), +table.stripes-hover > tbody > tr:hover { + background: #f8f8f7; +} +th.halign-left, +td.halign-left { + text-align: left; +} +th.halign-right, +td.halign-right { + text-align: right; +} +th.halign-center, +td.halign-center { + text-align: center; +} +th.valign-top, +td.valign-top { + vertical-align: top; +} +th.valign-bottom, +td.valign-bottom { + vertical-align: bottom; +} +th.valign-middle, +td.valign-middle { + vertical-align: middle; +} +table thead th, +table tfoot th { + font-weight: bold; +} +tbody tr th { + background: #f7f8f7; +} +tbody tr th, +tbody tr th p, +tfoot tr th, +tfoot tr th p { + color: rgba(0, 0, 0, 0.8); + font-weight: bold; +} +p.tableblock > code:only-child { + background: none; + padding: 0; +} +p.tableblock { + font-size: 1em; +} +ol { + margin-left: 1.75em; +} +ul li ol { + margin-left: 1.5em; +} +dl dd:last-child, +dl dd:last-child > :last-child { + margin-bottom: 0; +} +li p, +ul dd, +ol dd, +.olist .olist, +.ulist .ulist, +.ulist .olist, +.olist .ulist { + margin-bottom: 0.625em; +} +ul.checklist, +ul.none, +ol.none, +ul.no-bullet, +ol.no-bullet, +ol.unnumbered, +ul.unstyled, +ol.unstyled { + list-style-type: none; +} +ul.no-bullet, +ol.no-bullet, +ol.unnumbered { + margin-left: 0.625em; +} +ul.unstyled, +ol.unstyled { + margin-left: 0; +} +li > p:empty:only-child::before { + content: ''; + display: inline-block; +} +ul.checklist > li > p:first-child { + margin-left: -1em; +} +ul.checklist > li > p:first-child > .fa-square-o:first-child, +ul.checklist > li > p:first-child > .fa-check-square-o:first-child { + width: 1.25em; + font-size: 0.8em; + position: relative; + bottom: 0.125em; +} +ul.checklist > li > p:first-child > input[type='checkbox']:first-child { + font: inherit; + margin: 0 0.25em 0 0; + padding: 0; +} +ul.inline { + display: flex; + flex-flow: row wrap; + list-style: none; + margin: 0 0 0.625em -1.25em; +} +ul.inline > li { + margin-left: 1.25em; +} +.unstyled dl dt { + font-weight: 400; + font-style: normal; +} +ol.arabic { + list-style-type: decimal; +} +ol.decimal { + list-style-type: decimal-leading-zero; +} +ol.loweralpha { + list-style-type: lower-alpha; +} +ol.upperalpha { + list-style-type: upper-alpha; +} +ol.lowerroman { + list-style-type: lower-roman; +} +ol.upperroman { + list-style-type: upper-roman; +} +ol.lowergreek { + list-style-type: lower-greek; +} +.hdlist > table, +.colist > table { + border: 0; + background: none; +} +.hdlist > table > tbody > tr, +.colist > table > tbody > tr { + background: none; +} +td.hdlist1, +td.hdlist2 { + vertical-align: top; + padding: 0 0.625em; +} +td.hdlist1 { + font-weight: bold; + padding-bottom: 1.25em; +} +td.hdlist2 { + word-wrap: anywhere; +} +.literalblock + .colist, +.listingblock + .colist { + margin-top: -0.5em; +} +.colist td:not([class]):first-child { + padding: 0.4em 0.75em 0; + line-height: 1; + vertical-align: top; +} +.colist td:not([class]):first-child img { + max-width: none; +} +.colist td:not([class]):last-child { + padding: 0.25em 0; +} +.thumb, +.th { + line-height: 0; + display: inline-block; + border: 4px solid #fff; + box-shadow: 0 0 0 1px #ddd; +} +.imageblock.left { + margin: 0.25em 0.625em 1.25em 0; +} +.imageblock.right { + margin: 0.25em 0 1.25em 0.625em; +} +.imageblock > .title { + margin-bottom: 0; +} +.imageblock.thumb, +.imageblock.th { + border-width: 6px; +} +.imageblock.thumb > .title, +.imageblock.th > .title { + padding: 0 0.125em; +} +.image.left, +.image.right { + margin-top: 0.25em; + margin-bottom: 0.25em; + display: inline-block; + line-height: 0; +} +.image.left { + margin-right: 0.625em; +} +.image.right { + margin-left: 0.625em; +} +a.image { + text-decoration: none; + display: inline-block; +} +a.image object { + pointer-events: none; +} +sup.footnote, +sup.footnoteref { + font-size: 0.875em; + position: static; + vertical-align: super; +} +sup.footnote a, +sup.footnoteref a { + text-decoration: none; +} +sup.footnote a:active, +sup.footnoteref a:active, +#footnotes .footnote a:first-of-type:active { + text-decoration: underline; +} +#footnotes { + padding-top: 0.75em; + padding-bottom: 0.75em; + margin-bottom: 0.625em; +} +#footnotes hr { + width: 20%; + min-width: 6.25em; + margin: -0.25em 0 0.75em; + border-width: 1px 0 0; +} +#footnotes .footnote { + padding: 0 0.375em 0 0.225em; + line-height: 1.3334; + font-size: 0.875em; + margin-left: 1.2em; + margin-bottom: 0.2em; +} +#footnotes .footnote a:first-of-type { + font-weight: bold; + text-decoration: none; + margin-left: -1.05em; +} +#footnotes .footnote:last-of-type { + margin-bottom: 0; +} +#content #footnotes { + margin-top: -0.625em; + margin-bottom: 0; + padding: 0.75em 0; +} +div.page-break { + display: none; +} +div.unbreakable { + -moz-column-break-inside: avoid; + break-inside: avoid; +} +.big { + font-size: larger; +} +.small { + font-size: smaller; +} +.underline { + text-decoration: underline; +} +.overline { + text-decoration: overline; +} +.line-through { + text-decoration: line-through; +} +.aqua { + color: #00bfbf; +} +.aqua-background { + background: #00fafa; +} +.black { + color: #000; +} +.black-background { + background: #000; +} +.blue { + color: #0000bf; +} +.blue-background { + background: #0000fa; +} +.fuchsia { + color: #bf00bf; +} +.fuchsia-background { + background: #fa00fa; +} +.gray { + color: #606060; +} +.gray-background { + background: #7d7d7d; +} +.green { + color: #006000; +} +.green-background { + background: #007d00; +} +.lime { + color: #00bf00; +} +.lime-background { + background: #00fa00; +} +.maroon { + color: #600000; +} +.maroon-background { + background: #7d0000; +} +.navy { + color: #000060; +} +.navy-background { + background: #00007d; +} +.olive { + color: #606000; +} +.olive-background { + background: #7d7d00; +} +.purple { + color: #600060; +} +.purple-background { + background: #7d007d; +} +.red { + color: #bf0000; +} +.red-background { + background: #fa0000; +} +.silver { + color: #909090; +} +.silver-background { + background: #bcbcbc; +} +.teal { + color: #006060; +} +.teal-background { + background: #007d7d; +} +.white { + color: #bfbfbf; +} +.white-background { + background: #fafafa; +} +.yellow { + color: #bfbf00; +} +.yellow-background { + background: #fafa00; +} +span.icon > .fa { + cursor: default; +} +a span.icon > .fa { + cursor: inherit; +} +.admonitionblock td.icon [class^='fa icon-'] { + font-size: 2.5em; + text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); + cursor: default; +} +.admonitionblock td.icon .icon-note::before { + content: '\f05a'; + color: #19407c; +} +.admonitionblock td.icon .icon-tip::before { + content: '\f0eb'; + text-shadow: 1px 1px 2px rgba(155, 155, 0, 0.8); + color: #111; +} +.admonitionblock td.icon .icon-warning::before { + content: '\f071'; + color: #bf6900; +} +.admonitionblock td.icon .icon-caution::before { + content: '\f06d'; + color: #bf3400; +} +.admonitionblock td.icon .icon-important::before { + content: '\f06a'; + color: #bf0000; +} +.conum[data-value] { + display: inline-block; + color: #fff !important; + background: rgba(0, 0, 0, 0.8); + border-radius: 50%; + text-align: center; + font-size: 0.75em; + width: 1.67em; + height: 1.67em; + line-height: 1.67em; + font-family: 'Open Sans', 'DejaVu Sans', sans-serif; + font-style: normal; + font-weight: bold; +} +.conum[data-value] * { + color: #fff !important; +} +.conum[data-value] + b { + display: none; +} +.conum[data-value]::after { + content: attr(data-value); +} +pre .conum[data-value] { + position: relative; + top: -0.125em; +} +b.conum * { + color: inherit !important; +} +.conum:not([data-value]):empty { + display: none; +} +dt, +th.tableblock, +td.content, +div.footnote { + text-rendering: optimizeLegibility; +} +h1, +h2, +p, +td.content, +span.alt, +summary { + letter-spacing: -0.01em; +} +p strong, +td.content strong, +div.footnote strong { + letter-spacing: -0.005em; +} +p, +blockquote, +dt, +td.content, +td.hdlist1, +span.alt, +summary { + font-size: 1.0625rem; +} +.sidebarblock p, +.sidebarblock dt, +.sidebarblock td.content, +p.tableblock { + font-size: 1em; +} +.print-only { + display: none !important; +} +@page { + margin: 1.25cm 0.75cm; +} +@media print { + * { + box-shadow: none !important; + text-shadow: none !important; + } + html { + font-size: 80%; + } + a { + color: inherit !important; + text-decoration: underline !important; + } + a.bare, + a[href^='#'], + a[href^='mailto:'] { + text-decoration: none !important; + } + a[href^='http:']:not(.bare)::after, + a[href^='https:']:not(.bare)::after { + content: '(' attr(href) ')'; + display: inline-block; + font-size: 0.875em; + padding-left: 0.25em; + } + abbr[title] { + border-bottom: 1px dotted; + } + abbr[title]::after { + content: ' (' attr(title) ')'; + } + pre, + blockquote, + tr, + img, + object, + svg { + -moz-column-break-inside: avoid; + break-inside: avoid; + } + thead { + display: table-header-group; + } + p, + blockquote, + dt, + td.content { + font-size: 1em; + orphans: 3; + widows: 3; + } + h2, + h3, + #toctitle, + .sidebarblock > .content > .title { + -moz-column-break-after: avoid; + break-after: avoid; + } + body > div[id] { + max-width: none; + } + #toc, + .sidebarblock, + .exampleblock > .content { + background: none !important; + } + #toc { + border-bottom: 1px solid #dddddf !important; + padding-bottom: 0 !important; + } + body.book #header { + text-align: center; + } + body.book #header > h1:first-child { + border: 0 !important; + margin: 2.5em 0 1em; + } + body.book #header .details { + border: 0 !important; + display: block; + padding: 0 !important; + } + body.book #header .details span:first-child { + margin-left: 0 !important; + } + body.book #header .details br { + display: block; + } + body.book #header .details br + span::before { + content: none !important; + } + body.book #toc { + border: 0 !important; + text-align: left !important; + padding: 0 !important; + margin: 0 !important; + } + body.book #toc, + body.book #preamble, + body.book h1.sect0, + body.book .sect1 > h2 { + -moz-column-break-before: page; + break-before: page; + } + .listingblock code[data-lang]::before { + display: block; + } + div.page-break { + display: block; + -moz-column-break-after: page; + break-after: page; + } + #footer { + padding: 0 0.9375em; + } + .hide-on-print { + display: none !important; + } + .print-only { + display: block !important; + } + .hide-for-print { + display: none !important; + } + .show-for-print { + display: inherit !important; + } +} +@media amzn-kf8, print { + #header > h1:first-child { + margin-top: 1.25rem; + } + .sect1 { + padding: 0 !important; + } + .sect1 + .sect1 { + border: 0; + } + #footer { + background: none; + } + #footer-text { + color: rgba(0, 0, 0, 0.6); + font-size: 0.9em; + } +} +@media amzn-kf8 { + body > div[id] { + padding: 0; + } +} diff --git a/website/src/styles/main.css b/website/src/styles/main.css index 03cae68..fbe6233 100644 --- a/website/src/styles/main.css +++ b/website/src/styles/main.css @@ -1,5 +1,5 @@ -@import "tailwindcss"; -@import "./asciidoctor-scoped.css"; +@import 'tailwindcss'; +@import './asciidoctor-scoped.css'; /* Configure Tailwind v4 dark mode to use class strategy */ @variant dark (.dark &); @@ -26,16 +26,21 @@ body { margin: 0; - font-family: Inter, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-family: Inter, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background-color: var(--color-bg); color: var(--color-text); - transition: background-color 0.3s ease, color 0.3s ease; + transition: + background-color 0.3s ease, + color 0.3s ease; } *, *::before, *::after { - transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; + transition: + background-color 0.3s ease, + color 0.3s ease, + border-color 0.3s ease; } #app { @@ -47,7 +52,7 @@ body { /* Card Grid Styles */ .card-grid-container { @apply max-w-7xl mx-auto px-4 py-8; - font-family: Inter, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-family: Inter, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } #page-content, @@ -57,7 +62,7 @@ body { .anchor-card-meta, .anchor-card-proponents, .category-heading { - font-family: Inter, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + font-family: Inter, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; } /* Force sans-serif on all card descendants to override AsciiDoc serif fonts */ @@ -67,14 +72,14 @@ body { .card-grid-container *, .anchor-cards-grid, .anchor-cards-grid * { - font-family: Inter, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important; + font-family: Inter, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif !important; } .category-section { @apply mb-12; } -.category-section[style*="display: none"] { +.category-section[style*='display: none'] { @apply hidden; } @@ -84,7 +89,7 @@ body { } /* Ensure category name is visible in dark mode */ -.category-heading span[data-i18n^="categories."] { +.category-heading span[data-i18n^='categories.'] { @apply text-gray-900 dark:text-gray-100; } diff --git a/website/src/utils/data-loader.js b/website/src/utils/data-loader.js index 297b2a3..0d40463 100644 --- a/website/src/utils/data-loader.js +++ b/website/src/utils/data-loader.js @@ -1,7 +1,19 @@ const CATEGORY_PALETTE = [ - '#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', - '#3ba272', '#fc8452', '#9a60b4', '#ea7ccc', '#48b8d0', - '#c23531', '#2f4554', '#61a0a8', '#d48265', '#749f83' + '#5470c6', + '#91cc75', + '#fac858', + '#ee6666', + '#73c0de', + '#3ba272', + '#fc8452', + '#9a60b4', + '#ea7ccc', + '#48b8d0', + '#c23531', + '#2f4554', + '#61a0a8', + '#d48265', + '#749f83', ] export function getCategoryColor(index) { @@ -9,32 +21,32 @@ export function getCategoryColor(index) { } export function buildTreemapData(categories, anchors) { - const anchorMap = new Map(anchors.map(a => [a.id, a])) + const anchorMap = new Map(anchors.map((a) => [a.id, a])) return categories.map((category, index) => { const children = category.anchors - .map(anchorId => anchorMap.get(anchorId)) + .map((anchorId) => anchorMap.get(anchorId)) .filter(Boolean) - .map(anchor => ({ + .map((anchor) => ({ id: anchor.id, name: anchor.title, value: 1, roles: anchor.roles || [], - categoryId: category.id + categoryId: category.id, })) return { name: category.name, id: category.id, children, - itemStyle: { color: getCategoryColor(index) } + itemStyle: { color: getCategoryColor(index) }, } }) } export function getAnchorsByRole(anchors, roleId) { if (!roleId) return anchors - return anchors.filter(a => a.roles && a.roles.includes(roleId)) + return anchors.filter((a) => a.roles && a.roles.includes(roleId)) } export function getAnchorsBySearch(anchors, query) { @@ -42,11 +54,13 @@ export function getAnchorsBySearch(anchors, query) { const lowerQuery = query.toLowerCase().trim() - return anchors.filter(anchor => { + return anchors.filter((anchor) => { const titleMatch = anchor.title.toLowerCase().includes(lowerQuery) const idMatch = anchor.id.toLowerCase().includes(lowerQuery) - const tagsMatch = anchor.tags && anchor.tags.some(tag => tag.toLowerCase().includes(lowerQuery)) - const proponentsMatch = anchor.proponents && anchor.proponents.some(p => p.toLowerCase().includes(lowerQuery)) + const tagsMatch = + anchor.tags && anchor.tags.some((tag) => tag.toLowerCase().includes(lowerQuery)) + const proponentsMatch = + anchor.proponents && anchor.proponents.some((p) => p.toLowerCase().includes(lowerQuery)) return titleMatch || idMatch || tagsMatch || proponentsMatch }) @@ -81,8 +95,9 @@ export async function fetchData() { dataPromise = Promise.all([ fetchJson('data/anchors.json'), fetchJson('data/categories.json'), - fetchJson('data/roles.json') - ]).then(([anchors, categories, roles]) => ({ anchors, categories, roles })) + fetchJson('data/roles.json'), + ]) + .then(([anchors, categories, roles]) => ({ anchors, categories, roles })) .catch((error) => { dataPromise = null throw error diff --git a/website/src/utils/data-loader.test.js b/website/src/utils/data-loader.test.js index 019a630..dd19466 100644 --- a/website/src/utils/data-loader.test.js +++ b/website/src/utils/data-loader.test.js @@ -6,34 +6,87 @@ import { getFilteredAnchors, getCategoryColor, fetchData, - __resetDataCacheForTests + __resetDataCacheForTests, } from './data-loader.js' const mockCategories = [ { id: 'testing-quality', name: 'Testing & Quality', - anchors: ['tdd-london-school', 'tdd-chicago-school', 'mutation-testing'] + anchors: ['tdd-london-school', 'tdd-chicago-school', 'mutation-testing'], }, { id: 'architecture-design', name: 'Architecture & Design', - anchors: ['clean-architecture', 'hexagonal-architecture'] - } + anchors: ['clean-architecture', 'hexagonal-architecture'], + }, ] const mockAnchors = [ - { id: 'tdd-london-school', title: 'TDD, London School', categories: ['testing-quality'], roles: ['software-developer', 'qa-engineer'], tags: ['testing', 'tdd', 'mocking'], proponents: ['Steve Freeman', 'Nat Pryce'] }, - { id: 'tdd-chicago-school', title: 'TDD, Chicago School', categories: ['testing-quality'], roles: ['software-developer', 'qa-engineer'], tags: ['testing', 'tdd'], proponents: ['Kent Beck'] }, - { id: 'mutation-testing', title: 'Mutation Testing', categories: ['testing-quality'], roles: ['software-developer', 'qa-engineer'], tags: ['testing', 'quality'], proponents: [] }, - { id: 'clean-architecture', title: 'Clean Architecture', categories: ['architecture-design'], roles: ['software-architect', 'software-developer'], tags: ['architecture', 'design'], proponents: ['Robert C. Martin'] }, - { id: 'hexagonal-architecture', title: 'Hexagonal Architecture', categories: ['architecture-design'], roles: ['software-architect', 'software-developer'], tags: ['architecture', 'ports-and-adapters'], proponents: ['Alistair Cockburn'] } + { + id: 'tdd-london-school', + title: 'TDD, London School', + categories: ['testing-quality'], + roles: ['software-developer', 'qa-engineer'], + tags: ['testing', 'tdd', 'mocking'], + proponents: ['Steve Freeman', 'Nat Pryce'], + }, + { + id: 'tdd-chicago-school', + title: 'TDD, Chicago School', + categories: ['testing-quality'], + roles: ['software-developer', 'qa-engineer'], + tags: ['testing', 'tdd'], + proponents: ['Kent Beck'], + }, + { + id: 'mutation-testing', + title: 'Mutation Testing', + categories: ['testing-quality'], + roles: ['software-developer', 'qa-engineer'], + tags: ['testing', 'quality'], + proponents: [], + }, + { + id: 'clean-architecture', + title: 'Clean Architecture', + categories: ['architecture-design'], + roles: ['software-architect', 'software-developer'], + tags: ['architecture', 'design'], + proponents: ['Robert C. Martin'], + }, + { + id: 'hexagonal-architecture', + title: 'Hexagonal Architecture', + categories: ['architecture-design'], + roles: ['software-architect', 'software-developer'], + tags: ['architecture', 'ports-and-adapters'], + proponents: ['Alistair Cockburn'], + }, ] const mockRoles = [ - { id: 'software-developer', name: 'Software Developer / Engineer', anchors: ['tdd-london-school', 'tdd-chicago-school', 'mutation-testing', 'clean-architecture', 'hexagonal-architecture'] }, - { id: 'qa-engineer', name: 'QA Engineer / Tester', anchors: ['tdd-london-school', 'tdd-chicago-school', 'mutation-testing'] }, - { id: 'software-architect', name: 'Software Architect', anchors: ['clean-architecture', 'hexagonal-architecture'] } + { + id: 'software-developer', + name: 'Software Developer / Engineer', + anchors: [ + 'tdd-london-school', + 'tdd-chicago-school', + 'mutation-testing', + 'clean-architecture', + 'hexagonal-architecture', + ], + }, + { + id: 'qa-engineer', + name: 'QA Engineer / Tester', + anchors: ['tdd-london-school', 'tdd-chicago-school', 'mutation-testing'], + }, + { + id: 'software-architect', + name: 'Software Architect', + anchors: ['clean-architecture', 'hexagonal-architecture'], + }, ] describe('buildTreemapData', () => { @@ -59,7 +112,7 @@ describe('buildTreemapData', () => { it('should include anchor metadata in leaf nodes', () => { const result = buildTreemapData(mockCategories, mockAnchors) - const tddLondon = result[0].children.find(c => c.id === 'tdd-london-school') + const tddLondon = result[0].children.find((c) => c.id === 'tdd-london-school') expect(tddLondon).toBeDefined() expect(tddLondon.name).toBe('TDD, London School') expect(tddLondon.roles).toEqual(['software-developer', 'qa-engineer']) @@ -86,7 +139,7 @@ describe('getAnchorsByRole', () => { const result = getAnchorsByRole(mockAnchors, 'qa-engineer') expect(result).toHaveLength(3) - expect(result.every(a => a.roles.includes('qa-engineer'))).toBe(true) + expect(result.every((a) => a.roles.includes('qa-engineer'))).toBe(true) }) it('should return all anchors when no role specified', () => { @@ -183,7 +236,7 @@ describe('getFilteredAnchors', () => { const result = getFilteredAnchors(mockAnchors, 'qa-engineer', '') expect(result).toHaveLength(3) - expect(result.every(a => a.roles.includes('qa-engineer'))).toBe(true) + expect(result.every((a) => a.roles.includes('qa-engineer'))).toBe(true) }) it('should apply only search filter when no role', () => { diff --git a/website/src/utils/search-index.js b/website/src/utils/search-index.js index 588d912..2f4318f 100644 --- a/website/src/utils/search-index.js +++ b/website/src/utils/search-index.js @@ -16,7 +16,7 @@ export async function buildSearchIndex(anchors) { if (indexReady) return if (buildingPromise) return buildingPromise - console.log('Building search index for', anchors.length, 'anchors...') + console.warn('Building search index for', anchors.length, 'anchors...') buildingPromise = (async () => { for (let i = 0; i < anchors.length; i += BATCH_SIZE) { @@ -38,7 +38,7 @@ export async function buildSearchIndex(anchors) { tags: anchor.tags || [], proponents: anchor.proponents || [], roles: anchor.roles || [], - categories: anchor.categories || [] + categories: anchor.categories || [], }) } catch (error) { console.warn(`Error indexing ${anchor.id}:`, error) @@ -49,7 +49,7 @@ export async function buildSearchIndex(anchors) { } indexReady = true - console.log('Search index built:', searchIndex.size, 'anchors indexed') + console.warn('Search index built:', searchIndex.size, 'anchors indexed') })() try { @@ -73,10 +73,10 @@ function extractSearchableText(adocContent) { text = text.replace(/^=+\s+/gm, '') // Remove block delimiters - text = text.replace(/^[*_\-=]{4,}$/gm, '') + text = text.replace(/^[*_=-]{4,}$/gm, '') // Remove link syntax but keep text - text = text.replace(/link:([^\[]+)\[([^\]]+)\]/g, '$2') + text = text.replace(/link:([^[]+)\[([^\]]+)\]/g, '$2') text = text.replace(/<<([^,]+),([^>]+)>>/g, '$2') text = text.replace(/<<([^>]+)>>/g, '$1') @@ -87,7 +87,7 @@ function extractSearchableText(adocContent) { text = text.replace(/`([^`]+)`/g, '$1') // code // Remove list markers - text = text.replace(/^[\*\-]\s+/gm, '') + text = text.replace(/^[*-]\s+/gm, '') text = text.replace(/^\d+\.\s+/gm, '') // Remove source blocks @@ -122,19 +122,19 @@ export function search(query) { let score = 0 // Check each search word - words.forEach(word => { + words.forEach((word) => { // Title match (highest weight) if (data.title.toLowerCase().includes(word)) { score += 10 } // Proponents match (high weight) - if (data.proponents.some(p => p.toLowerCase().includes(word))) { + if (data.proponents.some((p) => p.toLowerCase().includes(word))) { score += 5 } // Tags match (medium weight) - if (data.tags.some(t => t.toLowerCase().includes(word))) { + if (data.tags.some((t) => t.toLowerCase().includes(word))) { score += 3 } @@ -145,12 +145,16 @@ export function search(query) { }) // Only include if all words matched - if (score > 0 && words.every(word => - data.title.toLowerCase().includes(word) || - data.proponents.some(p => p.toLowerCase().includes(word)) || - data.tags.some(t => t.toLowerCase().includes(word)) || - data.content.includes(word) - )) { + if ( + score > 0 && + words.every( + (word) => + data.title.toLowerCase().includes(word) || + data.proponents.some((p) => p.toLowerCase().includes(word)) || + data.tags.some((t) => t.toLowerCase().includes(word)) || + data.content.includes(word) + ) + ) { matches.push({ anchorId, score }) } }) @@ -158,7 +162,7 @@ export function search(query) { // Sort by score (highest first) matches.sort((a, b) => b.score - a.score) - return matches.map(m => m.anchorId) + return matches.map((m) => m.anchorId) } /** diff --git a/website/src/utils/search-index.test.js b/website/src/utils/search-index.test.js index 15a9fa1..d69f5a7 100644 --- a/website/src/utils/search-index.test.js +++ b/website/src/utils/search-index.test.js @@ -4,7 +4,7 @@ import { search, isIndexReady, isIndexBuilding, - __resetSearchIndexForTests + __resetSearchIndexForTests, } from './search-index.js' describe('search-index', () => { @@ -12,7 +12,7 @@ describe('search-index', () => { __resetSearchIndexForTests() global.fetch = vi.fn().mockResolvedValue({ ok: true, - text: async () => '= Test Anchor\n\nA practical testing method for teams.' + text: async () => '= Test Anchor\n\nA practical testing method for teams.', }) }) @@ -22,8 +22,18 @@ describe('search-index', () => { it('builds index and enables full-text search', async () => { const anchors = [ - { id: 'tdd-london-school', title: 'TDD London', tags: ['testing'], proponents: ['Steve Freeman'] }, - { id: 'clean-architecture', title: 'Clean Architecture', tags: ['architecture'], proponents: ['Robert Martin'] } + { + id: 'tdd-london-school', + title: 'TDD London', + tags: ['testing'], + proponents: ['Steve Freeman'], + }, + { + id: 'clean-architecture', + title: 'Clean Architecture', + tags: ['architecture'], + proponents: ['Robert Martin'], + }, ] await buildSearchIndex(anchors)