diff --git a/index.js b/index.js
index b7515df..e51cbf0 100644
--- a/index.js
+++ b/index.js
@@ -1,44 +1,24 @@
import express from "express";
-import fs from "fs";
const app = express();
// Static file serving middleware
app.use(express.static("public"));
-// Custom static file serving
-// app.get("/{:file}", (req, res, next) => {
-// const fileName = req.params.file ?? "index.html";
+app.set("view engine", "ejs");
-// // Verificar si archivo existe
-// if (!fs.existsSync(fileName)) {
-// next();
-// return;
-// }
-
-// const extension = fileName.split(".")[1];
-
-// if (extension === "html") {
-// res.setHeader("Content-Type", "text/html");
-// } else if (extension === "css") {
-// res.setHeader("Content-Type", "text/css");
-// } else if (extension === "json") {
-// res.setHeader("Content-Type", "application/json");
-// } else if (extension === "jpg") {
-// res.setHeader("Content-Type", "image/jpg");
-// } else if (extension === "png") {
-// res.setHeader("Content-Type", "image/png");
-// }
-
-// return res.sendFile(fileName, { root: "." });
-// });
+// Routes
+app.get("/", (req, res) => {
+ res.render("index.ejs", {
+ nombre: "Codeable",
+ tecnologias: ["HTML", "CSS", "JS"],
+ });
+});
app.use((req, res, next) => {
res.status(404).sendFile("404.html", { root: "public" });
});
-const PORT = process.env.PORT || 3000;
-
-app.listen(PORT, () => {
- console.log(`Server is running on http://localhost:${PORT}`);
+app.listen(3000, () => {
+ console.log(`Server is running on http://localhost:3000`);
});
diff --git a/package-lock.json b/package-lock.json
index ff04da4..e360bb7 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
+ "ejs": "^3.1.10",
"express": "^5.1.0"
}
},
@@ -25,6 +26,18 @@
"node": ">= 0.6"
}
},
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "license": "MIT"
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
"node_modules/body-parser": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz",
@@ -45,6 +58,15 @@
"node": ">=18"
}
},
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@@ -168,6 +190,21 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
+ "node_modules/ejs": {
+ "version": "3.1.10",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
+ "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "jake": "^10.8.5"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
@@ -264,6 +301,15 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/filelist": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
+ "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "minimatch": "^5.0.1"
+ }
+ },
"node_modules/finalhandler": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
@@ -439,6 +485,23 @@
"integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
"license": "MIT"
},
+ "node_modules/jake": {
+ "version": "10.9.4",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz",
+ "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "async": "^3.2.6",
+ "filelist": "^1.0.4",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -490,6 +553,18 @@
"node": ">= 0.6"
}
},
+ "node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -557,6 +632,12 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
"node_modules/proxy-addr": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
diff --git a/package.json b/package.json
index 21cf130..7be5df2 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"license": "ISC",
"type": "module",
"dependencies": {
+ "ejs": "^3.1.10",
"express": "^5.1.0"
}
}
diff --git a/public/404.html b/public/404.html
index 6a3fff9..ab62d6c 100644
--- a/public/404.html
+++ b/public/404.html
@@ -1,12 +1,32 @@
-
+
- Archivo no encontrado
+ Full Stock | Página no encontrada
+
+
+
+
+
+
- 404 - Archivo no encontrado
- Lo sentimos, el archivo que buscas no existe.
+
+
+
+
Página No Encontrada
+
+ La página que estás buscando no existe o ha sido movida
+
+
Volver al Inicio
+
+
+
diff --git a/public/about.html b/public/about.html
deleted file mode 100644
index cacc286..0000000
--- a/public/about.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Acerca de Nosotros
-
-
-
- Acerca de Nosotros
-
-
diff --git a/public/cart.html b/public/cart.html
new file mode 100644
index 0000000..130ecbe
--- /dev/null
+++ b/public/cart.html
@@ -0,0 +1,259 @@
+
+
+
+
+
+ Full Stock | Inicio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Carrito de compras
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/category.html b/public/category.html
new file mode 100644
index 0000000..cc72d10
--- /dev/null
+++ b/public/category.html
@@ -0,0 +1,371 @@
+
+
+
+
+
+ Full Stock | Polos
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/checkout.html b/public/checkout.html
new file mode 100644
index 0000000..adfbbc2
--- /dev/null
+++ b/public/checkout.html
@@ -0,0 +1,260 @@
+
+
+
+
+
+ Full Stock | Inicio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Resumen de la orden
+
+
+
+
+
+
+
+
+ Polo React
+
+
+
2
+
+
S/ 20
+
+
+
+
+
+
+
+
+
+ Taza GitHub
+
+
+
1
+
+
S/ 14.99
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/css/1_foundation/colors.css b/public/css/1_foundation/colors.css
new file mode 100644
index 0000000..8aacf29
--- /dev/null
+++ b/public/css/1_foundation/colors.css
@@ -0,0 +1,136 @@
+@layer definitions {
+ :root {
+ /* Colores base */
+ --white: hsl(0 0% 100%);
+ --black: hsl(0 0% 0%);
+ --base-background: var(--white);
+
+ --accent-1: hsl(240 33% 99%);
+ --accent-2: hsl(240 33% 98%);
+ --accent-3: hsl(240 33% 97%);
+ --accent-4: hsl(240 33% 94%);
+ --accent-5: hsl(240 33% 90%);
+ --accent-6: hsl(240 33% 83%);
+ --accent-7: hsl(240 73% 80%);
+ --accent-8: hsl(240 73% 74%);
+ --accent-9: hsl(240 62% 57%);
+ --accent-10: hsl(240 61% 52%);
+ --accent-11: hsl(240 54% 51%);
+ --accent-12: hsl(240 55% 22%);
+ --accent-a1: hsl(240 100% 50% / 0.01);
+ --accent-a2: hsl(240 100% 50% / 0.03);
+ --accent-a3: hsl(240 90% 47% / 0.06);
+ --accent-a4: hsl(240 100% 50% / 0.1);
+ --accent-a5: hsl(240 100% 50% / 0.15);
+ --accent-a6: hsl(240 100% 50% / 0.2);
+ --accent-a7: hsl(240 90% 45% / 0.28);
+ --accent-a8: hsl(240 85% 43% / 0.39);
+ --accent-a9: hsl(240 75% 38% / 0.64);
+ --accent-a10: hsl(240 72% 35% / 0.68);
+ --accent-a11: hsl(240 67% 34% / 0.67);
+ --accent-a12: hsl(240 85% 14% / 0.85);
+ --accent-contrast: var(--white);
+
+ --neutral-1: hsl(240 9% 99%);
+ --neutral-2: hsl(240 8% 98%);
+ --neutral-3: hsl(240 7% 95%);
+ --neutral-4: hsl(240 7% 92%);
+ --neutral-5: hsl(240 6% 89%);
+ --neutral-6: hsl(240 6% 86%);
+ --neutral-7: hsl(240 8% 83%);
+ --neutral-8: hsl(240 11% 76%);
+ --neutral-9: hsl(240 9% 59%);
+ --neutral-10: hsl(240 8% 53%);
+ --neutral-11: hsl(240 7% 41%);
+ --neutral-12: hsl(240 10% 14%);
+ --neutral-a1: hsl(240 100% 33% / 0.01);
+ --neutral-a2: hsl(240 100% 33% / 0.02);
+ --neutral-a3: hsl(240 100% 20% / 0.06);
+ --neutral-a4: hsl(240 100% 18% / 0.09);
+ --neutral-a5: hsl(240 100% 20% / 0.12);
+ --neutral-a6: hsl(240 100% 18% / 0.15);
+ --neutral-a7: hsl(240 100% 18% / 0.2);
+ --neutral-a8: hsl(240 100% 19% / 0.27);
+ --neutral-a9: hsl(240 100% 13% / 0.45);
+ --neutral-a10: hsl(240 100% 11% / 0.5);
+ --neutral-a11: hsl(240 100% 8% / 0.62);
+ --neutral-a12: hsl(240 100% 4% / 0.89);
+ --neutral-contrast: var(--white);
+
+ /* Tokens de color */
+ --background: var(--base-background);
+ --foreground: var(--neutral-12);
+ --accent-background: var(--accent-3);
+ --accent-foreground: var(--accent-11);
+ --muted-background: var(--neutral-a2);
+ --muted-foreground: var(--neutral-11);
+ --primary-background: var(--accent-9);
+ --primary-foreground: var(--accent-contrast);
+ --secondary-background: var(--neutral-3);
+ --secondary-foreground: var(--neutral-11);
+ --border: var(--neutral-7);
+ --outline: var(--accent-11);
+ --separator: var(--neutral-6);
+ --primary-background-hover: var(--accent-10);
+ --secondary-background-hover: var(--neutral-4);
+ --border-hover: var(--neutral-8);
+ }
+
+ @media (prefers-color-scheme: dark) {
+ :root {
+ --base-background: var(--neutral-1);
+
+ --accent-1: hsl(240 22% 10%);
+ --accent-2: hsl(244 25% 12%);
+ --accent-3: hsl(237 38% 20%);
+ --accent-4: hsl(236 45% 27%);
+ --accent-5: hsl(237 41% 32%);
+ --accent-6: hsl(239 36% 37%);
+ --accent-7: hsl(240 34% 44%);
+ --accent-8: hsl(241 36% 52%);
+ --accent-9: hsl(240 60% 60%);
+ --accent-10: hsl(242 64% 64%);
+ --accent-11: hsl(246 100% 83%);
+ --accent-12: hsl(242 94% 94%);
+ --accent-a1: hsl(240 99% 60% / 0.05);
+ --accent-a2: hsl(244 94% 64% / 0.09);
+ --accent-a3: hsl(237 100% 66% / 0.23);
+ --accent-a4: hsl(236 100% 65% / 0.35);
+ --accent-a5: hsl(237 98% 67% / 0.42);
+ --accent-a6: hsl(239 97% 71% / 0.48);
+ --accent-a7: hsl(240 99% 73% / 0.56);
+ --accent-a8: hsl(240 99% 74% / 0.67);
+ --accent-a9: hsl(240 99% 71% / 0.83);
+ --accent-a10: hsl(242 100% 74% / 0.86);
+ --accent-a11: hsl(246 100% 83%);
+ --accent-a12: hsl(242 100% 94%);
+ --accent-contrast: var(--white);
+
+ --neutral-1: hsl(240 6% 7%);
+ --neutral-2: hsl(220 6% 10%);
+ --neutral-3: hsl(225 6% 14%);
+ --neutral-4: hsl(210 7% 16%);
+ --neutral-5: hsl(214 7% 19%);
+ --neutral-6: hsl(213 8% 23%);
+ --neutral-7: hsl(213 8% 28%);
+ --neutral-8: hsl(212 8% 38%);
+ --neutral-9: hsl(219 6% 44%);
+ --neutral-10: hsl(222 5% 49%);
+ --neutral-11: hsl(216 7% 71%);
+ --neutral-12: hsl(220 9% 94%);
+ --neutral-a1: hsl(0 0% 0%);
+ --neutral-a2: hsl(184 63% 91% / 0.04);
+ --neutral-a3: hsl(211 66% 92% / 0.08);
+ --neutral-a4: hsl(198 73% 90% / 0.11);
+ --neutral-a5: hsl(208 95% 92% / 0.15);
+ --neutral-a6: hsl(208 91% 92% / 0.19);
+ --neutral-a7: hsl(208 100% 93% / 0.25);
+ --neutral-a8: hsl(208 100% 93% / 0.36);
+ --neutral-a9: hsl(216 88% 93% / 0.43);
+ --neutral-a10: hsl(220 86% 95% / 0.48);
+ --neutral-a11: hsl(212 87% 97% / 0.71);
+ --neutral-a12: hsl(220 100% 99% / 0.94);
+ --neutral-contrast: var(--white);
+ }
+ }
+}
diff --git a/public/css/1_foundation/global.css b/public/css/1_foundation/global.css
new file mode 100644
index 0000000..7a3aa25
--- /dev/null
+++ b/public/css/1_foundation/global.css
@@ -0,0 +1,74 @@
+@layer global {
+ :root {
+ color-scheme: light dark;
+ }
+
+ body {
+ font-family: var(--family-text);
+ font-size: 1rem;
+ line-height: 1.5rem;
+
+ color: var(--foreground);
+ background-color: var(--background);
+ }
+
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ font-family: var(--family-heading);
+ }
+
+ code {
+ font-family: var(--family-monospace);
+ }
+
+ em {
+ font-family: var(--family-emphasis);
+ }
+
+ hr {
+ border: none;
+ border-top: 1px solid var(--separator);
+ }
+
+ label {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1em;
+ }
+
+ input,
+ select {
+ height: 2.5rem;
+ width: 100%;
+ padding: 0.5rem 0.75rem;
+ border-radius: 0.75rem;
+ border: 1px solid var(--border);
+
+ font-size: 0.875rem;
+ line-height: 1.5rem;
+ background-color: var(--background);
+ }
+
+ fieldset {
+ all: unset;
+ }
+
+ :focus-visible {
+ outline: 2px solid var(--outline);
+ outline-offset: 2px;
+ }
+
+ /*Account for sticky header height (3rem) + gap (2.5rem) */
+ :focus {
+ scroll-margin-top: calc(3rem + 2.5rem);
+ }
+
+ * {
+ border-color: var(--border);
+ border-style: none;
+ }
+}
diff --git a/public/css/1_foundation/reset.css b/public/css/1_foundation/reset.css
new file mode 100644
index 0000000..38c8628
--- /dev/null
+++ b/public/css/1_foundation/reset.css
@@ -0,0 +1,81 @@
+@layer reset {
+ /* 1. Use a more-intuitive box-sizing model */
+ *,
+ *::before,
+ *::after {
+ box-sizing: border-box;
+ }
+
+ /* 2. Remove default margin */
+ * {
+ margin: 0;
+ }
+
+ body {
+ /* 3. Add accessible line-height */
+ line-height: 1.5;
+ /* 4. Improve text rendering */
+ -webkit-font-smoothing: antialiased;
+ }
+
+ /* 5. Improve media defaults */
+ img,
+ picture,
+ video,
+ canvas,
+ svg {
+ display: block;
+ max-width: 100%;
+ }
+
+ /* 6. Inherit fonts for form controls */
+ input,
+ button,
+ textarea,
+ select {
+ font: inherit;
+ }
+
+ /* 7. Avoid text overflows */
+ p,
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ overflow-wrap: break-word;
+ }
+
+ /* 8. Improve line wrapping */
+ p {
+ text-wrap: pretty;
+ }
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ text-wrap: balance;
+ }
+
+ /* Codeable extra resets */
+
+ /* Set default body height */
+ body {
+ min-height: 100vh;
+ }
+
+ /* Remove list styles on ul, ol elements with a list role, which suggests default styling will be removed */
+ ul[role="list"],
+ ol[role="list"] {
+ list-style: none;
+ }
+}
+
+a {
+ color: inherit;
+ -webkit-text-decoration: inherit;
+ text-decoration: inherit;
+}
diff --git a/public/css/1_foundation/typography.css b/public/css/1_foundation/typography.css
new file mode 100644
index 0000000..3c3f4e8
--- /dev/null
+++ b/public/css/1_foundation/typography.css
@@ -0,0 +1,14 @@
+@layer definitions {
+ :root {
+ /* Font stacks */
+ --family-text: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Helvetica Neue", Arial, sans-serif;
+ --family-heading: var(--family-text);
+ --family-display: "Protest Guerrilla", "Protest Strike", "Stencil", "Impact",
+ sans-serif;
+ --family-code: "Source Code Pro", "Menlo", "Consolas", "Monaco",
+ "Courier New", monospace;
+ --family-emphasis: "Lora", "Georgia", "Palatino Linotype", "Book Antiqua",
+ "Times New Roman", serif;
+ }
+}
diff --git a/public/css/2_layouts/container.css b/public/css/2_layouts/container.css
new file mode 100644
index 0000000..5dce272
--- /dev/null
+++ b/public/css/2_layouts/container.css
@@ -0,0 +1,27 @@
+@layer layouts {
+ .container {
+ margin-left: auto;
+ margin-right: auto;
+ max-width: 80rem;
+ padding-left: 1rem;
+ padding-right: 1rem;
+ }
+
+ .container--sm {
+ max-width: 48rem;
+ }
+
+ @media (min-width: 40rem) {
+ .container {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+ }
+ }
+
+ @media (min-width: 64rem) {
+ .container {
+ padding-left: 2rem;
+ padding-right: 2rem;
+ }
+ }
+}
diff --git a/public/css/2_layouts/section.css b/public/css/2_layouts/section.css
new file mode 100644
index 0000000..6d7b16c
--- /dev/null
+++ b/public/css/2_layouts/section.css
@@ -0,0 +1,20 @@
+@layer layouts {
+ .section {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+ }
+
+ @media (min-width: 40rem) {
+ .section {
+ padding-top: 4rem;
+ padding-bottom: 4rem;
+ }
+ }
+
+ @media (min-width: 64rem) {
+ .section {
+ padding-top: 6rem;
+ padding-bottom: 6rem;
+ }
+ }
+}
diff --git a/public/css/3_components/button.css b/public/css/3_components/button.css
new file mode 100644
index 0000000..ffc4add
--- /dev/null
+++ b/public/css/3_components/button.css
@@ -0,0 +1,131 @@
+.button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ height: 2rem;
+ gap: 0.5rem;
+ padding: 0.375rem 0.625rem;
+ border-radius: 0.5rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ white-space: nowrap;
+ color: var(--primary-foreground);
+ background-color: var(--primary-background);
+ transition: colors 0.2s;
+ cursor: pointer;
+}
+
+.button:disabled {
+ opacity: 0.5;
+ pointer-events: none;
+}
+
+.button svg {
+ width: 1rem;
+ height: 1rem;
+ flex-shrink: 0;
+ pointer-events: none;
+}
+
+/* Variants */
+.button:hover {
+ background-color: var(--primary-background-hover);
+}
+
+.button--secondary {
+ color: var(--secondary-foreground);
+ background-color: var(--secondary-background);
+}
+
+.button--secondary:hover {
+ background-color: var(--secondary-background-hover);
+}
+
+.button--outline {
+ border: 1px solid var(--border);
+ color: var(--muted-foreground);
+ background-color: var(--background);
+}
+
+.button--outline:hover {
+ background-color: var(--muted-background);
+}
+
+.button--ghost {
+ color: var(--muted-foreground);
+ background-color: transparent;
+}
+
+.button--ghost:hover {
+ background-color: var(--muted-background);
+}
+
+/* Sizes */
+.button--sm {
+ height: 1.5rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 0.375rem;
+}
+
+.button--lg {
+ height: 2.5rem;
+ padding: 0.625rem 0.875rem;
+ border-radius: 0.5rem;
+}
+
+.button--xl {
+ height: 3rem;
+ padding: 0.75rem 2rem;
+ border-radius: 0.5rem;
+ font-size: 1rem;
+}
+
+.button--xl svg {
+ width: 1.25rem;
+ height: 1.25rem;
+}
+
+.button--sm-icon {
+ width: 1.75rem;
+ height: 1.75rem;
+ padding: 0.25rem;
+ cursor: pointer;
+}
+
+.button--sm-icon svg {
+ width: 1.25rem;
+ height: 1.25rem;
+}
+
+.button--icon {
+ width: 2rem;
+ height: 2rem;
+ padding: 0.375rem;
+}
+
+.button--icon svg {
+ width: 1.25rem;
+ height: 1.25rem;
+}
+
+.button--lg-icon {
+ width: 2.25rem;
+ height: 2.25rem;
+ padding: 0.5rem;
+}
+
+.button--lg-icon svg {
+ width: 1.25rem;
+ height: 1.25rem;
+}
+
+.button--xl-icon {
+ width: 2.5rem;
+ height: 2.5rem;
+ padding: 0.5rem;
+}
+
+.button--xl-icon svg {
+ width: 1.5rem;
+ height: 1.5rem;
+}
diff --git a/public/css/3_components/cart.css b/public/css/3_components/cart.css
new file mode 100644
index 0000000..5d66349
--- /dev/null
+++ b/public/css/3_components/cart.css
@@ -0,0 +1,114 @@
+.cart {
+ max-width: 36rem;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.cart__title {
+ margin-bottom: 3rem;
+ font-size: 1.875rem;
+ font-weight: bold;
+ line-height: 2rem;
+ text-align: center;
+}
+
+.cart__container {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--border);
+ border-radius: 0.75rem;
+}
+
+.cart__item {
+ display: flex;
+ padding: 1.5rem;
+ gap: 1.5rem;
+ border-bottom: 1px solid var(--border);
+}
+
+.cart__item-image {
+ width: 5rem;
+ border-radius: 0.75rem;
+ background-color: var(--muted-background);
+}
+
+.cart__item-image-content {
+ width: 100%;
+ aspect-ratio: 2/3;
+ object-fit: contain;
+}
+
+.cart__item-details {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ flex-grow: 1;
+}
+
+.cart__item-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+}
+
+.cart__item-title {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.cart__item-footer {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.cart__item-price {
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+}
+
+.cart__item-quantity {
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+}
+
+.cart__item-quantity-display {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 2rem;
+ height: 2rem;
+ padding: 0.5rem 1rem;
+ border: 1px solid var(--border);
+ border-radius: 0.375rem;
+}
+
+.cart__total {
+ display: flex;
+ justify-content: space-between;
+ padding: 1.5rem;
+ border-bottom: 1px solid var(--border);
+ font-size: 1rem;
+ font-weight: 500;
+ line-height: 1.5rem;
+}
+
+.cart__action {
+ padding: 1.5rem;
+}
+
+.cart__action-button {
+ width: 100%;
+}
+
+@media (min-width: 48rem) {
+ .cart__item-footer {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ }
+}
diff --git a/public/css/3_components/categories.css b/public/css/3_components/categories.css
new file mode 100644
index 0000000..398f877
--- /dev/null
+++ b/public/css/3_components/categories.css
@@ -0,0 +1,50 @@
+.categories {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+}
+
+.categories__header {
+ max-width: 48rem;
+}
+
+.categories__title {
+ font-size: 1.5rem;
+ line-height: 2rem;
+ font-weight: bold;
+ margin-bottom: 1rem;
+}
+
+.categories__description {
+ color: var(--muted-foreground);
+ margin-bottom: 2.5rem;
+}
+
+.categories__description-break {
+ display: none;
+}
+
+.categories__grid {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+@media (min-width: 768px) {
+ .categories {
+ padding-top: 6rem;
+ padding-bottom: 6rem;
+ }
+
+ .categories__title {
+ font-size: 2.25rem;
+ line-height: 2.5rem;
+ }
+
+ .categories__description-break {
+ display: block;
+ }
+
+ .categories__grid {
+ flex-direction: row;
+ }
+}
diff --git a/public/css/3_components/category-header.css b/public/css/3_components/category-header.css
new file mode 100644
index 0000000..a50a74b
--- /dev/null
+++ b/public/css/3_components/category-header.css
@@ -0,0 +1,20 @@
+.category-header {
+ padding-top: 2.5rem;
+ padding-bottom: 2.5rem;
+ border-bottom: 1px solid var(--border);
+}
+
+.category-header__content {
+ max-width: 48rem;
+}
+
+.category-header__title {
+ font-size: 2.25rem;
+ font-weight: bold;
+ margin-bottom: 1rem;
+}
+
+.category-header__description {
+ font-size: 0.875rem;
+ color: var(--muted-foreground);
+}
diff --git a/public/css/3_components/category.css b/public/css/3_components/category.css
new file mode 100644
index 0000000..d646e46
--- /dev/null
+++ b/public/css/3_components/category.css
@@ -0,0 +1,31 @@
+.category {
+ flex: 1;
+ flex-basis: 0;
+}
+
+.category__image {
+ border-radius: 0.75rem;
+ overflow: hidden;
+ margin-bottom: 1rem;
+}
+
+.category__image img {
+ width: 100%;
+ aspect-ratio: 3/2;
+ object-fit: cover;
+}
+
+.category__title {
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+}
+
+.category:hover .category__title {
+ text-decoration: underline;
+}
+
+.category__description {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--muted-foreground);
+}
diff --git a/public/css/3_components/checkout.css b/public/css/3_components/checkout.css
new file mode 100644
index 0000000..4051494
--- /dev/null
+++ b/public/css/3_components/checkout.css
@@ -0,0 +1,136 @@
+.checkout {
+ background-color: var(--muted-background);
+}
+
+.checkout__layout {
+ display: flex;
+ flex-direction: column;
+ gap: 3rem;
+ max-width: 42rem;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.checkout__summary {
+ flex-grow: 1;
+}
+
+.checkout__summary_title {
+ margin-bottom: 1rem;
+ font-size: 1.125rem;
+ font-weight: 500;
+ line-height: 1.75rem;
+}
+
+.checkout__summary_container {
+ display: flex;
+ flex-direction: column;
+ border: 1px solid var(--border);
+ border-radius: 0.75rem;
+ background-color: var(--background);
+}
+
+.checkout__item {
+ display: flex;
+ padding: 1.5rem;
+ gap: 1.5rem;
+ border-bottom: 1px solid var(--border);
+}
+
+.checkout__item-image {
+ width: 5rem;
+ border-radius: 0.75rem;
+ background-color: var(--muted-background);
+}
+
+.checkout__item-image-content {
+ width: 100%;
+ aspect-ratio: 1;
+ object-fit: contain;
+}
+
+.checkout__item-details {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ flex-grow: 1;
+}
+
+.checkout__item-title {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+}
+
+.checkout__item-price {
+ display: flex;
+ align-items: center;
+ align-self: flex-end;
+ gap: 1rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ line-height: 1.25rem;
+}
+
+.checkout__item-price-icon {
+ width: 1rem;
+ height: 1rem;
+}
+
+.checkout__total {
+ display: flex;
+ justify-content: space-between;
+ padding: 1.5rem;
+ font-size: 1rem;
+ font-weight: 500;
+ line-height: 1.5rem;
+}
+
+.checkout__form {
+ flex-grow: 1;
+
+ fieldset {
+ display: block;
+ width: 100%;
+ }
+}
+
+.checkout__legend {
+ margin-bottom: 1.5rem;
+ font-size: 1.25rem;
+ font-weight: 500;
+ line-height: 1.75rem;
+}
+
+.checkout__separator {
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.checkout__form-fields {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.checkout__submit {
+ width: 100%;
+ margin-top: 1.5rem;
+}
+
+@media (min-width: 64rem) {
+ .checkout__layout {
+ flex-direction: row;
+ max-width: none;
+ align-items: start;
+ }
+
+ .checkout__form {
+ order: -1;
+ max-width: 56rem;
+ }
+
+ .checkout__summary {
+ position: sticky;
+ top: calc(96px + 49px);
+ }
+}
diff --git a/public/css/3_components/error.css b/public/css/3_components/error.css
new file mode 100644
index 0000000..7da9ce8
--- /dev/null
+++ b/public/css/3_components/error.css
@@ -0,0 +1,20 @@
+/*
+.error {
+}
+*/
+
+.error__content {
+ text-align: center;
+}
+
+.error__title {
+ margin-bottom: 0.75rem;
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: tomato;
+}
+
+.error__message {
+ margin-bottom: 1rem;
+ color: var(--muted-foreground);
+}
diff --git a/public/css/3_components/features.css b/public/css/3_components/features.css
new file mode 100644
index 0000000..9bc141f
--- /dev/null
+++ b/public/css/3_components/features.css
@@ -0,0 +1,54 @@
+.features {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+ text-align: center;
+ background-color: var(--muted-background);
+}
+
+.features__title {
+ margin-bottom: 3rem;
+ font-size: 1.5rem;
+ font-weight: bold;
+}
+
+.features__grid {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+.feature__icon {
+ display: inline-block;
+ margin-bottom: 1.5rem;
+}
+
+.feature__title {
+ margin-bottom: 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+}
+
+.feature__description {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--muted-foreground);
+}
+
+@media (min-width: 640px) {
+ .features__grid {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@media (min-width: 768px) {
+ .features {
+ padding-top: 6rem;
+ padding-bottom: 6rem;
+ }
+
+ .features__grid {
+ grid-template-columns: repeat(4, 1fr);
+ }
+}
diff --git a/public/css/3_components/footer.css b/public/css/3_components/footer.css
new file mode 100644
index 0000000..c4df0b4
--- /dev/null
+++ b/public/css/3_components/footer.css
@@ -0,0 +1,69 @@
+/* .footer {
+} */
+
+.footer__section {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+.footer__links {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem 2rem;
+ flex-grow: 1;
+}
+
+.footer__list {
+ flex: 1;
+ flex-basis: 10rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ padding: 0;
+ font-size: 0.875rem;
+ color: var(--muted-foreground);
+}
+
+.footer__title {
+ font-weight: 500;
+ color: var(--foreground);
+}
+
+.footer__newsletter {
+ max-width: 28rem;
+ font-size: 0.875rem;
+}
+
+.footer__newsletter-title {
+ margin-bottom: 1.5rem;
+ font-weight: 500;
+}
+
+.footer__newsletter-text {
+ margin-bottom: 0.5rem;
+ color: var(--muted-foreground);
+}
+
+.footer__form {
+ display: flex;
+ gap: 0.5rem;
+
+ input {
+ flex-grow: 1;
+ }
+}
+
+.footer__copyright {
+ display: block;
+ padding: 1.5rem 0;
+ font-size: 0.875rem;
+ text-align: center;
+ color: var(--muted-foreground);
+}
+
+@media (min-width: 64rem) {
+ .footer__section {
+ flex-direction: row;
+ }
+}
diff --git a/public/css/3_components/header-actions.css b/public/css/3_components/header-actions.css
new file mode 100644
index 0000000..3f1203e
--- /dev/null
+++ b/public/css/3_components/header-actions.css
@@ -0,0 +1,28 @@
+.header-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.header-actions__separator {
+ height: 1.5rem;
+}
+
+.header-actions__cart {
+ position: relative;
+}
+
+.header-actions__cart-badge {
+ position: absolute;
+ bottom: -0.25rem;
+ right: -0.25rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 1.25rem;
+ height: 1.25rem;
+ border-radius: 9999px;
+ font-size: 0.75rem;
+ color: var(--primary-foreground);
+ background-color: var(--primary-background);
+}
diff --git a/public/css/3_components/header-main.css b/public/css/3_components/header-main.css
new file mode 100644
index 0000000..2bf6aee
--- /dev/null
+++ b/public/css/3_components/header-main.css
@@ -0,0 +1,26 @@
+.header-main {
+ position: relative;
+}
+
+.header-main__top {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 3rem;
+}
+
+.header-main__separator {
+ display: none;
+}
+
+@media (min-width: 40rem) {
+ .header-main {
+ position: relative;
+ }
+}
+
+@media (max-width: 639px) {
+ .header-main__separator {
+ display: block;
+ }
+}
diff --git a/public/css/3_components/hero.css b/public/css/3_components/hero.css
new file mode 100644
index 0000000..f756eec
--- /dev/null
+++ b/public/css/3_components/hero.css
@@ -0,0 +1,39 @@
+.hero {
+ text-align: center;
+ color: var(--white);
+ background-size: cover;
+ background-repeat: no-repeat;
+ background-position: center;
+ background-image: linear-gradient(
+ 0deg,
+ rgba(0, 0, 0, 0.5) 0%,
+ rgba(0, 0, 0, 0.5) 100%
+ ),
+ url("/images/hero.jpg");
+}
+
+.hero__container {
+ padding-top: 8rem;
+ padding-bottom: 8rem;
+ max-width: 48rem;
+}
+
+.hero__title {
+ font-size: 2.25rem;
+ font-weight: bold;
+ margin-bottom: 1rem;
+ line-height: 2.5rem;
+}
+
+.hero__text {
+ font-size: 1.25rem;
+ margin-bottom: 2rem;
+ line-height: 1.75rem;
+}
+
+@media (min-width: 768px) {
+ .hero__title {
+ font-size: 3.75rem;
+ line-height: 1;
+ }
+}
diff --git a/public/css/3_components/input-field.css b/public/css/3_components/input-field.css
new file mode 100644
index 0000000..e2aa924
--- /dev/null
+++ b/public/css/3_components/input-field.css
@@ -0,0 +1,5 @@
+.input-field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
diff --git a/public/css/3_components/main-nav.css b/public/css/3_components/main-nav.css
new file mode 100644
index 0000000..a89a83c
--- /dev/null
+++ b/public/css/3_components/main-nav.css
@@ -0,0 +1,42 @@
+.main-nav {
+ position: static;
+}
+
+.main-nav__list {
+ display: flex;
+ justify-content: center;
+ height: 3rem;
+}
+
+.main-nav__item {
+ display: flex;
+ justify-content: center;
+}
+
+.main-nav__link {
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+ padding: 0.75rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ color: var(--muted-foreground);
+ transition: background-color 0.2s;
+}
+
+.main-nav__link:hover {
+ background-color: var(--accent-background);
+}
+
+.main-nav__link--active {
+ color: var(--accent-foreground);
+}
+
+@media (min-width: 640px) {
+ .main-nav {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ }
+}
diff --git a/public/css/3_components/not-found.css b/public/css/3_components/not-found.css
new file mode 100644
index 0000000..d9d59e2
--- /dev/null
+++ b/public/css/3_components/not-found.css
@@ -0,0 +1,17 @@
+/* .not-found {
+} */
+
+.not-found__content {
+ text-align: center;
+}
+
+.not-found__title {
+ margin-bottom: 0.75rem;
+ font-size: 1.5rem;
+ font-weight: 700;
+}
+
+.not-found__message {
+ margin-bottom: 1rem;
+ color: var(--muted-foreground);
+}
diff --git a/public/css/3_components/order-confirmation.css b/public/css/3_components/order-confirmation.css
new file mode 100644
index 0000000..2aa8c98
--- /dev/null
+++ b/public/css/3_components/order-confirmation.css
@@ -0,0 +1,51 @@
+.order-confirmation {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+}
+
+.order-confirmation__title {
+ margin-bottom: 0.5rem;
+ font-size: 1rem;
+ color: var(--accent-foreground);
+ font-weight: 500;
+}
+
+.order-confirmation__heading {
+ margin-bottom: 0.5rem;
+ font-size: 2.25rem;
+ line-height: 2.25rem;
+ font-weight: bold;
+}
+
+.order-confirmation__description {
+ margin-bottom: 3rem;
+ color: var(--muted-foreground);
+}
+
+.order-confirmation__tracking-label {
+ margin-bottom: 0.5rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+}
+
+.order-confirmation__tracking-code {
+ color: var(--accent-foreground);
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+}
+
+@media (min-width: 40rem) {
+ .order-confirmation {
+ padding-top: 3.5rem;
+ padding-bottom: 3.5rem;
+ }
+}
+
+@media (min-width: 64rem) {
+ .order-confirmation {
+ padding-top: 4rem;
+ padding-bottom: 4rem;
+ }
+}
diff --git a/public/css/3_components/price-filter.css b/public/css/3_components/price-filter.css
new file mode 100644
index 0000000..ee02589
--- /dev/null
+++ b/public/css/3_components/price-filter.css
@@ -0,0 +1,33 @@
+.price-filter {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+}
+
+.price-filter__legend {
+ font-size: 1rem;
+ line-height: 1.5rem;
+ font-weight: 500;
+ margin-bottom: 1rem;
+}
+
+.price-filter__inputs {
+ display: flex;
+ gap: 1.5rem;
+}
+
+.price-filter__field {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+}
+
+.price-filter__label {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+}
+
+.price-filter__button {
+ width: 100%;
+}
diff --git a/public/css/3_components/product-card.css b/public/css/3_components/product-card.css
new file mode 100644
index 0000000..8b26b20
--- /dev/null
+++ b/public/css/3_components/product-card.css
@@ -0,0 +1,70 @@
+.product-card {
+ display: block;
+ border-radius: 0.75rem;
+}
+
+.product-card__container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ overflow: hidden;
+ border: 1px solid var(--separator);
+ border-radius: 0.75rem;
+}
+
+.product-card__image-container {
+ aspect-ratio: 3/4;
+ background-color: var(--muted-background);
+}
+
+.product-card__image {
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ transition: transform 0.2s;
+}
+
+:is(.product-card:hover, .product-card:focus-visible) .product-card__image {
+ transform: scale(1.05);
+}
+
+.product-card__content {
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ gap: 0.5rem;
+ padding: 1rem;
+}
+
+.product-card__title {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+}
+
+.product-card__description {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--muted-foreground);
+}
+
+.product-card__price {
+ margin-top: auto;
+ font-size: 1rem;
+ line-height: 1.5rem;
+ font-weight: 500;
+}
+
+.product-card__sale-badge {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 0.25rem 0.5rem;
+ border-bottom-left-radius: 0.75rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 500;
+ color: var(--primary-foreground);
+ background-color: var(--primary-background);
+}
diff --git a/public/css/3_components/product.css b/public/css/3_components/product.css
new file mode 100644
index 0000000..c2d5698
--- /dev/null
+++ b/public/css/3_components/product.css
@@ -0,0 +1,92 @@
+.product {
+ padding-top: 3rem;
+ padding-bottom: 3rem;
+}
+
+.product__container {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+.product__image {
+ background-color: var(--muted-background);
+ border-radius: 0.75rem;
+ min-width: min(100%, 28rem);
+ align-self: center;
+ flex-basis: 0;
+ flex-grow: 1;
+ max-width: 36rem;
+}
+
+.product__image_content {
+ width: 100%;
+ aspect-ratio: 1;
+ object-fit: contain;
+}
+
+.product__info {
+ flex-grow: 1;
+ flex-basis: 0;
+}
+
+.product__title {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+ font-weight: bold;
+ margin-bottom: 0.75rem;
+}
+
+.product__price {
+ font-size: 1.875rem;
+ line-height: 2.25rem;
+ margin-bottom: 1.5rem;
+}
+
+.product__description {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--muted-foreground);
+ margin-bottom: 2.5rem;
+}
+
+.product__button {
+ width: 100%;
+}
+
+.product__separator {
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.product__features_title {
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ font-weight: 600;
+ color: var(--accent-foreground);
+ margin-bottom: 1.5rem;
+}
+
+.product__features_list {
+ list-style-type: disc;
+ padding-left: 1rem;
+ font-size: 0.875rem;
+ line-height: 1.25rem;
+ color: var(--muted-foreground);
+}
+
+@media (min-width: 48rem) {
+ .product__container {
+ flex-direction: row;
+ align-items: flex-start;
+ }
+
+ .product__image {
+ min-width: fit-content;
+ align-self: flex-start;
+ }
+
+ .product__button {
+ width: 20rem;
+ }
+}
diff --git a/public/css/3_components/products.css b/public/css/3_components/products.css
new file mode 100644
index 0000000..2bae9e0
--- /dev/null
+++ b/public/css/3_components/products.css
@@ -0,0 +1,32 @@
+.products {
+ padding-top: 2.5rem;
+ padding-bottom: 2.5rem;
+}
+
+.products__layout {
+ display: flex;
+ gap: 2rem;
+ flex-direction: column;
+}
+
+.products__price-filter {
+ width: 100%;
+ max-width: 24rem;
+}
+
+.products__grid {
+ display: grid;
+ gap: 2rem;
+ grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
+ flex-grow: 1;
+}
+
+@media (min-width: 64rem) {
+ .products__layout {
+ flex-direction: row;
+ }
+
+ .products__price-filter {
+ max-width: 20rem;
+ }
+}
diff --git a/public/css/3_components/prose.css b/public/css/3_components/prose.css
new file mode 100644
index 0000000..bc836da
--- /dev/null
+++ b/public/css/3_components/prose.css
@@ -0,0 +1,218 @@
+/* Content */
+
+.prose {
+ --headings: var(--foreground);
+ --body: var(--foreground);
+ --captions: var(--muted-foreground);
+ --bold: var(--foreground);
+ --code: var(--foreground);
+ --pre: var(--muted);
+ --pre-background: var(--foreground);
+ --hr: var(--border);
+ --td-border: var(--border);
+ --th-border: var(--border);
+ --links: var(--accent-foreground);
+ --markers: var(--accent-foreground);
+}
+
+.prose {
+ color: var(--body);
+ font-size: 1rem; /* 16px */
+ line-height: 1.75rem; /* 28px */
+}
+
+.prose h1 {
+ margin-top: 0;
+ margin-bottom: 2rem; /* 32px */
+
+ color: var(--headings);
+ font-weight: 800;
+ font-size: 2.25rem; /* 36px */
+ line-height: 2.5rem; /* 40px */
+}
+
+.prose h2 {
+ margin-top: 3rem; /* 48px */
+ margin-bottom: 1.5rem; /* 24px */
+
+ color: var(--headings);
+ font-weight: 700;
+ font-size: 1.5rem; /* 24px */
+ line-height: 2rem; /* 32px */
+}
+
+.prose h3 {
+ margin-top: 2rem; /* 32px */
+ margin-bottom: 0.75rem; /* 12px */
+
+ color: var(--headings);
+ font-weight: 600;
+ font-size: 1.25rem; /* 20px */
+ line-height: 2rem; /* 32px */
+}
+
+.prose h4 {
+ margin-top: 1.5rem; /* 24px */
+ margin-bottom: 0.5rem; /* 8px */
+
+ color: var(--headings);
+ font-weight: 600;
+ font-size: 1rem; /* 16px */
+ line-height: 1.5rem; /* 24px */
+}
+
+.prose img {
+ display: block;
+ max-width: 100%;
+ height: auto;
+ margin-block: 2rem; /* 32px */
+ border-radius: 0.375rem; /* 6px */
+}
+
+.prose figure {
+ margin-block: 2rem; /* 32px */
+}
+
+.prose figure img {
+ margin-block: 0;
+}
+
+.prose figure figcaption {
+ margin-top: 0.75rem; /* 12px */
+ margin-bottom: 0;
+
+ color: var(--captions);
+ font-size: 0.875rem; /* 14px */
+ line-height: 1.25rem; /* 20px */
+}
+
+.prose ol,
+.prose ul {
+ margin-block: 1.25rem; /* 20px */
+ padding-left: 32px;
+}
+
+.prose li {
+ margin-block: 0.5rem; /* 8px */
+}
+
+.prose li::marker {
+ color: var(--markers);
+}
+
+.prose li p {
+ margin-block: 0.75rem; /* 12px */
+}
+
+.prose hr {
+ margin-block: 3rem; /* 48px */
+}
+
+.prose p {
+ margin-block: 1.25rem; /* 20px */
+}
+
+.prose strong {
+ color: var(--bold);
+ font-weight: 600;
+}
+
+.prose a {
+ color: var(--links);
+ font-weight: 500;
+}
+
+.prose code {
+ color: var(--code);
+ font-family: var(--monospace);
+ font-size: 0.875rem; /* 14px */
+ font-weight: 600;
+ line-height: 1.75rem; /* 28px */
+}
+
+.prose code::before,
+.prose code::after {
+ content: "`";
+}
+
+.prose pre {
+ margin-block: 1.5rem; /* 24px */
+ padding: 12px 16px;
+ background: var(--pre-background);
+ border-radius: 0.375rem; /* 6px */
+ overflow: auto;
+
+ color: var(--pre);
+ font-family: var(--monospace);
+ font-size: 0.875rem; /* 14px */
+ line-height: 1.75rem; /* 28px */
+}
+
+.prose pre code {
+ all: unset;
+}
+
+.prose pre code::before,
+.prose pre code::after {
+ content: unset;
+}
+
+.prose table {
+ margin-block: 2rem; /* 32px */
+ width: 100%;
+ text-align: left;
+ border-collapse: collapse;
+}
+
+.prose th {
+ padding: 0 8px 8px;
+
+ color: var(--headings);
+ font-size: 0.875rem; /* 14px */
+ font-weight: 600;
+ line-height: 1.75rem; /* 28px */
+}
+
+.prose th:first-child,
+.prose td:first-child {
+ padding-left: 0;
+}
+
+.prose th:last-child,
+.prose td:last-child {
+ padding-right: 0;
+}
+
+.prose td {
+ padding: 8px;
+
+ font-size: 0.875rem; /* 14px */
+ font-weight: 400;
+ line-height: 1.75rem; /* 28px */
+}
+
+.prose th {
+ border-bottom: 1px solid var(--th-border);
+}
+
+.prose td {
+ border-bottom: 1px solid var(--td-border);
+}
+
+.prose tr:last-child td {
+ border-bottom: unset;
+}
+
+.prose h2 + *,
+.prose h3 + *,
+.prose h4 + * {
+ margin-top: 0;
+}
+
+.prose > *:first-child {
+ margin-top: 0;
+}
+
+.prose > *:last-child {
+ margin-bottom: 0;
+}
diff --git a/public/css/3_components/root.css b/public/css/3_components/root.css
new file mode 100644
index 0000000..d03ad6f
--- /dev/null
+++ b/public/css/3_components/root.css
@@ -0,0 +1,18 @@
+.root {
+ display: grid;
+ grid-template-rows: auto 1fr auto;
+ min-height: 100vh;
+ background-color: var(--background);
+}
+
+.root__header {
+ position: sticky;
+ top: 0;
+ z-index: 50;
+ border-bottom: 1px solid var(--border);
+ background-color: var(--background);
+}
+
+.root__footer {
+ border-top: 1px solid var(--border);
+}
diff --git a/public/css/3_components/separator.css b/public/css/3_components/separator.css
new file mode 100644
index 0000000..d399589
--- /dev/null
+++ b/public/css/3_components/separator.css
@@ -0,0 +1,12 @@
+.separator {
+ flex-shrink: 0;
+ width: 100%;
+ height: 1px;
+
+ background-color: var(--border);
+}
+
+.separator--vertical {
+ width: 1px;
+ height: 100%;
+}
diff --git a/public/css/index.css b/public/css/index.css
new file mode 100644
index 0000000..a8966c8
--- /dev/null
+++ b/public/css/index.css
@@ -0,0 +1,43 @@
+/* Declare layers in cascade order */
+@layer definitions, reset, global, layouts, components, utilities;
+
+/* 1 Foundation */
+@import "1_foundation/colors.css" layer(definitions);
+@import "1_foundation/typography.css" layer(definitions);
+@import "1_foundation/reset.css" layer(reset);
+@import "1_foundation/global.css" layer(global);
+
+/* 2 Layouts */
+@import "2_layouts/container.css" layer(layouts);
+@import "2_layouts/section.css" layer(layouts);
+
+/* 3 Components */
+/* UI */
+@import "3_components/button.css" layer(components);
+@import "3_components/separator.css" layer(components);
+@import "3_components/input-field.css" layer(components);
+
+/* Routes */
+@import "3_components/not-found.css" layer(components);
+@import "3_components/error.css" layer(components);
+@import "3_components/root.css" layer(components);
+@import "3_components/footer.css" layer(components);
+@import "3_components/header-main.css" layer(components);
+@import "3_components/header-actions.css" layer(components);
+@import "3_components/main-nav.css" layer(components);
+@import "3_components/categories.css" layer(components);
+@import "3_components/category.css" layer(components);
+@import "3_components/features.css" layer(components);
+@import "3_components/hero.css" layer(components);
+@import "3_components/category-header.css" layer(components);
+@import "3_components/products.css" layer(components);
+@import "3_components/price-filter.css" layer(components);
+@import "3_components/product-card.css" layer(components);
+@import "3_components/product.css" layer(components);
+@import "3_components/cart.css" layer(components);
+@import "3_components/checkout.css" layer(components);
+@import "3_components/order-confirmation.css" layer(components);
+@import "3_components/prose.css" layer(components);
+
+/* 4 Utilities */
+/* @import "4_utilities/__.css" layer(utilities); */
diff --git a/public/gatito.png b/public/gatito.png
deleted file mode 100644
index 0749e6f..0000000
Binary files a/public/gatito.png and /dev/null differ
diff --git a/public/home.html b/public/home.html
new file mode 100644
index 0000000..388b8dd
--- /dev/null
+++ b/public/home.html
@@ -0,0 +1,259 @@
+
+
+
+
+
+ Full Stock | Inicio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Nuevos productos disponibles
+
+ Un pequeño lote de increíbles productos acaba de llegar.
+
+ Agrega tus favoritos al carrito antes que se agoten.
+
+
Compra ahora
+
+
+
+
+
+
+
+
Nuestra Promesa de Calidad
+
+
+
+
Entrega rápida
+
+ Recibe tus productos en tiempo récord, directo a tu puerta,
+ para que puedas disfrutar de ellos cuanto antes.
+
+
+
+
+
+
Satisfacción Garantizada
+
+ Tu felicidad es nuestra prioridad. Si no estás 100%
+ satisfecho, estamos aquí para ayudarte con cambios o
+ devoluciones.
+
+
+
+
+
+
Materiales de Alta Calidad
+
+ Nos aseguramos de que todos nuestros productos estén hechos
+ con materiales de la más alta calidad.
+
+
+
+
+
+
Diseños Exclusivos
+
+ Cada producto está diseñado pensando en los desarrolladores,
+ con estilos únicos que no encontrarás en ningún otro lugar.
+
+
+
+
+
+
+
+
+
+
diff --git a/public/image.png b/public/image.png
deleted file mode 100644
index 6ef6fcc..0000000
Binary files a/public/image.png and /dev/null differ
diff --git a/public/images/fullstock-logo.svg b/public/images/fullstock-logo.svg
new file mode 100644
index 0000000..0f16800
--- /dev/null
+++ b/public/images/fullstock-logo.svg
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/images/hero.jpg b/public/images/hero.jpg
new file mode 100644
index 0000000..e97876c
Binary files /dev/null and b/public/images/hero.jpg differ
diff --git a/public/images/icons/cart.svg b/public/images/icons/cart.svg
new file mode 100644
index 0000000..3b3e1fa
--- /dev/null
+++ b/public/images/icons/cart.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/public/images/icons/idea.svg b/public/images/icons/idea.svg
new file mode 100644
index 0000000..6dde41d
--- /dev/null
+++ b/public/images/icons/idea.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/images/icons/minus.svg b/public/images/icons/minus.svg
new file mode 100644
index 0000000..8c2ee74
--- /dev/null
+++ b/public/images/icons/minus.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/public/images/icons/plus.svg b/public/images/icons/plus.svg
new file mode 100644
index 0000000..cb53d86
--- /dev/null
+++ b/public/images/icons/plus.svg
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/public/images/icons/return.svg b/public/images/icons/return.svg
new file mode 100644
index 0000000..027087b
--- /dev/null
+++ b/public/images/icons/return.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/public/images/icons/ribbon.svg b/public/images/icons/ribbon.svg
new file mode 100644
index 0000000..4eac618
--- /dev/null
+++ b/public/images/icons/ribbon.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/public/images/icons/trash.svg b/public/images/icons/trash.svg
new file mode 100644
index 0000000..0560c9d
--- /dev/null
+++ b/public/images/icons/trash.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/public/images/icons/truck.svg b/public/images/icons/truck.svg
new file mode 100644
index 0000000..c70699e
--- /dev/null
+++ b/public/images/icons/truck.svg
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/public/images/icons/x.svg b/public/images/icons/x.svg
new file mode 100644
index 0000000..cfafac6
--- /dev/null
+++ b/public/images/icons/x.svg
@@ -0,0 +1,5 @@
+
+
+
+
\ No newline at end of file
diff --git a/public/images/polos.jpg b/public/images/polos.jpg
new file mode 100644
index 0000000..925630f
Binary files /dev/null and b/public/images/polos.jpg differ
diff --git a/public/images/polos/polo-backend.png b/public/images/polos/polo-backend.png
new file mode 100644
index 0000000..7cf4eb9
Binary files /dev/null and b/public/images/polos/polo-backend.png differ
diff --git a/public/images/polos/polo-feature.png b/public/images/polos/polo-feature.png
new file mode 100644
index 0000000..cd5baa5
Binary files /dev/null and b/public/images/polos/polo-feature.png differ
diff --git a/public/images/polos/polo-frontend.png b/public/images/polos/polo-frontend.png
new file mode 100644
index 0000000..fd60002
Binary files /dev/null and b/public/images/polos/polo-frontend.png differ
diff --git a/public/images/polos/polo-fullstack.png b/public/images/polos/polo-fullstack.png
new file mode 100644
index 0000000..9f72803
Binary files /dev/null and b/public/images/polos/polo-fullstack.png differ
diff --git a/public/images/polos/polo-js.png b/public/images/polos/polo-js.png
new file mode 100644
index 0000000..6736016
Binary files /dev/null and b/public/images/polos/polo-js.png differ
diff --git a/public/images/polos/polo-node.png b/public/images/polos/polo-node.png
new file mode 100644
index 0000000..b89ad3f
Binary files /dev/null and b/public/images/polos/polo-node.png differ
diff --git a/public/images/polos/polo-react.png b/public/images/polos/polo-react.png
new file mode 100644
index 0000000..01e0e41
Binary files /dev/null and b/public/images/polos/polo-react.png differ
diff --git a/public/images/polos/polo-ts.png b/public/images/polos/polo-ts.png
new file mode 100644
index 0000000..8638555
Binary files /dev/null and b/public/images/polos/polo-ts.png differ
diff --git a/public/images/polos/polo-works.png b/public/images/polos/polo-works.png
new file mode 100644
index 0000000..89b3907
Binary files /dev/null and b/public/images/polos/polo-works.png differ
diff --git a/public/images/stickers.jpg b/public/images/stickers.jpg
new file mode 100644
index 0000000..13b90ba
Binary files /dev/null and b/public/images/stickers.jpg differ
diff --git a/public/images/stickers/sticker-docker.png b/public/images/stickers/sticker-docker.png
new file mode 100644
index 0000000..3b16a70
Binary files /dev/null and b/public/images/stickers/sticker-docker.png differ
diff --git a/public/images/stickers/sticker-git.png b/public/images/stickers/sticker-git.png
new file mode 100644
index 0000000..670da23
Binary files /dev/null and b/public/images/stickers/sticker-git.png differ
diff --git a/public/images/stickers/sticker-github.png b/public/images/stickers/sticker-github.png
new file mode 100644
index 0000000..53cc3d5
Binary files /dev/null and b/public/images/stickers/sticker-github.png differ
diff --git a/public/images/stickers/sticker-html.png b/public/images/stickers/sticker-html.png
new file mode 100644
index 0000000..bbce95d
Binary files /dev/null and b/public/images/stickers/sticker-html.png differ
diff --git a/public/images/stickers/sticker-js.png b/public/images/stickers/sticker-js.png
new file mode 100644
index 0000000..32b72c3
Binary files /dev/null and b/public/images/stickers/sticker-js.png differ
diff --git a/public/images/stickers/sticker-linux.png b/public/images/stickers/sticker-linux.png
new file mode 100644
index 0000000..a92a548
Binary files /dev/null and b/public/images/stickers/sticker-linux.png differ
diff --git a/public/images/stickers/sticker-react.png b/public/images/stickers/sticker-react.png
new file mode 100644
index 0000000..45f1dd5
Binary files /dev/null and b/public/images/stickers/sticker-react.png differ
diff --git a/public/images/stickers/sticker-vscode.png b/public/images/stickers/sticker-vscode.png
new file mode 100644
index 0000000..5b3bc77
Binary files /dev/null and b/public/images/stickers/sticker-vscode.png differ
diff --git a/public/images/tazas.jpg b/public/images/tazas.jpg
new file mode 100644
index 0000000..583b47e
Binary files /dev/null and b/public/images/tazas.jpg differ
diff --git a/public/images/tazas/taza-git.png b/public/images/tazas/taza-git.png
new file mode 100644
index 0000000..f30284a
Binary files /dev/null and b/public/images/tazas/taza-git.png differ
diff --git a/public/images/tazas/taza-github.png b/public/images/tazas/taza-github.png
new file mode 100644
index 0000000..5de43f1
Binary files /dev/null and b/public/images/tazas/taza-github.png differ
diff --git a/public/images/tazas/taza-js.png b/public/images/tazas/taza-js.png
new file mode 100644
index 0000000..968652d
Binary files /dev/null and b/public/images/tazas/taza-js.png differ
diff --git a/public/images/tazas/taza-linux.png b/public/images/tazas/taza-linux.png
new file mode 100644
index 0000000..807339e
Binary files /dev/null and b/public/images/tazas/taza-linux.png differ
diff --git a/public/images/tazas/taza-react.png b/public/images/tazas/taza-react.png
new file mode 100644
index 0000000..6f242e7
Binary files /dev/null and b/public/images/tazas/taza-react.png differ
diff --git a/public/images/tazas/taza-sql.png b/public/images/tazas/taza-sql.png
new file mode 100644
index 0000000..f69a69a
Binary files /dev/null and b/public/images/tazas/taza-sql.png differ
diff --git a/public/index.html b/public/index.html
deleted file mode 100644
index d2b9608..0000000
--- a/public/index.html
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
- FullStock MPA
-
-
-
- Welcome to FullStock MPA
- This is the home page.
-
-
-
diff --git a/public/layout.html b/public/layout.html
new file mode 100644
index 0000000..91c8422
--- /dev/null
+++ b/public/layout.html
@@ -0,0 +1,136 @@
+
+
+
+
+
+ Full Stock | Inicio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/public/nombres.json b/public/nombres.json
deleted file mode 100644
index 664f3e5..0000000
--- a/public/nombres.json
+++ /dev/null
@@ -1 +0,0 @@
-["Ana", "Luis", "María", "Carlos", "Sofía", "Javier"]
diff --git a/public/order-confirmation.html b/public/order-confirmation.html
new file mode 100644
index 0000000..3ddddbf
--- /dev/null
+++ b/public/order-confirmation.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+ Full Stock | Inicio
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ¡Muchas gracias por tu compra!
+
+
Tu orden está en camino
+
+ Llegaremos a la puerta de tu domicilio lo antes posible
+
+
Número de orden
+
1
+
+
+
+
+
+
+
diff --git a/public/product.html b/public/product.html
new file mode 100644
index 0000000..0a6bb3d
--- /dev/null
+++ b/public/product.html
@@ -0,0 +1,174 @@
+
+
+
+
+
+ Full Stock | Polo React
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Polo React
+
S/ 20
+
+ Viste tu pasión por React con estilo y comodidad en cada línea
+ de código.
+
+
+
+
+
Características
+
+
+ Estampado resistente que mantiene sus colores vibrantes
+ lavado tras lavado.
+
+
+ Hecho de algodón suave que asegura comodidad y frescura.
+
+ Costuras reforzadas para una mayor durabilidad.
+ Corte moderno que se adapta perfectamente al cuerpo.
+
+
+
+
+
+
+
+
+
+
diff --git a/public/style.css b/public/style.css
deleted file mode 100644
index fcb472e..0000000
--- a/public/style.css
+++ /dev/null
@@ -1,3 +0,0 @@
-body {
- color: tomato;
-}
diff --git a/public/terms.html b/public/terms.html
deleted file mode 100644
index 0c07f24..0000000
--- a/public/terms.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
- Terminos y Condiciones
-
-
-
- Terminos y Condiciones
-
-
diff --git a/template-system.md b/template-system.md
new file mode 100644
index 0000000..74eb96d
--- /dev/null
+++ b/template-system.md
@@ -0,0 +1,54 @@
+# ¿Qué es un Sistema de Plantillas en Server Side Rendering?
+
+Un **sistema de plantillas** es una herramienta que permite generar páginas HTML dinámicas a partir de datos y estructuras predefinidas. En el contexto de **server side rendering (SSR)**, el servidor utiliza estas plantillas para crear el HTML final antes de enviarlo al navegador del usuario.
+
+## ¿Por qué usar un sistema de plantillas?
+- **Separación de lógica y presentación:** Permite mantener el código de la aplicación separado del diseño visual.
+- **Reutilización:** Puedes definir fragmentos reutilizables (headers, footers, etc.) y usarlos en múltiples páginas.
+- **Dinamicidad:** El contenido de la página se puede adaptar según los datos recibidos del servidor.
+
+## Ejemplo: EJS (Embedded JavaScript Templates)
+
+**EJS** es uno de los sistemas de plantillas más populares en el ecosistema de Node.js. Permite incrustar código JavaScript dentro de archivos HTML usando la sintaxis `<%= %>` para imprimir valores y `<% %>` para ejecutar lógica.
+
+### Ejemplo básico de EJS
+Supongamos que tienes una plantilla `index.ejs`:
+
+```ejs
+
+
+
+ <%= title %>
+
+
+ Bienvenido, <%= user.name %>!
+
+ <% products.forEach(function(product) { %>
+ <%= product.name %> - $<%= product.price %>
+ <% }); %>
+
+
+
+```
+
+Y en tu servidor Node.js:
+
+```js
+app.get('/', (req, res) => {
+ res.render('index', {
+ title: 'Tienda Online',
+ user: { name: 'Diego' },
+ products: [
+ { name: 'Taza', price: 10 },
+ { name: 'Sticker', price: 5 }
+ ]
+ });
+});
+```
+
+El servidor procesa la plantilla, reemplaza las variables y genera el HTML final que se envía al cliente.
+
+## Ventajas de EJS en SSR
+- Fácil de aprender y usar.
+- Permite lógica sencilla dentro de la plantilla.
+- Integración directa con Express y otros frameworks de Node.js.
diff --git a/views/index.ejs b/views/index.ejs
new file mode 100644
index 0000000..8098744
--- /dev/null
+++ b/views/index.ejs
@@ -0,0 +1,18 @@
+
+
+
+
+
+ Document
+
+
+ Welcome to the EJS Template
+ <%= nombre %>
+ <% console.log("Hola desde EJS") %>
+
+ <% for (const tecnologia of tecnologias) { %>
+ <%= tecnologia %>
+ <% } %>
+
+
+