Merge pull request #6 from derekslenk/feature/css-optimization
CSS Optimization and Styling Improvements
This commit is contained in:
commit
6c90430ff6
14 changed files with 1843 additions and 1133 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -26,3 +26,7 @@ Thumbs.db
|
|||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
|
||||
# Generated files
|
||||
public/style.css
|
||||
public/style.css.map
|
||||
|
|
23
README.md
23
README.md
|
@ -29,6 +29,7 @@ A community-driven web application for tracking winter road conditions and icy h
|
|||
```bash
|
||||
npm install
|
||||
```
|
||||
*Note: CSS is automatically built via the `postinstall` script*
|
||||
|
||||
3. **Configure environment variables:**
|
||||
```bash
|
||||
|
@ -38,7 +39,9 @@ A community-driven web application for tracking winter road conditions and icy h
|
|||
|
||||
4. **Start the server:**
|
||||
```bash
|
||||
npm start
|
||||
npm start # Production mode
|
||||
npm run dev # Development mode
|
||||
npm run dev-with-css # Development with CSS watching
|
||||
```
|
||||
|
||||
5. **Visit the application:**
|
||||
|
@ -46,6 +49,22 @@ A community-driven web application for tracking winter road conditions and icy h
|
|||
http://localhost:3000
|
||||
```
|
||||
|
||||
### CSS Development
|
||||
|
||||
This project uses SCSS for styling. The CSS is **generated** and should not be committed to git.
|
||||
|
||||
- **Build CSS once:** `npm run build-css`
|
||||
- **Build CSS (dev mode):** `npm run build-css:dev`
|
||||
- **Watch CSS changes:** `npm run watch-css`
|
||||
- **Dev with CSS watching:** `npm run dev-with-css`
|
||||
|
||||
SCSS files are organized in `src/scss/`:
|
||||
- `main.scss` - Main entry point
|
||||
- `_variables.scss` - Theme variables and colors
|
||||
- `_mixins.scss` - Reusable SCSS mixins
|
||||
- `pages/` - Page-specific styles
|
||||
- `components/` - Component-specific styles
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
|
@ -72,7 +91,7 @@ PORT=3000
|
|||
```bash
|
||||
git clone git@github.com:deco/ice.git /opt/ice
|
||||
cd /opt/ice
|
||||
npm install
|
||||
npm install # This automatically builds CSS via postinstall
|
||||
```
|
||||
|
||||
3. **Configure environment:**
|
||||
|
|
508
package-lock.json
generated
508
package-lock.json
generated
|
@ -16,8 +16,10 @@
|
|||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"sass": "^1.89.2",
|
||||
"supertest": "^6.3.4"
|
||||
}
|
||||
},
|
||||
|
@ -1032,6 +1034,330 @@
|
|||
"@noble/hashes": "^1.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
|
||||
"integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^1.0.3",
|
||||
"is-glob": "^4.0.3",
|
||||
"micromatch": "^4.0.5",
|
||||
"node-addon-api": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher-android-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-arm64": "2.5.1",
|
||||
"@parcel/watcher-darwin-x64": "2.5.1",
|
||||
"@parcel/watcher-freebsd-x64": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-arm64-musl": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-glibc": "2.5.1",
|
||||
"@parcel/watcher-linux-x64-musl": "2.5.1",
|
||||
"@parcel/watcher-win32-arm64": "2.5.1",
|
||||
"@parcel/watcher-win32-ia32": "2.5.1",
|
||||
"@parcel/watcher-win32-x64": "2.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-android-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-darwin-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-freebsd-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-arm64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-glibc": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
|
||||
"integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-linux-x64-musl": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
|
||||
"integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-arm64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
|
||||
"integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-ia32": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
|
||||
"integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher-win32-x64": {
|
||||
"version": "2.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
|
||||
"integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/parcel"
|
||||
}
|
||||
},
|
||||
"node_modules/@parcel/watcher/node_modules/detect-libc": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
|
||||
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"detect-libc": "bin/detect-libc.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@sinclair/typebox": {
|
||||
"version": "0.27.8",
|
||||
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
|
||||
|
@ -2012,6 +2338,58 @@
|
|||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concurrently": {
|
||||
"version": "9.2.0",
|
||||
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.0.tgz",
|
||||
"integrity": "sha512-IsB/fiXTupmagMW4MNp2lx2cdSN2FfZq78vF90LBB+zZHArbIQZjQtzXCiXnvTxCZSvXanTqFLWBjw2UkLx1SQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"rxjs": "^7.8.1",
|
||||
"shell-quote": "^1.8.1",
|
||||
"supports-color": "^8.1.1",
|
||||
"tree-kill": "^1.2.2",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
"conc": "dist/bin/concurrently.js",
|
||||
"concurrently": "dist/bin/concurrently.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/concurrently/node_modules/has-flag": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
||||
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/concurrently/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
|
@ -2731,21 +3109,6 @@
|
|||
"devOptional": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||
|
@ -3141,6 +3504,13 @@
|
|||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/immutable": {
|
||||
"version": "5.1.3",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
|
||||
"integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/import-local": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
|
||||
|
@ -4177,6 +4547,13 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
|
@ -5256,6 +5633,16 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
|
@ -5282,6 +5669,57 @@
|
|||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.89.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz",
|
||||
"integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chokidar": "^4.0.0",
|
||||
"immutable": "^5.0.2",
|
||||
"source-map-js": ">=0.6.2 <2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"sass": "sass.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@parcel/watcher": "^2.4.1"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/sass/node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
|
||||
|
@ -5384,6 +5822,19 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
|
@ -5614,6 +6065,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.13",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
|
||||
|
@ -5986,6 +6447,23 @@
|
|||
"nodetouch": "bin/nodetouch.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"tree-kill": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tunnel-agent": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
|
|
14
package.json
14
package.json
|
@ -4,10 +4,16 @@
|
|||
"description": "Great Lakes Ice Report - Community-driven winter road conditions tracker for Michigan",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js",
|
||||
"start": "npm run build && node server.js",
|
||||
"dev": "npm run build-css && nodemon server.js",
|
||||
"build-css": "sass src/scss/main.scss public/style.css --style=compressed",
|
||||
"build-css:dev": "sass src/scss/main.scss public/style.css --style=expanded --source-map",
|
||||
"watch-css": "sass src/scss/main.scss public/style.css --watch --style=expanded --source-map",
|
||||
"dev-with-css": "concurrently \"npm run watch-css\" \"npm run dev\"",
|
||||
"build": "npm run build-css",
|
||||
"test": "jest --runInBand --forceExit",
|
||||
"test:coverage": "jest --coverage"
|
||||
"test:coverage": "jest --coverage",
|
||||
"postinstall": "npm run build-css"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
|
@ -17,8 +23,10 @@
|
|||
"sqlite3": "^5.1.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^9.2.0",
|
||||
"jest": "^29.7.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"sass": "^1.89.2",
|
||||
"supertest": "^6.3.4"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
|
@ -7,425 +7,14 @@
|
|||
<link rel="icon" type="image/svg+xml" href="https://iceymi.b-cdn.net/favicon.svg">
|
||||
<link rel="icon" type="image/x-icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIj4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLnNub3dmbGFrZSB7IGZpbGw6ICMyMTk2RjM7IH0KICAgICAgLmNlbnRlciB7IGZpbGw6ICMxOTc2RDI7IH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIAogIDxnIGNsYXNzPSJzbm93Zmxha2UiPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiAvPgogICAgPHJlY3QgeD0iMiIgeT0iMTUiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyIiAvPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cmVjdCB4PSIxNSIgeT0iMiIgd2lkdGg9IjIiIGhlaWdodD0iMjgiIHRyYW5zZm9ybT0icm90YXRlKC00NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjE2LDIgMTQsNiAxOCw2IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSIxNiwzMCAxNCwyNiAxOCwyNiIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMiwxNiA2LDE0IDYsMTgiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjMwLDE2IDI2LDE0IDI2LDE4IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSI2LjMsNi4zIDguNiw0IDkuOSw3LjciIHRyYW5zZm9ybT0icm90YXRlKDQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNywyNS43IDIzLjQsMjggMjIuMSwyNC4zIiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjYuMywyNS43IDguNiwyOCA5LjksMjQuMyIgdHJhbnNmb3JtPSJyb3RhdGUoLTQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNyw2LjMgMjMuNCw0IDIyLjEsNy43IiB0cmFuc2Zvcm09InJvdGF0ZSgtNDUgMTYgMTYpIiAvPgogIDwvZz4KICA8Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIzIiBjbGFzcz0iY2VudGVyIiAvPgo8L3N2Zz4K">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style>
|
||||
.admin-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.login-section {
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px var(--shadow);
|
||||
max-width: 400px;
|
||||
margin: 50px auto;
|
||||
}
|
||||
|
||||
.admin-section {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.locations-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 4px var(--shadow);
|
||||
}
|
||||
|
||||
.locations-table th,
|
||||
.locations-table td {
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.locations-table th {
|
||||
background-color: var(--table-header-bg);
|
||||
color: var(--text-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.locations-table tr:hover {
|
||||
background-color: var(--table-hover);
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 5px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.btn-edit {
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-delete {
|
||||
background-color: #dc3545;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-save {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.edit-row {
|
||||
background-color: #fff3cd !important;
|
||||
}
|
||||
|
||||
.edit-input {
|
||||
width: 100%;
|
||||
padding: 4px;
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
background-color: var(--input-bg);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
background-color: #6c757d;
|
||||
color: white;
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.theme-toggle-admin {
|
||||
background: var(--card-bg) !important;
|
||||
color: var(--text-color) !important;
|
||||
border: 2px solid var(--border-color) !important;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50% !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.header-btn:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.header-btn.btn-refresh {
|
||||
background-color: #007bff;
|
||||
}
|
||||
|
||||
.header-btn.btn-home {
|
||||
background-color: #28a745;
|
||||
}
|
||||
|
||||
.header-btn.btn-logout {
|
||||
background-color: #dc3545;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px var(--shadow);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.address-cell {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Tab Navigation */
|
||||
.tab-navigation {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #ddd;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 12px 20px;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 3px solid transparent;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
color: #007bff;
|
||||
border-bottom-color: #007bff;
|
||||
background: rgba(0, 123, 255, 0.1);
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
color: #007bff;
|
||||
background: rgba(0, 123, 255, 0.05);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Profanity Management Forms */
|
||||
.profanity-add-form,
|
||||
.profanity-test-form {
|
||||
background: #f8f9fa;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.profanity-add-form h4,
|
||||
.profanity-test-form h4 {
|
||||
margin-top: 0;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.form-row input,
|
||||
.form-row select {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-row input[type="text"] {
|
||||
flex: 1;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
.form-row select {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.test-results {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
min-height: 40px;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.test-results.clean {
|
||||
background: #d4edda;
|
||||
border: 1px solid #c3e6cb;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.test-results.profane {
|
||||
background: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.test-results.empty {
|
||||
background: #e2e3e5;
|
||||
border: 1px solid #d6d8db;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
/* Mobile responsiveness for admin panel */
|
||||
@media (max-width: 768px) {
|
||||
.admin-container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.login-section {
|
||||
margin: 20px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.admin-header h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
font-size: 12px;
|
||||
padding: 8px 12px;
|
||||
}
|
||||
|
||||
.stats {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.locations-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.locations-table th,
|
||||
.locations-table td {
|
||||
padding: 6px 4px;
|
||||
}
|
||||
|
||||
.address-cell {
|
||||
max-width: 120px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 4px 6px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.edit-input {
|
||||
font-size: 12px;
|
||||
padding: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.stats {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
font-size: 11px;
|
||||
padding: 6px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.locations-table th,
|
||||
.locations-table td {
|
||||
padding: 4px 2px;
|
||||
}
|
||||
|
||||
.address-cell {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 3px 4px;
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-container">
|
||||
<!-- Login Section -->
|
||||
<div id="login-section" class="login-section">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
||||
<h2 style="margin: 0;">🔐 Admin Login</h2>
|
||||
<a href="/" class="header-btn btn-home" style="text-decoration: none; font-size: 12px; padding: 8px 12px;">🏠 Back to Homepage</a>
|
||||
<div class="login-header">
|
||||
<h2>🔐 Admin Login</h2>
|
||||
<a href="/" class="header-btn btn-home">🏠 Back to Homepage</a>
|
||||
</div>
|
||||
<form id="login-form">
|
||||
<div class="form-group">
|
||||
|
@ -473,7 +62,7 @@
|
|||
<!-- Tab Navigation -->
|
||||
<div class="tab-navigation">
|
||||
<button class="tab-btn active" data-tab="locations">📍 Location Reports</button>
|
||||
<button class="tab-btn" data-tab="profanity">🚫 Profanity Filter</button>
|
||||
<button class="tab-btn" data-tab="profanity">🔒 Profanity Filter</button>
|
||||
</div>
|
||||
|
||||
<!-- Locations Tab -->
|
||||
|
@ -501,7 +90,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Profanity Management Tab -->
|
||||
<!-- Profanity Filter Tab -->
|
||||
<div id="profanity-tab" class="tab-content">
|
||||
<div class="form-section">
|
||||
<h3>Custom Profanity Words</h3>
|
||||
|
@ -557,6 +146,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p><strong>Safety Notice:</strong> This is a community tool for awareness. Stay safe and <a href="https://www.aclu.org/know-your-rights/immigrants-rights" target="_blank" rel="noopener noreferrer">know your rights</a>.</p>
|
||||
<div class="disclaimer">
|
||||
<small>This website is for informational purposes only. Verify information independently.
|
||||
Reports are automatically deleted after 48 hours. • <a href="/privacy">Privacy Policy</a></small>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="utils.js"></script>
|
||||
<script src="admin.js"></script>
|
||||
|
|
|
@ -7,88 +7,17 @@
|
|||
<link rel="icon" type="image/svg+xml" href="https://iceymi.b-cdn.net/favicon.svg">
|
||||
<link rel="icon" type="image/x-icon" href="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzMiAzMiIgd2lkdGg9IjMyIiBoZWlnaHQ9IjMyIj4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLnNub3dmbGFrZSB7IGZpbGw6ICMyMTk2RjM7IH0KICAgICAgLmNlbnRlciB7IGZpbGw6ICMxOTc2RDI7IH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIAogIDxnIGNsYXNzPSJzbm93Zmxha2UiPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiAvPgogICAgPHJlY3QgeD0iMiIgeT0iMTUiIHdpZHRoPSIyOCIgaGVpZ2h0PSIyIiAvPgogICAgPHJlY3QgeD0iMTUiIHk9IjIiIHdpZHRoPSIyIiBoZWlnaHQ9IjI4IiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cmVjdCB4PSIxNSIgeT0iMiIgd2lkdGg9IjIiIGhlaWdodD0iMjgiIHRyYW5zZm9ybT0icm90YXRlKC00NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjE2LDIgMTQsNiAxOCw2IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSIxNiwzMCAxNCwyNiAxOCwyNiIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMiwxNiA2LDE0IDYsMTgiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjMwLDE2IDI2LDE0IDI2LDE4IiAvPgogICAgPHBvbHlnb24gcG9pbnRzPSI2LjMsNi4zIDguNiw0IDkuOSw3LjciIHRyYW5zZm9ybT0icm90YXRlKDQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNywyNS43IDIzLjQsMjggMjIuMSwyNC4zIiB0cmFuc2Zvcm09InJvdGF0ZSg0NSAxNiAxNikiIC8+CiAgICA8cG9seWdvbiBwb2ludHM9IjYuMywyNS43IDguNiwyOCA5LjksMjQuMyIgdHJhbnNmb3JtPSJyb3RhdGUoLTQ1IDE2IDE2KSIgLz4KICAgIDxwb2x5Z29uIHBvaW50cz0iMjUuNyw2LjMgMjMuNCw0IDIyLjEsNy43IiB0cmFuc2Zvcm09InJvdGF0ZSgtNDUgMTYgMTYpIiAvPgogIDwvZz4KICA8Y2lyY2xlIGN4PSIxNiIgY3k9IjE2IiByPSIzIiBjbGFzcz0iY2VudGVyIiAvPgo8L3N2Zz4K">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<style>
|
||||
.privacy-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.privacy-header {
|
||||
text-align: center;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.privacy-content {
|
||||
background: var(--card-bg);
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px var(--shadow);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.privacy-content h2 {
|
||||
color: var(--text-color);
|
||||
margin-top: 30px;
|
||||
margin-bottom: 15px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.privacy-content h2:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.privacy-content ul {
|
||||
margin: 15px 0;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.privacy-content li {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.back-link {
|
||||
display: inline-block;
|
||||
margin-top: 30px;
|
||||
padding: 10px 20px;
|
||||
background-color: var(--button-bg);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.back-link:hover {
|
||||
background-color: var(--button-hover);
|
||||
}
|
||||
|
||||
.effective-date {
|
||||
color: #666;
|
||||
font-size: 0.9em;
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
background: var(--toggle-bg);
|
||||
padding: 20px;
|
||||
border-radius: 6px;
|
||||
margin-top: 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="privacy-container">
|
||||
<div class="privacy-header">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; gap: 20px; margin-bottom: 20px;">
|
||||
<div style="flex: 1; text-align: center;">
|
||||
<div class="privacy-header__title-wrapper">
|
||||
<div class="privacy-header__title-content">
|
||||
<h1>❄️ Privacy Policy</h1>
|
||||
<p>Great Lakes Ice Report</p>
|
||||
</div>
|
||||
<button id="theme-toggle" class="theme-toggle" title="Toggle theme mode" style="background: var(--card-bg); border: 2px solid var(--border-color); border-radius: 50%; width: 50px; height: 50px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 2px 4px var(--shadow); flex-shrink: 0;">
|
||||
<span class="theme-icon" style="font-size: 20px;">🌙</span>
|
||||
<button id="theme-toggle" class="privacy-header__theme-toggle" title="Toggle theme mode">
|
||||
<span class="theme-icon">🌙</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="effective-date">Effective Date: January 2025</div>
|
||||
|
@ -173,6 +102,14 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<p><strong>Safety Notice:</strong> This is a community tool for awareness. Stay safe and <a href="https://www.aclu.org/know-your-rights/immigrants-rights" target="_blank" rel="noopener noreferrer">know your rights</a>.</p>
|
||||
<div class="disclaimer">
|
||||
<small>This website is for informational purposes only. Verify information independently.
|
||||
Reports are automatically deleted after 48 hours. • <a href="/privacy">Privacy Policy</a></small>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
// Theme toggle functionality (shared with main page)
|
||||
function initializeTheme() {
|
||||
|
|
622
public/style.css
622
public/style.css
|
@ -1,622 +0,0 @@
|
|||
/* CSS Variables for theming */
|
||||
:root {
|
||||
--bg-color: #f4f4f9;
|
||||
--text-color: #333;
|
||||
--card-bg: white;
|
||||
--border-color: #ddd;
|
||||
--input-bg: white;
|
||||
--input-border: #ddd;
|
||||
--button-bg: #007bff;
|
||||
--button-hover: #0056b3;
|
||||
--header-bg: transparent;
|
||||
--footer-border: #ddd;
|
||||
--table-header-bg: #f8f9fa;
|
||||
--table-hover: #f8f9fa;
|
||||
--toggle-bg: #f8f9fa;
|
||||
--toggle-border: #dee2e6;
|
||||
--toggle-active-bg: #007bff;
|
||||
--shadow: rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Dark mode variables */
|
||||
[data-theme="dark"] {
|
||||
--bg-color: #1a1a1a;
|
||||
--text-color: #e0e0e0;
|
||||
--card-bg: #2d2d2d;
|
||||
--border-color: #404040;
|
||||
--input-bg: #333;
|
||||
--input-border: #555;
|
||||
--button-bg: #4a90e2;
|
||||
--button-hover: #357abd;
|
||||
--header-bg: transparent;
|
||||
--footer-border: #404040;
|
||||
--table-header-bg: #3a3a3a;
|
||||
--table-hover: #3a3a3a;
|
||||
--toggle-bg: #404040;
|
||||
--toggle-border: #555;
|
||||
--toggle-active-bg: #4a90e2;
|
||||
--shadow: rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Auto system theme detection */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root[data-theme="auto"] {
|
||||
--bg-color: #1a1a1a;
|
||||
--text-color: #e0e0e0;
|
||||
--card-bg: #2d2d2d;
|
||||
--border-color: #404040;
|
||||
--input-bg: #333;
|
||||
--input-border: #555;
|
||||
--button-bg: #4a90e2;
|
||||
--button-hover: #357abd;
|
||||
--header-bg: transparent;
|
||||
--footer-border: #404040;
|
||||
--table-header-bg: #3a3a3a;
|
||||
--table-hover: #3a3a3a;
|
||||
--toggle-bg: #404040;
|
||||
--toggle-border: #555;
|
||||
--toggle-active-bg: #4a90e2;
|
||||
--shadow: rgba(0,0,0,0.3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root[data-theme="auto"] {
|
||||
--bg-color: #f4f4f9;
|
||||
--text-color: #333;
|
||||
--card-bg: white;
|
||||
--border-color: #ddd;
|
||||
--input-bg: white;
|
||||
--input-border: #ddd;
|
||||
--button-bg: #007bff;
|
||||
--button-hover: #0056b3;
|
||||
--header-bg: transparent;
|
||||
--footer-border: #ddd;
|
||||
--table-header-bg: #f8f9fa;
|
||||
--table-hover: #f8f9fa;
|
||||
--toggle-bg: #f8f9fa;
|
||||
--toggle-border: #dee2e6;
|
||||
--toggle-active-bg: #007bff;
|
||||
--shadow: rgba(0,0,0,0.1);
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.map-section, .form-section {
|
||||
margin-bottom: 20px;
|
||||
padding: 15px;
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px var(--shadow);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.map-section {
|
||||
height: auto;
|
||||
min-height: 650px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
#map {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
input[type="text"], textarea {
|
||||
width: calc(100% - 20px);
|
||||
padding: 8px;
|
||||
margin-top: 5px;
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: 4px;
|
||||
background-color: var(--input-bg);
|
||||
color: var(--text-color);
|
||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.autocomplete-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.autocomplete-list {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-top: none;
|
||||
border-radius: 0 0 4px 4px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
box-shadow: 0 4px 6px var(--shadow);
|
||||
}
|
||||
|
||||
.autocomplete-item {
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.autocomplete-item:hover,
|
||||
.autocomplete-item.selected {
|
||||
background-color: var(--table-hover);
|
||||
}
|
||||
|
||||
.autocomplete-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.input-help {
|
||||
color: #666;
|
||||
font-size: 0.85em;
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
button[type="submit"] {
|
||||
background-color: var(--button-bg);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.2s ease-in;
|
||||
}
|
||||
|
||||
button[type="submit"]:hover {
|
||||
background-color: var(--button-hover);
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
display: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.message.error.profanity-rejection {
|
||||
background-color: #f5c6cb;
|
||||
color: #721c24;
|
||||
border: 2px solid #dc3545;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Only animate for users who haven't requested reduced motion */
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.message.error.profanity-rejection {
|
||||
animation: pulse 0.5s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme="dark"] .message.error.profanity-rejection {
|
||||
background-color: #3d1b1c;
|
||||
border-color: #dc3545;
|
||||
color: #f5c6cb;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.02); }
|
||||
100% { transform: scale(1); }
|
||||
}
|
||||
|
||||
/* Header layout for theme toggle */
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* Theme toggle button */
|
||||
.theme-toggle {
|
||||
background: var(--card-bg);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px var(--shadow);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: 0 4px 8px var(--shadow);
|
||||
}
|
||||
|
||||
.theme-icon {
|
||||
font-size: 20px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 30px 20px;
|
||||
margin-top: 30px;
|
||||
border-top: 1px solid var(--footer-border);
|
||||
clear: both;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
font-size: 0.8em;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
/* Reports header and toggle */
|
||||
.reports-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
background-color: var(--toggle-bg);
|
||||
border: 2px solid var(--toggle-border);
|
||||
color: var(--text-color);
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.toggle-btn:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.toggle-btn.active {
|
||||
background-color: var(--toggle-active-bg);
|
||||
border-color: var(--toggle-active-bg);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.toggle-btn.active:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* View containers */
|
||||
.view-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Table view styles */
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.reports-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--card-bg);
|
||||
font-size: 14px;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.reports-table th {
|
||||
background-color: var(--table-header-bg);
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
padding: 12px;
|
||||
text-align: left;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.reports-table td {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.reports-table tr:hover {
|
||||
background-color: var(--table-hover);
|
||||
}
|
||||
|
||||
.reports-table tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.table-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.table-info {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Table cell specific styles */
|
||||
.location-cell {
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
max-width: 250px;
|
||||
}
|
||||
|
||||
.details-cell {
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.time-cell {
|
||||
font-size: 12px;
|
||||
color: #868e96;
|
||||
}
|
||||
|
||||
.remaining-cell {
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.remaining-cell.urgent {
|
||||
color: #dc3545;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.remaining-cell.warning {
|
||||
color: #fd7e14;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.remaining-cell.normal {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
/* Dark mode table cell overrides - ensure text is visible */
|
||||
[data-theme="dark"] .location-cell,
|
||||
[data-theme="dark"] .details-cell,
|
||||
[data-theme="dark"] .time-cell {
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .remaining-cell {
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
/* Auto theme detection for dark mode */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root[data-theme="auto"] .location-cell,
|
||||
:root[data-theme="auto"] .details-cell,
|
||||
:root[data-theme="auto"] .time-cell {
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
|
||||
:root[data-theme="auto"] .remaining-cell {
|
||||
color: var(--text-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
header p {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.map-section, .form-section {
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
input[type="text"], textarea {
|
||||
font-size: 16px; /* Prevents zoom on iOS */
|
||||
width: calc(100% - 16px);
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
button[type="submit"] {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.reports-header {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.reports-header h2 {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.view-toggle {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
padding: 12px 8px;
|
||||
}
|
||||
|
||||
.reports-table {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.reports-table th,
|
||||
.reports-table td {
|
||||
padding: 8px 4px;
|
||||
}
|
||||
|
||||
.location-cell {
|
||||
max-width: 120px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.details-cell {
|
||||
max-width: 100px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.time-cell {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.remaining-cell {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.autocomplete-list {
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
.input-help {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 20px 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra small screens */
|
||||
@media (max-width: 480px) {
|
||||
.container {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.map-section, .form-section {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
#map {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.reports-table th {
|
||||
font-size: 10px;
|
||||
padding: 6px 2px;
|
||||
}
|
||||
|
||||
.reports-table td {
|
||||
font-size: 10px;
|
||||
padding: 6px 2px;
|
||||
}
|
||||
|
||||
.location-cell {
|
||||
max-width: 100px;
|
||||
}
|
||||
|
||||
.details-cell {
|
||||
max-width: 80px;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
font-size: 12px;
|
||||
padding: 10px 6px;
|
||||
}
|
||||
}
|
167
src/scss/_mixins.scss
Normal file
167
src/scss/_mixins.scss
Normal file
|
@ -0,0 +1,167 @@
|
|||
// Import variables
|
||||
@use 'variables' as *;
|
||||
|
||||
// Button mixin
|
||||
@mixin button($bg-color: $primary-color, $text-color: white, $size: md) {
|
||||
border: none;
|
||||
border-radius: $border-radius-sm;
|
||||
cursor: pointer;
|
||||
font-family: $font-family;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
@if $size == sm {
|
||||
padding: $spacing-xs $spacing-sm;
|
||||
font-size: $font-size-sm;
|
||||
} @else if $size == lg {
|
||||
padding: $spacing-md $spacing-lg;
|
||||
font-size: $font-size-lg;
|
||||
} @else {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
font-size: $font-size-md;
|
||||
}
|
||||
|
||||
background-color: $bg-color;
|
||||
color: $text-color;
|
||||
|
||||
&:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Card mixin
|
||||
@mixin card($padding: $spacing-lg) {
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
padding: $padding;
|
||||
border-radius: $border-radius-md;
|
||||
box-shadow: $shadow-md;
|
||||
}
|
||||
|
||||
// Flex layout mixins
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@mixin flex-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
@mixin flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// Table styling mixin
|
||||
@mixin table-base {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--card-bg);
|
||||
color: var(--text-color);
|
||||
border-radius: $border-radius-md;
|
||||
overflow: hidden;
|
||||
box-shadow: $shadow-md;
|
||||
|
||||
th, td {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: var(--table-header-bg);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: var(--table-hover);
|
||||
}
|
||||
}
|
||||
|
||||
// Form input mixin
|
||||
@mixin input-base {
|
||||
padding: $spacing-sm $spacing-md;
|
||||
border: 1px solid var(--input-border);
|
||||
border-radius: $border-radius-sm;
|
||||
font-size: $font-size-md;
|
||||
font-family: $font-family;
|
||||
background-color: var(--input-bg);
|
||||
color: var(--text-color);
|
||||
transition: border-color 0.2s ease;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border-color: $primary-color;
|
||||
box-shadow: 0 0 0 2px rgba($primary-color, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
// Status indicator mixin
|
||||
@mixin status-indicator($bg-color, $text-color) {
|
||||
padding: $spacing-xs $spacing-sm;
|
||||
border-radius: $border-radius-lg;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: bold;
|
||||
background-color: $bg-color;
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
// Back-link button mixin (shared component)
|
||||
@mixin back-link-styles {
|
||||
display: inline-block;
|
||||
margin-top: $spacing-lg;
|
||||
padding: $spacing-sm $spacing-md;
|
||||
color: white;
|
||||
background-color: var(--primary-color, $primary-color);
|
||||
border: none;
|
||||
border-radius: $border-radius-sm;
|
||||
text-decoration: none;
|
||||
transition: background-color 0.2s ease;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--primary-hover, $secondary-color);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid currentColor;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive breakpoint mixins
|
||||
@mixin mobile {
|
||||
@media (max-width: $mobile) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin tablet {
|
||||
@media (max-width: $tablet) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin desktop {
|
||||
@media (min-width: $desktop) {
|
||||
@content;
|
||||
}
|
||||
}
|
56
src/scss/_variables.scss
Normal file
56
src/scss/_variables.scss
Normal file
|
@ -0,0 +1,56 @@
|
|||
// Color Palette
|
||||
$primary-color: #2196F3;
|
||||
$secondary-color: #1976D2;
|
||||
$success-color: #28a745;
|
||||
$warning-color: #ffc107;
|
||||
$danger-color: #dc3545;
|
||||
$info-color: #17a2b8;
|
||||
|
||||
// Theme Colors (CSS Variables will be generated)
|
||||
$light-bg: #ffffff;
|
||||
$light-text: #1f2937;
|
||||
$light-card-bg: #f3f4f6;
|
||||
$light-border: #d1d5db;
|
||||
|
||||
$dark-bg: #111827;
|
||||
$dark-text: #f9fafb;
|
||||
$dark-card-bg: #1f2937;
|
||||
$dark-border: #374151;
|
||||
|
||||
// Spacing
|
||||
$spacing-xs: 4px;
|
||||
$spacing-sm: 8px;
|
||||
$spacing-md: 16px;
|
||||
$spacing-lg: 24px;
|
||||
$spacing-xl: 32px;
|
||||
$spacing-xxl: 48px;
|
||||
|
||||
// Border Radius
|
||||
$border-radius-sm: 4px;
|
||||
$border-radius-md: 8px;
|
||||
$border-radius-lg: 12px;
|
||||
$border-radius-full: 50%;
|
||||
|
||||
// Shadows
|
||||
$shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
$shadow-md: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
$shadow-lg: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
|
||||
// Typography
|
||||
$font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
$font-size-xs: 12px;
|
||||
$font-size-sm: 14px;
|
||||
$font-size-md: 16px;
|
||||
$font-size-lg: 18px;
|
||||
$font-size-xl: 20px;
|
||||
$font-size-xxl: 28px;
|
||||
|
||||
// Z-Index
|
||||
$z-index-modal: 1000;
|
||||
$z-index-dropdown: 100;
|
||||
$z-index-tooltip: 200;
|
||||
|
||||
// Breakpoints
|
||||
$mobile: 768px;
|
||||
$tablet: 1024px;
|
||||
$desktop: 1200px;
|
61
src/scss/components/_footer.scss
Normal file
61
src/scss/components/_footer.scss
Normal file
|
@ -0,0 +1,61 @@
|
|||
// Import variables and mixins
|
||||
@use '../variables' as *;
|
||||
@use '../mixins' as *;
|
||||
|
||||
// Shared footer styles
|
||||
footer {
|
||||
margin-top: $spacing-xl;
|
||||
padding: $spacing-lg;
|
||||
background-color: var(--bg-secondary);
|
||||
border-top: 1px solid var(--border-color);
|
||||
border-radius: $border-radius-md $border-radius-md 0 0;
|
||||
|
||||
p {
|
||||
margin: $spacing-sm 0;
|
||||
color: var(--text-color);
|
||||
text-align: center;
|
||||
font-size: $font-size-md;
|
||||
line-height: 1.6;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: var(--link-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
margin-top: $spacing-md;
|
||||
text-align: center;
|
||||
|
||||
small {
|
||||
color: var(--text-secondary);
|
||||
font-size: $font-size-md;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
padding: $spacing-md;
|
||||
|
||||
p {
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.disclaimer small {
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
}
|
||||
}
|
174
src/scss/main.scss
Normal file
174
src/scss/main.scss
Normal file
|
@ -0,0 +1,174 @@
|
|||
// Import variables and mixins first
|
||||
@use 'variables' as *;
|
||||
@use 'mixins' as *;
|
||||
@use 'components/footer';
|
||||
@use 'pages/admin';
|
||||
@use 'pages/index';
|
||||
@use 'pages/privacy';
|
||||
|
||||
// Override with additional variables from original style-backup
|
||||
$bg-hover: #f1f5f9;
|
||||
$status-active: #10b981;
|
||||
$status-warning: #f59e0b;
|
||||
$status-danger: #ef4444;
|
||||
$status-info: #3b82f6;
|
||||
// Import any additional styles from style-backup as necessary
|
||||
// We'll keep the existing theme variables and base styles
|
||||
|
||||
// Base styles and theme variables (from existing style.css)
|
||||
:root {
|
||||
/* Light theme (default) */
|
||||
--background-color: #{$light-bg};
|
||||
--text-color: #{$light-text};
|
||||
--card-bg: #{$light-card-bg};
|
||||
--border-color: #{$light-border};
|
||||
--input-bg: #{$light-bg};
|
||||
--input-border: #{$light-border};
|
||||
--table-header-bg: #e9ecef;
|
||||
--table-hover: #f5f5f5;
|
||||
--shadow: rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* Additional variables from original */
|
||||
--bg-secondary: #{$light-card-bg};
|
||||
--bg-hover: #{$bg-hover};
|
||||
--status-active: #{$status-active};
|
||||
--status-warning: #{$status-warning};
|
||||
--status-danger: #{$status-danger};
|
||||
--status-info: #{$status-info};
|
||||
--link-color: #007bff;
|
||||
--link-hover: #0056b3;
|
||||
--text-secondary: #6c757d;
|
||||
--primary-color: #{$primary-color};
|
||||
--primary-hover: #{$secondary-color};
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Dark theme */
|
||||
--background-color: #{$dark-bg};
|
||||
--text-color: #{$dark-text};
|
||||
--card-bg: #{$dark-card-bg};
|
||||
--border-color: #{$dark-border};
|
||||
--input-bg: #{$dark-card-bg};
|
||||
--input-border: #{$dark-border};
|
||||
--table-header-bg: #3d3d3d;
|
||||
--table-hover: #3d3d3d;
|
||||
--shadow: rgba(0, 0, 0, 0.3);
|
||||
|
||||
/* Dark theme additional variables */
|
||||
--bg-secondary: #{$dark-card-bg};
|
||||
--bg-hover: #4b5563;
|
||||
--link-color: #60a5fa;
|
||||
--link-hover: #93c5fd;
|
||||
--text-secondary: #9ca3af;
|
||||
--primary-color: #{$primary-color};
|
||||
--primary-hover: #{$secondary-color};
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-family;
|
||||
font-size: $font-size-md;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
line-height: 1.6;
|
||||
min-height: 100vh;
|
||||
transition: background-color 0.3s ease, color 0.3s ease;
|
||||
}
|
||||
|
||||
// Base form styles
|
||||
.form-group {
|
||||
margin-bottom: $spacing-md;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
margin-bottom: $spacing-xs;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
input, select, textarea {
|
||||
@include input-base;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
@include button;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
// Message styles
|
||||
.message {
|
||||
padding: $spacing-md;
|
||||
border-radius: $border-radius-sm;
|
||||
margin: $spacing-md 0;
|
||||
display: none;
|
||||
|
||||
&.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
&.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
&.info {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
}
|
||||
|
||||
// Page-specific styles are imported at the top with other @use statements
|
||||
|
||||
// Theme toggle styles (common across pages)
|
||||
.theme-toggle {
|
||||
@include button($bg-color: transparent);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: $border-radius-full;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
@include flex-center;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--table-hover);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Utility classes
|
||||
.text-center { text-align: center; }
|
||||
.text-left { text-align: left; }
|
||||
.text-right { text-align: right; }
|
||||
|
||||
.mb-sm { margin-bottom: $spacing-sm; }
|
||||
.mb-md { margin-bottom: $spacing-md; }
|
||||
.mb-lg { margin-bottom: $spacing-lg; }
|
||||
|
||||
.mt-sm { margin-top: $spacing-sm; }
|
||||
.mt-md { margin-top: $spacing-md; }
|
||||
.mt-lg { margin-top: $spacing-lg; }
|
||||
|
||||
.p-sm { padding: $spacing-sm; }
|
||||
.p-md { padding: $spacing-md; }
|
||||
.p-lg { padding: $spacing-lg; }
|
||||
|
||||
.d-flex { display: flex; }
|
||||
.flex-center { @include flex-center; }
|
||||
.flex-between { @include flex-between; }
|
||||
.flex-column { @include flex-column; }
|
||||
|
||||
.w-100 { width: 100%; }
|
||||
.h-100 { height: 100%; }
|
314
src/scss/pages/_admin.scss
Normal file
314
src/scss/pages/_admin.scss
Normal file
|
@ -0,0 +1,314 @@
|
|||
// Import variables and mixins
|
||||
@use '../variables' as *;
|
||||
@use '../mixins' as *;
|
||||
|
||||
// Admin Page Styles
|
||||
.admin-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: $spacing-lg;
|
||||
}
|
||||
|
||||
.login-section {
|
||||
@include card($spacing-xl);
|
||||
max-width: 400px;
|
||||
margin: 50px auto;
|
||||
|
||||
.login-header {
|
||||
@include flex-between;
|
||||
align-items: center;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin-section {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Admin Header
|
||||
.admin-header {
|
||||
@include flex-between;
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
display: flex;
|
||||
gap: $spacing-sm;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-btn {
|
||||
@include button($bg-color: #6c757d);
|
||||
|
||||
&.btn-refresh {
|
||||
@include button($bg-color: $primary-color);
|
||||
}
|
||||
|
||||
&.btn-home {
|
||||
@include button($bg-color: $success-color);
|
||||
}
|
||||
|
||||
&.btn-logout {
|
||||
@include button($bg-color: $danger-color);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-toggle-admin {
|
||||
background: var(--card-bg) !important;
|
||||
color: var(--text-color) !important;
|
||||
border: 2px solid var(--border-color) !important;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: $border-radius-full !important;
|
||||
@include flex-center;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
// Stats Cards
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: $spacing-lg;
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
@include card;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stat-number {
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
// Tables
|
||||
.locations-table {
|
||||
@include table-base;
|
||||
margin-top: $spacing-lg;
|
||||
}
|
||||
|
||||
// Buttons
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@include button($size: sm);
|
||||
|
||||
&-edit {
|
||||
@include button($bg-color: $primary-color, $size: sm);
|
||||
}
|
||||
|
||||
&-delete {
|
||||
@include button($bg-color: $danger-color, $size: sm);
|
||||
}
|
||||
|
||||
&-save {
|
||||
@include button($bg-color: $success-color, $size: sm);
|
||||
}
|
||||
|
||||
&-cancel {
|
||||
@include button($bg-color: #6c757d, $size: sm);
|
||||
}
|
||||
}
|
||||
|
||||
// Edit State
|
||||
.edit-row {
|
||||
background-color: #fff3cd !important;
|
||||
}
|
||||
|
||||
.edit-input {
|
||||
@include input-base;
|
||||
width: 100%;
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
// Status Indicators
|
||||
.status-indicator {
|
||||
@include status-indicator(transparent, inherit);
|
||||
}
|
||||
|
||||
.status-active {
|
||||
@include status-indicator(#d4edda, #155724);
|
||||
}
|
||||
|
||||
.status-expired {
|
||||
@include status-indicator(#f8d7da, #721c24);
|
||||
}
|
||||
|
||||
// Tabs
|
||||
.tab-navigation {
|
||||
display: flex;
|
||||
margin-bottom: $spacing-lg;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
@include button($bg-color: transparent, $text-color: var(--text-color));
|
||||
border-bottom: 3px solid transparent;
|
||||
border-radius: 0;
|
||||
|
||||
&.active {
|
||||
border-bottom-color: $primary-color;
|
||||
color: $primary-color;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--table-hover);
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
// Profanity Management
|
||||
.profanity-management {
|
||||
.management-section {
|
||||
@include card;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
color: $primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.profanity-form {
|
||||
display: grid;
|
||||
grid-template-columns: 2fr 1fr 1fr auto;
|
||||
gap: $spacing-sm;
|
||||
align-items: end;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
input, select {
|
||||
@include input-base;
|
||||
}
|
||||
}
|
||||
|
||||
.test-section {
|
||||
.test-form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: $spacing-sm;
|
||||
align-items: end;
|
||||
|
||||
textarea {
|
||||
@include input-base;
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.test-results {
|
||||
margin-top: $spacing-md;
|
||||
padding: $spacing-md;
|
||||
border-radius: $border-radius-sm;
|
||||
|
||||
&.profane {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
&.clean {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
&.empty {
|
||||
background-color: #f8f9fa;
|
||||
color: #6c757d;
|
||||
border: 1px solid #dee2e6;
|
||||
}
|
||||
}
|
||||
|
||||
.severity-low {
|
||||
@include status-indicator(#d1ecf1, #0c5460);
|
||||
}
|
||||
|
||||
.severity-medium {
|
||||
@include status-indicator(#fff3cd, #856404);
|
||||
}
|
||||
|
||||
.severity-high {
|
||||
@include status-indicator(#f8d7da, #721c24);
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
@include button($size: sm);
|
||||
|
||||
&.danger {
|
||||
@include button($bg-color: $danger-color, $size: sm);
|
||||
}
|
||||
}
|
||||
|
||||
// Persistent Toggle
|
||||
.persistent-toggle {
|
||||
&.active {
|
||||
@include button($bg-color: $warning-color, $text-color: #000);
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
@include button($bg-color: #6c757d);
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive Design
|
||||
@include mobile {
|
||||
.admin-container {
|
||||
padding: $spacing-md;
|
||||
}
|
||||
|
||||
.admin-header {
|
||||
flex-direction: column;
|
||||
gap: $spacing-md;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.header-buttons {
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.stats {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
|
||||
.locations-table {
|
||||
font-size: $font-size-sm;
|
||||
|
||||
th, td {
|
||||
padding: $spacing-xs $spacing-xs;
|
||||
}
|
||||
|
||||
.address-cell {
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 3px 4px;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.profanity-form {
|
||||
grid-template-columns: 1fr;
|
||||
gap: $spacing-sm;
|
||||
}
|
||||
}
|
401
src/scss/pages/_index.scss
Normal file
401
src/scss/pages/_index.scss
Normal file
|
@ -0,0 +1,401 @@
|
|||
// Import variables and mixins
|
||||
@use '../variables' as *;
|
||||
@use '../mixins' as *;
|
||||
|
||||
// Main application styles for index.html
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: $spacing-lg;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: $spacing-lg;
|
||||
padding: $spacing-md 0;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.header-content {
|
||||
@include flex-between;
|
||||
align-items: flex-start;
|
||||
gap: $spacing-lg;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
flex: 1;
|
||||
|
||||
h1 {
|
||||
color: var(--text-color);
|
||||
font-size: $font-size-xxl;
|
||||
margin: 0 0 $spacing-xs 0;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
margin: 0;
|
||||
font-size: $font-size-md;
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive header
|
||||
@include mobile {
|
||||
.header-content {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: $spacing-md;
|
||||
}
|
||||
|
||||
.header-text {
|
||||
text-align: center;
|
||||
|
||||
h1 {
|
||||
font-size: $font-size-xl;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
color: var(--text-color);
|
||||
font-size: $font-size-xxl;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
margin: $spacing-xs 0;
|
||||
}
|
||||
|
||||
// Map and view controls
|
||||
.view-controls {
|
||||
@include flex-center;
|
||||
gap: $spacing-sm;
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
@include button;
|
||||
|
||||
&.active {
|
||||
background-color: $primary-color;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
// Map container
|
||||
.map-view {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
border-radius: $border-radius-md;
|
||||
overflow: hidden;
|
||||
box-shadow: $shadow-md;
|
||||
|
||||
@include mobile {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
// Specific map element styling
|
||||
#map {
|
||||
height: 500px;
|
||||
width: 100%;
|
||||
border-radius: $border-radius-md;
|
||||
box-shadow: $shadow-md;
|
||||
margin: $spacing-lg 0;
|
||||
|
||||
@include mobile {
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
|
||||
// Reports table (main page)
|
||||
.reports-table {
|
||||
@include table-base;
|
||||
|
||||
th {
|
||||
background-color: var(--bg-secondary, var(--card-bg));
|
||||
color: var(--text-color);
|
||||
font-weight: 600;
|
||||
padding: 1rem;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: $border-radius-md;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: $border-radius-md;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
vertical-align: top;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
tbody tr {
|
||||
transition: background-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--bg-hover, var(--table-hover));
|
||||
}
|
||||
|
||||
&:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Specific cell styles
|
||||
td.location-cell {
|
||||
color: var(--text-color);
|
||||
font-size: $font-size-sm;
|
||||
font-weight: 500;
|
||||
max-width: 250px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
td.details-cell {
|
||||
color: var(--text-color);
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
td.time-cell {
|
||||
color: var(--text-color);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
td.remaining-cell {
|
||||
color: var(--text-color);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&.urgent {
|
||||
@include status-indicator(var(--status-danger, $danger-color), white);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
@include status-indicator(var(--status-warning, $warning-color), white);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
&.normal {
|
||||
@include status-indicator(var(--status-info, $info-color), white);
|
||||
}
|
||||
}
|
||||
|
||||
@include mobile {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
|
||||
th, td {
|
||||
min-width: 120px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Form section
|
||||
.form-section {
|
||||
@include card;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
h3 {
|
||||
margin-top: 0;
|
||||
margin-bottom: $spacing-md;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
// Map section with card styling
|
||||
.map-section {
|
||||
@include card;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
h2 {
|
||||
margin-top: 0;
|
||||
margin-bottom: $spacing-md;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
// Reports header
|
||||
.reports-header {
|
||||
@include flex-between;
|
||||
align-items: center;
|
||||
margin-bottom: $spacing-lg;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// View toggle buttons
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
gap: $spacing-xs;
|
||||
}
|
||||
|
||||
.toggle-btn {
|
||||
@include button($size: sm);
|
||||
|
||||
&.active {
|
||||
background-color: $primary-color;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&:not(.active) {
|
||||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
// View containers
|
||||
.view-container {
|
||||
margin-top: $spacing-md;
|
||||
}
|
||||
|
||||
// Map info styling
|
||||
.map-info {
|
||||
margin-top: $spacing-md;
|
||||
padding: $spacing-md;
|
||||
background-color: var(--bg-secondary);
|
||||
border-radius: $border-radius-sm;
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
p {
|
||||
margin: $spacing-xs 0;
|
||||
font-size: $font-size-sm;
|
||||
color: var(--text-color);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Table controls
|
||||
.table-controls {
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
.table-info {
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
color: var(--text-color);
|
||||
font-size: $font-size-md;
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
// Add location form
|
||||
.add-location-form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: $spacing-md;
|
||||
align-items: end;
|
||||
|
||||
@include mobile {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.location-input {
|
||||
@include input-base;
|
||||
min-width: 300px;
|
||||
|
||||
@include mobile {
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
@include button($bg-color: $success-color);
|
||||
}
|
||||
}
|
||||
|
||||
// Location count display
|
||||
.location-count {
|
||||
color: var(--text-color);
|
||||
font-size: $font-size-md;
|
||||
margin-bottom: $spacing-md;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
// Error/Success messages
|
||||
.error-message {
|
||||
@include card($spacing-md);
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
margin: $spacing-md 0;
|
||||
|
||||
&.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border-color: #c3e6cb;
|
||||
}
|
||||
}
|
||||
|
||||
// Loading states
|
||||
.loading {
|
||||
text-align: center;
|
||||
color: var(--text-color);
|
||||
opacity: 0.7;
|
||||
padding: $spacing-lg;
|
||||
}
|
||||
|
||||
// Theme toggle (shared with admin)
|
||||
.theme-section {
|
||||
position: fixed;
|
||||
top: $spacing-lg;
|
||||
right: $spacing-lg;
|
||||
z-index: 100;
|
||||
|
||||
@include mobile {
|
||||
position: static;
|
||||
margin-bottom: $spacing-lg;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
116
src/scss/pages/_privacy.scss
Normal file
116
src/scss/pages/_privacy.scss
Normal file
|
@ -0,0 +1,116 @@
|
|||
// Import variables and mixins
|
||||
@use '../variables' as *;
|
||||
@use '../mixins' as *;
|
||||
|
||||
// Privacy page specific styles
|
||||
.privacy-container {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: $spacing-md;
|
||||
}
|
||||
|
||||
.privacy-header {
|
||||
text-align: center;
|
||||
margin-bottom: $spacing-xl;
|
||||
|
||||
&__title-wrapper {
|
||||
position: relative;
|
||||
margin-bottom: $spacing-md;
|
||||
}
|
||||
|
||||
&__title-content {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__theme-toggle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
background: var(--card-bg);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
@include flex-center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px var(--shadow);
|
||||
|
||||
.theme-icon {
|
||||
font-size: $font-size-lg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.privacy-content {
|
||||
@include card($spacing-lg);
|
||||
line-height: 1.6;
|
||||
|
||||
h2 {
|
||||
color: var(--text-color);
|
||||
margin-top: $spacing-lg;
|
||||
margin-bottom: $spacing-md;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: $spacing-sm;
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: $spacing-md 0;
|
||||
padding-left: $spacing-lg;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: $spacing-xs;
|
||||
}
|
||||
|
||||
// Regular links within content (higher specificity than base styles)
|
||||
a:not(.back-link) {
|
||||
color: var(--link-color);
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: var(--link-hover);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
// Back-link button using shared mixin
|
||||
.back-link {
|
||||
@include back-link-styles;
|
||||
}
|
||||
}
|
||||
|
||||
.effective-date {
|
||||
color: var(--text-secondary);
|
||||
font-size: $font-size-sm;
|
||||
text-align: center;
|
||||
margin-bottom: $spacing-lg;
|
||||
}
|
||||
|
||||
.contact-info {
|
||||
background: var(--bg-secondary);
|
||||
padding: $spacing-md;
|
||||
border-radius: $border-radius-sm;
|
||||
margin-top: $spacing-md;
|
||||
border: 1px solid var(--border-color);
|
||||
|
||||
a {
|
||||
color: var(--link-color);
|
||||
text-decoration: underline;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
color: var(--link-hover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global back-link styling (for cases where it's used outside .privacy-content)
|
||||
a.back-link {
|
||||
@include back-link-styles;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue