diff --git a/combine-css.js b/combine-css.js new file mode 100644 index 0000000..6208d23 --- /dev/null +++ b/combine-css.js @@ -0,0 +1,55 @@ +// combine-css.js +const fs = require("fs"); +const path = require("path"); +const postcss = require("postcss"); +const atImport = require("postcss-import"); + +// Define your main CSS entry point file +// This is the file that contains all your @import statements. +const mainCssEntry = path.join(__dirname, "src", "css", "styles.css"); + +// Define the output path and filename for the combined CSS +const outputDir = path.join(__dirname, "public", "css"); +const outputFilename = "styles.css"; +const outputPath = path.join(outputDir, outputFilename); + +async function combineCssImports() { + console.log(`Starting CSS bundling from: ${mainCssEntry}`); + + try { + // Read the content of the main CSS file + const cssContent = fs.readFileSync(mainCssEntry, "utf8"); + + // Process with PostCSS and postcss-import plugin + const result = await postcss([ + atImport({ + // This 'path' option tells postcss-import where to look for imported files. + // It's crucial for resolving relative paths like "@import './components/header.css';" + path: [path.join(__dirname, "src", "css")], + }), + ]).process(cssContent, { + from: mainCssEntry, // Tell PostCSS the original file path + to: outputPath, // Tell PostCSS the output file path (useful for source maps, etc.) + }); + + // Ensure the output directory exists + if (!fs.existsSync(outputDir)) { + fs.mkdirSync(outputDir, { recursive: true }); + } + + // Write the combined CSS to the output file + fs.writeFileSync(outputPath, result.css); + console.log(`\nSuccessfully combined all imported CSS into: ${outputPath}`); + } catch (error) { + console.error("Error combining CSS files:", error); + if (error.file) { + console.error(`Error in file: ${error.file}`); + console.error( + `At line ${error.line}, column ${error.column}: ${error.reason}` + ); + } + process.exit(1); // Exit with an error code + } +} + +combineCssImports(); diff --git a/content b/content index aa9d99d..03dbafe 160000 --- a/content +++ b/content @@ -1 +1 @@ -Subproject commit aa9d99d81c3ed28f99d279e4f36c2c9ab5118aab +Subproject commit 03dbafe2b60845c65f2107b394ce1a4154ae5a29 diff --git a/package-lock.json b/package-lock.json index 911c25d..afd209f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,9 @@ }, "devDependencies": { "@faker-js/faker": "^9.8.0", - "pm2": "^6.0.6" + "pm2": "^6.0.6", + "postcss": "^8.5.6", + "postcss-import": "^16.1.1" } }, "node_modules/@faker-js/faker": { @@ -2145,6 +2147,25 @@ "dev": true, "license": "ISC" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/needle": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", @@ -2441,6 +2462,13 @@ "node": ">=16" } }, + "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": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -2466,6 +2494,16 @@ "node": ">=10" } }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pm2": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/pm2/-/pm2-6.0.6.tgz", @@ -2632,6 +2670,60 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "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.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.1.tgz", + "integrity": "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -2764,6 +2856,16 @@ "node": ">=0.8" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3124,6 +3226,16 @@ "node": ">=0.10.0" } }, + "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/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", diff --git a/package.json b/package.json index 03dc6a5..e3a763a 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,12 @@ "description": "", "main": "index.js", "scripts": { + "combine:css": "node combine-css.js", "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon ./src/app.js --trace-exit", "maildev": "maildev", "dev": "./node_modules/pm2/bin/pm2 start --env development --watch", + "prod-test": "NODE_ENV=production nodemon ./src/app.js --trace-exit", "prod": "./node_modules/pm2/bin/pm2 start --env production", "stop": "node_modules/pm2/bin/pm2 delete expressjs-blog" }, @@ -34,6 +36,8 @@ }, "devDependencies": { "@faker-js/faker": "^9.8.0", - "pm2": "^6.0.6" + "pm2": "^6.0.6", + "postcss": "^8.5.6", + "postcss-import": "^16.1.1" } } diff --git a/public/css/base.css b/public/css/base.css deleted file mode 100644 index c892667..0000000 --- a/public/css/base.css +++ /dev/null @@ -1,16 +0,0 @@ -/* Base styles */ -html, body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background-color: #f8f9fa; - color: #212529; - min-height: 100vh; - line-height: 1.6; -} - -/* Container utility */ -.container { - max-width: 800px; - margin: 0 auto; - padding: 1rem 1.5rem; -} - diff --git a/public/css/header.css b/public/css/header.css deleted file mode 100644 index 0186ce4..0000000 --- a/public/css/header.css +++ /dev/null @@ -1,64 +0,0 @@ -/* Header */ -header { - background-color: #2c3e50; - color: #ecf0f1; - box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); -} - -header .container { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 0.5rem; - padding: 1rem 0; -} - -header .logo { - font-size: 1.8rem; - font-weight: bolder; - position: relative; -} - -header .logo a { - color: inherit; - text-decoration: none; - position: relative; - display: inline-block; -} - -header .logo a::after { - content: ""; - position: absolute; - left: 0; - bottom: -2px; - width: 100%; - height: 2px; - background-color: #ecf0f1; - transform: scaleX(0); - transform-origin: left; - transition: transform 0.3s ease; -} - -header .logo a:hover::after { - transform: scaleX(1); -} - -header nav { - display: flex; - gap: 1rem; -} - -header nav a { - color: #ecf0f1; - text-decoration: none; - font-weight: 600; - padding: 0.3rem 0.6rem; - border-radius: 3px; - transition: background-color 0.2s ease-in-out; -} - -header nav a:hover, -header nav a:focus { - background-color: #34495e; -} - diff --git a/public/css/layout.css b/public/css/layout.css deleted file mode 100644 index 50873d3..0000000 --- a/public/css/layout.css +++ /dev/null @@ -1,16 +0,0 @@ -/* Layout */ -.layout { - display: flex; - max-width: 1200px; - margin: 2rem auto; - padding: 0 1.5rem; - gap: 2rem; -} - -/* Responsive layout */ -@media (max-width: 900px) { - .layout { - flex-direction: column; - } -} - diff --git a/public/css/main.css b/public/css/main.css deleted file mode 100644 index e3e94a6..0000000 --- a/public/css/main.css +++ /dev/null @@ -1,42 +0,0 @@ -/* Main content */ -main.container { - flex: 1; - background-color: #ffffff; - padding: 2rem; - border-radius: 6px; - box-shadow: 0 2px 6px rgba(0,0,0,0.05); -} - -main h1 { - font-size: 1.5rem; - font-weight: 600; - margin-bottom: 1rem; - border-bottom: 2px solid #2c3e50; - padding-bottom: 0.3rem; -} - -main article p { - font-size: 1rem; - color: #444; -} - -/* Content area */ -main { - margin-left: 200px; - padding: 1rem; -} - -/* Responsive main */ -@media (max-width: 900px) { - main.container { - margin: 0; - } -} - -@media (max-width: 600px) { - main { - margin-left: 0; - padding: 1rem 0.5rem; - } -} - diff --git a/public/css/menu.css b/public/css/menu.css deleted file mode 100644 index 781d695..0000000 --- a/public/css/menu.css +++ /dev/null @@ -1,46 +0,0 @@ -.menu-years, -.menu-months, -.menu-posts { - list-style: none; - padding-left: 0; - margin: 0; -} - -.menu-year { - margin-bottom: 1em; -} - -.year-label { - font-weight: bold; - font-size: 1.2em; - display: block; - margin-bottom: 0.5em; -} - -.menu-month { - margin-left: 1em; - margin-bottom: 0.5em; -} - -.month-label { - font-weight: normal; - display: block; - margin-bottom: 0.3em; -} - -.menu-posts { - margin-left: 1em; -} - -.menu-posts li { - margin-bottom: 0.3em; -} - -.menu-posts a { - text-decoration: none; - color: #007bff; -} - -.menu-posts a:hover { - text-decoration: underline; -} diff --git a/public/css/nav.css b/public/css/nav.css deleted file mode 100644 index 0120b73..0000000 --- a/public/css/nav.css +++ /dev/null @@ -1,34 +0,0 @@ -/* Navigation adjustments for small screens */ -@media (max-width: 600px) { - nav { - width: 100%; - height: auto; - float: none; - padding: 0.5rem; - text-align: center; - border-bottom: 1px solid #444; - background: #111; - } - nav ul { - display: block; - justify-content: center; - flex-wrap: wrap; - gap: 1rem; - padding: 0.5rem 0; - margin: 0; - list-style: none; - } - nav ul li { - margin: 0.25rem 0; - border: none; - } - nav ul li a { - padding: 0.5rem 1rem; - display: block; - border-radius: 0; - } - nav a { - text-transform: capitalize; - } -} - diff --git a/public/css/reset.css b/public/css/reset.css deleted file mode 100644 index 491e288..0000000 --- a/public/css/reset.css +++ /dev/null @@ -1,7 +0,0 @@ -/* Reset */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - diff --git a/public/css/responsive.css b/public/css/responsive.css deleted file mode 100644 index e69de29..0000000 --- a/public/css/responsive.css +++ /dev/null diff --git a/public/css/sidebar.css b/public/css/sidebar.css deleted file mode 100644 index 835befa..0000000 --- a/public/css/sidebar.css +++ /dev/null @@ -1,66 +0,0 @@ -/* Sidebar */ -.sidebar { - width: 250px; - background-color: #f8f9fa; - border-right: 1px solid #e0e0e0; - padding: 1em; - font-size: 0.95rem; - font-family: Arial, sans-serif; - box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); -} -.sidebar h3.menu-year, -.sidebar h4, -.sidebar a { - text-transform: capitalize !important; -} -.sidebar .menu-year { - font-weight: 700; - font-size: 1.2rem; - color: #333333; - margin-bottom: 1rem; - border-bottom: 2px solid #2c3e50; - padding-bottom: 0.5rem; - text-transform: uppercase; - letter-spacing: 1px; -} - -.sidebar nav ul { - list-style: none; - padding-left: 0; -} - -.sidebar .menu-month { - margin-bottom: 1rem; - font-weight: 600; - color: #555555; -} - -.sidebar .menu-month ul { - list-style: none; - padding-left: 1rem; - margin-top: 0.3rem; -} - -.sidebar .menu-month li a { - color: #2c3e50; - text-decoration: none; - display: block; - padding: 0.2rem 0; - transition: color 0.2s ease-in-out; -} - -.sidebar .menu-month li a:hover { - color: #1a73e8; - text-decoration: underline; -} - -/* Responsive sidebar */ -@media (max-width: 900px) { - .sidebar { - width: 100%; - border-right: none; - border-bottom: 1px solid #e0e0e0; - margin-bottom: 1rem; - } -} - diff --git a/public/css/styles.css b/public/css/styles.css index e243488..7ad3a39 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -1,12 +1,505 @@ -@import url('reset.css'); -@import url('base.css'); -@import url('layout.css'); -@import url('header.css'); -@import url('sidebar.css'); -@import url('toc.css'); -@import url('main.css'); -@import url('footer.css'); -@import url('responsive.css'); -@import url('menu.css'); -@import url('nav.css'); +/* Reset */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} +/* Base styles */ +html, body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f8f9fa; + color: #212529; + min-height: 100vh; + line-height: 1.6; +} +/* Container utility */ +.container { + max-width: 800px; + margin: 0 auto; + padding: 1rem 1.5rem; +} +/* Layout */ +.layout { + display: flex; + max-width: 1200px; + margin: 2rem auto; + padding: 0 1.5rem; + gap: 2rem; +} +/* Responsive layout */ +@media (max-width: 900px) { + .layout { + flex-direction: column; + } +} +/* Header */ +header { + background-color: #2c3e50; + color: #ecf0f1; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} +header .container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + padding: 1rem 0; +} +header .logo { + font-size: 1.8rem; + font-weight: bolder; + position: relative; +} +header .logo a { + color: inherit; + text-decoration: none; + position: relative; + display: inline-block; +} +header .logo a::after { + content: ""; + position: absolute; + left: 0; + bottom: -2px; + width: 100%; + height: 2px; + background-color: #ecf0f1; + transform: scaleX(0); + transform-origin: left; + transition: transform 0.3s ease; +} +header .logo a:hover::after { + transform: scaleX(1); +} +header nav { + display: flex; + gap: 1rem; +} +header nav a { + color: #ecf0f1; + text-decoration: none; + font-weight: 600; + padding: 0.3rem 0.6rem; + border-radius: 3px; + transition: background-color 0.2s ease-in-out; +} +header nav a:hover, +header nav a:focus { + background-color: #34495e; +} +/* Sidebar */ +.sidebar { + width: 250px; + background-color: #f8f9fa; + border-right: 1px solid #e0e0e0; + padding: 1em; + font-size: 0.95rem; + font-family: Arial, sans-serif; + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); +} +.sidebar h3.menu-year, +.sidebar h4, +.sidebar a { + text-transform: capitalize !important; +} +.sidebar .menu-year { + font-weight: 700; + font-size: 1.2rem; + color: #333333; + margin-bottom: 1rem; + border-bottom: 2px solid #2c3e50; + padding-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 1px; +} +.sidebar nav ul { + list-style: none; + padding-left: 0; +} +.sidebar .menu-month { + margin-bottom: 1rem; + font-weight: 600; + color: #555555; +} +.sidebar .menu-month ul { + list-style: none; + padding-left: 1rem; + margin-top: 0.3rem; +} +.sidebar .menu-month li a { + color: #2c3e50; + text-decoration: none; + display: block; + padding: 0.2rem 0; + transition: color 0.2s ease-in-out; +} +.sidebar .menu-month li a:hover { + color: #1a73e8; + text-decoration: underline; +} +/* Responsive sidebar */ +@media (max-width: 900px) { + .sidebar { + width: 100%; + border-right: none; + border-bottom: 1px solid #e0e0e0; + margin-bottom: 1rem; + } +} +/* Enhanced TOC Styles */ +.toc { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + box-sizing: border-box; + margin-bottom: 1em; + padding: 1.5em; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} +.toc.sticky { + position: fixed; + top: 20px; + z-index: 1000; + max-height: calc(100vh - 40px); + overflow-y: auto; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + border-color: #dee2e6; +} +.toc.sticky.bottom-boundary { + position: fixed; + max-height: none; + overflow-y: auto; +} +/* Custom Scrollbar - Subtle and Non-intrusive */ +.toc::-webkit-scrollbar { + width: 6px; +} +.toc::-webkit-scrollbar-track { + background: transparent; + border-radius: 3px; +} +.toc::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; + transition: background 0.3s ease; +} +.toc::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.4); +} +/* Firefox Scrollbar */ +.toc { + scrollbar-width: thin; + scrollbar-color: rgba(0, 0, 0, 0.2) transparent; +} +/* TOC Content Styles */ +.toc ul { + list-style: none; + padding: 0; + margin: 0; +} +.toc li { + margin: 0.5rem 0; + position: relative; +} +.toc li.h3 { + margin-left: 1rem; + font-size: 0.9rem; +} +.toc a { + display: block; + padding: 0.5rem 0.75rem; + text-decoration: none; + color: #495057; + border-radius: 4px; + transition: all 0.2s ease; + border-left: 3px solid transparent; + word-wrap: break-word; + line-height: 1.4; +} +.toc a:hover { + background-color: #e9ecef; + color: #2c3e50; + border-left-color: #007bff; + transform: translateX(2px); +} +.toc a:focus { + outline: 2px solid #007bff; + outline-offset: 2px; +} +/* Active link highlighting */ +.toc a.active { + background-color: #e3f2fd; + color: #1976d2; + border-left-color: #1976d2; + font-weight: 600; +} +.toc-header { + font-weight: 700; + font-size: 1.1rem; + margin-bottom: 1rem; + color: #2c3e50; + border-bottom: 2px solid #dee2e6; + padding-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} +/* Responsive adjustments */ +@media (max-width: 900px) { + .toc.sticky { + position: relative; + top: auto; + left: auto; + width: auto !important; + max-height: 300px; + z-index: auto; + } +} +@media (max-width: 600px) { + .toc { + padding: 1rem; + margin-bottom: 1.5rem; + } + + .toc-header { + font-size: 1rem; + } + + .toc a { + padding: 0.4rem 0.5rem; + font-size: 0.9rem; + } + + .toc li.h3 { + margin-left: 0.5rem; + } +} +/* Sidebar adjustments for better TOC integration */ +.sidebar { + width: 250px; + background-color: #f8f9fa; + border-right: 1px solid #e0e0e0; + padding: 1em; + font-size: 0.95rem; + font-family: Arial, sans-serif; + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); + position: relative; /* Ensure proper positioning context */ +} +/* Ensure proper spacing between sidebar sections */ +.sidebar nav > * + .toc { + margin-top: 2rem; + border-top: 1px solid #dee2e6; + padding-top: 1.5rem; +} +/* Animation for smooth transitions */ +.toc { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} +.toc.sticky { + animation: slideInSticky 0.3s ease-out; +} +@keyframes slideInSticky { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} +/* Main content */ +main.container { + flex: 1; + background-color: #ffffff; + padding: 2rem; + border-radius: 6px; + box-shadow: 0 2px 6px rgba(0,0,0,0.05); +} +main h1 { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 1rem; + border-bottom: 2px solid #2c3e50; + padding-bottom: 0.3rem; +} +main article p { + font-size: 1rem; + color: #444; +} +/* Content area */ +main { + margin-left: 200px; + padding: 1rem; +} +/* Responsive main */ +@media (max-width: 900px) { + main.container { + margin: 0; + } +} +@media (max-width: 600px) { + main { + margin-left: 0; + padding: 1rem 0.5rem; + } +} +footer { + background-color: #34495e; + color: #ecf0f1; + padding: 2rem 0; + text-align: center; + border-top: none; + box-shadow: 0 -2px 5px rgba(0,0,0,0.1); + font-size: 0.9rem; +} +footer .container { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} +footer p { + font-size: 0.85rem; + color: #bdc3c7; + line-height: 1.5; + margin: 0; + max-width: 600px; +} +footer p a { + color: #9bd3ff; + text-decoration: none; + transition: color 0.2s ease-in-out; +} +footer p a:hover { + color: #ecf0f1; + text-decoration: underline; +} +footer img { + vertical-align: middle; + margin-left: 0.5rem; + opacity: 0.8; + transition: opacity 0.2s ease-in-out; +} +footer img:hover { + opacity: 1; +} +footer nav { + margin-top: 0; + margin-bottom: 0; + display: flex; + gap: 1.5rem; /* Adjust this value to control spacing between links */ + /* If you want them centered within the nav */ + justify-content: center; + /* Allow items to wrap to the next line on smaller screens */ + flex-wrap: wrap; +} +footer nav p { + margin: 0; + +} +footer .social-contact { + display: flex; + gap: 1rem; + flex-wrap: wrap; + justify-content: center; + margin-top: 1rem; +} +footer .social-contact p { + margin: 0; + font-size: 1rem; + font-weight: 600; +} +footer .social-contact p img, +footer .social-contact p svg { + vertical-align: middle; + margin: 0 0.25rem; + width: 24px; + height: 24px; + opacity: 0.9; + transition: opacity 0.2s ease-in-out; +} +footer .social-contact p img:hover, +footer .social-contact p svg:hover { + opacity: 1; +} +@media (max-width: 600px) { + footer .container { + padding: 1.5rem; + } + footer p { + font-size: 0.8rem; + } +} +.menu-years, +.menu-months, +.menu-posts { + list-style: none; + padding-left: 0; + margin: 0; +} +.menu-year { + margin-bottom: 1em; +} +.year-label { + font-weight: bold; + font-size: 1.2em; + display: block; + margin-bottom: 0.5em; +} +.menu-month { + margin-left: 1em; + margin-bottom: 0.5em; +} +.month-label { + font-weight: normal; + display: block; + margin-bottom: 0.3em; +} +.menu-posts { + margin-left: 1em; +} +.menu-posts li { + margin-bottom: 0.3em; +} +.menu-posts a { + text-decoration: none; + color: #007bff; +} +.menu-posts a:hover { + text-decoration: underline; +} +/* Navigation adjustments for small screens */ +@media (max-width: 600px) { + nav { + width: 100%; + height: auto; + float: none; + padding: 0.5rem; + text-align: center; + border-bottom: 1px solid #444; + background: #111; + } + nav ul { + display: block; + justify-content: center; + flex-wrap: wrap; + gap: 1rem; + padding: 0.5rem 0; + margin: 0; + list-style: none; + } + nav ul li { + margin: 0.25rem 0; + border: none; + } + nav ul li a { + padding: 0.5rem 1rem; + display: block; + border-radius: 0; + } + nav a { + text-transform: capitalize; + } +} diff --git a/public/css/toc.css b/public/css/toc.css deleted file mode 100644 index 9c2cbd3..0000000 --- a/public/css/toc.css +++ /dev/null @@ -1,184 +0,0 @@ -/* Enhanced TOC Styles */ -.toc { - background: #f8f9fa; - border: 1px solid #e9ecef; - border-radius: 8px; - box-sizing: border-box; - margin-bottom: 1em; - padding: 1.5em; - transition: all 0.3s ease; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); -} - -.toc.sticky { - position: fixed; - top: 20px; - z-index: 1000; - max-height: calc(100vh - 40px); - overflow-y: auto; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); - border-color: #dee2e6; -} - -.toc.sticky.bottom-boundary { - position: fixed; - max-height: none; - overflow-y: auto; -} - -/* Custom Scrollbar - Subtle and Non-intrusive */ -.toc::-webkit-scrollbar { - width: 6px; -} - -.toc::-webkit-scrollbar-track { - background: transparent; - border-radius: 3px; -} - -.toc::-webkit-scrollbar-thumb { - background: rgba(0, 0, 0, 0.2); - border-radius: 3px; - transition: background 0.3s ease; -} - -.toc::-webkit-scrollbar-thumb:hover { - background: rgba(0, 0, 0, 0.4); -} - -/* Firefox Scrollbar */ -.toc { - scrollbar-width: thin; - scrollbar-color: rgba(0, 0, 0, 0.2) transparent; -} - -/* TOC Content Styles */ -.toc ul { - list-style: none; - padding: 0; - margin: 0; -} - -.toc li { - margin: 0.5rem 0; - position: relative; -} - -.toc li.h3 { - margin-left: 1rem; - font-size: 0.9rem; -} - -.toc a { - display: block; - padding: 0.5rem 0.75rem; - text-decoration: none; - color: #495057; - border-radius: 4px; - transition: all 0.2s ease; - border-left: 3px solid transparent; - word-wrap: break-word; - line-height: 1.4; -} - -.toc a:hover { - background-color: #e9ecef; - color: #2c3e50; - border-left-color: #007bff; - transform: translateX(2px); -} - -.toc a:focus { - outline: 2px solid #007bff; - outline-offset: 2px; -} - -/* Active link highlighting */ -.toc a.active { - background-color: #e3f2fd; - color: #1976d2; - border-left-color: #1976d2; - font-weight: 600; -} - -.toc-header { - font-weight: 700; - font-size: 1.1rem; - margin-bottom: 1rem; - color: #2c3e50; - border-bottom: 2px solid #dee2e6; - padding-bottom: 0.5rem; - text-transform: uppercase; - letter-spacing: 0.5px; -} - -/* Responsive adjustments */ -@media (max-width: 900px) { - .toc.sticky { - position: relative; - top: auto; - left: auto; - width: auto !important; - max-height: 300px; - z-index: auto; - } -} - -@media (max-width: 600px) { - .toc { - padding: 1rem; - margin-bottom: 1.5rem; - } - - .toc-header { - font-size: 1rem; - } - - .toc a { - padding: 0.4rem 0.5rem; - font-size: 0.9rem; - } - - .toc li.h3 { - margin-left: 0.5rem; - } -} - -/* Sidebar adjustments for better TOC integration */ -.sidebar { - width: 250px; - background-color: #f8f9fa; - border-right: 1px solid #e0e0e0; - padding: 1em; - font-size: 0.95rem; - font-family: Arial, sans-serif; - box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); - position: relative; /* Ensure proper positioning context */ -} - -/* Ensure proper spacing between sidebar sections */ -.sidebar nav > * + .toc { - margin-top: 2rem; - border-top: 1px solid #dee2e6; - padding-top: 1.5rem; -} - -/* Animation for smooth transitions */ -.toc { - transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); -} - -.toc.sticky { - animation: slideInSticky 0.3s ease-out; -} - -@keyframes slideInSticky { - from { - opacity: 0; - transform: translateY(-10px); - } - to { - opacity: 1; - transform: translateY(0); - } -} diff --git a/src/css/base.css b/src/css/base.css new file mode 100644 index 0000000..c892667 --- /dev/null +++ b/src/css/base.css @@ -0,0 +1,16 @@ +/* Base styles */ +html, body { + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background-color: #f8f9fa; + color: #212529; + min-height: 100vh; + line-height: 1.6; +} + +/* Container utility */ +.container { + max-width: 800px; + margin: 0 auto; + padding: 1rem 1.5rem; +} + diff --git a/src/css/header.css b/src/css/header.css new file mode 100644 index 0000000..0186ce4 --- /dev/null +++ b/src/css/header.css @@ -0,0 +1,64 @@ +/* Header */ +header { + background-color: #2c3e50; + color: #ecf0f1; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); +} + +header .container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + padding: 1rem 0; +} + +header .logo { + font-size: 1.8rem; + font-weight: bolder; + position: relative; +} + +header .logo a { + color: inherit; + text-decoration: none; + position: relative; + display: inline-block; +} + +header .logo a::after { + content: ""; + position: absolute; + left: 0; + bottom: -2px; + width: 100%; + height: 2px; + background-color: #ecf0f1; + transform: scaleX(0); + transform-origin: left; + transition: transform 0.3s ease; +} + +header .logo a:hover::after { + transform: scaleX(1); +} + +header nav { + display: flex; + gap: 1rem; +} + +header nav a { + color: #ecf0f1; + text-decoration: none; + font-weight: 600; + padding: 0.3rem 0.6rem; + border-radius: 3px; + transition: background-color 0.2s ease-in-out; +} + +header nav a:hover, +header nav a:focus { + background-color: #34495e; +} + diff --git a/src/css/layout.css b/src/css/layout.css new file mode 100644 index 0000000..50873d3 --- /dev/null +++ b/src/css/layout.css @@ -0,0 +1,16 @@ +/* Layout */ +.layout { + display: flex; + max-width: 1200px; + margin: 2rem auto; + padding: 0 1.5rem; + gap: 2rem; +} + +/* Responsive layout */ +@media (max-width: 900px) { + .layout { + flex-direction: column; + } +} + diff --git a/src/css/main.css b/src/css/main.css new file mode 100644 index 0000000..e3e94a6 --- /dev/null +++ b/src/css/main.css @@ -0,0 +1,42 @@ +/* Main content */ +main.container { + flex: 1; + background-color: #ffffff; + padding: 2rem; + border-radius: 6px; + box-shadow: 0 2px 6px rgba(0,0,0,0.05); +} + +main h1 { + font-size: 1.5rem; + font-weight: 600; + margin-bottom: 1rem; + border-bottom: 2px solid #2c3e50; + padding-bottom: 0.3rem; +} + +main article p { + font-size: 1rem; + color: #444; +} + +/* Content area */ +main { + margin-left: 200px; + padding: 1rem; +} + +/* Responsive main */ +@media (max-width: 900px) { + main.container { + margin: 0; + } +} + +@media (max-width: 600px) { + main { + margin-left: 0; + padding: 1rem 0.5rem; + } +} + diff --git a/src/css/menu.css b/src/css/menu.css new file mode 100644 index 0000000..781d695 --- /dev/null +++ b/src/css/menu.css @@ -0,0 +1,46 @@ +.menu-years, +.menu-months, +.menu-posts { + list-style: none; + padding-left: 0; + margin: 0; +} + +.menu-year { + margin-bottom: 1em; +} + +.year-label { + font-weight: bold; + font-size: 1.2em; + display: block; + margin-bottom: 0.5em; +} + +.menu-month { + margin-left: 1em; + margin-bottom: 0.5em; +} + +.month-label { + font-weight: normal; + display: block; + margin-bottom: 0.3em; +} + +.menu-posts { + margin-left: 1em; +} + +.menu-posts li { + margin-bottom: 0.3em; +} + +.menu-posts a { + text-decoration: none; + color: #007bff; +} + +.menu-posts a:hover { + text-decoration: underline; +} diff --git a/src/css/nav.css b/src/css/nav.css new file mode 100644 index 0000000..0120b73 --- /dev/null +++ b/src/css/nav.css @@ -0,0 +1,34 @@ +/* Navigation adjustments for small screens */ +@media (max-width: 600px) { + nav { + width: 100%; + height: auto; + float: none; + padding: 0.5rem; + text-align: center; + border-bottom: 1px solid #444; + background: #111; + } + nav ul { + display: block; + justify-content: center; + flex-wrap: wrap; + gap: 1rem; + padding: 0.5rem 0; + margin: 0; + list-style: none; + } + nav ul li { + margin: 0.25rem 0; + border: none; + } + nav ul li a { + padding: 0.5rem 1rem; + display: block; + border-radius: 0; + } + nav a { + text-transform: capitalize; + } +} + diff --git a/src/css/reset.css b/src/css/reset.css new file mode 100644 index 0000000..491e288 --- /dev/null +++ b/src/css/reset.css @@ -0,0 +1,7 @@ +/* Reset */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + diff --git a/src/css/responsive.css b/src/css/responsive.css new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/css/responsive.css diff --git a/src/css/sidebar.css b/src/css/sidebar.css new file mode 100644 index 0000000..835befa --- /dev/null +++ b/src/css/sidebar.css @@ -0,0 +1,66 @@ +/* Sidebar */ +.sidebar { + width: 250px; + background-color: #f8f9fa; + border-right: 1px solid #e0e0e0; + padding: 1em; + font-size: 0.95rem; + font-family: Arial, sans-serif; + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); +} +.sidebar h3.menu-year, +.sidebar h4, +.sidebar a { + text-transform: capitalize !important; +} +.sidebar .menu-year { + font-weight: 700; + font-size: 1.2rem; + color: #333333; + margin-bottom: 1rem; + border-bottom: 2px solid #2c3e50; + padding-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 1px; +} + +.sidebar nav ul { + list-style: none; + padding-left: 0; +} + +.sidebar .menu-month { + margin-bottom: 1rem; + font-weight: 600; + color: #555555; +} + +.sidebar .menu-month ul { + list-style: none; + padding-left: 1rem; + margin-top: 0.3rem; +} + +.sidebar .menu-month li a { + color: #2c3e50; + text-decoration: none; + display: block; + padding: 0.2rem 0; + transition: color 0.2s ease-in-out; +} + +.sidebar .menu-month li a:hover { + color: #1a73e8; + text-decoration: underline; +} + +/* Responsive sidebar */ +@media (max-width: 900px) { + .sidebar { + width: 100%; + border-right: none; + border-bottom: 1px solid #e0e0e0; + margin-bottom: 1rem; + } +} + diff --git a/src/css/styles.css b/src/css/styles.css new file mode 100644 index 0000000..3fb8646 --- /dev/null +++ b/src/css/styles.css @@ -0,0 +1,11 @@ +@import url('base.css'); +@import url('footer.css'); +@import url('header.css'); +@import url('layout.css'); +@import url('main.css'); +@import url('menu.css'); +@import url('nav.css'); +@import url('reset.css'); +@import url('responsive.css'); +@import url('sidebar.css'); +@import url('toc.css'); diff --git a/src/css/toc.css b/src/css/toc.css new file mode 100644 index 0000000..9c2cbd3 --- /dev/null +++ b/src/css/toc.css @@ -0,0 +1,184 @@ +/* Enhanced TOC Styles */ +.toc { + background: #f8f9fa; + border: 1px solid #e9ecef; + border-radius: 8px; + box-sizing: border-box; + margin-bottom: 1em; + padding: 1.5em; + transition: all 0.3s ease; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); +} + +.toc.sticky { + position: fixed; + top: 20px; + z-index: 1000; + max-height: calc(100vh - 40px); + overflow-y: auto; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + border-color: #dee2e6; +} + +.toc.sticky.bottom-boundary { + position: fixed; + max-height: none; + overflow-y: auto; +} + +/* Custom Scrollbar - Subtle and Non-intrusive */ +.toc::-webkit-scrollbar { + width: 6px; +} + +.toc::-webkit-scrollbar-track { + background: transparent; + border-radius: 3px; +} + +.toc::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; + transition: background 0.3s ease; +} + +.toc::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.4); +} + +/* Firefox Scrollbar */ +.toc { + scrollbar-width: thin; + scrollbar-color: rgba(0, 0, 0, 0.2) transparent; +} + +/* TOC Content Styles */ +.toc ul { + list-style: none; + padding: 0; + margin: 0; +} + +.toc li { + margin: 0.5rem 0; + position: relative; +} + +.toc li.h3 { + margin-left: 1rem; + font-size: 0.9rem; +} + +.toc a { + display: block; + padding: 0.5rem 0.75rem; + text-decoration: none; + color: #495057; + border-radius: 4px; + transition: all 0.2s ease; + border-left: 3px solid transparent; + word-wrap: break-word; + line-height: 1.4; +} + +.toc a:hover { + background-color: #e9ecef; + color: #2c3e50; + border-left-color: #007bff; + transform: translateX(2px); +} + +.toc a:focus { + outline: 2px solid #007bff; + outline-offset: 2px; +} + +/* Active link highlighting */ +.toc a.active { + background-color: #e3f2fd; + color: #1976d2; + border-left-color: #1976d2; + font-weight: 600; +} + +.toc-header { + font-weight: 700; + font-size: 1.1rem; + margin-bottom: 1rem; + color: #2c3e50; + border-bottom: 2px solid #dee2e6; + padding-bottom: 0.5rem; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +/* Responsive adjustments */ +@media (max-width: 900px) { + .toc.sticky { + position: relative; + top: auto; + left: auto; + width: auto !important; + max-height: 300px; + z-index: auto; + } +} + +@media (max-width: 600px) { + .toc { + padding: 1rem; + margin-bottom: 1.5rem; + } + + .toc-header { + font-size: 1rem; + } + + .toc a { + padding: 0.4rem 0.5rem; + font-size: 0.9rem; + } + + .toc li.h3 { + margin-left: 0.5rem; + } +} + +/* Sidebar adjustments for better TOC integration */ +.sidebar { + width: 250px; + background-color: #f8f9fa; + border-right: 1px solid #e0e0e0; + padding: 1em; + font-size: 0.95rem; + font-family: Arial, sans-serif; + box-shadow: 2px 0 5px rgba(0, 0, 0, 0.05); + position: relative; /* Ensure proper positioning context */ +} + +/* Ensure proper spacing between sidebar sections */ +.sidebar nav > * + .toc { + margin-top: 2rem; + border-top: 1px solid #dee2e6; + padding-top: 1.5rem; +} + +/* Animation for smooth transitions */ +.toc { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.toc.sticky { + animation: slideInSticky 0.3s ease-out; +} + +@keyframes slideInSticky { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/routes/index.js b/src/routes/index.js index e6eb621..db3eae6 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -4,25 +4,25 @@ const getBaseContext = require("../utils/baseContext"); -const newsletter = require("./newsletter"); +// const newsletter = require("./newsletter"); const contact = require("./contact"); const about = require("./about"); const site_map = require("./site-map"); const post = require("./post"); const construction = require("./construction"); -const isProduction = process.env.NODE_ENV === "production"; +// const isProduction = process.env.NODE_ENV === "production"; -if (isProduction) { - router.get("/newsletter", (req, res) => { - res.render("pages/construction", { - title: "Newsletter", - message: "This page is under construction. Please check back soon.", - }); - }); -} else { - router.use(newsletter); -} +// if (isProduction) { +// router.get("/newsletter", (req, res) => { +// res.render("pages/construction", { +// title: "Newsletter", +// message: "This page is under construction. Please check back soon.", +// }); +// }); +// } else { +// router.use(newsletter); +// } router.use(contact); router.use(about); diff --git a/src/routes/post.js b/src/routes/post.js index 2eb52e3..8e95b87 100644 --- a/src/routes/post.js +++ b/src/routes/post.js @@ -41,6 +41,9 @@ try { const fileContent = await fs.readFile(mdPath, "utf8"); const { data: frontmatter, content } = matter(fileContent); + if (!frontmatter.published && process.env.NODE_ENV === "production") { + throw new Error("Attempted to access an unpublished page in production"); + } const htmlContent = marked(content); const context = await getBaseContext({ title: frontmatter.title, diff --git a/src/services/postsMenuService.js b/src/services/postsMenuService.js new file mode 100644 index 0000000..f57418a --- /dev/null +++ b/src/services/postsMenuService.js @@ -0,0 +1,54 @@ +const matter = require("gray-matter"); + +const path = require("path"); +const fs = require("fs").promises; + +async function getPostsMenu(baseDir) { + const years = (await fs.readdir(baseDir, { withFileTypes: true })).filter( + (dirent) => dirent.isDirectory() && /^\d{4}$/.test(dirent.name) + ); + + const menu = []; + + for (const yearDir of years) { + const yearPath = path.join(baseDir, yearDir.name); + const months = await fs.readdir(yearPath, { withFileTypes: true }); + const monthsData = []; + + for (const monthDir of months.filter((d) => d.isDirectory())) { + const monthPath = path.join(yearPath, monthDir.name); + const files = await fs.readdir(monthPath); + + const posts = await Promise.all( + files + .filter((f) => f.endsWith(".md")) + .map(async (f) => { + const slug = f.replace(/\.md$/, ""); + const filePath = path.join(monthPath, f); + const fileContent = await fs.readFile(filePath, "utf8"); + const { data } = matter(fileContent); + + if (!data.published && process.env.NODE_ENV === "production") { + return null; + } + + return { + slug, + title: data.title || slug.replace(/-/g, " "), + date: data.date || null, + year: yearDir.name, + month: monthDir.name, + }; + }) + ); + + monthsData.push({ month: monthDir.name, posts: posts.filter(Boolean) }); + } + + menu.push({ year: yearDir.name, months: monthsData }); + } + + return menu; +} + +module.exports = getPostsMenu; diff --git a/src/services/postsService.js b/src/services/postsService.js deleted file mode 100644 index a314938..0000000 --- a/src/services/postsService.js +++ /dev/null @@ -1,50 +0,0 @@ -const matter = require("gray-matter"); - -const path = require("path"); -const fs = require("fs").promises; - -async function getPostsMenu(baseDir) { - const years = (await fs.readdir(baseDir, { withFileTypes: true })).filter( - (dirent) => dirent.isDirectory() && /^\d{4}$/.test(dirent.name) - ); - - const menu = []; - - for (const yearDir of years) { - const yearPath = path.join(baseDir, yearDir.name); - const months = await fs.readdir(yearPath, { withFileTypes: true }); - const monthsData = []; - - for (const monthDir of months.filter((d) => d.isDirectory())) { - const monthPath = path.join(yearPath, monthDir.name); - const files = await fs.readdir(monthPath); - - const posts = await Promise.all( - files - .filter((f) => f.endsWith(".md")) - .map(async (f) => { - const slug = f.replace(/\.md$/, ""); - const filePath = path.join(monthPath, f); - const fileContent = await fs.readFile(filePath, "utf8"); - const { data } = matter(fileContent); - - return { - slug, - title: data.title || slug.replace(/-/g, " "), - date: data.date || null, - year: yearDir.name, - month: monthDir.name, - }; - }) - ); - - monthsData.push({ month: monthDir.name, posts }); - } - - menu.push({ year: yearDir.name, months: monthsData }); - } - - return menu; -} - -module.exports = getPostsMenu; diff --git a/src/utils/baseContext.js b/src/utils/baseContext.js index 4daef11..b98ff46 100644 --- a/src/utils/baseContext.js +++ b/src/utils/baseContext.js @@ -1,6 +1,6 @@ // src/utils/baseContext.js const path = require("path"); -const getPostsMenu = require("../services/postsService"); +const getPostsMenu = require("../services/postsMenuService"); const { formatMonth } = require("../utils/formatMonth"); async function getBaseContext(overrides = {}) { diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..26bba14 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,29 @@ +// webpack.config.js +const path = require("path"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); + +module.exports = { + mode: "production", // or 'development' + entry: "./public/css/style.css", // Your main CSS file that imports others + output: { + path: path.resolve(__dirname, "dist"), + filename: "bundle.js", // Although this is for JS, Webpack needs an output filename + }, + module: { + rules: [ + { + test: /\.css$/, + use: [ + MiniCssExtractPlugin.loader, // Extracts CSS into a file + "css-loader", // Interprets @import + "postcss-loader", // For autoprefixing, etc. (optional) + ], + }, + ], + }, + plugins: [ + new MiniCssExtractPlugin({ + filename: "bundle.css", // The name of your output CSS file + }), + ], +}; diff --git a/yarn.lock b/yarn.lock index 6960a3a..6ecd96b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1212,6 +1212,11 @@ resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== +nanoid@^3.3.11: + version "3.3.11" + resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz" + integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w== + needle@2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz" @@ -1381,6 +1386,11 @@ process "^0.11.1" util "^0.10.3" +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + picomatch@^2.0.4, picomatch@^2.2.1: version "2.3.1" resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" @@ -1400,6 +1410,11 @@ dependencies: safe-buffer "^5.2.1" +pify@^2.3.0: + version "2.3.0" + resolved "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz" + integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== + pm2-axon-rpc@~0.7.0, pm2-axon-rpc@~0.7.1: version "0.7.1" resolved "https://registry.npmjs.org/pm2-axon-rpc/-/pm2-axon-rpc-0.7.1.tgz" @@ -1480,6 +1495,29 @@ optionalDependencies: pm2-sysmonit "^1.2.8" +postcss-import@^16.1.1: + version "16.1.1" + resolved "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.1.tgz" + integrity sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ== + dependencies: + postcss-value-parser "^4.0.0" + read-cache "^1.0.0" + resolve "^1.1.7" + +postcss-value-parser@^4.0.0: + version "4.2.0" + resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss@^8.0.0, postcss@^8.5.6: + version "8.5.6" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz" + integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== + dependencies: + nanoid "^3.3.11" + picocolors "^1.1.1" + source-map-js "^1.2.1" + process@^0.11.1: version "0.11.10" resolved "https://registry.npmjs.org/process/-/process-0.11.10.tgz" @@ -1551,6 +1589,13 @@ iconv-lite "0.6.3" unpipe "1.0.0" +read-cache@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz" + integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== + dependencies: + pify "^2.3.0" + read@^1.0.4: version "1.0.7" resolved "https://registry.npmjs.org/read/-/read-1.0.7.tgz" @@ -1574,7 +1619,7 @@ module-details-from-path "^1.0.3" resolve "^1.22.1" -resolve@^1.22.1: +resolve@^1.1.7, resolve@^1.22.1: version "1.22.10" resolved "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz" integrity sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w== @@ -1774,6 +1819,11 @@ ip-address "^9.0.5" smart-buffer "^4.2.0" +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + source-map-support@0.5.21: version "0.5.21" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz"