Compare commits
1 Commits
98
...
chat-overh
| Author | SHA1 | Date | |
|---|---|---|---|
| f803ec27a5 |
@@ -1,9 +1,9 @@
|
|||||||
POSTGRES_USER=f0ckm
|
POSTGRES_USER=f0ckm
|
||||||
POSTGRES_DB=f0ckm
|
POSTGRES_DB=f0ckm
|
||||||
POSTGRES_PASSWORD=f0ckm
|
POSTGRES_PASSWORD=f0ckm
|
||||||
# --- Nginx & Tor Profiles (Optional) ---
|
# --- Nginx & Let's Encrypt Configuration (Optional) ---
|
||||||
# Set to 'f0ckm-nginx' for Nginx, 'tor' for Tor hidden service, or 'f0ckm-nginx,tor' for both
|
# Set to 'f0ckm-nginx' to enable the Nginx & Let's Encrypt proxy stack
|
||||||
# COMPOSE_PROFILES=tor
|
# COMPOSE_PROFILES=f0ckm-nginx
|
||||||
#
|
#
|
||||||
# VIRTUAL_HOST=yourdomain.com
|
# VIRTUAL_HOST=yourdomain.com
|
||||||
# LETSENCRYPT_HOST=yourdomain.com
|
# LETSENCRYPT_HOST=yourdomain.com
|
||||||
|
|||||||
20
README.md
20
README.md
@@ -78,23 +78,3 @@ now visit http://localhost:1337 in your browser, you can develop without needing
|
|||||||
## NGINX
|
## NGINX
|
||||||
|
|
||||||
uncomment in .env # COMPOSE_PROFILES=f0ckm-nginx to enable nginx proxy with automatic lets encrypt.
|
uncomment in .env # COMPOSE_PROFILES=f0ckm-nginx to enable nginx proxy with automatic lets encrypt.
|
||||||
|
|
||||||
## Tor Hidden Service (.onion)
|
|
||||||
|
|
||||||
Tor is bundled in `docker-compose.yml` and preconfigured as long as COMPOSE_PROFILES=f0ckm-tor is set with a hidden service pointing to the application (`f0ckm:1337`).
|
|
||||||
|
|
||||||
When running `docker compose up -d`, Tor automatically generates a `.onion` address for your node. You can find your onion address by running:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat ./f0ckm-data/tor/f0ckm_hs/hostname
|
|
||||||
```
|
|
||||||
|
|
||||||
### Automatic Onion-Location Header
|
|
||||||
|
|
||||||
To advertise your `.onion` address to Tor Browser users visiting your clearnet site, add your `.onion` address to `config.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
"main": {
|
|
||||||
"onion": "http://yourgeneratedaddress.onion"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|||||||
11
build.bash
11
build.bash
@@ -16,7 +16,7 @@ fi
|
|||||||
# Parse arguments
|
# Parse arguments
|
||||||
TARGET="master"
|
TARGET="master"
|
||||||
CACHE_FLAG=""
|
CACHE_FLAG=""
|
||||||
CUSTOM_BRANCH=""
|
BRANCH_TARGET=""
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case $arg in
|
case $arg in
|
||||||
@@ -27,8 +27,8 @@ for arg in "$@"; do
|
|||||||
CACHE_FLAG="--no-cache"
|
CACHE_FLAG="--no-cache"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
if [ -z "$CUSTOM_BRANCH" ]; then
|
if [ -z "$BRANCH_TARGET" ]; then
|
||||||
CUSTOM_BRANCH=$arg
|
BRANCH_TARGET=$arg
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
@@ -43,10 +43,7 @@ fi
|
|||||||
# Set Image name and branch defaults
|
# Set Image name and branch defaults
|
||||||
IMAGE_NAME="f0ckm"
|
IMAGE_NAME="f0ckm"
|
||||||
|
|
||||||
if [ -n "$CUSTOM_BRANCH" ]; then
|
if [ "$TARGET" == "master" ]; then
|
||||||
BRANCH_TARGET="$CUSTOM_BRANCH"
|
|
||||||
echo "--- CUSTOM BRANCH DEPLOYMENT (${BRANCH_TARGET}) ---"
|
|
||||||
elif [ "$TARGET" == "master" ]; then
|
|
||||||
echo "--- PRODUCTION DEPLOYMENT (master) ---"
|
echo "--- PRODUCTION DEPLOYMENT (master) ---"
|
||||||
BRANCH_TARGET="master"
|
BRANCH_TARGET="master"
|
||||||
elif [ "$TARGET" == "stg" ]; then
|
elif [ "$TARGET" == "stg" ]; then
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
# Tor configuration for f0ckm
|
|
||||||
|
|
||||||
# Enable SOCKS proxy for internal container & external connections
|
|
||||||
SocksPort 0.0.0.0:9050
|
|
||||||
|
|
||||||
# Tor Hidden Service (v3 Onion Service) configuration
|
|
||||||
# Tor will automatically generate private keys and hostnames in /var/lib/tor/f0ckm_hs/
|
|
||||||
HiddenServiceDir /var/lib/tor/f0ckm_hs/
|
|
||||||
HiddenServicePort 80 f0ckm:1337
|
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
"domain": "example.com",
|
"domain": "example.com",
|
||||||
"regex": "example\\.com"
|
"regex": "example\\.com"
|
||||||
},
|
},
|
||||||
"onion": "http://your-onion-address.onion",
|
|
||||||
"socks": "socks5://127.0.0.1:9050",
|
"socks": "socks5://127.0.0.1:9050",
|
||||||
"mail": "admin@example.com",
|
"mail": "admin@example.com",
|
||||||
"maxfilesize": 104857600,
|
"maxfilesize": 104857600,
|
||||||
@@ -62,7 +61,6 @@
|
|||||||
"show_koepfe": false,
|
"show_koepfe": false,
|
||||||
"hide_sidebar_default": true,
|
"hide_sidebar_default": true,
|
||||||
"koepfe": [],
|
"koepfe": [],
|
||||||
"enable_tor_hs": true,
|
|
||||||
"enable_global_chat": true,
|
"enable_global_chat": true,
|
||||||
"enable_danmaku": true,
|
"enable_danmaku": true,
|
||||||
"private_messages": true,
|
"private_messages": true,
|
||||||
@@ -134,8 +132,6 @@
|
|||||||
"open_registration_require_mail_andor_token": false,
|
"open_registration_require_mail_andor_token": false,
|
||||||
"private_society": false,
|
"private_society": false,
|
||||||
"private_society_gate": "cloudflare",
|
"private_society_gate": "cloudflare",
|
||||||
"private_society_gate_location": "Frankfurt",
|
|
||||||
"private_society_gate_template": "_gate_template",
|
|
||||||
"public_nsfw": false,
|
"public_nsfw": false,
|
||||||
"paths": {
|
"paths": {
|
||||||
"images": "/b",
|
"images": "/b",
|
||||||
|
|||||||
@@ -49,14 +49,9 @@ services:
|
|||||||
tor:
|
tor:
|
||||||
image: dockurr/tor
|
image: dockurr/tor
|
||||||
container_name: tor
|
container_name: tor
|
||||||
profiles:
|
|
||||||
- tor
|
|
||||||
ports:
|
ports:
|
||||||
- "9050:9050"
|
- "9050:9050"
|
||||||
- "8118:8118"
|
- "8118:8118"
|
||||||
volumes:
|
|
||||||
- ./config/tor:/etc/tor:ro
|
|
||||||
- ./f0ckm-data/tor:/var/lib/tor:Z
|
|
||||||
networks:
|
networks:
|
||||||
- f0ckm-net
|
- f0ckm-net
|
||||||
restart: always
|
restart: always
|
||||||
|
|||||||
@@ -1,357 +0,0 @@
|
|||||||
git pu/* 98.css framework - Scoped exclusively for f0ck98 theme */
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Pixelated MS Sans Serif";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
src: url("/s/fonts/ms_sans_serif.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: "Pixelated MS Sans Serif";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
src: url("/s/fonts/ms_sans_serif_bold.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] {
|
|
||||||
--w98-silver: #c0c0c0;
|
|
||||||
--w98-teal: #008080;
|
|
||||||
--w98-navy: #000080;
|
|
||||||
--w98-navy-gradient: linear-gradient(90deg, navy, #1084d0);
|
|
||||||
--w98-dark-shadow: #0a0a0a;
|
|
||||||
--w98-light-highlight: #ffffff;
|
|
||||||
--w98-mid-gray: grey;
|
|
||||||
--w98-light-gray: #dfdfdf;
|
|
||||||
|
|
||||||
color: #000;
|
|
||||||
font-family: "Pixelated MS Sans Serif", Arial, sans-serif;
|
|
||||||
font-size: 11px;
|
|
||||||
background-color: var(--w98-silver) !important;
|
|
||||||
background: var(--w98-silver) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] body {
|
|
||||||
color: #000;
|
|
||||||
font-family: "Pixelated MS Sans Serif", Arial, sans-serif;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar,
|
|
||||||
html[theme="f0ck98"] .window,
|
|
||||||
html[theme="f0ck98"] button,
|
|
||||||
html[theme="f0ck98"] input,
|
|
||||||
html[theme="f0ck98"] label,
|
|
||||||
html[theme="f0ck98"] legend,
|
|
||||||
html[theme="f0ck98"] li[role=tab],
|
|
||||||
html[theme="f0ck98"] option,
|
|
||||||
html[theme="f0ck98"] select,
|
|
||||||
html[theme="f0ck98"] table,
|
|
||||||
html[theme="f0ck98"] textarea,
|
|
||||||
html[theme="f0ck98"] ul.tree-view {
|
|
||||||
-webkit-font-smoothing: none;
|
|
||||||
font-family: "Pixelated MS Sans Serif", Arial, sans-serif;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] u {
|
|
||||||
border-bottom: 0.5px solid #222;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Windows 98 Buttons */
|
|
||||||
html[theme="f0ck98"] button,
|
|
||||||
html[theme="f0ck98"] input[type=reset],
|
|
||||||
html[theme="f0ck98"] input[type=submit],
|
|
||||||
html[theme="f0ck98"] .btn {
|
|
||||||
background: silver;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: #000;
|
|
||||||
min-height: 23px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] button.default,
|
|
||||||
html[theme="f0ck98"] input[type=reset].default,
|
|
||||||
html[theme="f0ck98"] input[type=submit].default,
|
|
||||||
html[theme="f0ck98"] .btn.default {
|
|
||||||
box-shadow: inset -2px -2px #0a0a0a, inset 1px 1px #0a0a0a, inset 2px 2px #fff, inset -3px -3px grey, inset 3px 3px #dfdfdf;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .vertical-bar {
|
|
||||||
background: silver;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
height: 20px;
|
|
||||||
width: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] button:not(:disabled):active,
|
|
||||||
html[theme="f0ck98"] input[type=reset]:not(:disabled):active,
|
|
||||||
html[theme="f0ck98"] input[type=submit]:not(:disabled):active,
|
|
||||||
html[theme="f0ck98"] .btn:not(:disabled):active {
|
|
||||||
box-shadow: inset -1px -1px #fff, inset 1px 1px #0a0a0a, inset -2px -2px #dfdfdf, inset 2px 2px grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] button.default:not(:disabled):active,
|
|
||||||
html[theme="f0ck98"] input[type=reset].default:not(:disabled):active,
|
|
||||||
html[theme="f0ck98"] input[type=submit].default:not(:disabled):active {
|
|
||||||
box-shadow: inset 2px 2px #0a0a0a, inset -1px -1px #0a0a0a, inset -2px -2px #fff, inset 3px 3px grey, inset -3px -3px #dfdfdf;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] button:focus,
|
|
||||||
html[theme="f0ck98"] input[type=reset]:focus,
|
|
||||||
html[theme="f0ck98"] input[type=submit]:focus,
|
|
||||||
html[theme="f0ck98"] .btn:focus {
|
|
||||||
outline: 1px dotted #000;
|
|
||||||
outline-offset: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] :disabled,
|
|
||||||
html[theme="f0ck98"] :disabled+label,
|
|
||||||
html[theme="f0ck98"] input[readonly],
|
|
||||||
html[theme="f0ck98"] input[readonly]+label {
|
|
||||||
color: grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] :disabled+label,
|
|
||||||
html[theme="f0ck98"] button:disabled,
|
|
||||||
html[theme="f0ck98"] input[type=reset]:disabled,
|
|
||||||
html[theme="f0ck98"] input[type=submit]:disabled,
|
|
||||||
html[theme="f0ck98"] .btn:disabled {
|
|
||||||
text-shadow: 1px 1px 0 #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Window Shell & Title Bars */
|
|
||||||
html[theme="f0ck98"] .window {
|
|
||||||
background: silver;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #dfdfdf, inset -2px -2px grey, inset 2px 2px #fff;
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar {
|
|
||||||
align-items: center;
|
|
||||||
background: linear-gradient(90deg, navy, #1084d0);
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 3px 2px 3px 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar.inactive {
|
|
||||||
background: linear-gradient(90deg, grey, #b5b5b5);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar-text {
|
|
||||||
color: #fff;
|
|
||||||
font-weight: 700;
|
|
||||||
letter-spacing: 0;
|
|
||||||
margin-right: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar-controls {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button {
|
|
||||||
display: block;
|
|
||||||
min-height: 14px;
|
|
||||||
min-width: 16px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button:active {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button[aria-label=Minimize],
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button[aria-label].minimize {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='6' height='2' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23000' d='M0 0h6v2H0z'/%3E%3C/svg%3E");
|
|
||||||
background-position: bottom 3px left 4px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button[aria-label=Maximize],
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button[aria-label].maximize {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='9' height='9' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M9 0H0v9h9V0zM8 2H1v6h7V2z' fill='%23000'/%3E%3C/svg%3E");
|
|
||||||
background-position: top 2px left 3px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button[aria-label=Close],
|
|
||||||
html[theme="f0ck98"] .title-bar-controls button[aria-label].close {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='8' height='7' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h2v1h1v1h2V1h1V0h2v1H7v1H6v1H5v1h1v1h1v1h1v1H6V6H5V5H3v1H2v1H0V6h1V5h1V4h1V3H2V2H1V1H0V0z' fill='%23000'/%3E%3C/svg%3E");
|
|
||||||
background-position: top 3px left 4px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Status Bar */
|
|
||||||
html[theme="f0ck98"] .status-bar {
|
|
||||||
gap: 1px;
|
|
||||||
display: flex;
|
|
||||||
margin: 0 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .status-bar-field {
|
|
||||||
box-shadow: inset -1px -1px #dfdfdf, inset 1px 1px grey;
|
|
||||||
flex-grow: 1;
|
|
||||||
margin: 0;
|
|
||||||
padding: 2px 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .window-body {
|
|
||||||
margin: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] fieldset {
|
|
||||||
border-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='5' height='5' fill='gray' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h5v5H0V2h2v1h1V2H0' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M0 0h4v4H0V1h1v2h2V1H0'/%3E%3C/svg%3E") 2;
|
|
||||||
margin: 0;
|
|
||||||
padding: 10px;
|
|
||||||
padding-block-start: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] legend {
|
|
||||||
background: silver;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .field-row {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] [class^=field-row]+[class^=field-row] {
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .field-row>*+* {
|
|
||||||
margin-left: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] label {
|
|
||||||
align-items: center;
|
|
||||||
display: inline-flex;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Inputs & Textarea */
|
|
||||||
html[theme="f0ck98"] input[type=email],
|
|
||||||
html[theme="f0ck98"] input[type=number],
|
|
||||||
html[theme="f0ck98"] input[type=password],
|
|
||||||
html[theme="f0ck98"] input[type=search],
|
|
||||||
html[theme="f0ck98"] input[type=tel],
|
|
||||||
html[theme="f0ck98"] input[type=text],
|
|
||||||
html[theme="f0ck98"] input[type=url],
|
|
||||||
html[theme="f0ck98"] select,
|
|
||||||
html[theme="f0ck98"] textarea {
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: inset -1px -1px #fff, inset 1px 1px grey, inset -2px -2px #dfdfdf, inset 2px 2px #0a0a0a;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 3px 4px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] input[type=email]:disabled,
|
|
||||||
html[theme="f0ck98"] input[type=number]:disabled,
|
|
||||||
html[theme="f0ck98"] input[type=password]:disabled,
|
|
||||||
html[theme="f0ck98"] input[type=search]:disabled,
|
|
||||||
html[theme="f0ck98"] input[type=tel]:disabled,
|
|
||||||
html[theme="f0ck98"] input[type=text]:disabled,
|
|
||||||
html[theme="f0ck98"] input[type=url]:disabled,
|
|
||||||
html[theme="f0ck98"] textarea:disabled {
|
|
||||||
background-color: silver;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] select {
|
|
||||||
appearance: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 0H0v16h1V1h14V0z' fill='%23DFDFDF'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M2 1H1v14h1V2h12V1H2z' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M16 17H0v-1h15V0h1v17z' fill='%23000'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 1h-1v14H1v1h14V1z' fill='gray'/%3E%3Cpath fill='silver' d='M2 2h12v13H2z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 6H4v1h1v1h1v1h1v1h1V9h1V8h1V7h1V6z' fill='%23000'/%3E%3C/svg%3E");
|
|
||||||
background-position: top 2px right 2px;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
border-radius: 0;
|
|
||||||
padding-right: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] input[type=email]:focus,
|
|
||||||
html[theme="f0ck98"] input[type=number]:focus,
|
|
||||||
html[theme="f0ck98"] input[type=password]:focus,
|
|
||||||
html[theme="f0ck98"] input[type=search]:focus,
|
|
||||||
html[theme="f0ck98"] input[type=tel]:focus,
|
|
||||||
html[theme="f0ck98"] input[type=text]:focus,
|
|
||||||
html[theme="f0ck98"] input[type=url]:focus,
|
|
||||||
html[theme="f0ck98"] select:focus,
|
|
||||||
html[theme="f0ck98"] textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] a {
|
|
||||||
color: #000080;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] a:focus {
|
|
||||||
outline: 1px dotted #000080;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .sunken-panel {
|
|
||||||
background-color: #fff;
|
|
||||||
box-shadow: inset -1px -1px #fff, inset 1px 1px grey, inset -2px -2px #dfdfdf, inset 2px 2px #0a0a0a;
|
|
||||||
box-sizing: border-box;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Scoped Scrollbars for f0ck98 */
|
|
||||||
html[theme="f0ck98"] ::-webkit-scrollbar {
|
|
||||||
width: 16px;
|
|
||||||
height: 17px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] ::-webkit-scrollbar-corner {
|
|
||||||
background: #dfdfdf;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] ::-webkit-scrollbar-track {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='2' height='2' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M1 0H0v1h1v1h1V1H1V0z' fill='silver'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M2 0H1v1H0v1h1V1h1V0z' fill='%23fff'/%3E%3C/svg%3E");
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] ::-webkit-scrollbar-thumb {
|
|
||||||
background-color: #dfdfdf;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] ::-webkit-scrollbar-button:vertical:start {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 0H0v16h1V1h14V0z' fill='%23DFDFDF'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M2 1H1v14h1V2h12V1H2z' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M16 17H0v-1h15V0h1v17z' fill='%23000'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 1h-1v14H1v1h14V1z' fill='gray'/%3E%3Cpath fill='silver' d='M2 2h12v13H2z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8 6H7v1H6v1H5v1H4v1h7V9h-1V8H9V7H8V6z' fill='%23000'/%3E%3C/svg%3E");
|
|
||||||
height: 17px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] ::-webkit-scrollbar-button:vertical:end {
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='16' height='17' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 0H0v16h1V1h14V0z' fill='%23DFDFDF'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M2 1H1v14h1V2h12V1H2z' fill='%23fff'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M16 17H0v-1h15V0h1v17z' fill='%23000'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M15 1h-1v14H1v1h14V1z' fill='gray'/%3E%3Cpath fill='silver' d='M2 2h12v13H2z'/%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M11 6H4v1h1v1h1v1h1v1h1V9h1V8h1V7h1V6z' fill='%23000'/%3E%3C/svg%3E");
|
|
||||||
height: 17px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .sidebar-activity .comment-content.has-overflow:not(.expanded) .comment-content-inner::after {
|
|
||||||
background: linear-gradient(to bottom, transparent, rgb(192, 192, 192));
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .item-sidebar-right, .index-sidebar-right, .global-sidebar-right {
|
|
||||||
box-shadow: none!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .sidebar-activity .read-more-btn, .sidebar-activity .see-less-btn {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .modal-content p {
|
|
||||||
color: var(--black);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .drop-zone {
|
|
||||||
border: 2px dashed rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
@@ -1237,7 +1237,7 @@ html[theme='95'] .item-main-content .metadata {
|
|||||||
|
|
||||||
.notif-count {
|
.notif-count {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0px;
|
||||||
right: -5px;
|
right: -5px;
|
||||||
background: var(--badge-nsfw);
|
background: var(--badge-nsfw);
|
||||||
color: white;
|
color: white;
|
||||||
@@ -1650,10 +1650,6 @@ body.layout-modern .global-sidebar-right {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.sidebar-right-hidden #sidebar-drag-zone {
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Always show the handle on touch devices (no hover state available) */
|
/* Always show the handle on touch devices (no hover state available) */
|
||||||
@media (pointer: coarse) {
|
@media (pointer: coarse) {
|
||||||
#sidebar-drag-zone {
|
#sidebar-drag-zone {
|
||||||
@@ -1988,7 +1984,7 @@ body.sidebar-right-hidden #sidebar-drag-zone {
|
|||||||
right: 0;
|
right: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background: linear-gradient(to bottom, transparent, var(--bg));
|
background: linear-gradient(to bottom, transparent, rgba(28, 27, 27, 0.8));
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3878,7 +3874,7 @@ html[theme="f0ck95d"] ._204863 {
|
|||||||
line-height: 1.7;
|
line-height: 1.7;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0px 5px;
|
padding: 2px 4px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3897,6 +3893,8 @@ html[theme="f0ck95d"] ._204863 .location::before {
|
|||||||
background-size: contain;
|
background-size: contain;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-position: center;
|
background-position: center;
|
||||||
|
image-rendering: pixelated;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4028,338 +4026,11 @@ html[theme="f0ck95d"] .admin-search button {
|
|||||||
color: black;
|
color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
html[theme="f0ck98"] .embed-responsive.embed-responsive-16by9 {
|
html[theme="f0ck95d"] .embed-responsive.embed-responsive-16by9 {
|
||||||
background: black;
|
background: black;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================
|
|
||||||
f0ck98 Theme - Windows 98 Authentic Retro
|
|
||||||
========================================== */
|
|
||||||
|
|
||||||
html[theme="f0ck98"] {
|
|
||||||
--accent: #000080;
|
|
||||||
--bg: silver;
|
|
||||||
--black: #000000;
|
|
||||||
--white: #ffffff;
|
|
||||||
--gray: silver;
|
|
||||||
--nav-bg: silver;
|
|
||||||
--nav-brand-border: outset 2px silver;
|
|
||||||
--nav-brand-bg: silver;
|
|
||||||
--navigation-links-bg: silver;
|
|
||||||
--navigation-links-background-linear-gradient: none;
|
|
||||||
--navigation-links-border-color: #808080;
|
|
||||||
--navigation-links-box-shadow: none;
|
|
||||||
--nav-link-background-linear-gradient: none;
|
|
||||||
--nav-link-box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
--nav-link-hover-bg: #000080;
|
|
||||||
--nav-border-color: #808080;
|
|
||||||
--dropdown-bg: silver;
|
|
||||||
--dropdown-item-hover: #000080;
|
|
||||||
--nav-brand-font: 'Pixelated MS Sans Serif', Arial, sans-serif;
|
|
||||||
--font: 'Pixelated MS Sans Serif', Arial, sans-serif;
|
|
||||||
--pagination-background: silver;
|
|
||||||
--pagination-box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
--pagination-anchor-box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
--pagination-background-hover: #000080;
|
|
||||||
--pagination-border-color: #808080;
|
|
||||||
--metadata-bg: silver;
|
|
||||||
--badge-bg: silver;
|
|
||||||
--posts-meta-bg: rgba(192, 192, 192, 0.95);
|
|
||||||
--badge-sfw: #008080;
|
|
||||||
--badge-nsfw: #a72828;
|
|
||||||
--badge-nsfl: #660000;
|
|
||||||
--badge-tag: #808080;
|
|
||||||
--scrollbar-color: silver;
|
|
||||||
--scroller-bg: silver;
|
|
||||||
--footbar-color: #000000;
|
|
||||||
--loading-indicator-color: #000000;
|
|
||||||
--img-border-width: 0;
|
|
||||||
--img-border-color: #808080;
|
|
||||||
--bg-gradient: none;
|
|
||||||
--motd-bg: silver;
|
|
||||||
--comment-bg: #ffffff;
|
|
||||||
background-color: silver !important;
|
|
||||||
background: silver !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] body {
|
|
||||||
color: #000000;
|
|
||||||
font-family: 'Pixelated MS Sans Serif', Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] a {
|
|
||||||
color: #000080;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] a:hover {
|
|
||||||
color: #1084d0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header & Titlebar */
|
|
||||||
html[theme="f0ck98"] ._204863 {
|
|
||||||
background: linear-gradient(90deg, #000080, #1084d0);
|
|
||||||
color: #ffffff;
|
|
||||||
width: 100%;
|
|
||||||
border: 2px outset silver;
|
|
||||||
line-height: 1.7;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 2px 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: 'Pixelated MS Sans Serif', Arial, sans-serif;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] ._204863 .location {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] ._204863 .location::before {
|
|
||||||
content: "";
|
|
||||||
display: inline-block;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
background-image: var(--favicon-url, url('/s/img/favicon.gif'));
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
image-rendering: pixelated;
|
|
||||||
image-rendering: crisp-edges;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] ._204863::after {
|
|
||||||
content: " _ ❐ ✕ ";
|
|
||||||
font-family: 'Pixelated MS Sans Serif', Arial, sans-serif;
|
|
||||||
font-size: 10px;
|
|
||||||
font-weight: bold;
|
|
||||||
background: silver;
|
|
||||||
color: #000000;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
padding: 0 4px;
|
|
||||||
height: 14px;
|
|
||||||
line-height: 12px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-left: auto;
|
|
||||||
letter-spacing: 1px;
|
|
||||||
cursor: default;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] #tags .badge>a:first-child {
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .item_title {
|
|
||||||
background: silver;
|
|
||||||
color: #000000;
|
|
||||||
border-left: 2px outset silver;
|
|
||||||
border-right: 2px outset silver;
|
|
||||||
box-shadow: inset -1px -1px #dfdfdf, inset 1px 1px grey;
|
|
||||||
font-family: 'Pixelated MS Sans Serif', Arial, sans-serif;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: normal;
|
|
||||||
padding: 3px 8px;
|
|
||||||
-webkit-font-smoothing: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navbar */
|
|
||||||
html[theme="f0ck98"] .nav,
|
|
||||||
html[theme="f0ck98"] .navbar {
|
|
||||||
background: silver !important;
|
|
||||||
border-bottom: 2px outset #fff;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #dfdfdf, inset -2px -2px grey, inset 2px 2px #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .nav-link {
|
|
||||||
color: #000000 !important;
|
|
||||||
background: silver;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .nav-link:hover,
|
|
||||||
html[theme="f0ck98"] .nav-link.active {
|
|
||||||
background: #000080 !important;
|
|
||||||
color: #ffffff !important;
|
|
||||||
box-shadow: inset -1px -1px #fff, inset 1px 1px #0a0a0a, inset -2px -2px #dfdfdf, inset 2px 2px grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Sidebars & Window Containers */
|
|
||||||
html[theme="f0ck98"] .global-sidebar-left,
|
|
||||||
html[theme="f0ck98"] .global-sidebar-right,
|
|
||||||
html[theme="f0ck98"] .about,
|
|
||||||
html[theme="f0ck98"] .modal-content,
|
|
||||||
html[theme="f0ck98"] .card {
|
|
||||||
background: silver;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #dfdfdf, inset -2px -2px grey, inset 2px 2px #fff;
|
|
||||||
color: #000000;
|
|
||||||
padding: 6px;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Item Grid Tiles */
|
|
||||||
html[theme="f0ck98"] div.posts > a:not(.notif-item) {
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
background-color: silver;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] div.posts > a:not(.notif-item):hover,
|
|
||||||
html[theme="f0ck98"] div.posts > a:not(.notif-item):focus-visible {
|
|
||||||
box-shadow: inset -1px -1px #fff, inset 1px 1px #0a0a0a, inset -2px -2px #dfdfdf, inset 2px 2px grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] div.posts > a:not(.notif-item)::after {
|
|
||||||
color: #000000;
|
|
||||||
background: silver;
|
|
||||||
border-top: 1px solid grey;
|
|
||||||
font-family: 'Pixelated MS Sans Serif', Arial, sans-serif;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Comments Section */
|
|
||||||
html[theme="f0ck98"] .comment-item,
|
|
||||||
html[theme="f0ck98"] .comments-container {
|
|
||||||
background: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
box-shadow: inset -1px -1px #fff, inset 1px 1px grey, inset -2px -2px #dfdfdf, inset 2px 2px #0a0a0a;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .comment-author {
|
|
||||||
color: #000080 !important;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .comment-content {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .comment-children {
|
|
||||||
border-left: 1px dotted grey;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .comment-actions button,
|
|
||||||
html[theme="f0ck98"] .report-comment-btn,
|
|
||||||
html[theme="f0ck98"] .reply-btn,
|
|
||||||
html[theme="f0ck98"] .quote-btn {
|
|
||||||
color: #000000 !important;
|
|
||||||
opacity: 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .comment-actions button svg,
|
|
||||||
html[theme="f0ck98"] .report-comment-btn svg,
|
|
||||||
html[theme="f0ck98"] .reply-btn i,
|
|
||||||
html[theme="f0ck98"] .quote-btn i,
|
|
||||||
html[theme="f0ck98"] .report-comment-btn i {
|
|
||||||
color: #000000 !important;
|
|
||||||
fill: #000000 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .report-comment-btn:hover,
|
|
||||||
html[theme="f0ck98"] .report-comment-btn:hover svg,
|
|
||||||
html[theme="f0ck98"] .report-comment-btn:hover i {
|
|
||||||
color: #a72828 !important;
|
|
||||||
fill: #a72828 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .reply-btn:hover,
|
|
||||||
html[theme="f0ck98"] .quote-btn:hover,
|
|
||||||
html[theme="f0ck98"] .reply-btn:hover i,
|
|
||||||
html[theme="f0ck98"] .quote-btn:hover i {
|
|
||||||
color: #000080 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation Controls (.steuerung) */
|
|
||||||
html[theme="f0ck98"] .steuerung a,
|
|
||||||
html[theme="f0ck98"] .steuerung button,
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon a,
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon button {
|
|
||||||
background: silver !important;
|
|
||||||
color: #000000 !important;
|
|
||||||
border: none !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf !important;
|
|
||||||
box-sizing: border-box !important;
|
|
||||||
display: inline-flex !important;
|
|
||||||
align-items: center !important;
|
|
||||||
justify-content: center !important;
|
|
||||||
min-height: 23px !important;
|
|
||||||
min-width: 28px !important;
|
|
||||||
width: auto !important;
|
|
||||||
padding: 2px 8px !important;
|
|
||||||
text-decoration: none !important;
|
|
||||||
cursor: pointer !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .steuerung a:active,
|
|
||||||
html[theme="f0ck98"] .steuerung button:active,
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon a:active,
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon button:active {
|
|
||||||
box-shadow: inset -1px -1px #fff, inset 1px 1px #0a0a0a, inset -2px -2px #dfdfdf, inset 2px 2px grey !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .steuerung a:hover,
|
|
||||||
html[theme="f0ck98"] .steuerung button:hover,
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon a:hover,
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon button:hover {
|
|
||||||
color: #000080 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon .fa-solid {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon a:hover .fa-solid,
|
|
||||||
html[theme="f0ck98"] .steuerung.steuerung-icon button:hover .fa-solid {
|
|
||||||
color: #000080;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Badges & Tags */
|
|
||||||
html[theme="f0ck98"] .badge,
|
|
||||||
html[theme="f0ck98"] .badge-light,
|
|
||||||
html[theme="f0ck98"] .badge-dark,
|
|
||||||
html[theme="f0ck98"] .badge-success,
|
|
||||||
html[theme="f0ck98"] .badge-danger {
|
|
||||||
background: silver;
|
|
||||||
color: #000000;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #fff, inset -2px -2px grey, inset 2px 2px #dfdfdf;
|
|
||||||
border-radius: 0;
|
|
||||||
text-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dropdown Menu */
|
|
||||||
html[theme="f0ck98"] .dropdown-menu {
|
|
||||||
background: silver;
|
|
||||||
box-shadow: inset -1px -1px #0a0a0a, inset 1px 1px #dfdfdf, inset -2px -2px grey, inset 2px 2px #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .dropdown-item {
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[theme="f0ck98"] .dropdown-item:hover {
|
|
||||||
background: #000080;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Footer / Status Bar */
|
|
||||||
html[theme="f0ck98"] footer,
|
|
||||||
html[theme="f0ck98"] .footbar {
|
|
||||||
background: silver;
|
|
||||||
color: #000000;
|
|
||||||
box-shadow: inset -1px -1px #dfdfdf, inset 1px 1px grey;
|
|
||||||
padding: 3px 6px;
|
|
||||||
font-family: 'Pixelated MS Sans Serif', Arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* fullscreen */
|
/* fullscreen */
|
||||||
html[res="fullscreen"] ._204863 {
|
html[res="fullscreen"] ._204863 {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
@@ -4952,17 +4623,16 @@ span.placeholder {
|
|||||||
transition: right 0.3s ease-in-out;
|
transition: right 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Sidebar hidden — reclaim the full width */
|
||||||
|
body.sidebar-right-hidden .pagination-container-fluid {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Modern layout: wider sidebar needs wider offset */
|
/* Modern layout: wider sidebar needs wider offset */
|
||||||
body.layout-modern .pagination-container-fluid {
|
body.layout-modern .pagination-container-fluid {
|
||||||
right: 400px;
|
right: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar hidden — reclaim the full width */
|
|
||||||
body.sidebar-right-hidden .pagination-container-fluid,
|
|
||||||
body.layout-modern.sidebar-right-hidden .pagination-container-fluid {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bottom-pagination.fixed-pagination {
|
.bottom-pagination.fixed-pagination {
|
||||||
margin: 10px auto;
|
margin: 10px auto;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
@@ -4971,6 +4641,7 @@ body.layout-modern.sidebar-right-hidden .pagination-container-fluid {
|
|||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(12px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4);
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
max-width: fit-content;
|
max-width: fit-content;
|
||||||
}
|
}
|
||||||
@@ -10361,6 +10032,7 @@ html[theme="orange"] .image-brand {
|
|||||||
.pagewrapper:has(.index-layout-wrapper),
|
.pagewrapper:has(.index-layout-wrapper),
|
||||||
.pagewrapper:has(.item-layout-container),
|
.pagewrapper:has(.item-layout-container),
|
||||||
.pagewrapper:has(.meme-layout-wrapper),
|
.pagewrapper:has(.meme-layout-wrapper),
|
||||||
|
.pagewrapper:has(.chat-page),
|
||||||
body.private-gate-active .pagewrapper {
|
body.private-gate-active .pagewrapper {
|
||||||
padding-right: 0 !important;
|
padding-right: 0 !important;
|
||||||
}
|
}
|
||||||
@@ -10505,7 +10177,7 @@ ol {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
padding: 0 5px;
|
padding: 0 15px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
}
|
}
|
||||||
@@ -15033,6 +14705,7 @@ body.layout-modern .xd-score-wrapper {
|
|||||||
z-index: 950;
|
z-index: 950;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
font-family: var(--font, monospace);
|
font-family: var(--font, monospace);
|
||||||
@@ -15117,8 +14790,9 @@ body.scroller-active #gchat-reopen-bubble {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 40dvh;
|
height: 400px;
|
||||||
max-height: calc(100dvh - var(--navbar-h, 50px));
|
max-height: calc(100dvh - var(--navbar-h, 50px) - 15px);
|
||||||
|
min-height: 180px;
|
||||||
background: var(--bg, #1a1a1a);
|
background: var(--bg, #1a1a1a);
|
||||||
border: 1px solid var(--nav-border-color, rgba(255, 255, 255, 0.08));
|
border: 1px solid var(--nav-border-color, rgba(255, 255, 255, 0.08));
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.65);
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.65);
|
||||||
@@ -15303,22 +14977,12 @@ body.scroller-active #gchat-reopen-bubble {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gchat-topic {
|
|
||||||
padding: 5px 10px;
|
|
||||||
font-size: 0.78em;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--bg, #111);
|
|
||||||
background: var(--accent, #f2ef0b);
|
|
||||||
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
|
|
||||||
word-break: break-word;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
flex-shrink: 0;
|
|
||||||
opacity: 0.95;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#gchat-messages {
|
#gchat-messages {
|
||||||
flex: 1;
|
flex: 1 1 0%;
|
||||||
overflow-y: auto;
|
min-height: 0;
|
||||||
|
overflow-y: auto !important;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -15425,7 +15089,9 @@ body.scroller-active #gchat-reopen-bubble {
|
|||||||
|
|
||||||
/* Emoji images inside chat bubbles */
|
/* Emoji images inside chat bubbles */
|
||||||
.gchat-bubble .emoji {
|
.gchat-bubble .emoji {
|
||||||
height: 10.4em;
|
height: 1.4em;
|
||||||
|
max-height: 28px;
|
||||||
|
width: auto;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
@@ -15533,33 +15199,15 @@ body.scroller-active #gchat-reopen-bubble {
|
|||||||
transform: scale(0.92);
|
transform: scale(0.92);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Emoji picker grid (above input area, inside panel) */
|
|
||||||
#gchat-emoji-picker {
|
#gchat-emoji-picker {
|
||||||
display: none;
|
display: none;
|
||||||
grid-template-columns: repeat(6, 1fr);
|
flex-direction: column;
|
||||||
gap: 4px;
|
max-height: 220px;
|
||||||
padding: 8px;
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 140px;
|
|
||||||
background: var(--nav-bg, #111);
|
background: var(--nav-bg, #111);
|
||||||
border-top: 1px solid var(--nav-border-color, rgba(255, 255, 255, 0.08));
|
border-top: 1px solid var(--nav-border-color, rgba(255, 255, 255, 0.08));
|
||||||
scrollbar-width: thin;
|
scrollbar-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
#gchat-emoji-picker img {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
object-fit: contain;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 2px;
|
|
||||||
transition: background 0.1s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#gchat-emoji-picker img:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* :emoji inline autocomplete — portaled to body */
|
/* :emoji inline autocomplete — portaled to body */
|
||||||
#gchat-emoji-ac {
|
#gchat-emoji-ac {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -16072,14 +15720,319 @@ body.scroller-active #gchat-reopen-bubble {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gchat-item-card-meta {
|
/* ── Dedicated /chat Page ── */
|
||||||
font-size: 0.7rem;
|
html:has(.chat-page),
|
||||||
opacity: 0.5;
|
body:has(.chat-page) {
|
||||||
|
height: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body:has(.chat-page) .pagewrapper {
|
||||||
|
height: calc(100dvh - var(--navbar-h, 60px)) !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
transition: padding-right 0.3s ease-in-out !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1000px) {
|
||||||
|
body:has(.chat-page):not(.sidebar-right-hidden) .pagewrapper {
|
||||||
|
padding-right: 300px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body:has(.chat-page) footer,
|
||||||
|
body:has(.chat-page) .footer {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-chat-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
border-radius: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: none;
|
||||||
|
background: var(--bg, #1a1a1a);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel {
|
||||||
|
position: relative !important;
|
||||||
|
inset: auto !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
border: none !important;
|
||||||
|
border-radius: 0 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
transform: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel #gchat-resize-h,
|
||||||
|
.gchat-page-panel #gchat-resize-w,
|
||||||
|
.gchat-page-panel #gchat-resize-e,
|
||||||
|
.gchat-page-panel .gchat-header-btns {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Flex layout containers for chat body */
|
||||||
|
.gchat-body-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gchat-panel:not(.gchat-page-panel) .gchat-body-wrap {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-body-wrap {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-main-area {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
min-height: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel #gchat-messages {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 14px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-msg {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right-side user list in page mode */
|
||||||
|
.gchat-page-panel #gchat-online {
|
||||||
|
width: 250px;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 300px;
|
||||||
|
border-left: 1px solid var(--border-color, rgba(255, 255, 255, 0.1));
|
||||||
|
background: rgba(0, 0, 0, 0.25);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-header {
|
||||||
|
padding: 12px 14px 4px 14px;
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--accent, #f2ef0b);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-subhead {
|
||||||
|
padding: 2px 14px 10px 14px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.6;
|
||||||
|
border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-list {
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-user-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--fg, #e0e0e0);
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-user-item:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-user-avatar {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
flex-shrink: 0;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-user-avatar.guest-avatar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #aaa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-user-name {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 500;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.gchat-page-panel .gchat-body-wrap {
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex: 1 !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
height: 100% !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel #gchat-online {
|
||||||
|
width: 100% !important;
|
||||||
|
max-width: 100% !important;
|
||||||
|
height: 40px !important;
|
||||||
|
min-height: 40px !important;
|
||||||
|
max-height: 40px !important;
|
||||||
|
border-left: none !important;
|
||||||
|
border-bottom: 1px solid var(--border-color, rgba(255, 255, 255, 0.12)) !important;
|
||||||
|
order: -1 !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
align-items: center !important;
|
||||||
|
padding: 0 10px !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
background: rgba(0, 0, 0, 0.3) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-header {
|
||||||
|
padding: 0 !important;
|
||||||
|
font-size: 0.8rem !important;
|
||||||
|
white-space: nowrap !important;
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
gap: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-header span {
|
||||||
|
font-size: 0.78rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-subhead {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-list {
|
||||||
|
flex: 1 !important;
|
||||||
|
min-width: 0 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
overflow-x: auto !important;
|
||||||
|
overflow-y: hidden !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
padding: 0 4px !important;
|
||||||
|
align-items: center !important;
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
touch-action: pan-x !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-list::-webkit-scrollbar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-user-item {
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
padding: 2px 8px !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
border-radius: 12px !important;
|
||||||
|
background: rgba(255, 255, 255, 0.08) !important;
|
||||||
|
height: 28px !important;
|
||||||
|
align-items: center !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-user-avatar {
|
||||||
|
width: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-online-user-name {
|
||||||
|
font-size: 0.75rem !important;
|
||||||
|
max-width: 110px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-main-area {
|
||||||
|
flex: 1 !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
height: auto !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel #gchat-messages {
|
||||||
|
flex: 1 !important;
|
||||||
|
min-height: 0 !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
padding: 10px !important;
|
||||||
|
-webkit-overflow-scrolling: touch !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel .gchat-msg {
|
||||||
|
max-width: 92% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gchat-page-panel #gchat-input-area {
|
||||||
|
flex-shrink: 0 !important;
|
||||||
|
background: var(--nav-bg, #0d0d0d) !important;
|
||||||
|
padding-bottom: env(safe-area-inset-bottom, 0px) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* =============================================
|
/* =============================================
|
||||||
SETTINGS PAGE – MOBILE OVERFLOW FIXES
|
SETTINGS PAGE – MOBILE OVERFLOW FIXES
|
||||||
Prevents long i18n strings (Deutsch / Zange)
|
Prevents long i18n strings (Deutsch / Zange)
|
||||||
@@ -16462,21 +16415,12 @@ body.scroller-active #gchat-reopen-bubble {
|
|||||||
color: #ccc;
|
color: #ccc;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
word-break: break-word;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-table td a {
|
.info-table td a {
|
||||||
color: var(--accent, #9f0);
|
color: var(--accent, #9f0);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
word-break: break-all;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
#info-modal .modal-content .info-table td a {
|
|
||||||
word-break: break-all;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-table td a:hover {
|
.info-table td a:hover {
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@@ -1160,13 +1160,17 @@ class CommentSystem {
|
|||||||
const author = openerEl.dataset.display || openerEl.dataset.username || 'System';
|
const author = openerEl.dataset.display || openerEl.dataset.username || 'System';
|
||||||
const contentEl = body.querySelector('.comment-content');
|
const contentEl = body.querySelector('.comment-content');
|
||||||
if (contentEl) {
|
if (contentEl) {
|
||||||
|
const LINE_MAX = 200;
|
||||||
let rawText = (contentEl.dataset.raw || '').trim();
|
let rawText = (contentEl.dataset.raw || '').trim();
|
||||||
if (rawText.includes('>') || rawText.includes('<') || rawText.includes('&')) {
|
if (rawText.includes('>') || rawText.includes('<') || rawText.includes('&')) {
|
||||||
const txt = document.createElement('textarea');
|
const txt = document.createElement('textarea');
|
||||||
txt.innerHTML = rawText;
|
txt.innerHTML = rawText;
|
||||||
rawText = txt.value;
|
rawText = txt.value;
|
||||||
}
|
}
|
||||||
const lines = rawText.split('\n');
|
// Preserve all lines but cap any single line exceeding LINE_MAX chars
|
||||||
|
const lines = rawText.split('\n').map(line =>
|
||||||
|
line.length > LINE_MAX ? line.substring(0, LINE_MAX) + '\u2026' : line
|
||||||
|
);
|
||||||
const quote = `>>${id} \n>${author}\n${lines.map(line => `>${line}`).join('\n')}\n`;
|
const quote = `>>${id} \n>${author}\n${lines.map(line => `>${line}`).join('\n')}\n`;
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
@@ -1621,25 +1625,17 @@ class CommentSystem {
|
|||||||
syncLevel(roots, list, false);
|
syncLevel(roots, list, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Maximum characters & lines to fully render in the item view per comment
|
// Maximum characters to fully render in the item view per comment
|
||||||
static get ITEM_VIEW_MAX_CHARS() { return 500; }
|
static get ITEM_VIEW_MAX_CHARS() { return 2000; }
|
||||||
static get ITEM_VIEW_MAX_LINES() { return 6; }
|
|
||||||
|
|
||||||
renderCommentContent(content, commentId = null, bypassTruncation = false) {
|
renderCommentContent(content, commentId = null, bypassTruncation = false) {
|
||||||
if (!content) return '';
|
if (!content) return '';
|
||||||
|
|
||||||
// Truncate long comments before any processing
|
// Truncate extremely long comments before any processing
|
||||||
let truncated = false;
|
let truncated = false;
|
||||||
if (!bypassTruncation) {
|
if (!bypassTruncation && content.length > CommentSystem.ITEM_VIEW_MAX_CHARS) {
|
||||||
const lines = content.split('\n');
|
content = content.substring(0, CommentSystem.ITEM_VIEW_MAX_CHARS) + '\u2026';
|
||||||
if (lines.length > CommentSystem.ITEM_VIEW_MAX_LINES) {
|
truncated = true;
|
||||||
content = lines.slice(0, CommentSystem.ITEM_VIEW_MAX_LINES).join('\n');
|
|
||||||
truncated = true;
|
|
||||||
}
|
|
||||||
if (content.length > CommentSystem.ITEM_VIEW_MAX_CHARS) {
|
|
||||||
content = content.substring(0, CommentSystem.ITEM_VIEW_MAX_CHARS);
|
|
||||||
truncated = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof marked === 'undefined') {
|
if (typeof marked === 'undefined') {
|
||||||
@@ -1788,11 +1784,10 @@ class CommentSystem {
|
|||||||
if (trimmed.startsWith('>') && !trimmed.match(/^>>\d+/)) {
|
if (trimmed.startsWith('>') && !trimmed.match(/^>>\d+/)) {
|
||||||
const quoteContent = line.substring(line.indexOf('>') + 1);
|
const quoteContent = line.substring(line.indexOf('>') + 1);
|
||||||
const quoteEmojis = window.f0ckSession?.quote_emojis === true;
|
const quoteEmojis = window.f0ckSession?.quote_emojis === true;
|
||||||
const escapedQuote = quoteContent.replace(/>/g, '>');
|
|
||||||
const renderedContent = quoteEmojis
|
const renderedContent = quoteEmojis
|
||||||
? escapedQuote.replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n))
|
? quoteContent.replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n))
|
||||||
: escapedQuote;
|
: quoteContent;
|
||||||
return `<span class="greentext">>${renderedContent}</span>`;
|
return `<span class="greentext">>${renderedContent.replace(/>/g, '>')}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Per-line limit to prevent marked.parse recursion on single giant lines
|
// 2. Per-line limit to prevent marked.parse recursion on single giant lines
|
||||||
@@ -3608,9 +3603,9 @@ class CommentSystem {
|
|||||||
// element whenever the raw content exceeds ITEM_VIEW_MAX_CHARS. Called after every
|
// element whenever the raw content exceeds ITEM_VIEW_MAX_CHARS. Called after every
|
||||||
// optimistic DOM insert to guarantee the button regardless of rendering path.
|
// optimistic DOM insert to guarantee the button regardless of rendering path.
|
||||||
_ensureTruncationButton(commentEl, rawContent) {
|
_ensureTruncationButton(commentEl, rawContent) {
|
||||||
if (!rawContent) return;
|
console.log('[ensureBtn] called, rawContent.length:', rawContent?.length, 'threshold:', CommentSystem.ITEM_VIEW_MAX_CHARS);
|
||||||
const lineCount = rawContent.split('\n').length;
|
if (!rawContent || rawContent.length <= CommentSystem.ITEM_VIEW_MAX_CHARS) {
|
||||||
if (rawContent.length <= CommentSystem.ITEM_VIEW_MAX_CHARS && lineCount <= CommentSystem.ITEM_VIEW_MAX_LINES) {
|
console.log('[ensureBtn] below threshold, skipping');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const contentEl = commentEl.querySelector('.comment-content');
|
const contentEl = commentEl.querySelector('.comment-content');
|
||||||
|
|||||||
@@ -2438,7 +2438,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
const isAdmin = !!pathname.match(/^\/admin/);
|
const isAdmin = !!pathname.match(/^\/admin/);
|
||||||
const isMod = !!pathname.match(/^\/mod/);
|
const isMod = !!pathname.match(/^\/mod/);
|
||||||
const isSettings = pathname.match(/\/settings\/?(?:$|\?)/);
|
const isSettings = pathname.match(/\/settings\/?(?:$|\?)/);
|
||||||
const isStatic = pathname.match(/\/(about|rules|terms|upload|subscriptions|stats|docs|discord|ranking|meme|memes)($|\/|\?)/);
|
const isStatic = pathname.match(/\/(about|rules|terms|upload|subscriptions|stats|docs|discord|ranking|meme|memes|chat)($|\/|\?)/);
|
||||||
const isUpload = pathname.match(/\/upload\/?(?:$|\?)/);
|
const isUpload = pathname.match(/\/upload\/?(?:$|\?)/);
|
||||||
const parts = pathname.split('/').filter(Boolean);
|
const parts = pathname.split('/').filter(Boolean);
|
||||||
const isItem = !pathname.match(/\/p\//) && (
|
const isItem = !pathname.match(/\/p\//) && (
|
||||||
@@ -2446,8 +2446,9 @@ window.cancelAnimFrame = (function () {
|
|||||||
(parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && /^\d+$/.test(parts[parts.length - 1]))
|
(parts.length >= 3 && (parts[0] === 'tag' || parts[0] === 'user' || parts[0] === 'h') && /^\d+$/.test(parts[parts.length - 1]))
|
||||||
);
|
);
|
||||||
const isMessages = !!pathname.match(/^\/messages(\/|$)/);
|
const isMessages = !!pathname.match(/^\/messages(\/|$)/);
|
||||||
|
const isChat = !!pathname.match(/^\/chat(\/|$)/);
|
||||||
const isAbyss = !!pathname.match(/^\/abyss(\/|$|\?|#)/) || pathname === '/abyss';
|
const isAbyss = !!pathname.match(/^\/abyss(\/|$|\?|#)/) || pathname === '/abyss';
|
||||||
const isGrid = !isProfile && !isUserHall && !isUserHalls && !isHall && !isHalls && !isTags && !isComments && !isNotifs && !isItem && !isAdmin && !isMod && !isSettings && !isStatic && !isUpload && !isMessages && !isAbyss;
|
const isGrid = !isProfile && !isUserHall && !isUserHalls && !isHall && !isHalls && !isTags && !isComments && !isNotifs && !isItem && !isAdmin && !isMod && !isSettings && !isStatic && !isUpload && !isMessages && !isChat && !isAbyss;
|
||||||
|
|
||||||
if (isItem) {
|
if (isItem) {
|
||||||
isNavigating = false;
|
isNavigating = false;
|
||||||
@@ -4079,7 +4080,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isSpecialLink && (pathname === '/' || pathname.startsWith('/halls') || pathname.startsWith('/h/') || pathname.startsWith('/notifications') || pathname.startsWith('/tag') || pathname.startsWith('/user/') || pathname.match(/^\/(image|video|audio)/) || pathname.match(/\/p\/\d+/) || pathname.match(/^\/\d+/) || pathname.match(/^\/(about|rules|terms|upload|subscriptions|stats|docs|settings|admin|mod|ranking|messages|meme|memes)/) || pathname.startsWith('/abyss'))) {
|
if (!isSpecialLink && (pathname === '/' || pathname.startsWith('/halls') || pathname.startsWith('/h/') || pathname.startsWith('/notifications') || pathname.startsWith('/tag') || pathname.startsWith('/user/') || pathname.match(/^\/(image|video|audio)/) || pathname.match(/\/p\/\d+/) || pathname.match(/^\/\d+/) || pathname.match(/^\/(about|rules|terms|upload|subscriptions|stats|docs|settings|admin|mod|ranking|messages|meme|memes|chat)/) || pathname.startsWith('/abyss'))) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopImmediatePropagation();
|
e.stopImmediatePropagation();
|
||||||
|
|
||||||
@@ -4533,7 +4534,7 @@ window.cancelAnimFrame = (function () {
|
|||||||
(parts.length >= 3 && parts[0] === 'tag' && /^\d+$/.test(parts[parts.length - 1])) ||
|
(parts.length >= 3 && parts[0] === 'tag' && /^\d+$/.test(parts[parts.length - 1])) ||
|
||||||
(parts.length >= 3 && parts[0] === 'user' && /^\d+$/.test(parts[parts.length - 1]))
|
(parts.length >= 3 && parts[0] === 'user' && /^\d+$/.test(parts[parts.length - 1]))
|
||||||
);
|
);
|
||||||
const isSpecial = p.startsWith('/notifications') || p.startsWith('/tags') || p.startsWith('/user/') || p.startsWith('/subscriptions') || p.startsWith('/ranking');
|
const isSpecial = p.startsWith('/notifications') || p.startsWith('/tags') || p.startsWith('/user/') || p.startsWith('/subscriptions') || p.startsWith('/ranking') || p.startsWith('/chat');
|
||||||
const isGridLike = url.match(/\/p\/\d+/) || url.match(/[?&]page=\d+/) || p === '/';
|
const isGridLike = url.match(/\/p\/\d+/) || url.match(/[?&]page=\d+/) || p === '/';
|
||||||
|
|
||||||
window.f0ckDebug("[popstate] Navigation to:", url, { isItem, isSpecial, isGridLike });
|
window.f0ckDebug("[popstate] Navigation to:", url, { isItem, isSpecial, isGridLike });
|
||||||
@@ -7567,48 +7568,29 @@ class NotificationSystem {
|
|||||||
|
|
||||||
// Helper: apply a display name change to all relevant DOM nodes
|
// Helper: apply a display name change to all relevant DOM nodes
|
||||||
this.applyDisplayNameUpdate = (display_name, user) => {
|
this.applyDisplayNameUpdate = (display_name, user) => {
|
||||||
const isSelf = !user || (window.f0ckSession?.user && user.toLowerCase() === window.f0ckSession.user.toLowerCase());
|
|
||||||
|
|
||||||
// Capture old value before overwriting — needed for thumb matching below
|
// Capture old value before overwriting — needed for thumb matching below
|
||||||
const oldDisplayName = isSelf ? (window.f0ckSession?.display_name || null) : null;
|
const oldDisplayName = window.f0ckSession?.display_name || null;
|
||||||
if (isSelf && window.f0ckSession) {
|
if (window.f0ckSession) window.f0ckSession.display_name = display_name || null;
|
||||||
window.f0ckSession.display_name = display_name || null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userName = user || window.f0ckSession?.user;
|
const userName = user || window.f0ckSession?.user;
|
||||||
|
|
||||||
// Update navbar display name only if this update is for the logged-in user
|
// Update ALL nav-display-name instances (handles the profile page having a duplicate ID)
|
||||||
if (isSelf) {
|
document.querySelectorAll('#nav-display-name').forEach(nameSpan => {
|
||||||
document.querySelectorAll('#nav-display-name').forEach(nameSpan => {
|
// preserve the badges (Admin, Mod)
|
||||||
for (let i = nameSpan.childNodes.length - 1; i >= 0; i--) {
|
for (let i = nameSpan.childNodes.length - 1; i >= 0; i--) {
|
||||||
let node = nameSpan.childNodes[i];
|
let node = nameSpan.childNodes[i];
|
||||||
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim().length > 0) {
|
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim().length > 0) {
|
||||||
const match = node.nodeValue.match(new RegExp('^(\\s*)(.*)'));
|
// Match and preserve any non-breaking spaces at the start (from admin/mod badges)
|
||||||
node.nodeValue = (match ? match[1] : '') + (display_name || userName || '');
|
const match = node.nodeValue.match(new RegExp('^(\\s*)(.*)'));
|
||||||
break;
|
node.nodeValue = (match ? match[1] : '') + (display_name || userName || '');
|
||||||
}
|
break;
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update profile header display name if currently viewing this user's profile
|
|
||||||
const profileBox = document.getElementById('live-profile-preview-box');
|
|
||||||
if (profileBox && profileBox.dataset.bannerUser && userName && profileBox.dataset.bannerUser.toLowerCase() === userName.toLowerCase()) {
|
|
||||||
const profileNameSpan = document.getElementById('profile-display-name');
|
|
||||||
if (profileNameSpan) {
|
|
||||||
for (let i = profileNameSpan.childNodes.length - 1; i >= 0; i--) {
|
|
||||||
let node = profileNameSpan.childNodes[i];
|
|
||||||
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim().length > 0) {
|
|
||||||
const match = node.nodeValue.match(new RegExp('^(\\s*)(.*)'));
|
|
||||||
node.nodeValue = (match ? match[1] : '') + (display_name || userName || '');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const bracket = document.getElementById('username-bracket');
|
});
|
||||||
if (bracket) {
|
|
||||||
|
const bracket = document.getElementById('username-bracket');
|
||||||
|
if (bracket) {
|
||||||
bracket.style.display = display_name ? 'inline' : 'none';
|
bracket.style.display = display_name ? 'inline' : 'none';
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userName) {
|
if (userName) {
|
||||||
@@ -7635,7 +7617,6 @@ class NotificationSystem {
|
|||||||
|
|
||||||
// Helper: apply a username color change to relevant DOM nodes
|
// Helper: apply a username color change to relevant DOM nodes
|
||||||
this.applyUsernameColorUpdate = (username_color, user) => {
|
this.applyUsernameColorUpdate = (username_color, user) => {
|
||||||
const isSelf = !user || (window.f0ckSession?.user && user.toLowerCase() === window.f0ckSession.user.toLowerCase());
|
|
||||||
const userName = user || window.f0ckSession?.user;
|
const userName = user || window.f0ckSession?.user;
|
||||||
if (!userName) return;
|
if (!userName) return;
|
||||||
|
|
||||||
@@ -7682,21 +7663,22 @@ class NotificationSystem {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Profile header display name color (if currently viewing this user's profile)
|
// Special case for settings page preview block
|
||||||
const profileBox = document.getElementById('live-profile-preview-box');
|
if (window.f0ckSession && userName === window.f0ckSession.user) {
|
||||||
if (profileBox && profileBox.dataset.bannerUser && profileBox.dataset.bannerUser.toLowerCase() === userName.toLowerCase()) {
|
const previewBlock = document.getElementById('live-profile-preview-box');
|
||||||
const profileNameSpan = document.getElementById('profile-display-name');
|
if (previewBlock) {
|
||||||
if (profileNameSpan) {
|
|
||||||
if (username_color) {
|
if (username_color) {
|
||||||
profileNameSpan.style.color = username_color;
|
previewBlock.style.setProperty('--author-accent', username_color);
|
||||||
|
previewBlock.style.setProperty('--author-border', username_color);
|
||||||
} else {
|
} else {
|
||||||
profileNameSpan.style.removeProperty('color');
|
previewBlock.style.setProperty('--author-accent', 'var(--accent)');
|
||||||
|
previewBlock.style.setProperty('--author-border', 'var(--accent)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navbar display name color (only for current user)
|
// Handle the nav-display-name span elements (profile header and top navbar)
|
||||||
if (isSelf) {
|
if (window.f0ckSession && userName === window.f0ckSession.user) {
|
||||||
document.querySelectorAll('#nav-display-name').forEach(el => {
|
document.querySelectorAll('#nav-display-name').forEach(el => {
|
||||||
if (username_color) {
|
if (username_color) {
|
||||||
el.style.color = username_color;
|
el.style.color = username_color;
|
||||||
@@ -7709,21 +7691,14 @@ class NotificationSystem {
|
|||||||
|
|
||||||
// Helper: apply an avatar update to relevant DOM nodes
|
// Helper: apply an avatar update to relevant DOM nodes
|
||||||
this.applyAvatarUpdate = (avatar_file, user) => {
|
this.applyAvatarUpdate = (avatar_file, user) => {
|
||||||
const isSelf = !user || (window.f0ckSession?.user && user.toLowerCase() === window.f0ckSession.user.toLowerCase());
|
|
||||||
const userName = user || window.f0ckSession?.user;
|
const userName = user || window.f0ckSession?.user;
|
||||||
if (!userName) return;
|
if (!userName) return;
|
||||||
|
|
||||||
const newSrc = avatar_file ? `/a/${avatar_file}?t=${Date.now()}` : '/a/default.png';
|
const newSrc = avatar_file ? `/a/${avatar_file}?t=${Date.now()}` : '/a/default.png';
|
||||||
|
|
||||||
// Update Nav Avatar only for current user
|
// Update Nav Avatar & profile header Avatar (only for current user)
|
||||||
if (isSelf) {
|
if (window.f0ckSession && userName === window.f0ckSession.user) {
|
||||||
document.querySelectorAll('.nav-avatar-img').forEach(img => img.src = newSrc);
|
document.querySelectorAll('.nav-avatar-img, .profile_head_avatar img').forEach(img => img.src = newSrc);
|
||||||
}
|
|
||||||
|
|
||||||
// Update profile header avatar if currently viewing this user's profile
|
|
||||||
const profileBox = document.getElementById('live-profile-preview-box');
|
|
||||||
if (profileBox && profileBox.dataset.bannerUser && profileBox.dataset.bannerUser.toLowerCase() === userName.toLowerCase()) {
|
|
||||||
document.querySelectorAll('.profile_head_avatar img').forEach(img => img.src = newSrc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update globally (comments, sidebar, polls, rank, infoboxes, favs)
|
// Update globally (comments, sidebar, polls, rank, infoboxes, favs)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -153,6 +153,12 @@
|
|||||||
const renderCommentContent = (content, commentId = null, itemId = null) => {
|
const renderCommentContent = (content, commentId = null, itemId = null) => {
|
||||||
if (!content) return '';
|
if (!content) return '';
|
||||||
|
|
||||||
|
// Truncate extremely long comments before any processing to keep the sidebar
|
||||||
|
// fast and the DOM lean, regardless of markdown / regex complexity.
|
||||||
|
if (content.length > SIDEBAR_CONTENT_TRUNCATE) {
|
||||||
|
content = content.substring(0, SIDEBAR_CONTENT_TRUNCATE) + '\u2026';
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof marked === 'undefined') {
|
if (typeof marked === 'undefined') {
|
||||||
return escapeHtml(content)
|
return escapeHtml(content)
|
||||||
.replace(/:([a-z0-9_]+):/g, (m, n) => renderEmoji(m, n));
|
.replace(/:([a-z0-9_]+):/g, (m, n) => renderEmoji(m, n));
|
||||||
@@ -262,11 +268,10 @@
|
|||||||
// Manual greentext handling — apply emoji if the user preference allows it
|
// Manual greentext handling — apply emoji if the user preference allows it
|
||||||
const quoteContent = line.substring(line.indexOf('>') + 1);
|
const quoteContent = line.substring(line.indexOf('>') + 1);
|
||||||
const quoteEmojis = window.f0ckSession?.quote_emojis === true;
|
const quoteEmojis = window.f0ckSession?.quote_emojis === true;
|
||||||
const escapedQuote = quoteContent.replace(/>/g, '>');
|
|
||||||
const rendered = quoteEmojis
|
const rendered = quoteEmojis
|
||||||
? escapedQuote.replace(/:([a-z0-9_]+):/g, (m, n) => renderEmoji(m, n))
|
? quoteContent.replace(/:([a-z0-9_]+):/g, (m, n) => renderEmoji(m, n))
|
||||||
: escapedQuote;
|
: quoteContent;
|
||||||
return `<span class="greentext">>${rendered}</span>`;
|
return `<span class="greentext">>${rendered.replace(/>/g, '>')}</span>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-line limit to prevent marked.parse recursion on single giant lines
|
// Per-line limit to prevent marked.parse recursion on single giant lines
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const themes = window.f0ckThemes || ['amoled', 'atmos', 'f0ck', 'f0ck95d', 'f0ck98', 'iced', 'orange', 'p1nk', '4d'];
|
const themes = window.f0ckThemes || ['amoled', 'atmos', 'f0ck', 'f0ck95d', 'iced', 'orange', 'p1nk', '4d'];
|
||||||
const defaultTheme = window.f0ckDefaultTheme || (window.f0ckSession && window.f0ckSession.default_theme) || themes[0] || 'amoled';
|
const defaultTheme = window.f0ckDefaultTheme || (window.f0ckSession && window.f0ckSession.default_theme) || themes[0] || 'amoled';
|
||||||
|
|
||||||
const setTheme = (theme) => {
|
const setTheme = (theme) => {
|
||||||
|
|||||||
@@ -306,12 +306,7 @@ if (!window.UserCommentSystem) {
|
|||||||
const trimmed = line.trimStart();
|
const trimmed = line.trimStart();
|
||||||
if (trimmed.startsWith('>') && !trimmed.match(/^>>\d+/)) {
|
if (trimmed.startsWith('>') && !trimmed.match(/^>>\d+/)) {
|
||||||
const quoteContent = line.substring(line.indexOf('>') + 1);
|
const quoteContent = line.substring(line.indexOf('>') + 1);
|
||||||
const quoteEmojis = window.f0ckSession?.quote_emojis === true;
|
return `<span class="greentext">>${quoteContent.replace(/>/g, '>')}</span>`;
|
||||||
const escapedQuote = quoteContent.replace(/>/g, '>');
|
|
||||||
const rendered = quoteEmojis
|
|
||||||
? escapedQuote.replace(/:([a-z0-9_]+):/g, (m, n) => this.renderEmoji(m, n))
|
|
||||||
: escapedQuote;
|
|
||||||
return `<span class="greentext">>${rendered}</span>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Per-line limit
|
// Per-line limit
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
|
|
||||||
const cssDir = path.join(process.cwd(), 'public/s/css');
|
const cssDir = path.join(process.cwd(), 'public/s/css');
|
||||||
const files = ['f0ckm.css', 'v0ck.css', '98.css'];
|
const files = ['f0ckm.css', 'v0ck.css'];
|
||||||
const outputFile = path.join(cssDir, 'bundle.css');
|
const outputFile = path.join(cssDir, 'bundle.css');
|
||||||
|
|
||||||
let combined = '';
|
let combined = '';
|
||||||
|
|||||||
@@ -460,15 +460,4 @@ export default new class {
|
|||||||
console.error(`[ERROR REF ${errId}] ${context}:`, err);
|
console.error(`[ERROR REF ${errId}] ${context}:`, err);
|
||||||
return `Internal Error. Reference: ${errId}`;
|
return `Internal Error. Reference: ${errId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
isOnionRequest(req) {
|
|
||||||
if (!req || !req.headers) return false;
|
|
||||||
const rawHost = req.headers['x-forwarded-host'] || req.headers['host'] || req.headers['x-forwarded-server'] || '';
|
|
||||||
if (!rawHost) return false;
|
|
||||||
const hostStr = Array.isArray(rawHost) ? rawHost[0] : String(rawHost);
|
|
||||||
const firstHost = hostStr.split(',')[0].trim();
|
|
||||||
const hostNoPort = firstHost.split(':')[0].trim().toLowerCase();
|
|
||||||
return hostNoPort.endsWith('.onion');
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"meme": "Meme",
|
"meme": "Meme",
|
||||||
"halls": "Hallen",
|
"halls": "Hallen",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
|
"chat": "Chat",
|
||||||
"search": "Suchen",
|
"search": "Suchen",
|
||||||
"random": "Zufall",
|
"random": "Zufall",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"meme": "Meme",
|
"meme": "Meme",
|
||||||
"halls": "Halls",
|
"halls": "Halls",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
|
"chat": "Chat",
|
||||||
"search": "Search",
|
"search": "Search",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
"profile": "Profile",
|
"profile": "Profile",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"meme": "meme",
|
"meme": "meme",
|
||||||
"halls": "Hallen",
|
"halls": "Hallen",
|
||||||
"tags": "Tags",
|
"tags": "Tags",
|
||||||
|
"chat": "Chat",
|
||||||
"search": "Zoeken",
|
"search": "Zoeken",
|
||||||
"random": "Willekeurig",
|
"random": "Willekeurig",
|
||||||
"profile": "profiel",
|
"profile": "profiel",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"meme": "Memen",
|
"meme": "Memen",
|
||||||
"halls": "Hallen",
|
"halls": "Hallen",
|
||||||
"tags": "Etiketten",
|
"tags": "Etiketten",
|
||||||
|
"chat": "Geschwafel",
|
||||||
"search": "Suche",
|
"search": "Suche",
|
||||||
"random": "Zufall",
|
"random": "Zufall",
|
||||||
"profile": "Profil",
|
"profile": "Profil",
|
||||||
|
|||||||
@@ -1428,8 +1428,8 @@ export default (router, tpl) => {
|
|||||||
const userId = newUser[0].id;
|
const userId = newUser[0].id;
|
||||||
|
|
||||||
await db`
|
await db`
|
||||||
INSERT INTO user_options (user_id, mode, theme, fullscreen, avatar, avatar_file, use_new_layout, disable_autoplay, disable_swiping, use_alternative_infobox)
|
INSERT INTO user_options (user_id, mode, theme, fullscreen, avatar, avatar_file, use_new_layout, disable_autoplay, disable_swiping)
|
||||||
VALUES (${userId}, 3, 'amoled', 0, NULL, 'default.png', ${getDefaultLayout() === 'modern'}, ${cfg.websrv.enable_autoplay === false}, ${cfg.websrv.enable_swiping === false}, ${cfg.websrv.user_alternative_infobox !== false})
|
VALUES (${userId}, 3, 'amoled', 0, NULL, 'default.png', ${getDefaultLayout() === 'modern'}, ${cfg.websrv.enable_autoplay === false}, ${cfg.websrv.enable_swiping === false})
|
||||||
`;
|
`;
|
||||||
|
|
||||||
await audit.log(req.session.id, 'admin_create_user', 'user', userId, {
|
await audit.log(req.session.id, 'admin_create_user', 'user', userId, {
|
||||||
|
|||||||
@@ -47,6 +47,25 @@ function buildBgHostRegex() {
|
|||||||
|
|
||||||
export default (router, tpl) => {
|
export default (router, tpl) => {
|
||||||
|
|
||||||
|
// GET /chat — Dedicated site chat page
|
||||||
|
router.get('/chat', async (req, res) => {
|
||||||
|
if (!cfg.websrv.enable_global_chat) {
|
||||||
|
return res.reply({ code: 404, body: 'Not found' });
|
||||||
|
}
|
||||||
|
if (!req.session) {
|
||||||
|
return res.redirect('/login');
|
||||||
|
}
|
||||||
|
if (req.session.banned) {
|
||||||
|
return res.reply({ code: 403, body: 'Access denied: You are banned' });
|
||||||
|
}
|
||||||
|
return res.html(tpl.render('chat', {
|
||||||
|
session: req.session,
|
||||||
|
hidePagination: true,
|
||||||
|
link: { main: '/chat', path: '/chat' },
|
||||||
|
domain: cfg.main.url.domain
|
||||||
|
}, req));
|
||||||
|
});
|
||||||
|
|
||||||
// GET /api/chat — fetch recent messages
|
// GET /api/chat — fetch recent messages
|
||||||
router.get('/api/chat', async (req, res) => {
|
router.get('/api/chat', async (req, res) => {
|
||||||
if (!cfg.websrv.enable_global_chat) {
|
if (!cfg.websrv.enable_global_chat) {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default (router, tpl) => {
|
|||||||
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg: errorMsg }));
|
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg: errorMsg }));
|
||||||
}
|
}
|
||||||
return res.reply({
|
return res.reply({
|
||||||
body: tpl.render("register", { theme: req.cookies?.theme ?? (cfg.websrv.theme || "f0ck"), error: errorMsg, registration_open: getRegistrationOpen() }, req)
|
body: tpl.render("register", { theme: req.cookies.theme ?? (cfg.websrv.theme || "f0ck"), error: errorMsg, registration_open: getRegistrationOpen() })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ export default (router, tpl) => {
|
|||||||
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg }));
|
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false, msg }));
|
||||||
}
|
}
|
||||||
return res.reply({
|
return res.reply({
|
||||||
body: tpl.render("register", { theme: req.cookies?.theme ?? (cfg.websrv.theme || "f0ck"), error: msg, registration_open: getRegistrationOpen() }, req)
|
body: tpl.render("register", { theme: req.cookies.theme ?? (cfg.websrv.theme || "f0ck"), error: msg, registration_open: getRegistrationOpen() })
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ export default (router, tpl) => {
|
|||||||
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: true, msg }));
|
return res.writeHead(200, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: true, msg }));
|
||||||
}
|
}
|
||||||
return res.reply({
|
return res.reply({
|
||||||
body: tpl.render("register", { theme: req.cookies?.theme ?? (cfg.websrv.theme || "f0ck"), success: msg, registration_open: getRegistrationOpen() }, req)
|
body: tpl.render("register", { theme: req.cookies.theme ?? (cfg.websrv.theme || "f0ck"), success: msg, registration_open: getRegistrationOpen() })
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,9 +88,8 @@ export default (router, tpl) => {
|
|||||||
return renderError("Passwords do not match.");
|
return renderError("Passwords do not match.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// reCAPTCHA verification (bypassed for .onion requests as Google reCAPTCHA cannot validate .onion domains)
|
// reCAPTCHA verification
|
||||||
const isOnion = lib.isOnionRequest(req);
|
if (cfg.recaptcha?.enabled && cfg.recaptcha?.secret_key) {
|
||||||
if (!isOnion && cfg.recaptcha?.enabled && cfg.recaptcha?.secret_key) {
|
|
||||||
const rcToken = req.post['g-recaptcha-response'];
|
const rcToken = req.post['g-recaptcha-response'];
|
||||||
if (!rcToken) return renderError("Please complete the reCAPTCHA.");
|
if (!rcToken) return renderError("Please complete the reCAPTCHA.");
|
||||||
try {
|
try {
|
||||||
@@ -168,8 +167,8 @@ export default (router, tpl) => {
|
|||||||
const avatarFile = 'default.png';
|
const avatarFile = 'default.png';
|
||||||
|
|
||||||
await db`
|
await db`
|
||||||
insert into user_options (user_id, mode, theme, fullscreen, avatar, avatar_file, use_new_layout, disable_autoplay, disable_swiping, use_alternative_infobox)
|
insert into user_options (user_id, mode, theme, fullscreen, avatar, avatar_file, use_new_layout, disable_autoplay, disable_swiping)
|
||||||
values (${userId}, 3, 'amoled', 0, ${avatarId}, ${avatarFile}, ${getDefaultLayout() === 'modern'}, ${cfg.websrv.enable_autoplay === false}, ${cfg.websrv.enable_swiping === false}, ${cfg.websrv.user_alternative_infobox !== false})
|
values (${userId}, 3, 'amoled', 0, ${avatarId}, ${avatarFile}, ${getDefaultLayout() === 'modern'}, ${cfg.websrv.enable_autoplay === false}, ${cfg.websrv.enable_swiping === false})
|
||||||
`;
|
`;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[REGISTER] DB Error during user creation:`, err);
|
console.error(`[REGISTER] DB Error during user creation:`, err);
|
||||||
|
|||||||
149
src/index.mjs
149
src/index.mjs
@@ -49,7 +49,7 @@ const gateOptions = {
|
|||||||
},
|
},
|
||||||
cloudflare_status: {
|
cloudflare_status: {
|
||||||
status: 'ok',
|
status: 'ok',
|
||||||
location: cfg.websrv.private_society_gate_location || 'Frankfurt',
|
location: 'Frankfurt',
|
||||||
name: 'Cloudflare',
|
name: 'Cloudflare',
|
||||||
status_text: 'Working',
|
status_text: 'Working',
|
||||||
},
|
},
|
||||||
@@ -79,13 +79,11 @@ const nginx502Fallback = `<html>
|
|||||||
</html>`;
|
</html>`;
|
||||||
|
|
||||||
// Login + Register modal injected before </body>
|
// Login + Register modal injected before </body>
|
||||||
// Dynamic function so reCAPTCHA can be bypassed for .onion requests
|
// This string is built at startup so it can reference cfg.recaptcha values.
|
||||||
const _rcEnabled = !!(cfg.recaptcha && cfg.recaptcha.enabled && cfg.recaptcha.site_key);
|
const _rcEnabled = !!(cfg.recaptcha && cfg.recaptcha.enabled && cfg.recaptcha.site_key);
|
||||||
const _rcSiteKey = (cfg.recaptcha && cfg.recaptcha.site_key) || '';
|
const _rcSiteKey = (cfg.recaptcha && cfg.recaptcha.site_key) || '';
|
||||||
|
|
||||||
function getGateLoginInjection(req) {
|
const gateLoginInjection = `
|
||||||
const rcEnabled = _rcEnabled && !lib.isOnionRequest(req);
|
|
||||||
return `
|
|
||||||
<div id="hot-corner" style="position:fixed;bottom:0;left:0;width:20px;height:20px;z-index:9999;"></div>
|
<div id="hot-corner" style="position:fixed;bottom:0;left:0;width:20px;height:20px;z-index:9999;"></div>
|
||||||
|
|
||||||
<div id="gate-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:10000;align-items:center;justify-content:center;">
|
<div id="gate-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:10000;align-items:center;justify-content:center;">
|
||||||
@@ -125,7 +123,7 @@ function getGateLoginInjection(req) {
|
|||||||
<input type="text" name="token" placeholder="Invite token" autocomplete="off"
|
<input type="text" name="token" placeholder="Invite token" autocomplete="off"
|
||||||
style="background:white;color:black;border:1px solid #bbb;padding:7px 10px;width:100%;box-sizing:border-box;font-size:14px;font-family:inherit;" />
|
style="background:white;color:black;border:1px solid #bbb;padding:7px 10px;width:100%;box-sizing:border-box;font-size:14px;font-family:inherit;" />
|
||||||
<input type="text" name="email_confirm_field" style="display:none !important;" tabindex="-1" autocomplete="off" />
|
<input type="text" name="email_confirm_field" style="display:none !important;" tabindex="-1" autocomplete="off" />
|
||||||
${rcEnabled ? '<div id="gate-recaptcha" style="margin:4px 0;transform-origin:left top;"></div>' : ''}
|
${_rcEnabled ? '<div id="gate-recaptcha" style="margin:4px 0;transform-origin:left top;"></div>' : ''}
|
||||||
<button type="submit" id="gate-register-btn" style="background:#0051c3;color:white;border:none;padding:9px;font-weight:600;font-size:14px;cursor:pointer;font-family:inherit;"
|
<button type="submit" id="gate-register-btn" style="background:#0051c3;color:white;border:none;padding:9px;font-weight:600;font-size:14px;cursor:pointer;font-family:inherit;"
|
||||||
onmouseover="this.style.background='#003681'" onmouseout="if(!this.disabled)this.style.background='#0051c3'">Create account</button>
|
onmouseover="this.style.background='#003681'" onmouseout="if(!this.disabled)this.style.background='#0051c3'">Create account</button>
|
||||||
<p style="text-align:center;font-size:0.85em;margin:6px 0 0;color:#555;">
|
<p style="text-align:center;font-size:0.85em;margin:6px 0 0;color:#555;">
|
||||||
@@ -266,9 +264,8 @@ function getGateLoginInjection(req) {
|
|||||||
if (_sb.length > 12) _sb = _sb.slice(-12);
|
if (_sb.length > 12) _sb = _sb.slice(-12);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
${rcEnabled ? '<script src="https://www.google.com/recaptcha/api.js?onload=onRecaptchaGateReady&render=explicit" async defer><\/script>' : ''}
|
${_rcEnabled ? '<script src="https://www.google.com/recaptcha/api.js?onload=onRecaptchaGateReady&render=explicit" async defer><\/script>' : ''}
|
||||||
`;
|
`;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Text injected into the "What can I do?" section
|
// Text injected into the "What can I do?" section
|
||||||
@@ -285,40 +282,11 @@ try {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Called on every gated request — produces a fresh page with unique Ray ID + timestamp
|
// Called on every gated request — produces a fresh page with unique Ray ID + timestamp
|
||||||
// Accepts an optional request object to inject the visitor's real IP into the footer reveal and dynamic host into page text/title.
|
// Accepts an optional request object to inject the visitor's real IP into the footer reveal.
|
||||||
function buildGatePage(req) {
|
function buildGatePage(req) {
|
||||||
if (!_cfRender) return nginx502Fallback;
|
if (!_cfRender) return nginx502Fallback;
|
||||||
|
|
||||||
let reqHost = null;
|
let html = _cfRender({ ...gateOptions, what_can_i_do: gateSignInButton });
|
||||||
if (req && req.headers) {
|
|
||||||
const rawHost = req.headers['x-forwarded-host'] || req.headers['host'];
|
|
||||||
if (rawHost) {
|
|
||||||
const firstHost = rawHost.split(',')[0].trim();
|
|
||||||
let extracted = '';
|
|
||||||
if (firstHost.startsWith('[')) {
|
|
||||||
const closeBracket = firstHost.indexOf(']');
|
|
||||||
extracted = closeBracket !== -1 ? firstHost.substring(1, closeBracket) : firstHost;
|
|
||||||
} else {
|
|
||||||
extracted = firstHost.split(':')[0].trim();
|
|
||||||
}
|
|
||||||
const isLocalHost = ['localhost', '127.0.0.1', '::1', 'f0ckm', '0.0.0.0', 'localhost.localdomain'].includes(extracted.toLowerCase());
|
|
||||||
if (extracted && !isLocalHost) {
|
|
||||||
reqHost = extracted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!reqHost) reqHost = cfg.main.url.domain;
|
|
||||||
|
|
||||||
const currentGateOptions = {
|
|
||||||
...gateOptions,
|
|
||||||
host_status: {
|
|
||||||
...gateOptions.host_status,
|
|
||||||
location: reqHost,
|
|
||||||
},
|
|
||||||
what_can_i_do: gateSignInButton,
|
|
||||||
};
|
|
||||||
|
|
||||||
let html = _cfRender(currentGateOptions);
|
|
||||||
|
|
||||||
// Inject real visitor IP into the footer "Your IP: [Click to reveal]" span
|
// Inject real visitor IP into the footer "Your IP: [Click to reveal]" span
|
||||||
if (req) {
|
if (req) {
|
||||||
@@ -337,9 +305,9 @@ function buildGatePage(req) {
|
|||||||
// Patch the <title> to include the domain — matches real Cloudflare behaviour
|
// Patch the <title> to include the domain — matches real Cloudflare behaviour
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
'<title>502: Bad Gateway</title>',
|
'<title>502: Bad Gateway</title>',
|
||||||
`<title>${reqHost} | 502: Bad gateway</title>`
|
`<title>${cfg.main.url.domain} | 502: Bad gateway</title>`
|
||||||
);
|
);
|
||||||
html = html.replace('</body>', getGateLoginInjection(req) + '\n</body>');
|
html = html.replace('</body>', gateLoginInjection + '\n</body>');
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -348,14 +316,7 @@ const nginx502 = (cfg.websrv.private_society && cfg.websrv.private_society_gate
|
|||||||
? null
|
? null
|
||||||
: nginx502Fallback;
|
: nginx502Fallback;
|
||||||
|
|
||||||
// Custom gate template — resolved once at boot from config
|
|
||||||
// Set private_society_gate: "custom" and private_society_gate_template: "your-template-name" (no .html)
|
|
||||||
const _customGateTemplate = (cfg.websrv.private_society && cfg.websrv.private_society_gate === 'custom' && cfg.websrv.private_society_gate_template)
|
|
||||||
? String(cfg.websrv.private_society_gate_template).replace(/\.html$/i, '')
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (nginx502 === null) console.log('[BOOT] Private society gate: Cloudflare dynamic mode');
|
if (nginx502 === null) console.log('[BOOT] Private society gate: Cloudflare dynamic mode');
|
||||||
if (_customGateTemplate) console.log(`[BOOT] Private society gate: Custom template → views/${_customGateTemplate}.html (flummpress resolves extension)`);
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
// ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
||||||
@@ -495,22 +456,6 @@ process.on('uncaughtException', err => {
|
|||||||
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
|
res.setHeader('Permissions-Policy', 'geolocation=(), microphone=(), camera=()');
|
||||||
// Encourage connection reuse — helps external tools like ShareX avoid repeated TCP/TLS handshakes
|
// Encourage connection reuse — helps external tools like ShareX avoid repeated TCP/TLS handshakes
|
||||||
res.setHeader('Connection', 'keep-alive');
|
res.setHeader('Connection', 'keep-alive');
|
||||||
|
|
||||||
// Block incoming requests to .onion if Tor Hidden Service is explicitly disabled in config
|
|
||||||
if (cfg.websrv?.enable_tor_hs === false && lib.isOnionRequest(req)) {
|
|
||||||
res.writeHead(503).end();
|
|
||||||
req.url.pathname = '/tor_hs_disabled_bypass';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tor Onion-Location header: advertises .onion service counterpart to Tor Browser users when enabled
|
|
||||||
if (cfg.websrv?.enable_tor_hs !== false && cfg.main?.onion && !lib.isOnionRequest(req)) {
|
|
||||||
const p = req.url?.pathname || '/';
|
|
||||||
const s = req.url?.search || '';
|
|
||||||
const onionHost = cfg.main.onion.replace(/\/+$/, '');
|
|
||||||
res.setHeader('Onion-Location', `${onionHost}${p}${s}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSecure) {
|
if (isSecure) {
|
||||||
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
||||||
}
|
}
|
||||||
@@ -659,7 +604,7 @@ process.on('uncaughtException', err => {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const availableThemes = cfg.websrv.themes || ['amoled', 'atmos', 'f0ck', 'f0ck95d', 'f0ck98', 'iced', 'orange', 'p1nk', '4d'];
|
const availableThemes = cfg.websrv.themes || ['amoled', 'atmos', 'f0ck', 'f0ck95d', 'iced', 'orange', 'p1nk', '4d'];
|
||||||
const defaultTheme = cfg.websrv.theme || 'amoled';
|
const defaultTheme = cfg.websrv.theme || 'amoled';
|
||||||
req.theme = (req.cookies?.theme && availableThemes.includes(req.cookies.theme)) ? req.cookies.theme : defaultTheme;
|
req.theme = (req.cookies?.theme && availableThemes.includes(req.cookies.theme)) ? req.cookies.theme : defaultTheme;
|
||||||
req.fullscreen = req.cookies.fullscreen || 0;
|
req.fullscreen = req.cookies.fullscreen || 0;
|
||||||
@@ -856,21 +801,18 @@ process.on('uncaughtException', err => {
|
|||||||
req.url.pathname = '/private_society_bypass';
|
req.url.pathname = '/private_society_bypass';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// For page requests, serve the gate for all paths (custom template or fallback)
|
// For page requests, return 502 Bad Gateway for all paths except homepage (which shows the gate)
|
||||||
if (_customGateTemplate) {
|
if (req.url.pathname !== '/') {
|
||||||
res.writeHead(200, { 'Content-Type': 'text/html' }).end(tpl.render(_customGateTemplate, {}, req));
|
|
||||||
} else if (req.url.pathname !== '/') {
|
|
||||||
// Non-home paths: always show the static/cloudflare gate
|
|
||||||
res.writeHead(200, { 'Content-Type': 'text/html' }).end(nginx502 ?? buildGatePage(req));
|
res.writeHead(200, { 'Content-Type': 'text/html' }).end(nginx502 ?? buildGatePage(req));
|
||||||
} else if (nginx502 === null) {
|
req.url.pathname = '/private_society_bypass';
|
||||||
// Homepage in cloudflare dynamic mode: serve the CF gate directly
|
return;
|
||||||
res.writeHead(200, { 'Content-Type': 'text/html' }).end(buildGatePage(req));
|
}
|
||||||
} else {
|
// Homepage: in cloudflare dynamic mode serve gate directly; otherwise fall through to template
|
||||||
// Homepage in plain nginx mode: fall through to index.html template
|
if (nginx502 === null) {
|
||||||
|
res.writeHead(200, { 'Content-Type': 'text/html' }).end(buildGatePage(req));
|
||||||
|
req.url.pathname = '/private_society_bypass';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
req.url.pathname = '/private_society_bypass';
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1350,7 +1292,6 @@ process.on('uncaughtException', err => {
|
|||||||
show_mime_picker: cfg.websrv.show_mime_picker !== false,
|
show_mime_picker: cfg.websrv.show_mime_picker !== false,
|
||||||
private_society: cfg.websrv.private_society || false,
|
private_society: cfg.websrv.private_society || false,
|
||||||
private_society_gate: cfg.websrv.private_society_gate || '',
|
private_society_gate: cfg.websrv.private_society_gate || '',
|
||||||
private_society_gate_location: cfg.websrv.private_society_gate_location || 'Frankfurt',
|
|
||||||
show_content_warning: cfg.websrv.show_content_warning !== false,
|
show_content_warning: cfg.websrv.show_content_warning !== false,
|
||||||
web_url_upload: !!cfg.websrv.web_url_upload,
|
web_url_upload: !!cfg.websrv.web_url_upload,
|
||||||
enable_youtube_upload: cfg.websrv.enable_youtube_upload !== false,
|
enable_youtube_upload: cfg.websrv.enable_youtube_upload !== false,
|
||||||
@@ -1462,39 +1403,26 @@ process.on('uncaughtException', err => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve per-request recaptcha preference (disabled for .onion requests or when passed false)
|
|
||||||
const effectiveReq = req || (data && data.req);
|
|
||||||
const defaultRecaptcha = !!(cfg.recaptcha && cfg.recaptcha.enabled && cfg.recaptcha.site_key);
|
|
||||||
let perRequestRecaptcha = defaultRecaptcha;
|
|
||||||
|
|
||||||
if (effectiveReq && lib.isOnionRequest(effectiveReq)) {
|
|
||||||
perRequestRecaptcha = false;
|
|
||||||
} else if (data && typeof data.recaptcha_enabled === 'boolean') {
|
|
||||||
perRequestRecaptcha = data.recaptcha_enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build data: globals first, then caller-supplied data, then per-request i18n last
|
// Build data: globals first, then caller-supplied data, then per-request i18n last
|
||||||
// so t/lang always reflect the user's language, not the site default.
|
// so t/lang always reflect the user's language, not the site default.
|
||||||
// ALSO mutate globals.t, globals.lang, globals.recaptcha_enabled: flummpress spreads this.#globals LAST
|
// ALSO mutate globals.t and globals.lang: flummpress spreads this.#globals LAST
|
||||||
// inside render(), so globals must carry the per-request values too.
|
// inside render(), so globals must carry the per-request values too.
|
||||||
globals.t = perRequestT;
|
globals.t = perRequestT;
|
||||||
globals.lang = perRequestLang;
|
globals.lang = perRequestLang;
|
||||||
globals.recaptcha_enabled = perRequestRecaptcha;
|
|
||||||
|
|
||||||
// Resolve per-request infobox preference
|
// Resolve per-request infobox preference
|
||||||
// Guests always get false — the alternative infobox is a logged-in user preference only
|
// Guests always get false — the alternative infobox is a logged-in user preference only
|
||||||
const activeReq = req || effectiveReq;
|
const useAltInfobox = (req && req.session && typeof req.session.use_alternative_infobox === 'boolean')
|
||||||
const useAltInfobox = (activeReq && activeReq.session && typeof activeReq.session.use_alternative_infobox === 'boolean')
|
? req.session.use_alternative_infobox
|
||||||
? activeReq.session.use_alternative_infobox
|
: (req && !req.session
|
||||||
: (activeReq && !activeReq.session
|
|
||||||
? false
|
? false
|
||||||
: (data && typeof data.user_alternative_infobox === 'boolean'
|
: (data && typeof data.user_alternative_infobox === 'boolean'
|
||||||
? data.user_alternative_infobox
|
? data.user_alternative_infobox
|
||||||
: (cfg.websrv.user_alternative_infobox !== false)));
|
: (cfg.websrv.user_alternative_infobox !== false)));
|
||||||
|
|
||||||
const useAltSteuerung = (activeReq && activeReq.session && typeof activeReq.session.use_alternative_steuerung === 'boolean')
|
const useAltSteuerung = (req && req.session && typeof req.session.use_alternative_steuerung === 'boolean')
|
||||||
? activeReq.session.use_alternative_steuerung
|
? req.session.use_alternative_steuerung
|
||||||
: (activeReq && !activeReq.session
|
: (req && !req.session
|
||||||
? false
|
? false
|
||||||
: (data && typeof data.user_alternative_steuerung === 'boolean'
|
: (data && typeof data.user_alternative_steuerung === 'boolean'
|
||||||
? data.user_alternative_steuerung
|
? data.user_alternative_steuerung
|
||||||
@@ -1503,12 +1431,11 @@ process.on('uncaughtException', err => {
|
|||||||
data = Object.assign({}, globals, data || {}, {
|
data = Object.assign({}, globals, data || {}, {
|
||||||
t: perRequestT,
|
t: perRequestT,
|
||||||
lang: perRequestLang,
|
lang: perRequestLang,
|
||||||
recaptcha_enabled: perRequestRecaptcha,
|
|
||||||
user_alternative_infobox: useAltInfobox,
|
user_alternative_infobox: useAltInfobox,
|
||||||
user_alternative_steuerung: useAltSteuerung,
|
user_alternative_steuerung: useAltSteuerung,
|
||||||
user_banner_enabled: cfg.websrv.user_banner_enabled !== false,
|
user_banner_enabled: cfg.websrv.user_banner_enabled !== false,
|
||||||
comment_display_mode: (activeReq && activeReq.session && typeof activeReq.session.comment_display_mode === 'number')
|
comment_display_mode: (req && req.session && typeof req.session.comment_display_mode === 'number')
|
||||||
? activeReq.session.comment_display_mode
|
? req.session.comment_display_mode
|
||||||
: (data && typeof data.comment_display_mode === 'number'
|
: (data && typeof data.comment_display_mode === 'number'
|
||||||
? data.comment_display_mode
|
? data.comment_display_mode
|
||||||
: (cfg.websrv.default_comment_display_mode || 0))
|
: (cfg.websrv.default_comment_display_mode || 0))
|
||||||
@@ -1520,19 +1447,17 @@ process.on('uncaughtException', err => {
|
|||||||
data.custom_brand_image = brand[Math.floor(Math.random() * brand.length)];
|
data.custom_brand_image = brand[Math.floor(Math.random() * brand.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeReq) {
|
if (req) {
|
||||||
data.recaptcha_enabled = perRequestRecaptcha;
|
if (req.mode !== undefined) data.mode = req.mode;
|
||||||
if (activeReq.mode !== undefined) data.mode = activeReq.mode;
|
data.theme = req.theme || req.cookies?.theme || cfg.websrv.theme || 'f0ck';
|
||||||
data.theme = activeReq.theme || activeReq.cookies?.theme || cfg.websrv.theme || 'f0ck';
|
if (!data.url) data.url = req.url;
|
||||||
if (!data.url) data.url = activeReq.url;
|
data.user_strict_bool = (req.session && req.session.strict_mode) ? true : false;
|
||||||
data.user_strict_bool = (activeReq.session && activeReq.session.strict_mode) ? true : false;
|
data.user_logged_in_bool = !!req.session;
|
||||||
data.user_logged_in_bool = !!activeReq.session;
|
data.csrf_token = req.session?.csrf_token || '';
|
||||||
data.csrf_token = activeReq.session?.csrf_token || '';
|
data.max_file_size = lib.formatSize(cfg.main.maxfilesize * (req.session?.admin ? cfg.main.adminmultiplier : 1));
|
||||||
data.max_file_size = lib.formatSize(cfg.main.maxfilesize * (activeReq.session?.admin ? cfg.main.adminmultiplier : 1));
|
data.max_file_size_bytes = Math.floor(cfg.main.maxfilesize * (req.session?.admin ? cfg.main.adminmultiplier : 1));
|
||||||
data.max_file_size_bytes = Math.floor(cfg.main.maxfilesize * (activeReq.session?.admin ? cfg.main.adminmultiplier : 1));
|
|
||||||
data.web_url_upload = data.web_url_upload !== undefined ? data.web_url_upload : !!cfg.websrv.web_url_upload;
|
data.web_url_upload = data.web_url_upload !== undefined ? data.web_url_upload : !!cfg.websrv.web_url_upload;
|
||||||
} else {
|
} else {
|
||||||
data.recaptcha_enabled = perRequestRecaptcha;
|
|
||||||
data.theme = data.theme || cfg.websrv.theme || 'f0ck';
|
data.theme = data.theme || cfg.websrv.theme || 'f0ck';
|
||||||
data.user_strict_bool = false;
|
data.user_strict_bool = false;
|
||||||
data.user_logged_in_bool = false;
|
data.user_logged_in_bool = false;
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html theme="@if(typeof theme !== 'undefined'){{ theme }}@else{{ default_theme || 'amoled' }}@endif">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>{{ domain }}</title>
|
|
||||||
<link rel="icon" type="image/gif" href="/s/img/favicon.gif" />
|
|
||||||
<link rel="stylesheet" id="injected-gate-styles" href="/s/css/f0ckm.css?v={{ ts }}">
|
|
||||||
</head>
|
|
||||||
<body class="{{ default_layout === 'legacy' ? 'layout-legacy' : 'layout-modern' }}">
|
|
||||||
|
|
||||||
<div class="wrapper" style="text-align: center;">
|
|
||||||
<h2>Example Gate</h2>
|
|
||||||
<button type="button" onclick="openLoginGate();">Login</button>
|
|
||||||
|
|
||||||
<div id="gate-container" style="display:none;">
|
|
||||||
@include(snippets/navbar)
|
|
||||||
@include(snippets/footer)
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function openLoginGate() {
|
|
||||||
document.getElementById('gate-container').style.display = 'block';
|
|
||||||
const tryOpen = () => {
|
|
||||||
const m = document.getElementById('login-modal');
|
|
||||||
if (m) { m.style.display = 'flex'; }
|
|
||||||
else { setTimeout(tryOpen, 50); }
|
|
||||||
};
|
|
||||||
tryOpen();
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -319,7 +319,7 @@
|
|||||||
ModAction.confirm('Verify User', 'Manually verify account for <strong>' + escHTML(userName) + '</strong>?', async () => {
|
ModAction.confirm('Verify User', 'Manually verify account for <strong>' + escHTML(userName) + '</strong>?', async () => {
|
||||||
var res = await fetch('/api/v2/admin/users/activate', {
|
var res = await fetch('/api/v2/admin/users/activate', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id })
|
body: JSON.stringify({ user_id: id })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -339,7 +339,7 @@
|
|||||||
ModAction.confirm('Ban User', 'Reason for banning <strong>' + escHTML(userName) + '</strong>?', async (reason) => {
|
ModAction.confirm('Ban User', 'Reason for banning <strong>' + escHTML(userName) + '</strong>?', async (reason) => {
|
||||||
var res = await fetch('/api/v2/admin/ban', {
|
var res = await fetch('/api/v2/admin/ban', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id, reason: reason, duration: 'permanent' })
|
body: JSON.stringify({ user_id: id, reason: reason, duration: 'permanent' })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -359,7 +359,7 @@
|
|||||||
ModAction.confirm('Unban User', 'Unban account for <strong>' + escHTML(userName) + '</strong>?', async () => {
|
ModAction.confirm('Unban User', 'Unban account for <strong>' + escHTML(userName) + '</strong>?', async () => {
|
||||||
var res = await fetch('/api/v2/admin/unban', {
|
var res = await fetch('/api/v2/admin/unban', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id })
|
body: JSON.stringify({ user_id: id })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -379,7 +379,7 @@
|
|||||||
ModAction.confirm('Delete Uploads', 'Are you SURE you want to delete ALL uploads by <strong>' + escHTML(userName) + '</strong>? This cannot be undone.', async () => {
|
ModAction.confirm('Delete Uploads', 'Are you SURE you want to delete ALL uploads by <strong>' + escHTML(userName) + '</strong>? This cannot be undone.', async () => {
|
||||||
var res = await fetch('/api/v2/admin/users/bulk-delete-items', {
|
var res = await fetch('/api/v2/admin/users/bulk-delete-items', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id, username: btn.dataset.username })
|
body: JSON.stringify({ user_id: id, username: btn.dataset.username })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -398,7 +398,7 @@
|
|||||||
ModAction.confirm('Delete Comments', 'Are you SURE you want to delete ALL comments by <strong>' + escHTML(userName) + '</strong>? This will be permanent.', async () => {
|
ModAction.confirm('Delete Comments', 'Are you SURE you want to delete ALL comments by <strong>' + escHTML(userName) + '</strong>? This will be permanent.', async () => {
|
||||||
var res = await fetch('/api/v2/admin/users/bulk-delete-comments', {
|
var res = await fetch('/api/v2/admin/users/bulk-delete-comments', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id, username: btn.dataset.username })
|
body: JSON.stringify({ user_id: id, username: btn.dataset.username })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -422,7 +422,7 @@
|
|||||||
ModAction.confirm('Set Display Name', hint, async (newName) => {
|
ModAction.confirm('Set Display Name', hint, async (newName) => {
|
||||||
var res = await fetch('/api/v2/admin/users/set-display-name', {
|
var res = await fetch('/api/v2/admin/users/set-display-name', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id, display_name: newName || '' })
|
body: JSON.stringify({ user_id: id, display_name: newName || '' })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -457,7 +457,7 @@
|
|||||||
ModAction.confirm('Unlock Layout', 'Unlock comment layout for <strong>' + escHTML(userName) + '</strong>? They will be able to change it again.', async () => {
|
ModAction.confirm('Unlock Layout', 'Unlock comment layout for <strong>' + escHTML(userName) + '</strong>? They will be able to change it again.', async () => {
|
||||||
var res = await fetch('/api/v2/admin/users/lock-layout', {
|
var res = await fetch('/api/v2/admin/users/lock-layout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id, lock: false })
|
body: JSON.stringify({ user_id: id, lock: false })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -481,7 +481,7 @@
|
|||||||
var mode = document.getElementById('force-mode-select').value;
|
var mode = document.getElementById('force-mode-select').value;
|
||||||
var res = await fetch('/api/v2/admin/users/lock-layout', {
|
var res = await fetch('/api/v2/admin/users/lock-layout', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id, lock: true, mode: mode })
|
body: JSON.stringify({ user_id: id, lock: true, mode: mode })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -616,7 +616,7 @@
|
|||||||
try {
|
try {
|
||||||
var res = await fetch('/api/v2/admin/users/create', {
|
var res = await fetch('/api/v2/admin/users/create', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ username, email: email || null, password, role })
|
body: JSON.stringify({ username, email: email || null, password, role })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
@@ -662,7 +662,7 @@
|
|||||||
var role = selectEl.value;
|
var role = selectEl.value;
|
||||||
var res = await fetch('/api/v2/admin/users/set-role', {
|
var res = await fetch('/api/v2/admin/users/set-role', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': window.f0ckSession?.csrf_token },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ user_id: id, role })
|
body: JSON.stringify({ user_id: id, role })
|
||||||
});
|
});
|
||||||
var data = await res.json();
|
var data = await res.json();
|
||||||
|
|||||||
9
views/chat.html
Normal file
9
views/chat.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
@include(snippets/header)
|
||||||
|
<div class="pagewrapper">
|
||||||
|
<div id="main" class="chat-page">
|
||||||
|
<div id="site-chat-container" class="site-chat-container">
|
||||||
|
<!-- Site chat widget is mounted here by globalchat.js -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@include(snippets/footer)
|
||||||
@@ -296,7 +296,7 @@
|
|||||||
@if(item.src.short)
|
@if(item.src.short)
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ t('info_modal.source') || 'Source' }}</th>
|
<th>{{ t('info_modal.source') || 'Source' }}</th>
|
||||||
<td><a href="{{ item.src.long }}" target="_blank" style="word-break: break-all;">{{ item.src.short }}</a></td>
|
<td><a href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
@endif
|
@endif
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -238,7 +238,7 @@
|
|||||||
@if(item.src.short)
|
@if(item.src.short)
|
||||||
<tr>
|
<tr>
|
||||||
<th>{{ t('info_modal.source') || 'Source' }}</th>
|
<th>{{ t('info_modal.source') || 'Source' }}</th>
|
||||||
<td><a href="{{ item.src.long }}" target="_blank" style="word-break: break-all;">{{ item.src.short }}</a></td>
|
<td><a href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
@endif
|
@endif
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -56,7 +56,6 @@
|
|||||||
@if(!private_society || session)
|
@if(!private_society || session)
|
||||||
@if(development || (private_society && !session))
|
@if(development || (private_society && !session))
|
||||||
<link rel="stylesheet" href="/s/css/f0ckm.css?v={{ ts }}">
|
<link rel="stylesheet" href="/s/css/f0ckm.css?v={{ ts }}">
|
||||||
<link rel="stylesheet" href="/s/css/98.css?v={{ ts }}">
|
|
||||||
@endif
|
@endif
|
||||||
@if(development)
|
@if(development)
|
||||||
<link rel="stylesheet" href="/s/css/v0ck.css?v={{ ts }}">
|
<link rel="stylesheet" href="/s/css/v0ck.css?v={{ ts }}">
|
||||||
|
|||||||
@@ -26,6 +26,9 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<a href="/tags" title="{{ t('nav.tags') }}"><i class="fa-solid fa-tags"></i></a>
|
<a href="/tags" title="{{ t('nav.tags') }}"><i class="fa-solid fa-tags"></i></a>
|
||||||
|
@if(enable_global_chat)
|
||||||
|
<a href="/chat" title="{{ t('nav.chat') }}"><i class="fa-solid fa-comments"></i></a>
|
||||||
|
@endif
|
||||||
@if(abyss_enabled)
|
@if(abyss_enabled)
|
||||||
<a href="/abyss" title="{{ t('nav.abyss') }}"><i class="fa-solid fa-dice-d6"></i></a>
|
<a href="/abyss" title="{{ t('nav.abyss') }}"><i class="fa-solid fa-dice-d6"></i></a>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
<div class="profile_head_username">
|
<div class="profile_head_username">
|
||||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; gap: 10px;">
|
<div style="display: flex; justify-content: space-between; align-items: flex-start; gap: 10px;">
|
||||||
<div style="flex: 1; min-width: 0; word-break: break-word; line-height: 1.2; align-self: flex-start; margin-top: 0; display: flex; align-items: center; gap: 5px;" id="profile-name-container">
|
<div style="flex: 1; min-width: 0; word-break: break-word; line-height: 1.2; align-self: flex-start; margin-top: 0; display: flex; align-items: center; gap: 5px;" id="profile-name-container">
|
||||||
<span id="profile-display-name" @if(user.username_color) style="color: {{ user.username_color }}" @endif>@if(user.admin)⚡ @elseif(user.is_moderator)🛡 @endif{!! user.display_name || user.user !!}@if(user.is_ghost) <span class="badge badge-secondary" style="font-size: 0.5em; vertical-align: middle; background-color: #5bc0de; color: #fff; padding: 2px 5px; border-radius: 3px; margin-left: 5px;">LEGACY</span>@endif @if(user.banned) <span class="badge badge-danger" tooltip="{{ user.ban_duration }}" style="font-size: 0.5em; vertical-align: middle; background-color: #d9534f; color: #fff; padding: 2px 5px; border-radius: 3px; margin-left: 5px;">BANNED</span>@endif</span>@if(user.display_name) <span style="font-size: 0.65em; color: #666; font-weight: 400; margin-left: 5px; letter-spacing: 0.5px;" id="username-bracket">({!! user.user !!})</span>@endif
|
<span id="nav-display-name" @if(user.username_color) style="color: {{ user.username_color }}" @endif>@if(user.admin)⚡ @elseif(user.is_moderator)🛡 @endif{!! user.display_name || user.user !!}@if(user.is_ghost) <span class="badge badge-secondary" style="font-size: 0.5em; vertical-align: middle; background-color: #5bc0de; color: #fff; padding: 2px 5px; border-radius: 3px; margin-left: 5px;">LEGACY</span>@endif @if(user.banned) <span class="badge badge-danger" tooltip="{{ user.ban_duration }}" style="font-size: 0.5em; vertical-align: middle; background-color: #d9534f; color: #fff; padding: 2px 5px; border-radius: 3px; margin-left: 5px;">BANNED</span>@endif</span>@if(user.display_name) <span style="font-size: 0.65em; color: #666; font-weight: 400; margin-left: 5px; letter-spacing: 0.5px;" id="username-bracket">({!! user.user !!})</span>@endif
|
||||||
@if(session && session.id === user.user_id)
|
@if(session && session.id === user.user_id)
|
||||||
<button id="inline-name-edit-btn" style="background: transparent; border:none; color: var(--text-muted); cursor: pointer; font-size: 0.7em; padding: 0;" title="Edit Name & Color">
|
<button id="inline-name-edit-btn" style="background: transparent; border:none; color: var(--text-muted); cursor: pointer; font-size: 0.7em; padding: 0;" title="Edit Name & Color">
|
||||||
<i class="fa-solid fa-pen"></i>
|
<i class="fa-solid fa-pen"></i>
|
||||||
@@ -509,27 +509,18 @@
|
|||||||
nameContainer.style.display = 'flex';
|
nameContainer.style.display = 'flex';
|
||||||
nameEditContainer.style.display = 'none';
|
nameEditContainer.style.display = 'none';
|
||||||
|
|
||||||
const updateNameNode = (el, text) => {
|
const nameSpan = document.getElementById('nav-display-name');
|
||||||
if (!el) return;
|
if (nameSpan) {
|
||||||
for (let i = el.childNodes.length - 1; i >= 0; i--) {
|
nameSpan.style.color = color;
|
||||||
let node = el.childNodes[i];
|
for (let i = nameSpan.childNodes.length - 1; i >= 0; i--) {
|
||||||
|
let node = nameSpan.childNodes[i];
|
||||||
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim().length > 0) {
|
if (node.nodeType === Node.TEXT_NODE && node.nodeValue.trim().length > 0) {
|
||||||
const match = node.nodeValue.match(new RegExp('^(\\s*)(.*)'));
|
// Match and preserve any non-breaking spaces at the start (from admin/mod badges)
|
||||||
node.nodeValue = (match ? match[1] : '') + text;
|
const match = node.nodeValue.match(new RegExp('^(\\\\s*)(.*)'));
|
||||||
|
node.nodeValue = (match ? match[1] : '') + (displayName ? displayName : '{{ user.user }}');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
const targetName = displayName ? displayName : '{{ user.user }}';
|
|
||||||
const profileSpan = document.getElementById('profile-display-name');
|
|
||||||
if (profileSpan) {
|
|
||||||
profileSpan.style.color = color;
|
|
||||||
updateNameNode(profileSpan, targetName);
|
|
||||||
}
|
|
||||||
const navSpan = document.getElementById('nav-display-name');
|
|
||||||
if (navSpan) {
|
|
||||||
navSpan.style.color = color;
|
|
||||||
updateNameNode(navSpan, targetName);
|
|
||||||
}
|
}
|
||||||
const bracket = document.getElementById('username-bracket');
|
const bracket = document.getElementById('username-bracket');
|
||||||
if (bracket) {
|
if (bracket) {
|
||||||
|
|||||||
Reference in New Issue
Block a user