diff --git a/.claude/commands/qa-canonical.md b/.claude/commands/qa-canonical.md new file mode 100644 index 00000000..5aaeee61 --- /dev/null +++ b/.claude/commands/qa-canonical.md @@ -0,0 +1,42 @@ +You are running a QA pass on keepsimple.io. + +Profile: **canonical** — the desktop half of a full pass. Source of truth for desktop findings. + +Scope: all sections × all locales × desktop. +Time budget: 90-120 min, 30-min hard cap per section. +Output: reports/YYYY-MM-DD.md (and rendered .html) +Diff target: previous canonical run. + +Note: canonical alone is incomplete. Pair with mobile-followup for a full-matrix pass. + +SETUP + +1. Read .claude/keepsimple-qa/SKILL.md in full. This is your playbook for method. +2. Read qa-config.yml for routes, locales, viewports, and any per-section `auth_required:` lists. +3. Read known-issues.md — do not refile anything listed there. +4. Read .claude/keepsimple-qa/PROFILES.md and confirm the canonical profile spec matches what's above. Treat the profile as authoritative for scope; treat the skill as authoritative for method. +5. Confirm Playwright MCP is connected before proceeding. If not, stop and report what's missing. + +EXECUTION + +Run the pass per the skill. Honor the profile's scope exactly. Routes listed under `auth_required:` in their section are recorded as `skipped — auth required (configured)` and never visited. If you must cut scope mid-run, follow the skill's "Run is incomplete" rule — explicit, loud, in the report header. + +OUTPUT + +Produce the report at reports/YYYY-MM-DD.md following the skill's report structure: + +- Metadata header +- Coverage report table +- Findings summary +- Diff vs prior canonical +- Routes covered +- Routes skipped with reason +- Fixed-pass checklist +- JSON finding blocks +- Out-of-scope observations + +Then render HTML: `node .claude/keepsimple-qa/render-report.js reports/YYYY-MM-DD.md`. + +Do NOT auto-update qa-runs/findings-register.md or any other tracking file. The user updates those manually after reviewing the report. + +Return a 5-8 line summary in chat when done: profile, route coverage %, finding counts by severity, persistent vs new breakdown, run status. diff --git a/.claude/commands/qa-deploy-check.md b/.claude/commands/qa-deploy-check.md new file mode 100644 index 00000000..5a5ce81e --- /dev/null +++ b/.claude/commands/qa-deploy-check.md @@ -0,0 +1,53 @@ +You are running a **post-deploy regression check** on keepsimple.io. This is the most token-efficient profile — it only audits routes that could have broken (changed since last run) or were already broken (open findings from the prior run). + +Profile: **deploy-check** — surgical regression pass. + +Scope: + +- **Always include:** every route with an open finding in the most recent canonical / smoke / mobile-followup / full-matrix report. +- **Always include:** every route whose fingerprint differs from the prior batch (i.e. `changed` or `new` per `batch-fingerprint`). +- **Skip:** routes that are both `unchanged` AND have zero open prior findings. + +Time budget: 5 min per route, 30 min hard cap total. Designed to run after every deploy. +Output: reports/YYYY-MM-DD-deploy-check.md (and rendered .html) +Diff target: most recent canonical (or full-matrix) run. + +SETUP + +1. Read .claude/keepsimple-qa/SKILL.md in full. +2. Read qa-config.yml for routes, locales, viewports, and any per-section `auth_required:` lists. +3. Read known-issues.md — do not refile anything listed there. +4. Read .claude/keepsimple-qa/PROFILES.md and confirm the deploy-check profile spec matches what's above. +5. Confirm Playwright MCP is connected before proceeding. If not, stop and report what's missing. + +DECIDE THE SCOPE + +1. Run `node .claude/keepsimple-qa/helper.mjs batch-fingerprint keepsimple --save`. This reads every route from qa-config.yml, fetches each, computes a fingerprint, and compares against the prior batch stored in `qa-runs/state/fingerprints.json`. Output is a JSON array with per-route verdicts: `unchanged | changed | new | errored`. +2. Read the most recent prior report under `reports/`. Extract every finding whose status is open (`persistent`, `persistent (unchanged route)`, `new`, `confirmed`, `not seen this run`). Note their routes. +3. Build the **deploy-check route set** = (routes with `verdict: changed | new` from step 1) ∪ (routes with open prior findings from step 2). +4. Subtract any routes listed under `auth_required:` in their section — those are configured-skip. +5. If the resulting set is empty, write a 5-line "All clear — nothing changed and no open findings" report and exit. This is the happy path post-deploy. + +EXECUTION + +For every route in the deploy-check route set, run the per-route checklist from the skill (snapshot, console+network, click primary elements, fill any forms without submit, scroll, resize) plus the must-work fixed-pass items for any section whose hub route is in the set. Cap at 5 min per route; if you hit the cap mid-route, mark it `partial` and move on. + +For routes NOT in the deploy-check set (unchanged + no open findings), include them in the coverage table with status `unchanged-skip` and `Source: `. + +OUTPUT + +Produce the report at reports/YYYY-MM-DD-deploy-check.md following the skill's report structure: + +- Metadata header (build IDs, change summary from batch-fingerprint) +- **Deploy verdict** at the top: `pass | regressions found | inconclusive` based on whether any new high/critical findings appeared or any prior open findings persisted on changed routes +- Coverage table (every route from qa-config.yml, with per-route status: deep-audited / unchanged-skip / configured-skip) +- Findings summary +- Diff vs prior canonical (status of each prior finding: refuted / persistent / changed) +- JSON finding blocks +- Out-of-scope observations + +Then render HTML: `node .claude/keepsimple-qa/render-report.js reports/YYYY-MM-DD-deploy-check.md`. + +Do NOT auto-update qa-runs/findings-register.md or any other tracking file. The user updates those manually after reviewing the report. + +Return a 5-7 line summary in chat: deploy verdict, route counts (re-audited / skipped-unchanged / skipped-auth-required), prior-findings status (X resolved / Y persistent / Z new), run status. diff --git a/.claude/commands/qa-full-matrix.md b/.claude/commands/qa-full-matrix.md new file mode 100644 index 00000000..cad19737 --- /dev/null +++ b/.claude/commands/qa-full-matrix.md @@ -0,0 +1,40 @@ +You are running a QA pass on keepsimple.io. + +Profile: **full-matrix** — single pass covering everything: every section, every locale, every viewport. + +Scope: all sections × all locales × desktop+mobile. +Time budget: 3-4 hours. May need to be split across multiple Claude Code sessions; if so, sequence them and produce sub-reports per session, then a consolidated final report. +Output: reports/YYYY-MM-DD-full-matrix.md (or reports/YYYY-MM-DD-full-matrix-part-N.md for split runs) (and rendered .html) +Diff target: previous full-matrix run, or most recent canonical+mobile-followup pair. + +SETUP + +1. Read .claude/keepsimple-qa/SKILL.md in full. This is your playbook for method. +2. Read qa-config.yml for routes, locales, viewports, and any per-section `auth_required:` lists. +3. Read known-issues.md — do not refile anything listed there. +4. Read .claude/keepsimple-qa/PROFILES.md and confirm the full-matrix profile spec matches what's above. Treat the profile as authoritative for scope; treat the skill as authoritative for method. +5. Confirm Playwright MCP is connected before proceeding. If not, stop and report what's missing. + +EXECUTION + +Run the pass per the skill. Honor the profile's scope exactly. Routes listed under `auth_required:` in their section are recorded as `skipped — auth required (configured)` and never visited. If the run needs to be split across sessions due to time/context limits, produce part-N reports and call out clearly that a final consolidated report is needed. If you must cut scope mid-run, follow the skill's "Run is incomplete" rule — explicit, loud, in the report header. + +OUTPUT + +Produce the report at reports/YYYY-MM-DD-full-matrix.md (or part-N variant) following the skill's report structure: + +- Metadata header +- Coverage report table +- Findings summary +- Diff vs prior full-matrix or canonical+mobile-followup pair +- Routes covered +- Routes skipped with reason +- Fixed-pass checklist +- JSON finding blocks +- Out-of-scope observations + +Then render HTML: `node .claude/keepsimple-qa/render-report.js reports/YYYY-MM-DD-full-matrix.md` (or the part-N variant). + +Do NOT auto-update qa-runs/findings-register.md or any other tracking file. The user updates those manually after reviewing the report. + +Return a 5-8 line summary in chat when done: profile, route coverage %, finding counts by severity, persistent vs new breakdown, run status. diff --git a/.claude/commands/qa-init.md b/.claude/commands/qa-init.md new file mode 100644 index 00000000..dde88a86 --- /dev/null +++ b/.claude/commands/qa-init.md @@ -0,0 +1,136 @@ +--- +description: Configure (or re-configure) the QA agent for keepsimple.io. Walks through site URL, locales, routes, viewports, and writes qa-config.yml. +--- + +You are the QA agent's setup wizard. Your job is to interactively configure the QA pass by writing or updating `qa-config.yml` at the repo root. + +Do NOT start any QA pass during this wizard. Your only output is the config file (and a friendly recap at the end). + +## Pre-check + +If `qa-config.yml` already exists, ask: "qa-config.yml already exists. Back it up to `qa-config.yml.bak` and rewrite, edit in place, or cancel?" Do NOT silently overwrite. + +If the user picks "edit in place," surface the existing values as defaults at each step so they can keep what's there with a one-word reply. + +## Steps + +Walk the user through these questions a few at a time. Don't dump the whole list. Ask, wait for the answer, ask the next. + +### 1. Site URL + +Ask: "Production site URL? (default: https://keepsimple.io)" + +Then ask: "Staging URL too? (or 'skip')" + +### 2. Locales + +Ask: "Locales to test? (default: en, ru, hy)" + +If non-default, ask: "How are locales URL-prefixed?" + +- **A) default-unprefixed** — en routes are `/uxcore`, ru routes are `/ru/uxcore` _(current default for keepsimple)_ +- **B) all-prefixed** — every route has a locale segment +- **C) no-prefix** — single-locale, no segments + +### 3. Route discovery + +Ask: "How should we discover routes?" + +- **A) Pull from `/sitemap.xml`** +- **B) Crawl from the homepage** (one click deep from `/`) +- **C) Keep the routes in the existing qa-config.yml** _(default if you're re-running)_ +- **D) I'll paste the routes manually** + +If A/B: fetch via Playwright MCP, group discovered URLs by inferred section (path's first segment), present them back, ask: "Confirm these sections + routes? (yes / I want to edit)" + +If D: prompt for routes one section at a time: + +- "Section name (e.g. 'uxcore', 'site'):" +- "Routes for that section, one per line, leading slash, blank line to finish:" +- "Add another section? (yes/no)" + +### 4. Per-section options + +For each section, ask: + +- "Mark this section as `default: true` for smoke runs? (only one section gets this)" +- "Any `primary_interaction:` worth declaring? (e.g. 'open the bias modal on a card', or 'skip')" +- "Any routes in this section that require auth? (comma-separated paths, or 'none')" + +### 5. Viewports + +Ask: "Viewports? (default: desktop 1920×1080, mobile 390×844)" + +### 6. Timezone + +Ask: "Timezone for report timestamps? (default: Asia/Yerevan)" + +### 7. Design system reference + +Ask: "Path to design-system reference doc? (default: uxcore-visual-design-system.md, or 'skip')" + +### 8. Confirm and write + +Show the user the proposed `qa-config.yml` content as a code block. Ask: "Write this to qa-config.yml? (yes/edit)" + +Use this template (fill in the captured values): + +```yaml +# QA agent configuration — read by .claude/skills/keepsimple-qa/SKILL.md +# Generated by /qa-init on YYYY-MM-DD. + +environment: + production: + staging: + +locales: + - + # ... + +locale_url_strategy: + +viewports: + desktop: { width: 1920, height: 1080 } + mobile: { width: 390, height: 844 } + +report_timezone: + +design_system: + +sections: + : + # default: true # uncomment on exactly one section + routes: + - / + - / + # auth_required: + # - / + # primary_interaction: "..." + # ... repeat for each section +``` + +### 9. Recap + +Print: + +``` +✓ qa-config.yml written ({N} sections, {M} locales, {V} viewports) +✓ Default smoke section:
+ +Next steps: + /qa-smoke — quick render check + /qa-locale-smoke — locale routing only + /qa-canonical — desktop pass across all locales + /qa-mobile-followup — mobile pass to pair with canonical + /qa-full-matrix — everything in one report + /qa-deploy-check — surgical post-deploy regression + /qa-retest F#,F# — verify specific finding IDs after a fix +``` + +## Constraints + +- Do NOT begin a QA pass during this wizard. The user must invoke a profile separately. +- Do NOT auto-discover routes by visiting EVERY page on the site — keep discovery to sitemap or a single homepage crawl. +- Do NOT silently overwrite `qa-config.yml`. If one exists, ask first. +- Do NOT install Playwright MCP for the user. If it's not connected, just tell them how to install it (`claude mcp add playwright npx -- @playwright/mcp@latest`) and stop. +- Keep the conversation tight. Two or three questions per turn maximum. diff --git a/.claude/commands/qa-locale-smoke.md b/.claude/commands/qa-locale-smoke.md new file mode 100644 index 00000000..18289953 --- /dev/null +++ b/.claude/commands/qa-locale-smoke.md @@ -0,0 +1,40 @@ +You are running a QA pass on keepsimple.io. + +Profile: **locale-smoke** — confirm locale-prefix routing works across all configured locales. + +Scope: one section × all locales × desktop. Default section is the one marked `default: true` in `qa-config.yml` (or the first section if none is marked). +Time budget: 25 min hard cap. +Output: reports/YYYY-MM-DD-locale-smoke.md (and rendered .html) +Diff target: most recent canonical run. + +SETUP + +1. Read .claude/keepsimple-qa/SKILL.md in full. This is your playbook for method. +2. Read qa-config.yml for routes, locales, viewports, `locale_url_strategy:`, and any per-section `auth_required:` lists. +3. Read known-issues.md — do not refile anything listed there. +4. Read .claude/keepsimple-qa/PROFILES.md and confirm the locale-smoke profile spec matches what's above. Treat the profile as authoritative for scope; treat the skill as authoritative for method. +5. Confirm Playwright MCP is connected before proceeding. If not, stop and report what's missing. + +EXECUTION + +Run the pass per the skill. Honor the profile's scope exactly. Routes listed under `auth_required:` in their section are recorded as `skipped — auth required (configured)` and never visited. If you must cut scope mid-run, follow the skill's "Run is incomplete" rule — explicit, loud, in the report header. + +OUTPUT + +Produce the report at reports/YYYY-MM-DD-locale-smoke.md following the skill's report structure: + +- Metadata header +- Coverage report table +- Findings summary +- Diff vs prior canonical +- Routes covered +- Routes skipped with reason +- Fixed-pass checklist +- JSON finding blocks +- Out-of-scope observations + +Then render HTML: `node .claude/keepsimple-qa/render-report.js reports/YYYY-MM-DD-locale-smoke.md`. + +Do NOT auto-update qa-runs/findings-register.md or any other tracking file. The user updates those manually after reviewing the report. + +Return a 5-8 line summary in chat when done: profile, route coverage %, finding counts by severity, persistent vs new breakdown, run status. diff --git a/.claude/commands/qa-mobile-followup.md b/.claude/commands/qa-mobile-followup.md new file mode 100644 index 00000000..8eb08315 --- /dev/null +++ b/.claude/commands/qa-mobile-followup.md @@ -0,0 +1,42 @@ +You are running a QA pass on keepsimple.io. + +Profile: **mobile-followup** — the mobile half of a full pass. Pairs with canonical to close the full matrix. + +Scope: all sections × all locales × mobile. +Time budget: 90 min, 30-min hard cap per section. +Output: reports/YYYY-MM-DD-mobile.md (and rendered .html) +Diff target: paired canonical run from the same day or build. + +Note: mobile-followup alone is incomplete. Pair with canonical for a full-matrix pass. + +SETUP + +1. Read .claude/keepsimple-qa/SKILL.md in full. This is your playbook for method. +2. Read qa-config.yml for routes, locales, viewports, and any per-section `auth_required:` lists. +3. Read known-issues.md — do not refile anything listed there. +4. Read .claude/keepsimple-qa/PROFILES.md and confirm the mobile-followup profile spec matches what's above. Treat the profile as authoritative for scope; treat the skill as authoritative for method. +5. Confirm Playwright MCP is connected before proceeding. If not, stop and report what's missing. + +EXECUTION + +Run the pass per the skill. Honor the profile's scope exactly. Routes listed under `auth_required:` in their section are recorded as `skipped — auth required (configured)` and never visited. If you must cut scope mid-run, follow the skill's "Run is incomplete" rule — explicit, loud, in the report header. + +OUTPUT + +Produce the report at reports/YYYY-MM-DD-mobile.md following the skill's report structure: + +- Metadata header +- Coverage report table +- Findings summary +- Diff vs paired canonical run +- Routes covered +- Routes skipped with reason +- Fixed-pass checklist +- JSON finding blocks +- Out-of-scope observations + +Then render HTML: `node .claude/keepsimple-qa/render-report.js reports/YYYY-MM-DD-mobile.md`. + +Do NOT auto-update qa-runs/findings-register.md or any other tracking file. The user updates those manually after reviewing the report. + +Return a 5-8 line summary in chat when done: profile, route coverage %, finding counts by severity, persistent vs new breakdown, run status. diff --git a/.claude/commands/qa-retest.md b/.claude/commands/qa-retest.md new file mode 100644 index 00000000..c45dbf71 --- /dev/null +++ b/.claude/commands/qa-retest.md @@ -0,0 +1,48 @@ +You are running a targeted retest on keepsimple.io. + +Profile: **retest** — verify specific findings by ID after a deploy or local fix. + +Scope: only the routes referenced by the named finding IDs (provided by the user in the next message). +Time budget: 5 min per finding ID, 30 min cap. +Output: reports/YYYY-MM-DD-retest-{ids}.md (and rendered .html) +Diff target: report where each finding was last confirmed. + +Before proceeding, ask the user which finding IDs to retest (e.g. "F#7, F#11"). Do not begin browser work until the IDs are confirmed. + +SETUP + +1. Read .claude/keepsimple-qa/SKILL.md in full. This is your playbook for method. +2. Read qa-config.yml for routes, locales, viewports, and any per-section `auth_required:` lists. +3. Read known-issues.md — do not refile anything listed there. +4. Read .claude/keepsimple-qa/PROFILES.md and qa-runs/findings-register.md — the register tells you where each finding was last confirmed. +5. Confirm Playwright MCP is connected before proceeding. If not, stop and report what's missing. + +RETEST METHOD + +For each finding ID provided: + +1. Read the prior report where the finding was last confirmed to understand original reproduction steps, affected routes, and evidence. +2. Reproduce the verification steps on the current production build. +3. Classify the result as one of: + - confirmed (still reproduces, same root cause) + - refuted (no longer reproduces, fix appears to have shipped) + - persistent (reproduces with same signature but you suspect environmental rather than code factors) + - changed (reproduces but with a different signature — note the drift) + +Visit ONLY the routes referenced by the finding IDs. Do not perform broader exploratory testing. This is a focused verification pass, not a coverage pass. If a finding's route is in an `auth_required:` list, mark its retest as `skipped — auth required (configured)` and move on. + +OUTPUT + +Produce the report at reports/YYYY-MM-DD-retest-{ids}.md (replace {ids} with IDs separated by hyphens, e.g. "F5-F7-F11"). The retest report follows a simplified structure: + +- Metadata header (date, build ID, scope as "retest of {ids}") +- Per-finding verification table: ID, last-confirmed report, current status, brief evidence +- Routes touched +- JSON finding blocks ONLY for findings whose status changed (refuted, persistent-with-new-info, changed) +- Build ID note (current vs the build the finding was last confirmed against) + +Then render HTML: `node .claude/keepsimple-qa/render-report.js reports/YYYY-MM-DD-retest-{ids}.md`. + +Do NOT auto-update qa-runs/findings-register.md or any other tracking file. The user updates those manually after reviewing the report. + +Return a 3-5 line summary in chat when done: finding IDs retested, status counts (X confirmed / Y refuted / Z changed), build ID delta if any, anything unexpected. diff --git a/.claude/commands/qa-smoke.md b/.claude/commands/qa-smoke.md new file mode 100644 index 00000000..d0162cbc --- /dev/null +++ b/.claude/commands/qa-smoke.md @@ -0,0 +1,40 @@ +You are running a QA pass on keepsimple.io. + +Profile: **smoke** — fastest possible signal, does the site render at all? + +Scope: one section × default locale × desktop. Default section is the one marked `default: true` in `qa-config.yml` (or the first section if none is marked). Routes from `qa-config.yml`. +Time budget: 15 min hard cap. +Output: reports/YYYY-MM-DD-smoke.md (and rendered .html) +Diff target: most recent canonical run, if any. + +SETUP + +1. Read .claude/keepsimple-qa/SKILL.md in full. This is your playbook for method. +2. Read qa-config.yml for routes, locales, viewports, and any per-section `auth_required:` lists. +3. Read known-issues.md — do not refile anything listed there. +4. Read .claude/keepsimple-qa/PROFILES.md and confirm the smoke profile spec matches what's above. Treat the profile as authoritative for scope; treat the skill as authoritative for method. +5. Confirm Playwright MCP is connected before proceeding. If not, stop and report what's missing. + +EXECUTION + +Run the pass per the skill. Honor the profile's scope exactly. Routes listed under `auth_required:` in their section are recorded as `skipped — auth required (configured)` and never visited. If you must cut scope mid-run, follow the skill's "Run is incomplete" rule — explicit, loud, in the report header. + +OUTPUT + +Produce the report at reports/YYYY-MM-DD-smoke.md following the skill's report structure: + +- Metadata header +- Coverage report table (auth_required routes appear as `skipped — auth required (configured)`) +- Findings summary +- Diff vs prior canonical +- Routes covered +- Routes skipped with reason +- Fixed-pass checklist +- JSON finding blocks +- Out-of-scope observations + +Then render HTML: `node .claude/keepsimple-qa/render-report.js reports/YYYY-MM-DD-smoke.md`. + +Do NOT auto-update qa-runs/findings-register.md or any other tracking file. The user updates those manually after reviewing the report. + +Return a 5-8 line summary in chat when done: profile, route coverage %, finding counts by severity, persistent vs new breakdown, run status. diff --git a/.claude/keepsimple-qa/PROFILES.md b/.claude/keepsimple-qa/PROFILES.md new file mode 100644 index 00000000..bdbca0c5 --- /dev/null +++ b/.claude/keepsimple-qa/PROFILES.md @@ -0,0 +1,70 @@ +# QA Run Profiles + +Each profile defines scope for a single QA pass. The skill (`SKILL.md`) defines method; profiles define scope. Always run profiles in a fresh Claude Code session for the cleanest context. + +The defaults below are starting points — `qa-config.yml` overrides everything. If your project has only one locale and one viewport, the locale-smoke and mobile-followup profiles are no-ops; just run smoke / canonical / full-matrix. + +--- + +## smoke + +**Purpose:** fastest possible signal — does the site render at all? +**Scope:** one section × default locale × desktop. The default section is the homepage `/` plus whatever section is marked `default: true` in `qa-config.yml`. If none is marked, use the first section. +**Time budget:** 15 min hard cap. +**Output:** `reports/YYYY-MM-DD-smoke.md` (+ rendered `.html`). +**Diff target:** most recent canonical run, if any. +**When to use:** post-deploy verification; confirming the site isn't catastrophically broken before a longer run. + +## locale-smoke + +**Purpose:** confirm locale-prefix routing works across all configured locales. +**Scope:** one section × all locales × desktop. +**Default section:** the one marked `default: true`, or the first section. +**Time budget:** 25 min hard cap. +**Output:** `reports/YYYY-MM-DD-locale-smoke.md` (+ `.html`). +**Diff target:** most recent canonical run. +**When to use:** after i18n-related changes, or to validate locale infrastructure before a multi-locale follow-up run. Skip this profile if the project is single-locale. + +## canonical + +**Purpose:** the desktop half of a full pass. Source of truth for desktop findings. +**Scope:** all sections × all locales × desktop. +**Time budget:** 90–120 min, 30-min hard cap per section. +**Output:** `reports/YYYY-MM-DD.md` (+ `.html`). +**Diff target:** previous canonical run. +**When to use:** against a fresh build; primary baseline for findings tracking. Pair with mobile-followup for a complete matrix pass. + +## mobile-followup + +**Purpose:** the mobile half of a full pass. Pairs with canonical to close the full matrix (locales × 2 viewports = 2N combinations). +**Scope:** all sections × all locales × mobile. +**Time budget:** 90 min, 30-min hard cap per section. +**Output:** `reports/YYYY-MM-DD-mobile.md` (+ `.html`). +**Diff target:** paired canonical run from the same day or build. +**When to use:** paired with each canonical run. Canonical alone is incomplete — mobile-followup makes the pass full-matrix. + +## full-matrix + +**Purpose:** a single pass covering everything — every section, every locale, every viewport. Use when you want one report rather than a canonical + mobile-followup pair. +**Scope:** all sections × all locales × desktop + mobile. +**Time budget:** 3–4 hours. May need to be split across multiple Claude Code sessions; if so, sequence them and produce sub-reports per session, then a consolidated final report. +**Output:** `reports/YYYY-MM-DD-full-matrix.md` (or `reports/YYYY-MM-DD-full-matrix-part-N.md` for split runs) (+ `.html`). +**Diff target:** previous full-matrix run, or most recent canonical+mobile-followup pair. +**When to use:** major releases; quarterly baselines; when you want the cleanest single artifact rather than a pair. + +## retest + +**Purpose:** verify specific findings by ID after a deploy or local fix. +**Scope:** only the routes referenced by the named finding IDs (provided by the user). +**Time budget:** 5 min per finding ID, 30 min cap. +**Output:** `reports/YYYY-MM-DD-retest-{ids}.md` (+ `.html`). +**Diff target:** report where each finding was last confirmed. +**When to use:** after deploys, to confirm fixes shipped or regressions persist. + +--- + +## Profile vs scope + +The profile is fixed — `smoke` always means "fastest signal," `canonical` always means "all sections × all locales × desktop." But the _content_ of those scopes (which sections, which locales) comes from `qa-config.yml`. So a single project's `/qa-canonical` covers a different surface area than another project's `/qa-canonical`, and that's the point. + +If `qa-config.yml` doesn't exist, the profile commands will tell the user to run `/qa-init` first. diff --git a/.claude/keepsimple-qa/SKILL.md b/.claude/keepsimple-qa/SKILL.md new file mode 100644 index 00000000..28305c26 --- /dev/null +++ b/.claude/keepsimple-qa/SKILL.md @@ -0,0 +1,181 @@ +--- +name: keepsimple-qa +description: Use when the user asks to run a QA pass, test the site, or check production for regressions on keepsimple.io. Drives a real browser via the Playwright MCP server, visits each route in the test plan, interacts with key features, and produces a structured findings report (md + html). Do NOT use for writing Playwright spec files — that's a different task. +--- + +# QA Agent — Method Playbook + +You are running a manual QA pass on a production website. You drive a real Chromium browser via the Playwright MCP server. Your output is a structured findings report. + +The **profile** that invoked you (a slash command from `.claude/commands/qa-*.md`) defines the **scope**: which routes, locales, and viewports. This skill defines the **method**: how to run any pass. + +If `qa-config.yml` is missing, stop and tell the user to run `/qa-init` first. Do not invent routes. + +## How to run + +1. Read `qa-config.yml` for the route list, locales, viewports, and target environment (production / staging). +2. Read `known-issues.md` if it exists. Anything listed there is NOT a finding — do not report it. +3. Read the file referenced by `qa-config.yml`'s optional `design_system:` field, if set. Use it to spot design-system violations. +4. Run the **fixed pass** first (the must-work flows below), then the **exploratory pass**. +5. Output the findings report at the path defined by your invoking slash command (or by `PROFILES.md` if invoked manually). Filename patterns: + - `reports/YYYY-MM-DD.md` for canonical + - `reports/YYYY-MM-DD-smoke.md` for smoke + - `reports/YYYY-MM-DD-locale-smoke.md` for locale-smoke + - `reports/YYYY-MM-DD-mobile.md` for mobile-followup + - `reports/YYYY-MM-DD-full-matrix.md` for full-matrix + - `reports/YYYY-MM-DD-retest-{ids}.md` for retest (e.g. `retest-F5-F7`) + Use the timezone in `qa-config.yml`'s `report_timezone:` field if set, otherwise UTC. +6. After writing the markdown, render an HTML version: `node .claude/keepsimple-qa/render-report.js reports/.md`. The HTML lives next to the markdown and is what humans read. + +This skill is loaded by an activation prompt — typically a slash command from `.claude/commands/qa-*.md` (one per profile). The slash command tells you which profile to run; this skill tells you how to run any profile. **Profile = scope** (which routes, locales, viewports). **Skill = method** (the pre-flight, fixed pass, exploratory pass, checklists, severity rubric). + +## Pre-flight (run before any pass) + +1. Confirm Playwright MCP is connected. If not, stop and report what's missing. +2. Navigate to the environment URL (production or staging) once. +3. If navigation fails (MCP error, DNS, 5xx), STOP. Write a single critical finding describing the failure, with category `broken-flow`. Do not proceed to the fixed or exploratory pass. +4. Dismiss any blocking UI listed in `known-issues.md` (e.g. cookie consent banners, promo modals, GDPR overlays). +5. Confirm the page title and `` attribute match the expected locale. +6. Capture the build ID(s) — for Next.js sites this is `window.__NEXT_DATA__.buildId`; for other frameworks it might be a meta tag, a ` + + +`; +} + +function stripHeader(md) { + const lines = md.split('\n'); + let i = 0; + while (i < lines.length && !lines[i].startsWith('# ')) i++; + if (i < lines.length) i++; + while (i < lines.length) { + const line = lines[i]; + if (line.trim() === '') { + i++; + continue; + } + if (/^-\s+\*\*[^*]+:\*\*/.test(line)) { + i++; + continue; + } + break; + } + return lines.slice(i).join('\n'); +} + +function main() { + const inputPath = process.argv[2]; + if (!inputPath) { + console.error('Usage: node render-report.js '); + process.exit(1); + } + const absPath = path.resolve(inputPath); + if (!fs.existsSync(absPath)) { + console.error(`File not found: ${absPath}`); + process.exit(1); + } + + const md = fs.readFileSync(absPath, 'utf8'); + const meta = extractHeaderMeta(md); + const { findings, placeholderMd } = extractFindings(md); + const trimmedMd = stripHeader(placeholderMd); + + const coverage = extractCoverageStats(md); + const diff = extractDiffCounts(md); + const incomplete = isRunIncomplete(md); + + marked.setOptions({ + gfm: true, + breaks: false, + headerIds: false, + mangle: false, + }); + let bodyHtml = marked.parse(trimmedMd); + bodyHtml = postProcessHtml(bodyHtml); + + bodyHtml = bodyHtml.replace(//g, (_match, idx) => { + const f = findings[Number(idx)]; + return f ? renderFindingCard(f) : ''; + }); + + bodyHtml = bodyHtml.replace( + /

