{product.category}
-{product.name}
-{product.description}
-diff --git a/package-lock.json b/package-lock.json
index d34b2b5..eb4498a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,8 @@
"license": "ISC",
"dependencies": {
"react": "^19.2.0",
- "react-dom": "^19.2.0"
+ "react-dom": "^19.2.0",
+ "react-router-dom": "^7.9.6"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
@@ -182,6 +183,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5",
@@ -819,6 +821,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
},
@@ -862,6 +865,7 @@
}
],
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=18"
}
@@ -1655,6 +1659,7 @@
"integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@octokit/auth-token": "^6.0.0",
"@octokit/graphql": "^9.0.3",
@@ -2932,8 +2937,7 @@
"resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
"integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
@@ -3028,6 +3032,7 @@
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -3045,6 +3050,7 @@
"integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -3055,6 +3061,7 @@
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -3112,6 +3119,7 @@
"integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.47.0",
"@typescript-eslint/types": "8.47.0",
@@ -3448,6 +3456,7 @@
"integrity": "sha512-oWtNM89Np+YsQO3ttT5i1Aer/0xbzQzp66NzuJn/U16bB7MnvSzdLKXgk1kkMLYyKSSzA2ajzqMkYheaE9opuQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vitest/utils": "4.0.10",
"fflate": "^0.8.2",
@@ -3484,6 +3493,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -3574,7 +3584,6 @@
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=10"
},
@@ -3715,6 +3724,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.8.25",
"caniuse-lite": "^1.0.30001754",
@@ -4100,6 +4110,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/cookie": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
+ "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
@@ -4113,6 +4132,7 @@
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"env-paths": "^2.2.1",
"import-fresh": "^3.3.0",
@@ -4346,8 +4366,7 @@
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
"integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/dot-prop": {
"version": "5.3.0",
@@ -4675,6 +4694,7 @@
"integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -6342,7 +6362,6 @@
"integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"lz-string": "bin/bin.js"
}
@@ -6381,6 +6400,7 @@
"integrity": "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"marked": "bin/marked.js"
},
@@ -8911,6 +8931,7 @@
"dev": true,
"inBundle": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -9600,7 +9621,6 @@
"integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
@@ -9709,6 +9729,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -9718,6 +9739,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
@@ -9730,8 +9752,7 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
"dev": true,
- "license": "MIT",
- "peer": true
+ "license": "MIT"
},
"node_modules/react-refresh": {
"version": "0.18.0",
@@ -9743,6 +9764,44 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-router": {
+ "version": "7.9.6",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.6.tgz",
+ "integrity": "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA==",
+ "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.9.6",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.6.tgz",
+ "integrity": "sha512-2MkC2XSXq6HjGcihnx1s0DBWQETI4mlis4Ux7YTLvP67xnGxCvq+BcCQSO81qQHVUTM1V53tl4iVVaY5sReCOA==",
+ "license": "MIT",
+ "dependencies": {
+ "react-router": "7.9.6"
+ },
+ "engines": {
+ "node": ">=20.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=18",
+ "react-dom": ">=18"
+ }
+ },
"node_modules/read-package-up": {
"version": "11.0.0",
"resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz",
@@ -9988,6 +10047,7 @@
"integrity": "sha512-6qGjWccl5yoyugHt3jTgztJ9Y0JVzyH8/Voc/D8PlLat9pwxQYXz7W1Dpnq5h0/G5GCYGUaDSlYcyk3AMh5A6g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/error": "^4.0.0",
@@ -10538,6 +10598,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "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/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -11142,6 +11208,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -11296,6 +11363,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -11493,6 +11561,7 @@
"integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.5.0",
@@ -11586,6 +11655,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -11599,6 +11669,7 @@
"integrity": "sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vitest/expect": "4.0.10",
"@vitest/mocker": "4.0.10",
@@ -11969,6 +12040,7 @@
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/package.json b/package.json
index 5b17b0f..420410a 100644
--- a/package.json
+++ b/package.json
@@ -16,7 +16,8 @@
},
"dependencies": {
"react": "^19.2.0",
- "react-dom": "^19.2.0"
+ "react-dom": "^19.2.0",
+ "react-router-dom": "^7.9.6"
},
"devDependencies": {
"@commitlint/cli": "^20.1.0",
diff --git a/src/App.css b/src/App.css
index cfd784a..a104019 100644
--- a/src/App.css
+++ b/src/App.css
@@ -8,7 +8,8 @@
}
.hero {
- background: radial-gradient(circle at top left, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)),
+ background:
+ radial-gradient(circle at top left, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.1)),
var(--card);
border-radius: 32px;
display: grid;
@@ -65,7 +66,10 @@
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
- transition: transform 200ms ease, box-shadow 200ms ease, background 200ms ease;
+ transition:
+ transform 200ms ease,
+ box-shadow 200ms ease,
+ background 200ms ease;
}
.btn--primary {
@@ -132,6 +136,12 @@
justify-content: space-between;
}
+.products-page {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 1rem 2rem 2rem;
+}
+
.product-grid {
display: flex;
flex-direction: column;
@@ -140,10 +150,22 @@
.product-grid__items {
display: grid;
- grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 2rem;
}
+@media (min-width: 1200px) {
+ .product-grid__items {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
+
+@media (min-width: 900px) and (max-width: 1199px) {
+ .product-grid__items {
+ grid-template-columns: repeat(3, 1fr);
+ }
+}
+
.product-card {
background: var(--card);
border-radius: 28px;
@@ -152,7 +174,17 @@
display: flex;
flex-direction: column;
min-height: 100%;
- transition: transform 200ms ease, box-shadow 200ms ease;
+ transition:
+ transform 200ms ease,
+ box-shadow 200ms ease;
+ text-decoration: none;
+ color: inherit;
+ cursor: pointer;
+}
+
+.product-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 12px 32px rgba(15, 23, 42, 0.1);
}
.product-card__media {
@@ -233,7 +265,16 @@
text-transform: uppercase;
letter-spacing: 0.08em;
font-size: 0.75rem;
- transition: transform 200ms ease, box-shadow 200ms ease;
+ transition:
+ transform 200ms ease,
+ box-shadow 200ms ease;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.product-card__cta--highlight {
+ animation: buttonColorFade 1200ms ease-out forwards;
}
.product-card--highlight {
@@ -241,10 +282,17 @@
box-shadow: 0 10px 20px rgba(241, 84, 53, 0.08);
}
-.product-card__cta--pulse {
- animation: ctaPulse 500ms ease;
- background: linear-gradient(120deg, rgba(241, 84, 53, 0.95), rgba(241, 84, 53, 0.8));
- box-shadow: 0 8px 18px rgba(241, 84, 53, 0.18);
+.product-card__checkmark {
+ display: inline-flex;
+ align-items: center;
+ animation: checkmarkFade 1200ms ease-out forwards;
+ margin-right: -0.25rem;
+}
+
+.product-card__checkmark svg {
+ width: 16px;
+ height: 16px;
+ stroke: currentColor;
}
@keyframes cardPulse {
@@ -262,15 +310,43 @@
}
}
-@keyframes ctaPulse {
+@keyframes checkmarkFade {
0% {
- transform: scale(1);
+ opacity: 0;
+ transform: translateX(-4px) scale(0.8);
+ }
+ 20% {
+ opacity: 1;
+ transform: translateX(0) scale(1);
}
- 35% {
- transform: scale(1.03);
+ 70% {
+ opacity: 1;
+ transform: translateX(0) scale(1);
}
100% {
- transform: scale(1);
+ opacity: 0;
+ transform: translateX(4px) scale(0.8);
+ }
+}
+
+@keyframes buttonColorFade {
+ 0% {
+ background-color: rgba(15, 23, 42, 0.85);
+ }
+ 15% {
+ background-color: rgba(34, 197, 94, 0.9);
+ }
+ 50% {
+ background-color: rgba(34, 197, 94, 0.9);
+ }
+ 70% {
+ background-color: rgba(34, 197, 94, 0.75);
+ }
+ 85% {
+ background-color: rgba(34, 197, 94, 0.5);
+ }
+ 100% {
+ background-color: rgba(15, 23, 42, 0.85);
}
}
@@ -396,6 +472,12 @@
gap: 1rem;
}
+.cart-line__quantity-group {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+}
+
.quantity {
display: inline-flex;
align-items: center;
@@ -418,6 +500,38 @@
font-weight: 600;
}
+.cart-line__remove {
+ border: none;
+ background: transparent;
+ color: var(--muted-strong);
+ cursor: pointer;
+ padding: 0.5rem;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 8px;
+ transition:
+ color 200ms ease,
+ background-color 200ms ease,
+ transform 200ms ease;
+}
+
+.cart-line__remove:hover {
+ color: var(--accent);
+ background-color: rgba(241, 84, 53, 0.1);
+ transform: scale(1.1);
+}
+
+.cart-line__remove:active {
+ transform: scale(0.95);
+}
+
+.cart-line__remove svg {
+ width: 16px;
+ height: 16px;
+ stroke: currentColor;
+}
+
.cart-line__total {
font-weight: 600;
}
@@ -452,6 +566,390 @@
text-align: center;
}
+.product-page {
+ max-width: 1400px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+.product-page__back {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--muted-strong);
+ text-decoration: none;
+ margin-bottom: 2rem;
+ font-size: 0.9rem;
+ transition: color 200ms ease;
+}
+
+.product-page__back:hover {
+ color: var(--text);
+}
+
+.product-page__content {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
+ gap: 3rem;
+ align-items: start;
+}
+
+.product-page__media {
+ position: relative;
+ border-radius: 32px;
+ overflow: hidden;
+ aspect-ratio: 4 / 3;
+ background: var(--card);
+ border: 1px solid var(--border);
+}
+
+.product-page__media img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+}
+
+.product-page__details {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.product-page__category {
+ text-transform: uppercase;
+ letter-spacing: 0.2em;
+ font-size: 0.75rem;
+ color: var(--muted);
+ margin: 0;
+}
+
+.product-page__title {
+ font-size: clamp(2rem, 5vw, 3rem);
+ line-height: 1.2;
+ margin: 0;
+}
+
+.product-page__description {
+ font-size: 1.1rem;
+ line-height: 1.7;
+ color: var(--muted-strong);
+ margin: 0;
+}
+
+.product-page__meta {
+ display: flex;
+ align-items: center;
+ gap: 1.5rem;
+ font-size: 1.5rem;
+ font-weight: 600;
+}
+
+.product-page__price {
+ color: var(--text);
+}
+
+.product-page__rating {
+ color: var(--muted-strong);
+ font-size: 1.1rem;
+}
+
+.product-page__colors {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.product-page__colors-label {
+ font-size: 0.9rem;
+ color: var(--muted-strong);
+ margin: 0;
+ font-weight: 600;
+}
+
+.product-page__colors-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.75rem;
+}
+
+.product-page__color-option {
+ padding: 0.6rem 1.2rem;
+ border: 1px solid var(--border);
+ background: var(--card);
+ border-radius: 999px;
+ font-size: 0.9rem;
+ cursor: pointer;
+ transition: all 200ms ease;
+ color: var(--text);
+}
+
+.product-page__color-option:hover {
+ border-color: var(--accent);
+ transform: translateY(-1px);
+}
+
+.product-page__color-option--selected {
+ background: var(--accent);
+ color: #fff;
+ border-color: var(--accent);
+}
+
+.product-page__cta {
+ align-self: flex-start;
+ border-radius: 999px;
+ border: none;
+ background: rgba(15, 23, 42, 0.85);
+ color: #fff;
+ padding: 0.75rem 2rem;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+ font-size: 0.85rem;
+ font-weight: 600;
+ cursor: pointer;
+ transition:
+ transform 200ms ease,
+ box-shadow 200ms ease;
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.product-page__cta:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 8px 20px rgba(15, 23, 42, 0.2);
+}
+
+.product-page__info {
+ margin-top: 1rem;
+ padding-top: 2rem;
+ border-top: 1px solid var(--border);
+}
+
+.product-page__info h3 {
+ font-size: 1.2rem;
+ margin: 0 0 1rem 0;
+}
+
+.product-page__info ul {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+ color: var(--muted-strong);
+}
+
+.product-page__info li {
+ padding-left: 1.5rem;
+ position: relative;
+}
+
+.product-page__info li::before {
+ content: '•';
+ position: absolute;
+ left: 0;
+ color: var(--accent);
+ font-weight: bold;
+}
+
+.product-page__not-found {
+ text-align: center;
+ padding: 4rem 2rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ align-items: center;
+}
+
+.product-page__not-found h1 {
+ font-size: 2.5rem;
+ margin: 0;
+}
+
+.product-page__not-found p {
+ color: var(--muted-strong);
+ font-size: 1.1rem;
+ margin: 0;
+}
+
+.reviews {
+ margin-top: 4rem;
+ padding-top: 3rem;
+ border-top: 1px solid var(--border);
+}
+
+.reviews__title {
+ font-size: 2rem;
+ margin: 0 0 2rem 0;
+}
+
+.reviews__list {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ margin-bottom: 3rem;
+}
+
+.review {
+ background: var(--card);
+ border: 1px solid var(--border);
+ border-radius: 20px;
+ padding: 1.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 0.75rem;
+}
+
+.review__header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 1rem;
+ flex-wrap: wrap;
+}
+
+.review__header > div {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.review__name {
+ font-weight: 600;
+ margin: 0;
+ color: var(--text);
+}
+
+.review__date {
+ font-size: 0.85rem;
+ color: var(--muted-strong);
+}
+
+.review__text {
+ margin: 0;
+ line-height: 1.6;
+ color: var(--muted-strong);
+}
+
+.reviews__form {
+ background: var(--card);
+ border: 1px solid var(--border);
+ border-radius: 24px;
+ padding: 2rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.reviews__form-title {
+ font-size: 1.3rem;
+ margin: 0;
+}
+
+.reviews__form-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.reviews__label {
+ font-size: 0.9rem;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.reviews__input,
+.reviews__textarea {
+ padding: 0.75rem 1rem;
+ border: 1px solid var(--border);
+ border-radius: 12px;
+ font-size: 1rem;
+ font-family: inherit;
+ background: var(--bg);
+ color: var(--text);
+ transition:
+ border-color 200ms ease,
+ box-shadow 200ms ease;
+}
+
+.reviews__input:focus,
+.reviews__textarea:focus {
+ outline: none;
+ border-color: var(--accent);
+ box-shadow: 0 0 0 3px rgba(241, 84, 53, 0.1);
+}
+
+.reviews__input:disabled,
+.reviews__textarea:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.reviews__textarea {
+ resize: vertical;
+ min-height: 100px;
+}
+
+.reviews__input--captcha {
+ max-width: 120px;
+}
+
+.reviews__error {
+ color: var(--accent);
+ font-size: 0.9rem;
+ margin: 0;
+ padding: 0.75rem 1rem;
+ background: rgba(241, 84, 53, 0.1);
+ border-radius: 8px;
+ border: 1px solid rgba(241, 84, 53, 0.2);
+}
+
+.reviews__submit {
+ align-self: flex-start;
+ margin-top: 0.5rem;
+}
+
+.reviews__submit:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.star-rating {
+ display: inline-flex;
+ gap: 0.25rem;
+ align-items: center;
+}
+
+.star-rating__star {
+ background: none;
+ border: none;
+ padding: 0;
+ font-size: 1.25rem;
+ color: rgba(15, 23, 42, 0.2);
+ cursor: default;
+ transition:
+ color 150ms ease,
+ transform 150ms ease;
+ line-height: 1;
+}
+
+.star-rating__star--filled {
+ color: #fbbf24;
+}
+
+.star-rating__star--interactive {
+ cursor: pointer;
+}
+
+.star-rating__star--interactive:hover {
+ transform: scale(1.15);
+}
+
+.star-rating__star:disabled {
+ cursor: default;
+}
+
@media (max-width: 720px) {
.hero__actions {
flex-direction: column;
@@ -467,4 +965,34 @@
.cart-panel {
padding: 1.5rem;
}
+
+ .product-page {
+ padding: 1rem;
+ }
+
+ .product-page__content {
+ gap: 2rem;
+ }
+
+ .product-page__meta {
+ font-size: 1.2rem;
+ }
+
+ .products-page {
+ padding: 1rem;
+ }
+
+ .reviews {
+ margin-top: 2rem;
+ padding-top: 2rem;
+ }
+
+ .reviews__form {
+ padding: 1.5rem;
+ }
+
+ .review__header {
+ flex-direction: column;
+ align-items: flex-start;
+ }
}
diff --git a/src/App.test.tsx b/src/App.test.tsx
index 8b1dd6d..ae08345 100644
--- a/src/App.test.tsx
+++ b/src/App.test.tsx
@@ -1,21 +1,39 @@
import { describe, it, expect } from 'vitest'
import '@testing-library/jest-dom/vitest'
import { render, screen } from '@testing-library/react'
+import { MemoryRouter } from 'react-router-dom'
import userEvent from '@testing-library/user-event'
import App from './App'
import products from './data/products'
describe('App', () => {
- it('renders hero content and product cards', () => {
- render( {product.category} {product.description}
- {product.badge && {product.badge}}
-
{product.name}
-
New season edit
@@ -269,9 +216,9 @@ function App() { Curated furniture, lighting, and objects crafted in small batches and ready to ship. + > + ) +} + +function App() { + const [cart, setCart] = useState{product.category}
+{product.description}
+Sorry, we couldn't find the product you're looking for.
+ + Back to shop + +{product.category}
+{product.description}
+ +Available colors:
+Featured pieces
++ Mix tactile fabrics, natural woods, and sculptural silhouettes for your signature look. +
+{review.name}
+{review.text}
+