commit c050d25a84b2d0f15980851705f661f084e96b9a Author: chait Date: Sat Sep 12 03:08:23 2026 -0400 RRS First version diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf3f731 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +test-results/ +playwright-report/ +.DS_Store diff --git a/index.html b/index.html new file mode 100644 index 0000000..a0d4ef5 --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + + + + RRS — Radhe Radhe Synthetics + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a60108d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1943 @@ +{ + "name": "rrs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "rrs", + "version": "1.0.0", + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.6.0" + }, + "devDependencies": { + "@playwright/test": "^1.52.0", + "@vitejs/plugin-react": "^4.5.0", + "vite": "^6.3.5" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@playwright/test": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.63.0.tgz", + "integrity": "sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.1.tgz", + "integrity": "sha512-UZ8sUxPTiHWYX9QNdJedb1kDZSpS1t/VPWBWGSgqHNi9w3Cu6IXvu2mzbhiTiPvtrqgTQJ+zqiAq2iPIPilpaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.1.tgz", + "integrity": "sha512-cQ4nFQABN5cDvDpbvJ7bMStCpnaVxynZrRMfUJYgxcIk9Sh54FIO1vtfkg0B69REjER77ioZ/ov+eAApx/KmLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.1.tgz", + "integrity": "sha512-FQNqd1lRy/0QhDk3xeRIkSBiCpXCiDnZO3YLVdcDKN1UBiKToNftCzcXYNLshmPDUMlu2TdeS8tGcsU6f3YF1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.1.tgz", + "integrity": "sha512-pvD16V939D3CloK0+qikpGaxiPrDUXTe7Y5cWOMkMSy7m1cawa8EGy/kXYi/G/cKAC4HDAbSnzCIk1WmsoOKXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.1.tgz", + "integrity": "sha512-pcFGeL2345VwdTnJhA6zLbew+YgWB0qBG2+dMtXjCicf6+rm6kO6cOoh5VnTe0ZMrMRgRyuHmCJxZWrIdzYuOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.1.tgz", + "integrity": "sha512-mRJlqSRulVzcKq/LKA6ICSIc3K/l4fzlVn/gePn2nXIHy8seRi5z/eeRE0d/XMBxcMldiXtQTSpRj0tkkC3g8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.1.tgz", + "integrity": "sha512-YDUNvVM85TI3g/1OpnqKP1h4NeW/j64DfWMf+G3M809xNk1bJSnpFp4sh83NpmVE5DXnkh8ULor4LTVZKoYLHw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.1.tgz", + "integrity": "sha512-7Mcn71p9ZuQFAj+h+dhQXy/yeLePRS2yKRnmW1DijA9thKO5qap0GNOIQK4yQ6iP3SU0Mrb/yWo8h8vgRba8lw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.1.tgz", + "integrity": "sha512-4YiLQTX6U4CSl0L9cluep9A9W6UmTfqBDc2/CH6wlu54pl4E7Jn3cOD8oxzvBDEGk/JMKgJ47C8g+radF7mwvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.1.tgz", + "integrity": "sha512-2ra8F7w8OquwZN9z2/fKFnli69wa8PLwaVzRMIPGb13ByMJwC28Fbp8YcVGoUhlYMTt7j5j9bNgpysrN2UM+vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.1.tgz", + "integrity": "sha512-Sy20ncyhjmBP0Ml+UvQbimjlk6VFgjW5uNP+qqwHB00mTE8Bl2C1TuHTlRwK2YoXeZbee5lP2XevBWVkAQAtSQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.1.tgz", + "integrity": "sha512-noITLp8oNjYliPnGWmLyelIHwULGqbHloQHGw1rtxbWhTuWooRpnZarZQJ1y9EUC4szuCusCc+HEpUtxpIwYvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.1.tgz", + "integrity": "sha512-hlxxXd+F1mWiAcaFR7Sv9ZQT6m6UfI8+Vy/kFJzztq2pDMU/0wZ9sish0iszNZvsQDo8Gc0i5yuFEOz5dDf6fA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.1.tgz", + "integrity": "sha512-EF7OpqQTQ/BvGqLzUi4rEHuagCV9MugAUXSHemwPW5vxZ75RR+jxO/2j95Ph2dalMpFHSVECjRoioHZgA9zOYA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.1.tgz", + "integrity": "sha512-wQO3JesW9PRkwlabQ27y7sPfVOOTLRG73I4F2UYHG5PXun3J9U3y+b7ezVKSYbsvSKGQ1k1cq8Qlun4C9kLt3w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.1.tgz", + "integrity": "sha512-ouAGwhO6wHRXdnOVCOsB0tRFkA7nhNB2Nwax6oECXN0YiN8EYUTBAOudADOB1PI+yDL61TeNx/u7MVCzksNbkQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.1.tgz", + "integrity": "sha512-q2R38Sn+1J8RxhfJ+T54wSWmyKXWec+9jgDfqO2AtArEqHO5R2aeayp5H5OYLr5UYDVGsVaZPEFUooMhYCdz5A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.1.tgz", + "integrity": "sha512-gfI5T24WLLuFfSKw7Go/zDXjAAV0fny0swTaDv+WjK7vqcw4cRhFfdsyKL1n+ukI+ooBxn3bVQnyrn06WpI50w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.1.tgz", + "integrity": "sha512-4h6XqthmB4Hspji84wvgk+ElodTsGj+dbZqHJHHtKxj4mYq0ANSEEPX9ys3moJueqsRjwpaJYH7874Itwnj2ow==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.1.tgz", + "integrity": "sha512-dlfCOa87o1VAYegLQ9EKilx2JCeRofiyPGhTCmqnuXZ6bMPiycO1rq1+sKoulAp7pGLIsTIw+1x5R+zgh5LhhA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.1.tgz", + "integrity": "sha512-cjkLbOlfcm3QGhMM1J5zaZjsw1GggbN6rw9UTSSRrPrR1KkcXnN7Uq9rPw34xImQ9VOY9GN+6u2Zj80B9ptkcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.1.tgz", + "integrity": "sha512-Li1KdUnWGE4N3e1F/B4RTB1ms+nG4WBgjByO46pkeBVX/2UBsY53xf5vK9WygVmnH3RwncIST7lkSdLSY6P9lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.1.tgz", + "integrity": "sha512-t4ZYOSoLTgwhuFMrmTMLx/+i1DQVK7HYqMc6kY46EApwi8X0nIVphzdNoThU3xt6n+N5urG1/gxBdCaKDLavfg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.1.tgz", + "integrity": "sha512-RgroPfMmKlD1RzSDxvwgcPiy2HNQKoYV7OmwIXDsk73uKW5t6B/V8KIy27SMv/FNXFo/oSBtWc9J0X7t91ezZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.1.tgz", + "integrity": "sha512-at8QVep6S3h5Y6gSbdGU06bRY5WJkf6WUduM9YtvYMbYhB1MOFfUgc6kehitQXzOtMSaT70q7f9ydPhpqu821w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz", + "integrity": "sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.9.tgz", + "integrity": "sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.20", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.420", + "node-releases": "^2.0.54", + "update-browserslist-db": "^1.3.2" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.425", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.425.tgz", + "integrity": "sha512-QvPtl41EUOnuT1HBvMKgxXRIaHNcagBPs50u7VULzhZXaGfqTbZyE16LQsctZ/RQHlGu+FOWeDTR4mY6YbeF1g==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playwright": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.63.0.tgz", + "integrity": "sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.63.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/playwright-core": { + "version": "1.63.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.63.0.tgz", + "integrity": "sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", + "integrity": "sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.3.tgz", + "integrity": "sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rollup": { + "version": "4.63.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.1.tgz", + "integrity": "sha512-3Df9jsstwhccuEfmAMi9l8XUh/GOkVObmFTU7CCVBysEbcOZLl84jCtaAZMcPiMz2EGKsATzQcU+Xr3n/wU6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.1", + "@rollup/rollup-android-arm64": "4.63.1", + "@rollup/rollup-darwin-arm64": "4.63.1", + "@rollup/rollup-darwin-x64": "4.63.1", + "@rollup/rollup-freebsd-arm64": "4.63.1", + "@rollup/rollup-freebsd-x64": "4.63.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.1", + "@rollup/rollup-linux-arm-musleabihf": "4.63.1", + "@rollup/rollup-linux-arm64-gnu": "4.63.1", + "@rollup/rollup-linux-arm64-musl": "4.63.1", + "@rollup/rollup-linux-loong64-gnu": "4.63.1", + "@rollup/rollup-linux-loong64-musl": "4.63.1", + "@rollup/rollup-linux-ppc64-gnu": "4.63.1", + "@rollup/rollup-linux-ppc64-musl": "4.63.1", + "@rollup/rollup-linux-riscv64-gnu": "4.63.1", + "@rollup/rollup-linux-riscv64-musl": "4.63.1", + "@rollup/rollup-linux-s390x-gnu": "4.63.1", + "@rollup/rollup-linux-x64-gnu": "4.63.1", + "@rollup/rollup-linux-x64-musl": "4.63.1", + "@rollup/rollup-openbsd-x64": "4.63.1", + "@rollup/rollup-openharmony-arm64": "4.63.1", + "@rollup/rollup-win32-arm64-msvc": "4.63.1", + "@rollup/rollup-win32-ia32-msvc": "4.63.1", + "@rollup/rollup-win32-x64-gnu": "4.63.1", + "@rollup/rollup-win32-x64-msvc": "4.63.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz", + "integrity": "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..66e673b --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "rrs", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "preview": "vite preview --host 0.0.0.0", + "test": "playwright test" + }, + "dependencies": { + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.6.0" + }, + "devDependencies": { + "@playwright/test": "^1.52.0", + "@vitejs/plugin-react": "^4.5.0", + "vite": "^6.3.5" + } +} diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 0000000..1fa136f --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from "@playwright/test"; + +export default defineConfig({ + testDir: "./tests", + fullyParallel: true, + workers: 2, + reporter: "list", + use: { baseURL: "http://127.0.0.1:5173", screenshot: "only-on-failure" }, + webServer: { + command: "npm run dev -- --port 5173 --strictPort", + url: "http://127.0.0.1:5173", + reuseExistingServer: !process.env.CI, + }, +}); diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..048509c --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + diff --git a/public/fonts/dm-sans-OFL.txt b/public/fonts/dm-sans-OFL.txt new file mode 100644 index 0000000..4430b85 --- /dev/null +++ b/public/fonts/dm-sans-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2014 The DM Sans Project Authors (https://github.com/googlefonts/dm-fonts) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/dm-sans.woff2 b/public/fonts/dm-sans.woff2 new file mode 100644 index 0000000..01383d7 Binary files /dev/null and b/public/fonts/dm-sans.woff2 differ diff --git a/public/fonts/instrument-serif-OFL.txt b/public/fonts/instrument-serif-OFL.txt new file mode 100644 index 0000000..669ce79 --- /dev/null +++ b/public/fonts/instrument-serif-OFL.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Instrument Serif Project Authors (https://github.com/Instrument/instrument-serif) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/instrument-serif-italic.ttf b/public/fonts/instrument-serif-italic.ttf new file mode 100644 index 0000000..25d2a82 Binary files /dev/null and b/public/fonts/instrument-serif-italic.ttf differ diff --git a/public/fonts/instrument-serif-regular.ttf b/public/fonts/instrument-serif-regular.ttf new file mode 100644 index 0000000..8794c69 Binary files /dev/null and b/public/fonts/instrument-serif-regular.ttf differ diff --git a/public/images/knit-fine.jpg b/public/images/knit-fine.jpg new file mode 100644 index 0000000..7a8e53a Binary files /dev/null and b/public/images/knit-fine.jpg differ diff --git a/public/images/knit-natural.jpg b/public/images/knit-natural.jpg new file mode 100644 index 0000000..df1ce93 Binary files /dev/null and b/public/images/knit-natural.jpg differ diff --git a/public/images/knit-structured.jpg b/public/images/knit-structured.jpg new file mode 100644 index 0000000..a8fb8a5 Binary files /dev/null and b/public/images/knit-structured.jpg differ diff --git a/scripts/generate-textures.py b/scripts/generate-textures.py new file mode 100644 index 0000000..ab2305e --- /dev/null +++ b/scripts/generate-textures.py @@ -0,0 +1,48 @@ +"""Create original illustrative knit textures, not photographs of RRS products. +Requires Pillow and NumPy. Generated assets are committed; runtime needs neither. +""" +from pathlib import Path +import math +import random +import numpy as np +from PIL import Image, ImageDraw, ImageFilter + +OUTPUT = Path(__file__).resolve().parents[1] / 'public' / 'images' +OUTPUT.mkdir(parents=True, exist_ok=True) + + +def texture(name, spacing, base, seed): + rng = random.Random(seed) + width, height = 1600, 1200 + image = Image.new('RGB', (width, height), tuple(round(c * .7) for c in base)) + draw = ImageDraw.Draw(image) + for row in range(-2, round(height / (spacing * .72)) + 3): + for column in range(-2, width // spacing + 3): + x = column * spacing + math.sin(row * .16) * spacing * .16 + y = row * spacing * .72 + tone = rng.uniform(.91, 1.05) + for side in (-1, 1): + points = [] + for step in range(35): + t = step / 34 + px = x + side * spacing * (.04 + .43 * (1 - t) + .07 * math.sin(t * math.pi)) + py = y + spacing * (t * .95 - .15) + points.append((px, py)) + draw.line([(px + 3, py + 4) for px, py in points], fill=tuple(round(c * .55) for c in base), width=round(spacing * .36)) + draw.line(points, fill=tuple(min(255, round(c * tone * .84)) for c in base), width=round(spacing * .32)) + for strand in range(11): + offset = (strand - 5) * spacing * .027 + fibre = [(px + offset + math.sin(i * .9 + strand) * .7, py) for i, (px, py) in enumerate(points)] + light = tone * (.78 + .24 * math.sin(strand * .28)) + draw.line(fibre, fill=tuple(min(255, round(c * light)) for c in base), width=max(1, spacing // 32)) + pixels = np.asarray(image).astype(float) + yy, xx = np.mgrid[0:height, 0:width] + folds = .91 + .14 * np.sin(xx / 210 + yy / 380) + .05 * np.sin(xx / 82 - yy / 230) + noise = np.random.default_rng(seed).normal(0, 2.1, (height, width)) + pixels = np.clip(pixels * folds[:, :, None] + noise[:, :, None], 0, 255).astype('uint8') + Image.fromarray(pixels).filter(ImageFilter.GaussianBlur(.3)).save(OUTPUT / name, quality=90, optimize=True) + + +texture('knit-natural.jpg', 70, (218, 205, 181), 21) +texture('knit-fine.jpg', 27, (221, 214, 197), 42) +texture('knit-structured.jpg', 108, (180, 170, 147), 63) diff --git a/src/base.css b/src/base.css new file mode 100644 index 0000000..a68bb98 --- /dev/null +++ b/src/base.css @@ -0,0 +1,86 @@ +@font-face { + font-family: "Instrument Serif"; + src: url("/fonts/instrument-serif-regular.ttf") format("truetype"); + font-weight: 400; + font-style: normal; + font-display: swap; +} +@font-face { + font-family: "Instrument Serif"; + src: url("/fonts/instrument-serif-italic.ttf") format("truetype"); + font-weight: 400; + font-style: italic; + font-display: swap; +} +@font-face { + font-family: "DM Sans"; + src: url("/fonts/dm-sans.woff2") format("woff2"); + font-weight: 100 1000; + font-style: normal; + font-display: swap; +} +:root { + --paper: #f5f3ee; + --ink: #282a23; + --muted: #686b61; + --line: #d9d9cf; + --wash: #ecebe4; + --accent: #8f725c; + --serif: "Instrument Serif", Georgia, serif; + --sans: "DM Sans", Arial, sans-serif; + --mono: "Courier New", monospace; + --page-gutter: clamp(20px, 4vw, 72px); + color-scheme: light; + font-family: var(--sans); + color: var(--ink); + background: var(--paper); + font-synthesis: none; + -webkit-font-smoothing: antialiased; +} +/* Only box sizing, base document defaults and accessibility are global. + Every visual component owns a BEM block in its own stylesheet. */ +*, +*::before, +*::after { + box-sizing: border-box; +} +body { + margin: 0; + min-width: 320px; +} +button, +input, +textarea, +select { + font: inherit; +} +button, +a { + -webkit-tap-highlight-color: transparent; +} +:focus-visible:not([tabindex="-1"]) { + outline: 2px solid var(--accent); + outline-offset: 5px; +} +::selection { + background: #d1d3bf; + color: #282a23; +} +.not-found { + padding: 100px 0; +} +.not-found__label { + font-size: 14px; + font-weight: 500; + color: var(--muted); +} +.not-found__title { + font-family: var(--serif); + font-size: 72px; + font-weight: 400; + margin: 20px 0; +} +.not-found__text { + margin-bottom: 35px; + color: var(--muted); +} diff --git a/src/components/CapacityValue.css b/src/components/CapacityValue.css new file mode 100644 index 0000000..0fc2ff9 --- /dev/null +++ b/src/components/CapacityValue.css @@ -0,0 +1,63 @@ +.capacity-value { + display: inline-flex; + align-items: baseline; + line-height: 1; + white-space: nowrap; +} +.capacity-value__window { + display: inline-block; + height: 1em; + overflow: hidden; +} +.capacity-value__reel { + display: flex; + flex-direction: column; + height: 4em; +} +.capacity-value__reel--up { + transform: translateY(0); +} +.capacity-value__reel--down { + transform: translateY(-3em); +} +.capacity-value__step { + display: block; + height: 1em; + flex: 0 0 1em; + line-height: 1; +} +.capacity-value__dash { + display: inline-block; +} +.reveal--visible .capacity-value__reel--up { + animation: capacity-roll-up 1.2s cubic-bezier(0.22, 1, 0.36, 1) 180ms both; +} +.reveal--visible .capacity-value__reel--down { + animation: capacity-roll-down 1.2s cubic-bezier(0.22, 1, 0.36, 1) 180ms both; +} +@keyframes capacity-roll-up { + from { + transform: translateY(0); + } + to { + transform: translateY(-3em); + } +} +@keyframes capacity-roll-down { + from { + transform: translateY(-3em); + } + to { + transform: translateY(0); + } +} +@media (prefers-reduced-motion: reduce) { + .reveal--visible .capacity-value__reel--up { + animation: none; + transform: translateY(-3em); + } + .reveal--visible .capacity-value__reel--down { + animation: none; + transform: translateY(0); + } +} diff --git a/src/components/CapacityValue.jsx b/src/components/CapacityValue.jsx new file mode 100644 index 0000000..460a556 --- /dev/null +++ b/src/components/CapacityValue.jsx @@ -0,0 +1,29 @@ +import "./CapacityValue.css"; + +function Reel({ values, direction }) { + return ( + + ); +} + +export default function CapacityValue() { + return ( + + + + + + ); +} diff --git a/src/components/Gallery.css b/src/components/Gallery.css new file mode 100644 index 0000000..02a16e9 --- /dev/null +++ b/src/components/Gallery.css @@ -0,0 +1,125 @@ +.gallery { + padding-top: 100px; + scroll-margin-top: 30px; +} +.gallery__intro { + margin: 0 0 22px; +} +.gallery__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 38px 26px; +} +.sample { + min-width: 0; +} +.sample__visual { + position: relative; + aspect-ratio: 1 / 1.12; + overflow: hidden; + border-radius: 4px; + background: var(--wash); +} +.sample__image { + width: 100%; + height: 100%; + display: block; + object-fit: cover; + transition: transform 700ms ease; +} +.sample:hover .sample__image { + transform: scale(1.045); +} +.sample__badge { + position: absolute; + top: 16px; + left: 16px; + padding: 7px 10px; + background: #f5f3eedb; + border-radius: 2px; + font-size: 11px; +} +.sample__heading { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 12px; + padding-top: 20px; +} +.sample__name { + font-family: var(--serif); + font-size: 27px; + font-weight: 400; + letter-spacing: -0.035em; + margin: 0; +} +.sample__detail { + font-size: 11px; + color: var(--muted); + text-align: right; +} +.sample__pending { + font-size: 11px; + color: var(--muted); + margin: 9px 0 0; +} +.gallery__notice { + display: flex; + align-items: flex-start; + gap: 14px; + margin-top: 32px; + padding: 19px 22px; + background: var(--wash); + border-radius: 4px; + color: var(--muted); +} +.gallery__notice-mark { + flex-shrink: 0; + width: 17px; + height: 17px; + display: grid; + place-content: center; + border: 1px solid currentColor; + border-radius: 50%; + font-family: var(--serif); + font-size: 12px; +} +.gallery__notice-text { + font-size: 11px; + line-height: 1.8; + margin: 0; +} +@media (max-width: 900px) { + .sample__heading { + align-items: flex-start; + flex-direction: column; + gap: 6px; + } +} +@media (max-width: 600px) { + .gallery { + padding-top: 60px; + } + .gallery__grid { + grid-template-columns: 1fr; + gap: 30px; + } + .sample__visual { + aspect-ratio: 1.15; + } + .sample__heading { + flex-direction: row; + align-items: baseline; + } + .gallery__notice { + padding: 16px; + } +} +@media (prefers-reduced-motion: reduce) { + .sample__image { + transition: none; + } + .sample:hover .sample__image { + transform: none; + } +} diff --git a/src/components/Gallery.jsx b/src/components/Gallery.jsx new file mode 100644 index 0000000..e67fb3b --- /dev/null +++ b/src/components/Gallery.jsx @@ -0,0 +1,83 @@ +import { developments } from "../data"; +import { Action, SectionHeading } from "./Primitives"; +import "./Gallery.css"; + +const textures = ["fine", "natural", "structured"]; + +export default function Gallery({ preview = false }) { + const items = preview ? developments.slice(0, 3) : developments; + return ( +
+ + Structure changes +
+ everything. + + ) : ( + <> + From greige +
+ to possibility. + + ) + } + > +

+ {preview + ? "From subtle surface structures to more expressive knitted textures, different yarn combinations and machine setups produce entirely different fabric characteristics." + : "Selected developments are processed separately as reference samples to explore how a construction can translate after dyeing and finishing. RRS commercially manufactures greige fabric only."} +

+ {preview && ( + + View developments + + )} +
+
+ {items.map((name, index) => ( +
+
+ {`Illustrative + Image placeholder +
+
+

{name}

+ Development reference +
+

RRS sample photography to follow.

+
+ ))} +
+ +
+ ); +} diff --git a/src/components/Layout.css b/src/components/Layout.css new file mode 100644 index 0000000..ebf01d6 --- /dev/null +++ b/src/components/Layout.css @@ -0,0 +1,353 @@ +.site-main { + margin-inline: var(--page-gutter); + outline: none; +} +.reveal { + opacity: 0; + clip-path: inset(0 0 12% 0); + transform: translateY(36px); + transition: + opacity 700ms ease, + clip-path 850ms cubic-bezier(0.22, 1, 0.36, 1), + transform 850ms cubic-bezier(0.22, 1, 0.36, 1); + will-change: opacity, clip-path, transform; +} +.reveal--visible { + opacity: 1; + clip-path: inset(0); + transform: translateY(0); + will-change: auto; +} +.skip-link { + position: fixed; + left: 20px; + top: -80px; + z-index: 100; + padding: 16px 24px; + background: var(--ink); + color: var(--paper); +} +.skip-link:focus { + top: 10px; +} +.site-header { + border-bottom: 1px solid var(--line); +} +.site-header__inner { + display: flex; + justify-content: space-between; + align-items: center; + min-height: 100px; + margin-inline: var(--page-gutter); + gap: 30px; +} +.brand { + display: inline-flex; + align-items: center; + gap: 16px; + text-decoration: none; + color: var(--ink); +} +.brand__mark { + font-family: var(--serif); + font-size: 42px; + font-weight: 600; + line-height: 1; + letter-spacing: -0.065em; +} +.brand__period { + color: var(--accent); +} +.brand__name { + border-left: 1px solid var(--line); + padding-left: 16px; + font-size: 9px; + line-height: 1.65; + text-transform: uppercase; + letter-spacing: 0.12em; +} +.site-nav { + display: flex; + align-items: center; + gap: 34px; +} +.site-nav__link { + position: relative; + padding: 14px 0; + color: var(--muted); + font-size: 12px; + text-decoration: none; +} +.site-nav__link--active { + color: var(--ink); +} +.site-nav__link--active::after { + content: ""; + position: absolute; + bottom: 3px; + left: calc(50% - 2px); + width: 4px; + height: 4px; + background: var(--ink); + border-radius: 50%; +} +.site-nav__link:hover { + color: var(--ink); +} +.site-nav__contact { + display: flex; + align-items: center; + gap: 30px; + margin-left: 45px; + padding: 12px 20px; + border: 1px solid var(--ink); + border-radius: 30px; + color: var(--ink); + text-decoration: none; + font-size: 11px; +} +.site-nav__contact:hover { + background: var(--ink); + color: var(--paper); +} +.site-header__toggle { + display: none; +} +.site-footer { + margin-top: 100px; + padding: 55px var(--page-gutter) 24px; + background: var(--wash); +} +.site-footer__top { + display: grid; + grid-template-columns: 1.6fr 0.7fr 1fr; + gap: 60px; +} +.site-footer__name { + font-family: var(--serif); + font-size: 23px; + margin: 0 0 15px; +} +.site-footer__description { + max-width: 310px; + font-size: 13px; + color: var(--muted); + line-height: 1.8; +} +.site-footer__nav, +.site-footer__contact { + display: flex; + align-items: flex-start; + flex-direction: column; + gap: 12px; +} +.site-footer__label { + font-size: 13px; + font-weight: 500; + margin-bottom: 10px; + color: var(--muted); +} +.site-footer__link { + display: flex; + gap: 14px; + align-items: center; + color: var(--ink); + font-size: 12px; + text-decoration: none; +} +.site-footer__link:hover { + text-decoration: underline; + text-underline-offset: 4px; +} +.site-footer__pending { + margin: 0; + color: var(--muted); + font-size: 11px; +} +.site-footer__bottom { + display: flex; + justify-content: space-between; + align-items: flex-end; + padding-top: 60px; +} +.site-footer__wordmark { + font-family: var(--serif); + font-weight: 600; + font-size: clamp(110px, 18vw, 240px); + line-height: 0.88; + letter-spacing: -0.075em; +} +.site-footer__signature { + margin: 0 0 15px; + text-align: right; + font-family: var(--serif); + font-size: clamp(25px, 3vw, 40px); + line-height: 1.15; + letter-spacing: -0.03em; +} +.site-footer__legal { + display: flex; + justify-content: space-between; + gap: 20px; + margin-top: 38px; + padding-top: 23px; + border-top: 1px solid var(--line); + font-family: var(--mono); + font-size: 8px; + line-height: 1.6; + text-transform: uppercase; + letter-spacing: 0.02em; + color: var(--muted); +} +.site-footer__top-link { + color: var(--ink); + text-decoration: none; +} +.contact-banner { + position: relative; + overflow: hidden; + display: flex; + align-items: center; + min-height: 420px; + background: var(--ink); + color: var(--paper); + padding: 64px; + border-radius: 6px; + margin-top: 95px; +} +.contact-banner__copy { + position: relative; + z-index: 1; + max-width: 650px; +} +.contact-banner__label { + margin: 0 0 24px; + font-size: 14px; + font-weight: 500; + color: #c2c3b8; +} +.contact-banner__title { + margin: 0 0 22px; + font-family: var(--serif); + font-size: clamp(36px, 4.2vw, 60px); + font-weight: 400; + line-height: 1.1; + letter-spacing: -0.04em; +} +.contact-banner__text { + max-width: 440px; + color: #c2c3b8; + font-size: 14px; + line-height: 1.9; + margin: 0 0 30px; +} +.contact-banner__motif { + position: absolute; + display: flex; + gap: 25px; + right: -60px; + bottom: -80px; + transform: rotate(30deg); +} +.contact-banner__loop { + display: block; + width: 100px; + height: 430px; + border: 1px solid #74776955; + border-radius: 50%; +} +@media (prefers-reduced-motion: reduce) { + .reveal, + .reveal--visible { + opacity: 1; + clip-path: none; + transform: none; + transition: none; + } +} +@media (max-width: 950px) { + .site-nav { + gap: 23px; + } + .site-nav__contact { + margin-left: 12px; + } + .site-footer__top { + gap: 30px; + } +} +@media (max-width: 760px) { + .site-header__inner { + min-height: 80px; + flex-wrap: wrap; + gap: 0; + } + .brand__mark { + font-size: 35px; + } + .brand__name { + font-size: 8px; + } + .site-header__toggle { + display: flex; + justify-content: space-between; + gap: 25px; + align-items: center; + min-height: 44px; + padding: 8px 0 8px 15px; + border: 0; + color: var(--ink); + background: transparent; + font-size: 12px; + cursor: pointer; + } + .site-nav { + display: none; + width: 100%; + padding: 14px 0 24px; + } + .site-nav--open { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px 24px; + } + .site-nav__link { + padding: 15px 4px; + } + .site-nav__link--active::after { + left: auto; + right: 15px; + bottom: 20px; + } + .site-nav__contact { + grid-column: 1 / -1; + margin: 12px 0 0; + justify-content: space-between; + } + .contact-banner { + padding: 40px 25px; + min-height: 400px; + margin-top: 60px; + } + .site-footer { + padding: 40px 20px 24px; + margin-top: 65px; + } + .site-footer__top { + grid-template-columns: 1fr 1fr; + gap: 32px 20px; + } + .site-footer__intro { + grid-column: 1 / -1; + } + .site-footer__bottom { + padding-top: 45px; + gap: 15px; + } + .site-footer__legal { + flex-wrap: wrap; + } + .site-footer__signature { + font-size: 24px; + } +} diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx new file mode 100644 index 0000000..6382e1d --- /dev/null +++ b/src/components/Layout.jsx @@ -0,0 +1,213 @@ +import { useEffect, useState } from "react"; +import { Link, NavLink, Outlet, useLocation } from "react-router-dom"; +import { company, navigation } from "../data"; +import { Arrow, WhatsAppAction } from "./Primitives"; +import "./Layout.css"; + +function Brand() { + return ( + + + RRS. + + + Radhe Radhe +
+ Synthetics +
+ + ); +} + +function Header() { + const [menuOpen, setMenuOpen] = useState(false); + const closeMenu = () => setMenuOpen(false); + return ( +
{ + if (menuOpen && event.key === "Escape") { + closeMenu(); + document.getElementById("menu-toggle")?.focus(); + } + }} + > +
+ + + +
+
+ ); +} + +function Footer() { + return ( + + ); +} + +export function ContactBanner({ + title = ( + <> + Developing your +
+ next construction? + + ), + text = "Talk to RRS about fabric structure, yarn combinations, bulk requirements or new developments.", +}) { + return ( +
+
+

+ A conversation is where it begins +

+

{title}

+

{text}

+ +
+ +
+ ); +} + +export default function Layout() { + const { pathname, hash, key } = useLocation(); + useEffect(() => { + const title = + navigation.find(({ to }) => to === pathname)?.label ?? "Page not found"; + document.title = `${title === "Home" ? "RRS" : `${title} — RRS`} | Radhe Radhe Synthetics`; + const target = hash + ? document.getElementById(hash.slice(1)) + : document.getElementById("main-content"); + if (hash && target) target.scrollIntoView({ behavior: "instant" }); + else window.scrollTo({ top: 0, behavior: "instant" }); + target?.focus({ preventScroll: true }); + }, [pathname, hash, key]); + + useEffect(() => { + const sections = [...document.querySelectorAll("#main-content > section")]; + const reducedMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ).matches; + + if (reducedMotion || !("IntersectionObserver" in window)) { + sections.forEach((section) => section.classList.add("reveal--visible")); + return undefined; + } + + sections.forEach((section) => section.classList.add("reveal")); + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (!entry.isIntersecting) return; + entry.target.classList.add("reveal--visible"); + observer.unobserve(entry.target); + }); + }, + { threshold: 0.08, rootMargin: "0px 0px -8% 0px" }, + ); + sections.forEach((section) => observer.observe(section)); + + return () => observer.disconnect(); + }, [pathname, key]); + + return ( +
+ + Skip to content + +
+
+ +
+
+
+ ); +} diff --git a/src/components/Primitives.css b/src/components/Primitives.css new file mode 100644 index 0000000..1df8564 --- /dev/null +++ b/src/components/Primitives.css @@ -0,0 +1,194 @@ +.arrow { + flex: 0 0 auto; +} +.action { + display: inline-flex; + align-items: center; + justify-content: space-between; + gap: 30px; + min-height: 48px; + padding: 14px 23px; + border: 1px solid transparent; + border-radius: 100px; + font-size: 12px; + font-weight: 600; + text-decoration: none; + cursor: pointer; + transition: + background 180ms ease, + color 180ms ease; +} +.action--dark { + background: var(--ink); + color: var(--paper); +} +.action--light { + background: var(--paper); + color: var(--ink); +} +.action--outline { + background: transparent; + color: var(--ink); + border-color: var(--line); +} +.action--ghost { + background: transparent; + color: var(--paper); + border-color: #ffffff60; +} +.action--dark:hover:not(:disabled) { + background: #474b3f; +} +.action--light:hover { + background: #dedbcf; +} +.action--outline:hover { + background: var(--wash); +} +.action--ghost:hover { + background: #ffffff15; +} +.action:disabled { + cursor: not-allowed; + opacity: 0.55; +} +.eyebrow { + margin: 0 0 18px; + font-family: var(--sans); + font-size: 14px; + line-height: 1.6; + font-weight: 500; +} +.section-heading { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 45px; + margin-bottom: 44px; +} +.section-heading__title { + max-width: 680px; +} +.section-heading__heading { + margin: 0; + font-family: var(--serif); + font-size: clamp(32px, 3.6vw, 50px); + line-height: 1.12; + font-weight: 400; + letter-spacing: -0.045em; +} +.section-heading__aside { + max-width: 360px; + color: var(--muted); + font-size: 14px; + line-height: 1.8; +} +.section-heading--centered { + display: block; + text-align: center; +} +.section-heading--centered .section-heading__title { + max-width: none; +} +.section-heading--centered .section-heading__aside { + margin: 24px auto 0; +} +.textile { + position: relative; + min-width: 0; + margin: 0; + overflow: hidden; + border-radius: 5px; + background: #aba08b; +} +.textile__image { + display: block; + width: 100%; + height: 100%; + min-height: 300px; + object-fit: cover; + transition: transform 700ms ease; +} +.textile:hover .textile__image { + transform: scale(1.035); +} +.textile__caption { + position: absolute; + left: 18px; + bottom: 16px; + right: 18px; + width: fit-content; + padding: 7px 10px; + border-radius: 2px; + background: #f5f3eeeb; + color: #34352d; + font-size: 11px; + line-height: 1.5; +} +.page-hero { + display: grid; + grid-template-columns: 1.15fr 1fr; + gap: 70px; + padding: 66px 0 74px; +} +.page-hero__copy { + display: flex; + flex-direction: column; + align-items: flex-start; + padding: 22px 0 0; +} +.page-hero__title { + margin: 6px 0 28px; + font-family: var(--serif); + font-weight: 400; + font-size: clamp(48px, 5.6vw, 78px); + letter-spacing: -0.055em; + line-height: 1.04; +} +.page-hero__description { + max-width: 470px; + color: var(--muted); + font-size: 16px; + line-height: 1.9; +} +.page-hero__footnote { + display: block; + padding-top: 46px; + margin-top: auto; + font-size: 12px; + color: var(--muted); +} +@media (max-width: 760px) { + .section-heading { + align-items: flex-start; + flex-direction: column; + gap: 22px; + margin-bottom: 30px; + } + .page-hero { + grid-template-columns: 1fr; + gap: 30px; + padding: 35px 0 50px; + } + .page-hero__footnote { + padding-top: 26px; + } + .page-hero__title { + font-size: clamp(44px, 10vw, 70px); + } + .page-hero__copy { + padding: 0; + } + .textile__image { + max-height: 460px; + } +} +@media (prefers-reduced-motion: reduce) { + .action, + .textile__image { + transition: none; + } + .textile:hover .textile__image { + transform: none; + } +} diff --git a/src/components/Primitives.jsx b/src/components/Primitives.jsx new file mode 100644 index 0000000..a8ef64e --- /dev/null +++ b/src/components/Primitives.jsx @@ -0,0 +1,125 @@ +import { Link } from "react-router-dom"; +import { company, whatsappUrl } from "../data"; +import "./Primitives.css"; + +export function Arrow({ diagonal = false }) { + return ( + + ); +} + +export function Action({ children, to, variant = "dark" }) { + return ( + + {children} + + + ); +} + +export function WhatsAppAction({ + children = "Talk to us on WhatsApp", + variant = "dark", + direct = false, +}) { + const url = whatsappUrl(company.whatsapp); + const className = `action action--${variant}`; + if (url) + return ( + + {children} + + + ); + if (direct) + return ( + + ); + return ( + + {children} + + ); +} + +export function Eyebrow({ children }) { + return

{children}

; +} + +export function SectionHeading({ label, title, children, centered = false }) { + return ( +
+
+ {label} +

{title}

+
+ {children &&
{children}
} +
+ ); +} + +export function Textile({ + kind = "natural", + className = "", + caption = true, + eager = false, +}) { + return ( +
+ Illustrative knitted texture — not an RRS product photograph + {caption && ( +
+ Illustrative texture · RRS photography to follow +
+ )} +
+ ); +} + +export function PageHero({ label, title, children, texture = "natural" }) { + return ( +
+
+ {label} +

{title}

+
{children}
+ + Radhe Radhe Synthetics · Greige knitted fabrics + +
+ +
+ ); +} diff --git a/src/components/Stats.css b/src/components/Stats.css new file mode 100644 index 0000000..3b7121e --- /dev/null +++ b/src/components/Stats.css @@ -0,0 +1,56 @@ +.stats { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 32px; + border-block: 1px solid var(--line); + padding: 42px 0; + margin-top: 78px; +} +.stats__item { + min-width: 0; +} +.stats__label { + margin: 0 0 26px; + font-size: 13px; + font-weight: 500; + line-height: 1.6; + color: var(--muted); +} +.stats__value { + font-family: var(--serif); + font-size: clamp(38px, 4vw, 54px); + letter-spacing: -0.035em; + line-height: 1.1; + margin: 0 0 10px; +} +.stats__unit { + font-size: 12px; + font-weight: 500; + line-height: 1.6; + margin: 0 0 16px; +} +.stats__text { + max-width: 230px; + font-size: 13px; + color: var(--muted); + line-height: 1.8; + margin: 0; +} +@media (max-width: 850px) { + .stats { + grid-template-columns: 1fr 1fr; + gap: 36px 24px; + } +} +@media (max-width: 480px) { + .stats { + margin-top: 45px; + padding: 30px 0; + } + .stats__value { + font-size: 36px; + } + .stats__label { + margin-bottom: 18px; + } +} diff --git a/src/components/Stats.jsx b/src/components/Stats.jsx new file mode 100644 index 0000000..d6bb422 --- /dev/null +++ b/src/components/Stats.jsx @@ -0,0 +1,44 @@ +import CapacityValue from "./CapacityValue"; +import "./Stats.css"; + +const facts = [ + { + label: "Production capacity", + value: , + unit: "Tonnes / month", + text: "Approximate monthly manufacturing capacity.", + }, + { + label: "Our specialisation", + value: "Greige", + unit: "Knitted fabrics only", + text: "The starting point for a world of possibilities.", + }, + { + label: "The capability", + value: "Circular", + unit: "Knitting · multiple constructions", + text: "Different structures. Different configurations.", + }, + { + label: "The approach", + value: "Continuous", + unit: "Fabric development", + text: "Exploring yarns, blends and new constructions.", + }, +]; + +export default function Stats() { + return ( +
+ {facts.map(({ label, value, unit, text }) => ( +
+

{label}

+

{value}

+

{unit}

+

{text}

+
+ ))} +
+ ); +} diff --git a/src/data.js b/src/data.js new file mode 100644 index 0000000..3ee6457 --- /dev/null +++ b/src/data.js @@ -0,0 +1,82 @@ +export const company = Object.freeze({ + name: "Radhe Radhe Synthetics Pvt. Ltd.", + description: + "Greige knitted fabrics engineered for diverse apparel applications.", + whatsapp: "", // Supply the business number in international digits, without + or spaces. + phone: "", + email: "", + address: "", + mapsUrl: "", +}); + +export const navigation = [ + { to: "/", label: "Home" }, + { to: "/fabrics", label: "Fabrics" }, + { to: "/about", label: "About" }, + { to: "/contact", label: "Contact" }, +]; + +export const capabilities = [ + { + title: "Develop", + subtitle: "Continuous fabric development", + text: "Different structures call for different yarns, blends and machine configurations. RRS continually develops new knitted constructions for evolving apparel requirements.", + }, + { + title: "Repeat", + subtitle: "Consistency in bulk production", + text: "A successful development must remain dependable when production scales. RRS focuses on maintaining repeatability and quality across bulk manufacturing.", + }, + { + title: "Manufacture", + subtitle: "Extensive circular-knitting capability", + text: "A broad range of circular-knitting configurations allows RRS to work across different structures, materials and fabric characteristics.", + }, +]; + +export const applications = [ + { + title: "Shirting constructions", + text: "Greige knitted fabrics developed for shirt and apparel applications.", + }, + { + title: "Textured knits", + text: "Knitted constructions where yarn and machine configuration are used to create distinctive surface character.", + }, + { + title: "Structured knits", + text: "More pronounced knitted structures developed using different yarn combinations and knitting setups.", + }, + { + title: "Developed constructions", + text: "Fabric developments created around specific visual, structural or material requirements.", + }, +]; + +export const yarns = [ + "Polyester", + "Cotton", + "Rayon", + "Stretch yarns", + "Lycra / elastane combinations", + "Specialty yarn combinations", + "Blended yarn constructions", + "Other yarns selected according to fabric requirements", +]; + +// Development names are references, not technical categories or specifications. +export const developments = [ + "Micro Break", + "Luxe", + "Jerry", + "Killer", + "Joker", + "Ice Cool", + "Maple", +]; + +export function whatsappUrl(number) { + return /^[1-9]\d{7,14}$/.test(number) + ? `https://wa.me/${number}?text=${encodeURIComponent("Hello RRS, I'd like to discuss a fabric requirement.")}` + : null; +} diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..8d6b106 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,39 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import Layout from "./components/Layout"; +import { Action } from "./components/Primitives"; +import Home from "./pages/Home"; +import Fabrics from "./pages/Fabrics"; +import About from "./pages/About"; +import Contact from "./pages/Contact"; +import "./base.css"; + +function NotFound() { + return ( +
+

404 / Page not found

+

A loose thread.

+

+ This page doesn’t exist. Let’s return to the beginning. +

+ Back to home +
+ ); +} + +createRoot(document.getElementById("root")).render( + + + + }> + } /> + } /> + } /> + } /> + } /> + + + + , +); diff --git a/src/pages/About.css b/src/pages/About.css new file mode 100644 index 0000000..5ff080b --- /dev/null +++ b/src/pages/About.css @@ -0,0 +1,263 @@ +.philosophy { + border-top: 1px solid var(--line); + padding-top: 70px; +} +.philosophy__grid { + display: grid; + grid-template-columns: 1fr 1fr; + border-block: 1px solid var(--line); + padding-block: 48px; +} +.philosophy__item { + padding: 10px clamp(24px, 4vw, 72px); +} +.philosophy__item:first-child { + text-align: right; +} +.philosophy__item + .philosophy__item { + border-left: 1px solid var(--line); +} +.philosophy__title { + margin: 0 0 18px; + font-family: var(--serif); + font-weight: 400; + font-size: clamp(40px, 4vw, 58px); + letter-spacing: -0.04em; +} +.philosophy__text { + max-width: 390px; + margin: 0; + font-size: 14px; + line-height: 1.9; + color: var(--muted); +} +.philosophy__item:first-child .philosophy__text { + margin-left: auto; +} +.leadership { + padding-top: 100px; +} +.leadership__grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 28px; +} +.owner { + margin: 0; +} +.owner__portrait { + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-between; + position: relative; + min-height: 370px; + padding: 24px; + background: #e5e2d9; + color: #7e8271; + border: 1px solid var(--line); + border-radius: 4px; +} +.owner__image-label { + align-self: flex-start; + font-size: 12px; + font-weight: 500; + color: var(--muted); +} +.owner__silhouette { + width: 185px; + height: 220px; +} +.owner__pending { + font-size: 11px; + text-align: center; + color: var(--muted); +} +.owner__caption { + padding: 22px 0 0; +} +.owner__identity { + display: flex; + align-items: baseline; + gap: 20px; + justify-content: space-between; +} +.owner__name { + margin: 0; + font-family: var(--serif); + font-weight: 400; + font-size: 26px; +} +.owner__role { + color: var(--muted); + font-size: 11px; +} +.owner__bio { + max-width: 370px; + margin: 13px 0 0; + font-size: 13px; + color: var(--muted); + line-height: 1.9; +} +.approach { + padding-top: 100px; +} +.approach__grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 35px; +} +.approach__step { + border-top: 1px solid var(--ink); + padding-top: 20px; +} +.approach__title { + margin: 8px 0 18px; + font-family: var(--serif); + font-size: 36px; + font-weight: 400; + letter-spacing: -0.04em; +} +.approach__text { + font-size: 13px; + line-height: 1.9; + color: var(--muted); + margin: 0; +} +.location { + padding-top: 100px; +} +.location__grid { + display: grid; + grid-template-columns: 1.4fr 1fr; + gap: 50px; +} +.location__map { + position: relative; + display: grid; + place-items: center; + min-height: 360px; + border: 1px solid #d2d5c7; + border-radius: 4px; + overflow: hidden; + background-color: #e4e6dc; + background-image: + linear-gradient(#75866b16 1px, transparent 1px), + linear-gradient(90deg, #75866b16 1px, transparent 1px); + background-size: 40px 40px; +} +.location__map-label { + position: absolute; + top: 20px; + left: 20px; + right: 20px; + font-size: 11px; + line-height: 1.6; + color: var(--muted); +} +.location__pin { + display: flex; + align-items: center; + gap: 12px; + border: 1px solid var(--ink); + padding: 14px 21px; + background: var(--paper); + border-radius: 50px; + font-family: var(--serif); + font-size: 24px; +} +.location__pin-dot { + width: 9px; + height: 9px; + background: var(--accent); + border-radius: 50%; + outline: 1px solid #8f725c50; + outline-offset: 5px; + margin-right: 4px; +} +.location__map-caption { + position: absolute; + bottom: 22px; + font-family: var(--serif); + font-style: italic; + font-size: 18px; + color: var(--muted); +} +.location__details { + align-self: center; +} +.location__name { + max-width: 360px; + margin: 0 0 25px; + font-family: var(--serif); + font-size: 31px; + font-weight: 400; + line-height: 1.2; + letter-spacing: -0.025em; +} +.location__address { + font-size: 14px; + line-height: 1.9; + color: var(--muted); + margin-bottom: 25px; +} +.location__button { + display: inline-block; + padding: 15px 20px; + border: 1px solid var(--ink); + background: var(--ink); + color: var(--paper); + border-radius: 30px; + font-size: 11px; + text-decoration: none; +} +.location__button:disabled { + opacity: 0.5; + cursor: not-allowed; +} +.location__status { + font-size: 11px; + color: var(--muted); + line-height: 1.8; +} +@media (max-width: 650px) { + .philosophy { + padding-top: 45px; + } + .philosophy__grid, + .leadership__grid, + .approach__grid, + .location__grid { + grid-template-columns: 1fr; + } + .philosophy__grid { + padding-block: 0; + } + .philosophy__item { + padding: 32px 0; + } + .philosophy__item:first-child { + text-align: left; + } + .philosophy__item:first-child .philosophy__text { + margin-left: 0; + } + .philosophy__item + .philosophy__item { + border-top: 1px solid var(--line); + border-left: 0; + } + .leadership, + .approach, + .location { + padding-top: 60px; + } + .leadership__grid { + gap: 35px; + } + .owner__portrait { + min-height: 330px; + } + .location__grid { + gap: 30px; + } +} diff --git a/src/pages/About.jsx b/src/pages/About.jsx new file mode 100644 index 0000000..0b727b7 --- /dev/null +++ b/src/pages/About.jsx @@ -0,0 +1,232 @@ +import { company } from "../data"; +import { Eyebrow, PageHero, SectionHeading } from "../components/Primitives"; +import { ContactBanner } from "../components/Layout"; +import "./About.css"; + +const principles = [ + { + title: "Built to repeat.", + text: "A fabric developed successfully should remain dependable when manufactured at scale.", + }, + { + title: "Built to develop.", + text: "New yarn combinations, structures and constructions create new possibilities for apparel.", + }, +]; +const process = [ + { + title: "Develop.", + text: "A construction begins with the relationship between yarn, machine and structure, with the eventual application in mind.", + }, + { + title: "Test.", + text: "Selected constructions are evaluated through processed reference samples to explore their possibilities.", + }, + { + title: "Repeat.", + text: "Successful developments are reproduced consistently in commercial greige production.", + }, +]; + +function PortraitPlaceholder({ number }) { + return ( +
+
+ Portrait placeholder + + + Professional photograph to follow + +
+
+
+

[Owner Name]

+ [Director / Role] +
+

+ Biography to be supplied. This profile will introduce the person + behind the fabric. +

+
+
+ ); +} + +export default function About() { + return ( + <> + + Built around +
+ the fabric. + + } + > + {" "} +

+ Radhe Radhe Synthetics is a specialist manufacturer of greige knitted + fabrics with a focus on consistent bulk production and continuous + fabric development. +

+
+
+ + Consistency +
+ and curiosity. + + } + > +

+ Two ideas guide the way RRS works. Together, they define how we + approach knitting and production. +

+
+
+ {principles.map(({ title, text }) => ( +
+

{title}

+

{text}

+
+ ))} +
+
+
+ + The people +
+ behind RRS. + + } + > +

+ Meet our owners and directors. +
+ Names, roles and biographies will be added when supplied. +

+
+
+ + +
+
+
+ + Develop. Test. Repeat. + + } + /> +
+ {process.map(({ title, text }) => ( +
+

{title}

+

{text}

+
+ ))} +
+
+
+ + Where we work. + + } + /> +
+
+ + Map placeholder / Not a geographical location + +
+ + RRS +
+ + A place for our next conversation. + +
+
+ Factory or office +

{company.name}

+

+ {company.address || ( + <> + [Full Factory / Office Address] +
+ [City, State, PIN, India] + + )} +

+ {company.mapsUrl ? ( + + Open in Google Maps ↗ + + ) : ( + + )} +

+ Verified location details to be supplied. +

+
+
+
+ + + ); +} diff --git a/src/pages/Contact.css b/src/pages/Contact.css new file mode 100644 index 0000000..2479617 --- /dev/null +++ b/src/pages/Contact.css @@ -0,0 +1,148 @@ +.whatsapp { + display: grid; + grid-template-columns: 1.5fr 1fr; + align-items: center; + gap: 50px; + border-top: 2px solid var(--ink); + padding: 48px; + background: #fbfaf7; + border-radius: 5px; + scroll-margin-top: 24px; +} +.whatsapp__title { + margin: 0 0 22px; + font-family: var(--serif); + font-size: clamp(32px, 3.8vw, 48px); + font-weight: 400; + line-height: 1.13; + letter-spacing: -0.04em; +} +.whatsapp__text { + max-width: 460px; + font-size: 14px; + line-height: 1.9; + color: var(--muted); +} +.whatsapp__message { + margin: 28px 0 0; + padding: 18px 22px; + border-left: 2px solid var(--accent); + background: var(--wash); + font-family: var(--serif); + font-size: 20px; + font-style: italic; + line-height: 1.5; +} +.whatsapp__clipboard { + display: flex; + align-items: flex-start; + flex-direction: column; + gap: 8px; + margin-top: 12px; +} +.whatsapp__copy-button { + display: inline-flex; + gap: 18px; + align-items: center; + min-height: 44px; + padding: 8px 0; + border: none; + background: transparent; + color: var(--ink); + font-size: 11px; + cursor: pointer; +} +.whatsapp__copy-button:hover { + text-decoration: underline; + text-underline-offset: 5px; +} +.whatsapp__copy-status { + font-size: 11px; + color: var(--muted); + line-height: 1.6; +} +.whatsapp__action { + display: flex; + align-items: center; + flex-direction: column; + text-align: center; +} +.whatsapp__icon { + display: block; + width: 58px; + height: 58px; + margin-bottom: 28px; +} +.whatsapp__status { + max-width: 270px; + margin: 18px 0 0; + font-size: 11px; + color: var(--muted); + line-height: 1.8; +} +.channels { + padding-top: 100px; + scroll-margin-top: 25px; +} +.channels__grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 30px; +} +.channels__item { + min-width: 0; + border-top: 1px solid var(--line); + padding-top: 20px; +} +.channels__title { + margin: 8px 0 16px; + font-size: 13px; + color: var(--muted); + font-weight: 500; +} +.channels__value { + display: block; + font-family: var(--serif); + font-size: 24px; + color: var(--ink); + text-decoration: none; + line-height: 1.4; + margin: 0; + overflow-wrap: anywhere; +} +.channels__note { + font-size: 12px; + color: var(--muted); + line-height: 1.9; +} +@media (max-width: 900px) { + .whatsapp { + grid-template-columns: 1fr; + gap: 35px; + padding: 32px; + } + .whatsapp__action { + align-items: flex-start; + text-align: left; + } + .channels__grid { + grid-template-columns: 1fr 1fr; + } +} +@media (max-width: 600px) { + .whatsapp { + padding: 30px 22px; + } + .whatsapp__message { + padding: 16px; + } + .channels { + padding-top: 60px; + } + .channels__grid { + gap: 25px; + } + .channels__value { + font-size: 21px; + } +} diff --git a/src/pages/Contact.jsx b/src/pages/Contact.jsx new file mode 100644 index 0000000..45a8763 --- /dev/null +++ b/src/pages/Contact.jsx @@ -0,0 +1,165 @@ +import { useState } from "react"; +import { company } from "../data"; +import { + Eyebrow, + PageHero, + SectionHeading, + WhatsAppAction, +} from "../components/Primitives"; +import { ContactBanner } from "../components/Layout"; +import "./Contact.css"; + +const message = "Hello RRS, I'd like to discuss a fabric requirement."; + +export default function Contact() { + const [copyStatus, setCopyStatus] = useState(""); + async function copyMessage() { + try { + await navigator.clipboard.writeText(message); + setCopyStatus("Message copied."); + } catch { + setCopyStatus( + "Could not copy automatically. Please select and copy the message above.", + ); + } + } + const channels = [ + { + title: "WhatsApp", + value: company.whatsapp || "[Number]", + href: "#whatsapp", + note: "Discuss a fabric requirement.", + }, + { + title: "Phone", + value: company.phone || "[Number]", + href: company.phone ? `tel:${company.phone}` : null, + note: "A direct conversation with RRS.", + }, + { + title: "Email", + value: company.email || "[Email Address]", + href: company.email ? `mailto:${company.email}` : null, + note: "Share your requirement or reference.", + }, + { + title: "Address", + value: company.address || "[Full Business Address]", + href: company.mapsUrl || null, + note: "Factory / office location.", + }, + ]; + return ( + <> + + Let’s talk +
+ fabric. + + } + texture="structured" + > +

+ Whether you’re discussing an existing requirement, bulk production or + a new fabric construction, connect directly with RRS. +

+
+
+
+ A direct conversation +

+ Your next construction. +
+ Let’s begin here. +

+

+ Share your fabric requirement, intended application, composition or + reference with our team. +

+
{message}
+
+ + + {copyStatus} + +
+
+
+ + +

+ {company.whatsapp + ? "Opens WhatsApp with a conversation starter." + : "Business WhatsApp number to be supplied. Chat will be available once details are confirmed."} +

+
+
+
+ + A line to RRS. + + } + > +

+ Contact information is awaiting confirmation. No phone numbers, + email addresses or locations have been assumed. +

+
+
+ {channels.map(({ title, value, href, note }) => ( +
+

{title}

+ {href ? ( + + {value} + + ) : ( +

{value}

+ )} +

{note}

+
+ ))} +
+
+ + Have a construction +
+ in mind? + + } + text="Share your fabric requirement, intended application, composition or reference with our team and start a conversation about what can be developed." + /> + + ); +} diff --git a/src/pages/Fabrics.css b/src/pages/Fabrics.css new file mode 100644 index 0000000..9b52e79 --- /dev/null +++ b/src/pages/Fabrics.css @@ -0,0 +1,232 @@ +.applications { + border-top: 1px solid var(--line); + padding-top: 70px; + scroll-margin-top: 30px; +} +.applications__list { + border-top: 1px solid var(--line); +} +.applications__item { + border-bottom: 1px solid var(--line); +} +.applications__summary { + display: flex; + align-items: center; + gap: 30px; + padding: 25px 4px; + list-style: none; + cursor: pointer; +} +.applications__summary::-webkit-details-marker { + display: none; +} +.applications__title { + font-family: var(--serif); + font-size: clamp(24px, 2.6vw, 35px); + letter-spacing: -0.03em; +} +.applications__toggle { + margin-left: auto; + width: 28px; + height: 28px; + border: 1px solid var(--line); + border-radius: 50%; + display: grid; + place-items: center; + flex: 0 0 auto; +} +.applications__toggle::after { + content: "+"; + font-size: 18px; + font-weight: 300; +} +.applications__item[open] .applications__toggle::after { + content: "−"; +} +.applications__text { + max-width: 680px; + margin: 0 40px 28px 4px; + color: var(--muted); + font-size: 14px; + line-height: 1.9; +} +.materials { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 100px; + padding: 100px 0; +} +.materials__title { + font-family: var(--serif); + font-size: clamp(34px, 4vw, 52px); + line-height: 1.13; + letter-spacing: -0.045em; + font-weight: 400; + margin: 0 0 28px; +} +.materials__text { + font-size: 14px; + color: var(--muted); + line-height: 1.9; + max-width: 400px; +} +.materials__note { + display: block; + margin-top: 26px; + font-size: 12px; + font-weight: 500; + color: var(--muted); + line-height: 1.8; +} +.materials__list { + list-style: none; + margin: 0; + padding: 0; +} +.materials__item { + padding: 16px 0; + border-bottom: 1px solid var(--line); + font-size: 14px; + line-height: 1.6; +} +.materials__item:first-child { + padding-top: 0; +} +.knitting { + display: grid; + grid-template-columns: 1.1fr 1fr; + gap: 65px; + padding: 60px; + border-radius: 5px; + background: var(--ink); + color: var(--paper); + scroll-margin-top: 25px; +} +.knitting__title { + font-family: var(--serif); + font-size: clamp(38px, 4.1vw, 56px); + line-height: 1.13; + letter-spacing: -0.04em; + font-weight: 400; + margin: 0 0 25px; +} +.knitting__text { + font-size: 13px; + color: #c2c3b8; + line-height: 1.9; +} +.knitting__diagram { + align-self: center; + margin: 0; + color: #c1c0a9; +} +.knitting__drawing { + display: block; + width: 100%; + height: auto; +} +.knitting__caption { + font-size: 11px; + text-align: center; +} +.knitting__variables { + display: flex; + justify-content: space-between; + gap: 10px; + margin-top: 28px; + padding-top: 18px; + border-top: 1px solid #ffffff25; + color: #c2c3b8; + font-size: 10px; +} +.production { + padding-top: 100px; + scroll-margin-top: 25px; +} +.production__proof { + display: grid; + grid-template-columns: 1.2fr 1fr; + align-items: center; + gap: 50px; + padding: 42px 50px; + background: var(--wash); + border-radius: 4px; +} +.production__number { + font-family: var(--serif); + font-size: clamp(80px, 10vw, 140px); + line-height: 1; + letter-spacing: -0.065em; + margin: 0; +} +.production__unit { + display: block; + margin-top: 18px; + font-family: var(--sans); + font-size: 13px; + font-weight: 500; + letter-spacing: 0; +} +.production__label { + display: block; + font-size: 13px; + font-weight: 500; + line-height: 1.8; +} +.production__text { + margin: 20px 0 28px; + max-width: 320px; + color: var(--muted); + font-size: 14px; + line-height: 1.9; +} +.production__rule { + display: block; + height: 20px; + border-block: 1px solid var(--line); + background: repeating-linear-gradient( + to right, + transparent 0 9px, + #7c806b65 9px 10px + ); +} +@media (max-width: 850px) { + .materials { + gap: 45px; + } + .knitting { + padding: 35px; + gap: 25px; + } + .production__proof { + padding: 35px; + } +} +@media (max-width: 600px) { + .applications { + padding-top: 45px; + } + .applications__summary { + gap: 15px; + } + .applications__text { + margin-left: 49px; + } + .materials { + grid-template-columns: 1fr; + padding: 60px 0; + gap: 30px; + } + .knitting { + grid-template-columns: 1fr; + padding: 35px 24px; + } + .production { + padding-top: 60px; + } + .production__proof { + grid-template-columns: 1fr; + gap: 32px; + padding: 30px 24px; + } +} diff --git a/src/pages/Fabrics.jsx b/src/pages/Fabrics.jsx new file mode 100644 index 0000000..60b1908 --- /dev/null +++ b/src/pages/Fabrics.jsx @@ -0,0 +1,173 @@ +import { applications, yarns } from "../data"; +import { Eyebrow, PageHero, SectionHeading } from "../components/Primitives"; +import { ContactBanner } from "../components/Layout"; +import CapacityValue from "../components/CapacityValue"; +import Gallery from "../components/Gallery"; +import "./Fabrics.css"; + +export default function Fabrics() { + return ( + <> + + Different yarns. +
+ Different structures. + + } + > +

+ RRS manufactures greige knitted fabrics across diverse yarns, fibres + and blends, with constructions developed according to the required + texture, appearance and apparel application. +

+
+
+ + A construction for +
+ the application. + + } + > +

+ Explore some of the ways yarn and structure come together. These are + examples, not the limits of our manufacturing capability. +

+
+
+ {applications.map(({ title, text }, index) => ( +
+ + {title} + +

{text}

+
+ ))} +
+
+
+
+ Materials & yarns +

+ The structure starts +
+ with the yarn. +

+

+ Different fabric designs require different yarn characteristics. RRS + manufactures fabrics using a wide variety of yarns and blends, + selected around the construction. +

+ + We manufacture the fabric, not the yarn. + +
+
    + {yarns.map((yarn) => ( +
  • + {yarn} +
  • + ))} +
+
+
+
+ Circular knitting +

+ One medium. +
+ Many constructions. +

+

+ Extensive circular-knitting capability allows RRS to change yarn, + composition, structure and machine configuration according to the + fabric being developed. +

+

+ The result is a broad manufacturing vocabulary ranging from + understated shirting constructions to highly textured knitted + surfaces. +

+
+
+ + + + + + + + +
+ Circular knitting / Concept illustration +
+
+ Yarn + Composition + Structure +
+
+
+
+ + Developed +
+ to scale. + + } + > +

