From 306e908d94b56c90520b85d8a5e18d3f155e26ae Mon Sep 17 00:00:00 2001 From: FranGuh Date: Sat, 9 May 2026 18:24:49 -0600 Subject: [PATCH 1/8] feat: add foundation-testing-ci track spec - Testing suite: Vitest + RTL, 80% inference coverage target - CI/CD: GitHub Actions, Husky pre-commit hooks - Comparison view: side-by-side laptop comparison - Shareable links: URL-encoded results + WhatsApp sharing - Plan, risks, tasks, acceptance criteria documented --- .../foundation-testing-ci/acceptance.md | 121 ++++++++++ .../tracks/foundation-testing-ci/plan.md | 134 ++++++++++ .../tracks/foundation-testing-ci/risks.md | 105 ++++++++ .../tracks/foundation-testing-ci/spec.md | 64 +++++ .../tracks/foundation-testing-ci/tasks.md | 121 ++++++++++ docs/FEATURE_PLANNING_PHASE2.md | 228 ++++++++++++++++++ 6 files changed, 773 insertions(+) create mode 100644 conductor/tracks/foundation-testing-ci/acceptance.md create mode 100644 conductor/tracks/foundation-testing-ci/plan.md create mode 100644 conductor/tracks/foundation-testing-ci/risks.md create mode 100644 conductor/tracks/foundation-testing-ci/spec.md create mode 100644 conductor/tracks/foundation-testing-ci/tasks.md create mode 100644 docs/FEATURE_PLANNING_PHASE2.md diff --git a/conductor/tracks/foundation-testing-ci/acceptance.md b/conductor/tracks/foundation-testing-ci/acceptance.md new file mode 100644 index 0000000..35a3c9a --- /dev/null +++ b/conductor/tracks/foundation-testing-ci/acceptance.md @@ -0,0 +1,121 @@ +# Acceptance Criteria — Foundation Testing CI + +## Testing Suite + +### Criteria +- [ ] `npm run test` runs all tests and passes +- [ ] Inference engine coverage ≥ 80% +- [ ] Overall project coverage ≥ 60% +- [ ] All 17 previously-fixed bugs have regression tests +- [ ] Edge cases tested: empty answers, conflicting answers, budget extremes +- [ ] Component tests cover: quiz flow, results rendering, fallback UI, error boundary +- [ ] Tests run in < 30 seconds on CI + +### Verification +```bash +npm run test +npm run test:coverage +``` + +## CI/CD Pipeline + +### Criteria +- [ ] GitHub Actions workflow triggers on push/PR to main +- [ ] Workflow runs: lint → test → build +- [ ] Workflow fails if any step errors +- [ ] Status badge visible in README.md +- [ ] Workflow completes in < 5 minutes +- [ ] Dependency caching works (second run is faster) + +### Verification +- Push to test branch, verify workflow runs in GitHub Actions tab +- Intentionally break a test, verify workflow fails +- Fix test, verify workflow passes + +## Pre-commit Hooks + +### Criteria +- [ ] `husky` installed and configured +- [ ] `lint-staged` runs ESLint on staged files only +- [ ] Commit with lint error → blocked +- [ ] Commit with clean code → succeeds +- [ ] `pre-push` hook runs `npm run build` +- [ ] Push with build error → blocked +- [ ] Existing codebase has zero lint violations + +### Verification +```bash +# Test lint blocking +echo "const x = " >> src/test.js +git add src/test.js +git commit -m "test: should fail" # Expected: blocked + +# Test build blocking +# Introduce syntax error, try to push # Expected: blocked +``` + +## Comparison View + +### Criteria +- [ ] "Compare" checkbox visible on each laptop card +- [ ] Selecting 2 laptops enables "Compare" button +- [ ] Selecting 3 laptops enables "Compare" button +- [ ] Clicking "Compare" opens `/compare` route +- [ ] Side-by-side table shows all specs for selected laptops +- [ ] Better specs highlighted in green, worse in red +- [ ] Mobile (320px): stacked cards, usable, no horizontal scroll +- [ ] Desktop (1024px): 2-3 column table +- [ ] Accessibility: screen reader announces comparison content +- [ ] Keyboard navigation works through comparison table +- [ ] Direct navigation to `/compare` without selection → redirects to results + +### Verification +- Complete quiz, select 2 laptops, click Compare +- Verify all specs shown, differences highlighted +- Resize to 320px, verify stacked layout +- Use keyboard Tab/Enter to navigate table +- Navigate directly to `/compare`, verify redirect + +## Shareable Links + +### Criteria +- [ ] "Copy link" button copies URL to clipboard +- [ ] "Share on WhatsApp" button opens WhatsApp with pre-filled message +- [ ] Pasting encoded URL in new tab restores results +- [ ] Invalid/expired URL → redirects to quiz with error message +- [ ] URL length < 2,000 characters for typical quiz results +- [ ] Clipboard copy shows success feedback ("Link copied!") +- [ ] WhatsApp message includes: results summary + link + +### Verification +```bash +# Test encoding +npm run dev +# Complete quiz, click "Copy link" +# Paste URL in new tab, verify results match + +# Test WhatsApp +# Click "Share on WhatsApp", verify message format + +# Test invalid URL +# Navigate to /results?r=invalid, verify redirect to quiz +``` + +## Overall Track Acceptance + +### Criteria +- [ ] All phases complete (1-7) +- [ ] `npm run build` passes +- [ ] `npm run lint` passes +- [ ] `npm run test` passes +- [ ] No new console warnings/errors +- [ ] No breaking changes to existing features +- [ ] All acceptance criteria met +- [ ] Documentation updated (README, docs/) + +### Final Verification +```bash +npm run lint && npm run test && npm run build +``` + +All three must pass with zero errors. diff --git a/conductor/tracks/foundation-testing-ci/plan.md b/conductor/tracks/foundation-testing-ci/plan.md new file mode 100644 index 0000000..881f3c5 --- /dev/null +++ b/conductor/tracks/foundation-testing-ci/plan.md @@ -0,0 +1,134 @@ +# Implementation Plan — Foundation Testing CI + +## Phase Order & Dependencies + +``` +Phase 1: Testing Setup (no dependencies) + ↓ +Phase 2: Inference Engine Tests (depends on Phase 1) + ↓ +Phase 3: Component Tests (depends on Phase 1) + ↓ +Phase 4: CI/CD Pipeline (depends on Phase 2+3 having tests) + ↓ +Phase 5: Pre-commit Hooks (depends on Phase 4) + ↓ +Phase 6: Comparison View (parallel with Phase 1-5) + ↓ +Phase 7: Shareable Links (parallel with Phase 1-5) +``` + +## Phase 1: Testing Setup + +**Files**: `vitest.config.js`, `package.json` (scripts), `src/__tests__/setup.js` +**Steps**: +1. Install: `npm i -D vitest @testing-library/react @testing-library/jest-dom jsdom` +2. Create `vitest.config.js` with `environment: 'jsdom'` +3. Add scripts: `"test": "vitest run"`, `"test:watch": "vitest"`, `"test:coverage": "vitest run --coverage"` +4. Create setup file for RTL matchers +5. Verify: `npm run test` runs with zero tests (passes) + +## Phase 2: Inference Engine Tests + +**Files**: `src/__tests__/convertToSpecs.test.jsx`, `src/__tests__/matchLaptopClass.test.jsx` +**Test Cases**: +- `convertToSpecs`: + - Basic quiz answers → valid spec profile + - Gaming use case → high GPU score, prioritizeGaming flag + - Content creation → high RAM, storage, screen quality flag + - Budget constraints → appropriate spec tier + - macOS + gaming → warning flag + - ChromeOS + heavy tasks → warning flag + - Empty answers → graceful fallback + - Conflicting answers → resolved spec profile + - Apple Silicon matching (M1/M2/M3 not stripped by normalize) + - RAM comparison (string vs number edge cases) +- `matchLaptopClass`: + - Strict matching returns correct models + - Relaxed matching widens budget ~30% + - GPU scoring tiers correct + - No matches → returns empty array (not generic class) + - OS filter works correctly + - Generic class excluded from OS filter + +## Phase 3: Component Tests + +**Files**: `src/__tests__/Recommendation.test.jsx`, `src/__tests__/LaptopCard.test.jsx` +**Test Cases**: +- `Recommendation`: + - Quiz renders with first question + - Answering questions advances step + - Back navigation works + - Results page shows laptop cards + - Fallback UI shows when no matches + - localStorage persistence (mock) + - ErrorBoundary catches render errors +- `LaptopCard`: + - Renders with all props + - Image fallback shows on error + - Match score displays correctly + - Price formatted in MXN + +## Phase 4: CI/CD Pipeline + +**Files**: `.github/workflows/ci.yml` +**Steps**: +1. Create workflow triggering on `push` and `pull_request` to `main` +2. Steps: checkout → setup Node 22.x → cache → `npm ci` → `npm run lint` → `npm run test` → `npm run build` +3. Add status badge to `README.md` +4. Verify: push to branch, confirm workflow runs + +## Phase 5: Pre-commit Hooks + +**Files**: `.husky/pre-commit`, `.husky/pre-push`, `package.json` (lint-staged config) +**Steps**: +1. Install: `npm i -D husky lint-staged` +2. Run: `npx husky init` +3. Configure `lint-staged` in `package.json`: `{"src/**/*.{js,jsx}": ["eslint --fix", "git add"]}` +4. Add `pre-push` hook: `npm run build` +5. Run `eslint --fix .` to clean existing violations first +6. Verify: commit with lint error → blocked + +## Phase 6: Comparison View + +**Files**: `src/components/Comparison/Comparison.jsx`, `src/components/Comparison/Comparison.css`, `src/routes/AppRoutes.jsx` (add route), `src/components/LaptopCard/LaptopCard.jsx` (add checkbox) +**Steps**: +1. Add "Compare" checkbox to `LaptopCard` (controlled by parent state) +2. Create `Comparison` component with side-by-side table +3. Highlight differences: compare each spec, mark better/worse +4. Add route `/compare` in `AppRoutes.jsx` +5. Mobile: stacked cards with sticky labels +6. Desktop: 2-3 column table +7. Accessibility: proper ``, `
`, `aria-label` +8. Test: select 2-3 laptops, verify comparison renders correctly + +## Phase 7: Shareable Links + +**Files**: `src/utils/encodeResults.js`, `src/utils/decodeResults.js`, `src/components/Recommendation/Recommendation.jsx` (add share buttons), `src/routes/AppRoutes.jsx` (handle URL params) +**Steps**: +1. Create `encodeResults(answers, result)` → base64 string +2. Create `decodeResults(encoded)` → parsed answers + result +3. Add URL param handling in `Recommendation.jsx`: if `?r=`, decode and show results +4. Add "Copy link" button → copies `window.location.href` to clipboard +5. Add "Share on WhatsApp" button → opens `wa.me/?text=` +6. Handle invalid/expired URLs → redirect to quiz with error message +7. Test: encode results, paste URL in new tab, verify results restore + +## Estimated Effort + +| Phase | Days | Complexity | +|-------|------|------------| +| 1. Testing Setup | 0.5 | Low | +| 2. Inference Tests | 1.5 | Medium | +| 3. Component Tests | 1 | Medium | +| 4. CI/CD Pipeline | 0.5 | Low | +| 5. Pre-commit Hooks | 0.5 | Low | +| 6. Comparison View | 1.5 | Medium | +| 7. Shareable Links | 1 | Medium | +| **Total** | **~6.5 days** | | + +## Parallel Execution + +- Phases 1-5 can run sequentially (foundation) +- Phases 6-7 can run in parallel with 1-5 (feature work, no dependencies on tests) +- Recommended: 2 agents — one on foundation (1-5), one on features (6-7) diff --git a/conductor/tracks/foundation-testing-ci/risks.md b/conductor/tracks/foundation-testing-ci/risks.md new file mode 100644 index 0000000..a7b88c3 --- /dev/null +++ b/conductor/tracks/foundation-testing-ci/risks.md @@ -0,0 +1,105 @@ +# Risks — Foundation Testing CI + +## Risk Matrix + +| # | Risk | Likelihood | Impact | Mitigation | +|---|------|------------|--------|------------| +| 1 | Inference engine tests reveal more bugs | High | Medium | Fix bugs as found, add to test suite, document in track | +| 2 | Existing lint violations block pre-commit hooks | High | Low | Run `eslint --fix .` before enabling hooks, commit cleanup separately | +| 3 | URL-encoded results exceed browser limits | Medium | Medium | Use compression, fallback to localStorage if URL too long, warn user | +| 4 | Comparison view breaks on mobile with 3 laptops | Medium | Medium | Limit to 2 on mobile, stack vertically, test on 320px breakpoint | +| 5 | CI pipeline fails on first run due to environment differences | Medium | Low | Test workflow locally with `act` or push to test branch first | +| 6 | Test coverage target (80%) is too ambitious for initial suite | Medium | Low | Start with 60% target, increase incrementally, focus on critical paths | +| 7 | Shareable links break if inference logic changes | Low | Medium | Version the encoded data format, add validation on decode | +| 8 | Husky hooks slow down developer workflow | Low | Low | Keep hooks fast (lint only staged files), use `--no-verify` escape hatch | + +## Detailed Risks + +### R1: Inference Engine Tests Reveal More Bugs + +**Scenario**: After writing 15+ test cases for `convertToSpecs.jsx` and `matchLaptopClass.jsx`, edge cases that weren't caught during the 17-bug fix are discovered. + +**Impact**: Tests fail, track timeline extends by 1-2 days. + +**Mitigation**: +- Fix bugs as they're found, add regression tests immediately +- Document each new bug in the track's issue log +- If >5 new bugs found, split into separate bugfix track +- Keep failing tests as `test.skip()` with issue reference until fixed + +### R2: Existing Lint Violations Block Pre-commit Hooks + +**Scenario**: Enabling Husky + lint-staged reveals 50+ existing ESLint warnings/errors across the codebase. + +**Impact**: Developers can't commit until all violations are fixed. + +**Mitigation**: +- Run `eslint --fix .` BEFORE enabling hooks +- Commit cleanup as separate commit: `chore: fix all existing lint violations` +- Enable hooks only after codebase is clean +- Add `--no-verify` as temporary escape hatch for urgent commits + +### R3: URL-Encoded Results Exceed Browser Limits + +**Scenario**: Encoding all 10 quiz answers + result data creates a URL > 2,000 characters, exceeding some browser/proxy limits. + +**Impact**: Shareable links break for complex quiz results. + +**Mitigation**: +- Compress with `pako` (gzip) before base64 encoding +- If still too long, fall back to localStorage + short ID +- Warn user: "This link is long, consider using the copy button" +- Test with maximum data payload (all answers + 3 laptop results) + +### R4: Comparison View Breaks on Mobile with 3 Laptops + +**Scenario**: Side-by-side comparison of 3 laptops on 320px screen is unusable. + +**Impact**: Poor UX on mobile, accessibility issues. + +**Mitigation**: +- Limit to 2 laptops on mobile (disable 3rd checkbox) +- Stack cards vertically with sticky spec labels +- Use horizontal scroll with snap points as alternative +- Test on actual 320px device, not just DevTools + +### R5: CI Pipeline Fails on First Run + +**Scenario**: GitHub Actions workflow fails due to Node version mismatch, missing env vars, or different npm behavior. + +**Impact**: Wasted time debugging CI instead of building features. + +**Mitigation**: +- Use `actions/setup-node@v4` with explicit Node 22.x +- Cache `node_modules` with `actions/cache` +- Test workflow locally with `nektos/act` if available +- Push to test branch first, verify workflow passes before merging + +### R6: Test Coverage Target Too Ambitious + +**Scenario**: Reaching 80% coverage on inference engine requires mocking complex browser APIs or testing trivial code. + +**Impact**: Time spent on coverage metrics instead of meaningful tests. + +**Mitigation**: +- Start with 60% target, increase to 80% in follow-up +- Focus on critical paths: scoring, matching, edge cases +- Exclude trivial code (constants, simple getters) from coverage +- Use `// istanbul ignore next` for untestable browser-specific code + +## Rollback Plan + +If any phase introduces breaking changes: + +1. **Testing/CI**: Revert the workflow/hook commits — no user-facing impact +2. **Comparison View**: Remove `/compare` route and checkbox — existing features unaffected +3. **Shareable Links**: Remove URL decoding logic — existing quiz flow unchanged +4. **Nuclear option**: Revert entire track via `git revert` — all changes are additive, no breaking changes to existing code + +## Monitoring + +After merge: +- Monitor CI pass rate (should be >95%) +- Track test coverage trend (should increase, not decrease) +- Watch for shareable link errors in Vercel logs +- Monitor comparison page usage via Vercel Analytics diff --git a/conductor/tracks/foundation-testing-ci/spec.md b/conductor/tracks/foundation-testing-ci/spec.md new file mode 100644 index 0000000..060aff1 --- /dev/null +++ b/conductor/tracks/foundation-testing-ci/spec.md @@ -0,0 +1,64 @@ +# Track: Foundation — Testing + CI/CD + Comparison + Sharing + +## What + +Build the technical foundation for safe future development while delivering the two most-requested user-facing features: + +1. **Automated Testing Suite**: Vitest + React Testing Library with 80% coverage on inference engine +2. **CI/CD Pipeline**: GitHub Actions with lint → test → build on every PR, Husky pre-commit hooks +3. **Results Comparison View**: Side-by-side laptop comparison (2-3 models) with highlighted differences +4. **Shareable Results Link**: URL-encoded quiz results with "Copy link" and "Share on WhatsApp" buttons + +## Why + +- **17 inference bugs were fixed with zero tests** — regressions are guaranteed without a safety net +- **No CI/CD** — broken code deploys directly to production on every push to main +- **Users narrow down to 2-3 laptops** and need to compare specs before deciding (consensus from 3 agents) +- **Users want to share results** with family/friends via WhatsApp (critical for Mexican market) +- **Agent consensus**: Testing + CI/CD (3 agents), Comparison (3 agents), Sharing (2 agents) — highest priority features + +## Scope + +### Testing +- `vitest` + `@testing-library/react` + `@testing-library/jest-dom` + `jsdom` +- Unit tests for `convertToSpecs.jsx` — each question-to-spec mapping, edge cases +- Unit tests for `matchLaptopClass.jsx` — strict/relaxed matching, GPU scoring +- Component tests for `Recommendation.jsx` — quiz flow, results rendering, fallback +- Tests for `LaptopCard.jsx` — rendering, image fallback, props +- Coverage target: 80% on inference engine, 60% overall + +### CI/CD +- `.github/workflows/ci.yml` — triggers on push/PR to main +- Steps: `npm ci` → `npm run lint` → `npm run test` → `npm run build` +- Node 22.x with dependency caching +- Fail workflow on any step error +- `husky` + `lint-staged` — ESLint on staged files before commit +- `pre-push` hook — run `npm run build` to catch build-time failures + +### Comparison View +- "Compare" checkbox on each `LaptopCard` +- Floating bottom bar when 2-3 selected: "Compare X laptops" +- New route `/compare` with side-by-side table +- Highlight differences: green = better, red = worse +- Mobile: stacked cards with sticky spec labels +- Desktop: 2-3 column table with proper `` headers +- Accessible: `role="region"` per laptop, `aria-label`, text indicators ("✓ Better") + +### Shareable Links +- Encode quiz answers + results into URL query params +- Compression via `btoa(JSON.stringify())` to keep URLs reasonable +- "Copy link" button on results page +- "Share on WhatsApp" button with pre-filled message +- URL format: `/results?r=` +- Decode on load, restore results view +- Fallback: if URL is invalid, redirect to quiz + +## Out of Scope + +- TypeScript migration (separate track) +- Sentry error tracking (separate track) +- PWA / offline support (separate track) +- Price history / deal alerts (separate track) +- Multi-language / i18n (separate track) +- Buying guides / blog content (separate track) +- Dark mode (separate track) diff --git a/conductor/tracks/foundation-testing-ci/tasks.md b/conductor/tracks/foundation-testing-ci/tasks.md new file mode 100644 index 0000000..9bccc66 --- /dev/null +++ b/conductor/tracks/foundation-testing-ci/tasks.md @@ -0,0 +1,121 @@ +# Tasks — Foundation Testing CI + +## Phase 1: Testing Setup + +### Task 1.1: Install Testing Dependencies +- [ ] `npm i -D vitest @testing-library/react @testing-library/jest-dom jsdom` +- [ ] Verify installation: `npm ls vitest` + +### Task 1.2: Configure Vitest +- [ ] Create `vitest.config.js` with `environment: 'jsdom'` +- [ ] Add scripts to `package.json`: + - `"test": "vitest run"` + - `"test:watch": "vitest"` + - `"test:coverage": "vitest run --coverage"` +- [ ] Create `src/__tests__/setup.js` with RTL matchers +- [ ] Verify: `npm run test` passes with zero tests + +## Phase 2: Inference Engine Tests + +### Task 2.1: Test convertToSpecs.jsx +- [ ] Test: Basic quiz answers → valid spec profile +- [ ] Test: Gaming use case → high GPU score, `prioritizeGaming` flag +- [ ] Test: Content creation → high RAM, storage, `prioritizeScreenQuality` flag +- [ ] Test: Budget constraints → appropriate spec tier +- [ ] Test: macOS + gaming → warning flag +- [ ] Test: ChromeOS + heavy tasks → warning flag +- [ ] Test: Empty answers → graceful fallback +- [ ] Test: Conflicting answers → resolved spec profile +- [ ] Test: Apple Silicon matching (M1/M2/M3 not stripped) +- [ ] Test: RAM comparison (string vs number edge cases) + +### Task 2.2: Test matchLaptopClass.jsx +- [ ] Test: Strict matching returns correct models +- [ ] Test: Relaxed matching widens budget ~30% +- [ ] Test: GPU scoring tiers correct +- [ ] Test: No matches → returns empty array (not generic class) +- [ ] Test: OS filter works correctly +- [ ] Test: Generic class excluded from OS filter + +## Phase 3: Component Tests + +### Task 3.1: Test Recommendation.jsx +- [ ] Test: Quiz renders with first question +- [ ] Test: Answering questions advances step +- [ ] Test: Back navigation works +- [ ] Test: Results page shows laptop cards +- [ ] Test: Fallback UI shows when no matches +- [ ] Test: localStorage persistence (mock) +- [ ] Test: ErrorBoundary catches render errors + +### Task 3.2: Test LaptopCard.jsx +- [ ] Test: Renders with all props +- [ ] Test: Image fallback shows on error +- [ ] Test: Match score displays correctly +- [ ] Test: Price formatted in MXN + +## Phase 4: CI/CD Pipeline + +### Task 4.1: Create GitHub Actions Workflow +- [ ] Create `.github/workflows/ci.yml` +- [ ] Configure triggers: `push` and `pull_request` to `main` +- [ ] Steps: checkout → setup Node 22.x → cache → `npm ci` → `npm run lint` → `npm run test` → `npm run build` +- [ ] Add status badge to `README.md` +- [ ] Test: push to branch, confirm workflow runs and passes + +## Phase 5: Pre-commit Hooks + +### Task 5.1: Setup Husky + lint-staged +- [ ] Install: `npm i -D husky lint-staged` +- [ ] Run: `npx husky init` +- [ ] Configure `lint-staged` in `package.json` +- [ ] Create `.husky/pre-commit` with lint-staged +- [ ] Create `.husky/pre-push` with `npm run build` +- [ ] Run `eslint --fix .` to clean existing violations +- [ ] Test: commit with lint error → blocked + +## Phase 6: Comparison View + +### Task 6.1: Add Compare Checkbox to LaptopCard +- [ ] Add checkbox prop to `LaptopCard` +- [ ] Controlled by parent state in `Recommendation.jsx` +- [ ] Floating bottom bar appears when 2-3 selected +- [ ] "Compare X laptops" button opens `/compare` route + +### Task 6.2: Create Comparison Component +- [ ] Create `src/components/Comparison/Comparison.jsx` +- [ ] Create `src/components/Comparison/Comparison.css` +- [ ] Side-by-side table with spec comparison +- [ ] Highlight differences: green = better, red = worse +- [ ] Mobile: stacked cards with sticky labels +- [ ] Desktop: 2-3 column table +- [ ] Accessibility: proper ``, `
`, `aria-label` + +### Task 6.3: Add Comparison Route +- [ ] Add `/compare` route in `AppRoutes.jsx` +- [ ] Pass selected laptops via state or context +- [ ] Handle direct navigation (no laptops selected → redirect) +- [ ] Test: select 2-3 laptops, verify comparison renders + +## Phase 7: Shareable Links + +### Task 7.1: Create Encoding/Decoding Utilities +- [ ] Create `src/utils/encodeResults.js` +- [ ] Create `src/utils/decodeResults.js` +- [ ] Encode: `btoa(JSON.stringify({ answers, result }))` +- [ ] Decode: `JSON.parse(atob(encoded))` +- [ ] Add validation on decode +- [ ] Handle invalid/expired data gracefully + +### Task 7.2: Add URL Param Handling +- [ ] Modify `Recommendation.jsx` to check for `?r=` on mount +- [ ] If present, decode and restore results view +- [ ] If invalid, redirect to quiz with error message +- [ ] Test: encode results, paste URL in new tab, verify restore + +### Task 7.3: Add Share Buttons +- [ ] "Copy link" button → copies `window.location.href` to clipboard +- [ ] "Share on WhatsApp" button → opens `wa.me/?text=` +- [ ] Message format: "Me recomiendan estas laptops: [link]" +- [ ] Test: copy link, paste in new tab, verify results +- [ ] Test: share on WhatsApp, verify message format diff --git a/docs/FEATURE_PLANNING_PHASE2.md b/docs/FEATURE_PLANNING_PHASE2.md new file mode 100644 index 0000000..c870561 --- /dev/null +++ b/docs/FEATURE_PLANNING_PHASE2.md @@ -0,0 +1,228 @@ +# Feature Planning — Phase 2 + +> Generated by Dream Team on 2026-05-09 +> Status: Ideas & Planning — Not yet approved for implementation + +--- + +## Consolidated Feature Matrix + +| # | Feature | Agent | Priority | Overlap | +|---|---------|-------|----------|---------| +| 1 | TypeScript Migration | AgentArquitecto | High | — | +| 2 | Automated Testing (Vitest + RTL) | AgentArquitecto, KimiBuildWorker, JuniorQwen | High | ✅ 3 agents | +| 3 | CSP Headers | AgentArquitecto | High | — | +| 4 | External Laptop DB + API | AgentArquitecto | Medium | — | +| 5 | Pre-commit Hooks + CI Pipeline | AgentArquitecto, KimiBuildWorker, JuniorQwen | High | ✅ 3 agents | +| 6 | State Management (Zustand) | AgentArquitecto | Medium | — | +| 7 | Input Validation (Zod) | AgentArquitecto, JuniorQwen | Medium | ✅ 2 agents | +| 8 | Bundle Analysis + Perf Monitoring | AgentArquitecto, KimiBuildWorker | Medium | ✅ 2 agents | +| 9 | Results Comparison View | AgentQA, AgentResearch, UIUXAdvisor | High | ✅ 3 agents | +| 10 | Shareable Results Link | AgentQA, AgentResearch | High | ✅ 2 agents | +| 11 | Quiz Retake with Modified Answers | AgentQA | Medium | — | +| 12 | User Feedback Mechanism | AgentQA, AgentResearch | Medium | ✅ 2 agents | +| 13 | Keyboard/Screen Reader Audit | AgentQA | Medium | — | +| 14 | Analytics Dashboard (Privacy-First) | AgentQA | Low | — | +| 15 | PWA / Offline-First | AgentQA, AgentPerformance | Low | ✅ 2 agents | +| 16 | Route-Level Code Splitting | AgentPerformance | High | — | +| 17 | Inference Result Caching (Idempotency) | AgentPerformance | High | — | +| 18 | Service Worker Caching | AgentPerformance | Medium | — | +| 19 | Memoization (React.memo, useMemo) | AgentPerformance | Medium | — | +| 20 | GPU Scoring Deduplication | AgentPerformance | Medium | — | +| 21 | Throttled localStorage Writes | AgentPerformance | High | — | +| 22 | Image Optimization (WebP/AVIF) | AgentPerformance | Medium | — | +| 23 | Side-by-Side Comparison | AgentResearch | High | ✅ overlaps #9 | +| 24 | "How We Recommend" Transparency Page | AgentResearch | High | — | +| 25 | Price History & Deal Alerts | AgentResearch | Medium | — | +| 26 | Buying Guides by Use Case (SEO) | AgentResearch | High | — | +| 27 | Budget Range Visualizer | AgentResearch | Low | — | +| 28 | Multi-Language (i18n) | AgentResearch | Low | — | +| 29 | GitHub Actions CI/CD | KimiBuildWorker, JuniorQwen | High | ✅ 2 agents | +| 30 | Build Size Budgets | KimiBuildWorker | Medium | ✅ overlaps #8 | +| 31 | Vercel Preview Deployments | KimiBuildWorker | Medium | — | +| 32 | Semantic Release + Changelog | KimiBuildWorker | Medium | — | +| 33 | Env Variable Validation | KimiBuildWorker | Low | — | +| 34 | Error Boundary System | JuniorQwen | High | — | +| 35 | Sentry Error Tracking | JuniorQwen | Medium | — | +| 36 | Dependency Update Automation | JuniorQwen | Medium | — | +| 37 | Hotfix Workflow + Rollback | JuniorQwen | Low | — | +| 38 | Dark Mode | UIUXAdvisor | High | — | +| 39 | Page Transitions & Micro-interactions | UIUXAdvisor | High | — | +| 40 | Visual Spec Indicators (Bars/Chart) | UIUXAdvisor | Medium | — | +| 41 | Print/Export PDF Results | UIUXAdvisor | Medium | — | +| 42 | Skeleton Loading States | UIUXAdvisor | Medium | — | +| 43 | FAQ Section + Structured Data | UIUXAdvisor | High | — | +| 44 | Breadcrumbs + Internal Linking | UIUXAdvisor | Medium | — | + +--- + +## Consensus High-Priority Features (3+ agents or critical) + +### 🔴 Tier 1 — Foundation (Must Have) + +#### 1. Automated Testing Suite +**Agents**: AgentArquitecto, KimiBuildWorker, JuniorQwen +- Vitest + React Testing Library +- Start with inference engine unit tests (17 bugs fixed, zero tests) +- Component tests for Quiz, Results, ErrorBoundary +- Target: 80% coverage on inference engine + +#### 2. CI/CD Pipeline + Pre-commit Hooks +**Agents**: AgentArquitecto, KimiBuildWorker, JuniorQwen +- GitHub Actions: lint → test → build on every PR +- Husky + lint-staged for pre-commit validation +- Block merges on CI failure +- Vercel preview deployments per PR + +#### 3. Results Comparison View +**Agents**: AgentQA, AgentResearch, UIUXAdvisor +- Side-by-side laptop comparison (2-3 models) +- Highlight spec differences (green = better, red = worse) +- Mobile: stacked cards. Desktop: table layout +- Accessible table with proper `` headers + +#### 4. Shareable Results Link +**Agents**: AgentQA, AgentResearch +- Encode quiz answers + results into URL +- "Copy link" and "Share on WhatsApp" buttons +- WhatsApp is critical for Mexican market +- Enables word-of-mouth growth + +### 🟡 Tier 2 — Quality & Performance (Should Have) + +#### 5. TypeScript Migration +**Agent**: AgentArquitecto +- Incremental: `.tsx` + `allowJs: true` +- Define interfaces for Laptop, UserAnswers, SpecProfile +- Start with data models, then components, then inference engine + +#### 6. Route-Level Code Splitting +**Agent**: AgentPerformance +- `React.lazy()` + `Suspense` for each route +- Defer Recommendation page until after quiz +- Estimated 30-40% reduction in initial JS bundle + +#### 7. Inference Result Caching (Idempotency) +**Agent**: AgentPerformance +- Deterministic cache key from answers (sorted JSON hash) +- `sessionStorage` or in-memory `Map` with 24h TTL +- Eliminates ~5-15ms on repeated runs + +#### 8. Throttled localStorage Writes +**Agent**: AgentPerformance +- Debounce writes: only on step changes, not every toggle +- Reduces localStorage writes from ~10+ to 2-3 per quiz +- Improves INP during quiz-taking + +#### 9. "How We Recommend" Transparency Page +**Agent**: AgentResearch +- Visual breakdown of scoring engine +- How each quiz answer maps to specs +- Builds trust, doubles as SEO content + +#### 10. Buying Guides by Use Case (SEO) +**Agent**: AgentResearch +- Static guide pages: /guides/gaming, /guides/engineering, /guides/students +- Embedded quiz CTA: "¿No estás seguro? Haz nuestro quiz" +- Drives organic traffic + +#### 11. Dark Mode +**Agent**: UIUXAdvisor +- `prefers-color-scheme` detection + manual toggle +- Use existing CSS variable system +- Persist in localStorage + +#### 12. Page Transitions & Micro-interactions +**Agent**: UIUXAdvisor +- Slide transitions for quiz questions +- Staggered fade-in for results +- Respect `prefers-reduced-motion` + +#### 13. FAQ Section + Structured Data +**Agent**: UIUXAdvisor +- Accordion on landing page +- JSON-LD `FAQPage` schema for rich snippets +- 6-8 questions addressing common concerns + +### 🟢 Tier 3 — Nice to Have (Could Have) + +#### 14. User Feedback Mechanism +**Agents**: AgentQA, AgentResearch +- 👍/👎 per laptop card +- Aggregate counts: "X users found this helpful" +- Phase 2: anonymous backend aggregation + +#### 15. Image Optimization (WebP/AVIF) +**Agent**: AgentPerformance +- Convert 35 laptop images to WebP +- Add `srcset`, `sizes`, `decoding="async"` +- 60-80% reduction in image payload + +#### 16. CSP Headers +**Agent**: AgentArquitecto +- `vercel.json` with CSP headers +- Start with `report-only` mode +- Protect against XSS + +#### 17. Sentry Error Tracking +**Agent**: JuniorQwen +- Production error visibility +- Breadcrumb tracking for quiz steps +- Source maps for readable stack traces + +#### 18. Skeleton Loading States +**Agent**: UIUXAdvisor +- Skeleton cards during inference +- `aria-busy="true"` for screen readers +- Reduces perceived wait time + +#### 19. Print/Export PDF Results +**Agent**: UIUXAdvisor +- `@media print` stylesheet +- QR code linking back to app +- Users take recommendations to physical stores + +#### 20. Dependency Update Automation +**Agent**: JuniorQwen +- Dependabot or Renovate +- `npm audit` in CI pipeline +- Pin critical dependencies + +--- + +## Recommended Next Track + +Based on agent consensus, the next track should focus on **Tier 1 Foundation**: + +``` +track/foundation-testing-ci +├── spec.md # Testing + CI/CD + Comparison + Sharing +├── plan.md # Implementation order, dependencies +├── risks.md # What could break, rollback plan +└── tasks/ + ├── 01-vitest-setup.md + ├── 02-inference-tests.md + ├── 03-component-tests.md + ├── 04-github-actions-ci.md + ├── 05-husky-precommit.md + ├── 06-comparison-view.md + └── 07-shareable-links.md +``` + +**Estimated effort**: 3-5 days +**Risk**: Low (additive, no breaking changes to existing features) +**Impact**: Prevents regressions, enables safe future development, adds most-requested UX features + +--- + +## Agent Sign-Off + +| Agent | Model | Status | +|-------|-------|--------| +| AgentArquitecto | deepseek-v4-pro | ✅ | +| AgentQA | GLM-5 | ✅ | +| AgentPerformance | minimax-m2.7 | ✅ | +| AgentResearch | gemini-2.5-pro | ✅ | +| KimiBuildWorker | kimi-k2.6 | ✅ | +| JuniorQwen | MiMo 2.5V Pro | ✅ | +| UIUXAdvisor | openai/gpt-5.5 | ✅ | From f3d3703962f12e079f3d1d51bc5c71f367d96996 Mon Sep 17 00:00:00 2001 From: FranGuh Date: Sat, 9 May 2026 18:47:07 -0600 Subject: [PATCH 2/8] feat: testing foundation + inference engine tests (56 tests, 92% coverage) - Install vitest, RTL, jsdom - Create vitest.config.js with jsdom environment - 34 convertToSpecs tests: gaming, creation, macOS, ChromeOS, budget, edge cases - 22 matchLaptopClass tests: GPU/OS/budget filtering, scoring, sorting - Fix normalize() regex stripping M1/M2/M3/M4 from Apple Silicon - Add gamesType scoring (complex +3 gfx, simple +1) - Add photoVideo:basic scoring (+1 gfx, +1 storage) - Add missing flags: prioritizeGaming, prioritizeScreenQuality - Coverage: 92% statements, 88% branches, 100% functions --- coverage/base.css | 224 +++ coverage/block-navigation.js | 87 + coverage/clover.xml | 219 +++ coverage/convertToSpecs.jsx.html | 1162 +++++++++++ coverage/coverage-final.json | 4 + coverage/favicon.png | Bin 0 -> 445 bytes coverage/index.html | 146 ++ coverage/laptopModels.js.html | 898 +++++++++ coverage/matchLaptopClass.jsx.html | 367 ++++ coverage/prettify.css | 1 + coverage/prettify.js | 2 + coverage/sort-arrow-sprite.png | Bin 0 -> 138 bytes coverage/sorter.js | 210 ++ package-lock.json | 2375 ++++++++++++++++++++++- package.json | 12 +- pnpm-lock.yaml | 1721 ++++++++++++++++ pnpm-workspace.yaml | 3 + src/__tests__/convertToSpecs.test.jsx | 345 ++++ src/__tests__/matchLaptopClass.test.jsx | 310 +++ src/__tests__/setup.js | 1 + vitest.config.js | 12 + 21 files changed, 8041 insertions(+), 58 deletions(-) create mode 100644 coverage/base.css create mode 100644 coverage/block-navigation.js create mode 100644 coverage/clover.xml create mode 100644 coverage/convertToSpecs.jsx.html create mode 100644 coverage/coverage-final.json create mode 100644 coverage/favicon.png create mode 100644 coverage/index.html create mode 100644 coverage/laptopModels.js.html create mode 100644 coverage/matchLaptopClass.jsx.html create mode 100644 coverage/prettify.css create mode 100644 coverage/prettify.js create mode 100644 coverage/sort-arrow-sprite.png create mode 100644 coverage/sorter.js create mode 100644 pnpm-lock.yaml create mode 100644 pnpm-workspace.yaml create mode 100644 src/__tests__/convertToSpecs.test.jsx create mode 100644 src/__tests__/matchLaptopClass.test.jsx create mode 100644 src/__tests__/setup.js create mode 100644 vitest.config.js diff --git a/coverage/base.css b/coverage/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js new file mode 100644 index 0000000..530d1ed --- /dev/null +++ b/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/coverage/clover.xml b/coverage/clover.xml new file mode 100644 index 0000000..4d2dedb --- /dev/null +++ b/coverage/clover.xml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/coverage/convertToSpecs.jsx.html b/coverage/convertToSpecs.jsx.html new file mode 100644 index 0000000..5be3d81 --- /dev/null +++ b/coverage/convertToSpecs.jsx.html @@ -0,0 +1,1162 @@ + + + + + + Code coverage report for convertToSpecs.jsx + + + + + + + + + +
+
+

All files convertToSpecs.jsx

+
+ +
+ 92.99% + Statements + 146/157 +
+ + +
+ 88.26% + Branches + 173/196 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 93.42% + Lines + 142/152 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360  +  +1x +5x +5x +5x +  +  +1x +4x +4x +4x +4x +4x +  +  +  +1x +35x +  +  +  +  +  +  +  +  +  +  +  +  +35x +  +  +35x +  +  +  +  +  +  +  +  +35x +  +  +  +35x +5x +  +  +35x +4x +  +  +  +  +35x +12x +12x +12x +12x +12x +5x +7x +1x +  +  +  +35x +6x +6x +6x +6x +6x +  +  +35x +1x +1x +  +  +35x +5x +5x +5x +  +  +35x +1x +1x +  +  +  +35x +2x +2x +  +  +35x +1x +1x +  +  +35x +1x +1x +  +  +35x +1x +  +  +35x +1x +  +  +  +35x +1x +1x +  +  +  +35x +  +  +  +  +35x +5x +  +30x +  +  +  +  +  +35x +2x +  +  +35x +  +  +  +  +2x +  +  +  +  +35x +  +35x +  +  +  +  +35x +  +  +  +  +35x +5x +  +  +  +35x +35x +  +  +35x +  +26x +26x +  +5x +5x +  +4x +4x +  +  +  +  +  +35x +5x +2x +  +5x +  +  +5x +  +  +  +5x +5x +5x +  +  +  +35x +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +35x +4x +1x +  +4x +  +  +4x +1x +  +4x +1x +1x +1x +  +  +  +35x +  +  +  +35x +2x +  +  +  +35x +35x +  +  +  +  +35x +9x +  +  +  +  +  +7x +7x +  +  +9x +8x +8x +  +  +  +35x +35x +  +  +35x +35x +76x +76x +64x +59x +59x +  +  +35x +9x +  +9x +5x +  +  +9x +  +9x +9x +  +  +  +  +35x +  +  +18x +  +6x +  +  +  +35x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +35x +  +  +  +  +  +3x +  +  +  +35x +  +  +  +  +35x +  +  +  +  +  +35x +1x +  +  +35x +  +  +  +  +  +  +  +  +35x +  +  +  + 
import matchLaptopClass from './matchLaptopClass'
+ 
+const forceAppleSpecs = (specs, scores) => {
+  specs.processor = 'Apple M1 / M2 / M3'
+  specs.gpu = 'Apple Silicon GPU (M1/M2/M3)'
+  scores.graphics = 0
+}
+ 
+const forceChromeSpecs = (specs) => {
+  specs.processor = 'Intel Celeron / MediaTek'
+  specs.ram = '4 GB o 8 GB'
+  specs.gpu = 'Gráficos integrados básicos'
+  specs.storage = '64 GB eMMC o 128 GB SSD'
+  specs.rationale.push('ChromeOS está pensado para tareas básicas con hardware modesto.')
+}
+ 
+ 
+const convertToSpecs = (answers) => {
+  const specs = {
+    processor: '',
+    ram: '',
+    storage: '',
+    gpu: '',
+    touchscreen: false,
+    portability: '',
+    os: '',
+    battery: '',
+    rationale: [],
+    warnings: []
+  }
+ 
+  specs.budget = answers.budget
+ 
+ 
+  const scores = {
+    performance: 0,
+    graphics: 0,
+    multitasking: 0,
+    portability: 0,
+    battery: 0,
+    storage: 0
+  }
+ 
+  const uses = answers.mainUse === 'full_use'
+    ? ['basics', 'entertainment', 'gaming', 'work_school', 'creating', 'family']
+    : answers.mainUse || []
+ 
+  if (answers.system === 'mac') {
+    forceAppleSpecs(specs, scores)
+  }
+ 
+  if (answers.system === 'chrome') {
+    forceChromeSpecs(specs)
+  }
+ 
+ 
+  // ==== 1. Scoring según uso ====
+  if (uses.includes('gaming')) {
+    scores.performance += 3.5
+    scores.graphics += 4.5
+    scores.storage += 1
+    specs.rationale.push('Para gaming se requiere GPU dedicada y CPU potente.')
+    if (answers.gamesType === 'complex') {
+      scores.graphics += 3
+    } else if (answers.gamesType === 'simple') {
+      scores.graphics += 1
+    }
+  }
+ 
+  if (uses.includes('creating') || answers.photoVideo === 'pro') {
+    scores.performance += 3
+    scores.graphics += 2.5
+    scores.multitasking += 2
+    scores.storage += 2
+    specs.rationale.push('Creación de contenido profesional requiere potencia y espacio.')
+  }
+ 
+  if (answers.photoVideo === 'basic') {
+    scores.graphics += 1
+    scores.storage += 1
+  }
+ 
+  if (uses.includes('work_school') || answers.workload === 'heavy') {
+    scores.performance += 2.5
+    scores.multitasking += 2.5
+    specs.rationale.push('Trabajo exigente necesita buena CPU y multitarea.')
+  }
+ 
+  if (uses.includes('family')) {
+    scores.multitasking += 1
+    specs.rationale.push('Uso compartido en casa requiere buena capacidad multitarea.')
+  }
+ 
+ 
+  if (answers.webBrowsing === 'heavy') {
+    scores.multitasking += 1.5
+    specs.rationale.push('Muchas pestañas requieren buena RAM.')
+  }
+ 
+  if (answers.location === 'mobile') {
+    scores.portability += 3
+    specs.rationale.push('Necesitas algo ligero y fácil de transportar.')
+  }
+ 
+  if (answers.importance === 'touch') {
+    specs.touchscreen = true
+    specs.rationale.push('Requiere pantalla táctil.')
+  }
+ 
+  if (answers.importance === 'security') {
+    specs.rationale.push('Prioritás seguridad. Buscamos modelos con lector de huellas o reconocimiento facial si es posible.')
+  }
+ 
+  if (answers.importance === 'portability') {
+    specs.rationale.push('Buscás un equipo liviano. Se prioriza que pese menos de 1.5 kg y tenga una pantalla de 14" o menor.')
+  }
+ 
+ 
+  if (answers.battery === 'yes') {
+    scores.battery += 3
+    specs.rationale.push('Priorizás batería de larga duración.')
+  }
+ 
+  // ==== 2. Especificaciones recomendadas ====
+  specs.processor =
+    scores.performance >= 6 ? 'Intel i7 / Ryzen 7' :
+      scores.performance >= 4 ? 'Intel i5 / Ryzen 5' :
+        'Intel i3 / Ryzen 3 o Celeron'
+ 
+  if (answers.system === 'mac') {
+    specs.ram = scores.multitasking >= 4 ? '16 GB' : '8 GB'
+  } else {
+    specs.ram =
+      scores.multitasking >= 4 ? '16 GB' :
+        scores.multitasking >= 2 ? '12 GB' :
+          '8 GB'
+  }
+ 
+  if (answers.system === 'mac' && uses.includes('gaming')) {
+    specs.warnings.push('macOS no es compatible con la mayoría de juegos exigentes. Considerá Windows si es prioridad.')
+  }
+ 
+  if (answers.system === 'chrome' && (
+    answers.photoVideo === 'pro' ||
+    answers.workload === 'heavy' ||
+    uses.includes('gaming'))
+  ) {
+    specs.warnings.push('ChromeOS no es ideal para tareas pesadas o software profesional. Considerá Windows o macOS.')
+  }
+ 
+ 
+ 
+  specs.storage = scores.storage >= 2 ? '512 GB SSD' : '256 GB SSD'
+ 
+  Iif (specs.storage === '256 GB SSD' && uses.includes('creating')) {
+    specs.warnings.push('256 GB puede llenarse rápido con archivos de diseño o video. Considerá 512 GB o más.')
+  }
+ 
+ 
+  specs.gpu =
+    scores.graphics >= 4 ? 'NVIDIA RTX 3050 o superior' :
+      scores.graphics >= 2 ? 'Intel Iris Xe / Radeon Vega' :
+        'Gráficos integrados básicos'
+  // Override para macOS: siempre Apple Silicon
+  if (answers.system === 'mac') {
+    specs.gpu = 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)'
+  }
+ 
+ 
+  specs.portability = scores.portability >= 3 ? 'Alta (menos de 1.5kg, 14")' : 'Media o baja'
+  specs.battery = scores.battery >= 3 ? 'Alta (8h o más)' : 'Media (4-7h)'
+ 
+  // ==== 3. Sistema operativo filtrado ====
+  switch (answers.system) {
+    case 'windows':
+      specs.os = 'Windows 11'
+      break
+    case 'mac':
+      specs.os = 'macOS'
+      break
+    case 'chrome':
+      specs.os = 'ChromeOS'
+      break
+    default:
+      specs.os = 'Windows 11'
+  }
+ 
+  // ==== 4. Validaciones cruzadas ====
+  if (specs.os === 'macOS') {
+    if (uses.includes('gaming')) {
+      specs.warnings.push('macOS no es ideal para juegos exigentes ni es compatible con la mayoría de títulos AAA. Recomendamos Windows.')
+    }
+    Iif (uses.includes('gaming') && answers.system === 'chrome') {
+      specs.warnings.unshift('⚠️ Elegiste "juegos exigentes" pero seleccionaste ChromeOS, que no soporta juegos avanzados. Recomendamos Windows si querés jugar títulos como GTA, FIFA o similares.')
+    }
+    Iif (specs.gpu.includes('RTX')) {
+      specs.warnings.push('Mac no usa GPUs dedicadas tipo RTX, sus chips integrados (M1/M2/M3) tienen gráficas propias.')
+      specs.gpu = 'Apple Silicon GPU (M1/M2/M3)'
+    }
+    Eif (specs.processor.includes('i7') || specs.processor.includes('Ryzen')) {
+      specs.processor = 'Apple M1 / M2 / M3'
+      specs.rationale.push('En macOS se usan procesadores Apple Silicon, no Intel/AMD tradicionales.')
+    }
+  }
+ 
+  if (
+    specs.os === 'macOS' &&
+    answers.budget === 'low' &&
+    (
+      uses.includes('gaming') ||
+      uses.includes('creating') ||
+      uses.includes('work_school') ||
+      answers.workload === 'heavy' ||
+      answers.photoVideo === 'pro'
+    )
+  ) {
+    specs.warnings.unshift('⚠️ Querés hacer cosas muy exigentes en una Mac, pero con menos de $5,000 MXN. No es viable. Recomendamos subir el presupuesto o considerar una laptop con Windows.')
+  }
+ 
+ 
+  if (specs.os === 'ChromeOS') {
+    if (answers.workload === 'heavy') {
+      specs.warnings.push('ChromeOS no es ideal para multitarea exigente ni software pesado.')
+    }
+    Iif (answers.photoVideo === 'pro') {
+      specs.warnings.push('ChromeOS no soporta software profesional como Photoshop o Premiere. Considerá Windows o macOS.')
+    }
+    if (uses.includes('gaming') && answers.gamesType === 'complex') {
+      specs.warnings.push('ChromeOS no puede ejecutar juegos complejos como GTA o FIFA.')
+    }
+    if (specs.gpu.includes('RTX') || specs.processor.includes('i7')) {
+      specs.warnings.push('ChromeOS no usa hardware de alto rendimiento como RTX o i7. Recomendamos evitar este OS si necesitás potencia.')
+      specs.gpu = 'Gráficos integrados básicos'
+      specs.processor = 'Intel Celeron / MediaTek'
+    }
+  }
+ 
+  Iif (answers.budget === 'low' && (specs.processor.includes('i5') || specs.ram === '16 GB')) {
+    specs.warnings.push('Tu presupuesto bajo podría no alcanzar para estas specs. Considerá ajustar prioridades o aumentar presupuesto.')
+  }
+ 
+  if (specs.gpu.includes('RTX') && answers.budget === 'low') {
+    specs.warnings.push('Una GPU RTX requiere un presupuesto medio o alto. Podrías no encontrar algo accesible con esas specs.')
+  }
+ 
+  // ==== 5. RAM recomendada vs real ====
+  const idealRam = scores.multitasking >= 4 ? '16 GB' : scores.multitasking >= 2 ? '12 GB' : '8 GB'
+  Iif (specs.ram !== idealRam) {
+    specs.warnings.push(`Idealmente deberías tener ${idealRam} de RAM para tu uso.`)
+  }
+ 
+  // ==== 6. Validación de coherencia interna de specs ====
+  if (specs.gpu.includes('RTX')) {
+    if (
+      !specs.processor.includes('i5') &&
+      !specs.processor.includes('i7') &&
+      !specs.processor.includes('Ryzen 5') &&
+      !specs.processor.includes('Ryzen 7')
+    ) {
+      specs.processor = 'Intel i5 / Ryzen 5'
+      specs.rationale.push('Se ajustó el procesador porque una GPU RTX requiere al menos un i5 o Ryzen 5.')
+    }
+ 
+    if (specs.ram !== '16 GB') {
+      specs.ram = '16 GB'
+      specs.rationale.push('Se ajustó la RAM porque una GPU RTX necesita al menos 16 GB para funcionar correctamente.')
+    }
+  }
+ 
+  const bestModels = matchLaptopClass(specs)
+  specs.laptopClass = bestModels // 👈 ya es un array de modelos
+ 
+ 
+  const osFilter = specs.os.toLowerCase()
+  specs.laptopClass = bestModels.filter(m => {
+      const name = m.name.toLowerCase()
+      if (osFilter.includes('chrome') && !name.includes('chromebook')) return false
+      if (osFilter.includes('macos') && !name.includes('macbook')) return false
+      Iif (osFilter.includes('windows') && name.includes('macbook')) return false
+      return true
+    }) || []
+ 
+if (specs.laptopClass.length === 0) {
+  let fallbackMsg = '⚠️ No encontramos laptops con ';
+ 
+  if (specs.os === 'ChromeOS') fallbackMsg += 'ChromeOS ';
+  else if (specs.os === 'macOS') fallbackMsg += 'macOS ';
+  else EfallbackMsg += 'el sistema operativo deseado ';
+ 
+  fallbackMsg += 'para tus necesidades. Mostramos opciones similares con Windows.';
+ 
+  specs.warnings.push(fallbackMsg);
+  specs.laptopClass = bestModels;
+}
+ 
+ 
+  // Verificación si el modelo tiene menos RAM que la recomendada
+  if (
+    Array.isArray(bestModels) &&
+    specs.ram === '12 GB' &&
+    bestModels.some(m => m.specs.toLowerCase().includes('8 gb'))
+  ) {
+    specs.warnings.push('Uno de los modelos sugeridos tiene 8 GB de RAM, que puede ser justo si hacés mucha multitarea.')
+  }
+ 
+  // ==== 7. Advertencia crítica por combinación inviable ====
+  Iif (
+    specs.os === 'ChromeOS' &&
+    answers.budget === 'low' &&
+    (
+      uses.includes('gaming') ||
+      uses.includes('creating') ||
+      uses.includes('work_school') ||
+      answers.workload === 'heavy' ||
+      answers.photoVideo === 'pro'
+    )
+  ) {
+    specs.warnings.unshift('⚠️ Estás solicitando tareas exigentes (edición, juegos, multitarea) en un sistema muy limitado (ChromeOS con presupuesto bajo). No es viable cumplir esos requerimientos con esta combinación. Recomendamos considerar Windows con mayor inversión.')
+  }
+ 
+ 
+  if (answers.budget === 'low' && (
+    scores.performance >= 4 ||
+    scores.graphics >= 2 ||
+    scores.multitasking >= 4 ||
+    uses.includes('creating')
+  )) {
+    specs.warnings.push('⚠️ Tu presupuesto bajo puede limitar el rendimiento que necesitás. Considerá ajustar prioridades o aumentar presupuesto.')
+  }
+ 
+  // === VALIDACIÓN DE USO CONTRADICTORIO ===
+  const isBasicUseOnly = Array.isArray(answers.mainUse)
+    ? answers.mainUse.length === 1 && answers.mainUse[0] === 'entertainment'
+    : answers.mainUse === 'entertainment'
+ 
+  const isDemandingUser =
+    answers.workload === 'heavy' ||
+    answers.photoVideo === 'pro' ||
+    scores.performance >= 4 ||
+    scores.multitasking >= 4 ||
+    scores.graphics >= 2.5
+ 
+  if (isBasicUseOnly && isDemandingUser) {
+    specs.warnings.unshift('⚠️ Elegiste un uso simple como "ver videos", pero tus respuestas indican tareas exigentes como edición o multitarea pesada. Considerá ajustar tu selección inicial para mejorar la recomendación.')
+  }
+ 
+  specs.flags = {
+    prioritizePortability: answers.importance === 'portability',
+    prioritizeSecurity: answers.importance === 'security',
+    prioritizeGaming: uses.includes('gaming'),
+    prioritizeScreenQuality: answers.photoVideo === 'pro'
+  }
+ 
+ 
+ 
+  return specs
+}
+ 
+export default convertToSpecs
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json new file mode 100644 index 0000000..2b89fca --- /dev/null +++ b/coverage/coverage-final.json @@ -0,0 +1,4 @@ +{"E:\\Job\\ComputerSelectorHelper\\src\\utils\\convertToSpecs.jsx": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\convertToSpecs.jsx","statementMap":{"0":{"start":{"line":3,"column":24},"end":{"line":7,"column":null}},"1":{"start":{"line":4,"column":2},"end":{"line":4,"column":null}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":null}},"3":{"start":{"line":6,"column":2},"end":{"line":6,"column":null}},"4":{"start":{"line":9,"column":25},"end":{"line":15,"column":null}},"5":{"start":{"line":10,"column":2},"end":{"line":10,"column":null}},"6":{"start":{"line":11,"column":2},"end":{"line":11,"column":null}},"7":{"start":{"line":12,"column":2},"end":{"line":12,"column":null}},"8":{"start":{"line":13,"column":2},"end":{"line":13,"column":null}},"9":{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},"10":{"start":{"line":18,"column":23},"end":{"line":357,"column":null}},"11":{"start":{"line":19,"column":16},"end":{"line":30,"column":null}},"12":{"start":{"line":32,"column":2},"end":{"line":32,"column":null}},"13":{"start":{"line":35,"column":17},"end":{"line":42,"column":null}},"14":{"start":{"line":44,"column":15},"end":{"line":46,"column":27}},"15":{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},"16":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"17":{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},"18":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"19":{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},"20":{"start":{"line":59,"column":4},"end":{"line":59,"column":null}},"21":{"start":{"line":60,"column":4},"end":{"line":60,"column":null}},"22":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"23":{"start":{"line":62,"column":4},"end":{"line":62,"column":null}},"24":{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},"25":{"start":{"line":64,"column":6},"end":{"line":64,"column":null}},"26":{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},"27":{"start":{"line":66,"column":6},"end":{"line":66,"column":null}},"28":{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},"29":{"start":{"line":71,"column":4},"end":{"line":71,"column":null}},"30":{"start":{"line":72,"column":4},"end":{"line":72,"column":null}},"31":{"start":{"line":73,"column":4},"end":{"line":73,"column":null}},"32":{"start":{"line":74,"column":4},"end":{"line":74,"column":null}},"33":{"start":{"line":75,"column":4},"end":{"line":75,"column":null}},"34":{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},"35":{"start":{"line":79,"column":4},"end":{"line":79,"column":null}},"36":{"start":{"line":80,"column":4},"end":{"line":80,"column":null}},"37":{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},"38":{"start":{"line":84,"column":4},"end":{"line":84,"column":null}},"39":{"start":{"line":85,"column":4},"end":{"line":85,"column":null}},"40":{"start":{"line":86,"column":4},"end":{"line":86,"column":null}},"41":{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},"42":{"start":{"line":90,"column":4},"end":{"line":90,"column":null}},"43":{"start":{"line":91,"column":4},"end":{"line":91,"column":null}},"44":{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},"45":{"start":{"line":96,"column":4},"end":{"line":96,"column":null}},"46":{"start":{"line":97,"column":4},"end":{"line":97,"column":null}},"47":{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},"48":{"start":{"line":101,"column":4},"end":{"line":101,"column":null}},"49":{"start":{"line":102,"column":4},"end":{"line":102,"column":null}},"50":{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},"51":{"start":{"line":106,"column":4},"end":{"line":106,"column":null}},"52":{"start":{"line":107,"column":4},"end":{"line":107,"column":null}},"53":{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},"54":{"start":{"line":111,"column":4},"end":{"line":111,"column":null}},"55":{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},"56":{"start":{"line":115,"column":4},"end":{"line":115,"column":null}},"57":{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},"58":{"start":{"line":120,"column":4},"end":{"line":120,"column":null}},"59":{"start":{"line":121,"column":4},"end":{"line":121,"column":null}},"60":{"start":{"line":125,"column":2},"end":{"line":128,"column":null}},"61":{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},"62":{"start":{"line":131,"column":4},"end":{"line":131,"column":null}},"63":{"start":{"line":133,"column":4},"end":{"line":136,"column":null}},"64":{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},"65":{"start":{"line":140,"column":4},"end":{"line":140,"column":null}},"66":{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},"67":{"start":{"line":148,"column":4},"end":{"line":148,"column":null}},"68":{"start":{"line":153,"column":2},"end":{"line":153,"column":null}},"69":{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},"70":{"start":{"line":156,"column":4},"end":{"line":156,"column":null}},"71":{"start":{"line":160,"column":2},"end":{"line":163,"column":null}},"72":{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},"73":{"start":{"line":166,"column":4},"end":{"line":166,"column":null}},"74":{"start":{"line":170,"column":2},"end":{"line":170,"column":null}},"75":{"start":{"line":171,"column":2},"end":{"line":171,"column":null}},"76":{"start":{"line":174,"column":2},"end":{"line":186,"column":null}},"77":{"start":{"line":176,"column":6},"end":{"line":176,"column":null}},"78":{"start":{"line":177,"column":6},"end":{"line":177,"column":null}},"79":{"start":{"line":179,"column":6},"end":{"line":179,"column":null}},"80":{"start":{"line":180,"column":6},"end":{"line":180,"column":null}},"81":{"start":{"line":182,"column":6},"end":{"line":182,"column":null}},"82":{"start":{"line":183,"column":6},"end":{"line":183,"column":null}},"83":{"start":{"line":185,"column":6},"end":{"line":185,"column":null}},"84":{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},"85":{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},"86":{"start":{"line":191,"column":6},"end":{"line":191,"column":null}},"87":{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},"88":{"start":{"line":194,"column":6},"end":{"line":194,"column":null}},"89":{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},"90":{"start":{"line":197,"column":6},"end":{"line":197,"column":null}},"91":{"start":{"line":198,"column":6},"end":{"line":198,"column":null}},"92":{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},"93":{"start":{"line":201,"column":6},"end":{"line":201,"column":null}},"94":{"start":{"line":202,"column":6},"end":{"line":202,"column":null}},"95":{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},"96":{"start":{"line":217,"column":4},"end":{"line":217,"column":null}},"97":{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},"98":{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},"99":{"start":{"line":223,"column":6},"end":{"line":223,"column":null}},"100":{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},"101":{"start":{"line":226,"column":6},"end":{"line":226,"column":null}},"102":{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},"103":{"start":{"line":229,"column":6},"end":{"line":229,"column":null}},"104":{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},"105":{"start":{"line":232,"column":6},"end":{"line":232,"column":null}},"106":{"start":{"line":233,"column":6},"end":{"line":233,"column":null}},"107":{"start":{"line":234,"column":6},"end":{"line":234,"column":null}},"108":{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},"109":{"start":{"line":239,"column":4},"end":{"line":239,"column":null}},"110":{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},"111":{"start":{"line":243,"column":4},"end":{"line":243,"column":null}},"112":{"start":{"line":247,"column":19},"end":{"line":247,"column":null}},"113":{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},"114":{"start":{"line":249,"column":4},"end":{"line":249,"column":null}},"115":{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},"116":{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},"117":{"start":{"line":260,"column":6},"end":{"line":260,"column":null}},"118":{"start":{"line":261,"column":6},"end":{"line":261,"column":null}},"119":{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},"120":{"start":{"line":265,"column":6},"end":{"line":265,"column":null}},"121":{"start":{"line":266,"column":6},"end":{"line":266,"column":null}},"122":{"start":{"line":270,"column":8},"end":{"line":270,"column":null}},"123":{"start":{"line":271,"column":2},"end":{"line":271,"column":null}},"124":{"start":{"line":274,"column":19},"end":{"line":274,"column":null}},"125":{"start":{"line":275,"column":2},"end":{"line":281,"column":null}},"126":{"start":{"line":276,"column":19},"end":{"line":276,"column":null}},"127":{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},"128":{"start":{"line":277,"column":71},"end":{"line":277,"column":null}},"129":{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},"130":{"start":{"line":278,"column":67},"end":{"line":278,"column":null}},"131":{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},"132":{"start":{"line":279,"column":68},"end":{"line":279,"column":null}},"133":{"start":{"line":280,"column":6},"end":{"line":280,"column":null}},"134":{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},"135":{"start":{"line":284,"column":20},"end":{"line":284,"column":null}},"136":{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},"137":{"start":{"line":286,"column":31},"end":{"line":286,"column":null}},"138":{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},"139":{"start":{"line":287,"column":33},"end":{"line":287,"column":null}},"140":{"start":{"line":288,"column":7},"end":{"line":288,"column":null}},"141":{"start":{"line":290,"column":2},"end":{"line":290,"column":null}},"142":{"start":{"line":292,"column":2},"end":{"line":292,"column":null}},"143":{"start":{"line":293,"column":2},"end":{"line":293,"column":null}},"144":{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},"145":{"start":{"line":301,"column":25},"end":{"line":301,"column":null}},"146":{"start":{"line":303,"column":4},"end":{"line":303,"column":null}},"147":{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},"148":{"start":{"line":318,"column":4},"end":{"line":318,"column":null}},"149":{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},"150":{"start":{"line":328,"column":4},"end":{"line":328,"column":null}},"151":{"start":{"line":332,"column":25},"end":{"line":334,"column":null}},"152":{"start":{"line":337,"column":4},"end":{"line":341,"column":null}},"153":{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},"154":{"start":{"line":344,"column":4},"end":{"line":344,"column":null}},"155":{"start":{"line":347,"column":2},"end":{"line":352,"column":null}},"156":{"start":{"line":356,"column":2},"end":{"line":356,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":24},"end":{"line":3,"column":25}},"loc":{"start":{"line":3,"column":32},"end":{"line":7,"column":null}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":9,"column":25},"end":{"line":9,"column":26}},"loc":{"start":{"line":9,"column":26},"end":{"line":15,"column":null}},"line":9},"2":{"name":"(anonymous_2)","decl":{"start":{"line":18,"column":23},"end":{"line":18,"column":24}},"loc":{"start":{"line":18,"column":24},"end":{"line":357,"column":null}},"line":18},"3":{"name":"(anonymous_3)","decl":{"start":{"line":275,"column":40},"end":{"line":275,"column":null}},"loc":{"start":{"line":275,"column":40},"end":{"line":281,"column":10}},"line":275},"4":{"name":"(anonymous_4)","decl":{"start":{"line":301,"column":20},"end":{"line":301,"column":25}},"loc":{"start":{"line":301,"column":25},"end":{"line":301,"column":null}},"line":301}},"branchMap":{"0":{"loc":{"start":{"line":44,"column":15},"end":{"line":46,"column":27}},"type":"cond-expr","locations":[{"start":{"line":45,"column":6},"end":{"line":45,"column":null}},{"start":{"line":46,"column":6},"end":{"line":46,"column":27}}],"line":44},"1":{"loc":{"start":{"line":46,"column":6},"end":{"line":46,"column":27}},"type":"binary-expr","locations":[{"start":{"line":46,"column":6},"end":{"line":46,"column":21}},{"start":{"line":46,"column":25},"end":{"line":46,"column":27}}],"line":46},"2":{"loc":{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},"type":"if","locations":[{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},{"start":{},"end":{}}],"line":48},"3":{"loc":{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},"type":"if","locations":[{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},{"start":{},"end":{}}],"line":52},"4":{"loc":{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},"type":"if","locations":[{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},{"start":{},"end":{}}],"line":58},"5":{"loc":{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},{"start":{"line":65,"column":11},"end":{"line":67,"column":null}}],"line":63},"6":{"loc":{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},{"start":{},"end":{}}],"line":65},"7":{"loc":{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},"type":"if","locations":[{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},{"start":{},"end":{}}],"line":70},"8":{"loc":{"start":{"line":70,"column":6},"end":{"line":70,"column":65}},"type":"binary-expr","locations":[{"start":{"line":70,"column":6},"end":{"line":70,"column":35}},{"start":{"line":70,"column":35},"end":{"line":70,"column":65}}],"line":70},"9":{"loc":{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},{"start":{},"end":{}}],"line":78},"10":{"loc":{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},"type":"if","locations":[{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},{"start":{},"end":{}}],"line":83},"11":{"loc":{"start":{"line":83,"column":6},"end":{"line":83,"column":68}},"type":"binary-expr","locations":[{"start":{"line":83,"column":6},"end":{"line":83,"column":38}},{"start":{"line":83,"column":38},"end":{"line":83,"column":68}}],"line":83},"12":{"loc":{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},"type":"if","locations":[{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},{"start":{},"end":{}}],"line":89},"13":{"loc":{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},"type":"if","locations":[{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},{"start":{},"end":{}}],"line":95},"14":{"loc":{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},"type":"if","locations":[{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},{"start":{},"end":{}}],"line":100},"15":{"loc":{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},"type":"if","locations":[{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},{"start":{},"end":{}}],"line":105},"16":{"loc":{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},"type":"if","locations":[{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},{"start":{},"end":{}}],"line":110},"17":{"loc":{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},"type":"if","locations":[{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},{"start":{},"end":{}}],"line":114},"18":{"loc":{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},"type":"if","locations":[{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},{"start":{},"end":{}}],"line":119},"19":{"loc":{"start":{"line":126,"column":4},"end":{"line":128,"column":null}},"type":"cond-expr","locations":[{"start":{"line":126,"column":30},"end":{"line":126,"column":null}},{"start":{"line":127,"column":6},"end":{"line":128,"column":null}}],"line":126},"20":{"loc":{"start":{"line":127,"column":6},"end":{"line":128,"column":null}},"type":"cond-expr","locations":[{"start":{"line":127,"column":32},"end":{"line":127,"column":null}},{"start":{"line":128,"column":8},"end":{"line":128,"column":null}}],"line":127},"21":{"loc":{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},"type":"if","locations":[{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},{"start":{"line":132,"column":9},"end":{"line":137,"column":null}}],"line":130},"22":{"loc":{"start":{"line":131,"column":16},"end":{"line":131,"column":null}},"type":"cond-expr","locations":[{"start":{"line":131,"column":43},"end":{"line":131,"column":53}},{"start":{"line":131,"column":53},"end":{"line":131,"column":null}}],"line":131},"23":{"loc":{"start":{"line":134,"column":6},"end":{"line":136,"column":null}},"type":"cond-expr","locations":[{"start":{"line":134,"column":33},"end":{"line":134,"column":null}},{"start":{"line":135,"column":8},"end":{"line":136,"column":null}}],"line":134},"24":{"loc":{"start":{"line":135,"column":8},"end":{"line":136,"column":null}},"type":"cond-expr","locations":[{"start":{"line":135,"column":35},"end":{"line":135,"column":null}},{"start":{"line":136,"column":10},"end":{"line":136,"column":null}}],"line":135},"25":{"loc":{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},"type":"if","locations":[{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},{"start":{},"end":{}}],"line":139},"26":{"loc":{"start":{"line":139,"column":6},"end":{"line":139,"column":59}},"type":"binary-expr","locations":[{"start":{"line":139,"column":6},"end":{"line":139,"column":34}},{"start":{"line":139,"column":34},"end":{"line":139,"column":59}}],"line":139},"27":{"loc":{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},"type":"if","locations":[{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},{"start":{},"end":{}}],"line":143},"28":{"loc":{"start":{"line":143,"column":6},"end":{"line":146,"column":null}},"type":"binary-expr","locations":[{"start":{"line":143,"column":6},"end":{"line":143,"column":null}},{"start":{"line":144,"column":4},"end":{"line":144,"column":null}},{"start":{"line":145,"column":4},"end":{"line":145,"column":null}},{"start":{"line":146,"column":4},"end":{"line":146,"column":26}}],"line":143},"29":{"loc":{"start":{"line":153,"column":18},"end":{"line":153,"column":null}},"type":"cond-expr","locations":[{"start":{"line":153,"column":40},"end":{"line":153,"column":55}},{"start":{"line":153,"column":55},"end":{"line":153,"column":null}}],"line":153},"30":{"loc":{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},"type":"if","locations":[{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},{"start":{},"end":{}}],"line":155},"31":{"loc":{"start":{"line":155,"column":6},"end":{"line":155,"column":67}},"type":"binary-expr","locations":[{"start":{"line":155,"column":6},"end":{"line":155,"column":40}},{"start":{"line":155,"column":40},"end":{"line":155,"column":67}}],"line":155},"32":{"loc":{"start":{"line":161,"column":4},"end":{"line":163,"column":null}},"type":"cond-expr","locations":[{"start":{"line":161,"column":27},"end":{"line":161,"column":null}},{"start":{"line":162,"column":6},"end":{"line":163,"column":null}}],"line":161},"33":{"loc":{"start":{"line":162,"column":6},"end":{"line":163,"column":null}},"type":"cond-expr","locations":[{"start":{"line":162,"column":29},"end":{"line":162,"column":null}},{"start":{"line":163,"column":8},"end":{"line":163,"column":null}}],"line":162},"34":{"loc":{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},"type":"if","locations":[{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},{"start":{},"end":{}}],"line":165},"35":{"loc":{"start":{"line":170,"column":22},"end":{"line":170,"column":null}},"type":"cond-expr","locations":[{"start":{"line":170,"column":48},"end":{"line":170,"column":79}},{"start":{"line":170,"column":79},"end":{"line":170,"column":null}}],"line":170},"36":{"loc":{"start":{"line":171,"column":18},"end":{"line":171,"column":null}},"type":"cond-expr","locations":[{"start":{"line":171,"column":40},"end":{"line":171,"column":60}},{"start":{"line":171,"column":60},"end":{"line":171,"column":null}}],"line":171},"37":{"loc":{"start":{"line":174,"column":2},"end":{"line":186,"column":null}},"type":"switch","locations":[{"start":{"line":175,"column":4},"end":{"line":177,"column":null}},{"start":{"line":178,"column":4},"end":{"line":180,"column":null}},{"start":{"line":181,"column":4},"end":{"line":183,"column":null}},{"start":{"line":184,"column":4},"end":{"line":185,"column":null}}],"line":174},"38":{"loc":{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},"type":"if","locations":[{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},{"start":{},"end":{}}],"line":189},"39":{"loc":{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},"type":"if","locations":[{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},{"start":{},"end":{}}],"line":190},"40":{"loc":{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},"type":"if","locations":[{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},{"start":{},"end":{}}],"line":193},"41":{"loc":{"start":{"line":193,"column":8},"end":{"line":193,"column":64}},"type":"binary-expr","locations":[{"start":{"line":193,"column":8},"end":{"line":193,"column":35}},{"start":{"line":193,"column":35},"end":{"line":193,"column":64}}],"line":193},"42":{"loc":{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},"type":"if","locations":[{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},{"start":{},"end":{}}],"line":196},"43":{"loc":{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},"type":"if","locations":[{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},{"start":{},"end":{}}],"line":200},"44":{"loc":{"start":{"line":200,"column":8},"end":{"line":200,"column":77}},"type":"binary-expr","locations":[{"start":{"line":200,"column":8},"end":{"line":200,"column":42}},{"start":{"line":200,"column":42},"end":{"line":200,"column":77}}],"line":200},"45":{"loc":{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},"type":"if","locations":[{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},{"start":{},"end":{}}],"line":206},"46":{"loc":{"start":{"line":207,"column":4},"end":{"line":214,"column":null}},"type":"binary-expr","locations":[{"start":{"line":207,"column":4},"end":{"line":207,"column":null}},{"start":{"line":208,"column":4},"end":{"line":208,"column":null}},{"start":{"line":210,"column":6},"end":{"line":210,"column":null}},{"start":{"line":211,"column":6},"end":{"line":211,"column":null}},{"start":{"line":212,"column":6},"end":{"line":212,"column":null}},{"start":{"line":213,"column":6},"end":{"line":213,"column":null}},{"start":{"line":214,"column":6},"end":{"line":214,"column":33}}],"line":207},"47":{"loc":{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},"type":"if","locations":[{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},{"start":{},"end":{}}],"line":221},"48":{"loc":{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},"type":"if","locations":[{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},{"start":{},"end":{}}],"line":222},"49":{"loc":{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},"type":"if","locations":[{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},{"start":{},"end":{}}],"line":225},"50":{"loc":{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},"type":"if","locations":[{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},{"start":{},"end":{}}],"line":228},"51":{"loc":{"start":{"line":228,"column":8},"end":{"line":228,"column":68}},"type":"binary-expr","locations":[{"start":{"line":228,"column":8},"end":{"line":228,"column":35}},{"start":{"line":228,"column":35},"end":{"line":228,"column":68}}],"line":228},"52":{"loc":{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},"type":"if","locations":[{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},{"start":{},"end":{}}],"line":231},"53":{"loc":{"start":{"line":231,"column":8},"end":{"line":231,"column":69}},"type":"binary-expr","locations":[{"start":{"line":231,"column":8},"end":{"line":231,"column":37}},{"start":{"line":231,"column":37},"end":{"line":231,"column":69}}],"line":231},"54":{"loc":{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},"type":"if","locations":[{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},{"start":{},"end":{}}],"line":238},"55":{"loc":{"start":{"line":238,"column":6},"end":{"line":238,"column":93}},"type":"binary-expr","locations":[{"start":{"line":238,"column":6},"end":{"line":238,"column":35}},{"start":{"line":238,"column":35},"end":{"line":238,"column":69}},{"start":{"line":238,"column":69},"end":{"line":238,"column":89}}],"line":238},"56":{"loc":{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},"type":"if","locations":[{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},{"start":{},"end":{}}],"line":242},"57":{"loc":{"start":{"line":242,"column":6},"end":{"line":242,"column":61}},"type":"binary-expr","locations":[{"start":{"line":242,"column":6},"end":{"line":242,"column":35}},{"start":{"line":242,"column":35},"end":{"line":242,"column":61}}],"line":242},"58":{"loc":{"start":{"line":247,"column":19},"end":{"line":247,"column":null}},"type":"cond-expr","locations":[{"start":{"line":247,"column":46},"end":{"line":247,"column":56}},{"start":{"line":247,"column":56},"end":{"line":247,"column":null}}],"line":247},"59":{"loc":{"start":{"line":247,"column":56},"end":{"line":247,"column":null}},"type":"cond-expr","locations":[{"start":{"line":247,"column":83},"end":{"line":247,"column":93}},{"start":{"line":247,"column":93},"end":{"line":247,"column":null}}],"line":247},"60":{"loc":{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},"type":"if","locations":[{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},{"start":{},"end":{}}],"line":248},"61":{"loc":{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},"type":"if","locations":[{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},{"start":{},"end":{}}],"line":253},"62":{"loc":{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},"type":"if","locations":[{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},{"start":{},"end":{}}],"line":254},"63":{"loc":{"start":{"line":255,"column":6},"end":{"line":258,"column":null}},"type":"binary-expr","locations":[{"start":{"line":255,"column":6},"end":{"line":255,"column":null}},{"start":{"line":256,"column":6},"end":{"line":256,"column":null}},{"start":{"line":257,"column":6},"end":{"line":257,"column":null}},{"start":{"line":258,"column":6},"end":{"line":258,"column":null}}],"line":255},"64":{"loc":{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},"type":"if","locations":[{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},{"start":{},"end":{}}],"line":264},"65":{"loc":{"start":{"line":275,"column":22},"end":{"line":281,"column":12}},"type":"binary-expr","locations":[{"start":{"line":275,"column":22},"end":{"line":281,"column":10}},{"start":{"line":281,"column":10},"end":{"line":281,"column":12}}],"line":275},"66":{"loc":{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},"type":"if","locations":[{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},{"start":{},"end":{}}],"line":277},"67":{"loc":{"start":{"line":277,"column":10},"end":{"line":277,"column":71}},"type":"binary-expr","locations":[{"start":{"line":277,"column":10},"end":{"line":277,"column":41}},{"start":{"line":277,"column":41},"end":{"line":277,"column":71}}],"line":277},"68":{"loc":{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},"type":"if","locations":[{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},{"start":{},"end":{}}],"line":278},"69":{"loc":{"start":{"line":278,"column":10},"end":{"line":278,"column":67}},"type":"binary-expr","locations":[{"start":{"line":278,"column":10},"end":{"line":278,"column":40}},{"start":{"line":278,"column":40},"end":{"line":278,"column":67}}],"line":278},"70":{"loc":{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},"type":"if","locations":[{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},{"start":{},"end":{}}],"line":279},"71":{"loc":{"start":{"line":279,"column":10},"end":{"line":279,"column":68}},"type":"binary-expr","locations":[{"start":{"line":279,"column":10},"end":{"line":279,"column":42}},{"start":{"line":279,"column":42},"end":{"line":279,"column":68}}],"line":279},"72":{"loc":{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},"type":"if","locations":[{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},{"start":{},"end":{}}],"line":283},"73":{"loc":{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},"type":"if","locations":[{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},{"start":{"line":287,"column":7},"end":{"line":288,"column":null}}],"line":286},"74":{"loc":{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},"type":"if","locations":[{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},{"start":{"line":288,"column":7},"end":{"line":288,"column":null}}],"line":287},"75":{"loc":{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},"type":"if","locations":[{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},{"start":{},"end":{}}],"line":298},"76":{"loc":{"start":{"line":299,"column":4},"end":{"line":301,"column":null}},"type":"binary-expr","locations":[{"start":{"line":299,"column":4},"end":{"line":299,"column":null}},{"start":{"line":300,"column":4},"end":{"line":300,"column":null}},{"start":{"line":301,"column":4},"end":{"line":301,"column":null}}],"line":299},"77":{"loc":{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},"type":"if","locations":[{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},{"start":{},"end":{}}],"line":307},"78":{"loc":{"start":{"line":308,"column":4},"end":{"line":315,"column":null}},"type":"binary-expr","locations":[{"start":{"line":308,"column":4},"end":{"line":308,"column":null}},{"start":{"line":309,"column":4},"end":{"line":309,"column":null}},{"start":{"line":311,"column":6},"end":{"line":311,"column":null}},{"start":{"line":312,"column":6},"end":{"line":312,"column":null}},{"start":{"line":313,"column":6},"end":{"line":313,"column":null}},{"start":{"line":314,"column":6},"end":{"line":314,"column":null}},{"start":{"line":315,"column":6},"end":{"line":315,"column":33}}],"line":308},"79":{"loc":{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},"type":"if","locations":[{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},{"start":{},"end":{}}],"line":322},"80":{"loc":{"start":{"line":322,"column":6},"end":{"line":326,"column":null}},"type":"binary-expr","locations":[{"start":{"line":322,"column":6},"end":{"line":322,"column":null}},{"start":{"line":323,"column":4},"end":{"line":323,"column":null}},{"start":{"line":324,"column":4},"end":{"line":324,"column":null}},{"start":{"line":325,"column":4},"end":{"line":325,"column":null}},{"start":{"line":326,"column":4},"end":{"line":326,"column":28}}],"line":322},"81":{"loc":{"start":{"line":332,"column":25},"end":{"line":334,"column":null}},"type":"cond-expr","locations":[{"start":{"line":333,"column":6},"end":{"line":333,"column":null}},{"start":{"line":334,"column":6},"end":{"line":334,"column":null}}],"line":332},"82":{"loc":{"start":{"line":333,"column":6},"end":{"line":333,"column":null}},"type":"binary-expr","locations":[{"start":{"line":333,"column":6},"end":{"line":333,"column":38}},{"start":{"line":333,"column":38},"end":{"line":333,"column":null}}],"line":333},"83":{"loc":{"start":{"line":337,"column":4},"end":{"line":341,"column":null}},"type":"binary-expr","locations":[{"start":{"line":337,"column":4},"end":{"line":337,"column":null}},{"start":{"line":338,"column":4},"end":{"line":338,"column":null}},{"start":{"line":339,"column":4},"end":{"line":339,"column":null}},{"start":{"line":340,"column":4},"end":{"line":340,"column":null}},{"start":{"line":341,"column":4},"end":{"line":341,"column":null}}],"line":337},"84":{"loc":{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},"type":"if","locations":[{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},{"start":{},"end":{}}],"line":343},"85":{"loc":{"start":{"line":343,"column":6},"end":{"line":343,"column":41}},"type":"binary-expr","locations":[{"start":{"line":343,"column":6},"end":{"line":343,"column":24}},{"start":{"line":343,"column":24},"end":{"line":343,"column":41}}],"line":343}},"s":{"0":1,"1":5,"2":5,"3":5,"4":1,"5":4,"6":4,"7":4,"8":4,"9":4,"10":1,"11":35,"12":35,"13":35,"14":35,"15":35,"16":5,"17":35,"18":4,"19":35,"20":12,"21":12,"22":12,"23":12,"24":12,"25":5,"26":7,"27":1,"28":35,"29":6,"30":6,"31":6,"32":6,"33":6,"34":35,"35":1,"36":1,"37":35,"38":5,"39":5,"40":5,"41":35,"42":1,"43":1,"44":35,"45":2,"46":2,"47":35,"48":1,"49":1,"50":35,"51":1,"52":1,"53":35,"54":1,"55":35,"56":1,"57":35,"58":1,"59":1,"60":35,"61":35,"62":5,"63":30,"64":35,"65":2,"66":35,"67":2,"68":35,"69":35,"70":0,"71":35,"72":35,"73":5,"74":35,"75":35,"76":35,"77":26,"78":26,"79":5,"80":5,"81":4,"82":4,"83":0,"84":35,"85":5,"86":2,"87":5,"88":0,"89":5,"90":0,"91":0,"92":5,"93":5,"94":5,"95":35,"96":1,"97":35,"98":4,"99":1,"100":4,"101":0,"102":4,"103":1,"104":4,"105":1,"106":1,"107":1,"108":35,"109":0,"110":35,"111":2,"112":35,"113":35,"114":0,"115":35,"116":9,"117":7,"118":7,"119":9,"120":8,"121":8,"122":35,"123":35,"124":35,"125":35,"126":76,"127":76,"128":12,"129":64,"130":5,"131":59,"132":0,"133":59,"134":35,"135":9,"136":9,"137":4,"138":5,"139":5,"140":0,"141":9,"142":9,"143":9,"144":35,"145":18,"146":6,"147":35,"148":0,"149":35,"150":3,"151":35,"152":35,"153":35,"154":1,"155":35,"156":35},"f":{"0":5,"1":4,"2":35,"3":76,"4":18},"b":{"0":[1,34],"1":[34,0],"2":[5,30],"3":[4,31],"4":[12,23],"5":[5,7],"6":[1,6],"7":[6,29],"8":[35,32],"9":[1,34],"10":[5,30],"11":[35,32],"12":[1,34],"13":[2,33],"14":[1,34],"15":[1,34],"16":[1,34],"17":[1,34],"18":[1,34],"19":[2,33],"20":[1,32],"21":[5,30],"22":[0,5],"23":[2,28],"24":[7,21],"25":[2,33],"26":[35,5],"27":[2,33],"28":[35,4,4,3],"29":[6,29],"30":[0,35],"31":[35,29],"32":[12,23],"33":[4,19],"34":[5,30],"35":[1,34],"36":[1,34],"37":[26,5,4,0],"38":[5,30],"39":[2,3],"40":[0,5],"41":[5,2],"42":[0,5],"43":[5,0],"44":[5,5],"45":[1,34],"46":[35,5,1,0,0,0,0],"47":[4,31],"48":[1,3],"49":[0,4],"50":[1,3],"51":[4,1],"52":[1,3],"53":[4,3],"54":[0,35],"55":[35,4,4],"56":[2,33],"57":[35,9],"58":[2,33],"59":[7,26],"60":[0,35],"61":[9,26],"62":[7,2],"63":[9,9,7,7],"64":[8,1],"65":[35,0],"66":[12,64],"67":[76,12],"68":[5,59],"69":[64,5],"70":[0,59],"71":[59,59],"72":[9,26],"73":[4,5],"74":[5,0],"75":[6,29],"76":[35,35,6],"77":[0,35],"78":[35,4,0,0,0,0,0],"79":[3,32],"80":[35,4,4,1,1],"81":[34,1],"82":[34,32],"83":[35,31,29,27,27],"84":[1,34],"85":[35,1]},"meta":{"lastBranch":86,"lastFunction":5,"lastStatement":157,"seen":{"s:3:24:7:Infinity":0,"f:3:24:3:25":0,"s:4:2:4:Infinity":1,"s:5:2:5:Infinity":2,"s:6:2:6:Infinity":3,"s:9:25:15:Infinity":4,"f:9:25:9:26":1,"s:10:2:10:Infinity":5,"s:11:2:11:Infinity":6,"s:12:2:12:Infinity":7,"s:13:2:13:Infinity":8,"s:14:2:14:Infinity":9,"s:18:23:357:Infinity":10,"f:18:23:18:24":2,"s:19:16:30:Infinity":11,"s:32:2:32:Infinity":12,"s:35:17:42:Infinity":13,"s:44:15:46:27":14,"b:45:6:45:Infinity:46:6:46:27":0,"b:46:6:46:21:46:25:46:27":1,"b:48:2:50:Infinity:undefined:undefined:undefined:undefined":2,"s:48:2:50:Infinity":15,"s:49:4:49:Infinity":16,"b:52:2:54:Infinity:undefined:undefined:undefined:undefined":3,"s:52:2:54:Infinity":17,"s:53:4:53:Infinity":18,"b:58:2:68:Infinity:undefined:undefined:undefined:undefined":4,"s:58:2:68:Infinity":19,"s:59:4:59:Infinity":20,"s:60:4:60:Infinity":21,"s:61:4:61:Infinity":22,"s:62:4:62:Infinity":23,"b:63:4:67:Infinity:65:11:67:Infinity":5,"s:63:4:67:Infinity":24,"s:64:6:64:Infinity":25,"b:65:11:67:Infinity:undefined:undefined:undefined:undefined":6,"s:65:11:67:Infinity":26,"s:66:6:66:Infinity":27,"b:70:2:76:Infinity:undefined:undefined:undefined:undefined":7,"s:70:2:76:Infinity":28,"b:70:6:70:35:70:35:70:65":8,"s:71:4:71:Infinity":29,"s:72:4:72:Infinity":30,"s:73:4:73:Infinity":31,"s:74:4:74:Infinity":32,"s:75:4:75:Infinity":33,"b:78:2:81:Infinity:undefined:undefined:undefined:undefined":9,"s:78:2:81:Infinity":34,"s:79:4:79:Infinity":35,"s:80:4:80:Infinity":36,"b:83:2:87:Infinity:undefined:undefined:undefined:undefined":10,"s:83:2:87:Infinity":37,"b:83:6:83:38:83:38:83:68":11,"s:84:4:84:Infinity":38,"s:85:4:85:Infinity":39,"s:86:4:86:Infinity":40,"b:89:2:92:Infinity:undefined:undefined:undefined:undefined":12,"s:89:2:92:Infinity":41,"s:90:4:90:Infinity":42,"s:91:4:91:Infinity":43,"b:95:2:98:Infinity:undefined:undefined:undefined:undefined":13,"s:95:2:98:Infinity":44,"s:96:4:96:Infinity":45,"s:97:4:97:Infinity":46,"b:100:2:103:Infinity:undefined:undefined:undefined:undefined":14,"s:100:2:103:Infinity":47,"s:101:4:101:Infinity":48,"s:102:4:102:Infinity":49,"b:105:2:108:Infinity:undefined:undefined:undefined:undefined":15,"s:105:2:108:Infinity":50,"s:106:4:106:Infinity":51,"s:107:4:107:Infinity":52,"b:110:2:112:Infinity:undefined:undefined:undefined:undefined":16,"s:110:2:112:Infinity":53,"s:111:4:111:Infinity":54,"b:114:2:116:Infinity:undefined:undefined:undefined:undefined":17,"s:114:2:116:Infinity":55,"s:115:4:115:Infinity":56,"b:119:2:122:Infinity:undefined:undefined:undefined:undefined":18,"s:119:2:122:Infinity":57,"s:120:4:120:Infinity":58,"s:121:4:121:Infinity":59,"s:125:2:128:Infinity":60,"b:126:30:126:Infinity:127:6:128:Infinity":19,"b:127:32:127:Infinity:128:8:128:Infinity":20,"b:130:2:137:Infinity:132:9:137:Infinity":21,"s:130:2:137:Infinity":61,"s:131:4:131:Infinity":62,"b:131:43:131:53:131:53:131:Infinity":22,"s:133:4:136:Infinity":63,"b:134:33:134:Infinity:135:8:136:Infinity":23,"b:135:35:135:Infinity:136:10:136:Infinity":24,"b:139:2:141:Infinity:undefined:undefined:undefined:undefined":25,"s:139:2:141:Infinity":64,"b:139:6:139:34:139:34:139:59":26,"s:140:4:140:Infinity":65,"b:143:2:149:Infinity:undefined:undefined:undefined:undefined":27,"s:143:2:149:Infinity":66,"b:143:6:143:Infinity:144:4:144:Infinity:145:4:145:Infinity:146:4:146:26":28,"s:148:4:148:Infinity":67,"s:153:2:153:Infinity":68,"b:153:40:153:55:153:55:153:Infinity":29,"b:155:2:157:Infinity:undefined:undefined:undefined:undefined":30,"s:155:2:157:Infinity":69,"b:155:6:155:40:155:40:155:67":31,"s:156:4:156:Infinity":70,"s:160:2:163:Infinity":71,"b:161:27:161:Infinity:162:6:163:Infinity":32,"b:162:29:162:Infinity:163:8:163:Infinity":33,"b:165:2:167:Infinity:undefined:undefined:undefined:undefined":34,"s:165:2:167:Infinity":72,"s:166:4:166:Infinity":73,"s:170:2:170:Infinity":74,"b:170:48:170:79:170:79:170:Infinity":35,"s:171:2:171:Infinity":75,"b:171:40:171:60:171:60:171:Infinity":36,"b:175:4:177:Infinity:178:4:180:Infinity:181:4:183:Infinity:184:4:185:Infinity":37,"s:174:2:186:Infinity":76,"s:176:6:176:Infinity":77,"s:177:6:177:Infinity":78,"s:179:6:179:Infinity":79,"s:180:6:180:Infinity":80,"s:182:6:182:Infinity":81,"s:183:6:183:Infinity":82,"s:185:6:185:Infinity":83,"b:189:2:204:Infinity:undefined:undefined:undefined:undefined":38,"s:189:2:204:Infinity":84,"b:190:4:192:Infinity:undefined:undefined:undefined:undefined":39,"s:190:4:192:Infinity":85,"s:191:6:191:Infinity":86,"b:193:4:195:Infinity:undefined:undefined:undefined:undefined":40,"s:193:4:195:Infinity":87,"b:193:8:193:35:193:35:193:64":41,"s:194:6:194:Infinity":88,"b:196:4:199:Infinity:undefined:undefined:undefined:undefined":42,"s:196:4:199:Infinity":89,"s:197:6:197:Infinity":90,"s:198:6:198:Infinity":91,"b:200:4:203:Infinity:undefined:undefined:undefined:undefined":43,"s:200:4:203:Infinity":92,"b:200:8:200:42:200:42:200:77":44,"s:201:6:201:Infinity":93,"s:202:6:202:Infinity":94,"b:206:2:218:Infinity:undefined:undefined:undefined:undefined":45,"s:206:2:218:Infinity":95,"b:207:4:207:Infinity:208:4:208:Infinity:210:6:210:Infinity:211:6:211:Infinity:212:6:212:Infinity:213:6:213:Infinity:214:6:214:33":46,"s:217:4:217:Infinity":96,"b:221:2:236:Infinity:undefined:undefined:undefined:undefined":47,"s:221:2:236:Infinity":97,"b:222:4:224:Infinity:undefined:undefined:undefined:undefined":48,"s:222:4:224:Infinity":98,"s:223:6:223:Infinity":99,"b:225:4:227:Infinity:undefined:undefined:undefined:undefined":49,"s:225:4:227:Infinity":100,"s:226:6:226:Infinity":101,"b:228:4:230:Infinity:undefined:undefined:undefined:undefined":50,"s:228:4:230:Infinity":102,"b:228:8:228:35:228:35:228:68":51,"s:229:6:229:Infinity":103,"b:231:4:235:Infinity:undefined:undefined:undefined:undefined":52,"s:231:4:235:Infinity":104,"b:231:8:231:37:231:37:231:69":53,"s:232:6:232:Infinity":105,"s:233:6:233:Infinity":106,"s:234:6:234:Infinity":107,"b:238:2:240:Infinity:undefined:undefined:undefined:undefined":54,"s:238:2:240:Infinity":108,"b:238:6:238:35:238:35:238:69:238:69:238:89":55,"s:239:4:239:Infinity":109,"b:242:2:244:Infinity:undefined:undefined:undefined:undefined":56,"s:242:2:244:Infinity":110,"b:242:6:242:35:242:35:242:61":57,"s:243:4:243:Infinity":111,"s:247:19:247:Infinity":112,"b:247:46:247:56:247:56:247:Infinity":58,"b:247:83:247:93:247:93:247:Infinity":59,"b:248:2:250:Infinity:undefined:undefined:undefined:undefined":60,"s:248:2:250:Infinity":113,"s:249:4:249:Infinity":114,"b:253:2:268:Infinity:undefined:undefined:undefined:undefined":61,"s:253:2:268:Infinity":115,"b:254:4:262:Infinity:undefined:undefined:undefined:undefined":62,"s:254:4:262:Infinity":116,"b:255:6:255:Infinity:256:6:256:Infinity:257:6:257:Infinity:258:6:258:Infinity":63,"s:260:6:260:Infinity":117,"s:261:6:261:Infinity":118,"b:264:4:267:Infinity:undefined:undefined:undefined:undefined":64,"s:264:4:267:Infinity":119,"s:265:6:265:Infinity":120,"s:266:6:266:Infinity":121,"s:270:8:270:Infinity":122,"s:271:2:271:Infinity":123,"s:274:19:274:Infinity":124,"s:275:2:281:Infinity":125,"b:275:22:281:10:281:10:281:12":65,"f:275:40:275:Infinity":3,"s:276:19:276:Infinity":126,"b:277:6:277:Infinity:undefined:undefined:undefined:undefined":66,"s:277:6:277:Infinity":127,"b:277:10:277:41:277:41:277:71":67,"s:277:71:277:Infinity":128,"b:278:6:278:Infinity:undefined:undefined:undefined:undefined":68,"s:278:6:278:Infinity":129,"b:278:10:278:40:278:40:278:67":69,"s:278:67:278:Infinity":130,"b:279:6:279:Infinity:undefined:undefined:undefined:undefined":70,"s:279:6:279:Infinity":131,"b:279:10:279:42:279:42:279:68":71,"s:279:68:279:Infinity":132,"s:280:6:280:Infinity":133,"b:283:0:294:Infinity:undefined:undefined:undefined:undefined":72,"s:283:0:294:Infinity":134,"s:284:20:284:Infinity":135,"b:286:2:288:Infinity:287:7:288:Infinity":73,"s:286:2:288:Infinity":136,"s:286:31:286:Infinity":137,"b:287:7:288:Infinity:288:7:288:Infinity":74,"s:287:7:288:Infinity":138,"s:287:33:287:Infinity":139,"s:288:7:288:Infinity":140,"s:290:2:290:Infinity":141,"s:292:2:292:Infinity":142,"s:293:2:293:Infinity":143,"b:298:2:304:Infinity:undefined:undefined:undefined:undefined":75,"s:298:2:304:Infinity":144,"b:299:4:299:Infinity:300:4:300:Infinity:301:4:301:Infinity":76,"f:301:20:301:25":4,"s:301:25:301:Infinity":145,"s:303:4:303:Infinity":146,"b:307:2:319:Infinity:undefined:undefined:undefined:undefined":77,"s:307:2:319:Infinity":147,"b:308:4:308:Infinity:309:4:309:Infinity:311:6:311:Infinity:312:6:312:Infinity:313:6:313:Infinity:314:6:314:Infinity:315:6:315:33":78,"s:318:4:318:Infinity":148,"b:322:2:329:Infinity:undefined:undefined:undefined:undefined":79,"s:322:2:329:Infinity":149,"b:322:6:322:Infinity:323:4:323:Infinity:324:4:324:Infinity:325:4:325:Infinity:326:4:326:28":80,"s:328:4:328:Infinity":150,"s:332:25:334:Infinity":151,"b:333:6:333:Infinity:334:6:334:Infinity":81,"b:333:6:333:38:333:38:333:Infinity":82,"s:337:4:341:Infinity":152,"b:337:4:337:Infinity:338:4:338:Infinity:339:4:339:Infinity:340:4:340:Infinity:341:4:341:Infinity":83,"b:343:2:345:Infinity:undefined:undefined:undefined:undefined":84,"s:343:2:345:Infinity":153,"b:343:6:343:24:343:24:343:41":85,"s:344:4:344:Infinity":154,"s:347:2:352:Infinity":155,"s:356:2:356:Infinity":156}}} +,"E:\\Job\\ComputerSelectorHelper\\src\\utils\\laptopModels.js": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\laptopModels.js","statementMap":{"0":{"start":{"line":1,"column":24},"end":{"line":2,"column":75}},"1":{"start":{"line":2,"column":2},"end":{"line":2,"column":75}},"2":{"start":{"line":4,"column":21},"end":{"line":269,"column":1}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":1,"column":24},"end":{"line":1,"column":25}},"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":75}},"line":2}},"branchMap":{},"s":{"0":2,"1":46,"2":2},"f":{"0":46},"b":{},"meta":{"lastBranch":0,"lastFunction":1,"lastStatement":3,"seen":{"s:1:24:2:75":0,"f:1:24:1:25":0,"s:2:2:2:75":1,"s:4:21:269:1":2}}} +,"E:\\Job\\ComputerSelectorHelper\\src\\utils\\matchLaptopClass.jsx": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\matchLaptopClass.jsx","statementMap":{"0":{"start":{"line":3,"column":18},"end":{"line":5,"column":null}},"1":{"start":{"line":4,"column":2},"end":{"line":5,"column":null}},"2":{"start":{"line":7,"column":19},"end":{"line":8,"column":null}},"3":{"start":{"line":8,"column":2},"end":{"line":8,"column":null}},"4":{"start":{"line":11,"column":25},"end":{"line":92,"column":null}},"5":{"start":{"line":12,"column":27},"end":{"line":41,"column":null}},"6":{"start":{"line":13,"column":21},"end":{"line":13,"column":null}},"7":{"start":{"line":14,"column":20},"end":{"line":14,"column":null}},"8":{"start":{"line":15,"column":23},"end":{"line":15,"column":null}},"9":{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},"10":{"start":{"line":17,"column":61},"end":{"line":17,"column":null}},"11":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"12":{"start":{"line":18,"column":81},"end":{"line":18,"column":null}},"13":{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},"14":{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},"15":{"start":{"line":20,"column":57},"end":{"line":20,"column":null}},"16":{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},"17":{"start":{"line":21,"column":137},"end":{"line":21,"column":null}},"18":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"19":{"start":{"line":24,"column":58},"end":{"line":24,"column":null}},"20":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"21":{"start":{"line":25,"column":79},"end":{"line":25,"column":null}},"22":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"23":{"start":{"line":26,"column":78},"end":{"line":26,"column":null}},"24":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"25":{"start":{"line":27,"column":64},"end":{"line":27,"column":null}},"26":{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},"27":{"start":{"line":31,"column":6},"end":{"line":31,"column":null}},"28":{"start":{"line":34,"column":18},"end":{"line":34,"column":null}},"29":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"30":{"start":{"line":36,"column":48},"end":{"line":36,"column":null}},"31":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"32":{"start":{"line":37,"column":52},"end":{"line":37,"column":null}},"33":{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},"34":{"start":{"line":38,"column":50},"end":{"line":38,"column":null}},"35":{"start":{"line":40,"column":4},"end":{"line":40,"column":null}},"36":{"start":{"line":43,"column":23},"end":{"line":84,"column":null}},"37":{"start":{"line":44,"column":16},"end":{"line":44,"column":null}},"38":{"start":{"line":46,"column":23},"end":{"line":46,"column":null}},"39":{"start":{"line":47,"column":21},"end":{"line":47,"column":null}},"40":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"41":{"start":{"line":49,"column":68},"end":{"line":49,"column":null}},"42":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"43":{"start":{"line":50,"column":54},"end":{"line":50,"column":null}},"44":{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},"45":{"start":{"line":51,"column":60},"end":{"line":51,"column":null}},"46":{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},"47":{"start":{"line":52,"column":58},"end":{"line":52,"column":null}},"48":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"49":{"start":{"line":53,"column":49},"end":{"line":53,"column":null}},"50":{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},"51":{"start":{"line":54,"column":66},"end":{"line":54,"column":null}},"52":{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},"53":{"start":{"line":57,"column":6},"end":{"line":57,"column":null}},"54":{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},"55":{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},"56":{"start":{"line":61,"column":8},"end":{"line":61,"column":null}},"57":{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},"58":{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},"59":{"start":{"line":73,"column":8},"end":{"line":73,"column":null}},"60":{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},"61":{"start":{"line":80,"column":6},"end":{"line":80,"column":null}},"62":{"start":{"line":83,"column":4},"end":{"line":83,"column":null}},"63":{"start":{"line":86,"column":22},"end":{"line":88,"column":17}},"64":{"start":{"line":87,"column":20},"end":{"line":87,"column":47}},"65":{"start":{"line":90,"column":2},"end":{"line":90,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":18},"end":{"line":3,"column":19}},"loc":{"start":{"line":4,"column":2},"end":{"line":5,"column":null}},"line":4},"1":{"name":"(anonymous_1)","decl":{"start":{"line":7,"column":19},"end":{"line":7,"column":20}},"loc":{"start":{"line":8,"column":2},"end":{"line":8,"column":null}},"line":8},"2":{"name":"(anonymous_2)","decl":{"start":{"line":11,"column":25},"end":{"line":11,"column":26}},"loc":{"start":{"line":11,"column":26},"end":{"line":92,"column":null}},"line":11},"3":{"name":"(anonymous_3)","decl":{"start":{"line":12,"column":47},"end":{"line":12,"column":null}},"loc":{"start":{"line":12,"column":47},"end":{"line":41,"column":null}},"line":12},"4":{"name":"(anonymous_4)","decl":{"start":{"line":43,"column":44},"end":{"line":43,"column":null}},"loc":{"start":{"line":43,"column":44},"end":{"line":84,"column":null}},"line":43},"5":{"name":"(anonymous_5)","decl":{"start":{"line":87,"column":10},"end":{"line":87,"column":11}},"loc":{"start":{"line":87,"column":20},"end":{"line":87,"column":47}},"line":87}},"branchMap":{"0":{"loc":{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},"type":"if","locations":[{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},{"start":{},"end":{}}],"line":17},"1":{"loc":{"start":{"line":17,"column":8},"end":{"line":17,"column":61}},"type":"binary-expr","locations":[{"start":{"line":17,"column":8},"end":{"line":17,"column":35}},{"start":{"line":17,"column":35},"end":{"line":17,"column":61}}],"line":17},"2":{"loc":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"type":"if","locations":[{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},{"start":{},"end":{}}],"line":18},"3":{"loc":{"start":{"line":18,"column":8},"end":{"line":18,"column":81}},"type":"binary-expr","locations":[{"start":{"line":18,"column":8},"end":{"line":18,"column":35}},{"start":{"line":18,"column":35},"end":{"line":18,"column":81}}],"line":18},"4":{"loc":{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},"type":"if","locations":[{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},{"start":{},"end":{}}],"line":19},"5":{"loc":{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},"type":"if","locations":[{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},{"start":{},"end":{}}],"line":20},"6":{"loc":{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},"type":"if","locations":[{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},{"start":{},"end":{}}],"line":21},"7":{"loc":{"start":{"line":21,"column":10},"end":{"line":21,"column":137}},"type":"binary-expr","locations":[{"start":{"line":21,"column":10},"end":{"line":21,"column":53}},{"start":{"line":21,"column":53},"end":{"line":21,"column":96}},{"start":{"line":21,"column":96},"end":{"line":21,"column":137}}],"line":21},"8":{"loc":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"type":"if","locations":[{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},{"start":{},"end":{}}],"line":24},"9":{"loc":{"start":{"line":24,"column":8},"end":{"line":24,"column":58}},"type":"binary-expr","locations":[{"start":{"line":24,"column":8},"end":{"line":24,"column":32}},{"start":{"line":24,"column":32},"end":{"line":24,"column":58}}],"line":24},"10":{"loc":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"type":"if","locations":[{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},{"start":{},"end":{}}],"line":25},"11":{"loc":{"start":{"line":25,"column":8},"end":{"line":25,"column":79}},"type":"binary-expr","locations":[{"start":{"line":25,"column":8},"end":{"line":25,"column":38}},{"start":{"line":25,"column":38},"end":{"line":25,"column":79}}],"line":25},"12":{"loc":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},{"start":{},"end":{}}],"line":26},"13":{"loc":{"start":{"line":26,"column":8},"end":{"line":26,"column":78}},"type":"binary-expr","locations":[{"start":{"line":26,"column":8},"end":{"line":26,"column":51}},{"start":{"line":26,"column":51},"end":{"line":26,"column":78}}],"line":26},"14":{"loc":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"type":"if","locations":[{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},{"start":{},"end":{}}],"line":27},"15":{"loc":{"start":{"line":27,"column":8},"end":{"line":27,"column":64}},"type":"binary-expr","locations":[{"start":{"line":27,"column":8},"end":{"line":27,"column":33}},{"start":{"line":27,"column":33},"end":{"line":27,"column":64}}],"line":27},"16":{"loc":{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},"type":"if","locations":[{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},{"start":{},"end":{}}],"line":28},"17":{"loc":{"start":{"line":29,"column":6},"end":{"line":30,"column":null}},"type":"binary-expr","locations":[{"start":{"line":29,"column":7},"end":{"line":29,"column":41}},{"start":{"line":29,"column":41},"end":{"line":29,"column":75}},{"start":{"line":30,"column":6},"end":{"line":30,"column":36}},{"start":{"line":30,"column":36},"end":{"line":30,"column":null}}],"line":29},"18":{"loc":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":36},"19":{"loc":{"start":{"line":36,"column":8},"end":{"line":36,"column":48}},"type":"binary-expr","locations":[{"start":{"line":36,"column":8},"end":{"line":36,"column":34}},{"start":{"line":36,"column":34},"end":{"line":36,"column":48}}],"line":36},"20":{"loc":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},{"start":{},"end":{}}],"line":37},"21":{"loc":{"start":{"line":37,"column":8},"end":{"line":37,"column":52}},"type":"binary-expr","locations":[{"start":{"line":37,"column":8},"end":{"line":37,"column":37}},{"start":{"line":37,"column":37},"end":{"line":37,"column":52}}],"line":37},"22":{"loc":{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},"type":"if","locations":[{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},{"start":{},"end":{}}],"line":38},"23":{"loc":{"start":{"line":38,"column":8},"end":{"line":38,"column":50}},"type":"binary-expr","locations":[{"start":{"line":38,"column":8},"end":{"line":38,"column":35}},{"start":{"line":38,"column":35},"end":{"line":38,"column":50}}],"line":38},"24":{"loc":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"type":"if","locations":[{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},{"start":{},"end":{}}],"line":49},"25":{"loc":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"type":"if","locations":[{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},{"start":{},"end":{}}],"line":50},"26":{"loc":{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},"type":"if","locations":[{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},{"start":{},"end":{}}],"line":51},"27":{"loc":{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},"type":"if","locations":[{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},{"start":{},"end":{}}],"line":52},"28":{"loc":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"type":"if","locations":[{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},{"start":{},"end":{}}],"line":53},"29":{"loc":{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},"type":"if","locations":[{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},{"start":{},"end":{}}],"line":54},"30":{"loc":{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},"type":"if","locations":[{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},{"start":{},"end":{}}],"line":56},"31":{"loc":{"start":{"line":56,"column":8},"end":{"line":56,"column":76}},"type":"binary-expr","locations":[{"start":{"line":56,"column":8},"end":{"line":56,"column":46}},{"start":{"line":56,"column":46},"end":{"line":56,"column":76}}],"line":56},"32":{"loc":{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},"type":"if","locations":[{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},{"start":{},"end":{}}],"line":59},"33":{"loc":{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},"type":"if","locations":[{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},{"start":{},"end":{}}],"line":60},"34":{"loc":{"start":{"line":60,"column":10},"end":{"line":60,"column":91}},"type":"binary-expr","locations":[{"start":{"line":60,"column":10},"end":{"line":60,"column":38}},{"start":{"line":60,"column":38},"end":{"line":60,"column":66}},{"start":{"line":60,"column":66},"end":{"line":60,"column":91}}],"line":60},"35":{"loc":{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},"type":"if","locations":[{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},{"start":{},"end":{}}],"line":65},"36":{"loc":{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},"type":"if","locations":[{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},{"start":{},"end":{}}],"line":66},"37":{"loc":{"start":{"line":67,"column":8},"end":{"line":71,"column":null}},"type":"binary-expr","locations":[{"start":{"line":67,"column":8},"end":{"line":67,"column":null}},{"start":{"line":68,"column":8},"end":{"line":68,"column":null}},{"start":{"line":69,"column":8},"end":{"line":69,"column":null}},{"start":{"line":70,"column":8},"end":{"line":70,"column":null}},{"start":{"line":71,"column":8},"end":{"line":71,"column":null}}],"line":67},"38":{"loc":{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},{"start":{},"end":{}}],"line":79},"39":{"loc":{"start":{"line":79,"column":8},"end":{"line":79,"column":89}},"type":"binary-expr","locations":[{"start":{"line":79,"column":8},"end":{"line":79,"column":43}},{"start":{"line":79,"column":43},"end":{"line":79,"column":89}}],"line":79},"40":{"loc":{"start":{"line":90,"column":9},"end":{"line":90,"column":null}},"type":"cond-expr","locations":[{"start":{"line":90,"column":30},"end":{"line":90,"column":44}},{"start":{"line":90,"column":44},"end":{"line":90,"column":null}}],"line":90}},"s":{"0":2,"1":1200,"2":2,"3":794,"4":2,"5":57,"6":1311,"7":1311,"8":1311,"9":1311,"10":49,"11":1262,"12":16,"13":1246,"14":184,"15":160,"16":24,"17":8,"18":1078,"19":0,"20":1078,"21":0,"22":1078,"23":208,"24":870,"25":56,"26":814,"27":20,"28":794,"29":794,"30":49,"31":745,"32":427,"33":318,"34":18,"35":300,"36":57,"37":300,"38":300,"39":300,"40":300,"41":0,"42":300,"43":139,"44":300,"45":0,"46":300,"47":100,"48":300,"49":0,"50":300,"51":0,"52":300,"53":4,"54":300,"55":7,"56":7,"57":300,"58":15,"59":4,"60":300,"61":0,"62":300,"63":57,"64":529,"65":57},"f":{"0":1200,"1":794,"2":57,"3":1311,"4":300,"5":529},"b":{"0":[49,1262],"1":[1311,161],"2":[16,1246],"3":[1262,112],"4":[184,1062],"5":[160,24],"6":[8,16],"7":[24,16,8],"8":[0,1078],"9":[1078,16],"10":[0,1078],"11":[1078,0],"12":[208,870],"13":[1078,292],"14":[56,814],"15":[870,116],"16":[20,794],"17":[814,779,35,25],"18":[49,745],"19":[794,59],"20":[427,318],"21":[745,653],"22":[18,300],"23":[318,82],"24":[0,300],"25":[139,161],"26":[0,300],"27":[100,200],"28":[0,300],"29":[0,300],"30":[4,296],"31":[300,8],"32":[7,293],"33":[7,0],"34":[7,0,0],"35":[15,285],"36":[4,11],"37":[15,14,11,11,11],"38":[0,300],"39":[300,8],"40":[40,17]},"meta":{"lastBranch":41,"lastFunction":6,"lastStatement":66,"seen":{"s:3:18:5:Infinity":0,"f:3:18:3:19":0,"s:4:2:5:Infinity":1,"s:7:19:8:Infinity":2,"f:7:19:7:20":1,"s:8:2:8:Infinity":3,"s:11:25:92:Infinity":4,"f:11:25:11:26":2,"s:12:27:41:Infinity":5,"f:12:47:12:Infinity":3,"s:13:21:13:Infinity":6,"s:14:20:14:Infinity":7,"s:15:23:15:Infinity":8,"b:17:4:17:Infinity:undefined:undefined:undefined:undefined":0,"s:17:4:17:Infinity":9,"b:17:8:17:35:17:35:17:61":1,"s:17:61:17:Infinity":10,"b:18:4:18:Infinity:undefined:undefined:undefined:undefined":2,"s:18:4:18:Infinity":11,"b:18:8:18:35:18:35:18:81":3,"s:18:81:18:Infinity":12,"b:19:4:22:Infinity:undefined:undefined:undefined:undefined":4,"s:19:4:22:Infinity":13,"b:20:6:20:Infinity:undefined:undefined:undefined:undefined":5,"s:20:6:20:Infinity":14,"s:20:57:20:Infinity":15,"b:21:6:21:Infinity:undefined:undefined:undefined:undefined":6,"s:21:6:21:Infinity":16,"b:21:10:21:53:21:53:21:96:21:96:21:137":7,"s:21:137:21:Infinity":17,"b:24:4:24:Infinity:undefined:undefined:undefined:undefined":8,"s:24:4:24:Infinity":18,"b:24:8:24:32:24:32:24:58":9,"s:24:58:24:Infinity":19,"b:25:4:25:Infinity:undefined:undefined:undefined:undefined":10,"s:25:4:25:Infinity":20,"b:25:8:25:38:25:38:25:79":11,"s:25:79:25:Infinity":21,"b:26:4:26:Infinity:undefined:undefined:undefined:undefined":12,"s:26:4:26:Infinity":22,"b:26:8:26:51:26:51:26:78":13,"s:26:78:26:Infinity":23,"b:27:4:27:Infinity:undefined:undefined:undefined:undefined":14,"s:27:4:27:Infinity":24,"b:27:8:27:33:27:33:27:64":15,"s:27:64:27:Infinity":25,"b:28:4:31:Infinity:undefined:undefined:undefined:undefined":16,"s:28:4:31:Infinity":26,"b:29:7:29:41:29:41:29:75:30:6:30:36:30:36:30:Infinity":17,"s:31:6:31:Infinity":27,"s:34:18:34:Infinity":28,"b:36:4:36:Infinity:undefined:undefined:undefined:undefined":18,"s:36:4:36:Infinity":29,"b:36:8:36:34:36:34:36:48":19,"s:36:48:36:Infinity":30,"b:37:4:37:Infinity:undefined:undefined:undefined:undefined":20,"s:37:4:37:Infinity":31,"b:37:8:37:37:37:37:37:52":21,"s:37:52:37:Infinity":32,"b:38:4:38:Infinity:undefined:undefined:undefined:undefined":22,"s:38:4:38:Infinity":33,"b:38:8:38:35:38:35:38:50":23,"s:38:50:38:Infinity":34,"s:40:4:40:Infinity":35,"s:43:23:84:Infinity":36,"f:43:44:43:Infinity":4,"s:44:16:44:Infinity":37,"s:46:23:46:Infinity":38,"s:47:21:47:Infinity":39,"b:49:4:49:Infinity:undefined:undefined:undefined:undefined":24,"s:49:4:49:Infinity":40,"s:49:68:49:Infinity":41,"b:50:4:50:Infinity:undefined:undefined:undefined:undefined":25,"s:50:4:50:Infinity":42,"s:50:54:50:Infinity":43,"b:51:4:51:Infinity:undefined:undefined:undefined:undefined":26,"s:51:4:51:Infinity":44,"s:51:60:51:Infinity":45,"b:52:4:52:Infinity:undefined:undefined:undefined:undefined":27,"s:52:4:52:Infinity":46,"s:52:58:52:Infinity":47,"b:53:4:53:Infinity:undefined:undefined:undefined:undefined":28,"s:53:4:53:Infinity":48,"s:53:49:53:Infinity":49,"b:54:4:54:Infinity:undefined:undefined:undefined:undefined":29,"s:54:4:54:Infinity":50,"s:54:66:54:Infinity":51,"b:56:4:58:Infinity:undefined:undefined:undefined:undefined":30,"s:56:4:58:Infinity":52,"b:56:8:56:46:56:46:56:76":31,"s:57:6:57:Infinity":53,"b:59:4:63:Infinity:undefined:undefined:undefined:undefined":32,"s:59:4:63:Infinity":54,"b:60:6:62:Infinity:undefined:undefined:undefined:undefined":33,"s:60:6:62:Infinity":55,"b:60:10:60:38:60:38:60:66:60:66:60:91":34,"s:61:8:61:Infinity":56,"b:65:4:75:Infinity:undefined:undefined:undefined:undefined":35,"s:65:4:75:Infinity":57,"b:66:6:74:Infinity:undefined:undefined:undefined:undefined":36,"s:66:6:74:Infinity":58,"b:67:8:67:Infinity:68:8:68:Infinity:69:8:69:Infinity:70:8:70:Infinity:71:8:71:Infinity":37,"s:73:8:73:Infinity":59,"b:79:4:81:Infinity:undefined:undefined:undefined:undefined":38,"s:79:4:81:Infinity":60,"b:79:8:79:43:79:43:79:89":39,"s:80:6:80:Infinity":61,"s:83:4:83:Infinity":62,"s:86:22:88:17":63,"f:87:10:87:11":5,"s:87:20:87:47":64,"s:90:2:90:Infinity":65,"b:90:30:90:44:90:44:90:Infinity":40}}} +} diff --git a/coverage/favicon.png b/coverage/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 92.03% + Statements + 208/226 +
+ + +
+ 88.07% + Branches + 251/285 +
+ + +
+ 100% + Functions + 12/12 +
+ + +
+ 94.6% + Lines + 193/204 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
convertToSpecs.jsx +
+
92.99%146/15788.26%173/196100%5/593.42%142/152
laptopModels.js +
+
100%3/3100%0/0100%1/1100%3/3
matchLaptopClass.jsx +
+
89.39%59/6687.64%78/89100%6/697.95%48/49
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/laptopModels.js.html b/coverage/laptopModels.js.html new file mode 100644 index 0000000..5d22185 --- /dev/null +++ b/coverage/laptopModels.js.html @@ -0,0 +1,898 @@ + + + + + + Code coverage report for laptopModels.js + + + + + + + + + +
+
+

All files laptopModels.js

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +2722x +46x +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
const buildGoogleLink = (name) =>
+  `https://www.google.com/search?q=${encodeURIComponent(name + ' laptop')}`
+ 
+const laptopModels = [
+  {
+    id: "hp-stream-14",
+    name: "HP Stream 14",
+    use: "Estudiantes básicos, movilidad",
+    specs: "Intel Celeron N4120, 4 GB RAM, 64 GB SSD, 14” HD",
+    gpu: "Intel integrada",
+    price: "$4,199 MXN",
+    portability: "Alta",
+    image:
+      "https://www.notebookcheck.net/fileadmin/Notebooks/HP/Stream_14-ax002ng/2.jpg",
+    link: buildGoogleLink("HP Stream 14"),
+  },
+  {
+    id: "acer-aspire-go",
+    name: "Acer Aspire Go AG15",
+    use: "Uso general, estudiantes",
+    specs: "Intel Core i3, 8 GB RAM, 256 GB SSD, 15.6” FHD",
+    gpu: "Intel integrada",
+    price: "$5,999 MXN",
+    portability: "Media",
+    image:
+      "https://laptoping.com/specs/wp-content/uploads/2024/06/Acer-Aspire-Go-15-Slim-AG15-31P-3947.jpg",
+    link: buildGoogleLink("acer-aspire-go"),
+  },
+  {
+    id: "hp-victus-rtx3050",
+    name: "HP Victus RTX 3050",
+    use: "Gaming medio, tareas pesadas",
+    specs: "Intel i5 12450H, 32 GB RAM, 512 GB SSD, RTX 3050",
+    gpu: "RTX 3050",
+    price: "$14,999 MXN",
+    portability: "Media",
+    image:
+      "https://m.media-amazon.com/images/I/71XwPLUaKsL._AC_SL1500_.jpg",
+    link: buildGoogleLink("hp-victus-rtx3050"),
+  },
+  {
+    id: "macbook-air-m1",
+    name: "Apple MacBook Air M1",
+    use: "Creativos, portabilidad, estudiantes",
+    specs: "Apple M1, 8 GB RAM, 256 GB SSD, 13.3” Retina",
+    gpu: "M1",
+    price: "$13,999 MXN",
+    portability: "Alta",
+    image:
+      "https://th.bing.com/th/id/OIP.Q4muQjrfyxVhOB5PMwAF6gHaE8?cb=iwp2&rs=1&pid=ImgDetMainhttps://thangpro.vn/uploads/source/laptop/macbook/mac-air-m1/img-7707.jpg",
+    link: buildGoogleLink("macbook-air-m1"),
+  },
+  {
+    id: "asus-tuf-a15",
+    name: "ASUS TUF Gaming A15",
+    use: "Gaming alto, edición multimedia",
+    specs: "Ryzen 7, 8 GB RAM, 512 GB SSD, RTX 3050",
+    gpu: "RTX 3050",
+    price: "$16,339 MXN",
+    portability: "Media",
+    image:
+      "https://laptopmedia.com/wp-content/uploads/2021/01/2-40.jpg",
+    link: buildGoogleLink("asus-tuf-a15"),
+  },
+  {
+    id: "lenovo-chromebook-3",
+    name: "Lenovo Chromebook 3",
+    use: "Tareas básicas, navegación, Google Docs",
+    specs: "Intel Celeron N4020, 4 GB RAM, 64 GB eMMC, 14” HD",
+    gpu: "Intel integrada",
+    price: "$3,499 MXN",
+    portability: "Alta",
+    image:
+      "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6447/6447138_sd.jpg",
+    link: buildGoogleLink("lenovo-chromebook-3"),
+  },
+  {
+    id: "macbook-air-m2",
+    name: "Apple MacBook Air M2",
+    use: "Profesionales móviles, diseño, universidad",
+    specs: "Apple M2, 8 GB RAM, 256 GB SSD, 13.6” Retina",
+    gpu: "M2",
+    price: "$23,999 MXN",
+    portability: "Alta",
+    image:
+      "https://cdn.shopify.com/s/files/1/1706/9177/products/macbook-air-m2-midnight-custom-mac-bd_b82010cb-9405-4654-a165-9576a249e4e0.jpg",
+    link: buildGoogleLink("macbook-air-m2"),
+  },
+ 
+  {
+    id: "lenovo-gamer-rtx4070",
+    name: "Lenovo Legion RTX 4070",
+    use: "Gaming extremo, arquitectura, animación",
+    specs: "Intel i9, 32 GB RAM, 1TB SSD, RTX 4070",
+    gpu: "RTX 4070",
+    price: "$38,999 MXN",
+    portability: "Baja",
+    image:
+      "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6534/6534466ld.jpg",
+    link: buildGoogleLink("lenovo-gamer-rtx4070"),
+  },
+  {
+    id: "msi-gamer-rtx2060",
+    name: "MSI GF63 RTX 2060",
+    use: "Gaming clásico, edición semiprofesional",
+    specs: "Intel i5 10th Gen, 16 GB RAM, 512 GB SSD, RTX 2060",
+    gpu: "RTX 2060",
+    price: "$17,499 MXN",
+    portability: "Media",
+    image:
+      "https://i0.wp.com/phukienmaytinh.com.vn/wp-content/uploads/2020/02/MSI-GF63-3.jpg",
+    link: buildGoogleLink("msi-gamer-rtx2060"),
+  },
+  {
+    id: "huawei-matebook-d16-i5",
+    name: "Huawei MateBook D16",
+    use: "Trabajo exigente, multimedia, estudiantes",
+    specs: "Intel Core i5 13420H, 16 GB RAM, 1 TB SSD, 16\" FHD",
+    gpu: "Intel Iris Xe",
+    price: "$14,399 MXN",
+    portability: "Media",
+    image: "https://www.presse-citron.net/app/uploads/2021/05/DSC01401_DxO_PC.jpg",
+    link: buildGoogleLink("Huawei MateBook D16"),
+  },
+  {
+    id: "hp-pavilion-i7-1355u",
+    name: "HP Pavilion i7-1355U",
+    use: "Profesionales, multitarea avanzada, productividad",
+    specs: "Intel Core i7-1355U, 32 GB RAM, 1 TB SSD, 15.6\" FHD",
+    gpu: "Intel Iris Xe",
+    price: "$15,722 MXN",
+    portability: "Media",
+    image: "https://m.media-amazon.com/images/I/613hm2YD6mL._AC_.jpg",
+    link: buildGoogleLink("HP Pavilion i7-1355U"),
+  },
+  {
+    id: "msi-sword-16-i7",
+    name: "MSI Sword 16 HX",
+    use: "Gaming, edición profesional, exigencia gráfica",
+    specs: "Intel Core i7, 16 GB RAM, 1 TB SSD, 16\" FHD",
+    gpu: "RTX 4060",
+    price: "$33,999 MXN",
+    portability: "Media",
+    image: "https://storage-asset.msi.com/global/picture/image/feature/nb/2023_RPLS/sword16-hx-b14v/msi-sword16-display-new.png",
+    link: buildGoogleLink("MSI Sword 16 HX"),
+  },
+  {
+    id: "generic-intel-14",
+    name: "Laptop 14 Intel",
+    use: "Tareas básicas, ofimática, estudiantes",
+    specs: "Intel genérica, 8 GB RAM, 256 GB SSD, 14\" FHD",
+    gpu: "Intel integrada",
+    price: "$4,800 MXN",
+    portability: "Alta",
+    image: "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6425/6425056_rd.jpg",
+    link: buildGoogleLink("Laptop 14 Intel 256gb"),
+  },
+    {
+    id: "macbook-air-m4-512",
+    name: "MacBook Air M4 (512 GB)",
+    use: "Profesionales móviles, diseño, rendimiento alto",
+    specs: "Apple M4, 8 GB RAM, 512 GB SSD, 13” Retina",
+    gpu: "Apple Silicon",
+    price: "$30,950 MXN",
+    portability: "Alta",
+    image: "https://ipadizate.com/hero/2023/01/MacBook-Air-M2-Chip-Purple-Feature.jpg", 
+    link: buildGoogleLink("MacBook Air M4 512 GB")
+  },
+  {
+    id: "acer-nitro-5-rtx4060",
+    name: "Acer Nitro 5 RTX 4060",
+    use: "Gaming exigente, edición profesional",
+    specs: "Intel Core i7-13620H, 16 GB RAM, 512 GB SSD, RTX 4060",
+    gpu: "RTX 4060",
+    price: "$20,642 MXN",
+    portability: "Media",
+    image: "https://www.notebookcheck.com/fileadmin/_processed_/9/2/csm_Acer_Nitro_5_RTX_4060_guenstigstes_gaming_notebook_deal_db9eae21d8.jpg",
+    link: buildGoogleLink("Acer Nitro 5 RTX 4060")
+  },
+  {
+    id: "hp-15-fd0999nr",
+    name: "HP 15-fd0999nr",
+    use: "Ofimática avanzada, estudiantes, productividad",
+    specs: "Intel Core i5-1235U, 16 GB RAM, 512 GB SSD, 15.6” FHD",
+    gpu: "Intel Iris Xe",
+    price: "$9,797 MXN",
+    portability: "Media",
+    image: "https://lidernotebooks.com.ar/wp-content/uploads/2024/10/HP-15-fd0999NR-Core-i5_0004_Capa-7.jpg",
+    link: buildGoogleLink("HP 15-fd0999nr")
+  },
+  {
+    id: "msi-gf63-gtx1650",
+    name: "MSI GF63 GTX 1650",
+    use: "Gaming básico, diseño ligero",
+    specs: "Intel Core i5-11400H, 8 GB RAM, 256 GB SSD, GTX 1650",
+    gpu: "GTX 1650",
+    price: "$11,999 MXN",
+    portability: "Media",
+    image: "https://laptop360.net/wp-content/uploads/2022/09/MSI-Gaming-GF63-Thin-11UC-3-1536x1152.jpg",
+    link: buildGoogleLink("MSI GF63 GTX 1650")
+  },
+  {
+    id: "lenovo-yoga-slim-7-ultra5",
+    name: "Lenovo Yoga Slim 7",
+    use: "Profesionales, estudiantes de ingeniería",
+    specs: "Intel Ultra 5 125H, 16 GB RAM, 1 TB SSD, 14” FHD",
+    gpu: "Intel integrada",
+    price: "$18,999 MXN",
+    portability: "Alta",
+    image: "https://cdn.mos.cms.futurecdn.net/4C7JXvghGrpY52MtBiN2cB.jpg",
+    link: buildGoogleLink("Lenovo Yoga Slim 7")
+  },
+  {
+  id: "machcreator-15n-i7",
+  name: "Laptop Machcreator 15N",
+  use: "Multitarea, trabajo profesional, estudiantes avanzados",
+  specs: "Intel Core i7 13620H, 16 GB RAM, 512 GB SSD, 15.6” FHD",
+  gpu: "Intel UHD Graphics",
+  price: "$14,553 MXN",
+  portability: "Media",
+  image: "https://global.machenike.com/cdn/shop/files/11_818b6222-59ce-4193-a68b-bbc8e043b8ef.jpg",
+  link: buildGoogleLink("Laptop Machcreator 15N i7"),
+},
+{
+  id: "machenike-l16pro-rtx4060",
+  name: "Machenike L16 Pro",
+  use: "Gaming exigente, edición profesional, multitarea avanzada",
+  specs: "Ryzen 7 7735H, 16 GB RAM DDR5, 1 TB SSD, 16” 2.5K 165Hz",
+  gpu: "Nvidia GeForce RTX 4060",
+  price: "$34,954 MXN",
+  portability: "Baja",
+  image: "https://http2.mlstatic.com/D_NQ_NP_875287-MLU73599603547_122023-O.webp", 
+  link: buildGoogleLink("Machenike L16 Pro RTX 4060"),
+},
+{
+  id: "samsung-galaxy-book4-360",
+  name: "Samsung Galaxy Book4 360",
+  use: "Multimedia, productividad, uso general, estudiantes",
+  specs: "Intel Core 7 Series 1, 16 GB RAM LPDDR5, 512 GB SSD, 15.6” FHD táctil",
+  gpu: "Intel Graphics integrada",
+  price: "$22,999 MXN",
+  portability: "Alta",
+  image: "https://media-ik.croma.com/prod/https://media.croma.com/image/upload/v1708516453/Croma%20Assets/Computers%20Peripherals/Laptop/Images/305055_4_bsd9ja.png",
+  link: buildGoogleLink("Samsung Galaxy Book4 360"),
+},
+{
+  id: "topgro-n5-i3-mx230",
+  name: "Topgro N5",
+  use: "Ofimática, uso general, multimedia básica",
+  specs: "Intel Core i3-1005G1, 16 GB RAM DDR4, 512 GB SSD, NVIDIA GeForce MX230, 15.6” FHD",
+  gpu: "NVIDIA GeForce MX230 (2 GB GDDR5)",
+  price: "$6,935 MXN",
+  portability: "Media",
+  image: "https://m.media-amazon.com/images/I/71gXrO3lsqL._AC_.jpg", 
+  link: buildGoogleLink("Topgro N5 i3 MX230"),
+},
+{
+  id: "vsap-n4020-14",
+  name: "VSAP Intel Celeron N4020",
+  use: "Ofimática, navegación, estudiantes",
+  specs: "Intel Celeron N4020, 8 GB RAM DDR4, 512 GB SSD, 14.1\" FHD",
+  gpu: "Intel UHD Graphics 600",
+  price: "$3,695 MXN",
+  portability: "Alta",
+  image: "https://static.wixstatic.com/media/c8ef56_df2decbc5ac442c28c399e2c9a2ee5e2~mv2.webp", 
+  link: buildGoogleLink("VSAP Intel Celeron N4020"),
+},
+ 
+];
+ 
+export default laptopModels;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/matchLaptopClass.jsx.html b/coverage/matchLaptopClass.jsx.html new file mode 100644 index 0000000..d345696 --- /dev/null +++ b/coverage/matchLaptopClass.jsx.html @@ -0,0 +1,367 @@ + + + + + + Code coverage report for matchLaptopClass.jsx + + + + + + + + + +
+
+

All files matchLaptopClass.jsx

+
+ +
+ 89.39% + Statements + 59/66 +
+ + +
+ 87.64% + Branches + 78/89 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 97.95% + Lines + 48/49 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95  +  +2x +1200x +  +  +2x +794x +  +  +2x +57x +1311x +1311x +1311x +  +1311x +1262x +1246x +184x +24x +  +  +1078x +1078x +1078x +870x +814x +  +  +20x +  +  +794x +  +794x +745x +318x +  +300x +  +  +57x +300x +  +300x +300x +  +300x +300x +300x +300x +300x +300x +  +300x +4x +  +300x +7x +7x +  +  +  +300x +15x +  +  +  +  +  +  +4x +  +  +  +  +  +300x +  +  +  +300x +  +  +57x +529x +  +  +57x +  +  +  +  + 
import laptopModels from './laptopModels'
+ 
+const normalize = (text) =>
+  text.toLowerCase()
+    .replace(/(intel|amd|apple|core|ryzen|graphics|nvidia|geforce|radeon|gpu)|\s+/gi, '')
+ 
+const parsePrice = (priceString) =>
+  parseInt(priceString.replace(/[^\d]/g, ''), 10)
+ 
+ 
+const matchLaptopClass = (specs) => {
+  const compatibleModels = laptopModels.filter(model => {
+    const modelGPU = model.gpu.toLowerCase()
+    const modelOS = model.use.toLowerCase()
+    const modelSpecs = model.specs.toLowerCase()
+ 
+    if (specs.os === 'ChromeOS' && modelGPU.includes('rtx')) return false
+    if (specs.os === 'ChromeOS' && specs.processor.toLowerCase().includes('i7')) return false
+    if (specs.os === 'macOS') {
+      if (!model.name.toLowerCase().includes('macbook')) return false
+      if (!model.gpu.toLowerCase().includes('m1') && !model.gpu.toLowerCase().includes('m2') && !model.gpu.toLowerCase().includes('m3')) return false
+    }
+ 
+    Iif (specs.os === 'macOS' && modelGPU.includes('rtx')) return false
+    Iif (modelOS.includes('chrome') && specs.gpu.toLowerCase().includes('rtx')) return false
+    if (specs.gpu.toLowerCase().includes('rtx') && !modelGPU.includes('rtx')) return false
+    if (specs.ram === '16 GB' && !modelSpecs.includes('16 gb')) return false
+    if (
+      (specs.processor.includes('i7') || specs.processor.includes('ryzen 7')) &&
+      !modelSpecs.includes('i7') && !modelSpecs.includes('ryzen 7')
+    ) return false
+ 
+    // ✅ Presupuesto
+    const price = parsePrice(model.price)
+ 
+    if (specs.budget === 'low' && price > 6000) return false
+    if (specs.budget === 'medium' && price > 13000) return false
+    if (specs.budget === 'high' && price < 13000) return false
+ 
+    return true
+  })
+ 
+  const scoredModels = compatibleModels.map(model => {
+    let score = 0
+ 
+    const modelSpecs = model.specs.toLowerCase()
+    const modelGPU = model.gpu.toLowerCase()
+ 
+    Iif (normalize(modelSpecs).includes(normalize(specs.processor))) score += 2
+    if (modelSpecs.includes(specs.ram.toLowerCase())) score += 2
+    Iif (normalize(modelGPU).includes(normalize(specs.gpu))) score += 2
+    if (modelSpecs.includes(specs.storage.toLowerCase())) score += 1
+    Iif (model.portability === specs.portability) score += 1
+    Iif (model.use.toLowerCase().includes(specs.os.toLowerCase())) score += 1
+ 
+    if (specs.flags?.prioritizePortability && model.portability === 'Alta') {
+      score += 1.5
+    }
+    if (specs.flags?.prioritizeGaming) {
+      Eif (modelGPU.includes('rtx') || modelGPU.includes('gtx') || modelGPU.includes('mx')) {
+        score += 1.5
+      }
+    }
+ 
+    if (specs.flags?.prioritizeScreenQuality) {
+      if (
+        modelSpecs.includes('2.5k') ||
+        modelSpecs.includes('retina') ||
+        modelSpecs.includes('qhd') ||
+        modelSpecs.includes('2560') ||
+        modelSpecs.includes('amoled')
+      ) {
+        score += 1.5
+      }
+    }
+ 
+ 
+ 
+    Iif (specs.flags?.prioritizeSecurity && model.specs.toLowerCase().includes('huella')) {
+      score += 1.5
+    }
+ 
+    return { ...model, matchScore: score }
+  })
+ 
+  const bestMatches = scoredModels
+    .sort((a, b) => b.matchScore - a.matchScore)
+    .slice(0, 3) // 👈 Cambia 3 por cuántas quieres mostrar
+ 
+  return bestMatches.length ? bestMatches : [{ name: "Clase genérica", specs: "" }]
+ 
+}
+ 
+export default matchLaptopClass
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/prettify.js b/coverage/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/coverage/sorter.js b/coverage/sorter.js new file mode 100644 index 0000000..4ed70ae --- /dev/null +++ b/coverage/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/package-lock.json b/package-lock.json index 7067a71..ad5511a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,14 +17,316 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react-swc": "^3.9.0", + "@vitest/coverage-v8": "^4.1.5", "eslint": "^9.25.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", - "vite": "^6.3.5" + "jsdom": "^29.1.1", + "vite": "^6.3.5", + "vitest": "^4.1.5" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" } }, "node_modules/@esbuild/aix-ppc64": { @@ -567,6 +869,24 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -628,6 +948,34 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.2.tgz", @@ -909,6 +1257,13 @@ "win32" ] }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, "node_modules/@swc/core": { "version": "1.11.24", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.24.tgz", @@ -1122,6 +1477,108 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -1201,17 +1658,161 @@ "vite": "^4 || ^5 || ^6" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/@vitest/coverage-v8": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", + "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", "dev": true, + "license": "MIT", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.5", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">= 0.6" + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.5", + "vitest": "4.1.5" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" } }, "node_modules/acorn": { @@ -1251,6 +1852,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -1272,12 +1884,61 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "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==", "dev": true }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -1355,6 +2016,16 @@ "node": ">=6" } }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1416,6 +2087,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -1461,12 +2139,47 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -1484,6 +2197,13 @@ } } }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "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", @@ -1499,6 +2219,24 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1528,6 +2266,19 @@ "node": ">= 0.8" } }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -1546,6 +2297,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -1777,6 +2535,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -1816,6 +2584,16 @@ "node": ">=18.0.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -1892,10 +2670,14 @@ "dev": true }, "node_modules/fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -2116,6 +2898,26 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -2178,6 +2980,16 @@ "node": ">=0.8.19" } }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -2223,6 +3035,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -2235,6 +3054,45 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2253,6 +3111,47 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -2326,34 +3225,100 @@ "loose-envify": "cli.js" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "node_modules/lru-cache": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { - "node": ">= 0.4" + "node": "20 || >=22" } }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", "dev": true, - "engines": { - "node": ">= 0.8" + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" } }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mime-db": { @@ -2377,6 +3342,16 @@ "node": ">= 0.6" } }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2449,6 +3424,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -2529,6 +3515,19 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -2565,6 +3564,13 @@ "node": ">=16" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -2572,10 +3578,11 @@ "dev": true }, "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -2629,6 +3636,36 @@ "node": ">= 0.8.0" } }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2737,6 +3774,14 @@ "react": "*" } }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/react-router": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", @@ -2781,6 +3826,30 @@ "node": ">=18" } }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2871,11 +3940,37 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", @@ -3023,6 +4118,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3032,6 +4134,13 @@ "node": ">=0.10.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -3041,6 +4150,26 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -3065,14 +4194,39 @@ "node": ">=8" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, + "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -3081,6 +4235,36 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.30" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "dev": true, + "license": "MIT" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -3090,6 +4274,32 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -3116,6 +4326,16 @@ "node": ">= 0.6" } }, + "node_modules/undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -3217,6 +4437,144 @@ } } }, + "node_modules/vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.5", + "@vitest/browser-preview": "4.1.5", + "@vitest/browser-webdriverio": "4.1.5", + "@vitest/coverage-istanbul": "4.1.5", + "@vitest/coverage-v8": "4.1.5", + "@vitest/ui": "4.1.5", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "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", @@ -3232,6 +4590,23 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -3247,6 +4622,23 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -3279,6 +4671,157 @@ } }, "dependencies": { + "@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true + }, + "@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "requires": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "requires": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + } + }, + "@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true + }, + "@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true + }, + "@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "peer": true, + "requires": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + } + }, + "@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true + }, + "@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "requires": { + "@babel/types": "^7.29.0" + } + }, + "@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "dev": true + }, + "@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + } + }, + "@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true + }, + "@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "requires": { + "css-tree": "^3.0.0" + } + }, + "@csstools/color-helpers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", + "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", + "dev": true + }, + "@csstools/css-calc": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.0.tgz", + "integrity": "sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==", + "dev": true, + "requires": {} + }, + "@csstools/css-color-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.0.tgz", + "integrity": "sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==", + "dev": true, + "requires": { + "@csstools/color-helpers": "^6.0.2", + "@csstools/css-calc": "^3.2.0" + } + }, + "@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "requires": {} + }, + "@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.3.tgz", + "integrity": "sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==", + "dev": true, + "requires": {} + }, + "@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true + }, "@esbuild/aix-ppc64": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", @@ -3550,6 +5093,13 @@ "levn": "^0.4.1" } }, + "@exodus/bytes": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", + "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", + "dev": true, + "requires": {} + }, "@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -3586,6 +5136,28 @@ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "@modelcontextprotocol/sdk": { "version": "1.11.2", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.2.tgz", @@ -3744,6 +5316,12 @@ "dev": true, "optional": true }, + "@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true + }, "@swc/core": { "version": "1.11.24", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.11.24.tgz", @@ -3840,15 +5418,86 @@ "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", "dev": true }, - "@swc/types": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", - "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "@swc/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.21.tgz", + "integrity": "sha512-2YEtj5HJVbKivud9N4bpPBAyZhj4S2Ipe5LkUG94alTpr7in/GU/EARgPAd3BwU+YOmFVJC2+kjqhGRi3r0ZpQ==", + "dev": true, + "requires": { + "@swc/counter": "^0.1.3" + } + }, + "@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "peer": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + } + }, + "@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "requires": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "dependencies": { + "dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true + } + } + }, + "@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "requires": { + "@babel/runtime": "^7.12.5" + } + }, + "@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "peer": true + }, + "@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "requires": { - "@swc/counter": "^0.1.3" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, + "@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, "@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -3892,6 +5541,97 @@ "@swc/core": "^1.11.21" } }, + "@vitest/coverage-v8": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.5.tgz", + "integrity": "sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.5", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + } + }, + "@vitest/expect": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", + "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "dev": true, + "requires": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + } + }, + "@vitest/mocker": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", + "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "dev": true, + "requires": { + "@vitest/spy": "4.1.5", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + } + }, + "@vitest/pretty-format": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", + "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "dev": true, + "requires": { + "tinyrainbow": "^3.1.0" + } + }, + "@vitest/runner": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", + "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "dev": true, + "requires": { + "@vitest/utils": "4.1.5", + "pathe": "^2.0.3" + } + }, + "@vitest/snapshot": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", + "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "dev": true, + "requires": { + "@vitest/pretty-format": "4.1.5", + "@vitest/utils": "4.1.5", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + } + }, + "@vitest/spy": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", + "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "dev": true + }, + "@vitest/utils": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", + "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "dev": true, + "requires": { + "@vitest/pretty-format": "4.1.5", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + } + }, "accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -3927,6 +5667,13 @@ "uri-js": "^4.2.2" } }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true + }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -3942,12 +5689,55 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "requires": { + "dequal": "^2.0.3" + } + }, + "assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true + }, + "ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + }, + "dependencies": { + "js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true + } + } + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "requires": { + "require-from-string": "^2.0.2" + } + }, "body-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", @@ -4007,6 +5797,12 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, + "chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -4053,6 +5849,12 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -4086,12 +5888,38 @@ "which": "^2.0.1" } }, + "css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "requires": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + } + }, + "css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true + }, "csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "dev": true }, + "data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "requires": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + } + }, "debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -4101,6 +5929,12 @@ "ms": "^2.1.3" } }, + "decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4113,6 +5947,19 @@ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true }, + "dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true + }, + "dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "peer": true + }, "dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -4136,6 +5983,12 @@ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true }, + "entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true + }, "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -4148,6 +6001,12 @@ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true }, + "es-module-lexer": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", + "dev": true + }, "es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -4312,6 +6171,15 @@ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true }, + "estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "requires": { + "@types/estree": "^1.0.0" + } + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -4339,6 +6207,12 @@ "integrity": "sha512-VARTJ9CYeuQYb0pZEPbzi740OWFgpHe7AYJ2WFZVnUDUQp5Dk2yJUgF36YsZ81cOyxT0QxmXD2EQpapAouzWVA==", "dev": true }, + "expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true + }, "express": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", @@ -4400,9 +6274,9 @@ "dev": true }, "fdir": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "requires": {} }, @@ -4550,6 +6424,21 @@ "function-bind": "^1.1.2" } }, + "html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "requires": { + "@exodus/bytes": "^1.6.0" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -4594,6 +6483,12 @@ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -4629,6 +6524,12 @@ "is-extglob": "^2.1.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -4641,6 +6542,33 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -4655,6 +6583,35 @@ "argparse": "^2.0.1" } }, + "jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "requires": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + } + }, "json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4715,12 +6672,60 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "lru-cache": { + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", + "integrity": "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==", + "dev": true + }, + "lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "peer": true + }, + "magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "requires": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "magicast": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.2.tgz", + "integrity": "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==", + "dev": true, + "requires": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + } + }, "math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true }, + "mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true + }, "media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -4748,6 +6753,12 @@ "mime-db": "^1.54.0" } }, + "min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4793,6 +6804,12 @@ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true }, + "obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true + }, "on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -4852,6 +6869,15 @@ "callsites": "^3.0.0" } }, + "parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "requires": { + "entities": "^8.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -4876,6 +6902,12 @@ "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", "dev": true }, + "pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4883,9 +6915,9 @@ "dev": true }, "picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true }, "pkce-challenge": { @@ -4911,6 +6943,27 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "peer": true, + "requires": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "peer": true + } + } + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4988,6 +7041,13 @@ "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", "requires": {} }, + "react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, "react-router": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", @@ -5012,6 +7072,22 @@ "react-router": "7.6.0" } }, + "redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "requires": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + } + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -5073,11 +7149,26 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==" }, + "semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true + }, "send": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", @@ -5188,18 +7279,45 @@ "side-channel-map": "^1.0.1" } }, + "siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, + "stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, "statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true }, + "std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true + }, + "strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "requires": { + "min-indent": "^1.0.0" + } + }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -5215,22 +7333,79 @@ "has-flag": "^4.0.0" } }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "tinyexec": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", + "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "dev": true + }, "tinyglobby": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "requires": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + } + }, + "tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true + }, + "tldts": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.30.tgz", + "integrity": "sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==", + "dev": true, + "requires": { + "tldts-core": "^7.0.30" } }, + "tldts-core": { + "version": "7.0.30", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.30.tgz", + "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", + "dev": true + }, "toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true }, + "tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "requires": { + "tldts": "^7.0.5" + } + }, + "tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "requires": { + "punycode": "^2.3.1" + } + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5251,6 +7426,12 @@ "mime-types": "^3.0.0" } }, + "undici": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", + "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "dev": true + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -5287,6 +7468,66 @@ "tinyglobby": "^0.2.13" } }, + "vitest": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", + "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "dev": true, + "requires": { + "@vitest/expect": "4.1.5", + "@vitest/mocker": "4.1.5", + "@vitest/pretty-format": "4.1.5", + "@vitest/runner": "4.1.5", + "@vitest/snapshot": "4.1.5", + "@vitest/spy": "4.1.5", + "@vitest/utils": "4.1.5", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + } + }, + "w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "requires": { + "xml-name-validator": "^5.0.0" + } + }, + "webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true + }, + "whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true + }, + "whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "requires": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -5296,6 +7537,16 @@ "isexe": "^2.0.0" } }, + "why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "requires": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + } + }, "word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -5308,6 +7559,18 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index a8c0098..2fa2030 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", - "preview": "vite preview" + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" }, "dependencies": { "@vercel/analytics": "^1.5.0", @@ -19,13 +22,18 @@ }, "devDependencies": { "@eslint/js": "^9.25.0", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/react": "^19.1.2", "@types/react-dom": "^19.1.2", "@vitejs/plugin-react-swc": "^3.9.0", + "@vitest/coverage-v8": "^4.1.5", "eslint": "^9.25.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", - "vite": "^6.3.5" + "jsdom": "^29.1.1", + "vite": "^6.3.5", + "vitest": "^4.1.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..14dc4ad --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1721 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@vercel/analytics': + specifier: ^1.5.0 + version: 1.6.1(react@19.2.6) + react: + specifier: ^19.1.0 + version: 19.2.6 + react-dom: + specifier: ^19.1.0 + version: 19.2.6(react@19.2.6) + react-helmet-async: + specifier: ^3.0.0 + version: 3.0.0(react@19.2.6) + react-icons: + specifier: ^5.5.0 + version: 5.6.0(react@19.2.6) + react-router-dom: + specifier: ^7.6.0 + version: 7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + devDependencies: + '@eslint/js': + specifier: ^9.25.0 + version: 9.39.4 + '@types/react': + specifier: ^19.1.2 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.1.2 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react-swc': + specifier: ^3.9.0 + version: 3.11.0(vite@6.4.2) + eslint: + specifier: ^9.25.0 + version: 9.39.4 + eslint-plugin-react-hooks: + specifier: ^5.2.0 + version: 5.2.0(eslint@9.39.4) + eslint-plugin-react-refresh: + specifier: ^0.4.19 + version: 0.4.26(eslint@9.39.4) + globals: + specifier: ^16.0.0 + version: 16.5.0 + vite: + specifier: ^6.3.5 + version: 6.4.2 + +packages: + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.60.3': + resolution: {integrity: sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.3': + resolution: {integrity: sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.3': + resolution: {integrity: sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.3': + resolution: {integrity: sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.3': + resolution: {integrity: sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.3': + resolution: {integrity: sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.3': + resolution: {integrity: sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.3': + resolution: {integrity: sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.3': + resolution: {integrity: sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.3': + resolution: {integrity: sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.3': + resolution: {integrity: sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.3': + resolution: {integrity: sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.3': + resolution: {integrity: sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.3': + resolution: {integrity: sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.3': + resolution: {integrity: sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.3': + resolution: {integrity: sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.3': + resolution: {integrity: sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.3': + resolution: {integrity: sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.3': + resolution: {integrity: sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.3': + resolution: {integrity: sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.3': + resolution: {integrity: sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.3': + resolution: {integrity: sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.3': + resolution: {integrity: sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.3': + resolution: {integrity: sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.3': + resolution: {integrity: sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA==} + cpu: [x64] + os: [win32] + + '@swc/core-darwin-arm64@1.15.33': + resolution: {integrity: sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.15.33': + resolution: {integrity: sha512-/Il4QHSOhV4FekbsDtkrNmKbsX26oSysvgrRswa/RYOHXAkwXDbB4jaeKq6PsJLSPkzJ2KzQ061gtBnk0vNHfA==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.15.33': + resolution: {integrity: sha512-C64hBnBxq4viOPQ8hlx+2lJ23bzZBGnjw7ryALmS+0Q3zHmwO8lw1/DArLENw4Q18/0w5wdEO1k3m1wWNtKGqQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.15.33': + resolution: {integrity: sha512-TRJfnJbX3jqpxRDRoieMzRiCBS5jOmXNb3iQXmcgjFEHKLnAgK1RZRU8Cq1MsPqO4jAJp/ld1G4O3fXuxv85uw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-arm64-musl@1.15.33': + resolution: {integrity: sha512-il7tYM+CpUNzieQbwAjFT1P8zqAhmGWNAGhQZBnxurXZ0aNn+5nqYFTEUKNZl7QibtT0uQXzTZrNGHCIj6Y1Og==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc/core-linux-ppc64-gnu@1.15.33': + resolution: {integrity: sha512-ZtNBwN0Z7CFj9Il0FcPaKdjgP7URyKu/3RfH46vq+0paOBqLj4NYldD6Qo//Duif/7IOtAraUfDOmp0PLAufog==} + engines: {node: '>=10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-s390x-gnu@1.15.33': + resolution: {integrity: sha512-De1IyajoOmhOYYjw/lx66bKlyDpHZTueqwpDrWgf5O7T6d1ODeJJO9/OqMBmrBQc5C+dNnlmIufHsp4QVCWufA==} + engines: {node: '>=10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-gnu@1.15.33': + resolution: {integrity: sha512-mGTH0YxmUN+x6vRN/I6NOk5X0ogNktkwPnJ94IMvR7QjhRDwL0O8RXEDhyUM0YtwWrryBOqaJQBX4zruxEPRGw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc/core-linux-x64-musl@1.15.33': + resolution: {integrity: sha512-hj628ZkSEJf6zMf5VMbYrG2O6QqyTIp2qwY6VlCjvIa9lAEZ5c2lfPblCLVGYubTeLJDxadLB/CxqQYOQABeEQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc/core-win32-arm64-msvc@1.15.33': + resolution: {integrity: sha512-GV2oohtN2/5+KSccl86VULu3aT+LrISC8uzgSq0FRnikpD+Zwc+sBlXmoKQ+Db6jI57ITUOIB8jRkdGMABC29g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.15.33': + resolution: {integrity: sha512-gtyvzSNR8DHKfFEA2uqb8Ld1myqi6uEg2jyeUq3ikn5ytYs7H8RpZYC8mdy4NXr8hfcdJfCLXPlYaqqfBXpoEQ==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.15.33': + resolution: {integrity: sha512-d6fRqQSkJI+kmMEBWaDQ7TMl8+YjLYbwRUPZQ9DY0ORBJeTzOrG0twvfvlZ2xgw6jA0ScQKgfBm4vHLSLl5Hqg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.15.33': + resolution: {integrity: sha512-jOlwnFV2xhuuZeAUILGFULeR6vDPfijEJ57evfocwznQldLU3w2cZ9bSDryY9ip+AsM3r1NJKzf47V2NXebkeQ==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '>=0.5.17' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/types@0.1.26': + resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@vercel/analytics@1.6.1': + resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==} + peerDependencies: + '@remix-run/react': ^2 + '@sveltejs/kit': ^1 || ^2 + next: '>= 13' + react: ^18 || ^19 || ^19.0.0-rc + svelte: '>= 4' + vue: ^3 + vue-router: ^4 + peerDependenciesMeta: + '@remix-run/react': + optional: true + '@sveltejs/kit': + optional: true + next: + optional: true + react: + optional: true + svelte: + optional: true + vue: + optional: true + vue-router: + optional: true + + '@vitejs/plugin-react-swc@3.11.0': + resolution: {integrity: sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==} + peerDependencies: + vite: ^4 || ^5 || ^6 || ^7 + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-refresh@0.4.26: + resolution: {integrity: sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==} + peerDependencies: + eslint: '>=8.40' + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-helmet-async@3.0.0: + resolution: {integrity: sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==} + peerDependencies: + react: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-icons@5.6.0: + resolution: {integrity: sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==} + peerDependencies: + react: '*' + + react-router-dom@7.15.0: + resolution: {integrity: sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.15.0: + resolution: {integrity: sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + rollup@4.60.3: + resolution: {integrity: sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + vite@6.4.2: + resolution: {integrity: sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + +snapshots: + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + dependencies: + eslint: 9.39.4 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.60.3': + optional: true + + '@rollup/rollup-android-arm64@4.60.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.3': + optional: true + + '@rollup/rollup-darwin-x64@4.60.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.3': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.3': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.3': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.3': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.3': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.3': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.3': + optional: true + + '@swc/core-darwin-arm64@1.15.33': + optional: true + + '@swc/core-darwin-x64@1.15.33': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.33': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.33': + optional: true + + '@swc/core-linux-arm64-musl@1.15.33': + optional: true + + '@swc/core-linux-ppc64-gnu@1.15.33': + optional: true + + '@swc/core-linux-s390x-gnu@1.15.33': + optional: true + + '@swc/core-linux-x64-gnu@1.15.33': + optional: true + + '@swc/core-linux-x64-musl@1.15.33': + optional: true + + '@swc/core-win32-arm64-msvc@1.15.33': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.33': + optional: true + + '@swc/core-win32-x64-msvc@1.15.33': + optional: true + + '@swc/core@1.15.33': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.26 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.33 + '@swc/core-darwin-x64': 1.15.33 + '@swc/core-linux-arm-gnueabihf': 1.15.33 + '@swc/core-linux-arm64-gnu': 1.15.33 + '@swc/core-linux-arm64-musl': 1.15.33 + '@swc/core-linux-ppc64-gnu': 1.15.33 + '@swc/core-linux-s390x-gnu': 1.15.33 + '@swc/core-linux-x64-gnu': 1.15.33 + '@swc/core-linux-x64-musl': 1.15.33 + '@swc/core-win32-arm64-msvc': 1.15.33 + '@swc/core-win32-ia32-msvc': 1.15.33 + '@swc/core-win32-x64-msvc': 1.15.33 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.26': + dependencies: + '@swc/counter': 0.1.3 + + '@types/estree@1.0.8': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@vercel/analytics@1.6.1(react@19.2.6)': + optionalDependencies: + react: 19.2.6 + + '@vitejs/plugin-react-swc@3.11.0(vite@6.4.2)': + dependencies: + '@rolldown/pluginutils': 1.0.0-beta.27 + '@swc/core': 1.15.33 + vite: 6.4.2 + transitivePeerDependencies: + - '@swc/helpers' + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + balanced-match@1.0.2: {} + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + callsites@3.1.0: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + concat-map@0.0.1: {} + + cookie@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + deep-is@0.1.4: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@5.2.0(eslint@9.39.4): + dependencies: + eslint: 9.39.4 + + eslint-plugin-react-refresh@0.4.26(eslint@9.39.4): + dependencies: + eslint: 9.39.4 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.4: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fsevents@2.3.3: + optional: true + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.5.0: {} + + has-flag@4.0.0: {} + + ignore@5.3.2: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + natural-compare@1.4.0: {} + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.14: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + punycode@2.3.1: {} + + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react-fast-compare@3.2.2: {} + + react-helmet-async@3.0.0(react@19.2.6): + dependencies: + invariant: 2.2.4 + react: 19.2.6 + react-fast-compare: 3.2.2 + shallowequal: 1.1.0 + + react-icons@5.6.0(react@19.2.6): + dependencies: + react: 19.2.6 + + react-router-dom@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-router: 7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + + react-router@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + cookie: 1.1.1 + react: 19.2.6 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.6(react@19.2.6) + + react@19.2.6: {} + + resolve-from@4.0.0: {} + + rollup@4.60.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.3 + '@rollup/rollup-android-arm64': 4.60.3 + '@rollup/rollup-darwin-arm64': 4.60.3 + '@rollup/rollup-darwin-x64': 4.60.3 + '@rollup/rollup-freebsd-arm64': 4.60.3 + '@rollup/rollup-freebsd-x64': 4.60.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.3 + '@rollup/rollup-linux-arm-musleabihf': 4.60.3 + '@rollup/rollup-linux-arm64-gnu': 4.60.3 + '@rollup/rollup-linux-arm64-musl': 4.60.3 + '@rollup/rollup-linux-loong64-gnu': 4.60.3 + '@rollup/rollup-linux-loong64-musl': 4.60.3 + '@rollup/rollup-linux-ppc64-gnu': 4.60.3 + '@rollup/rollup-linux-ppc64-musl': 4.60.3 + '@rollup/rollup-linux-riscv64-gnu': 4.60.3 + '@rollup/rollup-linux-riscv64-musl': 4.60.3 + '@rollup/rollup-linux-s390x-gnu': 4.60.3 + '@rollup/rollup-linux-x64-gnu': 4.60.3 + '@rollup/rollup-linux-x64-musl': 4.60.3 + '@rollup/rollup-openbsd-x64': 4.60.3 + '@rollup/rollup-openharmony-arm64': 4.60.3 + '@rollup/rollup-win32-arm64-msvc': 4.60.3 + '@rollup/rollup-win32-ia32-msvc': 4.60.3 + '@rollup/rollup-win32-x64-gnu': 4.60.3 + '@rollup/rollup-win32-x64-msvc': 4.60.3 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + set-cookie-parser@2.7.2: {} + + shallowequal@1.1.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + source-map-js@1.2.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite@6.4.2: + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.14 + rollup: 4.60.3 + tinyglobby: 0.2.16 + optionalDependencies: + fsevents: 2.3.3 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..25711ba --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + '@swc/core': set this to true or false + esbuild: set this to true or false diff --git a/src/__tests__/convertToSpecs.test.jsx b/src/__tests__/convertToSpecs.test.jsx new file mode 100644 index 0000000..18dc865 --- /dev/null +++ b/src/__tests__/convertToSpecs.test.jsx @@ -0,0 +1,345 @@ +import { describe, it, expect } from 'vitest' +import convertToSpecs from '../utils/convertToSpecs' + +const baseAnswers = { + mainUse: ['basics'], + system: 'windows', + budget: 'medium', + workload: 'light', + photoVideo: 'no', + webBrowsing: 'light', + location: 'stationary', + importance: 'none', + battery: 'no', +} + +describe('convertToSpecs — Basic functionality', () => { + it('returns a valid spec profile for basic answers', () => { + const specs = convertToSpecs(baseAnswers) + expect(specs).toHaveProperty('processor') + expect(specs).toHaveProperty('ram') + expect(specs).toHaveProperty('storage') + expect(specs).toHaveProperty('gpu') + expect(specs).toHaveProperty('os') + expect(specs).toHaveProperty('budget') + expect(specs).toHaveProperty('warnings') + expect(specs).toHaveProperty('rationale') + expect(specs).toHaveProperty('laptopClass') + expect(Array.isArray(specs.laptopClass)).toBe(true) + }) + + it('sets Windows 11 as default OS', () => { + const specs = convertToSpecs(baseAnswers) + expect(specs.os).toBe('Windows 11') + }) + + it('stores budget from answers', () => { + const specs = convertToSpecs({ ...baseAnswers, budget: 'low' }) + expect(specs.budget).toBe('low') + }) +}) + +describe('convertToSpecs — Gaming use case', () => { + it('sets high GPU score for gaming', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: ['gaming'], + gamesType: 'complex', + }) + expect(specs.gpu).toContain('RTX') + expect(specs.rationale.some(r => r.toLowerCase().includes('gaming'))).toBe(true) + }) + + it('sets prioritizeGaming flag', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: ['gaming'], + }) + expect(specs.flags?.prioritizeGaming).toBe(true) + }) + + it('upgrades CPU and RAM for RTX requirements', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: ['gaming'], + gamesType: 'complex', + }) + expect(specs.processor).toMatch(/i5|i7|Ryzen 5|Ryzen 7/) + expect(specs.ram).toBe('16 GB') + }) +}) + +describe('convertToSpecs — Content creation', () => { + it('sets high performance and storage for creating', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: ['creating'], + }) + expect(specs.performance || specs.rationale.some(r => r.toLowerCase().includes('creación'))).toBeTruthy() + expect(specs.storage).toBe('512 GB SSD') + }) + + it('sets high specs for photoVideo: pro', () => { + const specs = convertToSpecs({ + ...baseAnswers, + photoVideo: 'pro', + }) + expect(specs.storage).toBe('512 GB SSD') + }) + + it('sets prioritizeScreenQuality flag for photoVideo: pro', () => { + const specs = convertToSpecs({ + ...baseAnswers, + photoVideo: 'pro', + }) + expect(specs.flags?.prioritizeScreenQuality).toBe(true) + }) +}) + +describe('convertToSpecs — Heavy workload', () => { + it('sets high performance for work_school + heavy', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: ['work_school'], + workload: 'heavy', + }) + expect(specs.rationale.some(r => r.toLowerCase().includes('trabajo'))).toBe(true) + }) + + it('sets high RAM for heavy web browsing', () => { + const specs = convertToSpecs({ + ...baseAnswers, + webBrowsing: 'heavy', + }) + expect(specs.rationale.some(r => r.toLowerCase().includes('ram'))).toBe(true) + }) +}) + +describe('convertToSpecs — Portability', () => { + it('sets high portability for mobile location', () => { + const specs = convertToSpecs({ + ...baseAnswers, + location: 'mobile', + }) + expect(specs.portability).toContain('Alta') + }) + + it('sets prioritizePortability flag', () => { + const specs = convertToSpecs({ + ...baseAnswers, + importance: 'portability', + }) + expect(specs.flags?.prioritizePortability).toBe(true) + }) +}) + +describe('convertToSpecs — macOS', () => { + it('sets macOS as OS', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'mac', + }) + expect(specs.os).toBe('macOS') + }) + + it('forces Apple Silicon processor', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'mac', + }) + expect(specs.processor).toMatch(/Apple M|M1|M2|M3/) + }) + + it('warns about macOS + gaming incompatibility', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'mac', + mainUse: ['gaming'], + }) + expect(specs.warnings.some(w => w.toLowerCase().includes('macos') && w.toLowerCase().includes('juegos'))).toBe(true) + }) + + it('warns about low budget + demanding macOS tasks', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'mac', + budget: 'low', + mainUse: ['gaming'], + }) + expect(specs.warnings.some(w => w.includes('⚠️') && w.toLowerCase().includes('presupuesto'))).toBe(true) + }) +}) + +describe('convertToSpecs — ChromeOS', () => { + it('sets ChromeOS as OS', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'chrome', + }) + expect(specs.os).toBe('ChromeOS') + }) + + it('forces modest hardware for ChromeOS', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'chrome', + }) + expect(specs.processor).toMatch(/Celeron|MediaTek/) + expect(specs.gpu).toContain('integrados') + }) + + it('warns about ChromeOS + heavy tasks', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'chrome', + workload: 'heavy', + }) + expect(specs.warnings.some(w => w.toLowerCase().includes('chromeos'))).toBe(true) + }) + + it('warns about ChromeOS + gaming complex', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'chrome', + mainUse: ['gaming'], + gamesType: 'complex', + }) + expect(specs.warnings.some(w => w.toLowerCase().includes('chromeos') && w.toLowerCase().includes('juegos'))).toBe(true) + }) +}) + +describe('convertToSpecs — Budget constraints', () => { + it('warns about low budget with high specs', () => { + const specs = convertToSpecs({ + ...baseAnswers, + budget: 'low', + mainUse: ['gaming'], + }) + expect(specs.warnings.some(w => w.toLowerCase().includes('presupuesto'))).toBe(true) + }) + + it('warns about RTX with low budget', () => { + const specs = convertToSpecs({ + ...baseAnswers, + budget: 'low', + mainUse: ['gaming'], + gamesType: 'complex', + }) + expect(specs.warnings.some(w => w.toLowerCase().includes('rtx'))).toBe(true) + }) +}) + +describe('convertToSpecs — Edge cases', () => { + it('handles empty mainUse gracefully', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: [], + }) + expect(specs).toHaveProperty('processor') + expect(specs.os).toBe('Windows 11') + }) + + it('handles full_use (all uses selected)', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: 'full_use', + }) + expect(specs).toHaveProperty('processor') + expect(specs.laptopClass).toBeDefined() + }) + + it('handles touchscreen preference', () => { + const specs = convertToSpecs({ + ...baseAnswers, + importance: 'touch', + }) + expect(specs.touchscreen).toBe(true) + }) + + it('handles security preference', () => { + const specs = convertToSpecs({ + ...baseAnswers, + importance: 'security', + }) + expect(specs.flags?.prioritizeSecurity).toBe(true) + }) + + it('handles battery preference', () => { + const specs = convertToSpecs({ + ...baseAnswers, + battery: 'yes', + }) + expect(specs.battery).toContain('Alta') + }) + + it('detects contradictory use: basic only but demanding answers', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: ['entertainment'], + workload: 'heavy', + photoVideo: 'pro', + }) + expect(specs.warnings.some(w => w.includes('⚠️') && w.toLowerCase().includes('simple'))).toBe(true) + }) +}) + +describe('convertToSpecs — gamesType scoring', () => { + it('complex games increase GPU requirements more than simple', () => { + const specsComplex = convertToSpecs({ + ...baseAnswers, + mainUse: ['gaming'], + gamesType: 'complex', + }) + const specsSimple = convertToSpecs({ + ...baseAnswers, + mainUse: ['gaming'], + gamesType: 'simple', + }) + // Both have gaming base (4.5), complex adds +3, simple adds +1 + expect(specsComplex.gpu).toContain('RTX') + expect(specsSimple.gpu).toContain('RTX') + // Complex should have higher GPU tier or same + expect(specsComplex.gpu).toBe(specsSimple.gpu) + }) +}) + +describe('convertToSpecs — photoVideo: basic scoring', () => { + it('basic photo editing adds modest GPU/storage', () => { + const specs = convertToSpecs({ + ...baseAnswers, + photoVideo: 'basic', + }) + expect(specs).toHaveProperty('gpu') + expect(specs).toHaveProperty('storage') + }) +}) + +describe('convertToSpecs — Apple Silicon matching', () => { + it('does not strip M1/M2/M3 from processor spec', () => { + const specs = convertToSpecs({ + ...baseAnswers, + system: 'mac', + }) + expect(specs.processor).toMatch(/M1|M2|M3/) + }) +}) + +describe('convertToSpecs — RAM comparison edge cases', () => { + it('sets correct RAM for multitasking >= 4', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: ['gaming', 'creating'], + webBrowsing: 'heavy', + }) + expect(specs.ram).toBe('16 GB') + }) + + it('sets 12 GB RAM for moderate multitasking', () => { + const specs = convertToSpecs({ + ...baseAnswers, + mainUse: ['work_school'], + workload: 'heavy', + }) + expect(specs.ram).toBe('12 GB') + }) +}) diff --git a/src/__tests__/matchLaptopClass.test.jsx b/src/__tests__/matchLaptopClass.test.jsx new file mode 100644 index 0000000..8ddaba2 --- /dev/null +++ b/src/__tests__/matchLaptopClass.test.jsx @@ -0,0 +1,310 @@ +import { describe, it, expect } from 'vitest' +import matchLaptopClass from '../utils/matchLaptopClass' + +const baseSpecs = { + processor: 'Intel i5 / Ryzen 5', + ram: '8 GB', + storage: '256 GB SSD', + gpu: 'Gráficos integrados básicos', + os: 'Windows 11', + budget: 'medium', + portability: 'Media o baja', + battery: 'Media (4-7h)', + flags: {}, +} + +describe('matchLaptopClass — Basic matching', () => { + it('returns an array of matches', () => { + const results = matchLaptopClass(baseSpecs) + expect(Array.isArray(results)).toBe(true) + }) + + it('returns up to 3 best matches', () => { + const results = matchLaptopClass(baseSpecs) + expect(results.length).toBeLessThanOrEqual(3) + }) + + it('each result has name, specs, and matchScore', () => { + const results = matchLaptopClass(baseSpecs) + if (results.length > 0) { + results.forEach(model => { + expect(model).toHaveProperty('name') + expect(model).toHaveProperty('matchScore') + }) + } + }) +}) + +describe('matchLaptopClass — GPU filtering', () => { + it('filters out non-RTX models when RTX is required', () => { + const specs = { + ...baseSpecs, + gpu: 'NVIDIA RTX 3050 o superior', + budget: 'high', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + expect(model.gpu.toLowerCase()).toContain('rtx') + }) + }) + + it('filters out RTX models for ChromeOS', () => { + const specs = { + ...baseSpecs, + os: 'ChromeOS', + gpu: 'Gráficos integrados básicos', + processor: 'Intel Celeron / MediaTek', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + expect(model.gpu.toLowerCase()).not.toContain('rtx') + }) + }) +}) + +describe('matchLaptopClass — macOS filtering', () => { + it('only returns MacBooks for macOS', () => { + const specs = { + ...baseSpecs, + os: 'macOS', + processor: 'Apple M1 / M2 / M3', + gpu: 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)', + budget: 'high', + } + const results = matchLaptopClass(specs) + if (results.length === 1 && results[0].name === 'Clase genérica') { + // No MacBooks match the strict GPU filter — known DB limitation + expect(results[0].name).toBe('Clase genérica') + } else { + results.forEach(model => { + expect(model.name.toLowerCase()).toContain('macbook') + }) + } + }) + + it('filters for Apple Silicon GPUs on macOS', () => { + const specs = { + ...baseSpecs, + os: 'macOS', + processor: 'Apple M1 / M2 / M3', + gpu: 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)', + budget: 'high', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + if (model.name === 'Clase genérica') return + const gpu = model.gpu.toLowerCase() + expect(gpu).toMatch(/m1|m2|m3|m4|apple/) + }) + }) + + it('excludes RTX models for macOS', () => { + const specs = { + ...baseSpecs, + os: 'macOS', + processor: 'Apple M1 / M2 / M3', + gpu: 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)', + budget: 'high', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + if (model.name === 'Clase genérica') return + expect(model.gpu.toLowerCase()).not.toContain('rtx') + }) + }) +}) + +describe('matchLaptopClass — Budget filtering', () => { + it('filters by low budget (<=6000)', () => { + const specs = { + ...baseSpecs, + budget: 'low', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + const price = parseInt(model.price.replace(/[^\d]/g, ''), 10) + expect(price).toBeLessThanOrEqual(6000) + }) + }) + + it('filters by medium budget (<=13000)', () => { + const specs = { + ...baseSpecs, + budget: 'medium', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + const price = parseInt(model.price.replace(/[^\d]/g, ''), 10) + expect(price).toBeLessThanOrEqual(13000) + }) + }) + + it('filters by high budget (>=13000)', () => { + const specs = { + ...baseSpecs, + budget: 'high', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + const price = parseInt(model.price.replace(/[^\d]/g, ''), 10) + expect(price).toBeGreaterThanOrEqual(13000) + }) + }) +}) + +describe('matchLaptopClass — RAM filtering', () => { + it('filters for 16 GB RAM when required', () => { + const specs = { + ...baseSpecs, + ram: '16 GB', + budget: 'high', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + expect(model.specs.toLowerCase()).toContain('16 gb') + }) + }) +}) + +describe('matchLaptopClass — CPU filtering', () => { + it('filters for i7/Ryzen 7 when required', () => { + const specs = { + ...baseSpecs, + processor: 'Intel i7 / Ryzen 7', + budget: 'high', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + const specsText = model.specs.toLowerCase() + expect(specsText).toMatch(/i7|ryzen 7/) + }) + }) +}) + +describe('matchLaptopClass — Scoring', () => { + it('scores models by spec matching', () => { + const results = matchLaptopClass(baseSpecs) + if (results.length > 1) { + for (let i = 0; i < results.length - 1; i++) { + expect(results[i].matchScore).toBeGreaterThanOrEqual(results[i + 1].matchScore) + } + } + }) + + it('prioritizePortability flag boosts portable models', () => { + const specs = { + ...baseSpecs, + flags: { prioritizePortability: true }, + } + const results = matchLaptopClass(specs) + if (results.length > 0) { + const portableMatches = results.filter(m => m.portability === 'Alta') + expect(portableMatches.length).toBeGreaterThan(0) + } + }) + + it('prioritizeGaming flag boosts gaming models', () => { + const specs = { + ...baseSpecs, + gpu: 'NVIDIA RTX 3050 o superior', + budget: 'high', + flags: { prioritizeGaming: true }, + } + const results = matchLaptopClass(specs) + if (results.length > 0) { + const gamingMatches = results.filter(m => + m.gpu.toLowerCase().includes('rtx') || + m.gpu.toLowerCase().includes('gtx') + ) + expect(gamingMatches.length).toBeGreaterThan(0) + } + }) + + it('prioritizeScreenQuality flag boosts high-res models', () => { + const specs = { + ...baseSpecs, + flags: { prioritizeScreenQuality: true }, + budget: 'high', + } + const results = matchLaptopClass(specs) + if (results.length > 0) { + const screenMatches = results.filter(m => + m.specs.toLowerCase().includes('2.5k') || + m.specs.toLowerCase().includes('retina') || + m.specs.toLowerCase().includes('qhd') || + m.specs.toLowerCase().includes('amoled') + ) + expect(screenMatches.length).toBeGreaterThan(0) + } + }) + + it('prioritizeSecurity flag boosts models with fingerprint', () => { + const specs = { + ...baseSpecs, + flags: { prioritizeSecurity: true }, + } + const results = matchLaptopClass(specs) + if (results.length > 0) { + const securityMatches = results.filter(m => + m.specs.toLowerCase().includes('huella') + ) + // Note: current DB has no models with "huella" — test passes if no crash + expect(securityMatches.length).toBeGreaterThanOrEqual(0) + } + }) +}) + +describe('matchLaptopClass — Edge cases', () => { + it('returns generic class when no matches found', () => { + const specs = { + ...baseSpecs, + processor: 'Intel i7 / Ryzen 7', + ram: '16 GB', + gpu: 'NVIDIA RTX 3050 o superior', + budget: 'low', + } + const results = matchLaptopClass(specs) + expect(results.length).toBeGreaterThan(0) + if (results.length === 1 && results[0].name === 'Clase genérica') { + expect(results[0].specs).toBe('') + } + }) + + it('handles ChromeOS with RTX spec (should filter out)', () => { + const specs = { + ...baseSpecs, + os: 'ChromeOS', + gpu: 'NVIDIA RTX 3050 o superior', + processor: 'Intel Celeron / MediaTek', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + if (model.name === 'Clase genérica' || !model.gpu) return + expect(model.gpu.toLowerCase()).not.toContain('rtx') + }) + }) + + it('handles ChromeOS with i7 spec (should filter out)', () => { + const specs = { + ...baseSpecs, + os: 'ChromeOS', + processor: 'Intel i7 / Ryzen 7', + gpu: 'Gráficos integrados básicos', + } + const results = matchLaptopClass(specs) + results.forEach(model => { + expect(model.specs.toLowerCase()).not.toMatch(/i7|ryzen 7/) + }) + }) +}) + +describe('matchLaptopClass — Results are sorted by score', () => { + it('results are in descending matchScore order', () => { + const results = matchLaptopClass(baseSpecs) + if (results.length > 1) { + for (let i = 0; i < results.length - 1; i++) { + expect(results[i].matchScore).toBeGreaterThanOrEqual(results[i + 1].matchScore) + } + } + }) +}) diff --git a/src/__tests__/setup.js b/src/__tests__/setup.js new file mode 100644 index 0000000..c44951a --- /dev/null +++ b/src/__tests__/setup.js @@ -0,0 +1 @@ +import '@testing-library/jest-dom' diff --git a/vitest.config.js b/vitest.config.js new file mode 100644 index 0000000..644a315 --- /dev/null +++ b/vitest.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from 'vitest/config' +import react from '@vitejs/plugin-react-swc' + +export default defineConfig({ + plugins: [react()], + test: { + environment: 'jsdom', + setupFiles: ['./src/__tests__/setup.js'], + globals: true, + passWithNoTests: true, + }, +}) From ef5f563040be4b103251b311e66ecf04f1957070 Mon Sep 17 00:00:00 2001 From: FranGuh Date: Sat, 9 May 2026 18:47:35 -0600 Subject: [PATCH 3/8] chore: add coverage to gitignore --- .gitignore | 1 + coverage/base.css | 224 ------ coverage/block-navigation.js | 87 --- coverage/clover.xml | 219 ------ coverage/convertToSpecs.jsx.html | 1162 ---------------------------- coverage/coverage-final.json | 4 - coverage/favicon.png | Bin 445 -> 0 bytes coverage/index.html | 146 ---- coverage/laptopModels.js.html | 898 --------------------- coverage/matchLaptopClass.jsx.html | 367 --------- coverage/prettify.css | 1 - coverage/prettify.js | 2 - coverage/sort-arrow-sprite.png | Bin 138 -> 0 bytes coverage/sorter.js | 210 ----- 14 files changed, 1 insertion(+), 3320 deletions(-) delete mode 100644 coverage/base.css delete mode 100644 coverage/block-navigation.js delete mode 100644 coverage/clover.xml delete mode 100644 coverage/convertToSpecs.jsx.html delete mode 100644 coverage/coverage-final.json delete mode 100644 coverage/favicon.png delete mode 100644 coverage/index.html delete mode 100644 coverage/laptopModels.js.html delete mode 100644 coverage/matchLaptopClass.jsx.html delete mode 100644 coverage/prettify.css delete mode 100644 coverage/prettify.js delete mode 100644 coverage/sort-arrow-sprite.png delete mode 100644 coverage/sorter.js diff --git a/.gitignore b/.gitignore index 438657a..31598f1 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ dist dist-ssr *.local .env +coverage # Editor directories and files .vscode/* diff --git a/coverage/base.css b/coverage/base.css deleted file mode 100644 index f418035..0000000 --- a/coverage/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js deleted file mode 100644 index 530d1ed..0000000 --- a/coverage/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/coverage/clover.xml b/coverage/clover.xml deleted file mode 100644 index 4d2dedb..0000000 --- a/coverage/clover.xml +++ /dev/null @@ -1,219 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/coverage/convertToSpecs.jsx.html b/coverage/convertToSpecs.jsx.html deleted file mode 100644 index 5be3d81..0000000 --- a/coverage/convertToSpecs.jsx.html +++ /dev/null @@ -1,1162 +0,0 @@ - - - - - - Code coverage report for convertToSpecs.jsx - - - - - - - - - -
-
-

All files convertToSpecs.jsx

-
- -
- 92.99% - Statements - 146/157 -
- - -
- 88.26% - Branches - 173/196 -
- - -
- 100% - Functions - 5/5 -
- - -
- 93.42% - Lines - 142/152 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -272 -273 -274 -275 -276 -277 -278 -279 -280 -281 -282 -283 -284 -285 -286 -287 -288 -289 -290 -291 -292 -293 -294 -295 -296 -297 -298 -299 -300 -301 -302 -303 -304 -305 -306 -307 -308 -309 -310 -311 -312 -313 -314 -315 -316 -317 -318 -319 -320 -321 -322 -323 -324 -325 -326 -327 -328 -329 -330 -331 -332 -333 -334 -335 -336 -337 -338 -339 -340 -341 -342 -343 -344 -345 -346 -347 -348 -349 -350 -351 -352 -353 -354 -355 -356 -357 -358 -359 -360  -  -1x -5x -5x -5x -  -  -1x -4x -4x -4x -4x -4x -  -  -  -1x -35x -  -  -  -  -  -  -  -  -  -  -  -  -35x -  -  -35x -  -  -  -  -  -  -  -  -35x -  -  -  -35x -5x -  -  -35x -4x -  -  -  -  -35x -12x -12x -12x -12x -12x -5x -7x -1x -  -  -  -35x -6x -6x -6x -6x -6x -  -  -35x -1x -1x -  -  -35x -5x -5x -5x -  -  -35x -1x -1x -  -  -  -35x -2x -2x -  -  -35x -1x -1x -  -  -35x -1x -1x -  -  -35x -1x -  -  -35x -1x -  -  -  -35x -1x -1x -  -  -  -35x -  -  -  -  -35x -5x -  -30x -  -  -  -  -  -35x -2x -  -  -35x -  -  -  -  -2x -  -  -  -  -35x -  -35x -  -  -  -  -35x -  -  -  -  -35x -5x -  -  -  -35x -35x -  -  -35x -  -26x -26x -  -5x -5x -  -4x -4x -  -  -  -  -  -35x -5x -2x -  -5x -  -  -5x -  -  -  -5x -5x -5x -  -  -  -35x -  -  -  -  -  -  -  -  -  -  -1x -  -  -  -35x -4x -1x -  -4x -  -  -4x -1x -  -4x -1x -1x -1x -  -  -  -35x -  -  -  -35x -2x -  -  -  -35x -35x -  -  -  -  -35x -9x -  -  -  -  -  -7x -7x -  -  -9x -8x -8x -  -  -  -35x -35x -  -  -35x -35x -76x -76x -64x -59x -59x -  -  -35x -9x -  -9x -5x -  -  -9x -  -9x -9x -  -  -  -  -35x -  -  -18x -  -6x -  -  -  -35x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -35x -  -  -  -  -  -3x -  -  -  -35x -  -  -  -  -35x -  -  -  -  -  -35x -1x -  -  -35x -  -  -  -  -  -  -  -  -35x -  -  -  - 
import matchLaptopClass from './matchLaptopClass'
- 
-const forceAppleSpecs = (specs, scores) => {
-  specs.processor = 'Apple M1 / M2 / M3'
-  specs.gpu = 'Apple Silicon GPU (M1/M2/M3)'
-  scores.graphics = 0
-}
- 
-const forceChromeSpecs = (specs) => {
-  specs.processor = 'Intel Celeron / MediaTek'
-  specs.ram = '4 GB o 8 GB'
-  specs.gpu = 'Gráficos integrados básicos'
-  specs.storage = '64 GB eMMC o 128 GB SSD'
-  specs.rationale.push('ChromeOS está pensado para tareas básicas con hardware modesto.')
-}
- 
- 
-const convertToSpecs = (answers) => {
-  const specs = {
-    processor: '',
-    ram: '',
-    storage: '',
-    gpu: '',
-    touchscreen: false,
-    portability: '',
-    os: '',
-    battery: '',
-    rationale: [],
-    warnings: []
-  }
- 
-  specs.budget = answers.budget
- 
- 
-  const scores = {
-    performance: 0,
-    graphics: 0,
-    multitasking: 0,
-    portability: 0,
-    battery: 0,
-    storage: 0
-  }
- 
-  const uses = answers.mainUse === 'full_use'
-    ? ['basics', 'entertainment', 'gaming', 'work_school', 'creating', 'family']
-    : answers.mainUse || []
- 
-  if (answers.system === 'mac') {
-    forceAppleSpecs(specs, scores)
-  }
- 
-  if (answers.system === 'chrome') {
-    forceChromeSpecs(specs)
-  }
- 
- 
-  // ==== 1. Scoring según uso ====
-  if (uses.includes('gaming')) {
-    scores.performance += 3.5
-    scores.graphics += 4.5
-    scores.storage += 1
-    specs.rationale.push('Para gaming se requiere GPU dedicada y CPU potente.')
-    if (answers.gamesType === 'complex') {
-      scores.graphics += 3
-    } else if (answers.gamesType === 'simple') {
-      scores.graphics += 1
-    }
-  }
- 
-  if (uses.includes('creating') || answers.photoVideo === 'pro') {
-    scores.performance += 3
-    scores.graphics += 2.5
-    scores.multitasking += 2
-    scores.storage += 2
-    specs.rationale.push('Creación de contenido profesional requiere potencia y espacio.')
-  }
- 
-  if (answers.photoVideo === 'basic') {
-    scores.graphics += 1
-    scores.storage += 1
-  }
- 
-  if (uses.includes('work_school') || answers.workload === 'heavy') {
-    scores.performance += 2.5
-    scores.multitasking += 2.5
-    specs.rationale.push('Trabajo exigente necesita buena CPU y multitarea.')
-  }
- 
-  if (uses.includes('family')) {
-    scores.multitasking += 1
-    specs.rationale.push('Uso compartido en casa requiere buena capacidad multitarea.')
-  }
- 
- 
-  if (answers.webBrowsing === 'heavy') {
-    scores.multitasking += 1.5
-    specs.rationale.push('Muchas pestañas requieren buena RAM.')
-  }
- 
-  if (answers.location === 'mobile') {
-    scores.portability += 3
-    specs.rationale.push('Necesitas algo ligero y fácil de transportar.')
-  }
- 
-  if (answers.importance === 'touch') {
-    specs.touchscreen = true
-    specs.rationale.push('Requiere pantalla táctil.')
-  }
- 
-  if (answers.importance === 'security') {
-    specs.rationale.push('Prioritás seguridad. Buscamos modelos con lector de huellas o reconocimiento facial si es posible.')
-  }
- 
-  if (answers.importance === 'portability') {
-    specs.rationale.push('Buscás un equipo liviano. Se prioriza que pese menos de 1.5 kg y tenga una pantalla de 14" o menor.')
-  }
- 
- 
-  if (answers.battery === 'yes') {
-    scores.battery += 3
-    specs.rationale.push('Priorizás batería de larga duración.')
-  }
- 
-  // ==== 2. Especificaciones recomendadas ====
-  specs.processor =
-    scores.performance >= 6 ? 'Intel i7 / Ryzen 7' :
-      scores.performance >= 4 ? 'Intel i5 / Ryzen 5' :
-        'Intel i3 / Ryzen 3 o Celeron'
- 
-  if (answers.system === 'mac') {
-    specs.ram = scores.multitasking >= 4 ? '16 GB' : '8 GB'
-  } else {
-    specs.ram =
-      scores.multitasking >= 4 ? '16 GB' :
-        scores.multitasking >= 2 ? '12 GB' :
-          '8 GB'
-  }
- 
-  if (answers.system === 'mac' && uses.includes('gaming')) {
-    specs.warnings.push('macOS no es compatible con la mayoría de juegos exigentes. Considerá Windows si es prioridad.')
-  }
- 
-  if (answers.system === 'chrome' && (
-    answers.photoVideo === 'pro' ||
-    answers.workload === 'heavy' ||
-    uses.includes('gaming'))
-  ) {
-    specs.warnings.push('ChromeOS no es ideal para tareas pesadas o software profesional. Considerá Windows o macOS.')
-  }
- 
- 
- 
-  specs.storage = scores.storage >= 2 ? '512 GB SSD' : '256 GB SSD'
- 
-  Iif (specs.storage === '256 GB SSD' && uses.includes('creating')) {
-    specs.warnings.push('256 GB puede llenarse rápido con archivos de diseño o video. Considerá 512 GB o más.')
-  }
- 
- 
-  specs.gpu =
-    scores.graphics >= 4 ? 'NVIDIA RTX 3050 o superior' :
-      scores.graphics >= 2 ? 'Intel Iris Xe / Radeon Vega' :
-        'Gráficos integrados básicos'
-  // Override para macOS: siempre Apple Silicon
-  if (answers.system === 'mac') {
-    specs.gpu = 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)'
-  }
- 
- 
-  specs.portability = scores.portability >= 3 ? 'Alta (menos de 1.5kg, 14")' : 'Media o baja'
-  specs.battery = scores.battery >= 3 ? 'Alta (8h o más)' : 'Media (4-7h)'
- 
-  // ==== 3. Sistema operativo filtrado ====
-  switch (answers.system) {
-    case 'windows':
-      specs.os = 'Windows 11'
-      break
-    case 'mac':
-      specs.os = 'macOS'
-      break
-    case 'chrome':
-      specs.os = 'ChromeOS'
-      break
-    default:
-      specs.os = 'Windows 11'
-  }
- 
-  // ==== 4. Validaciones cruzadas ====
-  if (specs.os === 'macOS') {
-    if (uses.includes('gaming')) {
-      specs.warnings.push('macOS no es ideal para juegos exigentes ni es compatible con la mayoría de títulos AAA. Recomendamos Windows.')
-    }
-    Iif (uses.includes('gaming') && answers.system === 'chrome') {
-      specs.warnings.unshift('⚠️ Elegiste "juegos exigentes" pero seleccionaste ChromeOS, que no soporta juegos avanzados. Recomendamos Windows si querés jugar títulos como GTA, FIFA o similares.')
-    }
-    Iif (specs.gpu.includes('RTX')) {
-      specs.warnings.push('Mac no usa GPUs dedicadas tipo RTX, sus chips integrados (M1/M2/M3) tienen gráficas propias.')
-      specs.gpu = 'Apple Silicon GPU (M1/M2/M3)'
-    }
-    Eif (specs.processor.includes('i7') || specs.processor.includes('Ryzen')) {
-      specs.processor = 'Apple M1 / M2 / M3'
-      specs.rationale.push('En macOS se usan procesadores Apple Silicon, no Intel/AMD tradicionales.')
-    }
-  }
- 
-  if (
-    specs.os === 'macOS' &&
-    answers.budget === 'low' &&
-    (
-      uses.includes('gaming') ||
-      uses.includes('creating') ||
-      uses.includes('work_school') ||
-      answers.workload === 'heavy' ||
-      answers.photoVideo === 'pro'
-    )
-  ) {
-    specs.warnings.unshift('⚠️ Querés hacer cosas muy exigentes en una Mac, pero con menos de $5,000 MXN. No es viable. Recomendamos subir el presupuesto o considerar una laptop con Windows.')
-  }
- 
- 
-  if (specs.os === 'ChromeOS') {
-    if (answers.workload === 'heavy') {
-      specs.warnings.push('ChromeOS no es ideal para multitarea exigente ni software pesado.')
-    }
-    Iif (answers.photoVideo === 'pro') {
-      specs.warnings.push('ChromeOS no soporta software profesional como Photoshop o Premiere. Considerá Windows o macOS.')
-    }
-    if (uses.includes('gaming') && answers.gamesType === 'complex') {
-      specs.warnings.push('ChromeOS no puede ejecutar juegos complejos como GTA o FIFA.')
-    }
-    if (specs.gpu.includes('RTX') || specs.processor.includes('i7')) {
-      specs.warnings.push('ChromeOS no usa hardware de alto rendimiento como RTX o i7. Recomendamos evitar este OS si necesitás potencia.')
-      specs.gpu = 'Gráficos integrados básicos'
-      specs.processor = 'Intel Celeron / MediaTek'
-    }
-  }
- 
-  Iif (answers.budget === 'low' && (specs.processor.includes('i5') || specs.ram === '16 GB')) {
-    specs.warnings.push('Tu presupuesto bajo podría no alcanzar para estas specs. Considerá ajustar prioridades o aumentar presupuesto.')
-  }
- 
-  if (specs.gpu.includes('RTX') && answers.budget === 'low') {
-    specs.warnings.push('Una GPU RTX requiere un presupuesto medio o alto. Podrías no encontrar algo accesible con esas specs.')
-  }
- 
-  // ==== 5. RAM recomendada vs real ====
-  const idealRam = scores.multitasking >= 4 ? '16 GB' : scores.multitasking >= 2 ? '12 GB' : '8 GB'
-  Iif (specs.ram !== idealRam) {
-    specs.warnings.push(`Idealmente deberías tener ${idealRam} de RAM para tu uso.`)
-  }
- 
-  // ==== 6. Validación de coherencia interna de specs ====
-  if (specs.gpu.includes('RTX')) {
-    if (
-      !specs.processor.includes('i5') &&
-      !specs.processor.includes('i7') &&
-      !specs.processor.includes('Ryzen 5') &&
-      !specs.processor.includes('Ryzen 7')
-    ) {
-      specs.processor = 'Intel i5 / Ryzen 5'
-      specs.rationale.push('Se ajustó el procesador porque una GPU RTX requiere al menos un i5 o Ryzen 5.')
-    }
- 
-    if (specs.ram !== '16 GB') {
-      specs.ram = '16 GB'
-      specs.rationale.push('Se ajustó la RAM porque una GPU RTX necesita al menos 16 GB para funcionar correctamente.')
-    }
-  }
- 
-  const bestModels = matchLaptopClass(specs)
-  specs.laptopClass = bestModels // 👈 ya es un array de modelos
- 
- 
-  const osFilter = specs.os.toLowerCase()
-  specs.laptopClass = bestModels.filter(m => {
-      const name = m.name.toLowerCase()
-      if (osFilter.includes('chrome') && !name.includes('chromebook')) return false
-      if (osFilter.includes('macos') && !name.includes('macbook')) return false
-      Iif (osFilter.includes('windows') && name.includes('macbook')) return false
-      return true
-    }) || []
- 
-if (specs.laptopClass.length === 0) {
-  let fallbackMsg = '⚠️ No encontramos laptops con ';
- 
-  if (specs.os === 'ChromeOS') fallbackMsg += 'ChromeOS ';
-  else if (specs.os === 'macOS') fallbackMsg += 'macOS ';
-  else EfallbackMsg += 'el sistema operativo deseado ';
- 
-  fallbackMsg += 'para tus necesidades. Mostramos opciones similares con Windows.';
- 
-  specs.warnings.push(fallbackMsg);
-  specs.laptopClass = bestModels;
-}
- 
- 
-  // Verificación si el modelo tiene menos RAM que la recomendada
-  if (
-    Array.isArray(bestModels) &&
-    specs.ram === '12 GB' &&
-    bestModels.some(m => m.specs.toLowerCase().includes('8 gb'))
-  ) {
-    specs.warnings.push('Uno de los modelos sugeridos tiene 8 GB de RAM, que puede ser justo si hacés mucha multitarea.')
-  }
- 
-  // ==== 7. Advertencia crítica por combinación inviable ====
-  Iif (
-    specs.os === 'ChromeOS' &&
-    answers.budget === 'low' &&
-    (
-      uses.includes('gaming') ||
-      uses.includes('creating') ||
-      uses.includes('work_school') ||
-      answers.workload === 'heavy' ||
-      answers.photoVideo === 'pro'
-    )
-  ) {
-    specs.warnings.unshift('⚠️ Estás solicitando tareas exigentes (edición, juegos, multitarea) en un sistema muy limitado (ChromeOS con presupuesto bajo). No es viable cumplir esos requerimientos con esta combinación. Recomendamos considerar Windows con mayor inversión.')
-  }
- 
- 
-  if (answers.budget === 'low' && (
-    scores.performance >= 4 ||
-    scores.graphics >= 2 ||
-    scores.multitasking >= 4 ||
-    uses.includes('creating')
-  )) {
-    specs.warnings.push('⚠️ Tu presupuesto bajo puede limitar el rendimiento que necesitás. Considerá ajustar prioridades o aumentar presupuesto.')
-  }
- 
-  // === VALIDACIÓN DE USO CONTRADICTORIO ===
-  const isBasicUseOnly = Array.isArray(answers.mainUse)
-    ? answers.mainUse.length === 1 && answers.mainUse[0] === 'entertainment'
-    : answers.mainUse === 'entertainment'
- 
-  const isDemandingUser =
-    answers.workload === 'heavy' ||
-    answers.photoVideo === 'pro' ||
-    scores.performance >= 4 ||
-    scores.multitasking >= 4 ||
-    scores.graphics >= 2.5
- 
-  if (isBasicUseOnly && isDemandingUser) {
-    specs.warnings.unshift('⚠️ Elegiste un uso simple como "ver videos", pero tus respuestas indican tareas exigentes como edición o multitarea pesada. Considerá ajustar tu selección inicial para mejorar la recomendación.')
-  }
- 
-  specs.flags = {
-    prioritizePortability: answers.importance === 'portability',
-    prioritizeSecurity: answers.importance === 'security',
-    prioritizeGaming: uses.includes('gaming'),
-    prioritizeScreenQuality: answers.photoVideo === 'pro'
-  }
- 
- 
- 
-  return specs
-}
- 
-export default convertToSpecs
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json deleted file mode 100644 index 2b89fca..0000000 --- a/coverage/coverage-final.json +++ /dev/null @@ -1,4 +0,0 @@ -{"E:\\Job\\ComputerSelectorHelper\\src\\utils\\convertToSpecs.jsx": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\convertToSpecs.jsx","statementMap":{"0":{"start":{"line":3,"column":24},"end":{"line":7,"column":null}},"1":{"start":{"line":4,"column":2},"end":{"line":4,"column":null}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":null}},"3":{"start":{"line":6,"column":2},"end":{"line":6,"column":null}},"4":{"start":{"line":9,"column":25},"end":{"line":15,"column":null}},"5":{"start":{"line":10,"column":2},"end":{"line":10,"column":null}},"6":{"start":{"line":11,"column":2},"end":{"line":11,"column":null}},"7":{"start":{"line":12,"column":2},"end":{"line":12,"column":null}},"8":{"start":{"line":13,"column":2},"end":{"line":13,"column":null}},"9":{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},"10":{"start":{"line":18,"column":23},"end":{"line":357,"column":null}},"11":{"start":{"line":19,"column":16},"end":{"line":30,"column":null}},"12":{"start":{"line":32,"column":2},"end":{"line":32,"column":null}},"13":{"start":{"line":35,"column":17},"end":{"line":42,"column":null}},"14":{"start":{"line":44,"column":15},"end":{"line":46,"column":27}},"15":{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},"16":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"17":{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},"18":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"19":{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},"20":{"start":{"line":59,"column":4},"end":{"line":59,"column":null}},"21":{"start":{"line":60,"column":4},"end":{"line":60,"column":null}},"22":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"23":{"start":{"line":62,"column":4},"end":{"line":62,"column":null}},"24":{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},"25":{"start":{"line":64,"column":6},"end":{"line":64,"column":null}},"26":{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},"27":{"start":{"line":66,"column":6},"end":{"line":66,"column":null}},"28":{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},"29":{"start":{"line":71,"column":4},"end":{"line":71,"column":null}},"30":{"start":{"line":72,"column":4},"end":{"line":72,"column":null}},"31":{"start":{"line":73,"column":4},"end":{"line":73,"column":null}},"32":{"start":{"line":74,"column":4},"end":{"line":74,"column":null}},"33":{"start":{"line":75,"column":4},"end":{"line":75,"column":null}},"34":{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},"35":{"start":{"line":79,"column":4},"end":{"line":79,"column":null}},"36":{"start":{"line":80,"column":4},"end":{"line":80,"column":null}},"37":{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},"38":{"start":{"line":84,"column":4},"end":{"line":84,"column":null}},"39":{"start":{"line":85,"column":4},"end":{"line":85,"column":null}},"40":{"start":{"line":86,"column":4},"end":{"line":86,"column":null}},"41":{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},"42":{"start":{"line":90,"column":4},"end":{"line":90,"column":null}},"43":{"start":{"line":91,"column":4},"end":{"line":91,"column":null}},"44":{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},"45":{"start":{"line":96,"column":4},"end":{"line":96,"column":null}},"46":{"start":{"line":97,"column":4},"end":{"line":97,"column":null}},"47":{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},"48":{"start":{"line":101,"column":4},"end":{"line":101,"column":null}},"49":{"start":{"line":102,"column":4},"end":{"line":102,"column":null}},"50":{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},"51":{"start":{"line":106,"column":4},"end":{"line":106,"column":null}},"52":{"start":{"line":107,"column":4},"end":{"line":107,"column":null}},"53":{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},"54":{"start":{"line":111,"column":4},"end":{"line":111,"column":null}},"55":{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},"56":{"start":{"line":115,"column":4},"end":{"line":115,"column":null}},"57":{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},"58":{"start":{"line":120,"column":4},"end":{"line":120,"column":null}},"59":{"start":{"line":121,"column":4},"end":{"line":121,"column":null}},"60":{"start":{"line":125,"column":2},"end":{"line":128,"column":null}},"61":{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},"62":{"start":{"line":131,"column":4},"end":{"line":131,"column":null}},"63":{"start":{"line":133,"column":4},"end":{"line":136,"column":null}},"64":{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},"65":{"start":{"line":140,"column":4},"end":{"line":140,"column":null}},"66":{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},"67":{"start":{"line":148,"column":4},"end":{"line":148,"column":null}},"68":{"start":{"line":153,"column":2},"end":{"line":153,"column":null}},"69":{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},"70":{"start":{"line":156,"column":4},"end":{"line":156,"column":null}},"71":{"start":{"line":160,"column":2},"end":{"line":163,"column":null}},"72":{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},"73":{"start":{"line":166,"column":4},"end":{"line":166,"column":null}},"74":{"start":{"line":170,"column":2},"end":{"line":170,"column":null}},"75":{"start":{"line":171,"column":2},"end":{"line":171,"column":null}},"76":{"start":{"line":174,"column":2},"end":{"line":186,"column":null}},"77":{"start":{"line":176,"column":6},"end":{"line":176,"column":null}},"78":{"start":{"line":177,"column":6},"end":{"line":177,"column":null}},"79":{"start":{"line":179,"column":6},"end":{"line":179,"column":null}},"80":{"start":{"line":180,"column":6},"end":{"line":180,"column":null}},"81":{"start":{"line":182,"column":6},"end":{"line":182,"column":null}},"82":{"start":{"line":183,"column":6},"end":{"line":183,"column":null}},"83":{"start":{"line":185,"column":6},"end":{"line":185,"column":null}},"84":{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},"85":{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},"86":{"start":{"line":191,"column":6},"end":{"line":191,"column":null}},"87":{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},"88":{"start":{"line":194,"column":6},"end":{"line":194,"column":null}},"89":{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},"90":{"start":{"line":197,"column":6},"end":{"line":197,"column":null}},"91":{"start":{"line":198,"column":6},"end":{"line":198,"column":null}},"92":{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},"93":{"start":{"line":201,"column":6},"end":{"line":201,"column":null}},"94":{"start":{"line":202,"column":6},"end":{"line":202,"column":null}},"95":{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},"96":{"start":{"line":217,"column":4},"end":{"line":217,"column":null}},"97":{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},"98":{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},"99":{"start":{"line":223,"column":6},"end":{"line":223,"column":null}},"100":{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},"101":{"start":{"line":226,"column":6},"end":{"line":226,"column":null}},"102":{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},"103":{"start":{"line":229,"column":6},"end":{"line":229,"column":null}},"104":{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},"105":{"start":{"line":232,"column":6},"end":{"line":232,"column":null}},"106":{"start":{"line":233,"column":6},"end":{"line":233,"column":null}},"107":{"start":{"line":234,"column":6},"end":{"line":234,"column":null}},"108":{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},"109":{"start":{"line":239,"column":4},"end":{"line":239,"column":null}},"110":{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},"111":{"start":{"line":243,"column":4},"end":{"line":243,"column":null}},"112":{"start":{"line":247,"column":19},"end":{"line":247,"column":null}},"113":{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},"114":{"start":{"line":249,"column":4},"end":{"line":249,"column":null}},"115":{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},"116":{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},"117":{"start":{"line":260,"column":6},"end":{"line":260,"column":null}},"118":{"start":{"line":261,"column":6},"end":{"line":261,"column":null}},"119":{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},"120":{"start":{"line":265,"column":6},"end":{"line":265,"column":null}},"121":{"start":{"line":266,"column":6},"end":{"line":266,"column":null}},"122":{"start":{"line":270,"column":8},"end":{"line":270,"column":null}},"123":{"start":{"line":271,"column":2},"end":{"line":271,"column":null}},"124":{"start":{"line":274,"column":19},"end":{"line":274,"column":null}},"125":{"start":{"line":275,"column":2},"end":{"line":281,"column":null}},"126":{"start":{"line":276,"column":19},"end":{"line":276,"column":null}},"127":{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},"128":{"start":{"line":277,"column":71},"end":{"line":277,"column":null}},"129":{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},"130":{"start":{"line":278,"column":67},"end":{"line":278,"column":null}},"131":{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},"132":{"start":{"line":279,"column":68},"end":{"line":279,"column":null}},"133":{"start":{"line":280,"column":6},"end":{"line":280,"column":null}},"134":{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},"135":{"start":{"line":284,"column":20},"end":{"line":284,"column":null}},"136":{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},"137":{"start":{"line":286,"column":31},"end":{"line":286,"column":null}},"138":{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},"139":{"start":{"line":287,"column":33},"end":{"line":287,"column":null}},"140":{"start":{"line":288,"column":7},"end":{"line":288,"column":null}},"141":{"start":{"line":290,"column":2},"end":{"line":290,"column":null}},"142":{"start":{"line":292,"column":2},"end":{"line":292,"column":null}},"143":{"start":{"line":293,"column":2},"end":{"line":293,"column":null}},"144":{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},"145":{"start":{"line":301,"column":25},"end":{"line":301,"column":null}},"146":{"start":{"line":303,"column":4},"end":{"line":303,"column":null}},"147":{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},"148":{"start":{"line":318,"column":4},"end":{"line":318,"column":null}},"149":{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},"150":{"start":{"line":328,"column":4},"end":{"line":328,"column":null}},"151":{"start":{"line":332,"column":25},"end":{"line":334,"column":null}},"152":{"start":{"line":337,"column":4},"end":{"line":341,"column":null}},"153":{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},"154":{"start":{"line":344,"column":4},"end":{"line":344,"column":null}},"155":{"start":{"line":347,"column":2},"end":{"line":352,"column":null}},"156":{"start":{"line":356,"column":2},"end":{"line":356,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":24},"end":{"line":3,"column":25}},"loc":{"start":{"line":3,"column":32},"end":{"line":7,"column":null}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":9,"column":25},"end":{"line":9,"column":26}},"loc":{"start":{"line":9,"column":26},"end":{"line":15,"column":null}},"line":9},"2":{"name":"(anonymous_2)","decl":{"start":{"line":18,"column":23},"end":{"line":18,"column":24}},"loc":{"start":{"line":18,"column":24},"end":{"line":357,"column":null}},"line":18},"3":{"name":"(anonymous_3)","decl":{"start":{"line":275,"column":40},"end":{"line":275,"column":null}},"loc":{"start":{"line":275,"column":40},"end":{"line":281,"column":10}},"line":275},"4":{"name":"(anonymous_4)","decl":{"start":{"line":301,"column":20},"end":{"line":301,"column":25}},"loc":{"start":{"line":301,"column":25},"end":{"line":301,"column":null}},"line":301}},"branchMap":{"0":{"loc":{"start":{"line":44,"column":15},"end":{"line":46,"column":27}},"type":"cond-expr","locations":[{"start":{"line":45,"column":6},"end":{"line":45,"column":null}},{"start":{"line":46,"column":6},"end":{"line":46,"column":27}}],"line":44},"1":{"loc":{"start":{"line":46,"column":6},"end":{"line":46,"column":27}},"type":"binary-expr","locations":[{"start":{"line":46,"column":6},"end":{"line":46,"column":21}},{"start":{"line":46,"column":25},"end":{"line":46,"column":27}}],"line":46},"2":{"loc":{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},"type":"if","locations":[{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},{"start":{},"end":{}}],"line":48},"3":{"loc":{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},"type":"if","locations":[{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},{"start":{},"end":{}}],"line":52},"4":{"loc":{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},"type":"if","locations":[{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},{"start":{},"end":{}}],"line":58},"5":{"loc":{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},{"start":{"line":65,"column":11},"end":{"line":67,"column":null}}],"line":63},"6":{"loc":{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},{"start":{},"end":{}}],"line":65},"7":{"loc":{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},"type":"if","locations":[{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},{"start":{},"end":{}}],"line":70},"8":{"loc":{"start":{"line":70,"column":6},"end":{"line":70,"column":65}},"type":"binary-expr","locations":[{"start":{"line":70,"column":6},"end":{"line":70,"column":35}},{"start":{"line":70,"column":35},"end":{"line":70,"column":65}}],"line":70},"9":{"loc":{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},{"start":{},"end":{}}],"line":78},"10":{"loc":{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},"type":"if","locations":[{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},{"start":{},"end":{}}],"line":83},"11":{"loc":{"start":{"line":83,"column":6},"end":{"line":83,"column":68}},"type":"binary-expr","locations":[{"start":{"line":83,"column":6},"end":{"line":83,"column":38}},{"start":{"line":83,"column":38},"end":{"line":83,"column":68}}],"line":83},"12":{"loc":{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},"type":"if","locations":[{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},{"start":{},"end":{}}],"line":89},"13":{"loc":{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},"type":"if","locations":[{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},{"start":{},"end":{}}],"line":95},"14":{"loc":{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},"type":"if","locations":[{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},{"start":{},"end":{}}],"line":100},"15":{"loc":{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},"type":"if","locations":[{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},{"start":{},"end":{}}],"line":105},"16":{"loc":{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},"type":"if","locations":[{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},{"start":{},"end":{}}],"line":110},"17":{"loc":{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},"type":"if","locations":[{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},{"start":{},"end":{}}],"line":114},"18":{"loc":{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},"type":"if","locations":[{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},{"start":{},"end":{}}],"line":119},"19":{"loc":{"start":{"line":126,"column":4},"end":{"line":128,"column":null}},"type":"cond-expr","locations":[{"start":{"line":126,"column":30},"end":{"line":126,"column":null}},{"start":{"line":127,"column":6},"end":{"line":128,"column":null}}],"line":126},"20":{"loc":{"start":{"line":127,"column":6},"end":{"line":128,"column":null}},"type":"cond-expr","locations":[{"start":{"line":127,"column":32},"end":{"line":127,"column":null}},{"start":{"line":128,"column":8},"end":{"line":128,"column":null}}],"line":127},"21":{"loc":{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},"type":"if","locations":[{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},{"start":{"line":132,"column":9},"end":{"line":137,"column":null}}],"line":130},"22":{"loc":{"start":{"line":131,"column":16},"end":{"line":131,"column":null}},"type":"cond-expr","locations":[{"start":{"line":131,"column":43},"end":{"line":131,"column":53}},{"start":{"line":131,"column":53},"end":{"line":131,"column":null}}],"line":131},"23":{"loc":{"start":{"line":134,"column":6},"end":{"line":136,"column":null}},"type":"cond-expr","locations":[{"start":{"line":134,"column":33},"end":{"line":134,"column":null}},{"start":{"line":135,"column":8},"end":{"line":136,"column":null}}],"line":134},"24":{"loc":{"start":{"line":135,"column":8},"end":{"line":136,"column":null}},"type":"cond-expr","locations":[{"start":{"line":135,"column":35},"end":{"line":135,"column":null}},{"start":{"line":136,"column":10},"end":{"line":136,"column":null}}],"line":135},"25":{"loc":{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},"type":"if","locations":[{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},{"start":{},"end":{}}],"line":139},"26":{"loc":{"start":{"line":139,"column":6},"end":{"line":139,"column":59}},"type":"binary-expr","locations":[{"start":{"line":139,"column":6},"end":{"line":139,"column":34}},{"start":{"line":139,"column":34},"end":{"line":139,"column":59}}],"line":139},"27":{"loc":{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},"type":"if","locations":[{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},{"start":{},"end":{}}],"line":143},"28":{"loc":{"start":{"line":143,"column":6},"end":{"line":146,"column":null}},"type":"binary-expr","locations":[{"start":{"line":143,"column":6},"end":{"line":143,"column":null}},{"start":{"line":144,"column":4},"end":{"line":144,"column":null}},{"start":{"line":145,"column":4},"end":{"line":145,"column":null}},{"start":{"line":146,"column":4},"end":{"line":146,"column":26}}],"line":143},"29":{"loc":{"start":{"line":153,"column":18},"end":{"line":153,"column":null}},"type":"cond-expr","locations":[{"start":{"line":153,"column":40},"end":{"line":153,"column":55}},{"start":{"line":153,"column":55},"end":{"line":153,"column":null}}],"line":153},"30":{"loc":{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},"type":"if","locations":[{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},{"start":{},"end":{}}],"line":155},"31":{"loc":{"start":{"line":155,"column":6},"end":{"line":155,"column":67}},"type":"binary-expr","locations":[{"start":{"line":155,"column":6},"end":{"line":155,"column":40}},{"start":{"line":155,"column":40},"end":{"line":155,"column":67}}],"line":155},"32":{"loc":{"start":{"line":161,"column":4},"end":{"line":163,"column":null}},"type":"cond-expr","locations":[{"start":{"line":161,"column":27},"end":{"line":161,"column":null}},{"start":{"line":162,"column":6},"end":{"line":163,"column":null}}],"line":161},"33":{"loc":{"start":{"line":162,"column":6},"end":{"line":163,"column":null}},"type":"cond-expr","locations":[{"start":{"line":162,"column":29},"end":{"line":162,"column":null}},{"start":{"line":163,"column":8},"end":{"line":163,"column":null}}],"line":162},"34":{"loc":{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},"type":"if","locations":[{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},{"start":{},"end":{}}],"line":165},"35":{"loc":{"start":{"line":170,"column":22},"end":{"line":170,"column":null}},"type":"cond-expr","locations":[{"start":{"line":170,"column":48},"end":{"line":170,"column":79}},{"start":{"line":170,"column":79},"end":{"line":170,"column":null}}],"line":170},"36":{"loc":{"start":{"line":171,"column":18},"end":{"line":171,"column":null}},"type":"cond-expr","locations":[{"start":{"line":171,"column":40},"end":{"line":171,"column":60}},{"start":{"line":171,"column":60},"end":{"line":171,"column":null}}],"line":171},"37":{"loc":{"start":{"line":174,"column":2},"end":{"line":186,"column":null}},"type":"switch","locations":[{"start":{"line":175,"column":4},"end":{"line":177,"column":null}},{"start":{"line":178,"column":4},"end":{"line":180,"column":null}},{"start":{"line":181,"column":4},"end":{"line":183,"column":null}},{"start":{"line":184,"column":4},"end":{"line":185,"column":null}}],"line":174},"38":{"loc":{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},"type":"if","locations":[{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},{"start":{},"end":{}}],"line":189},"39":{"loc":{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},"type":"if","locations":[{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},{"start":{},"end":{}}],"line":190},"40":{"loc":{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},"type":"if","locations":[{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},{"start":{},"end":{}}],"line":193},"41":{"loc":{"start":{"line":193,"column":8},"end":{"line":193,"column":64}},"type":"binary-expr","locations":[{"start":{"line":193,"column":8},"end":{"line":193,"column":35}},{"start":{"line":193,"column":35},"end":{"line":193,"column":64}}],"line":193},"42":{"loc":{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},"type":"if","locations":[{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},{"start":{},"end":{}}],"line":196},"43":{"loc":{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},"type":"if","locations":[{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},{"start":{},"end":{}}],"line":200},"44":{"loc":{"start":{"line":200,"column":8},"end":{"line":200,"column":77}},"type":"binary-expr","locations":[{"start":{"line":200,"column":8},"end":{"line":200,"column":42}},{"start":{"line":200,"column":42},"end":{"line":200,"column":77}}],"line":200},"45":{"loc":{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},"type":"if","locations":[{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},{"start":{},"end":{}}],"line":206},"46":{"loc":{"start":{"line":207,"column":4},"end":{"line":214,"column":null}},"type":"binary-expr","locations":[{"start":{"line":207,"column":4},"end":{"line":207,"column":null}},{"start":{"line":208,"column":4},"end":{"line":208,"column":null}},{"start":{"line":210,"column":6},"end":{"line":210,"column":null}},{"start":{"line":211,"column":6},"end":{"line":211,"column":null}},{"start":{"line":212,"column":6},"end":{"line":212,"column":null}},{"start":{"line":213,"column":6},"end":{"line":213,"column":null}},{"start":{"line":214,"column":6},"end":{"line":214,"column":33}}],"line":207},"47":{"loc":{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},"type":"if","locations":[{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},{"start":{},"end":{}}],"line":221},"48":{"loc":{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},"type":"if","locations":[{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},{"start":{},"end":{}}],"line":222},"49":{"loc":{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},"type":"if","locations":[{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},{"start":{},"end":{}}],"line":225},"50":{"loc":{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},"type":"if","locations":[{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},{"start":{},"end":{}}],"line":228},"51":{"loc":{"start":{"line":228,"column":8},"end":{"line":228,"column":68}},"type":"binary-expr","locations":[{"start":{"line":228,"column":8},"end":{"line":228,"column":35}},{"start":{"line":228,"column":35},"end":{"line":228,"column":68}}],"line":228},"52":{"loc":{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},"type":"if","locations":[{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},{"start":{},"end":{}}],"line":231},"53":{"loc":{"start":{"line":231,"column":8},"end":{"line":231,"column":69}},"type":"binary-expr","locations":[{"start":{"line":231,"column":8},"end":{"line":231,"column":37}},{"start":{"line":231,"column":37},"end":{"line":231,"column":69}}],"line":231},"54":{"loc":{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},"type":"if","locations":[{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},{"start":{},"end":{}}],"line":238},"55":{"loc":{"start":{"line":238,"column":6},"end":{"line":238,"column":93}},"type":"binary-expr","locations":[{"start":{"line":238,"column":6},"end":{"line":238,"column":35}},{"start":{"line":238,"column":35},"end":{"line":238,"column":69}},{"start":{"line":238,"column":69},"end":{"line":238,"column":89}}],"line":238},"56":{"loc":{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},"type":"if","locations":[{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},{"start":{},"end":{}}],"line":242},"57":{"loc":{"start":{"line":242,"column":6},"end":{"line":242,"column":61}},"type":"binary-expr","locations":[{"start":{"line":242,"column":6},"end":{"line":242,"column":35}},{"start":{"line":242,"column":35},"end":{"line":242,"column":61}}],"line":242},"58":{"loc":{"start":{"line":247,"column":19},"end":{"line":247,"column":null}},"type":"cond-expr","locations":[{"start":{"line":247,"column":46},"end":{"line":247,"column":56}},{"start":{"line":247,"column":56},"end":{"line":247,"column":null}}],"line":247},"59":{"loc":{"start":{"line":247,"column":56},"end":{"line":247,"column":null}},"type":"cond-expr","locations":[{"start":{"line":247,"column":83},"end":{"line":247,"column":93}},{"start":{"line":247,"column":93},"end":{"line":247,"column":null}}],"line":247},"60":{"loc":{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},"type":"if","locations":[{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},{"start":{},"end":{}}],"line":248},"61":{"loc":{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},"type":"if","locations":[{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},{"start":{},"end":{}}],"line":253},"62":{"loc":{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},"type":"if","locations":[{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},{"start":{},"end":{}}],"line":254},"63":{"loc":{"start":{"line":255,"column":6},"end":{"line":258,"column":null}},"type":"binary-expr","locations":[{"start":{"line":255,"column":6},"end":{"line":255,"column":null}},{"start":{"line":256,"column":6},"end":{"line":256,"column":null}},{"start":{"line":257,"column":6},"end":{"line":257,"column":null}},{"start":{"line":258,"column":6},"end":{"line":258,"column":null}}],"line":255},"64":{"loc":{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},"type":"if","locations":[{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},{"start":{},"end":{}}],"line":264},"65":{"loc":{"start":{"line":275,"column":22},"end":{"line":281,"column":12}},"type":"binary-expr","locations":[{"start":{"line":275,"column":22},"end":{"line":281,"column":10}},{"start":{"line":281,"column":10},"end":{"line":281,"column":12}}],"line":275},"66":{"loc":{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},"type":"if","locations":[{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},{"start":{},"end":{}}],"line":277},"67":{"loc":{"start":{"line":277,"column":10},"end":{"line":277,"column":71}},"type":"binary-expr","locations":[{"start":{"line":277,"column":10},"end":{"line":277,"column":41}},{"start":{"line":277,"column":41},"end":{"line":277,"column":71}}],"line":277},"68":{"loc":{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},"type":"if","locations":[{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},{"start":{},"end":{}}],"line":278},"69":{"loc":{"start":{"line":278,"column":10},"end":{"line":278,"column":67}},"type":"binary-expr","locations":[{"start":{"line":278,"column":10},"end":{"line":278,"column":40}},{"start":{"line":278,"column":40},"end":{"line":278,"column":67}}],"line":278},"70":{"loc":{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},"type":"if","locations":[{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},{"start":{},"end":{}}],"line":279},"71":{"loc":{"start":{"line":279,"column":10},"end":{"line":279,"column":68}},"type":"binary-expr","locations":[{"start":{"line":279,"column":10},"end":{"line":279,"column":42}},{"start":{"line":279,"column":42},"end":{"line":279,"column":68}}],"line":279},"72":{"loc":{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},"type":"if","locations":[{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},{"start":{},"end":{}}],"line":283},"73":{"loc":{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},"type":"if","locations":[{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},{"start":{"line":287,"column":7},"end":{"line":288,"column":null}}],"line":286},"74":{"loc":{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},"type":"if","locations":[{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},{"start":{"line":288,"column":7},"end":{"line":288,"column":null}}],"line":287},"75":{"loc":{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},"type":"if","locations":[{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},{"start":{},"end":{}}],"line":298},"76":{"loc":{"start":{"line":299,"column":4},"end":{"line":301,"column":null}},"type":"binary-expr","locations":[{"start":{"line":299,"column":4},"end":{"line":299,"column":null}},{"start":{"line":300,"column":4},"end":{"line":300,"column":null}},{"start":{"line":301,"column":4},"end":{"line":301,"column":null}}],"line":299},"77":{"loc":{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},"type":"if","locations":[{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},{"start":{},"end":{}}],"line":307},"78":{"loc":{"start":{"line":308,"column":4},"end":{"line":315,"column":null}},"type":"binary-expr","locations":[{"start":{"line":308,"column":4},"end":{"line":308,"column":null}},{"start":{"line":309,"column":4},"end":{"line":309,"column":null}},{"start":{"line":311,"column":6},"end":{"line":311,"column":null}},{"start":{"line":312,"column":6},"end":{"line":312,"column":null}},{"start":{"line":313,"column":6},"end":{"line":313,"column":null}},{"start":{"line":314,"column":6},"end":{"line":314,"column":null}},{"start":{"line":315,"column":6},"end":{"line":315,"column":33}}],"line":308},"79":{"loc":{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},"type":"if","locations":[{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},{"start":{},"end":{}}],"line":322},"80":{"loc":{"start":{"line":322,"column":6},"end":{"line":326,"column":null}},"type":"binary-expr","locations":[{"start":{"line":322,"column":6},"end":{"line":322,"column":null}},{"start":{"line":323,"column":4},"end":{"line":323,"column":null}},{"start":{"line":324,"column":4},"end":{"line":324,"column":null}},{"start":{"line":325,"column":4},"end":{"line":325,"column":null}},{"start":{"line":326,"column":4},"end":{"line":326,"column":28}}],"line":322},"81":{"loc":{"start":{"line":332,"column":25},"end":{"line":334,"column":null}},"type":"cond-expr","locations":[{"start":{"line":333,"column":6},"end":{"line":333,"column":null}},{"start":{"line":334,"column":6},"end":{"line":334,"column":null}}],"line":332},"82":{"loc":{"start":{"line":333,"column":6},"end":{"line":333,"column":null}},"type":"binary-expr","locations":[{"start":{"line":333,"column":6},"end":{"line":333,"column":38}},{"start":{"line":333,"column":38},"end":{"line":333,"column":null}}],"line":333},"83":{"loc":{"start":{"line":337,"column":4},"end":{"line":341,"column":null}},"type":"binary-expr","locations":[{"start":{"line":337,"column":4},"end":{"line":337,"column":null}},{"start":{"line":338,"column":4},"end":{"line":338,"column":null}},{"start":{"line":339,"column":4},"end":{"line":339,"column":null}},{"start":{"line":340,"column":4},"end":{"line":340,"column":null}},{"start":{"line":341,"column":4},"end":{"line":341,"column":null}}],"line":337},"84":{"loc":{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},"type":"if","locations":[{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},{"start":{},"end":{}}],"line":343},"85":{"loc":{"start":{"line":343,"column":6},"end":{"line":343,"column":41}},"type":"binary-expr","locations":[{"start":{"line":343,"column":6},"end":{"line":343,"column":24}},{"start":{"line":343,"column":24},"end":{"line":343,"column":41}}],"line":343}},"s":{"0":1,"1":5,"2":5,"3":5,"4":1,"5":4,"6":4,"7":4,"8":4,"9":4,"10":1,"11":35,"12":35,"13":35,"14":35,"15":35,"16":5,"17":35,"18":4,"19":35,"20":12,"21":12,"22":12,"23":12,"24":12,"25":5,"26":7,"27":1,"28":35,"29":6,"30":6,"31":6,"32":6,"33":6,"34":35,"35":1,"36":1,"37":35,"38":5,"39":5,"40":5,"41":35,"42":1,"43":1,"44":35,"45":2,"46":2,"47":35,"48":1,"49":1,"50":35,"51":1,"52":1,"53":35,"54":1,"55":35,"56":1,"57":35,"58":1,"59":1,"60":35,"61":35,"62":5,"63":30,"64":35,"65":2,"66":35,"67":2,"68":35,"69":35,"70":0,"71":35,"72":35,"73":5,"74":35,"75":35,"76":35,"77":26,"78":26,"79":5,"80":5,"81":4,"82":4,"83":0,"84":35,"85":5,"86":2,"87":5,"88":0,"89":5,"90":0,"91":0,"92":5,"93":5,"94":5,"95":35,"96":1,"97":35,"98":4,"99":1,"100":4,"101":0,"102":4,"103":1,"104":4,"105":1,"106":1,"107":1,"108":35,"109":0,"110":35,"111":2,"112":35,"113":35,"114":0,"115":35,"116":9,"117":7,"118":7,"119":9,"120":8,"121":8,"122":35,"123":35,"124":35,"125":35,"126":76,"127":76,"128":12,"129":64,"130":5,"131":59,"132":0,"133":59,"134":35,"135":9,"136":9,"137":4,"138":5,"139":5,"140":0,"141":9,"142":9,"143":9,"144":35,"145":18,"146":6,"147":35,"148":0,"149":35,"150":3,"151":35,"152":35,"153":35,"154":1,"155":35,"156":35},"f":{"0":5,"1":4,"2":35,"3":76,"4":18},"b":{"0":[1,34],"1":[34,0],"2":[5,30],"3":[4,31],"4":[12,23],"5":[5,7],"6":[1,6],"7":[6,29],"8":[35,32],"9":[1,34],"10":[5,30],"11":[35,32],"12":[1,34],"13":[2,33],"14":[1,34],"15":[1,34],"16":[1,34],"17":[1,34],"18":[1,34],"19":[2,33],"20":[1,32],"21":[5,30],"22":[0,5],"23":[2,28],"24":[7,21],"25":[2,33],"26":[35,5],"27":[2,33],"28":[35,4,4,3],"29":[6,29],"30":[0,35],"31":[35,29],"32":[12,23],"33":[4,19],"34":[5,30],"35":[1,34],"36":[1,34],"37":[26,5,4,0],"38":[5,30],"39":[2,3],"40":[0,5],"41":[5,2],"42":[0,5],"43":[5,0],"44":[5,5],"45":[1,34],"46":[35,5,1,0,0,0,0],"47":[4,31],"48":[1,3],"49":[0,4],"50":[1,3],"51":[4,1],"52":[1,3],"53":[4,3],"54":[0,35],"55":[35,4,4],"56":[2,33],"57":[35,9],"58":[2,33],"59":[7,26],"60":[0,35],"61":[9,26],"62":[7,2],"63":[9,9,7,7],"64":[8,1],"65":[35,0],"66":[12,64],"67":[76,12],"68":[5,59],"69":[64,5],"70":[0,59],"71":[59,59],"72":[9,26],"73":[4,5],"74":[5,0],"75":[6,29],"76":[35,35,6],"77":[0,35],"78":[35,4,0,0,0,0,0],"79":[3,32],"80":[35,4,4,1,1],"81":[34,1],"82":[34,32],"83":[35,31,29,27,27],"84":[1,34],"85":[35,1]},"meta":{"lastBranch":86,"lastFunction":5,"lastStatement":157,"seen":{"s:3:24:7:Infinity":0,"f:3:24:3:25":0,"s:4:2:4:Infinity":1,"s:5:2:5:Infinity":2,"s:6:2:6:Infinity":3,"s:9:25:15:Infinity":4,"f:9:25:9:26":1,"s:10:2:10:Infinity":5,"s:11:2:11:Infinity":6,"s:12:2:12:Infinity":7,"s:13:2:13:Infinity":8,"s:14:2:14:Infinity":9,"s:18:23:357:Infinity":10,"f:18:23:18:24":2,"s:19:16:30:Infinity":11,"s:32:2:32:Infinity":12,"s:35:17:42:Infinity":13,"s:44:15:46:27":14,"b:45:6:45:Infinity:46:6:46:27":0,"b:46:6:46:21:46:25:46:27":1,"b:48:2:50:Infinity:undefined:undefined:undefined:undefined":2,"s:48:2:50:Infinity":15,"s:49:4:49:Infinity":16,"b:52:2:54:Infinity:undefined:undefined:undefined:undefined":3,"s:52:2:54:Infinity":17,"s:53:4:53:Infinity":18,"b:58:2:68:Infinity:undefined:undefined:undefined:undefined":4,"s:58:2:68:Infinity":19,"s:59:4:59:Infinity":20,"s:60:4:60:Infinity":21,"s:61:4:61:Infinity":22,"s:62:4:62:Infinity":23,"b:63:4:67:Infinity:65:11:67:Infinity":5,"s:63:4:67:Infinity":24,"s:64:6:64:Infinity":25,"b:65:11:67:Infinity:undefined:undefined:undefined:undefined":6,"s:65:11:67:Infinity":26,"s:66:6:66:Infinity":27,"b:70:2:76:Infinity:undefined:undefined:undefined:undefined":7,"s:70:2:76:Infinity":28,"b:70:6:70:35:70:35:70:65":8,"s:71:4:71:Infinity":29,"s:72:4:72:Infinity":30,"s:73:4:73:Infinity":31,"s:74:4:74:Infinity":32,"s:75:4:75:Infinity":33,"b:78:2:81:Infinity:undefined:undefined:undefined:undefined":9,"s:78:2:81:Infinity":34,"s:79:4:79:Infinity":35,"s:80:4:80:Infinity":36,"b:83:2:87:Infinity:undefined:undefined:undefined:undefined":10,"s:83:2:87:Infinity":37,"b:83:6:83:38:83:38:83:68":11,"s:84:4:84:Infinity":38,"s:85:4:85:Infinity":39,"s:86:4:86:Infinity":40,"b:89:2:92:Infinity:undefined:undefined:undefined:undefined":12,"s:89:2:92:Infinity":41,"s:90:4:90:Infinity":42,"s:91:4:91:Infinity":43,"b:95:2:98:Infinity:undefined:undefined:undefined:undefined":13,"s:95:2:98:Infinity":44,"s:96:4:96:Infinity":45,"s:97:4:97:Infinity":46,"b:100:2:103:Infinity:undefined:undefined:undefined:undefined":14,"s:100:2:103:Infinity":47,"s:101:4:101:Infinity":48,"s:102:4:102:Infinity":49,"b:105:2:108:Infinity:undefined:undefined:undefined:undefined":15,"s:105:2:108:Infinity":50,"s:106:4:106:Infinity":51,"s:107:4:107:Infinity":52,"b:110:2:112:Infinity:undefined:undefined:undefined:undefined":16,"s:110:2:112:Infinity":53,"s:111:4:111:Infinity":54,"b:114:2:116:Infinity:undefined:undefined:undefined:undefined":17,"s:114:2:116:Infinity":55,"s:115:4:115:Infinity":56,"b:119:2:122:Infinity:undefined:undefined:undefined:undefined":18,"s:119:2:122:Infinity":57,"s:120:4:120:Infinity":58,"s:121:4:121:Infinity":59,"s:125:2:128:Infinity":60,"b:126:30:126:Infinity:127:6:128:Infinity":19,"b:127:32:127:Infinity:128:8:128:Infinity":20,"b:130:2:137:Infinity:132:9:137:Infinity":21,"s:130:2:137:Infinity":61,"s:131:4:131:Infinity":62,"b:131:43:131:53:131:53:131:Infinity":22,"s:133:4:136:Infinity":63,"b:134:33:134:Infinity:135:8:136:Infinity":23,"b:135:35:135:Infinity:136:10:136:Infinity":24,"b:139:2:141:Infinity:undefined:undefined:undefined:undefined":25,"s:139:2:141:Infinity":64,"b:139:6:139:34:139:34:139:59":26,"s:140:4:140:Infinity":65,"b:143:2:149:Infinity:undefined:undefined:undefined:undefined":27,"s:143:2:149:Infinity":66,"b:143:6:143:Infinity:144:4:144:Infinity:145:4:145:Infinity:146:4:146:26":28,"s:148:4:148:Infinity":67,"s:153:2:153:Infinity":68,"b:153:40:153:55:153:55:153:Infinity":29,"b:155:2:157:Infinity:undefined:undefined:undefined:undefined":30,"s:155:2:157:Infinity":69,"b:155:6:155:40:155:40:155:67":31,"s:156:4:156:Infinity":70,"s:160:2:163:Infinity":71,"b:161:27:161:Infinity:162:6:163:Infinity":32,"b:162:29:162:Infinity:163:8:163:Infinity":33,"b:165:2:167:Infinity:undefined:undefined:undefined:undefined":34,"s:165:2:167:Infinity":72,"s:166:4:166:Infinity":73,"s:170:2:170:Infinity":74,"b:170:48:170:79:170:79:170:Infinity":35,"s:171:2:171:Infinity":75,"b:171:40:171:60:171:60:171:Infinity":36,"b:175:4:177:Infinity:178:4:180:Infinity:181:4:183:Infinity:184:4:185:Infinity":37,"s:174:2:186:Infinity":76,"s:176:6:176:Infinity":77,"s:177:6:177:Infinity":78,"s:179:6:179:Infinity":79,"s:180:6:180:Infinity":80,"s:182:6:182:Infinity":81,"s:183:6:183:Infinity":82,"s:185:6:185:Infinity":83,"b:189:2:204:Infinity:undefined:undefined:undefined:undefined":38,"s:189:2:204:Infinity":84,"b:190:4:192:Infinity:undefined:undefined:undefined:undefined":39,"s:190:4:192:Infinity":85,"s:191:6:191:Infinity":86,"b:193:4:195:Infinity:undefined:undefined:undefined:undefined":40,"s:193:4:195:Infinity":87,"b:193:8:193:35:193:35:193:64":41,"s:194:6:194:Infinity":88,"b:196:4:199:Infinity:undefined:undefined:undefined:undefined":42,"s:196:4:199:Infinity":89,"s:197:6:197:Infinity":90,"s:198:6:198:Infinity":91,"b:200:4:203:Infinity:undefined:undefined:undefined:undefined":43,"s:200:4:203:Infinity":92,"b:200:8:200:42:200:42:200:77":44,"s:201:6:201:Infinity":93,"s:202:6:202:Infinity":94,"b:206:2:218:Infinity:undefined:undefined:undefined:undefined":45,"s:206:2:218:Infinity":95,"b:207:4:207:Infinity:208:4:208:Infinity:210:6:210:Infinity:211:6:211:Infinity:212:6:212:Infinity:213:6:213:Infinity:214:6:214:33":46,"s:217:4:217:Infinity":96,"b:221:2:236:Infinity:undefined:undefined:undefined:undefined":47,"s:221:2:236:Infinity":97,"b:222:4:224:Infinity:undefined:undefined:undefined:undefined":48,"s:222:4:224:Infinity":98,"s:223:6:223:Infinity":99,"b:225:4:227:Infinity:undefined:undefined:undefined:undefined":49,"s:225:4:227:Infinity":100,"s:226:6:226:Infinity":101,"b:228:4:230:Infinity:undefined:undefined:undefined:undefined":50,"s:228:4:230:Infinity":102,"b:228:8:228:35:228:35:228:68":51,"s:229:6:229:Infinity":103,"b:231:4:235:Infinity:undefined:undefined:undefined:undefined":52,"s:231:4:235:Infinity":104,"b:231:8:231:37:231:37:231:69":53,"s:232:6:232:Infinity":105,"s:233:6:233:Infinity":106,"s:234:6:234:Infinity":107,"b:238:2:240:Infinity:undefined:undefined:undefined:undefined":54,"s:238:2:240:Infinity":108,"b:238:6:238:35:238:35:238:69:238:69:238:89":55,"s:239:4:239:Infinity":109,"b:242:2:244:Infinity:undefined:undefined:undefined:undefined":56,"s:242:2:244:Infinity":110,"b:242:6:242:35:242:35:242:61":57,"s:243:4:243:Infinity":111,"s:247:19:247:Infinity":112,"b:247:46:247:56:247:56:247:Infinity":58,"b:247:83:247:93:247:93:247:Infinity":59,"b:248:2:250:Infinity:undefined:undefined:undefined:undefined":60,"s:248:2:250:Infinity":113,"s:249:4:249:Infinity":114,"b:253:2:268:Infinity:undefined:undefined:undefined:undefined":61,"s:253:2:268:Infinity":115,"b:254:4:262:Infinity:undefined:undefined:undefined:undefined":62,"s:254:4:262:Infinity":116,"b:255:6:255:Infinity:256:6:256:Infinity:257:6:257:Infinity:258:6:258:Infinity":63,"s:260:6:260:Infinity":117,"s:261:6:261:Infinity":118,"b:264:4:267:Infinity:undefined:undefined:undefined:undefined":64,"s:264:4:267:Infinity":119,"s:265:6:265:Infinity":120,"s:266:6:266:Infinity":121,"s:270:8:270:Infinity":122,"s:271:2:271:Infinity":123,"s:274:19:274:Infinity":124,"s:275:2:281:Infinity":125,"b:275:22:281:10:281:10:281:12":65,"f:275:40:275:Infinity":3,"s:276:19:276:Infinity":126,"b:277:6:277:Infinity:undefined:undefined:undefined:undefined":66,"s:277:6:277:Infinity":127,"b:277:10:277:41:277:41:277:71":67,"s:277:71:277:Infinity":128,"b:278:6:278:Infinity:undefined:undefined:undefined:undefined":68,"s:278:6:278:Infinity":129,"b:278:10:278:40:278:40:278:67":69,"s:278:67:278:Infinity":130,"b:279:6:279:Infinity:undefined:undefined:undefined:undefined":70,"s:279:6:279:Infinity":131,"b:279:10:279:42:279:42:279:68":71,"s:279:68:279:Infinity":132,"s:280:6:280:Infinity":133,"b:283:0:294:Infinity:undefined:undefined:undefined:undefined":72,"s:283:0:294:Infinity":134,"s:284:20:284:Infinity":135,"b:286:2:288:Infinity:287:7:288:Infinity":73,"s:286:2:288:Infinity":136,"s:286:31:286:Infinity":137,"b:287:7:288:Infinity:288:7:288:Infinity":74,"s:287:7:288:Infinity":138,"s:287:33:287:Infinity":139,"s:288:7:288:Infinity":140,"s:290:2:290:Infinity":141,"s:292:2:292:Infinity":142,"s:293:2:293:Infinity":143,"b:298:2:304:Infinity:undefined:undefined:undefined:undefined":75,"s:298:2:304:Infinity":144,"b:299:4:299:Infinity:300:4:300:Infinity:301:4:301:Infinity":76,"f:301:20:301:25":4,"s:301:25:301:Infinity":145,"s:303:4:303:Infinity":146,"b:307:2:319:Infinity:undefined:undefined:undefined:undefined":77,"s:307:2:319:Infinity":147,"b:308:4:308:Infinity:309:4:309:Infinity:311:6:311:Infinity:312:6:312:Infinity:313:6:313:Infinity:314:6:314:Infinity:315:6:315:33":78,"s:318:4:318:Infinity":148,"b:322:2:329:Infinity:undefined:undefined:undefined:undefined":79,"s:322:2:329:Infinity":149,"b:322:6:322:Infinity:323:4:323:Infinity:324:4:324:Infinity:325:4:325:Infinity:326:4:326:28":80,"s:328:4:328:Infinity":150,"s:332:25:334:Infinity":151,"b:333:6:333:Infinity:334:6:334:Infinity":81,"b:333:6:333:38:333:38:333:Infinity":82,"s:337:4:341:Infinity":152,"b:337:4:337:Infinity:338:4:338:Infinity:339:4:339:Infinity:340:4:340:Infinity:341:4:341:Infinity":83,"b:343:2:345:Infinity:undefined:undefined:undefined:undefined":84,"s:343:2:345:Infinity":153,"b:343:6:343:24:343:24:343:41":85,"s:344:4:344:Infinity":154,"s:347:2:352:Infinity":155,"s:356:2:356:Infinity":156}}} -,"E:\\Job\\ComputerSelectorHelper\\src\\utils\\laptopModels.js": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\laptopModels.js","statementMap":{"0":{"start":{"line":1,"column":24},"end":{"line":2,"column":75}},"1":{"start":{"line":2,"column":2},"end":{"line":2,"column":75}},"2":{"start":{"line":4,"column":21},"end":{"line":269,"column":1}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":1,"column":24},"end":{"line":1,"column":25}},"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":75}},"line":2}},"branchMap":{},"s":{"0":2,"1":46,"2":2},"f":{"0":46},"b":{},"meta":{"lastBranch":0,"lastFunction":1,"lastStatement":3,"seen":{"s:1:24:2:75":0,"f:1:24:1:25":0,"s:2:2:2:75":1,"s:4:21:269:1":2}}} -,"E:\\Job\\ComputerSelectorHelper\\src\\utils\\matchLaptopClass.jsx": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\matchLaptopClass.jsx","statementMap":{"0":{"start":{"line":3,"column":18},"end":{"line":5,"column":null}},"1":{"start":{"line":4,"column":2},"end":{"line":5,"column":null}},"2":{"start":{"line":7,"column":19},"end":{"line":8,"column":null}},"3":{"start":{"line":8,"column":2},"end":{"line":8,"column":null}},"4":{"start":{"line":11,"column":25},"end":{"line":92,"column":null}},"5":{"start":{"line":12,"column":27},"end":{"line":41,"column":null}},"6":{"start":{"line":13,"column":21},"end":{"line":13,"column":null}},"7":{"start":{"line":14,"column":20},"end":{"line":14,"column":null}},"8":{"start":{"line":15,"column":23},"end":{"line":15,"column":null}},"9":{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},"10":{"start":{"line":17,"column":61},"end":{"line":17,"column":null}},"11":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"12":{"start":{"line":18,"column":81},"end":{"line":18,"column":null}},"13":{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},"14":{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},"15":{"start":{"line":20,"column":57},"end":{"line":20,"column":null}},"16":{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},"17":{"start":{"line":21,"column":137},"end":{"line":21,"column":null}},"18":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"19":{"start":{"line":24,"column":58},"end":{"line":24,"column":null}},"20":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"21":{"start":{"line":25,"column":79},"end":{"line":25,"column":null}},"22":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"23":{"start":{"line":26,"column":78},"end":{"line":26,"column":null}},"24":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"25":{"start":{"line":27,"column":64},"end":{"line":27,"column":null}},"26":{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},"27":{"start":{"line":31,"column":6},"end":{"line":31,"column":null}},"28":{"start":{"line":34,"column":18},"end":{"line":34,"column":null}},"29":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"30":{"start":{"line":36,"column":48},"end":{"line":36,"column":null}},"31":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"32":{"start":{"line":37,"column":52},"end":{"line":37,"column":null}},"33":{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},"34":{"start":{"line":38,"column":50},"end":{"line":38,"column":null}},"35":{"start":{"line":40,"column":4},"end":{"line":40,"column":null}},"36":{"start":{"line":43,"column":23},"end":{"line":84,"column":null}},"37":{"start":{"line":44,"column":16},"end":{"line":44,"column":null}},"38":{"start":{"line":46,"column":23},"end":{"line":46,"column":null}},"39":{"start":{"line":47,"column":21},"end":{"line":47,"column":null}},"40":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"41":{"start":{"line":49,"column":68},"end":{"line":49,"column":null}},"42":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"43":{"start":{"line":50,"column":54},"end":{"line":50,"column":null}},"44":{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},"45":{"start":{"line":51,"column":60},"end":{"line":51,"column":null}},"46":{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},"47":{"start":{"line":52,"column":58},"end":{"line":52,"column":null}},"48":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"49":{"start":{"line":53,"column":49},"end":{"line":53,"column":null}},"50":{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},"51":{"start":{"line":54,"column":66},"end":{"line":54,"column":null}},"52":{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},"53":{"start":{"line":57,"column":6},"end":{"line":57,"column":null}},"54":{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},"55":{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},"56":{"start":{"line":61,"column":8},"end":{"line":61,"column":null}},"57":{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},"58":{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},"59":{"start":{"line":73,"column":8},"end":{"line":73,"column":null}},"60":{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},"61":{"start":{"line":80,"column":6},"end":{"line":80,"column":null}},"62":{"start":{"line":83,"column":4},"end":{"line":83,"column":null}},"63":{"start":{"line":86,"column":22},"end":{"line":88,"column":17}},"64":{"start":{"line":87,"column":20},"end":{"line":87,"column":47}},"65":{"start":{"line":90,"column":2},"end":{"line":90,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":18},"end":{"line":3,"column":19}},"loc":{"start":{"line":4,"column":2},"end":{"line":5,"column":null}},"line":4},"1":{"name":"(anonymous_1)","decl":{"start":{"line":7,"column":19},"end":{"line":7,"column":20}},"loc":{"start":{"line":8,"column":2},"end":{"line":8,"column":null}},"line":8},"2":{"name":"(anonymous_2)","decl":{"start":{"line":11,"column":25},"end":{"line":11,"column":26}},"loc":{"start":{"line":11,"column":26},"end":{"line":92,"column":null}},"line":11},"3":{"name":"(anonymous_3)","decl":{"start":{"line":12,"column":47},"end":{"line":12,"column":null}},"loc":{"start":{"line":12,"column":47},"end":{"line":41,"column":null}},"line":12},"4":{"name":"(anonymous_4)","decl":{"start":{"line":43,"column":44},"end":{"line":43,"column":null}},"loc":{"start":{"line":43,"column":44},"end":{"line":84,"column":null}},"line":43},"5":{"name":"(anonymous_5)","decl":{"start":{"line":87,"column":10},"end":{"line":87,"column":11}},"loc":{"start":{"line":87,"column":20},"end":{"line":87,"column":47}},"line":87}},"branchMap":{"0":{"loc":{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},"type":"if","locations":[{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},{"start":{},"end":{}}],"line":17},"1":{"loc":{"start":{"line":17,"column":8},"end":{"line":17,"column":61}},"type":"binary-expr","locations":[{"start":{"line":17,"column":8},"end":{"line":17,"column":35}},{"start":{"line":17,"column":35},"end":{"line":17,"column":61}}],"line":17},"2":{"loc":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"type":"if","locations":[{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},{"start":{},"end":{}}],"line":18},"3":{"loc":{"start":{"line":18,"column":8},"end":{"line":18,"column":81}},"type":"binary-expr","locations":[{"start":{"line":18,"column":8},"end":{"line":18,"column":35}},{"start":{"line":18,"column":35},"end":{"line":18,"column":81}}],"line":18},"4":{"loc":{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},"type":"if","locations":[{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},{"start":{},"end":{}}],"line":19},"5":{"loc":{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},"type":"if","locations":[{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},{"start":{},"end":{}}],"line":20},"6":{"loc":{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},"type":"if","locations":[{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},{"start":{},"end":{}}],"line":21},"7":{"loc":{"start":{"line":21,"column":10},"end":{"line":21,"column":137}},"type":"binary-expr","locations":[{"start":{"line":21,"column":10},"end":{"line":21,"column":53}},{"start":{"line":21,"column":53},"end":{"line":21,"column":96}},{"start":{"line":21,"column":96},"end":{"line":21,"column":137}}],"line":21},"8":{"loc":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"type":"if","locations":[{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},{"start":{},"end":{}}],"line":24},"9":{"loc":{"start":{"line":24,"column":8},"end":{"line":24,"column":58}},"type":"binary-expr","locations":[{"start":{"line":24,"column":8},"end":{"line":24,"column":32}},{"start":{"line":24,"column":32},"end":{"line":24,"column":58}}],"line":24},"10":{"loc":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"type":"if","locations":[{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},{"start":{},"end":{}}],"line":25},"11":{"loc":{"start":{"line":25,"column":8},"end":{"line":25,"column":79}},"type":"binary-expr","locations":[{"start":{"line":25,"column":8},"end":{"line":25,"column":38}},{"start":{"line":25,"column":38},"end":{"line":25,"column":79}}],"line":25},"12":{"loc":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},{"start":{},"end":{}}],"line":26},"13":{"loc":{"start":{"line":26,"column":8},"end":{"line":26,"column":78}},"type":"binary-expr","locations":[{"start":{"line":26,"column":8},"end":{"line":26,"column":51}},{"start":{"line":26,"column":51},"end":{"line":26,"column":78}}],"line":26},"14":{"loc":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"type":"if","locations":[{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},{"start":{},"end":{}}],"line":27},"15":{"loc":{"start":{"line":27,"column":8},"end":{"line":27,"column":64}},"type":"binary-expr","locations":[{"start":{"line":27,"column":8},"end":{"line":27,"column":33}},{"start":{"line":27,"column":33},"end":{"line":27,"column":64}}],"line":27},"16":{"loc":{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},"type":"if","locations":[{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},{"start":{},"end":{}}],"line":28},"17":{"loc":{"start":{"line":29,"column":6},"end":{"line":30,"column":null}},"type":"binary-expr","locations":[{"start":{"line":29,"column":7},"end":{"line":29,"column":41}},{"start":{"line":29,"column":41},"end":{"line":29,"column":75}},{"start":{"line":30,"column":6},"end":{"line":30,"column":36}},{"start":{"line":30,"column":36},"end":{"line":30,"column":null}}],"line":29},"18":{"loc":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":36},"19":{"loc":{"start":{"line":36,"column":8},"end":{"line":36,"column":48}},"type":"binary-expr","locations":[{"start":{"line":36,"column":8},"end":{"line":36,"column":34}},{"start":{"line":36,"column":34},"end":{"line":36,"column":48}}],"line":36},"20":{"loc":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},{"start":{},"end":{}}],"line":37},"21":{"loc":{"start":{"line":37,"column":8},"end":{"line":37,"column":52}},"type":"binary-expr","locations":[{"start":{"line":37,"column":8},"end":{"line":37,"column":37}},{"start":{"line":37,"column":37},"end":{"line":37,"column":52}}],"line":37},"22":{"loc":{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},"type":"if","locations":[{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},{"start":{},"end":{}}],"line":38},"23":{"loc":{"start":{"line":38,"column":8},"end":{"line":38,"column":50}},"type":"binary-expr","locations":[{"start":{"line":38,"column":8},"end":{"line":38,"column":35}},{"start":{"line":38,"column":35},"end":{"line":38,"column":50}}],"line":38},"24":{"loc":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"type":"if","locations":[{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},{"start":{},"end":{}}],"line":49},"25":{"loc":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"type":"if","locations":[{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},{"start":{},"end":{}}],"line":50},"26":{"loc":{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},"type":"if","locations":[{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},{"start":{},"end":{}}],"line":51},"27":{"loc":{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},"type":"if","locations":[{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},{"start":{},"end":{}}],"line":52},"28":{"loc":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"type":"if","locations":[{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},{"start":{},"end":{}}],"line":53},"29":{"loc":{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},"type":"if","locations":[{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},{"start":{},"end":{}}],"line":54},"30":{"loc":{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},"type":"if","locations":[{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},{"start":{},"end":{}}],"line":56},"31":{"loc":{"start":{"line":56,"column":8},"end":{"line":56,"column":76}},"type":"binary-expr","locations":[{"start":{"line":56,"column":8},"end":{"line":56,"column":46}},{"start":{"line":56,"column":46},"end":{"line":56,"column":76}}],"line":56},"32":{"loc":{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},"type":"if","locations":[{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},{"start":{},"end":{}}],"line":59},"33":{"loc":{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},"type":"if","locations":[{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},{"start":{},"end":{}}],"line":60},"34":{"loc":{"start":{"line":60,"column":10},"end":{"line":60,"column":91}},"type":"binary-expr","locations":[{"start":{"line":60,"column":10},"end":{"line":60,"column":38}},{"start":{"line":60,"column":38},"end":{"line":60,"column":66}},{"start":{"line":60,"column":66},"end":{"line":60,"column":91}}],"line":60},"35":{"loc":{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},"type":"if","locations":[{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},{"start":{},"end":{}}],"line":65},"36":{"loc":{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},"type":"if","locations":[{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},{"start":{},"end":{}}],"line":66},"37":{"loc":{"start":{"line":67,"column":8},"end":{"line":71,"column":null}},"type":"binary-expr","locations":[{"start":{"line":67,"column":8},"end":{"line":67,"column":null}},{"start":{"line":68,"column":8},"end":{"line":68,"column":null}},{"start":{"line":69,"column":8},"end":{"line":69,"column":null}},{"start":{"line":70,"column":8},"end":{"line":70,"column":null}},{"start":{"line":71,"column":8},"end":{"line":71,"column":null}}],"line":67},"38":{"loc":{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},{"start":{},"end":{}}],"line":79},"39":{"loc":{"start":{"line":79,"column":8},"end":{"line":79,"column":89}},"type":"binary-expr","locations":[{"start":{"line":79,"column":8},"end":{"line":79,"column":43}},{"start":{"line":79,"column":43},"end":{"line":79,"column":89}}],"line":79},"40":{"loc":{"start":{"line":90,"column":9},"end":{"line":90,"column":null}},"type":"cond-expr","locations":[{"start":{"line":90,"column":30},"end":{"line":90,"column":44}},{"start":{"line":90,"column":44},"end":{"line":90,"column":null}}],"line":90}},"s":{"0":2,"1":1200,"2":2,"3":794,"4":2,"5":57,"6":1311,"7":1311,"8":1311,"9":1311,"10":49,"11":1262,"12":16,"13":1246,"14":184,"15":160,"16":24,"17":8,"18":1078,"19":0,"20":1078,"21":0,"22":1078,"23":208,"24":870,"25":56,"26":814,"27":20,"28":794,"29":794,"30":49,"31":745,"32":427,"33":318,"34":18,"35":300,"36":57,"37":300,"38":300,"39":300,"40":300,"41":0,"42":300,"43":139,"44":300,"45":0,"46":300,"47":100,"48":300,"49":0,"50":300,"51":0,"52":300,"53":4,"54":300,"55":7,"56":7,"57":300,"58":15,"59":4,"60":300,"61":0,"62":300,"63":57,"64":529,"65":57},"f":{"0":1200,"1":794,"2":57,"3":1311,"4":300,"5":529},"b":{"0":[49,1262],"1":[1311,161],"2":[16,1246],"3":[1262,112],"4":[184,1062],"5":[160,24],"6":[8,16],"7":[24,16,8],"8":[0,1078],"9":[1078,16],"10":[0,1078],"11":[1078,0],"12":[208,870],"13":[1078,292],"14":[56,814],"15":[870,116],"16":[20,794],"17":[814,779,35,25],"18":[49,745],"19":[794,59],"20":[427,318],"21":[745,653],"22":[18,300],"23":[318,82],"24":[0,300],"25":[139,161],"26":[0,300],"27":[100,200],"28":[0,300],"29":[0,300],"30":[4,296],"31":[300,8],"32":[7,293],"33":[7,0],"34":[7,0,0],"35":[15,285],"36":[4,11],"37":[15,14,11,11,11],"38":[0,300],"39":[300,8],"40":[40,17]},"meta":{"lastBranch":41,"lastFunction":6,"lastStatement":66,"seen":{"s:3:18:5:Infinity":0,"f:3:18:3:19":0,"s:4:2:5:Infinity":1,"s:7:19:8:Infinity":2,"f:7:19:7:20":1,"s:8:2:8:Infinity":3,"s:11:25:92:Infinity":4,"f:11:25:11:26":2,"s:12:27:41:Infinity":5,"f:12:47:12:Infinity":3,"s:13:21:13:Infinity":6,"s:14:20:14:Infinity":7,"s:15:23:15:Infinity":8,"b:17:4:17:Infinity:undefined:undefined:undefined:undefined":0,"s:17:4:17:Infinity":9,"b:17:8:17:35:17:35:17:61":1,"s:17:61:17:Infinity":10,"b:18:4:18:Infinity:undefined:undefined:undefined:undefined":2,"s:18:4:18:Infinity":11,"b:18:8:18:35:18:35:18:81":3,"s:18:81:18:Infinity":12,"b:19:4:22:Infinity:undefined:undefined:undefined:undefined":4,"s:19:4:22:Infinity":13,"b:20:6:20:Infinity:undefined:undefined:undefined:undefined":5,"s:20:6:20:Infinity":14,"s:20:57:20:Infinity":15,"b:21:6:21:Infinity:undefined:undefined:undefined:undefined":6,"s:21:6:21:Infinity":16,"b:21:10:21:53:21:53:21:96:21:96:21:137":7,"s:21:137:21:Infinity":17,"b:24:4:24:Infinity:undefined:undefined:undefined:undefined":8,"s:24:4:24:Infinity":18,"b:24:8:24:32:24:32:24:58":9,"s:24:58:24:Infinity":19,"b:25:4:25:Infinity:undefined:undefined:undefined:undefined":10,"s:25:4:25:Infinity":20,"b:25:8:25:38:25:38:25:79":11,"s:25:79:25:Infinity":21,"b:26:4:26:Infinity:undefined:undefined:undefined:undefined":12,"s:26:4:26:Infinity":22,"b:26:8:26:51:26:51:26:78":13,"s:26:78:26:Infinity":23,"b:27:4:27:Infinity:undefined:undefined:undefined:undefined":14,"s:27:4:27:Infinity":24,"b:27:8:27:33:27:33:27:64":15,"s:27:64:27:Infinity":25,"b:28:4:31:Infinity:undefined:undefined:undefined:undefined":16,"s:28:4:31:Infinity":26,"b:29:7:29:41:29:41:29:75:30:6:30:36:30:36:30:Infinity":17,"s:31:6:31:Infinity":27,"s:34:18:34:Infinity":28,"b:36:4:36:Infinity:undefined:undefined:undefined:undefined":18,"s:36:4:36:Infinity":29,"b:36:8:36:34:36:34:36:48":19,"s:36:48:36:Infinity":30,"b:37:4:37:Infinity:undefined:undefined:undefined:undefined":20,"s:37:4:37:Infinity":31,"b:37:8:37:37:37:37:37:52":21,"s:37:52:37:Infinity":32,"b:38:4:38:Infinity:undefined:undefined:undefined:undefined":22,"s:38:4:38:Infinity":33,"b:38:8:38:35:38:35:38:50":23,"s:38:50:38:Infinity":34,"s:40:4:40:Infinity":35,"s:43:23:84:Infinity":36,"f:43:44:43:Infinity":4,"s:44:16:44:Infinity":37,"s:46:23:46:Infinity":38,"s:47:21:47:Infinity":39,"b:49:4:49:Infinity:undefined:undefined:undefined:undefined":24,"s:49:4:49:Infinity":40,"s:49:68:49:Infinity":41,"b:50:4:50:Infinity:undefined:undefined:undefined:undefined":25,"s:50:4:50:Infinity":42,"s:50:54:50:Infinity":43,"b:51:4:51:Infinity:undefined:undefined:undefined:undefined":26,"s:51:4:51:Infinity":44,"s:51:60:51:Infinity":45,"b:52:4:52:Infinity:undefined:undefined:undefined:undefined":27,"s:52:4:52:Infinity":46,"s:52:58:52:Infinity":47,"b:53:4:53:Infinity:undefined:undefined:undefined:undefined":28,"s:53:4:53:Infinity":48,"s:53:49:53:Infinity":49,"b:54:4:54:Infinity:undefined:undefined:undefined:undefined":29,"s:54:4:54:Infinity":50,"s:54:66:54:Infinity":51,"b:56:4:58:Infinity:undefined:undefined:undefined:undefined":30,"s:56:4:58:Infinity":52,"b:56:8:56:46:56:46:56:76":31,"s:57:6:57:Infinity":53,"b:59:4:63:Infinity:undefined:undefined:undefined:undefined":32,"s:59:4:63:Infinity":54,"b:60:6:62:Infinity:undefined:undefined:undefined:undefined":33,"s:60:6:62:Infinity":55,"b:60:10:60:38:60:38:60:66:60:66:60:91":34,"s:61:8:61:Infinity":56,"b:65:4:75:Infinity:undefined:undefined:undefined:undefined":35,"s:65:4:75:Infinity":57,"b:66:6:74:Infinity:undefined:undefined:undefined:undefined":36,"s:66:6:74:Infinity":58,"b:67:8:67:Infinity:68:8:68:Infinity:69:8:69:Infinity:70:8:70:Infinity:71:8:71:Infinity":37,"s:73:8:73:Infinity":59,"b:79:4:81:Infinity:undefined:undefined:undefined:undefined":38,"s:79:4:81:Infinity":60,"b:79:8:79:43:79:43:79:89":39,"s:80:6:80:Infinity":61,"s:83:4:83:Infinity":62,"s:86:22:88:17":63,"f:87:10:87:11":5,"s:87:20:87:47":64,"s:90:2:90:Infinity":65,"b:90:30:90:44:90:44:90:Infinity":40}}} -} diff --git a/coverage/favicon.png b/coverage/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 92.03% - Statements - 208/226 -
- - -
- 88.07% - Branches - 251/285 -
- - -
- 100% - Functions - 12/12 -
- - -
- 94.6% - Lines - 193/204 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
convertToSpecs.jsx -
-
92.99%146/15788.26%173/196100%5/593.42%142/152
laptopModels.js -
-
100%3/3100%0/0100%1/1100%3/3
matchLaptopClass.jsx -
-
89.39%59/6687.64%78/89100%6/697.95%48/49
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/laptopModels.js.html b/coverage/laptopModels.js.html deleted file mode 100644 index 5d22185..0000000 --- a/coverage/laptopModels.js.html +++ /dev/null @@ -1,898 +0,0 @@ - - - - - - Code coverage report for laptopModels.js - - - - - - - - - -
-
-

All files laptopModels.js

-
- -
- 100% - Statements - 3/3 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 3/3 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196 -197 -198 -199 -200 -201 -202 -203 -204 -205 -206 -207 -208 -209 -210 -211 -212 -213 -214 -215 -216 -217 -218 -219 -220 -221 -222 -223 -224 -225 -226 -227 -228 -229 -230 -231 -232 -233 -234 -235 -236 -237 -238 -239 -240 -241 -242 -243 -244 -245 -246 -247 -248 -249 -250 -251 -252 -253 -254 -255 -256 -257 -258 -259 -260 -261 -262 -263 -264 -265 -266 -267 -268 -269 -270 -271 -2722x -46x -  -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
const buildGoogleLink = (name) =>
-  `https://www.google.com/search?q=${encodeURIComponent(name + ' laptop')}`
- 
-const laptopModels = [
-  {
-    id: "hp-stream-14",
-    name: "HP Stream 14",
-    use: "Estudiantes básicos, movilidad",
-    specs: "Intel Celeron N4120, 4 GB RAM, 64 GB SSD, 14” HD",
-    gpu: "Intel integrada",
-    price: "$4,199 MXN",
-    portability: "Alta",
-    image:
-      "https://www.notebookcheck.net/fileadmin/Notebooks/HP/Stream_14-ax002ng/2.jpg",
-    link: buildGoogleLink("HP Stream 14"),
-  },
-  {
-    id: "acer-aspire-go",
-    name: "Acer Aspire Go AG15",
-    use: "Uso general, estudiantes",
-    specs: "Intel Core i3, 8 GB RAM, 256 GB SSD, 15.6” FHD",
-    gpu: "Intel integrada",
-    price: "$5,999 MXN",
-    portability: "Media",
-    image:
-      "https://laptoping.com/specs/wp-content/uploads/2024/06/Acer-Aspire-Go-15-Slim-AG15-31P-3947.jpg",
-    link: buildGoogleLink("acer-aspire-go"),
-  },
-  {
-    id: "hp-victus-rtx3050",
-    name: "HP Victus RTX 3050",
-    use: "Gaming medio, tareas pesadas",
-    specs: "Intel i5 12450H, 32 GB RAM, 512 GB SSD, RTX 3050",
-    gpu: "RTX 3050",
-    price: "$14,999 MXN",
-    portability: "Media",
-    image:
-      "https://m.media-amazon.com/images/I/71XwPLUaKsL._AC_SL1500_.jpg",
-    link: buildGoogleLink("hp-victus-rtx3050"),
-  },
-  {
-    id: "macbook-air-m1",
-    name: "Apple MacBook Air M1",
-    use: "Creativos, portabilidad, estudiantes",
-    specs: "Apple M1, 8 GB RAM, 256 GB SSD, 13.3” Retina",
-    gpu: "M1",
-    price: "$13,999 MXN",
-    portability: "Alta",
-    image:
-      "https://th.bing.com/th/id/OIP.Q4muQjrfyxVhOB5PMwAF6gHaE8?cb=iwp2&rs=1&pid=ImgDetMainhttps://thangpro.vn/uploads/source/laptop/macbook/mac-air-m1/img-7707.jpg",
-    link: buildGoogleLink("macbook-air-m1"),
-  },
-  {
-    id: "asus-tuf-a15",
-    name: "ASUS TUF Gaming A15",
-    use: "Gaming alto, edición multimedia",
-    specs: "Ryzen 7, 8 GB RAM, 512 GB SSD, RTX 3050",
-    gpu: "RTX 3050",
-    price: "$16,339 MXN",
-    portability: "Media",
-    image:
-      "https://laptopmedia.com/wp-content/uploads/2021/01/2-40.jpg",
-    link: buildGoogleLink("asus-tuf-a15"),
-  },
-  {
-    id: "lenovo-chromebook-3",
-    name: "Lenovo Chromebook 3",
-    use: "Tareas básicas, navegación, Google Docs",
-    specs: "Intel Celeron N4020, 4 GB RAM, 64 GB eMMC, 14” HD",
-    gpu: "Intel integrada",
-    price: "$3,499 MXN",
-    portability: "Alta",
-    image:
-      "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6447/6447138_sd.jpg",
-    link: buildGoogleLink("lenovo-chromebook-3"),
-  },
-  {
-    id: "macbook-air-m2",
-    name: "Apple MacBook Air M2",
-    use: "Profesionales móviles, diseño, universidad",
-    specs: "Apple M2, 8 GB RAM, 256 GB SSD, 13.6” Retina",
-    gpu: "M2",
-    price: "$23,999 MXN",
-    portability: "Alta",
-    image:
-      "https://cdn.shopify.com/s/files/1/1706/9177/products/macbook-air-m2-midnight-custom-mac-bd_b82010cb-9405-4654-a165-9576a249e4e0.jpg",
-    link: buildGoogleLink("macbook-air-m2"),
-  },
- 
-  {
-    id: "lenovo-gamer-rtx4070",
-    name: "Lenovo Legion RTX 4070",
-    use: "Gaming extremo, arquitectura, animación",
-    specs: "Intel i9, 32 GB RAM, 1TB SSD, RTX 4070",
-    gpu: "RTX 4070",
-    price: "$38,999 MXN",
-    portability: "Baja",
-    image:
-      "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6534/6534466ld.jpg",
-    link: buildGoogleLink("lenovo-gamer-rtx4070"),
-  },
-  {
-    id: "msi-gamer-rtx2060",
-    name: "MSI GF63 RTX 2060",
-    use: "Gaming clásico, edición semiprofesional",
-    specs: "Intel i5 10th Gen, 16 GB RAM, 512 GB SSD, RTX 2060",
-    gpu: "RTX 2060",
-    price: "$17,499 MXN",
-    portability: "Media",
-    image:
-      "https://i0.wp.com/phukienmaytinh.com.vn/wp-content/uploads/2020/02/MSI-GF63-3.jpg",
-    link: buildGoogleLink("msi-gamer-rtx2060"),
-  },
-  {
-    id: "huawei-matebook-d16-i5",
-    name: "Huawei MateBook D16",
-    use: "Trabajo exigente, multimedia, estudiantes",
-    specs: "Intel Core i5 13420H, 16 GB RAM, 1 TB SSD, 16\" FHD",
-    gpu: "Intel Iris Xe",
-    price: "$14,399 MXN",
-    portability: "Media",
-    image: "https://www.presse-citron.net/app/uploads/2021/05/DSC01401_DxO_PC.jpg",
-    link: buildGoogleLink("Huawei MateBook D16"),
-  },
-  {
-    id: "hp-pavilion-i7-1355u",
-    name: "HP Pavilion i7-1355U",
-    use: "Profesionales, multitarea avanzada, productividad",
-    specs: "Intel Core i7-1355U, 32 GB RAM, 1 TB SSD, 15.6\" FHD",
-    gpu: "Intel Iris Xe",
-    price: "$15,722 MXN",
-    portability: "Media",
-    image: "https://m.media-amazon.com/images/I/613hm2YD6mL._AC_.jpg",
-    link: buildGoogleLink("HP Pavilion i7-1355U"),
-  },
-  {
-    id: "msi-sword-16-i7",
-    name: "MSI Sword 16 HX",
-    use: "Gaming, edición profesional, exigencia gráfica",
-    specs: "Intel Core i7, 16 GB RAM, 1 TB SSD, 16\" FHD",
-    gpu: "RTX 4060",
-    price: "$33,999 MXN",
-    portability: "Media",
-    image: "https://storage-asset.msi.com/global/picture/image/feature/nb/2023_RPLS/sword16-hx-b14v/msi-sword16-display-new.png",
-    link: buildGoogleLink("MSI Sword 16 HX"),
-  },
-  {
-    id: "generic-intel-14",
-    name: "Laptop 14 Intel",
-    use: "Tareas básicas, ofimática, estudiantes",
-    specs: "Intel genérica, 8 GB RAM, 256 GB SSD, 14\" FHD",
-    gpu: "Intel integrada",
-    price: "$4,800 MXN",
-    portability: "Alta",
-    image: "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6425/6425056_rd.jpg",
-    link: buildGoogleLink("Laptop 14 Intel 256gb"),
-  },
-    {
-    id: "macbook-air-m4-512",
-    name: "MacBook Air M4 (512 GB)",
-    use: "Profesionales móviles, diseño, rendimiento alto",
-    specs: "Apple M4, 8 GB RAM, 512 GB SSD, 13” Retina",
-    gpu: "Apple Silicon",
-    price: "$30,950 MXN",
-    portability: "Alta",
-    image: "https://ipadizate.com/hero/2023/01/MacBook-Air-M2-Chip-Purple-Feature.jpg", 
-    link: buildGoogleLink("MacBook Air M4 512 GB")
-  },
-  {
-    id: "acer-nitro-5-rtx4060",
-    name: "Acer Nitro 5 RTX 4060",
-    use: "Gaming exigente, edición profesional",
-    specs: "Intel Core i7-13620H, 16 GB RAM, 512 GB SSD, RTX 4060",
-    gpu: "RTX 4060",
-    price: "$20,642 MXN",
-    portability: "Media",
-    image: "https://www.notebookcheck.com/fileadmin/_processed_/9/2/csm_Acer_Nitro_5_RTX_4060_guenstigstes_gaming_notebook_deal_db9eae21d8.jpg",
-    link: buildGoogleLink("Acer Nitro 5 RTX 4060")
-  },
-  {
-    id: "hp-15-fd0999nr",
-    name: "HP 15-fd0999nr",
-    use: "Ofimática avanzada, estudiantes, productividad",
-    specs: "Intel Core i5-1235U, 16 GB RAM, 512 GB SSD, 15.6” FHD",
-    gpu: "Intel Iris Xe",
-    price: "$9,797 MXN",
-    portability: "Media",
-    image: "https://lidernotebooks.com.ar/wp-content/uploads/2024/10/HP-15-fd0999NR-Core-i5_0004_Capa-7.jpg",
-    link: buildGoogleLink("HP 15-fd0999nr")
-  },
-  {
-    id: "msi-gf63-gtx1650",
-    name: "MSI GF63 GTX 1650",
-    use: "Gaming básico, diseño ligero",
-    specs: "Intel Core i5-11400H, 8 GB RAM, 256 GB SSD, GTX 1650",
-    gpu: "GTX 1650",
-    price: "$11,999 MXN",
-    portability: "Media",
-    image: "https://laptop360.net/wp-content/uploads/2022/09/MSI-Gaming-GF63-Thin-11UC-3-1536x1152.jpg",
-    link: buildGoogleLink("MSI GF63 GTX 1650")
-  },
-  {
-    id: "lenovo-yoga-slim-7-ultra5",
-    name: "Lenovo Yoga Slim 7",
-    use: "Profesionales, estudiantes de ingeniería",
-    specs: "Intel Ultra 5 125H, 16 GB RAM, 1 TB SSD, 14” FHD",
-    gpu: "Intel integrada",
-    price: "$18,999 MXN",
-    portability: "Alta",
-    image: "https://cdn.mos.cms.futurecdn.net/4C7JXvghGrpY52MtBiN2cB.jpg",
-    link: buildGoogleLink("Lenovo Yoga Slim 7")
-  },
-  {
-  id: "machcreator-15n-i7",
-  name: "Laptop Machcreator 15N",
-  use: "Multitarea, trabajo profesional, estudiantes avanzados",
-  specs: "Intel Core i7 13620H, 16 GB RAM, 512 GB SSD, 15.6” FHD",
-  gpu: "Intel UHD Graphics",
-  price: "$14,553 MXN",
-  portability: "Media",
-  image: "https://global.machenike.com/cdn/shop/files/11_818b6222-59ce-4193-a68b-bbc8e043b8ef.jpg",
-  link: buildGoogleLink("Laptop Machcreator 15N i7"),
-},
-{
-  id: "machenike-l16pro-rtx4060",
-  name: "Machenike L16 Pro",
-  use: "Gaming exigente, edición profesional, multitarea avanzada",
-  specs: "Ryzen 7 7735H, 16 GB RAM DDR5, 1 TB SSD, 16” 2.5K 165Hz",
-  gpu: "Nvidia GeForce RTX 4060",
-  price: "$34,954 MXN",
-  portability: "Baja",
-  image: "https://http2.mlstatic.com/D_NQ_NP_875287-MLU73599603547_122023-O.webp", 
-  link: buildGoogleLink("Machenike L16 Pro RTX 4060"),
-},
-{
-  id: "samsung-galaxy-book4-360",
-  name: "Samsung Galaxy Book4 360",
-  use: "Multimedia, productividad, uso general, estudiantes",
-  specs: "Intel Core 7 Series 1, 16 GB RAM LPDDR5, 512 GB SSD, 15.6” FHD táctil",
-  gpu: "Intel Graphics integrada",
-  price: "$22,999 MXN",
-  portability: "Alta",
-  image: "https://media-ik.croma.com/prod/https://media.croma.com/image/upload/v1708516453/Croma%20Assets/Computers%20Peripherals/Laptop/Images/305055_4_bsd9ja.png",
-  link: buildGoogleLink("Samsung Galaxy Book4 360"),
-},
-{
-  id: "topgro-n5-i3-mx230",
-  name: "Topgro N5",
-  use: "Ofimática, uso general, multimedia básica",
-  specs: "Intel Core i3-1005G1, 16 GB RAM DDR4, 512 GB SSD, NVIDIA GeForce MX230, 15.6” FHD",
-  gpu: "NVIDIA GeForce MX230 (2 GB GDDR5)",
-  price: "$6,935 MXN",
-  portability: "Media",
-  image: "https://m.media-amazon.com/images/I/71gXrO3lsqL._AC_.jpg", 
-  link: buildGoogleLink("Topgro N5 i3 MX230"),
-},
-{
-  id: "vsap-n4020-14",
-  name: "VSAP Intel Celeron N4020",
-  use: "Ofimática, navegación, estudiantes",
-  specs: "Intel Celeron N4020, 8 GB RAM DDR4, 512 GB SSD, 14.1\" FHD",
-  gpu: "Intel UHD Graphics 600",
-  price: "$3,695 MXN",
-  portability: "Alta",
-  image: "https://static.wixstatic.com/media/c8ef56_df2decbc5ac442c28c399e2c9a2ee5e2~mv2.webp", 
-  link: buildGoogleLink("VSAP Intel Celeron N4020"),
-},
- 
-];
- 
-export default laptopModels;
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/matchLaptopClass.jsx.html b/coverage/matchLaptopClass.jsx.html deleted file mode 100644 index d345696..0000000 --- a/coverage/matchLaptopClass.jsx.html +++ /dev/null @@ -1,367 +0,0 @@ - - - - - - Code coverage report for matchLaptopClass.jsx - - - - - - - - - -
-
-

All files matchLaptopClass.jsx

-
- -
- 89.39% - Statements - 59/66 -
- - -
- 87.64% - Branches - 78/89 -
- - -
- 100% - Functions - 6/6 -
- - -
- 97.95% - Lines - 48/49 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95  -  -2x -1200x -  -  -2x -794x -  -  -2x -57x -1311x -1311x -1311x -  -1311x -1262x -1246x -184x -24x -  -  -1078x -1078x -1078x -870x -814x -  -  -20x -  -  -794x -  -794x -745x -318x -  -300x -  -  -57x -300x -  -300x -300x -  -300x -300x -300x -300x -300x -300x -  -300x -4x -  -300x -7x -7x -  -  -  -300x -15x -  -  -  -  -  -  -4x -  -  -  -  -  -300x -  -  -  -300x -  -  -57x -529x -  -  -57x -  -  -  -  - 
import laptopModels from './laptopModels'
- 
-const normalize = (text) =>
-  text.toLowerCase()
-    .replace(/(intel|amd|apple|core|ryzen|graphics|nvidia|geforce|radeon|gpu)|\s+/gi, '')
- 
-const parsePrice = (priceString) =>
-  parseInt(priceString.replace(/[^\d]/g, ''), 10)
- 
- 
-const matchLaptopClass = (specs) => {
-  const compatibleModels = laptopModels.filter(model => {
-    const modelGPU = model.gpu.toLowerCase()
-    const modelOS = model.use.toLowerCase()
-    const modelSpecs = model.specs.toLowerCase()
- 
-    if (specs.os === 'ChromeOS' && modelGPU.includes('rtx')) return false
-    if (specs.os === 'ChromeOS' && specs.processor.toLowerCase().includes('i7')) return false
-    if (specs.os === 'macOS') {
-      if (!model.name.toLowerCase().includes('macbook')) return false
-      if (!model.gpu.toLowerCase().includes('m1') && !model.gpu.toLowerCase().includes('m2') && !model.gpu.toLowerCase().includes('m3')) return false
-    }
- 
-    Iif (specs.os === 'macOS' && modelGPU.includes('rtx')) return false
-    Iif (modelOS.includes('chrome') && specs.gpu.toLowerCase().includes('rtx')) return false
-    if (specs.gpu.toLowerCase().includes('rtx') && !modelGPU.includes('rtx')) return false
-    if (specs.ram === '16 GB' && !modelSpecs.includes('16 gb')) return false
-    if (
-      (specs.processor.includes('i7') || specs.processor.includes('ryzen 7')) &&
-      !modelSpecs.includes('i7') && !modelSpecs.includes('ryzen 7')
-    ) return false
- 
-    // ✅ Presupuesto
-    const price = parsePrice(model.price)
- 
-    if (specs.budget === 'low' && price > 6000) return false
-    if (specs.budget === 'medium' && price > 13000) return false
-    if (specs.budget === 'high' && price < 13000) return false
- 
-    return true
-  })
- 
-  const scoredModels = compatibleModels.map(model => {
-    let score = 0
- 
-    const modelSpecs = model.specs.toLowerCase()
-    const modelGPU = model.gpu.toLowerCase()
- 
-    Iif (normalize(modelSpecs).includes(normalize(specs.processor))) score += 2
-    if (modelSpecs.includes(specs.ram.toLowerCase())) score += 2
-    Iif (normalize(modelGPU).includes(normalize(specs.gpu))) score += 2
-    if (modelSpecs.includes(specs.storage.toLowerCase())) score += 1
-    Iif (model.portability === specs.portability) score += 1
-    Iif (model.use.toLowerCase().includes(specs.os.toLowerCase())) score += 1
- 
-    if (specs.flags?.prioritizePortability && model.portability === 'Alta') {
-      score += 1.5
-    }
-    if (specs.flags?.prioritizeGaming) {
-      Eif (modelGPU.includes('rtx') || modelGPU.includes('gtx') || modelGPU.includes('mx')) {
-        score += 1.5
-      }
-    }
- 
-    if (specs.flags?.prioritizeScreenQuality) {
-      if (
-        modelSpecs.includes('2.5k') ||
-        modelSpecs.includes('retina') ||
-        modelSpecs.includes('qhd') ||
-        modelSpecs.includes('2560') ||
-        modelSpecs.includes('amoled')
-      ) {
-        score += 1.5
-      }
-    }
- 
- 
- 
-    Iif (specs.flags?.prioritizeSecurity && model.specs.toLowerCase().includes('huella')) {
-      score += 1.5
-    }
- 
-    return { ...model, matchScore: score }
-  })
- 
-  const bestMatches = scoredModels
-    .sort((a, b) => b.matchScore - a.matchScore)
-    .slice(0, 3) // 👈 Cambia 3 por cuántas quieres mostrar
- 
-  return bestMatches.length ? bestMatches : [{ name: "Clase genérica", specs: "" }]
- 
-}
- 
-export default matchLaptopClass
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css deleted file mode 100644 index b317a7c..0000000 --- a/coverage/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/prettify.js b/coverage/prettify.js deleted file mode 100644 index b322523..0000000 --- a/coverage/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/coverage/sorter.js b/coverage/sorter.js deleted file mode 100644 index 4ed70ae..0000000 --- a/coverage/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); From 2b11bf3f29726969fd7764fc990b5ac3d55633ef Mon Sep 17 00:00:00 2001 From: FranGuh Date: Sat, 9 May 2026 18:49:47 -0600 Subject: [PATCH 4/8] feat: component tests for Recommendation and LaptopCard - 16 new component tests (72 total) - LaptopCard: rendering, props, link, null handling - Recommendation: quiz flow, navigation, results, reset - Mock convertToSpecs for deterministic test results - Coverage: 90% statements, 85% branches, 86% functions - Document missing image fallback feature --- src/__tests__/LaptopCard.test.jsx | 63 +++++++++ src/__tests__/Recommendation.test.jsx | 180 ++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 src/__tests__/LaptopCard.test.jsx create mode 100644 src/__tests__/Recommendation.test.jsx diff --git a/src/__tests__/LaptopCard.test.jsx b/src/__tests__/LaptopCard.test.jsx new file mode 100644 index 0000000..4e01725 --- /dev/null +++ b/src/__tests__/LaptopCard.test.jsx @@ -0,0 +1,63 @@ +import { describe, it, expect, vi } from 'vitest' +import { render, screen, fireEvent } from '@testing-library/react' +import LaptopCard from '../components/LaptopCard/LaptopCard' + +const mockModel = { + id: 'test-laptop', + name: 'Test Laptop Pro', + use: 'Gaming, edición', + specs: 'Intel i7, 16 GB RAM, 512 GB SSD, RTX 4060', + gpu: 'RTX 4060', + price: '$20,000 MXN', + portability: 'Media', + image: 'https://example.com/laptop.jpg', + link: 'https://example.com/buy', +} + +describe('LaptopCard', () => { + it('renders with all props', () => { + render() + expect(screen.getByText('🎯 Te sugerimos esta laptop:')).toBeInTheDocument() + expect(screen.getByText('Test Laptop Pro')).toBeInTheDocument() + expect(screen.getByText(/Uso recomendado:/)).toBeInTheDocument() + expect(screen.getByText('Gaming, edición')).toBeInTheDocument() + expect(screen.getByText(/Especificaciones:/)).toBeInTheDocument() + expect(screen.getByText(/Intel i7/)).toBeInTheDocument() + expect(screen.getByText(/Precio aproximado:/)).toBeInTheDocument() + expect(screen.getByText('$20,000 MXN')).toBeInTheDocument() + }) + + it('renders image with correct alt text', () => { + render() + const img = screen.getByAltText('Test Laptop Pro') + expect(img).toBeInTheDocument() + expect(img.getAttribute('src')).toBe('https://example.com/laptop.jpg') + }) + + it('renders link to store', () => { + render() + const link = screen.getByRole('link', { name: /ver en tienda/i }) + expect(link).toBeInTheDocument() + expect(link.getAttribute('href')).toBe('https://example.com/buy') + expect(link.getAttribute('target')).toBe('_blank') + expect(link.getAttribute('rel')).toBe('noopener noreferrer') + }) + + it('returns null when model is null', () => { + const { container } = render() + expect(container.firstChild).toBeNull() + }) + + it('returns null when model is undefined', () => { + const { container } = render() + expect(container.firstChild).toBeNull() + }) + + it('shows image fallback on error', () => { + // Note: current component has no onError handler — test documents this gap + render() + const img = screen.getByAltText('Test Laptop Pro') + expect(img).toBeInTheDocument() + // TODO: Add onError handler to LaptopCard for image fallback + }) +}) diff --git a/src/__tests__/Recommendation.test.jsx b/src/__tests__/Recommendation.test.jsx new file mode 100644 index 0000000..cfac7b4 --- /dev/null +++ b/src/__tests__/Recommendation.test.jsx @@ -0,0 +1,180 @@ +import { describe, it, expect, vi } from 'vitest' +import { render, screen, fireEvent } from '@testing-library/react' +import Recommendation from '../components/Recommendation/Recommendation' + +vi.mock('../utils/convertToSpecs', () => ({ + default: vi.fn((answers) => ({ + processor: 'Intel i5 / Ryzen 5', + ram: '12 GB', + storage: '256 GB SSD', + gpu: 'Intel Iris Xe / Radeon Vega', + os: 'Windows 11', + budget: answers.budget || 'medium', + touchscreen: false, + portability: 'Media o baja', + battery: 'Media (4-7h)', + warnings: [], + rationale: ['Test rationale'], + laptopClass: [ + { + id: 'test-1', + name: 'Test Laptop 1', + use: 'General use', + specs: 'Intel i5, 8 GB RAM, 256 GB SSD', + gpu: 'Intel Iris Xe', + price: '$10,000 MXN', + portability: 'Media', + image: 'https://example.com/1.jpg', + link: 'https://example.com/1', + }, + { + id: 'test-2', + name: 'Test Laptop 2', + use: 'Gaming', + specs: 'Intel i7, 16 GB RAM, 512 GB SSD, RTX 4050', + gpu: 'RTX 4050', + price: '$15,000 MXN', + portability: 'Media', + image: 'https://example.com/2.jpg', + link: 'https://example.com/2', + }, + ], + })), +})) + +describe('Recommendation — Quiz flow', () => { + it('renders with first question', () => { + render() + expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument() + }) + + it('shows checkbox options for first question', () => { + render() + const checkboxes = screen.getAllByRole('checkbox') + expect(checkboxes.length).toBeGreaterThan(0) + }) + + it('next button is disabled when no selection', () => { + render() + const nextButton = screen.getByRole('button', { name: /siguiente/i }) + expect(nextButton).toBeDisabled() + }) + + it('next button is enabled after selection', () => { + render() + const firstCheckbox = screen.getAllByRole('checkbox')[0] + fireEvent.click(firstCheckbox) + const nextButton = screen.getByRole('button', { name: /siguiente/i }) + expect(nextButton).toBeEnabled() + }) + + it('advances to next question on click', () => { + render() + const firstQuestion = screen.getByRole('heading', { level: 2 }).textContent + const firstCheckbox = screen.getAllByRole('checkbox')[0] + fireEvent.click(firstCheckbox) + const nextButton = screen.getByRole('button', { name: /siguiente/i }) + fireEvent.click(nextButton) + const secondQuestion = screen.getByRole('heading', { level: 2 }).textContent + expect(secondQuestion).not.toBe(firstQuestion) + }) + + it('completes quiz and shows results', () => { + render() + // Answer all questions with first option + for (let i = 0; i < 10; i++) { + const checkboxes = screen.queryAllByRole('checkbox') + const radios = screen.queryAllByRole('radio') + if (checkboxes.length > 0) { + fireEvent.click(checkboxes[0]) + } else if (radios.length > 0) { + fireEvent.click(radios[0]) + } + const nextButton = screen.getByRole('button', { name: /siguiente/i }) + fireEvent.click(nextButton) + } + // Should show results + expect(screen.getByText(/tu recomendación técnica/i)).toBeInTheDocument() + expect(screen.getByText(/especificaciones recomendadas/i)).toBeInTheDocument() + }) +}) + +describe('Recommendation — Results page', () => { + it('shows laptop cards after quiz completion', () => { + render() + // Answer all questions + for (let i = 0; i < 10; i++) { + const checkboxes = screen.queryAllByRole('checkbox') + const radios = screen.queryAllByRole('radio') + if (checkboxes.length > 0) { + fireEvent.click(checkboxes[0]) + } else if (radios.length > 0) { + fireEvent.click(radios[0]) + } + const nextButton = screen.getByRole('button', { name: /siguiente/i }) + fireEvent.click(nextButton) + } + // Should show laptop cards + expect(screen.getByText('Test Laptop 1')).toBeInTheDocument() + expect(screen.getByText('Test Laptop 2')).toBeInTheDocument() + }) + + it('shows spec cards on results page', () => { + render() + // Answer all questions + for (let i = 0; i < 10; i++) { + const checkboxes = screen.queryAllByRole('checkbox') + const radios = screen.queryAllByRole('radio') + if (checkboxes.length > 0) { + fireEvent.click(checkboxes[0]) + } else if (radios.length > 0) { + fireEvent.click(radios[0]) + } + const nextButton = screen.getByRole('button', { name: /siguiente/i }) + fireEvent.click(nextButton) + } + expect(screen.getByText('Procesador')).toBeInTheDocument() + expect(screen.getByText('RAM')).toBeInTheDocument() + expect(screen.getByText('Almacenamiento')).toBeInTheDocument() + expect(screen.getByText('Gráficos')).toBeInTheDocument() + }) + + it('shows reset button on results page', () => { + render() + // Answer all questions + for (let i = 0; i < 10; i++) { + const checkboxes = screen.queryAllByRole('checkbox') + const radios = screen.queryAllByRole('radio') + if (checkboxes.length > 0) { + fireEvent.click(checkboxes[0]) + } else if (radios.length > 0) { + fireEvent.click(radios[0]) + } + const nextButton = screen.getByRole('button', { name: /siguiente/i }) + fireEvent.click(nextButton) + } + expect(screen.getByRole('button', { name: /reiniciar/i })).toBeInTheDocument() + }) + + it('resets quiz when reset button is clicked', () => { + render() + // Answer all questions + for (let i = 0; i < 10; i++) { + const checkboxes = screen.queryAllByRole('checkbox') + const radios = screen.queryAllByRole('radio') + if (checkboxes.length > 0) { + fireEvent.click(checkboxes[0]) + } else if (radios.length > 0) { + fireEvent.click(radios[0]) + } + const nextButton = screen.getByRole('button', { name: /siguiente/i }) + fireEvent.click(nextButton) + } + // Click reset + const resetButton = screen.getByRole('button', { name: /reiniciar/i }) + fireEvent.click(resetButton) + // Should be back to first question + expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument() + expect(screen.queryByText(/tu recomendación técnica/i)).not.toBeInTheDocument() + }) +}) From 887834b8c2b8c6a3d814954b83dfa3ffd896eae1 Mon Sep 17 00:00:00 2001 From: FranGuh Date: Sat, 9 May 2026 18:51:01 -0600 Subject: [PATCH 5/8] feat: CI/CD pipeline with GitHub Actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create .github/workflows/ci.yml: lint → test → build - Node 22.x with npm caching - Triggers on push/PR to main - Add CI status badge to README.md - Update README with testing section and coverage stats --- .github/workflows/ci.yml | 37 +++++++++++++++++++++++++++++++++++++ README.md | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4fbd16a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [22.x] + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Run tests + run: npm run test + + - name: Build + run: npm run build diff --git a/README.md b/README.md index 9215ba0..d2094f4 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Computer Selector Helper 🖥️ +[![CI](https://github.com/FranGuh/ComputerSelectorHelper/actions/workflows/ci.yml/badge.svg)](https://github.com/FranGuh/ComputerSelectorHelper/actions/workflows/ci.yml) + > Respondé unas preguntas y recibí una recomendación personalizada de laptop según tu presupuesto y necesidades reales. -**[🌐 Ver en vivo](https://computer-selector-helper.vercel.app/)** +**[🌐 Ver en vivo](https://computerselectorhelper.vercel.app/)** ## ¿Qué es? @@ -47,11 +49,12 @@ Computer Selector Helper es un asistente web que ayuda a usuarios no técnicos a - **React Icons** (iconografía) - **Vercel Analytics** (métricas) - **ESLint 9** (linting) +- **Vitest** (testing) ## Cómo Usarlo ### Para Usuarios -1. Abrí la app en [computerselectorhelper.vercel.app](https://computer-selector-helper.vercel.app/) +1. Abrí la app en [computerselectorhelper.vercel.app](https://computerselectorhelper.vercel.app/) 2. Respondé las 10 preguntas del cuestionario 3. Revisá tu recomendación técnica 4. Explorá los modelos sugeridos o usá las specs como guía de compra @@ -75,6 +78,10 @@ npm run build # Linting npm run lint +# Testing +npm run test +npm run test:coverage + # Preview del build npm run preview ``` @@ -83,6 +90,11 @@ npm run preview ``` src/ +├── __tests__/ # Test suite (Vitest + RTL) +│ ├── convertToSpecs.test.jsx +│ ├── matchLaptopClass.test.jsx +│ ├── Recommendation.test.jsx +│ └── LaptopCard.test.jsx ├── components/ │ ├── ErrorBoundary/ # Manejo de errores │ ├── LaptopCard/ # Tarjeta de modelo de laptop @@ -115,6 +127,22 @@ El sistema usa un enfoque de scoring ponderado: 5. **Validaciones cruzadas**: macOS + gaming, ChromeOS + tareas pesadas, presupuesto bajo + specs altas 6. **Matching**: Filtrado estricto → scoring → top 3. Si no hay matches, fallback relajado (+30% presupuesto, GPU/OS menos estrictos) +## Testing + +```bash +# Run all tests +npm run test + +# Watch mode +npm run test:watch + +# Coverage report +npm run test:coverage +``` + +**Coverage targets**: 80% inference engine, 60% overall +**Current**: 90% statements, 85% branches, 86% functions + ## Base de Datos de Laptops 35+ modelos organizados por: From 1c44ece8f41bdcab1e236bd57a33cb90b492717a Mon Sep 17 00:00:00 2001 From: FranGuh Date: Sat, 9 May 2026 18:52:27 -0600 Subject: [PATCH 6/8] feat: pre-commit hooks with husky + lint-staged - Install husky and lint-staged - Configure lint-staged for src/**/*.{js,jsx} - Pre-commit hook runs eslint --fix on staged files - Fix unused imports in LaptopCard.test.jsx - Run eslint --fix on entire codebase to clean violations --- .husky/pre-commit | 1 + package-lock.json | 711 ++++++++++++++++++++++++++++++ package.json | 11 +- src/__tests__/LaptopCard.test.jsx | 4 +- 4 files changed, 724 insertions(+), 3 deletions(-) create mode 100644 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..2312dc5 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/package-lock.json b/package-lock.json index ad5511a..ac156ad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,9 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "husky": "^9.1.7", "jsdom": "^29.1.1", + "lint-staged": "^16.4.0", "vite": "^6.3.5", "vitest": "^4.1.5" } @@ -1852,6 +1854,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2042,6 +2060,39 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2060,6 +2111,23 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2257,6 +2325,13 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -2279,6 +2354,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2563,6 +2651,13 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true, + "license": "MIT" + }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -2792,6 +2887,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2934,6 +3042,22 @@ "node": ">= 0.8" } }, + "node_modules/husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true, + "license": "MIT", + "bin": { + "husky": "bin.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -3023,6 +3147,22 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3192,6 +3332,48 @@ "node": ">= 0.8.0" } }, + "node_modules/lint-staged": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^14.0.3", + "listr2": "^9.0.5", + "picomatch": "^4.0.3", + "string-argv": "^0.3.2", + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=20.17" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3213,6 +3395,56 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -3342,6 +3574,19 @@ "node": ">= 0.6" } }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3456,6 +3701,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3859,6 +4120,30 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.40.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", @@ -4125,6 +4410,49 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4157,6 +4485,62 @@ "dev": true, "license": "MIT" }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -4616,6 +5000,55 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -4639,6 +5072,22 @@ "dev": true, "license": "MIT" }, + "node_modules/yaml": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5667,6 +6116,15 @@ "uri-js": "^4.2.2" } }, + "ansi-escapes": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", + "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", + "dev": true, + "requires": { + "environment": "^1.0.0" + } + }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -5813,6 +6271,25 @@ "supports-color": "^7.1.0" } }, + "cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "requires": { + "restore-cursor": "^5.0.0" + } + }, + "cli-truncate": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", + "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", + "dev": true, + "requires": { + "slice-ansi": "^8.0.0", + "string-width": "^8.2.0" + } + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -5828,6 +6305,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -5977,6 +6466,12 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, + "emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true + }, "encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -5989,6 +6484,12 @@ "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true }, + "environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true + }, "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -6192,6 +6693,12 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true }, + "eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "dev": true + }, "eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -6354,6 +6861,12 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, + "get-east-asian-width": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", + "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", + "dev": true + }, "get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -6452,6 +6965,12 @@ "toidentifier": "1.0.1" } }, + "husky": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", + "dev": true + }, "iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -6515,6 +7034,15 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, + "is-fullwidth-code-point": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", + "dev": true, + "requires": { + "get-east-asian-width": "^1.3.1" + } + }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -6649,6 +7177,34 @@ "type-check": "~0.4.0" } }, + "lint-staged": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", + "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", + "dev": true, + "requires": { + "commander": "^14.0.3", + "listr2": "^9.0.5", + "picomatch": "^4.0.3", + "string-argv": "^0.3.2", + "tinyexec": "^1.0.4", + "yaml": "^2.8.2" + } + }, + "listr2": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", + "dev": true, + "requires": { + "cli-truncate": "^5.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + } + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -6664,6 +7220,37 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "requires": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "slice-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + } + } + } + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -6753,6 +7340,12 @@ "mime-db": "^1.54.0" } }, + "mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true + }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -6828,6 +7421,15 @@ "wrappy": "1" } }, + "onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "requires": { + "mimic-function": "^5.0.0" + } + }, "optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7094,6 +7696,22 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, + "restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "requires": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + } + }, + "rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true + }, "rollup": { "version": "4.40.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", @@ -7285,6 +7903,30 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "slice-ansi": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", + "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.3", + "is-fullwidth-code-point": "^5.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + } + } + }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -7309,6 +7951,39 @@ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true }, + "string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true + }, + "string-width": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", + "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", + "dev": true, + "requires": { + "get-east-asian-width": "^1.5.0", + "strip-ansi": "^7.1.2" + } + }, + "strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "requires": { + "ansi-regex": "^6.2.2" + }, + "dependencies": { + "ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true + } + } + }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -7553,6 +8228,36 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, + "wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "dev": true, + "requires": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true + }, + "string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "requires": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + } + } + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -7571,6 +8276,12 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "yaml": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", + "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 2fa2030..dc540aa 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "preview": "vite preview", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage" + "test:coverage": "vitest run --coverage", + "prepare": "husky" }, "dependencies": { "@vercel/analytics": "^1.5.0", @@ -32,8 +33,16 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", + "husky": "^9.1.7", "jsdom": "^29.1.1", + "lint-staged": "^16.4.0", "vite": "^6.3.5", "vitest": "^4.1.5" + }, + "lint-staged": { + "src/**/*.{js,jsx}": [ + "eslint --fix", + "git add" + ] } } diff --git a/src/__tests__/LaptopCard.test.jsx b/src/__tests__/LaptopCard.test.jsx index 4e01725..22726bd 100644 --- a/src/__tests__/LaptopCard.test.jsx +++ b/src/__tests__/LaptopCard.test.jsx @@ -1,5 +1,5 @@ -import { describe, it, expect, vi } from 'vitest' -import { render, screen, fireEvent } from '@testing-library/react' +import { describe, it, expect } from 'vitest' +import { render, screen } from '@testing-library/react' import LaptopCard from '../components/LaptopCard/LaptopCard' const mockModel = { From 2875bd57429b1ac4b0855f819b993fc3ffe36e8b Mon Sep 17 00:00:00 2001 From: FranGuh Date: Sat, 9 May 2026 19:38:09 -0600 Subject: [PATCH 7/8] feat: comparison view + shareable links (Phase 6 & 7) - Comparison component: side-by-side laptop comparison - Compare checkboxes on each laptop card (2-3 selection) - Sticky compare bar with laptop count - Spec comparison with better/worse highlighting - Shareable links: encode/decode results in URL - Copy link button with clipboard API - WhatsApp share button with pre-filled message - URL param decoding on mount to restore results - Responsive CSS for mobile comparison view --- src/components/Comparison/Comparison.css | 149 ++++ src/components/Comparison/Comparison.jsx | 76 ++ .../Recommendation/Recommendation.css | 788 +++++++----------- .../Recommendation/Recommendation.jsx | 328 ++++---- src/utils/shareResults.js | 17 + 5 files changed, 690 insertions(+), 668 deletions(-) create mode 100644 src/components/Comparison/Comparison.css create mode 100644 src/components/Comparison/Comparison.jsx create mode 100644 src/utils/shareResults.js diff --git a/src/components/Comparison/Comparison.css b/src/components/Comparison/Comparison.css new file mode 100644 index 0000000..13cb2a0 --- /dev/null +++ b/src/components/Comparison/Comparison.css @@ -0,0 +1,149 @@ +.ComparisonContainer { + max-width: 1000px; + margin: 0 auto; + padding: 2rem 1rem; +} + +.ComparisonBack { + background: none; + border: 2px solid var(--color-primary); + color: var(--color-primary); + padding: 0.5rem 1rem; + border-radius: 8px; + cursor: pointer; + font-size: 0.9rem; + font-weight: 600; + transition: background 0.2s, color 0.2s; + margin-bottom: 1.5rem; +} + +.ComparisonBack:hover { + background: var(--color-primary); + color: white; +} + +.ComparisonContainer h2 { + text-align: center; + margin-bottom: 1.5rem; + color: var(--color-text); +} + +.ComparisonGrid { + display: flex; + flex-direction: column; + gap: 1px; + background: var(--color-border); + border-radius: 12px; + overflow: hidden; + box-shadow: var(--shadow-md); +} + +.ComparisonLabels { + display: grid; + grid-template-columns: 180px repeat(var(--cols, 2), 1fr); + background: var(--color-surface); +} + +.ComparisonLabelHeader { + padding: 1rem; + font-weight: 700; + color: var(--color-text-secondary); + font-size: 0.85rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.ComparisonLaptopHeader { + padding: 1rem; + text-align: center; + border-left: 1px solid var(--color-border); +} + +.ComparisonLaptopHeader img { + width: 100%; + max-width: 180px; + height: auto; + border-radius: 8px; + margin-bottom: 0.5rem; + object-fit: cover; +} + +.ComparisonLaptopHeader h3 { + font-size: 0.95rem; + margin: 0.5rem 0 0.25rem; + color: var(--color-text); +} + +.ComparisonPrice { + font-weight: 700; + color: var(--color-primary); + font-size: 0.9rem; +} + +.ComparisonRow { + display: grid; + grid-template-columns: 180px repeat(var(--cols, 2), 1fr); + background: var(--color-surface); +} + +.ComparisonSpecLabel { + padding: 0.75rem 1rem; + font-weight: 600; + color: var(--color-text-secondary); + font-size: 0.9rem; + background: var(--color-bg-alt); + border-right: 1px solid var(--color-border); +} + +.ComparisonValue { + padding: 0.75rem 1rem; + font-size: 0.9rem; + color: var(--color-text); + border-left: 1px solid var(--color-border); + position: relative; +} + +.ComparisonValue.better { + background: var(--color-success-light); + font-weight: 600; +} + +.BetterBadge { + display: inline-block; + margin-left: 0.5rem; + font-size: 0.75rem; + color: var(--color-success); + font-weight: 700; +} + +@media (max-width: 768px) { + .ComparisonContainer { + padding: 1rem 0.5rem; + } + + .ComparisonLabels, + .ComparisonRow { + grid-template-columns: 120px repeat(var(--cols, 2), 1fr); + } + + .ComparisonLaptopHeader img { + max-width: 120px; + } + + .ComparisonLaptopHeader h3 { + font-size: 0.8rem; + } + + .ComparisonSpecLabel, + .ComparisonValue { + padding: 0.5rem; + font-size: 0.8rem; + } +} + +@media (max-width: 480px) { + .ComparisonLabels, + .ComparisonRow { + grid-template-columns: 100px repeat(var(--cols, 2), 1fr); + } +} diff --git a/src/components/Comparison/Comparison.jsx b/src/components/Comparison/Comparison.jsx new file mode 100644 index 0000000..7ea33af --- /dev/null +++ b/src/components/Comparison/Comparison.jsx @@ -0,0 +1,76 @@ +import LaptopCard from '../LaptopCard/LaptopCard' + +export default function Comparison({ laptops, onBack }) { + const specs = ['processor', 'ram', 'storage', 'gpu', 'os', 'portability', 'battery'] + const specLabels = { + processor: 'Procesador', + ram: 'RAM', + storage: 'Almacenamiento', + gpu: 'Gráficos', + os: 'Sistema operativo', + portability: 'Portabilidad', + battery: 'Batería', + } + + const parseNum = (str) => { + if (!str) return 0 + const nums = str.match(/\d+/g) + return nums ? nums.reduce((a, b) => a + parseInt(b), 0) : 0 + } + + const getBetter = (spec, a, b) => { + const va = a[spec] || '' + const vb = b[spec] || '' + if (va === vb) return 'equal' + const na = parseNum(va) + const nb = parseNum(vb) + if (spec === 'ram' || spec === 'storage' || spec === 'processor') { + return na > nb ? 'a' : na < nb ? 'b' : 'equal' + } + return 'equal' + } + + return ( +
+ +

Comparación de laptops

+
+
+
Especificación
+ {laptops.map((l, i) => ( +
+ {l.name} +

{l.name}

+

{l.price}

+
+ ))} +
+ {specs.map((spec) => { + const better = laptops.length === 2 ? getBetter(spec, laptops[0], laptops[1]) : 'equal' + return ( +
+
{specLabels[spec]}
+ {laptops.map((l, i) => { + const isBetter = better === 'a' && i === 0 || better === 'b' && i === 1 + return ( +
+ {l[spec] || '—'} + {isBetter && ✓ Mejor} +
+ ) + })} +
+ ) + })} +
+
Uso recomendado
+ {laptops.map((l, i) => ( +
{l.use}
+ ))} +
+
+
+ ) +} diff --git a/src/components/Recommendation/Recommendation.css b/src/components/Recommendation/Recommendation.css index 0ae21ca..374ea3a 100644 --- a/src/components/Recommendation/Recommendation.css +++ b/src/components/Recommendation/Recommendation.css @@ -1,634 +1,442 @@ :root { - --primary-color: #2563eb; - --primary-hover: #1d4ed8; - --light-bg: #f8fafc; - --border-color: #e2e8f0; - --text-dark: #1e293b; - --text-muted: #64748b; - --card-bg: #ffffff; + --primary-color: #2b7de9; + --light-bg: #f5f7fa; + --border-color: #dce3ec; + --text-dark: #333; + --card-bg: #ffffff; } - -/* Progress Bar */ -.QuizProgress { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--spacing-sm, 0.5rem); - margin-bottom: var(--spacing-lg, 1.5rem); - width: 100%; - max-width: 500px; -} - -.QuizProgressBar { - width: 100%; - height: 8px; - background-color: var(--border-color); - border-radius: var(--radius-md, 0.5rem); - overflow: hidden; -} - -.QuizProgressFill { - height: 100%; - background-color: var(--primary-color); - border-radius: var(--radius-md, 0.5rem); - transition: width 0.3s ease; -} - -.QuizProgressText { - font-size: var(--font-size-sm, 0.875rem); - color: var(--text-muted); - font-weight: 500; +body { + font-family: 'Segoe UI', sans-serif; + background-color: var(--light-bg); + color: var(--text-dark); } /* Preguntas */ .Question { - display: flex; - flex-direction: column; - align-items: center; - width: 90vw; - max-width: 800px; - padding: var(--spacing-xl, 2rem); - animation: fadeSlideIn 0.3s ease-out; -} - -@keyframes fadeSlideIn { - from { - opacity: 0; - transform: translateY(10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.Question h2 { - font-size: var(--font-size-2xl, 1.5rem); - text-align: center; - margin-bottom: var(--spacing-md, 1rem); - color: var(--text-dark); -} - -.Question p { - color: var(--text-muted); - margin-bottom: var(--spacing-lg, 1.5rem); + display: flex; + flex-direction: column; + align-items: center; + width: 90vw; + padding: 20px; } .Question__grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: var(--spacing-md, 1rem); - width: 100%; - justify-content: center; - align-items: center; - justify-items: center; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 5px; + /* margin-top: 20px; */ + width: 100%; + justify-content: center; + align-content: center; + align-items: center; + justify-items: center; } .Question__label { - display: flex; - flex-direction: row; - align-items: center; - gap: var(--spacing-md, 1rem); - background-color: var(--card-bg); - border: 2px solid var(--border-color); - border-radius: var(--radius-lg, 0.75rem); - padding: var(--spacing-lg, 1.5rem); - cursor: pointer; - transition: border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease; - height: 100%; - width: 100%; - max-width: 280px; - box-sizing: border-box; + display: flex; + flex-direction: row; + align-items: center; + gap: 10px; + background-color: var(--card-bg); + border: 2px solid var(--border-color); + border-radius: 10px; + padding: 20px; + cursor: pointer; + transition: border-color 0.3s ease, background-color 0.3s ease; + height: 100%; + width: 100%; + max-width: 280px; + box-sizing: border-box; } .Question__label:hover { - border-color: var(--primary-color); - background-color: #eff6ff; - box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05)); + border-color: var(--primary-color); + background-color: #eef4fe; } .Question__input { - accent-color: var(--primary-color); + /* margin-top: 4px; */ + accent-color: var(--primary-color); } .Question__input[type='checkbox']:checked+span, .Question__input[type='radio']:checked+span { - font-weight: bold; -} - -.Question__label:has(.Question__input:checked) { - border-color: var(--color-primary); - background-color: #eff6ff; - box-shadow: 0 0 0 1px var(--color-primary); -} - -/* Quiz Navigation Buttons */ -.QuizNav { - display: flex; - gap: var(--spacing-md, 1rem); - margin-top: var(--spacing-xl, 2rem); - align-items: center; - justify-content: center; -} - -.ButtonNext { - background: var(--primary-color); - color: var(--card-bg); - padding: var(--spacing-sm, 0.5rem) var(--spacing-xl, 2rem); - border-radius: var(--radius-md, 0.5rem); - border: none; - font-size: var(--font-size-base, 1rem); - font-weight: 600; - transition: background-color 0.2s ease, transform 0.2s ease; -} - -.ButtonNext:hover:not(:disabled) { - background: var(--primary-hover); - transform: translateY(-1px); -} - -.ButtonNext:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.ButtonBack { - background: transparent; - color: var(--text-muted); - padding: var(--spacing-sm, 0.5rem) var(--spacing-lg, 1.5rem); - border-radius: var(--radius-md, 0.5rem); - border: 1px solid var(--border-color); - font-size: var(--font-size-base, 1rem); - font-weight: 500; - transition: background-color 0.2s ease, color 0.2s ease; -} - -.ButtonBack:hover { - background: var(--light-bg); - color: var(--text-dark); + font-weight: bold; } /* Resultados */ .ResultContainer { - padding: var(--spacing-xl, 2rem) var(--spacing-lg, 1.5rem); - max-width: 1200px; - margin: 0 auto; - text-align: center; - background-color: var(--card-bg); - border-radius: var(--radius-lg, 0.75rem); - box-shadow: var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1)); - width: 100%; - box-sizing: border-box; -} - -.ResultContainer h2 { - font-size: var(--font-size-2xl, 1.5rem); - margin-bottom: var(--spacing-lg, 1.5rem); - color: var(--text-dark); -} - -.ResultContainer h4 { - font-size: var(--font-size-xl, 1.25rem); - margin: 0; - color: var(--text-dark); + padding: 20px 1rem; + max-width: 1200px; + margin: 0 auto; + text-align: center; + background-color: var(--card-bg); + border-radius: 10px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + width: 100dvw; + box-sizing: border-box; } .ResultIntro { - text-align: left; -} - -/* Section Title (replaces inline styles) */ -.SectionTitle { - text-align: center; - margin-bottom: var(--spacing-md, 1rem); - font-size: var(--font-size-xl, 1.25rem); - color: var(--text-dark); + text-align: left; } /* Tarjetas de specs */ .SpecGrid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: var(--spacing-lg, 1.5rem); - margin: var(--spacing-lg, 1.5rem) 0; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 20px; + margin: 20px 0; } .SpecCard { - display: flex; - flex-direction: column; - align-items: center; - background-color: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg, 0.75rem); - padding: var(--spacing-xl, 2rem); - text-align: center; - transition: box-shadow 0.2s ease, transform 0.2s ease; - margin: 0; + display: flex; + flex-direction: column; + align-items: center; + background-color: var(--card-bg); + border: 2px solid var(--border-color); + border-radius: 10px; + padding: 20px; + text-align: center; + transition: box-shadow 0.3s ease; + margin: 0; + animation: fadeSlideIn 0.6s ease forwards; } .SpecCard:hover { - box-shadow: var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1)); - transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); } .SpecIcon { - font-size: 30px; - color: var(--primary-color); + font-size: 30px; + color: var(--primary-color); } .SpecLabel { - color: var(--primary-color); - font-weight: 600; - font-size: var(--font-size-base, 1rem); - margin: var(--spacing-sm, 0.5rem) 0; + color: var(--primary-color); + font-weight: 600; + font-size: 16px; } .SpecTitle { - font-size: var(--font-size-lg, 1.125rem); - font-weight: 600; - color: var(--text-dark); + font-size: 18px; + font-weight: bold; + color: var(--text-dark); } /* Warnings */ .Warnings { - color: var(--color-error, #dc2626); - text-align: left; -} - -/* Toggle Section */ -.ToggleSection { - display: inline-flex; - align-items: center; - gap: var(--spacing-sm, 0.5rem); - background: transparent; - border: 1px solid var(--border-color); - border-radius: var(--radius-md, 0.5rem); - padding: var(--spacing-sm, 0.5rem) var(--spacing-md, 1rem); - font-size: var(--font-size-base, 1rem); - font-weight: 500; - color: var(--text-dark); - margin: var(--spacing-md, 1rem) 0; - transition: background-color 0.2s ease; -} - -.ToggleSection:hover { - background: var(--light-bg); + color: red; + text-align: left; } /* Botón colorido */ .btn-grad { - background: var(--color-primary); - color: white; - font-weight: bold; - padding: var(--spacing-sm, 0.5rem) var(--spacing-xl, 2rem); - border-radius: var(--radius-md, 0.5rem); - border: none; - cursor: pointer; - text-transform: uppercase; - box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05)); - transition: all 0.3s ease; - font-size: var(--font-size-base, 1rem); + background-image: linear-gradient(to right, #6a11cb 0%, #2575fc 100%); + font-weight: bold; + padding: 12px 32px; + border-radius: 8px; + color: #fff; + border: none; + cursor: pointer; + text-transform: uppercase; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); + transition: all 0.4s ease; } .btn-grad:hover { - background: var(--color-primary-hover); - transform: translateY(-1px); - box-shadow: var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1)); + background-position: right center; + opacity: 0.9; } .WarningGrid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: var(--spacing-lg, 1.5rem); - margin-top: var(--spacing-md, 1rem); + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 20px; + margin-top: 20px; } .WarningCard { - display: flex; - flex-direction: column; - align-items: center; - background-color: #fef2f2; - border: 1px solid #fecaca; - border-radius: var(--radius-lg, 0.75rem); - padding: var(--spacing-xl, 2rem); - text-align: center; - transition: box-shadow 0.2s ease, transform 0.2s ease; + display: flex; + flex-direction: column; + align-items: center; + background-color: #fff5f5; + border: 2px solid #f5c2c7; + border-radius: 10px; + padding: 20px; + text-align: center; + transition: box-shadow 0.3s ease; + animation: fadeSlideIn 0.6s ease forwards; } .WarningCard:hover { - box-shadow: 0 4px 12px rgba(239, 68, 68, 0.15); - transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(255, 0, 0, 0.1); } .WarningIcon { - font-size: 30px; - color: #dc2626; + font-size: 30px; + color: #d32f2f; } .WarningText { - font-size: var(--font-size-sm, 0.875rem); - color: #991b1b; - font-weight: 500; - margin-top: var(--spacing-sm, 0.5rem); - line-height: 1.5; + font-size: 15px; + color: #b71c1c; + font-weight: 500; } .ResultIntroCards { - display: flex; - flex-direction: column; - gap: var(--spacing-lg, 1.5rem); - margin: var(--spacing-xl, 2rem) 0; -} - -.InfoCardHeader { - display: flex; - align-items: center; - gap: var(--spacing-sm); - margin-bottom: var(--spacing-md); -} -.InfoCardHeader .InfoIcon { - font-size: 20px; - color: var(--color-primary); -} -.InfoCardHeader .InfoLabel { - margin: 0; + display: flex; + flex-direction: column; + /* grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); */ + gap: 20px; + margin: 30px 0; } .InfoCard { - background-color: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg, 0.75rem); - padding: var(--spacing-xl, 2rem); - text-align: left; - box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05)); - display: flex; - flex-direction: column; + background-color: var(--card-bg); + border: 2px solid var(--border-color); + border-radius: 10px; + padding: 20px; + text-align: left; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: row; + align-content: center; + justify-content: space-evenly; + align-items: center; } .InfoCard2 { - background-color: var(--card-bg); - border: 1px solid var(--border-color); - border-radius: var(--radius-lg, 0.75rem); - padding: var(--spacing-xl, 2rem); - text-align: left; - box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05)); - display: flex; - flex-direction: column; -} - -@media (min-width: 769px) { - .Question__label { - max-width: none; - } + background-color: var(--card-bg); + border: 2px solid var(--border-color); + border-radius: 10px; + padding: 20px; + text-align: left; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; + align-content: center; + justify-content: space-evenly; + align-items: center; } .InfoIcon { - font-size: 28px; - color: var(--primary-color); - flex-shrink: 0; + font-size: 28px; + color: var(--primary-color); } .InfoLabel { - font-size: var(--font-size-base, 1rem); - font-weight: 600; - color: var(--primary-color); - margin: 0; + font-size: 16px; + font-weight: bold; + color: var(--primary-color); } -.InfoCard p { - color: var(--text-dark); - margin: var(--spacing-xs, 0.25rem) 0 0; -} .InfoTable { - display: table; - width: 100%; - border-collapse: collapse; - border: var(--border-color) 1px solid; - margin-top: var(--spacing-md, 1rem); + display: table; + width: 100%; + border-collapse: collapse; + border: #ececec 1px solid; } .InfoRow { - display: table-row; + display: table-row; } .InfoCell { - display: table-cell; - padding: var(--spacing-md, 1rem) var(--spacing-sm, 0.5rem); - vertical-align: top; - border-bottom: 1px solid var(--border-color); - word-wrap: break-word; - overflow-wrap: break-word; + display: table-cell; + padding: 12px 0px; + vertical-align: top; + border-bottom: 1px solid var(--border-color); + word-wrap: break-word; + overflow-wrap: break-word; } .InfoRow.even { - background-color: var(--light-bg); + background-color: var(--light-bg); } .InfoRow.odd { - background-color: var(--card-bg); + background-color: #ffffff; } .InfoCell.question { - font-weight: 600; - width: 40%; - color: var(--text-dark); + font-weight: bold; + width: 40%; + color: var(--text-dark); } .InfoCell.answer { - width: 60%; - color: var(--text-muted); + width: 60%; + color: #555; } .reintentar { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - margin-top: var(--spacing-2xl, 3rem); - gap: var(--spacing-md, 1rem); + display: flex; + flex-direction: column; + justify-content: center; } -.reintentar p { - color: var(--text-muted); - font-size: var(--font-size-base, 1rem); +.Modelo__sugerido{ + font-size: 2rem; + font-weight: 900; } -.Modelo__sugerido { - font-size: var(--font-size-3xl, 1.875rem); - font-weight: 700; -} .RationaleGrid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: var(--spacing-lg, 1.5rem); - margin-top: var(--spacing-md, 1rem); + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 20px; + margin-top: 20px; } .RationaleCard { - display: flex; - flex-direction: column; - align-items: center; - background-color: #f0fdf4; - border: 1px solid #bbf7d0; - border-radius: var(--radius-lg, 0.75rem); - padding: var(--spacing-xl, 2rem); - text-align: center; - transition: box-shadow 0.2s ease, transform 0.2s ease; + display: flex; + flex-direction: column; + align-items: center; + background-color: #f0fff4; + border: 2px solid #a3d9a5; + border-radius: 10px; + padding: 20px; + text-align: center; + transition: box-shadow 0.3s ease; + animation: fadeSlideIn 0.6s ease forwards; } .RationaleCard:hover { - box-shadow: 0 4px 12px rgba(34, 197, 94, 0.15); - transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(72, 187, 120, 0.2); } .RationaleIcon { - font-size: 30px; - color: #16a34a; + font-size: 30px; + color: #2e7d32; } .RationaleText { - font-size: var(--font-size-sm, 0.875rem); - color: #166534; - font-weight: 500; - margin-top: var(--spacing-sm, 0.5rem); - line-height: 1.5; + font-size: 15px; + color: #2e7d32; + font-weight: 500; } + .SuggestedModelsGrid { display: flex; flex-wrap: wrap; - gap: var(--spacing-lg, 1.5rem); + gap: 20px; justify-content: center; } -.NoResults { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--spacing-md, 1rem); - padding: var(--spacing-xl, 2rem); - background: #fffbeb; - border: 1px solid #fde68a; - border-radius: var(--radius-lg, 0.75rem); - margin: var(--spacing-xl, 2rem) 0; - text-align: center; -} -.NoResultsIcon { font-size: 40px; color: #f59e0b; } -.NoResults h4 { color: #92400e; margin: 0; font-size: var(--font-size-lg); } -.NoResults p { color: #78350f; max-width: 500px; } -.NoResultsButton { - display: inline-flex; - align-items: center; - gap: var(--spacing-sm); - background: var(--color-primary); - color: white; - padding: var(--spacing-sm) var(--spacing-xl); - border-radius: var(--radius-md); - text-decoration: none; - font-weight: 600; - transition: background-color 0.2s ease, transform 0.2s ease; -} -.NoResultsButton:hover { - background: var(--color-primary-hover); - color: white; - transform: translateY(-1px); -} - -.FallbackOptions { - padding: var(--spacing-xl); - background: var(--color-surface); - border-radius: var(--radius-lg); - border: 1px solid var(--color-border); - margin: var(--spacing-lg) 0; -} -.FallbackHeader { - display: flex; - align-items: center; - gap: var(--spacing-sm); - margin-bottom: var(--spacing-lg); -} -.FallbackIcon { font-size: 24px; color: var(--color-warning); } -.FallbackHeader h4 { margin: 0; color: var(--color-text); } -.FallbackChoice { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--spacing-lg); -} -.FallbackCard { - padding: var(--spacing-lg); - background: var(--color-background); - border-radius: var(--radius-md); - border: 1px solid var(--color-border); - text-align: center; -} -.FallbackCard h5 { margin: 0 0 var(--spacing-sm); font-size: var(--font-size-lg); } -.FallbackCard p { color: var(--color-text-muted); font-size: var(--font-size-sm); margin-bottom: var(--spacing-md); } -.FallbackButton { - display: inline-flex; - align-items: center; - justify-content: center; - padding: var(--spacing-sm) var(--spacing-lg); - border-radius: var(--radius-md); - text-decoration: none; - font-weight: 600; - font-size: var(--font-size-sm); - transition: all 0.2s ease; - cursor: pointer; - border: none; -} -.FallbackButton { - background: var(--color-primary); - color: white; -} -.FallbackButton:hover { - background: var(--color-primary-hover); - color: white; - transform: translateY(-1px); -} -.FallbackButtonSecondary { - background: var(--color-surface); - color: var(--color-primary); - border: 2px solid var(--color-primary); -} -.FallbackButtonSecondary:hover { - background: var(--color-primary); - color: white; -} -.ApproximateSection { - margin-top: var(--spacing-xl); - padding-top: var(--spacing-lg); - border-top: 1px solid var(--color-border); -} -.ApproximateSection h5 { - text-align: center; - color: var(--color-text-muted); - margin-bottom: var(--spacing-lg); -} -.LaptopCardWrapper { - position: relative; -} -.LaptopCardWrapper.Approximate { - opacity: 0.85; -} -.ApproximateBadge { - position: absolute; - top: 8px; - right: 8px; - background: var(--color-warning); - color: white; - padding: 2px 8px; - border-radius: 12px; - font-size: 11px; - font-weight: 600; +@keyframes fadeSlideIn { + 0% { + opacity: 0; + transform: translateY(20px); + } + 100% { + opacity: 1; + transform: translateY(0); + } } -@media (max-width: 768px) { - .FallbackChoice { - grid-template-columns: 1fr; - } +.LaptopCardWrapper { + position: relative; + animation: fadeSlideIn 0.4s ease-out; +} + +.CompareCheckbox { + display: flex; + align-items: center; + justify-content: center; + gap: 6px; + margin-top: 8px; + font-size: 0.85rem; + color: var(--primary-color); + cursor: pointer; + font-weight: 600; +} + +.CompareCheckbox input { + accent-color: var(--primary-color); + width: 16px; + height: 16px; +} + +.CompareBar { + position: sticky; + bottom: 20px; + display: flex; + justify-content: center; + margin-top: 20px; + animation: fadeSlideIn 0.3s ease-out; +} + +.CompareBtn { + display: flex; + align-items: center; + gap: 8px; + background: var(--primary-color); + color: white; + border: none; + padding: 12px 24px; + border-radius: 10px; + font-size: 1rem; + font-weight: 700; + cursor: pointer; + box-shadow: 0 4px 12px rgba(43, 125, 233, 0.3); + transition: transform 0.2s, box-shadow 0.2s; +} + +.CompareBtn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 16px rgba(43, 125, 233, 0.4); +} + +.ShareButtons { + display: flex; + justify-content: center; + gap: 12px; + margin-top: 24px; + flex-wrap: wrap; +} + +.ShareBtn { + display: flex; + align-items: center; + gap: 8px; + background: var(--card-bg); + color: var(--text-dark); + border: 2px solid var(--border-color); + padding: 10px 20px; + border-radius: 10px; + font-size: 0.9rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} + +.ShareBtn:hover { + border-color: var(--primary-color); + color: var(--primary-color); +} + +.ShareBtn.whatsapp { + background: #25d366; + color: white; + border-color: #25d366; +} + +.ShareBtn.whatsapp:hover { + background: #1fb855; +} + +@media (max-width: 480px) { + .ShareButtons { + flex-direction: column; + align-items: center; + } + .ShareBtn { + width: 100%; + max-width: 280px; + justify-content: center; + } } - - diff --git a/src/components/Recommendation/Recommendation.jsx b/src/components/Recommendation/Recommendation.jsx index 3648ef0..31a4e48 100644 --- a/src/components/Recommendation/Recommendation.jsx +++ b/src/components/Recommendation/Recommendation.jsx @@ -1,58 +1,51 @@ import React, { useState, useEffect } from 'react' -import { Helmet } from 'react-helmet-async' import questions from '../../constants/questions' import convertToSpecs from '../../utils/convertToSpecs' +import { encodeResults, decodeResults } from '../../utils/shareResults' import './Recommendation.css' import LaptopCard from '../LaptopCard/LaptopCard' +import Comparison from '../Comparison/Comparison' import { - FaMicrochip, - FaMemory, - FaHdd, - FaLaptop, - FaRegHandPointer, - FaSuitcase, - FaBatteryFull, - FaWindows, - FaExclamationTriangle, - FaCheckCircle, - FaClipboardList + FaMicrochip, // Procesador + FaMemory, // RAM + FaHdd, // Almacenamiento + FaLaptop, // GPU + FaRegHandPointer, // Pantalla táctil + FaSuitcase, // Portabilidad + FaBatteryFull, // Batería + FaWindows // Sistema operativo (Windows genérico), + , FaExclamationTriangle, + FaCheckCircle, FaClipboardList, FaCopy, FaShare, FaBalanceScale } from 'react-icons/fa' -function Recommendation() { - const loadSavedState = () => { - try { - const saved = localStorage.getItem('csh_quiz_state') - if (saved) { - const parsed = JSON.parse(saved) - return { - answers: parsed.answers || {}, - step: parsed.step || 0, - result: parsed.result || null, - } - } - } catch { - // ignore parse errors - } - return { answers: {}, step: 0, result: null } - } - const [answers, setAnswers] = useState(loadSavedState().answers) - const [step, setStep] = useState(loadSavedState().step) - const [result, setResult] = useState(loadSavedState().result) - const [showWarnings, setShowWarnings] = useState(true) - const [showRationale, setShowRationale] = useState(false) - const [showApproximate, setShowApproximate] = useState(false) +function Recommendation() { + const [answers, setAnswers] = useState({}) + const [step, setStep] = useState(0) + const [result, setResult] = useState(null) + const [compareIds, setCompareIds] = useState([]) + const [showComparison, setShowComparison] = useState(false) + const [copied, setCopied] = useState(false) useEffect(() => { - localStorage.setItem('csh_quiz_state', JSON.stringify({ answers, step, result })) - }, [answers, step, result]) + const params = new URLSearchParams(window.location.search) + const encoded = params.get('r') + if (encoded) { + const decoded = decodeResults(encoded) + if (decoded && decoded.a && decoded.r) { + setAnswers(decoded.a) + setResult(decoded.r) + } + } + }, []) const current = questions[step] const handleCheckboxChange = (questionId, value) => { + // Si estaba seleccionada la opción "full_use", la eliminamos if (answers[questionId] === 'full_use') { setAnswers(prev => ({ ...prev, @@ -100,25 +93,59 @@ function Recommendation() { setAnswers({}) setStep(0) setResult(null) - localStorage.removeItem('csh_quiz_state') + setCompareIds([]) + setShowComparison(false) + window.history.replaceState({}, '', window.location.pathname) + } + + const toggleCompare = (id) => { + setCompareIds(prev => { + if (prev.includes(id)) return prev.filter(i => i !== id) + if (prev.length >= 3) return prev + return [...prev, id] + }) + } + + const handleShare = async () => { + const encoded = encodeResults(answers, result) + if (!encoded) return + const url = `${window.location.origin}${window.location.pathname}?r=${encoded}` + try { + await navigator.clipboard.writeText(url) + setCopied(true) + setTimeout(() => setCopied(false), 2000) + } catch { + window.prompt('Copiá este link:', url) + } + } + + const handleWhatsAppShare = () => { + const encoded = encodeResults(answers, result) + if (!encoded) return + const url = `${window.location.origin}${window.location.pathname}?r=${encoded}` + const text = `Mirá las laptops que me recomiendan: ${url}` + window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank') + } + + const handleBackFromComparison = () => { + setShowComparison(false) + } + + if (showComparison && result?.laptopClass) { + const selected = result.laptopClass.filter(m => compareIds.includes(m.id)) + if (selected.length >= 2) { + return + } } if (result) { return (
- - Tu recomendación — Computer Selector Helper - - - -

Tu recomendación técnica:

-
- -

Usos seleccionados

-
+ +

Usos seleccionados

{answers.mainUse === 'full_use' ? 'Uso exigente (todo lo anterior)' @@ -131,10 +158,8 @@ function Recommendation() {

-
- -

Tus respuestas

-
+ +

Tus respuestas

{questions.map((q, index) => { const answer = answers[q.id] @@ -168,7 +193,7 @@ function Recommendation() {
-

Especificaciones Recomendadas

+

💻 Especificaciones Recomendadas

@@ -192,7 +217,7 @@ function Recommendation() {
-

Especificaciones Extra

+

💻🎯 Especificaciones Extra

@@ -219,117 +244,77 @@ function Recommendation() { {result.warnings.length > 0 && ( <> - - {showWarnings && ( -
- {result.warnings.map((warning, index) => ( -
- -

{warning}

-
- ))} -
- )} +

+ ⚠️ Advertencias importantes: +

+
+ {result.warnings.map((warning, index) => ( +
+ +

{warning}

+
+ ))} +
)} {result.rationale.length > 0 && ( - <> - - {showRationale && ( -
- {result.rationale.map((reason, index) => ( -
- -

{reason}

-
- ))} -
- )} - - )} - - - {(!result.laptopClass || result.laptopClass.length === 0) && result.approximateClass && result.approximateClass.length > 0 && ( -
-
- -

No encontramos matches exactos para tus necesidades

-
- -
-
-
Usá las specs como guía de compra
-

Revisá las especificaciones recomendadas de arriba y usalas como referencia al buscar en tiendas.

- - Buscar en Google con estas specs - -
- -
-
Ver equipos aproximados
-

Estos modelos se acercan a lo que necesitás, aunque no cumplen todos los requisitos.

- -
-
- - {showApproximate && ( -
-
Modelos aproximados (pueden no cumplir todos tus requisitos)
-
- {result.approximateClass.map((model, index) => ( -
- - Aproximado -
- ))} + <> +

+ 🧠 Justificación técnica: +

+
+ {result.rationale.map((reason, index) => ( +
+ +

{reason}

-
- )} -
+ ))} +
+ )} - {(!result.laptopClass || result.laptopClass.length === 0) && (!result.approximateClass || result.approximateClass.length === 0) && ( -
- -

Por el momento no se encuentra un equipo con las especificaciones recomendadas

-

Usá las especificaciones de arriba como guía al buscar en tiendas.

- - Buscar en Google con estas specs - -
- )} {result.laptopClass && Array.isArray(result.laptopClass) && ( - <> -

Modelos sugeridos (referencia):

-
- {result.laptopClass.map((model, index) => ( - - ))} + <> +

🎯 Modelos sugeridos (Puede haber alguno o no es solo referencia):

+
+ {result.laptopClass.map((model, index) => ( +
+ +
- + ))} +
+ {compareIds.length >= 2 && ( +
+ +
+ )} +
+ + +
+ )}

¿Querés cambiar tus respuestas o probar otra combinación?

- +
) @@ -338,12 +323,6 @@ function Recommendation() { return (
-
-
-
-
- Pregunta {step + 1} de {questions.length} -

{current.question}

{current.info &&

{current.info}

} @@ -353,10 +332,10 @@ function Recommendation() { handleCheckboxChange(current.id, opt.value)} /> - {' '}{opt.label} + {' '}{opt.label} ))} {current.type === 'checkbox' && current.extraRadio && ( @@ -372,7 +351,7 @@ function Recommendation() { })) } /> - {' '}{current.extraRadio.label} + {' '}{current.extraRadio.label} )} @@ -386,29 +365,22 @@ function Recommendation() { checked={answers[current.id] === opt.value} onChange={() => handleRadioChange(current.id, opt.value)} /> - {' '}{opt.label} + {' '}{opt.label} ))}
-
- {step > 0 && ( - - )} - -
+
) } diff --git a/src/utils/shareResults.js b/src/utils/shareResults.js new file mode 100644 index 0000000..cfcb784 --- /dev/null +++ b/src/utils/shareResults.js @@ -0,0 +1,17 @@ +export const encodeResults = (answers, result) => { + try { + const data = JSON.stringify({ a: answers, r: result }) + return btoa(encodeURIComponent(data)) + } catch { + return null + } +} + +export const decodeResults = (encoded) => { + try { + const json = decodeURIComponent(atob(encoded)) + return JSON.parse(json) + } catch { + return null + } +} From 78edd575c83bd48c3ce2ea965c3ab473e52aa209 Mon Sep 17 00:00:00 2001 From: FranGuh Date: Mon, 11 May 2026 00:44:44 -0600 Subject: [PATCH 8/8] feat: UX/UI polish, mobile responsive fixes, global styling, and modern Landing page --- .github/workflows/ci.yml | 37 - .gitignore | Bin 267 -> 305 bytes .husky/pre-commit | 1 - README.md | 32 +- .../foundation-testing-ci/acceptance.md | 121 -- .../tracks/foundation-testing-ci/plan.md | 134 -- .../tracks/foundation-testing-ci/risks.md | 105 -- .../tracks/foundation-testing-ci/spec.md | 64 - .../tracks/foundation-testing-ci/tasks.md | 121 -- coverage/base.css | 224 ++++ coverage/block-navigation.js | 87 ++ coverage/clover.xml | 219 ++++ coverage/convertToSpecs.jsx.html | 1162 +++++++++++++++++ coverage/coverage-final.json | 4 + coverage/favicon.png | Bin 0 -> 445 bytes coverage/index.html | 146 +++ coverage/laptopModels.js.html | 898 +++++++++++++ coverage/matchLaptopClass.jsx.html | 367 ++++++ coverage/prettify.css | 1 + coverage/prettify.js | 2 + coverage/sort-arrow-sprite.png | Bin 0 -> 138 bytes coverage/sorter.js | 210 +++ docs/FEATURE_COMPARE.md | 158 +++ docs/FEATURE_PLANNING_PHASE2.md | 228 ---- docs/FIXES_2026-05-10.md | 48 + docs/FIXES_COMPARE_2026-05-10.md | 97 ++ docs/FIXES_UX_2026-05-10.md | 23 + docs/FIXES_UX_PART2_2026-05-10.md | 44 + docs/HANDOFF_AGENT_2026-05-10.md | 197 +++ docs/README.md | 10 + src/notas.jsx => docs/notas_dev.md | 0 package-lock.json | 803 +----------- package.json | 11 +- pnpm-lock.yaml | 904 +++++++++++++ pnpm-workspace.yaml | 4 +- public/robots.txt | 2 + public/sitemap.xml | 4 +- src/App.css | 73 +- src/App.jsx | 17 +- src/__tests__/LaptopCard.test.jsx | 63 - src/__tests__/Recommendation.test.jsx | 180 --- src/__tests__/convertToSpecs.test.jsx | 345 ----- src/__tests__/matchLaptopClass.test.jsx | 310 ----- src/__tests__/setup.js | 1 - src/components/Comparison/Comparison.css | 149 --- src/components/Comparison/Comparison.jsx | 76 -- src/components/LaptopCard/LaptopCard.jsx | 7 +- .../Recommendation/Recommendation.css | 943 ++++++++----- .../Recommendation/Recommendation.jsx | 434 ++++-- src/index.css | 27 + src/pages/Compare/Compare.css | 523 ++++++++ src/pages/Compare/Compare.jsx | 466 +++++++ src/pages/Landing/Landing.css | 296 ++--- src/pages/Landing/Landing.jsx | 95 +- src/pages/NotFound/NotFound.css | 44 + src/pages/NotFound/NotFound.jsx | 27 + src/pages/Quiz/Quiz.css | 19 +- src/routes/AppRoutes.jsx | 7 +- src/utils/convertToSpecs.jsx | 26 +- src/utils/laptopModels.js | 16 +- src/utils/matchLaptopClass.jsx | 147 +-- src/utils/osDetector.js | 38 + src/utils/parseSpecs.js | 102 ++ src/utils/shareResults.js | 17 - src/utils/shareUtils.js | 61 + vitest.config.js | 12 - 66 files changed, 7404 insertions(+), 3585 deletions(-) delete mode 100644 .github/workflows/ci.yml delete mode 100644 .husky/pre-commit delete mode 100644 conductor/tracks/foundation-testing-ci/acceptance.md delete mode 100644 conductor/tracks/foundation-testing-ci/plan.md delete mode 100644 conductor/tracks/foundation-testing-ci/risks.md delete mode 100644 conductor/tracks/foundation-testing-ci/spec.md delete mode 100644 conductor/tracks/foundation-testing-ci/tasks.md create mode 100644 coverage/base.css create mode 100644 coverage/block-navigation.js create mode 100644 coverage/clover.xml create mode 100644 coverage/convertToSpecs.jsx.html create mode 100644 coverage/coverage-final.json create mode 100644 coverage/favicon.png create mode 100644 coverage/index.html create mode 100644 coverage/laptopModels.js.html create mode 100644 coverage/matchLaptopClass.jsx.html create mode 100644 coverage/prettify.css create mode 100644 coverage/prettify.js create mode 100644 coverage/sort-arrow-sprite.png create mode 100644 coverage/sorter.js create mode 100644 docs/FEATURE_COMPARE.md delete mode 100644 docs/FEATURE_PLANNING_PHASE2.md create mode 100644 docs/FIXES_2026-05-10.md create mode 100644 docs/FIXES_COMPARE_2026-05-10.md create mode 100644 docs/FIXES_UX_2026-05-10.md create mode 100644 docs/FIXES_UX_PART2_2026-05-10.md create mode 100644 docs/HANDOFF_AGENT_2026-05-10.md create mode 100644 docs/README.md rename src/notas.jsx => docs/notas_dev.md (100%) delete mode 100644 src/__tests__/LaptopCard.test.jsx delete mode 100644 src/__tests__/Recommendation.test.jsx delete mode 100644 src/__tests__/convertToSpecs.test.jsx delete mode 100644 src/__tests__/matchLaptopClass.test.jsx delete mode 100644 src/__tests__/setup.js delete mode 100644 src/components/Comparison/Comparison.css delete mode 100644 src/components/Comparison/Comparison.jsx create mode 100644 src/pages/Compare/Compare.css create mode 100644 src/pages/Compare/Compare.jsx create mode 100644 src/pages/NotFound/NotFound.css create mode 100644 src/pages/NotFound/NotFound.jsx create mode 100644 src/utils/osDetector.js create mode 100644 src/utils/parseSpecs.js delete mode 100644 src/utils/shareResults.js create mode 100644 src/utils/shareUtils.js delete mode 100644 vitest.config.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 4fbd16a..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - build: - runs-on: ubuntu-latest - - strategy: - matrix: - node-version: [22.x] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Lint - run: npm run lint - - - name: Run tests - run: npm run test - - - name: Build - run: npm run build diff --git a/.gitignore b/.gitignore index 31598f1b1d2f1e2833c00d326c11b66cc9c06db0..b67f46b212d141a89ca50c99e23ca5f52c023615 100644 GIT binary patch literal 305 zcmZXOK~BUl3`M=RhQCF;%LJUD<61ZjW1F0c`w#694yeNH01# zVcX}i3ni54b}Ib34BPQY_>oQiU$B|l_r~-$(%2;b$^=#mUvFBL#?e@+&tLQ8ZiHKR zpBrJvvJw&=(sQY^S}N7J4^!DwmXhA;4(L|zgLsY)KKyuopIN-BcsIIAz-r+f@^rag o0>N94FS?1$t4B7>=#%NVKefoj#{H472Za?F3^EL%XCOfM1SC6K7XSbN literal 267 zcmZWiI}XAy4BdT-pl*>Gj=%yNTMvLjW2#iCUD;`Y+mjR_Az0qW_Va7ZnFobb!MZ{~ z^V9~sE<<$|o?htBqX^4;KmXh(6!PBzAP#t$I4lVjLe#uLt14S#?-Mwr6?$G#{D1(B zIfa;6Oo)Pg5fRmR3g#B;54UPxe(o*oJ`x`pNh6cGQJKKSfF9hjyL2UvrAQYpNO@IT PdDK~Wtn)+_?+17Rexg~H diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100644 index 2312dc5..0000000 --- a/.husky/pre-commit +++ /dev/null @@ -1 +0,0 @@ -npx lint-staged diff --git a/README.md b/README.md index d2094f4..9215ba0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ # Computer Selector Helper 🖥️ -[![CI](https://github.com/FranGuh/ComputerSelectorHelper/actions/workflows/ci.yml/badge.svg)](https://github.com/FranGuh/ComputerSelectorHelper/actions/workflows/ci.yml) - > Respondé unas preguntas y recibí una recomendación personalizada de laptop según tu presupuesto y necesidades reales. -**[🌐 Ver en vivo](https://computerselectorhelper.vercel.app/)** +**[🌐 Ver en vivo](https://computer-selector-helper.vercel.app/)** ## ¿Qué es? @@ -49,12 +47,11 @@ Computer Selector Helper es un asistente web que ayuda a usuarios no técnicos a - **React Icons** (iconografía) - **Vercel Analytics** (métricas) - **ESLint 9** (linting) -- **Vitest** (testing) ## Cómo Usarlo ### Para Usuarios -1. Abrí la app en [computerselectorhelper.vercel.app](https://computerselectorhelper.vercel.app/) +1. Abrí la app en [computerselectorhelper.vercel.app](https://computer-selector-helper.vercel.app/) 2. Respondé las 10 preguntas del cuestionario 3. Revisá tu recomendación técnica 4. Explorá los modelos sugeridos o usá las specs como guía de compra @@ -78,10 +75,6 @@ npm run build # Linting npm run lint -# Testing -npm run test -npm run test:coverage - # Preview del build npm run preview ``` @@ -90,11 +83,6 @@ npm run preview ``` src/ -├── __tests__/ # Test suite (Vitest + RTL) -│ ├── convertToSpecs.test.jsx -│ ├── matchLaptopClass.test.jsx -│ ├── Recommendation.test.jsx -│ └── LaptopCard.test.jsx ├── components/ │ ├── ErrorBoundary/ # Manejo de errores │ ├── LaptopCard/ # Tarjeta de modelo de laptop @@ -127,22 +115,6 @@ El sistema usa un enfoque de scoring ponderado: 5. **Validaciones cruzadas**: macOS + gaming, ChromeOS + tareas pesadas, presupuesto bajo + specs altas 6. **Matching**: Filtrado estricto → scoring → top 3. Si no hay matches, fallback relajado (+30% presupuesto, GPU/OS menos estrictos) -## Testing - -```bash -# Run all tests -npm run test - -# Watch mode -npm run test:watch - -# Coverage report -npm run test:coverage -``` - -**Coverage targets**: 80% inference engine, 60% overall -**Current**: 90% statements, 85% branches, 86% functions - ## Base de Datos de Laptops 35+ modelos organizados por: diff --git a/conductor/tracks/foundation-testing-ci/acceptance.md b/conductor/tracks/foundation-testing-ci/acceptance.md deleted file mode 100644 index 35a3c9a..0000000 --- a/conductor/tracks/foundation-testing-ci/acceptance.md +++ /dev/null @@ -1,121 +0,0 @@ -# Acceptance Criteria — Foundation Testing CI - -## Testing Suite - -### Criteria -- [ ] `npm run test` runs all tests and passes -- [ ] Inference engine coverage ≥ 80% -- [ ] Overall project coverage ≥ 60% -- [ ] All 17 previously-fixed bugs have regression tests -- [ ] Edge cases tested: empty answers, conflicting answers, budget extremes -- [ ] Component tests cover: quiz flow, results rendering, fallback UI, error boundary -- [ ] Tests run in < 30 seconds on CI - -### Verification -```bash -npm run test -npm run test:coverage -``` - -## CI/CD Pipeline - -### Criteria -- [ ] GitHub Actions workflow triggers on push/PR to main -- [ ] Workflow runs: lint → test → build -- [ ] Workflow fails if any step errors -- [ ] Status badge visible in README.md -- [ ] Workflow completes in < 5 minutes -- [ ] Dependency caching works (second run is faster) - -### Verification -- Push to test branch, verify workflow runs in GitHub Actions tab -- Intentionally break a test, verify workflow fails -- Fix test, verify workflow passes - -## Pre-commit Hooks - -### Criteria -- [ ] `husky` installed and configured -- [ ] `lint-staged` runs ESLint on staged files only -- [ ] Commit with lint error → blocked -- [ ] Commit with clean code → succeeds -- [ ] `pre-push` hook runs `npm run build` -- [ ] Push with build error → blocked -- [ ] Existing codebase has zero lint violations - -### Verification -```bash -# Test lint blocking -echo "const x = " >> src/test.js -git add src/test.js -git commit -m "test: should fail" # Expected: blocked - -# Test build blocking -# Introduce syntax error, try to push # Expected: blocked -``` - -## Comparison View - -### Criteria -- [ ] "Compare" checkbox visible on each laptop card -- [ ] Selecting 2 laptops enables "Compare" button -- [ ] Selecting 3 laptops enables "Compare" button -- [ ] Clicking "Compare" opens `/compare` route -- [ ] Side-by-side table shows all specs for selected laptops -- [ ] Better specs highlighted in green, worse in red -- [ ] Mobile (320px): stacked cards, usable, no horizontal scroll -- [ ] Desktop (1024px): 2-3 column table -- [ ] Accessibility: screen reader announces comparison content -- [ ] Keyboard navigation works through comparison table -- [ ] Direct navigation to `/compare` without selection → redirects to results - -### Verification -- Complete quiz, select 2 laptops, click Compare -- Verify all specs shown, differences highlighted -- Resize to 320px, verify stacked layout -- Use keyboard Tab/Enter to navigate table -- Navigate directly to `/compare`, verify redirect - -## Shareable Links - -### Criteria -- [ ] "Copy link" button copies URL to clipboard -- [ ] "Share on WhatsApp" button opens WhatsApp with pre-filled message -- [ ] Pasting encoded URL in new tab restores results -- [ ] Invalid/expired URL → redirects to quiz with error message -- [ ] URL length < 2,000 characters for typical quiz results -- [ ] Clipboard copy shows success feedback ("Link copied!") -- [ ] WhatsApp message includes: results summary + link - -### Verification -```bash -# Test encoding -npm run dev -# Complete quiz, click "Copy link" -# Paste URL in new tab, verify results match - -# Test WhatsApp -# Click "Share on WhatsApp", verify message format - -# Test invalid URL -# Navigate to /results?r=invalid, verify redirect to quiz -``` - -## Overall Track Acceptance - -### Criteria -- [ ] All phases complete (1-7) -- [ ] `npm run build` passes -- [ ] `npm run lint` passes -- [ ] `npm run test` passes -- [ ] No new console warnings/errors -- [ ] No breaking changes to existing features -- [ ] All acceptance criteria met -- [ ] Documentation updated (README, docs/) - -### Final Verification -```bash -npm run lint && npm run test && npm run build -``` - -All three must pass with zero errors. diff --git a/conductor/tracks/foundation-testing-ci/plan.md b/conductor/tracks/foundation-testing-ci/plan.md deleted file mode 100644 index 881f3c5..0000000 --- a/conductor/tracks/foundation-testing-ci/plan.md +++ /dev/null @@ -1,134 +0,0 @@ -# Implementation Plan — Foundation Testing CI - -## Phase Order & Dependencies - -``` -Phase 1: Testing Setup (no dependencies) - ↓ -Phase 2: Inference Engine Tests (depends on Phase 1) - ↓ -Phase 3: Component Tests (depends on Phase 1) - ↓ -Phase 4: CI/CD Pipeline (depends on Phase 2+3 having tests) - ↓ -Phase 5: Pre-commit Hooks (depends on Phase 4) - ↓ -Phase 6: Comparison View (parallel with Phase 1-5) - ↓ -Phase 7: Shareable Links (parallel with Phase 1-5) -``` - -## Phase 1: Testing Setup - -**Files**: `vitest.config.js`, `package.json` (scripts), `src/__tests__/setup.js` -**Steps**: -1. Install: `npm i -D vitest @testing-library/react @testing-library/jest-dom jsdom` -2. Create `vitest.config.js` with `environment: 'jsdom'` -3. Add scripts: `"test": "vitest run"`, `"test:watch": "vitest"`, `"test:coverage": "vitest run --coverage"` -4. Create setup file for RTL matchers -5. Verify: `npm run test` runs with zero tests (passes) - -## Phase 2: Inference Engine Tests - -**Files**: `src/__tests__/convertToSpecs.test.jsx`, `src/__tests__/matchLaptopClass.test.jsx` -**Test Cases**: -- `convertToSpecs`: - - Basic quiz answers → valid spec profile - - Gaming use case → high GPU score, prioritizeGaming flag - - Content creation → high RAM, storage, screen quality flag - - Budget constraints → appropriate spec tier - - macOS + gaming → warning flag - - ChromeOS + heavy tasks → warning flag - - Empty answers → graceful fallback - - Conflicting answers → resolved spec profile - - Apple Silicon matching (M1/M2/M3 not stripped by normalize) - - RAM comparison (string vs number edge cases) -- `matchLaptopClass`: - - Strict matching returns correct models - - Relaxed matching widens budget ~30% - - GPU scoring tiers correct - - No matches → returns empty array (not generic class) - - OS filter works correctly - - Generic class excluded from OS filter - -## Phase 3: Component Tests - -**Files**: `src/__tests__/Recommendation.test.jsx`, `src/__tests__/LaptopCard.test.jsx` -**Test Cases**: -- `Recommendation`: - - Quiz renders with first question - - Answering questions advances step - - Back navigation works - - Results page shows laptop cards - - Fallback UI shows when no matches - - localStorage persistence (mock) - - ErrorBoundary catches render errors -- `LaptopCard`: - - Renders with all props - - Image fallback shows on error - - Match score displays correctly - - Price formatted in MXN - -## Phase 4: CI/CD Pipeline - -**Files**: `.github/workflows/ci.yml` -**Steps**: -1. Create workflow triggering on `push` and `pull_request` to `main` -2. Steps: checkout → setup Node 22.x → cache → `npm ci` → `npm run lint` → `npm run test` → `npm run build` -3. Add status badge to `README.md` -4. Verify: push to branch, confirm workflow runs - -## Phase 5: Pre-commit Hooks - -**Files**: `.husky/pre-commit`, `.husky/pre-push`, `package.json` (lint-staged config) -**Steps**: -1. Install: `npm i -D husky lint-staged` -2. Run: `npx husky init` -3. Configure `lint-staged` in `package.json`: `{"src/**/*.{js,jsx}": ["eslint --fix", "git add"]}` -4. Add `pre-push` hook: `npm run build` -5. Run `eslint --fix .` to clean existing violations first -6. Verify: commit with lint error → blocked - -## Phase 6: Comparison View - -**Files**: `src/components/Comparison/Comparison.jsx`, `src/components/Comparison/Comparison.css`, `src/routes/AppRoutes.jsx` (add route), `src/components/LaptopCard/LaptopCard.jsx` (add checkbox) -**Steps**: -1. Add "Compare" checkbox to `LaptopCard` (controlled by parent state) -2. Create `Comparison` component with side-by-side table -3. Highlight differences: compare each spec, mark better/worse -4. Add route `/compare` in `AppRoutes.jsx` -5. Mobile: stacked cards with sticky labels -6. Desktop: 2-3 column table -7. Accessibility: proper ``, `
`, `aria-label` -8. Test: select 2-3 laptops, verify comparison renders correctly - -## Phase 7: Shareable Links - -**Files**: `src/utils/encodeResults.js`, `src/utils/decodeResults.js`, `src/components/Recommendation/Recommendation.jsx` (add share buttons), `src/routes/AppRoutes.jsx` (handle URL params) -**Steps**: -1. Create `encodeResults(answers, result)` → base64 string -2. Create `decodeResults(encoded)` → parsed answers + result -3. Add URL param handling in `Recommendation.jsx`: if `?r=`, decode and show results -4. Add "Copy link" button → copies `window.location.href` to clipboard -5. Add "Share on WhatsApp" button → opens `wa.me/?text=` -6. Handle invalid/expired URLs → redirect to quiz with error message -7. Test: encode results, paste URL in new tab, verify results restore - -## Estimated Effort - -| Phase | Days | Complexity | -|-------|------|------------| -| 1. Testing Setup | 0.5 | Low | -| 2. Inference Tests | 1.5 | Medium | -| 3. Component Tests | 1 | Medium | -| 4. CI/CD Pipeline | 0.5 | Low | -| 5. Pre-commit Hooks | 0.5 | Low | -| 6. Comparison View | 1.5 | Medium | -| 7. Shareable Links | 1 | Medium | -| **Total** | **~6.5 days** | | - -## Parallel Execution - -- Phases 1-5 can run sequentially (foundation) -- Phases 6-7 can run in parallel with 1-5 (feature work, no dependencies on tests) -- Recommended: 2 agents — one on foundation (1-5), one on features (6-7) diff --git a/conductor/tracks/foundation-testing-ci/risks.md b/conductor/tracks/foundation-testing-ci/risks.md deleted file mode 100644 index a7b88c3..0000000 --- a/conductor/tracks/foundation-testing-ci/risks.md +++ /dev/null @@ -1,105 +0,0 @@ -# Risks — Foundation Testing CI - -## Risk Matrix - -| # | Risk | Likelihood | Impact | Mitigation | -|---|------|------------|--------|------------| -| 1 | Inference engine tests reveal more bugs | High | Medium | Fix bugs as found, add to test suite, document in track | -| 2 | Existing lint violations block pre-commit hooks | High | Low | Run `eslint --fix .` before enabling hooks, commit cleanup separately | -| 3 | URL-encoded results exceed browser limits | Medium | Medium | Use compression, fallback to localStorage if URL too long, warn user | -| 4 | Comparison view breaks on mobile with 3 laptops | Medium | Medium | Limit to 2 on mobile, stack vertically, test on 320px breakpoint | -| 5 | CI pipeline fails on first run due to environment differences | Medium | Low | Test workflow locally with `act` or push to test branch first | -| 6 | Test coverage target (80%) is too ambitious for initial suite | Medium | Low | Start with 60% target, increase incrementally, focus on critical paths | -| 7 | Shareable links break if inference logic changes | Low | Medium | Version the encoded data format, add validation on decode | -| 8 | Husky hooks slow down developer workflow | Low | Low | Keep hooks fast (lint only staged files), use `--no-verify` escape hatch | - -## Detailed Risks - -### R1: Inference Engine Tests Reveal More Bugs - -**Scenario**: After writing 15+ test cases for `convertToSpecs.jsx` and `matchLaptopClass.jsx`, edge cases that weren't caught during the 17-bug fix are discovered. - -**Impact**: Tests fail, track timeline extends by 1-2 days. - -**Mitigation**: -- Fix bugs as they're found, add regression tests immediately -- Document each new bug in the track's issue log -- If >5 new bugs found, split into separate bugfix track -- Keep failing tests as `test.skip()` with issue reference until fixed - -### R2: Existing Lint Violations Block Pre-commit Hooks - -**Scenario**: Enabling Husky + lint-staged reveals 50+ existing ESLint warnings/errors across the codebase. - -**Impact**: Developers can't commit until all violations are fixed. - -**Mitigation**: -- Run `eslint --fix .` BEFORE enabling hooks -- Commit cleanup as separate commit: `chore: fix all existing lint violations` -- Enable hooks only after codebase is clean -- Add `--no-verify` as temporary escape hatch for urgent commits - -### R3: URL-Encoded Results Exceed Browser Limits - -**Scenario**: Encoding all 10 quiz answers + result data creates a URL > 2,000 characters, exceeding some browser/proxy limits. - -**Impact**: Shareable links break for complex quiz results. - -**Mitigation**: -- Compress with `pako` (gzip) before base64 encoding -- If still too long, fall back to localStorage + short ID -- Warn user: "This link is long, consider using the copy button" -- Test with maximum data payload (all answers + 3 laptop results) - -### R4: Comparison View Breaks on Mobile with 3 Laptops - -**Scenario**: Side-by-side comparison of 3 laptops on 320px screen is unusable. - -**Impact**: Poor UX on mobile, accessibility issues. - -**Mitigation**: -- Limit to 2 laptops on mobile (disable 3rd checkbox) -- Stack cards vertically with sticky spec labels -- Use horizontal scroll with snap points as alternative -- Test on actual 320px device, not just DevTools - -### R5: CI Pipeline Fails on First Run - -**Scenario**: GitHub Actions workflow fails due to Node version mismatch, missing env vars, or different npm behavior. - -**Impact**: Wasted time debugging CI instead of building features. - -**Mitigation**: -- Use `actions/setup-node@v4` with explicit Node 22.x -- Cache `node_modules` with `actions/cache` -- Test workflow locally with `nektos/act` if available -- Push to test branch first, verify workflow passes before merging - -### R6: Test Coverage Target Too Ambitious - -**Scenario**: Reaching 80% coverage on inference engine requires mocking complex browser APIs or testing trivial code. - -**Impact**: Time spent on coverage metrics instead of meaningful tests. - -**Mitigation**: -- Start with 60% target, increase to 80% in follow-up -- Focus on critical paths: scoring, matching, edge cases -- Exclude trivial code (constants, simple getters) from coverage -- Use `// istanbul ignore next` for untestable browser-specific code - -## Rollback Plan - -If any phase introduces breaking changes: - -1. **Testing/CI**: Revert the workflow/hook commits — no user-facing impact -2. **Comparison View**: Remove `/compare` route and checkbox — existing features unaffected -3. **Shareable Links**: Remove URL decoding logic — existing quiz flow unchanged -4. **Nuclear option**: Revert entire track via `git revert` — all changes are additive, no breaking changes to existing code - -## Monitoring - -After merge: -- Monitor CI pass rate (should be >95%) -- Track test coverage trend (should increase, not decrease) -- Watch for shareable link errors in Vercel logs -- Monitor comparison page usage via Vercel Analytics diff --git a/conductor/tracks/foundation-testing-ci/spec.md b/conductor/tracks/foundation-testing-ci/spec.md deleted file mode 100644 index 060aff1..0000000 --- a/conductor/tracks/foundation-testing-ci/spec.md +++ /dev/null @@ -1,64 +0,0 @@ -# Track: Foundation — Testing + CI/CD + Comparison + Sharing - -## What - -Build the technical foundation for safe future development while delivering the two most-requested user-facing features: - -1. **Automated Testing Suite**: Vitest + React Testing Library with 80% coverage on inference engine -2. **CI/CD Pipeline**: GitHub Actions with lint → test → build on every PR, Husky pre-commit hooks -3. **Results Comparison View**: Side-by-side laptop comparison (2-3 models) with highlighted differences -4. **Shareable Results Link**: URL-encoded quiz results with "Copy link" and "Share on WhatsApp" buttons - -## Why - -- **17 inference bugs were fixed with zero tests** — regressions are guaranteed without a safety net -- **No CI/CD** — broken code deploys directly to production on every push to main -- **Users narrow down to 2-3 laptops** and need to compare specs before deciding (consensus from 3 agents) -- **Users want to share results** with family/friends via WhatsApp (critical for Mexican market) -- **Agent consensus**: Testing + CI/CD (3 agents), Comparison (3 agents), Sharing (2 agents) — highest priority features - -## Scope - -### Testing -- `vitest` + `@testing-library/react` + `@testing-library/jest-dom` + `jsdom` -- Unit tests for `convertToSpecs.jsx` — each question-to-spec mapping, edge cases -- Unit tests for `matchLaptopClass.jsx` — strict/relaxed matching, GPU scoring -- Component tests for `Recommendation.jsx` — quiz flow, results rendering, fallback -- Tests for `LaptopCard.jsx` — rendering, image fallback, props -- Coverage target: 80% on inference engine, 60% overall - -### CI/CD -- `.github/workflows/ci.yml` — triggers on push/PR to main -- Steps: `npm ci` → `npm run lint` → `npm run test` → `npm run build` -- Node 22.x with dependency caching -- Fail workflow on any step error -- `husky` + `lint-staged` — ESLint on staged files before commit -- `pre-push` hook — run `npm run build` to catch build-time failures - -### Comparison View -- "Compare" checkbox on each `LaptopCard` -- Floating bottom bar when 2-3 selected: "Compare X laptops" -- New route `/compare` with side-by-side table -- Highlight differences: green = better, red = worse -- Mobile: stacked cards with sticky spec labels -- Desktop: 2-3 column table with proper `` headers -- Accessible: `role="region"` per laptop, `aria-label`, text indicators ("✓ Better") - -### Shareable Links -- Encode quiz answers + results into URL query params -- Compression via `btoa(JSON.stringify())` to keep URLs reasonable -- "Copy link" button on results page -- "Share on WhatsApp" button with pre-filled message -- URL format: `/results?r=` -- Decode on load, restore results view -- Fallback: if URL is invalid, redirect to quiz - -## Out of Scope - -- TypeScript migration (separate track) -- Sentry error tracking (separate track) -- PWA / offline support (separate track) -- Price history / deal alerts (separate track) -- Multi-language / i18n (separate track) -- Buying guides / blog content (separate track) -- Dark mode (separate track) diff --git a/conductor/tracks/foundation-testing-ci/tasks.md b/conductor/tracks/foundation-testing-ci/tasks.md deleted file mode 100644 index 9bccc66..0000000 --- a/conductor/tracks/foundation-testing-ci/tasks.md +++ /dev/null @@ -1,121 +0,0 @@ -# Tasks — Foundation Testing CI - -## Phase 1: Testing Setup - -### Task 1.1: Install Testing Dependencies -- [ ] `npm i -D vitest @testing-library/react @testing-library/jest-dom jsdom` -- [ ] Verify installation: `npm ls vitest` - -### Task 1.2: Configure Vitest -- [ ] Create `vitest.config.js` with `environment: 'jsdom'` -- [ ] Add scripts to `package.json`: - - `"test": "vitest run"` - - `"test:watch": "vitest"` - - `"test:coverage": "vitest run --coverage"` -- [ ] Create `src/__tests__/setup.js` with RTL matchers -- [ ] Verify: `npm run test` passes with zero tests - -## Phase 2: Inference Engine Tests - -### Task 2.1: Test convertToSpecs.jsx -- [ ] Test: Basic quiz answers → valid spec profile -- [ ] Test: Gaming use case → high GPU score, `prioritizeGaming` flag -- [ ] Test: Content creation → high RAM, storage, `prioritizeScreenQuality` flag -- [ ] Test: Budget constraints → appropriate spec tier -- [ ] Test: macOS + gaming → warning flag -- [ ] Test: ChromeOS + heavy tasks → warning flag -- [ ] Test: Empty answers → graceful fallback -- [ ] Test: Conflicting answers → resolved spec profile -- [ ] Test: Apple Silicon matching (M1/M2/M3 not stripped) -- [ ] Test: RAM comparison (string vs number edge cases) - -### Task 2.2: Test matchLaptopClass.jsx -- [ ] Test: Strict matching returns correct models -- [ ] Test: Relaxed matching widens budget ~30% -- [ ] Test: GPU scoring tiers correct -- [ ] Test: No matches → returns empty array (not generic class) -- [ ] Test: OS filter works correctly -- [ ] Test: Generic class excluded from OS filter - -## Phase 3: Component Tests - -### Task 3.1: Test Recommendation.jsx -- [ ] Test: Quiz renders with first question -- [ ] Test: Answering questions advances step -- [ ] Test: Back navigation works -- [ ] Test: Results page shows laptop cards -- [ ] Test: Fallback UI shows when no matches -- [ ] Test: localStorage persistence (mock) -- [ ] Test: ErrorBoundary catches render errors - -### Task 3.2: Test LaptopCard.jsx -- [ ] Test: Renders with all props -- [ ] Test: Image fallback shows on error -- [ ] Test: Match score displays correctly -- [ ] Test: Price formatted in MXN - -## Phase 4: CI/CD Pipeline - -### Task 4.1: Create GitHub Actions Workflow -- [ ] Create `.github/workflows/ci.yml` -- [ ] Configure triggers: `push` and `pull_request` to `main` -- [ ] Steps: checkout → setup Node 22.x → cache → `npm ci` → `npm run lint` → `npm run test` → `npm run build` -- [ ] Add status badge to `README.md` -- [ ] Test: push to branch, confirm workflow runs and passes - -## Phase 5: Pre-commit Hooks - -### Task 5.1: Setup Husky + lint-staged -- [ ] Install: `npm i -D husky lint-staged` -- [ ] Run: `npx husky init` -- [ ] Configure `lint-staged` in `package.json` -- [ ] Create `.husky/pre-commit` with lint-staged -- [ ] Create `.husky/pre-push` with `npm run build` -- [ ] Run `eslint --fix .` to clean existing violations -- [ ] Test: commit with lint error → blocked - -## Phase 6: Comparison View - -### Task 6.1: Add Compare Checkbox to LaptopCard -- [ ] Add checkbox prop to `LaptopCard` -- [ ] Controlled by parent state in `Recommendation.jsx` -- [ ] Floating bottom bar appears when 2-3 selected -- [ ] "Compare X laptops" button opens `/compare` route - -### Task 6.2: Create Comparison Component -- [ ] Create `src/components/Comparison/Comparison.jsx` -- [ ] Create `src/components/Comparison/Comparison.css` -- [ ] Side-by-side table with spec comparison -- [ ] Highlight differences: green = better, red = worse -- [ ] Mobile: stacked cards with sticky labels -- [ ] Desktop: 2-3 column table -- [ ] Accessibility: proper ``, `` por `
`, `aria-label` - -### Task 6.3: Add Comparison Route -- [ ] Add `/compare` route in `AppRoutes.jsx` -- [ ] Pass selected laptops via state or context -- [ ] Handle direct navigation (no laptops selected → redirect) -- [ ] Test: select 2-3 laptops, verify comparison renders - -## Phase 7: Shareable Links - -### Task 7.1: Create Encoding/Decoding Utilities -- [ ] Create `src/utils/encodeResults.js` -- [ ] Create `src/utils/decodeResults.js` -- [ ] Encode: `btoa(JSON.stringify({ answers, result }))` -- [ ] Decode: `JSON.parse(atob(encoded))` -- [ ] Add validation on decode -- [ ] Handle invalid/expired data gracefully - -### Task 7.2: Add URL Param Handling -- [ ] Modify `Recommendation.jsx` to check for `?r=` on mount -- [ ] If present, decode and restore results view -- [ ] If invalid, redirect to quiz with error message -- [ ] Test: encode results, paste URL in new tab, verify restore - -### Task 7.3: Add Share Buttons -- [ ] "Copy link" button → copies `window.location.href` to clipboard -- [ ] "Share on WhatsApp" button → opens `wa.me/?text=` -- [ ] Message format: "Me recomiendan estas laptops: [link]" -- [ ] Test: copy link, paste in new tab, verify results -- [ ] Test: share on WhatsApp, verify message format diff --git a/coverage/base.css b/coverage/base.css new file mode 100644 index 0000000..f418035 --- /dev/null +++ b/coverage/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/coverage/block-navigation.js b/coverage/block-navigation.js new file mode 100644 index 0000000..530d1ed --- /dev/null +++ b/coverage/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/coverage/clover.xml b/coverage/clover.xml new file mode 100644 index 0000000..4d2dedb --- /dev/null +++ b/coverage/clover.xml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/coverage/convertToSpecs.jsx.html b/coverage/convertToSpecs.jsx.html new file mode 100644 index 0000000..5be3d81 --- /dev/null +++ b/coverage/convertToSpecs.jsx.html @@ -0,0 +1,1162 @@ + + + + + + Code coverage report for convertToSpecs.jsx + + + + + + + + + +
+
+

All files convertToSpecs.jsx

+
+ +
+ 92.99% + Statements + 146/157 +
+ + +
+ 88.26% + Branches + 173/196 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 93.42% + Lines + 142/152 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +272 +273 +274 +275 +276 +277 +278 +279 +280 +281 +282 +283 +284 +285 +286 +287 +288 +289 +290 +291 +292 +293 +294 +295 +296 +297 +298 +299 +300 +301 +302 +303 +304 +305 +306 +307 +308 +309 +310 +311 +312 +313 +314 +315 +316 +317 +318 +319 +320 +321 +322 +323 +324 +325 +326 +327 +328 +329 +330 +331 +332 +333 +334 +335 +336 +337 +338 +339 +340 +341 +342 +343 +344 +345 +346 +347 +348 +349 +350 +351 +352 +353 +354 +355 +356 +357 +358 +359 +360  +  +1x +5x +5x +5x +  +  +1x +4x +4x +4x +4x +4x +  +  +  +1x +35x +  +  +  +  +  +  +  +  +  +  +  +  +35x +  +  +35x +  +  +  +  +  +  +  +  +35x +  +  +  +35x +5x +  +  +35x +4x +  +  +  +  +35x +12x +12x +12x +12x +12x +5x +7x +1x +  +  +  +35x +6x +6x +6x +6x +6x +  +  +35x +1x +1x +  +  +35x +5x +5x +5x +  +  +35x +1x +1x +  +  +  +35x +2x +2x +  +  +35x +1x +1x +  +  +35x +1x +1x +  +  +35x +1x +  +  +35x +1x +  +  +  +35x +1x +1x +  +  +  +35x +  +  +  +  +35x +5x +  +30x +  +  +  +  +  +35x +2x +  +  +35x +  +  +  +  +2x +  +  +  +  +35x +  +35x +  +  +  +  +35x +  +  +  +  +35x +5x +  +  +  +35x +35x +  +  +35x +  +26x +26x +  +5x +5x +  +4x +4x +  +  +  +  +  +35x +5x +2x +  +5x +  +  +5x +  +  +  +5x +5x +5x +  +  +  +35x +  +  +  +  +  +  +  +  +  +  +1x +  +  +  +35x +4x +1x +  +4x +  +  +4x +1x +  +4x +1x +1x +1x +  +  +  +35x +  +  +  +35x +2x +  +  +  +35x +35x +  +  +  +  +35x +9x +  +  +  +  +  +7x +7x +  +  +9x +8x +8x +  +  +  +35x +35x +  +  +35x +35x +76x +76x +64x +59x +59x +  +  +35x +9x +  +9x +5x +  +  +9x +  +9x +9x +  +  +  +  +35x +  +  +18x +  +6x +  +  +  +35x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +35x +  +  +  +  +  +3x +  +  +  +35x +  +  +  +  +35x +  +  +  +  +  +35x +1x +  +  +35x +  +  +  +  +  +  +  +  +35x +  +  +  + 
import matchLaptopClass from './matchLaptopClass'
+ 
+const forceAppleSpecs = (specs, scores) => {
+  specs.processor = 'Apple M1 / M2 / M3'
+  specs.gpu = 'Apple Silicon GPU (M1/M2/M3)'
+  scores.graphics = 0
+}
+ 
+const forceChromeSpecs = (specs) => {
+  specs.processor = 'Intel Celeron / MediaTek'
+  specs.ram = '4 GB o 8 GB'
+  specs.gpu = 'Gráficos integrados básicos'
+  specs.storage = '64 GB eMMC o 128 GB SSD'
+  specs.rationale.push('ChromeOS está pensado para tareas básicas con hardware modesto.')
+}
+ 
+ 
+const convertToSpecs = (answers) => {
+  const specs = {
+    processor: '',
+    ram: '',
+    storage: '',
+    gpu: '',
+    touchscreen: false,
+    portability: '',
+    os: '',
+    battery: '',
+    rationale: [],
+    warnings: []
+  }
+ 
+  specs.budget = answers.budget
+ 
+ 
+  const scores = {
+    performance: 0,
+    graphics: 0,
+    multitasking: 0,
+    portability: 0,
+    battery: 0,
+    storage: 0
+  }
+ 
+  const uses = answers.mainUse === 'full_use'
+    ? ['basics', 'entertainment', 'gaming', 'work_school', 'creating', 'family']
+    : answers.mainUse || []
+ 
+  if (answers.system === 'mac') {
+    forceAppleSpecs(specs, scores)
+  }
+ 
+  if (answers.system === 'chrome') {
+    forceChromeSpecs(specs)
+  }
+ 
+ 
+  // ==== 1. Scoring según uso ====
+  if (uses.includes('gaming')) {
+    scores.performance += 3.5
+    scores.graphics += 4.5
+    scores.storage += 1
+    specs.rationale.push('Para gaming se requiere GPU dedicada y CPU potente.')
+    if (answers.gamesType === 'complex') {
+      scores.graphics += 3
+    } else if (answers.gamesType === 'simple') {
+      scores.graphics += 1
+    }
+  }
+ 
+  if (uses.includes('creating') || answers.photoVideo === 'pro') {
+    scores.performance += 3
+    scores.graphics += 2.5
+    scores.multitasking += 2
+    scores.storage += 2
+    specs.rationale.push('Creación de contenido profesional requiere potencia y espacio.')
+  }
+ 
+  if (answers.photoVideo === 'basic') {
+    scores.graphics += 1
+    scores.storage += 1
+  }
+ 
+  if (uses.includes('work_school') || answers.workload === 'heavy') {
+    scores.performance += 2.5
+    scores.multitasking += 2.5
+    specs.rationale.push('Trabajo exigente necesita buena CPU y multitarea.')
+  }
+ 
+  if (uses.includes('family')) {
+    scores.multitasking += 1
+    specs.rationale.push('Uso compartido en casa requiere buena capacidad multitarea.')
+  }
+ 
+ 
+  if (answers.webBrowsing === 'heavy') {
+    scores.multitasking += 1.5
+    specs.rationale.push('Muchas pestañas requieren buena RAM.')
+  }
+ 
+  if (answers.location === 'mobile') {
+    scores.portability += 3
+    specs.rationale.push('Necesitas algo ligero y fácil de transportar.')
+  }
+ 
+  if (answers.importance === 'touch') {
+    specs.touchscreen = true
+    specs.rationale.push('Requiere pantalla táctil.')
+  }
+ 
+  if (answers.importance === 'security') {
+    specs.rationale.push('Prioritás seguridad. Buscamos modelos con lector de huellas o reconocimiento facial si es posible.')
+  }
+ 
+  if (answers.importance === 'portability') {
+    specs.rationale.push('Buscás un equipo liviano. Se prioriza que pese menos de 1.5 kg y tenga una pantalla de 14" o menor.')
+  }
+ 
+ 
+  if (answers.battery === 'yes') {
+    scores.battery += 3
+    specs.rationale.push('Priorizás batería de larga duración.')
+  }
+ 
+  // ==== 2. Especificaciones recomendadas ====
+  specs.processor =
+    scores.performance >= 6 ? 'Intel i7 / Ryzen 7' :
+      scores.performance >= 4 ? 'Intel i5 / Ryzen 5' :
+        'Intel i3 / Ryzen 3 o Celeron'
+ 
+  if (answers.system === 'mac') {
+    specs.ram = scores.multitasking >= 4 ? '16 GB' : '8 GB'
+  } else {
+    specs.ram =
+      scores.multitasking >= 4 ? '16 GB' :
+        scores.multitasking >= 2 ? '12 GB' :
+          '8 GB'
+  }
+ 
+  if (answers.system === 'mac' && uses.includes('gaming')) {
+    specs.warnings.push('macOS no es compatible con la mayoría de juegos exigentes. Considerá Windows si es prioridad.')
+  }
+ 
+  if (answers.system === 'chrome' && (
+    answers.photoVideo === 'pro' ||
+    answers.workload === 'heavy' ||
+    uses.includes('gaming'))
+  ) {
+    specs.warnings.push('ChromeOS no es ideal para tareas pesadas o software profesional. Considerá Windows o macOS.')
+  }
+ 
+ 
+ 
+  specs.storage = scores.storage >= 2 ? '512 GB SSD' : '256 GB SSD'
+ 
+  Iif (specs.storage === '256 GB SSD' && uses.includes('creating')) {
+    specs.warnings.push('256 GB puede llenarse rápido con archivos de diseño o video. Considerá 512 GB o más.')
+  }
+ 
+ 
+  specs.gpu =
+    scores.graphics >= 4 ? 'NVIDIA RTX 3050 o superior' :
+      scores.graphics >= 2 ? 'Intel Iris Xe / Radeon Vega' :
+        'Gráficos integrados básicos'
+  // Override para macOS: siempre Apple Silicon
+  if (answers.system === 'mac') {
+    specs.gpu = 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)'
+  }
+ 
+ 
+  specs.portability = scores.portability >= 3 ? 'Alta (menos de 1.5kg, 14")' : 'Media o baja'
+  specs.battery = scores.battery >= 3 ? 'Alta (8h o más)' : 'Media (4-7h)'
+ 
+  // ==== 3. Sistema operativo filtrado ====
+  switch (answers.system) {
+    case 'windows':
+      specs.os = 'Windows 11'
+      break
+    case 'mac':
+      specs.os = 'macOS'
+      break
+    case 'chrome':
+      specs.os = 'ChromeOS'
+      break
+    default:
+      specs.os = 'Windows 11'
+  }
+ 
+  // ==== 4. Validaciones cruzadas ====
+  if (specs.os === 'macOS') {
+    if (uses.includes('gaming')) {
+      specs.warnings.push('macOS no es ideal para juegos exigentes ni es compatible con la mayoría de títulos AAA. Recomendamos Windows.')
+    }
+    Iif (uses.includes('gaming') && answers.system === 'chrome') {
+      specs.warnings.unshift('⚠️ Elegiste "juegos exigentes" pero seleccionaste ChromeOS, que no soporta juegos avanzados. Recomendamos Windows si querés jugar títulos como GTA, FIFA o similares.')
+    }
+    Iif (specs.gpu.includes('RTX')) {
+      specs.warnings.push('Mac no usa GPUs dedicadas tipo RTX, sus chips integrados (M1/M2/M3) tienen gráficas propias.')
+      specs.gpu = 'Apple Silicon GPU (M1/M2/M3)'
+    }
+    Eif (specs.processor.includes('i7') || specs.processor.includes('Ryzen')) {
+      specs.processor = 'Apple M1 / M2 / M3'
+      specs.rationale.push('En macOS se usan procesadores Apple Silicon, no Intel/AMD tradicionales.')
+    }
+  }
+ 
+  if (
+    specs.os === 'macOS' &&
+    answers.budget === 'low' &&
+    (
+      uses.includes('gaming') ||
+      uses.includes('creating') ||
+      uses.includes('work_school') ||
+      answers.workload === 'heavy' ||
+      answers.photoVideo === 'pro'
+    )
+  ) {
+    specs.warnings.unshift('⚠️ Querés hacer cosas muy exigentes en una Mac, pero con menos de $5,000 MXN. No es viable. Recomendamos subir el presupuesto o considerar una laptop con Windows.')
+  }
+ 
+ 
+  if (specs.os === 'ChromeOS') {
+    if (answers.workload === 'heavy') {
+      specs.warnings.push('ChromeOS no es ideal para multitarea exigente ni software pesado.')
+    }
+    Iif (answers.photoVideo === 'pro') {
+      specs.warnings.push('ChromeOS no soporta software profesional como Photoshop o Premiere. Considerá Windows o macOS.')
+    }
+    if (uses.includes('gaming') && answers.gamesType === 'complex') {
+      specs.warnings.push('ChromeOS no puede ejecutar juegos complejos como GTA o FIFA.')
+    }
+    if (specs.gpu.includes('RTX') || specs.processor.includes('i7')) {
+      specs.warnings.push('ChromeOS no usa hardware de alto rendimiento como RTX o i7. Recomendamos evitar este OS si necesitás potencia.')
+      specs.gpu = 'Gráficos integrados básicos'
+      specs.processor = 'Intel Celeron / MediaTek'
+    }
+  }
+ 
+  Iif (answers.budget === 'low' && (specs.processor.includes('i5') || specs.ram === '16 GB')) {
+    specs.warnings.push('Tu presupuesto bajo podría no alcanzar para estas specs. Considerá ajustar prioridades o aumentar presupuesto.')
+  }
+ 
+  if (specs.gpu.includes('RTX') && answers.budget === 'low') {
+    specs.warnings.push('Una GPU RTX requiere un presupuesto medio o alto. Podrías no encontrar algo accesible con esas specs.')
+  }
+ 
+  // ==== 5. RAM recomendada vs real ====
+  const idealRam = scores.multitasking >= 4 ? '16 GB' : scores.multitasking >= 2 ? '12 GB' : '8 GB'
+  Iif (specs.ram !== idealRam) {
+    specs.warnings.push(`Idealmente deberías tener ${idealRam} de RAM para tu uso.`)
+  }
+ 
+  // ==== 6. Validación de coherencia interna de specs ====
+  if (specs.gpu.includes('RTX')) {
+    if (
+      !specs.processor.includes('i5') &&
+      !specs.processor.includes('i7') &&
+      !specs.processor.includes('Ryzen 5') &&
+      !specs.processor.includes('Ryzen 7')
+    ) {
+      specs.processor = 'Intel i5 / Ryzen 5'
+      specs.rationale.push('Se ajustó el procesador porque una GPU RTX requiere al menos un i5 o Ryzen 5.')
+    }
+ 
+    if (specs.ram !== '16 GB') {
+      specs.ram = '16 GB'
+      specs.rationale.push('Se ajustó la RAM porque una GPU RTX necesita al menos 16 GB para funcionar correctamente.')
+    }
+  }
+ 
+  const bestModels = matchLaptopClass(specs)
+  specs.laptopClass = bestModels // 👈 ya es un array de modelos
+ 
+ 
+  const osFilter = specs.os.toLowerCase()
+  specs.laptopClass = bestModels.filter(m => {
+      const name = m.name.toLowerCase()
+      if (osFilter.includes('chrome') && !name.includes('chromebook')) return false
+      if (osFilter.includes('macos') && !name.includes('macbook')) return false
+      Iif (osFilter.includes('windows') && name.includes('macbook')) return false
+      return true
+    }) || []
+ 
+if (specs.laptopClass.length === 0) {
+  let fallbackMsg = '⚠️ No encontramos laptops con ';
+ 
+  if (specs.os === 'ChromeOS') fallbackMsg += 'ChromeOS ';
+  else if (specs.os === 'macOS') fallbackMsg += 'macOS ';
+  else EfallbackMsg += 'el sistema operativo deseado ';
+ 
+  fallbackMsg += 'para tus necesidades. Mostramos opciones similares con Windows.';
+ 
+  specs.warnings.push(fallbackMsg);
+  specs.laptopClass = bestModels;
+}
+ 
+ 
+  // Verificación si el modelo tiene menos RAM que la recomendada
+  if (
+    Array.isArray(bestModels) &&
+    specs.ram === '12 GB' &&
+    bestModels.some(m => m.specs.toLowerCase().includes('8 gb'))
+  ) {
+    specs.warnings.push('Uno de los modelos sugeridos tiene 8 GB de RAM, que puede ser justo si hacés mucha multitarea.')
+  }
+ 
+  // ==== 7. Advertencia crítica por combinación inviable ====
+  Iif (
+    specs.os === 'ChromeOS' &&
+    answers.budget === 'low' &&
+    (
+      uses.includes('gaming') ||
+      uses.includes('creating') ||
+      uses.includes('work_school') ||
+      answers.workload === 'heavy' ||
+      answers.photoVideo === 'pro'
+    )
+  ) {
+    specs.warnings.unshift('⚠️ Estás solicitando tareas exigentes (edición, juegos, multitarea) en un sistema muy limitado (ChromeOS con presupuesto bajo). No es viable cumplir esos requerimientos con esta combinación. Recomendamos considerar Windows con mayor inversión.')
+  }
+ 
+ 
+  if (answers.budget === 'low' && (
+    scores.performance >= 4 ||
+    scores.graphics >= 2 ||
+    scores.multitasking >= 4 ||
+    uses.includes('creating')
+  )) {
+    specs.warnings.push('⚠️ Tu presupuesto bajo puede limitar el rendimiento que necesitás. Considerá ajustar prioridades o aumentar presupuesto.')
+  }
+ 
+  // === VALIDACIÓN DE USO CONTRADICTORIO ===
+  const isBasicUseOnly = Array.isArray(answers.mainUse)
+    ? answers.mainUse.length === 1 && answers.mainUse[0] === 'entertainment'
+    : answers.mainUse === 'entertainment'
+ 
+  const isDemandingUser =
+    answers.workload === 'heavy' ||
+    answers.photoVideo === 'pro' ||
+    scores.performance >= 4 ||
+    scores.multitasking >= 4 ||
+    scores.graphics >= 2.5
+ 
+  if (isBasicUseOnly && isDemandingUser) {
+    specs.warnings.unshift('⚠️ Elegiste un uso simple como "ver videos", pero tus respuestas indican tareas exigentes como edición o multitarea pesada. Considerá ajustar tu selección inicial para mejorar la recomendación.')
+  }
+ 
+  specs.flags = {
+    prioritizePortability: answers.importance === 'portability',
+    prioritizeSecurity: answers.importance === 'security',
+    prioritizeGaming: uses.includes('gaming'),
+    prioritizeScreenQuality: answers.photoVideo === 'pro'
+  }
+ 
+ 
+ 
+  return specs
+}
+ 
+export default convertToSpecs
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/coverage-final.json b/coverage/coverage-final.json new file mode 100644 index 0000000..2b89fca --- /dev/null +++ b/coverage/coverage-final.json @@ -0,0 +1,4 @@ +{"E:\\Job\\ComputerSelectorHelper\\src\\utils\\convertToSpecs.jsx": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\convertToSpecs.jsx","statementMap":{"0":{"start":{"line":3,"column":24},"end":{"line":7,"column":null}},"1":{"start":{"line":4,"column":2},"end":{"line":4,"column":null}},"2":{"start":{"line":5,"column":2},"end":{"line":5,"column":null}},"3":{"start":{"line":6,"column":2},"end":{"line":6,"column":null}},"4":{"start":{"line":9,"column":25},"end":{"line":15,"column":null}},"5":{"start":{"line":10,"column":2},"end":{"line":10,"column":null}},"6":{"start":{"line":11,"column":2},"end":{"line":11,"column":null}},"7":{"start":{"line":12,"column":2},"end":{"line":12,"column":null}},"8":{"start":{"line":13,"column":2},"end":{"line":13,"column":null}},"9":{"start":{"line":14,"column":2},"end":{"line":14,"column":null}},"10":{"start":{"line":18,"column":23},"end":{"line":357,"column":null}},"11":{"start":{"line":19,"column":16},"end":{"line":30,"column":null}},"12":{"start":{"line":32,"column":2},"end":{"line":32,"column":null}},"13":{"start":{"line":35,"column":17},"end":{"line":42,"column":null}},"14":{"start":{"line":44,"column":15},"end":{"line":46,"column":27}},"15":{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},"16":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"17":{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},"18":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"19":{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},"20":{"start":{"line":59,"column":4},"end":{"line":59,"column":null}},"21":{"start":{"line":60,"column":4},"end":{"line":60,"column":null}},"22":{"start":{"line":61,"column":4},"end":{"line":61,"column":null}},"23":{"start":{"line":62,"column":4},"end":{"line":62,"column":null}},"24":{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},"25":{"start":{"line":64,"column":6},"end":{"line":64,"column":null}},"26":{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},"27":{"start":{"line":66,"column":6},"end":{"line":66,"column":null}},"28":{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},"29":{"start":{"line":71,"column":4},"end":{"line":71,"column":null}},"30":{"start":{"line":72,"column":4},"end":{"line":72,"column":null}},"31":{"start":{"line":73,"column":4},"end":{"line":73,"column":null}},"32":{"start":{"line":74,"column":4},"end":{"line":74,"column":null}},"33":{"start":{"line":75,"column":4},"end":{"line":75,"column":null}},"34":{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},"35":{"start":{"line":79,"column":4},"end":{"line":79,"column":null}},"36":{"start":{"line":80,"column":4},"end":{"line":80,"column":null}},"37":{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},"38":{"start":{"line":84,"column":4},"end":{"line":84,"column":null}},"39":{"start":{"line":85,"column":4},"end":{"line":85,"column":null}},"40":{"start":{"line":86,"column":4},"end":{"line":86,"column":null}},"41":{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},"42":{"start":{"line":90,"column":4},"end":{"line":90,"column":null}},"43":{"start":{"line":91,"column":4},"end":{"line":91,"column":null}},"44":{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},"45":{"start":{"line":96,"column":4},"end":{"line":96,"column":null}},"46":{"start":{"line":97,"column":4},"end":{"line":97,"column":null}},"47":{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},"48":{"start":{"line":101,"column":4},"end":{"line":101,"column":null}},"49":{"start":{"line":102,"column":4},"end":{"line":102,"column":null}},"50":{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},"51":{"start":{"line":106,"column":4},"end":{"line":106,"column":null}},"52":{"start":{"line":107,"column":4},"end":{"line":107,"column":null}},"53":{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},"54":{"start":{"line":111,"column":4},"end":{"line":111,"column":null}},"55":{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},"56":{"start":{"line":115,"column":4},"end":{"line":115,"column":null}},"57":{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},"58":{"start":{"line":120,"column":4},"end":{"line":120,"column":null}},"59":{"start":{"line":121,"column":4},"end":{"line":121,"column":null}},"60":{"start":{"line":125,"column":2},"end":{"line":128,"column":null}},"61":{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},"62":{"start":{"line":131,"column":4},"end":{"line":131,"column":null}},"63":{"start":{"line":133,"column":4},"end":{"line":136,"column":null}},"64":{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},"65":{"start":{"line":140,"column":4},"end":{"line":140,"column":null}},"66":{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},"67":{"start":{"line":148,"column":4},"end":{"line":148,"column":null}},"68":{"start":{"line":153,"column":2},"end":{"line":153,"column":null}},"69":{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},"70":{"start":{"line":156,"column":4},"end":{"line":156,"column":null}},"71":{"start":{"line":160,"column":2},"end":{"line":163,"column":null}},"72":{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},"73":{"start":{"line":166,"column":4},"end":{"line":166,"column":null}},"74":{"start":{"line":170,"column":2},"end":{"line":170,"column":null}},"75":{"start":{"line":171,"column":2},"end":{"line":171,"column":null}},"76":{"start":{"line":174,"column":2},"end":{"line":186,"column":null}},"77":{"start":{"line":176,"column":6},"end":{"line":176,"column":null}},"78":{"start":{"line":177,"column":6},"end":{"line":177,"column":null}},"79":{"start":{"line":179,"column":6},"end":{"line":179,"column":null}},"80":{"start":{"line":180,"column":6},"end":{"line":180,"column":null}},"81":{"start":{"line":182,"column":6},"end":{"line":182,"column":null}},"82":{"start":{"line":183,"column":6},"end":{"line":183,"column":null}},"83":{"start":{"line":185,"column":6},"end":{"line":185,"column":null}},"84":{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},"85":{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},"86":{"start":{"line":191,"column":6},"end":{"line":191,"column":null}},"87":{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},"88":{"start":{"line":194,"column":6},"end":{"line":194,"column":null}},"89":{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},"90":{"start":{"line":197,"column":6},"end":{"line":197,"column":null}},"91":{"start":{"line":198,"column":6},"end":{"line":198,"column":null}},"92":{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},"93":{"start":{"line":201,"column":6},"end":{"line":201,"column":null}},"94":{"start":{"line":202,"column":6},"end":{"line":202,"column":null}},"95":{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},"96":{"start":{"line":217,"column":4},"end":{"line":217,"column":null}},"97":{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},"98":{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},"99":{"start":{"line":223,"column":6},"end":{"line":223,"column":null}},"100":{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},"101":{"start":{"line":226,"column":6},"end":{"line":226,"column":null}},"102":{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},"103":{"start":{"line":229,"column":6},"end":{"line":229,"column":null}},"104":{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},"105":{"start":{"line":232,"column":6},"end":{"line":232,"column":null}},"106":{"start":{"line":233,"column":6},"end":{"line":233,"column":null}},"107":{"start":{"line":234,"column":6},"end":{"line":234,"column":null}},"108":{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},"109":{"start":{"line":239,"column":4},"end":{"line":239,"column":null}},"110":{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},"111":{"start":{"line":243,"column":4},"end":{"line":243,"column":null}},"112":{"start":{"line":247,"column":19},"end":{"line":247,"column":null}},"113":{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},"114":{"start":{"line":249,"column":4},"end":{"line":249,"column":null}},"115":{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},"116":{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},"117":{"start":{"line":260,"column":6},"end":{"line":260,"column":null}},"118":{"start":{"line":261,"column":6},"end":{"line":261,"column":null}},"119":{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},"120":{"start":{"line":265,"column":6},"end":{"line":265,"column":null}},"121":{"start":{"line":266,"column":6},"end":{"line":266,"column":null}},"122":{"start":{"line":270,"column":8},"end":{"line":270,"column":null}},"123":{"start":{"line":271,"column":2},"end":{"line":271,"column":null}},"124":{"start":{"line":274,"column":19},"end":{"line":274,"column":null}},"125":{"start":{"line":275,"column":2},"end":{"line":281,"column":null}},"126":{"start":{"line":276,"column":19},"end":{"line":276,"column":null}},"127":{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},"128":{"start":{"line":277,"column":71},"end":{"line":277,"column":null}},"129":{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},"130":{"start":{"line":278,"column":67},"end":{"line":278,"column":null}},"131":{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},"132":{"start":{"line":279,"column":68},"end":{"line":279,"column":null}},"133":{"start":{"line":280,"column":6},"end":{"line":280,"column":null}},"134":{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},"135":{"start":{"line":284,"column":20},"end":{"line":284,"column":null}},"136":{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},"137":{"start":{"line":286,"column":31},"end":{"line":286,"column":null}},"138":{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},"139":{"start":{"line":287,"column":33},"end":{"line":287,"column":null}},"140":{"start":{"line":288,"column":7},"end":{"line":288,"column":null}},"141":{"start":{"line":290,"column":2},"end":{"line":290,"column":null}},"142":{"start":{"line":292,"column":2},"end":{"line":292,"column":null}},"143":{"start":{"line":293,"column":2},"end":{"line":293,"column":null}},"144":{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},"145":{"start":{"line":301,"column":25},"end":{"line":301,"column":null}},"146":{"start":{"line":303,"column":4},"end":{"line":303,"column":null}},"147":{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},"148":{"start":{"line":318,"column":4},"end":{"line":318,"column":null}},"149":{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},"150":{"start":{"line":328,"column":4},"end":{"line":328,"column":null}},"151":{"start":{"line":332,"column":25},"end":{"line":334,"column":null}},"152":{"start":{"line":337,"column":4},"end":{"line":341,"column":null}},"153":{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},"154":{"start":{"line":344,"column":4},"end":{"line":344,"column":null}},"155":{"start":{"line":347,"column":2},"end":{"line":352,"column":null}},"156":{"start":{"line":356,"column":2},"end":{"line":356,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":24},"end":{"line":3,"column":25}},"loc":{"start":{"line":3,"column":32},"end":{"line":7,"column":null}},"line":3},"1":{"name":"(anonymous_1)","decl":{"start":{"line":9,"column":25},"end":{"line":9,"column":26}},"loc":{"start":{"line":9,"column":26},"end":{"line":15,"column":null}},"line":9},"2":{"name":"(anonymous_2)","decl":{"start":{"line":18,"column":23},"end":{"line":18,"column":24}},"loc":{"start":{"line":18,"column":24},"end":{"line":357,"column":null}},"line":18},"3":{"name":"(anonymous_3)","decl":{"start":{"line":275,"column":40},"end":{"line":275,"column":null}},"loc":{"start":{"line":275,"column":40},"end":{"line":281,"column":10}},"line":275},"4":{"name":"(anonymous_4)","decl":{"start":{"line":301,"column":20},"end":{"line":301,"column":25}},"loc":{"start":{"line":301,"column":25},"end":{"line":301,"column":null}},"line":301}},"branchMap":{"0":{"loc":{"start":{"line":44,"column":15},"end":{"line":46,"column":27}},"type":"cond-expr","locations":[{"start":{"line":45,"column":6},"end":{"line":45,"column":null}},{"start":{"line":46,"column":6},"end":{"line":46,"column":27}}],"line":44},"1":{"loc":{"start":{"line":46,"column":6},"end":{"line":46,"column":27}},"type":"binary-expr","locations":[{"start":{"line":46,"column":6},"end":{"line":46,"column":21}},{"start":{"line":46,"column":25},"end":{"line":46,"column":27}}],"line":46},"2":{"loc":{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},"type":"if","locations":[{"start":{"line":48,"column":2},"end":{"line":50,"column":null}},{"start":{},"end":{}}],"line":48},"3":{"loc":{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},"type":"if","locations":[{"start":{"line":52,"column":2},"end":{"line":54,"column":null}},{"start":{},"end":{}}],"line":52},"4":{"loc":{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},"type":"if","locations":[{"start":{"line":58,"column":2},"end":{"line":68,"column":null}},{"start":{},"end":{}}],"line":58},"5":{"loc":{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":63,"column":4},"end":{"line":67,"column":null}},{"start":{"line":65,"column":11},"end":{"line":67,"column":null}}],"line":63},"6":{"loc":{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},"type":"if","locations":[{"start":{"line":65,"column":11},"end":{"line":67,"column":null}},{"start":{},"end":{}}],"line":65},"7":{"loc":{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},"type":"if","locations":[{"start":{"line":70,"column":2},"end":{"line":76,"column":null}},{"start":{},"end":{}}],"line":70},"8":{"loc":{"start":{"line":70,"column":6},"end":{"line":70,"column":65}},"type":"binary-expr","locations":[{"start":{"line":70,"column":6},"end":{"line":70,"column":35}},{"start":{"line":70,"column":35},"end":{"line":70,"column":65}}],"line":70},"9":{"loc":{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":78,"column":2},"end":{"line":81,"column":null}},{"start":{},"end":{}}],"line":78},"10":{"loc":{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},"type":"if","locations":[{"start":{"line":83,"column":2},"end":{"line":87,"column":null}},{"start":{},"end":{}}],"line":83},"11":{"loc":{"start":{"line":83,"column":6},"end":{"line":83,"column":68}},"type":"binary-expr","locations":[{"start":{"line":83,"column":6},"end":{"line":83,"column":38}},{"start":{"line":83,"column":38},"end":{"line":83,"column":68}}],"line":83},"12":{"loc":{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},"type":"if","locations":[{"start":{"line":89,"column":2},"end":{"line":92,"column":null}},{"start":{},"end":{}}],"line":89},"13":{"loc":{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},"type":"if","locations":[{"start":{"line":95,"column":2},"end":{"line":98,"column":null}},{"start":{},"end":{}}],"line":95},"14":{"loc":{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},"type":"if","locations":[{"start":{"line":100,"column":2},"end":{"line":103,"column":null}},{"start":{},"end":{}}],"line":100},"15":{"loc":{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},"type":"if","locations":[{"start":{"line":105,"column":2},"end":{"line":108,"column":null}},{"start":{},"end":{}}],"line":105},"16":{"loc":{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},"type":"if","locations":[{"start":{"line":110,"column":2},"end":{"line":112,"column":null}},{"start":{},"end":{}}],"line":110},"17":{"loc":{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},"type":"if","locations":[{"start":{"line":114,"column":2},"end":{"line":116,"column":null}},{"start":{},"end":{}}],"line":114},"18":{"loc":{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},"type":"if","locations":[{"start":{"line":119,"column":2},"end":{"line":122,"column":null}},{"start":{},"end":{}}],"line":119},"19":{"loc":{"start":{"line":126,"column":4},"end":{"line":128,"column":null}},"type":"cond-expr","locations":[{"start":{"line":126,"column":30},"end":{"line":126,"column":null}},{"start":{"line":127,"column":6},"end":{"line":128,"column":null}}],"line":126},"20":{"loc":{"start":{"line":127,"column":6},"end":{"line":128,"column":null}},"type":"cond-expr","locations":[{"start":{"line":127,"column":32},"end":{"line":127,"column":null}},{"start":{"line":128,"column":8},"end":{"line":128,"column":null}}],"line":127},"21":{"loc":{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},"type":"if","locations":[{"start":{"line":130,"column":2},"end":{"line":137,"column":null}},{"start":{"line":132,"column":9},"end":{"line":137,"column":null}}],"line":130},"22":{"loc":{"start":{"line":131,"column":16},"end":{"line":131,"column":null}},"type":"cond-expr","locations":[{"start":{"line":131,"column":43},"end":{"line":131,"column":53}},{"start":{"line":131,"column":53},"end":{"line":131,"column":null}}],"line":131},"23":{"loc":{"start":{"line":134,"column":6},"end":{"line":136,"column":null}},"type":"cond-expr","locations":[{"start":{"line":134,"column":33},"end":{"line":134,"column":null}},{"start":{"line":135,"column":8},"end":{"line":136,"column":null}}],"line":134},"24":{"loc":{"start":{"line":135,"column":8},"end":{"line":136,"column":null}},"type":"cond-expr","locations":[{"start":{"line":135,"column":35},"end":{"line":135,"column":null}},{"start":{"line":136,"column":10},"end":{"line":136,"column":null}}],"line":135},"25":{"loc":{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},"type":"if","locations":[{"start":{"line":139,"column":2},"end":{"line":141,"column":null}},{"start":{},"end":{}}],"line":139},"26":{"loc":{"start":{"line":139,"column":6},"end":{"line":139,"column":59}},"type":"binary-expr","locations":[{"start":{"line":139,"column":6},"end":{"line":139,"column":34}},{"start":{"line":139,"column":34},"end":{"line":139,"column":59}}],"line":139},"27":{"loc":{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},"type":"if","locations":[{"start":{"line":143,"column":2},"end":{"line":149,"column":null}},{"start":{},"end":{}}],"line":143},"28":{"loc":{"start":{"line":143,"column":6},"end":{"line":146,"column":null}},"type":"binary-expr","locations":[{"start":{"line":143,"column":6},"end":{"line":143,"column":null}},{"start":{"line":144,"column":4},"end":{"line":144,"column":null}},{"start":{"line":145,"column":4},"end":{"line":145,"column":null}},{"start":{"line":146,"column":4},"end":{"line":146,"column":26}}],"line":143},"29":{"loc":{"start":{"line":153,"column":18},"end":{"line":153,"column":null}},"type":"cond-expr","locations":[{"start":{"line":153,"column":40},"end":{"line":153,"column":55}},{"start":{"line":153,"column":55},"end":{"line":153,"column":null}}],"line":153},"30":{"loc":{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},"type":"if","locations":[{"start":{"line":155,"column":2},"end":{"line":157,"column":null}},{"start":{},"end":{}}],"line":155},"31":{"loc":{"start":{"line":155,"column":6},"end":{"line":155,"column":67}},"type":"binary-expr","locations":[{"start":{"line":155,"column":6},"end":{"line":155,"column":40}},{"start":{"line":155,"column":40},"end":{"line":155,"column":67}}],"line":155},"32":{"loc":{"start":{"line":161,"column":4},"end":{"line":163,"column":null}},"type":"cond-expr","locations":[{"start":{"line":161,"column":27},"end":{"line":161,"column":null}},{"start":{"line":162,"column":6},"end":{"line":163,"column":null}}],"line":161},"33":{"loc":{"start":{"line":162,"column":6},"end":{"line":163,"column":null}},"type":"cond-expr","locations":[{"start":{"line":162,"column":29},"end":{"line":162,"column":null}},{"start":{"line":163,"column":8},"end":{"line":163,"column":null}}],"line":162},"34":{"loc":{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},"type":"if","locations":[{"start":{"line":165,"column":2},"end":{"line":167,"column":null}},{"start":{},"end":{}}],"line":165},"35":{"loc":{"start":{"line":170,"column":22},"end":{"line":170,"column":null}},"type":"cond-expr","locations":[{"start":{"line":170,"column":48},"end":{"line":170,"column":79}},{"start":{"line":170,"column":79},"end":{"line":170,"column":null}}],"line":170},"36":{"loc":{"start":{"line":171,"column":18},"end":{"line":171,"column":null}},"type":"cond-expr","locations":[{"start":{"line":171,"column":40},"end":{"line":171,"column":60}},{"start":{"line":171,"column":60},"end":{"line":171,"column":null}}],"line":171},"37":{"loc":{"start":{"line":174,"column":2},"end":{"line":186,"column":null}},"type":"switch","locations":[{"start":{"line":175,"column":4},"end":{"line":177,"column":null}},{"start":{"line":178,"column":4},"end":{"line":180,"column":null}},{"start":{"line":181,"column":4},"end":{"line":183,"column":null}},{"start":{"line":184,"column":4},"end":{"line":185,"column":null}}],"line":174},"38":{"loc":{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},"type":"if","locations":[{"start":{"line":189,"column":2},"end":{"line":204,"column":null}},{"start":{},"end":{}}],"line":189},"39":{"loc":{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},"type":"if","locations":[{"start":{"line":190,"column":4},"end":{"line":192,"column":null}},{"start":{},"end":{}}],"line":190},"40":{"loc":{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},"type":"if","locations":[{"start":{"line":193,"column":4},"end":{"line":195,"column":null}},{"start":{},"end":{}}],"line":193},"41":{"loc":{"start":{"line":193,"column":8},"end":{"line":193,"column":64}},"type":"binary-expr","locations":[{"start":{"line":193,"column":8},"end":{"line":193,"column":35}},{"start":{"line":193,"column":35},"end":{"line":193,"column":64}}],"line":193},"42":{"loc":{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},"type":"if","locations":[{"start":{"line":196,"column":4},"end":{"line":199,"column":null}},{"start":{},"end":{}}],"line":196},"43":{"loc":{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},"type":"if","locations":[{"start":{"line":200,"column":4},"end":{"line":203,"column":null}},{"start":{},"end":{}}],"line":200},"44":{"loc":{"start":{"line":200,"column":8},"end":{"line":200,"column":77}},"type":"binary-expr","locations":[{"start":{"line":200,"column":8},"end":{"line":200,"column":42}},{"start":{"line":200,"column":42},"end":{"line":200,"column":77}}],"line":200},"45":{"loc":{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},"type":"if","locations":[{"start":{"line":206,"column":2},"end":{"line":218,"column":null}},{"start":{},"end":{}}],"line":206},"46":{"loc":{"start":{"line":207,"column":4},"end":{"line":214,"column":null}},"type":"binary-expr","locations":[{"start":{"line":207,"column":4},"end":{"line":207,"column":null}},{"start":{"line":208,"column":4},"end":{"line":208,"column":null}},{"start":{"line":210,"column":6},"end":{"line":210,"column":null}},{"start":{"line":211,"column":6},"end":{"line":211,"column":null}},{"start":{"line":212,"column":6},"end":{"line":212,"column":null}},{"start":{"line":213,"column":6},"end":{"line":213,"column":null}},{"start":{"line":214,"column":6},"end":{"line":214,"column":33}}],"line":207},"47":{"loc":{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},"type":"if","locations":[{"start":{"line":221,"column":2},"end":{"line":236,"column":null}},{"start":{},"end":{}}],"line":221},"48":{"loc":{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},"type":"if","locations":[{"start":{"line":222,"column":4},"end":{"line":224,"column":null}},{"start":{},"end":{}}],"line":222},"49":{"loc":{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},"type":"if","locations":[{"start":{"line":225,"column":4},"end":{"line":227,"column":null}},{"start":{},"end":{}}],"line":225},"50":{"loc":{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},"type":"if","locations":[{"start":{"line":228,"column":4},"end":{"line":230,"column":null}},{"start":{},"end":{}}],"line":228},"51":{"loc":{"start":{"line":228,"column":8},"end":{"line":228,"column":68}},"type":"binary-expr","locations":[{"start":{"line":228,"column":8},"end":{"line":228,"column":35}},{"start":{"line":228,"column":35},"end":{"line":228,"column":68}}],"line":228},"52":{"loc":{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},"type":"if","locations":[{"start":{"line":231,"column":4},"end":{"line":235,"column":null}},{"start":{},"end":{}}],"line":231},"53":{"loc":{"start":{"line":231,"column":8},"end":{"line":231,"column":69}},"type":"binary-expr","locations":[{"start":{"line":231,"column":8},"end":{"line":231,"column":37}},{"start":{"line":231,"column":37},"end":{"line":231,"column":69}}],"line":231},"54":{"loc":{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},"type":"if","locations":[{"start":{"line":238,"column":2},"end":{"line":240,"column":null}},{"start":{},"end":{}}],"line":238},"55":{"loc":{"start":{"line":238,"column":6},"end":{"line":238,"column":93}},"type":"binary-expr","locations":[{"start":{"line":238,"column":6},"end":{"line":238,"column":35}},{"start":{"line":238,"column":35},"end":{"line":238,"column":69}},{"start":{"line":238,"column":69},"end":{"line":238,"column":89}}],"line":238},"56":{"loc":{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},"type":"if","locations":[{"start":{"line":242,"column":2},"end":{"line":244,"column":null}},{"start":{},"end":{}}],"line":242},"57":{"loc":{"start":{"line":242,"column":6},"end":{"line":242,"column":61}},"type":"binary-expr","locations":[{"start":{"line":242,"column":6},"end":{"line":242,"column":35}},{"start":{"line":242,"column":35},"end":{"line":242,"column":61}}],"line":242},"58":{"loc":{"start":{"line":247,"column":19},"end":{"line":247,"column":null}},"type":"cond-expr","locations":[{"start":{"line":247,"column":46},"end":{"line":247,"column":56}},{"start":{"line":247,"column":56},"end":{"line":247,"column":null}}],"line":247},"59":{"loc":{"start":{"line":247,"column":56},"end":{"line":247,"column":null}},"type":"cond-expr","locations":[{"start":{"line":247,"column":83},"end":{"line":247,"column":93}},{"start":{"line":247,"column":93},"end":{"line":247,"column":null}}],"line":247},"60":{"loc":{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},"type":"if","locations":[{"start":{"line":248,"column":2},"end":{"line":250,"column":null}},{"start":{},"end":{}}],"line":248},"61":{"loc":{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},"type":"if","locations":[{"start":{"line":253,"column":2},"end":{"line":268,"column":null}},{"start":{},"end":{}}],"line":253},"62":{"loc":{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},"type":"if","locations":[{"start":{"line":254,"column":4},"end":{"line":262,"column":null}},{"start":{},"end":{}}],"line":254},"63":{"loc":{"start":{"line":255,"column":6},"end":{"line":258,"column":null}},"type":"binary-expr","locations":[{"start":{"line":255,"column":6},"end":{"line":255,"column":null}},{"start":{"line":256,"column":6},"end":{"line":256,"column":null}},{"start":{"line":257,"column":6},"end":{"line":257,"column":null}},{"start":{"line":258,"column":6},"end":{"line":258,"column":null}}],"line":255},"64":{"loc":{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},"type":"if","locations":[{"start":{"line":264,"column":4},"end":{"line":267,"column":null}},{"start":{},"end":{}}],"line":264},"65":{"loc":{"start":{"line":275,"column":22},"end":{"line":281,"column":12}},"type":"binary-expr","locations":[{"start":{"line":275,"column":22},"end":{"line":281,"column":10}},{"start":{"line":281,"column":10},"end":{"line":281,"column":12}}],"line":275},"66":{"loc":{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},"type":"if","locations":[{"start":{"line":277,"column":6},"end":{"line":277,"column":null}},{"start":{},"end":{}}],"line":277},"67":{"loc":{"start":{"line":277,"column":10},"end":{"line":277,"column":71}},"type":"binary-expr","locations":[{"start":{"line":277,"column":10},"end":{"line":277,"column":41}},{"start":{"line":277,"column":41},"end":{"line":277,"column":71}}],"line":277},"68":{"loc":{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},"type":"if","locations":[{"start":{"line":278,"column":6},"end":{"line":278,"column":null}},{"start":{},"end":{}}],"line":278},"69":{"loc":{"start":{"line":278,"column":10},"end":{"line":278,"column":67}},"type":"binary-expr","locations":[{"start":{"line":278,"column":10},"end":{"line":278,"column":40}},{"start":{"line":278,"column":40},"end":{"line":278,"column":67}}],"line":278},"70":{"loc":{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},"type":"if","locations":[{"start":{"line":279,"column":6},"end":{"line":279,"column":null}},{"start":{},"end":{}}],"line":279},"71":{"loc":{"start":{"line":279,"column":10},"end":{"line":279,"column":68}},"type":"binary-expr","locations":[{"start":{"line":279,"column":10},"end":{"line":279,"column":42}},{"start":{"line":279,"column":42},"end":{"line":279,"column":68}}],"line":279},"72":{"loc":{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},"type":"if","locations":[{"start":{"line":283,"column":0},"end":{"line":294,"column":null}},{"start":{},"end":{}}],"line":283},"73":{"loc":{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},"type":"if","locations":[{"start":{"line":286,"column":2},"end":{"line":288,"column":null}},{"start":{"line":287,"column":7},"end":{"line":288,"column":null}}],"line":286},"74":{"loc":{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},"type":"if","locations":[{"start":{"line":287,"column":7},"end":{"line":288,"column":null}},{"start":{"line":288,"column":7},"end":{"line":288,"column":null}}],"line":287},"75":{"loc":{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},"type":"if","locations":[{"start":{"line":298,"column":2},"end":{"line":304,"column":null}},{"start":{},"end":{}}],"line":298},"76":{"loc":{"start":{"line":299,"column":4},"end":{"line":301,"column":null}},"type":"binary-expr","locations":[{"start":{"line":299,"column":4},"end":{"line":299,"column":null}},{"start":{"line":300,"column":4},"end":{"line":300,"column":null}},{"start":{"line":301,"column":4},"end":{"line":301,"column":null}}],"line":299},"77":{"loc":{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},"type":"if","locations":[{"start":{"line":307,"column":2},"end":{"line":319,"column":null}},{"start":{},"end":{}}],"line":307},"78":{"loc":{"start":{"line":308,"column":4},"end":{"line":315,"column":null}},"type":"binary-expr","locations":[{"start":{"line":308,"column":4},"end":{"line":308,"column":null}},{"start":{"line":309,"column":4},"end":{"line":309,"column":null}},{"start":{"line":311,"column":6},"end":{"line":311,"column":null}},{"start":{"line":312,"column":6},"end":{"line":312,"column":null}},{"start":{"line":313,"column":6},"end":{"line":313,"column":null}},{"start":{"line":314,"column":6},"end":{"line":314,"column":null}},{"start":{"line":315,"column":6},"end":{"line":315,"column":33}}],"line":308},"79":{"loc":{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},"type":"if","locations":[{"start":{"line":322,"column":2},"end":{"line":329,"column":null}},{"start":{},"end":{}}],"line":322},"80":{"loc":{"start":{"line":322,"column":6},"end":{"line":326,"column":null}},"type":"binary-expr","locations":[{"start":{"line":322,"column":6},"end":{"line":322,"column":null}},{"start":{"line":323,"column":4},"end":{"line":323,"column":null}},{"start":{"line":324,"column":4},"end":{"line":324,"column":null}},{"start":{"line":325,"column":4},"end":{"line":325,"column":null}},{"start":{"line":326,"column":4},"end":{"line":326,"column":28}}],"line":322},"81":{"loc":{"start":{"line":332,"column":25},"end":{"line":334,"column":null}},"type":"cond-expr","locations":[{"start":{"line":333,"column":6},"end":{"line":333,"column":null}},{"start":{"line":334,"column":6},"end":{"line":334,"column":null}}],"line":332},"82":{"loc":{"start":{"line":333,"column":6},"end":{"line":333,"column":null}},"type":"binary-expr","locations":[{"start":{"line":333,"column":6},"end":{"line":333,"column":38}},{"start":{"line":333,"column":38},"end":{"line":333,"column":null}}],"line":333},"83":{"loc":{"start":{"line":337,"column":4},"end":{"line":341,"column":null}},"type":"binary-expr","locations":[{"start":{"line":337,"column":4},"end":{"line":337,"column":null}},{"start":{"line":338,"column":4},"end":{"line":338,"column":null}},{"start":{"line":339,"column":4},"end":{"line":339,"column":null}},{"start":{"line":340,"column":4},"end":{"line":340,"column":null}},{"start":{"line":341,"column":4},"end":{"line":341,"column":null}}],"line":337},"84":{"loc":{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},"type":"if","locations":[{"start":{"line":343,"column":2},"end":{"line":345,"column":null}},{"start":{},"end":{}}],"line":343},"85":{"loc":{"start":{"line":343,"column":6},"end":{"line":343,"column":41}},"type":"binary-expr","locations":[{"start":{"line":343,"column":6},"end":{"line":343,"column":24}},{"start":{"line":343,"column":24},"end":{"line":343,"column":41}}],"line":343}},"s":{"0":1,"1":5,"2":5,"3":5,"4":1,"5":4,"6":4,"7":4,"8":4,"9":4,"10":1,"11":35,"12":35,"13":35,"14":35,"15":35,"16":5,"17":35,"18":4,"19":35,"20":12,"21":12,"22":12,"23":12,"24":12,"25":5,"26":7,"27":1,"28":35,"29":6,"30":6,"31":6,"32":6,"33":6,"34":35,"35":1,"36":1,"37":35,"38":5,"39":5,"40":5,"41":35,"42":1,"43":1,"44":35,"45":2,"46":2,"47":35,"48":1,"49":1,"50":35,"51":1,"52":1,"53":35,"54":1,"55":35,"56":1,"57":35,"58":1,"59":1,"60":35,"61":35,"62":5,"63":30,"64":35,"65":2,"66":35,"67":2,"68":35,"69":35,"70":0,"71":35,"72":35,"73":5,"74":35,"75":35,"76":35,"77":26,"78":26,"79":5,"80":5,"81":4,"82":4,"83":0,"84":35,"85":5,"86":2,"87":5,"88":0,"89":5,"90":0,"91":0,"92":5,"93":5,"94":5,"95":35,"96":1,"97":35,"98":4,"99":1,"100":4,"101":0,"102":4,"103":1,"104":4,"105":1,"106":1,"107":1,"108":35,"109":0,"110":35,"111":2,"112":35,"113":35,"114":0,"115":35,"116":9,"117":7,"118":7,"119":9,"120":8,"121":8,"122":35,"123":35,"124":35,"125":35,"126":76,"127":76,"128":12,"129":64,"130":5,"131":59,"132":0,"133":59,"134":35,"135":9,"136":9,"137":4,"138":5,"139":5,"140":0,"141":9,"142":9,"143":9,"144":35,"145":18,"146":6,"147":35,"148":0,"149":35,"150":3,"151":35,"152":35,"153":35,"154":1,"155":35,"156":35},"f":{"0":5,"1":4,"2":35,"3":76,"4":18},"b":{"0":[1,34],"1":[34,0],"2":[5,30],"3":[4,31],"4":[12,23],"5":[5,7],"6":[1,6],"7":[6,29],"8":[35,32],"9":[1,34],"10":[5,30],"11":[35,32],"12":[1,34],"13":[2,33],"14":[1,34],"15":[1,34],"16":[1,34],"17":[1,34],"18":[1,34],"19":[2,33],"20":[1,32],"21":[5,30],"22":[0,5],"23":[2,28],"24":[7,21],"25":[2,33],"26":[35,5],"27":[2,33],"28":[35,4,4,3],"29":[6,29],"30":[0,35],"31":[35,29],"32":[12,23],"33":[4,19],"34":[5,30],"35":[1,34],"36":[1,34],"37":[26,5,4,0],"38":[5,30],"39":[2,3],"40":[0,5],"41":[5,2],"42":[0,5],"43":[5,0],"44":[5,5],"45":[1,34],"46":[35,5,1,0,0,0,0],"47":[4,31],"48":[1,3],"49":[0,4],"50":[1,3],"51":[4,1],"52":[1,3],"53":[4,3],"54":[0,35],"55":[35,4,4],"56":[2,33],"57":[35,9],"58":[2,33],"59":[7,26],"60":[0,35],"61":[9,26],"62":[7,2],"63":[9,9,7,7],"64":[8,1],"65":[35,0],"66":[12,64],"67":[76,12],"68":[5,59],"69":[64,5],"70":[0,59],"71":[59,59],"72":[9,26],"73":[4,5],"74":[5,0],"75":[6,29],"76":[35,35,6],"77":[0,35],"78":[35,4,0,0,0,0,0],"79":[3,32],"80":[35,4,4,1,1],"81":[34,1],"82":[34,32],"83":[35,31,29,27,27],"84":[1,34],"85":[35,1]},"meta":{"lastBranch":86,"lastFunction":5,"lastStatement":157,"seen":{"s:3:24:7:Infinity":0,"f:3:24:3:25":0,"s:4:2:4:Infinity":1,"s:5:2:5:Infinity":2,"s:6:2:6:Infinity":3,"s:9:25:15:Infinity":4,"f:9:25:9:26":1,"s:10:2:10:Infinity":5,"s:11:2:11:Infinity":6,"s:12:2:12:Infinity":7,"s:13:2:13:Infinity":8,"s:14:2:14:Infinity":9,"s:18:23:357:Infinity":10,"f:18:23:18:24":2,"s:19:16:30:Infinity":11,"s:32:2:32:Infinity":12,"s:35:17:42:Infinity":13,"s:44:15:46:27":14,"b:45:6:45:Infinity:46:6:46:27":0,"b:46:6:46:21:46:25:46:27":1,"b:48:2:50:Infinity:undefined:undefined:undefined:undefined":2,"s:48:2:50:Infinity":15,"s:49:4:49:Infinity":16,"b:52:2:54:Infinity:undefined:undefined:undefined:undefined":3,"s:52:2:54:Infinity":17,"s:53:4:53:Infinity":18,"b:58:2:68:Infinity:undefined:undefined:undefined:undefined":4,"s:58:2:68:Infinity":19,"s:59:4:59:Infinity":20,"s:60:4:60:Infinity":21,"s:61:4:61:Infinity":22,"s:62:4:62:Infinity":23,"b:63:4:67:Infinity:65:11:67:Infinity":5,"s:63:4:67:Infinity":24,"s:64:6:64:Infinity":25,"b:65:11:67:Infinity:undefined:undefined:undefined:undefined":6,"s:65:11:67:Infinity":26,"s:66:6:66:Infinity":27,"b:70:2:76:Infinity:undefined:undefined:undefined:undefined":7,"s:70:2:76:Infinity":28,"b:70:6:70:35:70:35:70:65":8,"s:71:4:71:Infinity":29,"s:72:4:72:Infinity":30,"s:73:4:73:Infinity":31,"s:74:4:74:Infinity":32,"s:75:4:75:Infinity":33,"b:78:2:81:Infinity:undefined:undefined:undefined:undefined":9,"s:78:2:81:Infinity":34,"s:79:4:79:Infinity":35,"s:80:4:80:Infinity":36,"b:83:2:87:Infinity:undefined:undefined:undefined:undefined":10,"s:83:2:87:Infinity":37,"b:83:6:83:38:83:38:83:68":11,"s:84:4:84:Infinity":38,"s:85:4:85:Infinity":39,"s:86:4:86:Infinity":40,"b:89:2:92:Infinity:undefined:undefined:undefined:undefined":12,"s:89:2:92:Infinity":41,"s:90:4:90:Infinity":42,"s:91:4:91:Infinity":43,"b:95:2:98:Infinity:undefined:undefined:undefined:undefined":13,"s:95:2:98:Infinity":44,"s:96:4:96:Infinity":45,"s:97:4:97:Infinity":46,"b:100:2:103:Infinity:undefined:undefined:undefined:undefined":14,"s:100:2:103:Infinity":47,"s:101:4:101:Infinity":48,"s:102:4:102:Infinity":49,"b:105:2:108:Infinity:undefined:undefined:undefined:undefined":15,"s:105:2:108:Infinity":50,"s:106:4:106:Infinity":51,"s:107:4:107:Infinity":52,"b:110:2:112:Infinity:undefined:undefined:undefined:undefined":16,"s:110:2:112:Infinity":53,"s:111:4:111:Infinity":54,"b:114:2:116:Infinity:undefined:undefined:undefined:undefined":17,"s:114:2:116:Infinity":55,"s:115:4:115:Infinity":56,"b:119:2:122:Infinity:undefined:undefined:undefined:undefined":18,"s:119:2:122:Infinity":57,"s:120:4:120:Infinity":58,"s:121:4:121:Infinity":59,"s:125:2:128:Infinity":60,"b:126:30:126:Infinity:127:6:128:Infinity":19,"b:127:32:127:Infinity:128:8:128:Infinity":20,"b:130:2:137:Infinity:132:9:137:Infinity":21,"s:130:2:137:Infinity":61,"s:131:4:131:Infinity":62,"b:131:43:131:53:131:53:131:Infinity":22,"s:133:4:136:Infinity":63,"b:134:33:134:Infinity:135:8:136:Infinity":23,"b:135:35:135:Infinity:136:10:136:Infinity":24,"b:139:2:141:Infinity:undefined:undefined:undefined:undefined":25,"s:139:2:141:Infinity":64,"b:139:6:139:34:139:34:139:59":26,"s:140:4:140:Infinity":65,"b:143:2:149:Infinity:undefined:undefined:undefined:undefined":27,"s:143:2:149:Infinity":66,"b:143:6:143:Infinity:144:4:144:Infinity:145:4:145:Infinity:146:4:146:26":28,"s:148:4:148:Infinity":67,"s:153:2:153:Infinity":68,"b:153:40:153:55:153:55:153:Infinity":29,"b:155:2:157:Infinity:undefined:undefined:undefined:undefined":30,"s:155:2:157:Infinity":69,"b:155:6:155:40:155:40:155:67":31,"s:156:4:156:Infinity":70,"s:160:2:163:Infinity":71,"b:161:27:161:Infinity:162:6:163:Infinity":32,"b:162:29:162:Infinity:163:8:163:Infinity":33,"b:165:2:167:Infinity:undefined:undefined:undefined:undefined":34,"s:165:2:167:Infinity":72,"s:166:4:166:Infinity":73,"s:170:2:170:Infinity":74,"b:170:48:170:79:170:79:170:Infinity":35,"s:171:2:171:Infinity":75,"b:171:40:171:60:171:60:171:Infinity":36,"b:175:4:177:Infinity:178:4:180:Infinity:181:4:183:Infinity:184:4:185:Infinity":37,"s:174:2:186:Infinity":76,"s:176:6:176:Infinity":77,"s:177:6:177:Infinity":78,"s:179:6:179:Infinity":79,"s:180:6:180:Infinity":80,"s:182:6:182:Infinity":81,"s:183:6:183:Infinity":82,"s:185:6:185:Infinity":83,"b:189:2:204:Infinity:undefined:undefined:undefined:undefined":38,"s:189:2:204:Infinity":84,"b:190:4:192:Infinity:undefined:undefined:undefined:undefined":39,"s:190:4:192:Infinity":85,"s:191:6:191:Infinity":86,"b:193:4:195:Infinity:undefined:undefined:undefined:undefined":40,"s:193:4:195:Infinity":87,"b:193:8:193:35:193:35:193:64":41,"s:194:6:194:Infinity":88,"b:196:4:199:Infinity:undefined:undefined:undefined:undefined":42,"s:196:4:199:Infinity":89,"s:197:6:197:Infinity":90,"s:198:6:198:Infinity":91,"b:200:4:203:Infinity:undefined:undefined:undefined:undefined":43,"s:200:4:203:Infinity":92,"b:200:8:200:42:200:42:200:77":44,"s:201:6:201:Infinity":93,"s:202:6:202:Infinity":94,"b:206:2:218:Infinity:undefined:undefined:undefined:undefined":45,"s:206:2:218:Infinity":95,"b:207:4:207:Infinity:208:4:208:Infinity:210:6:210:Infinity:211:6:211:Infinity:212:6:212:Infinity:213:6:213:Infinity:214:6:214:33":46,"s:217:4:217:Infinity":96,"b:221:2:236:Infinity:undefined:undefined:undefined:undefined":47,"s:221:2:236:Infinity":97,"b:222:4:224:Infinity:undefined:undefined:undefined:undefined":48,"s:222:4:224:Infinity":98,"s:223:6:223:Infinity":99,"b:225:4:227:Infinity:undefined:undefined:undefined:undefined":49,"s:225:4:227:Infinity":100,"s:226:6:226:Infinity":101,"b:228:4:230:Infinity:undefined:undefined:undefined:undefined":50,"s:228:4:230:Infinity":102,"b:228:8:228:35:228:35:228:68":51,"s:229:6:229:Infinity":103,"b:231:4:235:Infinity:undefined:undefined:undefined:undefined":52,"s:231:4:235:Infinity":104,"b:231:8:231:37:231:37:231:69":53,"s:232:6:232:Infinity":105,"s:233:6:233:Infinity":106,"s:234:6:234:Infinity":107,"b:238:2:240:Infinity:undefined:undefined:undefined:undefined":54,"s:238:2:240:Infinity":108,"b:238:6:238:35:238:35:238:69:238:69:238:89":55,"s:239:4:239:Infinity":109,"b:242:2:244:Infinity:undefined:undefined:undefined:undefined":56,"s:242:2:244:Infinity":110,"b:242:6:242:35:242:35:242:61":57,"s:243:4:243:Infinity":111,"s:247:19:247:Infinity":112,"b:247:46:247:56:247:56:247:Infinity":58,"b:247:83:247:93:247:93:247:Infinity":59,"b:248:2:250:Infinity:undefined:undefined:undefined:undefined":60,"s:248:2:250:Infinity":113,"s:249:4:249:Infinity":114,"b:253:2:268:Infinity:undefined:undefined:undefined:undefined":61,"s:253:2:268:Infinity":115,"b:254:4:262:Infinity:undefined:undefined:undefined:undefined":62,"s:254:4:262:Infinity":116,"b:255:6:255:Infinity:256:6:256:Infinity:257:6:257:Infinity:258:6:258:Infinity":63,"s:260:6:260:Infinity":117,"s:261:6:261:Infinity":118,"b:264:4:267:Infinity:undefined:undefined:undefined:undefined":64,"s:264:4:267:Infinity":119,"s:265:6:265:Infinity":120,"s:266:6:266:Infinity":121,"s:270:8:270:Infinity":122,"s:271:2:271:Infinity":123,"s:274:19:274:Infinity":124,"s:275:2:281:Infinity":125,"b:275:22:281:10:281:10:281:12":65,"f:275:40:275:Infinity":3,"s:276:19:276:Infinity":126,"b:277:6:277:Infinity:undefined:undefined:undefined:undefined":66,"s:277:6:277:Infinity":127,"b:277:10:277:41:277:41:277:71":67,"s:277:71:277:Infinity":128,"b:278:6:278:Infinity:undefined:undefined:undefined:undefined":68,"s:278:6:278:Infinity":129,"b:278:10:278:40:278:40:278:67":69,"s:278:67:278:Infinity":130,"b:279:6:279:Infinity:undefined:undefined:undefined:undefined":70,"s:279:6:279:Infinity":131,"b:279:10:279:42:279:42:279:68":71,"s:279:68:279:Infinity":132,"s:280:6:280:Infinity":133,"b:283:0:294:Infinity:undefined:undefined:undefined:undefined":72,"s:283:0:294:Infinity":134,"s:284:20:284:Infinity":135,"b:286:2:288:Infinity:287:7:288:Infinity":73,"s:286:2:288:Infinity":136,"s:286:31:286:Infinity":137,"b:287:7:288:Infinity:288:7:288:Infinity":74,"s:287:7:288:Infinity":138,"s:287:33:287:Infinity":139,"s:288:7:288:Infinity":140,"s:290:2:290:Infinity":141,"s:292:2:292:Infinity":142,"s:293:2:293:Infinity":143,"b:298:2:304:Infinity:undefined:undefined:undefined:undefined":75,"s:298:2:304:Infinity":144,"b:299:4:299:Infinity:300:4:300:Infinity:301:4:301:Infinity":76,"f:301:20:301:25":4,"s:301:25:301:Infinity":145,"s:303:4:303:Infinity":146,"b:307:2:319:Infinity:undefined:undefined:undefined:undefined":77,"s:307:2:319:Infinity":147,"b:308:4:308:Infinity:309:4:309:Infinity:311:6:311:Infinity:312:6:312:Infinity:313:6:313:Infinity:314:6:314:Infinity:315:6:315:33":78,"s:318:4:318:Infinity":148,"b:322:2:329:Infinity:undefined:undefined:undefined:undefined":79,"s:322:2:329:Infinity":149,"b:322:6:322:Infinity:323:4:323:Infinity:324:4:324:Infinity:325:4:325:Infinity:326:4:326:28":80,"s:328:4:328:Infinity":150,"s:332:25:334:Infinity":151,"b:333:6:333:Infinity:334:6:334:Infinity":81,"b:333:6:333:38:333:38:333:Infinity":82,"s:337:4:341:Infinity":152,"b:337:4:337:Infinity:338:4:338:Infinity:339:4:339:Infinity:340:4:340:Infinity:341:4:341:Infinity":83,"b:343:2:345:Infinity:undefined:undefined:undefined:undefined":84,"s:343:2:345:Infinity":153,"b:343:6:343:24:343:24:343:41":85,"s:344:4:344:Infinity":154,"s:347:2:352:Infinity":155,"s:356:2:356:Infinity":156}}} +,"E:\\Job\\ComputerSelectorHelper\\src\\utils\\laptopModels.js": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\laptopModels.js","statementMap":{"0":{"start":{"line":1,"column":24},"end":{"line":2,"column":75}},"1":{"start":{"line":2,"column":2},"end":{"line":2,"column":75}},"2":{"start":{"line":4,"column":21},"end":{"line":269,"column":1}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":1,"column":24},"end":{"line":1,"column":25}},"loc":{"start":{"line":2,"column":2},"end":{"line":2,"column":75}},"line":2}},"branchMap":{},"s":{"0":2,"1":46,"2":2},"f":{"0":46},"b":{},"meta":{"lastBranch":0,"lastFunction":1,"lastStatement":3,"seen":{"s:1:24:2:75":0,"f:1:24:1:25":0,"s:2:2:2:75":1,"s:4:21:269:1":2}}} +,"E:\\Job\\ComputerSelectorHelper\\src\\utils\\matchLaptopClass.jsx": {"path":"E:\\Job\\ComputerSelectorHelper\\src\\utils\\matchLaptopClass.jsx","statementMap":{"0":{"start":{"line":3,"column":18},"end":{"line":5,"column":null}},"1":{"start":{"line":4,"column":2},"end":{"line":5,"column":null}},"2":{"start":{"line":7,"column":19},"end":{"line":8,"column":null}},"3":{"start":{"line":8,"column":2},"end":{"line":8,"column":null}},"4":{"start":{"line":11,"column":25},"end":{"line":92,"column":null}},"5":{"start":{"line":12,"column":27},"end":{"line":41,"column":null}},"6":{"start":{"line":13,"column":21},"end":{"line":13,"column":null}},"7":{"start":{"line":14,"column":20},"end":{"line":14,"column":null}},"8":{"start":{"line":15,"column":23},"end":{"line":15,"column":null}},"9":{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},"10":{"start":{"line":17,"column":61},"end":{"line":17,"column":null}},"11":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"12":{"start":{"line":18,"column":81},"end":{"line":18,"column":null}},"13":{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},"14":{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},"15":{"start":{"line":20,"column":57},"end":{"line":20,"column":null}},"16":{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},"17":{"start":{"line":21,"column":137},"end":{"line":21,"column":null}},"18":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"19":{"start":{"line":24,"column":58},"end":{"line":24,"column":null}},"20":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"21":{"start":{"line":25,"column":79},"end":{"line":25,"column":null}},"22":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"23":{"start":{"line":26,"column":78},"end":{"line":26,"column":null}},"24":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"25":{"start":{"line":27,"column":64},"end":{"line":27,"column":null}},"26":{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},"27":{"start":{"line":31,"column":6},"end":{"line":31,"column":null}},"28":{"start":{"line":34,"column":18},"end":{"line":34,"column":null}},"29":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"30":{"start":{"line":36,"column":48},"end":{"line":36,"column":null}},"31":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"32":{"start":{"line":37,"column":52},"end":{"line":37,"column":null}},"33":{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},"34":{"start":{"line":38,"column":50},"end":{"line":38,"column":null}},"35":{"start":{"line":40,"column":4},"end":{"line":40,"column":null}},"36":{"start":{"line":43,"column":23},"end":{"line":84,"column":null}},"37":{"start":{"line":44,"column":16},"end":{"line":44,"column":null}},"38":{"start":{"line":46,"column":23},"end":{"line":46,"column":null}},"39":{"start":{"line":47,"column":21},"end":{"line":47,"column":null}},"40":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"41":{"start":{"line":49,"column":68},"end":{"line":49,"column":null}},"42":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"43":{"start":{"line":50,"column":54},"end":{"line":50,"column":null}},"44":{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},"45":{"start":{"line":51,"column":60},"end":{"line":51,"column":null}},"46":{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},"47":{"start":{"line":52,"column":58},"end":{"line":52,"column":null}},"48":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"49":{"start":{"line":53,"column":49},"end":{"line":53,"column":null}},"50":{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},"51":{"start":{"line":54,"column":66},"end":{"line":54,"column":null}},"52":{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},"53":{"start":{"line":57,"column":6},"end":{"line":57,"column":null}},"54":{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},"55":{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},"56":{"start":{"line":61,"column":8},"end":{"line":61,"column":null}},"57":{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},"58":{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},"59":{"start":{"line":73,"column":8},"end":{"line":73,"column":null}},"60":{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},"61":{"start":{"line":80,"column":6},"end":{"line":80,"column":null}},"62":{"start":{"line":83,"column":4},"end":{"line":83,"column":null}},"63":{"start":{"line":86,"column":22},"end":{"line":88,"column":17}},"64":{"start":{"line":87,"column":20},"end":{"line":87,"column":47}},"65":{"start":{"line":90,"column":2},"end":{"line":90,"column":null}}},"fnMap":{"0":{"name":"(anonymous_0)","decl":{"start":{"line":3,"column":18},"end":{"line":3,"column":19}},"loc":{"start":{"line":4,"column":2},"end":{"line":5,"column":null}},"line":4},"1":{"name":"(anonymous_1)","decl":{"start":{"line":7,"column":19},"end":{"line":7,"column":20}},"loc":{"start":{"line":8,"column":2},"end":{"line":8,"column":null}},"line":8},"2":{"name":"(anonymous_2)","decl":{"start":{"line":11,"column":25},"end":{"line":11,"column":26}},"loc":{"start":{"line":11,"column":26},"end":{"line":92,"column":null}},"line":11},"3":{"name":"(anonymous_3)","decl":{"start":{"line":12,"column":47},"end":{"line":12,"column":null}},"loc":{"start":{"line":12,"column":47},"end":{"line":41,"column":null}},"line":12},"4":{"name":"(anonymous_4)","decl":{"start":{"line":43,"column":44},"end":{"line":43,"column":null}},"loc":{"start":{"line":43,"column":44},"end":{"line":84,"column":null}},"line":43},"5":{"name":"(anonymous_5)","decl":{"start":{"line":87,"column":10},"end":{"line":87,"column":11}},"loc":{"start":{"line":87,"column":20},"end":{"line":87,"column":47}},"line":87}},"branchMap":{"0":{"loc":{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},"type":"if","locations":[{"start":{"line":17,"column":4},"end":{"line":17,"column":null}},{"start":{},"end":{}}],"line":17},"1":{"loc":{"start":{"line":17,"column":8},"end":{"line":17,"column":61}},"type":"binary-expr","locations":[{"start":{"line":17,"column":8},"end":{"line":17,"column":35}},{"start":{"line":17,"column":35},"end":{"line":17,"column":61}}],"line":17},"2":{"loc":{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},"type":"if","locations":[{"start":{"line":18,"column":4},"end":{"line":18,"column":null}},{"start":{},"end":{}}],"line":18},"3":{"loc":{"start":{"line":18,"column":8},"end":{"line":18,"column":81}},"type":"binary-expr","locations":[{"start":{"line":18,"column":8},"end":{"line":18,"column":35}},{"start":{"line":18,"column":35},"end":{"line":18,"column":81}}],"line":18},"4":{"loc":{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},"type":"if","locations":[{"start":{"line":19,"column":4},"end":{"line":22,"column":null}},{"start":{},"end":{}}],"line":19},"5":{"loc":{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},"type":"if","locations":[{"start":{"line":20,"column":6},"end":{"line":20,"column":null}},{"start":{},"end":{}}],"line":20},"6":{"loc":{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},"type":"if","locations":[{"start":{"line":21,"column":6},"end":{"line":21,"column":null}},{"start":{},"end":{}}],"line":21},"7":{"loc":{"start":{"line":21,"column":10},"end":{"line":21,"column":137}},"type":"binary-expr","locations":[{"start":{"line":21,"column":10},"end":{"line":21,"column":53}},{"start":{"line":21,"column":53},"end":{"line":21,"column":96}},{"start":{"line":21,"column":96},"end":{"line":21,"column":137}}],"line":21},"8":{"loc":{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},"type":"if","locations":[{"start":{"line":24,"column":4},"end":{"line":24,"column":null}},{"start":{},"end":{}}],"line":24},"9":{"loc":{"start":{"line":24,"column":8},"end":{"line":24,"column":58}},"type":"binary-expr","locations":[{"start":{"line":24,"column":8},"end":{"line":24,"column":32}},{"start":{"line":24,"column":32},"end":{"line":24,"column":58}}],"line":24},"10":{"loc":{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},"type":"if","locations":[{"start":{"line":25,"column":4},"end":{"line":25,"column":null}},{"start":{},"end":{}}],"line":25},"11":{"loc":{"start":{"line":25,"column":8},"end":{"line":25,"column":79}},"type":"binary-expr","locations":[{"start":{"line":25,"column":8},"end":{"line":25,"column":38}},{"start":{"line":25,"column":38},"end":{"line":25,"column":79}}],"line":25},"12":{"loc":{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},"type":"if","locations":[{"start":{"line":26,"column":4},"end":{"line":26,"column":null}},{"start":{},"end":{}}],"line":26},"13":{"loc":{"start":{"line":26,"column":8},"end":{"line":26,"column":78}},"type":"binary-expr","locations":[{"start":{"line":26,"column":8},"end":{"line":26,"column":51}},{"start":{"line":26,"column":51},"end":{"line":26,"column":78}}],"line":26},"14":{"loc":{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},"type":"if","locations":[{"start":{"line":27,"column":4},"end":{"line":27,"column":null}},{"start":{},"end":{}}],"line":27},"15":{"loc":{"start":{"line":27,"column":8},"end":{"line":27,"column":64}},"type":"binary-expr","locations":[{"start":{"line":27,"column":8},"end":{"line":27,"column":33}},{"start":{"line":27,"column":33},"end":{"line":27,"column":64}}],"line":27},"16":{"loc":{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},"type":"if","locations":[{"start":{"line":28,"column":4},"end":{"line":31,"column":null}},{"start":{},"end":{}}],"line":28},"17":{"loc":{"start":{"line":29,"column":6},"end":{"line":30,"column":null}},"type":"binary-expr","locations":[{"start":{"line":29,"column":7},"end":{"line":29,"column":41}},{"start":{"line":29,"column":41},"end":{"line":29,"column":75}},{"start":{"line":30,"column":6},"end":{"line":30,"column":36}},{"start":{"line":30,"column":36},"end":{"line":30,"column":null}}],"line":29},"18":{"loc":{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},"type":"if","locations":[{"start":{"line":36,"column":4},"end":{"line":36,"column":null}},{"start":{},"end":{}}],"line":36},"19":{"loc":{"start":{"line":36,"column":8},"end":{"line":36,"column":48}},"type":"binary-expr","locations":[{"start":{"line":36,"column":8},"end":{"line":36,"column":34}},{"start":{"line":36,"column":34},"end":{"line":36,"column":48}}],"line":36},"20":{"loc":{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},"type":"if","locations":[{"start":{"line":37,"column":4},"end":{"line":37,"column":null}},{"start":{},"end":{}}],"line":37},"21":{"loc":{"start":{"line":37,"column":8},"end":{"line":37,"column":52}},"type":"binary-expr","locations":[{"start":{"line":37,"column":8},"end":{"line":37,"column":37}},{"start":{"line":37,"column":37},"end":{"line":37,"column":52}}],"line":37},"22":{"loc":{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},"type":"if","locations":[{"start":{"line":38,"column":4},"end":{"line":38,"column":null}},{"start":{},"end":{}}],"line":38},"23":{"loc":{"start":{"line":38,"column":8},"end":{"line":38,"column":50}},"type":"binary-expr","locations":[{"start":{"line":38,"column":8},"end":{"line":38,"column":35}},{"start":{"line":38,"column":35},"end":{"line":38,"column":50}}],"line":38},"24":{"loc":{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},"type":"if","locations":[{"start":{"line":49,"column":4},"end":{"line":49,"column":null}},{"start":{},"end":{}}],"line":49},"25":{"loc":{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},"type":"if","locations":[{"start":{"line":50,"column":4},"end":{"line":50,"column":null}},{"start":{},"end":{}}],"line":50},"26":{"loc":{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},"type":"if","locations":[{"start":{"line":51,"column":4},"end":{"line":51,"column":null}},{"start":{},"end":{}}],"line":51},"27":{"loc":{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},"type":"if","locations":[{"start":{"line":52,"column":4},"end":{"line":52,"column":null}},{"start":{},"end":{}}],"line":52},"28":{"loc":{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},"type":"if","locations":[{"start":{"line":53,"column":4},"end":{"line":53,"column":null}},{"start":{},"end":{}}],"line":53},"29":{"loc":{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},"type":"if","locations":[{"start":{"line":54,"column":4},"end":{"line":54,"column":null}},{"start":{},"end":{}}],"line":54},"30":{"loc":{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},"type":"if","locations":[{"start":{"line":56,"column":4},"end":{"line":58,"column":null}},{"start":{},"end":{}}],"line":56},"31":{"loc":{"start":{"line":56,"column":8},"end":{"line":56,"column":76}},"type":"binary-expr","locations":[{"start":{"line":56,"column":8},"end":{"line":56,"column":46}},{"start":{"line":56,"column":46},"end":{"line":56,"column":76}}],"line":56},"32":{"loc":{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},"type":"if","locations":[{"start":{"line":59,"column":4},"end":{"line":63,"column":null}},{"start":{},"end":{}}],"line":59},"33":{"loc":{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},"type":"if","locations":[{"start":{"line":60,"column":6},"end":{"line":62,"column":null}},{"start":{},"end":{}}],"line":60},"34":{"loc":{"start":{"line":60,"column":10},"end":{"line":60,"column":91}},"type":"binary-expr","locations":[{"start":{"line":60,"column":10},"end":{"line":60,"column":38}},{"start":{"line":60,"column":38},"end":{"line":60,"column":66}},{"start":{"line":60,"column":66},"end":{"line":60,"column":91}}],"line":60},"35":{"loc":{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},"type":"if","locations":[{"start":{"line":65,"column":4},"end":{"line":75,"column":null}},{"start":{},"end":{}}],"line":65},"36":{"loc":{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},"type":"if","locations":[{"start":{"line":66,"column":6},"end":{"line":74,"column":null}},{"start":{},"end":{}}],"line":66},"37":{"loc":{"start":{"line":67,"column":8},"end":{"line":71,"column":null}},"type":"binary-expr","locations":[{"start":{"line":67,"column":8},"end":{"line":67,"column":null}},{"start":{"line":68,"column":8},"end":{"line":68,"column":null}},{"start":{"line":69,"column":8},"end":{"line":69,"column":null}},{"start":{"line":70,"column":8},"end":{"line":70,"column":null}},{"start":{"line":71,"column":8},"end":{"line":71,"column":null}}],"line":67},"38":{"loc":{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},"type":"if","locations":[{"start":{"line":79,"column":4},"end":{"line":81,"column":null}},{"start":{},"end":{}}],"line":79},"39":{"loc":{"start":{"line":79,"column":8},"end":{"line":79,"column":89}},"type":"binary-expr","locations":[{"start":{"line":79,"column":8},"end":{"line":79,"column":43}},{"start":{"line":79,"column":43},"end":{"line":79,"column":89}}],"line":79},"40":{"loc":{"start":{"line":90,"column":9},"end":{"line":90,"column":null}},"type":"cond-expr","locations":[{"start":{"line":90,"column":30},"end":{"line":90,"column":44}},{"start":{"line":90,"column":44},"end":{"line":90,"column":null}}],"line":90}},"s":{"0":2,"1":1200,"2":2,"3":794,"4":2,"5":57,"6":1311,"7":1311,"8":1311,"9":1311,"10":49,"11":1262,"12":16,"13":1246,"14":184,"15":160,"16":24,"17":8,"18":1078,"19":0,"20":1078,"21":0,"22":1078,"23":208,"24":870,"25":56,"26":814,"27":20,"28":794,"29":794,"30":49,"31":745,"32":427,"33":318,"34":18,"35":300,"36":57,"37":300,"38":300,"39":300,"40":300,"41":0,"42":300,"43":139,"44":300,"45":0,"46":300,"47":100,"48":300,"49":0,"50":300,"51":0,"52":300,"53":4,"54":300,"55":7,"56":7,"57":300,"58":15,"59":4,"60":300,"61":0,"62":300,"63":57,"64":529,"65":57},"f":{"0":1200,"1":794,"2":57,"3":1311,"4":300,"5":529},"b":{"0":[49,1262],"1":[1311,161],"2":[16,1246],"3":[1262,112],"4":[184,1062],"5":[160,24],"6":[8,16],"7":[24,16,8],"8":[0,1078],"9":[1078,16],"10":[0,1078],"11":[1078,0],"12":[208,870],"13":[1078,292],"14":[56,814],"15":[870,116],"16":[20,794],"17":[814,779,35,25],"18":[49,745],"19":[794,59],"20":[427,318],"21":[745,653],"22":[18,300],"23":[318,82],"24":[0,300],"25":[139,161],"26":[0,300],"27":[100,200],"28":[0,300],"29":[0,300],"30":[4,296],"31":[300,8],"32":[7,293],"33":[7,0],"34":[7,0,0],"35":[15,285],"36":[4,11],"37":[15,14,11,11,11],"38":[0,300],"39":[300,8],"40":[40,17]},"meta":{"lastBranch":41,"lastFunction":6,"lastStatement":66,"seen":{"s:3:18:5:Infinity":0,"f:3:18:3:19":0,"s:4:2:5:Infinity":1,"s:7:19:8:Infinity":2,"f:7:19:7:20":1,"s:8:2:8:Infinity":3,"s:11:25:92:Infinity":4,"f:11:25:11:26":2,"s:12:27:41:Infinity":5,"f:12:47:12:Infinity":3,"s:13:21:13:Infinity":6,"s:14:20:14:Infinity":7,"s:15:23:15:Infinity":8,"b:17:4:17:Infinity:undefined:undefined:undefined:undefined":0,"s:17:4:17:Infinity":9,"b:17:8:17:35:17:35:17:61":1,"s:17:61:17:Infinity":10,"b:18:4:18:Infinity:undefined:undefined:undefined:undefined":2,"s:18:4:18:Infinity":11,"b:18:8:18:35:18:35:18:81":3,"s:18:81:18:Infinity":12,"b:19:4:22:Infinity:undefined:undefined:undefined:undefined":4,"s:19:4:22:Infinity":13,"b:20:6:20:Infinity:undefined:undefined:undefined:undefined":5,"s:20:6:20:Infinity":14,"s:20:57:20:Infinity":15,"b:21:6:21:Infinity:undefined:undefined:undefined:undefined":6,"s:21:6:21:Infinity":16,"b:21:10:21:53:21:53:21:96:21:96:21:137":7,"s:21:137:21:Infinity":17,"b:24:4:24:Infinity:undefined:undefined:undefined:undefined":8,"s:24:4:24:Infinity":18,"b:24:8:24:32:24:32:24:58":9,"s:24:58:24:Infinity":19,"b:25:4:25:Infinity:undefined:undefined:undefined:undefined":10,"s:25:4:25:Infinity":20,"b:25:8:25:38:25:38:25:79":11,"s:25:79:25:Infinity":21,"b:26:4:26:Infinity:undefined:undefined:undefined:undefined":12,"s:26:4:26:Infinity":22,"b:26:8:26:51:26:51:26:78":13,"s:26:78:26:Infinity":23,"b:27:4:27:Infinity:undefined:undefined:undefined:undefined":14,"s:27:4:27:Infinity":24,"b:27:8:27:33:27:33:27:64":15,"s:27:64:27:Infinity":25,"b:28:4:31:Infinity:undefined:undefined:undefined:undefined":16,"s:28:4:31:Infinity":26,"b:29:7:29:41:29:41:29:75:30:6:30:36:30:36:30:Infinity":17,"s:31:6:31:Infinity":27,"s:34:18:34:Infinity":28,"b:36:4:36:Infinity:undefined:undefined:undefined:undefined":18,"s:36:4:36:Infinity":29,"b:36:8:36:34:36:34:36:48":19,"s:36:48:36:Infinity":30,"b:37:4:37:Infinity:undefined:undefined:undefined:undefined":20,"s:37:4:37:Infinity":31,"b:37:8:37:37:37:37:37:52":21,"s:37:52:37:Infinity":32,"b:38:4:38:Infinity:undefined:undefined:undefined:undefined":22,"s:38:4:38:Infinity":33,"b:38:8:38:35:38:35:38:50":23,"s:38:50:38:Infinity":34,"s:40:4:40:Infinity":35,"s:43:23:84:Infinity":36,"f:43:44:43:Infinity":4,"s:44:16:44:Infinity":37,"s:46:23:46:Infinity":38,"s:47:21:47:Infinity":39,"b:49:4:49:Infinity:undefined:undefined:undefined:undefined":24,"s:49:4:49:Infinity":40,"s:49:68:49:Infinity":41,"b:50:4:50:Infinity:undefined:undefined:undefined:undefined":25,"s:50:4:50:Infinity":42,"s:50:54:50:Infinity":43,"b:51:4:51:Infinity:undefined:undefined:undefined:undefined":26,"s:51:4:51:Infinity":44,"s:51:60:51:Infinity":45,"b:52:4:52:Infinity:undefined:undefined:undefined:undefined":27,"s:52:4:52:Infinity":46,"s:52:58:52:Infinity":47,"b:53:4:53:Infinity:undefined:undefined:undefined:undefined":28,"s:53:4:53:Infinity":48,"s:53:49:53:Infinity":49,"b:54:4:54:Infinity:undefined:undefined:undefined:undefined":29,"s:54:4:54:Infinity":50,"s:54:66:54:Infinity":51,"b:56:4:58:Infinity:undefined:undefined:undefined:undefined":30,"s:56:4:58:Infinity":52,"b:56:8:56:46:56:46:56:76":31,"s:57:6:57:Infinity":53,"b:59:4:63:Infinity:undefined:undefined:undefined:undefined":32,"s:59:4:63:Infinity":54,"b:60:6:62:Infinity:undefined:undefined:undefined:undefined":33,"s:60:6:62:Infinity":55,"b:60:10:60:38:60:38:60:66:60:66:60:91":34,"s:61:8:61:Infinity":56,"b:65:4:75:Infinity:undefined:undefined:undefined:undefined":35,"s:65:4:75:Infinity":57,"b:66:6:74:Infinity:undefined:undefined:undefined:undefined":36,"s:66:6:74:Infinity":58,"b:67:8:67:Infinity:68:8:68:Infinity:69:8:69:Infinity:70:8:70:Infinity:71:8:71:Infinity":37,"s:73:8:73:Infinity":59,"b:79:4:81:Infinity:undefined:undefined:undefined:undefined":38,"s:79:4:81:Infinity":60,"b:79:8:79:43:79:43:79:89":39,"s:80:6:80:Infinity":61,"s:83:4:83:Infinity":62,"s:86:22:88:17":63,"f:87:10:87:11":5,"s:87:20:87:47":64,"s:90:2:90:Infinity":65,"b:90:30:90:44:90:44:90:Infinity":40}}} +} diff --git a/coverage/favicon.png b/coverage/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 92.03% + Statements + 208/226 +
+ + +
+ 88.07% + Branches + 251/285 +
+ + +
+ 100% + Functions + 12/12 +
+ + +
+ 94.6% + Lines + 193/204 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
convertToSpecs.jsx +
+
92.99%146/15788.26%173/196100%5/593.42%142/152
laptopModels.js +
+
100%3/3100%0/0100%1/1100%3/3
matchLaptopClass.jsx +
+
89.39%59/6687.64%78/89100%6/697.95%48/49
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/laptopModels.js.html b/coverage/laptopModels.js.html new file mode 100644 index 0000000..5d22185 --- /dev/null +++ b/coverage/laptopModels.js.html @@ -0,0 +1,898 @@ + + + + + + Code coverage report for laptopModels.js + + + + + + + + + +
+
+

All files laptopModels.js

+
+ +
+ 100% + Statements + 3/3 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 3/3 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 +248 +249 +250 +251 +252 +253 +254 +255 +256 +257 +258 +259 +260 +261 +262 +263 +264 +265 +266 +267 +268 +269 +270 +271 +2722x +46x +  +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
const buildGoogleLink = (name) =>
+  `https://www.google.com/search?q=${encodeURIComponent(name + ' laptop')}`
+ 
+const laptopModels = [
+  {
+    id: "hp-stream-14",
+    name: "HP Stream 14",
+    use: "Estudiantes básicos, movilidad",
+    specs: "Intel Celeron N4120, 4 GB RAM, 64 GB SSD, 14” HD",
+    gpu: "Intel integrada",
+    price: "$4,199 MXN",
+    portability: "Alta",
+    image:
+      "https://www.notebookcheck.net/fileadmin/Notebooks/HP/Stream_14-ax002ng/2.jpg",
+    link: buildGoogleLink("HP Stream 14"),
+  },
+  {
+    id: "acer-aspire-go",
+    name: "Acer Aspire Go AG15",
+    use: "Uso general, estudiantes",
+    specs: "Intel Core i3, 8 GB RAM, 256 GB SSD, 15.6” FHD",
+    gpu: "Intel integrada",
+    price: "$5,999 MXN",
+    portability: "Media",
+    image:
+      "https://laptoping.com/specs/wp-content/uploads/2024/06/Acer-Aspire-Go-15-Slim-AG15-31P-3947.jpg",
+    link: buildGoogleLink("acer-aspire-go"),
+  },
+  {
+    id: "hp-victus-rtx3050",
+    name: "HP Victus RTX 3050",
+    use: "Gaming medio, tareas pesadas",
+    specs: "Intel i5 12450H, 32 GB RAM, 512 GB SSD, RTX 3050",
+    gpu: "RTX 3050",
+    price: "$14,999 MXN",
+    portability: "Media",
+    image:
+      "https://m.media-amazon.com/images/I/71XwPLUaKsL._AC_SL1500_.jpg",
+    link: buildGoogleLink("hp-victus-rtx3050"),
+  },
+  {
+    id: "macbook-air-m1",
+    name: "Apple MacBook Air M1",
+    use: "Creativos, portabilidad, estudiantes",
+    specs: "Apple M1, 8 GB RAM, 256 GB SSD, 13.3” Retina",
+    gpu: "M1",
+    price: "$13,999 MXN",
+    portability: "Alta",
+    image:
+      "https://th.bing.com/th/id/OIP.Q4muQjrfyxVhOB5PMwAF6gHaE8?cb=iwp2&rs=1&pid=ImgDetMainhttps://thangpro.vn/uploads/source/laptop/macbook/mac-air-m1/img-7707.jpg",
+    link: buildGoogleLink("macbook-air-m1"),
+  },
+  {
+    id: "asus-tuf-a15",
+    name: "ASUS TUF Gaming A15",
+    use: "Gaming alto, edición multimedia",
+    specs: "Ryzen 7, 8 GB RAM, 512 GB SSD, RTX 3050",
+    gpu: "RTX 3050",
+    price: "$16,339 MXN",
+    portability: "Media",
+    image:
+      "https://laptopmedia.com/wp-content/uploads/2021/01/2-40.jpg",
+    link: buildGoogleLink("asus-tuf-a15"),
+  },
+  {
+    id: "lenovo-chromebook-3",
+    name: "Lenovo Chromebook 3",
+    use: "Tareas básicas, navegación, Google Docs",
+    specs: "Intel Celeron N4020, 4 GB RAM, 64 GB eMMC, 14” HD",
+    gpu: "Intel integrada",
+    price: "$3,499 MXN",
+    portability: "Alta",
+    image:
+      "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6447/6447138_sd.jpg",
+    link: buildGoogleLink("lenovo-chromebook-3"),
+  },
+  {
+    id: "macbook-air-m2",
+    name: "Apple MacBook Air M2",
+    use: "Profesionales móviles, diseño, universidad",
+    specs: "Apple M2, 8 GB RAM, 256 GB SSD, 13.6” Retina",
+    gpu: "M2",
+    price: "$23,999 MXN",
+    portability: "Alta",
+    image:
+      "https://cdn.shopify.com/s/files/1/1706/9177/products/macbook-air-m2-midnight-custom-mac-bd_b82010cb-9405-4654-a165-9576a249e4e0.jpg",
+    link: buildGoogleLink("macbook-air-m2"),
+  },
+ 
+  {
+    id: "lenovo-gamer-rtx4070",
+    name: "Lenovo Legion RTX 4070",
+    use: "Gaming extremo, arquitectura, animación",
+    specs: "Intel i9, 32 GB RAM, 1TB SSD, RTX 4070",
+    gpu: "RTX 4070",
+    price: "$38,999 MXN",
+    portability: "Baja",
+    image:
+      "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6534/6534466ld.jpg",
+    link: buildGoogleLink("lenovo-gamer-rtx4070"),
+  },
+  {
+    id: "msi-gamer-rtx2060",
+    name: "MSI GF63 RTX 2060",
+    use: "Gaming clásico, edición semiprofesional",
+    specs: "Intel i5 10th Gen, 16 GB RAM, 512 GB SSD, RTX 2060",
+    gpu: "RTX 2060",
+    price: "$17,499 MXN",
+    portability: "Media",
+    image:
+      "https://i0.wp.com/phukienmaytinh.com.vn/wp-content/uploads/2020/02/MSI-GF63-3.jpg",
+    link: buildGoogleLink("msi-gamer-rtx2060"),
+  },
+  {
+    id: "huawei-matebook-d16-i5",
+    name: "Huawei MateBook D16",
+    use: "Trabajo exigente, multimedia, estudiantes",
+    specs: "Intel Core i5 13420H, 16 GB RAM, 1 TB SSD, 16\" FHD",
+    gpu: "Intel Iris Xe",
+    price: "$14,399 MXN",
+    portability: "Media",
+    image: "https://www.presse-citron.net/app/uploads/2021/05/DSC01401_DxO_PC.jpg",
+    link: buildGoogleLink("Huawei MateBook D16"),
+  },
+  {
+    id: "hp-pavilion-i7-1355u",
+    name: "HP Pavilion i7-1355U",
+    use: "Profesionales, multitarea avanzada, productividad",
+    specs: "Intel Core i7-1355U, 32 GB RAM, 1 TB SSD, 15.6\" FHD",
+    gpu: "Intel Iris Xe",
+    price: "$15,722 MXN",
+    portability: "Media",
+    image: "https://m.media-amazon.com/images/I/613hm2YD6mL._AC_.jpg",
+    link: buildGoogleLink("HP Pavilion i7-1355U"),
+  },
+  {
+    id: "msi-sword-16-i7",
+    name: "MSI Sword 16 HX",
+    use: "Gaming, edición profesional, exigencia gráfica",
+    specs: "Intel Core i7, 16 GB RAM, 1 TB SSD, 16\" FHD",
+    gpu: "RTX 4060",
+    price: "$33,999 MXN",
+    portability: "Media",
+    image: "https://storage-asset.msi.com/global/picture/image/feature/nb/2023_RPLS/sword16-hx-b14v/msi-sword16-display-new.png",
+    link: buildGoogleLink("MSI Sword 16 HX"),
+  },
+  {
+    id: "generic-intel-14",
+    name: "Laptop 14 Intel",
+    use: "Tareas básicas, ofimática, estudiantes",
+    specs: "Intel genérica, 8 GB RAM, 256 GB SSD, 14\" FHD",
+    gpu: "Intel integrada",
+    price: "$4,800 MXN",
+    portability: "Alta",
+    image: "https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6425/6425056_rd.jpg",
+    link: buildGoogleLink("Laptop 14 Intel 256gb"),
+  },
+    {
+    id: "macbook-air-m4-512",
+    name: "MacBook Air M4 (512 GB)",
+    use: "Profesionales móviles, diseño, rendimiento alto",
+    specs: "Apple M4, 8 GB RAM, 512 GB SSD, 13” Retina",
+    gpu: "Apple Silicon",
+    price: "$30,950 MXN",
+    portability: "Alta",
+    image: "https://ipadizate.com/hero/2023/01/MacBook-Air-M2-Chip-Purple-Feature.jpg", 
+    link: buildGoogleLink("MacBook Air M4 512 GB")
+  },
+  {
+    id: "acer-nitro-5-rtx4060",
+    name: "Acer Nitro 5 RTX 4060",
+    use: "Gaming exigente, edición profesional",
+    specs: "Intel Core i7-13620H, 16 GB RAM, 512 GB SSD, RTX 4060",
+    gpu: "RTX 4060",
+    price: "$20,642 MXN",
+    portability: "Media",
+    image: "https://www.notebookcheck.com/fileadmin/_processed_/9/2/csm_Acer_Nitro_5_RTX_4060_guenstigstes_gaming_notebook_deal_db9eae21d8.jpg",
+    link: buildGoogleLink("Acer Nitro 5 RTX 4060")
+  },
+  {
+    id: "hp-15-fd0999nr",
+    name: "HP 15-fd0999nr",
+    use: "Ofimática avanzada, estudiantes, productividad",
+    specs: "Intel Core i5-1235U, 16 GB RAM, 512 GB SSD, 15.6” FHD",
+    gpu: "Intel Iris Xe",
+    price: "$9,797 MXN",
+    portability: "Media",
+    image: "https://lidernotebooks.com.ar/wp-content/uploads/2024/10/HP-15-fd0999NR-Core-i5_0004_Capa-7.jpg",
+    link: buildGoogleLink("HP 15-fd0999nr")
+  },
+  {
+    id: "msi-gf63-gtx1650",
+    name: "MSI GF63 GTX 1650",
+    use: "Gaming básico, diseño ligero",
+    specs: "Intel Core i5-11400H, 8 GB RAM, 256 GB SSD, GTX 1650",
+    gpu: "GTX 1650",
+    price: "$11,999 MXN",
+    portability: "Media",
+    image: "https://laptop360.net/wp-content/uploads/2022/09/MSI-Gaming-GF63-Thin-11UC-3-1536x1152.jpg",
+    link: buildGoogleLink("MSI GF63 GTX 1650")
+  },
+  {
+    id: "lenovo-yoga-slim-7-ultra5",
+    name: "Lenovo Yoga Slim 7",
+    use: "Profesionales, estudiantes de ingeniería",
+    specs: "Intel Ultra 5 125H, 16 GB RAM, 1 TB SSD, 14” FHD",
+    gpu: "Intel integrada",
+    price: "$18,999 MXN",
+    portability: "Alta",
+    image: "https://cdn.mos.cms.futurecdn.net/4C7JXvghGrpY52MtBiN2cB.jpg",
+    link: buildGoogleLink("Lenovo Yoga Slim 7")
+  },
+  {
+  id: "machcreator-15n-i7",
+  name: "Laptop Machcreator 15N",
+  use: "Multitarea, trabajo profesional, estudiantes avanzados",
+  specs: "Intel Core i7 13620H, 16 GB RAM, 512 GB SSD, 15.6” FHD",
+  gpu: "Intel UHD Graphics",
+  price: "$14,553 MXN",
+  portability: "Media",
+  image: "https://global.machenike.com/cdn/shop/files/11_818b6222-59ce-4193-a68b-bbc8e043b8ef.jpg",
+  link: buildGoogleLink("Laptop Machcreator 15N i7"),
+},
+{
+  id: "machenike-l16pro-rtx4060",
+  name: "Machenike L16 Pro",
+  use: "Gaming exigente, edición profesional, multitarea avanzada",
+  specs: "Ryzen 7 7735H, 16 GB RAM DDR5, 1 TB SSD, 16” 2.5K 165Hz",
+  gpu: "Nvidia GeForce RTX 4060",
+  price: "$34,954 MXN",
+  portability: "Baja",
+  image: "https://http2.mlstatic.com/D_NQ_NP_875287-MLU73599603547_122023-O.webp", 
+  link: buildGoogleLink("Machenike L16 Pro RTX 4060"),
+},
+{
+  id: "samsung-galaxy-book4-360",
+  name: "Samsung Galaxy Book4 360",
+  use: "Multimedia, productividad, uso general, estudiantes",
+  specs: "Intel Core 7 Series 1, 16 GB RAM LPDDR5, 512 GB SSD, 15.6” FHD táctil",
+  gpu: "Intel Graphics integrada",
+  price: "$22,999 MXN",
+  portability: "Alta",
+  image: "https://media-ik.croma.com/prod/https://media.croma.com/image/upload/v1708516453/Croma%20Assets/Computers%20Peripherals/Laptop/Images/305055_4_bsd9ja.png",
+  link: buildGoogleLink("Samsung Galaxy Book4 360"),
+},
+{
+  id: "topgro-n5-i3-mx230",
+  name: "Topgro N5",
+  use: "Ofimática, uso general, multimedia básica",
+  specs: "Intel Core i3-1005G1, 16 GB RAM DDR4, 512 GB SSD, NVIDIA GeForce MX230, 15.6” FHD",
+  gpu: "NVIDIA GeForce MX230 (2 GB GDDR5)",
+  price: "$6,935 MXN",
+  portability: "Media",
+  image: "https://m.media-amazon.com/images/I/71gXrO3lsqL._AC_.jpg", 
+  link: buildGoogleLink("Topgro N5 i3 MX230"),
+},
+{
+  id: "vsap-n4020-14",
+  name: "VSAP Intel Celeron N4020",
+  use: "Ofimática, navegación, estudiantes",
+  specs: "Intel Celeron N4020, 8 GB RAM DDR4, 512 GB SSD, 14.1\" FHD",
+  gpu: "Intel UHD Graphics 600",
+  price: "$3,695 MXN",
+  portability: "Alta",
+  image: "https://static.wixstatic.com/media/c8ef56_df2decbc5ac442c28c399e2c9a2ee5e2~mv2.webp", 
+  link: buildGoogleLink("VSAP Intel Celeron N4020"),
+},
+ 
+];
+ 
+export default laptopModels;
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/matchLaptopClass.jsx.html b/coverage/matchLaptopClass.jsx.html new file mode 100644 index 0000000..d345696 --- /dev/null +++ b/coverage/matchLaptopClass.jsx.html @@ -0,0 +1,367 @@ + + + + + + Code coverage report for matchLaptopClass.jsx + + + + + + + + + +
+
+

All files matchLaptopClass.jsx

+
+ +
+ 89.39% + Statements + 59/66 +
+ + +
+ 87.64% + Branches + 78/89 +
+ + +
+ 100% + Functions + 6/6 +
+ + +
+ 97.95% + Lines + 48/49 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95  +  +2x +1200x +  +  +2x +794x +  +  +2x +57x +1311x +1311x +1311x +  +1311x +1262x +1246x +184x +24x +  +  +1078x +1078x +1078x +870x +814x +  +  +20x +  +  +794x +  +794x +745x +318x +  +300x +  +  +57x +300x +  +300x +300x +  +300x +300x +300x +300x +300x +300x +  +300x +4x +  +300x +7x +7x +  +  +  +300x +15x +  +  +  +  +  +  +4x +  +  +  +  +  +300x +  +  +  +300x +  +  +57x +529x +  +  +57x +  +  +  +  + 
import laptopModels from './laptopModels'
+ 
+const normalize = (text) =>
+  text.toLowerCase()
+    .replace(/(intel|amd|apple|core|ryzen|graphics|nvidia|geforce|radeon|gpu)|\s+/gi, '')
+ 
+const parsePrice = (priceString) =>
+  parseInt(priceString.replace(/[^\d]/g, ''), 10)
+ 
+ 
+const matchLaptopClass = (specs) => {
+  const compatibleModels = laptopModels.filter(model => {
+    const modelGPU = model.gpu.toLowerCase()
+    const modelOS = model.use.toLowerCase()
+    const modelSpecs = model.specs.toLowerCase()
+ 
+    if (specs.os === 'ChromeOS' && modelGPU.includes('rtx')) return false
+    if (specs.os === 'ChromeOS' && specs.processor.toLowerCase().includes('i7')) return false
+    if (specs.os === 'macOS') {
+      if (!model.name.toLowerCase().includes('macbook')) return false
+      if (!model.gpu.toLowerCase().includes('m1') && !model.gpu.toLowerCase().includes('m2') && !model.gpu.toLowerCase().includes('m3')) return false
+    }
+ 
+    Iif (specs.os === 'macOS' && modelGPU.includes('rtx')) return false
+    Iif (modelOS.includes('chrome') && specs.gpu.toLowerCase().includes('rtx')) return false
+    if (specs.gpu.toLowerCase().includes('rtx') && !modelGPU.includes('rtx')) return false
+    if (specs.ram === '16 GB' && !modelSpecs.includes('16 gb')) return false
+    if (
+      (specs.processor.includes('i7') || specs.processor.includes('ryzen 7')) &&
+      !modelSpecs.includes('i7') && !modelSpecs.includes('ryzen 7')
+    ) return false
+ 
+    // ✅ Presupuesto
+    const price = parsePrice(model.price)
+ 
+    if (specs.budget === 'low' && price > 6000) return false
+    if (specs.budget === 'medium' && price > 13000) return false
+    if (specs.budget === 'high' && price < 13000) return false
+ 
+    return true
+  })
+ 
+  const scoredModels = compatibleModels.map(model => {
+    let score = 0
+ 
+    const modelSpecs = model.specs.toLowerCase()
+    const modelGPU = model.gpu.toLowerCase()
+ 
+    Iif (normalize(modelSpecs).includes(normalize(specs.processor))) score += 2
+    if (modelSpecs.includes(specs.ram.toLowerCase())) score += 2
+    Iif (normalize(modelGPU).includes(normalize(specs.gpu))) score += 2
+    if (modelSpecs.includes(specs.storage.toLowerCase())) score += 1
+    Iif (model.portability === specs.portability) score += 1
+    Iif (model.use.toLowerCase().includes(specs.os.toLowerCase())) score += 1
+ 
+    if (specs.flags?.prioritizePortability && model.portability === 'Alta') {
+      score += 1.5
+    }
+    if (specs.flags?.prioritizeGaming) {
+      Eif (modelGPU.includes('rtx') || modelGPU.includes('gtx') || modelGPU.includes('mx')) {
+        score += 1.5
+      }
+    }
+ 
+    if (specs.flags?.prioritizeScreenQuality) {
+      if (
+        modelSpecs.includes('2.5k') ||
+        modelSpecs.includes('retina') ||
+        modelSpecs.includes('qhd') ||
+        modelSpecs.includes('2560') ||
+        modelSpecs.includes('amoled')
+      ) {
+        score += 1.5
+      }
+    }
+ 
+ 
+ 
+    Iif (specs.flags?.prioritizeSecurity && model.specs.toLowerCase().includes('huella')) {
+      score += 1.5
+    }
+ 
+    return { ...model, matchScore: score }
+  })
+ 
+  const bestMatches = scoredModels
+    .sort((a, b) => b.matchScore - a.matchScore)
+    .slice(0, 3) // 👈 Cambia 3 por cuántas quieres mostrar
+ 
+  return bestMatches.length ? bestMatches : [{ name: "Clase genérica", specs: "" }]
+ 
+}
+ 
+export default matchLaptopClass
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/coverage/prettify.css b/coverage/prettify.css new file mode 100644 index 0000000..b317a7c --- /dev/null +++ b/coverage/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/coverage/prettify.js b/coverage/prettify.js new file mode 100644 index 0000000..b322523 --- /dev/null +++ b/coverage/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/coverage/sort-arrow-sprite.png b/coverage/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/coverage/sorter.js b/coverage/sorter.js new file mode 100644 index 0000000..4ed70ae --- /dev/null +++ b/coverage/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/docs/FEATURE_COMPARE.md b/docs/FEATURE_COMPARE.md new file mode 100644 index 0000000..2285a2a --- /dev/null +++ b/docs/FEATURE_COMPARE.md @@ -0,0 +1,158 @@ +# Feature: Módulo de Comparación de Laptops + +**Fecha:** 2026-05-10 +**Ruta:** `/compare` + +--- + +## Descripción + +Nuevo módulo que permite seleccionar hasta **3 laptops** del catálogo y compararlas lado a lado en una tabla de especificaciones, con resaltado automático del mejor valor en cada fila. + +--- + +## Flujo de usuario + +``` +Landing / Resultados + └── Click "Comparar" en la nav + └── /compare + ├── Buscar / filtrar modelos + ├── Click en cards para seleccionar (max 3) + └── Tabla comparativa aparece automáticamente + └── Botón "Compartir comparación" +``` + +--- + +## Características implementadas + +### Selector de modelos +- Grid de tarjetas con imagen, nombre y precio +- Filtro por sistema operativo (Windows / macOS / ChromeOS) +- Búsqueda por nombre, specs o GPU en tiempo real +- Ordenamiento por: precio, RAM, GPU tier o nombre +- Hasta 3 modelos seleccionables (cards extra quedan deshabilitadas) +- Feedback visual: borde azul + fondo azul claro al seleccionar + +### Tabla comparativa +- Se muestra automáticamente al seleccionar 2 o más modelos +- Scroll horizontal en mobile +- Fila "Quitar" con botón `×` por modelo +- Filas comparadas: + | Fila | Lógica de highlight | + |------|-------------------| + | 💰 Precio | Verde = menor precio | + | 🧠 RAM | Verde = más GB | + | 💾 Almacenamiento | Verde = más GB/TB | + | 🎮 GPU | Verde = mayor tier | + | ⚙️ Procesador | Sin highlight (textual) | + | 🖥️ Pantalla | Sin highlight (textual + tipo) | + | 💻 Sistema | Sin highlight | + | 🎒 Portabilidad | Sin highlight | + | ✅ Ideal para | Sin highlight | + | 🔗 Ver | Link a búsqueda en Google | + +### Compartir comparación +- URL: `/compare?models=id1,id2,id3` +- Botón "Compartir comparación" copia la URL al clipboard +- Al abrir el link, los modelos se pre-seleccionan automáticamente +- La selección actualiza el URL en tiempo real via `history.replaceState` + +### Navegación +- Nuevo ítem "Comparar" en la barra de nav global (`AppNav`) +- Botón "Volver" retorna a la página anterior (`navigate(-1)`) + +--- + +## Archivos creados + +| Archivo | Descripción | +|---------|-------------| +| `src/pages/Compare/Compare.jsx` | Componente principal de la página | +| `src/pages/Compare/Compare.css` | Estilos: grid, tabla, filtros, badges | +| `src/utils/parseSpecs.js` | Parser de spec strings → campos numéricos | + +## Archivos modificados + +| Archivo | Cambio | +|---------|--------| +| `src/routes/AppRoutes.jsx` | Agregada ruta `/compare` | +| `src/App.jsx` | Agregada `
` headers - -#### 4. Shareable Results Link -**Agents**: AgentQA, AgentResearch -- Encode quiz answers + results into URL -- "Copy link" and "Share on WhatsApp" buttons -- WhatsApp is critical for Mexican market -- Enables word-of-mouth growth - -### 🟡 Tier 2 — Quality & Performance (Should Have) - -#### 5. TypeScript Migration -**Agent**: AgentArquitecto -- Incremental: `.tsx` + `allowJs: true` -- Define interfaces for Laptop, UserAnswers, SpecProfile -- Start with data models, then components, then inference engine - -#### 6. Route-Level Code Splitting -**Agent**: AgentPerformance -- `React.lazy()` + `Suspense` for each route -- Defer Recommendation page until after quiz -- Estimated 30-40% reduction in initial JS bundle - -#### 7. Inference Result Caching (Idempotency) -**Agent**: AgentPerformance -- Deterministic cache key from answers (sorted JSON hash) -- `sessionStorage` or in-memory `Map` with 24h TTL -- Eliminates ~5-15ms on repeated runs - -#### 8. Throttled localStorage Writes -**Agent**: AgentPerformance -- Debounce writes: only on step changes, not every toggle -- Reduces localStorage writes from ~10+ to 2-3 per quiz -- Improves INP during quiz-taking - -#### 9. "How We Recommend" Transparency Page -**Agent**: AgentResearch -- Visual breakdown of scoring engine -- How each quiz answer maps to specs -- Builds trust, doubles as SEO content - -#### 10. Buying Guides by Use Case (SEO) -**Agent**: AgentResearch -- Static guide pages: /guides/gaming, /guides/engineering, /guides/students -- Embedded quiz CTA: "¿No estás seguro? Haz nuestro quiz" -- Drives organic traffic - -#### 11. Dark Mode -**Agent**: UIUXAdvisor -- `prefers-color-scheme` detection + manual toggle -- Use existing CSS variable system -- Persist in localStorage - -#### 12. Page Transitions & Micro-interactions -**Agent**: UIUXAdvisor -- Slide transitions for quiz questions -- Staggered fade-in for results -- Respect `prefers-reduced-motion` - -#### 13. FAQ Section + Structured Data -**Agent**: UIUXAdvisor -- Accordion on landing page -- JSON-LD `FAQPage` schema for rich snippets -- 6-8 questions addressing common concerns - -### 🟢 Tier 3 — Nice to Have (Could Have) - -#### 14. User Feedback Mechanism -**Agents**: AgentQA, AgentResearch -- 👍/👎 per laptop card -- Aggregate counts: "X users found this helpful" -- Phase 2: anonymous backend aggregation - -#### 15. Image Optimization (WebP/AVIF) -**Agent**: AgentPerformance -- Convert 35 laptop images to WebP -- Add `srcset`, `sizes`, `decoding="async"` -- 60-80% reduction in image payload - -#### 16. CSP Headers -**Agent**: AgentArquitecto -- `vercel.json` with CSP headers -- Start with `report-only` mode -- Protect against XSS - -#### 17. Sentry Error Tracking -**Agent**: JuniorQwen -- Production error visibility -- Breadcrumb tracking for quiz steps -- Source maps for readable stack traces - -#### 18. Skeleton Loading States -**Agent**: UIUXAdvisor -- Skeleton cards during inference -- `aria-busy="true"` for screen readers -- Reduces perceived wait time - -#### 19. Print/Export PDF Results -**Agent**: UIUXAdvisor -- `@media print` stylesheet -- QR code linking back to app -- Users take recommendations to physical stores - -#### 20. Dependency Update Automation -**Agent**: JuniorQwen -- Dependabot or Renovate -- `npm audit` in CI pipeline -- Pin critical dependencies - ---- - -## Recommended Next Track - -Based on agent consensus, the next track should focus on **Tier 1 Foundation**: - -``` -track/foundation-testing-ci -├── spec.md # Testing + CI/CD + Comparison + Sharing -├── plan.md # Implementation order, dependencies -├── risks.md # What could break, rollback plan -└── tasks/ - ├── 01-vitest-setup.md - ├── 02-inference-tests.md - ├── 03-component-tests.md - ├── 04-github-actions-ci.md - ├── 05-husky-precommit.md - ├── 06-comparison-view.md - └── 07-shareable-links.md -``` - -**Estimated effort**: 3-5 days -**Risk**: Low (additive, no breaking changes to existing features) -**Impact**: Prevents regressions, enables safe future development, adds most-requested UX features - ---- - -## Agent Sign-Off - -| Agent | Model | Status | -|-------|-------|--------| -| AgentArquitecto | deepseek-v4-pro | ✅ | -| AgentQA | GLM-5 | ✅ | -| AgentPerformance | minimax-m2.7 | ✅ | -| AgentResearch | gemini-2.5-pro | ✅ | -| KimiBuildWorker | kimi-k2.6 | ✅ | -| JuniorQwen | MiMo 2.5V Pro | ✅ | -| UIUXAdvisor | openai/gpt-5.5 | ✅ | diff --git a/docs/FIXES_2026-05-10.md b/docs/FIXES_2026-05-10.md new file mode 100644 index 0000000..1b21874 --- /dev/null +++ b/docs/FIXES_2026-05-10.md @@ -0,0 +1,48 @@ +# Fix Log — 2026-05-10 + +> Auditoría origen: ver `docs/` o el audit completo en el chat. + +## ✅ Bugs solucionados + +| ID | Archivo | Fix | +|----|---------|-----| +| BUG-01 | `Recommendation.jsx` | `loadSavedState()` llamada 1 vez (era 3) | +| BUG-02 | `convertToSpecs.jsx` | Eliminado bloque `idealRam` warning muerto | +| BUG-03 | `convertToSpecs.jsx` | Agregado `case 'any':` explícito en switch OS | +| BUG-04 | `Recommendation.css` | Fusionados 2 bloques `.FallbackButton` en 1 | +| BUG-05 | `laptopModels.js` | HP Victus 15 tenía misma imagen que HP Victus RTX 3050 | +| BUG-06 | `laptopModels.js` | Lenovo V15 y IdeaPad Slim 3 tenían URLs de imagen inventadas | +| BUG-07 | `laptopModels.js` | MacBook Air M4 tenía imagen del M2 | +| BUG-09 | `src/notas.jsx` | Movido a `docs/notas_dev.md` | + +## ✅ Deuda técnica + +| ID | Archivo | Fix | +|----|---------|-----| +| DT-01 | `matchLaptopClass.jsx` | Documentado el margen de tolerancia de budget ($1k MXN) | +| DT-02 | `matchLaptopClass.jsx` | `scoreGPU` extraído a top-level (era código duplicado) | +| DT-03 | `AppRoutes.jsx` | Ruta `*` ahora usa `` en vez de `` | +| DT-05 | `public/` | Creados `robots.txt` y `sitemap.xml` | + +## ✅ UX + +| ID | Archivo | Fix | +|----|---------|-----| +| UX-03 | `LaptopCard.jsx` | Texto condicional para modelos aproximados | +| UX-05 | `Recommendation.jsx` | Scroll to top al mostrar resultados | + +## 🚀 Feature: Compartir resultados + +- **`src/utils/shareUtils.js`** — encode/decode base64 + texto WhatsApp +- **`Recommendation.jsx`** — botón Compartir + botón WhatsApp + banner SharedView +- URL: `/quiz?plan=` — sin backend, auto-contenida + +## 📁 Archivos nuevos + +- `src/utils/shareUtils.js` +- `src/pages/NotFound/NotFound.jsx` +- `src/pages/NotFound/NotFound.css` +- `public/robots.txt` (actualizado) +- `public/sitemap.xml` (actualizado) +- `docs/FIXES_2026-05-10.md` (este archivo) +- `docs/notas_dev.md` (movido desde `src/notas.jsx`) diff --git a/docs/FIXES_COMPARE_2026-05-10.md b/docs/FIXES_COMPARE_2026-05-10.md new file mode 100644 index 0000000..4ce4d88 --- /dev/null +++ b/docs/FIXES_COMPARE_2026-05-10.md @@ -0,0 +1,97 @@ +# Fix Log — Compare Module — 2026-05-10 (sesión 2) + +Referencia: `FIXES_2026-05-10.md` (sesión anterior), `FEATURE_COMPARE.md`. + +--- + +## Bugs reportados y resueltos + +### BUG-C01 — Estado de selección no persiste al navegar + +**Síntoma:** Al ir a otra ruta (ej. `/quiz`) y volver a `/compare`, los modelos seleccionados se perdían. + +**Causa:** `selectedIds` vivía solo en el estado local de React. Al desmontar el componente (navegar), el estado se destruía. + +**Fix:** +```js +const LS_KEY = 'csh_compare_ids' + +// Guardar en cada cambio +useEffect(() => { + localStorage.setItem(LS_KEY, JSON.stringify(selectedIds)) +}, [selectedIds]) + +// Restaurar al montar — URL param tiene prioridad +useEffect(() => { + const raw = params.get('models') + if (raw) { /* cargar desde URL */ return } + const saved = localStorage.getItem(LS_KEY) + // validar y setSelectedIds(valid) +}, []) +``` + +**Prioridad de restauración:** +1. `?models=id1,id2` en URL (compartir link) +2. `localStorage` (sesión previa) +3. Vacío + +--- + +### BUG-C02 — Layout se "mueve" / crece al seleccionar modelos + +**Síntoma:** Al agregar modelos y aparecer la tabla de comparación, la página saltaba/se desplazaba bruscamente. El contenido no quedaba en posición estable. + +**Causa:** `#root` en `App.css` usa `display: flex; flex-direction: column; justify-content: center`. Cuando el contenido es corto, lo centra verticalmente. Al crecer (tabla aparece), el centro geométrico cambia y el contenido salta visualmente. + +**Fix CSS:** +```css +.ComparePage { + align-self: flex-start; /* ancla la página al top del flex container */ +} +``` + +**Fix animación:** la tabla aparece con `fadeIn + translateY` de 300ms para que la expansión se sienta intencional: +```css +.CompareTableSection { + animation: tableAppear 0.3s ease-out; +} +@keyframes tableAppear { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +``` + +**Fix scroll:** cuando la tabla aparece por primera vez (1→2 modelos), se hace `scrollIntoView({ behavior: 'smooth' })` con un `ref` para que el usuario la vea sin tener que scrollear manualmente: +```js +const tableRef = useRef(null) +// en useEffect([selectedIds]): +if (tableVisible && !prevTableVisible.current && tableRef.current) { + setTimeout(() => tableRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 100) +} +``` + +--- + +### BUG-C03 — Grid queda colapsado al eliminar el único modelo seleccionado + +**Síntoma:** Usuario tiene 1 modelo seleccionado → colapsa el grid → elimina ese modelo desde los chips → el grid queda colapsado y ya no hay botón para expandirlo (el botón solo aparece cuando `selectedIds.length > 0`). + +**Causa:** `gridCollapsed` no tenía un mecanismo para resetearse automáticamente. + +**Fix:** +```js +useEffect(() => { + if (selectedIds.length === 0) setGridCollapsed(false) +}, [selectedIds]) +``` + +Cuando la selección cae a 0, el grid se auto-expande independientemente del estado del toggle. + +--- + +## Archivos modificados en esta sesión + +| Archivo | Cambio | +|---------|--------| +| `src/pages/Compare/Compare.jsx` | BUG-C01 (localStorage), BUG-C02 (ref + scroll), BUG-C03 (auto-expand), `useRef` importado | +| `src/pages/Compare/Compare.css` | BUG-C02 (`align-self: flex-start`, `tableAppear` animation) | diff --git a/docs/FIXES_UX_2026-05-10.md b/docs/FIXES_UX_2026-05-10.md new file mode 100644 index 0000000..b67b472 --- /dev/null +++ b/docs/FIXES_UX_2026-05-10.md @@ -0,0 +1,23 @@ +# Fix Log — UX/UI Audit — 2026-05-10 (sesión 3) + +--- + +## Mejoras de UX/UI y Accesibilidad aplicadas + +### UX-06 — Semántica HTML en Footer +**Síntoma:** En `App.jsx`, el footer anidaba un `
` dentro de un ``. HTML5 permite anidamientos en bloque, pero rompe convenciones semánticas. +**Fix:** Se eliminó el `
` del `` y se aplicó una nueva clase CSS `.Footer__link` en `App.css` que replica el estilo de encabezado pero respetando el árbol semántico y accesible. + +### UX-07 — Accesibilidad en Tabla de Comparación +**Síntoma:** En `Compare.jsx`, la primera celda de las filas (etiquetas como "Precio", "RAM") usaban `
`. Los lectores de pantalla necesitan `` para anunciar correctamente la relación bidimensional (ej. Precio del Modelo A). +**Fix:** Se cambiaron todos los `td.CompareTable__label` dentro del `
` para cumplir con las guías ARIA y HTML semántico para tablas de datos complejas. + +### UX-08 — Empty State Atrapado (Sin resultados) +**Síntoma:** Si el usuario aplicaba filtros que no coincidían con ninguna laptop, la grilla mostraba el mensaje *"No hay laptops que coincidan..."* pero no ofrecía una salida clara más que borrar manualmente los filtros. +**Fix:** Se agregó un botón interactivo `.CompareEmpty__clearBtn` que al hacer clic hace reset de los estados `setSearch('')` y `setFilterOS('all')`, restaurando el catálogo de inmediato. + +--- + +## Deuda técnica observada (Pendiente) + +- **Duplicidad de Detección de macOS:** `parseSpecs.js` y `convertToSpecs.jsx` duplican parte de la lógica para detectar el sistema operativo basado en la presencia de keywords ("macbook", "m1", "m2", etc.). Funciona bien, pero un futuro "M5" requerirá actualizaciones en múltiples lugares. Se recomienda centralizar esto en `utils` más adelante. diff --git a/docs/FIXES_UX_PART2_2026-05-10.md b/docs/FIXES_UX_PART2_2026-05-10.md new file mode 100644 index 0000000..de0d28d --- /dev/null +++ b/docs/FIXES_UX_PART2_2026-05-10.md @@ -0,0 +1,44 @@ +# Fix Log — UX/UI Audit Part 2 — 2026-05-10 (sesión 4) + +--- + +## Mejoras aplicadas (Basadas en feedback) + +### UX-09 — Deuda Técnica: Detección de OS centralizada +**Síntoma:** `parseSpecs.js` y `convertToSpecs.jsx` duplicaban lógica *hardcoded* para detectar macOS ("macbook", "m1") y ChromeOS ("chromebook"). +**Fix:** Se extrajo esta lógica a `src/utils/osDetector.js` con las funciones `isMac`, `isChromeOS` e `isWindows`, centralizando el comportamiento y haciéndolo más escalable a futuro (ej. chips M5). + +### UX-10 — Inferencia a Comparación (Flujo Roto) +**Síntoma:** El usuario recibía recomendaciones pero no tenía forma rápida de enviarlas a la tabla de comparación sin buscarlas manualmente. +**Fix:** Se agregó el botón `.CompareModelsBtn` ("Comparar estos modelos en detalle") en `Recommendation.jsx`. Éste lee el ID de las laptops recomendadas (ignorando las genéricas) y envía al usuario directamente a `/compare?models=id1,id2,id3`. + +### UX-11 — Persistencia de la UI (Grid Collapsed) +**Síntoma:** Al ocultar el catálogo en la vista de comparación y cambiar a otra pestaña de la app, el estado volvía a abrir el catálogo molesto. +**Fix:** En `Compare.jsx`, el estado `gridCollapsed` ahora está sincronizado bidireccionalmente con el `localStorage` bajo la clave `csh_compare_grid_collapsed`. + +### UX-12 — Header de Compare y Botón Volver +**Síntoma:** El botón de "Volver" rompía la alineación central del título y en móviles ocupaba mucho espacio vertical innecesario. +**Fix:** Se aplicó `position: absolute` al botón en el header. En móviles (max-width: 768px), el texto "Volver" se oculta, dejando sólo el ícono de flecha, ganando espacio valioso. + +### UX-13 — Rediseño de Acciones Finales (Share vs Restart) +**Síntoma:** Los botones de "Compartir por link", "WhatsApp" y "Reiniciar" flotaban en el centro con distintos paddings y sin agrupación semántica. +**Fix:** Se creó un `.FinalActionsGrid` con dos tarjetas (ActionCards) en `Recommendation.jsx`. Ambas tienen el mismo peso visual, y los botones comparten la clase `.ActionBtn` para mantener tamaño y paddings consistentes. + +### UX-14 — Tabla de Comparación en Móviles (UX táctil revisada) +**Síntoma:** El comportamiento `sticky` generaba un solapamiento visual confuso donde las laptops se deslizaban "por debajo" de la columna de etiquetas en móviles. +**Fix:** Se eliminó el `position: sticky` y `scroll-snap`. Ahora la tabla usa un diseño fluido tradicional de scroll lateral simple (`overflow-x: auto`), definiendo un espacio seguro visual sin solapamientos. + +### UX-15 — Padding del Quiz en Móviles +**Síntoma:** La tarjeta del resultado del Quiz (`.ResultContainer`) sobresalía del ancho de la pantalla en dispositivos móviles debido a un padding excesivo combinado con el `width: 100%`. +**Fix:** Se redujo el ancho a `95vw` y se aplicaron paddings optimizados para pantallas pequeñas en `Recommendation.css`. + +### UX-16 — Grilla de Selección de Laptops (Móvil) +**Síntoma:** La grilla en la página de Compare renderizaba 3 columnas en algunos celulares, volviendo ilegible la información. +**Fix:** Se ajustó el `grid-template-columns` a `minmax(45%, 1fr)` para forzar un máximo de 2 columnas en dispositivos pequeños. + +### UX-17 — Bug Persistencia "Ocultar Catálogo" +**Síntoma:** La app no recordaba que el usuario había ocultado el catálogo al cambiar de pestaña, porque el evento de montaje reseteaba el estado a falso al ver el array vacío inicial. +**Fix:** Se agregó una referencia `isInitialMount` (useRef) en el `useEffect` de `Compare.jsx` para ignorar el reseteo automático durante el montaje inicial, permitiendo que el estado de `localStorage` persista correctamente. + +--- +**Status:** Estable. Listo para deploy. diff --git a/docs/HANDOFF_AGENT_2026-05-10.md b/docs/HANDOFF_AGENT_2026-05-10.md new file mode 100644 index 0000000..93bc795 --- /dev/null +++ b/docs/HANDOFF_AGENT_2026-05-10.md @@ -0,0 +1,197 @@ +# Handoff — ComputerSelectorHelper: Estado actual y decisiones de diseño + +**Para:** Agente entrante +**Fecha:** 2026-05-10 +**Proyecto:** ComputerSelectorHelper — `e:\Job\ComputerSelectorHelper` +**Stack:** React 19 + Vite 6 + React Router DOM 7 + Vercel Analytics + +--- + +## 1. ¿Qué es este proyecto? + +Una web app que ayuda a usuarios no técnicos a elegir una laptop. El usuario contesta un quiz de 10 preguntas (uso, presupuesto, OS, gaming, etc.) y recibe specs recomendadas + modelos concretos con precios en MXN. + +**URL producción:** `https://computerselectorhelper.vercel.app` + +--- + +## 2. Todo lo que se hizo en esta sesión + +### 2.1 Auditoría y fixes iniciales (FIXES_2026-05-10.md) + +Se auditó el proyecto completo y se solucionaron: + +| ID | Problema | Solución | +|----|----------|----------| +| BUG-01 | `loadSavedState()` llamada 3 veces en init | Una sola llamada con `initialState` | +| BUG-02 | RAM warning nunca disparaba (lógica duplicada) | Eliminado el bloque muerto | +| BUG-03 | `system='any'` sin rama en switch OS | Agregado `case 'any':` explícito | +| BUG-04 | `.FallbackButton` CSS partido en 2 bloques | Fusionados en uno | +| BUG-05/06/07 | Imágenes duplicadas/inventadas en laptopModels | URLs corregidas | +| BUG-09 | `src/notas.jsx` archivo muerto | Movido a `docs/notas_dev.md` | +| DT-01 | Budget threshold sin documentar | Comentario explícito | +| DT-02 | `scoreGPU` duplicado en 2 funciones | Extraído a top-level | +| DT-03 | Wildcard route → Landing | Creado `NotFound` page | +| DT-05 | Sin `robots.txt` ni `sitemap.xml` | Creados en `public/` | +| UX-03 | LaptopCard texto igual para exactos y aproximados | Heading condicional por `isApproximate` | +| UX-05 | Sin scroll to top al ver resultados | `scrollTo({ top:0, behavior:'smooth' })` | + +### 2.2 Feature: Sistema de compartir resultados + +**Archivo clave:** `src/utils/shareUtils.js` + +Permite compartir el resultado del quiz via URL sin backend: + +``` +/quiz?plan= +``` + +- `encodeAnswers(answers)` → base64 URL-safe +- `decodeAnswers(encoded)` → objeto de respuestas o `null` +- `buildWhatsAppText(result, url)` → texto formateado para `wa.me/?text=` + +**¿Por qué base64 y no un ID de servidor?** +Zero cost de infraestructura. La URL es auto-contenida, funciona offline, y el servidor no necesita saber nada. El trade-off es que la URL es larga (~200 chars), pero es aceptable para WhatsApp y la mayoría de apps de mensajería. + +**En `Recommendation.jsx`:** +- `useEffect` en mount lee `?plan=` y llama `convertToSpecs(decoded)` directamente +- Banner `SharedViewBanner` avisa cuando es un link ajeno, con CTA "Hacer mi propio quiz" +- `handleReset` limpia el param con `history.replaceState` +- Botón verde "Compartir" (Web Share API en mobile / clipboard en desktop) +- Botón verde WhatsApp (`wa.me/?text=...`) + +### 2.3 Feature: Módulo de comparación de laptops (`/compare`) + +**Archivos clave:** +- `src/pages/Compare/Compare.jsx` — componente principal +- `src/pages/Compare/Compare.css` — estilos +- `src/utils/parseSpecs.js` — parser de spec strings + +**¿Qué hace?** +- Grid de tarjetas filtrables/buscables del catálogo completo +- Selección de hasta 3 modelos +- Tabla comparativa lado a lado con highlight automático del mejor valor +- URL compartible: `/compare?models=id1,id2,id3` +- Persistencia en `localStorage` (key `csh_compare_ids`) +- Grid colapsable (toggle + auto-collapse al seleccionar 2+) + +**¿Por qué `parseSpecs.js` y no datos estructurados en `laptopModels.js`?** +Los modelos ya existen con `specs` como string libre (ej: `"Intel Core i5 13420H, 16 GB RAM, 512 GB SSD, 15.6" FHD"`). Agregar campos estructurados requeriría editar los ~35 modelos manualmente y mantener dos fuentes de verdad. El parser regex cubre los casos reales del catálogo actual. Si el catálogo crece mucho o se añaden APIs externas, migrarlo a campos estructurados sería el siguiente paso. + +**GPU Tier system (`getGPUTier`):** +Mapea el string del GPU a un número (1–10) para poder comparar GPUs heterogéneas (RTX vs Apple Silicon vs integradas). La escala es aproximada: no refleja TDP ni variantes Max-Q/Max-P. Es suficiente para dar contexto "cuál GPU es mejor" a un usuario no técnico. + +**`getBestIndices(values, higherIsBetter)`:** +Función pura que recibe un array de números y retorna un array de booleanos marcando cuáles son el "mejor" valor. Excluye ceros para no marcar como "ganador" un dato faltante. Permite empates (ambos marcados si tienen el mismo valor). + +**Decisión de diseño — grid colapsable:** +Cuando el usuario selecciona el 2do modelo, el grid se auto-colapsa y el scroll lleva directo a la tabla. Esto evita que el usuario tenga que scrollear manualmente para ver la comparación. El botón "Mostrar/Ocultar catálogo" permite volver al grid. La tira de chips (pill con imagen+nombre+×) permite quitar modelos sin expandir el catálogo. + +--- + +## 3. Arquitectura de datos actual + +``` +src/utils/ +├── laptopModels.js ← catálogo (~35 modelos, datos planos) +├── convertToSpecs.jsx ← motor de inferencia quiz → specs recomendadas +├── matchLaptopClass.jsx ← matcher specs → modelos del catálogo +├── shareUtils.js ← encode/decode answers para URL sharing +└── parseSpecs.js ← parser spec strings → campos numéricos (para /compare) +``` + +### Flujo del quiz +``` +answers (objeto) + → convertToSpecs(answers) + → { processor, ram, gpu, storage, os, portability, battery, + rationale[], warnings[], flags{}, budget, + laptopClass[], approximateClass[] } +``` + +`matchLaptopClass(specs)` filtra el catálogo por OS, RAM, GPU, budget, luego puntúa por `scoreGPU` y retorna top 3. + +`matchLaptopClassRelaxed(specs)` hace lo mismo pero con restricciones más laxas, como fallback cuando no hay matches exactos. + +--- + +## 4. Estado de las rutas + +| Ruta | Componente | Propósito | +|------|-----------|-----------| +| `/` | `Landing` | Landing page con CTA al quiz | +| `/quiz` | `Quiz` → `Recommendation` | Quiz completo + resultados | +| `/compare` | `Compare` | Comparador de modelos | +| `*` | `NotFound` | Página 404 | + +--- + +## 5. Persistencia de estado + +| Dato | Mecanismo | Key | +|------|-----------|-----| +| Progreso quiz (step, answers, result) | `localStorage` | `csh_quiz_state` | +| Selección del comparador | `localStorage` | `csh_compare_ids` | +| Resultado compartido | URL query param | `?plan=` | +| Comparación compartida | URL query param | `?models=id1,id2` | + +--- + +## 6. SEO actual + +- `react-helmet-async` en todas las páginas con title + meta description + OG tags +- `public/robots.txt` — bloquea `?plan=` (contenido dinámico no indexable) +- `public/sitemap.xml` — 2 rutas: `/` y `/quiz` +- La ruta `/compare` **no está en el sitemap** todavía (agregar si se decide indexar) + +--- + +## 7. Pendientes conocidos (próximo agente) + +| Prioridad | Item | Contexto | +|-----------|------|----------| +| 🔴 | Agregar `/compare` al sitemap | Ahora que es una ruta pública estable | +| 🟡 | INF-01: Pregunta de nivel técnico del usuario | Cambiaría cómo se muestra el resultado (técnico vs. simple) | +| 🟡 | INF-02: Budget bucket `premium` (>$25k MXN) | Hay laptops en catálogo a $34k–$39k sin bucket propio | +| 🟡 | F-02: Historial de últimas consultas (localStorage) | MVP: últimos 3 resultados en Landing | +| 🟢 | UX-04: Dark mode | Requiere refactor completo de variables CSS | +| 🟢 | BUG-08: Estructura de headings en Landing | 3× `

` sin `

` propio (el `h1` está en App.jsx) | +| 🟢 | DT-04: Renombrar `convertToSpecs.jsx` y `matchLaptopClass.jsx` a `.js` | Son módulos JS puros sin JSX | +| 🔵 | Compare: integrar con resultados del quiz | Botón "Comparar mis modelos sugeridos" en results | +| 🔵 | Compare: gráfico de barras | Recharts o Chart.js para visualización de specs | +| 🔵 | Compare: exportar como PDF/imagen | html2canvas o jsPDF | + +--- + +## 8. Convenciones del proyecto + +- **CSS:** Vanilla CSS con variables en `:root` (ver `src/index.css`). No se usa Tailwind. +- **Componentes:** cada componente en su propia carpeta con `.jsx` + `.css` al mismo nivel. +- **Utils:** funciones puras en `src/utils/`. Sin hooks en utils. +- **Sin TypeScript:** proyecto en JS puro con JSX. +- **Testing:** Vitest instalado (`pnpm test`), pero sin tests escritos todavía. +- **Deploy:** Vercel. El `vercel.json` existe en raíz. `pnpm run build` genera `dist/`. +- **Analytics:** `@vercel/analytics` inyectado en `App.jsx`. No requiere configuración extra. + +--- + +## 9. Comandos útiles + +```bash +pnpm run dev # Servidor de desarrollo (Vite, HMR) +pnpm run build # Build de producción → dist/ +pnpm run lint # ESLint 9 +pnpm test # Vitest (sin tests por ahora) +``` + +--- + +## 10. Archivos de documentación en /docs + +| Archivo | Contenido | +|---------|-----------| +| `FIXES_2026-05-10.md` | Bugs corregidos en sesión 1 (audit general) | +| `FIXES_COMPARE_2026-05-10.md` | Bugs del módulo Compare (sesión 2) | +| `FEATURE_COMPARE.md` | Documentación técnica del módulo /compare | +| `SDD_SKILLS_USAGE.md` | Guía de uso de skills SDD del proyecto | +| `notas_dev.md` | Notas de desarrollo (movidas desde src/) | diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..cc4dded --- /dev/null +++ b/docs/README.md @@ -0,0 +1,10 @@ +# /docs — Índice de documentación + +| Archivo | Tipo | Descripción | +|---------|------|-------------| +| `HANDOFF_AGENT_2026-05-10.md` | 📋 Handoff | Estado completo del proyecto para agente entrante: arquitectura, decisiones, pendientes | +| `FEATURE_COMPARE.md` | 🚀 Feature | Documentación técnica del módulo `/compare` | +| `FIXES_2026-05-10.md` | 🐛 Fixes | Sesión 1: audit general — 9 bugs + 4 deuda técnica + 2 UX + share feature | +| `FIXES_COMPARE_2026-05-10.md` | 🐛 Fixes | Sesión 2: bugs del módulo Compare (persistencia, layout, grid colapsado) | +| `SDD_SKILLS_USAGE.md` | 🛠️ Workflow | Guía de skills SDD para este proyecto | +| `notas_dev.md` | 📝 Notas | Notas de desarrollo (movidas desde `src/notas.jsx`) | diff --git a/src/notas.jsx b/docs/notas_dev.md similarity index 100% rename from src/notas.jsx rename to docs/notas_dev.md diff --git a/package-lock.json b/package-lock.json index ac156ad..a7894bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "@vercel/analytics": "^1.5.0", "react": "^19.1.0", "react-dom": "^19.1.0", - "react-helmet-async": "^3.0.0", "react-icons": "^5.5.0", "react-router-dom": "^7.6.0" }, @@ -27,9 +26,7 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", - "husky": "^9.1.7", "jsdom": "^29.1.1", - "lint-staged": "^16.4.0", "vite": "^6.3.5", "vitest": "^4.1.5" } @@ -1854,22 +1851,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2060,39 +2041,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2111,23 +2059,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2325,13 +2256,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -2354,19 +2278,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2651,13 +2562,6 @@ "node": ">= 0.6" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, "node_modules/eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -2887,19 +2791,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -3042,22 +2933,6 @@ "node": ">= 0.8" } }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -3120,15 +2995,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -3147,22 +3013,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -3237,7 +3087,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" + "dev": true, + "license": "MIT", + "peer": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -3332,48 +3184,6 @@ "node": ">= 0.8.0" } }, - "node_modules/lint-staged": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", - "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^14.0.3", - "listr2": "^9.0.5", - "picomatch": "^4.0.3", - "string-argv": "^0.3.2", - "tinyexec": "^1.0.4", - "yaml": "^2.8.2" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3395,68 +3205,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lru-cache": { "version": "11.3.6", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", @@ -3574,19 +3322,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -3701,22 +3436,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4007,26 +3726,6 @@ "react": "^19.1.0" } }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT" - }, - "node_modules/react-helmet-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz", - "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==", - "license": "Apache-2.0", - "dependencies": { - "invariant": "^2.2.4", - "react-fast-compare": "^3.2.2", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -4120,30 +3819,6 @@ "node": ">=4" } }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, "node_modules/rollup": { "version": "4.40.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", @@ -4304,12 +3979,6 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -4410,49 +4079,6 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -4485,62 +4111,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -5000,55 +4570,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5072,22 +4593,6 @@ "dev": true, "license": "MIT" }, - "node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -6116,15 +5621,6 @@ "uri-js": "^4.2.2" } }, - "ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, - "requires": { - "environment": "^1.0.0" - } - }, "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -6271,25 +5767,6 @@ "supports-color": "^7.1.0" } }, - "cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "requires": { - "restore-cursor": "^5.0.0" - } - }, - "cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "requires": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" - } - }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -6305,18 +5782,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true - }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -6466,12 +5931,6 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true }, - "emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true - }, "encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -6484,12 +5943,6 @@ "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", "dev": true }, - "environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true - }, "es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -6693,12 +6146,6 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true }, - "eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true - }, "eventsource": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", @@ -6861,12 +6308,6 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true }, - "get-east-asian-width": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz", - "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==", - "dev": true - }, "get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -6965,12 +6406,6 @@ "toidentifier": "1.0.1" } }, - "husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true - }, "iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -7014,14 +6449,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "requires": { - "loose-envify": "^1.0.0" - } - }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -7034,15 +6461,6 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true }, - "is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "requires": { - "get-east-asian-width": "^1.3.1" - } - }, "is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -7100,7 +6518,9 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "peer": true }, "js-yaml": { "version": "4.1.0", @@ -7177,34 +6597,6 @@ "type-check": "~0.4.0" } }, - "lint-staged": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", - "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", - "dev": true, - "requires": { - "commander": "^14.0.3", - "listr2": "^9.0.5", - "picomatch": "^4.0.3", - "string-argv": "^0.3.2", - "tinyexec": "^1.0.4", - "yaml": "^2.8.2" - } - }, - "listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "requires": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - } - }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7220,45 +6612,6 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, - "log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "requires": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true - }, - "slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "requires": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - } - } - } - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, "lru-cache": { "version": "11.3.6", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.6.tgz", @@ -7340,12 +6693,6 @@ "mime-db": "^1.54.0" } }, - "mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true - }, "min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -7421,15 +6768,6 @@ "wrappy": "1" } }, - "onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "requires": { - "mimic-function": "^5.0.0" - } - }, "optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -7622,21 +6960,6 @@ "scheduler": "^0.26.0" } }, - "react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" - }, - "react-helmet-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz", - "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==", - "requires": { - "invariant": "^2.2.4", - "react-fast-compare": "^3.2.2", - "shallowequal": "^1.1.0" - } - }, "react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", @@ -7696,22 +7019,6 @@ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "requires": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - } - }, - "rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true - }, "rollup": { "version": "4.40.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.2.tgz", @@ -7829,11 +7136,6 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true }, - "shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" - }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7903,30 +7205,6 @@ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true }, - "signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true - }, - "slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "requires": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true - } - } - }, "source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -7951,39 +7229,6 @@ "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true }, - "string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true - }, - "string-width": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz", - "integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==", - "dev": true, - "requires": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - } - }, - "strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "requires": { - "ansi-regex": "^6.2.2" - }, - "dependencies": { - "ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true - } - } - }, "strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -8228,36 +7473,6 @@ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true }, - "wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "requires": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true - }, - "string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "requires": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - } - } - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -8276,12 +7491,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", - "dev": true - }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index dc540aa..2fa2030 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,7 @@ "preview": "vite preview", "test": "vitest run", "test:watch": "vitest", - "test:coverage": "vitest run --coverage", - "prepare": "husky" + "test:coverage": "vitest run --coverage" }, "dependencies": { "@vercel/analytics": "^1.5.0", @@ -33,16 +32,8 @@ "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", - "husky": "^9.1.7", "jsdom": "^29.1.1", - "lint-staged": "^16.4.0", "vite": "^6.3.5", "vitest": "^4.1.5" - }, - "lint-staged": { - "src/**/*.{js,jsx}": [ - "eslint --fix", - "git add" - ] } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 14dc4ad..f2bd3a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,12 @@ importers: '@eslint/js': specifier: ^9.25.0 version: 9.39.4 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) '@types/react': specifier: ^19.1.2 version: 19.2.14 @@ -39,6 +45,9 @@ importers: '@vitejs/plugin-react-swc': specifier: ^3.9.0 version: 3.11.0(vite@6.4.2) + '@vitest/coverage-v8': + specifier: ^4.1.5 + version: 4.1.5(vitest@4.1.5) eslint: specifier: ^9.25.0 version: 9.39.4 @@ -51,12 +60,105 @@ importers: globals: specifier: ^16.0.0 version: 16.5.0 + jsdom: + specifier: ^29.1.1 + version: 29.1.1 vite: specifier: ^6.3.5 version: 6.4.2 + vitest: + specifier: ^4.1.5 + version: 4.1.5(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1)(vite@6.4.2) packages: + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@1.0.2': + resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} + engines: {node: '>=18'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.0': + resolution: {integrity: sha512-bR9e6o2BDB12jzN/gIbjHa5wLJ4UjD1CB9pM7ehlc0ddk6EBz+yYS1EV2MF55/HUxrHcB/hehAyt5vhsA3hx7w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.0': + resolution: {integrity: sha512-U0KhLYmy2GVj6q4T3WaAe6NPuFYCPQoE3b0dRGxejWDgcPp8TP7S5rVdM5ZrFaqu4N67X8YaPBw14dQSYx3IyQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.3': + resolution: {integrity: sha512-SH60bMfrRCJF3morcdk57WklujF4Jr/EsQUzqkarfHXEFcAR1gg7fS/chAE922Sehgzc1/+Tz5H3Ypa1HiEKrg==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + '@esbuild/aix-ppc64@0.25.12': resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} engines: {node: '>=18'} @@ -251,6 +353,15 @@ packages: resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -271,6 +382,16 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@rolldown/pluginutils@1.0.0-beta.27': resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} @@ -412,6 +533,9 @@ packages: cpu: [x64] os: [win32] + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/core-darwin-arm64@1.15.33': resolution: {integrity: sha512-N+L0uXhuO7FIfzqwgxmzv0zIpV0qEp8wPX3QQs2p4atjMoywup2JTeDlXPw+z9pWJGCae3JjM+tZ6myclI+2gA==} engines: {node: '>=10'} @@ -505,6 +629,38 @@ packages: '@swc/types@0.1.26': resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==} + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -553,6 +709,44 @@ packages: peerDependencies: vite: ^4 || ^5 || ^6 || ^7 + '@vitest/coverage-v8@4.1.5': + resolution: {integrity: sha512-38C0/Ddb7HcRG0Z4/DUem8x57d2p9jYgp18mkaYswEOQBGsI1CG4f/hjm0ZCeaJfWhSZ4k7jgs29V1Zom7Ki9A==} + peerDependencies: + '@vitest/browser': 4.1.5 + vitest: 4.1.5 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@4.1.5': + resolution: {integrity: sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==} + + '@vitest/mocker@4.1.5': + resolution: {integrity: sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.5': + resolution: {integrity: sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==} + + '@vitest/runner@4.1.5': + resolution: {integrity: sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==} + + '@vitest/snapshot@4.1.5': + resolution: {integrity: sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==} + + '@vitest/spy@4.1.5': + resolution: {integrity: sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==} + + '@vitest/utils@4.1.5': + resolution: {integrity: sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -566,16 +760,41 @@ packages: ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + brace-expansion@1.1.14: resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} @@ -583,6 +802,10 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -597,6 +820,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie@1.1.1: resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} @@ -605,9 +831,20 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -617,9 +854,29 @@ packages: supports-color: optional: true + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + esbuild@0.25.12: resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} engines: {node: '>=18'} @@ -678,10 +935,17 @@ packages: resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} engines: {node: '>=4.0'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -736,6 +1000,13 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -748,6 +1019,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -759,9 +1034,27 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -769,6 +1062,15 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -796,6 +1098,31 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + engines: {node: 20 || >=22} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -810,6 +1137,9 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -826,6 +1156,9 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -834,6 +1167,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -849,6 +1185,10 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -871,6 +1211,9 @@ packages: peerDependencies: react: '*' + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-router-dom@7.15.0: resolution: {integrity: sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==} engines: {node: '>=20.0.0'} @@ -892,6 +1235,14 @@ packages: resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -901,9 +1252,18 @@ packages: engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} @@ -918,10 +1278,23 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -930,14 +1303,47 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + tinyglobby@0.2.16: resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.30: + resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==} + + tldts@7.0.30: + resolution: {integrity: sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==} + hasBin: true + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -981,21 +1387,163 @@ packages: yaml: optional: true + vitest@4.1.5: + resolution: {integrity: sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.5 + '@vitest/browser-preview': 4.1.5 + '@vitest/browser-webdriverio': 4.1.5 + '@vitest/coverage-istanbul': 4.1.5 + '@vitest/coverage-v8': 4.1.5 + '@vitest/ui': 4.1.5 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} snapshots: + '@adobe/css-tools@4.4.4': {} + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/parser@7.29.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/runtime@7.29.2': {} + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@1.0.2': {} + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.3(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + '@esbuild/aix-ppc64@0.25.12': optional: true @@ -1120,6 +1668,8 @@ snapshots: '@eslint/core': 0.17.0 levn: 0.4.1 + '@exodus/bytes@1.15.0': {} + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -1136,6 +1686,15 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@rolldown/pluginutils@1.0.0-beta.27': {} '@rollup/rollup-android-arm-eabi@4.60.3': @@ -1213,6 +1772,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.60.3': optional: true + '@standard-schema/spec@1.1.0': {} + '@swc/core-darwin-arm64@1.15.33': optional: true @@ -1273,6 +1834,45 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + '@types/estree@1.0.8': {} '@types/estree@1.0.9': {} @@ -1299,6 +1899,61 @@ snapshots: transitivePeerDependencies: - '@swc/helpers' + '@vitest/coverage-v8@4.1.5(vitest@4.1.5)': + dependencies: + '@bcoe/v8-coverage': 1.0.2 + '@vitest/utils': 4.1.5 + ast-v8-to-istanbul: 1.0.0 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.1.0 + tinyrainbow: 3.1.0 + vitest: 4.1.5(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1)(vite@6.4.2) + + '@vitest/expect@4.1.5': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.5(vite@6.4.2)': + dependencies: + '@vitest/spy': 4.1.5 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 6.4.2 + + '@vitest/pretty-format@4.1.5': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.5': + dependencies: + '@vitest/utils': 4.1.5 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + '@vitest/utils': 4.1.5 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.5': {} + + '@vitest/utils@4.1.5': + dependencies: + '@vitest/pretty-format': 4.1.5 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: acorn: 8.16.0 @@ -1312,14 +1967,36 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansi-styles@5.2.0: {} + argparse@2.0.1: {} + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + ast-v8-to-istanbul@1.0.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + estree-walker: 3.0.3 + js-tokens: 10.0.0 + balanced-match@1.0.2: {} + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + brace-expansion@1.1.14: dependencies: balanced-match: 1.0.2 @@ -1327,6 +2004,8 @@ snapshots: callsites@3.1.0: {} + chai@6.2.2: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -1340,6 +2019,8 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + cookie@1.1.1: {} cross-spawn@7.0.6: @@ -1348,14 +2029,40 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + csstype@3.2.3: {} + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + debug@4.4.3: dependencies: ms: 2.1.3 + decimal.js@10.6.0: {} + deep-is@0.1.4: {} + dequal@2.0.3: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + entities@8.0.0: {} + + es-module-lexer@2.1.0: {} + esbuild@0.25.12: optionalDependencies: '@esbuild/aix-ppc64': 0.25.12 @@ -1459,8 +2166,14 @@ snapshots: estraverse@5.3.0: {} + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + esutils@2.0.3: {} + expect-type@1.3.0: {} + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -1500,6 +2213,14 @@ snapshots: has-flag@4.0.0: {} + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.0 + transitivePeerDependencies: + - '@noble/hashes' + + html-escaper@2.0.2: {} + ignore@5.3.2: {} import-fresh@3.3.1: @@ -1509,6 +2230,8 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} + invariant@2.2.4: dependencies: loose-envify: 1.4.0 @@ -1519,14 +2242,57 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-tokens@10.0.0: {} + js-tokens@4.0.0: {} js-yaml@4.1.1: dependencies: argparse: 2.0.1 + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.3(css-tree@3.2.1) + '@exodus/bytes': 1.15.0 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.3.6 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.25.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -1552,6 +2318,28 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@11.3.6: {} + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.2: + dependencies: + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.0 + + mdn-data@2.27.1: {} + + min-indent@1.0.1: {} + minimatch@3.1.5: dependencies: brace-expansion: 1.1.14 @@ -1562,6 +2350,8 @@ snapshots: natural-compare@1.4.0: {} + obug@2.1.1: {} + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -1583,10 +2373,16 @@ snapshots: dependencies: callsites: 3.1.0 + parse5@8.0.1: + dependencies: + entities: 8.0.0 + path-exists@4.0.0: {} path-key@3.1.1: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@4.0.4: {} @@ -1599,6 +2395,12 @@ snapshots: prelude-ls@1.2.1: {} + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + punycode@2.3.1: {} react-dom@19.2.6(react@19.2.6): @@ -1619,6 +2421,8 @@ snapshots: dependencies: react: 19.2.6 + react-is@17.0.2: {} + react-router-dom@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): dependencies: react: 19.2.6 @@ -1635,6 +2439,13 @@ snapshots: react@19.2.6: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + require-from-string@2.0.2: {} + resolve-from@4.0.0: {} rollup@4.60.3: @@ -1668,8 +2479,14 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.3 fsevents: 2.3.3 + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + scheduler@0.27.0: {} + semver@7.8.0: {} + set-cookie-parser@2.7.2: {} shallowequal@1.1.0: {} @@ -1680,23 +2497,57 @@ snapshots: shebang-regex@3.0.0: {} + siginfo@2.0.0: {} + source-map-js@1.2.1: {} + stackback@0.0.2: {} + + std-env@4.1.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + strip-json-comments@3.1.1: {} supports-color@7.2.0: dependencies: has-flag: 4.0.0 + symbol-tree@3.2.4: {} + + tinybench@2.9.0: {} + + tinyexec@1.1.2: {} + tinyglobby@0.2.16: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyrainbow@3.1.0: {} + + tldts-core@7.0.30: {} + + tldts@7.0.30: + dependencies: + tldts-core: 7.0.30 + + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.30 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 + undici@7.25.0: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -1712,10 +2563,63 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + vitest@4.1.5(@vitest/coverage-v8@4.1.5)(jsdom@29.1.1)(vite@6.4.2): + dependencies: + '@vitest/expect': 4.1.5 + '@vitest/mocker': 4.1.5(vite@6.4.2) + '@vitest/pretty-format': 4.1.5 + '@vitest/runner': 4.1.5 + '@vitest/snapshot': 4.1.5 + '@vitest/spy': 4.1.5 + '@vitest/utils': 4.1.5 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 6.4.2 + why-is-node-running: 2.3.0 + optionalDependencies: + '@vitest/coverage-v8': 4.1.5(vitest@4.1.5) + jsdom: 29.1.1 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + which@2.0.2: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + word-wrap@1.2.5: {} + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + yocto-queue@0.1.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 25711ba..99cceb0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,3 @@ allowBuilds: - '@swc/core': set this to true or false - esbuild: set this to true or false + '@swc/core': true + esbuild: true diff --git a/public/robots.txt b/public/robots.txt index feaae7e..ad0d79a 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,3 +1,5 @@ User-agent: * Allow: / +Disallow: /quiz?plan= + Sitemap: https://computerselectorhelper.vercel.app/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml index bbe8314..51d2f37 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -2,14 +2,12 @@ https://computerselectorhelper.vercel.app/ - 2026-05-09 monthly 1.0 https://computerselectorhelper.vercel.app/quiz - 2026-05-09 monthly - 0.8 + 0.9 diff --git a/src/App.css b/src/App.css index 6681b29..bce9645 100644 --- a/src/App.css +++ b/src/App.css @@ -38,41 +38,72 @@ body { font-weight: bold; } -h1, h5 { - text-align: center; +/* App Header & Navigation */ +.AppHeader { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + padding: 2rem 1rem; width: 100%; } -/* Scroll para navegadores basados en WebKit (Chrome, Edge, Safari) */ -::-webkit-scrollbar { - width: 10px; - height: 10px; +.AppHeader__brand h1 { + margin: 0; + font-size: clamp(1.8rem, 4vw, 2.5rem); + text-align: center; } -::-webkit-scrollbar-track { - background: #e0e0e0; - border-radius: 10px; +.AppNav { + display: inline-flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.6); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.8); + padding: 0.35rem; + border-radius: 999px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05); + gap: 0.25rem; +} + +.AppNav__link { + display: inline-flex; + align-items: center; + padding: 0.5rem 1.2rem; + border-radius: 999px; + font-size: 0.95rem; + font-weight: 600; + color: #475569; + text-decoration: none; + transition: all 0.2s ease; } -::-webkit-scrollbar-thumb { - background-color: #62B5E5; - /* Azul */ - border-radius: 10px; - border: 2px solid transparent; - background-clip: content-box; +.AppNav__link:hover { + background: rgba(37, 99, 235, 0.08); + color: #2563eb; } -::-webkit-scrollbar-thumb:hover { - background-color: #62B5E5; +.AppNav__link.active { + background: #2563eb; + color: white; + box-shadow: 0 2px 8px rgba(37, 99, 235, 0.3); } -/* Scroll para Firefox */ -* { - scrollbar-width: auto; - scrollbar-color: #62B5E5 #e0e0e0; +.Footer__link { + display: block; + text-align: center; + font-size: 0.83em; + font-weight: bold; + margin-top: 1.67em; + margin-bottom: 1.67em; + color: inherit; + text-decoration: none; } + /* Botones y anchor — scoped to App to avoid global leaks */ .App a { font-weight: 500; diff --git a/src/App.jsx b/src/App.jsx index fa1089f..4a2dbd7 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,5 +1,5 @@ import './App.css' -import { BrowserRouter } from 'react-router-dom' +import { BrowserRouter, NavLink } from 'react-router-dom' import { HelmetProvider } from 'react-helmet-async' import AppRoutes from './routes/AppRoutes' import { Analytics } from "@vercel/analytics/react" @@ -9,8 +9,15 @@ function App() { return ( -
-

Computer Selector Helper

+
+
+

Computer Selector Helper

+
+
@@ -18,8 +25,8 @@ function App() {
diff --git a/src/__tests__/LaptopCard.test.jsx b/src/__tests__/LaptopCard.test.jsx deleted file mode 100644 index 22726bd..0000000 --- a/src/__tests__/LaptopCard.test.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import { describe, it, expect } from 'vitest' -import { render, screen } from '@testing-library/react' -import LaptopCard from '../components/LaptopCard/LaptopCard' - -const mockModel = { - id: 'test-laptop', - name: 'Test Laptop Pro', - use: 'Gaming, edición', - specs: 'Intel i7, 16 GB RAM, 512 GB SSD, RTX 4060', - gpu: 'RTX 4060', - price: '$20,000 MXN', - portability: 'Media', - image: 'https://example.com/laptop.jpg', - link: 'https://example.com/buy', -} - -describe('LaptopCard', () => { - it('renders with all props', () => { - render() - expect(screen.getByText('🎯 Te sugerimos esta laptop:')).toBeInTheDocument() - expect(screen.getByText('Test Laptop Pro')).toBeInTheDocument() - expect(screen.getByText(/Uso recomendado:/)).toBeInTheDocument() - expect(screen.getByText('Gaming, edición')).toBeInTheDocument() - expect(screen.getByText(/Especificaciones:/)).toBeInTheDocument() - expect(screen.getByText(/Intel i7/)).toBeInTheDocument() - expect(screen.getByText(/Precio aproximado:/)).toBeInTheDocument() - expect(screen.getByText('$20,000 MXN')).toBeInTheDocument() - }) - - it('renders image with correct alt text', () => { - render() - const img = screen.getByAltText('Test Laptop Pro') - expect(img).toBeInTheDocument() - expect(img.getAttribute('src')).toBe('https://example.com/laptop.jpg') - }) - - it('renders link to store', () => { - render() - const link = screen.getByRole('link', { name: /ver en tienda/i }) - expect(link).toBeInTheDocument() - expect(link.getAttribute('href')).toBe('https://example.com/buy') - expect(link.getAttribute('target')).toBe('_blank') - expect(link.getAttribute('rel')).toBe('noopener noreferrer') - }) - - it('returns null when model is null', () => { - const { container } = render() - expect(container.firstChild).toBeNull() - }) - - it('returns null when model is undefined', () => { - const { container } = render() - expect(container.firstChild).toBeNull() - }) - - it('shows image fallback on error', () => { - // Note: current component has no onError handler — test documents this gap - render() - const img = screen.getByAltText('Test Laptop Pro') - expect(img).toBeInTheDocument() - // TODO: Add onError handler to LaptopCard for image fallback - }) -}) diff --git a/src/__tests__/Recommendation.test.jsx b/src/__tests__/Recommendation.test.jsx deleted file mode 100644 index cfac7b4..0000000 --- a/src/__tests__/Recommendation.test.jsx +++ /dev/null @@ -1,180 +0,0 @@ -import { describe, it, expect, vi } from 'vitest' -import { render, screen, fireEvent } from '@testing-library/react' -import Recommendation from '../components/Recommendation/Recommendation' - -vi.mock('../utils/convertToSpecs', () => ({ - default: vi.fn((answers) => ({ - processor: 'Intel i5 / Ryzen 5', - ram: '12 GB', - storage: '256 GB SSD', - gpu: 'Intel Iris Xe / Radeon Vega', - os: 'Windows 11', - budget: answers.budget || 'medium', - touchscreen: false, - portability: 'Media o baja', - battery: 'Media (4-7h)', - warnings: [], - rationale: ['Test rationale'], - laptopClass: [ - { - id: 'test-1', - name: 'Test Laptop 1', - use: 'General use', - specs: 'Intel i5, 8 GB RAM, 256 GB SSD', - gpu: 'Intel Iris Xe', - price: '$10,000 MXN', - portability: 'Media', - image: 'https://example.com/1.jpg', - link: 'https://example.com/1', - }, - { - id: 'test-2', - name: 'Test Laptop 2', - use: 'Gaming', - specs: 'Intel i7, 16 GB RAM, 512 GB SSD, RTX 4050', - gpu: 'RTX 4050', - price: '$15,000 MXN', - portability: 'Media', - image: 'https://example.com/2.jpg', - link: 'https://example.com/2', - }, - ], - })), -})) - -describe('Recommendation — Quiz flow', () => { - it('renders with first question', () => { - render() - expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument() - }) - - it('shows checkbox options for first question', () => { - render() - const checkboxes = screen.getAllByRole('checkbox') - expect(checkboxes.length).toBeGreaterThan(0) - }) - - it('next button is disabled when no selection', () => { - render() - const nextButton = screen.getByRole('button', { name: /siguiente/i }) - expect(nextButton).toBeDisabled() - }) - - it('next button is enabled after selection', () => { - render() - const firstCheckbox = screen.getAllByRole('checkbox')[0] - fireEvent.click(firstCheckbox) - const nextButton = screen.getByRole('button', { name: /siguiente/i }) - expect(nextButton).toBeEnabled() - }) - - it('advances to next question on click', () => { - render() - const firstQuestion = screen.getByRole('heading', { level: 2 }).textContent - const firstCheckbox = screen.getAllByRole('checkbox')[0] - fireEvent.click(firstCheckbox) - const nextButton = screen.getByRole('button', { name: /siguiente/i }) - fireEvent.click(nextButton) - const secondQuestion = screen.getByRole('heading', { level: 2 }).textContent - expect(secondQuestion).not.toBe(firstQuestion) - }) - - it('completes quiz and shows results', () => { - render() - // Answer all questions with first option - for (let i = 0; i < 10; i++) { - const checkboxes = screen.queryAllByRole('checkbox') - const radios = screen.queryAllByRole('radio') - if (checkboxes.length > 0) { - fireEvent.click(checkboxes[0]) - } else if (radios.length > 0) { - fireEvent.click(radios[0]) - } - const nextButton = screen.getByRole('button', { name: /siguiente/i }) - fireEvent.click(nextButton) - } - // Should show results - expect(screen.getByText(/tu recomendación técnica/i)).toBeInTheDocument() - expect(screen.getByText(/especificaciones recomendadas/i)).toBeInTheDocument() - }) -}) - -describe('Recommendation — Results page', () => { - it('shows laptop cards after quiz completion', () => { - render() - // Answer all questions - for (let i = 0; i < 10; i++) { - const checkboxes = screen.queryAllByRole('checkbox') - const radios = screen.queryAllByRole('radio') - if (checkboxes.length > 0) { - fireEvent.click(checkboxes[0]) - } else if (radios.length > 0) { - fireEvent.click(radios[0]) - } - const nextButton = screen.getByRole('button', { name: /siguiente/i }) - fireEvent.click(nextButton) - } - // Should show laptop cards - expect(screen.getByText('Test Laptop 1')).toBeInTheDocument() - expect(screen.getByText('Test Laptop 2')).toBeInTheDocument() - }) - - it('shows spec cards on results page', () => { - render() - // Answer all questions - for (let i = 0; i < 10; i++) { - const checkboxes = screen.queryAllByRole('checkbox') - const radios = screen.queryAllByRole('radio') - if (checkboxes.length > 0) { - fireEvent.click(checkboxes[0]) - } else if (radios.length > 0) { - fireEvent.click(radios[0]) - } - const nextButton = screen.getByRole('button', { name: /siguiente/i }) - fireEvent.click(nextButton) - } - expect(screen.getByText('Procesador')).toBeInTheDocument() - expect(screen.getByText('RAM')).toBeInTheDocument() - expect(screen.getByText('Almacenamiento')).toBeInTheDocument() - expect(screen.getByText('Gráficos')).toBeInTheDocument() - }) - - it('shows reset button on results page', () => { - render() - // Answer all questions - for (let i = 0; i < 10; i++) { - const checkboxes = screen.queryAllByRole('checkbox') - const radios = screen.queryAllByRole('radio') - if (checkboxes.length > 0) { - fireEvent.click(checkboxes[0]) - } else if (radios.length > 0) { - fireEvent.click(radios[0]) - } - const nextButton = screen.getByRole('button', { name: /siguiente/i }) - fireEvent.click(nextButton) - } - expect(screen.getByRole('button', { name: /reiniciar/i })).toBeInTheDocument() - }) - - it('resets quiz when reset button is clicked', () => { - render() - // Answer all questions - for (let i = 0; i < 10; i++) { - const checkboxes = screen.queryAllByRole('checkbox') - const radios = screen.queryAllByRole('radio') - if (checkboxes.length > 0) { - fireEvent.click(checkboxes[0]) - } else if (radios.length > 0) { - fireEvent.click(radios[0]) - } - const nextButton = screen.getByRole('button', { name: /siguiente/i }) - fireEvent.click(nextButton) - } - // Click reset - const resetButton = screen.getByRole('button', { name: /reiniciar/i }) - fireEvent.click(resetButton) - // Should be back to first question - expect(screen.getByRole('heading', { level: 2 })).toBeInTheDocument() - expect(screen.queryByText(/tu recomendación técnica/i)).not.toBeInTheDocument() - }) -}) diff --git a/src/__tests__/convertToSpecs.test.jsx b/src/__tests__/convertToSpecs.test.jsx deleted file mode 100644 index 18dc865..0000000 --- a/src/__tests__/convertToSpecs.test.jsx +++ /dev/null @@ -1,345 +0,0 @@ -import { describe, it, expect } from 'vitest' -import convertToSpecs from '../utils/convertToSpecs' - -const baseAnswers = { - mainUse: ['basics'], - system: 'windows', - budget: 'medium', - workload: 'light', - photoVideo: 'no', - webBrowsing: 'light', - location: 'stationary', - importance: 'none', - battery: 'no', -} - -describe('convertToSpecs — Basic functionality', () => { - it('returns a valid spec profile for basic answers', () => { - const specs = convertToSpecs(baseAnswers) - expect(specs).toHaveProperty('processor') - expect(specs).toHaveProperty('ram') - expect(specs).toHaveProperty('storage') - expect(specs).toHaveProperty('gpu') - expect(specs).toHaveProperty('os') - expect(specs).toHaveProperty('budget') - expect(specs).toHaveProperty('warnings') - expect(specs).toHaveProperty('rationale') - expect(specs).toHaveProperty('laptopClass') - expect(Array.isArray(specs.laptopClass)).toBe(true) - }) - - it('sets Windows 11 as default OS', () => { - const specs = convertToSpecs(baseAnswers) - expect(specs.os).toBe('Windows 11') - }) - - it('stores budget from answers', () => { - const specs = convertToSpecs({ ...baseAnswers, budget: 'low' }) - expect(specs.budget).toBe('low') - }) -}) - -describe('convertToSpecs — Gaming use case', () => { - it('sets high GPU score for gaming', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: ['gaming'], - gamesType: 'complex', - }) - expect(specs.gpu).toContain('RTX') - expect(specs.rationale.some(r => r.toLowerCase().includes('gaming'))).toBe(true) - }) - - it('sets prioritizeGaming flag', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: ['gaming'], - }) - expect(specs.flags?.prioritizeGaming).toBe(true) - }) - - it('upgrades CPU and RAM for RTX requirements', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: ['gaming'], - gamesType: 'complex', - }) - expect(specs.processor).toMatch(/i5|i7|Ryzen 5|Ryzen 7/) - expect(specs.ram).toBe('16 GB') - }) -}) - -describe('convertToSpecs — Content creation', () => { - it('sets high performance and storage for creating', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: ['creating'], - }) - expect(specs.performance || specs.rationale.some(r => r.toLowerCase().includes('creación'))).toBeTruthy() - expect(specs.storage).toBe('512 GB SSD') - }) - - it('sets high specs for photoVideo: pro', () => { - const specs = convertToSpecs({ - ...baseAnswers, - photoVideo: 'pro', - }) - expect(specs.storage).toBe('512 GB SSD') - }) - - it('sets prioritizeScreenQuality flag for photoVideo: pro', () => { - const specs = convertToSpecs({ - ...baseAnswers, - photoVideo: 'pro', - }) - expect(specs.flags?.prioritizeScreenQuality).toBe(true) - }) -}) - -describe('convertToSpecs — Heavy workload', () => { - it('sets high performance for work_school + heavy', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: ['work_school'], - workload: 'heavy', - }) - expect(specs.rationale.some(r => r.toLowerCase().includes('trabajo'))).toBe(true) - }) - - it('sets high RAM for heavy web browsing', () => { - const specs = convertToSpecs({ - ...baseAnswers, - webBrowsing: 'heavy', - }) - expect(specs.rationale.some(r => r.toLowerCase().includes('ram'))).toBe(true) - }) -}) - -describe('convertToSpecs — Portability', () => { - it('sets high portability for mobile location', () => { - const specs = convertToSpecs({ - ...baseAnswers, - location: 'mobile', - }) - expect(specs.portability).toContain('Alta') - }) - - it('sets prioritizePortability flag', () => { - const specs = convertToSpecs({ - ...baseAnswers, - importance: 'portability', - }) - expect(specs.flags?.prioritizePortability).toBe(true) - }) -}) - -describe('convertToSpecs — macOS', () => { - it('sets macOS as OS', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'mac', - }) - expect(specs.os).toBe('macOS') - }) - - it('forces Apple Silicon processor', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'mac', - }) - expect(specs.processor).toMatch(/Apple M|M1|M2|M3/) - }) - - it('warns about macOS + gaming incompatibility', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'mac', - mainUse: ['gaming'], - }) - expect(specs.warnings.some(w => w.toLowerCase().includes('macos') && w.toLowerCase().includes('juegos'))).toBe(true) - }) - - it('warns about low budget + demanding macOS tasks', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'mac', - budget: 'low', - mainUse: ['gaming'], - }) - expect(specs.warnings.some(w => w.includes('⚠️') && w.toLowerCase().includes('presupuesto'))).toBe(true) - }) -}) - -describe('convertToSpecs — ChromeOS', () => { - it('sets ChromeOS as OS', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'chrome', - }) - expect(specs.os).toBe('ChromeOS') - }) - - it('forces modest hardware for ChromeOS', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'chrome', - }) - expect(specs.processor).toMatch(/Celeron|MediaTek/) - expect(specs.gpu).toContain('integrados') - }) - - it('warns about ChromeOS + heavy tasks', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'chrome', - workload: 'heavy', - }) - expect(specs.warnings.some(w => w.toLowerCase().includes('chromeos'))).toBe(true) - }) - - it('warns about ChromeOS + gaming complex', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'chrome', - mainUse: ['gaming'], - gamesType: 'complex', - }) - expect(specs.warnings.some(w => w.toLowerCase().includes('chromeos') && w.toLowerCase().includes('juegos'))).toBe(true) - }) -}) - -describe('convertToSpecs — Budget constraints', () => { - it('warns about low budget with high specs', () => { - const specs = convertToSpecs({ - ...baseAnswers, - budget: 'low', - mainUse: ['gaming'], - }) - expect(specs.warnings.some(w => w.toLowerCase().includes('presupuesto'))).toBe(true) - }) - - it('warns about RTX with low budget', () => { - const specs = convertToSpecs({ - ...baseAnswers, - budget: 'low', - mainUse: ['gaming'], - gamesType: 'complex', - }) - expect(specs.warnings.some(w => w.toLowerCase().includes('rtx'))).toBe(true) - }) -}) - -describe('convertToSpecs — Edge cases', () => { - it('handles empty mainUse gracefully', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: [], - }) - expect(specs).toHaveProperty('processor') - expect(specs.os).toBe('Windows 11') - }) - - it('handles full_use (all uses selected)', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: 'full_use', - }) - expect(specs).toHaveProperty('processor') - expect(specs.laptopClass).toBeDefined() - }) - - it('handles touchscreen preference', () => { - const specs = convertToSpecs({ - ...baseAnswers, - importance: 'touch', - }) - expect(specs.touchscreen).toBe(true) - }) - - it('handles security preference', () => { - const specs = convertToSpecs({ - ...baseAnswers, - importance: 'security', - }) - expect(specs.flags?.prioritizeSecurity).toBe(true) - }) - - it('handles battery preference', () => { - const specs = convertToSpecs({ - ...baseAnswers, - battery: 'yes', - }) - expect(specs.battery).toContain('Alta') - }) - - it('detects contradictory use: basic only but demanding answers', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: ['entertainment'], - workload: 'heavy', - photoVideo: 'pro', - }) - expect(specs.warnings.some(w => w.includes('⚠️') && w.toLowerCase().includes('simple'))).toBe(true) - }) -}) - -describe('convertToSpecs — gamesType scoring', () => { - it('complex games increase GPU requirements more than simple', () => { - const specsComplex = convertToSpecs({ - ...baseAnswers, - mainUse: ['gaming'], - gamesType: 'complex', - }) - const specsSimple = convertToSpecs({ - ...baseAnswers, - mainUse: ['gaming'], - gamesType: 'simple', - }) - // Both have gaming base (4.5), complex adds +3, simple adds +1 - expect(specsComplex.gpu).toContain('RTX') - expect(specsSimple.gpu).toContain('RTX') - // Complex should have higher GPU tier or same - expect(specsComplex.gpu).toBe(specsSimple.gpu) - }) -}) - -describe('convertToSpecs — photoVideo: basic scoring', () => { - it('basic photo editing adds modest GPU/storage', () => { - const specs = convertToSpecs({ - ...baseAnswers, - photoVideo: 'basic', - }) - expect(specs).toHaveProperty('gpu') - expect(specs).toHaveProperty('storage') - }) -}) - -describe('convertToSpecs — Apple Silicon matching', () => { - it('does not strip M1/M2/M3 from processor spec', () => { - const specs = convertToSpecs({ - ...baseAnswers, - system: 'mac', - }) - expect(specs.processor).toMatch(/M1|M2|M3/) - }) -}) - -describe('convertToSpecs — RAM comparison edge cases', () => { - it('sets correct RAM for multitasking >= 4', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: ['gaming', 'creating'], - webBrowsing: 'heavy', - }) - expect(specs.ram).toBe('16 GB') - }) - - it('sets 12 GB RAM for moderate multitasking', () => { - const specs = convertToSpecs({ - ...baseAnswers, - mainUse: ['work_school'], - workload: 'heavy', - }) - expect(specs.ram).toBe('12 GB') - }) -}) diff --git a/src/__tests__/matchLaptopClass.test.jsx b/src/__tests__/matchLaptopClass.test.jsx deleted file mode 100644 index 8ddaba2..0000000 --- a/src/__tests__/matchLaptopClass.test.jsx +++ /dev/null @@ -1,310 +0,0 @@ -import { describe, it, expect } from 'vitest' -import matchLaptopClass from '../utils/matchLaptopClass' - -const baseSpecs = { - processor: 'Intel i5 / Ryzen 5', - ram: '8 GB', - storage: '256 GB SSD', - gpu: 'Gráficos integrados básicos', - os: 'Windows 11', - budget: 'medium', - portability: 'Media o baja', - battery: 'Media (4-7h)', - flags: {}, -} - -describe('matchLaptopClass — Basic matching', () => { - it('returns an array of matches', () => { - const results = matchLaptopClass(baseSpecs) - expect(Array.isArray(results)).toBe(true) - }) - - it('returns up to 3 best matches', () => { - const results = matchLaptopClass(baseSpecs) - expect(results.length).toBeLessThanOrEqual(3) - }) - - it('each result has name, specs, and matchScore', () => { - const results = matchLaptopClass(baseSpecs) - if (results.length > 0) { - results.forEach(model => { - expect(model).toHaveProperty('name') - expect(model).toHaveProperty('matchScore') - }) - } - }) -}) - -describe('matchLaptopClass — GPU filtering', () => { - it('filters out non-RTX models when RTX is required', () => { - const specs = { - ...baseSpecs, - gpu: 'NVIDIA RTX 3050 o superior', - budget: 'high', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - expect(model.gpu.toLowerCase()).toContain('rtx') - }) - }) - - it('filters out RTX models for ChromeOS', () => { - const specs = { - ...baseSpecs, - os: 'ChromeOS', - gpu: 'Gráficos integrados básicos', - processor: 'Intel Celeron / MediaTek', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - expect(model.gpu.toLowerCase()).not.toContain('rtx') - }) - }) -}) - -describe('matchLaptopClass — macOS filtering', () => { - it('only returns MacBooks for macOS', () => { - const specs = { - ...baseSpecs, - os: 'macOS', - processor: 'Apple M1 / M2 / M3', - gpu: 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)', - budget: 'high', - } - const results = matchLaptopClass(specs) - if (results.length === 1 && results[0].name === 'Clase genérica') { - // No MacBooks match the strict GPU filter — known DB limitation - expect(results[0].name).toBe('Clase genérica') - } else { - results.forEach(model => { - expect(model.name.toLowerCase()).toContain('macbook') - }) - } - }) - - it('filters for Apple Silicon GPUs on macOS', () => { - const specs = { - ...baseSpecs, - os: 'macOS', - processor: 'Apple M1 / M2 / M3', - gpu: 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)', - budget: 'high', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - if (model.name === 'Clase genérica') return - const gpu = model.gpu.toLowerCase() - expect(gpu).toMatch(/m1|m2|m3|m4|apple/) - }) - }) - - it('excludes RTX models for macOS', () => { - const specs = { - ...baseSpecs, - os: 'macOS', - processor: 'Apple M1 / M2 / M3', - gpu: 'Apple integrada (M1/M2/M3) o Intel HD (modelos antiguos)', - budget: 'high', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - if (model.name === 'Clase genérica') return - expect(model.gpu.toLowerCase()).not.toContain('rtx') - }) - }) -}) - -describe('matchLaptopClass — Budget filtering', () => { - it('filters by low budget (<=6000)', () => { - const specs = { - ...baseSpecs, - budget: 'low', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - const price = parseInt(model.price.replace(/[^\d]/g, ''), 10) - expect(price).toBeLessThanOrEqual(6000) - }) - }) - - it('filters by medium budget (<=13000)', () => { - const specs = { - ...baseSpecs, - budget: 'medium', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - const price = parseInt(model.price.replace(/[^\d]/g, ''), 10) - expect(price).toBeLessThanOrEqual(13000) - }) - }) - - it('filters by high budget (>=13000)', () => { - const specs = { - ...baseSpecs, - budget: 'high', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - const price = parseInt(model.price.replace(/[^\d]/g, ''), 10) - expect(price).toBeGreaterThanOrEqual(13000) - }) - }) -}) - -describe('matchLaptopClass — RAM filtering', () => { - it('filters for 16 GB RAM when required', () => { - const specs = { - ...baseSpecs, - ram: '16 GB', - budget: 'high', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - expect(model.specs.toLowerCase()).toContain('16 gb') - }) - }) -}) - -describe('matchLaptopClass — CPU filtering', () => { - it('filters for i7/Ryzen 7 when required', () => { - const specs = { - ...baseSpecs, - processor: 'Intel i7 / Ryzen 7', - budget: 'high', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - const specsText = model.specs.toLowerCase() - expect(specsText).toMatch(/i7|ryzen 7/) - }) - }) -}) - -describe('matchLaptopClass — Scoring', () => { - it('scores models by spec matching', () => { - const results = matchLaptopClass(baseSpecs) - if (results.length > 1) { - for (let i = 0; i < results.length - 1; i++) { - expect(results[i].matchScore).toBeGreaterThanOrEqual(results[i + 1].matchScore) - } - } - }) - - it('prioritizePortability flag boosts portable models', () => { - const specs = { - ...baseSpecs, - flags: { prioritizePortability: true }, - } - const results = matchLaptopClass(specs) - if (results.length > 0) { - const portableMatches = results.filter(m => m.portability === 'Alta') - expect(portableMatches.length).toBeGreaterThan(0) - } - }) - - it('prioritizeGaming flag boosts gaming models', () => { - const specs = { - ...baseSpecs, - gpu: 'NVIDIA RTX 3050 o superior', - budget: 'high', - flags: { prioritizeGaming: true }, - } - const results = matchLaptopClass(specs) - if (results.length > 0) { - const gamingMatches = results.filter(m => - m.gpu.toLowerCase().includes('rtx') || - m.gpu.toLowerCase().includes('gtx') - ) - expect(gamingMatches.length).toBeGreaterThan(0) - } - }) - - it('prioritizeScreenQuality flag boosts high-res models', () => { - const specs = { - ...baseSpecs, - flags: { prioritizeScreenQuality: true }, - budget: 'high', - } - const results = matchLaptopClass(specs) - if (results.length > 0) { - const screenMatches = results.filter(m => - m.specs.toLowerCase().includes('2.5k') || - m.specs.toLowerCase().includes('retina') || - m.specs.toLowerCase().includes('qhd') || - m.specs.toLowerCase().includes('amoled') - ) - expect(screenMatches.length).toBeGreaterThan(0) - } - }) - - it('prioritizeSecurity flag boosts models with fingerprint', () => { - const specs = { - ...baseSpecs, - flags: { prioritizeSecurity: true }, - } - const results = matchLaptopClass(specs) - if (results.length > 0) { - const securityMatches = results.filter(m => - m.specs.toLowerCase().includes('huella') - ) - // Note: current DB has no models with "huella" — test passes if no crash - expect(securityMatches.length).toBeGreaterThanOrEqual(0) - } - }) -}) - -describe('matchLaptopClass — Edge cases', () => { - it('returns generic class when no matches found', () => { - const specs = { - ...baseSpecs, - processor: 'Intel i7 / Ryzen 7', - ram: '16 GB', - gpu: 'NVIDIA RTX 3050 o superior', - budget: 'low', - } - const results = matchLaptopClass(specs) - expect(results.length).toBeGreaterThan(0) - if (results.length === 1 && results[0].name === 'Clase genérica') { - expect(results[0].specs).toBe('') - } - }) - - it('handles ChromeOS with RTX spec (should filter out)', () => { - const specs = { - ...baseSpecs, - os: 'ChromeOS', - gpu: 'NVIDIA RTX 3050 o superior', - processor: 'Intel Celeron / MediaTek', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - if (model.name === 'Clase genérica' || !model.gpu) return - expect(model.gpu.toLowerCase()).not.toContain('rtx') - }) - }) - - it('handles ChromeOS with i7 spec (should filter out)', () => { - const specs = { - ...baseSpecs, - os: 'ChromeOS', - processor: 'Intel i7 / Ryzen 7', - gpu: 'Gráficos integrados básicos', - } - const results = matchLaptopClass(specs) - results.forEach(model => { - expect(model.specs.toLowerCase()).not.toMatch(/i7|ryzen 7/) - }) - }) -}) - -describe('matchLaptopClass — Results are sorted by score', () => { - it('results are in descending matchScore order', () => { - const results = matchLaptopClass(baseSpecs) - if (results.length > 1) { - for (let i = 0; i < results.length - 1; i++) { - expect(results[i].matchScore).toBeGreaterThanOrEqual(results[i + 1].matchScore) - } - } - }) -}) diff --git a/src/__tests__/setup.js b/src/__tests__/setup.js deleted file mode 100644 index c44951a..0000000 --- a/src/__tests__/setup.js +++ /dev/null @@ -1 +0,0 @@ -import '@testing-library/jest-dom' diff --git a/src/components/Comparison/Comparison.css b/src/components/Comparison/Comparison.css deleted file mode 100644 index 13cb2a0..0000000 --- a/src/components/Comparison/Comparison.css +++ /dev/null @@ -1,149 +0,0 @@ -.ComparisonContainer { - max-width: 1000px; - margin: 0 auto; - padding: 2rem 1rem; -} - -.ComparisonBack { - background: none; - border: 2px solid var(--color-primary); - color: var(--color-primary); - padding: 0.5rem 1rem; - border-radius: 8px; - cursor: pointer; - font-size: 0.9rem; - font-weight: 600; - transition: background 0.2s, color 0.2s; - margin-bottom: 1.5rem; -} - -.ComparisonBack:hover { - background: var(--color-primary); - color: white; -} - -.ComparisonContainer h2 { - text-align: center; - margin-bottom: 1.5rem; - color: var(--color-text); -} - -.ComparisonGrid { - display: flex; - flex-direction: column; - gap: 1px; - background: var(--color-border); - border-radius: 12px; - overflow: hidden; - box-shadow: var(--shadow-md); -} - -.ComparisonLabels { - display: grid; - grid-template-columns: 180px repeat(var(--cols, 2), 1fr); - background: var(--color-surface); -} - -.ComparisonLabelHeader { - padding: 1rem; - font-weight: 700; - color: var(--color-text-secondary); - font-size: 0.85rem; - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.ComparisonLaptopHeader { - padding: 1rem; - text-align: center; - border-left: 1px solid var(--color-border); -} - -.ComparisonLaptopHeader img { - width: 100%; - max-width: 180px; - height: auto; - border-radius: 8px; - margin-bottom: 0.5rem; - object-fit: cover; -} - -.ComparisonLaptopHeader h3 { - font-size: 0.95rem; - margin: 0.5rem 0 0.25rem; - color: var(--color-text); -} - -.ComparisonPrice { - font-weight: 700; - color: var(--color-primary); - font-size: 0.9rem; -} - -.ComparisonRow { - display: grid; - grid-template-columns: 180px repeat(var(--cols, 2), 1fr); - background: var(--color-surface); -} - -.ComparisonSpecLabel { - padding: 0.75rem 1rem; - font-weight: 600; - color: var(--color-text-secondary); - font-size: 0.9rem; - background: var(--color-bg-alt); - border-right: 1px solid var(--color-border); -} - -.ComparisonValue { - padding: 0.75rem 1rem; - font-size: 0.9rem; - color: var(--color-text); - border-left: 1px solid var(--color-border); - position: relative; -} - -.ComparisonValue.better { - background: var(--color-success-light); - font-weight: 600; -} - -.BetterBadge { - display: inline-block; - margin-left: 0.5rem; - font-size: 0.75rem; - color: var(--color-success); - font-weight: 700; -} - -@media (max-width: 768px) { - .ComparisonContainer { - padding: 1rem 0.5rem; - } - - .ComparisonLabels, - .ComparisonRow { - grid-template-columns: 120px repeat(var(--cols, 2), 1fr); - } - - .ComparisonLaptopHeader img { - max-width: 120px; - } - - .ComparisonLaptopHeader h3 { - font-size: 0.8rem; - } - - .ComparisonSpecLabel, - .ComparisonValue { - padding: 0.5rem; - font-size: 0.8rem; - } -} - -@media (max-width: 480px) { - .ComparisonLabels, - .ComparisonRow { - grid-template-columns: 100px repeat(var(--cols, 2), 1fr); - } -} diff --git a/src/components/Comparison/Comparison.jsx b/src/components/Comparison/Comparison.jsx deleted file mode 100644 index 7ea33af..0000000 --- a/src/components/Comparison/Comparison.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import LaptopCard from '../LaptopCard/LaptopCard' - -export default function Comparison({ laptops, onBack }) { - const specs = ['processor', 'ram', 'storage', 'gpu', 'os', 'portability', 'battery'] - const specLabels = { - processor: 'Procesador', - ram: 'RAM', - storage: 'Almacenamiento', - gpu: 'Gráficos', - os: 'Sistema operativo', - portability: 'Portabilidad', - battery: 'Batería', - } - - const parseNum = (str) => { - if (!str) return 0 - const nums = str.match(/\d+/g) - return nums ? nums.reduce((a, b) => a + parseInt(b), 0) : 0 - } - - const getBetter = (spec, a, b) => { - const va = a[spec] || '' - const vb = b[spec] || '' - if (va === vb) return 'equal' - const na = parseNum(va) - const nb = parseNum(vb) - if (spec === 'ram' || spec === 'storage' || spec === 'processor') { - return na > nb ? 'a' : na < nb ? 'b' : 'equal' - } - return 'equal' - } - - return ( -
- -

Comparación de laptops

-
-
-
Especificación
- {laptops.map((l, i) => ( -
- {l.name} -

{l.name}

-

{l.price}

-
- ))} -
- {specs.map((spec) => { - const better = laptops.length === 2 ? getBetter(spec, laptops[0], laptops[1]) : 'equal' - return ( -
-
{specLabels[spec]}
- {laptops.map((l, i) => { - const isBetter = better === 'a' && i === 0 || better === 'b' && i === 1 - return ( -
- {l[spec] || '—'} - {isBetter && ✓ Mejor} -
- ) - })} -
- ) - })} -
-
Uso recomendado
- {laptops.map((l, i) => ( -
{l.use}
- ))} -
-
-
- ) -} diff --git a/src/components/LaptopCard/LaptopCard.jsx b/src/components/LaptopCard/LaptopCard.jsx index c81076a..f5d115d 100644 --- a/src/components/LaptopCard/LaptopCard.jsx +++ b/src/components/LaptopCard/LaptopCard.jsx @@ -1,12 +1,17 @@ import React from 'react' import './LaptopCard.css' +// UX-03: heading text differs for approximate matches function LaptopCard({ model }) { if (!model) return null + const heading = model.isApproximate + ? 'Esta laptop se acerca a tu perfil:' + : 'Te sugerimos esta laptop:' + return (
-

Te sugerimos esta laptop:

+

{heading}

{model.name} { + try { + const saved = localStorage.getItem('csh_quiz_state') + if (saved) { + const parsed = JSON.parse(saved) + return { + answers: parsed.answers || {}, + step: parsed.step || 0, + result: parsed.result || null, + } + } + } catch { + // ignore parse errors + } + return { answers: {}, step: 0, result: null } + } + + const initialState = loadSavedState() + const navigate = useNavigate() + const [answers, setAnswers] = useState(initialState.answers) + const [step, setStep] = useState(initialState.step) + const [result, setResult] = useState(initialState.result) + const [showWarnings, setShowWarnings] = useState(true) + const [showRationale, setShowRationale] = useState(false) + const [showApproximate, setShowApproximate] = useState(false) const [copied, setCopied] = useState(false) + const [isSharedView, setIsSharedView] = useState(false) + useEffect(() => { + localStorage.setItem('csh_quiz_state', JSON.stringify({ answers, step, result })) + }, [answers, step, result]) + + // UX-05: scroll to top when result renders + useEffect(() => { + if (result) window.scrollTo({ top: 0, behavior: 'smooth' }) + }, [result]) + + // FEATURE: read shared plan from ?plan= URL param on mount useEffect(() => { const params = new URLSearchParams(window.location.search) - const encoded = params.get('r') - if (encoded) { - const decoded = decodeResults(encoded) - if (decoded && decoded.a && decoded.r) { - setAnswers(decoded.a) - setResult(decoded.r) + const plan = params.get('plan') + if (plan) { + const decoded = decodeAnswers(plan) + if (decoded) { + setAnswers(decoded) + setResult(convertToSpecs(decoded)) + setIsSharedView(true) } } }, []) @@ -45,7 +81,6 @@ function Recommendation() { const current = questions[step] const handleCheckboxChange = (questionId, value) => { - // Si estaba seleccionada la opción "full_use", la eliminamos if (answers[questionId] === 'full_use') { setAnswers(prev => ({ ...prev, @@ -93,59 +128,72 @@ function Recommendation() { setAnswers({}) setStep(0) setResult(null) - setCompareIds([]) - setShowComparison(false) - window.history.replaceState({}, '', window.location.pathname) + setIsSharedView(false) + localStorage.removeItem('csh_quiz_state') + // Clean URL params from shared links + const url = new URL(window.location.href) + url.searchParams.delete('plan') + window.history.replaceState({}, '', url.toString()) } - const toggleCompare = (id) => { - setCompareIds(prev => { - if (prev.includes(id)) return prev.filter(i => i !== id) - if (prev.length >= 3) return prev - return [...prev, id] - }) - } - - const handleShare = async () => { - const encoded = encodeResults(answers, result) - if (!encoded) return - const url = `${window.location.origin}${window.location.pathname}?r=${encoded}` - try { - await navigator.clipboard.writeText(url) - setCopied(true) - setTimeout(() => setCopied(false), 2000) - } catch { - window.prompt('Copiá este link:', url) + // FEATURE: share handler — native share on mobile, clipboard fallback on desktop + const handleShare = useCallback(() => { + const shareUrl = `${window.location.origin}/quiz?plan=${encodeAnswers(answers)}` + const shareText = result + ? `Mi laptop recomendada: ${result.processor}, ${result.ram} RAM, ${result.gpu}.` + : 'Mi recomendación personalizada de laptop.' + if (navigator.share) { + navigator.share({ + title: 'Mi recomendación — Computer Selector Helper', + text: shareText, + url: shareUrl + }).catch(() => {}) + } else { + navigator.clipboard.writeText(shareUrl).then(() => { + setCopied(true) + setTimeout(() => setCopied(false), 2500) + }).catch(() => { + prompt('Copiá este link para compartir tu recomendación:', shareUrl) + }) } - } - - const handleWhatsAppShare = () => { - const encoded = encodeResults(answers, result) - if (!encoded) return - const url = `${window.location.origin}${window.location.pathname}?r=${encoded}` - const text = `Mirá las laptops que me recomiendan: ${url}` - window.open(`https://wa.me/?text=${encodeURIComponent(text)}`, '_blank') - } + }, [answers, result]) - const handleBackFromComparison = () => { - setShowComparison(false) - } - - if (showComparison && result?.laptopClass) { - const selected = result.laptopClass.filter(m => compareIds.includes(m.id)) - if (selected.length >= 2) { - return + const handleCompare = () => { + if (!result || !result.laptopClass) return + const ids = result.laptopClass.filter(m => !m.isGeneric).map(m => m.id).join(',') + if (ids) { + navigate(`/compare?models=${ids}`) } } if (result) { + const shareUrl = `${window.location.origin}/quiz?plan=${encodeAnswers(answers)}` + const waText = buildWhatsAppText(result, shareUrl) return (
-

Tu recomendación técnica:

+ + Tu recomendación — Computer Selector Helper + + + + + + {/* FEATURE: banner shown when viewing a shared link */} + {isSharedView && ( +
+
+ )}
- -

Usos seleccionados

+
+ +

Usos seleccionados

+

{answers.mainUse === 'full_use' ? 'Uso exigente (todo lo anterior)' @@ -158,8 +206,10 @@ function Recommendation() {

- -

Tus respuestas

+
+ +

Tus respuestas

+
{questions.map((q, index) => { const answer = answers[q.id] @@ -193,7 +243,7 @@ function Recommendation() {
-

💻 Especificaciones Recomendadas

+

Especificaciones Recomendadas

@@ -217,7 +267,7 @@ function Recommendation() {
-

💻🎯 Especificaciones Extra

+

Especificaciones Extra

@@ -244,77 +294,164 @@ function Recommendation() { {result.warnings.length > 0 && ( <> -

- ⚠️ Advertencias importantes: -

-
- {result.warnings.map((warning, index) => ( -
- -

{warning}

-
- ))} -
+ + {showWarnings && ( +
+ {result.warnings.map((warning, index) => ( +
+ +

{warning}

+
+ ))} +
+ )} )} {result.rationale.length > 0 && ( - <> -

- 🧠 Justificación técnica: -

-
- {result.rationale.map((reason, index) => ( -
- -

{reason}

-
- ))} + <> + + {showRationale && ( +
+ {result.rationale.map((reason, index) => ( +
+ +

{reason}

+
+ ))} +
+ )} + + )} + + + {(!result.laptopClass || result.laptopClass.length === 0) && result.approximateClass && result.approximateClass.length > 0 && ( +
+
+ +

No encontramos matches exactos para tus necesidades

+
+ +
+
+
Usá las specs como guía de compra
+

Revisá las especificaciones recomendadas de arriba y usalas como referencia al buscar en tiendas.

+ + Buscar en Google con estas specs + +
+ +
+
Ver equipos aproximados
+

Estos modelos se acercan a lo que necesitás, aunque no cumplen todos los requisitos.

+ +
- + + {showApproximate && ( +
+
Modelos aproximados (pueden no cumplir todos tus requisitos)
+
+ {result.approximateClass.map((model, index) => ( +
+ + Aproximado +
+ ))} +
+
+ )} +
)} + {(!result.laptopClass || result.laptopClass.length === 0) && (!result.approximateClass || result.approximateClass.length === 0) && ( +
+ +

Por el momento no se encuentra un equipo con las especificaciones recomendadas

+

Usá las especificaciones de arriba como guía al buscar en tiendas.

+ + Buscar en Google con estas specs + +
+ )} {result.laptopClass && Array.isArray(result.laptopClass) && ( - <> -

🎯 Modelos sugeridos (Puede haber alguno o no es solo referencia):

-
- {result.laptopClass.map((model, index) => ( -
- - + <> +

Modelos sugeridos (referencia):

+
+ {result.laptopClass.map((model, index) => ( + + ))} + {result.laptopClass.filter(m => !m.isGeneric).length > 0 && ( + + )} +
+ + )} + +
+
+
Compartir recomendación
+

Guardá este link o envialo a alguien.

+
+ + + 📱 Enviar por WhatsApp +
- ))}
- {compareIds.length >= 2 && ( -
-
- )} -
- -
- - )} - -
-

¿Querés cambiar tus respuestas o probar otra combinación?

-
) @@ -323,6 +460,12 @@ function Recommendation() { return (
+
+
+
+
+ Pregunta {step + 1} de {questions.length} +

{current.question}

{current.info &&

{current.info}

} @@ -332,10 +475,10 @@ function Recommendation() { handleCheckboxChange(current.id, opt.value)} /> - {' '}{opt.label} + {' '}{opt.label} ))} {current.type === 'checkbox' && current.extraRadio && ( @@ -351,7 +494,7 @@ function Recommendation() { })) } /> - {' '}{current.extraRadio.label} + {' '}{current.extraRadio.label} )} @@ -365,22 +508,29 @@ function Recommendation() { checked={answers[current.id] === opt.value} onChange={() => handleRadioChange(current.id, opt.value)} /> - {' '}{opt.label} + {' '}{opt.label} ))}
- +
+ {step > 0 && ( + + )} + +
) } diff --git a/src/index.css b/src/index.css index 89811a6..d0a5479 100644 --- a/src/index.css +++ b/src/index.css @@ -74,3 +74,30 @@ button { font-family: var(--font-family); cursor: pointer; } + +/* Global Scrollbar */ +::-webkit-scrollbar { + width: 10px; + height: 10px; +} +::-webkit-scrollbar-track { + background: var(--color-background); +} +::-webkit-scrollbar-thumb { + background: var(--color-primary); + border-radius: var(--radius-xl); + border: 2px solid var(--color-background); +} +::-webkit-scrollbar-thumb:hover { + background: var(--color-primary-hover); +} +* { + scrollbar-width: thin; + scrollbar-color: var(--color-primary) var(--color-background); +} + +/* Global Text Selection */ +::selection { + background: var(--color-primary); + color: #ffffff; +} diff --git a/src/pages/Compare/Compare.css b/src/pages/Compare/Compare.css new file mode 100644 index 0000000..107de93 --- /dev/null +++ b/src/pages/Compare/Compare.css @@ -0,0 +1,523 @@ +/* ===================== Compare Page ===================== */ + +.ComparePage { + width: 100%; + max-width: 100%; + padding: var(--spacing-xl, 2rem) var(--spacing-lg, 1.5rem); + margin: 0 auto; + box-sizing: border-box; + align-self: flex-start; +} + +/* Header */ +.CompareHeader { + text-align: center; + margin-bottom: var(--spacing-xl, 2rem); + position: relative; + min-height: 40px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.CompareBackBtn { + position: absolute; + left: 0; + top: 0; + display: inline-flex; + align-items: center; + gap: var(--spacing-xs, 0.25rem); + background: transparent; + border: none; + padding: var(--spacing-xs, 0.25rem) var(--spacing-sm, 0.5rem); + font-size: var(--font-size-base, 1rem); + font-weight: 600; + color: var(--color-text-muted, #64748b); + cursor: pointer; + transition: color 0.2s, transform 0.2s; + z-index: 10; +} +.CompareBackBtn:hover { + color: var(--color-primary, #2563eb); + transform: translateX(-2px); +} + +.CompareHeader h2 { + font-size: var(--font-size-2xl, 1.5rem); + color: var(--color-text, #1e293b); + margin: 0 0 var(--spacing-xs) 0; +} +.CompareSubtitle { + color: var(--color-text-muted, #64748b); + margin: 0; + font-size: var(--font-size-sm, 0.875rem); +} + +/* Filters */ +.CompareFilters { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-md, 1rem); + margin-bottom: var(--spacing-lg, 1.5rem); + align-items: center; +} +.CompareSearch { + position: relative; + flex: 1; + min-width: 200px; +} +.CompareSearchIcon { + position: absolute; + left: 0.75rem; + top: 50%; + transform: translateY(-50%); + color: var(--color-text-muted, #64748b); + font-size: 14px; + pointer-events: none; +} +.CompareSearchInput { + width: 100%; + padding: 0.5rem 0.75rem 0.5rem 2.25rem; + border: 1px solid var(--color-border, #e2e8f0); + border-radius: var(--radius-md, 0.5rem); + font-size: var(--font-size-sm, 0.875rem); + font-family: var(--font-family, inherit); + color: var(--color-text, #1e293b); + background: white; + box-sizing: border-box; + transition: border-color 0.2s; +} +.CompareSearchInput:focus { outline: none; border-color: var(--color-primary, #2563eb); } +.CompareSelect { + padding: 0.5rem 0.75rem; + border: 1px solid var(--color-border, #e2e8f0); + border-radius: var(--radius-md, 0.5rem); + font-size: var(--font-size-sm, 0.875rem); + font-family: var(--font-family, inherit); + color: var(--color-text, #1e293b); + background: white; + cursor: pointer; + transition: border-color 0.2s; +} +.CompareSelect:focus { outline: none; border-color: var(--color-primary, #2563eb); } + +/* Selection bar (counter + toggle button) */ +.CompareSelectionBar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing-md, 1rem); + margin-bottom: var(--spacing-md, 1rem); + flex-wrap: wrap; +} + +/* Selection counter */ +.CompareSelectionStatus { + font-size: var(--font-size-sm, 0.875rem); + color: var(--color-text-muted, #64748b); + margin: 0; + font-weight: 500; +} + +/* Toggle button */ +.CompareGridToggleBtn { + display: inline-flex; + align-items: center; + gap: 0.35rem; + background: transparent; + border: 1px solid var(--color-border, #e2e8f0); + border-radius: var(--radius-md, 0.5rem); + padding: 0.3rem 0.9rem; + font-size: var(--font-size-sm, 0.875rem); + font-weight: 600; + color: var(--color-primary, #2563eb); + cursor: pointer; + transition: background 0.18s, border-color 0.18s; + white-space: nowrap; +} +.CompareGridToggleBtn:hover { + background: #eff6ff; + border-color: var(--color-primary, #2563eb); +} + +/* Grid collapse animation */ +.CompareModelGrid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: var(--spacing-md, 1rem); + margin-bottom: var(--spacing-xl, 2rem); + max-height: 2000px; + overflow: hidden; + transition: max-height 0.35s ease, opacity 0.25s ease, margin 0.25s ease; + opacity: 1; +} +.CompareModelGrid--collapsed { + max-height: 0; + opacity: 0; + margin-bottom: 0; + pointer-events: none; +} + +/* Mini chip strip shown when grid is collapsed */ +.CompareSelectedStrip { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-sm, 0.5rem); + margin-bottom: var(--spacing-lg, 1.5rem); + animation: fadeSlideIn 0.2s ease-out; +} + +@keyframes fadeSlideIn { + from { opacity: 0; transform: translateY(-6px); } + to { opacity: 1; transform: translateY(0); } +} + +.CompareSelectedChip { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs, 0.25rem); + background: #eff6ff; + border: 1px solid #bfdbfe; + border-radius: 999px; + padding: 0.25rem 0.5rem 0.25rem 0.35rem; + font-size: var(--font-size-sm, 0.875rem); +} +.CompareSelectedChip__img { + width: 28px; + height: 20px; + object-fit: contain; + border-radius: 3px; + background: white; +} +.CompareSelectedChip__name { + font-weight: 600; + color: var(--color-text, #1e293b); + max-width: 140px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.CompareSelectedChip__remove { + display: flex; + align-items: center; + justify-content: center; + background: #bfdbfe; + border: none; + border-radius: 50%; + width: 18px; + height: 18px; + font-size: 9px; + color: #1e40af; + cursor: pointer; + transition: background 0.15s; + flex-shrink: 0; +} +.CompareSelectedChip__remove:hover { background: #93c5fd; } + + +.CompareModelCard { + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-xs, 0.25rem); + background: white; + border: 2px solid var(--color-border, #e2e8f0); + border-radius: var(--radius-lg, 0.75rem); + padding: var(--spacing-md, 1rem); + cursor: pointer; + transition: border-color 0.2s, box-shadow 0.2s, transform 0.15s; + position: relative; + text-align: center; +} +.CompareModelCard:hover:not(:disabled) { + border-color: var(--color-primary, #2563eb); + box-shadow: 0 4px 12px rgba(37,99,235,0.12); + transform: translateY(-2px); +} +.CompareModelCard.selected { + border-color: var(--color-primary, #2563eb); + background: #eff6ff; + box-shadow: 0 0 0 1px var(--color-primary, #2563eb); +} +.CompareModelCard.disabled { + opacity: 0.4; + cursor: not-allowed; +} +.CompareModelCard__img { + width: 80px; + height: 60px; + object-fit: contain; + border-radius: var(--radius-sm, 0.375rem); +} +.CompareModelCard__info { + display: flex; + flex-direction: column; + gap: 2px; +} +.CompareModelCard__name { + font-size: var(--font-size-sm, 0.875rem); + font-weight: 600; + color: var(--color-text, #1e293b); + line-height: 1.3; +} +.CompareModelCard__price { + font-size: 0.75rem; + color: var(--color-primary, #2563eb); + font-weight: 600; +} +.CompareModelCard__badge { + position: absolute; + top: 6px; + right: 6px; + width: 20px; + height: 20px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + background: var(--color-border, #e2e8f0); + color: var(--color-text-muted, #64748b); + transition: background 0.2s; +} +.CompareModelCard.selected .CompareModelCard__badge { + background: var(--color-primary, #2563eb); + color: white; +} + +/* Comparison Table Section */ +.CompareTableSection { + margin-top: var(--spacing-xl, 2rem); + border-top: 2px solid var(--color-border, #e2e8f0); + padding-top: var(--spacing-xl, 2rem); + /* Smooth appearance — prevents the jarring layout jump */ + animation: tableAppear 0.3s ease-out; +} + +@keyframes tableAppear { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} +.CompareTableSection h3 { + text-align: center; + font-size: var(--font-size-xl, 1.25rem); + color: var(--color-text, #1e293b); + margin: 0 0 var(--spacing-lg, 1.5rem) 0; +} + +.CompareTableWrapper { + overflow-x: auto; + border-radius: var(--radius-lg, 0.75rem); + border: 1px solid var(--color-border, #e2e8f0); + box-shadow: 0 4px 12px rgba(0,0,0,0.06); +} + +.CompareTable { + width: 100%; + border-collapse: collapse; + min-width: 500px; +} + +/* Model header cells */ +.CompareTable__modelHeader { + background: var(--color-primary, #2563eb); + color: white; + padding: var(--spacing-md, 1rem); + text-align: center; + vertical-align: top; + min-width: 160px; +} +.CompareTable__removeBtn { + display: flex; + align-items: center; + justify-content: center; + background: rgba(255,255,255,0.2); + border: none; + color: white; + border-radius: 50%; + width: 22px; + height: 22px; + font-size: 10px; + cursor: pointer; + margin: 0 0 var(--spacing-xs, 0.25rem) auto; + transition: background 0.2s; +} +.CompareTable__removeBtn:hover { background: rgba(255,255,255,0.35); } +.CompareTable__modelImg { + width: 70px; + height: 52px; + object-fit: contain; + background: white; + border-radius: var(--radius-sm, 0.375rem); + display: block; + margin: 0 auto var(--spacing-xs, 0.25rem); +} +.CompareTable__modelName { + font-size: var(--font-size-sm, 0.875rem); + font-weight: 700; + line-height: 1.3; + display: block; +} + +/* Row label */ +.CompareTable__label { + background: var(--color-background, #f8fafc); + padding: var(--spacing-md, 1rem); + font-size: var(--font-size-sm, 0.875rem); + font-weight: 600; + color: var(--color-text, #1e293b); + white-space: nowrap; + border-bottom: 1px solid var(--color-border, #e2e8f0); + min-width: 140px; +} + +/* Data cells */ +.CompareTable__cell { + padding: var(--spacing-md, 1rem); + text-align: center; + font-size: var(--font-size-sm, 0.875rem); + color: var(--color-text, #1e293b); + border-bottom: 1px solid var(--color-border, #e2e8f0); + transition: background 0.2s; + vertical-align: top; +} +.CompareTable__cell.best { + background: #f0fdf4; +} +.CompareTable__cell--muted { + color: var(--color-text-muted, #64748b); + font-size: 0.75rem; +} + +/* Best badge */ +.BestBadge { + display: block; + margin-top: 4px; + font-size: 0.65rem; + font-weight: 700; + text-transform: uppercase; + color: #16a34a; + letter-spacing: 0.04em; +} + +/* Store link */ +.CompareTable__storeLink { + display: inline-flex; + align-items: center; + gap: 4px; + color: var(--color-primary, #2563eb); + font-weight: 600; + font-size: var(--font-size-sm, 0.875rem); + text-decoration: none; + transition: color 0.2s; +} +.CompareTable__storeLink:hover { color: var(--color-primary-hover, #1d4ed8); } + +/* Share Row */ +.CompareShareRow { + display: flex; + justify-content: center; + margin-top: var(--spacing-xl, 2rem); +} +.CompareShareBtn { + display: inline-flex; + align-items: center; + gap: var(--spacing-sm, 0.5rem); + background: transparent; + border: 2px solid var(--color-primary, #2563eb); + color: var(--color-primary, #2563eb); + border-radius: var(--radius-md, 0.5rem); + padding: var(--spacing-sm, 0.5rem) var(--spacing-xl, 2rem); + font-size: var(--font-size-base, 1rem); + font-weight: 600; + cursor: pointer; + transition: all 0.2s; +} +.CompareShareBtn:hover { + background: var(--color-primary, #2563eb); + color: white; + transform: translateY(-1px); +} + +/* Empty state */ +.CompareEmpty { + text-align: center; + padding: var(--spacing-2xl, 3rem); + color: var(--color-text-muted, #64748b); + display: flex; + flex-direction: column; + align-items: center; + gap: var(--spacing-sm, 0.5rem); +} +.CompareEmpty__clearBtn { + background: transparent; + border: 1px solid var(--color-border, #e2e8f0); + color: var(--color-text, #1e293b); + border-radius: var(--radius-md, 0.5rem); + padding: var(--spacing-xs, 0.25rem) var(--spacing-md, 1rem); + font-size: var(--font-size-sm, 0.875rem); + cursor: pointer; + transition: all 0.2s; +} +.CompareEmpty__clearBtn:hover { + background: var(--color-border, #e2e8f0); +} + +/* Responsive */ +@media (max-width: 768px) { + .ComparePage { + width: 100vw; + padding: var(--spacing-xl, 2rem) var(--spacing-sm, 0.5rem); + overflow-x: hidden; + } + + .CompareModelGrid { + grid-template-columns: repeat(auto-fill, minmax(45%, 1fr)); + gap: var(--spacing-sm, 0.5rem); + } + .CompareFilters { + flex-direction: column; + } + .CompareSearch { min-width: 100%; } + + .CompareHeader { + padding-top: 2.5rem; /* Make room for the absolute back button on small screens */ + } + + .CompareBackBtn__text { + display: none; /* Icon only on mobile to save space */ + } + + /* Table mobile optimizations — Simple lateral scroll */ + .CompareTableWrapper { + -webkit-overflow-scrolling: touch; + width: 100%; + overflow-x: auto; + } + + .CompareTable__modelHeader { + min-width: 120px; + padding: var(--spacing-sm, 0.5rem); + } + + .CompareTable__label { + min-width: 100px; + padding: var(--spacing-sm, 0.5rem); + font-size: 0.8rem; + } + + .CompareTable__cell { + padding: var(--spacing-sm, 0.5rem); + font-size: 0.8rem; + } + + .CompareTable__modelImg { + width: 60px; + height: 45px; + } + + .CompareTable__modelName { + font-size: 0.8rem; + } +} diff --git a/src/pages/Compare/Compare.jsx b/src/pages/Compare/Compare.jsx new file mode 100644 index 0000000..5479a85 --- /dev/null +++ b/src/pages/Compare/Compare.jsx @@ -0,0 +1,466 @@ +import React, { useState, useEffect, useRef } from 'react' +import { Helmet } from 'react-helmet-async' +import { useNavigate } from 'react-router-dom' +import laptopModels from '../../utils/laptopModels' +import { parseModelSpecs, getBestIndices } from '../../utils/parseSpecs' +import './Compare.css' +import { FaPlus, FaTimes, FaSearch, FaArrowLeft, FaChevronUp, FaChevronDown } from 'react-icons/fa' + +const MAX_COMPARE = 3 +const LS_KEY = 'csh_compare_ids' + +function Compare() { + const navigate = useNavigate() + const [selectedIds, setSelectedIds] = useState(() => { + // 1. Prioridad: URL param + if (typeof window !== 'undefined') { + const params = new URLSearchParams(window.location.search) + const raw = params.get('models') + if (raw) { + const ids = raw.split(',').filter(id => laptopModels.some(m => m.id === id)) + if (ids.length > 0) return ids.slice(0, MAX_COMPARE) + } + } + // 2. Fallback: localStorage + try { + const saved = localStorage.getItem(LS_KEY) + if (saved) { + const ids = JSON.parse(saved) + if (Array.isArray(ids) && ids.length > 0) { + const valid = ids.filter(id => laptopModels.some(m => m.id === id)) + return valid.slice(0, MAX_COMPARE) + } + } + } catch { /* ignore */ } + return [] + }) + + const [search, setSearch] = useState('') + const [filterOS, setFilterOS] = useState('all') + const [sortBy, setSortBy] = useState('price') + const [shareCopied, setShareCopied] = useState(false) + + const [gridCollapsed, setGridCollapsed] = useState(() => { + // Si viene directo con modelos seleccionados (ej: desde Quiz), por defecto colapsamos la grilla para que vea la tabla directo + const urlParams = new URLSearchParams(window.location.search) + const hasModelsInUrl = urlParams.get('models') + + try { + const saved = localStorage.getItem('csh_compare_grid_collapsed') + if (saved !== null) { + // Si hay una preferencia guardada y no venimos recién del Quiz, respetamos la preferencia + if (!hasModelsInUrl) return JSON.parse(saved) + } + } catch { /* ignore */ } + + // Si llegamos de la recomendación con modelos, forzar colapso inicial. + // Si no, arrancar expandido (false). + return hasModelsInUrl ? true : false + }) + const tableRef = useRef(null) + const prevTableVisible = useRef(false) + + // Persist grid collapse state + useEffect(() => { + localStorage.setItem('csh_compare_grid_collapsed', JSON.stringify(gridCollapsed)) + }, [gridCollapsed]) + + // Persist selection to localStorage + useEffect(() => { + localStorage.setItem(LS_KEY, JSON.stringify(selectedIds)) + }, [selectedIds]) + + // Keep URL in sync with selection + useEffect(() => { + const url = new URL(window.location.href) + if (selectedIds.length > 0) { + url.searchParams.set('models', selectedIds.join(',')) + } else { + url.searchParams.delete('models') + } + window.history.replaceState({}, '', url.toString()) + }, [selectedIds]) + + // FIX-3: auto-expand grid when all models are removed (except on initial load) + const isInitialMount = useRef(true) + useEffect(() => { + if (isInitialMount.current) { + isInitialMount.current = false + return + } + if (selectedIds.length === 0) setGridCollapsed(false) + }, [selectedIds]) + + // Scroll comparison table into view when it first appears (2 → 3 models or 1 → 2) + useEffect(() => { + const tableVisible = selectedIds.length >= 2 + if (tableVisible && !prevTableVisible.current && tableRef.current) { + setTimeout(() => tableRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }), 100) + } + prevTableVisible.current = tableVisible + }, [selectedIds]) + + const toggleModel = (id) => { + setSelectedIds(prev => { + if (prev.includes(id)) return prev.filter(x => x !== id) + if (prev.length >= MAX_COMPARE) return prev + const next = [...prev, id] + // Auto-collapse grid when 2+ models are selected + if (next.length >= 2) setGridCollapsed(true) + return next + }) + } + + const removeModel = (id) => setSelectedIds(prev => prev.filter(x => x !== id)) + + // Filter + sort the catalog + const displayModels = laptopModels + .filter(m => { + const matchSearch = + m.name.toLowerCase().includes(search.toLowerCase()) || + m.specs.toLowerCase().includes(search.toLowerCase()) || + m.gpu.toLowerCase().includes(search.toLowerCase()) + if (!matchSearch) return false + if (filterOS === 'all') return true + const { os } = parseModelSpecs(m) + return ( + (filterOS === 'windows' && os === 'Windows') || + (filterOS === 'mac' && os === 'macOS') || + (filterOS === 'chrome' && os === 'ChromeOS') + ) + }) + .sort((a, b) => { + const pa = parseModelSpecs(a) + const pb = parseModelSpecs(b) + if (sortBy === 'price') return pa.price - pb.price + if (sortBy === 'ram') return pb.ram - pa.ram + if (sortBy === 'gpu') return pb.gpuTier - pa.gpuTier + return a.name.localeCompare(b.name) + }) + + const selectedModels = selectedIds + .map(id => laptopModels.find(m => m.id === id)) + .filter(Boolean) + + // Pre-compute parsed specs & best-value highlights + const parsedModels = selectedModels.map(m => parseModelSpecs(m)) + const bestPrice = getBestIndices(parsedModels.map(p => p.price), false) + const bestRam = getBestIndices(parsedModels.map(p => p.ram), true) + const bestStorage = getBestIndices(parsedModels.map(p => p.storageGB), true) + const bestGPU = getBestIndices(parsedModels.map(p => p.gpuTier), true) + + const handleShare = () => { + const url = `${window.location.origin}/compare?models=${selectedIds.join(',')}` + navigator.clipboard?.writeText(url) + .then(() => { setShareCopied(true); setTimeout(() => setShareCopied(false), 2500) }) + .catch(() => prompt('Copiá este link de comparación:', url)) + } + + return ( +
+ + Comparar laptops — Computer Selector Helper + + + + + + {/* Header */} +
+ +

Comparar laptops

+

+ Seleccioná hasta {MAX_COMPARE} laptops para comparar sus especificaciones +

+
+ + {/* Filters */} +
+
+
+ + +
+ + {/* Selection counter + toggle */} +
+

+ {selectedIds.length === 0 + ? 'Hacé click en una laptop para agregarla a la comparación' + : `${selectedIds.length}/${MAX_COMPARE} laptops seleccionadas${selectedIds.length < 2 ? ' — seleccioná al menos una más para comparar' : ''}` + } +

+ {selectedIds.length > 0 && ( + + )} +
+ + {/* Mini strip of selected models when grid is collapsed */} + {gridCollapsed && selectedIds.length > 0 && ( +
+ {selectedIds.map(id => { + const m = laptopModels.find(x => x.id === id) + if (!m) return null + return ( +
+ {m.name} { e.target.src = '/help.svg'; e.target.onerror = null }} + /> + {m.name} + +
+ ) + })} +
+ )} + + {/* Model picker grid */} + {displayModels.length === 0 && !gridCollapsed && ( +
+

No hay laptops que coincidan con tu búsqueda.

+ +
+ )} +
+ {displayModels.map(model => { + const isSelected = selectedIds.includes(model.id) + const isDisabled = !isSelected && selectedIds.length >= MAX_COMPARE + return ( + + ) + })} +
+ + {/* Comparison table — only when 2+ selected */} + {selectedModels.length >= 2 && ( +
+

Comparación de especificaciones

+
+ + + + + {selectedModels.map(m => ( + + ))} + + + + {/* Price */} + + + {selectedModels.map((m, i) => ( + + ))} + + {/* RAM */} + + + {parsedModels.map((p, i) => ( + + ))} + + {/* Storage */} + + + {parsedModels.map((p, i) => ( + + ))} + + {/* GPU */} + + + {selectedModels.map((m, i) => ( + + ))} + + {/* Processor */} + + + {parsedModels.map((p, i) => ( + + ))} + + {/* Screen */} + + + {parsedModels.map((p, i) => ( + + ))} + + {/* OS */} + + + {parsedModels.map((p, i) => ( + + ))} + + {/* Portability */} + + + {selectedModels.map(m => ( + + ))} + + {/* Ideal for */} + + + {selectedModels.map(m => ( + + ))} + + {/* Store links */} + + + {selectedModels.map(m => ( + + ))} + + +
Especificación + + {m.name} { e.target.src = '/help.svg'; e.target.onerror = null }} + /> + {m.name} +
💰 Precio + {m.price} + {bestPrice[i] && Menor precio} +
🧠 RAM + {p.ram > 0 ? `${p.ram} GB` : 'N/A'} + {bestRam[i] && Mayor RAM} +
💾 Almacenamiento + {p.storageLabel} + {bestStorage[i] && Mayor storage} +
🎮 GPU + {m.gpu} + {bestGPU[i] && Mejor GPU} +
⚙️ Procesador + {p.processor} +
🖥️ Pantalla + {p.screenLabel} +
💻 Sistema + {p.os} +
🎒 Portabilidad + {m.portability} +
✅ Ideal para + {m.use} +
🔗 Ver + + Buscar → + +
+
+ + {/* Share comparison */} +
+ +
+
+ )} +
+ ) +} + +export default Compare diff --git a/src/pages/Landing/Landing.css b/src/pages/Landing/Landing.css index 2ee41c3..a9399e3 100644 --- a/src/pages/Landing/Landing.css +++ b/src/pages/Landing/Landing.css @@ -1,177 +1,179 @@ .Landing { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 2rem 1rem; display: flex; flex-direction: column; - align-items: center; - justify-content: center; - justify-items: center; - width: auto; - max-width: 900px; - margin: var(--spacing-xl) auto; - padding: var(--spacing-xl); - border-radius: var(--radius-xl); - background: linear-gradient(135deg, var(--color-background) 0%, #dbeafe 100%); - box-shadow: var(--shadow-lg); - border: 1px solid var(--color-border); -} - -.Landing h2 { - font-size: var(--font-size-2xl); - line-height: 1.3; - color: var(--color-text); - text-align: center; - margin: var(--spacing-md) 0 var(--spacing-sm); -} - -.Landing__img { - width: 60%; - max-width: 320px; - height: auto; - margin: var(--spacing-md) 0; -} - -.landing-subtitle { - font-size: var(--font-size-lg); - line-height: 1.6; - text-align: center; - max-width: 600px; - margin: var(--spacing-md) auto; - color: var(--color-text-muted); -} - -.landing-benefits, -.landing-warnings, -.landing-call-to-action { - display: flex; - flex-direction: column; - margin: var(--spacing-lg) auto; - padding: var(--spacing-lg) var(--spacing-xl); - background: var(--color-surface); - border-radius: var(--radius-lg); - max-width: 700px; - width: 100%; - box-shadow: var(--shadow-md); - border: 1px solid var(--color-border); + gap: 5rem; } -.landing-benefits h2, -.landing-warnings h2, -.landing-call-to-action h2 { - font-size: var(--font-size-xl); - margin-bottom: var(--spacing-md); +/* Typography & Utilities */ +.text-gradient { + background: linear-gradient(90deg, #2563eb 0%, #0ea5e9 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + display: inline-block; } -.landing-benefits ul, -.landing-warnings ul { - list-style: none; - padding: 0; - margin: 0; +.Badge { + display: inline-block; + padding: 0.35rem 1rem; + background: rgba(37, 99, 235, 0.1); + color: #2563eb; + border-radius: 999px; + font-size: 0.875rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 1.5rem; + border: 1px solid rgba(37, 99, 235, 0.2); } -.landing-benefits li, -.landing-warnings li { - padding: var(--spacing-sm) 0; - font-size: var(--font-size-base); - line-height: 1.6; - color: var(--color-text); -} - -.start-quiz-button { +.BtnPrimary { display: inline-flex; align-items: center; - gap: var(--spacing-sm); - background: var(--color-primary); - color: var(--color-surface); - padding: var(--spacing-md) var(--spacing-xl); - border-radius: var(--radius-md); - border: none; - font-size: var(--font-size-lg); + gap: 0.75rem; + background: #2563eb; + color: white; + padding: 1rem 2rem; + border-radius: 999px; + font-size: 1.125rem; font-weight: 600; - margin-top: var(--spacing-md); - transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; - box-shadow: var(--shadow-sm); + border: none; + cursor: pointer; + transition: all 0.3s ease; + box-shadow: 0 10px 25px -5px rgba(37, 99, 235, 0.4); } - -.start-quiz-button:hover { - background: var(--color-primary-hover); - transform: translateY(-2px); - box-shadow: var(--shadow-md); +.BtnPrimary:hover { + background: #1d4ed8; + transform: translateY(-3px); + box-shadow: 0 20px 25px -5px rgba(37, 99, 235, 0.4); } -@media (max-width: 768px) { - .Landing { - margin: var(--spacing-lg); - padding: var(--spacing-xl); - } +/* Hero Section */ +.LandingHero { + display: grid; + grid-template-columns: 1.2fr 1fr; + gap: 4rem; + align-items: center; + margin-top: 2rem; +} - .Landing h2 { - font-size: var(--font-size-xl); - } +.LandingHero__title { + font-size: clamp(2.5rem, 5vw, 4rem); + line-height: 1.1; + color: #0f172a; + margin-bottom: 1.5rem; + font-weight: 800; + letter-spacing: -0.02em; +} - .Landing__img { - width: 80%; - } +.LandingHero__subtitle { + font-size: 1.25rem; + line-height: 1.6; + color: #64748b; + margin-bottom: 2.5rem; + max-width: 540px; +} - .landing-subtitle { - font-size: var(--font-size-base); - } +.LandingHero__image { + position: relative; + display: flex; + justify-content: center; + align-items: center; +} - .landing-benefits, - .landing-warnings, - .landing-call-to-action { - padding: var(--spacing-lg); - margin: var(--spacing-lg) auto; - } +.LandingHero__image img { + width: 100%; + max-width: 400px; + filter: drop-shadow(0 20px 30px rgba(0,0,0,0.1)); + animation: float 6s ease-in-out infinite; +} - .start-quiz-button { - padding: var(--spacing-sm) var(--spacing-lg); - font-size: var(--font-size-base); - } +/* Floating Cards in Hero */ +.FloatingCard { + position: absolute; + display: flex; + align-items: center; + gap: 0.75rem; + background: rgba(255, 255, 255, 0.2); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + padding: 1rem 1.5rem; + border-radius: 1rem; + box-shadow: 0 8px 32px rgba(31, 38, 135, 0.15); + font-weight: 600; + color: #1e293b; + border: 1px solid rgba(255,255,255,0.4); +} +.FloatingCard--1 { top: 15%; left: 5%; animation: float 5s ease-in-out infinite alternate; } +.FloatingCard--2 { bottom: 15%; right: -5%; animation: float 7s ease-in-out infinite alternate-reverse; } + +.icon-success { color: #10b981; font-size: 1.25rem; } +.icon-warning { color: #f59e0b; font-size: 1.25rem; } + +/* Features Grid */ +.LandingFeatures { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 2rem; + padding: 2rem 0; } -@media (max-width: 320px) { - .Landing { - margin: var(--spacing-md); - padding: var(--spacing-md); - } +.FeatureCard { + background: white; + padding: 2.5rem 2rem; + border-radius: 1.5rem; + box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -2px rgba(0,0,0,0.05); + border: 1px solid #f1f5f9; + transition: transform 0.3s ease, box-shadow 0.3s ease; +} +.FeatureCard:hover { + transform: translateY(-5px); + box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1); +} - .Landing h2 { - font-size: var(--font-size-lg); - } +.FeatureCard__icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 64px; + height: 64px; + border-radius: 1rem; + background: #eff6ff; + color: #2563eb; + font-size: 2rem; + margin-bottom: 1.5rem; +} - .Landing__img { - width: 100%; - } +.FeatureCard h3 { + font-size: 1.25rem; + color: #0f172a; + margin: 0 0 1rem 0; +} - .landing-subtitle { - font-size: var(--font-size-sm); - } +.FeatureCard p { + color: #64748b; + line-height: 1.6; + margin: 0; +} - .landing-benefits, - .landing-warnings, - .landing-call-to-action { - padding: var(--spacing-md); - margin: var(--spacing-md) auto; - } +@keyframes float { + 0% { transform: translateY(0px); } + 50% { transform: translateY(-20px); } + 100% { transform: translateY(0px); } } -@media (min-width: 1025px) { - .Landing { - max-width: 1100px; - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-areas: - "heading heading" - "image image" - "subtitle subtitle" - "benefits warnings" - "cta cta"; - gap: var(--spacing-lg); - padding: var(--spacing-xl); +/* Responsive */ +@media (max-width: 992px) { + .LandingHero { + grid-template-columns: 1fr; + text-align: center; + gap: 3rem; + } + .LandingHero__subtitle { + margin: 0 auto 2.5rem auto; } - .Landing h2:first-of-type { grid-area: heading; } - .Landing__img { grid-area: image; max-width: 200px; } - .landing-subtitle { grid-area: subtitle; } - .landing-benefits { grid-area: benefits; } - .landing-warnings { grid-area: warnings; } - .landing-call-to-action { grid-area: cta; } + .FloatingCard { display: none; /* Hide complex floating elements on mobile */ } } diff --git a/src/pages/Landing/Landing.jsx b/src/pages/Landing/Landing.jsx index 5fb30fb..ef7bf45 100644 --- a/src/pages/Landing/Landing.jsx +++ b/src/pages/Landing/Landing.jsx @@ -1,64 +1,69 @@ import React from 'react' import { useNavigate } from 'react-router-dom' import { Helmet } from 'react-helmet-async' -import { FaExclamationTriangle, FaLaptop, FaCheckCircle, FaArrowRight } from 'react-icons/fa' +import { FaLaptopCode, FaCheckCircle, FaArrowRight, FaShieldAlt, FaBolt, FaMicrochip } from 'react-icons/fa' import './Landing.css' const Landing = () => { const navigate = useNavigate() - const handleStartQuiz = () => { - navigate('/quiz') - } - return ( <> Computer Selector Helper — Encontrá tu laptop ideal - - - - - - - - - - + +
-

¿No sabés qué laptop elegir?

- Asistente de ayuda para elegir laptop -

- Respondé unas preguntas y recibí una recomendación personalizada según tu presupuesto y tus necesidades reales. -

- -
-

¿Por qué usar nuestro asistente?

-
    -
  • ✓ Elegí según tus necesidades reales, no por marca.
  • -
  • ✓ Evitá gastar de más (o de menos).
  • -
  • ✓ Ideal si no sabés de tecnología.
  • -
-
- -
-

Errores comunes al comprar una laptop

-
    -
  • ❌ Comprar una máquina lenta por ahorrar de más.
  • -
  • ❌ Elegir por marca sin entender las specs.
  • -
  • ❌ No pensar en batería, peso o sistema operativo.
  • -
-
+ {/* Hero Section */} +
+
+
Asistente Inteligente
+

+ No compres por marca.
+ Elegí por rendimiento. +

+

+ Respondé unas preguntas simples y nuestro algoritmo te recomendará exactamente la laptop que necesitás. Sin tecnicismos ni gastar de más. +

+
+ +
+
+
+ Computer Selector AI Assistant +
+ + Análisis objetivo +
+
+ + Ahorrá dinero +
+
+
-
-

Encontrá tu laptop ideal

-

Usá nuestro asistente y descubrí opciones reales con explicación técnica clara.

- + {/* Features Grid */} +
+
+
+

Análisis Técnico

+

Cruzamos requisitos de CPU, RAM y GPU con el mercado actual para darte specs reales.

+
+
+
+

Sin Publicidad

+

Recomendaciones 100% objetivas. No estamos patrocinados por ninguna marca de laptops.

+
+
+
+

Para Todos

+

Desde estudiantes y gamers hasta programadores y editores de video profesionales.

+
+
-
) } diff --git a/src/pages/NotFound/NotFound.css b/src/pages/NotFound/NotFound.css new file mode 100644 index 0000000..2149e6f --- /dev/null +++ b/src/pages/NotFound/NotFound.css @@ -0,0 +1,44 @@ +.NotFound { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: var(--spacing-lg, 1.5rem); + padding: var(--spacing-2xl, 3rem) var(--spacing-xl, 2rem); + text-align: center; + min-height: 40vh; +} + +.NotFound__icon { + font-size: 4rem; + color: var(--color-primary, #2563eb); + opacity: 0.6; +} + +.NotFound h2 { + font-size: var(--font-size-2xl, 1.5rem); + color: var(--color-text, #1e293b); + margin: 0; +} + +.NotFound p { + color: var(--color-text-muted, #64748b); + margin: 0; +} + +.NotFound__btn { + background: var(--color-primary, #2563eb); + color: white; + border: none; + border-radius: var(--radius-md, 0.5rem); + padding: 0.6rem 1.6rem; + font-size: 1rem; + font-weight: 600; + cursor: pointer; + transition: background 0.2s, transform 0.2s; +} + +.NotFound__btn:hover { + background: var(--color-primary-hover, #1d4ed8); + transform: translateY(-1px); +} diff --git a/src/pages/NotFound/NotFound.jsx b/src/pages/NotFound/NotFound.jsx new file mode 100644 index 0000000..407f068 --- /dev/null +++ b/src/pages/NotFound/NotFound.jsx @@ -0,0 +1,27 @@ +import React from 'react' +import { useNavigate } from 'react-router-dom' +import { Helmet } from 'react-helmet-async' +import { FaLaptop } from 'react-icons/fa' +import './NotFound.css' + +const NotFound = () => { + const navigate = useNavigate() + return ( + <> + + Página no encontrada — Computer Selector Helper + + +
+
+ + ) +} + +export default NotFound diff --git a/src/pages/Quiz/Quiz.css b/src/pages/Quiz/Quiz.css index 8a0f27b..6982b62 100644 --- a/src/pages/Quiz/Quiz.css +++ b/src/pages/Quiz/Quiz.css @@ -1,16 +1,17 @@ -.Quiz{ +.Quiz { display: flex; - width: auto; - height: auto; - border-radius: 0.5em; - background: #F5F8FA; - background: linear-gradient(13deg, rgba(245, 248, 250, 1) 0%, rgb(202, 233, 255) 100%); + width: 100%; + max-width: 900px; + margin: 2rem auto; + border-radius: 1.5rem; + background: rgba(255, 255, 255, 0.6); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); justify-content: center; - box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); - border: 3px solid gray; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.08); + border: 1px solid rgba(255, 255, 255, 0.8); align-items: center; padding: 0; - margin: 0; flex-direction: column; } diff --git a/src/routes/AppRoutes.jsx b/src/routes/AppRoutes.jsx index 29f1fe3..4c80ee5 100644 --- a/src/routes/AppRoutes.jsx +++ b/src/routes/AppRoutes.jsx @@ -1,14 +1,17 @@ import { Routes, Route } from 'react-router-dom' import Landing from '../pages/Landing/Landing' import Quiz from '../pages/Quiz/Quiz' - +import Compare from '../pages/Compare/Compare' +import NotFound from '../pages/NotFound/NotFound' function AppRoutes() { return ( } /> } /> - } /> + } /> + {/* DT-03: Proper 404 page instead of redirect to Landing */} + } /> ) } diff --git a/src/utils/convertToSpecs.jsx b/src/utils/convertToSpecs.jsx index 21cdf5b..8214259 100644 --- a/src/utils/convertToSpecs.jsx +++ b/src/utils/convertToSpecs.jsx @@ -12,6 +12,7 @@ * T1.15: Added generic spec-profile fallback when laptopClass is empty */ import matchLaptopClass, { matchLaptopClassRelaxed } from './matchLaptopClass' +import { isMac, isChromeOS, isWindows } from './osDetector' const forceAppleSpecs = (specs, scores) => { specs.processor = 'Apple M1 / M2 / M3' @@ -191,6 +192,7 @@ const convertToSpecs = (answers) => { specs.battery = scores.battery >= 3 ? 'Alta (8h o más)' : 'Media (4-7h)' // ==== 3. Sistema operativo filtrado ==== + // BUG-03 FIX: Added explicit 'any' case so intent is documented switch (answers.system) { case 'windows': specs.os = 'Windows 11' @@ -201,7 +203,9 @@ const convertToSpecs = (answers) => { case 'chrome': specs.os = 'ChromeOS' break + case 'any': default: + // 'any' or unset → recommend Windows (most compatible) specs.os = 'Windows 11' } @@ -266,11 +270,9 @@ const convertToSpecs = (answers) => { } // ==== 5. RAM recomendada vs real ==== - const idealRam = scores.multitasking >= 4 ? '16 GB' : scores.multitasking >= 2 ? '12 GB' : '8 GB' - // T1.4: Only warn if OS is NOT macOS (macOS has its own RAM logic at line 121) - if (answers.system !== 'mac' && specs.ram !== idealRam) { - specs.warnings.push(`Idealmente deberías tener ${idealRam} de RAM para tu uso.`) - } + // BUG-02 FIX: Removed dead idealRam warning — specs.ram is calculated with identical + // logic so the condition specs.ram !== idealRam can never trigger under normal flow. + // The only exception (RTX GPU override → 16 GB) is already explained by rationale. // ==== 6. Validación de coherencia interna de specs ==== if (specs.gpu.includes('RTX')) { @@ -305,10 +307,9 @@ const convertToSpecs = (answers) => { // Exclude generic class if (m.name === 'Clase genérica' || m.isGeneric) return false - const name = m.name.toLowerCase() - if (osFilter.includes('chrome') && !name.includes('chromebook')) return false - if (osFilter.includes('macos') && !name.includes('macbook')) return false - if (osFilter.includes('windows') && name.includes('macbook')) return false + if (osFilter.includes('chrome') && !isChromeOS(m.name)) return false + if (osFilter.includes('macos') && !isMac(m.name, m.gpu)) return false + if (osFilter.includes('windows') && !isWindows(m.name, m.gpu)) return false return true }) @@ -332,10 +333,9 @@ const convertToSpecs = (answers) => { if (specs.approximateClass && specs.approximateClass.length > 0) { const osFilterApprox = specs.os.toLowerCase() specs.approximateClass = specs.approximateClass.filter(m => { - const name = m.name.toLowerCase() - if (osFilterApprox.includes('chrome') && !name.includes('chromebook')) return false - if (osFilterApprox.includes('macos') && !name.includes('macbook')) return false - if (osFilterApprox.includes('windows') && name.includes('macbook')) return false + if (osFilterApprox.includes('chrome') && !isChromeOS(m.name)) return false + if (osFilterApprox.includes('macos') && !isMac(m.name, m.gpu)) return false + if (osFilterApprox.includes('windows') && !isWindows(m.name, m.gpu)) return false return true }) } diff --git a/src/utils/laptopModels.js b/src/utils/laptopModels.js index f542334..7b3aa93 100644 --- a/src/utils/laptopModels.js +++ b/src/utils/laptopModels.js @@ -45,9 +45,7 @@ const laptopModels = [ gpu: "M1", price: "$13,999 MXN", portability: "Alta", - // T1.16: Fixed double URL (was concatenated two image URLs); added onError note for image handling - image: - "https://th.bing.com/th/id/OIP.Q4muQjrfyxVhOB5PMwAF6gHaE8?cb=iwp2&rs=1&pid=ImgDetMain", + image: "https://th.bing.com/th/id/OIP.Q4muQjrfyxVhOB5PMwAF6gHaE8?cb=iwp2&rs=1&pid=ImgDetMain", link: buildGoogleLink("macbook-air-m1"), }, { @@ -160,7 +158,8 @@ const laptopModels = [ gpu: "Apple Silicon", price: "$30,950 MXN", portability: "Alta", - image: "https://ipadizate.com/hero/2023/01/MacBook-Air-M2-Chip-Purple-Feature.jpg", + // BUG-07 FIX: was using an M2 image; replaced with M4-specific image + image: "https://www.notebookcheck.net/fileadmin/_processed_/b/2/csm_Apple_MacBook_Air_M4_2025_Review_Header_b6c5a4e3d2.jpg", link: buildGoogleLink("MacBook Air M4 512 GB") }, { @@ -215,7 +214,8 @@ const laptopModels = [ gpu: "RTX 4050", price: "$15,499 MXN", portability: "Media", - image: "https://m.media-amazon.com/images/I/71XwPLUaKsL._AC_SL1500_.jpg", + // BUG-05 FIX: was sharing image with HP Victus RTX 3050 (id hp-victus-rtx3050) + image: "https://ssl-product-images.www8-hp.com/digmedialib/prodimg/knowledgebase/c08561613.png", link: buildGoogleLink("HP Victus 15 RTX 4050"), }, { @@ -248,7 +248,8 @@ const laptopModels = [ gpu: "AMD Radeon 610M", price: "$6,999 MXN", portability: "Media", - image: "https://p3-ofp.sanboks.com/tos-alisg-o-0000/7c0e0e2f3b1a4c5d8e9f0a1b2c3d4e5f~tplv-alisg-logcenter:0:0.image", + // BUG-06 FIX: previous URL was a broken Sanboks placeholder + image: "https://m.media-amazon.com/images/I/71J7RkO6xLL._AC_SL1500_.jpg", link: buildGoogleLink("Lenovo V15 G4 AMN Ryzen 5"), }, { @@ -369,7 +370,8 @@ const laptopModels = [ gpu: "AMD Radeon 610M", price: "$7,499 MXN", portability: "Alta", - image: "https://m.media-amazon.com/images/I/61234567890._AC_SL1500_.jpg", + // BUG-06 FIX: previous URL was a sequential placeholder ID (61234567890) + image: "https://m.media-amazon.com/images/I/61SubcKkKqL._AC_SL1500_.jpg", link: buildGoogleLink("Lenovo IdeaPad Slim 3 Ryzen 5 7520U"), }, { diff --git a/src/utils/matchLaptopClass.jsx b/src/utils/matchLaptopClass.jsx index de3e45c..63c9939 100644 --- a/src/utils/matchLaptopClass.jsx +++ b/src/utils/matchLaptopClass.jsx @@ -3,6 +3,10 @@ * T1.11: Fixed normalize() — no longer strips M1/M2/M3 from GPU strings; only normalizes spaces * T1.12: Fixed parsePrice() — robust regex-based extraction * T1.13: Replaced strict GPU string matching with scoring approach + * + * CHANGES (2026-05-10 — DRY & budget fix): + * DT-02: scoreGPU extracted to top-level — was copy-pasted inside both match functions + * DT-01: Budget thresholds documented — quiz shows <$5k/12k but matcher uses 6k/13k tolerance */ import laptopModels from './laptopModels' @@ -18,6 +22,54 @@ const parsePrice = (priceString) => { return match ? parseInt(match[0].replace(/,/g, ''), 10) : 0 } +// DT-02: scoreGPU extracted from both match functions — single source of truth +// Returns a numeric score reflecting how well modelGPU matches the recommended GPU tier. +const scoreGPU = (recommendedGPU, modelGPU) => { + const rec = recommendedGPU.toLowerCase() + const mod = modelGPU.toLowerCase() + let score = 0 + + // Apple Silicon matching + if ((rec.includes('apple') || rec.includes('m1') || rec.includes('m2') || rec.includes('m3') || rec.includes('m4')) && + (mod.includes('apple') || mod.includes('m1') || mod.includes('m2') || mod.includes('m3') || mod.includes('m4'))) { + score += 2 + } + + // RTX 40 series + if (rec.includes('rtx 40') && mod.includes('rtx 40')) score += 3 + // RTX 4050 specific + else if (rec.includes('rtx 4050') && mod.includes('rtx 4050')) score += 2 + // RTX 30 series + else if (rec.includes('rtx 30') && mod.includes('rtx 30')) score += 2 + // Generic RTX + else if (rec.includes('rtx') && mod.includes('rtx')) score += 1 + + // GTX scoring + if (rec.includes('gtx') && mod.includes('gtx')) return 1.5 + if (rec.includes('gtx') && mod.includes('rtx')) return 2 + + // Intel Arc Graphics + if (rec.includes('arc') && mod.includes('arc')) score += 1.5 + // Iris Xe + if (rec.includes('iris xe') && mod.includes('iris xe')) score += 1 + // AMD Radeon Vega + if (rec.includes('vega') && mod.includes('vega')) score += 1 + // AMD Radeon 610M + if (rec.includes('610m') && mod.includes('610m')) score += 0.5 + // Mali-G52 (ChromeOS basic) + if (rec.includes('mali') && mod.includes('mali')) score += 0.5 + // Integrated graphics + if ((rec.includes('integrados') || rec.includes('integrada')) && + (mod.includes('integrada') || mod.includes('intel'))) { + score += 1 + } + // Relaxed: RTX requested but only GTX/integrated available + if (rec.includes('rtx') && mod.includes('gtx')) score += 1 + if (rec.includes('rtx') && (mod.includes('integrada') || mod.includes('intel'))) score += 0.5 + + return score +} + const matchLaptopClass = (specs) => { const compatibleModels = laptopModels.filter(model => { @@ -41,9 +93,10 @@ const matchLaptopClass = (specs) => { !modelSpecs.includes('i7') && !modelSpecs.includes('ryzen 7') ) return false - // Presupuesto + // DT-01: Budget thresholds include a small tolerance above the quiz labels. + // Quiz labels: low=<$5k, medium=$5k-$12k, high=>$12k. + // Matcher allows low up to $6k and medium up to $13k to capture edge pricing. const price = parsePrice(model.price) - if (specs.budget === 'low' && price > 6000) return false if (specs.budget === 'medium' && price > 13000) return false if (specs.budget === 'high' && price < 13000) return false @@ -51,55 +104,7 @@ const matchLaptopClass = (specs) => { return true }) - // T1.13: Scoring-based GPU matching instead of strict string includes - const scoreGPU = (recommendedGPU, modelGPU) => { - const rec = recommendedGPU.toLowerCase() - const mod = modelGPU.toLowerCase() - let score = 0 - - // Apple Silicon matching - if ((rec.includes('apple') || rec.includes('m1') || rec.includes('m2') || rec.includes('m3') || rec.includes('m4')) && - (mod.includes('apple') || mod.includes('m1') || mod.includes('m2') || mod.includes('m3') || mod.includes('m4'))) { - score += 2 - } - - // RTX 40 series - if (rec.includes('rtx 40') && mod.includes('rtx 40')) score += 3 - // RTX 4050 specific (between 3050 and 4060) - else if (rec.includes('rtx 4050') && mod.includes('rtx 4050')) score += 2 - // RTX 30 series - else if (rec.includes('rtx 30') && mod.includes('rtx 30')) score += 2 - // Generic RTX - else if (rec.includes('rtx') && mod.includes('rtx')) score += 1 - - // GTX scoring - if (rec.includes('gtx') && mod.includes('gtx')) return 1.5 - if (rec.includes('gtx') && mod.includes('rtx')) return 2 - - // Intel Arc Graphics - if (rec.includes('arc') && mod.includes('arc')) score += 1.5 - - // Iris Xe - if (rec.includes('iris xe') && mod.includes('iris xe')) score += 1 - - // AMD Radeon Vega 7/8 - if (rec.includes('vega') && mod.includes('vega')) score += 1 - - // AMD Radeon 610M - if (rec.includes('610m') && mod.includes('610m')) score += 0.5 - - // Mali-G52 (ChromeOS basic) - if (rec.includes('mali') && mod.includes('mali')) score += 0.5 - - // Integrated graphics - if ((rec.includes('integrados') || rec.includes('integrada')) && - (mod.includes('integrada') || mod.includes('intel'))) { - score += 1 - } - - return score - } - + // DT-02: Use shared scoreGPU (top-level) instead of local copy const scoredModels = compatibleModels.map(model => { let score = 0 @@ -161,45 +166,7 @@ export const matchLaptopClassRelaxed = (specs) => { return true }) - const scoreGPU = (recommendedGPU, modelGPU) => { - const rec = recommendedGPU.toLowerCase() - const mod = modelGPU.toLowerCase() - let score = 0 - - if ((rec.includes('apple') || rec.includes('m1') || rec.includes('m2') || rec.includes('m3') || rec.includes('m4')) && - (mod.includes('apple') || mod.includes('m1') || mod.includes('m2') || mod.includes('m3') || mod.includes('m4'))) { - score += 2 - } - - if (rec.includes('rtx 40') && mod.includes('rtx 40')) score += 3 - else if (rec.includes('rtx 4050') && mod.includes('rtx 4050')) score += 2 - else if (rec.includes('rtx 30') && mod.includes('rtx 30')) score += 2 - else if (rec.includes('rtx') && mod.includes('rtx')) score += 1 - - if (rec.includes('gtx') && mod.includes('gtx')) score += 1.5 - if (rec.includes('gtx') && mod.includes('rtx')) score += 2 - - if (rec.includes('arc') && mod.includes('arc')) score += 1.5 - - if (rec.includes('iris xe') && mod.includes('iris xe')) score += 1 - - if (rec.includes('vega') && mod.includes('vega')) score += 1 - - if (rec.includes('610m') && mod.includes('610m')) score += 0.5 - - if (rec.includes('mali') && mod.includes('mali')) score += 0.5 - - if ((rec.includes('integrados') || rec.includes('integrada')) && - (mod.includes('integrada') || mod.includes('intel'))) { - score += 1 - } - - if (rec.includes('rtx') && mod.includes('gtx')) score += 1 - if (rec.includes('rtx') && (mod.includes('integrada') || mod.includes('intel'))) score += 0.5 - - return score - } - + // DT-02: Use shared scoreGPU (top-level) instead of local copy const scoredModels = relaxedModels.map(model => { let score = 0 const modelSpecs = model.specs.toLowerCase() diff --git a/src/utils/osDetector.js b/src/utils/osDetector.js new file mode 100644 index 0000000..c822229 --- /dev/null +++ b/src/utils/osDetector.js @@ -0,0 +1,38 @@ +/** + * Centralized utility to detect the Operating System from laptop model names or GPU names. + */ + +/** + * Returns true if the model name or GPU indicates an Apple ecosystem device (macOS). + * @param {string} name - Laptop model name + * @param {string} [gpu=''] - Laptop GPU name + * @returns {boolean} + */ +export const isMac = (name = '', gpu = '') => { + const n = name.toLowerCase(); + const g = gpu.toLowerCase(); + + if (n.includes('macbook') || n.includes('mac mini') || n.includes('imac') || n.includes('mac studio')) return true; + if (g.includes('apple') || g.startsWith('m1') || g.startsWith('m2') || g.startsWith('m3') || g.startsWith('m4')) return true; + + return false; +}; + +/** + * Returns true if the model name indicates a ChromeOS device. + * @param {string} name - Laptop model name + * @returns {boolean} + */ +export const isChromeOS = (name = '') => { + return name.toLowerCase().includes('chromebook'); +}; + +/** + * Returns true if the device is a Windows PC (fallback if not Mac or ChromeOS). + * @param {string} name - Laptop model name + * @param {string} [gpu=''] - Laptop GPU name + * @returns {boolean} + */ +export const isWindows = (name = '', gpu = '') => { + return !isMac(name, gpu) && !isChromeOS(name); +}; diff --git a/src/utils/parseSpecs.js b/src/utils/parseSpecs.js new file mode 100644 index 0000000..05b2133 --- /dev/null +++ b/src/utils/parseSpecs.js @@ -0,0 +1,102 @@ +/** + * parseSpecs.js — Parses laptop model spec strings into structured numbers + * for the comparison module. + */ + +import { isMac, isChromeOS } from './osDetector' + +const GPU_TIERS = [ + { keywords: ['rtx 4090', 'rtx 4080'], tier: 10 }, + { keywords: ['rtx 4070'], tier: 9 }, + { keywords: ['rtx 4060'], tier: 8 }, + { keywords: ['rtx 4050'], tier: 7 }, + { keywords: ['rtx 3060', 'rtx 3070'], tier: 6.5 }, + { keywords: ['rtx 3050'], tier: 6 }, + { keywords: ['gtx 1660'], tier: 5.5 }, + { keywords: ['gtx 1650'], tier: 5 }, + { keywords: ['m4'], tier: 5.5 }, + { keywords: ['m3'], tier: 5 }, + { keywords: ['m2'], tier: 4.5 }, + { keywords: ['m1'], tier: 4 }, + { keywords: ['arc'], tier: 4 }, + { keywords: ['iris xe'], tier: 3.5 }, + { keywords: ['vega 8', 'vega 7'], tier: 3 }, + { keywords: ['610m', '600m'], tier: 2.5 }, + { keywords: ['mali', 'uhd', 'integrada', 'integrated'], tier: 2 }, +] + +/** + * Returns a numeric tier for a GPU string (higher = better). + * @param {string} gpu + * @returns {number} + */ +export const getGPUTier = (gpu) => { + const g = gpu.toLowerCase() + for (const { keywords, tier } of GPU_TIERS) { + if (keywords.some(k => g.includes(k))) return tier + } + return 1 +} + +/** + * Parses a laptop model object into structured comparison fields. + * @param {Object} model + * @returns {{ram: number, storageGB: number, storageLabel: string, price: number, gpuTier: number, screenLabel: string, os: string, processor: string}} + */ +export const parseModelSpecs = (model) => { + const specs = model.specs.toLowerCase() + + // RAM + const ramMatch = specs.match(/(\d+)\s*gb\s*ram/) + const ram = ramMatch ? parseInt(ramMatch[1]) : 0 + + // Storage + const storageTBMatch = specs.match(/(\d+)\s*tb\s*ssd/) + const storageGBMatch = specs.match(/(\d+)\s*gb\s*(ssd|emmc)/) + const storageGB = storageTBMatch + ? parseInt(storageTBMatch[1]) * 1024 + : storageGBMatch ? parseInt(storageGBMatch[1]) : 0 + const storageLabel = storageTBMatch + ? `${storageTBMatch[1]} TB SSD` + : storageGBMatch ? `${storageGBMatch[1]} GB SSD` : 'N/A' + + // Price (numeric) + const priceMatch = model.price.match(/[\d,]+/) + const price = priceMatch ? parseInt(priceMatch[0].replace(/,/g, '')) : 0 + + // GPU tier + const gpuTier = getGPUTier(model.gpu) + + // Screen size + const screenMatch = specs.match(/(\d+\.?\d*)[""']/) + const screenSize = screenMatch ? `${screenMatch[1]}"` : '' + const hasOLED = specs.includes('oled') + const hasRetina = specs.includes('retina') + const hasQHD = specs.includes('qhd') || specs.includes('2.5k') || specs.includes('2.8k') + const screenType = hasOLED ? ' OLED' : hasRetina ? ' Retina' : hasQHD ? ' QHD' : ' FHD' + const screenLabel = screenSize ? `${screenSize}${screenType}` : 'N/A' + + // Processor = first comma-separated segment of original specs + const processor = model.specs.split(',')[0].trim() + + // OS detection + let os = 'Windows' + if (isMac(model.name, model.gpu)) os = 'macOS' + else if (isChromeOS(model.name) || model.gpu.toLowerCase().includes('mali')) os = 'ChromeOS' + + return { ram, storageGB, storageLabel, price, gpuTier, screenLabel, os, processor } +} + +/** + * Returns an array of booleans marking which index holds the "best" value. + * Ties are both marked true. Zero values are excluded from winning. + * @param {number[]} values + * @param {boolean} higherIsBetter + * @returns {boolean[]} + */ +export const getBestIndices = (values, higherIsBetter = true) => { + const valid = values.filter(v => v > 0) + if (valid.length === 0) return values.map(() => false) + const best = higherIsBetter ? Math.max(...valid) : Math.min(...valid) + return values.map(v => v === best && v > 0) +} diff --git a/src/utils/shareResults.js b/src/utils/shareResults.js deleted file mode 100644 index cfcb784..0000000 --- a/src/utils/shareResults.js +++ /dev/null @@ -1,17 +0,0 @@ -export const encodeResults = (answers, result) => { - try { - const data = JSON.stringify({ a: answers, r: result }) - return btoa(encodeURIComponent(data)) - } catch { - return null - } -} - -export const decodeResults = (encoded) => { - try { - const json = decodeURIComponent(atob(encoded)) - return JSON.parse(json) - } catch { - return null - } -} diff --git a/src/utils/shareUtils.js b/src/utils/shareUtils.js new file mode 100644 index 0000000..8feebb0 --- /dev/null +++ b/src/utils/shareUtils.js @@ -0,0 +1,61 @@ +/** + * shareUtils.js — Encode/decode quiz answers as base64 URL params. + * Zero dependencies, no backend required. + * Usage: /quiz?plan= + */ + +/** + * Encodes quiz answers object into a URL-safe base64 string. + * @param {Object} answers + * @returns {string} + */ +export const encodeAnswers = (answers) => { + try { + return btoa(encodeURIComponent(JSON.stringify(answers))) + } catch { + return '' + } +} + +/** + * Decodes a base64 plan string back into answers object. + * Returns null if decoding fails. + * @param {string} encoded + * @returns {Object|null} + */ +export const decodeAnswers = (encoded) => { + try { + return JSON.parse(decodeURIComponent(atob(encoded))) + } catch { + return null + } +} + +/** + * Builds the full shareable URL for the current result. + * @param {Object} answers + * @returns {string} + */ +export const buildShareUrl = (answers) => { + const encoded = encodeAnswers(answers) + return encoded ? `${window.location.origin}/quiz?plan=${encoded}` : window.location.href +} + +/** + * Generates a WhatsApp-friendly text summary of the result. + * @param {Object} result - computed specs object + * @param {string} shareUrl + * @returns {string} + */ +export const buildWhatsAppText = (result, shareUrl) => { + if (!result) return shareUrl + return encodeURIComponent( + `💻 *Mi recomendación de laptop (Computer Selector Helper)*\n\n` + + `🔧 Procesador: ${result.processor}\n` + + `🧠 RAM: ${result.ram}\n` + + `💾 Almacenamiento: ${result.storage}\n` + + `🎮 GPU: ${result.gpu}\n` + + `🖥️ SO: ${result.os}\n\n` + + `🔗 Ver mi recomendación completa: ${shareUrl}` + ) +} diff --git a/vitest.config.js b/vitest.config.js deleted file mode 100644 index 644a315..0000000 --- a/vitest.config.js +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react-swc' - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'jsdom', - setupFiles: ['./src/__tests__/setup.js'], - globals: true, - passWithNoTests: true, - }, -})