\s*(

{ + acc[s] = findings.filter( + f => (f.severity || '').toLowerCase() === s, + ).length; + return acc; + }, {}); + + console.log(`Rendered: ${outPath}`); + console.log( + `Findings parsed: ${findings.length} (${SEVERITY_ORDER.map(s => `${s}:${counts[s]}`).join(', ')})`, + ); +} + +main(); diff --git a/.claude/skills/keepsimple/SKILL.md b/.claude/skills/keepsimple/SKILL.md index c71c4a6a..7e1df3f7 100644 --- a/.claude/skills/keepsimple/SKILL.md +++ b/.claude/skills/keepsimple/SKILL.md @@ -106,7 +106,7 @@ Use path aliases for cross-folder imports. Relative imports only within the same - Tailwind, styled-components, CSS-in-JS - Named exports in `index.ts` barrels - State management libraries -- Testing frameworks beyond Cypress +- Testing frameworks beyond Playwright - Import global CSS outside `_app.tsx` - Invent design values — read `keepsimple-style.md` diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..737a5075 --- /dev/null +++ b/.env.example @@ -0,0 +1,87 @@ +# ============================================================ +# UXCore — Environment Variables +# ============================================================ +# Copy this file to `.env.local` and fill in the values you need. +# Most contributors only need a minimal setup — see README. +# +# QUICKSTART (minimum to run the app): +# 1. Copy this file: cp .env.example .env.local +# 2. Generate a secret: openssl rand -base64 32 +# → paste it as NEXTAUTH_SECRET below +# 3. Run the app: yarn dev +# +# Everything else is optional unless you're working on that feature. +# ============================================================ + +# ---------- Environment ---------- +# Options: dev | staging | production +NEXT_PUBLIC_ENV=dev + +# Set to "off" to disable search engine indexing on non-prod envs +NEXT_PUBLIC_INDEXING=off + +# ---------- Domain ---------- +# Your local dev URL (or deployed domain in production) +NEXT_PUBLIC_DOMAIN=http://localhost:3005 + +# ---------- Analytics (optional for contributors) ---------- +# Get a free token at https://mixpanel.com — leave empty to disable analytics +NEXT_PUBLIC_MIXPANEL_TOKEN= + +# Ahrefs Analytics key — leave empty for local development +NEXT_PUBLIC_AHREFS_ANALYTICS_KEY= + +# Google Analytics measurement ID — only used in production with indexing on, +# safe to leave empty for local development +NEXT_PUBLIC_GA_MEASUREMENT_ID= + +# ---------- Backend / API ---------- +# Strapi CMS public URL (used by the browser) — staging instance is fine for dev +NEXT_PUBLIC_STRAPI=https://staging-strapi.keepsimple.io + +# Strapi CMS server-side URL (used by Next.js server) +# Usually the same as NEXT_PUBLIC_STRAPI +STRAPI_URL=https://staging-strapi.keepsimple.io + +# UX Cat API endpoint — staging instance for development +NEXT_PUBLIC_UXCAT_API=https://staging-uxcat.keepsimple.io/ + +# ---------- NextAuth ---------- +# Generate a random secret with: `openssl rand -base64 32` +NEXTAUTH_SECRET= +# Should match your local dev URL +NEXTAUTH_URL=http://localhost:3005 + +# ============================================================ +# OAuth Providers (OPTIONAL for contributors) +# ============================================================ +# You only need these if you're working on authentication features. +# For most UI/feature work, you can leave them empty — the app will +# run without auth, you just won't be able to sign in. +# +# If you do need auth locally, follow the setup guides below to +# create your own OAuth apps (takes ~5 min per provider). +# Set the redirect URL to: +# http://localhost:3005/api/auth/callback/[provider] +# ============================================================ + +# ---------- Google OAuth ---------- +# Setup: https://console.cloud.google.com/apis/credentials +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= + +# ---------- LinkedIn OAuth ---------- +# Setup: https://www.linkedin.com/developers/apps +LINKEDIN_CLIENT_ID= +LINKEDIN_CLIENT_SECRET= + +# ---------- Discord OAuth ---------- +# Setup: https://discord.com/developers/applications +DISCORD_CLIENT_ID= +DISCORD_CLIENT_SECRET= + +# ---------- Mail.ru OAuth ---------- +# Setup: https://o2.mail.ru/app/ +# Server-only — never prefix with NEXT_PUBLIC_, never reference from a .tsx file. +MAILRU_CLIENT_ID= +MAILRU_CLIENT_SECRET= \ No newline at end of file diff --git a/.github/workflows/cypress-manual.yml b/.github/workflows/cypress-manual.yml deleted file mode 100644 index 3a073e5e..00000000 --- a/.github/workflows/cypress-manual.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Run Cypress Tests - -on: - schedule: - - cron: '0 4 * * 1' # Every Monday at 4:00 AM UTC - workflow_dispatch: - inputs: - target_env: - description: Target environment for Cypress run - required: true - default: staging - type: choice - options: - - staging - - prod - -jobs: - cypress-run: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Use Node.js - uses: actions/setup-node@v4 - with: - node-version: 20 - - - name: Install dependencies - run: yarn install - - - name: Copy production env file - if: (inputs.target_env || 'staging') == 'prod' - run: | - echo ${{ secrets.ENV_PRODUCTION }} | base64 -d > .env.prod - - - name: Copy staging env file - if: (inputs.target_env || 'staging') == 'staging' - run: | - echo ${{ secrets.ENV_STAGING }} | base64 -d > .env.staging - - - name: Build app - run: npx cross-env NODE_ENV=production APP_ENV=${{ inputs.target_env || 'staging' }} next build - - - name: Run Cypress tests - uses: cypress-io/github-action@v6 - with: - start: npx cross-env NODE_ENV=production APP_ENV=${{ inputs.target_env || 'staging' }} next start -p 3005 - wait-on: 'http://localhost:3005' - wait-on-timeout: 300 - command: yarn cypress:run - env: - NODE_ENV: production - APP_ENV: ${{ inputs.target_env || 'staging' }} - CYPRESS_BASE_URL: http://localhost:3005 diff --git a/.github/workflows/playwright-scheduled.yml b/.github/workflows/playwright-scheduled.yml index 176da1d8..2c93007a 100644 --- a/.github/workflows/playwright-scheduled.yml +++ b/.github/workflows/playwright-scheduled.yml @@ -1,32 +1,24 @@ -name: Playwright scheduled +name: Playwright Tests (scheduled) -# Runs the Playwright suite weekly and on manual dispatch. -# Does NOT gate pull requests — pull-request-check.yml is unrelated. -# See QA_PLAN.md §5 for the full specification. -# -# Mirrors the cypress-manual.yml pattern: the workflow builds *this branch's -# source* with the chosen environment's `.env.` file, starts a local -# `next start` server, and runs Playwright against `http://localhost:3005`. -# This decouples the test from whatever is currently deployed at staging / -# prod URLs — newly added selectors, fixtures, and components are exercised -# against real Strapi data without waiting for a deploy. +# Weekly scheduled run on main + on-demand via workflow_dispatch. +# Does NOT run on PRs (by design — see QA_PLAN.md §7). on: schedule: - # Monday 06:00 UTC = Monday 10:00 Yerevan (UTC+4). + # Monday 06:00 UTC = 10:00 Yerevan (UTC+4) - cron: '0 6 * * 1' workflow_dispatch: inputs: - target_env: - description: 'Which env file to build with (controls Strapi backend, OAuth IDs, etc.)' + environment: + description: Environment to test against type: choice required: true default: staging options: - staging - - prod + - production scope: - description: 'Which tier to run (ignored if spec_path is provided)' + description: Which tier to run type: choice required: true default: all @@ -36,12 +28,12 @@ on: - P1 - P2 spec_path: - description: 'Optional specific spec file or glob (overrides scope)' + description: 'Override: specific test path or grep pattern (leave blank to use scope)' type: string required: false default: '' browser: - description: 'Browser to run against' + description: Browser to use type: choice required: true default: chromium @@ -56,91 +48,90 @@ permissions: jobs: playwright: - name: Playwright (${{ github.event.inputs.target_env || 'staging' }} / ${{ github.event.inputs.scope || 'all' }} / ${{ github.event.inputs.browser || 'chromium' }}) runs-on: ubuntu-latest - timeout-minutes: 45 - env: - CI: 'true' - APP_ENV: ${{ github.event.inputs.target_env || 'staging' }} - # NODE_ENV is intentionally NOT set at the job level — yarn treats - # NODE_ENV=production as a signal to skip devDependencies, which - # would drop @axe-core/playwright (used by tests/p2/a11y.spec.ts). - # The build step and the webServer block in playwright.config.ts - # each wrap their own command in `cross-env NODE_ENV=production`, - # so production mode is still enforced where it matters. - # - # The webServer block in playwright.config.ts reads APP_ENV and runs - # `next start` against the freshly-built `.next/`. baseURL stays at - # the default localhost:3005. + timeout-minutes: 30 + steps: - name: Checkout uses: actions/checkout@v4 - - name: Setup Node + - name: Setup Node.js uses: actions/setup-node@v4 with: + # Aligned with Dockerfile (node:20.19.0-alpine). README's 18.18.0 + # is stale; Node 20 is the canonical runtime for this repo. node-version: 20 - # Intentionally NOT caching yarn — a stale runner cache was - # leaving @axe-core/playwright out of node_modules even though - # it sat in yarn.lock. Mirrors cypress-manual.yml which also - # opts out of caching. - name: Install dependencies - # Plain `yarn install` (no --frozen-lockfile) — matches the - # cypress-manual.yml pattern. Slower per run (~30-60s vs cached) - # but guarantees a clean node_modules every time. - run: yarn install - - - name: Decode prod env file - if: env.APP_ENV == 'prod' - run: echo "${{ secrets.ENV_PRODUCTION }}" | base64 -d > .env.prod + run: yarn install --frozen-lockfile - - name: Decode staging env file - if: env.APP_ENV == 'staging' - run: echo "${{ secrets.ENV_STAGING }}" | base64 -d > .env.staging - - - name: Install Playwright browsers - run: yarn playwright install --with-deps ${{ github.event.inputs.browser == 'all' && 'chromium firefox webkit' || github.event.inputs.browser || 'chromium' }} - - - name: Build app - run: yarn cross-env NODE_ENV=production APP_ENV=${{ env.APP_ENV }} next build - - - name: Resolve scope path + - name: Resolve test path from scope id: scope + shell: bash run: | - SPEC_PATH="${{ github.event.inputs.spec_path }}" - SCOPE="${{ github.event.inputs.scope || 'all' }}" - if [ -n "$SPEC_PATH" ]; then - echo "path=$SPEC_PATH" >> "$GITHUB_OUTPUT" + spec_path="${{ inputs.spec_path }}" + scope="${{ inputs.scope || 'all' }}" + + if [ -n "$spec_path" ]; then + echo "path=$spec_path" >> "$GITHUB_OUTPUT" else - case "$SCOPE" in - P0) echo "path=tests/p0/" >> "$GITHUB_OUTPUT" ;; - P1) echo "path=tests/p1/" >> "$GITHUB_OUTPUT" ;; - P2) echo "path=tests/p2/" >> "$GITHUB_OUTPUT" ;; - *) echo "path=tests/" >> "$GITHUB_OUTPUT" ;; + case "$scope" in + P0) echo "path=tests/p0" >> "$GITHUB_OUTPUT" ;; + P1) echo "path=tests/p1" >> "$GITHUB_OUTPUT" ;; + P2) echo "path=tests/p2" >> "$GITHUB_OUTPUT" ;; + *) echo "path=tests" >> "$GITHUB_OUTPUT" ;; esac fi - - name: Resolve Playwright projects - id: projects + - name: Resolve browser flag + id: browser + shell: bash run: | - BROWSER="${{ github.event.inputs.browser || 'chromium' }}" - if [ "$BROWSER" = "all" ]; then - echo "args=--project=chromium --project=firefox --project=webkit" >> "$GITHUB_OUTPUT" + browser="${{ inputs.browser || 'chromium' }}" + if [ "$browser" = "all" ]; then + # Empty flag → Playwright runs every project in config. + echo "flag=" >> "$GITHUB_OUTPUT" + echo "install=chromium firefox webkit" >> "$GITHUB_OUTPUT" else - echo "args=--project=$BROWSER" >> "$GITHUB_OUTPUT" + echo "flag=--project=$browser" >> "$GITHUB_OUTPUT" + echo "install=$browser" >> "$GITHUB_OUTPUT" + fi + + - name: Install Playwright browsers + run: yarn playwright install --with-deps ${{ steps.browser.outputs.install }} + + - name: Resolve base URL + id: url + shell: bash + run: | + env="${{ inputs.environment || 'staging' }}" + case "$env" in + production) echo "base=${{ secrets.PLAYWRIGHT_PRODUCTION_URL }}" >> "$GITHUB_OUTPUT" ;; + *) echo "base=${{ secrets.PLAYWRIGHT_STAGING_URL }}" >> "$GITHUB_OUTPUT" ;; + esac + + - name: Verify base URL is configured + shell: bash + run: | + if [ -z "${{ steps.url.outputs.base }}" ]; then + echo "::error::Base URL is empty. Configure PLAYWRIGHT_STAGING_URL / PLAYWRIGHT_PRODUCTION_URL as repo secrets." >&2 + exit 1 fi - - name: Run Playwright - # webServer block in playwright.config.ts spawns `next start` itself - # (because APP_ENV is set), waits for http://localhost:3005, runs - # the suite, and tears the server down at the end. - run: yarn playwright test ${{ steps.scope.outputs.path }} ${{ steps.projects.outputs.args }} + - name: Run Playwright tests + env: + PLAYWRIGHT_BASE_URL: ${{ steps.url.outputs.base }} + # Staging is behind HTTP Basic Auth; production is public. + PLAYWRIGHT_HTTP_USERNAME: ${{ (inputs.environment || 'staging') != 'production' && secrets.PLAYWRIGHT_STAGING_USERNAME || '' }} + PLAYWRIGHT_HTTP_PASSWORD: ${{ (inputs.environment || 'staging') != 'production' && secrets.PLAYWRIGHT_STAGING_PASSWORD || '' }} + PLAYWRIGHT_NO_SERVER: '1' + CI: 'true' + run: yarn playwright test ${{ steps.scope.outputs.path }} ${{ steps.browser.outputs.flag }} - - name: Upload HTML report + - name: Upload Playwright HTML report if: failure() uses: actions/upload-artifact@v4 with: - name: playwright-report-${{ github.run_id }} - path: playwright-report + name: playwright-report + path: playwright-report/ retention-days: 14 diff --git a/.gitignore b/.gitignore index 4e234b54..9c6e078a 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,17 @@ blob-report/ QA_PLAN.md QA_RECON.md TODO.md +marys-notes.md + +# Playwright MCP cache +.playwright-mcp/ + +# Loose QA screenshots at repo root (they belong in reports/screenshots//) +/qa-*.png +/uxcore-*.png + +# QA agent runtime artifacts (regenerable; never commit) +qa-runs/state/ +qa-runs/baselines/ +qa-runs/screenshots/ +qa-runs/auth/ diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 00000000..f00f0585 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "playwright": { + "command": "bash", + "args": [ + "-lc", + "source ~/.nvm/nvm.sh && nvm use 20 >/dev/null && exec npx -y @playwright/mcp@latest --headless --browser chrome --executable-path $HOME/.cache/ms-playwright/chromium_headless_shell-1219/chrome-headless-shell-linux64/chrome-headless-shell" + ] + } + } +} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..5bd68117 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20.19.0 diff --git a/AGENTS.md b/AGENTS.md index 871062fe..020ac7c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ How to work in this codebase. Read this before writing code. | Auth | NextAuth v4 | 4.23.2 | | CMS | Strapi (external) | — | | State | React Context only | — | -| Testing | Cypress E2E only | 14.5.2 | +| Testing | Playwright E2E only | 1.59.1 | | Linting | ESLint flat config + simple-import-sort | 9.x | | Formatting | Prettier | 3.x | | Git hooks | Husky + lint-staged | 9.x | diff --git a/README.md b/README.md index 6a091746..a6c941b1 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ KeepSimple was founded in 2019 by Wolf Alexanyan as a free, public initiative ex - SCSS modules -- Node.js version: 18.18.0 +- Node.js version: 20.19.0 (see `.nvmrc`) ### Additional tooling: @@ -81,7 +81,7 @@ There are **two ways** of creating a branch: ## 📝Folder Structure and Naming Conventions -- cypress - contains all the e2e tests +- tests - contains all the Playwright e2e tests, organized by priority tier (`tests/p0/`, `tests/p1/`, `tests/p2/`) with shared `tests/fixtures/` and `tests/helpers/`. Run with `yarn test:e2e` (or `:p0` / `:p1` / `:p2` for a single tier). - public - contains static assets like images, icons, etc. - src - contains the main application code - api - contains API requests and related code diff --git a/cypress.config.ts b/cypress.config.ts deleted file mode 100644 index 3e181cc6..00000000 --- a/cypress.config.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { defineConfig } from 'cypress'; -import { GoogleSocialLogin } from 'cypress-social-logins/src/Plugins'; - -export default defineConfig({ - e2e: { - baseUrl: process.env.CYPRESS_BASE_URL || 'http://localhost:3005', - setupNodeEvents(on, config) { - on('task', { - // We don't need this for now. - GoogleSocialLogin, - }); - return config; - }, - env: { - googleRefreshToken: process.env.GOOGLE_REFRESH_TOKEN, - googleClientId: process.env.GOOGLE_CLIENT_ID, - googleClientSecret: process.env.GOOGLE_CLIENT_SECRET, - STRAPI_URL: - process.env.STRAPI_URL || - process.env.CYPRESS_STRAPI_URL || - 'https://strapi.keepsimple.io', - }, - }, -}); diff --git a/cypress/cypress.json b/cypress/cypress.json deleted file mode 100644 index 14290219..00000000 --- a/cypress/cypress.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "baseUrl": "http://localhost:3005", - "chromeWebSecurity": false -} diff --git a/cypress/e2e/articles/articles-links.spec.cy.ts b/cypress/e2e/articles/articles-links.spec.cy.ts deleted file mode 100644 index 4a0c2b62..00000000 --- a/cypress/e2e/articles/articles-links.spec.cy.ts +++ /dev/null @@ -1,52 +0,0 @@ -describe('External Links from API', () => { - let routes: string[] = []; - - before(() => { - const strapiUrl = - Cypress.env('STRAPI_URL') || 'https://strapi.keepsimple.io'; - const apiUrl = `${strapiUrl}/api/articles?locale=en&fields[1]=newUrl`; - - cy.request(apiUrl).then(response => { - routes = response.body.data.map( - item => - `${Cypress.config().baseUrl}/articles/${item.attributes.newUrl}` || - '/', - ); - }); - }); - - it('should check external links on every route from API', () => { - routes.forEach(route => { - cy.wait(1000); - cy.visit(route); - - cy.get('a').each($a => { - const message = $a.text(); - const href = $a.prop('href'); - - expect($a, message) - .to.have.attr('href') - .and.not.match(/undefined|null|^$/); - - if ( - href && - href.startsWith('http') && - !href.includes('http://localhost:3005') && - !href.includes('linkedin.com') && - !href.includes('facebook.com') && - !href.includes('/uxcore') - ) { - cy.request({ - url: href, - failOnStatusCode: false, - }).then(response => { - cy.log( - `Checking external link: ${href} - Status: ${response.status}`, - ); - expect(response.status).to.be.oneOf([200, 301, 302, 403]); - }); - } - }); - }); - }); -}); diff --git a/cypress/e2e/articles/articles.cy.ts b/cypress/e2e/articles/articles.cy.ts deleted file mode 100644 index 4cd98fca..00000000 --- a/cypress/e2e/articles/articles.cy.ts +++ /dev/null @@ -1,37 +0,0 @@ -describe('template spec', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(`${Cypress.config().baseUrl}/articles`); - }); - - it('Should show a h1', () => { - cy.checkH1(); - }); - - it("Should click and scroll to 'UX Core' section", () => { - cy.scrollToSection('UX Core'); - }); - - it("Should click and scroll to 'Thoughts' section", () => { - cy.scrollToSection('Thoughts'); - }); - - it("Should click and scroll to 'Project Management' section", () => { - cy.scrollToSection('Project Management'); - }); - - it('Should check all internal links', () => { - cy.get('a').each($a => { - const message = $a.text(); - expect($a, message).to.have.attr('href').not.contain('undefined'); - }); - }); - - it('Should toggle Show Less Button', () => { - cy.get('[data-cy="show more-less button"]').click(); - }); - - it('Should toggle Show More Button', () => { - cy.get('[data-cy="show more-less button"]').click(); - }); -}); diff --git a/cypress/e2e/articles/what-is-ux-core.cy.ts b/cypress/e2e/articles/what-is-ux-core.cy.ts deleted file mode 100644 index b4d84365..00000000 --- a/cypress/e2e/articles/what-is-ux-core.cy.ts +++ /dev/null @@ -1,21 +0,0 @@ -describe('template spec', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(`${Cypress.config().baseUrl}/articles/what-is-ux-core`); - }); - - it('Should show a h1', () => { - cy.checkH1(); - }); - - it('verifies all image src URLs are valid', () => { - cy.validateAllImages(); - }); - - it('zooms in on the first image and zooms out on second click', () => { - cy.get('[data-cy="zoom-trigger"]').first().click(); - cy.get('[data-cy="zoomed-image"]:visible').should('exist'); - cy.get('[data-cy="zoomed-image"]:visible').click(); - cy.get('[data-cy="zoomed-image"]:visible').should('not.exist'); - }); -}); diff --git a/cypress/e2e/company-management/company-management.cy.ts b/cypress/e2e/company-management/company-management.cy.ts deleted file mode 100644 index aef1327e..00000000 --- a/cypress/e2e/company-management/company-management.cy.ts +++ /dev/null @@ -1,51 +0,0 @@ -describe('template spec', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(`${Cypress.config().baseUrl}/company-management`); - }); - - it('Should show a h1', () => { - cy.checkH1(); - }); - - it('Should start playing a music', () => { - cy.playAudio(); - }); - - it('should switch pyramids correctly', () => { - cy.checkPyramidChange('1', '2', '0'); - }); - - it('checks external links on a specific page', () => { - const singleRoute = `${Cypress.config().baseUrl}/company-management`; - - cy.visit(singleRoute); - - cy.get('a').each($a => { - const href = $a.prop('href'); - const message = $a.text(); - - expect($a, message) - .to.have.attr('href') - .and.not.match(/undefined|null|^$/); - - if ( - href && - href.startsWith('http') && - !href.includes('localhost') && - !href.includes('linkedin.com') && - !href.includes('facebook.com') - ) { - cy.request({ - url: href, - failOnStatusCode: false, - }).then(response => { - cy.log( - `Checking external link: ${href} - Status: ${response.status}`, - ); - expect(response.status).to.be.oneOf([200, 301, 302, 403]); - }); - } - }); - }); -}); diff --git a/cypress/e2e/config.ts b/cypress/e2e/config.ts deleted file mode 100644 index c86050f4..00000000 --- a/cypress/e2e/config.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { defineConfig } from 'cypress'; - -export default defineConfig({ - e2e: { - setupNodeEvents() { - // setup code - }, - baseUrl: 'http://localhost:3005', - viewportWidth: 1920, - viewportHeight: 900, - }, -}); - -export const viewports = [ - { name: 'Desktop', width: 1920, height: 920 }, - { name: 'Mobile', width: 375, height: 667 }, -]; diff --git a/cypress/e2e/cross-browser.spec.cy.ts b/cypress/e2e/cross-browser.spec.cy.ts deleted file mode 100644 index a3baab77..00000000 --- a/cypress/e2e/cross-browser.spec.cy.ts +++ /dev/null @@ -1,37 +0,0 @@ -// Keeping this for the future when we want to implement cross-browser testing -// -// const viewports = [ -// { name: 'Desktop', width: 1920, height: 920 }, -// { name: 'Mobile', width: 375, height: 667 }, -// ]; -// -// const browsers = ['chrome', 'firefox', 'edge']; -// -// beforeEach(() => { -// cy.on('uncaught:exception', err => { -// cy.log(`Uncaught exception: ${err.message}`); -// return false; -// }); -// }); -// -// describe('Route Tests Across Browsers', () => { -// browsers.forEach(browser => { -// describe(`Cross-browser tests in ${browser}`, () => { -// routesToCheck.forEach(route => { -// describe(`Testing route: ${route}`, () => { -// viewports.forEach(vp => { -// it(`should load the route ${route} on ${vp.name} in ${browser}`, () => { -// Cypress.browser.name = browser; -// -// cy.viewport(vp.width, vp.height); -// cy.visit(route); -// cy.url().should('include', route); -// cy.visit(route, { timeout: 20000 }); -// cy.get('section').should('exist'); -// }); -// }); -// }); -// }); -// }); -// }); -// }); diff --git a/cypress/e2e/landing-page/landing-page.cy.ts b/cypress/e2e/landing-page/landing-page.cy.ts deleted file mode 100644 index f5c85d43..00000000 --- a/cypress/e2e/landing-page/landing-page.cy.ts +++ /dev/null @@ -1,38 +0,0 @@ -describe('template spec', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(`${Cypress.config().baseUrl}`); - }); - - it('Should show a h1', () => { - cy.checkH1(); - }); - - it('should check external links and their accessibility', () => { - cy.checkExternalLinks(['linkedin.com', 'facebook.com']); - }); - - it('should check Our Tools', () => { - cy.get('[data-test-id="tool"]').click({ multiple: true }); - }); - - it('should start Serenity Mode', () => { - cy.get('[data-test-id="start-serenity"]').click(); - cy.get('audio').should('have.prop', 'paused', false); - - cy.get('[data-test-id="exit-serenity"]').click(); - }); - - it('should change theme color', () => { - cy.get('[data-test-id="theme-toggle"]').click(); - cy.get('body').should('have.class', 'darkTheme keepsimplePagesDark'); - }); - - it('should change the language to Russian', () => { - cy.get('[data-test-id="language-toggle"]', { timeout: 5000 }) - .should('exist') - .click(); - cy.url().should('include', '/ru'); - cy.checkH1(); - }); -}); diff --git a/cypress/e2e/longevity-protocol/about-project.cy.ts b/cypress/e2e/longevity-protocol/about-project.cy.ts deleted file mode 100644 index 0551cc56..00000000 --- a/cypress/e2e/longevity-protocol/about-project.cy.ts +++ /dev/null @@ -1,65 +0,0 @@ -export {}; -const PAGE = '/tools/longevity-protocol/about-project'; -const DEFAULT_DNA_SRC = '/keepsimple_/assets/longevity/dna/default.mp4'; - -describe('About Project – /tools/longevity-protocol/about-project', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. DNA canvas: default.mp4 is present, autoplaying and looping - it('renders the default DNA video with autoplay and loop', () => { - cy.get(`video[src="${DEFAULT_DNA_SRC}"]`) - .should('exist') - .should($video => { - expect($video).to.have.attr('autoplay'); - expect($video).to.have.attr('loop'); - }); - }); - - // 3. All images load without undefined or broken src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - - // 4. Basic stats section contains all 5 stats with non-empty values - it('shows all basic stats with non-empty values', () => { - cy.get('[data-cy="basic-stats"]').should('exist'); - cy.get('[data-cy="stat-item"]').should('have.length', 5); - - cy.get('[data-cy="stat-value"]').each($span => { - const text = $span.text().trim(); - expect(text, 'Stat value should not be empty').to.not.be.empty; - expect(text, 'Stat value should not contain "undefined"').to.not.include( - 'undefined', - ); - }); - }); - - // 5. All internal and external links are valid (reuses checkPageLinks command) - it('has no broken internal or external links', () => { - cy.checkPageLinks(); - }); -}); diff --git a/cypress/e2e/longevity-protocol/diet.cy.ts b/cypress/e2e/longevity-protocol/diet.cy.ts deleted file mode 100644 index 569a0bb1..00000000 --- a/cypress/e2e/longevity-protocol/diet.cy.ts +++ /dev/null @@ -1,162 +0,0 @@ -const PAGE = '/tools/longevity-protocol/habits/diet'; - -describe('Diet – /tools/longevity-protocol/habits/diet', () => { - describe('Desktop (1920x900)', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. Japanese text existence - it('has Japanese text rendered', () => { - cy.checkJapaneseText(); - }); - - // 3. WhatToEatOrAvoid – click second selectable item, checkbox state updates - it('updates checkbox state when clicking a WhatToEatOrAvoid item', () => { - // Items with role="checkbox" only exist in the "what to eat" section - cy.scrollTo(0, 1600); - cy.get('[data-cy="diet-checkbox"]').eq(0).scrollIntoView(); - - // First item starts selected – checkmark is present - cy.get('[data-cy="diet-checkbox"]') - .eq(0) - .find('[data-cy="diet-checkmark"]') - .should('exist'); - - // Click the second selectable item - cy.get('[data-cy="diet-checkbox"]') - .eq(1) - .closest('[data-cy="what-to-eat-or-avoid"]') - .click({ force: true }); - - // Second item is now checked - cy.get('[data-cy="diet-checkbox"]') - .eq(1) - .find('[data-cy="diet-checkmark"]') - .should('exist'); - - // First item is now unchecked - cy.get('[data-cy="diet-checkbox"]') - .eq(0) - .find('[data-cy="diet-checkmark"]') - .should('not.exist'); - }); - - // 4. DietResults – click second item → active states update + YourDiet data changes - it('activates second DietResults item and updates YourDiet data', () => { - // Scroll to the DietResults component - cy.get('[data-cy="diet-results-item"]').first().scrollIntoView(); - cy.wait(300); - - // Dismiss the cookie banner so it does not cover the result items - cy.get('[data-cy="cookie-box-accept"]').click(); - - // Initial state: first item (id=1) is active, second is not - cy.get('[data-cy="diet-results-item"]') - .eq(0) - .should('have.attr', 'data-active', 'true'); - cy.get('[data-cy="diet-results-item"]') - .eq(1) - .should('have.attr', 'data-active', 'false'); - - // Capture the current YourDiet selected id before the change - cy.get('[data-cy="your-diet"]') - .invoke('attr', 'data-selected-id') - .then(idBefore => { - // Click the inner img of the second item; the img click bubbles to the - // parent div's onClick and avoids any ::after overlay that may block the hit - cy.get('[data-cy="diet-results-item"]') - .eq(1) - .find('img') - .click({ force: true }); - - cy.wait(300); - - // Active states have flipped - cy.get('[data-cy="diet-results-item"]') - .eq(1) - .should('have.attr', 'data-active', 'true'); - cy.get('[data-cy="diet-results-item"]') - .eq(0) - .should('have.attr', 'data-active', 'false'); - - // YourDiet animation was triggered - cy.get('[data-cy="your-diet"]').should( - 'have.attr', - 'data-active', - 'true', - ); - - // YourDiet is now displaying a different diet entry (data-selected-id changed) - cy.get('[data-cy="your-diet"]') - .invoke('attr', 'data-selected-id') - .should('not.equal', idBefore); - }); - }); - // 5. All internal and external links are valid (reuses shared checkPageLinks command) - it('has no broken internal or external links', () => { - cy.checkPageLinks(); - }); - - // 6. All images load – no undefined in src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - }); - - describe('Mobile (390x844)', () => { - beforeEach(() => { - cy.viewport(390, 844); - cy.visit(PAGE); - }); - - // Mobile modal – tapping the heart image in WhatToEatOrAvoid opens the - // AboutTheProduct modal; closing via the modal close icon dismisses it - it('opens and closes the mobile AboutTheProduct modal on heart image tap', () => { - // Dismiss the cookie banner before any interaction - cy.get('[data-cy="cookie-box-accept"]').click(); - - // Scroll to the first WhatToEatOrAvoid card that has a heart trigger - cy.get('[data-cy="heart-trigger"]').first().scrollIntoView(); - - // Tap the heart image – on mobile this opens the modal instead of a tooltip - cy.get('[data-cy="heart-trigger"]') - .first() - .find('img') - .click({ force: true }); - - // AboutTheProduct content is now visible inside the portal modal - cy.get('[data-cy="about-product"]').should('be.visible'); - - // Close via the modal close icon - cy.get('[data-cy="modal-close-icon"]').click(); - - // Modal and its content are gone - cy.get('[data-cy="about-product"]').should('not.exist'); - }); - }); -}); - -export {}; diff --git a/cypress/e2e/longevity-protocol/environment.cy.ts b/cypress/e2e/longevity-protocol/environment.cy.ts deleted file mode 100644 index a1632b12..00000000 --- a/cypress/e2e/longevity-protocol/environment.cy.ts +++ /dev/null @@ -1,51 +0,0 @@ -export {}; -const PAGE = '/tools/longevity-protocol/environment'; -const DEFAULT_DNA_SRC = '/keepsimple_/assets/longevity/dna/blue.mp4'; - -describe('Environment – /tools/longevity-protocol/environment', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. DNA canvas: default.mp4 is present, autoplaying and looping - it('renders the default DNA video with autoplay and loop', () => { - cy.get(`video[src="${DEFAULT_DNA_SRC}"]`) - .should('exist') - .should($video => { - expect($video).to.have.attr('autoplay'); - expect($video).to.have.attr('loop'); - }); - }); - - // 3. All images load without undefined or broken src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - - // 4. All internal and external links are valid (reuses checkPageLinks command) - it('has no broken internal or external links', () => { - cy.checkPageLinks(); - }); -}); diff --git a/cypress/e2e/longevity-protocol/lifestyle.cy.ts b/cypress/e2e/longevity-protocol/lifestyle.cy.ts deleted file mode 100644 index 15123575..00000000 --- a/cypress/e2e/longevity-protocol/lifestyle.cy.ts +++ /dev/null @@ -1,97 +0,0 @@ -const PAGE = '/tools/longevity-protocol/habits/lifestyle'; -const RED_DNA_SRC = '/keepsimple_/assets/longevity/dna/red.mp4'; -describe('Lifestyle – /tools/longevity-protocol/habits/lifestyle', () => { - // 1–4, 6–7: desktop viewport - describe('Desktop (1920x900)', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. Japanese text existence - it('has Japanese text rendered', () => { - cy.checkJapaneseText(); - }); - - // 3. DNA layer: red.mp4 is loaded for habits pages and has autoplay + loop - it('renders the red DNA video with autoplay and loop', () => { - cy.get(`video[src="${RED_DNA_SRC}"]`) - .should('exist') - .should($video => { - expect($video).to.have.attr('autoplay'); - expect($video).to.have.attr('loop'); - }); - }); - - // 4. "Why do this" tooltip appears on desktop. - // react-tooltip v5 lazy-renders children; the content only exists after hover. - // it('shows why-do-this tooltip with content on hover (desktop)', () => { - // cy.visit(PAGE); - - // cy.get('[data-cy="why-do-this-trigger"]') - // .first() - // .should('be.visible') - // .scrollIntoView() - // .trigger('mouseenter', { force: true }) - // .trigger('mouseover', { force: true }); - // cy.wait(10000); - - // cy.get('[data-cy="why-do-this-content"]', { timeout: 10000 }).should( - // 'be.visible', - // ); - // }); - // // 5. All internal and external links are valid - // it('has no broken internal or external links', () => { - // cy.checkPageLinks(); - // }); - - // 6. All images load without undefined or broken src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - }); - - // 7. "Why do this" modal opens and closes on mobile - describe('Mobile (390x844)', () => { - beforeEach(() => { - cy.viewport(390, 844); - cy.visit(PAGE); - }); - - it('opens and closes the Why do this modal on mobile', () => { - cy.scrollTo(0, 400); - cy.get('[data-cy="why-do-this-trigger"]').first().click({ force: true }); - cy.wait(3000); - - cy.get('[data-cy="why-do-this-content"]').should('be.visible'); - - cy.get('[data-cy="cookie-box-accept"]').click(); - cy.get('[data-cy="why-do-this-modal-close"]').click(); - - cy.get('[data-cy="why-do-this-modal"]').should('not.exist'); - }); - }); -}); - -export {}; diff --git a/cypress/e2e/longevity-protocol/mobile-navigation.cy.ts b/cypress/e2e/longevity-protocol/mobile-navigation.cy.ts deleted file mode 100644 index 7ae39c3f..00000000 --- a/cypress/e2e/longevity-protocol/mobile-navigation.cy.ts +++ /dev/null @@ -1,167 +0,0 @@ -/** - * MobileNavigation – tests for the "Next" button (BorderedPill) and the - * getNextNavItem / buildNavOrder ordering logic. - * - * buildNavOrder expands the "Habits" entry into its six sub-pages, skips the - * external AI-Assistant link, and produces this sequence: - * - * about-project → lifestyle → study → diet → workout → - * sleep → supplements → environment → results → (wraps to about-project) - */ - -const BASE = '/tools/longevity-protocol'; - -/** Full ordered sequence that buildNavOrder builds */ -const NAV_ORDER = [ - { path: `${BASE}/about-project`, name: 'About Project' }, - { path: `${BASE}/habits/lifestyle`, name: 'Lifestyle' }, - { path: `${BASE}/habits/study`, name: 'Study' }, - { path: `${BASE}/habits/diet`, name: 'Diet' }, - { path: `${BASE}/habits/workout`, name: 'Workout' }, - { path: `${BASE}/habits/sleep`, name: 'Sleep' }, - { path: `${BASE}/habits/supplements`, name: 'Supplements' }, - { path: `${BASE}/environment`, name: 'Environment' }, - { path: `${BASE}/results`, name: 'Results' }, -]; - -describe('MobileNavigation', () => { - beforeEach(() => { - // Mobile viewport – MobileNavigation is only rendered on small screens - cy.viewport(390, 844); - }); - - // ─── Next button: appearance ────────────────────────────────────────────── - - describe('Next button appearance (lines 246-253)', () => { - it('is visible on about-project', () => { - cy.visit(`${BASE}/about-project`); - cy.get('[data-cy="mobile-next-button"]').should('be.visible'); - }); - - it('shows the "Next:" prefix text', () => { - cy.visit(`${BASE}/about-project`); - cy.get('[data-cy="mobile-next-button"]').should('contain.text', 'Next:'); - }); - - it('shows "Lifestyle" as the next page from about-project', () => { - cy.visit(`${BASE}/about-project`); - cy.get('[data-cy="mobile-next-button"]').should( - 'contain.text', - 'Lifestyle', - ); - }); - - it('shows "About Project" as the next page from results (wrap-around)', () => { - cy.visit(`${BASE}/results`); - cy.get('[data-cy="mobile-next-button"]').should( - 'contain.text', - 'About Project', - ); - }); - }); - - // ─── getNextNavItem: full navigation order (lines 130-145) ──────────────── - - describe('getNextNavItem navigation order (lines 130-145)', () => { - NAV_ORDER.forEach((current, idx) => { - const next = NAV_ORDER[(idx + 1) % NAV_ORDER.length]; - - it(`${current.name} → next button shows "${next.name}"`, () => { - cy.visit(current.path); - cy.get('[data-cy="mobile-next-button"]').should( - 'contain.text', - next.name, - ); - }); - }); - }); - - // ─── Next button: click navigates to correct URL ────────────────────────── - - describe('Next button click navigation (lines 246-253)', () => { - it('navigates from about-project to lifestyle on click', () => { - cy.visit(`${BASE}/about-project`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/habits/lifestyle'); - }); - - it('navigates from lifestyle to study on click', () => { - cy.visit(`${BASE}/habits/lifestyle`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/habits/study'); - }); - - it('navigates from study to diet on click', () => { - cy.visit(`${BASE}/habits/study`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/habits/diet'); - }); - - it('navigates from diet to workout on click', () => { - cy.visit(`${BASE}/habits/diet`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/habits/workout'); - }); - - it('navigates from workout to sleep on click', () => { - cy.visit(`${BASE}/habits/workout`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/habits/sleep'); - }); - - it('navigates from sleep to supplements on click', () => { - cy.visit(`${BASE}/habits/sleep`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/habits/supplements'); - }); - - it('navigates from supplements to environment on click', () => { - cy.visit(`${BASE}/habits/supplements`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/environment'); - }); - - it('navigates from environment to results on click', () => { - cy.visit(`${BASE}/environment`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/results'); - }); - - it('wraps from results back to about-project on click', () => { - cy.visit(`${BASE}/results`); - cy.get('[data-cy="mobile-next-button"]').click(); - cy.url({ timeout: 10000 }).should('include', '/about-project'); - }); - }); - - // ─── Nav dropdown + Habits sub-menu flow ───────────────────────────────── - - describe('Nav dropdown → Habits sub-menu → Lifestyle route', () => { - it('opens the nav, expands Habits, clicks Lifestyle and lands on the correct route', () => { - cy.visit(`${BASE}/about-project`); - - // 1. Click the active-page toggle to open the nav dropdown - cy.get('[data-cy="mobile-nav-toggle"]').click(); - - // Nav list should now be open (contains the Habits item) - cy.get('[data-cy="mobile-habits-toggle"]').should('be.visible'); - - // 2. Click the Habits item to expand its sub-menu - cy.get('[data-cy="mobile-habits-toggle"]').click(); - - // Sub-nav dropdown should now be visible with all habit pages - cy.get('[data-cy="mobile-subnav"]').should('be.visible'); - - // 3. Click the first sub-nav item (Lifestyle) - cy.get('[data-cy="mobile-subnav"]').find('a').first().click(); - - // 4. URL should change to the Lifestyle page - cy.url({ timeout: 10000 }).should( - 'include', - '/tools/longevity-protocol/habits/lifestyle', - ); - }); - }); -}); - -export {}; diff --git a/cypress/e2e/longevity-protocol/navigation.cy.ts b/cypress/e2e/longevity-protocol/navigation.cy.ts deleted file mode 100644 index 74973f14..00000000 --- a/cypress/e2e/longevity-protocol/navigation.cy.ts +++ /dev/null @@ -1,34 +0,0 @@ -export {}; -const PAGES = [ - '/tools/longevity-protocol/about-project', - '/tools/longevity-protocol/habits/lifestyle', - '/tools/longevity-protocol/habits/study', - '/tools/longevity-protocol/habits/diet', - '/tools/longevity-protocol/habits/workout', - '/tools/longevity-protocol/habits/sleep', - '/tools/longevity-protocol/habits/supplements', - '/tools/longevity-protocol/environment', - '/tools/longevity-protocol/results', -]; - -describe('Longevity Protocol – Navigation', () => { - PAGES.forEach(path => { - it(`loads ${path} without errors`, () => { - cy.visit(path); - cy.get('main').should('exist'); - // No uncaught JS errors (Cypress catches these by default) - }); - }); - - it('navigates between habit sub-pages via nav/sidebar', () => { - cy.visit('/tools/longevity-protocol/habits/lifestyle'); - cy.contains('a', /diet/i).click(); - cy.url().should('include', '/habits/diet'); - }); - - it('does not 404 on any protocol page', () => { - PAGES.forEach(path => { - cy.request(path).its('status').should('eq', 200); - }); - }); -}); diff --git a/cypress/e2e/longevity-protocol/results.cy.ts b/cypress/e2e/longevity-protocol/results.cy.ts deleted file mode 100644 index d0c5bc73..00000000 --- a/cypress/e2e/longevity-protocol/results.cy.ts +++ /dev/null @@ -1,51 +0,0 @@ -export {}; -const PAGE = '/tools/longevity-protocol/results'; -const DEFAULT_DNA_SRC = '/keepsimple_/assets/longevity/dna/red-and-blue.mp4'; - -describe('Results – /tools/longevity-protocol/results', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. DNA canvas: default.mp4 is present, autoplaying and looping - it('renders the default DNA video with autoplay and loop', () => { - cy.get(`video[src="${DEFAULT_DNA_SRC}"]`) - .should('exist') - .should($video => { - expect($video).to.have.attr('autoplay'); - expect($video).to.have.attr('loop'); - }); - }); - - // 3. All images load without undefined or broken src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - - // 4. All internal and external links are valid (reuses checkPageLinks command) - it('has no broken internal or external links', () => { - cy.checkPageLinks(); - }); -}); diff --git a/cypress/e2e/longevity-protocol/sleep.cy.ts b/cypress/e2e/longevity-protocol/sleep.cy.ts deleted file mode 100644 index 82486c0f..00000000 --- a/cypress/e2e/longevity-protocol/sleep.cy.ts +++ /dev/null @@ -1,79 +0,0 @@ -const PAGE = '/tools/longevity-protocol/habits/sleep'; - -describe('Sleep – /tools/longevity-protocol/habits/sleep', () => { - describe('Desktop (1920x900)', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. Japanese text existence - it('has Japanese text rendered', () => { - cy.checkJapaneseText(); - }); - - // 3. All internal and external links are valid (reuses shared checkPageLinks command) - it('has no broken internal or external links', () => { - cy.checkPageLinks(); - }); - - // 4. All images load – no undefined in src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - }); - - // 5. Mobile – open chart modal and close via the modal close icon - describe('Mobile (390x844)', () => { - beforeEach(() => { - cy.viewport(390, 844); - cy.visit(PAGE); - }); - - it('opens the sleep chart modal and closes it via the close icon', () => { - // Scroll down so the button is reachable (it renders below the supplements section) - cy.get('[data-cy="open-chart-btn"]') - .scrollIntoView() - .should('be.visible'); - - cy.get('[data-cy="open-chart-btn"]').click(); - - // Modal is rendered immediately; html2canvas fills the image asynchronously - cy.get('[data-cy="sleep-chart-modal"]').should('be.visible'); - - // Wait for html2canvas to produce the data-URL – src changes from '' to data:image/png... - cy.get('[data-cy="sleep-chart-img"]', { timeout: 15000 }) - .should('have.attr', 'src') - .and('not.be.empty') - .and('not.include', 'undefined'); - - // Close via the modal's X icon - cy.get('[data-cy="modal-close-icon"]').click({ force: true }); - - cy.get('[data-cy="sleep-chart-modal"]').should('not.exist'); - }); - }); -}); - -export {}; diff --git a/cypress/e2e/longevity-protocol/study.cy.ts b/cypress/e2e/longevity-protocol/study.cy.ts deleted file mode 100644 index cf25c726..00000000 --- a/cypress/e2e/longevity-protocol/study.cy.ts +++ /dev/null @@ -1,99 +0,0 @@ -const PAGE = '/tools/longevity-protocol/habits/study'; - -describe('Study – /tools/longevity-protocol/habits/study', () => { - describe('Desktop (1920x900)', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. Japanese text existence - it('has Japanese text rendered', () => { - cy.checkJapaneseText(); - }); - - // 3. StudySection page-switcher click → FlipCard wrapper becomes visible - it('shows FlipCard after clicking the page-switcher image', () => { - cy.scrollTo(0, 800); - cy.get('[data-cy="page-switcher"]').first().click(); - - cy.get('[data-cy="flip-card-wrapper"]').first().should('be.visible'); - }); - - // 4. All internal and external links are valid (reuses shared checkPageLinks command) - it('has no broken internal or external links', () => { - cy.checkPageLinks(); - }); - - // 5. All images load – no undefined in src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - }); - - describe('Mobile (390x844)', () => { - beforeEach(() => { - cy.viewport(390, 844); - cy.visit(PAGE); - }); - - // 6. Scroll to second StudySection, expand clamped HTMLClamp via show-more button - it('expands clamped content via show-more button in second StudySection', () => { - cy.get('[data-cy="study-section"]').eq(1).scrollIntoView(); - - cy.get('[data-cy="study-section"]') - .eq(1) - .find('[data-cy="show-more-btn"]') - .should('be.visible') - .click(); - - cy.get('[data-cy="study-section"]') - .eq(1) - .find('[data-cy="html-clamp-content"]') - .should('have.attr', 'data-expanded', 'true'); - }); - - // 7. Scroll to second StudySection, open FlipCard modal via Learn more (BorderedPill), - // then close it via the Close BorderedPill inside the modal - it('opens and closes FlipCard modal via Learn more / Close buttons in second StudySection', () => { - cy.get('[data-cy="study-section"]').eq(1).scrollIntoView(); - - cy.get('[data-cy="study-section"]') - .eq(1) - .find('[data-cy="learn-more-btn"]') - .should('be.visible') - .click({ force: true }); - - cy.get('[data-cy="study-flip-card-modal"]').should('be.visible'); - cy.get('[data-cy="cookie-box-accept"]').click(); - cy.get('[data-cy="study-close-btn"]') - .should('be.visible') - .click({ force: true }); - - cy.get('[data-cy="study-flip-card-modal"]').should('not.exist'); - }); - }); -}); - -export {}; diff --git a/cypress/e2e/longevity-protocol/supplements.cy.ts b/cypress/e2e/longevity-protocol/supplements.cy.ts deleted file mode 100644 index 6188548e..00000000 --- a/cypress/e2e/longevity-protocol/supplements.cy.ts +++ /dev/null @@ -1,78 +0,0 @@ -const PAGE = '/tools/longevity-protocol/habits/supplements'; - -describe('Supplements – /tools/longevity-protocol/habits/supplements', () => { - describe('Desktop (1920x900)', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. Japanese text existence - it('has Japanese text rendered', () => { - cy.checkJapaneseText(); - }); - - // 3. All internal and external links are valid (reuses shared checkPageLinks command) - it('has no broken internal or external links', () => { - cy.checkPageLinks(); - }); - - // 4. All images load – no undefined in src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - }); - - // 5. Mobile – open chart modal and close via the modal close icon - describe('Mobile (390x844)', () => { - beforeEach(() => { - cy.viewport(390, 844); - cy.visit(PAGE); - }); - - it('opens the supplements chart modal and closes it via the close icon', () => { - cy.get('[data-cy="open-chart-btn"]') - .scrollIntoView() - .should('be.visible'); - - cy.get('[data-cy="open-chart-btn"]').click(); - - // Modal opens immediately; html2canvas fills the image asynchronously - cy.get('[data-cy="supplements-chart-modal"]').should('be.visible'); - - // Wait for html2canvas to produce the data-URL - cy.get('[data-cy="supplements-chart-img"]', { timeout: 15000 }) - .should('have.attr', 'src') - .and('not.be.empty') - .and('not.include', 'undefined'); - - // Close via the modal's X icon - cy.get('[data-cy="modal-close-icon"]').click({ force: true }); - - cy.get('[data-cy="supplements-chart-modal"]').should('not.exist'); - }); - }); -}); - -export {}; diff --git a/cypress/e2e/longevity-protocol/workout.cy.ts b/cypress/e2e/longevity-protocol/workout.cy.ts deleted file mode 100644 index e897e715..00000000 --- a/cypress/e2e/longevity-protocol/workout.cy.ts +++ /dev/null @@ -1,183 +0,0 @@ -// ACTIVITY_LEVELS (from src/constants/longevity.ts): -// index 0 → Novice : totalMinutesPerWeek=240, minutesPerSession=65 -// index 4 → Elite : totalMinutesPerWeek=90, minutesPerSession=30 -// -// STOPS (WeeklyWorkout): [0, 75, 150, 225, 300] -// BRAIN_AGE_TABLE baselines: [20, 32, 45, 55, 67, 78, 90] -// index 1 (default) → baseline=32: active=29, sedentary=37 -// index 2 → baseline=45: active=40, sedentary=53 - -const PAGE = '/tools/longevity-protocol/habits/workout'; - -describe('Workout – /tools/longevity-protocol/habits/workout', () => { - describe('Desktop (1920x900)', () => { - beforeEach(() => { - cy.viewport(1920, 900); - cy.visit(PAGE); - }); - - // 1. H1 existence - it('has a visible H1', () => { - cy.checkH1(); - }); - - // 2. Japanese text existence - it('has Japanese text rendered', () => { - cy.checkJapaneseText(); - }); - - // 3. All internal and external links are valid (reuses shared checkPageLinks command) - it('has no broken internal or external links', () => { - cy.checkPageLinks(); - }); - - // 4. All images load – no undefined in src paths - it('has no images with undefined or broken src paths', () => { - cy.get('img').each($img => { - const src = $img.attr('src'); - - expect(src, 'img src should be defined').to.not.be.undefined; - expect( - src, - `img src should not contain "undefined": ${src}`, - ).to.not.include('undefined'); - - if (src && src.startsWith('/')) { - cy.request({ url: src, failOnStatusCode: false }).then(res => { - expect(res.status, `Image returned ${res.status}: ${src}`).to.be.lt( - 400, - ); - }); - } - }); - }); - - // 5. WeeklyWorkout – ProgressBar fill & thumb exist; interacting with the bar - // changes the active image and the summary content - it('WeeklyWorkout: ProgressBar fill and thumb exist; dragging bar changes active image and summary', () => { - cy.get('[data-cy="weekly-workout"]').scrollIntoView(); - - // ProgressBar fill and thumb are rendered - cy.get('[data-cy="weekly-workout"]').within(() => { - cy.get('[data-cy="progress-bar-fill"]') - .should('exist') - .and('have.attr', 'style'); - cy.get('[data-cy="progress-bar-thumb"]').should('exist'); - }); - - // Initial state: image 0 is active - cy.get('[data-cy="weekly-workout-image"][data-active="true"]').should( - 'have.attr', - 'data-id', - '0', - ); - - // Capture initial summary risk value - cy.get('[data-cy="weekly-workout-summary"]') - .invoke('attr', 'data-risk') - .then(initialRisk => { - // Click the right end of the ProgressBar → jumps to last stop (index 4, 300 min) - cy.get('[data-cy="weekly-workout"]').within(() => { - cy.get('[data-cy="progress-bar-container"]').click('right'); - }); - - cy.wait(300); - - // Image at index 4 should now be active - cy.get('[data-cy="weekly-workout-image"][data-active="true"]').should( - 'have.attr', - 'data-id', - '4', - ); - - // Summary content (risk value) should have changed - cy.get('[data-cy="weekly-workout-summary"]') - .invoke('attr', 'data-risk') - .should('not.equal', initialRisk); - }); - }); - - // 6. BrainAgeActivity – clicking the second age button makes it active; - // passive (sedentary) and active brain-age values update accordingly - it('BrainAgeActivity: clicking second age button activates it and updates passive/active content', () => { - cy.get('[data-cy="brain-age-activity"]').scrollIntoView(); - cy.wait(300); - - // First, click the third button (baseline=45) to move away from the default (baseline=32) - cy.get('[data-cy="age-button"][data-baseline="45"]') - .scrollIntoView() - .click(); - cy.get('[data-cy="age-button"][data-baseline="45"]').should( - 'have.attr', - 'data-active', - 'true', - ); - - // Capture current result values (baseline=45 → sedentary=53, active=40) - cy.get('[data-cy="brain-age-result"]') - .invoke('attr', 'data-sedentary') - .then(sedentaryBefore => { - // Click the second button (baseline=32) - cy.get('[data-cy="age-button"][data-baseline="32"]') - .scrollIntoView() - .click(); - - // Second button (baseline=32) becomes active, third (baseline=45) becomes inactive - cy.get('[data-cy="age-button"][data-baseline="32"]').should( - 'have.attr', - 'data-active', - 'true', - ); - cy.get('[data-cy="age-button"][data-baseline="45"]').should( - 'have.attr', - 'data-active', - 'false', - ); - - // Result data attributes reflect baseline=32 (sedentary=37, active=29) - cy.get('[data-cy="brain-age-result"]') - .should('have.attr', 'data-sedentary', '37') - .and('have.attr', 'data-active-age', '29'); - - // Values are different from baseline=45 (which had sedentary=53) - cy.get('[data-cy="brain-age-result"]') - .invoke('attr', 'data-sedentary') - .should('not.equal', sedentaryBefore); - }); - }); - - // 7. StrengthAndTimeCompression – clicking the progress bar to +1 stop - // updates totalMins and minutesPerSession (quantity) - it('StrengthAndTimeCompression: advancing progress bar changes totalMins and per-session quantity', () => { - cy.get('[data-cy="strength-section"]').scrollIntoView(); - - // Initial state: Novice (index 0) → totalMinutesPerWeek=240, minutesPerSession=65 - cy.get('[data-cy="strength-total-mins"]').should('contain', '240'); - cy.get('[data-cy="strength-per-session-qty"]').should( - 'have.attr', - 'data-value', - '65', - ); - - // Click the right end of the ProgressBar → Elite (index 4) - // totalMinutesPerWeek=90, minutesPerSession=30 - cy.get('[data-cy="strength-section"]').within(() => { - cy.get('[data-cy="progress-bar-container"]').click('right'); - }); - - cy.wait(300); - - // totalMins should now show 90 - cy.get('[data-cy="strength-total-mins"]').should('contain', '90'); - - // Per-session quantity data-value should now be 30 - cy.get('[data-cy="strength-per-session-qty"]').should( - 'have.attr', - 'data-value', - '30', - ); - }); - }); -}); - -export {}; diff --git a/cypress/e2e/utils.ts b/cypress/e2e/utils.ts deleted file mode 100644 index 3510879f..00000000 --- a/cypress/e2e/utils.ts +++ /dev/null @@ -1,24 +0,0 @@ -export const checkPageLoad = ( - route: string, - viewport: { width: number; height: number; name: string }, -) => { - cy.viewport(viewport.width, viewport.height); - - cy.on('fail', error => { - cy.log(`Failed to load ${route}: ${error.message}`); - throw error; - }); - - cy.visit(route, { - timeout: 10000, - retryOnNetworkFailure: true, - }); - - cy.url().should('include', route); - - cy.get('section', { timeout: 10000 }).should('exist').should('be.visible'); - - cy.window().then(win => { - expect(win.document.readyState).to.eq('complete'); - }); -}; diff --git a/cypress/support/commands.d.ts b/cypress/support/commands.d.ts deleted file mode 100644 index 2fdf4fd2..00000000 --- a/cypress/support/commands.d.ts +++ /dev/null @@ -1,67 +0,0 @@ -/// -import { mount } from 'cypress/react'; - -declare global { - namespace Cypress { - interface Chainable { - /** - * Custom command to check all external links on the page. - * @param excludedDomains Array of domain strings to skip - * @param selector CSS selector for links (defaults to 'a') - */ - checkExternalLinks( - excludedDomains?: string[], - selector?: string, - ): Chainable; - - checkH1(): Chainable; - - scrollToSection(sectionText: string): Chainable; - - validateAllImages(): Chainable; - - uxcgTestSearchBehavior( - validWord: string, - invalidWord: string, - ): Chainable; - - checkSocialMediaLink(title: string, domain: string): Chainable; - - showCopiedTooltip(): Chainable; - - clickArrowWhenReady( - direction: 'next' | 'prev', - expectedUrlPart: string, - ): Chainable; - - showMoreAndLess(): Chainable; - - uxcoreSearchBehavior: ( - validWord: string, - invalidWord: string, - ) => Chainable; - - playAudio(): Chainable; - - checkPyramidChange( - bluePyramidId: string, - orangePyramidId: string, - purplePyramidId: string, - ): Chainable; - - checkSwiperSlide(prevUrl, nextUrl): Chainable; - - uxcpSearchBehavior( - validWord: string, - invalidWord: string, - ): Chainable; - - uxcpAddBiases: () => Chainable; - checkAllLinks: (routes) => Chainable; - checkPageLinks: () => Chainable; - checkJapaneseText: () => Chainable; - openLoginModalByButtonClick: (buttonLabel: string) => Chainable; - loginBySession: () => Chainable; - } - } -} diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts deleted file mode 100644 index 918cf476..00000000 --- a/cypress/support/commands.ts +++ /dev/null @@ -1,326 +0,0 @@ -// 1. Check if a h1 element is visible and not empty -Cypress.Commands.add('checkH1', () => { - cy.get('h1').should('be.visible').and('not.be.empty'); -}); - -// 2. Check all external links on the page -Cypress.Commands.add('checkExternalLinks', (excludedDomains = []) => { - cy.get('a').each($link => { - const href = $link.prop('href'); - - const isExternal = - href && - href.startsWith('http') && - !href.includes('localhost') && - !excludedDomains.some(domain => href.includes(domain)); - - if (isExternal) { - // URL format validation - expect(href).to.match( - /^(https?:\/\/)?([\w-]+\.)+[\w-]+(\/[\w\- ./?%&=]*)?$/, - ); - - // Request to validate link is reachable - cy.request({ - url: href, - failOnStatusCode: false, - }).then(response => { - expect(response.status).to.be.oneOf([200, 301, 302, 403]); - }); - } - }); -}); - -// 3. Scroll to a specific section by clicking a button and checking scroll position -Cypress.Commands.add('scrollToSection', sectionText => { - cy.get('button').contains('Project Management').click(); - cy.window().then(win => { - expect(win.scrollY).to.be.greaterThan(100); - }); -}); - -// 4. Validate Images -Cypress.Commands.add('validateAllImages', () => { - cy.get('img').each($img => { - const imgSrc = $img.attr('src'); - - if (imgSrc) { - cy.request({ - url: imgSrc, - failOnStatusCode: false, - }).then(response => { - expect(response.status).to.be.oneOf([200, 301, 302]); - }); - } - }); -}); - -// 5. UXCG Search Field -Cypress.Commands.add( - 'uxcgTestSearchBehavior', - (validWord: string, invalidWord: string) => { - cy.get('[data-cy="Search Input"]').type(validWord); - cy.wait(500); - cy.get('[data-cy="open-question"]').should('be.visible'); - cy.get('[data-cy="No Results Found"]').should('not.exist'); - - cy.get('[data-cy="Search Input"]').clear().type(invalidWord); - cy.wait(500); - cy.get('[data-cy="open-question"]').should('not.exist'); - cy.get('[data-cy="No Results Found"]').should('be.visible'); - }, -); - -// 6. UXCP Search Field -Cypress.Commands.add( - 'uxcpSearchBehavior', - (validWord: string, invalidWord: string) => { - cy.get('[data-cy="input-field"]').eq(1).type(validWord); - - cy.wait(500); - - cy.get('[data-cy="uxcp-bias-action-cell"]').should('be.visible'); - cy.get('[data-cy="input-field"]').eq(1).type(invalidWord); - - cy.wait(500); - cy.get('[data-cy="uxcp-bias-action-cell"]').should('not.exist'); - }, -); - -// 7.Check Social Media Links -Cypress.Commands.add( - 'checkSocialMediaLink', - (title: string, domain: string) => { - cy.get(`a[title="${title}"]`) - .should('have.attr', 'href') - .and('include', domain) - .and('match', /^https?:\/\//) - .and('not.include', 'undefined'); - - cy.get(`a[title="${title}"]`).should('have.attr', 'target', '_blank'); - }, -); - -// 8. Shows tooltip Copy and Copied -Cypress.Commands.add('showCopiedTooltip', () => { - cy.get('[data-cy="copy-container"]').click(); - cy.get('[data-cy="copy-tooltip"]').should('contain', 'Copied!'); - cy.wait(2500); - - cy.get('[data-cy="copy-tooltip"]').should('not.have.class', 'visible'); -}); - -// 9. Clicks the arrow and checks if the URL contains the expected part -Cypress.Commands.add( - 'clickArrowWhenReady', - (direction: 'next' | 'prev', expectedUrlPart: string) => { - const selector = `[data-cy="arrow-${direction}"]`; - - cy.get(selector) - .invoke('attr', 'class') - .should('not.include', 'Disabled') - .then(() => { - cy.get(selector).click(); - }); - - if (expectedUrlPart) { - cy.url({ timeout: 10000 }).should('include', expectedUrlPart); - } - }, -); - -// 10. UX Core Search Behavior -Cypress.Commands.add( - 'uxcoreSearchBehavior', - (validWord: string, invalidWord) => { - cy.get('[data-cy="uxcore-search-input"]') - .clear() - .type(validWord, { delay: 100 }); - - cy.get('[data-cy="search-result-item"][data-state="hovered"]', { - timeout: 5000, - }).should('exist'); - }, -); - -// 11. Show more and less button (UX Core) -Cypress.Commands.add('showMoreAndLess', () => { - cy.get('[data-cy="show-more-button"]').should('be.visible').click(); - - cy.wait(500); - - cy.get('[data-cy="show-less-button"]').should('be.visible').click(); -}); - -// 12. Check Japanese text exists and is not empty -Cypress.Commands.add('checkJapaneseText', () => { - cy.get('[data-cy="japanese-text"]') - .should('exist') - .invoke('text') - .should('not.be.empty'); -}); - -// 13. Play Audio -Cypress.Commands.add('playAudio', () => { - // Initially paused: play icon visible, pause icon hidden - cy.get('[data-cy="pyramid-play-icon"]').should('be.visible'); - cy.get('[data-cy="pyramid-pause-icon"]').should('not.be.visible'); - - // Click to play - cy.get('[data-cy="audio-player"]').click({ force: true }); - - // Now playing: pause icon visible, play icon hidden - cy.get('[data-cy="pyramid-pause-icon"]').should('be.visible'); - cy.get('[data-cy="pyramid-play-icon"]').should('not.be.visible'); - - // Click to pause - cy.get('[data-cy="audio-player"]').click({ force: true }); - - // Paused again: play icon visible, pause icon hidden - cy.get('[data-cy="pyramid-play-icon"]').should('be.visible'); - cy.get('[data-cy="pyramid-pause-icon"]').should('not.be.visible'); - - // Click to play again - cy.get('[data-cy="audio-player"]').click({ force: true }); - - // Playing again: pause icon visible, play icon hidden - cy.get('[data-cy="pyramid-pause-icon"]').should('be.visible'); - cy.get('[data-cy="pyramid-play-icon"]').should('not.be.visible'); -}); - -// 14. Check all links on the current page (internal + external + no empty hrefs) -const SKIP_LINK_DOMAINS = [ - 'linkedin.com', - 'twitter.com', - 'instagram.com', - 'facebook.com', -]; - -Cypress.Commands.add('checkPageLinks', () => { - // No empty or missing hrefs - cy.get('a').each($a => { - const href = $a.attr('href'); - expect(href, `Anchor "${$a.text().trim()}" has no href`).to.not.be - .undefined; - expect( - href.trim(), - `Anchor "${$a.text().trim()}" has empty href`, - ).to.not.equal(''); - expect( - href.trim(), - `Anchor "${$a.text().trim()}" has bare # href`, - ).to.not.equal('#'); - expect( - href, - `Anchor "${$a.text().trim()}" has undefined in href`, - ).to.not.include('undefined'); - }); - - // Internal links (skip /uxcore which requires auth) - cy.get('a[href^="/"]').each($a => { - const href = $a.attr('href'); - if (!href || href.includes('/uxcore')) return; - cy.request({ url: href, failOnStatusCode: false }).then(res => { - expect(res.status, `Internal link broken: ${href}`).to.be.lt(400); - }); - }); - - // External links (skip social media that blocks bots) - cy.get('a[href^="http"]').each($a => { - const href = $a.attr('href'); - if (!href || SKIP_LINK_DOMAINS.some(domain => href.includes(domain))) - return; - cy.request({ - url: href, - failOnStatusCode: false, - timeout: 10000, - method: 'GET', - headers: { - 'User-Agent': 'Mozilla/5.0 (compatible; Cypress link checker)', - }, - }).then(res => { - expect(res.status, `External link broken: ${href}`).to.be.lt(400); - }); - }); -}); - -// 15. Check Pyramid Change -Cypress.Commands.add( - 'checkPyramidChange', - (bluePyramidId: string, orangePyramidId: string, purplePyramidId: string) => { - cy.get(`[data-id="${bluePyramidId}"]`).click(); - cy.get('[data-cy="orange-pyramid"]', { timeout: 4000 }).should( - 'be.visible', - ); - - cy.get(`[data-id="${orangePyramidId}"]`).click(); - cy.get('[data-cy="purple-pyramid"]', { timeout: 4000 }).should( - 'be.visible', - ); - - cy.get(`[data-id="${purplePyramidId}"]`).click(); - cy.get('[data-cy="blue-pyramid"]', { timeout: 4000 }).should('be.visible'); - }, -); - -// 16. Checks swiper slide -Cypress.Commands.add('checkSwiperSlide', (prevUrl: string, nextUrl: string) => { - cy.get('[data-cy="slide-move-right"]').first().click({ force: true }); - cy.wait(1000); - cy.url().should('include', nextUrl); - - cy.get('[data-cy="slide-move-left"]').eq(1).click({ force: true }); - cy.wait(1000); - cy.url().should('include', prevUrl); -}); - -// 17. UXCP Adding BIases -Cypress.Commands.add('uxcpAddBiases', () => { - cy.get('[data-cy="add-bias"]').first().click(); - cy.get('[data-cy="added-bias-item"]').first().should('be.visible'); - cy.wait(1000); - - cy.get('[data-cy="add-bias"]').eq(1).click(); - cy.get('[data-cy="added-bias-item"]').eq(1).should('be.visible'); - cy.wait(1000); - - cy.get('[data-cy="add-bias"]').eq(2).click(); - cy.get('[data-cy="added-bias-item"]').eq(2).should('be.visible'); - - cy.get('[data-cy="remove-bias"]').eq(2).click(); - cy.get('[data-cy="added-bias-item"]').eq(2).should('not.be.visible'); -}); - -// 18. Check all links on the page -Cypress.Commands.add('checkAllLinks', (routes: []) => { - routes.forEach(route => { - cy.visit(route); - - cy.get('a').each($a => { - const message = $a.text(); - const href = $a.prop('href'); - - expect($a, message) - .to.have.attr('href') - .and.not.match(/undefined|null|^$/); - - if ( - href && - href.startsWith('http') && - !href.includes('http//localhost:3005') && - !href.includes('linkedin.com') && - !href.includes('facebook.com') - ) { - cy.request({ - url: href, - failOnStatusCode: false, - }).then(response => { - cy.log( - `Checking external link: ${href} - Status: ${response.status}`, - ); - expect(response.status).to.be.oneOf([200, 301, 302, 403]); - }); - } - }); - }); -}); diff --git a/cypress/support/component-index.html b/cypress/support/component-index.html deleted file mode 100644 index 3e16e9b0..00000000 --- a/cypress/support/component-index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Components App - -
- - -
- - \ No newline at end of file diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts deleted file mode 100644 index b2a28f83..00000000 --- a/cypress/support/e2e.ts +++ /dev/null @@ -1,9 +0,0 @@ -// Import custom commands -import './commands'; - -// Suppress Next.js route cancellation errors (thrown during rapid navigation) -Cypress.on('uncaught:exception', err => { - if (err.message.includes('Cancel rendering route')) { - return false; - } -}); diff --git a/eslint.config.mjs b/eslint.config.mjs index dd95727b..e4c85a6e 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -13,7 +13,7 @@ const compat = new FlatCompat({ // eslint-disable-next-line import/no-anonymous-default-export export default [ { - ignores: ['.next/**', 'out/**', 'build/**', 'next-env.d.ts'], + ignores: ['.next/**', 'out/**', 'build/**', 'next-env.d.ts', '.claude/keepsimple-qa/**'], }, ...compat.extends('next/core-web-vitals', 'next/typescript'), diff --git a/known-issues.md b/known-issues.md new file mode 100644 index 00000000..368a2da2 --- /dev/null +++ b/known-issues.md @@ -0,0 +1,53 @@ +# Known Issues + +This file lists known issues, intentional behaviors, and quirks that the QA agent should NOT report as findings. The agent reads this before every pass. + +## How to add an entry + +Each entry needs: + +- A short title. +- The route(s) it affects (or "global"). +- A one-sentence reason it's not a finding (intentional / wontfix / third-party / pending fix). +- Date added. + +## Format + +### [Short title] + +- **Routes:** `/path` or `global` +- **Reason:** intentional | wontfix | third-party | pending-fix +- **Note:** One sentence. +- **Added:** YYYY-MM-DD + +--- + +## Entries + +### Cookie consent banner on first visit + +- **Routes:** global +- **Reason:** intentional +- **Note:** A cookie banner appears on first visit and must be dismissed before interactions work. Agent should accept/dismiss it at the start of every pass. +- **Added:** 2026-04-28 + +### "New Update!" promo modal on first visit + +- **Routes:** global +- **Reason:** intentional +- **Note:** A promotional modal appears on first visit and blocks clicks. Agent should dismiss it at the start of every pass. +- **Added:** 2026-04-28 + +### English-only header tabs: "Awareness Test" and "Bob - AI Assistant" + +- **Routes:** global (header on all locale-prefixed pages) +- **Reason:** intentional +- **Note:** Per team decision, these two header tabs stay English in all locales (en/ru/hy). Same as "UX CORE / GUIDE / PERSONA" tool brand names. +- **Added:** 2026-04-28 + +### English-only motto: "Be Kind. Do Good." + +- **Routes:** global (under UX CORE hexagon) +- **Reason:** intentional +- **Note:** Brand motto stays English in all locales. Same convention as tool brand names. +- **Added:** 2026-04-28 diff --git a/linkinator.config.json b/linkinator.config.json new file mode 100644 index 00000000..8611b22c --- /dev/null +++ b/linkinator.config.json @@ -0,0 +1,18 @@ +{ + "recurse": true, + "concurrency": 10, + "timeout": 15000, + "retry": true, + "retryErrors": true, + "retryErrorsCount": 3, + "skip": [ + "linkedin.com", + "twitter.com", + "x.com", + "facebook.com", + "instagram.com", + "discord.com", + "discord.gg" + ], + "verbosity": "INFO" +} diff --git a/package.json b/package.json index 1b5787e3..dcf50f54 100644 --- a/package.json +++ b/package.json @@ -11,19 +11,16 @@ "start": "cross-env NODE_ENV=production APP_ENV=production yarn build && next start -p 4033", "start:staging": "cross-env NODE_ENV=production APP_ENV=staging next start -p 3005", "build:staging": "cross-env NODE_ENV=production APP_ENV=staging next build", - "test:e2e:staging": "start-server-and-test start:staging http://localhost:3005 cypress run", - "cypress:open": "cypress open", - "cypress:run": "cypress run", - "test:chrome": "cypress run --browser chrome", - "test:firefox": "cypress run --browser firefox", - "test:edge": "cypress run --browser edge", - "test:all": "npm run test:chrome && npm run test:firefox && npm run test:edge", "test:e2e": "playwright test --project=chromium", "test:e2e:ui": "playwright test --project=chromium --ui", "test:e2e:p0": "playwright test tests/p0 --project=chromium", "test:e2e:p1": "playwright test tests/p1 --project=chromium", "test:e2e:p2": "playwright test tests/p2 --project=chromium", "test:e2e:report": "playwright show-report", + "render-report": "node .claude/keepsimple-qa/render-report.js", + "check-links": "linkinator https://keepsimple.io --config linkinator.config.json --format json > reports/links-$(date +%Y-%m-%d).json && echo 'Done. Report saved to reports/links-YYYY-MM-DD.json'", + "check-links:summary": "linkinator https://keepsimple.io --config linkinator.config.json", + "render-links": "node .claude/keepsimple-qa/render-links-report.js", "prepare": "husky install" }, "dependencies": { @@ -32,9 +29,6 @@ "classnames": "2.3.1", "cookie": "0.6.0", "cross-env": "7.0.3", - "cypress": "^14.5.2", - "cypress-social-logins": "^1.14.2", - "cypress-v10-preserve-cookie": "^1.2.1", "date-fns": "2.30.0", "dotenv": "^16.4.5", "eslint-plugin-simple-import-sort": "^12.1.1", @@ -75,8 +69,6 @@ "devDependencies": { "@axe-core/playwright": "^4.11.2", "@babel/core": "7.19.3", - "@cypress/react": "^9.0.1", - "@cypress/vite-dev-server": "^6.0.3", "@playwright/test": "^1.59.1", "@types/amplitude-js": "8.16.2", "@types/classnames": "2.2.11", @@ -88,13 +80,19 @@ "@types/react-slick": "0.23.10", "@types/swiper": "6.0.0", "@types/uuid": "8.3.1", + "axe-core": "^4.11.4", "babel-loader": "8.2.5", "eslint": "^9.32.0", "eslint-config-next": "^15.4.4", "husky": "^9.1.7", + "linkinator": "^7.6.1", + "marked": "^18.0.2", + "pixelmatch": "^7.2.0", + "pngjs": "^7.0.0", "sass-loader": "13.1.0", - "start-server-and-test": "^2.0.12", "ts-node": "^10.9.2", - "typescript": "5.2.2" + "typescript": "5.2.2", + "web-vitals": "^5.2.0", + "yaml": "^2.8.4" } } diff --git a/playwright.config.ts b/playwright.config.ts index 7c4d2869..c62e0989 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,31 +1,43 @@ -/// import { defineConfig, devices } from '@playwright/test'; +// Local: `yarn dev` on port 3005. CI: hit a deployed URL via PLAYWRIGHT_BASE_URL. const baseURL = process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:3005'; -const isCI = !!process.env.CI; + +// CI sets PLAYWRIGHT_NO_SERVER=1 to skip the local webServer and target a deployed URL. +const skipWebServer = process.env.PLAYWRIGHT_NO_SERVER === '1'; + +// Staging sits behind HTTP Basic Auth; production and local dev don't. +const httpCredentials = + process.env.PLAYWRIGHT_HTTP_USERNAME && process.env.PLAYWRIGHT_HTTP_PASSWORD + ? { + username: process.env.PLAYWRIGHT_HTTP_USERNAME, + password: process.env.PLAYWRIGHT_HTTP_PASSWORD, + } + : undefined; export default defineConfig({ testDir: './tests', - testIgnore: ['**/fixtures/**', '**/helpers/**'], + globalSetup: './tests/global-setup.ts', fullyParallel: true, - forbidOnly: isCI, - retries: isCI ? 2 : 1, - // Cap local workers. `next dev` compiles routes on-demand; unbounded - // parallelism starves the server and the first-hit latency blows past - // navigationTimeout on pages the compiler hasn't warmed yet. 2 is - // deliberately conservative — the suite still runs under 3 min. - workers: isCI ? 1 : 2, - reporter: [['list'], ['html', { open: 'never' }]], + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 1 : 0, + // 1 worker locally: `next dev` compile-on-first-hit contention causes + // intermittent net::ERR_ABORTED flakes when multiple workers hammer the + // same uncompiled dynamic route at once. CI hits a prebuilt deployed URL + // so parallelism is fine there; but scheduled runs use 1 worker anyway + // to keep behavior consistent. + workers: 1, + reporter: [['html', { open: 'never' }], ['list']], + use: { baseURL, - locale: 'en-US', - trace: 'on-first-retry', + httpCredentials, testIdAttribute: 'data-testid', - actionTimeout: 15_000, - // Raised from 30s — accommodates `next dev`'s compile-on-first-hit cost - // when multiple workers each land on cold routes simultaneously. - navigationTimeout: 60_000, + trace: 'on-first-retry', + screenshot: 'only-on-failure', + video: 'retain-on-failure', }, + projects: [ { name: 'chromium', @@ -40,23 +52,17 @@ export default defineConfig({ use: { ...devices['Desktop Safari'] }, }, ], - // Server selection: - // - PLAYWRIGHT_NO_SERVER=1 → don't manage one (e.g. workflow already - // started it, or running against a deployed URL). - // - APP_ENV=staging|prod → pre-built production server (`next start`). - // The CI workflow runs `next build` first, then this block boots - // `next start` against the chosen env file. See - // .github/workflows/playwright-scheduled.yml. - // - else → local `yarn dev`. - webServer: process.env.PLAYWRIGHT_NO_SERVER + + webServer: skipWebServer ? undefined : { - command: - process.env.APP_ENV === 'staging' || process.env.APP_ENV === 'prod' - ? `cross-env NODE_ENV=production APP_ENV=${process.env.APP_ENV} next start -p 3005` - : 'yarn dev', - url: baseURL, - reuseExistingServer: !isCI, + command: 'yarn dev', + // Health-check against a known-200 route. Root (/) currently returns + // 404 in dev (see QA_PLAN Phase 1 findings), so we can't use baseURL. + // English is served at the root (no /en prefix) — use the canonical + // path. + url: `${baseURL}/uxcore`, + reuseExistingServer: true, timeout: 180_000, stdout: 'ignore', stderr: 'pipe', diff --git a/qa-config.yml b/qa-config.yml new file mode 100644 index 00000000..131b56c6 --- /dev/null +++ b/qa-config.yml @@ -0,0 +1,116 @@ +# QA agent configuration — read by .claude/skills/keepsimple-qa/SKILL.md +# The agent re-reads this every run; edit freely. + +environment: + production: https://keepsimple.io + staging: # leave empty for now, fill in when we have one + +# Locales to test. The first locale is the default. +# hy falls back to en at runtime, so test it anyway. +locales: + - en + - ru + - hy + +# How locales appear in URLs: +# - default-unprefixed: en routes are /uxcore, ru routes are /ru/uxcore +# - all-prefixed: every route has a locale segment +# - no-prefix: single-locale, no locale segments +locale_url_strategy: default-unprefixed + +# Browser viewports. +viewports: + desktop: { width: 1920, height: 1080 } + mobile: { width: 390, height: 844 } + +# Timezone for report timestamps. +report_timezone: Asia/Yerevan + +# Design-system reference. Skill reads this and flags violations as +# `design-system` findings. +design_system: uxcore-visual-design-system.md + +# Sections — groups of routes that conceptually belong together. +# The smoke profile runs against the section marked `default: true`. +# Each section may declare: +# - primary_interaction: the main thing to exercise on the hub route +# during the fixed pass. +# - auth_required: list of routes that need authentication; the +# agent records them in coverage as +# "skipped — auth required (configured)" and +# does not attempt to reach them. + +sections: + + site: + default: true + routes: + - / + - /tools + - /contributors + - /company-management + - /auth + primary_interaction: "Click the primary CTA in the hero on / and confirm navigation works." + + uxcore: + routes: + - /uxcore + - /uxcore/96-conjunction-fallacy + - /uxcore/75-barnum-effect + primary_interaction: "Open the bias detail modal on a card and verify it opens without console errors and shows the bias content." + + uxcg: + routes: + - /uxcg + - /uxcg/what-causes-users-to-make-mistakes-when-using-our-product + - /uxcg/what-to-consider-when-adding-new-functionality-into-product + - /uxcg/how-to-involve-users-into-product-testing + - /uxcg/involving-users-in-product-development + - /uxcg/what-if-our-team-members-dont-share-their-opinion + primary_interaction: "Use the stage selector on /uxcg and verify the question list updates to match the selected stage." + + uxcp: + routes: + - /uxcp + primary_interaction: "Apply the priority filter and verify the visible cards change accordingly." + + uxcat: + routes: + - /uxcat + - /uxcat/ongoing + - /uxcat/start-test + - /uxcat/test-result + auth_required: + - /uxcat/ongoing + - /uxcat/start-test + - /uxcat/test-result + primary_interaction: "Click the primary CTA on the hub /uxcat and verify it routes correctly. Do NOT submit the test (creates real data)." + + uxcore-api: + routes: + - /uxcore-api + # primary_interaction omitted — agent clicks the most prominent element + + longevity-protocol: + # No hub route exists; first route below is treated as the hub. + routes: + - /tools/longevity-protocol/about-project + - /tools/longevity-protocol/environment + - /tools/longevity-protocol/habits/diet + - /tools/longevity-protocol/habits/lifestyle + - /tools/longevity-protocol/habits/sleep + - /tools/longevity-protocol/habits/study + - /tools/longevity-protocol/habits/supplements + - /tools/longevity-protocol/habits/workout + - /tools/longevity-protocol/results + primary_interaction: "Click a habit link in the side nav from /tools/longevity-protocol/about-project and verify the destination habit page loads without console errors." + + articles: + routes: + - /articles + - /articles/create-behavioral-user-personas + - /articles/table-of-contents + - /articles/what-is-a-project + - /articles/awareness-test + - /articles/summarize-like-your-job-depends-on-it + primary_interaction: "From /articles, click the first article in the listing and verify it navigates to a working article page that renders without errors." diff --git a/qa-runs/README.md b/qa-runs/README.md new file mode 100644 index 00000000..9c611ca4 --- /dev/null +++ b/qa-runs/README.md @@ -0,0 +1,108 @@ +# QA Agent + +Manual QA pass for keepsimple.io, driven by Claude via the Playwright +MCP server. Visits production routes, watches console + network, +interacts with key features, produces a findings report. + +This is the **agent QA** system. The Playwright spec suite (in +`tests/`, see `QA_PLAN.md`) is a separate effort. + +## How to run + +In a fresh Claude Code session at the repo root, type a slash command: + +| Command | What it runs | Time | +| --------------------- | --------------------------------------------------------------------------------------- | ------------- | +| `/qa-init` | Interactive wizard to (re-)write `qa-config.yml`. | ~5 min | +| `/qa-smoke` | Default section × en × desktop. Fast post-deploy check. | ~15 min | +| `/qa-locale-smoke` | Default section × all locales × desktop. Locale-routing check. | ~25 min | +| `/qa-canonical` | All sections × all locales × desktop. Source of truth for desktop findings. | ~90-120 min | +| `/qa-mobile-followup` | All sections × all locales × mobile. Pairs with canonical for full matrix. | ~90 min | +| `/qa-full-matrix` | Everything in one pass: all sections × all locales × desktop + mobile. | 3-4 hours | +| `/qa-deploy-check` | Surgical post-deploy regression. Only audits routes that changed OR have open findings. | ~30 min | +| `/qa-retest` | Verify specific finding IDs after a deploy or local fix. | ~5 min per ID | + +Always start in a **fresh** Claude Code session. Same chat ≠ same +session. Open chat memory pollutes results. + +After the run finishes, the agent writes the report to `reports/` and +returns a short summary in chat. The agent renders the HTML itself +(`node .claude/keepsimple-qa/render-report.js …`) at the end of every pass — or +you can re-render manually with `yarn render-report reports/.md`. + +## What the system is made of + +| File | Role | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| `.claude/keepsimple-qa/SKILL.md` | The method playbook. Pre-flight, fixed pass, exploratory pass, severity rubric, finding schema. Loaded by every slash command. | +| `.claude/keepsimple-qa/PROFILES.md` | Profile catalogue — what each profile's scope actually covers. | +| `.claude/keepsimple-qa/helper.mjs` | Helper CLI: fingerprints, axe, vitals, screenshots, pixelmatch visual-diff. | +| `.claude/keepsimple-qa/render-report.js` | Markdown → HTML renderer. | +| `.claude/commands/qa-*.md` | Slash commands, one per profile. Each loads the skill and pins scope. | +| `qa-config.yml` | Sections, routes, locales, viewports, design-system reference, per-section `auth_required:` and `primary_interaction:` declarations. | +| `known-issues.md` | Suppressions. Anything listed here is intentional and not a finding. | +| `qa-runs/findings-register.md` | Living index of open findings. Updated manually after each run. | +| `qa-runs/state/` | Helper-managed: route fingerprints from `batch-fingerprint`. Gitignored. | +| `qa-runs/baselines/` and `screenshots/` | Helper-managed: visual-regression baseline + current PNGs. Gitignored. | +| `reports/` | Per-run reports (`.md` source + `.html` rendered). Source of truth for finding details. | + +The skill defines **how**. The profile (and slash command) defines +**what scope**. The register tracks **what's currently broken**. + +## After a run + +The agent does NOT auto-update the findings register. You do. + +For each new finding the report surfaces: + +1. Decide if it's real (cross-check against `known-issues.md` if + you're unsure). +2. Add a row to `qa-runs/findings-register.md` under "Open + findings" with a new `F#` ID. +3. Reference the report it came from in `last-confirmed`. + +For findings the report shows as persistent: update `last-confirmed` +to today's date. Status stays the same. + +For findings the report shows as refuted (after a fix and retest): +flip status `fixed-deployed` → `verified-fixed`. After one more +clean run with no regression, move the row to the Archive section. + +## Fix flow + +When a finding gets fixed: +open → fixed-locally → fixed-deployed → verified-fixed → archive +↑ | +| ↓ +(you write the fix) (one more clean run later) + +Update register status manually as it moves through the stages. +Run `/qa-retest` after a deploy to verify `fixed-deployed` → +`verified-fixed`. Or run `/qa-deploy-check` for a broader surgical +sweep that re-audits anything that changed plus all open findings. + +## Constraints worth knowing + +- The agent never logs in or submits forms that create real data. +- Routes listed under a section's `auth_required:` field in + `qa-config.yml` are intentionally skipped. The agent records them + as `skipped — auth required (configured)` in the coverage table. +- The agent will not refile anything in `known-issues.md`. If + something there should actually be fixed, remove it from + known-issues first. +- Reports stick around indefinitely. They're the diff chain — each + run references prior runs by name. Don't delete old ones. + +## When something's off + +- Slash command shows "no matching commands" → restart Claude Code + panel. New files don't always get picked up live. +- Agent runs but produces no report → check Playwright MCP is + connected. Should show in available tools. +- Agent reports findings already in `known-issues.md` → known-issues + isn't being read. Check the file path is correct from repo root. +- Report references a build ID that doesn't match current production + → you're looking at a pre-deploy report. Check `reports/` for the + most recent one. +- Helper command fails with `Cannot find module 'pngjs'` (or similar) + → run `yarn install`; the kit's devDeps may not be installed yet. diff --git a/report.md b/report.md index 9775b5e6..32e588c2 100644 --- a/report.md +++ b/report.md @@ -1,3 +1,8 @@ +> **TODO (2026-05-01):** Cypress was removed and replaced by Playwright + the QA-agent setup +> (`tests/`, `.claude/keepsimple-qa/`). This report still references Cypress as the test +> framework (see §1 stack table and §10). Review and refresh against the current state of +> the repo before relying on it. + ● KeepSimpleOSS Codebase Audit Report 1. Stack & Tooling diff --git a/tests/global-setup.ts b/tests/global-setup.ts new file mode 100644 index 00000000..e869cb1e --- /dev/null +++ b/tests/global-setup.ts @@ -0,0 +1,6 @@ +// Playwright global setup. The config references this file; intentionally a +// no-op until there is shared setup work to do (auth state, seeded fixtures, +// etc.). +export default async function globalSetup(): Promise { + return; +} diff --git a/tests/p1/mobile-navigation.spec.ts b/tests/p1/mobile-navigation.spec.ts index 3fea73e7..7bd1254e 100644 --- a/tests/p1/mobile-navigation.spec.ts +++ b/tests/p1/mobile-navigation.spec.ts @@ -1,6 +1,6 @@ import { expect, test } from '../fixtures/base'; -// 390x844 = existing Cypress convention (see QA_RECON.md §5). +// 390x844 = standard mobile viewport (see QA_RECON.md §5). test.use({ viewport: { width: 390, height: 844 } }); // --------------------------------------------------------------------------- diff --git a/tsconfig.json b/tsconfig.json index 623b1068..c67bbbf8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "types": ["cypress", "node", "./cypress/support/commands"], + "types": ["node"], "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, @@ -34,13 +34,6 @@ }, "incremental": true }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - "cypress", - "cypress/support/commands.d.ts", - "cypress/**/*.ts" - ], + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules", "tests", "playwright.config.ts"] } diff --git a/uxcore-visual-design-system.md b/uxcore-visual-design-system.md new file mode 100644 index 00000000..c1383674 --- /dev/null +++ b/uxcore-visual-design-system.md @@ -0,0 +1,725 @@ +--- +name: UXCore Design System +description: Build UI components and pages that match the UXCore visual identity — covers typography, colors, spacing, components, motion, and do/don't rules for UXCore, UXCG, UXCP, and UXCat. +--- + +# Workflow + +When triggered, follow these steps: + +1. **Read this file fully** before generating any code. Internalize the design tokens, component specs, and do/don't rules. +2. **Identify which visual scope applies** — is this for the shared platform style (UXCore/UXCG/UXCP) or for UXCat? This determines accent colors, heading weights, and animation behavior (see Section 10b). +3. **Build the component or page** using the exact values from this spec — font sizes, colors, spacing, border-radius, shadows, and easing curves. Do not approximate or substitute. +4. **Check against Section 11 (Do / Don't Rules)** before finalizing. These rules override any instinct to add gradients, pill buttons, glassmorphism, or decorative elements. +5. **Reference `AGENTS.md`** at the repo root for code-structure rules (component pattern, file layout, prop typing, etc.) whenever you write or modify actual code files. + +# UXCore Visual Design System + +> A standalone design specification for building interfaces that match the UXCore visual identity (keepsimple.io). No codebase access needed — every value is resolved and every component described well enough to implement from scratch in any tech stack. +> +> **Two visual scopes:** +> +> - **Sections 1–10** describe the **shared platform style** used across UXCore (bias explorer), UXCG (UX Case Generator), and UXCP (UX Companion Personas). These three tools share the same typography, colors, layout, and component styles. +> - **Section 10b** describes **UXCat-specific styling** — the quiz/gamification tool that has its own distinct visual personality built on top of the shared foundation. +> +> Section 11 (Do / Don't Rules) and Tone left as `____` for manual completion. + +--- + +## Design Philosophy + +The platform has **two visual modes** that share a common foundation but serve different moods. + +**The core platform (UXCore, UXCG, UXCP)** is quiet, structured, and informational. It presents dense educational content — cognitive biases, UX questions, persona-driven analysis — without competing with that content for attention. The palette is desaturated and cool, anchored by a muted steel-blue and generous white space. Typography is deliberately lightweight for headings (thin Red Hat Display) and utilitarian for body text (Lato), letting the content hierarchy do the work rather than visual flair. Interactions are subtle: cards lift a few pixels on hover, borders shift color to confirm focus, and the navigation pill slides between tabs with a smooth, physical-feeling ease. The overall impression should be a well-organized reference tool — closer to a thoughtfully designed textbook than a marketing landing page. + +**UXCat (the quiz tool)** inherits the same layout grid, font stack, and color tokens, but layers on a warmer, more energetic personality. Orange replaces steel-blue as the accent for headings and CTAs. Content appears through staggered fade-in sequences. A progress-and-reward system adds gamification elements — achievement badges with gold glow effects, animated leaderboards, and a branded brain-spinner loading animation. UXCat feels like taking a well-designed exam in a serious but encouraging environment. + +--- + +## 1. Typography + +### Fonts + +- **Primary font (body text):** Lato (fallback: Arial, serif). A clean, humanist sans-serif used for all body copy, buttons, labels, and UI chrome across every tool. +- **Heading font (page and section titles):** Red Hat Display (fallback: sans-serif). Used at light weight for a refined, modern feel on page titles — shared by UXCore, UXCG, UXCP, and UXCat. +- **Monospace font (bias number labels, meta tags):** IBM Plex Mono (fallback: sans-serif). Used for small uppercase category labels on bias detail views — gives a technical, cataloging feel. +- **Font loading:** Self-hosted via `@font-face` with `font-display: swap` for Lato and `font-display: optional` for secondary fonts. Not loaded from Google Fonts CDN. +- **Special-purpose fonts:** + - Noto Sans Armenian (sans-serif fallback) — full locale override for Armenian language, applied globally when locale is `hy` + - Source Serif 4 (Regular/Bold/SemiBold) — article and long-form content layouts + - Cormorant Garamond (Regular/Medium) — article layouts, Russian locale variant + - Aboreto — article titles (decorative) + - See [Section 10b (UXCat)](#10b-uxcat-specific-styles) for additional fonts used only in the quiz/gamification context. + +### Type Scale + +| Role | Size | Weight | Line-height | Notes | +| ----------------------- | ------- | ----------- | ----------- | ------------------------------------------------------------------------------------------------------- | +| Page title (h1) | 36px | 300 (light) | default | Intentionally thin — lets the content breathe. Drops to 24px / weight 600 on mobile (≤800px). | +| Section heading (h2) | 24px | 600–800 | ~28.8px | Used for quiz questions and result titles. Weight 800 for maximum-emphasis question text. | +| Subsection heading (h3) | 18–20px | 400–700 | ~25.6px | 20px for mottos and API section headers; 18px for quiz sub-questions (weight 700). | +| Body text | 16px | 400 | 150% (24px) | Standard body copy. Some contexts use 140% line-height. Drops to 14px at ≤1440px on data-dense screens. | +| Small / caption text | 12px | 400–600 | default | Used for axis labels, search result hints, and small meta labels. Weight 600 for emphasized labels. | +| Label / tag text | 14px | 400 | ~17px | Buttons, filter controls, nav items, tooltip content, sidebar descriptors. | +| Navigation links | 14px | 400 | default | Always uppercase. | + +### Text Rules + +- **Letter spacing on headings:** -0.01em on bias detail modal titles (a very subtle tightening). +- **Uppercase (`text-transform: uppercase`)** is used on: navigation items, bias number labels in detail modals, section headers, view switcher controls, panel headers, and completion bar labels. It signals "system chrome" vs. content. +- **Max line width for readability:** Content containers are capped at 900px. This keeps body text to a comfortable ~75-character line length at 16px. + +--- + +## 2. Colors + +### Brand / Primary + +- **Primary brand color:** `#28587b` — a muted steel-blue. The dominant accent across UXCore, UXCG, and UXCP. Used for primary buttons, link text, page headings, active filter states, and interactive affordances. +- **Primary hover state:** `#28587b` (no lightening — primary buttons don't shift color on hover; the interaction feedback comes from border changes on outline variants). +- **Primary active/pressed:** `#28587b` (used as background on active filter buttons with white text). + +### Secondary Accent Colors + +- **Secondary blue:** `#5b88bd` — a lighter, softer blue. Used for active-state borders (selected answers, active product tabs), interactive link text in modals, active toggle controls, checkboxes, and radio-style selections. This is the "something is selected/active" color — shared across all tools. +- **Orange accent:** `#de915b` — warm terracotta orange. Used for call-to-action buttons (start actions, submit). In UXCat, this is the dominant accent for titles and primary CTAs (see Section 10b). +- **Orange hover:** `#cd7232` — deeper, toasted orange. Orange buttons darken on hover rather than lighten. +- **Purple accent:** `#a36aa4` — muted plum. Used sparingly for secondary feature tooltips, tab decorations, date stamps, and highlight actions. + +### Backgrounds + +- **Page background (global):** `linear-gradient(180deg, #f9fafb 0%, #e8e8e8 100%)` with fallback `#f9fafb` — a very subtle warm-to-cool gray gradient from top to bottom. The page never feels stark white. +- **Page background (tool pages):** `#fafafa` — a flat, near-white gray used as the canvas for all main tool interfaces across UXCore, UXCG, UXCP, and UXCat. +- **Card / content block background:** `#fff` — pure white. Cards sit on the `#fafafa` canvas, creating a very gentle lift even before shadows. +- **Card hover background:** No background change on hover — hover feedback is handled through shadow deepening and subtle vertical movement. +- **Modal / overlay backdrop:** `rgba(0, 0, 0, 0.35)` — a light dim, not a heavy blackout. Content behind the modal remains partially visible. +- **Spinner / loading overlay:** `rgba(0, 0, 0, 0.6)` — darker than modal backdrops to fully focus attention on the loading indicator. +- **Header background:** `#fff` (white, with a subtle bottom shadow to separate from content). +- **Hover highlight (UI chrome):** `#f1f1f1` — used for nav item hover states, dropdown item highlights, and user menu hover. A barely-there gray tint. + +### Text + +- **Primary text:** `#252626` — near-black with just enough warmth to avoid harshness. Some components also use pure `#000`. +- **Secondary / muted text:** `#9e9e9e` — medium gray for subtitles, mottos, short descriptions, and secondary labels. Also `rgba(0, 0, 0, 0.65)` (a 65% black) for slightly more prominent secondary text on sidebar labels. +- **Disabled text:** `#c4c4c4` — light gray signaling non-interactive or inactive elements. +- **Link color (default):** `#28587b` (primary blue, same as brand); some secondary links use `#5b88bd` (lighter blue). +- **Link color (hover):** An underline appears on hover (no color change). Underline-on-hover is the standard link interaction pattern. +- **Link color (visited):** Not styled — visited links look identical to unvisited links. + +### Borders & Dividers + +- **Default border:** `1px solid #d9d9d9` — a light, neutral gray. Used on buttons, separators, section dividers, and card outlines. +- **Divider / separator:** `#d9d9d9` is the primary separator color; `#c4c4c4` (slightly darker gray) appears on secondary dividers like header section separators. +- **Input border:** `1px solid #cbcbcb` — marginally darker than the general border color, giving form fields a bit more definition. +- **Input border on focus:** `#6d6d6d` — a medium-dark gray. Notably, focus does _not_ use the brand blue — it uses a neutral dark gray, keeping the field feeling calm rather than highlighted. + +### Status / Feedback + +- **Error / danger:** `red` (plain CSS red, used for form validation messages). +- **Success:** `#4caf50` (Material-style green for checkmarks); `#2db675` (progress bar fill); `#77a34b` / `#42c256` (glowing tooltip accents). +- **Warning:** Not used in the current design system. +- **Info:** Not used in the current design system. + +### UXCG Stage Colors + +Each UX Case Generator stage has a distinct color identity, used for icon strokes and category swatches: + +| Stage | Default stroke | Selected stroke | Swatch background | +| ------------- | ------------------------ | ----------------------- | ---------------------- | +| Team Assembly | `#6087ab` (steel blue) | `#6087ab` | `#bbe4f2` (light sky) | +| Development | `#186930` (forest green) | `#669e77` (sage) | `#c5eadd` (mint) | +| Marketing/BD | `#9e4579` (berry) | `#e5ccee` (lavender) | `#dbcad1` (dusty rose) | +| Released | `#c3a70c` (dark gold) | `#fff2ae` (pale yellow) | — | +| Monitoring | `#ff7300` (vivid orange) | `#ffd9b9` (peach) | — | + +### Bias Category Colors + +The four cognitive bias categories each have a background tint and border/accent color. Used across UXCore (bias explorer), UXCG (case connections), and UXCP (persona analysis): + +| Category | Background | Border/accent | Label text | +| ------------------------ | ----------------------- | ------------------------ | ------------------------ | +| Too much information | `#c7ebf8` (ice blue) | `#4c8cc1` (ocean blue) | `#4c8cc1` | +| Not enough meaning | `#c5eadd` (soft mint) | `#75b3b3` (teal) | `#75b3b3` | +| Need to act fast | `#e9dfe3` (blush gray) | `#80739b` (dusty purple) | `#573e48` (wine) | +| What should we remember? | `#e9e6ff` (pale violet) | `#8073ff` (indigo) | `#5c578d` (slate violet) | + +Note: These are intentionally pastel and desaturated — they color-code without screaming. + +### UXCP Priority Colors + +Priority levels (high/medium/low) are distinguished by icon shape, not by color. The active filter button uses the primary brand color (`#28587b` background, white text) regardless of which priority level is selected. + +--- + +## 3. Spacing + +### Base Unit + +No formal spacing token system. Values cluster around **multiples of 4px and 8px**, which is the effective base grid. This applies uniformly across UXCore, UXCG, UXCP, and UXCat. + +### Common Values + +| Token | Value | Typical use | +| ----- | ----- | ------------------------------------------------------------------- | +| XS | 4px | Tight gaps between inline elements, small margins | +| SM | 8px | Standard gaps in flex containers, icon spacing, small padding | +| MD | 16px | Mobile page padding, vertical section spacing, common inner padding | +| LG | 24px | Section vertical gaps, content area spacing | +| XL | 32px | Major section breaks, button group top margins | +| 2XL | 48px | Large section bottom margins, content-to-loader spacing | + +Intermediate values also appear: 10px (input inner padding), 14px (filter bar padding), 20px (modal body padding), 28px (modal side padding). + +### Page Layout + +- **Max content width:** 900px for all standard content pages across every tool (question lists, persona builders, quiz views, API docs, user profiles). The bias explorer (folder/tree view) in UXCore uses a wider 1360px max-width. +- **Page horizontal padding (desktop):** Content is centered via auto margins with `max-width: 100%`, so there is no fixed side padding — the 900px cap and centering handle it. +- **Page horizontal padding (mobile):** 16px on each side (≤800px). Some screens use 8px side margins or 20px padding at narrow widths. +- **Content top margin from header:** 94px on desktop (accounts for the 46px fixed header plus breathing room). Reduces to 28–35px on mobile where the header is no longer fixed. + +--- + +## 4. Breakpoints + +| Name | Value | Role | +| ------- | ----------------- | ---------------------------------------------------------------------- | +| Mobile | ≤800px | Primary mobile layout switch | +| Tablet | ≤1010px | Intermediate adjustments; desktop navigation collapses | +| Desktop | ≤1440px | Font-size scaling (16px → 14px on dense screens) | +| Wide | ≥2500px / ≥3500px | Large-display scaling (headings and body text grow via viewport units) | + +### What Changes at Each Breakpoint + +- **At mobile (≤800px):** Desktop horizontal navigation disappears, replaced by a tool-name dropdown (PageSwitcher). Content gets 16px side padding. Page titles shrink from 36px light to 24px semibold — the weight increase compensates for the smaller size to maintain hierarchy. Subtitles drop from 18px to 16px. Content stacks vertically. Search inputs lose their border-radius (become flat-edged to span full width). +- **At tablet (≤1010px):** Top content margin shrinks from 94px to ~28–35px. Multi-column layouts (like the bias folder view) collapse to single column. Navigation header simplifies. +- **At desktop (≤1440px):** A density pass — body text, labels, and controls scale from 16px to 14px in data-heavy components (inputs, filter bars, test results, API docs). This fits more content without scrolling on standard laptops. +- **At wide (≥2500px / ≥3500px):** The bias explorer and its labels scale up using viewport-relative units (e.g., headings grow to ~4vh, descriptions to ~1.5vh) so the interface doesn't feel tiny on ultrawide or 4K displays. + +--- + +## 5. Component Styles + +### Cards + +Cards are the primary content containers across all tools — used for bias detail boxes, UXCG case question wrappers, UXCP persona result sections, and general info panels. + +**At rest:** + +- Background: `#fff` (white, sitting on the `#fafafa` page canvas). +- Border radius: 8px for standalone content cards; 4px for inline interactive cards and smaller elements. +- Border: most cards rely on shadow alone (no visible border); some section cards use `1px solid #d9d9d9`. +- Shadow (resting): `0px 2px 8px rgba(0, 0, 0, 0.05)` — barely visible, just enough to lift the card off the background. +- Shadow (elevated/prominent): A multi-layered progressive shadow for high-importance cards: `0px 8px 17px rgba(0, 0, 0, 0.05), 0px 30px 30px rgba(0, 0, 0, 0.04), 0px 68px 41px rgba(0, 0, 0, 0.03), 0px 122px 49px rgba(0, 0, 0, 0.01), 0px 190px 53px rgba(0, 0, 0, 0)`. This creates a realistic, diffused paper-floating effect. +- Padding: 15px vertical, 20px horizontal (standard); 10px top/bottom, 16px left / 32px right (answer cards with action space). + +**On hover (interactive cards):** + +- Cards lift slightly — 8px upward shift with a smooth spring-like ease (`transform: translateY(-8px)`, `transition: transform 320ms cubic-bezier(0.22, 1, 0.36, 1)`). The cubic-bezier gives a slight overshoot that feels physical. +- No background color change on hover — the movement and shadow shift are the only feedback. + +**Selected state:** + +- Border becomes `2px solid #5b88bd` (secondary blue). +- A very faint blue wash appears: `rgba(147, 183, 255, 0.2)` background. + +**Grid spacing:** + +- Gaps between cards: 13px (category tag grids in UXCG); 3px (dense bias lists in UXCore explorer view). + +### Buttons + +Buttons come in several variants, all compact and understated — they don't shout. Shared across every tool. + +**Default (outline) button:** + +- Background: `#fff`. Text: `#000`. Border: `1px solid #d9d9d9`. Border-radius: 2px (very slightly rounded — nearly square). Padding: 6px 16px. Height: 32px. Font-size: 14px (inherited from label text scale). +- On hover: border shifts to `#28587b` and text shifts to `#28587b` — the button "activates" with the brand color. Transition: 200ms all properties. +- When disabled: `cursor: not-allowed`, no hover effect. + +**Primary (filled) button:** + +- Background: `#28587b` (brand blue). Text: `#fff`. Border: `1px solid #d9d9d9`. Border-radius: 2px. Same sizing as default. +- On hover: no visible change (already in its active color). The primary button is always "on." + +**Secondary (outline, softer) button:** + +- Background: `#fff`. Text: `rgba(0, 0, 0, 0.65)` (muted). Border: `1px solid #d9d9d9`. Border-radius: 4px (slightly more rounded than default). Padding: 8px 18px. + +**Orange (CTA) button:** + +- Background: `#de915b` (terracotta). Text: `#fff`. On hover: darkens to `#cd7232`. A warm, attention-drawing variant for start/submit actions. + +**Orange outline button:** + +- Background: `#fff`. Text/border: `#de915b`. On hover: fills to `#cd7232` background with white text — a smooth inversion. + +**Blue outline button:** + +- Background: `#fff`. Text/border: `#28587b`. On hover: fills to `#28587b` background with white text. + +**Big button variant:** + +- Height: 54px (45px on mobile). Used for major actions. + +**Disabled state:** + +- Background: `#d9d9d9`. Text: `#c4c4c4`. `cursor: not-allowed`. No hover effect. + +**Loading state:** + +- An inline spinner appears inside the button (CSS rotation: 360deg, 500ms, infinite, linear). + +**Transition:** All button properties animate at 200ms ease. + +### Input Fields + +Clean, minimal form inputs used across all tools. + +**At rest:** + +- Height: 32px minimum. Background: `#fff`. Border: `1px solid #cbcbcb`. Border-radius: 4px. Padding: 6px 10px. Font-size: 16px. +- Placeholder text: `#ababab` (light gray, clearly secondary). + +**On focus:** + +- Border darkens to `#6d6d6d` (a calm, neutral dark gray — not the brand blue). Outline removed. Any associated icon also shifts to `#6d6d6d`. +- This neutral focus state is deliberate: it confirms the field is active without creating a "selected" feeling that might conflict with validation states. + +**With icon:** + +- Right padding increases to 34px to make room for an inline icon (search, clear, etc.). + +**Error state:** + +- Error message appears below the field in `red`, 12px font, with a 200ms slide-in transition. + +**Responsive:** + +- Font-size drops to 14px at ≤1440px on dense screens. + +### Tags / Badges / Chips + +Small, pill-like labels used across tools — categorizing biases in UXCore, marking UXCG stages, and labeling UXCP relevance. + +- Border-radius: 2px (very subtly rounded — almost rectangular, not pill-shaped). +- Padding: 3px 17px (generous horizontal padding for readability). +- Font-size: 12px (default) or 14px (large variant, 32px height). +- Text color: `#fff` (always white — the background color carries the category meaning). +- Background: set per-category from the Bias Category Colors or UXCG Stage Colors tables (section 2). No default background — always determined by semantic context. +- Border: none. + +### Toggles / Switches + +A two-button segmented control (not a sliding iOS-style toggle). Used for binary choices like Yes/No in the UXCP persona builder. + +- Two buttons sit side by side, sharing a border. Left button has `border-radius: 4px 0 0 4px`, right has `0 4px 4px 0`. +- Each button: width 60px, font-size 14px. +- **Inactive:** white background, `#000` text, `1px solid #c4c4c4` border. +- **Active:** white background, `#5b88bd` text (secondary blue), border color shifts to `#5b88bd`. +- **Hover (inactive):** a whisper of blue tint appears: `rgba(53, 158, 255, 0.12)` background. + +### Navigation + +A minimal, horizontally tabbed header bar spanning the full viewport width. Shared across all four tools — the nav items are UXCore, UXCG, UXCP, and UXCat. + +**Desktop (>1010px):** + +- Height: 46px. Background: `#fff`. Box-shadow: `0px 2px 8px rgba(0, 0, 0, 0.05)` (the subtle "SM" shadow). +- Tabs are uppercase text, 14px, color `rgba(0, 0, 0, 0.85)`. +- **Active tab indicator:** A dark pill (`rgba(0, 0, 0, 0.85)` background, 4px border-radius, 33px height) that slides horizontally behind the active tab. Text turns to `#fafafa` (near-white) when active. The pill slides with `transition: transform 450ms cubic-bezier(0.22, 0.95, 0.35, 1)` — a fast-start, soft-landing ease that feels physical and satisfying. +- **Hover (inactive tabs):** `#f1f1f1` background tint with 4px border-radius. Subtle enough to not distract from the active indicator. + +**Mobile (≤1010px):** + +- The horizontal tab bar disappears entirely. It's replaced by a PageSwitcher — a single dropdown-style button showing the current tool name, 40px tall, `1px solid #c4c4c4` border, 4px border-radius, with 16px page padding. + +### Modals / Dialogs + +Clean, centered overlays used across all tools — for bias detail views (UXCore), case detail views (UXCG), persona confirmations (UXCP), and various settings. + +- **Backdrop:** `rgba(0, 0, 0, 0.35)` — a light dim that keeps the page context partially visible. +- **Modal panel:** `#fff` background. Border-radius: 4px. Border: `1px solid #cbcbcb`. Box-shadow: `0px 4px 16px rgba(0, 0, 0, 0.15)`. +- **Title:** 16px, color `#28587b` (brand blue). Alternate title styles: `#000` (strong), `#9e9e9e` (gray/subtle). +- **Body padding:** 16px top/bottom, 28px sides (generous horizontal space for reading). +- **Header area:** 13px top padding, 8px bottom padding, with a bottom border when present. +- **Close button:** Positioned 15px from the right edge. +- **Entry animation:** Modals appear immediately (no slide-in or scale animation). Some secondary modals use a slow 1s transition. +- **Z-index stacking:** Backdrop at z-45, modal content at z-80 (well above any dropdown or tooltip). + +### Tooltips + +Small, informational popovers that appear on hover for help icons and contextual hints. Shared across all tools. + +**Light variant (default):** + +- Background: `#fff`. Text: `#000`. Border: `1px solid #cbcbcb`. Border-radius: 4px. Box-shadow: `0px 4px 16px rgba(0, 0, 0, 0.15)`. Padding: 10px. Font-size: 14px. Max-width: 380px (preferred width 300px). +- Has a small CSS arrow (8px triangle) pointing toward the trigger element. + +**Dark variant:** + +- Background: `#000`. Text: `#fff`. Border-radius: 4px. Padding: 2px 6px. Font-size: 12px. Compact and label-like — used for quick, single-line hints. + +--- + +## 6. Icons & Images + +### Icon System + +- **No shared Icon wrapper component.** Icons are either inline SVG (React components) or static `.svg` files referenced by URL. This pattern is the same across all tools. +- **Common sizes:** 14–25px for UI icons. Typical: 20×20 (search), 25×25 (header nav), 16–18px (buttons and small actions), 13×17 (info/question marks). +- **Icon color:** Usually inherits text color via `fill: currentColor`, or is set explicitly (e.g., `#28587b` for brand-colored icons, `#5b88bd` for interactive states, `#fafafa` for icons on dark backgrounds). +- **Style:** Flat, outlined SVGs — not filled/solid. UXCG stage icons use stroke-based drawing on circles and paths. + +### Images + +- **Optimization:** Next.js `Image` component is the standard; however, many images set `unoptimized: true` for static SVGs and small assets. Remote image domains are whitelisted for user avatars (Google, Discord). +- **Illustration style:** SVG-based, flat/outlined. No photographic hero images. Background textures for UXCG category tags use small PNG cards (170×250px) with a neutral gray placeholder for unselected state. +- **Avatar shape:** Circular (`border-radius: 50%`). + +### Language Flags + +- Three flags for the language switcher: English (UK flag), Armenian, Russian — all SVGs. +- Main switcher size: 22×15px. Dropdown size: 16×13px. + +--- + +## 7. Motion & Transitions + +### Timing + +| Category | Duration | Use | +| ------------------------------ | ---------- | --------------------------------------------------------------------------- | +| Micro (hover, focus) | 150–250ms | Button color/border shifts, label opacity fades, input focus border changes | +| Component (expand, tab switch) | 300–320ms | Tooltip show/hide, search expand, card hover lift, dropdown open | +| Navigation (pill slide) | 450ms | Active tab indicator sliding between nav items | +| Page / section | 500–1000ms | Staggered content fade-ins (UXCat), slide-in panels, layout transitions | + +### Easing + +- **Default easing:** `ease-in-out` — used for most fade and slide animations across all tools. +- **Card hover lift:** `cubic-bezier(0.22, 1, 0.36, 1)` — a fast-start, overshoot-then-settle curve that feels springy and physical. +- **Nav pill slide:** `cubic-bezier(0.22, 0.95, 0.35, 1)` — similar springy feel but slightly more damped for a heavier, satisfying slide. +- **Loading spinner:** `cubic-bezier(0.5, 0, 0.5, 1)` — symmetrical ease for continuous rotation. +- **Dropdown open:** `cubic-bezier(0.4, 0, 0.2, 1)` — Material-style standard easing. + +### Hover Effects + +- **Cards:** Lift upward by 8px with a springy ease (320ms). Shadow doesn't change explicitly — the movement itself creates the perception of increased depth. +- **Buttons:** Border and text color shift to brand blue (200ms). Orange buttons darken. Blue/orange outline buttons invert to filled on hover. +- **Links:** An underline appears (no color change, no fade — it's a binary appear/disappear). +- **Nav items:** A `#f1f1f1` background tint fades in with 4px border-radius. Associated icons go from 85% opacity to 100%. +- **Dropdown items / user menu:** `#f1f1f1` background highlight; user name text shifts to `#5b88bd`. + +### Loading States + +- **Primary loader (brain spinner):** A branded animation shared across the entire platform — three layered brain SVG images (brain outline, rotating circle, rotating gears) stacked at 50×50px, counter-rotating at 3s linear infinite. Displayed over a `rgba(0, 0, 0, 0.6)` overlay. This is the signature loading state. +- **Secondary loader (ring spinner):** A 32×32px CSS border-based spinner: `border: 4px solid #5b88bd`, animating at 1.2s with cubic-bezier easing. +- **Skeleton screens:** Used while content loads — flat `#ebebeb` rectangles with 4px border-radius that hold the space where content will appear. +- **Button loading:** An inline spinner rotates inside the button (500ms, linear, infinite). + +### Scroll Behavior + +- **Smooth scroll:** Used sparingly (only in specific selection views), not globally. +- **No scroll-triggered animations** — content is static once rendered. +- **Sticky elements:** Mobile view headers and folder-view sidebars use `position: sticky`. +- **Custom scrollbar:** Thin (6–8px), border-radius 5px, with a brand-tinted thumb (`rgba(40, 88, 123, 0.5)` — the primary blue at 50% opacity) on a transparent track. + +--- + +## 8. Shadows & Depth + +Shadows are always pure black at low opacity — never tinted. The system uses three tiers, shared across all tools: + +| Level | Value | Feel | Used for | +| ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------- | +| SM | `0px 2px 8px rgba(0, 0, 0, 0.05)` | Barely visible, just separates from background | Header bar, resting content cards, section headers | +| MD | `0px 4px 16px rgba(0, 0, 0, 0.15)` | Clearly floating, still subtle | Tooltips, dropdowns, user menus, modal panels, search popups | +| LG | `0px 8px 17px rgba(0, 0, 0, 0.05), 0px 30px 30px rgba(0, 0, 0, 0.04), 0px 68px 41px rgba(0, 0, 0, 0.03), 0px 122px 49px rgba(0, 0, 0, 0.01), 0px 190px 53px rgba(0, 0, 0, 0)` | Diffused, paper-floating-on-desk feel | Elevated content panels, answer cards, NPS survey, start-test cards | + +- **Shadow color:** Always pure black with opacity ranging from 0.01 to 0.15. No colored or tinted shadows. +- **Specialty:** Upward-facing shadow (`0px -4px 16px rgba(0, 0, 0, 0.15)`) for bottom-anchored action bars. See Section 10b for UXCat-specific achievement glow effects. + +--- + +## 9. Border Radius Scale + +| Token | Value | Used for | +| ----- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------- | +| None | 0px | Mobile search inputs (full-width, edge-to-edge) | +| SM | 2px | Buttons (default and primary), tags/badges, small inline elements. Nearly square — a signature detail. | +| MD | 4px | The dominant radius. Modals, tooltips, inputs, textareas, search fields, toggles, nav indicator pill, hover states, answer cards, filter buttons. | +| LG | 8px | Standalone content cards, result sections, statistics boxes. | +| XL | 15px | Achievement badge containers (UXCat-specific decorative element). | +| Full | 50% | Avatars, score indicators, loading spinners, circular decorative elements. | + +The design leans heavily on **4px** as the standard radius. The 2px radius on buttons is an intentional choice — it gives buttons a more precise, tool-like feel compared to the slightly softer 4px on containers. + +--- + +## 10. Z-Index Layers + +A simplified, recommended stacking order based on the patterns in the design: + +| Layer | Z-index | Elements | +| ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------ | +| Base content | 0 | Default stacking — page content, card interiors | +| Local lifts | 1–5 | Header chrome, input fields above their error messages, tooltip arrows, snackbar notifications | +| Dropdowns / tooltips | 10 | Tooltip popups, user dropdowns, modal overlays (bias detail, UXCG detail) | +| Fixed navigation | 44–50 | Dropdown menus, leaderboard overlays, language switcher, mobile nav arrows | +| Modal backdrop | 45 | Semi-transparent overlay behind modals | +| Modal content | 80 | Modal panels (above their own backdrop) | +| Special overlays | 100+ | Mobile user dropdowns, fullscreen achievement announcements | +| Full-screen spinner | 1000 | The brain-spinner loading overlay (must be above everything) | +| Achievement celebrations | 99999+ | Achievement badge pop-ups and gamification overlays (UXCat — intentionally sky-high to guarantee visibility) | + +Note: for new implementations, a tighter scale (e.g., 0/10/20/30/40/50/100/1000) following the same layer order would be more maintainable. + +--- + +## 10b. UXCat-Specific Styles + +UXCat is the quiz and gamification tool. It inherits the entire shared foundation above (fonts, colors, spacing, breakpoints, component styles, shadows, border-radius) but adds its own visual personality on top of it. + +### How UXCat Differs from the Platform Style + +| Aspect | Platform (UXCore/UXCG/UXCP) | UXCat | +| ---------------- | --------------------------- | ------------------------------------------------------------ | +| Dominant accent | Steel-blue `#28587b` | Orange `#de915b` for titles and CTAs | +| Heading weight | 300 (light) | Bold (for quiz titles and start-test headings) | +| Content entrance | Immediate (no animation) | Staggered fade-in sequence | +| Interactivity | Browse and explore | Select, answer, submit — quiz-flow interaction | +| Reward layer | None | Achievement badges, leaderboard, progress bars, certificates | +| Emotional tone | Calm, reference-like | Encouraging, slightly competitive | + +### Additional Fonts (UXCat only) + +- **Oswald** (bold, serif fallback) — used for achievement badge display names. A condensed, impactful typeface that signals "reward" and "accomplishment." +- **Dela Gothic One** (weight 400) — used for gamification ranking displays (leaderboard ranks, score labels). Thick and monumental — makes rank numbers feel important. +- **IBM Plex Sans** (Regular/SemiBold) and **Manrope ExtraLight** — used for certificate generation. These give certificates a formal, clean appearance distinct from the UI chrome. + +### Start Test Screen + +A full-viewport introduction screen shown before a quiz begins. Sets the mood for the UXCat experience. + +- **Layout:** Full-screen (100vw × 100vh) with a background illustration image covering the viewport. Content is centered in a white modal-like card floating over the image. +- **Title:** 36px, **bold** weight, color `#de915b` (orange accent), centered. This is the key visual difference — UXCat headings use orange and bold weight, not the thin steel-blue of other tools. +- **Description:** 16px, line-height 22px, centered, 25px side padding. +- **Duration badge:** Positioned top-center, 20px, `#c4c4c4` gray text. On mobile (≤901px), this repositions to a small pill in the top-right corner with `1px solid #c4c4c4` border, 4px border-radius, white background. +- **Card shadow:** Uses the LG multi-layered shadow for a prominent floating effect. +- **CTA button:** Orange button variant (`#de915b` → `#cd7232` on hover), centered below the description. + +### Quiz Flow (Ongoing Screen) + +The active quiz-taking interface. Content is broken into a question zone at the top and selectable answer cards below. + +**Question area:** + +- White card with SM shadow (`0px 2px 8px rgba(0, 0, 0, 0.05)`), minimum height 223px. +- Question text: 24px, weight 800 (the heaviest weight in the system), centered, `#000`. On mobile (≤800px), drops to 16px. +- Sub-question text: 18px, weight 700, centered. +- Bias title and description: 16px, line-height 25.6px, left-aligned, with bold weight for the title. +- Top padding: 80px on desktop (creates breathing room below the header). Removed on mobile. +- User selection is disabled (`user-select: none`) — the interface is interaction-only. + +**Answer cards:** + +- White cards with the LG multi-layered shadow, `2px solid #fafafa` border (nearly invisible at rest), 4px border-radius, minimum height 72px. +- Each card has a **prefix label** (e.g., "A", "B") in a dark pill: `#000000a6` background, `#fff` text, 2px border-radius, 0 8px padding. +- Answer text: 16px, `rgba(0, 0, 0, 0.65)` (slightly muted), centered. +- **Selected state:** Border becomes `2px solid #5b88bd`, background gets a faint blue wash `rgba(147, 183, 255, 0.2)`. A quick scale pulse plays (scale 1 → 0.99 → 1, 200ms) to give tactile feedback. +- **Skeleton state (loading):** Background `#ebebeb`, no text, same dimensions. On mobile, border changes to `1px solid #c4c4c4`. + +**Staggered entrance animation:** + +- When a new question loads, elements fade in sequentially: + - Question text: 500ms + - Bias title: 600ms + - Description: 700ms + - Sub-question: 800ms + - Answer cards: 900ms +- Each uses `opacity: 0 → 1`, `ease-in-out`. On exit, the same elements fade out in the same staggered order. +- Answers can also slide down from above (`translateY(-100%) → 0`, 400ms, `ease-in-out`) when new options appear. + +**Action buttons:** + +- Centered below answers, 32px top margin, 60px bottom padding, 17px gap between buttons. +- Fixed width: 205px per button (235px for Russian locale). + +### Calculating Results Screen + +A transitional screen shown while quiz results are being processed. + +- **Layout:** Full height (`100vh - 45px`), centered content, 125px top margin. +- **Ring spinner:** 32×32px, `border: 4px solid #5b88bd`, three staggered rings with `-0.45s`, `-0.3s`, `-0.15s` animation delays. Duration: 1.2s, `cubic-bezier(0.5, 0, 0.5, 1)`. +- **Title:** 24px, weight 600, line-height 28.8px. +- **Progress card:** White card with the LG multi-layered shadow, max-width 497px, 64px vertical padding. +- **Progress bars:** 2px height, `#e0e0e0` background track, `#2db675` (green) fill that animates from 0% to 100% width over 1 second. +- **Checkmark:** `#4caf50` green, 18px. + +### Test Results Screen + +Displays quiz results with score, reading recommendations, and next-test information. + +- **Header:** 396px tall white card with SM shadow, containing score visualization. +- **Body:** 900px max-width, 24px top margin, standard platform layout. +- **Result cards:** 8px border-radius, `1px solid #d9d9d9` border, MD shadow (`0 4px 16px rgba(0, 0, 0, 0.15)`), white background. +- **Reading list links:** 16px, `#000` text, 2px border-radius, ellipsis overflow at 380px max-width. Links stack with 8px gap. +- **Next test date:** `#a36aa4` (purple accent) text. +- **Action buttons:** 218px fixed width. Centered in the footer with 61px top margin. + +### Achievement Badges + +Pop-up reward notifications that appear when users unlock achievements during quizzes. These are the most visually distinctive elements in the entire platform — intentionally flashy compared to the understated core aesthetic. + +- **Container:** 420×140px, border-radius 15px, transparent background, z-index 99999 (above everything). +- **Inner wrapper:** 320×100px, border-radius 10px, gold background `rgb(177, 136, 56)` with asymmetric gold borders (top: `5px solid rgb(216, 188, 131)`, right: `5px solid rgb(168, 130, 53)`, bottom: `5px solid rgb(177, 134, 53)`). A metallic gradient fills the text area: `linear-gradient(-23deg, #9e7931, #cb9d4a 39%, #d1ad66 51%)`. +- **Badge name:** Oswald font, 24px (20px on mobile), `#fff` white text, uppercase. +- **Title label:** 15px (12px on mobile), `#fff` text, `rgb(151, 100, 0)` background, `2px solid rgb(166, 128, 53)` border, uppercase. +- **Glow effect:** Pulsing gold glow shadow: `0px 0px 4px rgb(252, 249, 218), 0px 0px 8px rgb(252, 249, 218), 0px 0px 16px rgb(252, 249, 218)`. On hover, glow intensifies (spreads to 8/12/20px). 4px border-radius on the glow elements. +- **Star decorations:** Rotating star images (5s ease-in infinite) with fade-in-and-out particles (2s linear infinite). +- **Entrance animations:** Slides in from the side (`translateX(100%) → 0`, 1s ease-in-out). Exits with `translateX(0) → translateX(100%)` + opacity fade, 500ms. +- **Mobile:** Container shrinks (reduced padding, smaller text — 12px title, 20px badge name, 75px wrapper height). + +### UXCat Page Layout + +The UXCat hub page (test selection, leaderboard) uses the same layout pattern as other tools: + +- Page title: 36px, weight 400, `#28587b` (brand blue — the hub page uses the platform color, not orange). +- Subtitle: 18px, `#9e9e9e`, 9px top padding. +- Description: 16px, line-height 19.6px. +- Content: 900px max-width, 86px top margin. +- **Accordion sections:** 32px bottom margin, 150% line-height. +- **Leaderboard:** 32px top margin, 62px bottom margin. +- On mobile (≤801px): title goes to 24px bold, subtitle to 16px, description hidden, accordion margin to 16px. + +### Achievement Banner (In-Quiz) + +A full-width gradient banner that appears during a quiz when an achievement is unlocked: + +- Background: `linear-gradient(261.09deg, rgba(195, 55, 100, 0.89) 0.45%, rgba(29, 38, 113, 0.89) 90.78%)` — a dramatic pink-to-navy gradient, the only gradient element in the entire UI. +- Padding: 14px vertical, 20px horizontal text. +- Text: 14px, white. +- Twinkling star emojis at corners, fading in and out (2–3s infinite). +- Z-index: 999999 (the absolute highest layer — must appear above quiz content and any existing badges). + +--- + +## 11. Do / Don't Rules + +# Section 11 — Do / Don't Rules (DRAFT) + +> Go through these. Keep the ones that match your vision, +> delete the ones that don't apply, reword anything that +> needs adjusting, and add your own. These are the rules +> that stop Claude from making "technically correct but +> visually wrong" output. + +## Always Do + +### Layout & Structure + +- Keep layouts flat and simple — content speaks, decoration doesn't +- Use generous whitespace between sections — let things breathe +- Maintain strict visual hierarchy: one clear focal point per section +- Align elements to a consistent grid — nothing should feel randomly placed +- Keep content containers at a predictable max-width — never let text stretch edge-to-edge + +### Typography + +- Use font weights exactly as specified — don't improvise with weights we don't use +- Keep headings lightweight (thin/light weight) — heavy headings feel wrong for UXCore/UXCG/UXCP (UXCat uses bold for quiz titles — that's the exception) +- Use uppercase only where the system specifies it (nav, labels, tags) — nowhere else +- Maintain consistent text sizes — don't introduce sizes between our scale steps + +### Color + +- Use color sparingly and with purpose — every colored element should mean something +- Keep the palette desaturated and cool — the platform is calm, not vibrant +- Use the steel-blue (#28587b) as the single dominant accent for UXCore/UXCG/UXCP — don't spread multiple accent colors +- For UXCat quiz screens, use orange (#de915b) as the dominant accent — but only on titles and CTAs, not everywhere +- Let white/near-white backgrounds do most of the work + +### Components + +- Cards should feel like paper — white, slightly lifted, clean edges +- Buttons should be small and compact — they don't shout +- Inputs should feel invisible until focused — minimal borders, no shadows at rest +- Tags/badges should be subtle color-coded labels, not attention-grabbing pills + +### Interactions + +- Keep hover effects subtle — a slight lift, a border color shift, nothing dramatic +- Use the exact easing curves specified — the springy cubic-bezier is part of the feel +- Transitions should be fast (150-300ms) — the UI should feel responsive, not animated + +## Never Do + +### The "AI Look" Killers (most important) + +- Never use gradient backgrounds on cards or sections — the platform uses flat white/gray only (the UXCat achievement banner is the sole exception) +- Never use colored shadows or glows (except the specific gold achievement glow in UXCat) +- Never use rounded pill buttons (large border-radius like 24px+) — our buttons are nearly square (2-4px radius) +- Never use hero sections with giant text and centered CTAs — the platform is a tool, not a landing page +- Never use decorative SVG blobs, waves, or organic shapes as backgrounds +- Never use glassmorphism, frosted glass, or backdrop-blur effects +- Never add decorative gradients to text +- Never use icon-heavy empty states with cute illustrations +- Never center-align body text — always left-align (except specific centered headings) + +### Typography + +- Never use font weights that aren't in our scale (no 900/black, no 100/hairline unless specified) +- Never use font sizes outside our type scale +- Never add text shadows +- Never use italic for emphasis — use weight or color instead + +### Color & Visual + +- Never use pure black (#000000) as a background — our darkest background is near-white gray +- Never introduce new accent colors — stay within the defined palette +- Never use high-saturation colors — everything in the platform is muted and desaturated +- Never use colored borders on cards — borders are always neutral gray or the specific selection blue +- Never use alternating row colors in lists/tables (zebra striping) unless it already exists in the design + +### Spacing & Layout + +- Never add more than 3 levels of visual nesting — keep the hierarchy shallow +- Never use full-bleed colored sections that span the viewport +- Never overlap elements for decorative effect — the layout is clean and grid-aligned +- Never use masonry layouts — our grids are uniform + +### Motion + +- Never add entrance animations to every element — staggered reveals are used only in UXCat quiz flow +- Never use bounce, elastic, or exaggerated easing — motion is subtle and physical +- Never animate color changes on hover — change happens instantly, only position/shadow animates +- Never add loading animations to elements that load instantly +- Never use parallax scrolling + +### Components + +- Never make buttons taller than specified — our buttons are compact (32px default, 54px max for big variant) +- Never add icons inside buttons unless the existing design does +- Never use floating action buttons (FABs) +- Never use toast notifications that slide in from corners (unless the project has them) +- Never add empty state illustrations — use simple text + +## Tone of the UI + +- *** + (Suggestion: "Quiet, structured, informational. A well-organized + reference tool — closer to a thoughtfully designed textbook than + a marketing page. The UI stays out of the way and lets dense + educational content be the focus. Nothing is flashy, playful, + or decorative without purpose. UXCat adds warmth and encouragement + to the quiz experience, but never crosses into playful or casual.") + +--- + +## Code Conventions + +This skill covers **visual design only** — colors, typography, spacing, component appearance, motion. + +For code conventions (component structure, file layout, prop typing, static data, locales, path aliases, testing, gotchas), see **`AGENTS.md`** at the repo root. That file is the authoritative source for how to write code in this repo. + +If a visual instruction here conflicts with a code convention in `AGENTS.md`, both apply — this skill governs how the UI _looks_, `AGENTS.md` governs how the code is _structured_. diff --git a/yarn.lock b/yarn.lock index 6b0784d3..4936d259 100644 --- a/yarn.lock +++ b/yarn.lock @@ -920,53 +920,6 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@cypress/react@^9.0.1": - version "9.0.1" - resolved "https://registry.npmjs.org/@cypress/react/-/react-9.0.1.tgz" - integrity sha512-qu6ziP2smdlfy3Yvrhm6PadxEtkc/cl6YhZu3h6KCtz+0s54joqxp6uGYOglpwyMBp3qjtSil1JVlFX0hUi5LQ== - -"@cypress/request@^3.0.9": - version "3.0.10" - resolved "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz" - integrity sha512-hauBrOdvu08vOsagkZ/Aju5XuiZx6ldsLfByg1htFeldhex+PeMrYauANzFsMJeAA0+dyPLbDoX2OYuvVoLDkQ== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~4.0.4" - http-signature "~1.4.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - performance-now "^2.1.0" - qs "~6.14.1" - safe-buffer "^5.1.2" - tough-cookie "^5.0.0" - tunnel-agent "^0.6.0" - uuid "^8.3.2" - -"@cypress/vite-dev-server@^6.0.3": - version "6.0.3" - resolved "https://registry.npmjs.org/@cypress/vite-dev-server/-/vite-dev-server-6.0.3.tgz" - integrity sha512-iw5koemvIOzwjtGOKKGfNVGvmjwBmjj5DCiBW6ATUB+m1HzFM9Zmq1dpll+zym5dM+pxb5iA8zKLoAoSKutYVg== - dependencies: - debug "^4.3.4" - find-up "6.3.0" - node-html-parser "5.3.3" - semver "^7.7.1" - -"@cypress/xvfb@^1.2.4": - version "1.2.4" - resolved "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz" - integrity sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q== - dependencies: - debug "^3.1.0" - lodash.once "^4.1.1" - "@discoveryjs/json-ext@0.5.7": version "0.5.7" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" @@ -1094,40 +1047,6 @@ resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz" integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ== -"@hapi/address@^5.1.1": - version "5.1.1" - resolved "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz" - integrity sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA== - dependencies: - "@hapi/hoek" "^11.0.2" - -"@hapi/formula@^3.0.2": - version "3.0.2" - resolved "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz" - integrity sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw== - -"@hapi/hoek@^11.0.2", "@hapi/hoek@^11.0.7": - version "11.0.7" - resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz" - integrity sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ== - -"@hapi/pinpoint@^2.0.1": - version "2.0.1" - resolved "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz" - integrity sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q== - -"@hapi/tlds@^1.1.1": - version "1.1.6" - resolved "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz" - integrity sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw== - -"@hapi/topo@^6.0.2": - version "6.0.2" - resolved "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz" - integrity sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg== - dependencies: - "@hapi/hoek" "^11.0.2" - "@humanfs/core@^0.19.1": version "0.19.1" resolved "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz" @@ -1443,44 +1362,6 @@ resolved "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz" integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA== -"@otplib/core@^12.0.1": - version "12.0.1" - resolved "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz" - integrity sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA== - -"@otplib/plugin-crypto@^12.0.1": - version "12.0.1" - resolved "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz" - integrity sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g== - dependencies: - "@otplib/core" "^12.0.1" - -"@otplib/plugin-thirty-two@^12.0.1": - version "12.0.1" - resolved "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz" - integrity sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA== - dependencies: - "@otplib/core" "^12.0.1" - thirty-two "^1.0.2" - -"@otplib/preset-default@^12.0.1": - version "12.0.1" - resolved "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz" - integrity sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ== - dependencies: - "@otplib/core" "^12.0.1" - "@otplib/plugin-crypto" "^12.0.1" - "@otplib/plugin-thirty-two" "^12.0.1" - -"@otplib/preset-v11@^12.0.1": - version "12.0.1" - resolved "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz" - integrity sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg== - dependencies: - "@otplib/core" "^12.0.1" - "@otplib/plugin-crypto" "^12.0.1" - "@otplib/plugin-thirty-two" "^12.0.1" - "@panva/hkdf@^1.0.2": version "1.2.1" resolved "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz" @@ -1498,20 +1379,6 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.29.tgz#5a40109a1ab5f84d6fd8fc928b19f367cbe7e7b1" integrity sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww== -"@puppeteer/browsers@2.3.0": - version "2.3.0" - resolved "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz" - integrity sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA== - dependencies: - debug "^4.3.5" - extract-zip "^2.0.1" - progress "^2.0.3" - proxy-agent "^6.4.0" - semver "^7.6.3" - tar-fs "^3.0.6" - unbzip2-stream "^1.4.3" - yargs "^17.7.2" - "@rtsao/scc@^1.1.0": version "1.1.0" resolved "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz" @@ -1522,11 +1389,6 @@ resolved "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz" integrity sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag== -"@standard-schema/spec@^1.0.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz" - integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== - "@svgr/babel-plugin-add-jsx-attribute@8.0.0": version "8.0.0" resolved "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz" @@ -1645,11 +1507,6 @@ dependencies: tslib "^2.4.0" -"@tootallnate/quickjs-emscripten@^0.23.0": - version "0.23.0" - resolved "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz" - integrity sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA== - "@tsconfig/node10@^1.0.7": version "1.0.12" resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz" @@ -1759,7 +1616,7 @@ resolved "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz" integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== -"@types/node@*", "@types/node@^24.5.2": +"@types/node@^24.5.2": version "24.10.14" resolved "https://registry.npmjs.org/@types/node/-/node-24.10.14.tgz" integrity sha512-OowOUbD1lBCOFIPOZ8xnMIhgqA4sCutMiYOmPHL1PTLt5+y1XA+g2+yC9OOyz8p+deMZqPZLxfMjYIfrKsPeFg== @@ -1814,16 +1671,6 @@ resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz" integrity sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA== -"@types/sinonjs__fake-timers@8.1.1": - version "8.1.1" - resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz" - integrity sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g== - -"@types/sizzle@^2.3.2": - version "2.3.10" - resolved "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.10.tgz" - integrity sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww== - "@types/swiper@6.0.0": version "6.0.0" resolved "https://registry.npmjs.org/@types/swiper/-/swiper-6.0.0.tgz" @@ -1841,13 +1688,6 @@ resolved "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.1.tgz" integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== -"@types/yauzl@^2.9.1": - version "2.10.3" - resolved "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz" - integrity sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q== - dependencies: - "@types/node" "*" - "@typescript-eslint/eslint-plugin@^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0": version "8.56.1" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz" @@ -2063,19 +1903,6 @@ acorn@^8.0.4, acorn@^8.11.0, acorn@^8.15.0, acorn@^8.4.1: resolved "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz" integrity sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw== -agent-base@^7.1.0, agent-base@^7.1.2: - version "7.1.4" - resolved "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz" - integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ== - -aggregate-error@^3.0.0: - version "3.1.0" - resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz" - integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== - dependencies: - clean-stack "^2.0.0" - indent-string "^4.0.0" - ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz" @@ -2091,24 +1918,7 @@ ajv@^6.12.4, ajv@^6.14.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ansi-colors@^4.1.1: - version "4.1.3" - resolved "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz" - integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== - -ansi-escapes@^4.3.0: - version "4.3.2" - resolved "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -2123,21 +1933,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" -arch@^2.2.0: - version "2.2.0" - resolved "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz" - integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== - arg@^4.1.0: version "4.1.3" resolved "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz" integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== -arg@^5.0.2: - version "5.0.2" - resolved "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - argparse@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz" @@ -2239,35 +2039,11 @@ arraybuffer.prototype.slice@^1.0.4: get-intrinsic "^1.2.6" is-array-buffer "^3.0.4" -asn1@~0.2.3: - version "0.2.6" - resolved "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz" - integrity sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ== - dependencies: - safer-buffer "~2.1.0" - -assert-plus@1.0.0, assert-plus@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz" - integrity sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw== - ast-types-flow@^0.0.8: version "0.0.8" resolved "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz" integrity sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ== -ast-types@^0.13.4: - version "0.13.4" - resolved "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz" - integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== - dependencies: - tslib "^2.0.1" - -astral-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz" - integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== - async-function@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz" @@ -2280,21 +2056,6 @@ async@^2.1.1: dependencies: lodash "^4.17.14" -async@^3.2.0: - version "3.2.6" - resolved "https://registry.npmjs.org/async/-/async-3.2.6.tgz" - integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== - -at-least-node@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz" - integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== - available-typed-arrays@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz" @@ -2302,16 +2063,6 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -aws-sign2@~0.7.0: - version "0.7.0" - resolved "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz" - integrity sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA== - -aws4@^1.8.0: - version "1.13.2" - resolved "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz" - integrity sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw== - axe-core@^4.10.0: version "4.11.1" resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz" @@ -2322,25 +2073,11 @@ axe-core@~4.11.3: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.11.3.tgz#d23cf404edaa5f97bdfd9afed6eea8405e5326e7" integrity sha512-zBQouZixDTbo3jMGqHKyePxYxr1e5W8UdTmBQ7sNtaA9M2bE32daxxPLS/jojhKOHxQ7LWwPjfiwf/fhaJWzlg== -axios@^1.13.5: - version "1.13.5" - resolved "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz" - integrity sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q== - dependencies: - follow-redirects "^1.15.11" - form-data "^4.0.5" - proxy-from-env "^1.1.0" - axobject-query@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz" integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== -b4a@^1.6.4: - version "1.8.0" - resolved "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz" - integrity sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg== - babel-loader@8.2.5: version "8.2.5" resolved "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz" @@ -2390,76 +2127,16 @@ balanced-match@^4.0.2: resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== -bare-events@^2.5.4, bare-events@^2.7.0: - version "2.8.2" - resolved "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz" - integrity sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ== - -bare-fs@^4.0.1: - version "4.5.5" - resolved "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.5.tgz" - integrity sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w== - dependencies: - bare-events "^2.5.4" - bare-path "^3.0.0" - bare-stream "^2.6.4" - bare-url "^2.2.2" - fast-fifo "^1.3.2" - -bare-os@^3.0.1: - version "3.7.0" - resolved "https://registry.npmjs.org/bare-os/-/bare-os-3.7.0.tgz" - integrity sha512-64Rcwj8qlnTZU8Ps6JJEdSmxBEUGgI7g8l+lMtsJLl4IsfTcHMTfJ188u2iGV6P6YPRZrtv72B2kjn+hp+Yv3g== - -bare-path@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz" - integrity sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw== - dependencies: - bare-os "^3.0.1" - -bare-stream@^2.6.4: - version "2.8.0" - resolved "https://registry.npmjs.org/bare-stream/-/bare-stream-2.8.0.tgz" - integrity sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA== - dependencies: - streamx "^2.21.0" - teex "^1.0.1" - -bare-url@^2.2.2: - version "2.3.2" - resolved "https://registry.npmjs.org/bare-url/-/bare-url-2.3.2.tgz" - integrity sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw== - dependencies: - bare-path "^3.0.0" - base64-arraybuffer@^1.0.1, base64-arraybuffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz" integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - baseline-browser-mapping@^2.9.0: version "2.10.0" resolved "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz" integrity sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA== -basic-ftp@^5.0.2: - version "5.2.0" - resolved "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz" - integrity sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw== - -bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz" - integrity sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w== - dependencies: - tweetnacl "^0.14.3" - big.js@^5.2.2: version "5.2.2" resolved "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz" @@ -2470,16 +2147,6 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz" integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== -blob-util@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz" - integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== - -bluebird@3.7.2, bluebird@^3.7.2: - version "3.7.2" - resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz" - integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== - boolbase@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz" @@ -2523,14 +2190,6 @@ buffer-crc32@~0.2.3: resolved "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== -buffer@^5.2.1, buffer@^5.7.1: - version "5.7.1" - resolved "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - busboy@1.6.0: version "1.6.0" resolved "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz" @@ -2538,11 +2197,6 @@ busboy@1.6.0: dependencies: streamsearch "^1.1.0" -cachedir@^2.3.0: - version "2.4.0" - resolved "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz" - integrity sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ== - call-bind-apply-helpers@^1.0.0, call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" @@ -2584,12 +2238,7 @@ caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001759: resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001774.tgz" integrity sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA== -caseless@~0.12.0: - version "0.12.0" - resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz" - integrity sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw== - -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0: version "4.1.2" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2597,16 +2246,16 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@^5.0.0: + version "5.6.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.6.2.tgz#b1238b6e23ea337af71c7f8a295db5af0c158aea" + integrity sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA== + character-entities@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz" integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== -check-more-types@2.24.0, check-more-types@^2.24.0: - version "2.24.0" - resolved "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz" - integrity sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA== - "chokidar@>=2.0.0 <4.0.0": version "3.6.0" resolved "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz" @@ -2622,68 +2271,16 @@ check-more-types@2.24.0, check-more-types@^2.24.0: optionalDependencies: fsevents "~2.3.2" -chromium-bidi@0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz" - integrity sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A== - dependencies: - mitt "3.0.1" - urlpattern-polyfill "10.0.0" - zod "3.23.8" - -ci-info@^4.1.0: - version "4.4.0" - resolved "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz" - integrity sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg== - classnames@2.3.1, classnames@^2.2.5, classnames@^2.3.0: version "2.3.1" resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz" integrity sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA== -clean-stack@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz" - integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-table3@0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz" - integrity sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA== - dependencies: - string-width "^4.2.0" - optionalDependencies: - colors "1.4.0" - -cli-truncate@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz" - integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== - dependencies: - slice-ansi "^3.0.0" - string-width "^4.2.0" - client-only@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz" integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA== -cliui@^8.0.1: - version "8.0.1" - resolved "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz" - integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.1" - wrap-ansi "^7.0.0" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz" @@ -2712,43 +2309,21 @@ color@^4.2.3: color-convert "^2.0.1" color-string "^1.9.0" -colorette@^2.0.16: - version "2.0.20" - resolved "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz" - integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== - -colors@1.4.0, colors@^1.1.2: +colors@^1.1.2: version "1.4.0" resolved "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== -combined-stream@^1.0.8, combined-stream@~1.0.6: - version "1.0.8" - resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - comma-separated-tokens@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz" integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== -commander@^6.2.1: - version "6.2.1" - resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - commander@^7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -common-tags@^1.8.0: - version "1.8.2" - resolved "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz" - integrity sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA== - commondir@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz" @@ -2786,11 +2361,6 @@ core-js-compat@^3.48.0: dependencies: browserslist "^4.28.1" -core-util-is@1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz" - integrity sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ== - cosmiconfig@^8.1.3: version "8.3.6" resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz" @@ -2801,16 +2371,6 @@ cosmiconfig@^8.1.3: parse-json "^5.2.0" path-type "^4.0.0" -cosmiconfig@^9.0.0: - version "9.0.0" - resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz" - integrity sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg== - dependencies: - env-paths "^2.2.1" - import-fresh "^3.3.0" - js-yaml "^4.1.0" - parse-json "^5.2.0" - create-require@^1.1.0: version "1.1.1" resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz" @@ -2823,7 +2383,7 @@ cross-env@7.0.3: dependencies: cross-spawn "^7.0.1" -cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.3, cross-spawn@^7.0.6: +cross-spawn@^7.0.1, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -2855,17 +2415,6 @@ css-line-break@^2.1.0: dependencies: utrie "^1.0.2" -css-select@^4.2.1: - version "4.3.0" - resolved "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz" - integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ== - dependencies: - boolbase "^1.0.0" - css-what "^6.0.1" - domhandler "^4.3.1" - domutils "^2.8.0" - nth-check "^2.0.1" - css-select@^5.1.0: version "5.2.2" resolved "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz" @@ -2901,7 +2450,7 @@ css-vendor@^2.0.8: "@babel/runtime" "^7.8.3" is-in-browser "^1.0.2" -css-what@^6.0.1, css-what@^6.1.0: +css-what@^6.1.0: version "6.2.2" resolved "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz" integrity sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA== @@ -2918,88 +2467,11 @@ csstype@^3.0.2: resolved "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz" integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== -cypress-social-logins@^1.14.2: - version "1.14.2" - resolved "https://registry.npmjs.org/cypress-social-logins/-/cypress-social-logins-1.14.2.tgz" - integrity sha512-acTzY6axp/GJnmxCA6aHiXxg6F2vIfsa6YVvTvvu4P2MLc/cdXqhbh5JJsk5r3iGLTQwLAIos8YZuAik6eWqVQ== - dependencies: - otplib "^12.0.1" - puppeteer "^22.15.0" - -cypress-v10-preserve-cookie@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/cypress-v10-preserve-cookie/-/cypress-v10-preserve-cookie-1.2.1.tgz" - integrity sha512-8JAfcrDrCCiFyce7ygtqO9Gad0yzFla9WNDGA9j4sz6kLSvh2m2fWcBcON2VwWw5GfRDXmpe+yzT2r8Mu3jGdA== - dependencies: - debug "^4.3.4" - -cypress@^14.5.2: - version "14.5.4" - resolved "https://registry.npmjs.org/cypress/-/cypress-14.5.4.tgz" - integrity sha512-0Dhm4qc9VatOcI1GiFGVt8osgpPdqJLHzRwcAB5MSD/CAAts3oybvPUPawHyvJZUd8osADqZe/xzMsZ8sDTjXw== - dependencies: - "@cypress/request" "^3.0.9" - "@cypress/xvfb" "^1.2.4" - "@types/sinonjs__fake-timers" "8.1.1" - "@types/sizzle" "^2.3.2" - arch "^2.2.0" - blob-util "^2.0.2" - bluebird "^3.7.2" - buffer "^5.7.1" - cachedir "^2.3.0" - chalk "^4.1.0" - check-more-types "^2.24.0" - ci-info "^4.1.0" - cli-cursor "^3.1.0" - cli-table3 "0.6.1" - commander "^6.2.1" - common-tags "^1.8.0" - dayjs "^1.10.4" - debug "^4.3.4" - enquirer "^2.3.6" - eventemitter2 "6.4.7" - execa "4.1.0" - executable "^4.1.1" - extract-zip "2.0.1" - figures "^3.2.0" - fs-extra "^9.1.0" - getos "^3.2.1" - hasha "5.2.2" - is-installed-globally "~0.4.0" - lazy-ass "^1.6.0" - listr2 "^3.8.3" - lodash "^4.17.21" - log-symbols "^4.0.0" - minimist "^1.2.8" - ospath "^1.2.2" - pretty-bytes "^5.6.0" - process "^0.11.10" - proxy-from-env "1.0.0" - request-progress "^3.0.0" - semver "^7.7.1" - supports-color "^8.1.1" - tmp "~0.2.3" - tree-kill "1.2.2" - untildify "^4.0.0" - yauzl "^2.10.0" - damerau-levenshtein@^1.0.8: version "1.0.8" resolved "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz" integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== -dashdash@^1.12.0: - version "1.14.1" - resolved "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz" - integrity sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g== - dependencies: - assert-plus "^1.0.0" - -data-uri-to-buffer@^6.0.2: - version "6.0.2" - resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz" - integrity sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw== - data-view-buffer@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz" @@ -3034,30 +2506,25 @@ date-fns@2.30.0: dependencies: "@babel/runtime" "^7.21.0" -dayjs@^1.10.4: - version "1.11.19" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz" - integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw== - debounce@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5" integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug== -debug@4, debug@4.4.3, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6, debug@^4.4.0, debug@^4.4.3: - version "4.4.3" - resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" - integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== - dependencies: - ms "^2.1.3" - -debug@^3.1.0, debug@^3.2.7: +debug@^3.2.7: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" +debug@^4.0.0, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.4.0, debug@^4.4.3: + version "4.4.3" + resolved "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== + dependencies: + ms "^2.1.3" + decode-named-character-reference@^1.0.0: version "1.3.0" resolved "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz" @@ -3093,20 +2560,6 @@ define-properties@^1.1.3, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -degenerator@^5.0.0: - version "5.0.1" - resolved "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz" - integrity sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ== - dependencies: - ast-types "^0.13.4" - escodegen "^2.1.0" - esprima "^4.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== - dequal@^2.0.0: version "2.0.3" resolved "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz" @@ -3117,11 +2570,6 @@ detect-libc@^2.0.3: resolved "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz" integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== -devtools-protocol@0.0.1312386: - version "0.0.1312386" - resolved "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz" - integrity sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA== - diff@^4.0.1: version "4.0.4" resolved "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz" @@ -3139,15 +2587,6 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -dom-serializer@^1.0.1: - version "1.4.1" - resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz" - integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - dom-serializer@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz" @@ -3157,18 +2596,11 @@ dom-serializer@^2.0.0: domhandler "^5.0.2" entities "^4.2.0" -domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: +domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== -domhandler@^4.2.0, domhandler@^4.3.1: - version "4.3.1" - resolved "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - domhandler@^5.0.2, domhandler@^5.0.3: version "5.0.3" resolved "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz" @@ -3176,16 +2608,7 @@ domhandler@^5.0.2, domhandler@^5.0.3: dependencies: domelementtype "^2.3.0" -domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" - -domutils@^3.0.1: +domutils@^3.0.1, domutils@^3.2.2: version "3.2.2" resolved "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz" integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== @@ -3216,29 +2639,16 @@ dunder-proto@^1.0.0, dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" -duplexer@^0.1.2, duplexer@~0.1.1: +duplexer@^0.1.2: version "0.1.2" resolved "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== -ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz" - integrity sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw== - dependencies: - jsbn "~0.1.0" - safer-buffer "^2.1.0" - electron-to-chromium@^1.5.263: version "1.5.302" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.302.tgz" integrity sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg== -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz" @@ -3249,40 +2659,20 @@ emojis-list@^3.0.0: resolved "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz" integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== -end-of-stream@^1.1.0: - version "1.4.5" - resolved "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz" - integrity sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg== - dependencies: - once "^1.4.0" - enquire.js@^2.1.6: version "2.1.6" resolved "https://registry.npmjs.org/enquire.js/-/enquire.js-2.1.6.tgz" integrity sha512-/KujNpO+PT63F7Hlpu4h3pE3TokKRHN26JYmQpPyjkRD/N57R7bPDNojMXdi7uveAKjYB7yQnartCxZnFWr0Xw== -enquirer@^2.3.6: - version "2.4.1" - resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz" - integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== - dependencies: - ansi-colors "^4.1.1" - strip-ansi "^6.0.1" - -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== - entities@^4.2.0, entities@^4.4.0: version "4.5.0" resolved "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz" integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== -env-paths@^2.2.1: - version "2.2.1" - resolved "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz" - integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== +entities@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" + integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== error-ex@^1.3.1: version "1.3.4" @@ -3416,32 +2806,21 @@ es-to-primitive@^1.3.0: is-date-object "^1.0.5" is-symbol "^1.0.4" -escalade@^3.1.1, escalade@^3.2.0: +escalade@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" - integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== +escape-html@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escodegen@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz" - integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionalDependencies: - source-map "~0.6.1" - eslint-config-next@^15.4.4: version "15.5.12" resolved "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.12.tgz" @@ -3639,11 +3018,6 @@ espree@^10.0.1, espree@^10.4.0: acorn-jsx "^5.3.2" eslint-visitor-keys "^4.2.1" -esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - esquery@^1.5.0: version "1.7.0" resolved "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz" @@ -3668,99 +3042,16 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-stream@=3.3.4: - version "3.3.4" - resolved "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz" - integrity sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g== - dependencies: - duplexer "~0.1.1" - from "~0" - map-stream "~0.1.0" - pause-stream "0.0.11" - split "0.3" - stream-combiner "~0.0.4" - through "~2.3.1" - -eventemitter2@6.4.7: - version "6.4.7" - resolved "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz" - integrity sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg== - -events-universal@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz" - integrity sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw== - dependencies: - bare-events "^2.7.0" - -execa@4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - -execa@5.1.1: - version "5.1.1" - resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -executable@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz" - integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== - dependencies: - pify "^2.2.0" - -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0: version "3.0.2" resolved "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== -extract-zip@2.0.1, extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -extsprintf@1.3.0, extsprintf@^1.2.0: - version "1.3.0" - resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz" - integrity sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g== - fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-fifo@^1.2.0, fast-fifo@^1.3.2: - version "1.3.2" - resolved "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz" - integrity sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ== - fast-glob@3.3.1: version "3.3.1" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz" @@ -3801,13 +3092,6 @@ fdir@^6.5.0: resolved "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== -figures@^3.2.0: - version "3.2.0" - resolved "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - file-entry-cache@^8.0.0: version "8.0.0" resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz" @@ -3831,14 +3115,6 @@ find-cache-dir@^3.3.1: make-dir "^3.0.2" pkg-dir "^4.1.0" -find-up@6.3.0: - version "6.3.0" - resolved "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz" - integrity sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw== - dependencies: - locate-path "^7.1.0" - path-exists "^5.0.0" - find-up@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz" @@ -3868,11 +3144,6 @@ flatted@^3.2.9: resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== -follow-redirects@^1.15.11: - version "1.15.11" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz" - integrity sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ== - for-each@^0.3.3, for-each@^0.3.5: version "0.3.5" resolved "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz" @@ -3880,37 +3151,6 @@ for-each@^0.3.3, for-each@^0.3.5: dependencies: is-callable "^1.2.7" -forever-agent@~0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" - integrity sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw== - -form-data@^4.0.5, form-data@~4.0.4: - version "4.0.5" - resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz" - integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - es-set-tostringtag "^2.1.0" - hasown "^2.0.2" - mime-types "^2.1.12" - -from@~0: - version "0.1.7" - resolved "https://registry.npmjs.org/from/-/from-0.1.7.tgz" - integrity sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g== - -fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" @@ -3971,11 +3211,6 @@ geoip-lite@1.4.2: rimraf "^2.5.2" yauzl "^2.9.2" -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - get-intrinsic@^1.2.4, get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.2.7, get-intrinsic@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" @@ -4000,18 +3235,6 @@ get-proto@^1.0.0, get-proto@^1.0.1: dunder-proto "^1.0.1" es-object-atoms "^1.0.0" -get-stream@^5.0.0, get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0: - version "6.0.1" - resolved "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - get-symbol-description@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz" @@ -4028,28 +3251,10 @@ get-tsconfig@^4.10.0: dependencies: resolve-pkg-maps "^1.0.0" -get-uri@^6.0.1: - version "6.0.5" - resolved "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz" - integrity sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg== - dependencies: - basic-ftp "^5.0.2" - data-uri-to-buffer "^6.0.2" - debug "^4.3.4" - -getos@^3.2.1: - version "3.2.1" - resolved "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz" - integrity sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q== - dependencies: - async "^3.2.0" - -getpass@^0.1.1: - version "0.1.7" - resolved "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz" - integrity sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng== - dependencies: - assert-plus "^1.0.0" +github-slugger@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-2.0.0.tgz#52cf2f9279a21eb6c59dd385b410f0c0adda8f1a" + integrity sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw== glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" @@ -4065,6 +3270,15 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@^13.0.0: + version "13.0.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-13.0.6.tgz#078666566a425147ccacfbd2e332deb66a2be71d" + integrity sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw== + dependencies: + minimatch "^10.2.2" + minipass "^7.1.3" + path-scurry "^2.0.2" + glob@^7.1.3: version "7.2.3" resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz" @@ -4077,13 +3291,6 @@ glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^3.0.0: - version "3.0.1" - resolved "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz" - integrity sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA== - dependencies: - ini "2.0.0" - globals@^14.0.0: version "14.0.0" resolved "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz" @@ -4102,11 +3309,6 @@ gopd@^1.0.1, gopd@^1.2.0: resolved "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz" integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== -graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.11" - resolved "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz" - integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== - gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -4150,14 +3352,6 @@ has-tostringtag@^1.0.2: dependencies: has-symbols "^1.0.3" -hasha@5.2.2: - version "5.2.2" - resolved "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz" - integrity sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ== - dependencies: - is-stream "^2.0.0" - type-fest "^0.8.0" - hasown@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz" @@ -4230,11 +3424,6 @@ hastscript@^7.0.0: property-information "^6.0.0" space-separated-tokens "^2.0.0" -he@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/he/-/he-1.2.0.tgz" - integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== - hoist-non-react-statics@^3.2.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz" @@ -4260,40 +3449,15 @@ html2canvas@^1.4.1: css-line-break "^2.1.0" text-segmentation "^1.0.3" -http-proxy-agent@^7.0.0, http-proxy-agent@^7.0.1: - version "7.0.2" - resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz" - integrity sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig== - dependencies: - agent-base "^7.1.0" - debug "^4.3.4" - -http-signature@~1.4.0: - version "1.4.0" - resolved "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz" - integrity sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg== - dependencies: - assert-plus "^1.0.0" - jsprim "^2.0.2" - sshpk "^1.18.0" - -https-proxy-agent@^7.0.6: - version "7.0.6" - resolved "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz" - integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw== +htmlparser2@^10.0.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-10.1.0.tgz#fe3f2e12c73b6e462d4e10395db9c1119e4d6ae4" + integrity sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ== dependencies: - agent-base "^7.1.2" - debug "4" - -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.2.2" + entities "^7.0.1" husky@^9.1.7: version "9.1.7" @@ -4312,11 +3476,6 @@ iconv-lite@^0.4.13: dependencies: safer-buffer ">= 2.1.2 < 3" -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - ignore@^5.2.0: version "5.3.2" resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz" @@ -4340,11 +3499,6 @@ imurmurhash@^0.1.4: resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - inflight@^1.0.4: version "1.0.6" resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" @@ -4358,11 +3512,6 @@ inherits@2: resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -ini@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz" - integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== - inline-style-parser@0.1.1: version "0.1.1" resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz" @@ -4377,11 +3526,6 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" -ip-address@^10.0.1: - version "10.1.0" - resolved "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz" - integrity sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q== - ip-address@^5.8.9: version "5.9.4" resolved "https://registry.npmjs.org/ip-address/-/ip-address-5.9.4.tgz" @@ -4496,11 +3640,6 @@ is-finalizationregistry@^1.1.0: dependencies: call-bound "^1.0.3" -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - is-generator-function@^1.0.10: version "1.1.2" resolved "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz" @@ -4524,14 +3663,6 @@ is-in-browser@^1.0.2, is-in-browser@^1.1.3: resolved "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz" integrity sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g== -is-installed-globally@~0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz" - integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== - dependencies: - global-dirs "^3.0.0" - is-path-inside "^3.0.2" - is-map@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz" @@ -4555,11 +3686,6 @@ is-number@^7.0.0: resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== -is-path-inside@^3.0.2: - version "3.0.3" - resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz" - integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== - is-plain-obj@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz" @@ -4592,11 +3718,6 @@ is-shared-array-buffer@^1.0.4: dependencies: call-bound "^1.0.3" -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - is-string@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz" @@ -4621,16 +3742,6 @@ is-typed-array@^1.1.13, is-typed-array@^1.1.14, is-typed-array@^1.1.15: dependencies: which-typed-array "^1.1.16" -is-typedarray@~1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz" - integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA== - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - is-weakmap@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz" @@ -4661,11 +3772,6 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== -isstream@~0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" - integrity sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g== - iterator.prototype@^1.1.5: version "1.1.5" resolved "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz" @@ -4678,19 +3784,6 @@ iterator.prototype@^1.1.5: has-symbols "^1.1.0" set-function-name "^2.0.2" -joi@^18.0.2: - version "18.0.2" - resolved "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz" - integrity sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA== - dependencies: - "@hapi/address" "^5.1.1" - "@hapi/formula" "^3.0.2" - "@hapi/hoek" "^11.0.7" - "@hapi/pinpoint" "^2.0.1" - "@hapi/tlds" "^1.1.1" - "@hapi/topo" "^6.0.2" - "@standard-schema/spec" "^1.0.0" - jose@^4.11.4, jose@^4.15.9: version "4.15.9" resolved "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz" @@ -4713,11 +3806,6 @@ jsbn@1.1.0: resolved "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz" integrity sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A== -jsbn@~0.1.0: - version "0.1.1" - resolved "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" - integrity sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg== - jsesc@^3.0.2, jsesc@~3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz" @@ -4743,21 +3831,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== -json-schema@0.4.0: - version "0.4.0" - resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz" - integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA== - json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== -json-stringify-safe@~5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" - integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== - json2mq@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz" @@ -4777,25 +3855,6 @@ json5@^2.1.2, json5@^2.2.1, json5@^2.2.3: resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonfile@^6.0.1: - version "6.2.0" - resolved "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz" - integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsprim@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz" - integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ== - dependencies: - assert-plus "1.0.0" - extsprintf "1.3.0" - json-schema "0.4.0" - verror "1.10.0" - jss-plugin-camel-case@10.10.0: version "10.10.0" resolved "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.10.0.tgz" @@ -4969,11 +4028,6 @@ language-tags@^1.0.9: dependencies: language-subtag-registry "^0.3.20" -lazy-ass@1.6.0, lazy-ass@^1.6.0: - version "1.6.0" - resolved "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz" - integrity sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw== - lazy@^1.0.11: version "1.0.11" resolved "https://registry.npmjs.org/lazy/-/lazy-1.0.11.tgz" @@ -4992,19 +4046,21 @@ lines-and-columns@^1.1.6: resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== -listr2@^3.8.3: - version "3.14.0" - resolved "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz" - integrity sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g== - dependencies: - cli-truncate "^2.1.0" - colorette "^2.0.16" - log-update "^4.0.0" - p-map "^4.0.0" - rfdc "^1.3.0" - rxjs "^7.5.1" - through "^2.3.8" - wrap-ansi "^7.0.0" +linkinator@^7.6.1: + version "7.6.1" + resolved "https://registry.yarnpkg.com/linkinator/-/linkinator-7.6.1.tgz#b90a46b561eb8e8b4e4270bd558660308fd81781" + integrity sha512-+VzKKZXA8wyCW1x0B0YeQvyuenDu3vZGIWMdzK1yEK0HlBfmEKopt87+Judt4VdPOGRMioKzJK0+X8ifIvOc5Q== + dependencies: + chalk "^5.0.0" + escape-html "^1.0.3" + glob "^13.0.0" + htmlparser2 "^10.0.0" + marked "^17.0.0" + marked-gfm-heading-id "^4.1.3" + meow "^14.0.0" + mime "^4.0.0" + srcset "^5.0.0" + undici "^7.16.0" loader-utils@^2.0.0: version "2.0.4" @@ -5029,13 +4085,6 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -locate-path@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz" - integrity sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA== - dependencies: - p-locate "^6.0.0" - lodash.debounce@4.0.8, lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz" @@ -5046,39 +4095,16 @@ lodash.merge@^4.6.2: resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash.once@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz" - integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== - lodash.unescape@4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz" integrity sha512-DhhGRshNS1aX6s5YdBE3njCCouPgnG29ebyHvImlZzXZf2SHgt+J08DHgytTPnpywNbO1Y8mNUFyQuIDBq2JZg== -lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.23: +lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21: version "4.17.23" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz" integrity sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w== -log-symbols@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -log-update@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz" - integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== - dependencies: - ansi-escapes "^4.3.0" - cli-cursor "^3.1.0" - slice-ansi "^4.0.0" - wrap-ansi "^6.2.0" - loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" @@ -5093,6 +4119,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lru-cache@^11.0.0: + version "11.3.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.3.5.tgz#29047d348c0b2793e3112a01c739bb7c6d855637" + integrity sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz" @@ -5107,11 +4138,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -lru-cache@^7.14.1: - version "7.18.3" - resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz" - integrity sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA== - make-dir@^3.0.2, make-dir@^3.1.0: version "3.1.0" resolved "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz" @@ -5124,10 +4150,22 @@ make-error@^1.1.1: resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== -map-stream@~0.1.0: - version "0.1.0" - resolved "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz" - integrity sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g== +marked-gfm-heading-id@^4.1.3: + version "4.1.4" + resolved "https://registry.yarnpkg.com/marked-gfm-heading-id/-/marked-gfm-heading-id-4.1.4.tgz#9f0ee7bace35ce9c90c58700593d6cdbb4618706" + integrity sha512-CspnvVfHSkb/znqdPS4jUR8HtCjq3M/DnrsJCrfLBLvdrgbemmoINKpeWKQYkBiXAoBGejw0cV7xzqrPdup3WA== + dependencies: + github-slugger "^2.0.0" + +marked@^17.0.0: + version "17.0.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-17.0.6.tgz#2a97586a272d3be5880f198e020b74ad27cf86ba" + integrity sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA== + +marked@^18.0.2: + version "18.0.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-18.0.2.tgz#180cb158a2d2dc377821cfb088a10ca1b5630ef0" + integrity sha512-NsmlUYBS/Zg57rgDWMYdnre6OTj4e+qq/JS2ot3KrYLSoHLw+sDu0Nm1ZGpRgYAq6c+b1ekaY5NzVchMCQnzcg== math-intrinsics@^1.1.0: version "1.1.0" @@ -5197,10 +4235,10 @@ memoize-one@^5.1.1: resolved "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== +meow@^14.0.0: + version "14.1.0" + resolved "https://registry.yarnpkg.com/meow/-/meow-14.1.0.tgz#3cd2d16ad534829ab12fcb5010fc2fdb89facd31" + integrity sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw== merge2@^1.3.0: version "1.4.1" @@ -5409,22 +4447,10 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.35" - resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" - integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== - dependencies: - mime-db "1.52.0" - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mime@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-4.1.0.tgz#ec55df7aa21832a36d44f0bbee5c04639b27802f" + integrity sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw== minimatch@^10.2.2: version "10.2.4" @@ -5440,12 +4466,17 @@ minimatch@^3.1.1, minimatch@^3.1.2, minimatch@^3.1.3: dependencies: brace-expansion "^1.1.7" -minimist@^1.2.0, minimist@^1.2.6, minimist@^1.2.8: +minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -mitt@3.0.1, mitt@^3.0.0: +minipass@^7.1.2, minipass@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.3.tgz#79389b4eb1bb2d003a9bba87d492f2bd37bdc65b" + integrity sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A== + +mitt@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz" integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== @@ -5494,11 +4525,6 @@ neo-async@^2.6.2: resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== -netmask@^2.0.2: - version "2.0.2" - resolved "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz" - integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== - next-auth@4.23.2: version "4.23.2" resolved "https://registry.npmjs.org/next-auth/-/next-auth-4.23.2.tgz" @@ -5555,14 +4581,6 @@ node-exports-info@^1.6.0: object.entries "^1.1.9" semver "^6.3.1" -node-html-parser@5.3.3: - version "5.3.3" - resolved "https://registry.npmjs.org/node-html-parser/-/node-html-parser-5.3.3.tgz" - integrity sha512-ncg1033CaX9UexbyA7e1N0aAoAYRDiV8jkTvzEnfd1GDvzFdrsXLzR4p4ik8mwLgnaKP/jyUFWDy9q3jvRT2Jw== - dependencies: - css-select "^4.2.1" - he "1.2.0" - node-releases@^2.0.27: version "2.0.27" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz" @@ -5578,13 +4596,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -npm-run-path@^4.0.0, npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - nth-check@^2.0.1: version "2.1.1" resolved "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz" @@ -5673,20 +4684,13 @@ oidc-token-hash@^5.0.3: resolved "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.2.0.tgz" integrity sha512-6gj2m8cJZ+iSW8bm0FXdGF0YhIQbKrfP4yWTNzxc31U6MOjfEmB1rHvlYvxI1B7t7BCi1F2vYTT6YhtQRG4hxw== -once@^1.3.0, once@^1.3.1, once@^1.4.0: +once@^1.3.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -5714,20 +4718,6 @@ optionator@^0.9.3: type-check "^0.4.0" word-wrap "^1.2.5" -ospath@^1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz" - integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== - -otplib@^12.0.1: - version "12.0.1" - resolved "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz" - integrity sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg== - dependencies: - "@otplib/core" "^12.0.1" - "@otplib/preset-default" "^12.0.1" - "@otplib/preset-v11" "^12.0.1" - own-keys@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz" @@ -5751,13 +4741,6 @@ p-limit@^3.0.2: dependencies: yocto-queue "^0.1.0" -p-limit@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz" - integrity sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ== - dependencies: - yocto-queue "^1.0.0" - p-locate@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz" @@ -5772,47 +4755,11 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-locate@^6.0.0: - version "6.0.0" - resolved "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz" - integrity sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw== - dependencies: - p-limit "^4.0.0" - -p-map@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz" - integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== - dependencies: - aggregate-error "^3.0.0" - p-try@^2.0.0: version "2.2.0" resolved "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pac-proxy-agent@^7.1.0: - version "7.2.0" - resolved "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz" - integrity sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA== - dependencies: - "@tootallnate/quickjs-emscripten" "^0.23.0" - agent-base "^7.1.2" - debug "^4.3.4" - get-uri "^6.0.1" - http-proxy-agent "^7.0.0" - https-proxy-agent "^7.0.6" - pac-resolver "^7.0.1" - socks-proxy-agent "^8.0.5" - -pac-resolver@^7.0.1: - version "7.0.1" - resolved "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz" - integrity sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg== - dependencies: - degenerator "^5.0.0" - netmask "^2.0.2" - parent-module@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" @@ -5840,17 +4787,12 @@ path-exists@^4.0.0: resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-exists@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz" - integrity sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ== - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== -path-key@^3.0.0, path-key@^3.1.0: +path-key@^3.1.0: version "3.1.1" resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== @@ -5860,28 +4802,24 @@ path-parse@^1.0.7: resolved "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.2.tgz#6be0d0ee02a10d9e0de7a98bae65e182c9061f85" + integrity sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + path-type@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pause-stream@0.0.11: - version "0.0.11" - resolved "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz" - integrity sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A== - dependencies: - through "~2.3" - pend@~1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz" integrity sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg== -performance-now@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz" - integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== - picocolors@^1.0.0, picocolors@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" @@ -5897,11 +4835,6 @@ picomatch@^4.0.3: resolved "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== -pify@^2.2.0: - version "2.3.0" - resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - pkg-dir@^4.1.0: version "4.2.0" resolved "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz" @@ -5968,26 +4901,11 @@ prettier@^3.6.2: resolved "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz" integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg== -pretty-bytes@^5.6.0: - version "5.6.0" - resolved "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz" - integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== - pretty-format@^3.8.0: version "3.8.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz" integrity sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew== -process@^0.11.10: - version "0.11.10" - resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" - integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== - -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - prop-types@^15.0.0, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" @@ -6002,78 +4920,11 @@ property-information@^6.0.0: resolved "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz" integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== -proxy-agent@^6.4.0: - version "6.5.0" - resolved "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz" - integrity sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A== - dependencies: - agent-base "^7.1.2" - debug "^4.3.4" - http-proxy-agent "^7.0.1" - https-proxy-agent "^7.0.6" - lru-cache "^7.14.1" - pac-proxy-agent "^7.1.0" - proxy-from-env "^1.1.0" - socks-proxy-agent "^8.0.5" - -proxy-from-env@1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz" - integrity sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A== - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -ps-tree@1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz" - integrity sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA== - dependencies: - event-stream "=3.3.4" - -pump@^3.0.0: - version "3.0.3" - resolved "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz" - integrity sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - punycode@^2.1.0: version "2.3.1" resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -puppeteer-core@22.15.0: - version "22.15.0" - resolved "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz" - integrity sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA== - dependencies: - "@puppeteer/browsers" "2.3.0" - chromium-bidi "0.6.3" - debug "^4.3.6" - devtools-protocol "0.0.1312386" - ws "^8.18.0" - -puppeteer@^22.15.0: - version "22.15.0" - resolved "https://registry.npmjs.org/puppeteer/-/puppeteer-22.15.0.tgz" - integrity sha512-XjCY1SiSEi1T7iSYuxS82ft85kwDJUS7wj1Z0eGVXKdtr5g4xnVcbjwxhq5xBnpK/E7x1VZZoJDxpjAOasHT4Q== - dependencies: - "@puppeteer/browsers" "2.3.0" - cosmiconfig "^9.0.0" - devtools-protocol "0.0.1312386" - puppeteer-core "22.15.0" - -qs@~6.14.1: - version "6.14.2" - resolved "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz" - integrity sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q== - dependencies: - side-channel "^1.1.0" - queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" @@ -6341,18 +5192,6 @@ remark-rehype@^10.0.0: mdast-util-to-hast "^12.1.0" unified "^10.0.0" -request-progress@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz" - integrity sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg== - dependencies: - throttleit "^1.0.0" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== - resize-observer-polyfill@^1.5.0: version "1.5.1" resolved "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz" @@ -6389,24 +5228,11 @@ resolve@^2.0.0-next.5: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - reusify@^1.0.4: version "1.1.0" resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== -rfdc@^1.3.0: - version "1.4.1" - resolved "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz" - integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== - rimraf@^2.5.2: version "2.7.1" resolved "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz" @@ -6421,13 +5247,6 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -rxjs@^7.5.1, rxjs@^7.8.2: - version "7.8.2" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz" - integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== - dependencies: - tslib "^2.1.0" - sade@^1.7.3: version "1.8.1" resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz" @@ -6446,11 +5265,6 @@ safe-array-concat@^1.1.3: has-symbols "^1.1.0" isarray "^2.0.5" -safe-buffer@^5.0.1, safe-buffer@^5.1.2: - version "5.2.1" - resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - safe-push-apply@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz" @@ -6468,7 +5282,7 @@ safe-regex-test@^1.0.3, safe-regex-test@^1.1.0: es-errors "^1.3.0" is-regex "^1.2.1" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -6634,11 +5448,6 @@ side-channel@^1.1.0: side-channel-map "^1.0.1" side-channel-weakmap "^1.0.2" -signal-exit@^3.0.2, signal-exit@^3.0.3: - version "3.0.7" - resolved "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz" - integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== - simple-swizzle@^0.2.2: version "0.2.4" resolved "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz" @@ -6655,34 +5464,11 @@ sirv@^2.0.3: mrmime "^2.0.0" totalist "^3.0.0" -slice-ansi@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz" - integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - -slice-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz" - integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== - dependencies: - ansi-styles "^4.0.0" - astral-regex "^2.0.0" - is-fullwidth-code-point "^3.0.0" - slick-carousel@1.8.1: version "1.8.1" resolved "https://registry.npmjs.org/slick-carousel/-/slick-carousel-1.8.1.tgz" integrity sha512-XB9Ftrf2EEKfzoQXt3Nitrt/IPbT+f1fgqBdoxO3W/+JYvtEOW6EgxnWfr9GH6nmULv7Y2tPmEX3koxThVmebA== -smart-buffer@^4.2.0: - version "4.2.0" - resolved "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz" - integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== - snake-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz" @@ -6691,84 +5477,31 @@ snake-case@^3.0.4: dot-case "^3.0.4" tslib "^2.0.3" -socks-proxy-agent@^8.0.5: - version "8.0.5" - resolved "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz" - integrity sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw== - dependencies: - agent-base "^7.1.2" - debug "^4.3.4" - socks "^2.8.3" - -socks@^2.8.3: - version "2.8.7" - resolved "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz" - integrity sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A== - dependencies: - ip-address "^10.0.1" - smart-buffer "^4.2.0" - source-map-js@^1.0.1, source-map-js@^1.0.2, source-map-js@^1.2.1: 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== -source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - space-separated-tokens@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz" integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== -split@0.3: - version "0.3.3" - resolved "https://registry.npmjs.org/split/-/split-0.3.3.tgz" - integrity sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA== - dependencies: - through "2" - sprintf-js@1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz" integrity sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug== -sshpk@^1.18.0: - version "1.18.0" - resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz" - integrity sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ== - dependencies: - asn1 "~0.2.3" - assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" - dashdash "^1.12.0" - ecc-jsbn "~0.1.1" - getpass "^0.1.1" - jsbn "~0.1.0" - safer-buffer "^2.0.2" - tweetnacl "~0.14.0" +srcset@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/srcset/-/srcset-5.0.3.tgz#4d174bcff89a4ac995ec3d23b213bf8661bbc8ca" + integrity sha512-AZswtOXIsu0LeHdo6YY7d0r2pCH2Rl1D8ae1utvXUX4GxG3RggsVUAOFX1r8RI4YHFMYb4g89+UBPBv3mNUU2g== stable-hash@^0.0.5: version "0.0.5" resolved "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz" integrity sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA== -start-server-and-test@^2.0.12: - version "2.1.5" - resolved "https://registry.npmjs.org/start-server-and-test/-/start-server-and-test-2.1.5.tgz" - integrity sha512-A/SbXpgXE25ScSkpLLqvGvVZT0ykN6+AzS8tVqMBCTxbJy2Nwuen59opT+afalK5aS+AuQmZs0EsLwjnuDN+/g== - dependencies: - arg "^5.0.2" - bluebird "3.7.2" - check-more-types "2.24.0" - debug "4.4.3" - execa "5.1.1" - lazy-ass "1.6.0" - ps-tree "1.2.0" - wait-on "9.0.4" - stop-iteration-iterator@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz" @@ -6777,41 +5510,16 @@ stop-iteration-iterator@^1.1.0: es-errors "^1.3.0" internal-slot "^1.1.0" -stream-combiner@~0.0.4: - version "0.0.4" - resolved "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz" - integrity sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw== - dependencies: - duplexer "~0.1.1" - streamsearch@^1.1.0: version "1.1.0" resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -streamx@^2.12.5, streamx@^2.15.0, streamx@^2.21.0: - version "2.23.0" - resolved "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz" - integrity sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg== - dependencies: - events-universal "^1.0.0" - fast-fifo "^1.3.2" - text-decoder "^1.1.0" - string-convert@^0.2.0: version "0.2.1" resolved "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz" integrity sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A== -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string.prototype.includes@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz" @@ -6880,23 +5588,11 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz" integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz" @@ -6923,13 +5619,6 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz" @@ -6963,40 +5652,6 @@ symbol-observable@^1.2.0: resolved "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== -tar-fs@^3.0.6: - version "3.1.1" - resolved "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz" - integrity sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg== - dependencies: - pump "^3.0.0" - tar-stream "^3.1.5" - optionalDependencies: - bare-fs "^4.0.1" - bare-path "^3.0.0" - -tar-stream@^3.1.5: - version "3.1.7" - resolved "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz" - integrity sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ== - dependencies: - b4a "^1.6.4" - fast-fifo "^1.2.0" - streamx "^2.15.0" - -teex@^1.0.1: - version "1.0.1" - resolved "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz" - integrity sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg== - dependencies: - streamx "^2.12.5" - -text-decoder@^1.1.0: - version "1.2.7" - resolved "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz" - integrity sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ== - dependencies: - b4a "^1.6.4" - text-segmentation@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz" @@ -7014,21 +5669,6 @@ theming@^3.3.0: react-display-name "^0.2.4" tiny-warning "^1.0.2" -thirty-two@^1.0.2: - version "1.0.2" - resolved "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz" - integrity sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA== - -throttleit@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz" - integrity sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ== - -through@2, through@^2.3.8, through@~2.3, through@~2.3.1: - version "2.3.8" - resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz" - integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== - tiny-invariant@^1.0.6: version "1.3.3" resolved "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz" @@ -7047,23 +5687,6 @@ tinyglobby@^0.2.13, tinyglobby@^0.2.15: fdir "^6.5.0" picomatch "^4.0.3" -tldts-core@^6.1.86: - version "6.1.86" - resolved "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz" - integrity sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA== - -tldts@^6.1.32: - version "6.1.86" - resolved "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz" - integrity sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ== - dependencies: - tldts-core "^6.1.86" - -tmp@~0.2.3: - version "0.2.5" - resolved "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz" - integrity sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow== - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" @@ -7076,18 +5699,6 @@ totalist@^3.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8" integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ== -tough-cookie@^5.0.0: - version "5.1.2" - resolved "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz" - integrity sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A== - dependencies: - tldts "^6.1.32" - -tree-kill@1.2.2: - version "1.2.2" - resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz" - integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== - trim-lines@^3.0.0: version "3.0.1" resolved "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz" @@ -7132,28 +5743,16 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0: +tslib@^2.0.3, tslib@^2.4.0: version "2.8.1" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== -tunnel-agent@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz" - integrity sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w== - dependencies: - safe-buffer "^5.0.1" - tween-functions@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/tween-functions/-/tween-functions-1.2.0.tgz" integrity sha512-PZBtLYcCLtEcjL14Fzb1gSxPBeL7nWvGhO5ZFPGqziCcr8uvHp0NDmdjBchp6KHL+tExcg0m3NISmKxhU394dA== -tweetnacl@^0.14.3, tweetnacl@~0.14.0: - version "0.14.5" - resolved "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz" - integrity sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA== - type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz" @@ -7161,16 +5760,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.8.0: - version "0.8.1" - resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - typed-array-buffer@^1.0.3: version "1.0.3" resolved "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz" @@ -7231,19 +5820,16 @@ unbox-primitive@^1.1.0: has-symbols "^1.1.0" which-boxed-primitive "^1.1.1" -unbzip2-stream@^1.4.3: - version "1.4.3" - resolved "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz" - integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== - dependencies: - buffer "^5.2.1" - through "^2.3.8" - undici-types@~7.16.0: version "7.16.0" resolved "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz" integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== +undici@^7.16.0: + version "7.25.0" + resolved "https://registry.yarnpkg.com/undici/-/undici-7.25.0.tgz#7d72fc429a0421769ca2966fd07cac875c85b781" + integrity sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz" @@ -7323,11 +5909,6 @@ unist-util-visit@^4.0.0: unist-util-is "^5.0.0" unist-util-visit-parents "^5.1.1" -universalify@^2.0.0: - version "2.0.1" - resolved "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz" - integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw== - unrs-resolver@^1.6.2: version "1.11.1" resolved "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz" @@ -7355,11 +5936,6 @@ unrs-resolver@^1.6.2: "@unrs/resolver-binding-win32-ia32-msvc" "1.11.1" "@unrs/resolver-binding-win32-x64-msvc" "1.11.1" -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - update-browserslist-db@^1.2.0: version "1.2.3" resolved "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz" @@ -7375,11 +5951,6 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -urlpattern-polyfill@10.0.0: - version "10.0.0" - resolved "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz" - integrity sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg== - use-memo-one@^1.1.1: version "1.1.3" resolved "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz" @@ -7412,15 +5983,6 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -verror@1.10.0: - version "1.10.0" - resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz" - integrity sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw== - dependencies: - assert-plus "^1.0.0" - core-util-is "1.0.2" - extsprintf "^1.2.0" - vfile-location@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/vfile-location/-/vfile-location-4.1.0.tgz" @@ -7447,17 +6009,6 @@ vfile@^5.0.0: unist-util-stringify-position "^3.0.0" vfile-message "^3.0.0" -wait-on@9.0.4: - version "9.0.4" - resolved "https://registry.npmjs.org/wait-on/-/wait-on-9.0.4.tgz" - integrity sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ== - dependencies: - axios "^1.13.5" - joi "^18.0.2" - lodash "^4.17.23" - minimist "^1.2.8" - rxjs "^7.8.2" - web-namespaces@^2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz" @@ -7547,24 +6098,6 @@ word-wrap@^1.2.5: resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" @@ -7575,16 +6108,6 @@ ws@^7.3.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.18.0: - version "8.19.0" - resolved "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz" - integrity sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - yallist@^3.0.2: version "3.1.1" resolved "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz" @@ -7595,25 +6118,7 @@ yallist@^4.0.0: resolved "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^21.1.1: - version "21.1.1" - resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^17.7.2: - version "17.7.2" - resolved "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz" - integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== - dependencies: - cliui "^8.0.1" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.1.1" - -yauzl@^2.10.0, yauzl@^2.9.2: +yauzl@^2.9.2: version "2.10.0" resolved "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz" integrity sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g== @@ -7631,16 +6136,6 @@ yocto-queue@^0.1.0: resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -yocto-queue@^1.0.0: - version "1.2.2" - resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz" - integrity sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ== - -zod@3.23.8: - version "3.23.8" - resolved "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== - zwitch@^2.0.0: version "2.0.4" resolved "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz"