diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml
index e389e6371..7f82a865f 100644
--- a/.stylelintrc.yaml
+++ b/.stylelintrc.yaml
@@ -1,5 +1,10 @@
extends: stylelint-config-standard
plugins:
- stylelint-no-unsupported-browser-features
+ - stylelint-scss
rules:
+ at-rule-no-unknown: null
+ function-no-unknown: null
+ import-notation: string
no-descending-specificity: ~
+ scss/at-rule-no-unknown: true
diff --git a/bin/test-eslint b/bin/test-eslint
index 084b4f876..28caf5518 100755
--- a/bin/test-eslint
+++ b/bin/test-eslint
@@ -6,4 +6,4 @@ cd "$(dirname "$0")/.."
echo 'Running ESLint...'
-./node_modules/.bin/eslint -c ./eslint.config.js ./betty ./playwright "$@"
+./node_modules/.bin/eslint -c ./eslint.config.js ./betty ./playwright ./raspberry-mint-dev "$@"
diff --git a/bin/test-stylelint b/bin/test-stylelint
index 0ffb9cf13..a3557fb9d 100755
--- a/bin/test-stylelint
+++ b/bin/test-stylelint
@@ -6,4 +6,4 @@ cd "$(dirname "$0")/.."
echo 'Running Stylelint...'
-./node_modules/.bin/stylelint "./betty/**/*.css"
+./node_modules/.bin/stylelint "./betty/**/*.css" "./raspberry-mint-dev/**/*.css" "./raspberry-mint-dev/**/*.scss"
diff --git a/package.json b/package.json
index 82ca19101..0b5090132 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"stylelint": "^16.10.0",
"stylelint-config-standard": "^36.0.1",
"stylelint-no-unsupported-browser-features": "^8.0.1",
+ "stylelint-scss": "^6.8.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.11.0"
},
diff --git a/raspberry-mint-dev/.gitignore b/raspberry-mint-dev/.gitignore
new file mode 100644
index 000000000..8987626e9
--- /dev/null
+++ b/raspberry-mint-dev/.gitignore
@@ -0,0 +1,5 @@
+/build
+/cache
+/node_modules
+/package-lock.json
+
diff --git a/raspberry-mint-dev/package.json b/raspberry-mint-dev/package.json
new file mode 100644
index 000000000..35dacdce1
--- /dev/null
+++ b/raspberry-mint-dev/package.json
@@ -0,0 +1,35 @@
+{
+ "engines": {
+ "node": ">= 20"
+ },
+ "dependencies": {
+ "@babel/core": "^7.26.0",
+ "@babel/preset-env": "^7.26.0",
+ "@babel/preset-typescript": "^7.26.0",
+ "@popperjs/core": "^2.11.8",
+ "babel-loader": "^9.2.1",
+ "bootstrap": "^5.3.3",
+ "clean-webpack-plugin": "^4.0.0",
+ "core-js": "^3.38.1",
+ "css-loader": "^7.1.2",
+ "css-minimizer-webpack-plugin": "^7.0.0",
+ "file-loader": "^6.2.0",
+ "mini-css-extract-plugin": "^2.9.1",
+ "postcss-loader": "^8.1.1",
+ "resolve-url-loader": "^5.0.0",
+ "sass": "^1.80.4",
+ "sass-loader": "^16.0.2",
+ "style-loader": "^4.0.0",
+ "terser-webpack-plugin": "^5.3.10",
+ "typescript": "^5.6.3",
+ "webpack": "^5.95.0",
+ "webpack-cli": "^5.1.4",
+ "webpack-dev-server": "^5.1.0",
+ "html-webpack-plugin": "^5.6.3"
+ },
+ "scripts": {
+ "webpack": "webpack",
+ "serve": "webpack serve --mode development --open"
+ },
+ "type": "module"
+}
diff --git a/raspberry-mint-dev/src/css/components/button.scss b/raspberry-mint-dev/src/css/components/button.scss
new file mode 100644
index 000000000..db7d618f3
--- /dev/null
+++ b/raspberry-mint-dev/src/css/components/button.scss
@@ -0,0 +1,7 @@
+.btn {
+ &:focus {
+ background-color: $focus;
+ outline: 2px $black solid;
+ color: $black;
+ }
+}
\ No newline at end of file
diff --git a/raspberry-mint-dev/src/css/components/card.scss b/raspberry-mint-dev/src/css/components/card.scss
new file mode 100644
index 000000000..e58666d00
--- /dev/null
+++ b/raspberry-mint-dev/src/css/components/card.scss
@@ -0,0 +1,5 @@
+.card {
+ background-color: $gray-100;
+ border:none;
+ box-shadow: 1px 1px $gray-300;
+}
diff --git a/raspberry-mint-dev/src/css/components/footer.scss b/raspberry-mint-dev/src/css/components/footer.scss
new file mode 100644
index 000000000..cac671ffc
--- /dev/null
+++ b/raspberry-mint-dev/src/css/components/footer.scss
@@ -0,0 +1,3 @@
+#footer {
+ background-color: $mint-900;
+}
diff --git a/raspberry-mint-dev/src/css/components/header.scss b/raspberry-mint-dev/src/css/components/header.scss
new file mode 100644
index 000000000..7fd1281c8
--- /dev/null
+++ b/raspberry-mint-dev/src/css/components/header.scss
@@ -0,0 +1,15 @@
+#header {
+ background-color: $mint-900;
+}
+
+.nav-brand-site-logo {
+ background-image: url("/betty-192x192.png");
+ background-position: center left;
+ background-repeat: no-repeat;
+ background-size: 3rem;
+ box-shadow: none;
+ display: inline-block;
+ line-height: 3rem;
+ min-height: 3rem;
+ padding-left: 4rem;
+}
diff --git a/raspberry-mint-dev/src/css/dev.scss b/raspberry-mint-dev/src/css/dev.scss
new file mode 100644
index 000000000..1677f02a4
--- /dev/null
+++ b/raspberry-mint-dev/src/css/dev.scss
@@ -0,0 +1,113 @@
+/* @todo Do not migrate these to the final product! */
+code {
+ color: #000 !important;
+ background-color: #fff;
+}
+
+.bg-raspberry-100 {
+ background-color: $raspberry-100 !important;
+}
+
+.bg-raspberry-200 {
+ background-color: $raspberry-200 !important;
+}
+
+.bg-raspberry-300 {
+ background-color: $raspberry-300 !important;
+}
+
+.bg-raspberry-400 {
+ background-color: $raspberry-400 !important;
+}
+
+.bg-raspberry-500 {
+ background-color: $raspberry-500 !important;
+}
+
+.bg-raspberry-600 {
+ background-color: $raspberry-600 !important;
+}
+
+.bg-raspberry-700 {
+ background-color: $raspberry-700 !important;
+}
+
+.bg-raspberry-800 {
+ background-color: $raspberry-800 !important;
+}
+
+.bg-raspberry-900 {
+ background-color: $raspberry-900 !important;
+}
+
+.bg-mint-100 {
+ background-color: $mint-100 !important;
+}
+
+.bg-mint-200 {
+ background-color: $mint-200 !important;
+}
+
+.bg-mint-300 {
+ background-color: $mint-300 !important;
+}
+
+.bg-mint-400 {
+ background-color: $mint-400 !important;
+}
+
+.bg-mint-500 {
+ background-color: $mint-500 !important;
+}
+
+.bg-mint-600 {
+ background-color: $mint-600 !important;
+}
+
+.bg-mint-700 {
+ background-color: $mint-700 !important;
+}
+
+.bg-mint-800 {
+ background-color: $mint-800 !important;
+}
+
+.bg-mint-900 {
+ background-color: $mint-900 !important;
+}
+
+.bg-orange-100 {
+ background-color: $orange-100 !important;
+}
+
+.bg-orange-200 {
+ background-color: $orange-200 !important;
+}
+
+.bg-orange-300 {
+ background-color: $orange-300 !important;
+}
+
+.bg-orange-400 {
+ background-color: $orange-400 !important;
+}
+
+.bg-orange-500 {
+ background-color: $orange-500 !important;
+}
+
+.bg-orange-600 {
+ background-color: $orange-600 !important;
+}
+
+.bg-orange-700 {
+ background-color: $orange-700 !important;
+}
+
+.bg-orange-800 {
+ background-color: $orange-800 !important;
+}
+
+.bg-orange-900 {
+ background-color: $orange-900 !important;
+}
\ No newline at end of file
diff --git a/raspberry-mint-dev/src/css/main.scss b/raspberry-mint-dev/src/css/main.scss
new file mode 100644
index 000000000..05ee5aca8
--- /dev/null
+++ b/raspberry-mint-dev/src/css/main.scss
@@ -0,0 +1,55 @@
+/* @todo Finetune the imports and remove unneeded comments */
+
+/* 1. Include functions first (so you can manipulate colors, SVGs, calc, etc) */
+@import "../../node_modules/bootstrap/scss/functions";
+
+/* 2. Include any default variable overrides here */
+@import "./variables/colors";
+@import "./variables/focus";
+@import "./variables/font";
+@import "./variables/border";
+
+/* 3. Include remainder of required Bootstrap stylesheets (including any separate color mode stylesheets) */
+@import "../../node_modules/bootstrap/scss/variables";
+@import "../../node_modules/bootstrap/scss/variables-dark";
+
+/* 4. Include any default map overrides here */
+$theme-colors: (
+ "primary": $raspberry,
+ "secondary": $mint,
+ "danger": $orange,
+);
+
+/* 5. Include remainder of required parts */
+@import "../../node_modules/bootstrap/scss/maps";
+@import "../../node_modules/bootstrap/scss/mixins";
+@import "../../node_modules/bootstrap/scss/root";
+
+/* 6. Optionally include any other parts as needed */
+@import "../../node_modules/bootstrap/scss/utilities";
+@import "../../node_modules/bootstrap/scss/reboot";
+@import "../../node_modules/bootstrap/scss/type";
+@import "../../node_modules/bootstrap/scss/images";
+@import "../../node_modules/bootstrap/scss/containers";
+@import "../../node_modules/bootstrap/scss/grid";
+@import "../../node_modules/bootstrap/scss/helpers";
+
+/* Components */
+@import "../../node_modules/bootstrap/scss/buttons";
+@import "../../node_modules/bootstrap/scss/card";
+@import "../../node_modules/bootstrap/scss/list-group";
+@import "../../node_modules/bootstrap/scss/nav";
+@import "../../node_modules/bootstrap/scss/navbar";
+
+/* 7. Optionally include utilities API last to generate classes based on the Sass map in `_utilities.scss` */
+@import "../../node_modules/bootstrap/scss/utilities/api";
+
+/* 8. Add additional custom code here */
+@import "./text.scss";
+@import "./components/button.scss";
+@import "./components/card.scss";
+@import "./components/header.scss";
+@import "./components/footer.scss";
+
+/* @todo Do not migrate this to the final product! */
+@import "./dev.scss";
diff --git a/raspberry-mint-dev/src/css/text.scss b/raspberry-mint-dev/src/css/text.scss
new file mode 100644
index 000000000..13e01f39d
--- /dev/null
+++ b/raspberry-mint-dev/src/css/text.scss
@@ -0,0 +1,17 @@
+a {
+ color: $mint-300;
+ box-shadow: 0 2px $mint-300;
+ text-decoration: none;
+
+ &:focus {
+ background-color: $focus;
+ box-shadow: 0 0 $focus, 0 2px $black;
+ color: $black;
+ text-decoration-color: $black;
+ outline: none;
+ }
+
+ &:hover {
+ color: $mint-100;
+ }
+}
diff --git a/raspberry-mint-dev/src/css/variables/border.scss b/raspberry-mint-dev/src/css/variables/border.scss
new file mode 100644
index 000000000..b1eb763fe
--- /dev/null
+++ b/raspberry-mint-dev/src/css/variables/border.scss
@@ -0,0 +1,6 @@
+$border-radius:0;
+$border-radius-sm:0;
+$border-radius-lg:0;
+$border-radius-xl:0;
+$border-radius-xxl:0;
+$border-radius-pill:0;
diff --git a/raspberry-mint-dev/src/css/variables/colors.scss b/raspberry-mint-dev/src/css/variables/colors.scss
new file mode 100644
index 000000000..e34c8184f
--- /dev/null
+++ b/raspberry-mint-dev/src/css/variables/colors.scss
@@ -0,0 +1,33 @@
+$mint: #3eb489;
+$raspberry: #b3446c;
+$orange: #ffbd22;
+
+$mint-100: mix(black, $mint, 80%);
+$mint-200: mix(black, $mint, 60%);
+$mint-300: mix(black, $mint, 40%);
+$mint-400: mix(black, $mint, 20%);
+$mint-500: $mint;
+$mint-600: mix(white, $mint, 20%);
+$mint-700: mix(white, $mint, 40%);
+$mint-800: mix(white, $mint, 60%);
+$mint-900: mix(white, $mint, 80%);
+
+$raspberry-100: mix(black, $raspberry, 80%);
+$raspberry-200: mix(black, $raspberry, 60%);
+$raspberry-300: mix(black, $raspberry, 40%);
+$raspberry-400: mix(black, $raspberry, 20%);
+$raspberry-500: $raspberry;
+$raspberry-600: mix(white, $raspberry, 20%);
+$raspberry-700: mix(white, $raspberry, 40%);
+$raspberry-800: mix(white, $raspberry, 60%);
+$raspberry-900: mix(white, $raspberry, 80%);
+
+$orange-100: mix(black, $orange, 80%);
+$orange-200: mix(black, $orange, 60%);
+$orange-300: mix(black, $orange, 40%);
+$orange-400: mix(black, $orange, 20%);
+$orange-500: $orange;
+$orange-600: mix(white, $orange, 20%);
+$orange-700: mix(white, $orange, 40%);
+$orange-800: mix(white, $orange, 60%);
+$orange-900: mix(white, $orange, 80%);
diff --git a/raspberry-mint-dev/src/css/variables/focus.scss b/raspberry-mint-dev/src/css/variables/focus.scss
new file mode 100644
index 000000000..43db1ed52
--- /dev/null
+++ b/raspberry-mint-dev/src/css/variables/focus.scss
@@ -0,0 +1,7 @@
+$focus: $orange-700;
+
+$focus-ring-width: 0;
+$focus-ring-opacity: 1;
+$focus-ring-color: red;
+$focus-ring-blur: 0;
+$focus-ring-box-shadow: 0;
\ No newline at end of file
diff --git a/raspberry-mint-dev/src/css/variables/font.scss b/raspberry-mint-dev/src/css/variables/font.scss
new file mode 100644
index 000000000..41b83b29e
--- /dev/null
+++ b/raspberry-mint-dev/src/css/variables/font.scss
@@ -0,0 +1,2 @@
+$font-family-base: "Noto Sans", system-ui, -apple-system, "Segoe UI", "Roboto", "Helvetica Neue", "Noto Sans", "Liberation Sans", "Arial", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+$font-size-base: 1.3rem;
diff --git a/raspberry-mint-dev/src/js/main.ts b/raspberry-mint-dev/src/js/main.ts
new file mode 100644
index 000000000..2ba3a0c40
--- /dev/null
+++ b/raspberry-mint-dev/src/js/main.ts
@@ -0,0 +1,3 @@
+import '../css/main.scss'
+import * as Popper from "@popperjs/core" // eslint-disable-line @typescript-eslint/no-unused-vars
+import "../../node_modules/bootstrap/js/dist/collapse"
diff --git a/raspberry-mint-dev/src/www/index.html b/raspberry-mint-dev/src/www/index.html
new file mode 100644
index 000000000..8697b084d
--- /dev/null
+++ b/raspberry-mint-dev/src/www/index.html
@@ -0,0 +1,221 @@
+
+
+
+ Raspberry Mint development environment
+
+
+
+
+
+
+
+
+
+
+
Welcome to Betty's new theme: Raspberry Mint!
+
+
+ Colors
+
+ $raspberry
+ $mint
+ $orange
+
+
+
+
+ Raspberry
+
+ $raspberry-100
+ $raspberry-200
+ $raspberry-300
+ $raspberry-400
+ $raspberry-500
+ $raspberry-600
+ $raspberry-700
+ $raspberry-800
+ $raspberry-900
+
+
+
+
+ Mint
+
+ $mint-100
+ $mint-200
+ $mint-300
+ $mint-400
+ $mint-500
+ $mint-600
+ $mint-700
+ $mint-800
+ $mint-900
+
+
+
+
+ Orange
+
+ $orange-100
+ $orange-200
+ $orange-300
+ $orange-400
+ $orange-500
+ $orange-600
+ $orange-700
+ $orange-800
+ $orange-900
+
+
+
+
+
+
+ Text
+
+
+ Paragraph
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas risus mi, aliquam eget bibendum id,
+ venenatis et leo. Suspendisse potenti. Orci varius natoque penatibus et magnis dis parturient montes,
+ nascetur ridiculus mus. Morbi ultricies dapibus mauris. Ut cursus pharetra nibh, ut venenatis tellus.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam erat volutpat. Vestibulum bibendum, ex
+ sed convallis egestas, erat leo venenatis eros, vel elementum mauris tortor a arcu. Aenean bibendum
+ malesuada elit. Pellentesque eleifend, mi sit amet molestie congue, augue metus volutpat lacus, eu
+ posuere mauris turpis sed turpis. Proin pulvinar finibus orci sit amet consectetur. Quisque scelerisque
+ tempus dui ac fermentum.
+ Donec ac nunc ligula. Sed sit amet metus in diam tincidunt condimentum. Phasellus sodales odio nec arcu
+ tempus cursus. Aenean fermentum purus eros, molestie tincidunt magna tincidunt sed. Proin sagittis ut ex
+ in luctus. Suspendisse potenti. Donec sed ullamcorper metus. Morbi sed quam eget tortor commodo
+ congue.
+ Nam quis est est. Ut pellentesque at ligula sit amet venenatis. Phasellus id consectetur tortor. Cras
+ odio erat, tristique dapibus dapibus in, convallis at orci. Sed magna tortor, ultrices nec posuere quis,
+ gravida vel lorem. Nulla tincidunt nec neque non bibendum. Aenean finibus erat sit amet pulvinar
+ posuere. Cras tellus erat, imperdiet nec dolor at, egestas laoreet ante. In eget orci quis ligula porta
+ placerat et at quam. Morbi fringilla condimentum magna, in ornare ligula aliquam quis.
+
+
+
+ Link
+ A link to someplace else.
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce vulputate magna sem, eget
+ rutrum eros consequat id. Aenean eu mauris condimentum, fermentum leo at, mollis diam. Pellentesque
+ hendrerit ornare sagittis. Cras imperdiet, nisi ac pulvinar ornare, elit magna semper risus, et posuere
+ mi justo ut sem. Morbi tincidunt odio non risus auctor facilisis in ut dolor. Vestibulum sed sapien
+ interdum, maximus leo quis, lacinia mi. Mauris ipsum orci, commodo quis lorem in, laoreet eleifend ante.
+ Etiam euismod neque sit amet diam pulvinar tincidunt vel vel urna.
+
+
+
+
+ Components
+
+
+ Cards
+
+
+ Minimal card
+
+
+
Some quick example text to build on the card title and make up the bulk of
+ the card's content.
+
+
+
+
+
+ Card with title and link
+
+
+
Card title
+
Some quick example text to build on the card title and make up the bulk of
+ the card's content.
+
Card link
+
Another link
+
+
+
+
+
+
+
+
+
+
+
diff --git a/raspberry-mint-dev/webpack.config.js b/raspberry-mint-dev/webpack.config.js
new file mode 100644
index 000000000..0c02a29db
--- /dev/null
+++ b/raspberry-mint-dev/webpack.config.js
@@ -0,0 +1,131 @@
+'use strict'
+
+import {CleanWebpackPlugin} from 'clean-webpack-plugin'
+import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'
+import HtmlWebpackPlugin from 'html-webpack-plugin'
+import MiniCssExtractPlugin from 'mini-css-extract-plugin'
+import path from 'path'
+import TerserPlugin from 'terser-webpack-plugin'
+import url from 'node:url'
+
+const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
+
+const webpackConfiguration = {
+ mode: 'development',
+ devtool: 'eval-source-map',
+ entry: path.join(__dirname, 'src', 'js', 'main.ts'),
+ output: {
+ path: path.join(__dirname, 'build'),
+ filename: 'js/[name].js'
+ },
+ /* @todo Do not migrate this to the final product! */
+ devServer: {
+ watchFiles: {
+ paths: [
+ "./src/**/*",
+ "../betty/assets/public/static/**/*",
+ ],
+ },
+ static: {
+ directory: path.join(__dirname, "..", "betty", "assets", "public","static")
+ }
+ },
+ optimization: {
+ concatenateModules: true,
+ minimize: false,
+ minimizer: [
+ new CssMinimizerPlugin(),
+ new TerserPlugin({
+ extractComments: false,
+ terserOptions: {
+ output: {
+ comments: false
+ }
+ }
+ })
+ ],
+ },
+ plugins: [
+ new CleanWebpackPlugin(),
+ new MiniCssExtractPlugin({
+ filename: 'css/[name].css'
+ }),
+ new HtmlWebpackPlugin({ template: path.join(__dirname, './src/www/index.html') })
+
+ ],
+ module: {
+ rules: [
+ {
+ test: /\.(js|ts)$/,
+ exclude: /node_modules/,
+ use: [
+ {
+ loader: 'babel-loader',
+ options: {
+ cacheDirectory: path.resolve(__dirname, 'cache'),
+ presets: [
+ [
+ '@babel/preset-env', {
+ debug: true,
+ modules: false,
+ useBuiltIns: 'usage',
+ corejs: 3
+ },
+ ],
+ '@babel/preset-typescript',
+ ]
+ }
+ }
+ ]
+ },
+ {
+ test: /\.s?css$/,
+ use: [
+ {
+ loader: MiniCssExtractPlugin.loader,
+ options: {
+ publicPath: '/'
+ }
+ },
+ {
+ loader: 'css-loader',
+ options: {
+ url: {
+ // Betty's own assets are generated through the assets file system,
+ // so we use Webpack for vendor assets only.
+ filter: (url, resourcePath) => resourcePath.includes('/node_modules/'),
+ }
+ }
+ },
+ {
+ loader: 'postcss-loader',
+ options: {
+ postcssOptions: {
+ plugins: () => [
+ require('autoprefixer')
+ ]
+ }
+ }
+ },
+ {
+ loader: 'sass-loader',
+ options:{
+ sassOptions: {
+ silenceDeprecations: ["color-functions", "global-builtin", "import", "mixed-decls"]
+ }
+ }
+ }
+ ]
+ },
+ {
+ test: /.*\.png|gif|jpg|jpeg|svg/,
+ type: 'asset/resource',
+ generator: {
+ filename: 'images/[hash][ext]'
+ }
+ }
+ ]
+ }
+}
+
+export default webpackConfiguration