+ Development is only one part of the process. RRS is built to + reproduce successful constructions consistently across commercial + quantities. +

+
+
+

+ + tonnes / month +

+
+ + Approximate monthly production capacity + +

+ Consistency across bulk orders remains one of our core + manufacturing priorities. +

+
+
+
+ + + + ); +} diff --git a/src/pages/Home.css b/src/pages/Home.css new file mode 100644 index 0000000..6ae3dc9 --- /dev/null +++ b/src/pages/Home.css @@ -0,0 +1,219 @@ +.home-hero { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + min-height: 590px; + margin-top: 24px; + border-radius: 7px; + color: var(--paper); + background: var(--ink); +} +.home-hero__image { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center 65%; +} +.home-hero__shade { + position: absolute; + inset: 0; + background: #1419108f; +} +.home-hero__content { + position: relative; + z-index: 1; + padding: 65px 62px 90px; +} +.home-hero__title { + margin: 38px 0 25px; + font-family: var(--serif); + font-size: clamp(54px, 6.7vw, 94px); + font-weight: 400; + line-height: 1.02; + letter-spacing: -0.055em; +} +.home-hero__description { + max-width: 400px; + margin: 0 0 30px; + color: #eeece5; + font-size: 15px; + line-height: 1.8; +} +.home-hero__actions { + display: flex; + flex-wrap: wrap; + gap: 12px; +} +.home-hero__bottom { + position: absolute; + bottom: 0; + left: 32px; + right: 32px; + display: flex; + justify-content: space-between; + gap: 20px; + padding: 20px 0; + border-top: 1px solid #ffffff35; + font-size: 11px; + line-height: 1.6; +} +.home-hero__tagline { + font-size: clamp(14px, 1.2vw, 16px); + font-weight: 500; + color: #ffffff; + letter-spacing: 0.01em; +} +.home-hero__image-note { + color: #e2e2dc; +} +.home-intro { + display: grid; + gap: 44px; + padding-top: 92px; +} +.home-intro__heading { + text-align: center; +} +.home-intro__body { + width: min(100%, 820px); + margin-inline: auto; +} +.home-intro__title { + margin: 0; + font-family: var(--serif); + font-size: clamp(36px, 4.2vw, 57px); + font-weight: 400; + line-height: 1.08; + letter-spacing: -0.045em; +} +.home-intro__lead { + font-family: var(--serif); + font-size: clamp(25px, 2.5vw, 34px); + line-height: 1.4; + letter-spacing: -0.015em; + margin: 0 0 24px; +} +.home-intro__text { + color: var(--muted); + font-size: 14px; + line-height: 1.9; + margin: 0 0 15px; +} +.home-intro__link { + display: inline-flex; + align-items: center; + gap: 30px; + margin-top: 12px; + padding-bottom: 7px; + border-bottom: 1px solid var(--ink); + color: var(--ink); + font-size: 11px; + text-decoration: none; +} +.process { + padding-top: 95px; +} +.process__list { + border-top: 1px solid var(--line); +} +.process__item { + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); + align-items: center; + gap: clamp(45px, 7vw, 120px); + padding: 58px 0; + border-bottom: 1px solid var(--line); +} +.process__item:nth-child(even) .process__visual { + order: -1; +} +.process__copy { + max-width: 560px; + padding-inline: clamp(0px, 2vw, 34px); +} +.process__verb { + margin: 0 0 24px; + font-size: 14px; + font-weight: 500; + color: var(--muted); +} +.process__title { + margin: 0 0 22px; + font-family: var(--serif); + font-size: clamp(35px, 3.3vw, 52px); + font-weight: 400; + line-height: 1.08; + letter-spacing: -0.04em; +} +.process__text { + margin: 0 0 28px; + color: var(--muted); + font-size: 14px; + line-height: 1.9; +} +.process__link { + display: inline-flex; + align-items: center; + gap: 24px; + padding-bottom: 7px; + border-bottom: 1px solid var(--ink); + color: var(--ink); + font-size: 12px; + text-decoration: none; +} +.process__visual { + width: 100%; + min-height: 320px; + aspect-ratio: 16 / 10; +} +.process__link:hover { + color: var(--accent); +} +@media (max-width: 760px) { + .home-hero { + margin-top: 18px; + min-height: 560px; + } + .home-hero__content { + padding: 42px 26px 130px; + } + .home-hero__title { + font-size: clamp(44px, 8.9vw, 68px); + margin-top: 32px; + } + .home-hero__bottom { + left: 26px; + right: 26px; + flex-direction: column; + gap: 8px; + padding: 15px 0; + } + .home-intro { + gap: 30px; + padding-top: 55px; + } + .process { + padding-top: 60px; + } + .process__item { + grid-template-columns: 1fr; + gap: 28px; + padding: 42px 0; + } + .process__visual, + .process__item:nth-child(even) .process__visual { + order: -1; + } + .process__copy { + max-width: none; + padding-inline: 0; + } + .process__visual { + min-height: 280px; + aspect-ratio: 4 / 3; + } +} diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx new file mode 100644 index 0000000..f444db1 --- /dev/null +++ b/src/pages/Home.jsx @@ -0,0 +1,137 @@ +import { Link } from "react-router-dom"; +import { capabilities, company } from "../data"; +import { + Action, + Arrow, + Eyebrow, + SectionHeading, + Textile, + WhatsAppAction, +} from "../components/Primitives"; +import { ContactBanner } from "../components/Layout"; +import Gallery from "../components/Gallery"; +import Stats from "../components/Stats"; +import "./Home.css"; + +const processItems = [ + { + ...capabilities[0], + image: "fine", + href: "/fabrics#developments", + }, + { + ...capabilities[2], + image: "structured", + href: "/fabrics#circular-knitting", + }, + { + ...capabilities[1], + image: "natural", + href: "/fabrics#production", + }, +]; + +export default function Home() { + return ( + <> +
+ Original illustrative macro knit texture, pending RRS photography +
+
+ Circular knitted fabrics · RRS +

+ Built to develop. +
+ Built to repeat. +

+

{company.description}

+
+ + Explore our fabrics + + Talk to RRS +
+
+
+ + Consistency at scale. Continuous development. + + + Illustrative texture · Photography to follow + +
+
+ +
+
+ Our fabrics +

+ Greige is where +
+ it begins. +

+
+
+

+ RRS specialises in circular-knitted greige fabrics developed for + varied apparel applications. +

+

+ Different constructions require different yarns, blends and machine + configurations. Our manufacturing capability allows us to develop + fabrics around structure, texture, composition and intended use. +

+

+ From shirting constructions to textured and structured knits, we + work across polyester, cotton, rayon, stretch yarns and varied + blends. +

+ + Get to know RRS + +
+
+ +
+ + Behind the fabric: +
+ our process. + + } + > +

+ Development, circular knitting and repeatable bulk production work + as one discipline. +

+
+
+ {processItems.map(({ title, subtitle, text, image, href }) => ( +
+
+

{title}

+

{subtitle}

+

{text}

+ + Explore this capability + +
+ +
+ ))} +
+
+ + + ); +} diff --git a/ss1.png b/ss1.png new file mode 100644 index 0000000..7274b24 Binary files /dev/null and b/ss1.png differ diff --git a/ss2.png b/ss2.png new file mode 100644 index 0000000..78b048f Binary files /dev/null and b/ss2.png differ diff --git a/ss3.png b/ss3.png new file mode 100644 index 0000000..10246ff Binary files /dev/null and b/ss3.png differ diff --git a/tests/site.spec.js b/tests/site.spec.js new file mode 100644 index 0000000..4613c8c --- /dev/null +++ b/tests/site.spec.js @@ -0,0 +1,265 @@ +import { test, expect } from "@playwright/test"; +import { whatsappUrl } from "../src/data.js"; + +const pages = [ + ["/", "Built to develop."], + ["/fabrics", "Different yarns."], + ["/about", "Built around"], + ["/contact", "Let’s talk"], +]; + +for (const width of [1440, 768, 390, 320]) { + for (const [path, heading] of pages) { + test(`${path} renders without errors at ${width}px`, async ({ + page, + }, testInfo) => { + const errors = []; + page.on("pageerror", (error) => errors.push(error.message)); + page.on("console", (message) => { + if (message.type() === "error") errors.push(message.text()); + }); + await page.setViewportSize({ width, height: 900 }); + await page.goto(path); + await expect(page.getByRole("heading", { level: 1 })).toContainText( + heading, + ); + await page.evaluate(() => document.fonts.ready); + for (const image of await page.locator("img").all()) { + await image.scrollIntoViewIfNeeded(); + await expect(image).toHaveJSProperty("complete", true); + expect( + await image.evaluate((element) => element.naturalWidth), + ).toBeGreaterThan(0); + } + await page.evaluate(() => window.scrollTo(0, 0)); + await expect(page.locator("h1")).toHaveCount(1); + await expect(page.locator("[style]")).toHaveCount(0); + await expect(page.locator("form")).toHaveCount(0); + const metrics = await page.evaluate(() => ({ + overflow: document.documentElement.scrollWidth > window.innerWidth, + unloadedImages: [...document.images].filter( + (image) => !image.complete || !image.naturalWidth, + ).length, + fontsLoaded: + document.fonts.check('16px "Instrument Serif"') && + document.fonts.check('16px "DM Sans"'), + contentWidthRatio: + document.querySelector("#main-content").getBoundingClientRect() + .width / window.innerWidth, + })); + expect(metrics).toMatchObject({ + overflow: false, + unloadedImages: 0, + fontsLoaded: true, + }); + expect(metrics.contentWidthRatio).toBeGreaterThan(0.85); + expect(errors).toEqual([]); + if (width === 1440 || width === 390) { + await page.screenshot({ + path: testInfo.outputPath("page.png"), + fullPage: true, + }); + } + }); + } +} + +test("desktop navigation, anchored CTA and browser back work", async ({ + page, +}) => { + await page.goto("/"); + await page + .getByRole("link", { name: "Explore our fabrics", exact: true }) + .click(); + await expect(page).toHaveURL("/fabrics"); + await expect( + page + .getByRole("navigation", { name: "Main navigation" }) + .getByRole("link", { name: "Fabrics", exact: true }), + ).toHaveAttribute("aria-current", "page"); + await page + .getByRole("link", { name: "Talk to us on WhatsApp", exact: true }) + .click(); + await expect(page).toHaveURL("/contact#whatsapp"); + await expect(page.locator("#whatsapp")).toBeInViewport(); + await expect( + page.getByRole("button", { name: "Talk to us on WhatsApp", exact: true }), + ).toBeDisabled(); + await expect(page.locator("#whatsapp-status")).toContainText( + "number to be supplied", + ); + await page.goBack(); + await expect(page).toHaveURL("/fabrics"); + await page.goto("/contact#whatsapp"); + await page + .getByRole("link", { name: "Talk to us on WhatsApp", exact: true }) + .click(); + await expect(page.locator("#whatsapp")).toBeInViewport(); +}); + +test("mobile navigation closes on selection and Escape", async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }); + await page.goto("/"); + const toggle = page.locator("#menu-toggle"); + await toggle.click(); + await expect(toggle).toHaveAttribute("aria-expanded", "true"); + await page.keyboard.press("Escape"); + await expect(toggle).toHaveAttribute("aria-expanded", "false"); + await expect(toggle).toBeFocused(); + await toggle.click(); + await page + .getByRole("navigation", { name: "Main navigation" }) + .getByRole("link", { name: "About", exact: true }) + .click(); + await expect(page).toHaveURL("/about"); + await expect(toggle).toHaveAttribute("aria-expanded", "false"); + await expect(page.locator(".owner")).toHaveCount(2); + await toggle.click(); + await page.getByRole("link", { name: "RRS home", exact: true }).click(); + await expect(page).toHaveURL("/"); + await expect(toggle).toHaveAttribute("aria-expanded", "false"); +}); + +test("application disclosures work from the keyboard", async ({ page }) => { + await page.goto("/fabrics"); + const summary = page.locator("summary").filter({ hasText: "Textured knits" }); + await summary.focus(); + await page.keyboard.press("Enter"); + await expect( + page.getByText( + "Knitted constructions where yarn and machine configuration are used to create distinctive surface character.", + ), + ).toBeVisible(); + await page.keyboard.press("Enter"); + await expect( + page.getByText( + "Knitted constructions where yarn and machine configuration are used to create distinctive surface character.", + ), + ).not.toBeVisible(); + await expect(page.locator(".sample")).toHaveCount(7); +}); + +test("conversation starter copies with accessible feedback", async ({ + page, + context, +}) => { + await context.grantPermissions(["clipboard-read", "clipboard-write"]); + await page.goto("/contact"); + await page.getByRole("button", { name: "Copy conversation starter" }).click(); + await expect(page.getByRole("status")).toHaveText("Message copied."); + expect(await page.evaluate(() => navigator.clipboard.readText())).toBe( + "Hello RRS, I'd like to discuss a fabric requirement.", + ); +}); + +test("clipboard denial has a useful fallback", async ({ page }) => { + await page.goto("/contact"); + await page.evaluate(() => { + Object.defineProperty(navigator, "clipboard", { + value: { writeText: () => Promise.reject(new Error("Denied")) }, + configurable: true, + }); + }); + await page.getByRole("button", { name: "Copy conversation starter" }).click(); + await expect(page.getByRole("status")).toContainText( + "Please select and copy", + ); +}); + +test("WhatsApp requires a real international number and encodes the message", () => { + for (const value of ["", "[Number]", "123", "+12345678901", "not-a-number"]) + expect(whatsappUrl(value)).toBeNull(); + const url = new URL(whatsappUrl("12345678901")); + expect(url.origin).toBe("https://wa.me"); + expect(url.searchParams.get("text")).toBe( + "Hello RRS, I'd like to discuss a fabric requirement.", + ); +}); + +test("every page section unveils once when scrolled into view", async ({ + page, +}) => { + for (const [path] of pages) { + await page.goto(path); + const sections = page.locator("#main-content > section"); + expect(await sections.count()).toBeGreaterThan(0); + for (const section of await sections.all()) { + await section.scrollIntoViewIfNeeded(); + await expect(section).toHaveClass(/reveal--visible/); + await expect(section).toHaveCSS("opacity", "1"); + } + } +}); + +test("homepage feature headings are centered semantic h2 elements", async ({ + page, +}) => { + await page.goto("/"); + const fabricsHeading = page.getByRole("heading", { + level: 2, + name: "Greige is where it begins.", + }); + const developmentsHeading = page.getByRole("heading", { + level: 2, + name: "Structure changes everything.", + }); + await expect(fabricsHeading).toHaveCSS("text-align", "center"); + await expect(developmentsHeading.locator("..").locator("..")).toHaveClass( + /section-heading--centered/, + ); +}); + +test("capacity values roll in opposite directions and settle at 60–70", async ({ + page, +}) => { + for (const path of ["/", "/fabrics"]) { + await page.goto(path); + const capacity = page.getByLabel("60 to 70"); + await expect(capacity).toHaveCount(1); + await capacity.scrollIntoViewIfNeeded(); + await expect + .poll(() => + capacity + .locator(".capacity-value__reel--up") + .evaluate((element) => element.getAnimations().length), + ) + .toBeGreaterThan(0); + await expect(capacity.locator(".capacity-value__reel--up")).toHaveCSS( + "transform", + /matrix/, + ); + await expect(capacity.locator(".capacity-value__reel--down")).toHaveCSS( + "transform", + /matrix/, + ); + } +}); + +test("section unveiling respects reduced-motion preferences", async ({ + page, +}) => { + await page.emulateMedia({ reducedMotion: "reduce" }); + await page.goto("/"); + const sections = page.locator("#main-content > section"); + await expect(sections.first()).toHaveClass(/reveal--visible/); + for (const section of await sections.all()) { + await expect(section).toHaveCSS("opacity", "1"); + await expect(section).toHaveCSS("transform", "none"); + } + const capacity = page.getByLabel("60 to 70"); + await capacity.scrollIntoViewIfNeeded(); + expect( + await capacity + .locator(".capacity-value__reel--up") + .evaluate((element) => element.getAnimations().length), + ).toBe(0); +}); + +test("unknown route provides a way home", async ({ page }) => { + await page.goto("/missing-page"); + await expect(page.getByRole("heading", { level: 1 })).toHaveText( + "A loose thread.", + ); + await page.getByRole("link", { name: "Back to home", exact: true }).click(); + await expect(page).toHaveURL("/"); +}); diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..d1e138a --- /dev/null +++ b/vite.config.js @@ -0,0 +1,12 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + server: { + watch: { + // Avoid caching a temporarily empty module while an editor writes a file. + awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 }, + }, + }, +});