commit b646107eb7ad52ee3ed684ae28f53146e8e301d1 Author: Kibi Kelburton Date: Sat Apr 25 19:51:52 2026 +0200 init f0ckm diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..6efa0a6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,24 @@ +# Git +.git +.gitignore +.gitattributes +.gitea +.eslintrc.json + +# Dependencies (rebuilt in container) +node_modules + +# Docker files +Dockerfile + +# Config (mounted at runtime) +config.json + +# Development +logs/ +tmp/ +*.log + +# Documentation +README.md +LICENSE diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..3febcf3 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,29 @@ +{ + "env": { + "es6": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "sourceType": "module" + }, + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "double" + ], + "semi": [ + "error", + "always" + ], + "no-console": "off" + } +} \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ddd8fda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.mjs gitlab-language=javascript +*.hbs gitlab-language=handlebars diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c472e6b --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +node_modules/ +logs/*.log +config.json +public/b +public/ca +public/t +deleted/b +deleted/ca +deleted/t +tmp/* +tools +public/a +public/tag_cache \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..44dba1c --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM node:22-alpine +ARG GIT_HASH=unknown +ENV GIT_HASH=$GIT_HASH + +RUN apk add --no-cache \ + ffmpeg \ + yt-dlp \ + ffmpegthumbnailer \ + imagemagick \ + git \ + mailcap \ + file \ + curl \ + torsocks \ + exiftool + +WORKDIR /opt/f0bm +COPY . . + +RUN npm i +RUN npm run build + +ENTRYPOINT ["npm", "run", "start"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d2ef4ea --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 keinBot + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/config_example.json b/config_example.json new file mode 100644 index 0000000..68150ec --- /dev/null +++ b/config_example.json @@ -0,0 +1,167 @@ +{ + "main": { + "url": { + "full": "https://example.com", + "domain": "example.com", + "regex": "example\\.com" + }, + "socks": "socks5://127.0.0.1:9050", + "mail": "admin@example.com", + "discord": "", + "maxfilesize": 104857600, + "adminmultiplier": 3.5, + "upload_limit": 300, + "ignored": [ + "example.net", + "example.org" + ], + "invite_secret": "YOUR_SECRET_HERE", + "hide_comments_from_public": false, + "development": true + }, + "allowedModes": [ "sfw", "nsfw", "untagged", "all", "nsfl" ], + "enable_nsfl": true, + "nsfl_tag_id": 1234, + "allowedMimes": [ "audio", "image", "video" ], + "nsfp": [ + 1, 2, 3 + ], + "websrv": { + "port": "1337", + "language": "en", + "allow_language_change": true, + "cache": false, + "eps": 100, + "background": true, + + "description": "Example Description", + "themes": [ "amoled" ], + "theme": "amoled", + "default_layout": "legacy", + "custom_favicon": "/s/img/favicon.gif", + "custom_brand_image": [], + "show_koepfe": false, + "koepfe": [], + + "enable_global_chat": true, + "enable_danmaku": true, + "private_messages": true, + "halls_enabled": true, + "meme_creator": true, + + "web_url_upload": true, + "enable_youtube_upload": true, + "web_meta_extraction": true, + "bypass_duplicate_check": true, + "protect_files": false, + "allowed_comment_images": [ + "i.imgur.com", + "tenor.com", + "giphy.com" + ], + "show_mime_picker": true, + + "embed_youtube_in_comments": true, + "show_content_warning": true, + "phrases": [ + "Hello World" + ], + "ban_video": "", + "enable_xd_score": true, + "enable_autoplay": false, + "enable_swiping": true, + "enable_profile_description": true, + "use_ententeich": true, + "enable_swf": true, + "swf_thumb": "/s/img/swf.png", + + "open_registration": true, + "open_registration_web_toggle": false, + "private_society": false, + + "paths": { + "images": "/b", + "thumbnails": "/t", + "coverarts": "/ca", + "emojis": "/emojis", + "memes": "/memes" + } + }, +"clients": [{ + "type": "tg", + "enabled": false, + "token": "", + "pollrate": 1001 +}, +{ + "type": "matrix", + "enabled": false, + "baseUrl": "https://matrix.org", + "token": "", + "userId": "@user:matrix.org", + "channels": [], + "upload_channel_id": "", + "notification_channel_id": "" +}, +{ + "type": "irc", + "enabled": false, + "network": "example", + "host": "irc.example.net", + "port": 6697, + "ssl": true, + "selfSigned": true, + "sasl": false, + "nickname": "bot", + "username": "bot", + "password": "", + "realname": "bot", + "channels": [ + "#example" + ] +}], +"sql": { + "host": "localhost", + "port": 5432, + "user": "db_user", + "password": "db_password", + "database": "db_name", + "multipleStatements": true, + "max": 50 +}, +"admins": [{}], +"mimes": { + "image/png": "png", + "video/webm": "webm", + "image/gif": "gif", + "image/jpg": "jpg", + "image/jpeg": "jpeg", + "image/webp": "webp", + "video/mp4": "mp4", + "video/quicktime": "mp4", + "audio/mpeg": "mpg", + "audio/mp3": "mp3", + "audio/ogg": "ogg", + "audio/opus": "opus", + "audio/flac": "flac", + "audio/x-flac": "flac", + "audio/mp4": "m4a", + "audio/x-m4a": "m4a", + "audio/aac": "m4a", + "video/x-m4v": "mp4", + "video/x-matroska": "mkv", + "application/x-shockwave-flash": "swf", + "application/vnd.adobe.flash.movie": "swf" +}, +"apis": {}, +"smtp": { + "enabled": false, + "host": "smtp.example.com", + "port": 465, + "secure": true, + "user": "smtp_user", + "password": "smtp_password", + "from": "admin@example.com", + "mail_reset_password": false +} +} \ No newline at end of file diff --git a/debug/autotagger.mjs b/debug/autotagger.mjs new file mode 100644 index 0000000..8fa75e6 --- /dev/null +++ b/debug/autotagger.mjs @@ -0,0 +1,60 @@ +import db from "../src/inc/sql.mjs"; +import lib from "../src/inc/lib.mjs"; + +(async () => { + const _args = process.argv.slice(2); + const _from = +_args[0]; + const _to = _from + 500; + + const f0cks = await db` + select * + from items + where + id not in (select item_id from tags_assign group by item_id) and + mime like 'image/%' and + id between ${_from} and ${_to} + `; + + console.time('blah'); + for(let f of f0cks) { + const tmp = await lib.detectNSFW(f.dest); + + console.log( + 'https://f0ck.me/' + f.id, + tmp.isNSFW, + tmp.score.toFixed(2), + { + sexy: tmp.scores.sexy.toFixed(2), + porn: tmp.scores.porn.toFixed(2), + hentai: tmp.scores.hentai.toFixed(2), + neutral: tmp.scores.neutral.toFixed(2) + } + ); + + await db` + insert into "tags_assign" ${ + db({ + item_id: f.id, + tag_id: tmp.nsfw ? 2 : 1, + user_id: 1 + }) + } + `; + + if(tmp.hentai >= .7) { + await db` + insert into "tags_assign" ${ + db({ + item_id: f.id, + tag_id: 4, // hentai + user_id: 1 // autotagger + }) + } + `; + } + + }; + + console.timeEnd('blah'); + process.exit(); +})(); diff --git a/debug/backfill_phash.mjs b/debug/backfill_phash.mjs new file mode 100644 index 0000000..8a356b0 --- /dev/null +++ b/debug/backfill_phash.mjs @@ -0,0 +1,78 @@ +import db from "../src/inc/sql.mjs"; +import queue from "../src/inc/queue.mjs"; +import fs from "fs"; +import path from "path"; + +const __dirname = path.dirname(new URL(import.meta.url).pathname); +const PROJECT_ROOT = path.resolve(__dirname, '..'); +const BATCH_SIZE = 50; + +async function backfill() { + console.log("Starting PHash backfill..."); + + try { + // Count total items needing backfill + const countResult = await db`SELECT count(*) as count FROM items WHERE (phash IS NULL OR phash = '') AND active = true`; + const total = countResult[0].count; + console.log(`Found ${total} items to process.`); + + let processed = 0; + let errors = 0; + + while (true) { + const items = await db` + SELECT id, dest + FROM items + WHERE (phash IS NULL OR phash = '') + AND active = true + ORDER BY id DESC + LIMIT ${BATCH_SIZE} + `; + + if (items.length === 0) break; + + for (const item of items) { + // Correctly resolve path relative to project root + const filePath = path.join(PROJECT_ROOT, 'public', 'b', item.dest); + + if (!fs.existsSync(filePath)) { + console.log(`[SKIP] File not found: ${item.dest} (ID: ${item.id})`); + // Mark as MISSING so we don't pick it up again + await db`UPDATE items SET phash = 'MISSING' WHERE id = ${item.id}`; + processed++; + continue; + } + + try { + console.log(`[PROCESSING] ID ${item.id}: ${item.dest}`); + const phash = await queue.generatePHash(filePath); + + if (phash) { + await db`UPDATE items SET phash = ${phash} WHERE id = ${item.id}`; + console.log(`[SUCCESS] ID ${item.id}: Updated PHash.`); + } else { + console.log(`[FAILED] ID ${item.id}: Could not generate PHash.`); + // Mark as ERROR so we don't pick it up again + await db`UPDATE items SET phash = 'ERROR' WHERE id = ${item.id}`; + errors++; + } + } catch (err) { + console.error(`[ERROR] ID ${item.id}:`, err); + await db`UPDATE items SET phash = 'ERROR' WHERE id = ${item.id}`; + errors++; + } + processed++; + } + console.log(`Progress: ${processed} completed (Remaining in batch loop...)`); + } + + console.log("Backfill complete!"); + process.exit(0); + + } catch (err) { + console.error("Fatal error:", err); + process.exit(1); + } +} + +backfill(); diff --git a/debug/blur_existing_thumbnails.mjs b/debug/blur_existing_thumbnails.mjs new file mode 100644 index 0000000..0950b2c --- /dev/null +++ b/debug/blur_existing_thumbnails.mjs @@ -0,0 +1,102 @@ +/** + * Backfill script to generate blurred thumbnails for existing NSFW items + * + * Run with: node debug/blur_existing_thumbnails.mjs + */ + +import { exec as _exec } from "child_process"; +import fs from "fs"; +import path from "path"; +import cfg from "../src/inc/config.mjs"; +import sql from "../src/inc/sql.mjs"; + +const db = sql; + +const exec = (cmd) => new Promise((resolve, reject) => { + _exec(cmd, { maxBuffer: 5e3 * 1024 }, (err, stdout, stderr) => { + if (err) { + err.stderr = stderr; + return reject(err); + } + resolve({ stdout }); + }); +}); + +async function main() { + console.log('[BACKFILL] Starting blurred thumbnail generation for existing NSFW items...'); + + // Find all items with NSFW tag (tag_id = 2) that are active + const nsfwItems = await db` + SELECT DISTINCT items.id + FROM items + JOIN tags_assign ON tags_assign.item_id = items.id + WHERE tags_assign.tag_id = 2 + AND items.active = true + `; + + console.log(`[BACKFILL] Found ${nsfwItems.length} NSFW items`); + + const tDir = cfg.paths.t; + console.log(`[BACKFILL] Thumbnail directory: ${tDir}`); + + // Debug: check if directory exists and list some files + try { + const files = await fs.promises.readdir(tDir); + console.log(`[BACKFILL] Directory exists, contains ${files.length} files`); + console.log(`[BACKFILL] Sample files: ${files.slice(0, 5).join(', ')}`); + } catch (e) { + console.error(`[BACKFILL] ERROR: Cannot access thumbnail directory: ${e.message}`); + process.exit(1); + } + + let processed = 0; + let skipped = 0; + let errors = 0; + + for (const item of nsfwItems) { + const src = path.join(tDir, `${item.id}.webp`); + const dst = path.join(tDir, `${item.id}_blur.webp`); + + // Check if blur already exists + try { + await fs.promises.access(dst); + skipped++; + continue; // Already exists + } catch { + // Doesn't exist, proceed + } + + // Check if source exists + try { + await fs.promises.access(src); + } catch { + console.log(`[BACKFILL] Source thumbnail missing for item ${item.id}`); + errors++; + continue; + } + + // Generate blurred thumbnail + try { + await exec(`magick "${src}" -blur 0x20 "${dst}"`); + processed++; + if (processed % 100 === 0) { + console.log(`[BACKFILL] Progress: ${processed} processed, ${skipped} skipped, ${errors} errors`); + } + } catch (err) { + console.error(`[BACKFILL] Failed to blur item ${item.id}:`, err.message); + errors++; + } + } + + console.log(`[BACKFILL] Complete!`); + console.log(` Processed: ${processed}`); + console.log(` Skipped (already exist): ${skipped}`); + console.log(` Errors: ${errors}`); + + process.exit(0); +} + +main().catch(err => { + console.error('[BACKFILL] Fatal error:', err); + process.exit(1); +}); diff --git a/debug/clean.mjs b/debug/clean.mjs new file mode 100644 index 0000000..567ce6a --- /dev/null +++ b/debug/clean.mjs @@ -0,0 +1,95 @@ +import cfg from "../src/inc/config.mjs"; +import db from "../src/inc/sql.mjs"; +import fs from "node:fs"; +import readline from 'node:readline/promises'; + +const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + +// npm run clean -- --dry-run +const dry = !!process.argv.filter(a => a == '--dry-run').length; +console.log(`dry run? ${dry}`); + +const dirs = { + b: "./public/b", + t: "./public/t", + tmp: "./tmp" +}; +const files = { + b: (await fs.promises.readdir(dirs.b)) .filter(f => f !== '.empty'), + t: (await fs.promises.readdir(dirs.t)) .filter(f => f !== '.empty'), + tmp: (await fs.promises.readdir(dirs.tmp)).filter(f => f !== '.empty') +}; +const extensions = [ ...Object.values(cfg.mimes), 'mov' ]; +const count = { + missing: { b: [], t: [] }, + invalid: { b: [], t: [] }, + spare: { b: [], t: [] }, + tmp: files.tmp +}; +const rows = await db`select id, dest from items where active = true`; +const f0cks = { + b: rows.flatMap(f => f.dest), + t: rows.flatMap(f => `${f.id}.webp`) +}; + +// missing +for(const row of rows) { + if(!fs.existsSync(`${dirs.b}/${row.dest}`)) + count.missing.b.push(row.id); + if(!fs.existsSync(`${dirs.t}/${row.id}.webp`)) + count.missing.t.push(row.id); +} + +// invalid +count.invalid.b = files.b.filter(f => !extensions.includes(f.toLowerCase().split('.')[1])); +count.invalid.t = files.t.filter(f => !f.endsWith('.webp')); + +// spare +for(const file of files.b) + if(!f0cks.b.includes(file)) + count.spare.b.push(`${dirs.b}/${file}`); + +for(const file of files.t) + if(!f0cks.t.includes(file)) + count.spare.t.push(`${dirs.t}/${file}`); + +// show confusing summary +console.log(count); + +// delete spare if --dry-run +if(!dry) { + let q; + if(count.spare.b.length > 0) { + q = (await rl.question(`delete ${count.spare.b.length} unnecessary files in ${dirs.b}? [y/N] `)) == 'y'; + if(q) { + await Promise.all(count.spare.b.map(f => fs.promises.unlink(f))); + console.log(`deleted ${count.spare.b.length} files`); + } + else + console.log('abort...'); + } + + if(count.spare.t.length > 0) { + q = (await rl.question(`delete ${count.spare.t.length} unnecessary files in ${dirs.t}? [y/N] `)) == 'y'; + if(q) { + await Promise.all(count.spare.t.map(f => fs.promises.unlink(f))); + console.log(`deleted ${count.spare.t.length} files`); + } + else + console.log('abort...'); + } + + if(files.tmp.length > 0) { + q = (await rl.question(`delete ${files.tmp.length} files in ${dirs.tmp}? [y/N] `)) == 'y'; + if(q) { + await Promise.all(files.tmp.map(f => fs.promises.unlink(`${dirs.tmp}/${f}`))); + console.log(`deleted ${files.tmp.length} files`); + } + else + console.log('abort...'); + } +} + +// close connection +await db.end(); +process.exit(); diff --git a/debug/find_duplicates.mjs b/debug/find_duplicates.mjs new file mode 100644 index 0000000..3522ca0 --- /dev/null +++ b/debug/find_duplicates.mjs @@ -0,0 +1,104 @@ + +import db from "../src/inc/sql.mjs"; + +const THRESHOLD = 15; +const REQUIRED_MATCHES = 2; + +// Hamming distance helper — operates on a single hex-encoded hash segment +const getHammingDistance = (h1, h2) => { + if (!h1 || !h2 || h1.length !== h2.length) return 9999; + let distance = 0; + for (let i = 0; i < h1.length; i += 2) { + const v1 = parseInt(h1.substr(i, 2), 16); + const v2 = parseInt(h2.substr(i, 2), 16); + let xor = v1 ^ v2; + while (xor) { + distance += xor & 1; + xor >>= 1; + } + } + return distance; +}; + +async function findDuplicates() { + console.log("Fetching items..."); + + // Fetch all valid phashes + const items = await db` + SELECT id, phash + FROM items + WHERE phash IS NOT NULL + AND phash != '' + AND phash != 'MISSING' + AND phash != 'ERROR' + AND phash NOT LIKE '00000000%' + ORDER BY id ASC + `; + + console.log(`Checking ${items.length} items for duplicates (Threshold: ${THRESHOLD}, Required frame matches: ${REQUIRED_MATCHES})...`); + + const duplicates = new Map(); // Map> + const processed = new Set(); + + for (let i = 0; i < items.length; i++) { + const current = items[i]; + + if (processed.has(current.id)) continue; + + const matchList = []; + + for (let j = i + 1; j < items.length; j++) { + const compare = items[j]; + if (processed.has(compare.id)) continue; + + // Split multi-frame hashes properly — do NOT compare the whole string + const aHashes = current.phash.split('_'); + const bHashes = compare.phash.split('_'); + const framesToCompare = Math.min(aHashes.length, bHashes.length); + + let matchCount = 0; + for (let f = 0; f < framesToCompare; f++) { + const dist = getHammingDistance(aHashes[f], bHashes[f]); + if (dist <= THRESHOLD) matchCount++; + } + + const isMatch = (framesToCompare >= 3 && matchCount >= REQUIRED_MATCHES) + || (framesToCompare === 2 && matchCount >= 2) + || (framesToCompare === 1 && matchCount === 1); + + if (isMatch) { + const avgDist = Math.round( + aHashes.slice(0, framesToCompare) + .reduce((sum, h, idx) => sum + getHammingDistance(h, bHashes[idx]), 0) + / framesToCompare + ); + matchList.push({ id: compare.id, dist: avgDist }); + processed.add(compare.id); + } + } + + if (matchList.length > 0) { + duplicates.set(current.id, matchList); + processed.add(current.id); + } + } + + if (duplicates.size === 0) { + console.log("No duplicates found."); + } else { + console.log(`Found ${duplicates.size} duplicate sets:`); + console.log("---------------------------------------------------"); + } + + for (const [originalId, matchList] of duplicates.entries()) { + const matchStr = matchList.map(m => `ID:${m.id} (avg-dist:${m.dist})`).join(", "); + console.log(`Original ID: ${originalId} matches with: ${matchStr}`); + } + + process.exit(0); +} + +findDuplicates().catch(err => { + console.error(err); + process.exit(1); +}); diff --git a/debug/fix_deleted.mjs b/debug/fix_deleted.mjs new file mode 100644 index 0000000..670c5d9 --- /dev/null +++ b/debug/fix_deleted.mjs @@ -0,0 +1,72 @@ + +import db from "../src/inc/sql.mjs"; +import { promises as fs } from "fs"; + +(async () => { + console.log("Starting migration..."); + + // 1. Ensure column exists + try { + await db`select is_deleted from items limit 1`; + console.log("Column 'is_deleted' already exists."); + } catch (err) { + if (err.message.includes('column "is_deleted" does not exist')) { + console.log("Column 'is_deleted' missing. Adding it now..."); + await db`ALTER TABLE items ADD COLUMN is_deleted BOOLEAN DEFAULT FALSE`; + console.log("Column added successfully."); + } else { + console.error("Unexpected error checking column:", err); + process.exit(1); + } + } + + const items = await db`select id, dest from items where active = false`; + console.log(`Found ${items.length} inactive items.`); + + let trashCount = 0; + let pendingCount = 0; + let brokenCount = 0; + + for (const item of items) { + try { + await fs.access(`./deleted/b/${item.dest}`); + // File exists in deleted, mark as is_deleted = true + await db`update items set is_deleted = true where id = ${item.id}`; + trashCount++; + } catch { + // Not in deleted, check public + try { + await fs.access(`./public/b/${item.dest}`); + // In public, is_deleted = false (default) + pendingCount++; + } catch { + // Not in either? Broken. + console.log(`Item ${item.id} (${item.dest}) missing from both locations. Cleaning up...`); + + // 2. Fix FK constraint: Check if this item is used as an avatar + try { + // Find a safe fallback avatar (active item) + const fallback = await db`select id from items where active = true limit 1`; + if (fallback.length > 0) { + const safeId = fallback[0].id; + const users = await db`update "user_options" set avatar = ${safeId} where avatar = ${item.id} returning user_id`; + if (users.length > 0) { + console.log(` > Reassigned avatar for ${users.length} users (from ${item.id} to ${safeId})`); + } + } + } catch (fkErr) { + console.error(` ! Error fixing avatar FK for ${item.id}:`, fkErr.message); + } + + await db`delete from items where id = ${item.id}`; + brokenCount++; + } + } + } + + console.log(`Migration complete.`); + console.log(`Trash (soft-deleted): ${trashCount}`); + console.log(`Pending: ${pendingCount}`); + console.log(`Broken: ${brokenCount}`); + process.exit(0); +})(); diff --git a/debug/recreate_hashes.mjs b/debug/recreate_hashes.mjs new file mode 100644 index 0000000..167d14d --- /dev/null +++ b/debug/recreate_hashes.mjs @@ -0,0 +1,84 @@ +import fs from 'fs'; +import crypto from 'crypto'; +import db from '../src/inc/sql.mjs'; +import path from 'path'; + +const run = async () => { + console.log('Starting hash recreation (Production Mode - Streams)...'); + + try { + // Fetch only necessary columns + const items = await db`SELECT id, dest, checksum, size FROM items ORDER BY id ASC`; + console.log(`Found ${items.length} items. Processing...`); + + let updated = 0; + let errors = 0; + let skipped = 0; + + for (const [index, item] of items.entries()) { + const filePath = path.join('./public/b', item.dest); + + try { + if (!fs.existsSync(filePath)) { + // Silent error in logs for missing files to avoid spamming "thousands" of lines if many are missing + // Use verbose logging if needed, but here we'll just count them. + // Actually, precise logs are better for "production" to know what's wrong. + console.error(`[MISSING] File not found for item ${item.id}: ${filePath}`); + errors++; + continue; + } + + // Get file size without reading content + const stats = await fs.promises.stat(filePath); + const size = stats.size; + + // Calculate hash using stream to ensure low memory usage + const hash = await new Promise((resolve, reject) => { + const hashStream = crypto.createHash('sha256'); + const rs = fs.createReadStream(filePath); + + rs.on('error', reject); + rs.on('data', chunk => hashStream.update(chunk)); + rs.on('end', () => resolve(hashStream.digest('hex'))); + }); + + if (hash !== item.checksum || size !== item.size) { + console.log(`[UPDATE] Item ${item.id} (${index + 1}/${items.length})`); + if (hash !== item.checksum) console.log(` - Hash: ${item.checksum} -> ${hash}`); + if (size !== item.size) console.log(` - Size: ${item.size} -> ${size}`); + + await db` + UPDATE items + SET checksum = ${hash}, size = ${size} + WHERE id = ${item.id} + `; + updated++; + } else { + skipped++; + } + + // Log progress every 100 items + if ((index + 1) % 100 === 0) { + console.log(`Progress: ${index + 1}/${items.length} (Updated: ${updated}, Errors: ${errors})`); + } + + } catch (err) { + console.error(`[ERROR] Processing item ${item.id}:`, err); + errors++; + } + } + + console.log('Done.'); + console.log(`Total: ${items.length}`); + console.log(`Updated: ${updated}`); + console.log(`Skipped (No changes): ${skipped}`); + console.log(`Errors (Missing files): ${errors}`); + + } catch (err) { + console.error('Fatal error:', err); + } finally { + process.exit(0); + } +}; + +run(); diff --git a/debug/thumbnailer.mjs b/debug/thumbnailer.mjs new file mode 100644 index 0000000..251c42e --- /dev/null +++ b/debug/thumbnailer.mjs @@ -0,0 +1,87 @@ +import sql from "../src/inc/sql.mjs"; +import fs from "fs"; +import { exec as _exec } from "child_process"; + +const exec = cmd => new Promise((resolve, reject) => { + _exec(cmd, { maxBuffer: 5e3 * 1024 }, (err, stdout, stderr) => { + if(err) + return reject(err); + if(stderr) + console.error(stderr); + resolve({ stdout: stdout }); + }); +}); + +const _args = process.argv.slice(2); +const _itemid = +_args[0] || 0; + +// Ensure temp and output directories exist +if (!fs.existsSync('./tmp')) fs.mkdirSync('./tmp', { recursive: true }); +if (!fs.existsSync('./public/t')) fs.mkdirSync('./public/t', { recursive: true }); + +let items; +if(_itemid > 0) + items = await sql`select id, dest, mime, src from "items" where id = ${_itemid}`; +else + items = await sql`select id, dest, mime, src from "items"`; +let count = 1; +let total = items.length; + +for(let item of items) { + const itemid = item.id; + const filename = item.dest; + const mime = item.mime; + const link = item.src; + try { + if(mime.startsWith('video/') || mime == 'image/gif') { + const seeks = ['20%', '40%', '60%', '80%']; + for (const seek of seeks) { + await exec(`ffmpegthumbnailer -i./public/b/${filename} -s1024 -t ${seek} -o./tmp/${itemid}.png`); + try { + const { stdout } = await exec(`magick "./tmp/${itemid}.png" -colorspace Gray -format "%[fx:mean]" info:`); + if (parseFloat(stdout.trim()) > 0.05) break; + } catch (e) { break; } + } + } + else if(mime.startsWith('image/') && mime != 'image/gif') + await exec(`magick ./public/b/${filename}[0] ./tmp/${itemid}.png`); + else if(mime.startsWith('audio/')) { + if(link.match(/soundcloud/)) { + let cover = (await exec(`yt-dlp --get-thumbnail "${link}"`)).stdout.trim(); + if(!cover.match(/default_avatar/)) { + cover = cover.replace(/-(large|original)\./, '-t500x500.'); + try { + await exec(`wget "${cover}" -O ./tmp/${itemid}.jpg`); + const size = (await fs.promises.stat(`./tmp/${itemid}.jpg`)).size; + if(size >= 0) { + await exec(`magick ./tmp/${itemid}.jpg ./tmp/${itemid}.png`); + await exec(`magick ./tmp/${itemid}.jpg ./public/ca/${itemid}.webp`); + } + } catch(err) { + //console.log(err); + } + } + else { + await exec(`ffmpeg -i ./public/b/${filename} -update 1 -map 0:v -map 0:1 -c copy ./tmp/${itemid}.png`); + await exec(`magick ./tmp/${itemid}.png ./public/ca/${itemid}.webp`); + } + } + else { + await exec(`ffmpeg -i ./public/b/${filename} -update 1 -map 0:v -map 0:1 -c copy ./tmp/${itemid}.png`); + await exec(`magick ./tmp/${itemid}.png ./public/ca/${itemid}.webp`); + } + } + + await exec(`magick "./tmp/${itemid}.png" -resize "128x128^" -gravity center -crop 128x128+0+0 +repage ./public/t/${itemid}.webp`); + await fs.promises.unlink(`./tmp/${itemid}.png`).catch(err => {}); + await fs.promises.unlink(`./tmp/${itemid}.jpg`).catch(err => {}); + } catch(err) { + console.error(`Failed to generate thumbnail for ${itemid}:`, err.message); + // await exec(`magick ./mugge.png ./public/t/${itemid}.webp`); + } + console.log(`current: ${itemid} (${count} / ${total})`); + count++; +} + +console.log('Thumbnail generation complete.'); +process.exit(0); diff --git a/debug/trigger.mjs b/debug/trigger.mjs new file mode 100644 index 0000000..63e899c --- /dev/null +++ b/debug/trigger.mjs @@ -0,0 +1,34 @@ +import { promises as fs } from "fs"; + +(async () => { + const _args = process.argv.slice(2); + const _e = { + network: "console", + message: _args.join(" "), + args: _args.slice(1), + channel: "console", + user: { + prefix: "console!console@console", + nick: "console", + username: "console", + account: "console" + }, + reply: (...args) => console.log(args), + replyAction: (...args) => console.log(args), + replyNotice: (...args) => console.log(args) + }; + + const trigger = (await Promise.all((await fs.readdir("./src/inc/trigger")) + .filter(f => f.endsWith(".mjs")) + .map(async t => await (await import(`../src/inc/trigger/${t}`)).default()) + )).filter(t => t[0].call.test(_e.message)).map(t => ({ name: t[0].name, f: t[0].f })); + + try { + if(trigger.length === 0) + return console.error("no matches"); + console.log(`triggered > ${trigger[0].name} (${_e.message})`); + await trigger[0].f(_e); + } catch(err) { + console.error(err); + } +})(); diff --git a/debug/user-admin.mjs b/debug/user-admin.mjs new file mode 100644 index 0000000..cf3aeb8 --- /dev/null +++ b/debug/user-admin.mjs @@ -0,0 +1,110 @@ +import db from "../src/inc/sql.mjs"; +import lib from "../src/inc/lib.mjs"; + +const usage = () => { + console.log(` +Usage: node user-admin.mjs [args] + +Commands: + hash - Generate a password hash + create [--admin] [--mod] - Create a new user + delete - Delete a user + passwd - Change a user's password + list - List all users +`); + process.exit(1); +}; + +const args = process.argv.slice(2); +if (args.length === 0) usage(); + +const cmd = args[0]; + +(async () => { + try { + switch (cmd) { + case "hash": { + if (args.length < 2) usage(); + const hash = await lib.hash(args[1]); + console.log(`Hash: ${hash}`); + break; + } + + case "create": { + if (args.length < 3) usage(); + const username = args[1]; + const password = args[2]; + const isAdmin = args.includes("--admin"); + const isMod = args.includes("--mod"); + + const hash = await lib.hash(password); + const ts = ~~(Date.now() / 1e3); + + const existing = await db`select id from "user" where login = ${username.toLowerCase()}`; + if (existing.length > 0) { + console.error(`Error: User '${username}' already exists.`); + process.exit(1); + } + + const newUser = await db` + insert into "user" ("login", "user", "password", "admin", "is_moderator", "created_at") + values (${username.toLowerCase()}, ${username}, ${hash}, ${isAdmin}, ${isMod}, to_timestamp(${ts})) + returning id + `; + + const userId = newUser[0].id; + + // Add default options + await db` + insert into user_options (user_id, mode, theme, avatar) + values (${userId}, 3, 'amoled', 0) + `; + + console.log(`Successfully created user '${username}' (ID: ${userId})`); + break; + } + + case "delete": { + if (args.length < 2) usage(); + const username = args[1].toLowerCase(); + + const result = await db`delete from "user" where login = ${username} returning id`; + if (result.length === 0) { + console.error(`Error: User '${username}' not found.`); + process.exit(1); + } + console.log(`Successfully deleted user '${username}' (ID: ${result[0].id})`); + break; + } + + case "passwd": { + if (args.length < 3) usage(); + const username = args[1].toLowerCase(); + const newPass = args[2]; + + const hash = await lib.hash(newPass); + const result = await db`update "user" set password = ${hash} where login = ${username} returning id`; + + if (result.length === 0) { + console.error(`Error: User '${username}' not found.`); + process.exit(1); + } + console.log(`Successfully updated password for user '${username}'`); + break; + } + + case "list": { + const users = await db`select id, login, "user", admin, is_moderator, created_at from "user" order by id asc`; + console.table(users); + break; + } + + default: + usage(); + } + } catch (err) { + console.error("Fatal error:", err); + } finally { + process.exit(0); + } +})(); diff --git a/logs/.empty b/logs/.empty new file mode 100644 index 0000000..e69de29 diff --git a/migrations/f0ckm_schema.sql b/migrations/f0ckm_schema.sql new file mode 100644 index 0000000..a64c8ef --- /dev/null +++ b/migrations/f0ckm_schema.sql @@ -0,0 +1,2428 @@ +-- +-- PostgreSQL database dump +-- + +\restrict ifoUZevi3oYdI7OmgFxUaco0kNV6kdlFS55QWa8PuaWXA3AY2nPUcs8ekmXvMEU + +-- Dumped from database version 17.7 (Debian 17.7-3.pgdg13+1) +-- Dumped by pg_dump version 18.3 + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET transaction_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: public; Type: SCHEMA; Schema: -; Owner: f0ckm +-- + +-- *not* creating schema, since initdb creates it + + +ALTER SCHEMA public OWNER TO f0ckm; + +-- +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: f0ckm +-- + +COMMENT ON SCHEMA public IS ''; + + +-- +-- Name: unaccent; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS unaccent WITH SCHEMA public; + + +-- +-- Name: EXTENSION unaccent; Type: COMMENT; Schema: -; Owner: +-- + +COMMENT ON EXTENSION unaccent IS 'text search dictionary that removes accents'; + + +-- +-- Name: delete_unused_tags(); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.delete_unused_tags() RETURNS trigger + LANGUAGE plpgsql + AS $$ +begin + delete from tags + where + tags.id not in (select tag_id from tags_assign) and + tags.id = OLD.tag_id and + tags.tag != 'sfw' and + tags.tag != 'nsfw'; + return OLD; +end $$; + + +ALTER FUNCTION public.delete_unused_tags() OWNER TO f0ckm; + +-- +-- Name: fill_normalized(); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.fill_normalized() RETURNS trigger + LANGUAGE plpgsql + AS $$ +begin + NEW.normalized = slugify(NEW.tag); + return NEW; +end$$; + + +ALTER FUNCTION public.fill_normalized() OWNER TO f0ckm; + +-- +-- Name: notify_activity(); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.notify_activity() RETURNS trigger + LANGUAGE plpgsql + AS $$ + DECLARE + payload JSON; + BEGIN + SELECT json_build_object( + 'id', NEW.id, + 'user_id', NEW.user_id, + 'item_id', NEW.item_id, + 'parent_id', NEW.parent_id, + 'body', LEFT(NEW.content, 2000), -- Truncate to prevent payload string too long error + 'created_at', NEW.created_at + ) INTO payload; + + PERFORM pg_notify('activity', payload::text); + RETURN NEW; + END; + $$; + + +ALTER FUNCTION public.notify_activity() OWNER TO f0ckm; + +-- +-- Name: notify_global_chat(); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.notify_global_chat() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + PERFORM pg_notify('global_chat', row_to_json(NEW)::text); + RETURN NEW; +END; +$$; + + +ALTER FUNCTION public.notify_global_chat() OWNER TO f0ckm; + +-- +-- Name: notify_notification(); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.notify_notification() RETURNS trigger + LANGUAGE plpgsql + AS $$ +DECLARE + payload JSON; +BEGIN + -- Only notify on INSERT or when transitioning to is_read = true + IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE' AND OLD.is_read = false AND NEW.is_read = true) THEN + SELECT json_build_object( + 'id', NEW.id, + 'user_id', NEW.user_id, + 'type', NEW.type, + 'item_id', NEW.item_id, + 'is_read', NEW.is_read, + 'created_at', NEW.created_at + ) INTO payload; + + PERFORM pg_notify('notifications', payload::text); + END IF; + RETURN NEW; +END; +$$; + + +ALTER FUNCTION public.notify_notification() OWNER TO f0ckm; + +-- +-- Name: notify_private_message(); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.notify_private_message() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + PERFORM pg_notify('private_message', json_build_object( + 'id', NEW.id, + 'sender_id', NEW.sender_id, + 'recipient_id', NEW.recipient_id, + 'created_at', NEW.created_at + )::text); + RETURN NEW; +END; +$$; + + +ALTER FUNCTION public.notify_private_message() OWNER TO f0ckm; + +-- +-- Name: slugify(text); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.slugify(v text) RETURNS text + LANGUAGE plpgsql + AS $$ +BEGIN + RETURN trim(BOTH '-' FROM regexp_replace(lower(unaccent(trim(v))), '[\u0000-\u002f \u003a-\u0040\u005b-\u0060\u007b-\u00bf]+', '', 'gi')); +END; +$$; + + +ALTER FUNCTION public.slugify(v text) OWNER TO f0ckm; + +-- +-- Name: trg_update_xd_score(); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.trg_update_xd_score() RETURNS trigger + LANGUAGE plpgsql + AS $$ +BEGIN + IF TG_OP = 'DELETE' THEN + PERFORM update_item_xd_score(OLD.item_id); + ELSE + PERFORM update_item_xd_score(NEW.item_id); + END IF; + RETURN NULL; +END; +$$; + + +ALTER FUNCTION public.trg_update_xd_score() OWNER TO f0ckm; + +-- +-- Name: unaccent_text(text); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.unaccent_text(text) RETURNS text + LANGUAGE sql IMMUTABLE COST 1 + AS $_$ + -- unaccent is STABLE, but the indexes must use IMMUTABLE functions. + -- comment this line out when calling pg_dump. + SELECT unaccent($1); + + -- Uncomment this line when calling pg_dump. + --SELECT ''::text; +$_$; + + +ALTER FUNCTION public.unaccent_text(text) OWNER TO f0ckm; + +-- +-- Name: update_item_xd_score(bigint); Type: FUNCTION; Schema: public; Owner: f0ckm +-- + +CREATE FUNCTION public.update_item_xd_score(p_item_id bigint) RETURNS void + LANGUAGE plpgsql + AS $$ +BEGIN + UPDATE public.items SET xd_score = ( + SELECT COALESCE(SUM(length(m[1])), 0) + FROM public.comments c + CROSS JOIN LATERAL regexp_matches(c.content, 'x(D+)', 'gi') AS m + WHERE c.item_id = p_item_id AND c.is_deleted = false + ) + WHERE id = p_item_id; +END; +$$; + + +ALTER FUNCTION public.update_item_xd_score(p_item_id bigint) OWNER TO f0ckm; + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: audit_log; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.audit_log ( + id integer NOT NULL, + user_id integer NOT NULL, + action character varying(50) NOT NULL, + target_type character varying(50), + target_id character varying(50), + details jsonb, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.audit_log OWNER TO f0ckm; + +-- +-- Name: audit_log_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.audit_log_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.audit_log_id_seq OWNER TO f0ckm; + +-- +-- Name: audit_log_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.audit_log_id_seq OWNED BY public.audit_log.id; + + +-- +-- Name: comment_subscriptions; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.comment_subscriptions ( + user_id integer NOT NULL, + item_id integer NOT NULL, + created_at timestamp with time zone DEFAULT now(), + is_subscribed boolean DEFAULT true +); + + +ALTER TABLE public.comment_subscriptions OWNER TO f0ckm; + +-- +-- Name: comments; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.comments ( + id integer NOT NULL, + item_id integer NOT NULL, + user_id integer NOT NULL, + parent_id integer, + content text NOT NULL, + is_deleted boolean DEFAULT false, + created_at timestamp with time zone DEFAULT now(), + updated_at timestamp with time zone, + vote_score integer DEFAULT 0, + is_pinned boolean DEFAULT false, + video_time numeric(10,3) +); + + +ALTER TABLE public.comments OWNER TO f0ckm; + +-- +-- Name: comments_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.comments_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.comments_id_seq OWNER TO f0ckm; + +-- +-- Name: comments_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.comments_id_seq OWNED BY public.comments.id; + + +-- +-- Name: custom_emojis; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.custom_emojis ( + id integer NOT NULL, + name text NOT NULL, + url text NOT NULL, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.custom_emojis OWNER TO f0ckm; + +-- +-- Name: custom_emojis_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.custom_emojis_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.custom_emojis_id_seq OWNER TO f0ckm; + +-- +-- Name: custom_emojis_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.custom_emojis_id_seq OWNED BY public.custom_emojis.id; + + +-- +-- Name: discord_queue; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.discord_queue ( + id integer NOT NULL, + user_id integer NOT NULL, + item_id integer NOT NULL, + type character varying(50) DEFAULT 'approve'::character varying NOT NULL, + sent boolean DEFAULT false, + created_at timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.discord_queue OWNER TO f0ckm; + +-- +-- Name: discord_queue_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.discord_queue_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.discord_queue_id_seq OWNER TO f0ckm; + +-- +-- Name: discord_queue_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.discord_queue_id_seq OWNED BY public.discord_queue.id; + + +-- +-- Name: favorites; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.favorites ( + user_id integer NOT NULL, + item_id integer NOT NULL +); + + +ALTER TABLE public.favorites OWNER TO f0ckm; + +-- +-- Name: global_chat; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.global_chat ( + id bigint NOT NULL, + user_id integer NOT NULL, + message text NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +ALTER TABLE public.global_chat OWNER TO f0ckm; + +-- +-- Name: global_chat_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.global_chat_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.global_chat_id_seq OWNER TO f0ckm; + +-- +-- Name: global_chat_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.global_chat_id_seq OWNED BY public.global_chat.id; + + +-- +-- Name: global_chat_settings; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.global_chat_settings ( + key text NOT NULL, + value text +); + + +ALTER TABLE public.global_chat_settings OWNER TO f0ckm; + +-- +-- Name: halls; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.halls ( + id integer NOT NULL, + name text NOT NULL, + slug text NOT NULL, + created_at timestamp with time zone DEFAULT now(), + description text, + custom_image text, + rating text DEFAULT 'sfw'::text NOT NULL +); + + +ALTER TABLE public.halls OWNER TO f0ckm; + +-- +-- Name: halls_assign; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.halls_assign ( + hall_id integer NOT NULL, + item_id integer NOT NULL, + user_id integer, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.halls_assign OWNER TO f0ckm; + +-- +-- Name: halls_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.halls_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.halls_id_seq OWNER TO f0ckm; + +-- +-- Name: halls_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.halls_id_seq OWNED BY public.halls.id; + + +-- +-- Name: invite_tokens; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.invite_tokens ( + id integer NOT NULL, + token character varying(32) NOT NULL, + created_at bigint NOT NULL, + created_by integer, + used_by integer, + is_used boolean DEFAULT false, + created_by_discord character varying(255) DEFAULT NULL::character varying, + created_by_matrix character varying(255) DEFAULT NULL::character varying +); + + +ALTER TABLE public.invite_tokens OWNER TO f0ckm; + +-- +-- Name: invite_tokens_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.invite_tokens_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.invite_tokens_id_seq OWNER TO f0ckm; + +-- +-- Name: invite_tokens_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.invite_tokens_id_seq OWNED BY public.invite_tokens.id; + + +-- +-- Name: items_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.items_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.items_id_seq OWNER TO f0ckm; + +-- +-- Name: items; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.items ( + id integer DEFAULT nextval('public.items_id_seq'::regclass) NOT NULL, + src character varying(255) NOT NULL, + dest character varying(40) NOT NULL, + mime character varying(100) NOT NULL, + size integer NOT NULL, + checksum character varying(255) NOT NULL, + username character varying(40) NOT NULL, + userchannel character varying(255) NOT NULL, + usernetwork character varying(40) NOT NULL, + stamp integer NOT NULL, + active boolean NOT NULL, + thumb character varying(100), + is_deleted boolean DEFAULT false, + is_comments_locked boolean DEFAULT false, + phash text, + is_purged boolean DEFAULT false, + has_coverart boolean DEFAULT false, + is_pinned boolean DEFAULT false, + is_oc boolean DEFAULT false, + xd_score integer DEFAULT 0 NOT NULL +); + + +ALTER TABLE public.items OWNER TO f0ckm; + +-- +-- Name: COLUMN items.src; Type: COMMENT; Schema: public; Owner: f0ckm +-- + +COMMENT ON COLUMN public.items.src IS 'src-Link'; + + +-- +-- Name: COLUMN items.dest; Type: COMMENT; Schema: public; Owner: f0ckm +-- + +COMMENT ON COLUMN public.items.dest IS 'filename'; + + +-- +-- Name: items_li; Type: VIEW; Schema: public; Owner: f0ckm +-- + +CREATE VIEW public.items_li AS +SELECT + NULL::integer AS id, + NULL::character varying(255) AS src, + NULL::character varying(40) AS dest, + NULL::character varying(100) AS mime, + NULL::integer AS size, + NULL::character varying(255) AS checksum, + NULL::character varying(40) AS username, + NULL::character varying(255) AS userchannel, + NULL::character varying(40) AS usernetwork, + NULL::integer AS stamp; + + +ALTER VIEW public.items_li OWNER TO f0ckm; + +-- +-- Name: tags_assign; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.tags_assign ( + item_id integer NOT NULL, + tag_id integer NOT NULL, + user_id integer DEFAULT 10 NOT NULL +); + + +ALTER TABLE public.tags_assign OWNER TO f0ckm; + +-- +-- Name: tags_nsfp; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.tags_nsfp ( + id integer NOT NULL +); + + +ALTER TABLE public.tags_nsfp OWNER TO f0ckm; + +-- +-- Name: items_sfw; Type: VIEW; Schema: public; Owner: f0ckm +-- + +CREATE VIEW public.items_sfw AS + SELECT ( SELECT + CASE + WHEN (tags_assign.tag_id > 0) THEN tags_assign.tag_id + ELSE 0 + END AS "case" + FROM public.tags_assign + WHERE ((tags_assign.tag_id = ANY (ARRAY[1, 2])) AND (tags_assign.item_id = items.id))) AS sfw, + ( SELECT + CASE + WHEN (tags_assign.tag_id > 0) THEN 1 + ELSE 0 + END AS "case" + FROM public.tags_assign + WHERE ((tags_assign.tag_id IN ( SELECT tags_nsfp.id + FROM public.tags_nsfp)) AND (tags_assign.item_id = items.id)) + LIMIT 1) AS nsfp, + id, + src, + dest, + mime, + size, + checksum, + username, + userchannel, + usernetwork, + stamp, + active + FROM public.items; + + +ALTER VIEW public.items_sfw OWNER TO f0ckm; + +-- +-- Name: link_token; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.link_token ( + id integer NOT NULL, + user_id integer NOT NULL, + token character varying(64) NOT NULL, + created_at timestamp without time zone DEFAULT CURRENT_TIMESTAMP, + expires_at timestamp without time zone DEFAULT (CURRENT_TIMESTAMP + '00:10:00'::interval) +); + + +ALTER TABLE public.link_token OWNER TO f0ckm; + +-- +-- Name: link_token_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.link_token_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.link_token_id_seq OWNER TO f0ckm; + +-- +-- Name: link_token_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.link_token_id_seq OWNED BY public.link_token.id; + + +-- +-- Name: login_attempts; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.login_attempts ( + id integer NOT NULL, + ip_hash text NOT NULL, + username text, + type text NOT NULL, + attempted_at timestamp with time zone DEFAULT now(), + success boolean NOT NULL +); + + +ALTER TABLE public.login_attempts OWNER TO f0ckm; + +-- +-- Name: login_attempts_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.login_attempts_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.login_attempts_id_seq OWNER TO f0ckm; + +-- +-- Name: login_attempts_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.login_attempts_id_seq OWNED BY public.login_attempts.id; + + +-- +-- Name: meme_templates; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.meme_templates ( + id integer NOT NULL, + template_id character varying(255) NOT NULL, + name character varying(255) NOT NULL, + url text NOT NULL, + created_at integer DEFAULT (EXTRACT(epoch FROM now()))::integer, + category character varying(100) DEFAULT 'General'::character varying, + sub_category text +); + + +ALTER TABLE public.meme_templates OWNER TO f0ckm; + +-- +-- Name: meme_templates_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.meme_templates_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.meme_templates_id_seq OWNER TO f0ckm; + +-- +-- Name: meme_templates_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.meme_templates_id_seq OWNED BY public.meme_templates.id; + + +-- +-- Name: notifications; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.notifications ( + id integer NOT NULL, + user_id integer NOT NULL, + type character varying(32) NOT NULL, + reference_id integer NOT NULL, + item_id integer, + is_read boolean DEFAULT false, + created_at timestamp with time zone DEFAULT now(), + data jsonb DEFAULT '{}'::jsonb +); + + +ALTER TABLE public.notifications OWNER TO f0ckm; + +-- +-- Name: notifications_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.notifications_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.notifications_id_seq OWNER TO f0ckm; + +-- +-- Name: notifications_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.notifications_id_seq OWNED BY public.notifications.id; + + +-- +-- Name: private_messages; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.private_messages ( + id bigint NOT NULL, + sender_id integer NOT NULL, + recipient_id integer NOT NULL, + ciphertext text NOT NULL, + iv text NOT NULL, + is_read boolean DEFAULT false, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.private_messages OWNER TO f0ckm; + +-- +-- Name: private_messages_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.private_messages_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.private_messages_id_seq OWNER TO f0ckm; + +-- +-- Name: private_messages_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.private_messages_id_seq OWNED BY public.private_messages.id; + + +-- +-- Name: reports; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.reports ( + id integer NOT NULL, + reporter_id integer, + item_id integer, + comment_id integer, + user_id integer, + reason text NOT NULL, + status text DEFAULT 'pending'::text, + resolved_by integer, + created_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.reports OWNER TO f0ckm; + +-- +-- Name: reports_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.reports_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.reports_id_seq OWNER TO f0ckm; + +-- +-- Name: reports_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.reports_id_seq OWNED BY public.reports.id; + + +-- +-- Name: site_settings; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.site_settings ( + key text NOT NULL, + value text NOT NULL +); + + +ALTER TABLE public.site_settings OWNER TO f0ckm; + +-- +-- Name: tags_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.tags_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.tags_id_seq OWNER TO f0ckm; + +-- +-- Name: tags; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.tags ( + id integer DEFAULT nextval('public.tags_id_seq'::regclass) NOT NULL, + tag character varying(255) NOT NULL, + normalized character varying(255) +); + + +ALTER TABLE public.tags OWNER TO f0ckm; + +-- +-- Name: tags_alias; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.tags_alias ( + tag_orig_id integer NOT NULL, + tag_alias character varying NOT NULL +); + + +ALTER TABLE public.tags_alias OWNER TO f0ckm; + +-- +-- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.user_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.user_id_seq OWNER TO f0ckm; + +-- +-- Name: user; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public."user" ( + id integer DEFAULT nextval('public.user_id_seq'::regclass) NOT NULL, + login character varying(255) NOT NULL, + "user" character varying(255) NOT NULL, + password character varying(167) NOT NULL, + admin boolean NOT NULL, + created_at timestamp without time zone DEFAULT now() NOT NULL, + ban_expires timestamp with time zone, + banned boolean DEFAULT false, + ban_reason text, + is_moderator boolean NOT NULL, + activated boolean DEFAULT true, + activation_token text, + email text, + reset_token text, + reset_expires timestamp without time zone, + username_color character varying, + last_seen integer DEFAULT 0, + force_password_change boolean DEFAULT false +); + + +ALTER TABLE public."user" OWNER TO f0ckm; + +-- +-- Name: user_alias; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_alias ( + userid integer NOT NULL, + alias character varying(255) NOT NULL, + type character varying(32) DEFAULT 'discord'::character varying NOT NULL +); + +ALTER TABLE ONLY public.user_alias REPLICA IDENTITY FULL; + + +ALTER TABLE public.user_alias OWNER TO f0ckm; + +-- +-- Name: user_conversation_states; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_conversation_states ( + user_id integer NOT NULL, + other_id integer NOT NULL, + is_hidden boolean DEFAULT false +); + + +ALTER TABLE public.user_conversation_states OWNER TO f0ckm; + +-- +-- Name: user_dm_keyvault; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_dm_keyvault ( + user_id integer NOT NULL, + salt text NOT NULL, + iv text NOT NULL, + ciphertext text NOT NULL, + version smallint DEFAULT 1, + created_at timestamp with time zone DEFAULT now(), + updated_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.user_dm_keyvault OWNER TO f0ckm; + +-- +-- Name: user_halls; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_halls ( + id integer NOT NULL, + user_id integer NOT NULL, + name text NOT NULL, + slug text NOT NULL, + description text, + is_private boolean DEFAULT false NOT NULL, + custom_image boolean DEFAULT false NOT NULL, + created_at timestamp with time zone DEFAULT now() NOT NULL +); + + +ALTER TABLE public.user_halls OWNER TO f0ckm; + +-- +-- Name: user_halls_assign; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_halls_assign ( + hall_id integer NOT NULL, + item_id integer NOT NULL, + user_id integer NOT NULL, + added_at timestamp with time zone DEFAULT now() NOT NULL +); + + +ALTER TABLE public.user_halls_assign OWNER TO f0ckm; + +-- +-- Name: user_halls_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.user_halls_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.user_halls_id_seq OWNER TO f0ckm; + +-- +-- Name: user_halls_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.user_halls_id_seq OWNED BY public.user_halls.id; + + +-- +-- Name: user_options; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_options ( + user_id integer NOT NULL, + mode integer NOT NULL, + theme character varying(50) NOT NULL, + avatar integer, + fullscreen smallint DEFAULT '0'::smallint NOT NULL, + excluded_tags integer[] DEFAULT '{}'::integer[] NOT NULL, + avatar_file character varying(255), + show_motd boolean DEFAULT true, + strict_mode boolean DEFAULT false, + use_new_layout boolean DEFAULT false, + username_color character varying(20) DEFAULT NULL::character varying, + font character varying(255) DEFAULT NULL::character varying, + font_size smallint, + disable_autoplay boolean DEFAULT false, + disable_swiping boolean DEFAULT false, + description text, + display_name character varying(60), + min_xd_score integer DEFAULT 0 NOT NULL, + show_background boolean, + ruffle_volume numeric(3,2) DEFAULT NULL::numeric, + ruffle_background boolean DEFAULT true, + quote_emojis boolean DEFAULT true NOT NULL, + embed_youtube_in_comments boolean DEFAULT true NOT NULL, + hide_koepfe boolean DEFAULT false NOT NULL, + language text +); + + +ALTER TABLE public.user_options OWNER TO f0ckm; + +-- +-- Name: COLUMN user_options.avatar_file; Type: COMMENT; Schema: public; Owner: f0ckm +-- + +COMMENT ON COLUMN public.user_options.avatar_file IS 'Custom uploaded avatar filename, stored in public/a/'; + + +-- +-- Name: user_pubkeys; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_pubkeys ( + user_id integer NOT NULL, + pubkey text NOT NULL, + fingerprint text, + created_at timestamp with time zone DEFAULT now(), + updated_at timestamp with time zone DEFAULT now() +); + + +ALTER TABLE public.user_pubkeys OWNER TO f0ckm; + +-- +-- Name: user_sessions_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.user_sessions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.user_sessions_id_seq OWNER TO f0ckm; + +-- +-- Name: user_sessions; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_sessions ( + id integer DEFAULT nextval('public.user_sessions_id_seq'::regclass) NOT NULL, + user_id integer NOT NULL, + session character varying(64) NOT NULL, + browser character varying(255) NOT NULL, + created_at integer NOT NULL, + last_used integer NOT NULL, + last_action character varying(255) NOT NULL, + kmsi smallint DEFAULT '0'::smallint NOT NULL, + csrf_token character varying(64) +); + + +ALTER TABLE public.user_sessions OWNER TO f0ckm; + +-- +-- Name: user_video_views; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_video_views ( + user_id integer NOT NULL, + video_id integer NOT NULL, + view_count integer DEFAULT 1, + last_viewed timestamp without time zone DEFAULT now() +); + + +ALTER TABLE public.user_video_views OWNER TO f0ckm; + +-- +-- Name: user_warnings; Type: TABLE; Schema: public; Owner: f0ckm +-- + +CREATE TABLE public.user_warnings ( + id integer NOT NULL, + user_id integer, + admin_id integer, + reason text NOT NULL, + created_at timestamp with time zone DEFAULT now(), + acknowledged boolean DEFAULT false +); + + +ALTER TABLE public.user_warnings OWNER TO f0ckm; + +-- +-- Name: user_warnings_id_seq; Type: SEQUENCE; Schema: public; Owner: f0ckm +-- + +CREATE SEQUENCE public.user_warnings_id_seq + AS integer + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE public.user_warnings_id_seq OWNER TO f0ckm; + +-- +-- Name: user_warnings_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: f0ckm +-- + +ALTER SEQUENCE public.user_warnings_id_seq OWNED BY public.user_warnings.id; + + +-- +-- Name: audit_log id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.audit_log ALTER COLUMN id SET DEFAULT nextval('public.audit_log_id_seq'::regclass); + + +-- +-- Name: comments id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.comments ALTER COLUMN id SET DEFAULT nextval('public.comments_id_seq'::regclass); + + +-- +-- Name: custom_emojis id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.custom_emojis ALTER COLUMN id SET DEFAULT nextval('public.custom_emojis_id_seq'::regclass); + + +-- +-- Name: discord_queue id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.discord_queue ALTER COLUMN id SET DEFAULT nextval('public.discord_queue_id_seq'::regclass); + + +-- +-- Name: global_chat id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.global_chat ALTER COLUMN id SET DEFAULT nextval('public.global_chat_id_seq'::regclass); + + +-- +-- Name: halls id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.halls ALTER COLUMN id SET DEFAULT nextval('public.halls_id_seq'::regclass); + + +-- +-- Name: invite_tokens id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.invite_tokens ALTER COLUMN id SET DEFAULT nextval('public.invite_tokens_id_seq'::regclass); + + +-- +-- Name: link_token id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.link_token ALTER COLUMN id SET DEFAULT nextval('public.link_token_id_seq'::regclass); + + +-- +-- Name: login_attempts id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.login_attempts ALTER COLUMN id SET DEFAULT nextval('public.login_attempts_id_seq'::regclass); + + +-- +-- Name: meme_templates id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.meme_templates ALTER COLUMN id SET DEFAULT nextval('public.meme_templates_id_seq'::regclass); + + +-- +-- Name: notifications id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.notifications ALTER COLUMN id SET DEFAULT nextval('public.notifications_id_seq'::regclass); + + +-- +-- Name: private_messages id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.private_messages ALTER COLUMN id SET DEFAULT nextval('public.private_messages_id_seq'::regclass); + + +-- +-- Name: reports id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.reports ALTER COLUMN id SET DEFAULT nextval('public.reports_id_seq'::regclass); + + +-- +-- Name: user_halls id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_halls ALTER COLUMN id SET DEFAULT nextval('public.user_halls_id_seq'::regclass); + + +-- +-- Name: user_warnings id; Type: DEFAULT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_warnings ALTER COLUMN id SET DEFAULT nextval('public.user_warnings_id_seq'::regclass); + + +-- +-- Name: audit_log audit_log_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.audit_log + ADD CONSTRAINT audit_log_pkey PRIMARY KEY (id); + + +-- +-- Name: comment_subscriptions comment_subscriptions_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.comment_subscriptions + ADD CONSTRAINT comment_subscriptions_pkey PRIMARY KEY (user_id, item_id); + + +-- +-- Name: comments comments_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.comments + ADD CONSTRAINT comments_pkey PRIMARY KEY (id); + + +-- +-- Name: custom_emojis custom_emojis_name_key; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.custom_emojis + ADD CONSTRAINT custom_emojis_name_key UNIQUE (name); + + +-- +-- Name: custom_emojis custom_emojis_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.custom_emojis + ADD CONSTRAINT custom_emojis_pkey PRIMARY KEY (id); + + +-- +-- Name: discord_queue discord_queue_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.discord_queue + ADD CONSTRAINT discord_queue_pkey PRIMARY KEY (id); + + +-- +-- Name: global_chat global_chat_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.global_chat + ADD CONSTRAINT global_chat_pkey PRIMARY KEY (id); + + +-- +-- Name: global_chat_settings global_chat_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.global_chat_settings + ADD CONSTRAINT global_chat_settings_pkey PRIMARY KEY (key); + + +-- +-- Name: halls_assign halls_assign_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.halls_assign + ADD CONSTRAINT halls_assign_pkey PRIMARY KEY (hall_id, item_id); + + +-- +-- Name: halls halls_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.halls + ADD CONSTRAINT halls_pkey PRIMARY KEY (id); + + +-- +-- Name: halls halls_slug_key; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.halls + ADD CONSTRAINT halls_slug_key UNIQUE (slug); + + +-- +-- Name: favorites idx_16521_primary; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.favorites + ADD CONSTRAINT idx_16521_primary PRIMARY KEY (user_id, item_id); + + +-- +-- Name: items idx_16526_primary; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.items + ADD CONSTRAINT idx_16526_primary PRIMARY KEY (id); + + +-- +-- Name: user idx_16554_primary; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public."user" + ADD CONSTRAINT idx_16554_primary PRIMARY KEY (id); + + +-- +-- Name: user_options idx_16567_user_id; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_options + ADD CONSTRAINT idx_16567_user_id UNIQUE (user_id); + + +-- +-- Name: user_sessions idx_16572_primary; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_sessions + ADD CONSTRAINT idx_16572_primary PRIMARY KEY (id); + + +-- +-- Name: invite_tokens invite_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.invite_tokens + ADD CONSTRAINT invite_tokens_pkey PRIMARY KEY (id); + + +-- +-- Name: invite_tokens invite_tokens_token_key; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.invite_tokens + ADD CONSTRAINT invite_tokens_token_key UNIQUE (token); + + +-- +-- Name: items items_checksum; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.items + ADD CONSTRAINT items_checksum UNIQUE (checksum); + + +-- +-- Name: link_token link_token_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.link_token + ADD CONSTRAINT link_token_pkey PRIMARY KEY (id); + + +-- +-- Name: link_token link_token_token_key; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.link_token + ADD CONSTRAINT link_token_token_key UNIQUE (token); + + +-- +-- Name: login_attempts login_attempts_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.login_attempts + ADD CONSTRAINT login_attempts_pkey PRIMARY KEY (id); + + +-- +-- Name: meme_templates meme_templates_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.meme_templates + ADD CONSTRAINT meme_templates_pkey PRIMARY KEY (id); + + +-- +-- Name: meme_templates meme_templates_template_id_key; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.meme_templates + ADD CONSTRAINT meme_templates_template_id_key UNIQUE (template_id); + + +-- +-- Name: notifications notifications_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.notifications + ADD CONSTRAINT notifications_pkey PRIMARY KEY (id); + + +-- +-- Name: private_messages private_messages_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.private_messages + ADD CONSTRAINT private_messages_pkey PRIMARY KEY (id); + + +-- +-- Name: reports reports_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.reports + ADD CONSTRAINT reports_pkey PRIMARY KEY (id); + + +-- +-- Name: site_settings site_settings_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.site_settings + ADD CONSTRAINT site_settings_pkey PRIMARY KEY (key); + + +-- +-- Name: tags_alias tags_alias_tag_alias_tag_orig_id; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags_alias + ADD CONSTRAINT tags_alias_tag_alias_tag_orig_id UNIQUE (tag_alias, tag_orig_id); + + +-- +-- Name: tags_alias tags_alias_tag_orig_id; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags_alias + ADD CONSTRAINT tags_alias_tag_orig_id PRIMARY KEY (tag_orig_id); + + +-- +-- Name: tags_assign tags_assign_item_id_tag_id_primary; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags_assign + ADD CONSTRAINT tags_assign_item_id_tag_id_primary PRIMARY KEY (item_id, tag_id); + + +-- +-- Name: tags_assign tags_assign_item_id_tag_id_unique; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags_assign + ADD CONSTRAINT tags_assign_item_id_tag_id_unique UNIQUE (item_id, tag_id); + + +-- +-- Name: tags tags_id; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags + ADD CONSTRAINT tags_id PRIMARY KEY (id); + + +-- +-- Name: user_conversation_states user_conversation_states_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_conversation_states + ADD CONSTRAINT user_conversation_states_pkey PRIMARY KEY (user_id, other_id); + + +-- +-- Name: user_dm_keyvault user_dm_keyvault_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_dm_keyvault + ADD CONSTRAINT user_dm_keyvault_pkey PRIMARY KEY (user_id); + + +-- +-- Name: user_halls_assign user_halls_assign_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_halls_assign + ADD CONSTRAINT user_halls_assign_pkey PRIMARY KEY (hall_id, item_id); + + +-- +-- Name: user_halls user_halls_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_halls + ADD CONSTRAINT user_halls_pkey PRIMARY KEY (id); + + +-- +-- Name: user_halls user_halls_user_id_slug_key; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_halls + ADD CONSTRAINT user_halls_user_id_slug_key UNIQUE (user_id, slug); + + +-- +-- Name: user user_login_unique; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public."user" + ADD CONSTRAINT user_login_unique UNIQUE (login); + + +-- +-- Name: user user_name_unique; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public."user" + ADD CONSTRAINT user_name_unique UNIQUE ("user"); + + +-- +-- Name: user_options user_options_user_id; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_options + ADD CONSTRAINT user_options_user_id PRIMARY KEY (user_id); + + +-- +-- Name: user_pubkeys user_pubkeys_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_pubkeys + ADD CONSTRAINT user_pubkeys_pkey PRIMARY KEY (user_id); + + +-- +-- Name: user_video_views user_video_views_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_video_views + ADD CONSTRAINT user_video_views_pkey PRIMARY KEY (user_id, video_id); + + +-- +-- Name: user_warnings user_warnings_pkey; Type: CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_warnings + ADD CONSTRAINT user_warnings_pkey PRIMARY KEY (id); + + +-- +-- Name: idx_audit_log_action; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_audit_log_action ON public.audit_log USING btree (action); + + +-- +-- Name: idx_audit_log_created_at; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_audit_log_created_at ON public.audit_log USING btree (created_at); + + +-- +-- Name: idx_audit_log_user_id; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_audit_log_user_id ON public.audit_log USING btree (user_id); + + +-- +-- Name: idx_comments_is_pinned; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_comments_is_pinned ON public.comments USING btree (is_pinned) WHERE (is_pinned = true); + + +-- +-- Name: idx_comments_item_id; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_comments_item_id ON public.comments USING btree (item_id); + + +-- +-- Name: idx_comments_user_id; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_comments_user_id ON public.comments USING btree (user_id); + + +-- +-- Name: idx_discord_queue_sent; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_discord_queue_sent ON public.discord_queue USING btree (sent) WHERE (sent = false); + + +-- +-- Name: idx_global_chat_created_at; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_global_chat_created_at ON public.global_chat USING btree (created_at DESC); + + +-- +-- Name: idx_items_active_id; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_items_active_id ON public.items USING btree (id) WHERE (active = true); + + +-- +-- Name: idx_items_is_deleted; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_items_is_deleted ON public.items USING btree (is_deleted) WHERE (is_deleted = false); + + +-- +-- Name: idx_items_is_purged; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_items_is_purged ON public.items USING btree (is_purged) WHERE (is_purged = true); + + +-- +-- Name: idx_items_username; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_items_username ON public.items USING btree (username); + + +-- +-- Name: idx_items_username_lower; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_items_username_lower ON public.items USING btree (lower((username)::text)); + + +-- +-- Name: idx_items_xd_score; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_items_xd_score ON public.items USING btree (xd_score) WHERE (xd_score > 0); + + +-- +-- Name: idx_link_token_token; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_link_token_token ON public.link_token USING btree (token); + + +-- +-- Name: idx_login_attempts_ip_time; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_login_attempts_ip_time ON public.login_attempts USING btree (ip_hash, attempted_at DESC); + + +-- +-- Name: idx_login_attempts_username_time; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_login_attempts_username_time ON public.login_attempts USING btree (username, attempted_at DESC); + + +-- +-- Name: idx_notifications_unread; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_notifications_unread ON public.notifications USING btree (user_id) WHERE (is_read = false); + + +-- +-- Name: idx_notifications_user_id; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_notifications_user_id ON public.notifications USING btree (user_id); + + +-- +-- Name: idx_pm_conversation; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_pm_conversation ON public.private_messages USING btree (LEAST(sender_id, recipient_id), GREATEST(sender_id, recipient_id), created_at DESC); + + +-- +-- Name: idx_pm_recipient; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_pm_recipient ON public.private_messages USING btree (recipient_id, created_at DESC); + + +-- +-- Name: idx_pm_sender; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_pm_sender ON public.private_messages USING btree (sender_id, created_at DESC); + + +-- +-- Name: idx_reports_status; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_reports_status ON public.reports USING btree (status); + + +-- +-- Name: idx_user_alias_alias; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_user_alias_alias ON public.user_alias USING btree (alias); + + +-- +-- Name: idx_user_alias_type; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_user_alias_type ON public.user_alias USING btree (type); + + +-- +-- Name: idx_user_alias_userid; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_user_alias_userid ON public.user_alias USING btree (userid); + + +-- +-- Name: idx_user_halls_assign_hall; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_user_halls_assign_hall ON public.user_halls_assign USING btree (hall_id); + + +-- +-- Name: idx_user_halls_assign_item; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_user_halls_assign_item ON public.user_halls_assign USING btree (item_id); + + +-- +-- Name: idx_user_halls_user_id; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_user_halls_user_id ON public.user_halls USING btree (user_id); + + +-- +-- Name: idx_user_last_seen; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_user_last_seen ON public."user" USING btree (last_seen); + + +-- +-- Name: idx_user_warnings_user_status; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX idx_user_warnings_user_status ON public.user_warnings USING btree (user_id, acknowledged); + + +-- +-- Name: items_phash; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX items_phash ON public.items USING btree (phash); + + +-- +-- Name: tags_assign_tag_id_idx; Type: INDEX; Schema: public; Owner: f0ckm +-- + +CREATE INDEX tags_assign_tag_id_idx ON public.tags_assign USING btree (tag_id); + + +-- +-- Name: items_li _RETURN; Type: RULE; Schema: public; Owner: f0ckm +-- + +CREATE OR REPLACE VIEW public.items_li AS + SELECT items.id, + items.src, + items.dest, + items.mime, + items.size, + items.checksum, + items.username, + items.userchannel, + items.usernetwork, + items.stamp + FROM ((public.items + JOIN public.tags_assign ta1 ON (((ta1.tag_id = 1) AND (ta1.item_id = items.id)))) + JOIN public.tags_assign ta2 ON (((NOT (ta2.tag_id IN ( SELECT tags_nsfp.id + FROM public.tags_nsfp))) AND (ta2.item_id = items.id)))) + WHERE items.active + GROUP BY items.id; + + +-- +-- Name: global_chat global_chat_notify; Type: TRIGGER; Schema: public; Owner: f0ckm +-- + +CREATE TRIGGER global_chat_notify AFTER INSERT ON public.global_chat FOR EACH ROW EXECUTE FUNCTION public.notify_global_chat(); + + +-- +-- Name: tags_assign tags_assign_ad; Type: TRIGGER; Schema: public; Owner: f0ckm +-- + +CREATE TRIGGER tags_assign_ad AFTER DELETE ON public.tags_assign FOR EACH ROW EXECUTE FUNCTION public.delete_unused_tags(); + + +-- +-- Name: tags tags_bi; Type: TRIGGER; Schema: public; Owner: f0ckm +-- + +CREATE TRIGGER tags_bi BEFORE INSERT ON public.tags FOR EACH ROW EXECUTE FUNCTION public.fill_normalized(); + + +-- +-- Name: tags tags_bu; Type: TRIGGER; Schema: public; Owner: f0ckm +-- + +CREATE TRIGGER tags_bu BEFORE UPDATE ON public.tags FOR EACH ROW EXECUTE FUNCTION public.fill_normalized(); + + +-- +-- Name: comments trg_comments_xd_score; Type: TRIGGER; Schema: public; Owner: f0ckm +-- + +CREATE TRIGGER trg_comments_xd_score AFTER INSERT OR DELETE OR UPDATE OF content, is_deleted ON public.comments FOR EACH ROW EXECUTE FUNCTION public.trg_update_xd_score(); + + +-- +-- Name: comments trigger_notify_activity; Type: TRIGGER; Schema: public; Owner: f0ckm +-- + +CREATE TRIGGER trigger_notify_activity AFTER INSERT ON public.comments FOR EACH ROW EXECUTE FUNCTION public.notify_activity(); + + +-- +-- Name: notifications trigger_notify_notification; Type: TRIGGER; Schema: public; Owner: f0ckm +-- + +CREATE TRIGGER trigger_notify_notification AFTER INSERT OR UPDATE ON public.notifications FOR EACH ROW EXECUTE FUNCTION public.notify_notification(); + + +-- +-- Name: private_messages trigger_notify_private_message; Type: TRIGGER; Schema: public; Owner: f0ckm +-- + +CREATE TRIGGER trigger_notify_private_message AFTER INSERT ON public.private_messages FOR EACH ROW EXECUTE FUNCTION public.notify_private_message(); + + +-- +-- Name: comment_subscriptions comment_subscriptions_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.comment_subscriptions + ADD CONSTRAINT comment_subscriptions_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: comment_subscriptions comment_subscriptions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.comment_subscriptions + ADD CONSTRAINT comment_subscriptions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: comments comments_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.comments + ADD CONSTRAINT comments_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: comments comments_parent_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.comments + ADD CONSTRAINT comments_parent_id_fkey FOREIGN KEY (parent_id) REFERENCES public.comments(id) ON DELETE SET NULL; + + +-- +-- Name: comments comments_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.comments + ADD CONSTRAINT comments_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: discord_queue discord_queue_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.discord_queue + ADD CONSTRAINT discord_queue_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: discord_queue discord_queue_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.discord_queue + ADD CONSTRAINT discord_queue_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: favorites favorites_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.favorites + ADD CONSTRAINT favorites_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: favorites favorites_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.favorites + ADD CONSTRAINT favorites_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: global_chat global_chat_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.global_chat + ADD CONSTRAINT global_chat_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: halls_assign halls_assign_hall_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.halls_assign + ADD CONSTRAINT halls_assign_hall_id_fkey FOREIGN KEY (hall_id) REFERENCES public.halls(id) ON DELETE CASCADE; + + +-- +-- Name: halls_assign halls_assign_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.halls_assign + ADD CONSTRAINT halls_assign_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: halls_assign halls_assign_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.halls_assign + ADD CONSTRAINT halls_assign_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE SET NULL; + + +-- +-- Name: invite_tokens invite_tokens_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.invite_tokens + ADD CONSTRAINT invite_tokens_created_by_fkey FOREIGN KEY (created_by) REFERENCES public."user"(id); + + +-- +-- Name: invite_tokens invite_tokens_used_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.invite_tokens + ADD CONSTRAINT invite_tokens_used_by_fkey FOREIGN KEY (used_by) REFERENCES public."user"(id); + + +-- +-- Name: link_token link_token_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.link_token + ADD CONSTRAINT link_token_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: notifications notifications_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.notifications + ADD CONSTRAINT notifications_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: notifications notifications_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.notifications + ADD CONSTRAINT notifications_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: private_messages private_messages_recipient_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.private_messages + ADD CONSTRAINT private_messages_recipient_id_fkey FOREIGN KEY (recipient_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: private_messages private_messages_sender_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.private_messages + ADD CONSTRAINT private_messages_sender_id_fkey FOREIGN KEY (sender_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: reports reports_comment_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.reports + ADD CONSTRAINT reports_comment_id_fkey FOREIGN KEY (comment_id) REFERENCES public.comments(id) ON DELETE CASCADE; + + +-- +-- Name: reports reports_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.reports + ADD CONSTRAINT reports_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: reports reports_reporter_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.reports + ADD CONSTRAINT reports_reporter_id_fkey FOREIGN KEY (reporter_id) REFERENCES public."user"(id) ON DELETE SET NULL; + + +-- +-- Name: reports reports_resolved_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.reports + ADD CONSTRAINT reports_resolved_by_fkey FOREIGN KEY (resolved_by) REFERENCES public."user"(id) ON DELETE SET NULL; + + +-- +-- Name: reports reports_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.reports + ADD CONSTRAINT reports_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: tags_alias tags_alias_tag_orig_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags_alias + ADD CONSTRAINT tags_alias_tag_orig_id_fkey FOREIGN KEY (tag_orig_id) REFERENCES public.tags(id) ON DELETE CASCADE; + + +-- +-- Name: tags_assign tags_assign_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags_assign + ADD CONSTRAINT tags_assign_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: tags_assign tags_assign_tag_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags_assign + ADD CONSTRAINT tags_assign_tag_id_fkey FOREIGN KEY (tag_id) REFERENCES public.tags(id) ON DELETE CASCADE; + + +-- +-- Name: tags_assign tags_assign_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.tags_assign + ADD CONSTRAINT tags_assign_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE SET DEFAULT; + + +-- +-- Name: user_conversation_states user_conversation_states_other_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_conversation_states + ADD CONSTRAINT user_conversation_states_other_id_fkey FOREIGN KEY (other_id) REFERENCES public."user"(id); + + +-- +-- Name: user_conversation_states user_conversation_states_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_conversation_states + ADD CONSTRAINT user_conversation_states_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id); + + +-- +-- Name: user_dm_keyvault user_dm_keyvault_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_dm_keyvault + ADD CONSTRAINT user_dm_keyvault_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: user_halls_assign user_halls_assign_hall_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_halls_assign + ADD CONSTRAINT user_halls_assign_hall_id_fkey FOREIGN KEY (hall_id) REFERENCES public.user_halls(id) ON DELETE CASCADE; + + +-- +-- Name: user_halls_assign user_halls_assign_item_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_halls_assign + ADD CONSTRAINT user_halls_assign_item_id_fkey FOREIGN KEY (item_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: user_halls_assign user_halls_assign_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_halls_assign + ADD CONSTRAINT user_halls_assign_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: user_halls user_halls_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_halls + ADD CONSTRAINT user_halls_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: user_options user_options_avatar_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_options + ADD CONSTRAINT user_options_avatar_fkey FOREIGN KEY (avatar) REFERENCES public.items(id) ON DELETE SET DEFAULT; + + +-- +-- Name: user_options user_options_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_options + ADD CONSTRAINT user_options_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: user_pubkeys user_pubkeys_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_pubkeys + ADD CONSTRAINT user_pubkeys_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: user_sessions user_sessions_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_sessions + ADD CONSTRAINT user_sessions_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: user_video_views user_video_views_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_video_views + ADD CONSTRAINT user_video_views_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: user_video_views user_video_views_video_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_video_views + ADD CONSTRAINT user_video_views_video_id_fkey FOREIGN KEY (video_id) REFERENCES public.items(id) ON DELETE CASCADE; + + +-- +-- Name: user_warnings user_warnings_admin_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_warnings + ADD CONSTRAINT user_warnings_admin_id_fkey FOREIGN KEY (admin_id) REFERENCES public."user"(id) ON DELETE SET NULL; + + +-- +-- Name: user_warnings user_warnings_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: f0ckm +-- + +ALTER TABLE ONLY public.user_warnings + ADD CONSTRAINT user_warnings_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE; + + +-- +-- Name: alltables; Type: PUBLICATION; Schema: -; Owner: f0ckm +-- + +CREATE PUBLICATION alltables FOR ALL TABLES WITH (publish = 'insert, update, delete, truncate'); + + +ALTER PUBLICATION alltables OWNER TO f0ckm; + +-- +-- Name: SCHEMA public; Type: ACL; Schema: -; Owner: f0ckm +-- + +REVOKE USAGE ON SCHEMA public FROM PUBLIC; +GRANT ALL ON SCHEMA public TO PUBLIC; + + +-- +-- PostgreSQL database dump complete +-- + +\unrestrict ifoUZevi3oYdI7OmgFxUaco0kNV6kdlFS55QWa8PuaWXA3AY2nPUcs8ekmXvMEU + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..44fe811 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,418 @@ +{ + "name": "f0ckv2", + "version": "2.2.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "f0ckv2", + "version": "2.2.1", + "license": "MIT", + "dependencies": { + "@ruffle-rs/ruffle": "^0.2.0-nightly.2026.4.21", + "cuffeo": "git+https://git.lat/naimi/scuffeo.git", + "flumm-fetch": "^1.0.1", + "flummpress": "^2.0.5", + "marked": "^18.0.2", + "matrix-js-sdk": "^40.3.0-rc.0", + "postgres": "^3.3.4" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@matrix-org/matrix-sdk-crypto-wasm": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-17.1.0.tgz", + "integrity": "sha512-yKPqBvKlHSqkt/UJh+Z+zLKQP8bd19OxokXYXh3VkKbW0+C44nPHsidSwd3SH+RxT+Ck2PDRwVcVXEnUft+/2g==", + "license": "Apache-2.0", + "engines": { + "node": ">= 18" + } + }, + "node_modules/@ruffle-rs/ruffle": { + "version": "0.2.0-nightly.2026.4.21", + "resolved": "https://registry.npmjs.org/@ruffle-rs/ruffle/-/ruffle-0.2.0-nightly.2026.4.21.tgz", + "integrity": "sha512-pLnuuZG6aTTPWTZs8gLDV+A6JloNju+we9dbd/ya22FwGcfFziaiARBNeI5HKn3OLK1Q216e9QeUJkQyx0jkbA==", + "license": "(MIT OR Apache-2.0)" + }, + "node_modules/@types/events": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", + "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==", + "license": "MIT" + }, + "node_modules/another-json": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", + "integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==", + "license": "Apache-2.0" + }, + "node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cuffeo": { + "version": "1.2.2", + "resolved": "git+https://git.lat/naimi/scuffeo.git#4e1641c0aeaa5d4b410feb327be01eb70a787580", + "license": "MIT", + "dependencies": { + "flumm-fetch": "^1.0.1" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/flumm-fetch": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flumm-fetch/-/flumm-fetch-1.0.1.tgz", + "integrity": "sha512-pZ5U0hheCSW43vfGZQMunr03U7rUOX+iy2y13Tu4nc3iRL+E/Qfeo5nZ2B2JMYKOGIx1A1anUYOz+ulyhouyjg==" + }, + "node_modules/flummpress": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/flummpress/-/flummpress-2.0.5.tgz", + "integrity": "sha512-C/8Im6OvoZw67q9DvYIXKjKr28zHYLJdH4DucQ6zpVbN1eWPySmxkJTURbkq3uEwABXLngXLifS6mjxAC++umQ==" + }, + "node_modules/is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/marked": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.2.tgz", + "integrity": "sha512-NsmlUYBS/Zg57rgDWMYdnre6OTj4e+qq/JS2ot3KrYLSoHLw+sDu0Nm1ZGpRgYAq6c+b1ekaY5NzVchMCQnzcg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/matrix-events-sdk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz", + "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==", + "license": "Apache-2.0" + }, + "node_modules/matrix-js-sdk": { + "version": "40.3.0-rc.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-40.3.0-rc.0.tgz", + "integrity": "sha512-MxFPnlzca/g5Fq/MMN5jkzwExUv725/weW+Ids7Dwmcy34WC8NROBOhfqGJkwbAWLkJDEUKRe3+//2tHXmnnHQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.12.5", + "@matrix-org/matrix-sdk-crypto-wasm": "^17.0.0", + "another-json": "^0.2.0", + "bs58": "^6.0.0", + "content-type": "^1.0.4", + "jwt-decode": "^4.0.0", + "loglevel": "^1.9.2", + "matrix-events-sdk": "0.0.1", + "matrix-widget-api": "^1.16.1", + "oidc-client-ts": "^3.0.1", + "p-retry": "7", + "sdp-transform": "^3.0.0", + "unhomoglyph": "^1.0.6", + "uuid": "13" + }, + "engines": { + "node": ">=22.0.0" + } + }, + "node_modules/matrix-widget-api": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.17.0.tgz", + "integrity": "sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/events": "^3.0.0", + "events": "^3.2.0" + } + }, + "node_modules/oidc-client-ts": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz", + "integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==", + "license": "Apache-2.0", + "dependencies": { + "jwt-decode": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/p-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz", + "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==", + "license": "MIT", + "dependencies": { + "is-network-error": "^1.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postgres": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.4.tgz", + "integrity": "sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg==", + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/porsager" + } + }, + "node_modules/sdp-transform": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz", + "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==", + "license": "MIT", + "bin": { + "sdp-verify": "checker.js" + } + }, + "node_modules/unhomoglyph": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", + "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + } + }, + "dependencies": { + "@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==" + }, + "@matrix-org/matrix-sdk-crypto-wasm": { + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/@matrix-org/matrix-sdk-crypto-wasm/-/matrix-sdk-crypto-wasm-17.1.0.tgz", + "integrity": "sha512-yKPqBvKlHSqkt/UJh+Z+zLKQP8bd19OxokXYXh3VkKbW0+C44nPHsidSwd3SH+RxT+Ck2PDRwVcVXEnUft+/2g==" + }, + "@ruffle-rs/ruffle": { + "version": "0.2.0-nightly.2026.4.21", + "resolved": "https://registry.npmjs.org/@ruffle-rs/ruffle/-/ruffle-0.2.0-nightly.2026.4.21.tgz", + "integrity": "sha512-pLnuuZG6aTTPWTZs8gLDV+A6JloNju+we9dbd/ya22FwGcfFziaiARBNeI5HKn3OLK1Q216e9QeUJkQyx0jkbA==" + }, + "@types/events": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.3.tgz", + "integrity": "sha512-trOc4AAUThEz9hapPtSd7wf5tiQKvTtu5b371UxXdTuqzIh0ArcRspRP0i0Viu+LXstIQ1z96t1nsPxT9ol01g==" + }, + "another-json": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/another-json/-/another-json-0.2.0.tgz", + "integrity": "sha512-/Ndrl68UQLhnCdsAzEXLMFuOR546o2qbYRqCglaNHbjXrwG1ayTcdwr3zkSGOGtGXDyR5X9nCFfnyG2AFJIsqg==" + }, + "base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==" + }, + "bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "requires": { + "base-x": "^5.0.0" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cuffeo": { + "version": "git+https://git.lat/naimi/scuffeo.git#4e1641c0aeaa5d4b410feb327be01eb70a787580", + "from": "cuffeo@git+https://git.lat/naimi/scuffeo.git", + "requires": { + "flumm-fetch": "^1.0.1" + } + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" + }, + "flumm-fetch": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/flumm-fetch/-/flumm-fetch-1.0.1.tgz", + "integrity": "sha512-pZ5U0hheCSW43vfGZQMunr03U7rUOX+iy2y13Tu4nc3iRL+E/Qfeo5nZ2B2JMYKOGIx1A1anUYOz+ulyhouyjg==" + }, + "flummpress": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/flummpress/-/flummpress-2.0.5.tgz", + "integrity": "sha512-C/8Im6OvoZw67q9DvYIXKjKr28zHYLJdH4DucQ6zpVbN1eWPySmxkJTURbkq3uEwABXLngXLifS6mjxAC++umQ==" + }, + "is-network-error": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", + "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==" + }, + "jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==" + }, + "loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==" + }, + "marked": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.2.tgz", + "integrity": "sha512-NsmlUYBS/Zg57rgDWMYdnre6OTj4e+qq/JS2ot3KrYLSoHLw+sDu0Nm1ZGpRgYAq6c+b1ekaY5NzVchMCQnzcg==" + }, + "matrix-events-sdk": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/matrix-events-sdk/-/matrix-events-sdk-0.0.1.tgz", + "integrity": "sha512-1QEOsXO+bhyCroIe2/A5OwaxHvBm7EsSQ46DEDn8RBIfQwN5HWBpFvyWWR4QY0KHPPnnJdI99wgRiAl7Ad5qaA==" + }, + "matrix-js-sdk": { + "version": "40.3.0-rc.0", + "resolved": "https://registry.npmjs.org/matrix-js-sdk/-/matrix-js-sdk-40.3.0-rc.0.tgz", + "integrity": "sha512-MxFPnlzca/g5Fq/MMN5jkzwExUv725/weW+Ids7Dwmcy34WC8NROBOhfqGJkwbAWLkJDEUKRe3+//2tHXmnnHQ==", + "requires": { + "@babel/runtime": "^7.12.5", + "@matrix-org/matrix-sdk-crypto-wasm": "^17.0.0", + "another-json": "^0.2.0", + "bs58": "^6.0.0", + "content-type": "^1.0.4", + "jwt-decode": "^4.0.0", + "loglevel": "^1.9.2", + "matrix-events-sdk": "0.0.1", + "matrix-widget-api": "^1.16.1", + "oidc-client-ts": "^3.0.1", + "p-retry": "7", + "sdp-transform": "^3.0.0", + "unhomoglyph": "^1.0.6", + "uuid": "13" + } + }, + "matrix-widget-api": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.17.0.tgz", + "integrity": "sha512-5FHoo3iEP3Bdlv5jsYPWOqj+pGdFQNLWnJLiB0V7Ygne7bb+Gsj3ibyFyHWC6BVw+Z+tSW4ljHpO17I9TwStwQ==", + "requires": { + "@types/events": "^3.0.0", + "events": "^3.2.0" + } + }, + "oidc-client-ts": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/oidc-client-ts/-/oidc-client-ts-3.4.1.tgz", + "integrity": "sha512-jNdst/U28Iasukx/L5MP6b274Vr7ftQs6qAhPBCvz6Wt5rPCA+Q/tUmCzfCHHWweWw5szeMy2Gfrm1rITwUKrw==", + "requires": { + "jwt-decode": "^4.0.0" + } + }, + "p-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz", + "integrity": "sha512-J5ApzjyRkkf601HpEeykoiCvzHQjWxPAHhyjFcEUP2SWq0+35NKh8TLhpLw+Dkq5TZBFvUM6UigdE9hIVYTl5w==", + "requires": { + "is-network-error": "^1.1.0" + } + }, + "postgres": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.3.4.tgz", + "integrity": "sha512-XVu0+d/Y56pl2lSaf0c7V19AhAEfpVrhID1IENWN8nf0xch6hFq6dAov5dtUX6ZD46wfr1TxvLhxLtV8WnNsOg==" + }, + "sdp-transform": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-3.0.0.tgz", + "integrity": "sha512-gfYVRGxjHkGF2NPeUWHw5u6T/KGFtS5/drPms73gaSuMaVHKCY3lpLnGDfswVQO0kddeePoti09AwhYP4zA8dQ==" + }, + "unhomoglyph": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", + "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==" + }, + "uuid": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", + "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8ee5133 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "f0ckv2", + "version": "2.2.1", + "description": "f0ck, kennste?", + "main": "index.mjs", + "type": "module", + "scripts": { + "start": "node --trace-uncaught src/index.mjs", + "trigger": "node debug/trigger.mjs", + "autotagger": "node debug/autotagger.mjs", + "thumbnailer": "node debug/thumbnailer.mjs", + "test": "node debug/test.mjs", + "clean": "node debug/clean.mjs", + "fix:deleted": "node debug/fix_deleted.mjs", + "build": "node scripts/build-css.mjs" + }, + "author": "Flummi", + "license": "MIT", + "dependencies": { + "@ruffle-rs/ruffle": "0.2.0-nightly.2026.4.21", + "cuffeo": "git+https://git.lat/naimi/scuffeo.git", + "flumm-fetch": "^1.0.1", + "flummpress": "^2.0.5", + "marked": "^18.0.2", + "matrix-js-sdk": "^40.3.0-rc.0", + "postgres": "^3.3.4" + } +} diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..7a06554 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,21 @@ +{ + "id": "/", + "name": "", + "short_name": "", + "description": "", + "start_url": "/", + "scope": "/", + "display": "standalone", + "background_color": "#000000", + "theme_color": "#0096ff", + "icons": [ + { + "src": "/s/img/favicon.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + } + ], + "screenshots": [], + "categories": ["entertainment", "social", "memes"] +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..a830c67 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,6 @@ +User-agent: Googlebot +User-agent: AdsBot-Google +Disallow: / + +User-agent: * +Disallow: / diff --git a/public/s/95.ttf b/public/s/95.ttf new file mode 100644 index 0000000..4375172 Binary files /dev/null and b/public/s/95.ttf differ diff --git a/public/s/OpenSans.ttf b/public/s/OpenSans.ttf new file mode 100644 index 0000000..51dd3c3 Binary files /dev/null and b/public/s/OpenSans.ttf differ diff --git a/public/s/css/dm.css b/public/s/css/dm.css new file mode 100644 index 0000000..5794b0c --- /dev/null +++ b/public/s/css/dm.css @@ -0,0 +1,463 @@ + +/* ═══════════════════════════════════════════════════════════ + PRIVATE MESSAGES / DM SYSTEM + ═══════════════════════════════════════════════════════════ */ + +/* Page layout */ +.messages-page { + max-width: 720px; + margin: 0 auto; + padding: 16px; + display: flex; + flex-direction: column; + min-height: calc(100vh - 80px); +} + +.messages-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 18px; + border-bottom: 1px solid var(--border, #333); + padding-bottom: 12px; + gap: 12px; +} + +.messages-header h2 { + margin: 0; + font-size: 1.1em; + letter-spacing: 0.08em; + color: var(--accent); +} + +.dm-back-btn { + color: var(--accent); + font-size: 1.4em; + text-decoration: none; + line-height: 1; + padding: 4px 8px; + border-radius: 4px; + transition: background 0.15s; +} + +.dm-back-btn:hover { background: var(--bg2, #222); } + +.dm-convo-header-info { + display: flex; + align-items: center; + gap: 10px; + flex: 1; +} + +.dm-header-avatar { + width: 32px; + height: 32px; + border-radius: 4px; + object-fit: cover; +} + +.dm-header-username { + font-weight: 600; + font-size: 1em; + text-decoration: none; + color: var(--fg, #ddd); +} + +.dm-header-username:hover { text-decoration: underline; } + +/* Key notice banner */ +.dm-key-notice { + background: rgba(255, 200, 80, 0.15); + border: 1px solid rgba(255, 200, 80, 0.4); + color: #ffc850; + padding: 10px 14px; + border-radius: 6px; + font-size: 0.88em; + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 14px; + flex-wrap: wrap; +} + +.dm-key-export-inline { + background: rgba(255, 200, 80, 0.25); + border: 1px solid rgba(255, 200, 80, 0.5); + color: #ffc850; + padding: 3px 10px; + border-radius: 4px; + cursor: pointer; + font-size: 0.9em; +} + +.dm-key-export-inline:hover { background: rgba(255, 200, 80, 0.4); } + +/* Inbox list */ +.dm-inbox-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.dm-convo-card { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 14px; + border-radius: 8px; + background: var(--bg2, #1a1a1a); + border: 1px solid transparent; + text-decoration: none; + color: var(--fg, #ccc); + transition: background 0.15s, border-color 0.15s; + position: relative; +} + +.dm-convo-card:hover { + background: var(--bg3, #222); + border-color: var(--accent); +} + +.dm-convo-card.dm-convo-unread { border-color: var(--accent); } + +.dm-convo-avatar { + width: 42px; + height: 42px; + border-radius: 6px; + object-fit: cover; + flex-shrink: 0; +} + +.dm-convo-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.dm-convo-name { + font-weight: 600; + font-size: 0.95em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.dm-convo-time { + font-size: 0.78em; + color: #666; +} + +.dm-convo-badge { + background: var(--accent); + color: var(--bg, #000); + font-size: 0.72em; + font-weight: 700; + min-width: 20px; + height: 20px; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 5px; + flex-shrink: 0; +} + +/* Thread */ +.dm-thread { + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 8px; + padding: 8px 0; + min-height: 200px; + max-height: calc(100vh - 280px); +} + +.dm-load-more { + align-self: center; + background: transparent; + border: 1px solid #444; + color: #888; + padding: 6px 16px; + border-radius: 4px; + cursor: pointer; + font-size: 0.82em; + margin-bottom: 8px; + transition: border-color 0.15s, color 0.15s; +} + +.dm-load-more:hover { border-color: var(--accent); color: var(--accent); } + +/* Message bubbles */ +.dm-msg { + display: flex; + flex-direction: column; + max-width: 75%; + gap: 3px; +} + +.dm-msg-mine { + align-self: flex-end; + align-items: flex-end; +} + +.dm-msg-theirs { + align-self: flex-start; + align-items: flex-start; +} + +.dm-bubble { + padding: 9px 13px; + border-radius: 14px; + font-size: 0.92em; + line-height: 1.45; + word-break: break-word; + white-space: pre-wrap; +} + +.dm-msg-mine .dm-bubble { + background: var(--accent); + color: var(--bg, #000); + border-bottom-right-radius: 4px; +} + +.dm-msg-theirs .dm-bubble { + background: var(--bg2, #222); + color: var(--fg, #ddd); + border: 1px solid #333; + border-bottom-left-radius: 4px; +} + +.dm-msg-time { + font-size: 0.72em; + color: #555; + padding: 0 4px; +} + +.dm-unreadable { + font-style: italic; + opacity: 0.5; + font-size: 0.88em; +} + +/* Send form: column layout matching comment form */ +.dm-send-form { + display: flex; + flex-direction: column; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid #333; + flex-shrink: 0; +} + +.dm-input { + width: 100%; + background: var(--bg2, #1a1a1a); + border: 1px solid #444; + color: var(--fg, #ddd); + border-radius: 8px; + padding: 10px 12px; + font-size: 0.92em; + resize: none; + min-height: 44px; + max-height: 140px; + font-family: inherit; + transition: border-color 0.15s; + line-height: 1.4; + box-sizing: border-box; +} + +.dm-input:focus { + outline: none; + border-color: var(--accent); +} + +/* Actions bar: ☺ emoji trigger on left, Send on right */ +.dm-send-form .input-actions { + display: flex; + flex-direction: row; + align-items: center; + gap: 6px; + margin-top: 6px; +} + +.dm-send-btn { + background: var(--accent); + color: var(--bg, #000); + border: none; + border-radius: 6px; + padding: 6px 18px; + font-weight: 700; + cursor: pointer; + font-size: 0.9em; + margin-left: auto; + transition: opacity 0.15s; +} + +.dm-send-btn:hover { opacity: 0.85; } +.dm-send-btn:disabled { opacity: 0.4; cursor: not-allowed; } + +/* Emojis rendered inside DM bubbles */ +.dm-bubble img.emoji { + width: 22px; + height: 22px; + vertical-align: middle; + object-fit: contain; +} + +/* State messages */ +.dm-loading, .dm-empty, .dm-error { + color: #666; + font-size: 0.9em; + padding: 24px; + text-align: center; + line-height: 1.6; +} + +.dm-error { color: #e06c6c; } + +/* Key Manager Modal */ +.dm-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.75); + z-index: 15000; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; +} + +.dm-modal { + background: var(--bg, #111); + border: 1px solid var(--accent); + border-radius: 10px; + width: 100%; + max-width: 480px; + max-height: 90vh; + overflow-y: auto; + padding: 28px; + position: relative; +} + +.dm-modal h2 { + margin: 0 0 6px; + font-size: 1.1em; + color: var(--accent); +} + +.dm-modal-sub { + font-size: 0.85em; + color: #888; + margin: 0 0 18px; + line-height: 1.5; +} + +.dm-modal-close { + position: absolute; + top: 14px; + right: 16px; + background: none; + border: none; + color: #888; + font-size: 1.4em; + cursor: pointer; + line-height: 1; +} + +.dm-modal-close:hover { color: var(--fg, #ddd); } + +.dm-key-status { + background: rgba(255,255,255,0.04); + border-radius: 6px; + padding: 10px 12px; + font-size: 0.85em; + margin-bottom: 18px; + border: 1px solid #333; +} + +.dm-key-section { + margin-bottom: 20px; + padding-bottom: 20px; + border-bottom: 1px solid #2a2a2a; +} + +.dm-key-section:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; } + +.dm-key-section h3 { + font-size: 0.9em; + margin: 0 0 6px; + color: var(--fg, #ccc); +} + +.dm-key-section p { + font-size: 0.82em; + color: #777; + margin: 0 0 10px; + line-height: 1.5; +} + +.dm-key-input { + display: block; + width: 100%; + background: var(--bg2, #1a1a1a); + border: 1px solid #444; + color: var(--fg, #ddd); + border-radius: 6px; + padding: 8px 10px; + font-size: 0.88em; + margin-bottom: 8px; + box-sizing: border-box; + font-family: inherit; +} + +.dm-key-input:focus { outline: none; border-color: var(--accent); } + +.dm-key-btn { + background: var(--accent); + color: var(--bg, #000); + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: 600; + cursor: pointer; + font-size: 0.86em; + transition: opacity 0.15s; +} + +.dm-key-btn:hover { opacity: 0.85; } + +.dm-key-btn-danger { + background: #d94f4f; + color: #fff; +} + +.dm-key-danger h3 { color: #d94f4f; } + +.dm-key-msg { + font-size: 0.82em; + margin-top: 8px; + min-height: 18px; +} + +.dm-msg-ok { color: #5cb85c; } +.dm-msg-err { color: #e06c6c; } + +/* Navbar DM icon */ +#nav-dm-btn { + position: relative; + display: inline-flex; + align-items: center; + gap: 3px; +} + +/* Mobile */ +@media (max-width: 600px) { + .messages-page { padding: 10px; } + .dm-msg { max-width: 90%; } + .dm-thread { max-height: calc(100vh - 240px); } + .dm-modal { padding: 20px; } +} diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css new file mode 100644 index 0000000..b8fca92 --- /dev/null +++ b/public/s/css/f0ckm.css @@ -0,0 +1,11833 @@ +/* ============================================= + f0ckm.css - Unified Stylesheet + Merged: f0ck.css + w0bm.css + view styles + Born anew => f0ckm.css + ============================================= */ + +/* f0ckwork omega */ +/* written by sirx for f0ck.me */ +/* use whatever you like */ +/* once upon a time this was a stiefelstrapse! but no more! */ +/* Licensed under wtfpl */ + +html[theme='f0ck'] { + --accent: #9f0; + --accent-rgb: 153, 255, 0; + --bg: #111; + --black: #000; + --white: #fff; + --gray: #262626; + --nav-bg: #2b2b2b; + --nav-brand-border: inset 1px #242424; + --nav-brand-bg: #171717; + --navigation-links-bg: #2b2b2b; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #333; + --nav-border-color: rgba(255, 255, 255, .05); + --dropdown-bg: #232323; + --dropdown-item-hover: #0d0d0d; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: #2b2b2b; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #333; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: #2b2b2b; + --badge-bg: #171717; + --posts-meta-bg: #000000b8; + --badge-sfw: #68a728; + --badge-nsfw: #E10DC3; + --badge-nsfl: #660000; + --badge-tag: #090909; + --scrollbar-color: #2b2b2b; + --scroller-bg: #424242; + --footbar-color: #9f0; + --loading-indicator-color: #9f0; + --img-border-width: 0; + --img-border-color: #363636; + --maximize_button: #9f0; + --bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%); + /* appearance */ + background: black; +} + +html[theme="f0ck"] .admin-search button { + color: var(--black) !important; +} + +html[theme="f0ck"] .pagination>a, +.pagination>span { + border-radius: 3px; + border: 1px solid var(--black); +} + +html[theme="f0ck"] .pagination>span { + border-radius: 3px; +} + +html[theme="f0ck"] span#favs { + border: 1px solid var(--black); +} + +html[theme='p1nk'] { + --accent: #ff00d0; + --accent-rgb: 255, 0, 208; + --bg: #111; + --black: #000; + --white: #fff; + --gray: #262626; + --nav-bg: #201f1f; + --nav-brand-border: inset 1px #242424; + --nav-brand-bg: #171717; + --navigation-links-bg: #201f1f; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #333; + --nav-border-color: rgba(255, 255, 255, .05); + --dropdown-bg: #232323; + --dropdown-item-hover: #0d0d0d; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: #201f1f; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + ; + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #333; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --badge-bg: #171717; + --badge-sfw: #68a728; + --badge-nsfw: #E10DC3; + --badge-nsfl: #660000; + --badge-tag: #090909; + --metadata-bg: #2b2b2b; + --posts-meta-bg: #000000b8; + --scrollbar-color: #2b2b2b; + --scroller-bg: #424242; + --footbar-color: #ff00d0; + --loading-indicator-color: #ff00d0; + --img-border-width: 0; + --img-border-color: #363636; + --maximize_button: #ff00d0; + --bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%); + /* appearance */ + background: black; + --motd-bg: #111; +} + +html[theme="p1nk"] .pagination>a, +.pagination>span { + border-radius: 3px; + border: 1px solid var(--black); +} + +html[theme="p1nk"] .pagination>span { + border-radius: 3px; +} + +html[theme="p1nk"] span#favs { + border: 1px solid var(--black); +} + +html[theme='orange'] { + --accent: #ff6f00; + --accent-rgb: 255, 111, 0; + --bg: #111; + --black: #000; + --white: #fff; + --gray: #262626; + --nav-bg: #2b2b2b; + --nav-brand-border: inset 1px #242424; + --nav-brand-bg: #171717; + --navigation-links-bg: #2b2b2b; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #333; + --dropdown-bg: #232323; + --nav-border-color: rgba(255, 255, 255, .05); + --dropdown-item-hover: #0d0d0d; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: #2b2b2b; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #333; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: #2b2b2b; + --badge-bg: #151515; + --posts-meta-bg: #000000b8; + --badge-tag: #090909; + --badge-sfw: #68a728; + --badge-nsfw: #E10DC3; + --badge-nsfl: #660000; + --scrollbar-color: #2b2b2b; + --scroller-bg: #424242; + --footbar-color: #ff6f00; + --loading-indicator-color: #ff6f00; + --img-border-width: 0; + --img-border-color: #363636; + --maximize_button: #ff6f00; + --bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%); + /* appearance */ + background: black; + --motd-bg: #111; +} + +html[theme="orange"] .pagination>a, +.pagination>span { + border-radius: 3px; + border: 1px solid var(--black); +} + +html[theme="orange"] .pagination>span { + border-radius: 3px; +} + +html[theme="orange"] span#favs { + border: 1px solid var(--black); + user-select: none; +} + +html[theme='amoled'] { + --accent: #fff; + --accent-rgb: 255, 255, 255; + --bg: #000; + --black: #000; + --white: #fff; + --gray: #2f2d2d; + --nav-bg: #000; + --nav-brand-border: inset 1px #242424; + --nav-brand-bg: #000; + --navigation-links-bg: #000; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgb(92, 92, 92), inset 0 1px rgb(92, 92, 92), inset 0 -1px rgb(92, 92, 92), 0 1px 1px rgba(92, 92, 92, 0); + --nav-link-hover-bg: #6a6a6a70; + --nav-border-color: rgba(255, 255, 255, .05); + --dropdown-bg: #232323; + --dropdown-item-hover: #0d0d0d; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: #000; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0), inset 0 0px rgba(255, 255, 255, 0), inset 0 0px rgba(248, 248, 248, 0), 0 0px 0px rgba(255, 255, 255, 0); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgb(92, 92, 92), inset 0 1px rgb(92, 92, 92), inset 0 -1px rgb(92, 92, 92), 0 1px 1px rgba(92, 92, 92, 0); + --pagination-background-hover: #6a6a6a70; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: #000; + --badge-bg: #000; + --posts-meta-bg: #000000b8; + --badge-sfw: #68a728; + --badge-nsfw: #E10DC3; + --badge-nsfl: #660000; + --badge-tag: #1a1a1a; + --scrollbar-color: #1d1c1c; + --scroller-bg: #424242; + --footbar-color: #fff; + --loading-indicator-color: #fff; + --img-border-width: 0; + --img-border-color: #363636; + --maximize_button: #fff; + --bg-gradient: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(0, 0, 0) 10%, rgb(0, 0, 0) 100%); + background: black; +} + +html[theme="amoled"] .metadata { + border: none; +} + +html[theme="amoled"] .pagination>a, +.pagination>span { + border-radius: 3px; + border: 1px solid var(--black); +} + +html[theme="amoled"] .pagination>span { + border-radius: 3px; +} + +html[theme="amoled"] span#favs { + border: 1px solid var(--black); +} + +html[theme="amoled"] .admin-search button { + color: var(--black) !important; +} + +html[theme="amoled"] table.table tbody tr:nth-of-type(2n+1) a { + color: var(--white) !important; + text-decoration: underline; +} + +html[theme="amoled"] table.table tbody tr:nth-of-type(2n+1) { + color: var(--white) !important; +} + +html[theme="amoled"] table.table a { + text-decoration: underline; +} + +html[theme="paper"] { + --accent: #000; + --accent-rgb: 0, 0, 0; + --bg: #fff; + --black: #000; + --white: #fff; + --gray: #262626; + --nav-bg: #fff; + --nav-brand-border: inset 1px #242424; + --nav-brand-bg: #fff; + --navigation-links-bg: #fff; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #3939354a; + --nav-border-color: rgba(255, 255, 255, .05); + --dropdown-bg: #fff; + --dropdown-item-hover: #3939354a; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: #fff; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #3939354a; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: #fff; + --badge-bg: #fff; + --posts-meta-bg: #000000db; + --badge-sfw: #68a728; + --badge-nsfw: #E10DC3; + --badge-nsfl: #660000; + --badge-tag: #000000; + --scrollbar-color: #6c6c6c; + --scroller-bg: #424242; + --footbar-color: #000; + --loading-indicator-color: #000; + --img-border-width: 0; + --img-border-color: #363636; + --maximize_button: #fff; +} + +html[theme="paper"] .err { + background: black; + color: white; +} + +html[theme="paper"] ._204863 { + color: white; +} + +html[theme="paper"] svg.iconset { + background: black; +} + +html[theme="paper"] svg.iconset:hover { + background: rgb(31, 30, 30); +} + +html[theme="paper"] .iconset#a_favo { + fill: var(--white); +} + +html[theme="paper"] .iconset#a_delete { + stroke: var(--white); +} + +html[theme="paper"] .pagination>a, +.pagination>span { + border-radius: 3px; + border: 1px solid var(--black); +} + +html[theme="paper"] .pagination>span { + border-radius: 3px; +} + +html[theme="paper"] span#favs { + border: 1px solid var(--black); +} + +html[theme="paper"] body { + color: var(--black); +} + +html[theme="paper"] a#next { + color: var(--white) !important; + -webkit-text-stroke: 1px var(--black); + font-size: 40px; +} + +html[theme="paper"] a#prev { + color: var(--white) !important; + -webkit-text-stroke: 1px var(--black); + font-size: 40px; +} + +html[theme="paper"] .about { + color: var(--black); +} + +html[theme="paper"] .about a:hover { + text-decoration: underline; +} + +html[theme="paper"] div.posts>a::after { + color: var(--white); + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +html[theme="paper"] .v0ck_player_controls>input[type="range"][name="volume"]::-webkit-slider-thumb { + background: var(--white); + box-shadow: -100vw 0 0 100vw var(--white); +} + +html[theme="paper"] .v0ck_player_controls>input[type="range"][name="volume"]::-moz-range-thumb { + background: var(--white); + box-shadow: -100vw 0 0 100vw var(--white); +} + +html[theme="paper"] div.posts>a { + box-shadow: 1px 1px 1px black; + border-radius: 5px; +} + +html[theme="paper"] .v0ck_progress_filled { + background: var(--white); +} + +html[theme="paper"] .metadata { + color: var(--black); + border: none; +} + +html[theme="paper"] .navbar-brand { + background: var(--black); + color: var(--white) !important; +} + +html[theme="paper"] a.removetag { + color: var(--white) !important; +} + +html[theme="paper"] .navbar { + border-bottom: 1px solid var(--black); +} + +html[theme="paper"] .embed-responsive-image { + background: var(--white); +} + +html[theme="paper"] .v0ck_player_button svg:hover { + fill: var(--black); + stroke: var(--white); +} + +html[theme="paper"] .badge-dark, +#themeselector { + box-shadow: 1px 1px 0px 0px var(--black); +} + +html[theme="paper"] span#favs { + border: 1px solid var(--white); +} + +html[theme="paper"] .admin-search button { + color: var(--white) !important; + opacity: 0.9; +} + +html[theme="paper"] table.table tbody tr:nth-of-type(2n+1) a { + color: var(--white) !important; + text-decoration: underline; +} + +html[theme="paper"] table.table tbody tr:nth-of-type(2n+1) { + color: var(--white) !important; +} + +html[theme="paper"] table.table a { + text-decoration: underline; +} + +html[theme="paper"] a { + color: var(--black); +} + +html[theme="paper"] .login-form { + background: white; +} + +html[theme="paper"] .login-form input { + color: black; +} + +html[theme="paper"] .login-form button[type="submit"] { + color: black; +} + +html[theme="atmos"] { + --accent: #1fb2b0; + --accent-rgb: 31, 178, 176; + --bg: #111; + --black: #000; + --white: #fff; + --gray: #262626; + --nav-bg: rgb(32, 32, 32); + --nav-brand-border: inset 1px #242424; + --nav-brand-bg: #171717; + --navigation-links-bg: rgb(32, 32, 32); + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #333; + --nav-border-color: rgba(255, 255, 255, .05); + --dropdown-bg: #232323; + --dropdown-item-hover: #0d0d0d; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: rgb(32, 32, 32); + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #333; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: rgba(34, 34, 34, 0.8); + --badge-bg: #131313; + --posts-meta-bg: #000000b8; + --badge-sfw: #68a728; + --badge-nsfw: #a72828; + --badge-nsfl: #660000; + --badge-tag: #353535; + --scrollbar-color: #2b2b2b; + --footbar-color: #1fb2b0; + --loading-indicator-color: #1fb2b0; + --img-border-width: 0; + --img-border-color: #363636; + --maximize_button: #1fb2b0; + --bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%); + /* appearance */ + background: black; + --motd-bg: #111; +} + +html[theme="atmos"] .pagination>a, +.pagination>span { + border-radius: 3px; + border: 1px solid var(--black); +} + +html[theme="atmos"] .pagination>span { + border-radius: 3px; +} + +html[theme="atmos"] span#favs { + border: 1px solid var(--black); +} + +html[theme="term"] { + --accent: #00DF00; + --accent-rgb: 0, 223, 0; + --bg: #000; + --black: #000; + --white: #fff; + --gray: #262626; + --nav-bg: #040404; + --nav-brand-border: inset 1px #111111; + --nav-brand-bg: #040404; + --navigation-links-bg: #040404; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #282828; + --nav-border-color: rgba(255, 255, 255, .05); + --dropdown-bg: #040404; + --dropdown-item-hover: #282828; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: #040404; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #282828; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: #000; + --badge-bg: #060606; + --posts-meta-bg: #000000b8; + --badge-sfw: #076a19; + --badge-nsfw: #E10DC3; + --badge-nsfl: #660000; + --badge-tag: #131212; + --scrollbar-color: #2b2b2b; + --scroller-bg: #424242; + --tooltip-bg: #131212; + --footbar-color: #00DF00; + --loading-indicator-color: #00DF00; + --img-border-width: 0; + --img-border-color: #363636; + --maximize_button: #00DF00; + --bg-gradient: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(0, 0, 0) 10%, rgb(0, 0, 0) 100%); + /* appearance */ + background: black; +} + +html[theme="term"] .pagination>a, +.pagination>span { + border-radius: 3px; + border: 1px solid var(--black); +} + +html[theme="term"] .pagination>span { + border-radius: 3px; +} + +html[theme="term"] span#favs { + box-shadow: var(--pagination-anchor-box-shadow); + background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient)); + border: 1px solid var(--black); +} + +html[theme="term"] .metadata { + text-shadow: 0px 0px 1px var(--accent), 1px 0px 2px var(--accent), 0px 0px 5px var(--black); +} + +html[theme="term"] .metadata>.badge-dark, +#themeselector { + box-shadow: var(--pagination-anchor-box-shadow); + background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient)); +} + +html[theme="term"] .navbar-brand:hover { + background: #0a2008; +} + +html[theme="iced"] { + --accent: #0084ff; + --accent-rgb: 0, 132, 255; + --bg: #031c35; + --black: #000; + --white: #fff; + --gray: rgba(255, 255, 255, .05); + --nav-bg: #111d37; + --nav-brand-border: inset 1px #242424; + --nav-brand-bg: #000; + --navigation-links-bg: #111d37; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #0a3f53; + --nav-border-color: rgba(255, 255, 255, .05); + --dropdown-bg: #111d37; + --dropdown-item-hover: #0a3f53; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: #111d37; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #0a3f53; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: #000e1a; + --badge-bg: #091f39; + --posts-meta-bg: #000000b8; + --badge-sfw: #07866e; + --badge-nsfw: #E10DC3; + --badge-nsfl: #660000; + --badge-tag: #22083c; + --scrollbar-color: #2b2b2b; + --scroller-bg: #424242; + --tooltip-bg: #0a3f53; + --footbar-color: #0084ff; + --loading-indicator-color: #0084ff; + --img-border-width: 0; + --img-border-color: #363636; + --maximize_button: #0084ff; + --bg-gradient: linear-gradient(0deg, rgb(0, 0, 0) 0%, rgb(0, 0, 0) 10%, #062631 100%); + /* appearance */ + background: black; +} + +html[theme="iced"] ._204863 { + background: -webkit-linear-gradient(left, #102c5e, #111d37); +} + +html[theme="iced"] svg.iconset { + background: #111d37; +} + +html[theme="iced"] svg.iconset#subscribe-btn { + background: #111d37; +} + +html[theme="iced"] svg.iconset:hover { + background: #1b2f5a; +} + +html[theme="iced"] .pagination>a, +.pagination>span { + border-radius: 3px; + border: 1px solid var(--black); +} + +html[theme="iced"] .pagination>span { + border-radius: 3px; +} + +html[theme="iced"] span#favs { + border: 1px solid var(--black); +} + +html[theme="iced"] .navbar-brand:hover { + background: #1d2941; +} + +/* f0ck95 */ +html[theme='f0ck95'] { + --accent: silver; + --accent-rgb: 192, 192, 192; + --bg: teal; + --black: #000; + --white: #fff; + --gray: rgba(255, 255, 255, .05);; + --nav-bg: silver; + --nav-brand-border: inset 1px black; + --nav-brand-bg: silver; + --navigation-links-bg: silver; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #8a8989; + --nav-border-color: #282727; + --dropdown-bg: silver; + --dropdown-item-hover: #8a8989; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: silver; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #8a8989; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: silver; + --badge-bg: #a8a5a5; + --posts-meta-bg: #000000b8; + --badge-sfw: #339f72; + --badge-nsfw: #E10DC3; + --badge-nsfl: #660000; + --badge-tag: #959393; + --scrollbar-color: #2b2b2b; + --scroller-bg: #424242; + --footbar-color: #000; + --loading-indicator-color: #000; + --img-border-width: 0; + --img-border-color: #808080; + --maximize_button: #000; + --bg-gradient: linear-gradient(0deg, rgba(0, 128, 128, 0.514), rgba(0, 0, 0, 0) 10%, rgba(0, 128, 128, 0.363) 100%); + /* appearance */ + background: teal; +} + +html[theme="f0ck95"] html, +body { + color: var(--black); +} + +html[theme="f0ck95"] a { + color: var(--black); +} + +html[theme="f0ck95"] span#favs { + border: inset 1px gray; +} + +html[theme="f0ck95"] .embed-responsive-image { + border: none; +} + +html[theme="f0ck95"] .nav-link, +.pagination>a, +.pagination>span { + border-radius: 0; + border-left: inset 1px silver; + border-bottom: outset 1px silver; + border-right: outset 1px silver; + border-top: 1px outset gray; + box-shadow: var(--pagination-anchor-box-shadow); + background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient)); +} + +html[theme="f0ck95"] .pagination>a:active, +.pagination>span { + color: black; +} + +html[theme="f0ck95"] .pagination>a, +.pagination>span { + border-radius: 0; + border-left: inset 1px silver; + border-bottom: outset 1px silver; + border-right: outset 1px silver; + border-top: 1px outset gray; +} + +html[theme="f0ck95"] .metadata { + border-left: inset 2px silver; + border-bottom: outset 2px silver; + border-right: outset 2px silver; + border-top: 2px outset #8c8888; + color: var(--black); +} + +html[theme="f0ck95"] .media-object { + border-left: inset 2px silver; + border-right: outset 2px silver; +} + +html[theme="f0ck95"] .embed-responsive.embed-responsive-16by9 { + background: black; +} + +html[theme="f0ck95"] .embed-responsive-image { + background: #0000; +} + +html[theme="f0ck95"] span.placeholder { + border-left: 1px solid var(--black); +} + +html[theme="f0ck95"] #prev { + color: white !important; +} + +html[theme="f0ck95"] #next { + color: white !important; +} + +html[theme="4d"] { + --accent:#f2ef0b; + --accent-rgb:31,178,176; + --bg:#242424; + --black:#000; + --white:#fff; + --gray:#262626; + --nav-bg:#171717; + --nav-brand-border:inset 1px #242424; + --nav-brand-bg:#171717; + --motd-bg:#252424; + --navigation-links-bg:rgb(32,32,32); + --navigation-links-background-linear-gradient:rgba(0,0,0,.12),rgba(0,0,0,0); + --navigation-links-border-color:rgba(0,0,0,.8) rgba(0,0,0,.65) rgba(0,0,0,.5); + --navigation-links-box-shadow:rgba(255,255,255,.05); + --nav-link-background-linear-gradient:rgba(255,255,255,.04),rgba(255,255,255,0); + --nav-link-box-shadow:inset 0 0 0 1px rgba(255,255,255,.04),inset 0 0px rgba(255,255,255,.04),inset 0 0px rgba(0,0,0,.15),0 0px 0px rgba(0,0,0,.1); + --nav-link-hover-bg:#333; + --nav-border-color:rgba(255,255,255,.05); + --dropdown-bg:#232323; + --dropdown-item-hover:#0d0d0d; + --nav-brand-font:'VCR'; + --font:monospace; + --pagination-background:#171717; + --pagination-box-shadow:inset 0 0 0 1px rgba(255,255,255,.04),inset 0 0px rgba(255,255,255,.04),inset 0 0px rgba(0,0,0,.15),0 0px 0px rgba(0,0,0,.1); + --pagination-anchor-box-shadow:inset 0 0 0 1px rgba(255,255,255,.04),inset 0 1px rgba(255,255,255,.04),inset 0 -1px rgba(0,0,0,.15),0 1px 1px rgba(0,0,0,.1); + --pagination-background-hover:#333; + --pagination-border-color:rgba(0,0,0,.8) rgba(0,0,0,.65) rgba(0,0,0,.5); + --metadata-bg:rgba(34,34,34,0.8); + --badge-bg:#131313; + --posts-meta-bg:#000000b8; + --badge-sfw:#68a728; + --badge-nsfw:#E10DC3; + --badge-nsfl: #660000; + --badge-tag:#353535; + --scrollbar-color:#2b2b2b; + --footbar-color:#1fb2b0; + --loading-indicator-color:#1fb2b0; + --img-border-width:0; + --img-border-color:#363636; + --maximize_button:#1fb2b0; + --bg-gradient:linear-gradient(0deg,rgba(0,0,0,0.94) 0%,rgb(6,6,6) 10%,rgb(43,43,43) 100%); + background:black; +} + +/* removing in favor of new appearance */ +/* + +/* Notifications */ +.nav-item-rel { + position: relative; + display: inline-block; +} + +.notif-count { + position: absolute; + top: 0px; + right: -5px; + background: var(--badge-nsfw); + color: white; + font-size: 10px; + padding: 2px 2px; + border-radius: 3px; + line-height: 1; +} + +.flash-error { + color: #ff4444; + text-align: center; + margin-bottom: 10px; + font-weight: bold; +} + +.flash-success { + color: #000; + background: var(--accent); + padding: 10px; + border-radius: 4px; + margin-bottom: 10px; + text-align: center; + font-weight: bold; +} + + +.notif-dropdown { + display: none; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + width: 320px; + background: var(--dropdown-bg); + border: 1px solid var(--nav-border-color); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.45); + z-index: 1000; + margin-top: 6px; + border-radius: 6px; +} + +/* Arrow caret pointing up toward the icon */ +.notif-dropdown::before { + content: ''; + position: absolute; + top: -6px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-bottom: 6px solid var(--nav-border-color); +} + +.notif-dropdown::after { + content: ''; + position: absolute; + top: -5px; + left: 50%; + transform: translateX(-50%); + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid var(--dropdown-bg); +} + +.notif-dropdown.visible { + display: block; +} + +@media (max-width: 768px) { + .notif-dropdown { + position: fixed; + transform: none; + width: 320px; + max-width: calc(100vw - 16px); + margin-top: 0; + } + /* Hide arrow on mobile — bell position varies */ + .notif-dropdown::before, + .notif-dropdown::after { + display: none; + } +} + +.notif-header { + padding: 10px; + border-bottom: 1px solid var(--nav-border-color); + display: flex; + justify-content: space-between; + align-items: center; + font-size: 0.9rem; + color: var(--white); + background: var(--nav-bg); +} + +.notif-header button { + background: none; + border: none; + color: var(--accent); + cursor: pointer; + font-size: 0.8rem; + padding: 0; +} + +.notif-list { + max-height: 300px; + overflow-y: auto; +} + +.notif-item { + padding: 10px; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + background: var(--dropdown-bg); + cursor: pointer; + transition: background 0.2s; + font-size: 0.85rem; + color: var(--white); + display: block; + text-decoration: none; +} + +.notif-item:hover { + background: var(--dropdown-item-hover); + text-decoration: none; + color: var(--white); +} + +.notif-item.unread { + border-left: 3px solid var(--accent); + background: rgba(255, 255, 255, 0.02); +} + +.notif-item.notif-with-thumb { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.notif-with-thumb .notif-thumb { + flex-shrink: 0; + width: 70px; + height: 70px; + border-radius: 4px; + overflow: hidden; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.notif-with-thumb .notif-thumb img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.2s; +} + +.notif-item:hover .notif-thumb img { + transform: scale(1.1); +} + +.notif-with-thumb .notif-content { + flex: 1; + min-width: 0; +} + +.notif-with-thumb .notif-info { + font-weight: bold; + color: var(--accent); + font-size: 0.9em; + margin-bottom: 2px; +} + +.notif-with-thumb .notif-msg { + color: #ccc; + font-size: 0.85em; + line-height: 1.2; +} + +.notif-time { + font-size: 0.75rem; + color: #888; + margin-top: 3px; + display: block; +} + +.notif-expandable:hover .notif-reason-preview { + text-decoration: underline; + color: #ffcccc; +} + +.notif-reason-full { + animation: slideDown 0.2s ease-out; +} + +.notif-empty { + text-align: center; + font-size: smaller; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-5px); + } + + to { + opacity: 1; + transform: translateY(0); + } +} + +.notif-fade-out { + opacity: 0 !important; + transform: translateX(20px); + max-height: 0 !important; + padding-top: 0 !important; + padding-bottom: 0 !important; + margin: 0 !important; + border: 0 !important; + transition: all 0.3s ease-in-out; + pointer-events: none; + overflow: hidden; +} + +@keyframes notif-pulse { + 0% { box-shadow: 0 0 0 0 var(--accent, #e91e63); background: rgba(233,30,99,.18); } + 50% { box-shadow: 0 0 0 6px rgba(233,30,99,.0); background: rgba(233,30,99,.1); } + 100% { box-shadow: none; background: transparent; } +} +.notif-highlight { + animation: notif-pulse 1.2s ease-out 2; + border-radius: 6px; +} + + + +/* Base style for all right sidebars */ +.item-sidebar-right, +.index-sidebar-right, +.global-sidebar-right { + display: flex !important; + flex-direction: column !important; + position: fixed !important; + top: var(--navbar-h, 50px) !important; + bottom: 0 !important; + right: 0; + width: 300px; + max-width: 85%; + height: auto !important; + max-height: none !important; + background: var(--bg) !important; + border-left: 1px solid var(--nav-border-color) !important; + z-index: 900 !important; /* below navbar dropdowns (1000+) but above page content */ + transition: right 0.3s ease-in-out, visibility 0.3s !important; + box-shadow: -5px 0 15px rgba(0,0,0,0.5) !important; + overflow: visible !important; + /* iOS Safari: allow vertical scroll but let JS handle horizontal swipe-to-close */ + touch-action: pan-y; +} + +body.sidebar-right-hidden .global-sidebar-right { + right: -300px !important; +} + +/* Prevent native image drag from hijacking the sidebar mouse-drag gesture */ +.global-sidebar-right img, +.item-sidebar-right img, +.index-sidebar-right img { + -webkit-user-drag: none; + user-select: none; +} + +/* Edge zone drag handle — simple centered vertical pill */ +#sidebar-drag-zone { + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.2s ease; +} + +#sidebar-drag-zone::after { + content: ''; + display: block; + width: 4px; + height: 40px; + border-radius: 2px; + background: var(--accent, #888); + transition: height 0.2s ease, opacity 0.2s ease; +} + +#sidebar-drag-zone:hover { + opacity: 1; +} + +/* Always show the handle on touch devices (no hover state available) */ +@media (pointer: coarse) { + #sidebar-drag-zone { + opacity: 0.7; + justify-content: flex-end; + padding-right: 6px; + } +} + +#sidebar-drag-zone:hover::after { + height: 56px; +} + +/* Ensure intermediate tablet screens also hide correctly */ +@media (max-width: 1199px) { + body.sidebar-right-hidden .item-sidebar-right, + body.sidebar-right-hidden .index-sidebar-right, + body.sidebar-right-hidden .global-sidebar-right { + right: -100% !important; /* Fallback for variable width sidebars */ + right: -300px !important; + } +} + + + +/* ============================================================ + ITEM PAGE LAYOUTS — Desktop (min-width: 1000px) + Two layouts, clearly scoped to body class. + body.layout-modern = 3-column (guests + use_new_layout members) + body.layout-legacy = 2-column scrolling (!use_new_layout members) + ============================================================ */ +@media (min-width: 1000px) { + + /* ---------------------------------------------------------- + LAYOUT: MODERN — 3-column viewport-locked grid + ---------------------------------------------------------- */ + body.layout-modern .item-layout-container { + display: grid; + grid-template-columns: 350px minmax(0, 1fr) 350px; + grid-template-rows: minmax(0, 1fr); + flex: 1; + min-height: 0; + width: 100%; + height: 100%; + overflow: hidden; + transition: grid-template-columns 0.3s ease; + } + + /* Collapsed right sidebar — grid track shrinks to 0 */ + body.layout-modern.sidebar-right-hidden .item-layout-container { + grid-template-columns: 300px minmax(0, 1fr) 0px; + } + + body.layout-modern .item-sidebar-left { + grid-column: 1; + height: 100%; + max-height: 100%; + display: flex; + flex-direction: column; + overflow-y: auto; + overflow-x: hidden; + border-right: 1px solid var(--nav-border-color); + z-index: 1; + } + + body.layout-modern .item-layout-container .item-main-content { + grid-column: 2; + height: 100%; + display: grid; + grid-template-rows: 0.5fr auto 2fr; + justify-items: center; + overflow-y: scroll; + overflow-x: hidden; + padding: 20px; + min-height: 0; + min-width: 0; + gap: 0; + } + + body.layout-modern .item-layout-container .item-main-content ._204863 { + align-self: end; + } + + body.layout-modern .item-layout-container .item-main-content .content { + align-self: center; + } + + body.layout-modern .item-layout-container .item-main-content .metadata { + align-self: start; + } + + /* Sidebar toggle: hide left sidebar (e.g. keybind toggle) */ + body.layout-modern .item-layout-container.sidebar-hidden { + grid-template-columns: 1fr; + } + body.layout-modern .item-layout-container.sidebar-hidden .item-sidebar-left { + display: none; + } + body.layout-modern .item-layout-container.sidebar-hidden .item-main-content { + grid-column: 1; + } + + body.layout-modern .item-sidebar-right, + body.layout-modern .global-sidebar-right { + grid-column: 3; + /* Inheritance from universal .item-sidebar-right (fixed, right: 0) */ + } + + body.layout-modern.sidebar-right-hidden .item-sidebar-right, + body.layout-modern.sidebar-right-hidden .global-sidebar-right { + /* Inheritance from universal body.sidebar-right-hidden .item-sidebar-right (right: -300px) */ + min-width: 0; + } + + /* Toggle tab button — positioned absolutely inside sidebar, pokes out left edge */ + .item-sidebar-left { + position: relative; + } + /* Removed local sidebar and toggle overrides — now using universal rules */ + + /* Collapse right sidebar on narrower screens in modern view */ + @media (max-width: 1400px) { + body.layout-modern .item-layout-container { + grid-template-columns: 300px minmax(0, 1fr); + } + /* Uses universal fixed slide-out logic; no need for display: none at this breakpoint */ + } + + /* ---------------------------------------------------------- + LAYOUT: LEGACY — 2-column, natural page scroll + Right sidebar is fixed to viewport. + ---------------------------------------------------------- */ + body.layout-legacy .item-layout-container { + display: grid; + grid-template-columns: minmax(0, 1fr) 300px; + height: auto !important; + overflow: visible !important; + } + + body.layout-legacy.sidebar-right-hidden .item-layout-container { + grid-template-columns: 1fr; + } + + body.layout-legacy .item-layout-container .item-main-content { + grid-column: 1; + display: flex !important; + flex-direction: column; + align-items: center; + height: auto !important; + max-height: none !important; + overflow: visible !important; + gap: 0; + padding: 20px; + } + + body.layout-legacy .item-layout-container .item-main-content ._204863 { + align-self: center; + width: 100%; + } + + body.layout-legacy .item-layout-container .item-main-content .content { + align-self: center; + width: 100%; + } + + body.layout-legacy .item-layout-container .item-main-content .metadata { + align-self: center; + width: 800px; + max-width: 100%; + } + + body.layout-legacy .item-layout-container #comments-container { + width: 800px; + max-width: 100%; + align-self: center; + flex: 0 0 auto !important; + z-index: 1; + } + + body.layout-legacy .item-layout-container .ententeich-block { + width: 800px; + max-width: 100%; + } + + body.layout-legacy .item-layout-container .item-sidebar-right, + body.layout-legacy .item-layout-container .global-sidebar-right { + /* Inheritance from universal .item-sidebar-right (fixed, right: 0) */ + grid-column: 2; + grid-row: 1 / span 10; + } + + /* Slide the legacy sidebar off-screen — handled by universal rule */ +} + +.sidebar-activity { + display: flex; + flex-direction: column; + flex: 1; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + touch-action: pan-y; /* iOS Safari: keep horizontal swipe-to-close claimable by JS */ +} + +.sidebar-activity-header { + padding: 15px; + font-weight: bold; + color: var(--accent); + border-bottom: 1px solid var(--nav-border-color); + font-family: 'VCR'; + font-size: 0.9em; + letter-spacing: 1px; +} + +#sidebar-activity-container.sidebar-comments-list { + flex: 1; + overflow-y: auto; + padding: 5px; + touch-action: pan-y; +} + +#sidebar-activity-container.sidebar-comments-list .comment { + margin-bottom: 5px; + padding: 5px; + font-size: 0.9em; + border-bottom: 1px solid rgba(255,255,255,0.05); +} + +.sidebar-activity .comment-content-inner { + display: block; + max-height: 80px; /* Roughly 4-5 lines */ + overflow: hidden; + position: relative; + white-space: pre-wrap; +} + +/* Subtle fade to indicate more content */ +.sidebar-activity .comment-content.has-overflow:not(.expanded) .comment-content-inner::after { + content: ""; + position: absolute; + bottom: 0; + right: 0; + width: 100%; + height: 20px; + background: linear-gradient(to bottom, transparent, rgba(17, 17, 17, 0.9)); + pointer-events: none; +} + +.sidebar-activity .comment-content.expanded .comment-content-inner { + max-height: unset; + overflow: visible; +} + +.sidebar-activity .read-more-btn, +.sidebar-activity .see-less-btn { + background: none; + border: none; + color: var(--accent); + cursor: pointer; + padding: 0; + margin-top: 5px; + font-size: 0.85em; + display: none; /* Hidden by default, shown via JS if overflow detected */ +} + +/* Avatar in sidebar activity */ +.sidebar-avatar-link { + flex-shrink: 0; + display: flex; +} + +.sidebar-avatar { + width: 24px; + height: 24px; + object-fit: cover; + border: 1px solid rgba(255,255,255,0.1); +} + +.read-more-btn, +.see-less-btn { + display: inline; + padding: 0; + background: none; + border: none; + color: var(--accent); + font-size: inherit; + cursor: pointer; + opacity: 0.8; + text-decoration: underline; + text-underline-offset: 2px; +} +.read-more-btn:hover, +.see-less-btn:hover { + opacity: 1; +} + + +#sidebar-activity-container.sidebar-comments-list .comment:last-child { + border-bottom: none; +} + +#sidebar-activity-container.sidebar-comments-list .comment-header { + margin-bottom: 4px; +} + +/* Mobile Stacking for Legacy Mode (max-width: 999px) */ +@media (max-width: 999px) { + .item-layout-container { + display: flex; + flex-direction: column; + width: 100%; + padding: 5px; + } + .item-layout-container .item-main-content { + order: 1; /* Main content (video) on top */ + /* padding: 10px; */ + } + .item-sidebar-left { + order: 2; /* Comments/Tags on bottom */ + /* padding: 10px; */ + z-index: 1; + } + /* Mobile Overrides if necessary — most styles now universal */ + .item-sidebar-right, + .index-sidebar-right, + .global-sidebar-right { + width: 280px !important; + } + + body.sidebar-right-hidden .item-sidebar-right, + body.sidebar-right-hidden .index-sidebar-right, + body.sidebar-right-hidden .global-sidebar-right { + right: -280px !important; + } + + #sidebar-right-toggle { + top: 80% !important; + transform: none !important; + } +} + +/* Common sizing for the Legacy content area */ +.item-layout-container .item-main-content > ._204863, +.item-layout-container .item-main-content > .content, +.item-layout-container .item-main-content > .metadata, +.item-layout-container .item-main-content #comments-container { + width: 100%; + max-width: 1100px; /* Optional cap to prevent excessive width on huge screens */ + min-height: 0; + min-width: 0; +} + +.item-layout-container .item-main-content > .content { + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; +} + +.item-layout-container .item-main-content .media-object { + height: auto; + width: 100%; + max-width: 100%; + min-height: 0; + min-width: 0; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +.item-layout-container .item-main-content .embed-responsive { + width: 100%; + max-width: calc(100vh * (16 / 9)); /* Cap width based on viewport height to fit vertically */ + margin: 0 auto; +} + +.item-main-content video, +.item-main-content img { + max-height: calc(100vh - 180px); /* Leave space for ID and Metadata bars */ + object-fit: contain; +} + +#comments-container { + color: var(--white); + width: 100%; + box-sizing: border-box; + display: flex; + flex-direction: column; +} + +/* Modern: comments fill all remaining left-sidebar space; list scrolls internally */ +body.layout-modern .item-sidebar-left #comments-container { + display: flex !important; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + overflow: hidden; +} + +body.layout-modern #comments-container .main-input { + order: -1; +} + +/* Legacy: comments sit in the page flow, no forced height */ +body.layout-legacy #comments-container { + display: flex !important; + flex-direction: column; + flex: 0 0 auto !important; + min-height: 0; + overflow: visible; +} + +/* Pin tags + controls to the bottom of the left sidebar */ +body.layout-modern .item-sidebar-left .sidebar-tags-container, +body.layout-modern .item-sidebar-left .tag-controls { + flex-shrink: 0; +} + +#comments-container.faded-out { + opacity: 0; + pointer-events: none; +} + + +.comments-controls { + display: flex; + align-items: center; + gap: 5px; +} + +.comments-controls button, +.comments-controls select { + background: rgba(255, 255, 255, 0.1); + border: 1px solid var(--nav-border-color); + color: var(--white); + padding: 0 10px; + height: 24px; + font-size: 11px; + text-transform: uppercase; + font-weight: 600; + border-radius: 3px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: background 0.2s; +} + +.comments-controls button:hover { + background: rgba(255, 255, 255, 0.2); +} + +.admin-pin-btn, +.admin-edit-btn, +.admin-delete-btn { + background: none; + border: none; + padding: 0; + margin: 0; + color: #888; + cursor: pointer; + text-decoration: underline; +} + +.admin-delete-btn:hover { + color: #ff4444; +} + +.admin-pin-btn:hover, +.admin-edit-btn:hover { + color: var(--accent); +} + +.comment-input { + margin-bottom: 5px; + background: rgba(255, 255, 255, 0.05); + padding: 5px; + border: 1px solid var(--nav-border-color); + border-radius: 0;; + margin: 5px; +} + +.comment-input textarea { + width: 100%; + background: rgba(0, 0, 0, 0.2); + border: 1px solid var(--nav-border-color); + color: var(--white); + padding: 10px; + min-height: 80px; + resize: vertical; + border-radius: 0; + /* No rounded corners */ +} + +.input-actions { + margin-top: 10px; + display: flex; + justify-content: flex-end; + align-items: center; + gap: 5px; +} + +.submit-comment { + background: var(--accent); + color: var(--black); + border: none; + padding: 5px 15px; + font-weight: bold; + cursor: pointer; + border-radius: 0; + /* No rounded corners */ +} + +.submit-comment.loading { + pointer-events: none; + opacity: 0.7; + cursor: wait; +} + +.cancel-reply { + background: transparent; + color: var(--white); + border: 1px solid var(--nav-border-color); + margin-left: 10px; + padding: 5px 10px; + cursor: pointer; + border-radius: 0; +} + +.comments-list { + display: flex; + flex-direction: column; + gap: 5px; + padding-right: 5px; + padding-left: 5px; + padding-top: 5px; +} + +body.layout-modern .comments-list { + position: relative; + display: flex; + flex-direction: column; + gap: 5px; + flex: 1 1 auto; /* Grow to fill #comments-container */ + overflow-y: auto; /* Scroll comments here, not on the container */ + overflow-x: hidden; + min-height: 0; +} + +body.layout-legacy .comments-list { + position: relative; + display: flex; + flex-direction: column; + gap: 5px; + flex: 0 0 auto; /* Natural height */ + overflow: visible; /* Unified scrolling */ + min-height: 0; +} + +body.layout-legacy .scroll-nav-wrapper { + position: absolute; + top: 0; + bottom: 0; + right: -50px; + width: 40px; + pointer-events: none; + z-index: 1000; +} + +body.layout-legacy .scroll-to-bottom { + position: sticky; + top: calc(100vh - 80px); + width: 40px; + height: 40px; + background: var(--nav-bg); + border: 1px solid var(--nav-border-color); + color: var(--accent); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + pointer-events: auto; + border-radius: 50%; + box-shadow: 0 4px 10px rgba(0,0,0,0.5); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + opacity: 0.8; +} + +body.layout-legacy .scroll-to-bottom:hover { + background: var(--nav-link-hover-bg); + opacity: 1; + transform: translateY(-2px); +} + + + +body.layout-legacy .scroll-to-bottom .icon-up { + display: none; +} + +body.layout-legacy .scroll-to-bottom.is-at-bottom .icon-up { + display: block; +} + +body.layout-legacy .scroll-to-bottom.is-at-bottom .icon-down { + display: none; +} + +body.layout-legacy .scroll-to-bottom svg { + width: 20px; + height: 20px; +} + + + +.comments-list::-webkit-scrollbar { + width: 6px; +} +.comments-list::-webkit-scrollbar-thumb { + background: var(--gray); + border-radius: 3px; +} + +.comment { + display: flex; + gap: 15px; + padding: 10px; + background: rgba(255, 255, 255, 0.03); + /* Very light seethrough */ + border: 1px solid rgba(255, 255, 255, 0.05); + border-radius: 0; + /* No rounded corners */ + position: relative; + padding-bottom: 25px; /* Room for absolute permalink */ +} + +.comment.deleted { + opacity: 0.5; +} + +.comment-avatar img { + width: 40px; + height: 40px; + object-fit: cover; + border-radius: 0; + /* Square avatars */ +} + +.comment-avatar::after { + content: ""; + display: block; + height: 20px; +} + +.comment-body { + flex: 1; + min-width: 0; +} + +.comment-body { + flex: 1; + min-width: 0; +} + +.comment-content p { + margin: 0; +} + +.comment-header { + margin-bottom: 8px; + font-size: 0.85em; + color: #888; + display: flex; + justify-content: space-between; + align-items: flex-end; + line-height: normal; + flex-wrap: wrap; + gap: 2px 10px; +} + +.comment-header-left { + display: flex; + align-items: center; + gap: 5px; +} + +.pinned-badge { + background: var(--accent); + color: var(--nav-bg); + font-size: 0.8em; + padding: 1px 6px; + border-radius: 2px; + font-weight: bold; + display: inline-flex; + align-items: center; + gap: 3px; + line-height: 1; + margin-right: 5px; +} + +.comment-header-left .pinned-badge { + padding: 2px 3px; +} + +.pinned-badge svg { + width: 10px; + height: 10px; + display: block; + stroke-width: 3; +} + +.comment-footer { + margin-top: 8px; + font-size: 0.85em; + color: #888; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 2px 10px; +} + +.comment-footer-left, +.comment-footer-right { + display: flex; + align-items: center; + min-width: 0; +} + +.comment-footer-right { + flex-grow: 1; + justify-content: flex-end; +} + +.comment-actions { + display: flex; + align-items: center; + gap: 5px; +} + +.comment-header button, +.comment-header a, +.comment-header span, +.comment-footer button, +.comment-footer a, +.comment-footer span { + padding: 0; + margin: 0; + line-height: 1; + font-family: inherit; + font-size: inherit; + vertical-align: top; +} + +.comment-meta button, +.comment-meta a { + background: none; + border: none; + cursor: pointer; + color: #888; + padding-right: 8px; + display: flex; + align-items: center; + transition: color 0.1s; + text-decoration: none; +} + +.comment-meta button svg, +.comment-meta a svg { + width: 14px; + height: 14px; + display: block; + margin-right: 2px; +} + +.comment-header button:hover, +.comment-header a:hover, +.comment-footer button:hover, +.comment-footer a:hover { + color: var(--white); +} + +.report-comment-btn:hover { + opacity: 1 !important; + color: var(--accent, #e91e63) !important; +} + +.comment-permalink { + position: absolute; + bottom: 5px; + left: 10px; + font-size: 0.7em !important; + color: #555 !important; + white-space: nowrap; + font-family: 'VCR'; +} + +.comment-permalink:hover { + color: #888 !important; +} + +.comment-author { + font-weight: bold; + color: var(--accent); + margin-right: 8px; + padding-right: 0 !important; +} + +.comment-time { + margin-right: 5px; + font-size: 0.8em; + color: #777; + margin-bottom: 1px; + text-decoration: none; + align-self: baseline; +} + +.comment-time:hover { + color: #aaa; + text-decoration: underline; +} + + +.admin-delete-btn:hover { + color: #ff4444 !important; +} + +.admin-pin-btn.active { + color: var(--accent) !important; +} + + +.comment-permalink { + color: #666; + text-decoration: none; +} + +.comment-permalink:hover { + text-decoration: underline; +} + +.reply-btn { + background: none; + border: none; + color: #888; + cursor: pointer; + text-decoration: underline; + padding: 0; + margin: 0; +} + +.comment-content { + line-height: 1.4; + word-break: break-word; + white-space: pre-wrap; +} + +.spoiler { + background-color: #000 !important; + color: #000 !important; + cursor: pointer !important; + transition: color 0.1s; + border-radius: 4px; + pointer-events: auto !important; /* Ensure interaction even if parent has pointer-events: none */ +} + +/* Ensure spoilers properly envelope media */ +.spoiler:has(img, video, iframe, audio) { + display: inline-block; + max-width: 100%; +} + +.spoiler:has(.video-embed-wrap, .yt-embed-wrap, .audio-embed-wrap, img, video, iframe, audio) { + display: inline-grid; + grid-auto-flow: column; + max-width: 100%; + vertical-align: middle; +} + +.spoiler .video-embed-wrap, +.spoiler .yt-embed-wrap, +.spoiler .audio-embed-wrap { + display: inline-block; + vertical-align: middle; + width: auto; + max-width: 100%; + margin: 4px 2px; +} + +.spoiler .yt-embed-wrap { + width: 480px; /* YouTube needs a base width for the aspect ratio padding trick */ +} + +.spoiler img, +.spoiler video, +.spoiler iframe, +.spoiler audio, +.spoiler .sidebar-video-link { + display: inline-block; + vertical-align: middle; + filter: brightness(0); + background-color: #000 !important; + color: #000 !important; + pointer-events: none; + transition: filter 0.1s; +} + +.spoiler .mention, +.spoiler a { + filter: brightness(0); + background-color: #000 !important; + color: #000 !important; + pointer-events: none; + transition: filter 0.1s; +} + +.spoiler:hover { + color: #fff !important; +} + +.spoiler:hover img, +.spoiler:hover video, +.spoiler:hover iframe, +.spoiler:hover audio, +.spoiler:hover .sidebar-video-link, +.spoiler:hover .mention, +.spoiler:hover a { + filter: none; + background-color: transparent !important; + color: inherit !important; + pointer-events: auto; +} + +.spoiler.revealed { + color: #fff !important; +} + +.spoiler.revealed img, +.spoiler.revealed video, +.spoiler.revealed iframe, +.spoiler.revealed audio, +.spoiler.revealed .sidebar-video-link, +.spoiler.revealed .mention, +.spoiler.revealed a { + filter: none; + background-color: transparent !important; + color: inherit !important; + pointer-events: auto; +} + +.blur-text { + filter: blur(10px); + cursor: pointer !important; + transition: filter 0.2s ease; + pointer-events: auto !important; + display: inline-block; + max-width: 100%; +} + +.blur-text:hover, +.blur-text.revealed { + filter: blur(0); +} + +.blur-text img, +.blur-text video, +.blur-text iframe, +.blur-text audio, +.blur-text .video-embed-wrap, +.blur-text .yt-embed-wrap, +.blur-text .audio-embed-wrap { + filter: blur(10px); + transition: filter 0.2s ease; +} + +.blur-text:hover img, +.blur-text:hover video, +.blur-text:hover iframe, +.blur-text:hover audio, +.blur-text:hover .video-embed-wrap, +.blur-text:hover .yt-embed-wrap, +.blur-text:hover .audio-embed-wrap, +.blur-text.revealed img, +.blur-text.revealed video, +.blur-text.revealed iframe, +.blur-text.revealed audio, +.blur-text.revealed .video-embed-wrap, +.blur-text.revealed .yt-embed-wrap, +.blur-text.revealed .audio-embed-wrap { + filter: none; +} + +.comment-children { + margin-top: 15px; + margin-left: 10px; + border-left: 1px solid var(--nav-border-color); + padding-left: 15px; +} + +@media screen and (max-width: 600px) { + .comment-children { + margin-left: 5px; + padding-left: 8px; + } +} + +.loading, +.error { + text-align: center; + padding: 20px; + color: #888; +} + +.deleted-msg { + color: #666; + font-style: italic; +} + + +html[theme="f0ck95"] #tags .badge>a:first-child { + text-shadow: 1px 1px #8080805e; +} + +html[theme="f0ck95"] .badge.badge-greentext.badge-light.mr-2 { + color: #b5bd68; +} + +html[theme="f0ck95"] .pagination>span { + border: 1px dashed grey !important; + color: black; + text-shadow: none; +} + +html[theme="f0ck95"] .dropdown-menu { + border-color: gray; +} + +html[theme="f0ck95"] .err span { + text-shadow: 0px 0px transparent; +} + +html[theme="f0ck95"] .err { + background: silver; + border: outset 2px gray; +} + +html[theme="f0ck95"] div.posts>a::after { + color: var(--white); +} + +html[theme="f0ck95"] .about { + color: black; +} + +html[theme="f0ck95"] .badge-dark, +#themeselector { + border: inset 1px gray; + background: #a8a5a5; +} + +html[theme="f0ck95"] .badge-light { + color: #000; + text-shadow: none; + border: 1px outset; + border-radius: 0; +} + +html[theme="f0ck95"] .badge-success { + color: #000; + text-shadow: none; + border: 1px outset; + border-radius: 0; +} + +html[theme="f0ck95"] .badge-danger { + color: #000; + text-shadow: none; + border: 1px outset; + border-radius: 0; +} + +html[theme="f0ck95"] img.avatar { + border-top-left-radius: 0; +} + +html[theme="f0ck95"] ._204863 { + content: " "; + background: -webkit-linear-gradient(left, #08216b, #a5cef7); + text-align: left; + width: 100%; + color: white; + height: auto; + border-left: inset 2px silver; + border-top: inset 2px silver; + border-right: outset 2px silver; + border-bottom: outset 2px silver; + line-height: 1.7; +} + +html[theme="f0ck95"] .iconset#a_delete { + stroke: var(--black); +} + +html[theme="f0ck95"] .iconset#a_favo { + fill: var(--black); +} + +html[theme="f0ck95"] .gapD { + height: 16px; + background: #08f; + width: max-content; + display: flex; +} + +html[theme="f0ck95"] svg.iconset { + display: inline-block; + height: 18px; + cursor: pointer; + width: 21px; + background: silver; + margin: 1px; + padding: 1px; + border: 1px outset silver; + position: relative; + right: 2px; +} + +html[theme="f0ck95"] .gapRight { + display: flex; + align-items: center; +} + +html[theme="f0ck95"] input { + color: #000; +} + +html[theme="f0ck95"] .embed-responsive-image { + left: 2px; + right: 2px; +} + +html[theme="f0ck95"] ._error_message { + color: black; + text-shadow: none; +} + +html[theme="f0ck95"] ._error_topbar { + background: -webkit-linear-gradient(left, #08216b, #a5cef7); +} + +html[theme="f0ck95"] .f0ckgle { + color: black; +} + +html[theme="f0ck95"] .admin-search button { + color: black; +} + +/* f0ck95dARK */ +html[theme='f0ck95d'] { + --accent: #fff; + --bg: #0e0f0f; + --black: #000; + --white: #fff; + --gray: silver; + --nav-bg: #0b0a0a; + --nav-brand-border: inset 1px black; + --nav-brand-bg: #0000; + --navigation-links-bg: #0b0b0b; + --navigation-links-background-linear-gradient: rgba(0, 0, 0, .12), rgba(0, 0, 0, 0); + --navigation-links-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --navigation-links-box-shadow: rgba(255, 255, 255, .05); + --nav-link-background-linear-gradient: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + --nav-link-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --nav-link-hover-bg: #333131; + --nav-border-color: #282727; + --dropdown-bg: #333131; + --dropdown-item-hover: #444242; + --nav-brand-font: 'VCR'; + --font: monospace; + --pagination-background: #0b0a0a; + --pagination-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 0px rgba(255, 255, 255, .04), inset 0 0px rgba(0, 0, 0, .15), 0 0px 0px rgba(0, 0, 0, .1); + --pagination-anchor-box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .04), inset 0 1px rgba(255, 255, 255, .04), inset 0 -1px rgba(0, 0, 0, .15), 0 1px 1px rgba(0, 0, 0, .1); + --pagination-background-hover: #8a8989; + --pagination-border-color: rgba(0, 0, 0, .8) rgba(0, 0, 0, .65) rgba(0, 0, 0, .5); + --metadata-bg: #0b0a0a; + --badge-bg: #0f0e0e; + --posts-meta-bg: #000000b8; + --badge-sfw: teal; + --badge-nsfw: #a72828; + --badge-tag: #2f2f2f; + --scrollbar-color: #2b2b2b; + --scroller-bg: #424242; + --footbar-color: #fff; + --loading-indicator-color: #fff; + --img-border-width: 0; + --img-border-color: #808080; + --maximize_button: #fff; + --bg-gradient: linear-gradient(0deg, rgba(0, 0, 0, 0.94) 0%, rgb(6, 6, 6) 10%, rgb(43, 43, 43) 100%); + /* appearance */ + background: black; + --motd-bg: #111; +} + +html[theme="f0ck95d"] .err { + background: #4c4c4c; + border: 2px outset gray; +} + +html[theme="f0ck95d"] ._error_topbar { + background: #081f3e; +} + +html[theme="f0ck95d"] ._204863 { + content: " "; + background: -webkit-linear-gradient(left, #0e111a, #073c71); + text-align: left; + width: 100%; + color: white; + height: auto; + border-left: inset 2px silver; + border-top: inset 2px silver; + border-right: outset 2px silver; + border-bottom: outset 2px silver; + line-height: 1.7; +} + +html[theme="f0ck95d"] .embed-responsive-image { + left: 2px; + right: 2px; +} + +html[theme="f0ck95d"] .media-object { + border-left: inset 2px silver; + border-right: outset 2px silver; +} + +html[theme="f0ck95d"] .embed-responsive.embed-responsive-16by9 { + background: black; + border-left: inset 1px silver; + border-right: outset 1px silver; +} + +html[theme="f0ck95d"] .embed-responsive-image { + border: none; +} + +html[theme="f0ck95d"] .embed-responsive-image { + background: #0000; +} + +html[theme="f0ck95d"] html, +body { + color: var(--white); +} + +html[theme="f0ck95d"] a { + color: var(--white); +} + +html[theme="f0ck95d"] span#favs { + border: inset 1px gray; + background: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient)); + +} + +html[theme="f0ck95d"] .nav-link, +.pagination>a, +.pagination>span { + border-radius: 0; + border-left: inset 1px black; + border-bottom: outset 1px black; + border-right: outset 1px black; + border-top: 1px inset black; +} + +html[theme="f0ck95d"] .metadata { + border-left: inset 1px silver; + border-bottom: outset 1px silver; + border-right: outset 1px silver; + border-top: inset 2px silver; + color: var(--accent); +} + +html[theme="f0ck95d"] .media-object { + border-left: inset 1px silver; + border-right: outset 1px silver; +} + +html[theme="f0ck95d"] span.placeholder { + border-left: 1px solid var(--gray); + opacity: 0.5; +} + +html[theme="f0ck95d"] #prev { + color: white; +} + +html[theme="f0ck95d"] #next { + color: white; +} + +/* same */ +/* html[theme="f0ck95d"] .navbar-brand:hover { + background: transparent; +} */ + +html[theme="f0ck95d"] .pagination>span { + border: 1px dashed var(--accent) !important; + color: white; + text-shadow: none; +} + +html[theme="f0ck95d"] .dropdown-menu { + border-left: inset 1px #4c4c4c; + border-bottom: outset 1px #4c4c4c; + border-right: outset 1px #4c4c4c; + border-top: outset 0px #4c4c4c; +} + +html[theme="f0ck95d"] .err span { + text-shadow: 0px 0px transparent; +} + +html[theme="f0ck95d"] div.posts>a::after { + color: var(--white); +} + +html[theme="f0ck95d"] .about { + color: var(--white); +} + +html[theme="f0ck95d"] .badge-dark, +#themeselector { + border: inset 1px gray; + background: rgba(255, 255, 255, .04), rgba(255, 255, 255, 0); + background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient)); + box-shadow: var(--nav-link-box-shadow); +} + +html[theme="f0ck95d"] .badge-light { + text-shadow: none; + border: 1px outset; + border-radius: 0; +} + +html[theme="f0ck95d"] .badge-success { + text-shadow: none; + border: 1px outset; + border-radius: 0; +} + +html[theme="f0ck95d"] .badge-danger { + text-shadow: none; + border: 1px outset; + border-radius: 0; +} + +html[theme="f0ck95d"] .admin-search button { + color: black; +} + +/* fullscreen */ +html[res="fullscreen"] ._204863 { + border-top: none; +} + +html[res="fullscreen"] .metadata { + border-bottom: none; +} + +html[res="fullscreen"] .embed-responsive-16by9::before { + padding-top: 32.99%; +} + +html[res="fullscreen"] .container { + max-width: 80% !important; + padding: 0 !important; +} + +@media screen and (max-width: 600px) { + html[res="fullscreen"] .container { + max-width: 100% !important; + } + html[res="fullscreen"] #main { + padding: 0 !important; + } +} + +html[res="fullscreen"] span#favs { + padding: 0 !important; +} + + +::-webkit-scrollbar { + width: 2px; +} + +::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-color); +} + +::-webkit-scrollbar-track { + background-color: transparent; +} + +*, +::before, +::after { + box-sizing: border-box; + outline: 0; + -webkit-tap-highlight-color: transparent; +} + +@font-face { + font-family: 'VCR'; + src: url('/s/vcr.ttf') format('truetype'); + /* https://www.dafont.com/vcr-osd-mono.font */ +} + +html { + background-color: var(--bg) !important; + background: var(--bg) !important; +} + +body { + background: transparent !important; + color: var(--white); + margin: 0; + font-family: var(--font); + line-height: 2; + scrollbar-color: var(--scrollbar-color) transparent; + scrollbar-width: thin; + overflow-x: clip; + font-size: 14px; +} + +html { + width: 100%; + height: 100%; + scrollbar-gutter: stable; +} + + +.noscript-badge { + display: block; +} + +a { + color: var(--accent) + /* !important */ + ; + cursor: pointer; + text-decoration: none; + display: inline-block; +} + +a.post_source:hover, +a.id-link:hover { + text-decoration: underline; +} + +a.btn.disabled { + pointer-events: none; + user-select: none; +} + +.btn.disabled, +.btn:disabled { + user-select: none; + font-weight: bold; + text-shadow: 1px 1px 1px var(--black); +} + +h5 { + margin: 0; + font-size: x-large; +} + +.mr-2 { + margin-right: 0.5rem !important; +} + +div.posts { + width: 100%; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(118px, 1fr)); + justify-items: center; + grid-gap: 5px; + margin: 0; + padding: 0; + margin-bottom: 35px; +} + +/* Mobile: Ensure 3 columns of thumbnails */ +@media (max-width: 600px) { + div.posts { + grid-template-columns: repeat(3, 1fr); + } +} + +div.posts>a:not(.notif-item) { + display: inline-block; + position: relative; + width: 100%; + background-repeat: no-repeat; + background-size: cover; + background-position: center; +} + +div.posts>a:not(.notif-item)::before { + content: ""; + display: block; + margin-top: 100%; +} + +div.posts>a:not(.notif-item):hover, +div.posts>a:not(.notif-item).touch-active { + box-shadow: 0 0 0 1px var(--white); +} + +div.posts>a:not(.notif-item)::after { + position: absolute; + right: 0; + top: 0; + content: attr(data-ext) " / " attr(data-user); + color: var(--white); + text-shadow: 0px 1px var(--black); + font-size: 10px; + background: var(--posts-meta-bg); + visibility: hidden; + width: 100%; + text-align: center; + font-family: vcr; + text-transform: uppercase; +} + +div.posts>a:not(.notif-item):hover::after, +div.posts>a:not(.notif-item).touch-active::after { + visibility: visible; +} + +/* Navbar */ +.nav { + background: var(--bg); + text-align: left; +} + +/* .navbar layout defined in view-specific navbar section below */ + + +.navbar.scrolled { + background: black !important; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); +} + +.navbar.scrolled .navbar-brand { + background: transparent !important; +} + +.navbar-brand { + padding-right: 5px; + padding-left: 5px; +} + +.navbar-brand:hover { + text-decoration: none; + transition: ease all .2s; + background: var(--black); +} + +span.f0ck { + font-family: var(--nav-brand-font); + font-size: 20px; + text-shadow: 1px 1px 1px var(--black), 0px 0px 0px var(--green), -1px -1px 0px var(--black), 1px -1px 0px var(--black), -1px 1px 0px var(--black), 0px 0px 0px var(--green); +} + +.navbar.navbar-expand-lg { + background: var(--nav-bg); +} + +.navbar-expand-lg .navbar-collapse { + display: flex; + flex-basis: auto; +} + +.navbar-collapse { + flex-grow: 1; +} + +#navbarSupportedContent { + padding: 0; +} + +.nav-link-identifier { + width: 100%; +} + +.navbar-expand-lg .navbar-nav { + flex-direction: row; +} + +.navbar-nav { + width: 100%; + display: flex; + flex-direction: column; + padding: 5px 0 5px 0; + margin-bottom: 0; + list-style: none; + margin: 0; + align-self: center; + margin-left: .5rem; + margin-right: .5rem; +} + +ul.navbar-nav li.nav-item { + margin-right: .5rem; +} + +.navigation-links { + display: flex; + border: 1px solid var(--navigation-links-border-color); + background: var(--navigation-links-bg); +} + +.nav-item { + width: auto; + white-space: nowrap; + text-align: center; +} + +.nav-link { + padding: 0; + padding-right: 0; + padding-left: 0; + display: flex; + justify-content: center; + min-width: 17px; + border: 1px solid var(--black); + border-radius: 3px; + background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient)); + box-shadow: var(--nav-link-box-shadow); +} + +.navbar-expand-lg .navbar-nav .nav-link, +.pagination>a, +.pagination>span { + padding-right: .5rem; + padding-left: .5rem; +} + +.pagination>a, +.pagination>span { + margin-right: 0px; + margin-left: 5px; +} + +.nav-item:hover>a:hover, +.dropdown-item:hover>a:hover { + background-color: var(--nav-link-hover-bg); +} + +.nav-link[data-toggle="dropdown"].ddcontent::after { + content: "\00a0(" attr(content) ")\00a0\25bc"; +} + +.nav-link[data-toggle="dropdown"]:not(.ddcontent)::after { + content: "\00a0\25bc"; +} + +.mandy { + visibility: hidden; +} + +@media (max-width: 444px) { + .mandy { + position: fixed; + width: inherit; + height: 100%; + top: 0; + z-index: -1; + bottom: 25px; + visibility: visible; + } + + #randbutton { + height: inherit; + width: inherit; + background: var(--bg); + border: none; + transition: .2s ease; + } + + #randbutton:active { + background: var(--gray); + transition: .2s ease; + } + + /* .metadata { + margin-bottom: 45px; + } */ + + .embed-responsive-16by9::before { + padding-top: 75% !important; + } + + .pagination { + justify-content: center !important; + } +} + +@media (max-width: 1240px) { + .nav-link[data-toggle="dropdown"]::after { + content: "" !important; + } +} + +@media (max-width: 768px) { + /* .nav-link[data-toggle="dropdown"]::after { + content: "" !important; + } */ + + .navbar { + display: flex !important; + flex-wrap: wrap !important; + } + + .navbar-header { + flex: 1; + } + + .nav-right-group { + flex-shrink: 0; + margin-left: 0; + padding: 0 8px; + } + + /* nav-left-group / navbarContent spans full width on the second row */ + .nav-left-group { + width: 100%; + order: 10; + } + + .navbar-brand { + grid-area: unset !important; + } + + .navigation-links { + grid-row: 2; + } + + .navbar-expand-lg .navbar-nav { + justify-content: center; + } + + #navbarSupportedContent { + grid-row: 3; + } + + /* On mobile: center the notification dropdown in the viewport */ + .nav-right-group .notif-dropdown { + right: 0; + left: auto; + transform: none; + } + /* Hide caret arrow on mobile */ + .nav-right-group .notif-dropdown::before, + .nav-right-group .notif-dropdown::after { + display: none; + } +} + +span.placeholder { + border-left: 1px solid var(--accent); + margin-left: 3px; + user-select: none; +} + +.navbar .nav-item .dropdown-menu { + display: none; +} + +.dropup, +.dropright, +.dropdown, +.dropleft { + position: relative; +} + +.navbar .nav-item:hover .dropdown-menu { + display: block; +} + +.dropdown:hover .nav-link { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + transition: .2s ease; + background-color: var(--nav-link-hover-bg); +} + +.navbar-nav .dropdown-menu { + float: none; +} + +.dropdown-menu { + background: var(--dropdown-bg); + position: absolute; + top: 100%; + width: 100%; + z-index: 1000; + min-width: calc(100% - 10px); + padding: 0; + margin: 0; + font-size: 1rem; + text-align: center; + list-style: none; + border-left: 1px solid; + border-right: 1px solid; + border-bottom: 1px solid; + border-color: var(--black); +} + +.dropdown-item { + width: 100%; + display: block; +} + +.dropdown-menu>li:hover { + background: var(--dropdown-item-hover); +} + +.dropdown-menu a { + width: 100%; + display: inline-block; +} + +@media (max-width: 1056px) { + + /* Navbar grid layout removed for modern-navbar compatibility */ + + .pagination-container-fluid { + justify-content: center; + } + + .pagination { + justify-content: flex-end !important; + } + + .pagination a:nth-last-child(2), + .pagination a:nth-last-child(3), + .pagination a:nth-child(2), + .pagination a:nth-child(3) { + display: none; + } + + html, + body { + text-align: center; + } +} + +@media (max-width: 1325px) { + /* ranking page - idea */ + /* .ranking { + grid-template-columns: 1fr 1fr !important; + } */ + + .by-user, + .by-stats, + .by-hoster { + grid-column: unset !important; + grid-row: unset !important; + } +} + + +@media (max-width: 905px) { + .ranking { + display: flex !important; + flex-flow: column; + justify-content: center !important; + } + + .ranking div { + display: flex; + flex-flow: column; + } +} + +.media-object { + text-align: center; + align-self: center; + overflow: hidden; + border: 1px solid #363636; + border-top: none; +} + +/* Pagination */ +.pagination-wrapper { + align-items: center; + display: flex; + background: var(--pagination-background); + border: 1px solid var(--pagination-border-color); + width: 100%; +} + + + +.pagination-container-fluid { + display: flex; + margin-left: 0; + width: 100%; + justify-content: center; + position: fixed; + bottom: 0; + left: 0; + right: 0; + pointer-events: none; /* Allow clicking through container if needed, but not on wrapper */ + z-index: 10; +} + +.bottom-pagination.fixed-pagination { + margin: 10px auto; + pointer-events: auto; + background: rgba(15, 15, 15, 0.75); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 8px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4); + padding: 2px; + max-width: fit-content; +} + +/* Ensure pagination container is hidden when requested */ +.pagination-container-fluid[style*="display: none"] { + display: none !important; +} + +.pagination { + display: flex; + justify-content: space-around; + width: 100%; + padding: 5px; + color: transparent; +} + +.pagination>a, +.pagination>span { + float: left; + padding: 0 5px; + min-width: 17px; + font-size: 14px; + font-weight: 500; + color: var(--accent); + text-align: center; +} + +.pagination>span { + border: 1px solid var(--accent) !important; +} + +.pagination :first-child { + margin-left: 0; +} + +.pagination>a { + text-decoration: none; + text-shadow: 0 1px var(--black); + background-clip: padding-box; + box-shadow: var(--pagination-anchor-box-shadow); + background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient)); +} + +.pagination>a:hover { + background-color: var(--pagination-background-hover); +} + +.pagination>a:active, +.pagination>span { + color: var(--accent); + text-shadow: 0 -1px var(--black); + box-shadow: var(--pagination-anchor-box-shadow); + box-shadow: inset 0px -2px 0px var(--accent); +} + +/* Content */ +@media (max-width: 999px) { + .container { + max-width: 960px; + } + + .dropdown-menu { + position: absolute; + left: 0; + } + + .dropdown-menu li { + white-space: normal; + word-wrap: anywhere; + } +} + +.content { + display: grid; + justify-content: space-around; + grid-template-columns: 0fr 1fr 0fr; + position: relative; + max-width: 100%; +} + +.container { + width: 100%; + margin-right: auto; + margin-left: auto; + z-index: 1; + position: relative; + padding-top: 5vh; + padding-bottom: 15px; +} + +.populateME { + position: relative; + left: 5px; +} + +.gapRight { + align-items: center; + display: flex; + +} + +.admin-lock-btn { + display: inline-flex; + align-items: center; + justify-content: center; + height: 20px; + min-width: 21px; + cursor: pointer; + background: #312c2c; + margin: 1px; + padding: 0 2px; + border: 1px outset #221d1d; + color: var(--white); + font-size: 11px; + text-transform: uppercase; + line-height: normal; + transition: all 0.1s ease; + vertical-align: middle; +} + +.iconset-svg { + width: 14px; + height: 14px; + fill: currentColor; + stroke: none; + display: block; +} + +#a_hall { + color: inherit; +} + +.gapLeft { + align-self: center; +} + +.imageDoor { + width: 25px; + height: 29px; + display: inline-flex; + padding: 2px; +} + +.imageDoor img { + background: var(--accent); + width: inherit; +} + +._204863 { + content: " "; + background: -webkit-linear-gradient(left, #131313, #4f4a4f); + text-align: left; + width: 100%; + color: var(--white); + height: auto; + display: grid; + grid-template-rows: 1fr; + grid-template-columns: auto 1fr auto; + line-height: 1.7; + border: 1px solid #363636; + z-index: 1; + position: relative; +} + +.location { + padding-left: 5px; +} + +/* Index page layout with sidebar */ +.index-layout-wrapper { + display: flex; + width: 100%; + gap: 0; +} + +.index-layout-wrapper .index-container { + flex: 1; + min-width: 0; +} + +@media (min-width: 1200px) { + /* Reserve space for the fixed sidebar so content doesn't flow behind it */ + .index-layout-wrapper { + padding-right: 300px; + } + + /* Collapse reserved space when sidebar is hidden */ + body.sidebar-right-hidden .index-layout-wrapper { + padding-right: 0; + } +} + +.index-container { + width: 100%; + padding: 5px; + margin: 0px 20px 0px 20px; +} + +@media (min-width: 361px) { + .embed-responsive-image { + bottom: unset !important; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } + + .embed-responsive-image { + bottom: unset !important; + } + +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } + + .embed-responsive-image { + bottom: unset !important; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } + + .embed-responsive-image { + bottom: unset !important; + } +} + +@media (min-width: 576px) { + .container { + max-width: 100%; + } +} + +@media (min-width: 768px) { + .container { + max-width: 100%; + } +} + +@media (min-width: 992px) { + .container:not(:has(.item-layout-container)) { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container:not(:has(.item-layout-container)) { + max-width: 1140px; + } +} + + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; +} + +.embed-responsive-16by9::before { + padding-top: 56.25%; +} + +.embed-responsive-4by3::before { + padding-top: 75%; +} + +.embed-responsive::before { + display: block; + content: ""; +} + +.embed-responsive .embed-responsive-item, +.embed-responsive video, +#ruffle-container { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; + object-fit: contain; + touch-action: none; + user-select: none; +} + +.ruffle-gesture-overlay { + position: absolute; + top: 0; + right: 0; + width: 5%; + height: 100%; + z-index: 10; + pointer-events: auto; +} + +@media (max-width: 768px) { + .ruffle-gesture-overlay { + display: none; /* No overlay on mobile to keep Flash interactive */ + } +} + +.embed-responsive-image { + position: absolute; + top: 0; + left: 0; + right: 0; + width: auto; + height: 100%; + box-sizing: content-box; + scrollbar-color: var(--scrollbar-color) transparent; + scrollbar-width: thin; + border-top: none; +} + +.previous-post, +.next-post { + display: flex; + height: 100%; + position: relative; +} + +#prev { + z-index: 1; + font-size: 30px; + opacity: 0.6; + text-shadow: 2px 2px 2px var(--black); + padding: 25px; +} + +#prev::before { + content: "\00BB"; +} + +#next::before { + content: "\00AB"; +} + +#next { + z-index: 1; + font-size: 30px; + opacity: 0.6; + text-shadow: 2px 2px 2px var(--black); + padding: 25px; +} + +#next:hover, +#prev:hover { + opacity: 1; +} + +.arrow-next { + height: 100%; + display: flex; + width: 100%; + align-items: center; + justify-content: start; + position: absolute; +} + +.arrow-prev { + height: 100%; + display: flex; + width: 100%; + align-items: center; + justify-content: flex-end; + position: absolute; +} + +/* image */ +#f0ck-image { + height: 100%; + width: 100%; + max-width: 100%; + object-fit: contain; +} + +a#elfe { + height: 100%; + width: 100%; + display: flex; + justify-content: center; +} + +/* metadata */ +/* Hall badge — compact primary + overflow pill */ +.hall-badge-wrap { + display: inline-flex; + align-items: center; + gap: 3px; + padding: 1.5px 5px; +} + +.hall-badge-primary { + color: var(--accent); + text-decoration: none; + font-size: inherit; +} + +.hall-badge-primary:hover { + text-decoration: underline; +} + +a.remove-from-hall { + color: rgba(255,255,255,0.4); + font-size: 0.8em; + text-decoration: none; + vertical-align: middle; + line-height: 1; + transition: color 0.15s; +} + +a.remove-from-hall:hover { + color: #e74c3c; +} + + +.hall-overflow-pill { + position: relative; + display: inline-flex; + align-items: center; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.15); + border-radius: 3px; + padding: 0 4px; + font-size: 0.78em; + color: rgba(255,255,255,0.55); + cursor: default; + line-height: 1.6; + user-select: none; +} + +.hall-overflow-pill:hover { + color: var(--white); + background: rgba(255,255,255,0.14); +} + +.hall-overflow-tooltip { + display: none; + position: absolute; + bottom: calc(100% + 5px); + left: 50%; + transform: translateX(-50%); + background: var(--dropdown-bg, #1e1e1e); + border: 1px solid rgba(255,255,255,0.15); + border-radius: 4px; + padding: 4px 0; + min-width: 120px; + max-width: 200px; + z-index: 200; + box-shadow: 0 4px 16px rgba(0,0,0,0.6); + white-space: nowrap; +} + +.hall-overflow-tooltip::after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + transform: translateX(-50%); + border: 5px solid transparent; + border-top-color: rgba(255,255,255,0.15); +} + +.hall-overflow-pill:hover .hall-overflow-tooltip { + display: block; +} + +.hall-overflow-tooltip a { + display: block; + padding: 3px 10px; + color: var(--white); + text-decoration: none; + font-size: 0.9em; + transition: background 0.1s; +} + +.hall-overflow-tooltip a:hover { + background: rgba(255,255,255,0.08); + color: var(--accent); +} + +#halls-container { + grid-template-columns: 1fr 1fr 1fr; +} + +@media (max-width: 768px) { + #halls-container { + grid-template-columns: 1fr 1fr; + } +} + +.badge:empty { + display: none; +} + +.metadata { + text-align: center; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + background-color: var(--metadata-bg); + border: 1px solid #363636; + border-top: none; + z-index: 1; + position: relative; +} + +#themeselector { + color: var(--white); + border: 0; + margin: 0; + padding: 0; + font-family: var(--font); + background-color: var(--metadata-bg); + text-align: center; +} + +.badge-dark, +#themeselector { + background-color: var(--badge-bg); + border: 1px solid var(--black); + /*margin: 5px;*/ +} + +.badge.badge-dark.image-source { + white-space: normal; + word-wrap: anywhere; +} + +@keyframes newTagGlow { + 0% { background-color: #302f2fa3; } + 100% { } +} + +.new-tag-glow { + animation: newTagGlow 1s ease-out; +} + +span#tags { + grid-column: 1/4; + display: inline-block; + vertical-align: middle; + padding-top: 2.5px; + padding-bottom: 2.5px; +} + +span#tags:empty { + display: none; +} + +.tags-inner { + display: contents; +} + +span#tags .tags-inner > span { + display: inline-block; + margin-top: 2.5px; + margin-bottom: 2.5px; +} + +span#tags:not(.tags-expanded) .tags-inner > span:nth-child(n+11) { + display: none; +} + +.show-tags-toggle { + font-size: 0.8em; + color: var(--accent); + margin-top: 5px; + cursor: pointer; + text-decoration: underline; + vertical-align: middle; +} + +.badge-success { + color: var(--white); + background-color: var(--badge-sfw); + padding-right: 5px; + padding-left: 5px; + padding-top: 1.5px; + padding-bottom: 1.5px; + border-radius: 3px; + text-shadow: 1px 1px var(--black); + text-transform: uppercase; +} + +.badge-danger { + color: var(--white); + background-color: var(--badge-nsfw); + padding-right: 5px; + padding-left: 5px; + padding-top: 1.5px; + padding-bottom: 1.5px; + border-radius: 3px; + text-shadow: 1px 1px var(--black); + text-transform: uppercase; +} + +.badge-light { + color: var(--white); + background-color: var(--badge-tag); + border-radius: 3px; + text-shadow: 1px 1px var(--black); + padding-right: 5px; + padding-left: 5px; + padding-top: 1.5px; + padding-bottom: 1.5px; +} + +.badge-greentext { + color: #789922; + text-shadow: inherit !important; + background-color: #252525; +} +.badge-greentext .tag-name { + color: #789922 !important; +} + +.badge-german { + background: linear-gradient(180deg, black 33.33%, red 33.33%, red 66.66%, yellow 66.66%) !important; +} + +.badge-dutch { + background: linear-gradient(180deg, #ad1c23 33.33%, white 33.33%, white 66.66%, #26468f 66.66%) !important; + color: #000 !important; +} + +.badge-void { + background: #000 !important; + color: #000 !important; +} + +.about { + padding: 10px; + color: white; + word-break: break-word; + background: var(--navigation-links-bg); + text-align: left; +} + +@media (max-width: 999px) { + .index-container { + padding-left: 5px; + padding-right: 5px; + } +} + +/* .pbwork box-shadow removed as per user request */ +/* .navbar.pbwork::after handles the top progress bar now */ + +@media(max-height: 820px) { + .embed-responsive-16by9::before { + padding-top: 50.25%; + } +} + +@media(max-height: 755px) { + .embed-responsive-16by9::before { + padding-top: 42.25%; + } +} + +@media(max-height: 640px) { + .embed-responsive-16by9::before { + padding-top: 36.25%; + } +} + +@media(max-height: 525px) { + .embed-responsive-16by9::before { + padding-top: 26.25%; + } +} + +@media(max-height: 414px) { + .embed-responsive-16by9::before { + padding-top: 16.25%; + } +} + +@media(max-height: 300px) { + .embed-responsive-16by9::before { + padding-top: 14.5%; + } +} + +@media(max-width: 580px) { + .embed-responsive-16by9::before { + padding-top: 100%; + } +} + +@media(max-width: 555px) { + .metadata { + grid-template-columns: 1fr 1fr; + } + + span#tags { + grid-column: 1/3; + } + + a#prev:focus, + a#next:focus { + background: var(--nav-bg); + border-radius: 350px; + opacity: 0.6; + transition: .2s ease-in-out; + } + + span#favs { + grid-column: 1/3 !important; + } + +} + +.post_source { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 20ch; + display: inline-block; +} + +audio::-webkit-media-controls { + filter: invert(100%) contrast(2) opacity(95%) hue-rotate(180deg); +} + +audio::-webkit-media-controls-timeline { + filter: contrast(0); +} + +div.about>div { + float: right; + max-width: 500px; + margin-left: 5px; +} + +div.about>div img { + max-width: 99%; + height: auto; +} + +div.about>div p { + font-size: 7pt; + margin-top: 0; +} + +/* loginform */ +body[type='login'] { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; +} + +.login-form { + display: flex; + flex-direction: column; + align-items: center; + padding: 20px 20px; + background: rgba(0, 0, 0, 0.8); + border-radius: 0; + border: 1px solid var(--accent); +} + +.login-form button[type="submit"] { + width: 100%; + margin: 18px 0 9px 0; + background: var(--bg); + border: 1px solid var(--black); + color: var(--white); + opacity: 0.6; + padding: 8px 20px; + border-radius: 0; + overflow: hidden; +} + +.login-form button[type="submit"]:hover { + cursor: pointer; + opacity: 1; +} + +.login-form input { + background: var(--bg); + border: 1px solid var(--black); + padding: 5px; + margin-bottom: 10px; + border-radius: 0; + width: max-content; + max-width: 300px; + +} + +/* Markdown Styles */ +.comment-content .greentext { + color: #789922; + font-family: monospace; + display: inline-block; +} + +.comment-content blockquote { + border-left: 3px solid var(--accent); + margin: 5px 0; + padding-left: 10px; + opacity: 0.8; +} + +.comment-content pre { + background: var(--gray); + padding: 15px; + border-radius: 4px; + overflow-x: auto; +} + +.comment-content code { + background: var(--gray); + font-family: monospace !important; +} + +.comment-content ul, +.comment-content ol { + padding-left: 20px; +} + +/* Dynamic page content (about / rules) */ +.dynamic-page-content blockquote { + color: #789922; + border-left: 3px solid #789922; + margin: 6px 0; + padding-left: 10px; + font-family: monospace; +} + +.dynamic-page-content blockquote p { + margin: 0; + color: #789922; +} + +.dynamic-page-content pre { + background: #222; + padding: 10px; + border-radius: 4px; + overflow-x: auto; +} + +.dynamic-page-content code { + background: #333; + padding: 2px 4px; + border-radius: 3px; + font-family: monospace; +} + +.dynamic-page-content ul, +.dynamic-page-content ol { + padding-left: 20px; +} + +.dynamic-page-content h1, +.dynamic-page-content h2, +.dynamic-page-content h3, +.dynamic-page-content h4 { + color: var(--white); +} + +.dynamic-page-content p { + margin-top: 0; + margin-bottom: 1.2em; +} + +.dynamic-page-content p:last-child { + margin-bottom: 0; +} + + +/* Emoji Picker */ +.input-actions { + position: relative; + /* Context for picker */ +} + +/* .emoji-picker { + position: relative; + width: 100%; + max-width: 85vw; + max-height: 300px; + background: var(--dropdown-bg); + border: 1px solid var(--black); + padding: 10px; + z-index: 1000; + overflow-y: auto; + display: flex; + flex-wrap: wrap; + gap: 8px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5); + border-radius: 8px; +} */ + +.emoji-picker { + position: relative; + width: 100%; + max-width: 100%; + max-height: 135px; + background: var(--dropdown-bg); + border: 1px solid var(--black); + padding: 5px; + z-index: 1000; + overflow-y: auto; + display: flex; + flex-wrap: wrap; + gap: 0px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5); + border-radius: 0px; + margin-top: 5px; +} + +.emoji-picker img { + width: 60px; + height: 60px; + object-fit: contain; + cursor: pointer; + transition: transform 0.1s, background 0.1s; + padding: 4px; + border-radius: 4px; +} + +.emoji-picker img:hover { + transform: scale(1.1); + background: rgba(255, 255, 255, 0.1); +} + +/* User Mentions */ +.mention { + text-decoration: none !important; + color: var(--accent); + font-weight: bold; +} + +.mention:hover { + text-decoration: underline !important; +} + +/* Mention Autocomplete Dropdown */ +.mention-suggestions { + position: fixed; + z-index: 10001; + background: #222; + border: 1px solid #444; + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0,0,0,0.5); + max-height: 200px; + overflow-y: auto; + font-family: var(--font); +} + +.mention-suggestion-item { + display: flex; + align-items: center; + padding: 8px 12px; + cursor: pointer; + border-bottom: 1px solid #333; +} + +.mention-suggestion-item:last-child { + border-bottom: none; +} + +.mention-suggestion-item.active, +.mention-suggestion-item:hover { + background: #333; +} + +.mention-suggestion-item img { + width: 24px; + height: 24px; + border-radius: 50%; + margin-right: 10px; + object-fit: cover; +} + +.mention-name { + font-weight: bold; + color: #eee; + margin-right: 8px; +} + +.mention-display { + font-size: 0.85em; + color: #888; +} + +.external-link-icon { + font-size: 0.65em; + margin-left: 4px; + vertical-align: middle; + position: relative; + top: -1px; +} + +/* ── Inline emoji autocomplete dropdown ── */ +.emoji-autocomplete { + position: fixed; /* set by JS via getBoundingClientRect */ + z-index: 9999; + background: var(--dropdown-bg, #1a1a1a); + border: 1px solid rgba(255,255,255,0.1); + border-radius: 6px; + box-shadow: 0 -4px 20px rgba(0,0,0,0.6); + flex-direction: column; + max-height: 220px; + overflow-y: auto; + padding: 4px; + gap: 2px; + overscroll-behavior: contain; +} + +.emoji-ac-item { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 8px; + border-radius: 4px; + cursor: pointer; + transition: background 0.1s; + user-select: none; +} + +.emoji-ac-item:hover, +.emoji-ac-item.active { + background: rgba(255,255,255,0.1); +} + +.emoji-ac-item img { + width: 24px; + height: 24px; + object-fit: contain; + flex-shrink: 0; +} + +.emoji-ac-item span { + font-size: 0.85em; + color: var(--text-muted, #aaa); + font-family: var(--font-mono, monospace); +} + +.emoji-trigger { + background: none; + border: none; + color: var(--white); + font-size: 20px; + cursor: pointer; + margin-right: 5px; + padding: 0 5px; + vertical-align: middle; + transition: text-shadow 0.2s; +} + +.emoji-trigger:hover { + text-shadow: 0 0 8px var(--accent); +} + +.spoiler-trigger { + background: black; + border: none; + color: var(--white); + font-size: 0.85em; + font-weight: bold; + cursor: pointer; + vertical-align: middle; +} + +.spoiler-trigger:hover { + text-decoration: underline; +} + + +/* visualizer */ +canvas { + position: absolute; + top: 1px; + /* This removes the ugly 1px line seen at the bottom when you hover over the audio element */ + left: 0; + bottom: 0; + right: 0; + height: 100%; + width: 100%; + pointer-events: none; +} + +/* tags */ +span#tags { + display: flex; + justify-content: flex-start; + align-items: center; + flex-wrap: wrap; + line-height: 1.4; + padding: 5px; +} + +input { + padding: 0; + margin: 0; + border: none; + color: #fff; + background-color: var(--badge-tag); +} + +/* Tag Autocomplete Dropdown */ +.tag-ac-wrapper { + position: relative; + display: inline-flex; + align-items: center; + vertical-align: middle; + padding-top: 0 !important; + padding-bottom: 0 !important; +} + +.tag-ac-wrapper form { + display: flex; + align-items: center; + height: 100%; +} + +.tag-ac-input { + font-size: inherit; + padding: 0 4px; + min-width: 80px; + background-color: var(--badge-tag); + color: #fff; + border: none; + outline: none; + height: 20px; + line-height: normal; + display: block; +} + +.tag-ac-input::placeholder { + color: rgba(255, 255, 255, 0.4); +} + +.tag-suggestions { + position: absolute; + bottom: 100%; + top: auto; + left: 0; + min-width: 220px; + max-width: 320px; + max-height: 260px; + overflow-y: auto; + background: var(--dropdown-bg, #1a1a1a); + border: 1px solid var(--black, #000); + border-radius: 6px; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6); + z-index: 1000; + margin-bottom: 8px; + animation: tagDropIn 0.15s ease-out; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-color, #555) transparent; +} + +@keyframes tagDropIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.tag-suggestion-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 12px; + min-height: 44px; + cursor: pointer; + transition: background 0.12s; + box-sizing: border-box; + -webkit-tap-highlight-color: transparent; + user-select: none; +} + +.tag-suggestion-item:not(:last-child) { + border-bottom: 1px solid rgba(255, 255, 255, 0.06); +} + +.tag-suggestion-item:hover, +.tag-suggestion-item:active, +.tag-suggestion-item.active { + background: rgba(255, 255, 255, 0.1); +} + +.tag-suggestion-name { + font-size: 14px; + color: var(--accent, #e0e0e0); + font-weight: 500; +} + +.tag-suggestion-meta { + font-size: 11px; + color: rgba(255, 255, 255, 0.35); + margin-left: 12px; + white-space: nowrap; +} + +@media (max-width: 555px) { + .tag-suggestions { + position: fixed; + left: 10px; + right: 10px; + top: auto; + bottom: 60px; + min-width: unset; + max-width: unset; + max-height: 40vh; + border-radius: 10px; + } + + .tag-suggestion-item { + min-height: 48px; + padding: 12px 16px; + } + + .tag-suggestion-name { + font-size: 16px; + } + + .tag-suggestion-meta { + font-size: 12px; + } +} + + +/* toasts */ +div#flash { + position: fixed; + bottom: -28px; + z-index: 999; + left: 0; + width: 100%; + text-align: center; + font-weight: bold; +} + +div#flash.error { + background-color: #fddfdf; + border-bottom: 2px solid #f1a899; + color: #5f3f3f; +} + +div#flash.success { + background-color: #4caf50; + border-bottom: 2px solid #006018; + color: #001c07; +} + +div#flash.warn { + background-color: #fffa90; + border-bottom: 2px solid #dad55e; + color: #777620; +} + +/* sfw/nsfw indexpage */ +div.posts>a>p { + height: 100%; + width: 100%; + position: absolute; + bottom: calc(0px - 12px); +} + +div.posts>a>p:before { + content: ""; + position: absolute; + right: 0; + bottom: -2px; + height: 13px; + width: 100%; +} + +div.posts>a[data-mode="sfw"]>p:before { +background: #000000; +background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(63, 196, 61, 0) 85%, rgba(63, 196, 61, 1) 100%); +} + +div.posts>a[data-mode="nsfw"]>p:before { +background: #000000; +background: linear-gradient(90deg,rgba(0,0,0,0) 0%,rgba(227,7,7,0) 85%,rgb(227, 7, 203) 100%); +} + +div.posts > a[data-mode="nsfl"] > p::before { + background: #000; + background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, rgba(227, 7, 7, 0) 85%, rgb(231, 3, 3) 100%); +} + +div.posts>a[data-mode="null"]>p:before { + background-color: #dcd512; + /* untagged */ +} + +div#footbar { + width: 100%; + text-align: center; + margin-top: -30px; + transition: .2s ease-in-out; + user-select: none; + color: transparent; +} + +/* avatar */ +img.avatar { + height: auto; + position: relative; + width: 25px; + margin: 1px; +/* left: -7px; */ + top: 0; + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} + +.ranking-table img.avatar { + left: 0; + border-radius: 3px; + display: block; + margin: 0 auto; +} + +/* log */ +div.logwrap { + height: auto; + text-align: left; +} + +div.logwrap>p { + line-height: 0; +} + +/* ranking page overhaul - clean tables */ +.ranking-page { + padding: 30px; + max-width: 1200px; + margin: 0 auto; +} + +.ranking-page h1, .ranking-page h3 { + color: var(--white); + letter-spacing: 1px; +} + +.ranking-wrapper { + display: flex; + gap: 40px; + align-items: flex-start; + margin-top: 30px; +} + +@media (max-width: 992px) { + .ranking-wrapper { + flex-direction: column; + gap: 30px; + } + + .section-big, .section-small { + width: 100%; + flex: none; + } +} + +.section-big { + flex: 2; +} + +.section-small { + flex: 1; + display: flex; + flex-direction: column; + gap: 30px; +} + +.clean-table { + width: 100%; + border-collapse: separate; + border-spacing: 0; + margin-top: 15px; + background: rgba(255, 255, 255, 0.02); + border-radius: 6px; + overflow: hidden; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.clean-table th { + background: rgba(255, 255, 255, 0.05); + color: var(--accent); + text-align: left; + padding: 15px; + font-size: 0.85em; + text-transform: uppercase; + letter-spacing: 1px; + font-weight: bold; + border-bottom: 2px solid var(--accent); +} + +.clean-table td { + padding: 12px 15px; + vertical-align: middle; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); + color: rgba(255, 255, 255, 0.9); + line-height: 1.6; +} + +.clean-table tbody tr:last-child td { + border-bottom: none; +} + +.clean-table tbody tr:hover { + background: rgba(var(--accent-rgb), 0.03); +} + +.rank-cell { + width: 60px; + font-weight: bold; + color: var(--accent) !important; + font-size: 1.1em; +} + +.avatar-cell { + width: 80px; +} + +.rank-avatar { + width: 36px; + height: 36px; + border-radius: 4px; + object-fit: cover; + border: 1px solid rgba(255, 255, 255, 0.1); + display: block; +} + +.user-cell a { + font-weight: bold; + text-decoration: none !important; +} + +.count-cell { + font-weight: bold; + text-align: right !important; +} + +.clean-table .count-col { + text-align: right !important; +} + +.stats-box-simple { + width: 100%; + background: rgba(255, 255, 255, 0.03); + padding: 20px; + border-radius: 12px; + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); +} + +.stats-box-simple h3 { + margin-top: 0; + font-size: 1.1em; + text-transform: uppercase; + letter-spacing: 2px; + border-bottom: 1px solid rgba(var(--accent-rgb), 0.3); + padding-bottom: 10px; + margin-bottom: 15px; +} + +.stats-table td:first-child, .f0cks-table td:first-child { + font-size: 0.9em; + opacity: 0.7; +} + +.stats-table td:last-child, .f0cks-table td:last-child { + text-align: right; + font-weight: bold; +} + +/* Premium Ranking Podium */ +.ranking-podium { + display: flex; + justify-content: center; + align-items: flex-end; + gap: 30px; + margin: 40px 0 60px; + padding: 20px; + background: radial-gradient(circle at center, rgba(var(--accent-rgb), 0.1) 0%, transparent 70%); +} + +.podium-item { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275); +} + +.podium-item:hover { + transform: translateY(-15px); +} + +.podium-avatar-wrapper { + position: relative; + margin-bottom: 20px; +} + +.podium-avatar { + width: 100px; + height: 100px; + border-radius: 50%; + object-fit: cover; + border: 4px solid var(--accent); + background: var(--black); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + display: block; + margin: 0 auto; +} + +.podium-crown { + position: absolute; + top: -25px; + left: 50%; + transform: translateX(-50%) rotate(-10deg); + font-size: 2em; + filter: drop-shadow(0 0 10px gold); + z-index: 2; +} + +.podium-rank-label { + position: absolute; + bottom: -10px; + left: 50%; + transform: translateX(-50%); + background: var(--accent); + color: var(--white); + padding: 4px 15px; + border-radius: 20px; + font-weight: 800; + font-size: 1.2em; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.3); + z-index: 1; +} + +.podium-info { + text-align: center; +} + +.podium-name { + font-weight: 800; + font-size: 1.4em; + color: var(--white); + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.5); + margin-bottom: 5px; + display: block; +} + +.podium-count { + font-size: 1em; + color: var(--accent); + font-weight: bold; + letter-spacing: 1px; +} + +/* Rank specific podium styles */ +.podium-item.rank-1 { order: 2; transform: scale(1.1); } +.podium-item.rank-1:hover { transform: scale(1.15) translateY(-15px); } +.podium-item.rank-2 { order: 1; } +.podium-item.rank-3 { order: 3; } + +.rank-1 .podium-avatar { border-color: #FFD700; width: 140px; height: 140px; box-shadow: 0 0 40px rgba(255, 215, 0, 0.3); } +.rank-2 .podium-avatar { border-color: #C0C0C0; width: 110px; height: 110px; box-shadow: 0 0 30px rgba(192, 192, 192, 0.2); } +.rank-3 .podium-avatar { border-color: #CD7F32; width: 100px; height: 100px; box-shadow: 0 0 20px rgba(205, 127, 50, 0.1); } + +.rank-1 .podium-rank-label { background: linear-gradient(135deg, #FFD700, #FFA500); color: #000; } +.rank-1 .podium-count { color: #FFD700; } + +.rank-2 .podium-rank-label { background: linear-gradient(135deg, #C0C0C0, #808080); } +.rank-2 .podium-count { color: #C0C0C0; } + +.rank-3 .podium-rank-label { background: linear-gradient(135deg, #CD7F32, #8B4513); } +.rank-3 .podium-count { color: #CD7F32; } + +@media (max-width: 992px) { + .ranking-podium { + flex-direction: column; + align-items: center; + gap: 50px; + padding-top: 40px; + } + .podium-item { + order: unset !important; + } + .podium-item.rank-1 { + transform: scale(1); + } +} + +/* Responsive clean table for Ranking */ +@media (max-width: 600px) { + .ranking-table tr { + display: grid; + grid-template-areas: + "rank avatar user" + "rank avatar count"; + grid-template-columns: 50px 70px 1fr; + grid-template-rows: auto auto; + gap: 0 15px; + padding: 15px; + align-items: center; + } + + .rank-cell { grid-area: rank; font-size: 1.4em !important; } + .avatar-cell { grid-area: avatar; padding: 0 !important; width: auto; } + .user-cell { grid-area: user; padding: 0 !important; align-self: end; } + .count-cell { + grid-area: count; + padding: 0 !important; + text-align: left !important; + align-self: start; + opacity: 0.7; + font-size: 0.9em; + } + .count-cell:after { content: " tags"; } + + .ranking-table thead { display: none; } + + .rank-avatar { + width: 50px; + height: 50px; + border-radius: 50%; + } + + .ranking-page { + padding: 15px; + } + + .ranking-wrapper { + margin-top: 15px; + } +} + +.admin-icon { + margin-right: 6px; + color: #ffeb3b; + font-size: 0.9em; +} + +.topf0ckers { + text-align: center; + padding: 20px 0; +} + +/* tags */ +#tags .badge>a:first-child { + color: inherit !important; + text-shadow: 1px 1px black; +} + +/* tooltips */ +[tooltip] { + position: relative; +} + +[tooltip]::before, +[tooltip]::after { + text-transform: none; + font-size: .9em; + line-height: 1; + user-select: none; + pointer-events: none; + position: absolute; + display: block; + visibility: hidden; + opacity: 0; + transition: + opacity 0.18s ease, + transform 0.18s ease, + visibility 0s linear 0.18s; +} + +[tooltip]::before { + content: ''; + border: 5px solid transparent; + z-index: 100001; +} + +[tooltip]::after { + content: attr(tooltip); + font-family: Helvetica, sans-serif; + text-align: center; + min-width: 3em; + max-width: 70em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 1ch 1.5ch; + border-radius: .3ch; + box-shadow: 0 1em 2em -.5em rgba(0, 0, 0, 0.35); + background: var(--tooltip-bg, #333); + color: #fff; + z-index: 100000; +} + +[tooltip]:hover::before, +[tooltip]:hover::after { + visibility: visible; + opacity: 1; + transition: + opacity 0.18s ease, + transform 0.18s ease, + visibility 0s linear 0s; +} + +[tooltip='']::before, +[tooltip='']::after { + display: none !important; + visibility: hidden !important; +} + +[tooltip]:not([flow])::before, +[tooltip][flow^="up"]::before { + bottom: 100%; + border-bottom-width: 0; + border-top-color: var(--tooltip-bg, #333); +} + +[tooltip]:not([flow])::after, +[tooltip][flow^="up"]::after { + bottom: calc(100% + 5px); +} + +[tooltip]:not([flow])::before, +[tooltip]:not([flow])::after, +[tooltip][flow^="up"]::before, +[tooltip][flow^="up"]::after { + left: 50%; + transform: translate(-50%, -.25em); +} + +[tooltip][flow^="down"]::before { + top: 100%; + border-top-width: 0; + border-bottom-color: var(--tooltip-bg, #333); +} + +[tooltip][flow^="down"]::after { + top: calc(100% + 5px); +} + +[tooltip][flow^="down"]::before, +[tooltip][flow^="down"]::after { + left: 50%; + transform: translate(-50%, .25em); +} + +[tooltip][flow^="left"]::before { + top: 50%; + border-right-width: 0; + border-left-color: var(--tooltip-bg, #333); + left: calc(0em - 5px); + transform: translate(-.5em, -50%); +} + +[tooltip][flow^="left"]::after { + top: 50%; + right: calc(100% + 5px); + transform: translate(-.5em, -50%); +} + +[tooltip][flow^="right"]::before { + top: 50%; + border-left-width: 0; + border-right-color: var(--tooltip-bg, #333); + right: calc(0em - 5px); + transform: translate(.5em, -50%); +} + +[tooltip][flow^="right"]::after { + top: 50%; + left: calc(100% + 5px); + transform: translate(.5em, -50%); +} + +[tooltip]:not([flow]):hover::before, +[tooltip]:not([flow]):hover::after, +[tooltip][flow^="up"]:hover::before, +[tooltip][flow^="up"]:hover::after, +[tooltip][flow^="down"]:hover::before, +[tooltip][flow^="down"]:hover::after { + opacity: 1; + transform: translate(-50%, 0); +} + +[tooltip][flow^="left"]:hover::before, +[tooltip][flow^="left"]:hover::after, +[tooltip][flow^="right"]:hover::before, +[tooltip][flow^="right"]:hover::after { + opacity: 1; + transform: translate(0, -50%); +} + +.timeago:hover { + cursor: pointer; +} + +/* buttons */ +svg.iconset, +i.iconset { + display: inline-flex; + align-items: center; + justify-content: center; + height: 20px; + width: 23px; + font-size: 13px; + cursor: pointer; + background: #312c2c; + margin: 1px; + padding: 1px; + border: 1px outset #221d1d; + position: relative; + right: 2px; +} + +svg.iconset:hover, +i.iconset:hover { + background: #7b7b77 !important; + transition: .2s ease; +} + +svg.iconset.active, +i.iconset.active { + color: var(--accent) !important; + border-style: inset; +} + + + +/* favorites */ +span#favs { + text-align: center; + margin-left: 0; + grid-column: 1/4; + padding: 5px; + margin: 5px; + line-height: 0; + background: var(--badge-bg); +} + +#favs>a img { + border: 2px solid var(--accent); +} + +/* audio thumbnails */ +/*a[data-mime^="audio"] { + background-color: var(--accent); +}*/ + +/* err page */ +._error_wrapper { + display: flex; + justify-content: center; +} + +.err { + display: grid; + grid-template-rows: auto; + grid-template-columns: auto; + margin: 50px; + background: var(--gray); + max-width: 100%; + width: 500px; +} + +._error_topbar { + border-bottom: inset 2px gray; + text-align: left; + padding-left: 10px; +} + +._error_content { + display: grid; + grid-template-columns: auto; +} + +._error_content img { + max-width: 100%; + width: 100%; + padding: 5px; + margin: 0; +} + +._error_message { + display: grid; + grid-template-columns: auto; + grid-template-rows: auto 1fr; + margin: 5px; + padding: 5px; + text-shadow: 1px 1px black; + text-align: center; +} + +.err code { + font-weight: bold; + text-align: center; + align-self: baseline; + font-size: 15px; +} + +/* Admin search css early test */ +.admin-search { + margin-top: 15px; + display: flex; + justify-content: center; +} + +.admin-search button { + background: var(--accent); + border: none; + color: var(--white); + padding: 15px; +} + +.admin-search button:hover { + transition: .2s all; + opacity: 0.7; + cursor: pointer; +} + +.admin-search input { + padding: 15px; + border: 1px solid var(--accent); +} + +table img { + border: 1px solid; +} + +.results { + padding: 5px; +} + +.mview_desc { + visibility: hidden; +} + +/* fix for mobile table view in search! */ +@media screen and (max-width: 650px) { + .results table.table th, + .results table.table td, + .admin-users-table th, + .admin-users-table td { + display: flex; + padding: 5px; + margin: 5px; + } + + .results table.table tr td:first-child, + .admin-users-table tr td:first-child { + float: right; + } + + .results table.table tbody td, + .admin-users-table tbody td { + display: block; + box-shadow: 0px 1px 0px var(--accent); + } + + .results table.table tbody td:before, + .admin-users-table tbody td:before { + content: attr(data-th); + display: block; + text-align: left; + } + + .results table thead, + .admin-users-table thead { + display: none; + } + + .results table.table td img { + position: relative; + width: 100%; + } + + .mview_desc { + visibility: visible; + } +} + +/* gapRight icon colors: white for all, pink for favorites */ +.gapRight i.iconset { + color: #fff !important; + fill: #fff !important; +} + +.gapRight #a_favo { + color: #ff6b9d !important; + fill: #ff6b9d !important; +} + +/* pin icon */ +.iconset#a_pin { + color: inherit; + fill: inherit; +} +.iconset#a_pin::before { + display: inline-block; + transition: transform 0.25s ease; +} +/* pinned: rotate just the glyph so tip points down – "pushed into the board" */ +.iconset#a_pin.active::before { + transform: rotate(-45deg); +} + +.iconset#subscribe-btn { + border-style: outset; +} + +.imageDoor:hover:after { + content: ""; + background-image: var(--hover-image); + height: 128px; + width: 128px; + display: inline-block; + position: absolute; + z-index: 999; +} + +/* Fix for navbar on mobile devices */ +@media (max-width: 768px) { + .navbar-nav { + display: grid !important; + grid-template-rows: 1fr 1fr; + grid-template-columns: 1fr 1fr 1fr; + grid-gap: 0.25em; + } +} + +/* Navbar Rework */ +.navbar-nav-guests { + width: 100%; + display: flex; + flex-direction: column; + padding: 5px 0 5px 0; + margin-bottom: 0; + list-style: none; + margin: 0; + margin-right: 0px; + margin-left: 0px; + align-self: center; + margin-left: .5rem; + margin-right: .5rem; +} + +.navbar-expand-lg .navbar-nav-guests { + flex-direction: row; +} + +ul.navbar-nav-guests li.nav-item { + margin-right: .5rem; +} + +.navbar-expand-lg .navbar-nav-guests .nav-link, +.pagination>a, +.pagination>span { + padding-right: .5rem; + padding-left: .5rem; +} + +@media (max-width: 768px) { + ul.navbar-nav-guests { + display: grid !important; + grid-template-columns: 1fr 1fr; + grid-gap: .25em; + margin: 0; + padding: 5px; + } + + ul.navbar-nav-guests li.nav-item { + margin-right: unset; + } + + .nav-link.user { + justify-content: left; + } +} + +.navigation-links-guest { + justify-content: center; + display: grid; + grid-template-columns: 1fr; + align-content: center; + width: 100%; +} + +/* Pagination Responsiveness */ +@media (max-width: 799px) { + + .navbar-expand-lg .navbar-nav-guests .nav-link, + .pagination>a, + .pagination>span { + padding-right: 2px; + padding-left: 2px; + } + + .pagination>a, + .pagination>span { + margin-right: 2px; + margin-left: 2px; + } + + .pagination { + justify-content: center !important; + } +} + +/* f0ckgle */ +.f0ckgle { + display: grid; + align-content: center; + height: auto; +} + +.search-title { + text-align: center; + font-size: xxx-large; +} + +.admin-search input { + padding: 15px; + border: 1px solid var(--accent); + min-width: 30%; +} + +.profile_head_avatar { + margin: 0; +} + +.profile_head_username { + font-weight: bold; + padding-left: 5px; +} + +.layersoffear { + display: grid; +} + +.user_content_wrapper { + display: grid; + grid-template-columns: 1fr 1fr; + grid-column-gap: 5px; + padding: 5px; +} + +.f0cks h5, +.favs h5 { + background: var(--dropdown-bg); +} + +.uploads-header, +.favs-header { + display: grid; + grid-template-columns: 1fr auto; + background: var(--img-border-color); + padding: 0px 5px 0px 5px; +} + +div.user-uploads div.posts { + padding: 5px; + background: var(--dropdown-bg); +} + +div.favs div.posts { + padding: 5px; + background: var(--dropdown-bg); +} + + + +#bg { + position: fixed; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 100%; + height: 100%; + -webkit-filter: blur(100px); + filter: blur(100px); + transform: translate3d(0, 0, 0); + z-index: 0; + transition: opacity 1.5s cubic-bezier(0.4, 0, 0.2, 1); + opacity: 0.2; + z-index: -2; +} + +button#togglebg { + display: inline; + cursor: pointer; + background: transparent; + border: 0; +} + +@keyframes fadeInFX { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes fadeOutFX { + 0% { + opacity: 1; + } + + 100% { + opacity: 0; + } +} + + +@keyframes fadeIn { + 0% { + opacity: 0; + } + + 100% { + opacity: 0.2; + } +} + +@keyframes fadeOut { + 0% { + opacity: 0.2; + } + + 100% { + opacity: 0; + } +} + +.fader-in { + opacity: 0.4 !important; +} + +.fader-out { + opacity: 0 !important; +} + +.fast-fade { + transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important; +} + +.grid-transition { + opacity: 0; +} + +.grid-transition.show { + opacity: 1 !important; + transition: opacity 0.3s ease-in-out; +} + +/* Fix double transition: disable individual item entry animation when the whole grid is fading in, except for pinning animations */ +.posts.grid-transition a:not(.anim-boxshadow) { + animation: none; +} + +/* Make individual item entry subtler and faster for infinite scroll */ +@keyframes fadeInFX { + 0% { + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +img#f0ck-image, +div.imageDoor, +div.posts a, +video { + animation: 0.4s ease-out 0s 1 fadeInFX; +} + +.settings { + display: grid; + justify-content: center; +} + +/* Touch-friendly font-size slider */ +#font_size_slider { + -webkit-appearance: none; + appearance: none; + height: 6px; + border-radius: 3px; + background: var(--nav-border-color, #555); + outline: none; + touch-action: manipulation; /* prevent scroll stealing */ + cursor: pointer; +} +#font_size_slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 28px; + height: 28px; + border-radius: 50%; + background: var(--accent, #e91e63); + cursor: grab; + border: 3px solid var(--white, #fff); + box-shadow: 0 2px 6px rgba(0,0,0,.45); + transition: transform .1s; +} +#font_size_slider::-webkit-slider-thumb:active { transform: scale(1.2); cursor: grabbing; } +#font_size_slider::-moz-range-thumb { + width: 28px; + height: 28px; + border-radius: 50%; + background: var(--accent, #e91e63); + cursor: grab; + border: 3px solid var(--white, #fff); + box-shadow: 0 2px 6px rgba(0,0,0,.45); + transition: transform .1s; +} +#font_size_slider::-moz-range-thumb:active { transform: scale(1.2); cursor: grabbing; } + +input[name="i_avatar"] { + text-align: center; + width: 50px; + padding: 10px; +} + +input#s_avatar { + padding: 5px; + border: 1px solid var(--black); + border-radius: 0; + background-image: linear-gradient(to bottom, var(--nav-link-background-linear-gradient)); + box-shadow: var(--nav-link-box-shadow); + cursor: pointer; +} + +#s_avatar:hover { + background: #ffffff0f; +} + +.theforceofthree { + display: grid; + grid-template-columns: 0.4fr 1fr 0.4fr; +} + +.upload { + padding: 10px; +} + +/* Infinite scroll loading indicator */ +.loading-spinner { + display: inline-block; + font-size: 12px; + color: var(--footbar-color); + animation: pulse 1s ease-in-out infinite; +} + +@keyframes pulse { + + 0%, + 100% { + opacity: 0.1; + } + + 50% { + opacity: 1; + } +} + + + +/* Modern Tags Layout */ +.tags-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 20px; + padding: 20px 0; +} + +.tag-card { + display: flex; + flex-direction: column; + background: var(--badge-bg, #171717); + border-radius: 0; + overflow: hidden; + text-decoration: none !important; + transition: transform 0.2s, box-shadow 0.2s; + border: 1px solid var(--nav-border-color, rgba(255, 255, 255, 0.1)); + position: relative; +} + +.tag-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4); + background: var(--dropdown-bg, #232323); + border-color: var(--accent, #9f0); +} + +.tag-card-image { + width: 100%; + height: 100px; + overflow: hidden; + position: relative; + background: #000; +} + +.tag-card-image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.5s; + opacity: 0.8; +} + +.tag-card:hover .tag-card-image img { + transform: scale(1.1); + opacity: 1; +} + +.tag-card-image-title { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.1em; + font-weight: 700; + font-family: var(--font, monospace); + color: #fff; + text-align: center; + padding: 0 10px; + letter-spacing: 0.04em; + text-transform: uppercase; + text-shadow: + 0 0 8px rgba(0,0,0,0.9), + 0 1px 3px rgba(0,0,0,0.8), + 0 0 24px rgba(0,0,0,0.7); + background: linear-gradient( + to bottom, + transparent 0%, + transparent 35%, + rgba(0,0,0,0.4) 100% + ); + transition: letter-spacing 0.25s, font-size 0.25s; + pointer-events: none; +} + +.tag-card:hover .tag-card-image-title { + font-size: 1.18em; + letter-spacing: 0.1em; +} + +/* Hall rating badge */ +.hall-rating-badge { + position: absolute; + bottom: 6px; + left: 8px; + font-size: 0.65em; + font-weight: 800; + font-family: var(--font, monospace); + letter-spacing: 0.08em; + padding: 2px 6px; + border-radius: 2px; + pointer-events: none; + text-transform: uppercase; + line-height: 1.4; + z-index: 2; +} + +.hall-rating-sfw { + background: rgba(0, 180, 80, 0.85); + color: #fff; + border: 1px solid rgba(0, 255, 100, 0.3); +} + +.hall-rating-nsfw { + background: rgba(220, 40, 60, 0.85); + color: #fff; + border: 1px solid rgba(255, 60, 80, 0.3); +} + +.hall-rating-nsfl { + background: rgba(80, 0, 120, 0.9); + color: #e0b0ff; + border: 1px solid rgba(160, 60, 200, 0.4); +} + +.tag-card-content { + padding: 15px; + display: flex; + flex-direction: column; + gap: 5px; +} + +.tag-name { + color: var(--white, #fff); + font-weight: bold; + font-size: 1.1em; + font-family: var(--font, monospace); +} + +.tag-count { + color: #888; + font-size: 0.9em; +} + +.tag-description { + display: -webkit-box; + -webkit-line-clamp: 3; + line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; + font-size: 0.85em; + color: #aaa; + line-height: 1.4; + word-break: break-word; +} + +/* Search Overlay */ +#search-overlay, +#excluded-tags-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.9); + backdrop-filter: blur(5px); + z-index: 10000; + display: none; + align-items: center; + justify-content: center; + padding: 20px; + opacity: 0; + transition: opacity 0.2s ease-in-out; +} + +#search-overlay.visible, +#excluded-tags-overlay.visible { + opacity: 1; +} + +#search-input { + background: transparent; + border: none; + border-bottom: 2px solid var(--accent); + color: var(--white); + font-size: 3rem; + width: 100%; + max-width: 800px; + text-align: center; + outline: none; + font-family: var(--font); + padding: 10px; +} + +#search-input::placeholder { + color: #555; + text-transform: uppercase; +} + +#search-close, +#excluded-tags-close { + position: absolute; + top: 20px; + right: 30px; + color: var(--white); + font-size: 2rem; + cursor: pointer; + font-family: sans-serif; + opacity: 0.7; + transition: opacity 0.2s; +} + +#search-close:hover, +#excluded-tags-close:hover { + opacity: 1; +} + +/* Delete Tag Modal */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.85); + backdrop-filter: blur(5px); + z-index: 10001; + display: flex; + align-items: center; + justify-content: center; +} + +.modal-content { + background: var(--dropdown-bg); + border: 1px solid var(--nav-border-color); + padding: 30px; + border-radius: 0; + text-align: center; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); + min-width: 300px; +} + +.modal-content h3 { + margin-top: 0; + color: var(--white); +} + +.modal-content p { + color: #ccc; + margin: 20px 0; +} + +.modal-actions { + display: flex; + justify-content: center; + gap: 15px; +} + +.modal-actions button { + padding: 10px 20px; + border: none; + border-radius: 0; + cursor: pointer; + font-weight: bold; + font-family: var(--font); +} + +.btn-danger { + background: #e74c3c; + color: white; +} + +.btn-danger:hover { + background: #c0392b; +} + +.btn-secondary { + background: #555; + color: white; +} + +.btn-secondary:hover { + background: #666; +} + +/* Nav User Dropdown */ +.nav-user-dropdown { + position: relative; + margin-left: 5px; +} + +.nav-mime-dropdown { + position: relative; + display: inline-block; +} + +.nav-mime-btn { + background: none; + border: 1px solid rgba(255, 255, 255, 0.2); + color: var(--white); + padding: 5px 5px; + font-size: 0.8em; + font-weight: bold; + cursor: pointer; + border-radius: 3px; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 5px; + min-width: 50px; + justify-content: center; + opacity: 0.8; +} + +.nav-mime-btn:hover { + background: rgba(255, 255, 255, 0.1); + opacity: 1; +} + +.nav-mime-btn::after { + content: ''; + position: absolute; + top: 100%; + left: 0; + width: 100%; + height: 12px; +} + +.nav-mime-menu { + display: none; + position: absolute; + top: calc(100% + 5px); + left: 0; + background: var(--dropdown-bg); + min-width: 150px; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.4); + z-index: 1000; + border-radius: 0; + border: 1px solid var(--nav-border-color); + overflow: hidden; +} + + +.nav-mime-dropdown:hover .nav-mime-menu, +.nav-mime-dropdown.stay-open .nav-mime-menu { + display: block; +} + +.nav-mime-item { + color: #ccc; + padding: 10px 15px; + display: flex; + align-items: center; + gap: 10px; + font-size: 0.9em; + transition: all 0.2s; + cursor: pointer; + user-select: none; +} + +.nav-mime-item:hover { + background: rgba(255, 255, 255, 0.05); + color: var(--accent); +} + +.nav-mime-item input[type="checkbox"] { + accent-color: var(--black); + cursor: pointer; +} + +.nav-mime-divider { + height: 1px; + background: rgba(255, 255, 255, 0.1); + margin: 5px 0; +} + +.nav-mime-clear { + background: transparent; + border: none; + color: #888; + padding: 8px 15px; + width: 100%; + text-align: left; + font-size: 0.8em; + cursor: pointer; + transition: all 0.2s; +} + +.nav-mime-clear:hover { + color: #fff; + background: rgba(255, 255, 255, 0.05); +} + +.nav-mime-menu.visible { + display: block; + animation: fadeInDown 0.2s ease-out; +} + +.nav-mime-menu a { + color: #ccc; + padding: 10px 15px; + text-decoration: none; + display: flex; + align-items: center; + gap: 10px; + font-size: 0.9em; + transition: all 0.2s; +} + +.nav-mime-menu a:hover { + background: rgba(255, 255, 255, 0.05); + color: var(--accent); +} + +.nav-mime-menu a svg { + opacity: 0.7; +} + +.nav-mime-menu a:hover svg { + opacity: 1; +} + +@keyframes fadeInDown { + from { opacity: 0; transform: translateY(-10px); } + to { opacity: 1; transform: translateY(0); } +} + +.nav-user-btn { + background: none; + border: 1px solid rgba(255, 255, 255, 0.2); + color: var(--white); + padding: 5px 5px; + font-size: 0.8em; + font-weight: bold; + cursor: pointer; + border-radius: 3px; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 5px; + min-width: 50px; + justify-content: center; + opacity: 0.8; +} + + +.nav-user-btn:hover { + background: rgba(255, 255, 255, 0.1); +} + +/* Avatar-style button variant */ +.nav-avatar-btn { + background: none; + border: 1px solid transparent; + min-width: unset; + border-radius: 3px; + line-height: 1; + opacity: 1; + display: flex; + align-items: center; + gap: 6px; + transition: border-color 0.2s, background 0.2s; +} + +.nav-avatar-btn:hover { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.15); + transform: none; + box-shadow: none; +} + +.nav-avatar-btn.is-active { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.15); +} + +.nav-avatar-btn.is-active .nav-avatar-caret { + transform: rotate(180deg); +} + +.nav-avatar-caret { + transition: transform 0.2s ease; +} + +.nav-avatar-img { + display: block; + width: 22px; + height: 22px; + object-fit: cover; + flex-shrink: 0; +} + +.nav-avatar-name { + font-size: 14px; + color: var(--white); + white-space: nowrap; + max-width: 140px; + text-overflow: ellipsis; +} + +.nav-avatar-caret { + font-size: 0.7em; + opacity: 0.6; + flex-shrink: 0; +} + + + + + +.nav-user-btn::after { + content: ''; + position: absolute; + top: 100%; + left: 0; + width: 100%; + height: 10px; +} + +.nav-user-menu { + display: none; + position: absolute; + top: calc(100% + 5px); + left: auto; + right: 0; + min-width: 100%; /* at least as wide as the avatar button */ + background: var(--dropdown-bg); + border: 1px solid var(--nav-border-color); + border-radius: 0; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); + z-index: 10000; + overflow: hidden; +} + + +.nav-user-menu.show { + display: block; +} + +.nav-user-menu a { + display: block; + padding: 10px 15px; + color: var(--white); + text-decoration: none; + transition: background 0.2s; +} + +.nav-user-menu a:hover { + background: rgba(255, 255, 255, 0.1); +} + +.nav-user-divider { + height: 1px; + background: var(--nav-border-color); + margin: 5px 0; +} + +/* nav-links base: horizontal flex row on desktop */ +.nav-links { + display: flex; + align-items: center; + gap: 10px; + padding-left: 5px; +} + +.nav-links a { + color: var(--white); + text-decoration: none; + font-size: 14px; + opacity: 0.8; + transition: opacity 0.2s; +} + +.nav-links a:hover { + opacity: 1; +} + +.nav-links svg { + vertical-align: middle; +} + + +/* Login Modal */ +#login-modal, +#register-modal, +#shortcuts-modal { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: rgba(0, 0, 0, 0.85); + /* Semi-transparent black backdrop */ + z-index: 99999; + display: flex; + /* Hidden by default via inline style usually, or JS toggles class */ + justify-content: center; + align-items: center; + padding: 20px; + backdrop-filter: blur(5px); +} + +.flash-success { + color: var(--accent); + background: var(--bg); + border: 1px solid var(--accent); + padding: 10px; + margin-bottom: 15px; + font-size: 0.9em; + text-align: center; +} + +.flash-error { + color: #ff4444; + background: var(--bg); + border: 1px solid #ff4444; + padding: 10px; + margin-bottom: 15px; + font-size: 0.9em; + text-align: center; +} + +#flash-container { + position: fixed; + top: 70px; + left: 50%; + transform: translateX(-50%); + width: 90%; + max-width: 600px; + z-index: 10001; + display: flex; + flex-direction: column; + gap: 10px; + pointer-events: none; +} + +.flash-message { + background: var(--bg); + padding: 15px 20px; + border-radius: 0; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.6); + animation: slideDown 0.3s ease-out; + pointer-events: auto; + font-size: 0.95em; + border: 1px solid var(--accent); +} + +@keyframes slideDown { + from { transform: translateY(-20px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +.flash-message.flash-success { + border-color: var(--accent); + color: var(--accent); +} + +.flash-message.flash-error { + border-color: #ff4444; + color: #ff4444; +} + +.flash-message.flash-info { + border-color: #f0a030; + color: #f0a030; +} + +.login-modal-content { + background: var(--bg); + border: 1px solid var(--accent); + border-radius: 0; + box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); + max-width: 400px; + width: 100%; + position: relative; + text-align: center; +} + +.login-modal-content { + max-height: 90vh; + overflow-y: auto; + display: flex; + flex-direction: column; + scrollbar-width: thin; + scrollbar-color: var(--accent) transparent; +} + +.login-modal-content .login-form { + display: flex; + flex-direction: column; + gap: 15px; + background: transparent; + /* Override default login-form bg if needed */ +} + +.login-modal-content .login-image { + max-width: 100%; + margin-bottom: 1rem; + border-radius: 0; +} + +.login-notice { + background: rgba(var(--accent-rgb), 0.1); + border: 1px solid var(--accent); + padding: 12px; + margin-bottom: 15px; + font-size: 0.85em; + color: var(--white); + text-align: left; + line-height: 1.5; + border-radius: 2px; +} + +.notification-dot { + width: 10px; + height: 10px; + background-color: #39ff14; + /* Matrix neon green */ + border-radius: 50%; + display: inline-block; + margin-left: 5px; + box-shadow: 0 0 5px #39ff14; +} + +.login-modal-content input[type="text"], +.login-modal-content input[type="email"], +.login-modal-content input[type="password"] { + width: 100%; + padding: 10px; + background: rgba(255, 255, 255, 0.05); + border: 1px solid var(--nav-border-color); + color: var(--white); + font-family: var(--font); +} + +.login-modal-content input:focus { + border-color: var(--accent); + outline: none; +} + +.login-modal-content button[type="submit"] { + background: var(--accent); + color: var(--black); + border: none; + padding: 10px; + font-weight: bold; + cursor: pointer; + font-family: var(--font); + margin-top: 10px; +} + +.login-modal-content button[type="submit"]:hover { + filter: brightness(1.1); +} + +#login-modal-close, +#register-modal-close, +#shortcuts-modal-close { + position: absolute; + top: 10px; + right: 15px; + background: none; + border: none; + color: var(--white); + font-size: 1.5rem; + cursor: pointer; + opacity: 0.7; +} + +#login-modal-close:hover, +#register-modal-close:hover { + opacity: 1; + color: var(--accent); +} + +/* Comments System */ +/* Primary definition moved up to line 1082 to avoid overrides */ +#comments-container { + color: var(--white); + font-family: var(--font); + background: rgba(0,0,0,0.2); +} + +/* Definitions consolidated at line 1099 */ + +.comment { + padding: 10px; + background: rgba(0, 0, 0, 0.2); + display: flex; + gap: 15px; + margin-bottom: 0px; +} + +.comment.deleted .comment-content { + color: #888; + font-style: italic; +} + +.comment-avatar img { + width: 40px; + height: 40px; +} + +.comment-body { + flex: 1; + min-width: 0; +} + +/* .comment-meta styles moved to earlier consolidation block */ + +.comment-author { + font-weight: bold; + color: var(--white); + margin-right: 3px; + align-self: baseline; +} + +.comment-permalink { + color: #666; + text-decoration: none; +} + +.comment-content { + line-height: 1.4; + word-break: break-word; + overflow: auto; + white-space: pre-wrap; +} + +/* .reply-btn consolidated earlier */ + +/* .comment-children consolidated earlier */ + +.comment-input textarea { + width: 100%; + min-height: 80px; + background: #000000a1; + border: 1px solid var(--gray); + color: var(--white); + padding: 10px; + border-radius: 4px; + font-family: inherit; + resize: vertical; +} + +.input-actions { + margin-top: 5px; + text-align: right; +} + +.submit-comment, +.cancel-reply { + background: var(--accent); + color: var(--black); + border: none; + padding: 6px 12px; + border-radius: 3px; + cursor: pointer; + font-weight: bold; + opacity: 0.8; +} + +.submit-comment:hover, +.cancel-reply:hover { + opacity: 1; +} + +.cancel-reply { + background: #666; + color: white; + margin-left: 5px; +} + +.comment-input.reply-input { + margin-top: 10px; + margin-bottom: 10px; +} + +.loading, +.error { + text-align: center; + padding: 20px; + color: #888; +} + +.login-placeholder { + padding: 15px; + text-align: center; + background: rgba(0, 0, 0, 0.1); + margin-bottom: 5px; +} + +/* Markdown Styles */ +.comment-content .greentext { + color: #789922; + font-family: monospace; + display: inline-block; +} + +.comment-content blockquote { + border-left: 3px solid var(--accent); + margin: 5px 0; + padding-left: 10px; + opacity: 0.8; +} + +.comment-content ul, +.comment-content ol { + padding-left: 20px; +} + +.comment-content a { + color: #1fb2b0; +} + +.comment-content a:hover { + text-decoration: underline; +} + +/* YouTube embed wrapper */ +.yt-embed-wrap { + display: block; + position: relative; + width: 100%; + max-width: 480px; + margin: 8px 0; + border-radius: 4px; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0,0,0,0.5); +} + +.yt-embed-wrap::before { + content: ''; + display: block; + padding-top: 56.25%; /* 16:9 */ +} + +.yt-embed-wrap iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: none; +} + +/* Same-site video embed wrapper */ +.video-embed-wrap { + display: block; + position: relative; + width: 100%; + max-width: 480px; + margin: 8px 0; + border-radius: 4px; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0,0,0,0.5); + background: #000; +} + +.video-embed-wrap video { + width: 100%; + display: block; + max-height: 500px; + background: #000; +} + +/* Same-site audio embed wrapper */ +.audio-embed-wrap { + display: block; + width: 100%; + max-width: 480px; + margin: 8px 0; + border-radius: 4px; + overflow: hidden; + box-shadow: 0 2px 12px rgba(0,0,0,0.5); + background: rgba(0,0,0,0.3); + padding: 6px 8px; +} + +.audio-embed-wrap audio { + width: 100%; + display: block; + outline: none; +} + +@media (max-width: 600px) { + .yt-embed-wrap, + .video-embed-wrap, + .audio-embed-wrap { + max-width: 100%; + } +} + +#comment-sor, +#lock-thread-btn { + background: none; + border: none; +} + +/* Admin buttons consolidated earlier in the file */ + +/* Edit mode */ +.edit-textarea { + width: 100%; + min-height: 80px; + background: var(--bg); + border: 1px solid var(--accent); + color: var(--white); + padding: 10px; + border-radius: 4px; + font-family: inherit; + resize: vertical; + margin-bottom: 8px; +} + +.edit-actions { + display: flex; + gap: 8px; +} + +.save-edit-btn, +.cancel-edit-btn { + padding: 6px 12px; + border: none; + border-radius: 3px; + cursor: pointer; + font-weight: bold; +} + +.save-edit-btn { + background: var(--accent); + color: var(--black); +} + +.cancel-edit-btn { + background: #666; + color: white; +} + +/* Pinned comments */ +.comment.pinned { + background: rgba(var(--accent-rgb, 153, 255, 0), 0.1); + border-left: 3px solid var(--accent); +} + +.pinned-badge { + background: var(--accent); + color: var(--black); + padding: 2px 6px; + border-radius: 3px; + font-size: 0.75em; + font-weight: bold; + margin-right: 8px; +} + +/* .admin-pin-btn consolidated earlier */ + +/* Two-level comment replies */ +.comment-replies { + margin-left: 5px; + border-left: 2px solid var(--badge-tag); + padding-left: 5px; + margin-top: 0px; +} + +.comment.reply { + background: rgba(0, 0, 0, 0.1); +} + +.reply-mention { + color: var(--accent); + font-weight: bold; + margin-right: 4px; +} + +.submanage { + text-align: center; + border-top: 1px solid black; +} + +/* AJAX Loading Bar */ +.navbar.pbwork::after { + content: ""; + position: absolute; + top: 0; + left: 0; + height: 3px; + background: var(--accent); + z-index: 2000; + animation: pbwork-anim 2s infinite linear; + width: 100%; + transform-origin: 0% 50%; +} + +@keyframes pbwork-anim { + 0% { + transform: scaleX(0); + } + + 50% { + transform: scaleX(0.5); + } + + 100% { + transform: scaleX(1); + } +} + +/* Session Page Grid Layout */ +.session-grid { + display: grid !important; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 20px; + padding: 20px; +} + +.session-card { + background: var(--nav-bg); + border: 1px solid transparent; + border-radius: 4px; + display: flex; + flex-direction: column; + overflow: hidden; + transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); +} + +.session-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); + border-color: var(--accent); +} + +.session-card.current { + border: 1px solid #2ecc71; + box-shadow: 0 0 10px rgba(46, 204, 113, 0.2); +} + +.session-card.current .session-user { + color: #2ecc71; +} + +.session-header { + background: rgba(0, 0, 0, 0.2); + padding: 10px 15px; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.session-user { + font-weight: bold; + font-size: 1.1em; + color: var(--accent); +} + +.session-badges { + display: flex; + align-items: center; + gap: 8px; +} + +.session-id { + font-family: monospace; + color: #666; + font-size: 0.9em; +} + +.session-body { + padding: 15px; + flex-grow: 1; +} + +.session-info { + margin-bottom: 8px; + font-size: 0.9em; + line-height: 1.4; + display: flex; + flex-direction: column; +} + +.session-info:last-child { + margin-bottom: 0; +} + +.session-info .label { + font-weight: bold; + color: #888; + font-size: 0.8em; + text-transform: uppercase; + letter-spacing: 0.5px; + margin-bottom: 2px; +} + +.session-info .value { + color: var(--white); + word-break: break-all; +} + +.browser-info { + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + font-family: monospace; + font-size: 0.85em; + opacity: 0.9; +} + +.session-footer { + padding: 10px 15px; + background: rgba(0, 0, 0, 0.1); + display: flex; + gap: 10px; + justify-content: flex-end; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +.badge-kmsi { + background: #333; + color: #ccc; + border: 1px solid #555; +} + +span.badge.badge-current { + font-weight: bold; + font-size: 12.5px; + margin: 5px; + padding: 2px; + text-transform: uppercase; +} + +.badge-current { + background: var(--accent); + color: var(--black); +} + +.session-page-title { + grid-column: 1 / -1; + margin: 0; + padding-bottom: 10px; + border-bottom: 2px solid var(--accent); + color: var(--white); + font-size: 1.5em; + text-transform: uppercase; + letter-spacing: 1px; +} + +.session-stats { + font-size: 0.6em; + color: var(--accent); + vertical-align: middle; + margin-left: 10px; +} + +.session-delete { + color: #888; + text-decoration: none; + font-size: 1.2em; + margin-left: auto; + transition: color 0.2s; + padding: 0 5px; +} + +.session-delete:hover { + color: #e74c3c; + text-decoration: none; +} + +/* Avatar Upload Styles */ +.avatar-settings-wrapper { + display: grid; + grid-template-columns: auto 1fr; + gap: 30px; + background: var(--metadata-bg); + border-radius: 8px; + margin-bottom: 20px; +} + +@media (max-width: 900px) { + .avatar-settings-wrapper { + grid-template-columns: 1fr; + } +} + +.avatar-preview-container { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + margin-left: 10px; +} + +.avatar-preview-label { + font-size: 0.85em; + color: #888; + text-transform: uppercase; + letter-spacing: 1px; +} + +.avatar-preview-img { + width: 128px; + height: 128px; + object-fit: cover; + border: 3px solid var(--accent); + transition: transform 0.3s ease, box-shadow 0.3s ease; +} + +.avatar-placeholder { + display: flex; + align-items: center; + justify-content: center; + background: var(--gray); + color: #666; + font-size: 3em; + font-weight: bold; +} + +.avatar-upload-section, +.avatar-item-section { + display: flex; + flex-direction: column; + gap: 12px; +} + +.avatar-upload-section h4, +.avatar-item-section h4 { + margin: 0; + color: var(--white); + font-size: 1em; + border-bottom: 1px solid var(--accent); + padding-bottom: 5px; +} + +.avatar-hint { + margin: 0; + font-size: 0.8em; + color: #666; +} + +.avatar-upload-wrapper { + display: flex; + align-items: center; + gap: 12px; + flex-wrap: wrap; +} + +.avatar-filename { + color: #888; + font-size: 0.9em; + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.avatar-progress-wrapper { + display: flex; + align-items: center; + gap: 10px; +} + +.avatar-progress-bar { + flex: 1; + height: 8px; + background: var(--gray); + border-radius: 4px; + overflow: hidden; + position: relative; +} + +.avatar-progress-fill { + height: 100%; + width: 0%; + background: linear-gradient(90deg, var(--accent), #4ade80); + border-radius: 4px; + transition: width 0.15s ease; + position: relative; +} + +.avatar-progress-fill::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, + transparent, + rgba(255, 255, 255, 0.3), + transparent); + animation: shimmer 1.5s infinite; +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + + 100% { + transform: translateX(100%); + } +} + +.avatar-progress-text { + font-size: 0.85em; + color: var(--accent); + min-width: 40px; + text-align: right; + font-family: monospace; +} + +.avatar-upload-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.avatar-status { + font-size: 0.9em; + padding: 5px 0; + min-height: 1.5em; +} + +.avatar-status.success { + color: #4ade80; +} + +.avatar-status.error { + color: #ef4444; +} + +.avatar-item-input-wrapper { + display: flex; + gap: 10px; + align-items: center; +} + +.avatar-item-input-wrapper .input { + flex: 1; + max-width: 150px; +} + +.button { + padding: 8px 16px; + background: var(--accent); + color: var(--black); + border: none; + border-radius: 4px; + cursor: pointer; + font-family: inherit; + font-size: 0.9em; + transition: background 0.2s ease, transform 0.1s ease, opacity 0.2s ease; +} + +.button:hover:not(:disabled) { + filter: brightness(1.1); + transform: translateY(-1px); +} + +.button:active:not(:disabled) { + transform: translateY(0); +} + +.button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.button-danger { + background: #ef4444; + color: white; +} + +.button-danger:hover:not(:disabled) { + background: #dc2626; +} + +.button-secondary { + background: #6b7280; + color: white; +} + +.button-secondary:hover:not(:disabled) { + background: #4b5563; +} + +.git-hash { + font-size: 10px; + color: #666; + user-select: none; + text-align: center; + align-content: center; +} + +#help-button { + padding: 5px; + cursor: pointer; +} + +.comment-content img { + max-height: 200px; + max-width: 100%; + border-radius: 4px; + +} + +.sidebar-comment-img { + max-height: 128px !important; +} + +.sidebar-activity .video-embed-wrap { + display: block; + max-width: 100%; + box-shadow: none; + margin: 4px 0; +} + +.sidebar-video-link { + display: block; + font-size: 0.8em; + color: var(--accent); + text-decoration: none; + margin: 4px 0; + font-style: italic; +} + +.sidebar-video-link i { + margin-right: 4px; +} + +.sidebar-video-link:hover { + text-decoration: underline; +} + +.sidebar-comment-img.emoji { + width: auto; + display: inline-block; + vertical-align: middle; + height: min-content; +} + +.comment-content img:not(.emoji) { + display: block; + +} + +/* Smaller emojis in the narrow left sidebar */ +body.layout-modern .comment-content img.emoji { + height: auto; + max-width: 60px !important; + vertical-align: middle; +} + + +@keyframes flashGreen { + 0% { + background-color: rgba(var(--accent-rgb), 0.3); + } + + 100% { + background-color: var(--item-bg); + } +} + +@keyframes newItemFlash { + 0% { border-left-color: var(--accent); background: rgba(var(--accent-rgb), 0.15); } + 60% { border-left-color: var(--accent); background: rgba(var(--accent-rgb), 0.05); } + 100% { border-left-color: transparent; background: transparent; } +} + +.new-item-fade { + animation: newItemFlash 2.5s ease forwards; + border-left: 3px solid transparent; +} + +.sidebar-activity .new-item-fade { + border-left: none; + animation: none; +} + +/* Comment posted by the current user — fades in + accent border that auto-disappears */ +@keyframes commentEnter { + 0% { opacity: 0; transform: translateY(6px); border-left-color: var(--accent); background: rgba(var(--accent-rgb), 0.15); } + 20% { opacity: 1; transform: translateY(0); border-left-color: var(--accent); background: rgba(var(--accent-rgb), 0.15); } + 60% { background: rgba(var(--accent-rgb), 0.05); } + 70% { border-left-color: var(--accent); background: transparent; } + 100% { border-left-color: transparent; } +} + +.comment-entering { + animation: commentEnter 7.5s ease forwards; + border-left: 3px solid transparent; +} + +.comment-highlighted { + border-left: 3px solid var(--accent) !important; + background: rgba(17, 17, 17, 0.55) !important; +} + +@keyframes activityGlow { + 0% { + color: var(--accent); + /* transform: scale(1); */ + filter: drop-shadow(0 0 0 rgba(0, 0, 0, 0)); + } + + 50% { + color: #1fb2b0; + /* transform: scale(1.2); */ + filter: drop-shadow(0 0 10px #1fb2b0); + } + + 100% { + color: var(--accent); + /* transform: scale(1); */ + filter: drop-shadow(0 0 0 rgba(0, 0, 0, 0)); + } +} + +.activity-glowing { + animation: activityGlow 1s ease-in-out; + transform-origin: center; + display: inline-block; +} + +/* MOTD Display (Integrated into Navbar) */ +.motd-container { + grid-column: 1 / -1; + width: 100%; + background: var(--motd-bg); + text-align: left; + z-index: 999; + border-top: 1px solid var(--nav-border-color); + border-bottom: 1px solid var(--nav-border-color); + font-size: 0.85em; + padding: 4px 0; + position: relative; + order: 2; +} + +#motd-display { + text-transform: initial; +} + +.motd-content { + line-height: 1.4; + /* padding: 0 30px 0 10px; */ + /* Space for X button */ +} + +#motd-display, #motd-display-guest { + left: 5px; + position: relative; +} + +.motd-close { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + background: none; + border: none; + color: var(--text-muted, #888); + font-size: 1.2em; + cursor: pointer; + padding: 0 5px; + line-height: 1; + transition: color 0.2s; +} + +.motd-close:hover { + color: var(--accent); +} + +.motd-content a { + color: var(--accent, #9f0); + text-decoration: underline; +} + +.motd-content a:hover { + filter: brightness(1.2); +} + +/* === SITE OVERRIDES (formerly w0bm.css) === */ + +.navbar-brand { + display: grid; + grid-template-columns: 1fr auto; +} + +.image-brand { + width: 5cm; +} + +.kontrollelement { + grid-column: 2; +} + +.metadata { + grid-template-columns: 0.4fr auto 0.4fr; + grid-template-rows: auto 1fr; + background-color: transparent !important; + border: none !important; +} + +html[theme="f0ck95d"] .badge-dark { + border: none; + background-image: none; + box-shadow: none; +} + +.steuerung { + font-size: x-large; + font-family: monospace; +} + +.steuerung a { + color: white; +} + +.blahlol { + grid-column: 1 / 4; + width: 100%; + background: rgba(0,0,0,0.2); +} + + +span#favs { + background: transparent !important; + border: none !important; +} + +.badge-dark, +#themeselector { + background-color: unset; + border: unset; +} + +._204863 { + background: none; + border: none; + background-color: none; +} + +html[theme="iced"] ._204863 { + background: none; +} + +.media-object { + border: none; +} + +/*.v0ck { + background: transparent !important; +}*/ + + + +html[theme="f0ck95"] .embed-responsive.embed-responsive-16by9 { + background: var(--nav-bg) !important; +} + +.v0ck_overlay { + background-color: transparent; +} + +.v0ck_player_controls { + background: rgba(0, 0, 0, 0.95) !important; +} + +html[theme="term"] .image-brand { + filter: hue-rotate(-50deg); +} + +html[theme="f0ck"] .image-brand { + filter: hue-rotate(-50deg); +} + +html[theme="p1nk"] .image-brand { + filter: hue-rotate(150deg); +} + +html[theme="orange"] .image-brand { + filter: hue-rotate(-160deg); +} + +.v0ck_overlay { + background-color: none !important; +} + +.tags { + display: grid; +} + +@media (min-width: 600px) { + .tags { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 900px) { + .tags { + grid-template-columns: repeat(3, 1fr); + } +} + +.tag { + box-shadow: 1px 1px 1px black; + display: grid; + margin: 5px; + grid-template-columns: auto; + grid-template-rows: 1fr; +} + +.navigation-rechts { + display: flex; +} + +.about { + background: none !important; +} + +.login-image { + width: 300px; +} + +.tos { + margin: 2em; +} + +.nav-link[data-toggle="dropdown"]::after { + content: "" !important; +} + +/* .dropdown-menu.shii { + width: auto; +} */ + +@media (min-width: 900px) { + .tags { + grid-template-columns: repeat(auto-fill, 20em); + justify-content: center; + } +} + +.tag img { + width: 100%; +} + +@media (max-width: 1056px) { + + html, + body { + text-align: left; + max-width: 100vw; + overflow-x: clip; + } +} + +html[theme="f0ck95d"] .embed-responsive.embed-responsive-16by9 { + border-bottom: outset 2px silver; +} + +html[theme="f0ck95"] .embed-responsive.embed-responsive-16by9 { + border-bottom: outset 2px silver; +} + +/* Fade in video on page load */ + +video { + opacity: 0; + animation: fadeIn 2s forwards; +} + +@keyframes fadeIn { + to { + opacity: 1; + } +} + + + +.navbar { + border-bottom: none; + /* padding: 5px; */ + /* testing if it works good */ +} + +.image-brand { + width: 4cm; +} +.pagewrapper { + padding: 5px; + padding-bottom: 65px; /* Reserve space for fixed pagination */ + position: relative; +} + +@media (min-width: 1200px) { + .pagewrapper { + padding-right: 300px; + } + body.sidebar-right-hidden .pagewrapper { + padding-right: 5px; + } +} + +/* + Since Index and Item pages have their own layout wrappers that handle space reservation, + we disable the generic pagewrapper padding on those pages to avoid "double padding" or broken expansion. +*/ +@media (min-width: 1200px) { + .pagewrapper:has(.index-layout-wrapper), + .pagewrapper:has(.item-layout-container), + .pagewrapper:has(.meme-layout-wrapper), + body.private-gate-active .pagewrapper { + padding-right: 0 !important; + } +} + +.container { + padding-top: 1vh; +} + +#next, +#prev { + visibility: hidden; +} + +@media (max-width: 1056px) { + .navigation-links { + display: grid; + grid-row: 1; + grid-column: 2; + } + + .navbar-brand { + grid-row: 1; + grid-column: 1; + } + + #navbarSupportedContent { + grid-row: 1; + grid-column: 3; + } + + .navbar { + grid-template-columns: 0fr 1fr auto !important; + grid-template-rows: 1fr; + } + + .navbar-nav { + display: grid !important; + grid-template-rows: auto; + grid-template-columns: auto auto 1fr; + grid-gap: 0.25em; + } +} + +.bg-black { + background: #0000008a !important; +} + +.pagination>a { + background: #232323b2; +} + +html[theme="paper"] .pagination { + color: white; +} + +html[theme="paper"] .navbar-brand { + background: transparent; +} + +.navbar-brand:hover { + background: transparent; +} + +div.search { + display: grid; +} + +div.sbt { + display: grid; + grid-template-columns: 1fr auto; + align-content: center; +} + +#sbtButton { + visibility: hidden; +} + +#sbtInput { + background: #00000021; + box-shadow: -1px -1px 0px #252525; + border: inset 1px #0000001c; + padding: revert; + box-shadow: inset 0px 0px 5px 1px #0000005e; + width: 100%; +} + +.navigation-links { + display: grid; + grid-row: 1; + grid-column: 2; + grid-template-columns: auto auto 1fr; +} + +.navigation-links-guest, +ol { + margin: 5px; + margin-block-start: 0; + margin-block-end: 0; + padding-inline-start: 0; +} + +/* === VIEW-SPECIFIC STYLES === */ + +/* === NAVBAR STYLES === */ + +/* ── Desktop layout: single flex row ── */ +.navbar { + position: sticky; + top: 0; + z-index: 1005; + width: 100%; + display: flex; + flex-direction: row; + align-items: center; + flex-wrap: wrap; /* allows motd-container to wrap to its own full-width row */ + font-family: var(--font); + text-transform: uppercase; + border-bottom: 1px solid var(--nav-border-color); + background: var(--nav-bg); +} + +/* Brand: always visible, flush left */ +.navbar-brand { + flex-shrink: 0; + display: flex; + align-items: center; + height: 40px; + width: 180px; +} + +/* Nav collapse: fills middle, always visible on desktop */ +.nav-collapse { + flex: 1; + display: flex; + align-items: center; + min-width: 0; +} + +/* Right group: flush right */ +.nav-right-group { + display: flex; + align-items: center; + gap: 20px; + padding: 0 15px; + flex-shrink: 0; + margin-left: auto; +} + +/* MOTD: wraps to its own full-width row below brand+links+right-group */ +.motd-container { + order: 999; + width: 100%; + flex-shrink: 0; +} + +/* Uniform size for all Font Awesome icons in the navbar */ +.navbar .fa-solid, +.navbar .fa-regular, +.navbar .fa { + font-size: 14px; + width: 14px; + text-align: center; + line-height: 1; +} + +/* Hamburger button: hidden on desktop */ +.navbar-toggler { + display: none; + flex-direction: column; + justify-content: center; + align-items: center; + width: 32px; + height: 32px; + gap: 5px; + background: transparent; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 4px; + cursor: pointer; + padding: 4px; + flex-shrink: 0; +} + +.navbar-toggler span { + display: block; + width: 18px; + height: 2px; + background: currentColor; + border-radius: 2px; + transition: transform 0.25s ease, opacity 0.25s ease; +} + +/* Animated X when open */ +.navbar-toggler.is-open span:nth-child(1) { transform: translateY(7px) rotate(45deg); } +.navbar-toggler.is-open span:nth-child(2) { opacity: 0; } +.navbar-toggler.is-open span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); } + +/* Icon cluster: tags / filter / search */ +.nav-icon-cluster { + display: flex; + align-items: center; + gap: 10px; + padding: 0 6px; + border-left: 1px solid rgba(255, 255, 255, 0.08); + border-right: 1px solid rgba(255, 255, 255, 0.08); +} + +.nav-icon-cluster a, +.nav-item-rel > a, +.nav-right-group > a { + color: #fff; + opacity: 0.75; + transition: opacity 0.2s; +} + +.nav-icon-cluster a:hover, +.nav-item-rel > a:hover, +.nav-right-group > a:hover { + opacity: 1; +} + +/* Right-anchor all dropdowns inside nav-right-group */ +.nav-right-group .nav-user-menu { + left: auto; + right: 0; + transform: none; +} + +.nav-right-group .notif-dropdown { + left: auto; + right: 0; /* anchor to bell container right edge; JS overrides on mobile */ + transform: none; +} + +/* Shift arrow to sit directly under the bell icon */ +.nav-right-group .notif-dropdown::before, +.nav-right-group .notif-dropdown::after { + left: auto; + right: 0; + transform: none; +} + +/* Bell active state: lights up when notification dropdown is open */ +#nav-notif-btn { + transition: color 0.15s ease, filter 0.15s ease, transform 0.15s ease; +} + +#nav-notif-btn.is-active { + color: var(--accent); + filter: drop-shadow(0 0 5px var(--accent)); + opacity: 1; +} + +/* ── Mobile: ≤768px ── */ +@media (max-width: 768px) { + + /* Show hamburger */ + .navbar-toggler { + display: flex; + color: white; + } + + /* Hide desktop icon cluster */ + .nav-icon-cluster { + display: none; + } + + /* On mobile: right group stays in the single top row */ + .nav-right-group { + gap: 15px; + padding: 0 4px; + margin-left: auto; + } + + /* nav-collapse: hidden by default, opens as full-width row below brand+right */ + .nav-collapse { + display: none; + flex: none; /* override desktop flex:1 so width:100% takes effect */ + order: 10; + width: 100%; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + padding: 6px 8px; + border-top: 1px solid rgba(255, 255, 255, 0.08); + background: var(--nav-bg); + box-shadow: 0 4px 12px rgba(0,0,0,0.3); + gap: 4px; + } + + .nav-collapse.show { + display: flex; + animation: navFadeIn 0.2s ease-out; + } + + @keyframes navFadeIn { + from { opacity: 0; transform: translateY(-4px); } + to { opacity: 1; transform: translateY(0); } + } + + /* Nav links: horizontal row on mobile too */ + .nav-links { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: center; + width: 100%; + gap: 4px; + } + + .nav-links > a { + display: inline-flex; + align-items: center; + padding: 8px 12px; + font-size: 0.9em; + } + + + + .nav-halls-dropdown .nav-halls-btn { + display: block; + padding: 13px 16px; + width: 100%; + text-align: left; + } + + .nav-halls-dropdown .nav-user-menu { + position: static !important; + display: none; + background: rgba(255,255,255,0.04); + box-shadow: none; + border: none; + right: auto; + left: auto; + width: 100%; + } + + .nav-halls-dropdown:hover .nav-user-menu, + .nav-halls-dropdown.is-open .nav-user-menu { + display: block; + } + + /* Avatar dropdown stays floating (absolute) on mobile */ + .nav-right-group .nav-user-menu { + position: absolute; + } + + .nav-item-rel { + position: relative; + display: inline-flex; + } + +} + +@media (max-width: 467px) { + .nav-avatar-name, + .nav-avatar-caret { + display: none; + } +} + + +.switch-container { + display: inline-flex; + align-items: center; + margin-right: 15px; +} + +.switch { + position: relative; + display: inline-block; + width: 34px; + height: 18px; + margin-bottom: 0; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + transition: .4s; + border-radius: 34px; +} + +.slider:before { + position: absolute; + content: ""; + height: 14px; + width: 14px; + left: 2px; + bottom: 2px; + background-color: white; + transition: .4s; + border-radius: 50%; +} + +input:checked+.slider { + background-color: #f44336; +} + +input:checked+.slider:before { + transform: translateX(16px); +} + +.switch-label { + margin-right: 8px; + font-size: 13px; + color: rgba(255, 255, 255, 0.7); +} + +.mode-selector { + display: inline-flex; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 3px; + overflow: hidden; + vertical-align: middle; + grid-column: 3; +} + +.mode-btn { + display: inline-block; + background: transparent; + color: rgba(255, 255, 255, 0.7); + border: none; + padding: 2px 8px; + font-size: 11px; + cursor: pointer; + text-decoration: none; + transition: all 0.2s; + line-height: 18px; +} + +.mode-btn:hover { + color: #fff; + background: rgba(255, 255, 255, 0.1); + text-decoration: none; +} + +.mode-btn.active { + background: var(--accent); + color: var(--bg); + font-weight: bold; +} + +.mode-btn:not(:last-child) { + border-right: 1px solid rgba(255, 255, 255, 0.2); +} + +/* ---------- Random / Shuffle mode button ---------- */ +.shuffle-btn { + background: none; + border: 1px solid rgba(255, 255, 255, 0.2); + color: var(--white); + padding: 5px 5px; + font-size: 0.8em; + font-weight: bold; + cursor: pointer; + border-radius: 3px; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 5px; + min-width: 50px; + justify-content: center; + opacity: 0.8; +} + +.shuffle-btn:hover { + color: #fff; + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.4); + opacity: 1; +} + +.shuffle-btn.active { + background: var(--accent); + color: var(--bg, #000); + border-color: rgba(255,255,255,0.2); + font-weight: bold; +} + +@keyframes shuffle-spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.shuffle-btn.is-shuffling .shuffle-icon { + display: inline-block; + animation: shuffle-spin 0.4s ease-out; +} + + +.notif-footer { + padding: 10px; + text-align: center; + border-top: 1px solid rgba(255, 255, 255, 0.05); + background: rgba(0, 0, 0, 0.2); +} + +.view-all-notifs { + color: var(--accent) !important; + font-size: 11px; + text-transform: uppercase; + font-weight: bold; + letter-spacing: 0.5px; +} + +.view-all-notifs:hover { + text-decoration: underline !important; +} + +/* ---------- footer.html styles ---------- */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; +} + +.modal-content { + background: #222; + padding: 20px; + border-radius: 8px; + width: 90%; + max-width: 400px; + color: #fff; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); +} + +.modal-actions { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 15px; +} + +textarea.mod-reason { + width: 100%; + height: 80px; + margin-top: 10px; + background: #333; + color: #fff; + border: 1px solid #444; + resize: vertical; + padding: 5px; +} + +.error-msg { + color: #ff6b6b; + font-size: 0.9em; + margin-top: 5px; + display: none; +} + +/* ---------- notifications.html styles ---------- */ +.notif-history-container { + max-width: 760px !important; + margin: 0 auto; + padding: 20px 10px; +} + +.notif-page-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding: 5px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.notif-page-header h2 { + font-size: 0.9em; + margin: 0; + text-transform: uppercase; + letter-spacing: 2px; + color: #777; +} + +.notifications-list-full.posts { + display: grid !important; + grid-template-columns: 1fr; + gap: 0 !important; + width: 100% !important; + border: 1px solid rgba(255, 255, 255, 0.05); + background: rgba(0, 0, 0, 0.1); + border-radius: 4px; + overflow: hidden; + margin: 0 !important; + padding: 0 !important; +} + +.notifications-page .notif-item { + display: grid !important; + grid-template-columns: 0.3fr 1fr 1fr; + width: 100%; + align-items: center; + padding: 8px 15px; + text-decoration: none; + color: #999; + border-bottom: 1px solid rgba(255, 255, 255, 0.02); + font-size: 0.9em; + transition: all 0.1s; +} + +.notifications-page .notif-item:last-child { + border-bottom: none; +} + +.notifications-page .notif-item:hover { + background: rgba(255, 255, 255, 0.04); + color: #eee; +} + +.notifications-page .notif-item.unread { + background: rgba(var(--accent-rgb, 31, 178, 176), 0.03); + color: #eee; + border-left: 2px solid var(--accent, #1fb2b0); + font-weight: 500; + grid-template-columns: auto; +} + +.notif-user { + font-weight: bold; + color: #bbb; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.notif-item.unread .notif-user { + color: var(--accent, #1fb2b0); +} + +.notif-msg { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + padding: 0 15px; +} + +.notif-time { + font-family: monospace; + font-size: 0.8em; + color: #444; + text-align: right; +} + +.notif-item:hover .notif-time { + color: #777; +} + +.btn-small { + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.1); + color: #666; + padding: 3px 10px; + border-radius: 3px; + cursor: pointer; + font-size: 10px; + text-transform: uppercase; + transition: all 0.2s; +} + +.btn-small:hover { + background: rgba(255, 255, 255, 0.1); + border-color: #888; + color: #ccc; +} + +/* ---------- subscriptions.html styles ---------- */ +.subs-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 15px; + margin-bottom: 40px; +} + +.sub-card { + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + overflow: hidden; + display: flex; + flex-direction: column; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.sub-link { + text-decoration: none; + color: inherit; + display: block; + flex-grow: 1; +} + +.sub-card img { + width: 100%; + height: 110px; + object-fit: cover; + display: block; +} + +.sub-info { + padding: 8px; +} + +.sub-id { + display: block; + font-weight: bold; + color: var(--accent); +} + +.sub-user { + display: block; + font-size: 0.85em; + color: #888; +} + +.unsub-btn { + width: 100%; + border: none; + background: rgba(200, 50, 50, 0.2); + color: #ff6666; + padding: 8px; + cursor: pointer; + font-size: 0.9em; + transition: background 0.2s; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +.unsub-btn:hover { + background: rgba(200, 50, 50, 0.4); +} + +/* ---------- mod/approve.html styles ---------- */ +.approval-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 20px; + margin-bottom: 40px; +} + +.approval-card { + background: var(--nav-bg); + border: 1px solid var(--nav-border-color); + border-radius: 8px; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.approval-card-media { + width: 100%; + background: #000; + display: flex; + align-items: center; + justify-content: center; + min-height: 200px; +} + +.approval-card-media img, +.approval-card-media video { + max-width: 100%; + max-height: 300px; + width: 100%; + object-fit: contain; +} + +.approval-card-body { + padding: 15px; + flex: 1; + display: flex; + flex-direction: column; + gap: 10px; +} + +.approval-card-info { + font-size: 14px; + color: var(--white); +} + +.approval-card-tags { + display: flex; + flex-wrap: wrap; + gap: 5px; +} + +.approval-card-actions { + margin-top: auto; + display: flex; + gap: 10px; + padding-top: 10px; + border-top: 1px solid var(--nav-border-color); +} + +.approval-card-actions a { + flex: 1; + text-align: center; + padding: 8px; + text-decoration: none; +} + +/* ---------- mod/audit.html styles ---------- */ +.audit-grid { + display: grid; + grid-template-columns: 1fr; + gap: 20px; + margin-top: 20px; +} + +@media (min-width: 768px) { + .audit-grid { + grid-template-columns: 1fr 1fr; + } +} + +.audit-card { + background: var(--badge-bg); + border: 1px solid var(--nav-border-color); + border-radius: 8px; + padding: 15px; + display: flex; + flex-direction: column; + gap: 10px; + transition: transform 0.2s ease, border-color 0.2s ease; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); +} + +.audit-card:hover { + transform: translateY(-2px); + border-color: var(--accent); +} + +.audit-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + border-bottom: 1px solid var(--nav-border-color); + padding-bottom: 10px; + margin-bottom: 5px; +} + +.audit-card-user { + font-weight: bold; + font-size: 1.1em; +} + +.audit-card-time { + font-size: 0.85em; + color: #888; +} + +.audit-card-body { + display: flex; + flex-direction: column; + gap: 8px; +} + +.audit-card-row { + display: flex; + gap: 8px; + align-items: baseline; +} + +.audit-label { + font-size: 0.8em; + text-transform: uppercase; + color: #888; + min-width: 80px; +} + +.audit-value { + word-break: break-all; +} + +.audit-reason-box { + margin-top: 5px; + padding: 8px; + background: rgba(0, 0, 0, 0.2); + border-left: 3px solid var(--accent); + font-style: italic; + font-size: 0.9em; +} + +.badge-action { + text-transform: uppercase; + font-size: 0.75em; +} + +.audit-diff { + background: rgba(0, 0, 0, 0.3); + border-radius: 4px; + padding: 10px; + font-family: 'Consolas', 'Monaco', 'Courier New', monospace; + font-size: 0.85em; + line-height: 1.4; + word-break: break-all; + border: 1px solid rgba(255, 255, 255, 0.05); + margin-top: 5px; +} + +.diff-removed { + color: #ff8080; + background-color: rgba(255, 0, 0, 0.15); + display: block; + padding: 0 4px; +} + +.diff-added { + color: #80ff80; + background-color: rgba(0, 255, 0, 0.15); + display: block; + padding: 0 4px; +} + +div.posts > a.thumb.has-notif { + box-shadow: inset 0 0 0 1000px rgba(255, 0, 0, 0.28) !important; + border: 1px solid var(--accent); +} + +div.posts > a.thumb.has-notif:hover { + box-shadow: inset 0 0 0 1000px rgba(255, 0, 0, 0.4), 0 0 0 2px var(--accent) !important; +} + +div.posts > a.thumb.has-notif::after { + content: attr(data-ext) " / " attr(data-user) !important; + visibility: hidden; + opacity: 1 !important; + background-color: var(--posts-meta-bg) !important; + color: var(--accent) !important; + font-weight: normal !important; + z-index: 10 !important; +} + +div.posts > a.thumb.has-notif:hover::after { + visibility: visible !important; +} + +div.posts > a.thumb.has-notif p::after { + content: "!" !important; + position: absolute !important; + bottom: 10px !important; + left: 50% !important; + transform: translateX(-50%) !important; + color: var(--accent) !important; + font-size: 40px !important; + font-weight: 900 !important; + text-shadow: 0 0 10px #000, 0 0 20px rgba(var(--accent-rgb), 0.5) !important; + z-index: 200 !important; + pointer-events: none !important; + visibility: visible !important; +} + +.thumb { + position: relative; + overflow: hidden; +} +.thumb > .preview-video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 5; + pointer-events: none; + opacity: 0; + transition: opacity 0.2s ease-in; +} +.thumb > .preview-video.playing { + opacity: 1; +} +.thumb p { + position: relative; + z-index: 10; + pointer-events: none; +} +div.posts > a::after { + z-index: 10 !important; + pointer-events: none; +} +/* Fallback placeholder for thumbnails that failed to load */ +.thumb.thumb-fallback { + background-size: contain !important; + background-color: #0e0e1a !important; + opacity: 0.6; +} + + +.pin-indicator { + position: absolute; + top: 5px; + left: 5px; + font-size: 13px; + fill: var(--accent); + color: var(--accent); + filter: drop-shadow(0 0 2px rgba(0,0,0,0.8)); + z-index: 15; + pointer-events: none; +} + +.oc-indicator { + position: absolute; + left: 1px; + fill: #ffd700; + pointer-events: none; + bottom: 1px; + line-height: normal; + font-size: 13px; + font-family: 'VCR' !important; + z-index: 11; +} + +/* For sub-cards which have a simpler structure */ +.sub-link .pin-indicator { + top: 3px; + left: 3px; +} + +/* Content Warning Modal */ +#content-warning-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.95); + z-index: 99999; + display: flex; + justify-content: center; + align-items: center; + backdrop-filter: blur(5px); +} + +.content-warning-content { + background: var(--bg); + border: 1px solid var(--nav-border-color); + padding: 2rem; + max-width: 500px; + width: 90%; + text-align: center; + border-radius: 4px; + box-shadow: 0 0 20px rgba(0,0,0,0.5); + color: var(--font-color); +} + +.content-warning-content h3 { + margin-top: 0; + color: var(--accent); + margin-bottom: 1rem; +} + +.content-warning-content p { + margin-bottom: 2rem; + line-height: 1.5; +} + +.content-warning-content .modal-actions { + display: flex; + justify-content: center; + gap: 1rem; +} + +.content-warning-content button { + padding: 10px 20px; + cursor: pointer; + font-family: var(--font); + font-weight: bold; + border: none; + border-radius: 3px; + text-transform: uppercase; + color: var(--black); +} + +.content-warning-content #cw-accept { + background: var(--accent); +} + +.content-warning-content #cw-accept:hover { + opacity: 0.9; +} + +.content-warning-content #cw-decline { + background: var(--gray); + color: var(--white); +} + + +.content-warning-content #cw-decline:hover { + background: #333; +} + +/* Video Player Settings Menu */ +.v0ck_settings_container { + position: relative; + display: flex; /* Fix alignment */ + align-items: center; +} + +.v0ck_settings_menu { + position: absolute; + bottom: 100%; /* Position above the button */ + right: 0; + margin-bottom: 5px; + background-color: rgba(0, 0, 0, 0.9); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 4px; + padding: 5px 0; + display: flex; + flex-direction: column; + min-width: 100px; + z-index: 20; /* Ensure it's above other controls */ + box-shadow: 0 4px 6px rgba(0,0,0,0.3); +} + +.v0ck_settings_menu.v0ck_hidden { + display: none; +} + +.v0ck_menu_item { + background: transparent; + border: none; + color: #fff; + padding: 8px 15px; + text-align: left; + cursor: pointer; + font-size: 14px; + font-family: inherit; + white-space: nowrap; + transition: background-color 0.2s; + width: 100%; +} + +.v0ck_menu_item:hover { + background-color: rgba(255, 255, 255, 0.1); +} + + + +/* ... (rest of CSS) */ +/* Specific styling for SWF/BG buttons now in menu */ +#toggleswf { + display: block; + width: 100%; + text-align: center; /* Center text */ +} + +/* Background Row Styling */ +.v0ck_bg_row { + display: flex; + align-items: center; + justify-content: space-between; /* Label on left, switch on right? Or center? User asked for "items centered". */ + /* If items centered, maybe justify-content: center with a gap? */ + /* Let's try space-between for the row content (Label ... Switch) looks better usually, + but if the menu is wide, it might look odd. + Let's stick to space-between for the row, but ensure the menu itself centers the row? + The row IS the menu item. + Let's use space-between for label and switch. + */ + justify-content: space-between; + padding: 8px 15px; + width: 100%; + box-sizing: border-box; +} + +.v0ck_switch_label { + margin-right: 10px; + font-size: 14px; + color: #fff; +} + +/* Cool Switch Styling */ +.v0ck_cool_switch { + width: 36px; + height: 20px; + background-color: #555; + border-radius: 10px; + position: relative; + cursor: pointer; + transition: background-color 0.3s; +} + +.v0ck_cool_switch::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + background-color: #fff; + border-radius: 50%; + transition: transform 0.3s; +} + +.v0ck_cool_switch.active { + background-color: var(--gray); +} + +.v0ck_cool_switch.active::after { + transform: translateX(16px); +} + +.v0ck_cool_switch:hover { + opacity: 0.9; +} + +/* Update Menu Item Styling for Centering */ +.v0ck_menu_item { + /* ... existing styles ... */ + /* We want to center the SWF button text */ + text-align: center; +} + +/* Ensure menu itself centers items? */ +.v0ck_settings_menu { + /* ... existing styles ... */ + align-items: center; /* Center items horizontally */ + min-width: 160px; /* Ensure enough width */ +} + +/* Fix hover effect for settings button (viewBox scaling issue) */ +.v0ck_settings_btn svg:hover { + stroke-width: 2px !important; /* Override the default 30px which is for large viewBoxes */ +} +/* Ensure SVG is vertically centered */ +.v0ck_settings_btn svg { + vertical-align: middle; +} + +/* Mobile Optimized Player Controls */ +@media screen and (max-width: 600px) { + .v0ck_volume:hover+input[type="range"][name="volume"], + .v0ck_player_controls>input[type="range"][name="volume"]:hover { + min-width: 40px !important; /* Smaller expansion on mobile */ + max-width: 40px !important; + } +} + +.navbar { + background: rgba(0,0,0,0.2); + border-bottom: 1px solid var(--nav-border-color); +} + +body.layout-modern .navbar-header, +body.layout-legacy .navbar-header { + background: transparent; +} + + .sidebar-tags-container { + padding: 5px; + background: rgba(0,0,0,0.2); + border-top: 1px solid var(--nav-border-color); + overflow: visible; + flex-shrink: 0; + } + +/* ===== layout-modern: flexible by default, viewport-locked for item view ===== */ +@media (min-width: 1000px) { + body.layout-modern { + display: flex; + flex-direction: column; + min-height: 100vh; + overflow-y: auto; + } + + /* App-like viewport lock: only for item view */ + body.layout-modern:has(.item-layout-container) { + overflow: hidden; + height: 100vh; + } + + body.layout-modern > .pagewrapper { + flex: 1; + display: flex; + flex-direction: column; + /* Reset top/side padding but let sidebar-aware padding-right be handled below */ + padding-top: 0 !important; + padding-left: 0 !important; + padding-bottom: 0 !important; + padding-right: 0; /* overridden to 300px by sidebar rule below for generic pages */ + } + + body.layout-modern:has(.item-layout-container) > .pagewrapper { + min-height: 0; + overflow: hidden; + } + + body.layout-modern > .pagewrapper > #main, + body.layout-modern > #main { + flex: 1; + display: flex; + flex-direction: column; + align-items: normal; + } + + body.layout-modern:has(.item-layout-container) > .pagewrapper > #main, + body.layout-modern:has(.item-layout-container) > #main { + min-height: 0; + overflow: hidden; + } +} + +/* layout-modern: restore sidebar right-padding AND block-flow for generic pages + (profile, settings, subscriptions, etc.) — identical behaviour to layout-legacy. + Specialized pages (index, item, meme) manage their own layout, so exclude them. */ +@media (min-width: 1200px) { + body.layout-modern > .pagewrapper:not(:has(.index-layout-wrapper)):not(:has(.item-layout-container)):not(:has(.meme-layout-wrapper)) { + padding-right: 300px; + } + body.layout-modern.sidebar-right-hidden > .pagewrapper:not(:has(.index-layout-wrapper)):not(:has(.item-layout-container)):not(:has(.meme-layout-wrapper)) { + padding-right: 0; + } +} + +/* For generic pages in layout-modern, reset #main to plain block flow so children + fill the full available width — mirrors layout-legacy exactly and prevents any + flex child centering (e.g. margin:0 auto shrinking content like on /subscriptions). */ +body.layout-modern > .pagewrapper:not(:has(.index-layout-wrapper)):not(:has(.item-layout-container)):not(:has(.meme-layout-wrapper)):not(:has(.messages-convo-page)) > #main { + display: block !important; + width: 100%; +} + +@media (min-width: 1000px) { + + + /* metadata + comment headers must sit above the grid overflow */ + body.layout-modern .metadata, + body.layout-modern .comment-header { + z-index: 100 !important; + position: relative; + } + + body.layout-modern .sidebar-tags-container { + overflow-y: visible !important; + } + + body.layout-modern .sidebar-tags-container span#tags { + display: flex; + flex-wrap: wrap; + gap: 5px; + max-height: 70px; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-color) transparent; + } +} + +/* ===== layout-legacy: natural page scroll (2-column, body scrolls) ===== */ +@media (min-width: 1000px) { + body.layout-legacy { + overflow: visible !important; + height: auto !important; + max-height: none !important; + display: block; + } + + body.layout-legacy > .wrapper, + body.layout-legacy > .pagewrapper { + display: block; + height: auto; + min-height: 100vh; + padding-top: 0 !important; + } + + body.layout-legacy > #main:not(.messages-convo-page), + body.layout-legacy #main:not(.messages-convo-page) { + display: block; + height: auto; + /*padding-top: 0 !important;*/ + } + + body.layout-legacy .item-layout-container { + height: auto !important; + min-height: 0; + } + + body.layout-legacy .metadata, + body.layout-legacy .comment-header { + z-index: 100 !important; + position: relative; + } + + body.layout-legacy .sidebar-tags-container, + body.layout-legacy .comments-list { + overflow-y: visible !important; + } +} + +/* Global Breakout Tooltip System (Replaces native CSS tooltips to prevent clipping/scrolling bugs) */ +[tooltip]::before, +[tooltip]::after { + display: none !important; +} + +#f0ck-breakout-tooltip { + font-family: Helvetica, sans-serif; + text-align: center; + padding: 5px 10px; + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + background: #535050; + color: #fff; + font-size: 11px; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; + white-space: nowrap; + z-index: 1000000; + pointer-events: none; + position: fixed; + line-height: normal; + display: block; + opacity: 0; + transform: translateY(-5px); + transition: opacity 0.15s ease, transform 0.15s ease; +} + +#f0ck-breakout-tooltip.is-visible { + opacity: 1; + transform: translateY(0); +} + +#f0ck-breakout-tooltip::before { + content: ''; + position: absolute; + border: 5px solid transparent; + z-index: 1000001; +} + +#f0ck-breakout-tooltip[data-flow="up"]::before, +#f0ck-breakout-tooltip[data-flow="up-left"]::before { + top: 100%; + left: 50%; + transform: translateX(-50%); + border-top-color: #535050; + border-bottom-width: 0; +} + +#f0ck-breakout-tooltip[data-flow="up-left"]::before { + left: 10px; + transform: none; +} + +#f0ck-breakout-tooltip[data-flow="down"]::before { + bottom: 100%; + left: 50%; + transform: translateX(-50%); + border-bottom-color: var(--tooltip-bg, #131212); + border-top-width: 0; +} + +#f0ck-breakout-tooltip[data-flow="left"]::before { + top: 50%; + left: 100%; + transform: translateY(-50%); + border-left-color: var(--tooltip-bg, #131212); + border-right-width: 0; +} + +#f0ck-breakout-tooltip[data-flow="right"]::before { + top: 50%; + right: 100%; + transform: translateY(-50%); + border-right-color: var(--tooltip-bg, #131212); + border-left-width: 0; +} + +/* Tag Controls Refinement */ +.tag-controls { + display: flex; + align-items: center; + padding: 5px; + gap: 5px; +} + +.tag-btn { + display: inline-flex; + align-items: center; + justify-content: center; + background: var(--badge-tag, #171717); + border: 1px outset #221d1d; + color: var(--white, #fff) !important; + transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + text-decoration: none !important; +} + + +.tag-btn.is-sfw { + color: #28a745 !important; +} + +.tag-btn.is-nsfw { + color: #dc3545 !important; +} + +/* Rating Button in gapRight */ +#a_toggle.rating-btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 5px; + height: 14px; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.15); + background-color: #444; + color: #fff; + font-size: 8px; + font-weight: 800; + letter-spacing: 0.05em; + text-transform: uppercase; + cursor: pointer; + white-space: nowrap; + transition: background-color 0.2s ease, border-color 0.2s ease, transform 0.1s ease; + line-height: 1; + font-family: inherit; +} + +#a_toggle.rating-btn:hover { + filter: brightness(1.15); + transform: scale(1.05); +} + +#a_toggle.rating-btn.is-sfw { + background-color: var(--badge-sfw); + border-color: rgba(0, 0, 0, 0.15); +} + +#a_toggle.rating-btn.is-nsfw { + background-color: var(--badge-nsfw); + border-color: rgba(0, 0, 0, 0.15); +} + +#a_toggle.rating-btn.is-nsfl { + background-color: var(--badge-nsfl); + border-color: rgba(0, 0, 0, 0.15); +} + +#a_toggle.rating-btn.is-untagged { + background-color: #444; +} + +.tag-btn svg { + fill: currentColor; + width: 20px; + height: 20px; +} + +.rules { + padding: 10px; +} + +/* Shortcuts List */ +#shortcuts-modal .login-modal-content { + max-height: 90vh; + overflow-y: auto; + display: flex; + flex-direction: column; + scrollbar-width: thin; + scrollbar-color: var(--accent) transparent; +} + +#shortcuts-modal .login-modal-content::-webkit-scrollbar { + width: 6px; +} + +#shortcuts-modal .login-modal-content::-webkit-scrollbar-thumb { + background-color: var(--accent); + border-radius: 10px; +} + +.shortcuts-content { + padding: 10px 20px 20px 20px; + color: var(--white); + text-align: left; +} + +.shortcut-list { + display: flex; + flex-direction: column; + gap: 12px; + margin-top: 15px; +} + +.shortcut-item { + display: flex; + justify-content: space-between; + align-items: flex-start; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); + padding-bottom: 8px; + font-family: var(--font); + gap: 15px; +} + +.shortcut-item span:first-child { + flex-shrink: 0; + white-space: nowrap; +} + +.shortcut-item span:last-child { + text-align: right; + opacity: 0.8; +} + +.shortcut-item kbd { + background: var(--badge-bg); + border: 1px solid rgba(255, 255, 255, 0.2); + color: var(--accent); + padding: 2px 6px; + border-radius: 3px; + font-size: 0.9em; + box-shadow: 0 1px 0 rgba(0,0,0,0.5); +} + +#help-button:hover { + opacity: 1 !important; + color: var(--white) !important; +} + +#shortcut-theme { + cursor: pointer; +} + +@media (max-width: 480px) { + #shortcuts-modal { + padding: 10px; + } + + .shortcuts-content h2 { + font-size: 1.2rem; + margin-bottom: 15px !important; + } + + .shortcut-item { + font-size: 12px; + gap: 8px; + } +} + +#a_username { + color: var(--white); + display: inline; +} + +.id-link { + color: var(--white); +} +/* Image Modal / Lightbox */ +.image-modal-overlay { + background-color: rgba(0, 0, 0, 0.7); + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 10000; + overflow: auto; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + transition: opacity 0.3s ease; + display: none; /* Hidden by default */ + flex-direction: column; +} + +.image-modal-overlay.visible { + display: flex !important; +} + +.image-modal-close { + position: fixed; + top: 20px; + right: 30px; + color: #fff; + font-size: 32px; + font-weight: bold; + cursor: pointer; + z-index: 10002; + width: 50px; + height: 50px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 50%; + transition: all 0.2s; +} + +.image-modal-close:hover { + background: rgba(255, 255, 255, 0.2); + transform: scale(1.1); +} + +.image-modal-container { + display: flex; + align-items: center; + justify-content: center; + min-height: 100%; + padding: 40px; + box-sizing: border-box; + margin: auto; +} + +/* Explicitly isolate modal image from on-page styles */ +.image-modal-container img#image-modal-img { + display: block; + margin: auto; + max-width: none !important; + max-height: none !important; + width: auto !important; + height: 100% !important; + border-radius: 2px; + animation: modalZoomIn 0.3s cubic-bezier(0.165, 0.84, 0.44, 1); + cursor: move !important; /* Fallback */ + cursor: -webkit-grab !important; + cursor: -moz-grab !important; + cursor: grab !important; /* Standard rules must be last */ + transform-origin: center; + transition: transform 0.05s ease-out; + user-select: none; +} + +/* Mobile responsive scaling for the image modal */ +@media (max-width: 768px) { + .image-modal-container { + padding: 0 !important; + } + .image-modal-container img#image-modal-img { + max-width: 100vw !important; + max-height: 100vh !important; + object-fit: contain; + } + .image-modal-close { + top: 10px; + right: 15px; + width: 40px; + height: 40px; + font-size: 28px; + background: rgba(0, 0, 0, 0.8); /* Higher contrast on mobile */ + } +} + +/* Hardened 'Closed Grab' / Grabbing state */ +body.modal-is-grabbing, +body.modal-is-grabbing * { + cursor: move !important; /* Fallback */ + cursor: -webkit-grabbing !important; + cursor: -moz-grabbing !important; + cursor: grabbing !important; /* Standard rules must be last */ +} + +@keyframes modalZoomIn { + from { transform: scale(0.98); opacity: 0; } + to { transform: scale(1); opacity: 1; } +} + +body.modal-open { + overflow: hidden !important; + padding-right: var(--scrollbar-width, 0px); +} + +body.layout-modern .tag-controls { + background: rgba(0,0,0,0.2); + border-top: 1px solid var(--nav-border-color); +} + +.phrase { + text-align: center; + font-size: small; + background: rgba(0,0,0,0.2); +} +/* Global Drag & Drop Overlay */ +#drop-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(var(--accent-rgb), 0.15); + backdrop-filter: blur(8px); + z-index: 10000; + display: none; + flex-direction: column; + align-items: center; + justify-content: center; + pointer-events: none; + border: 4px dashed var(--accent); + margin: 10px; + width: calc(100% - 20px); + height: calc(100% - 20px); + border-radius: 20px; +} + +#drop-overlay.active { + display: flex; + pointer-events: auto; +} + +#drop-overlay .overlay-content { + background: var(--shade1, #111); + padding: 40px; + border-radius: 20px; + text-align: center; + box-shadow: 0 20px 60px rgba(0,0,0,1); + border: 1px solid var(--shade3, #333); +} + +#drop-overlay h2 { + margin: 15px 0 0 0; + font-size: 2rem; + color: var(--white); + text-transform: uppercase; + letter-spacing: 2px; +} + +#drop-overlay svg { + color: var(--accent); + filter: drop-shadow(0 0 15px var(--accent)); +} + +/* Upload Drag Modal */ +#upload-drag-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.8); + backdrop-filter: blur(5px); + z-index: 10001; + display: none; + align-items: center; + justify-content: center; +} + +#upload-drag-modal.show { + display: flex; +} + +#upload-drag-modal .modal-content { + background: var(--shade1, #111); + border: 1px solid var(--shade3, #333); + border-radius: 8px; + padding: 0; + width: 900px; + max-width: 95vw; + position: relative; + box-shadow: 0 10px 40px rgba(0,0,0,0.8); + max-height: 90vh; + overflow-y: auto; +} + +#upload-drag-modal .modal-close { + position: absolute; + top: 15px; + right: 15px; + background: none; + border: none; + color: var(--white); + font-size: 24px; + cursor: pointer; + line-height: 1; + z-index: 10; +} + +#upload-drag-modal .upload-container { + max-width: 100%; + width: 100%; +} + +/* Private Society Gate */ +.private-gate { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 60vh; + text-align: center; + gap: 15px; +} + +.private-gate h1 { + font-family: 'VCR', monospace; + font-size: 3rem; + color: var(--accent); + letter-spacing: 2px; +} + +.private-gate p { + color: var(--white); + opacity: 0.7; + font-size: 1.1rem; +} + +.private-gate-btn { + background: var(--accent); + color: var(--black); + border: none; + padding: 10px 40px; + font-weight: bold; + font-size: 1rem; + cursor: pointer; + letter-spacing: 1px; + text-transform: uppercase; + transition: opacity 0.2s; +} + +.private-gate-btn:hover { + opacity: 0.85; +} + +.private-gate-actions { + display: flex; + gap: 15px; + align-items: center; +} + +.private-gate-btn-secondary { + background: transparent; + color: var(--accent); + border: 1px solid var(--accent); +} + +.private-gate-btn-secondary:hover { + background: var(--accent); + color: var(--black); +} + +.index-sidebar-right-footer, +.item-sidebar-right-footer, +.global-sidebar-right-footer { + display: grid; + grid-template-columns: 1fr 1fr 1fr auto; + align-items: center; + align-content: center; + border-top: 1px solid var(--nav-border-color); + flex-shrink: 0; + background: rgba(0,0,0,0.3); + min-height: 30px; + text-align: center; +} + +.global-sidebar-right-footer > a { + border-right: 1px solid var(--gray); + opacity: 0.8; +} + +.global-sidebar-right-footer > a:hover { + opacity: 1; +} + +#help-button { + cursor: pointer; + padding: 0 10px; + font-size: 16px; + line-height: 1; +} + +.git-hash { + font-family: monospace; + font-size: 10px; + opacity: 0.5; + text-align: right; + padding-right: 5px; +} + +.item-preview { + background: #0000001f; + display: grid; + grid-template-columns: auto 1fr; + margin-top: 5px; + gap: 5px; +} + +.item-preview a { + align-self: center; +} + +.item-preview a img { + display: grid; + height: 32px; + background: black; + border-radius: 5px; + margin: 5px; +} +/* ═══════════════════════════════════════════════════════════ + PRIVATE MESSAGES / DM SYSTEM + ═══════════════════════════════════════════════════════════ */ + +/* ── Page layout ─────────────────────────────────────────── */ +.messages-page { + max-width: 720px; + margin: 0 auto; + padding: 16px; + display: flex; + flex-direction: column; + min-height: auto; +} + +.messages-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 18px; + border-bottom: 1px solid var(--border, #333); + padding-bottom: 12px; + gap: 12px; +} + +.messages-header h2 { + margin: 0; + font-size: 1.1em; + letter-spacing: 0.08em; + color: var(--accent); +} + +.dm-back-btn { + color: var(--accent); + font-size: 1.4em; + text-decoration: none; + line-height: 1; + padding: 4px 8px; + border-radius: 4px; + transition: background 0.15s; +} + +.dm-back-btn:hover { background: var(--bg2, #222); } + +.dm-convo-header-info { + display: flex; + align-items: center; + gap: 10px; + flex: 1; +} + +.dm-convo-badge.badge-nsfw { + background: var(--badge-nsfw); +} + +.badge-nsfl { + color: var(--white); + background-color: var(--badge-nsfl); + padding-right: 5px; + padding-left: 5px; + padding-top: 1.5px; + padding-bottom: 1.5px; + border-radius: 3px; + text-shadow: 1px 1px var(--black); + text-transform: uppercase; +} + +.dm-convo-delete { + background: var(--badge-nsfw); + border: none; + border-radius: 5px; +} + +.dm-header-avatar { + width: 32px; + height: 32px; + border-radius: 4px; + object-fit: cover; +} + +.dm-header-username { + font-weight: 600; + font-size: 1em; + text-decoration: none; + color: var(--fg, #ddd); +} + +.dm-header-username:hover { text-decoration: underline; } + +/* ── Key notice banner ───────────────────────────────────── */ +.dm-key-notice { + background: rgba(255, 200, 80, 0.15); + border: 1px solid rgba(255, 200, 80, 0.4); + color: #ffc850; + padding: 10px 14px; + border-radius: 6px; + font-size: 0.88em; + display: flex; + align-items: center; + gap: 10px; + margin-bottom: 14px; + flex-wrap: wrap; +} + +.dm-key-export-inline { + background: rgba(255, 200, 80, 0.25); + border: 1px solid rgba(255, 200, 80, 0.5); + color: #ffc850; + padding: 3px 10px; + border-radius: 4px; + cursor: pointer; + font-size: 0.9em; +} + +.dm-key-export-inline:hover { background: rgba(255, 200, 80, 0.4); } + +/* ── Inbox list ──────────────────────────────────────────── */ +.dm-inbox-list { + display: flex; + flex-direction: column; + gap: 6px; +} + +.dm-convo-card { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 14px; + border-radius: 8px; + background: var(--bg2, #1a1a1a); + border: 1px solid transparent; + text-decoration: none; + color: var(--fg, #ccc); + transition: background 0.15s, border-color 0.15s; + position: relative; +} + +.dm-convo-card:hover { + background: var(--bg3, #222); + border-color: var(--accent); +} + +.dm-convo-card.dm-convo-unread { + border-color: var(--accent); +} + +.dm-convo-avatar { + width: 42px; + height: 42px; + border-radius: 6px; + object-fit: cover; + flex-shrink: 0; +} + +.dm-convo-info { + flex: 1; + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.dm-convo-name { + font-weight: 600; + font-size: 0.95em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.dm-convo-time { + font-size: 0.78em; + color: #666; +} + +.dm-convo-badge { + background: var(--accent); + color: var(--bg, #000); + font-size: 0.72em; + font-weight: 700; + min-width: 20px; + height: 20px; + border-radius: 10px; + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0 5px; + flex-shrink: 0; +} + +/* ── Thread ──────────────────────────────────────────────── */ +.dm-thread { + flex: 1; + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 8px; + padding: 15px; + min-height: 200px; + max-height: calc(100vh - 420px); +} + +/* ── Message bubbles ─────────────────────────────────────── */ +.dm-msg { + display: flex; + flex-direction: column; + max-width: 75%; + gap: 3px; +} + +.dm-msg-mine { + align-self: flex-end; + align-items: flex-end; +} + +.dm-msg-theirs { + align-self: flex-start; + align-items: flex-start; +} + +.dm-bubble { + padding: 9px 13px; + border-radius: 14px; + font-size: 0.92em; + line-height: 1.45; + word-break: break-word; +} + +/* Full-width when message contains a block embed (YouTube etc.) */ +.dm-msg.dm-has-embed { + width: 90%; + min-width: 280px; + max-width: 90%; +} + +.dm-bubble .yt-embed-wrap { + max-width: 100%; + margin: 4px 0 0; + min-width: 400px; +} + +@media (max-width: 700px) { + .dm-bubble .yt-embed-wrap { + min-width: 300px; + } +} + +.dm-msg-mine .dm-bubble { + background: var(--badge-bg); + color: var(--white); + border-bottom-right-radius: 4px; +} + +.dm-msg-theirs .dm-bubble { + background: var(--bg2, #222); + color: var(--fg, #ddd); + border: 1px solid #333; + border-bottom-left-radius: 4px; +} + +.dm-msg-time { + font-size: 0.72em; + color: #555; + padding: 0 4px; +} + +.dm-unreadable { + font-style: italic; + opacity: 0.5; + font-size: 0.88em; +} + +/* ── Send form ───────────────────────────────────────────── */ +.dm-send-form { + gap: 8px; + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid #333; + flex-shrink: 0; +} + +.dm-input { + flex: 1; + background: var(--bg2, #1a1a1a); + border: 1px solid #444; + color: var(--fg, #ddd); + border-radius: 8px; + padding: 10px 12px; + font-size: 0.92em; + resize: none; + min-height: 44px; + max-height: 140px; + font-family: inherit; + transition: border-color 0.15s; + line-height: 1.4; +} + +.dm-input:focus { + outline: none; + border-color: var(--accent); +} + +.dm-send-btn { + background: var(--accent); + color: var(--bg, #000); + border: none; + border-radius: 8px; + padding: 10px 18px; + font-weight: 700; + cursor: pointer; + font-size: 0.9em; + height: 44px; + flex-shrink: 0; + transition: opacity 0.15s; +} + +.dm-send-btn:hover { opacity: 0.85; } +.dm-send-btn:disabled { opacity: 0.4; cursor: not-allowed; } + +/* ── State messages ──────────────────────────────────────── */ +.dm-loading, .dm-empty, .dm-error { + color: #666; + font-size: 0.9em; + padding: 24px; + text-align: center; + line-height: 1.6; +} + +.dm-error { color: #e06c6c; } + +/* ── Key Manager Modal ───────────────────────────────────── */ +.dm-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.75); + z-index: 15000; + display: flex; + align-items: center; + justify-content: center; + padding: 16px; +} + +.dm-modal { + background: var(--bg, #111); + border: 1px solid var(--accent); + border-radius: 10px; + width: 100%; + max-width: 480px; + max-height: 90vh; + overflow-y: auto; + padding: 28px; + position: relative; +} + +.dm-modal h2 { + margin: 0 0 6px; + font-size: 1.1em; + color: var(--accent); +} + +.dm-modal-sub { + font-size: 0.85em; + color: #888; + margin: 0 0 18px; + line-height: 1.5; +} + +.dm-modal-close { + position: absolute; + top: 14px; + right: 16px; + background: none; + border: none; + color: #888; + font-size: 1.4em; + cursor: pointer; + line-height: 1; +} + +.dm-modal-close:hover { color: var(--fg, #ddd); } + +.dm-key-status { + background: rgba(255,255,255,0.04); + border-radius: 6px; + padding: 10px 12px; + font-size: 0.85em; + margin-bottom: 18px; + border: 1px solid #333; +} + +.dm-key-section { + margin-bottom: 20px; + padding-bottom: 20px; + border-bottom: 1px solid #2a2a2a; +} + +.dm-key-section:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; } + +.dm-key-section h3 { + font-size: 0.9em; + margin: 0 0 6px; + color: var(--fg, #ccc); +} + +.dm-key-section p { + font-size: 0.82em; + color: #777; + margin: 0 0 10px; + line-height: 1.5; +} + +.dm-key-input { + display: block; + width: 100%; + background: var(--bg2, #1a1a1a); + border: 1px solid #444; + color: var(--fg, #ddd); + border-radius: 6px; + padding: 8px 10px; + font-size: 0.88em; + margin-bottom: 8px; + box-sizing: border-box; + font-family: inherit; +} + +.dm-key-input:focus { outline: none; border-color: var(--accent); } + +.dm-key-btn { + background: var(--accent); + color: var(--bg, #000); + border: none; + border-radius: 6px; + padding: 8px 16px; + font-weight: 600; + cursor: pointer; + font-size: 0.86em; + transition: opacity 0.15s; +} + +.dm-key-btn:hover { opacity: 0.85; } + +.dm-key-btn-danger { + background: #d94f4f; + color: #fff; +} + +.dm-key-danger h3 { color: #d94f4f; } + +.dm-key-msg { + font-size: 0.82em; + margin-top: 8px; + min-height: 18px; +} + +.dm-msg-ok { color: #5cb85c; } +.dm-msg-err { color: #e06c6c; } + +/* ── Blocking modal (seed-phrase setup / recovery) ───────── */ +.dm-modal-blocking { + z-index: 20000; /* above everything */ +} +.dm-modal-blocking .dm-modal { + max-width: 540px; +} + +/* ── Seed word grid ──────────────────────────────────────── */ +.dm-seed-grid, +.dm-recovery-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 8px; + margin: 16px 0; +} + +.dm-seed-word { + display: flex; + align-items: center; + gap: 8px; + background: rgba(255,255,255,0.05); + border: 1px solid #333; + border-radius: 6px; + padding: 8px 10px; + font-size: 0.9em; +} + +.dm-seed-num { + color: #555; + font-size: 0.78em; + min-width: 16px; + text-align: right; + flex-shrink: 0; +} + +.dm-seed-val { + color: var(--accent); + font-weight: 600; + font-family: monospace; + letter-spacing: 0.03em; + word-break: break-all; +} + +/* Recovery inputs reuse .dm-seed-word layout */ +.dm-seed-input-wrap input.dm-recovery-word { + flex: 1; + background: none; + border: none; + outline: none; + color: var(--fg, #ddd); + font-size: 0.88em; + font-family: monospace; + padding: 0; + width: 100%; + min-width: 0; +} + +.dm-seed-input-wrap:focus-within { + border-color: var(--accent); +} + +/* Copy / primary button */ +.dm-key-btn-primary { + background: var(--accent); + color: var(--bg, #000); + width: 100%; + margin-top: 4px; +} + +.dm-key-btn-primary:disabled { + opacity: 0.35; + cursor: not-allowed; +} + +.dm-copy-seed { + width: 100%; + background: rgba(255,255,255,0.07); + color: var(--fg, #ddd); + border: 1px solid #444; + margin-bottom: 14px; +} + +/* Forced-confirm checkbox */ +.dm-seed-confirm { + background: rgba(255,200,80,0.08); + border: 1px solid rgba(255,200,80,0.3); + border-radius: 6px; + padding: 12px 14px; + margin-bottom: 14px; +} + +.dm-seed-confirm label { + display: flex; + align-items: flex-start; + gap: 10px; + cursor: pointer; + font-size: 0.87em; + color: #ffc850; + line-height: 1.4; +} + +.dm-seed-confirm input[type="checkbox"] { + margin-top: 2px; + flex-shrink: 0; + accent-color: var(--accent); + width: 16px; + height: 16px; +} + +@media (max-width: 500px) { + .dm-seed-grid, .dm-recovery-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +/* ── Navbar DM icon ──────────────────────────────────────── */ +#nav-dm-btn { + position: relative; + display: inline-flex; + align-items: center; + gap: 3px; +} + +/* reuse .notif-count styling for dm badge — already defined */ + +/* ── Mobile adjustments ──────────────────────────────────── */ +@media (max-width: 600px) { + .messages-page { padding: 10px; } + .dm-msg { max-width: 90%; } + .dm-thread { max-height: calc(100vh - 240px); } + .dm-modal { padding: 20px; } +} + +/* ══════════════════════════════════════════════════════════ + Full-height chat layout — conversation page + ══════════════════════════════════════════════════════════ */ + +/* ── DESKTOP (> 768px): same pattern as the sidebar ─────── + position:fixed takes it out of flow entirely — no pagewrapper + padding, no body scroll involvement, just viewport-anchored. */ +@media (min-width: 769px) { + .messages-convo-page { + position: fixed !important; + top: var(--navbar-h, 50px) !important; + bottom: 0 !important; + left: 0 !important; + right: 0 !important; + z-index: 1; + display: flex !important; + flex-direction: column !important; + } + + /* Kill the layout-legacy pagewrapper min-height:100vh that causes body overflow */ + body:has(.messages-convo-page) .pagewrapper { + min-height: 0 !important; + overflow: hidden; + } +} + +/* ── MOBILE (≤ 768px): position:fixed pagewrapper ───────── + Works on mobile; locks the pagewrapper to the viewport. */ +@media (max-width: 768px) { + html:has(.messages-convo-page), + body:has(.messages-convo-page) { + height: 100%; + overflow: hidden; + } + + body:has(.messages-convo-page) .pagewrapper { + position: fixed; + top: var(--navbar-h, 50px); + left: 0; + right: 0; + bottom: 0; + overflow: hidden; + padding: 0; + display: flex; + flex-direction: column; + } +} + +/* ── Shared: convo pane is a flex column ────────────────── + Both viewports share this — desktop gets explicit height + above, mobile gets height from fixed pagewrapper. */ +.messages-convo-page { + overflow: hidden; + padding: 0; + max-width: 720px; + width: 100%; + margin: 0 auto; + display: flex; + flex-direction: column; + /* flex:1 + min-height:0 for mobile where parent is fixed pagewrapper */ + flex: 1; + min-height: 0; +} + +/* Header: never shrinks */ +.messages-convo-page .messages-header { + flex-shrink: 0; + padding: 10px 16px; + margin-bottom: 0; + border-bottom: 1px solid var(--border, #333); +} + +/* Key notice banner if visible */ +.messages-convo-page #dm-key-notice { + flex-shrink: 0; +} + +/* Thread: grows + scrolls — min-height:0 is critical */ +.messages-convo-page .dm-thread { + flex: 1; + min-height: 0; + max-height: none; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding: 12px 16px; +} + +/* Input bar: never shrinks */ +.messages-convo-page .dm-send-form { + flex-shrink: 0; + margin-top: 0; + padding: 10px 16px; + padding-bottom: calc(10px + env(safe-area-inset-bottom, 0px)); + border-top: 1px solid #333; +} + + +.notif-count-dm { + position: absolute; + top: -7px; + right: -5px; + background: var(--badge-nsfw); + color: white; + font-size: 10px; + padding: 2px 2px; + border-radius: 3px; + line-height: 1; +} + +.dm-bubble .emoji { + max-width: 200px; +} + +/* Köpfe — random corner image */ +#koepfe-img { + position: fixed; + bottom: 0; + left: 0; + z-index: -1; + max-height: 200px; + opacity: 0; + transition: opacity 0.3s ease; + user-select: none; + -webkit-user-drag: none; + will-change: opacity; + pointer-events: none; +} +#koepfe-img.visible { + opacity: 1; +} + +.mode-filter { + display: grid; + justify-content: center; + margin: 20px; + grid-template-rows: 1fr; + grid-template-columns: auto 1fr; +} + +#nav_excluded_tags_list { + margin-bottom: 15px; + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + max-width: 100%; +} +.nav-exclude { + width: 100%; + max-width: 600px; + margin: 0 auto; + position: relative; +} + +#nav_exclude_tag_input { + width: 100%; + background: rgba(255,255,255,0.05); + border: 1px solid var(--nav-border-color); + color: var(--white); + padding: 10px; +} + +#nav_exclude_suggestions { + display: none; position: absolute; bottom: 100%; left: 0; right: 0; background: #1e1e1e; border: 1px solid rgba(255,255,255,0.1); box-shadow: 0 -4px 12px rgba(0,0,0,0.5); max-height: 200px; overflow-y: auto; z-index: 1000; +} + +.nav-mime-filter { + position: relative; + display: grid; + grid-template-rows: 1fr; + grid-template-columns: 1fr 1fr 1fr; +} +/* ── Meme Pages (Critical) ────────────────────────────── */ +.meme-select-container, .meme-creator-container { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 5vh 20px 20px; /* matched to .container standards */ + position: relative; + z-index: 10; +} + +.meme-header { + margin-bottom: 25px; + border-bottom: 1px solid var(--accent, #9f0); + padding-bottom: 15px; +} + +.meme-title { + font-family: var(--nav-brand-font, 'VCR'), monospace; + color: var(--accent, #9f0); + text-transform: uppercase; + margin: 0; +} + +.meme-subtitle { + font-family: var(--font, monospace); + color: #888; + margin: 5px 0 20px 0; +} + +.category-filter-bar { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 30px; + border-bottom: 1px solid rgba(255,255,255,0.05); + padding-bottom: 20px; +} + +.category-chip { + background: var(--nav-bg, #2b2b2b); + border: 1px solid var(--nav-border-color, rgba(255, 255, 255, .05)); + color: var(--white, #fff); + padding: 6px 14px; + border-radius: 20px; + font-family: var(--font, monospace); + font-size: 0.85em; + cursor: pointer; +} + +.category-chip.active { + background: var(--accent, #9f0); + color: var(--black, #000); + border-color: var(--accent, #9f0); +} + +.template-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 15px; +} + +.anim { + margin: 0; + animation: color 0.6s linear infinite !important; +} + +@keyframes color { + 0% { + color: red; + } + 25% { + color: yellow; + } + 50% { + color: #1FB2B0; + } + 75% { + color: #23dd06; + } + 100% { + color: red; + } +} + +.anim-boxshadow { + margin: 0; + animation: boxshadow 0.6s linear infinite !important; +} + +@keyframes boxshadow { + 0% { + box-shadow: 0 0 0 2px red; + } + 25% { + box-shadow: 0 0 0 2px yellow; + } + 50% { + box-shadow: 0 0 0 2px #1FB2B0; + } + 75% { + box-shadow: 0 0 0 2px #23dd06; + } + 100% { + box-shadow: 0 0 0 2px red; + } +} + +body.layout-modern .gapRight { + justify-content: center; +} + +body.layout-modern .gapRight > svg.iconset, +body.layout-modern .gapRight > i.iconset { + height: 30px; + width: 30px; + margin: 5px; + padding: 5px; + right: 0px; + background: var(--badge-tag); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 18px; + box-sizing: border-box; +} + +body.layout-legacy .gapRight { + justify-content: center; +} + +body.layout-legacy .gapRight > svg.iconset, +body.layout-legacy .gapRight > i.iconset { + height: 30px; + width: 30px; + margin: 5px; + padding: 5px; + right: 0px; + background: var(--badge-tag); + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 18px; + box-sizing: border-box; +} + +body.layout-legacy .gapRight > .iconset#a_favo { + fill: #ff009b; +} +/* OC Toggle Icon - now i element, color inherited from .gapRight i.iconset */ +i.iconset#a_oc { + color: #fff !important; + fill: #fff !important; +} + +/* OC Feature */ +.oc-badge { + background: #009fff; + color: #000; + padding: 2px 6px; + border-radius: 4px; + font-size: 0.75rem; + font-weight: bold; + box-shadow: 0 0 5px rgba(0, 0, 0, 0.4); + user-select: none; + font-family: 'VCR'; +} + +.oc-badge svg.iconset-oc { + width: 14px; + height: 14px; +} + +.oc-option { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + user-select: none; +} + +.oc-option input[type="checkbox"] { + cursor: pointer; + width: 16px; + height: 16px; +} + +.oc-label { + font-weight: 500; + color: var(--white); + opacity: 0.9; +} + +.oc-label i { + color: #ffd700; + margin-right: 4px; +} + +/* Premium Responsive Table System */ +.responsive-table { + width: 100%; + border-collapse: collapse; + color: var(--white); +} + +.responsive-table th, +.responsive-table td { + padding: 12px; + text-align: left; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +@media (max-width: 768px) { + .responsive-table, + .responsive-table thead, + .responsive-table tbody, + .responsive-table th, + .responsive-table td, + .responsive-table tr { + display: block; + } + + /* Hide table headers (but keep for accessibility) */ + .responsive-table thead tr { + position: absolute; + top: -9999px; + left: -9999px; + } + + .responsive-table tr { + margin-bottom: 30px; + background: linear-gradient(145deg, rgba(255, 255, 255, 0.03) 0%, rgba(255, 255, 255, 0.01) 100%); + border: 1px solid rgba(255, 255, 255, 0.08); + border-radius: 16px; + padding: 0; + box-shadow: 0 8px 32px rgba(0,0,0,0.3); + overflow: hidden; + backdrop-filter: blur(8px); + -webkit-backdrop-filter: blur(8px); + } + + .responsive-table td { + border: none; + position: relative; + padding: 12px 15px 12px 42% !important; /* Slightly reduced labels width for more content space */ + min-height: 48px; + display: flex; + align-items: center; + justify-content: flex-start; + flex-wrap: nowrap; /* Prevent wrapping inside standard cells */ + gap: 12px; + text-align: left !important; + border-bottom: 1px solid rgba(255, 255, 255, 0.03); + box-sizing: border-box; + } + + /* Highlight the first cell (User Info) as a card header */ + .responsive-table td:first-child { + background: rgba(255, 255, 255, 0.06); + padding: 20px 20px !important; + padding-left: 20px !important; /* Explicit reset to fix "cramped to right" bug */ + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + margin-bottom: 5px; + display: block; /* Ensure it takes full width for the header style */ + width: 100%; + box-sizing: border-box; + } + + .responsive-table td:first-child::before { + display: none !important; /* Force hide label */ + } + + .user-info-cell { + width: 100%; + justify-content: flex-start; + } + + .responsive-table td::before { + content: attr(data-label); + position: absolute; + left: 15px; + width: 35%; + white-space: nowrap; + font-weight: 800; + color: var(--accent); + font-size: 0.65em; + text-transform: uppercase; + letter-spacing: 1px; + opacity: 0.8; + } + + /* Special handling for the Actions cell */ + .responsive-table td[data-label="Actions"] { + background: rgba(0, 0, 0, 0.2); + padding: 15px !important; + margin-top: 10px; + display: block; + border-bottom: none; + } + + .responsive-table td[data-label="Actions"]::before { + position: static; + display: block; + width: 100%; + margin-bottom: 12px; + text-align: center; + } + + .user-actions-row { + display: grid !important; + grid-template-columns: repeat(2, 1fr); + gap: 10px !important; + width: 100%; + justify-content: stretch !important; + } + + .user-actions-row button { + margin: 0 !important; + width: 100%; + justify-content: center; + font-size: 0.8em; + padding: 10px 5px !important; + } + + /* Fix for ghost users/incomplete rows */ + .responsive-table td:empty { + display: none; + } +} + +.admin-header-flex { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 35px; + flex-wrap: wrap; + gap: 15px; + padding: 0 10px; +} + +@media (max-width: 600px) { + .admin-header-flex { + flex-direction: column; + align-items: center; + text-align: center; + } + .admin-header-flex h2 { + margin-bottom: 8px !important; + font-size: 1.8em; + } + .admin-header-flex button { + width: 100%; + max-width: 300px; + } +} + +/* profile stuff */ + +.profile_head { + display: grid; + grid-template-columns: auto 1fr; + background: var(--nav-bg); +} + +.profile_head_avatar { + display: grid; + align-items: normal; + padding: 5px; +} + +.layersoffear { + display: grid; + grid-template-columns: auto; +} + +.profile_head_user_stats { + display: grid; + grid-auto-flow: column; + grid-auto-columns: max-content; +} + +.stat-id, .stat-joined, .stat-comments, .stat-tags, .stat-halls { + padding: 5px; +} + +.uploads-header, .favs-header { + background: var(--black); + padding: 5px; +} + + + +@media (max-width: 700px) { + .profile_head_user_stats { + grid-auto-flow: row; + grid-auto-columns: 1fr !important; + grid-template-columns: 1fr 1fr; + } + + .user_content_wrapper { + grid-template-columns: auto; + grid-auto-flow: column; + grid-template-rows: auto auto; + } +} + +.setting-item { + display: grid; +} + +textarea#profile_description { + height: 7em; + background: var(--nav-bg); + color: var(--white); +} + +.profile-settings-actions { + padding-top: 10px; +} + +.profile_description { + font-size: 0.8em; + color: var(--white); + max-width: 500px; + word-wrap: break-word; +} + +/* ============================================= + ENTENTEICH PROFILE STYLES + ============================================= */ + +/* When ENTEN profile is active, expand .blahlol to full metadata width */ +.blahlol:has(.ententeich-block) { + grid-column: 1 / -1; +} + +.ententeich-block { + display: flex; + gap: 10px; + background: rgba(0,0,0,0.2); + border: 1px solid var(--author-border, rgba(0, 255, 0, 0.2)); + line-height: 1; + padding: 5px; + align-items: flex-start; + width: 100%; + max-width: 100%; + box-sizing: border-box; + margin-bottom: 10px; +} + + +.enten-avatar { + flex-shrink: 0; +} + +.enten-avatar img { + width: 64px; + height: 64px; + object-fit: cover; +} + +.enten-info { + flex-grow: 1; + display: flex; + flex-direction: column; +} + +.enten-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 6px; +} + +.enten-username { + font-weight: 700; + color: var(--author-accent, inherit) !important; + text-decoration: none; +} + + +.enten-timestamp { + font-size: 0.8em; + color: #888; + letter-spacing: 0.5px; +} + +.enten-description { + font-size: 0.9em; + line-height: 1.5; + color: #ccc; + font-style: italic; + text-align: left; + padding-top: 4px; + border-top: 1px solid rgba(255, 255, 255, 0.05); +} + +/* ============================================= + xD Score Badge + ============================================= */ + +.xd-score-badge { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 3px 8px; + border-radius: 4px; + font-family: var(--font, monospace); + font-size: 0.82em; + font-weight: bold; + letter-spacing: 0.5px; + cursor: default; + user-select: none; + border: 1px solid transparent; + transition: filter 0.2s ease; +} + +.xd-score-badge:hover { + filter: brightness(1.15); +} + +/* xD num */ +.xd-score-num { + font-size: 0.85em; + opacity: 0.75; + font-weight: normal; +} + +/* Tier 1: 1–4 pts — muted lime */ +.xd-tier-1 { + background: #1a2e10; + color: #7ec850; + border-color: #3a5a20; +} + +/* Tier 2: 5–14 pts — yellow-green */ +.xd-tier-2 { + background: #2a2d00; + color: #c8d830; + border-color: #5a6400; +} + +/* Tier 3: 15–29 pts — orange */ +.xd-tier-3 { + background: #2d1800; + color: #e08030; + border-color: #6a3800; +} + +/* Tier 4: 30–59 pts — red-orange */ +.xd-tier-4 { + background: #2d0a00; + color: #e84020; + border-color: #7a2010; +} + +/* Tier 5: 60+ pts — neon, pulsing glow */ +.xd-tier-5 { + background: #1a0000; + color: #ff5500; + border-color: #ff3300; + box-shadow: 0 0 6px rgba(255, 80, 0, 0.5); + animation: xd-pulse 1.8s ease-in-out infinite; +} + +/* ─── xD score filter in filter modal ─────────────────────────────── */ +.nav-xd-filter { + margin-top: 14px; + padding-top: 12px; + border-top: 1px solid rgba(255, 255, 255, 0.07); +} + +.nav-xd-label { + display: block; + font-size: 0.8em; + text-transform: uppercase; + letter-spacing: 0.5px; + color: #888; + margin-bottom: 6px; +} + +.nav-xd-controls { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +} +@keyframes xd-pulse { + 0%,100% { + box-shadow: 0 0 5px rgba(255, 80, 0, 0.4), 0 0 12px rgba(255, 50, 0, 0.2); + } + 50% { + box-shadow: 0 0 10px rgba(255, 100, 0, 0.8), 0 0 22px rgba(255, 60, 0, 0.4); + } +} + +/* ─── xD Score Slider ──────────────────────────────────────────────── */ +.xd-slider { + -webkit-appearance: none; + appearance: none; + flex: 1; + height: 6px; + border-radius: 3px; + background: linear-gradient(to right, + var(--xd-fill, #4caf50) 0%, + var(--xd-fill, #4caf50) var(--xd-pct, 0%), + rgba(255,255,255,0.12) var(--xd-pct, 0%) + ); + outline: none; + cursor: pointer; + min-width: 120px; +} + +.xd-slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + border-radius: 50%; + background: #fff; + border: 2px solid var(--xd-fill, #4caf50); + box-shadow: 0 0 4px rgba(0,0,0,0.4); + cursor: pointer; + transition: border-color 0.2s, transform 0.1s; +} + +.xd-slider::-webkit-slider-thumb:hover { + transform: scale(1.2); +} + +.xd-slider::-moz-range-thumb { + width: 16px; + height: 16px; + border-radius: 50%; + background: #fff; + border: 2px solid var(--xd-fill, #4caf50); + cursor: pointer; +} + +.xd-slider-val { + display: inline-block; + min-width: 28px; + text-align: center; + font-family: monospace; + font-size: 0.9em; + font-weight: bold; + color: var(--xd-fill, #4caf50); + padding: 1px 5px; + border: 1px solid rgba(255,255,255,0.15); + border-radius: 4px; + background: rgba(0,0,0,0.2); +} + +/* ─── xD score wrapper ─────────────────────────────────────────────── */ +.xd-score-wrapper { + margin-top: 8px; + text-align: left; + padding: 5px; +} + + + +/* ─── xD score thumbnail indicator (position fix) ─────────────────── */ +.thumb-xd-indicator { + position: absolute; + bottom: 5px; + left: 5px; + padding: 1px 5px; + border-radius: 4px; + font-size: 0.65em; + font-weight: 700; + letter-spacing: 0.03em; + line-height: 1.4; + pointer-events: none; + background: rgba(0,0,0,0.6); + backdrop-filter: blur(4px); + color: #fff; + border: 1px solid rgba(255,255,255,0.2); + z-index: 12; +} +.thumb-xd-indicator.xd-tier-1 { color: #a8e6a3; border-color: #a8e6a3; } +.thumb-xd-indicator.xd-tier-2 { color: #ffe066; border-color: #ffe066; } +.thumb-xd-indicator.xd-tier-3 { color: #ffa94d; border-color: #ffa94d; } +.thumb-xd-indicator.xd-tier-4 { color: #ff6b6b; border-color: #ff6b6b; } +.thumb-xd-indicator.xd-tier-5 { color: #ff3cac; border-color: #ff3cac; } + + +/* Performance & Utility */ +.desktop-only { + display: inline-block !important; +} +.mobile-only { + display: none !important; +} + +@media (max-width: 768px) { + .desktop-only { + display: none !important; + } + .mobile-only { + display: block !important; + } +} + +#reports-table-body tr { + text-align: center; +} +/* Meta Suggestions */ +.meta-suggestions-list { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 10px; +} + +.meta-suggestion { + padding: 0.35rem 0.75rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 50px; + font-size: 0.85rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 0.5rem; + color: rgba(255, 255, 255, 0.8); + user-select: text; + max-width: 100%; + min-width: 0; +} + +.meta-suggestion span { + overflow-wrap: break-word; + word-break: break-word; + white-space: normal; + min-width: 0; + flex: 1; +} + +.meta-suggestion:hover { + background: var(--accent); + border-color: var(--accent); + color: #000 !important; + transform: translateY(-1px); +} + +.meta-suggestion i { + font-size: 0.7rem; + opacity: 0.6; +} + +.meta-suggestion.selected { + opacity: 0.4; + cursor: default; + pointer-events: none; + background: rgba(255, 255, 255, 0.02); + border-color: rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.4) !important; +} + +.meta-suggestion.selected i { + color: var(--accent); + opacity: 1; +} + +#metadata-modal .f0ck-modal-body { + max-height: 400px; + overflow-y: auto; +} + +/* Selection-edit popover for meta-suggestion pills */ +.sel-tag-popover { + position: fixed; + z-index: 99999; + display: flex; + align-items: center; + gap: 6px; + background: #1e1e2e; + border: 1px solid var(--accent, #9f0); + border-radius: 8px; + padding: 5px 8px; + box-shadow: 0 4px 20px rgba(0,0,0,0.6); + animation: sel-pop-in 0.12s ease; +} +@keyframes sel-pop-in { + from { opacity: 0; transform: translateY(4px) scale(0.96); } + to { opacity: 1; transform: translateY(0) scale(1); } +} +.sel-tag-popover input { + background: transparent; + border: none; + outline: none; + color: #fff; + font-size: 0.85em; + font-family: var(--font, monospace); + min-width: 80px; + max-width: 220px; + width: auto; +} +.sel-tag-popover-confirm { + background: var(--accent, #9f0); + color: #000; + border: none; + border-radius: 4px; + padding: 2px 8px; + font-size: 0.8em; + font-weight: bold; + cursor: pointer; + white-space: nowrap; +} +.sel-tag-popover-confirm:hover { filter: brightness(1.15); } + +/* ── Emoji Admin Card Grid ─────────────────────────────────────────── */ +.emoji-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + gap: 12px; + padding: 4px; +} +.emoji-card { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 16px 10px 12px; + background: var(--dropdown-bg); + border: 1px solid var(--nav-border-color); + border-radius: 6px; + transition: border-color .2s, box-shadow .2s, transform .15s; +} +.emoji-card:hover { + border-color: var(--accent); + box-shadow: 0 0 12px rgba(var(--accent-rgb, 100,100,255), .25); + transform: translateY(-2px); +} +.emoji-card .emoji-preview { + height: 48px; + max-width: 80px; + object-fit: contain; + image-rendering: auto; +} +.emoji-card .emoji-label { + font-family: monospace; + font-size: .85em; + color: var(--accent); + text-align: center; + word-break: break-all; + line-height: 1.2; +} +.emoji-card .emoji-url { + font-size: .65em; + opacity: .45; + text-align: center; + word-break: break-all; + line-height: 1.15; + max-height: 2.3em; + overflow: hidden; +} +.emoji-card .emoji-delete { + position: absolute; + top: 5px; + right: 5px; + width: 22px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + background: rgba(200, 0, 0, .85); + color: #fff; + border: none; + border-radius: 50%; + font-size: 13px; + line-height: 1; + cursor: pointer; + opacity: 0; + transition: opacity .15s; +} +.emoji-card:hover .emoji-delete { opacity: 1; } +.emoji-card .emoji-delete:hover { background: #e00; } + +/* ================================================================ + GLOBAL CHAT WIDGET — Facebook-style, bottom-right (left of sidebar) + ================================================================ */ + +#gchat-widget { + position: fixed; + bottom: 0; + right: calc(300px + 18px); + z-index: 950; + display: flex; + flex-direction: column; + align-items: flex-end; + pointer-events: none; + font-family: var(--font, monospace); + max-height: calc(100dvh - var(--navbar-h, 50px)); + overflow: hidden; +} + +body.sidebar-right-hidden #gchat-widget { + right: 18px; +} + +body.scroller-active #gchat-widget { + display: none !important; +} + +/* Reopen bubble — appears when chat is closed */ +#gchat-reopen-bubble { + position: fixed; + bottom: 18px; + right: 18px; + z-index: 9998; + width: 42px; + height: 42px; + border-radius: 50%; + border: none; + background: var(--color-primary, #2a2a2a); + color: #fff; + font-size: 1.1rem; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 2px 12px rgba(0,0,0,0.4); + opacity: 0.75; + transition: opacity 0.15s, transform 0.15s; +} +#gchat-reopen-bubble:hover { + opacity: 1; + transform: scale(1.08); +} +body.sidebar-right-hidden #gchat-reopen-bubble { + right: 18px; +} +body.scroller-active #gchat-reopen-bubble { + display: none !important; +} + +@media (max-width: 999px) { + #gchat-widget { + right: calc(280px + 14px); + } + body.sidebar-right-hidden #gchat-widget { + right: 14px; + } +} + +/* Chat panel — always visible */ +#gchat-panel { + pointer-events: all; + display: flex; + flex-direction: column; + width: 300px; + max-height: calc(100dvh - var(--navbar-h, 50px)); + background: var(--bg, #1a1a1a); + border: 1px solid var(--nav-border-color, rgba(255,255,255,0.08)); + box-shadow: 0 8px 32px rgba(0,0,0,0.65); + overflow: hidden; +} + +/* Float mode — detached from bottom-right, freely positioned */ +#gchat-panel.gchat-floating { + position: fixed; + right: auto; + bottom: auto; + max-height: calc(100dvh - var(--navbar-h, 50px)); +} + +/* Draggable header cursor in float mode */ +#gchat-panel.gchat-floating #gchat-header { + cursor: move; +} + +/* Minimized state — only header visible */ +#gchat-panel.gchat-minimized { + max-height: 42px; +} + +#gchat-panel.gchat-minimized #gchat-messages, +#gchat-panel.gchat-minimized #gchat-input-area { + display: none !important; +} + +/* Header */ +#gchat-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 10px; + height: 42px; + background: var(--nav-bg, #111); + border-bottom: 1px solid var(--nav-border-color, rgba(255,255,255,0.08)); + cursor: default; + user-select: none; + flex-shrink: 0; +} + +.gchat-header-btns { + display: flex; + align-items: center; + gap: 0; + flex-shrink: 0; +} + +#gchat-title { + font-size: 0.82em; + font-weight: bold; + color: var(--accent, #f2ef0b); + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 6px; +} + +.gchat-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + border-radius: 999px; + background: #e53935; + color: #fff; + font-size: 0.72em; + font-weight: 700; + line-height: 1; + letter-spacing: 0; + animation: gchat-badge-pop 0.2s ease; +} + +@keyframes gchat-badge-pop { + 0% { transform: scale(0.6); opacity: 0; } + 60% { transform: scale(1.2); } + 100% { transform: scale(1); opacity: 1; } +} + +.gchat-icon-btn { + background: none; + border: none; + color: rgba(255,255,255,0.5); + cursor: pointer; + padding: 4px 6px; + border-radius: 4px; + font-size: 12px; + transition: color 0.15s, background 0.15s; + line-height: 1; +} + +.gchat-icon-btn:hover { + color: #fff; + background: rgba(255,255,255,0.08); +} + +/* Messages area */ +/* Pinned topic bar */ +#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 { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + padding: 8px; + display: flex; + flex-direction: column; + gap: 6px; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-color, #333) transparent; + overscroll-behavior: contain; +} + +/* Single message row */ +.gchat-msg { + display: flex; + align-items: flex-end; + gap: 6px; + opacity: 0; + transform: translateY(6px); + transition: opacity 0.15s ease, transform 0.15s ease; + max-width: 100%; +} + +.gchat-msg.gchat-msg-in { + opacity: 1; + transform: translateY(0); +} + +.gchat-msg.gchat-msg-self { + flex-direction: row-reverse; +} + +.gchat-avatar-link { + flex-shrink: 0; + display: flex; + align-items: flex-end; +} + +.gchat-avatar { + width: 26px; + height: 26px; + border-radius: 50%; + object-fit: cover; + border: 1px solid rgba(255,255,255,0.1); +} + +.gchat-bubble-wrap { + display: flex; + flex-direction: column; + gap: 2px; + max-width: calc(100% - 36px); +} + +.gchat-msg-self .gchat-bubble-wrap { + align-items: flex-end; +} + +.gchat-username { + font-size: 0.7em; + font-weight: bold; + opacity: 0.85; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 180px; + color: var(--white, #fff); +} + +.gchat-bubble { + background: rgba(0, 0, 0, 0.98); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 14px 14px 14px 3px; + padding: 6px 10px; + font-size: 0.82em; + line-height: 1.4; + color: var(--white, #f0f0f0); + word-break: break-word; + white-space: pre-wrap; + max-width: 100%; +} + +.gchat-msg-self .gchat-bubble { + background: rgba(0, 0, 0, 0.98); + border-color: rgba(var(--accent-rgb, 242,239,11), 0.25); + border-radius: 14px 14px 3px 14px; + color: #fff; +} + +.gchat-time { + font-size: 0.65em; + opacity: 0.45; + white-space: nowrap; +} + +/* Greentext inside chat bubbles */ +.gchat-bubble .gchat-greentext { + color: #6abf69; + display: block; +} + +/* Spoiler/blur inside chat bubbles inherit existing styles */ +.gchat-bubble .spoiler, +.gchat-bubble .blur-text { + cursor: pointer; +} + +/* Emoji images inside chat bubbles */ +.gchat-bubble .emoji { + height: 10.4em; + vertical-align: middle; + display: inline-block; +} + +/* @mention links in chat */ +.gchat-bubble .mention { + color: var(--accent, #f2ef0b); + text-decoration: none; + font-weight: bold; +} +.gchat-bubble .mention:hover { text-decoration: underline; } + +/* Input area — stacked: toolbar on top, input row below */ +#gchat-input-area { + display: flex; + flex-direction: column; + border-top: 1px solid var(--nav-border-color, rgba(255,255,255,0.07)); + background: var(--nav-bg, #0d0d0d); + flex-shrink: 0; + position: relative; +} + +/* Toolbar row */ +#gchat-toolbar { + display: flex; + align-items: center; + gap: 4px; + padding: 4px 8px 2px; +} + +.gchat-tool-btn { + background: none; + border: none; + color: rgba(255,255,255,0.45); + cursor: pointer; + padding: 2px 6px; + border-radius: 4px; + font-size: 13px; + transition: color 0.15s, background 0.15s; + line-height: 1.3; + user-select: none; +} + +.gchat-tool-btn:hover { + color: #fff; + background: rgba(255,255,255,0.08); +} + +/* Input + send row */ +#gchat-input-row { + display: flex; + align-items: flex-end; + gap: 6px; + padding: 4px 8px 8px; +} + +#gchat-input { + flex: 1; + background: rgba(255,255,255,0.06); + border: 1px solid rgba(255,255,255,0.1); + color: var(--white, #fff); + font-family: var(--font, monospace); + font-size: 0.82em; + padding: 7px 12px; + resize: none; + outline: none; + line-height: 1.4; + transition: border-color 0.15s; + scrollbar-width: thin; +} + +#gchat-input:focus { + border-color: var(--accent, #f2ef0b); +} + +#gchat-input::placeholder { + opacity: 0.4; +} + +#gchat-send-btn { + width: 34px; + height: 34px; + border-radius: 50%; + border: none; + background: var(--accent, #f2ef0b); + color: var(--bg, #111); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + transition: transform 0.15s ease, box-shadow 0.15s ease; + flex-shrink: 0; +} + +#gchat-send-btn:hover { + transform: scale(1.1); + box-shadow: 0 2px 10px rgba(0,0,0,0.4); +} + +#gchat-send-btn:active { + transform: scale(0.92); +} + +/* Emoji picker grid (above input area, inside panel) */ +#gchat-emoji-picker { + display: none; + grid-template-columns: repeat(6, 1fr); + gap: 4px; + padding: 8px; + overflow-y: auto; + max-height: 140px; + background: var(--nav-bg, #111); + border-top: 1px solid var(--nav-border-color, rgba(255,255,255,0.08)); + 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 */ +#gchat-emoji-ac { + position: fixed; + z-index: 2000; + background: var(--bg, #1a1a1a); + border: 1px solid var(--nav-border-color, rgba(255,255,255,0.12)); + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0,0,0,0.6); + display: flex; + flex-direction: column; + overflow-y: auto; + max-height: 180px; + scrollbar-width: thin; +} + +.gchat-emoji-ac-item { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 10px; + cursor: pointer; + font-size: 0.82em; + color: rgba(255,255,255,0.8); + transition: background 0.1s; +} + +.gchat-emoji-ac-item:hover, +.gchat-emoji-ac-item.active { + background: rgba(255,255,255,0.08); + color: #fff; +} + +.gchat-emoji-ac-item img { + width: 22px; + height: 22px; + object-fit: contain; + flex-shrink: 0; +} + +@media (max-width: 999px) { + #gchat-widget { + right: calc(280px + 14px); + } + body.sidebar-right-hidden #gchat-widget { + right: 14px; + } +} + +/* @user mention autocomplete — portaled to body, always opens upward */ +#gchat-mention-ac { + position: fixed; + z-index: 2000; + background: var(--bg, #1a1a1a); + border: 1px solid var(--nav-border-color, rgba(255,255,255,0.12)); + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0,0,0,0.6); + display: flex; + flex-direction: column; + overflow-y: auto; + max-height: 200px; + scrollbar-width: thin; +} + +.gchat-mention-item { + display: flex; + align-items: center; + gap: 8px; + padding: 5px 10px; + cursor: pointer; + font-size: 0.82em; + color: rgba(255,255,255,0.8); + transition: background 0.1s; +} + +.gchat-mention-item:hover, +.gchat-mention-item.active { + background: rgba(255,255,255,0.08); + color: #fff; +} + +.gchat-mention-item img { + width: 22px; + height: 22px; + border-radius: 50%; + object-fit: cover; + flex-shrink: 0; +} + +.gchat-mention-name { + font-weight: bold; +} + +.gchat-mention-display { + opacity: 0.55; + margin-left: 4px; + font-size: 0.9em; +} + +/* Top-edge height resize handle */ +#gchat-resize-h { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 5px; + cursor: ns-resize; + z-index: 10; + user-select: none; + touch-action: none; +} + +/* Left-side width resize handle */ +#gchat-resize-w { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 5px; + cursor: ew-resize; + z-index: 10; + user-select: none; + touch-action: none; +} + +#gchat-resize-e { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 5px; + cursor: ew-resize; + z-index: 10; + user-select: none; + touch-action: none; +} + + +/* Panel needs relative positioning for the absolute left handle */ +#gchat-panel { + position: relative; +} + +/* Message action row — timestamp + reply + delete */ +.gchat-msg-actions { + display: flex; + align-items: center; + gap: 4px; + opacity: 0; + transition: opacity 0.15s; + margin-top: 2px; +} + +.gchat-msg:hover .gchat-msg-actions { + opacity: 1; +} + +.gchat-reply-btn, +.gchat-del-btn { + background: none; + border: none; + cursor: pointer; + padding: 2px 4px; + border-radius: 3px; + font-size: 10px; + line-height: 1; + transition: background 0.12s, color 0.12s; +} + +.gchat-reply-btn { + color: rgba(255,255,255,0.4); +} + +.gchat-reply-btn:hover { + color: var(--accent, #f2ef0b); + background: rgba(255,255,255,0.06); +} + +.gchat-del-btn { + color: rgba(255,100,100,0.5); +} + +.gchat-del-btn:hover { + color: #ff5555; + background: rgba(255,80,80,0.1); +} + +/* ── Chat embedded media ─────────────────────────────────────────────────── */ +.gchat-embed-img, +.gchat-embed-video, +.gchat-embed-audio, +.gchat-embed-yt { + display: block; + margin-top: 6px; + max-width: 100%; +} + +.gchat-embed-img img { + max-width: 100%; + max-height: 200px; + border-radius: 6px; + display: block; + object-fit: contain; + cursor: zoom-in; +} + +.gchat-embed-img img:hover { + opacity: 0.9; +} + +.gchat-embed-video video { + max-width: 100%; + max-height: 180px; + border-radius: 6px; + display: block; +} + +.gchat-embed-audio audio { + width: 100%; + height: 36px; +} + +/* Old iframe embed (no longer used, kept for safety) */ +.gchat-embed-yt { + position: relative; + width: 100%; + padding-bottom: 56.25%; + height: 0; + overflow: hidden; + border-radius: 6px; +} + +.gchat-embed-yt iframe { + position: absolute; + top: 0; left: 0; + width: 100%; + height: 100%; + border: none; +} + +/* ── YouTube oEmbed preview card ──────────────────────────────────────────── */ +.gchat-yt-card { + display: flex; + align-items: center; + gap: 10px; + margin-top: 6px; + border-radius: 8px; + overflow: hidden; + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.08); + text-decoration: none; + color: inherit; + transition: background 0.15s; + max-width: 320px; +} +.gchat-yt-card:hover { + background: rgba(255,255,255,0.1); +} +.gchat-yt-thumb-wrap { + position: relative; + flex-shrink: 0; + width: 110px; + height: 62px; + overflow: hidden; +} +.gchat-yt-thumb { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} +.gchat-yt-play { + position: absolute; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0,0,0,0.35); + font-size: 1.6rem; + color: #ff0000; + pointer-events: none; + transition: background 0.15s; +} +.gchat-yt-card:hover .gchat-yt-play { + background: rgba(0,0,0,0.55); +} +.gchat-yt-info { + flex: 1; + min-width: 0; + padding: 6px 8px 6px 0; + display: flex; + flex-direction: column; + gap: 2px; +} +.gchat-yt-title { + font-size: 0.78rem; + font-weight: 600; + line-height: 1.3; + color: #fff; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} +.gchat-yt-author { + font-size: 0.7rem; + opacity: 0.55; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +/* ── Chat image modal ──────────────────────────────────────────────────────── */ +#gchat-img-modal { + display: none; + position: fixed; + inset: 0; + z-index: 99999; + background: rgba(0,0,0,0.85); + align-items: center; + justify-content: center; + cursor: zoom-out; + animation: gchat-modal-fade 0.18s ease; +} +#gchat-img-modal.gchat-img-modal-open { + display: flex; +} + +@keyframes gchat-modal-fade { + from { opacity: 0; } + to { opacity: 1; } +} + +#gchat-img-modal-inner { + position: relative; + max-width: 92vw; + max-height: 92vh; + display: flex; + align-items: center; + justify-content: center; +} + +#gchat-img-modal-img { + max-width: 92vw; + max-height: 92vh; + object-fit: contain; + border-radius: 4px; + box-shadow: 0 8px 48px rgba(0,0,0,0.9); + cursor: default; +} + diff --git a/public/s/css/meme-creator.css b/public/s/css/meme-creator.css new file mode 100644 index 0000000..9195d2d --- /dev/null +++ b/public/s/css/meme-creator.css @@ -0,0 +1,262 @@ +@font-face { + font-family: 'Impact'; + src: url('/s/impact.woff') format('woff'); +} + +/* Meme Creator Styles */ + +.meme-creator-container { + padding: 20px; + max-width: 1200px; + width: 100%; + margin: 0 auto; + position: relative; + z-index: 10; +} + +.meme-header { + margin-bottom: 25px; + border-bottom: 1px solid var(--accent, #9f0); + padding-bottom: 15px; +} + +.meme-title { + font-family: var(--nav-brand-font, 'VCR'), monospace; + color: var(--accent, #9f0); + text-transform: uppercase; + margin: 0; +} + +.meme-subtitle { + font-family: var(--font, monospace); + color: #888; + margin: 5px 0 20px 0; +} + +/* Template Grid */ +.template-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: 15px; +} + +.template-item { + background: var(--nav-bg, #2b2b2b); + border: 1px solid var(--nav-border-color, rgba(255, 255, 255, .05)); + border-radius: 4px; + overflow: hidden; + text-decoration: none; + transition: transform 0.2s ease, border-color 0.2s ease; + display: flex; + flex-direction: column; +} + +.template-item:hover { + transform: translateY(-5px); + border-color: var(--accent, #9f0); +} + +.template-image-wrapper { + aspect-ratio: 1/1; + overflow: hidden; + background: #000; + display: flex; + align-items: center; + justify-content: center; +} + +.template-image-wrapper img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.template-info { + padding: 10px; + text-align: center; +} + +.template-name { + font-family: var(--font, monospace); + color: var(--white, #fff); + font-size: 0.9em; + display: block; + margin-bottom: 4px; +} + +.template-category-tag { + font-size: 0.7em; + color: var(--accent, #9f0); + opacity: 0.6; + text-transform: uppercase; + font-family: var(--font, monospace); +} + +/* Creator Layout - Simple Flexbox */ +.meme-editor-layout { + display: flex; + flex-direction: row; /* Col 1: Meme, Col 2: Controls */ + justify-content: center; + gap: 30px; + margin-top: 20px; + align-items: flex-start; +} + +.canvas-wrapper { + flex: 1; + min-width: 0; /* Allow shrinking */ + background: #000; + border: 2px solid var(--nav-bg, #2b2b2b); + display: flex; + align-items: center; + justify-content: center; + position: relative; + box-shadow: 0 0 20px rgba(0,0,0,0.5); + overflow: visible; +} + +canvas#memeCanvas { + max-width: 100%; + height: auto; + cursor: crosshair; + touch-action: none; + pointer-events: auto !important; + display: block; + position: relative; +} + +.meme-controls { + width: 380px; + flex-shrink: 0; + background: var(--nav-bg, #2b2b2b); + padding: 24px; + border-radius: 8px; + border: 1px solid var(--nav-border-color, rgba(255, 255, 255, .05)); + box-shadow: 0 4px 15px rgba(0,0,0,0.3); +} + +/* Form Styles — SCOPED to .meme-layout-wrapper to prevent global leakage */ +.meme-layout-wrapper .form-group { + margin-bottom: 20px; +} + +.meme-layout-wrapper .form-group label { + display: block; + font-family: var(--font, monospace); + color: var(--accent, #9f0); + margin-bottom: 8px; + font-size: 0.9em; + text-transform: uppercase; +} + +.meme-layout-wrapper .form-group textarea, +.meme-layout-wrapper .form-group input[type="text"], +.meme-layout-wrapper .form-group select { + width: 100%; + background: var(--black, #000); + border: 1px solid #444; + color: var(--white, #fff); + padding: 10px; + font-family: var(--font, monospace); + border-radius: 2px; + box-sizing: border-box; +} + +.meme-layout-wrapper .form-group textarea:focus, +.meme-layout-wrapper .form-group input[type="text"]:focus { + border-color: var(--accent, #9f0); + outline: none; +} + +.meme-layout-wrapper .checkbox-group { + display: flex; + gap: 15px; +} + +.meme-layout-wrapper .checkbox-group label { + display: flex; + align-items: center; + gap: 5px; + color: var(--white, #fff); + text-transform: none; + cursor: pointer; +} + +.meme-layout-wrapper input[type="range"] { + width: 100%; + accent-color: var(--accent, #9f0); +} + +.meme-layout-wrapper .layer-input-group { + border-bottom: 1px solid rgba(255,255,255,0.05); + padding-bottom: 15px; + margin-bottom: 20px; +} + +.meme-layout-wrapper .layer-input-group:last-child { + border-bottom: none; +} + +.meme-layout-wrapper .remove-layer:hover { + color: #ff0000 !important; + transform: scale(1.1); +} + +/* .btn — SCOPED to .meme-layout-wrapper to prevent global leakage into navbar */ +.meme-layout-wrapper .btn { + padding: 12px; + font-family: var(--font, monospace); + font-weight: bold; + text-transform: uppercase; + border: none; + cursor: pointer; + border-radius: 2px; + display: inline-block; + text-align: center; + text-decoration: none; + width: 100%; + box-sizing: border-box; + margin-bottom: 10px; +} + +.meme-layout-wrapper .btn-primary { + background: var(--accent, #9f0); + color: var(--black, #000); +} + +.meme-layout-wrapper .btn-secondary { + background: #444; + color: var(--white, #fff); +} + +/* Mobile Stacking - Simple 2-Row Layout */ +@media (max-width: 950px) { + .meme-editor-layout { + display: grid !important; + grid-template-columns: 1fr !important; + grid-template-rows: 0.6fr 1fr !important; + gap: 20px; + } + .meme-controls { + width: 100% !important; + grid-row: 2; + overflow: visible !important; + } + .canvas-wrapper { + width: 100% !important; + grid-row: 1; + margin-bottom: 10px; + overflow: visible !important; + height: auto !important; + } +} + +@media (max-width: 480px) { + .meme-creator-container, .meme-select-container { + padding: 10px; + } + .template-grid { + grid-template-columns: repeat(2, 1fr); + gap: 10px; + } +} diff --git a/public/s/css/upload.css b/public/s/css/upload.css new file mode 100644 index 0000000..637cd20 --- /dev/null +++ b/public/s/css/upload.css @@ -0,0 +1,816 @@ +/* Upload Page Styles */ +.upload-container { + max-width: 800px; + width: 100%; + padding: 0; + animation: uploadReveal 0.5s cubic-bezier(0.4, 0, 0.2, 1) forwards; + opacity: 0; + margin: 0 auto; +} + + +@keyframes uploadReveal { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.upload-container h2 { + margin-bottom: 0.5rem; + color: var(--accent); + text-align: center; +} + +/* Upload Limit Info */ +.upload-limit-info { + text-align: center; + margin-bottom: 1.5rem; + font-size: 0.9rem; + opacity: 0.7; +} + +.upload-limit-info i { + margin-right: 0.3rem; +} + +.limit-unlimited { + color: var(--accent); +} + +.limit-exhausted { + color: #ff6b6b; + font-weight: 600; + opacity: 1; +} + +.limit-remaining { + color: rgba(255, 255, 255, 0.7); +} + +/* Upload Form */ +.upload-form { + display: flex; + flex-direction: column; + gap: 1.5rem; + background: rgba(255, 255, 255, 0.02); + padding: 1rem; + border-radius: 0; + border: 1px solid rgba(255, 255, 255, 0.05); +} + +.form-section label { + display: block; + margin-bottom: 0.5rem; + font-weight: 500; +} + +.required { + color: #ff6b6b; +} + +/* Drop Zone */ +.drop-zone { + border: 2px dashed rgba(255, 255, 255, 0.2); + border-radius: 0; + padding: 5px; + text-align: center; + cursor: pointer; + transition: all 0.2s; + position: relative; + min-height: 200px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.drop-zone:hover, +.drop-zone.dragover { + border-color: var(--accent); + background: rgba(255, 255, 255, 0.02); +} + +.drop-zone input[type="file"] { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + cursor: pointer; +} + +.drop-zone-prompt { + color: rgba(255, 255, 255, 0.5); + pointer-events: none; + /* Let input handle clicks */ +} + +/* File Preview (Stacked) */ +.file-preview { + display: flex; + flex-direction: column; + /* Stacked */ + align-items: center; + gap: 1rem; + width: 100%; +} + +.preview-media { + max-width: 100%; + max-height: 500px; +} + +.file-preview video { + max-width: 100%; + max-height: 500px; + border-radius: 0; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + outline: none; + margin-bottom: 1rem; +} + +.file-meta-row { + display: flex; + align-items: center; + gap: 1rem; + width: 100%; + justify-content: center; +} + +.file-info { + display: flex; + gap: 1rem; + align-items: center; + background: rgba(0, 0, 0, 0.3); + padding: 0.5rem 1rem; + border-radius: 0; +} + +.file-name { + font-weight: 500; +} + +.file-size { + opacity: 0.6; + font-size: 0.9rem; +} + +.btn-remove { + background: #ff6b6b; + color: white; + border: none; + padding: 0.5rem 1.5rem; + border-radius: 0; + cursor: pointer; + font-weight: 600; + transition: background 0.2s; + /* remove margin-top as it's now in a flex row */ +} + +.btn-remove:hover { + background: #fa5252; +} + +/* Ratings */ +@media(max-width: 700px) { + .rating-options { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 10px; + } + + .rating-option:nth-child(3) { + grid-column: 1 / span 2; + } + + .tag-suggestions { + left: 0px; + right: 0px; + } + +} + +.rating-options { + display: flex; + gap: 1rem; + justify-content: center; +} + +.rating-option input { + display: none; +} + +.rating-label { + display: block; + padding: 0.1rem 2rem; + border-radius: 0; + border: 2px solid transparent; + transition: all 0.2s; + font-weight: 600; + text-align: center; + cursor: pointer; +} + +.rating-label.sfw { + background: rgba(6, 115, 24, 0.36); + border-color: rgba(19, 134, 38, 0.96); + color: #fff; + opacity: 0.5; +} + +.rating-label.sfw:hover { + background: #04200c9e; +} + +.rating-label.nsfw { + background: #cb009866; + border-color: rgb(255, 0, 227); + color: #f9f9f9; + opacity: 0.5; +} + +.rating-label.nsfw:hover { + background: rgba(69, 0, 45, 0.74); +} + +.rating-label.nsfl { + background: #2b01016e; + color: #f2f2f2; + border-color: #990000; + opacity: 0.5; +} + +.rating-label.nsfl:hover { + background: #130101; +} + +.rating-option input:checked + .rating-label.sfw { + background: rgba(6, 130, 8, 0.73); + border-color: #51cf66; + color: var(--white); + opacity: 1; +} + +.rating-option input:checked + .rating-label.nsfw { + background: #880059; + box-shadow: 0 0 0 2px var(--bg-form), 0 0 0 4px #cd0030; + color: var(--white); + opacity: 1; +} + +.rating-option input:checked + .rating-label.nsfl { + background: #500404; + box-shadow: 0 0 0 2px var(--bg-form), 0 0 0 4px #660000; + color: var(--white); + opacity: 1; +} + +/* Tags */ +.tag-input-container { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 0; + padding: 0.5rem; + display: flex; + flex-wrap: wrap; + position: relative; + gap: 0.5rem; + z-index: 10000 !important; +} + +.tags-list { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.tag-chip { + display: inline-flex; + align-items: center; + gap: 0.3rem; + background: var(--accent); + color: #000; + padding: 0.3rem 0.6rem; + border-radius: 0; + font-size: 0.9rem; + font-weight: 500; +} + +.tag-chip button { + background: none; + border: none; + color: inherit; + cursor: pointer; + padding: 0; + font-size: 1.1rem; + line-height: 1; +} + +.tag-input { + flex: 1; + min-width: 120px; + background: transparent; + border: none; + color: inherit; + padding: 0.5rem; + outline: none; +} + +.tag-count { + font-weight: normal; + font-size: 0.85rem; + opacity: 0.7; +} + +.tag-count.valid { + color: #51cf66; + font-weight: bold; + opacity: 1; +} + +/* Upload Comment */ +.upload-comment-input { + position: relative; +} + +.upload-comment { + width: 100%; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 0; + color: inherit; + padding: 0.6rem 0.8rem; + font-family: inherit; + font-size: 0.9rem; + resize: vertical; + min-height: 60px; + max-height: 200px; + outline: none; + transition: border-color 0.2s; + box-sizing: border-box; +} + +.upload-comment:focus { + border-color: var(--accent, #7c5cbf); +} + +.upload-comment::placeholder { + color: rgba(255, 255, 255, 0.3); +} + +.upload-comment-input .input-actions { + display: flex; + align-items: center; + gap: 5px; + margin-top: 4px; +} + +.upload-comment-input .emoji-picker { + position: absolute; + top: 100%; + left: 0; + z-index: 100; +} + +.upload-form .tag-suggestions { + position: absolute !important; + min-width: 220px !important; + max-width: 320px !important; + max-height: 260px !important; + overflow-y: auto !important; + background: var(--dropdown-bg, #1a1a1a) !important; + border: 1px solid var(--black, #000) !important; + border-radius: 6px !important; + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.6) !important; + margin-top: 8px !important; /* Spacing from input */ + display: none; /* Controlled by JS .style.display */ + z-index: 200000 !important; + animation: tagDropIn 0.15s ease-out !important; + scrollbar-width: thin !important; +} + +@keyframes tagDropIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +#upload-form .tag-suggestion-item { + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + padding: 10px 12px !important; + min-height: 44px !important; + cursor: pointer !important; + transition: background 0.12s !important; + box-sizing: border-box !important; + user-select: none !important; +} + +#upload-form .tag-suggestion-item:not(:last-child) { + border-bottom: 1px solid rgba(255, 255, 255, 0.06) !important; +} + +#upload-form .tag-suggestion-item:hover, +#upload-form .tag-suggestion-item.active { + background: rgba(255, 255, 255, 0.1) !important; +} + +#upload-form .tag-suggestion-name { + font-size: 14px !important; + color: var(--accent, #66d9ef) !important; + font-weight: 500 !important; +} + +#upload-form .tag-suggestion-meta { + font-size: 11px !important; + color: rgba(255, 255, 255, 0.35) !important; + margin-left: 12px !important; + white-space: nowrap !important; +} + +/* Submit Button */ +.btn-upload { + background: var(--accent); + color: #000; + border: none; + padding: 1rem 2rem; + border-radius: 0; + font-size: 1.1rem; + font-weight: 700; + cursor: pointer; + transition: all 0.2s; + width: 100%; +} + +.btn-upload:disabled { + background: rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.4); + cursor: not-allowed; +} + +.btn-upload:not(:disabled):hover { + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4); +} + +/* Progress */ +.upload-progress { + display: flex; + align-items: center; + gap: 1rem; + background: rgba(0, 0, 0, 0.2); + padding: 1rem; + border-radius: 0; +} + +.progress-bar { + flex: 1; + height: 10px; + background: rgba(255, 255, 255, 0.1); + border-radius: 0; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: var(--accent); + width: 0%; + transition: width 0.2s; +} + +.progress-text { + font-weight: bold; + font-family: monospace; +} + +.upload-status { + text-align: center; + padding: 1rem; + font-weight: 600; +} + +.upload-status.error { + color: #ff6b6b; +} + +.upload-status.success { + color: #51cf66; +} + +/* Login Required */ +.login-required { + text-align: center; + padding: 4rem 2rem; + border: 1px dashed rgba(255, 255, 255, 0.2); + border-radius: 0; +} + +.btn-login { + display: inline-block; + margin-top: 1rem; + padding: 0.75rem 2rem; + background: var(--accent); + color: #000; + text-decoration: none; + border-radius: 0; + font-weight: 700; +} + +/* Upload Mode Tabs */ +.upload-mode-tabs { + display: flex; + gap: 0; + margin-bottom: 1rem; + border: 1px solid rgba(255, 255, 255, 0.1); + overflow: hidden; +} + +.upload-mode-tab { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + background: transparent; + border: none; + color: rgba(255, 255, 255, 0.45); + font-size: 0.95rem; + font-weight: 600; + cursor: pointer; + transition: all 0.2s; + position: relative; +} + +.upload-mode-tab:first-child { + border-right: 1px solid rgba(255, 255, 255, 0.1); +} + +.upload-mode-tab:hover { + color: rgba(255, 255, 255, 0.7); + background: rgba(255, 255, 255, 0.03); +} + +.upload-mode-tab.active { + color: var(--accent); + background: rgba(255, 255, 255, 0.04); +} + +.upload-mode-tab.active::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + right: 0; + height: 2px; + background: var(--accent); +} + +.upload-mode-tab svg { + flex-shrink: 0; +} + +/* URL Upload */ +.url-input-container { + position: relative; +} + +.url-input-container input[type="url"] { + width: 100%; + background: rgba(255, 255, 255, 0.05); + border: 2px dashed rgba(255, 255, 255, 0.2); + color: #fff; + padding: 1.5rem 1rem; + font-size: 1rem; + outline: none; + transition: border-color 0.2s; + box-sizing: border-box; + text-align: center; +} + +.url-input-container input[type="url"]:focus { + border-color: var(--accent); +} + +.url-input-container input[type="url"]::placeholder { + color: rgba(255, 255, 255, 0.35); +} + +.url-type-badge { + display: none; /* shown via JS */ + margin-top: 6px; + padding: 5px 14px; + font-size: 0.78rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; + border-radius: 3px; + cursor: pointer; + pointer-events: auto; + width: fit-content; +} + +.url-type-badge.youtube { + background: #ff0000; + color: #fff; +} + +.url-type-badge.direct { + background: var(--accent); + color: #000; +} + +.url-type-badge.fetching { + background: rgba(0, 0, 0, 0.4); + color: var(--accent); + border: 1px solid var(--accent); + display: flex; + align-items: center; + gap: 8px; + padding: 3px 12px; +} + +.url-type-badge.success { + background: #2b8a3e; + color: #fff; +} + +.loading-spinner { + display: inline-block; + width: 10px; + height: 10px; + border: 2px solid rgba(255, 255, 255, 0.3); + border-radius: 50%; + border-top-color: var(--accent); + animation: metaSpin 0.8s linear infinite; +} + +@keyframes metaSpin { + to { transform: rotate(360deg); } +} +/* Metadata Sync Spinner */ +.sync-spinner { + display: none; + align-items: center; + gap: 8px; + font-size: 0.8rem; + color: var(--accent); + margin: 5px 0 5px 5px; + background: rgba(0, 0, 0, 0.4); + padding: 4px 10px; + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); + white-space: nowrap; +} + +.sync-spinner.active { + display: flex; + animation: spinnerReveal 0.3s ease-out; +} + +@keyframes spinnerReveal { + from { opacity: 0; transform: translateX(-5px); } + to { opacity: 1; transform: translateX(0); } +} + +.sync-spinner .spinner-icon { + width: 12px; + height: 12px; + border: 2px solid rgba(255, 255, 255, 0.2); + border-top-color: var(--accent); + border-radius: 50%; + animation: metaSpin 0.8s linear infinite; +} + +.meta-suggestions-container { + margin-top: 1rem; + padding: 0.75rem; + background: rgba(88, 101, 242, 0.05); + border: 1px dashed rgba(88, 101, 242, 0.2); + border-radius: 8px; +} + +.suggestions-header { + font-size: 0.75rem; + color: var(--accent); + margin-bottom: 0.5rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.meta-suggestions-list { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.meta-suggestion { + padding: 0.35rem 0.75rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 50px; + font-size: 0.85rem; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 0.5rem; + color: rgba(255, 255, 255, 0.8); +} + +.meta-suggestion:hover { + background: var(--accent); + border-color: var(--accent); + color: white !important; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(88, 101, 242, 0.3); +} + +.meta-suggestion i { + font-size: 0.7rem; + opacity: 0.6; +} + +.meta-suggestion:hover i { + opacity: 1; +} + +.meta-suggestion.selected { + opacity: 0.4; + cursor: default; + pointer-events: none; + background: rgba(255, 255, 255, 0.02); + border-color: rgba(255, 255, 255, 0.05); + color: rgba(255, 255, 255, 0.4) !important; +} + +.meta-suggestion.selected i { + color: var(--accent); + opacity: 1; +} + +/* GPS Privacy Warning Banner */ +.gps-privacy-warning { + display: flex; + align-items: center; + gap: 10px; + padding: 10px 14px; + margin: 8px 0; + background: rgba(255, 160, 0, 0.12); + border: 1px solid rgba(255, 160, 0, 0.4); + border-radius: 8px; + font-size: 0.85em; + color: #ffa500; + flex-wrap: wrap; +} +.gps-privacy-warning i { flex-shrink: 0; font-size: 1.1em; } +.gps-privacy-warning span { flex: 1; min-width: 160px; color: var(--text, #ccc); } +.gps-privacy-warning span strong { color: #ffa500; } +.gps-strip-btn { + background: #ffa500; + color: #000; + border: none; + border-radius: 5px; + padding: 4px 12px; + font-size: 0.85em; + font-weight: bold; + cursor: pointer; + white-space: nowrap; + transition: background 0.2s; +} +.gps-strip-btn:hover { background: #ffb733; } +.gps-strip-btn:disabled { opacity: 0.6; cursor: not-allowed; } +.gps-dismiss-btn { + background: transparent; + border: none; + color: #888; + cursor: pointer; + font-size: 1.2em; + padding: 0 4px; + line-height: 1; + margin-left: auto; +} +.gps-dismiss-btn:hover { color: #ccc; } +.gps-privacy-warning.gps-stripped { + background: rgba(76, 175, 80, 0.12); + border-color: rgba(76, 175, 80, 0.4); + color: #4caf50; +} +.gps-privacy-warning.gps-stripped i, +.gps-privacy-warning.gps-stripped span { color: #4caf50; } diff --git a/public/s/css/v0ck.css b/public/s/css/v0ck.css new file mode 100644 index 0000000..75f789c --- /dev/null +++ b/public/s/css/v0ck.css @@ -0,0 +1,495 @@ +.v0ck { + position: relative; + font-size: 0; + overflow: hidden; +/* background-color: #000;*/ + background-size: contain; + background-repeat: no-repeat; + background-position: center center; + touch-action: none; /* Prevent pull-to-refresh and scroll while interacting */ + z-index: 0; +} + +.v0ck video { + display: block; +} + +.v0ck.v0ck_fullscreen { + max-width: none; + max-height: none; + width: 100%; + height: 100%; + background-color: black; +} + +#main:fullscreen .v0ck.v0ck_fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100vw; + max-width: 100%; + height: 100vh; + z-index: 2147483647; + background-color: #000 !important; + background-size: contain; + background-repeat: no-repeat; + background-position: center center; + display: flex !important; + align-items: center; + justify-content: center; +} + +#main:fullscreen .v0ck.v0ck_fullscreen video { + width: 100%; + height: 100%; + object-fit: contain; + min-height: 100vh; +} + +/* Audio in fullscreen: hide the invisible audio element, show cover art via background */ +#main:fullscreen .v0ck.v0ck_fullscreen audio { + display: none; +} + +.v0ck_overlay { + pointer-events: none; + position: absolute; + z-index: 1; + top: 0; + left: 0; + height: 100%; + width: 100%; + display: none; +} + +.v0ck.v0ck_initial .v0ck_overlay { + display: block; + pointer-events: auto; + cursor: pointer; +} + +.v0ck_overlay>svg { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + height: 60px; + width: 60px; + filter: drop-shadow(0 0 9px var(--accent)); + stroke: var(--accent); + stroke-width: 20px; +} + +.v0ck_player_button { + background: none; + border: 0; + line-height: 1; + color: white; + text-align: center; + outline: 0; + padding: 8px 6px; /* Increased hit area height and horizontal spacing */ + margin: 0; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + min-width: 36px; + height: 40px; /* Consistent taller height for easier tapping */ +} + +.v0ck_fs_btn { + max-width: none; +} + +.v0ck_player_button.v0ck_tplay>svg { + height: 17px; +} + +.v0ck_player_button svg:hover { + filter: drop-shadow(0 0 9px var(--accent)); + fill: #000; + stroke: var(--accent); + stroke-width: 30px; +} + +.v0ck_hidden { + display: none; +} + +.v0ck_player_controls svg { + width: 20px; + height: 20px; + fill: #fff; + stroke: #fff; + cursor: pointer; +} + +.v0ck_player_controls { + display: flex; + position: absolute; + bottom: -1px; + width: 100%; + padding: 0; + align-items: center; + z-index: 2; + background: linear-gradient(0deg, rgba(0, 0, 0, 0.8) 20%, rgba(0, 0, 0, 0) 100%); + transition: opacity .3s, transform .3s; + flex-wrap: wrap; + transform: translateY(100%) translateY(-3px); +} + +.v0ck:hover .v0ck_player_controls, +.v0ck.v0ck_hover .v0ck_player_controls, +.v0ck.v0ck_swf_active .v0ck_player_controls { + transform: translateY(0); +} + +.v0ck:hover .v0ck_progress, +.v0ck.v0ck_hover .v0ck_progress, +.v0ck.v0ck_swf_active .v0ck_progress { + height: 8px; +} + + + +.v0ck_progress { + flex: 10; + position: relative; + display: flex; + flex-basis: 100%; + height: 5px; + transition: height 0.4s; + background: rgba(255, 255, 255, 0.1); + cursor: pointer; + overflow: hidden; +} + +.v0ck_progress_buffered { + position: absolute; + top: 0; + left: 0; + height: 100%; + background: rgba(255, 255, 255, 0.3); + width: 0%; + transition: width 0.2s; + z-index: 1; +} + +.v0ck_progress_filled { + height: 100%; + background: var(--accent); + position: relative; + z-index: 2; + flex-basis: 0%; +} + +.v0ck_player_controls>input[type="range"][name="volume"]::after { + position: absolute; + top: -2px; + text-shadow: 1px 1px 1px 0 rgba(0, 0, 0, 0.5); + font-size: 0.8em; +} + +.v0ck_volume_group { + display: flex; + align-items: center; + position: relative; +} + +.v0ck_volume_group input[type="range"][name="volume"] { + position: relative; + height: 5px; + margin: auto; + -webkit-appearance: none; + appearance: none; + overflow: hidden; + min-width: 0; + max-width: 0; + cursor: pointer; + border-radius: 0; + transition: min-width 0.3s, max-width 0.3s, opacity 0.3s; + flex: none; + opacity: 0; + pointer-events: none; +} + +@media (hover: hover) { + .v0ck_volume_group:hover input[type="range"][name="volume"] { + min-width: 50px; + max-width: 50px; + opacity: 1; + pointer-events: auto; + } +} + +@media (max-width: 600px) { + .v0ck_volume_group input[type="range"][name="volume"] { + display: none !important; + } +} + +.v0ck_player_controls>input[type=range]:focus { + outline: none; +} + +.v0ck_player_button.v0ck_playtime { + max-width: none; + cursor: default; + min-width: 100px; +} + +/* Volume/Gesture HUD */ +.v0ck_hud { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: rgba(0, 0, 0, 0.7); + color: #fff; + padding: 20px; + border-radius: 15px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s; + z-index: 100; + min-width: 100px; +} + +.v0ck_hud:not(.v0ck_hidden) { + opacity: 1; +} + +.v0ck_hud svg { + width: 40px; + height: 40px; + fill: #fff; + margin-bottom: 10px; +} + +.v0ck_hud_bar_container { + width: 100px; + height: 6px; + background: rgba(255, 255, 255, 0.2); + border-radius: 3px; + overflow: hidden; +} + +.v0ck_hud_bar { + height: 100%; + background: var(--accent); + width: 0%; +} + +.v0ck_volume_group input[type="range"][name="volume"]::-webkit-slider-runnable-track { + background-color: rgb(65, 65, 65); +} + +.v0ck_volume_group input[type="range"][name="volume"]::-moz-range-track { + height: 5px; + background-color: rgb(65, 65, 65); +} + +.v0ck_volume_group input[type="range"][name="volume"]::-webkit-slider-thumb { + -webkit-appearance: none; + background: var(--accent); + height: 5px; + width: 0.1px; + border: 0; + box-shadow: -100vw 0 0 100vw var(--accent); +} + +.v0ck_volume_group input[type="range"][name="volume"]::-moz-range-thumb { + background: var(--accent); + height: 5px; + width: 0.1px; + border: 0; + box-shadow: -100vw 0 0 100vw var(--accent); +} + +.v0ck.v0ck_no_transition .v0ck_player_controls, +.v0ck.v0ck_no_transition .v0ck_progress { + transition: none !important; +} + +/* Seek Marker Ripple */ +.v0ck_seek_marker { + position: absolute; + top: 0; + height: 100%; + width: 2px; + background: #fff; + opacity: 0; + pointer-events: none; + z-index: 3; +} + +.v0ck_seek_marker.active { + animation: v0ck-seek-ripple 0.6s ease-out; +} + +@keyframes v0ck-seek-ripple { + 0% { opacity: 0.8; transform: scaleX(1); } + 100% { opacity: 0; transform: scaleX(20); } +} + +/* Loading Spinner */ +.v0ck_loader { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 101; + pointer-events: none; +} + +.v0ck_loader div { + width: 40px; + height: 40px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-top: 4px solid #fff; + border-radius: 50%; + animation: v0ck-spin 0.8s linear infinite; +} + +@keyframes v0ck-spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* ── Danmaku overlay ───────────────────────────────────────── */ +.danmaku-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + pointer-events: none; + overflow-x: clip; + overflow-y: visible; + z-index: 1; + user-select: none; +} + +.danmaku-pill { + position: absolute; + left: 0; + white-space: normal; /* allow multiline for quotes */ + max-width: 70vw; /* prevent full-width wrapping */ + display: inline-flex; + flex-direction: column; /* stack lines vertically */ + align-items: flex-start; + padding: 2px 0; + font-family: var(--font, inherit); + font-size: 35px; + font-weight: 700; + line-height: 1.1; + gap: 0; + color: #fff; + /* Multi-layer outline shadow for readability over any background */ + text-shadow: + -1px -1px 0 #000, + 1px -1px 0 #000, + -1px 1px 0 #000, + 1px 1px 0 #000, + 0 0 6px rgba(0,0,0,0.9), + 0 0 12px rgba(0,0,0,0.6); + will-change: transform; + background: none; + border: none; + text-align: left; +} + +.danmaku-pill .dpill-text { + font-family: var(--font, inherit); + font-size: 35px; + font-weight: 700; + white-space: nowrap; +} + +/* Each plain text line inside a multiline pill */ +.danmaku-pill .dpill-line { + display: block; + white-space: nowrap; +} + +/* Quoted lines > rendered as greentext */ +.danmaku-pill .dpill-greentext { + color: #78b87a; + display: block; + white-space: nowrap; +} + +/* Spoiler inside a flying pill — black-on-black, click to reveal */ +.danmaku-pill .dpill-spoiler { + background: #000; + color: #000 !important; + cursor: pointer; + pointer-events: auto; + transition: color 0.15s ease, background 0.15s ease; + border-radius: 3px; + padding: 0 3px; + text-shadow: none; +} +/* Hide images (emojis, inline imgs) inside unrevealed spoiler */ +.danmaku-pill .dpill-spoiler img { + opacity: 0; + transition: opacity 0.15s ease; +} +.danmaku-pill .dpill-spoiler:hover, +.danmaku-pill .dpill-spoiler.revealed { + color: #fff !important; + background: rgba(255,255,255,0.15); + text-shadow: + -1px -1px 0 #000, 1px -1px 0 #000, + -1px 1px 0 #000, 1px 1px 0 #000; +} +.danmaku-pill .dpill-spoiler:hover img, +.danmaku-pill .dpill-spoiler.revealed img { + opacity: 1; +} + +/* Blur inside a flying pill — blurred text, click/hover to clear */ +.danmaku-pill .dpill-blur { + filter: blur(6px); + cursor: pointer; + pointer-events: auto; + transition: filter 0.25s ease; + border-radius: 3px; + padding: 0 3px; +} +.danmaku-pill .dpill-blur:hover, +.danmaku-pill .dpill-blur.revealed { + filter: none; +} + +/* Emoji inline image */ +.danmaku-pill .dpill-emoji { + height: 1.3em; + width: auto; + vertical-align: middle; + object-fit: contain; + display: inline-block; + margin: 0 1px; +} + +/* Inline image URL embedded in pill */ +.danmaku-pill .dpill-img { + max-height: 80px; + max-width: 120px; + width: auto; + height: auto; + vertical-align: middle; + object-fit: contain; + display: inline-block; + border-radius: 4px; + margin: 0 2px; +} + +@keyframes danmaku-fly { + from { transform: translateX(calc(100vw + 100%)); } + to { transform: translateX(calc(-100% - 200px)); } +} \ No newline at end of file diff --git a/public/s/fa/all.min.css b/public/s/fa/all.min.css new file mode 100644 index 0000000..02d8a03 --- /dev/null +++ b/public/s/fa/all.min.css @@ -0,0 +1,9 @@ +/*! + * Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + * Copyright 2024 Fonticons, Inc. + */ +.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-regular,.fa-solid,.fab,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-brands:before,.fa-regular:before,.fa-solid:before,.fa:before,.fab:before,.far:before,.fas:before{content:var(--fa)}.fa-classic,.fa-regular,.fa-solid,.far,.fas{font-family:"Font Awesome 6 Free"}.fa-brands,.fab{font-family:"Font Awesome 6 Brands"}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{animation-name:fa-beat;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{animation-name:fa-bounce;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{animation-name:fa-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{animation-name:fa-beat-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{animation-name:fa-flip;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{animation-name:fa-shake;animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{animation-name:fa-spin;animation-duration:var(--fa-animation-duration,2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{animation-name:fa-spin;animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{animation-delay:-1ms;animation-duration:1ms;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}}@keyframes fa-beat{0%,90%{transform:scale(1)}45%{transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-bounce{0%{transform:scale(1) translateY(0)}10%{transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{transform:scale(1) translateY(0)}to{transform:scale(1) translateY(0)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);transform:scale(1)}50%{opacity:1;transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-flip{50%{transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-shake{0%{transform:rotate(-15deg)}4%{transform:rotate(15deg)}8%,24%{transform:rotate(-18deg)}12%,28%{transform:rotate(18deg)}16%{transform:rotate(-22deg)}20%{transform:rotate(22deg)}32%{transform:rotate(-12deg)}36%{transform:rotate(12deg)}40%,to{transform:rotate(0deg)}}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}.fa-rotate-by{transform:rotate(var(--fa-rotate-angle,0))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} + +.fa-0{--fa:"\30"}.fa-1{--fa:"\31"}.fa-2{--fa:"\32"}.fa-3{--fa:"\33"}.fa-4{--fa:"\34"}.fa-5{--fa:"\35"}.fa-6{--fa:"\36"}.fa-7{--fa:"\37"}.fa-8{--fa:"\38"}.fa-9{--fa:"\39"}.fa-fill-drip{--fa:"\f576"}.fa-arrows-to-circle{--fa:"\e4bd"}.fa-chevron-circle-right,.fa-circle-chevron-right{--fa:"\f138"}.fa-at{--fa:"\40"}.fa-trash-alt,.fa-trash-can{--fa:"\f2ed"}.fa-text-height{--fa:"\f034"}.fa-user-times,.fa-user-xmark{--fa:"\f235"}.fa-stethoscope{--fa:"\f0f1"}.fa-comment-alt,.fa-message{--fa:"\f27a"}.fa-info{--fa:"\f129"}.fa-compress-alt,.fa-down-left-and-up-right-to-center{--fa:"\f422"}.fa-explosion{--fa:"\e4e9"}.fa-file-alt,.fa-file-lines,.fa-file-text{--fa:"\f15c"}.fa-wave-square{--fa:"\f83e"}.fa-ring{--fa:"\f70b"}.fa-building-un{--fa:"\e4d9"}.fa-dice-three{--fa:"\f527"}.fa-calendar-alt,.fa-calendar-days{--fa:"\f073"}.fa-anchor-circle-check{--fa:"\e4aa"}.fa-building-circle-arrow-right{--fa:"\e4d1"}.fa-volleyball,.fa-volleyball-ball{--fa:"\f45f"}.fa-arrows-up-to-line{--fa:"\e4c2"}.fa-sort-desc,.fa-sort-down{--fa:"\f0dd"}.fa-circle-minus,.fa-minus-circle{--fa:"\f056"}.fa-door-open{--fa:"\f52b"}.fa-right-from-bracket,.fa-sign-out-alt{--fa:"\f2f5"}.fa-atom{--fa:"\f5d2"}.fa-soap{--fa:"\e06e"}.fa-heart-music-camera-bolt,.fa-icons{--fa:"\f86d"}.fa-microphone-alt-slash,.fa-microphone-lines-slash{--fa:"\f539"}.fa-bridge-circle-check{--fa:"\e4c9"}.fa-pump-medical{--fa:"\e06a"}.fa-fingerprint{--fa:"\f577"}.fa-hand-point-right{--fa:"\f0a4"}.fa-magnifying-glass-location,.fa-search-location{--fa:"\f689"}.fa-forward-step,.fa-step-forward{--fa:"\f051"}.fa-face-smile-beam,.fa-smile-beam{--fa:"\f5b8"}.fa-flag-checkered{--fa:"\f11e"}.fa-football,.fa-football-ball{--fa:"\f44e"}.fa-school-circle-exclamation{--fa:"\e56c"}.fa-crop{--fa:"\f125"}.fa-angle-double-down,.fa-angles-down{--fa:"\f103"}.fa-users-rectangle{--fa:"\e594"}.fa-people-roof{--fa:"\e537"}.fa-people-line{--fa:"\e534"}.fa-beer,.fa-beer-mug-empty{--fa:"\f0fc"}.fa-diagram-predecessor{--fa:"\e477"}.fa-arrow-up-long,.fa-long-arrow-up{--fa:"\f176"}.fa-burn,.fa-fire-flame-simple{--fa:"\f46a"}.fa-male,.fa-person{--fa:"\f183"}.fa-laptop{--fa:"\f109"}.fa-file-csv{--fa:"\f6dd"}.fa-menorah{--fa:"\f676"}.fa-truck-plane{--fa:"\e58f"}.fa-record-vinyl{--fa:"\f8d9"}.fa-face-grin-stars,.fa-grin-stars{--fa:"\f587"}.fa-bong{--fa:"\f55c"}.fa-pastafarianism,.fa-spaghetti-monster-flying{--fa:"\f67b"}.fa-arrow-down-up-across-line{--fa:"\e4af"}.fa-spoon,.fa-utensil-spoon{--fa:"\f2e5"}.fa-jar-wheat{--fa:"\e517"}.fa-envelopes-bulk,.fa-mail-bulk{--fa:"\f674"}.fa-file-circle-exclamation{--fa:"\e4eb"}.fa-circle-h,.fa-hospital-symbol{--fa:"\f47e"}.fa-pager{--fa:"\f815"}.fa-address-book,.fa-contact-book{--fa:"\f2b9"}.fa-strikethrough{--fa:"\f0cc"}.fa-k{--fa:"\4b"}.fa-landmark-flag{--fa:"\e51c"}.fa-pencil,.fa-pencil-alt{--fa:"\f303"}.fa-backward{--fa:"\f04a"}.fa-caret-right{--fa:"\f0da"}.fa-comments{--fa:"\f086"}.fa-file-clipboard,.fa-paste{--fa:"\f0ea"}.fa-code-pull-request{--fa:"\e13c"}.fa-clipboard-list{--fa:"\f46d"}.fa-truck-loading,.fa-truck-ramp-box{--fa:"\f4de"}.fa-user-check{--fa:"\f4fc"}.fa-vial-virus{--fa:"\e597"}.fa-sheet-plastic{--fa:"\e571"}.fa-blog{--fa:"\f781"}.fa-user-ninja{--fa:"\f504"}.fa-person-arrow-up-from-line{--fa:"\e539"}.fa-scroll-torah,.fa-torah{--fa:"\f6a0"}.fa-broom-ball,.fa-quidditch,.fa-quidditch-broom-ball{--fa:"\f458"}.fa-toggle-off{--fa:"\f204"}.fa-archive,.fa-box-archive{--fa:"\f187"}.fa-person-drowning{--fa:"\e545"}.fa-arrow-down-9-1,.fa-sort-numeric-desc,.fa-sort-numeric-down-alt{--fa:"\f886"}.fa-face-grin-tongue-squint,.fa-grin-tongue-squint{--fa:"\f58a"}.fa-spray-can{--fa:"\f5bd"}.fa-truck-monster{--fa:"\f63b"}.fa-w{--fa:"\57"}.fa-earth-africa,.fa-globe-africa{--fa:"\f57c"}.fa-rainbow{--fa:"\f75b"}.fa-circle-notch{--fa:"\f1ce"}.fa-tablet-alt,.fa-tablet-screen-button{--fa:"\f3fa"}.fa-paw{--fa:"\f1b0"}.fa-cloud{--fa:"\f0c2"}.fa-trowel-bricks{--fa:"\e58a"}.fa-face-flushed,.fa-flushed{--fa:"\f579"}.fa-hospital-user{--fa:"\f80d"}.fa-tent-arrow-left-right{--fa:"\e57f"}.fa-gavel,.fa-legal{--fa:"\f0e3"}.fa-binoculars{--fa:"\f1e5"}.fa-microphone-slash{--fa:"\f131"}.fa-box-tissue{--fa:"\e05b"}.fa-motorcycle{--fa:"\f21c"}.fa-bell-concierge,.fa-concierge-bell{--fa:"\f562"}.fa-pen-ruler,.fa-pencil-ruler{--fa:"\f5ae"}.fa-people-arrows,.fa-people-arrows-left-right{--fa:"\e068"}.fa-mars-and-venus-burst{--fa:"\e523"}.fa-caret-square-right,.fa-square-caret-right{--fa:"\f152"}.fa-cut,.fa-scissors{--fa:"\f0c4"}.fa-sun-plant-wilt{--fa:"\e57a"}.fa-toilets-portable{--fa:"\e584"}.fa-hockey-puck{--fa:"\f453"}.fa-table{--fa:"\f0ce"}.fa-magnifying-glass-arrow-right{--fa:"\e521"}.fa-digital-tachograph,.fa-tachograph-digital{--fa:"\f566"}.fa-users-slash{--fa:"\e073"}.fa-clover{--fa:"\e139"}.fa-mail-reply,.fa-reply{--fa:"\f3e5"}.fa-star-and-crescent{--fa:"\f699"}.fa-house-fire{--fa:"\e50c"}.fa-minus-square,.fa-square-minus{--fa:"\f146"}.fa-helicopter{--fa:"\f533"}.fa-compass{--fa:"\f14e"}.fa-caret-square-down,.fa-square-caret-down{--fa:"\f150"}.fa-file-circle-question{--fa:"\e4ef"}.fa-laptop-code{--fa:"\f5fc"}.fa-swatchbook{--fa:"\f5c3"}.fa-prescription-bottle{--fa:"\f485"}.fa-bars,.fa-navicon{--fa:"\f0c9"}.fa-people-group{--fa:"\e533"}.fa-hourglass-3,.fa-hourglass-end{--fa:"\f253"}.fa-heart-broken,.fa-heart-crack{--fa:"\f7a9"}.fa-external-link-square-alt,.fa-square-up-right{--fa:"\f360"}.fa-face-kiss-beam,.fa-kiss-beam{--fa:"\f597"}.fa-film{--fa:"\f008"}.fa-ruler-horizontal{--fa:"\f547"}.fa-people-robbery{--fa:"\e536"}.fa-lightbulb{--fa:"\f0eb"}.fa-caret-left{--fa:"\f0d9"}.fa-circle-exclamation,.fa-exclamation-circle{--fa:"\f06a"}.fa-school-circle-xmark{--fa:"\e56d"}.fa-arrow-right-from-bracket,.fa-sign-out{--fa:"\f08b"}.fa-chevron-circle-down,.fa-circle-chevron-down{--fa:"\f13a"}.fa-unlock-alt,.fa-unlock-keyhole{--fa:"\f13e"}.fa-cloud-showers-heavy{--fa:"\f740"}.fa-headphones-alt,.fa-headphones-simple{--fa:"\f58f"}.fa-sitemap{--fa:"\f0e8"}.fa-circle-dollar-to-slot,.fa-donate{--fa:"\f4b9"}.fa-memory{--fa:"\f538"}.fa-road-spikes{--fa:"\e568"}.fa-fire-burner{--fa:"\e4f1"}.fa-flag{--fa:"\f024"}.fa-hanukiah{--fa:"\f6e6"}.fa-feather{--fa:"\f52d"}.fa-volume-down,.fa-volume-low{--fa:"\f027"}.fa-comment-slash{--fa:"\f4b3"}.fa-cloud-sun-rain{--fa:"\f743"}.fa-compress{--fa:"\f066"}.fa-wheat-alt,.fa-wheat-awn{--fa:"\e2cd"}.fa-ankh{--fa:"\f644"}.fa-hands-holding-child{--fa:"\e4fa"}.fa-asterisk{--fa:"\2a"}.fa-check-square,.fa-square-check{--fa:"\f14a"}.fa-peseta-sign{--fa:"\e221"}.fa-header,.fa-heading{--fa:"\f1dc"}.fa-ghost{--fa:"\f6e2"}.fa-list,.fa-list-squares{--fa:"\f03a"}.fa-phone-square-alt,.fa-square-phone-flip{--fa:"\f87b"}.fa-cart-plus{--fa:"\f217"}.fa-gamepad{--fa:"\f11b"}.fa-circle-dot,.fa-dot-circle{--fa:"\f192"}.fa-dizzy,.fa-face-dizzy{--fa:"\f567"}.fa-egg{--fa:"\f7fb"}.fa-house-medical-circle-xmark{--fa:"\e513"}.fa-campground{--fa:"\f6bb"}.fa-folder-plus{--fa:"\f65e"}.fa-futbol,.fa-futbol-ball,.fa-soccer-ball{--fa:"\f1e3"}.fa-paint-brush,.fa-paintbrush{--fa:"\f1fc"}.fa-lock{--fa:"\f023"}.fa-gas-pump{--fa:"\f52f"}.fa-hot-tub,.fa-hot-tub-person{--fa:"\f593"}.fa-map-location,.fa-map-marked{--fa:"\f59f"}.fa-house-flood-water{--fa:"\e50e"}.fa-tree{--fa:"\f1bb"}.fa-bridge-lock{--fa:"\e4cc"}.fa-sack-dollar{--fa:"\f81d"}.fa-edit,.fa-pen-to-square{--fa:"\f044"}.fa-car-side{--fa:"\f5e4"}.fa-share-alt,.fa-share-nodes{--fa:"\f1e0"}.fa-heart-circle-minus{--fa:"\e4ff"}.fa-hourglass-2,.fa-hourglass-half{--fa:"\f252"}.fa-microscope{--fa:"\f610"}.fa-sink{--fa:"\e06d"}.fa-bag-shopping,.fa-shopping-bag{--fa:"\f290"}.fa-arrow-down-z-a,.fa-sort-alpha-desc,.fa-sort-alpha-down-alt{--fa:"\f881"}.fa-mitten{--fa:"\f7b5"}.fa-person-rays{--fa:"\e54d"}.fa-users{--fa:"\f0c0"}.fa-eye-slash{--fa:"\f070"}.fa-flask-vial{--fa:"\e4f3"}.fa-hand,.fa-hand-paper{--fa:"\f256"}.fa-om{--fa:"\f679"}.fa-worm{--fa:"\e599"}.fa-house-circle-xmark{--fa:"\e50b"}.fa-plug{--fa:"\f1e6"}.fa-chevron-up{--fa:"\f077"}.fa-hand-spock{--fa:"\f259"}.fa-stopwatch{--fa:"\f2f2"}.fa-face-kiss,.fa-kiss{--fa:"\f596"}.fa-bridge-circle-xmark{--fa:"\e4cb"}.fa-face-grin-tongue,.fa-grin-tongue{--fa:"\f589"}.fa-chess-bishop{--fa:"\f43a"}.fa-face-grin-wink,.fa-grin-wink{--fa:"\f58c"}.fa-deaf,.fa-deafness,.fa-ear-deaf,.fa-hard-of-hearing{--fa:"\f2a4"}.fa-road-circle-check{--fa:"\e564"}.fa-dice-five{--fa:"\f523"}.fa-rss-square,.fa-square-rss{--fa:"\f143"}.fa-land-mine-on{--fa:"\e51b"}.fa-i-cursor{--fa:"\f246"}.fa-stamp{--fa:"\f5bf"}.fa-stairs{--fa:"\e289"}.fa-i{--fa:"\49"}.fa-hryvnia,.fa-hryvnia-sign{--fa:"\f6f2"}.fa-pills{--fa:"\f484"}.fa-face-grin-wide,.fa-grin-alt{--fa:"\f581"}.fa-tooth{--fa:"\f5c9"}.fa-v{--fa:"\56"}.fa-bangladeshi-taka-sign{--fa:"\e2e6"}.fa-bicycle{--fa:"\f206"}.fa-rod-asclepius,.fa-rod-snake,.fa-staff-aesculapius,.fa-staff-snake{--fa:"\e579"}.fa-head-side-cough-slash{--fa:"\e062"}.fa-ambulance,.fa-truck-medical{--fa:"\f0f9"}.fa-wheat-awn-circle-exclamation{--fa:"\e598"}.fa-snowman{--fa:"\f7d0"}.fa-mortar-pestle{--fa:"\f5a7"}.fa-road-barrier{--fa:"\e562"}.fa-school{--fa:"\f549"}.fa-igloo{--fa:"\f7ae"}.fa-joint{--fa:"\f595"}.fa-angle-right{--fa:"\f105"}.fa-horse{--fa:"\f6f0"}.fa-q{--fa:"\51"}.fa-g{--fa:"\47"}.fa-notes-medical{--fa:"\f481"}.fa-temperature-2,.fa-temperature-half,.fa-thermometer-2,.fa-thermometer-half{--fa:"\f2c9"}.fa-dong-sign{--fa:"\e169"}.fa-capsules{--fa:"\f46b"}.fa-poo-bolt,.fa-poo-storm{--fa:"\f75a"}.fa-face-frown-open,.fa-frown-open{--fa:"\f57a"}.fa-hand-point-up{--fa:"\f0a6"}.fa-money-bill{--fa:"\f0d6"}.fa-bookmark{--fa:"\f02e"}.fa-align-justify{--fa:"\f039"}.fa-umbrella-beach{--fa:"\f5ca"}.fa-helmet-un{--fa:"\e503"}.fa-bullseye{--fa:"\f140"}.fa-bacon{--fa:"\f7e5"}.fa-hand-point-down{--fa:"\f0a7"}.fa-arrow-up-from-bracket{--fa:"\e09a"}.fa-folder,.fa-folder-blank{--fa:"\f07b"}.fa-file-medical-alt,.fa-file-waveform{--fa:"\f478"}.fa-radiation{--fa:"\f7b9"}.fa-chart-simple{--fa:"\e473"}.fa-mars-stroke{--fa:"\f229"}.fa-vial{--fa:"\f492"}.fa-dashboard,.fa-gauge,.fa-gauge-med,.fa-tachometer-alt-average{--fa:"\f624"}.fa-magic-wand-sparkles,.fa-wand-magic-sparkles{--fa:"\e2ca"}.fa-e{--fa:"\45"}.fa-pen-alt,.fa-pen-clip{--fa:"\f305"}.fa-bridge-circle-exclamation{--fa:"\e4ca"}.fa-user{--fa:"\f007"}.fa-school-circle-check{--fa:"\e56b"}.fa-dumpster{--fa:"\f793"}.fa-shuttle-van,.fa-van-shuttle{--fa:"\f5b6"}.fa-building-user{--fa:"\e4da"}.fa-caret-square-left,.fa-square-caret-left{--fa:"\f191"}.fa-highlighter{--fa:"\f591"}.fa-key{--fa:"\f084"}.fa-bullhorn{--fa:"\f0a1"}.fa-globe{--fa:"\f0ac"}.fa-synagogue{--fa:"\f69b"}.fa-person-half-dress{--fa:"\e548"}.fa-road-bridge{--fa:"\e563"}.fa-location-arrow{--fa:"\f124"}.fa-c{--fa:"\43"}.fa-tablet-button{--fa:"\f10a"}.fa-building-lock{--fa:"\e4d6"}.fa-pizza-slice{--fa:"\f818"}.fa-money-bill-wave{--fa:"\f53a"}.fa-area-chart,.fa-chart-area{--fa:"\f1fe"}.fa-house-flag{--fa:"\e50d"}.fa-person-circle-minus{--fa:"\e540"}.fa-ban,.fa-cancel{--fa:"\f05e"}.fa-camera-rotate{--fa:"\e0d8"}.fa-air-freshener,.fa-spray-can-sparkles{--fa:"\f5d0"}.fa-star{--fa:"\f005"}.fa-repeat{--fa:"\f363"}.fa-cross{--fa:"\f654"}.fa-box{--fa:"\f466"}.fa-venus-mars{--fa:"\f228"}.fa-arrow-pointer,.fa-mouse-pointer{--fa:"\f245"}.fa-expand-arrows-alt,.fa-maximize{--fa:"\f31e"}.fa-charging-station{--fa:"\f5e7"}.fa-shapes,.fa-triangle-circle-square{--fa:"\f61f"}.fa-random,.fa-shuffle{--fa:"\f074"}.fa-person-running,.fa-running{--fa:"\f70c"}.fa-mobile-retro{--fa:"\e527"}.fa-grip-lines-vertical{--fa:"\f7a5"}.fa-spider{--fa:"\f717"}.fa-hands-bound{--fa:"\e4f9"}.fa-file-invoice-dollar{--fa:"\f571"}.fa-plane-circle-exclamation{--fa:"\e556"}.fa-x-ray{--fa:"\f497"}.fa-spell-check{--fa:"\f891"}.fa-slash{--fa:"\f715"}.fa-computer-mouse,.fa-mouse{--fa:"\f8cc"}.fa-arrow-right-to-bracket,.fa-sign-in{--fa:"\f090"}.fa-shop-slash,.fa-store-alt-slash{--fa:"\e070"}.fa-server{--fa:"\f233"}.fa-virus-covid-slash{--fa:"\e4a9"}.fa-shop-lock{--fa:"\e4a5"}.fa-hourglass-1,.fa-hourglass-start{--fa:"\f251"}.fa-blender-phone{--fa:"\f6b6"}.fa-building-wheat{--fa:"\e4db"}.fa-person-breastfeeding{--fa:"\e53a"}.fa-right-to-bracket,.fa-sign-in-alt{--fa:"\f2f6"}.fa-venus{--fa:"\f221"}.fa-passport{--fa:"\f5ab"}.fa-thumb-tack-slash,.fa-thumbtack-slash{--fa:"\e68f"}.fa-heart-pulse,.fa-heartbeat{--fa:"\f21e"}.fa-people-carry,.fa-people-carry-box{--fa:"\f4ce"}.fa-temperature-high{--fa:"\f769"}.fa-microchip{--fa:"\f2db"}.fa-crown{--fa:"\f521"}.fa-weight-hanging{--fa:"\f5cd"}.fa-xmarks-lines{--fa:"\e59a"}.fa-file-prescription{--fa:"\f572"}.fa-weight,.fa-weight-scale{--fa:"\f496"}.fa-user-friends,.fa-user-group{--fa:"\f500"}.fa-arrow-up-a-z,.fa-sort-alpha-up{--fa:"\f15e"}.fa-chess-knight{--fa:"\f441"}.fa-face-laugh-squint,.fa-laugh-squint{--fa:"\f59b"}.fa-wheelchair{--fa:"\f193"}.fa-arrow-circle-up,.fa-circle-arrow-up{--fa:"\f0aa"}.fa-toggle-on{--fa:"\f205"}.fa-person-walking,.fa-walking{--fa:"\f554"}.fa-l{--fa:"\4c"}.fa-fire{--fa:"\f06d"}.fa-bed-pulse,.fa-procedures{--fa:"\f487"}.fa-shuttle-space,.fa-space-shuttle{--fa:"\f197"}.fa-face-laugh,.fa-laugh{--fa:"\f599"}.fa-folder-open{--fa:"\f07c"}.fa-heart-circle-plus{--fa:"\e500"}.fa-code-fork{--fa:"\e13b"}.fa-city{--fa:"\f64f"}.fa-microphone-alt,.fa-microphone-lines{--fa:"\f3c9"}.fa-pepper-hot{--fa:"\f816"}.fa-unlock{--fa:"\f09c"}.fa-colon-sign{--fa:"\e140"}.fa-headset{--fa:"\f590"}.fa-store-slash{--fa:"\e071"}.fa-road-circle-xmark{--fa:"\e566"}.fa-user-minus{--fa:"\f503"}.fa-mars-stroke-up,.fa-mars-stroke-v{--fa:"\f22a"}.fa-champagne-glasses,.fa-glass-cheers{--fa:"\f79f"}.fa-clipboard{--fa:"\f328"}.fa-house-circle-exclamation{--fa:"\e50a"}.fa-file-arrow-up,.fa-file-upload{--fa:"\f574"}.fa-wifi,.fa-wifi-3,.fa-wifi-strong{--fa:"\f1eb"}.fa-bath,.fa-bathtub{--fa:"\f2cd"}.fa-underline{--fa:"\f0cd"}.fa-user-edit,.fa-user-pen{--fa:"\f4ff"}.fa-signature{--fa:"\f5b7"}.fa-stroopwafel{--fa:"\f551"}.fa-bold{--fa:"\f032"}.fa-anchor-lock{--fa:"\e4ad"}.fa-building-ngo{--fa:"\e4d7"}.fa-manat-sign{--fa:"\e1d5"}.fa-not-equal{--fa:"\f53e"}.fa-border-style,.fa-border-top-left{--fa:"\f853"}.fa-map-location-dot,.fa-map-marked-alt{--fa:"\f5a0"}.fa-jedi{--fa:"\f669"}.fa-poll,.fa-square-poll-vertical{--fa:"\f681"}.fa-mug-hot{--fa:"\f7b6"}.fa-battery-car,.fa-car-battery{--fa:"\f5df"}.fa-gift{--fa:"\f06b"}.fa-dice-two{--fa:"\f528"}.fa-chess-queen{--fa:"\f445"}.fa-glasses{--fa:"\f530"}.fa-chess-board{--fa:"\f43c"}.fa-building-circle-check{--fa:"\e4d2"}.fa-person-chalkboard{--fa:"\e53d"}.fa-mars-stroke-h,.fa-mars-stroke-right{--fa:"\f22b"}.fa-hand-back-fist,.fa-hand-rock{--fa:"\f255"}.fa-caret-square-up,.fa-square-caret-up{--fa:"\f151"}.fa-cloud-showers-water{--fa:"\e4e4"}.fa-bar-chart,.fa-chart-bar{--fa:"\f080"}.fa-hands-bubbles,.fa-hands-wash{--fa:"\e05e"}.fa-less-than-equal{--fa:"\f537"}.fa-train{--fa:"\f238"}.fa-eye-low-vision,.fa-low-vision{--fa:"\f2a8"}.fa-crow{--fa:"\f520"}.fa-sailboat{--fa:"\e445"}.fa-window-restore{--fa:"\f2d2"}.fa-plus-square,.fa-square-plus{--fa:"\f0fe"}.fa-torii-gate{--fa:"\f6a1"}.fa-frog{--fa:"\f52e"}.fa-bucket{--fa:"\e4cf"}.fa-image{--fa:"\f03e"}.fa-microphone{--fa:"\f130"}.fa-cow{--fa:"\f6c8"}.fa-caret-up{--fa:"\f0d8"}.fa-screwdriver{--fa:"\f54a"}.fa-folder-closed{--fa:"\e185"}.fa-house-tsunami{--fa:"\e515"}.fa-square-nfi{--fa:"\e576"}.fa-arrow-up-from-ground-water{--fa:"\e4b5"}.fa-glass-martini-alt,.fa-martini-glass{--fa:"\f57b"}.fa-square-binary{--fa:"\e69b"}.fa-rotate-back,.fa-rotate-backward,.fa-rotate-left,.fa-undo-alt{--fa:"\f2ea"}.fa-columns,.fa-table-columns{--fa:"\f0db"}.fa-lemon{--fa:"\f094"}.fa-head-side-mask{--fa:"\e063"}.fa-handshake{--fa:"\f2b5"}.fa-gem{--fa:"\f3a5"}.fa-dolly,.fa-dolly-box{--fa:"\f472"}.fa-smoking{--fa:"\f48d"}.fa-compress-arrows-alt,.fa-minimize{--fa:"\f78c"}.fa-monument{--fa:"\f5a6"}.fa-snowplow{--fa:"\f7d2"}.fa-angle-double-right,.fa-angles-right{--fa:"\f101"}.fa-cannabis{--fa:"\f55f"}.fa-circle-play,.fa-play-circle{--fa:"\f144"}.fa-tablets{--fa:"\f490"}.fa-ethernet{--fa:"\f796"}.fa-eur,.fa-euro,.fa-euro-sign{--fa:"\f153"}.fa-chair{--fa:"\f6c0"}.fa-check-circle,.fa-circle-check{--fa:"\f058"}.fa-circle-stop,.fa-stop-circle{--fa:"\f28d"}.fa-compass-drafting,.fa-drafting-compass{--fa:"\f568"}.fa-plate-wheat{--fa:"\e55a"}.fa-icicles{--fa:"\f7ad"}.fa-person-shelter{--fa:"\e54f"}.fa-neuter{--fa:"\f22c"}.fa-id-badge{--fa:"\f2c1"}.fa-marker{--fa:"\f5a1"}.fa-face-laugh-beam,.fa-laugh-beam{--fa:"\f59a"}.fa-helicopter-symbol{--fa:"\e502"}.fa-universal-access{--fa:"\f29a"}.fa-chevron-circle-up,.fa-circle-chevron-up{--fa:"\f139"}.fa-lari-sign{--fa:"\e1c8"}.fa-volcano{--fa:"\f770"}.fa-person-walking-dashed-line-arrow-right{--fa:"\e553"}.fa-gbp,.fa-pound-sign,.fa-sterling-sign{--fa:"\f154"}.fa-viruses{--fa:"\e076"}.fa-square-person-confined{--fa:"\e577"}.fa-user-tie{--fa:"\f508"}.fa-arrow-down-long,.fa-long-arrow-down{--fa:"\f175"}.fa-tent-arrow-down-to-line{--fa:"\e57e"}.fa-certificate{--fa:"\f0a3"}.fa-mail-reply-all,.fa-reply-all{--fa:"\f122"}.fa-suitcase{--fa:"\f0f2"}.fa-person-skating,.fa-skating{--fa:"\f7c5"}.fa-filter-circle-dollar,.fa-funnel-dollar{--fa:"\f662"}.fa-camera-retro{--fa:"\f083"}.fa-arrow-circle-down,.fa-circle-arrow-down{--fa:"\f0ab"}.fa-arrow-right-to-file,.fa-file-import{--fa:"\f56f"}.fa-external-link-square,.fa-square-arrow-up-right{--fa:"\f14c"}.fa-box-open{--fa:"\f49e"}.fa-scroll{--fa:"\f70e"}.fa-spa{--fa:"\f5bb"}.fa-location-pin-lock{--fa:"\e51f"}.fa-pause{--fa:"\f04c"}.fa-hill-avalanche{--fa:"\e507"}.fa-temperature-0,.fa-temperature-empty,.fa-thermometer-0,.fa-thermometer-empty{--fa:"\f2cb"}.fa-bomb{--fa:"\f1e2"}.fa-registered{--fa:"\f25d"}.fa-address-card,.fa-contact-card,.fa-vcard{--fa:"\f2bb"}.fa-balance-scale-right,.fa-scale-unbalanced-flip{--fa:"\f516"}.fa-subscript{--fa:"\f12c"}.fa-diamond-turn-right,.fa-directions{--fa:"\f5eb"}.fa-burst{--fa:"\e4dc"}.fa-house-laptop,.fa-laptop-house{--fa:"\e066"}.fa-face-tired,.fa-tired{--fa:"\f5c8"}.fa-money-bills{--fa:"\e1f3"}.fa-smog{--fa:"\f75f"}.fa-crutch{--fa:"\f7f7"}.fa-cloud-arrow-up,.fa-cloud-upload,.fa-cloud-upload-alt{--fa:"\f0ee"}.fa-palette{--fa:"\f53f"}.fa-arrows-turn-right{--fa:"\e4c0"}.fa-vest{--fa:"\e085"}.fa-ferry{--fa:"\e4ea"}.fa-arrows-down-to-people{--fa:"\e4b9"}.fa-seedling,.fa-sprout{--fa:"\f4d8"}.fa-arrows-alt-h,.fa-left-right{--fa:"\f337"}.fa-boxes-packing{--fa:"\e4c7"}.fa-arrow-circle-left,.fa-circle-arrow-left{--fa:"\f0a8"}.fa-group-arrows-rotate{--fa:"\e4f6"}.fa-bowl-food{--fa:"\e4c6"}.fa-candy-cane{--fa:"\f786"}.fa-arrow-down-wide-short,.fa-sort-amount-asc,.fa-sort-amount-down{--fa:"\f160"}.fa-cloud-bolt,.fa-thunderstorm{--fa:"\f76c"}.fa-remove-format,.fa-text-slash{--fa:"\f87d"}.fa-face-smile-wink,.fa-smile-wink{--fa:"\f4da"}.fa-file-word{--fa:"\f1c2"}.fa-file-powerpoint{--fa:"\f1c4"}.fa-arrows-h,.fa-arrows-left-right{--fa:"\f07e"}.fa-house-lock{--fa:"\e510"}.fa-cloud-arrow-down,.fa-cloud-download,.fa-cloud-download-alt{--fa:"\f0ed"}.fa-children{--fa:"\e4e1"}.fa-blackboard,.fa-chalkboard{--fa:"\f51b"}.fa-user-alt-slash,.fa-user-large-slash{--fa:"\f4fa"}.fa-envelope-open{--fa:"\f2b6"}.fa-handshake-alt-slash,.fa-handshake-simple-slash{--fa:"\e05f"}.fa-mattress-pillow{--fa:"\e525"}.fa-guarani-sign{--fa:"\e19a"}.fa-arrows-rotate,.fa-refresh,.fa-sync{--fa:"\f021"}.fa-fire-extinguisher{--fa:"\f134"}.fa-cruzeiro-sign{--fa:"\e152"}.fa-greater-than-equal{--fa:"\f532"}.fa-shield-alt,.fa-shield-halved{--fa:"\f3ed"}.fa-atlas,.fa-book-atlas{--fa:"\f558"}.fa-virus{--fa:"\e074"}.fa-envelope-circle-check{--fa:"\e4e8"}.fa-layer-group{--fa:"\f5fd"}.fa-arrows-to-dot{--fa:"\e4be"}.fa-archway{--fa:"\f557"}.fa-heart-circle-check{--fa:"\e4fd"}.fa-house-chimney-crack,.fa-house-damage{--fa:"\f6f1"}.fa-file-archive,.fa-file-zipper{--fa:"\f1c6"}.fa-square{--fa:"\f0c8"}.fa-glass-martini,.fa-martini-glass-empty{--fa:"\f000"}.fa-couch{--fa:"\f4b8"}.fa-cedi-sign{--fa:"\e0df"}.fa-italic{--fa:"\f033"}.fa-table-cells-column-lock{--fa:"\e678"}.fa-church{--fa:"\f51d"}.fa-comments-dollar{--fa:"\f653"}.fa-democrat{--fa:"\f747"}.fa-z{--fa:"\5a"}.fa-person-skiing,.fa-skiing{--fa:"\f7c9"}.fa-road-lock{--fa:"\e567"}.fa-a{--fa:"\41"}.fa-temperature-arrow-down,.fa-temperature-down{--fa:"\e03f"}.fa-feather-alt,.fa-feather-pointed{--fa:"\f56b"}.fa-p{--fa:"\50"}.fa-snowflake{--fa:"\f2dc"}.fa-newspaper{--fa:"\f1ea"}.fa-ad,.fa-rectangle-ad{--fa:"\f641"}.fa-arrow-circle-right,.fa-circle-arrow-right{--fa:"\f0a9"}.fa-filter-circle-xmark{--fa:"\e17b"}.fa-locust{--fa:"\e520"}.fa-sort,.fa-unsorted{--fa:"\f0dc"}.fa-list-1-2,.fa-list-numeric,.fa-list-ol{--fa:"\f0cb"}.fa-person-dress-burst{--fa:"\e544"}.fa-money-check-alt,.fa-money-check-dollar{--fa:"\f53d"}.fa-vector-square{--fa:"\f5cb"}.fa-bread-slice{--fa:"\f7ec"}.fa-language{--fa:"\f1ab"}.fa-face-kiss-wink-heart,.fa-kiss-wink-heart{--fa:"\f598"}.fa-filter{--fa:"\f0b0"}.fa-question{--fa:"\3f"}.fa-file-signature{--fa:"\f573"}.fa-arrows-alt,.fa-up-down-left-right{--fa:"\f0b2"}.fa-house-chimney-user{--fa:"\e065"}.fa-hand-holding-heart{--fa:"\f4be"}.fa-puzzle-piece{--fa:"\f12e"}.fa-money-check{--fa:"\f53c"}.fa-star-half-alt,.fa-star-half-stroke{--fa:"\f5c0"}.fa-code{--fa:"\f121"}.fa-glass-whiskey,.fa-whiskey-glass{--fa:"\f7a0"}.fa-building-circle-exclamation{--fa:"\e4d3"}.fa-magnifying-glass-chart{--fa:"\e522"}.fa-arrow-up-right-from-square,.fa-external-link{--fa:"\f08e"}.fa-cubes-stacked{--fa:"\e4e6"}.fa-krw,.fa-won,.fa-won-sign{--fa:"\f159"}.fa-virus-covid{--fa:"\e4a8"}.fa-austral-sign{--fa:"\e0a9"}.fa-f{--fa:"\46"}.fa-leaf{--fa:"\f06c"}.fa-road{--fa:"\f018"}.fa-cab,.fa-taxi{--fa:"\f1ba"}.fa-person-circle-plus{--fa:"\e541"}.fa-chart-pie,.fa-pie-chart{--fa:"\f200"}.fa-bolt-lightning{--fa:"\e0b7"}.fa-sack-xmark{--fa:"\e56a"}.fa-file-excel{--fa:"\f1c3"}.fa-file-contract{--fa:"\f56c"}.fa-fish-fins{--fa:"\e4f2"}.fa-building-flag{--fa:"\e4d5"}.fa-face-grin-beam,.fa-grin-beam{--fa:"\f582"}.fa-object-ungroup{--fa:"\f248"}.fa-poop{--fa:"\f619"}.fa-location-pin,.fa-map-marker{--fa:"\f041"}.fa-kaaba{--fa:"\f66b"}.fa-toilet-paper{--fa:"\f71e"}.fa-hard-hat,.fa-hat-hard,.fa-helmet-safety{--fa:"\f807"}.fa-eject{--fa:"\f052"}.fa-arrow-alt-circle-right,.fa-circle-right{--fa:"\f35a"}.fa-plane-circle-check{--fa:"\e555"}.fa-face-rolling-eyes,.fa-meh-rolling-eyes{--fa:"\f5a5"}.fa-object-group{--fa:"\f247"}.fa-chart-line,.fa-line-chart{--fa:"\f201"}.fa-mask-ventilator{--fa:"\e524"}.fa-arrow-right{--fa:"\f061"}.fa-map-signs,.fa-signs-post{--fa:"\f277"}.fa-cash-register{--fa:"\f788"}.fa-person-circle-question{--fa:"\e542"}.fa-h{--fa:"\48"}.fa-tarp{--fa:"\e57b"}.fa-screwdriver-wrench,.fa-tools{--fa:"\f7d9"}.fa-arrows-to-eye{--fa:"\e4bf"}.fa-plug-circle-bolt{--fa:"\e55b"}.fa-heart{--fa:"\f004"}.fa-mars-and-venus{--fa:"\f224"}.fa-home-user,.fa-house-user{--fa:"\e1b0"}.fa-dumpster-fire{--fa:"\f794"}.fa-house-crack{--fa:"\e3b1"}.fa-cocktail,.fa-martini-glass-citrus{--fa:"\f561"}.fa-face-surprise,.fa-surprise{--fa:"\f5c2"}.fa-bottle-water{--fa:"\e4c5"}.fa-circle-pause,.fa-pause-circle{--fa:"\f28b"}.fa-toilet-paper-slash{--fa:"\e072"}.fa-apple-alt,.fa-apple-whole{--fa:"\f5d1"}.fa-kitchen-set{--fa:"\e51a"}.fa-r{--fa:"\52"}.fa-temperature-1,.fa-temperature-quarter,.fa-thermometer-1,.fa-thermometer-quarter{--fa:"\f2ca"}.fa-cube{--fa:"\f1b2"}.fa-bitcoin-sign{--fa:"\e0b4"}.fa-shield-dog{--fa:"\e573"}.fa-solar-panel{--fa:"\f5ba"}.fa-lock-open{--fa:"\f3c1"}.fa-elevator{--fa:"\e16d"}.fa-money-bill-transfer{--fa:"\e528"}.fa-money-bill-trend-up{--fa:"\e529"}.fa-house-flood-water-circle-arrow-right{--fa:"\e50f"}.fa-poll-h,.fa-square-poll-horizontal{--fa:"\f682"}.fa-circle{--fa:"\f111"}.fa-backward-fast,.fa-fast-backward{--fa:"\f049"}.fa-recycle{--fa:"\f1b8"}.fa-user-astronaut{--fa:"\f4fb"}.fa-plane-slash{--fa:"\e069"}.fa-trademark{--fa:"\f25c"}.fa-basketball,.fa-basketball-ball{--fa:"\f434"}.fa-satellite-dish{--fa:"\f7c0"}.fa-arrow-alt-circle-up,.fa-circle-up{--fa:"\f35b"}.fa-mobile-alt,.fa-mobile-screen-button{--fa:"\f3cd"}.fa-volume-high,.fa-volume-up{--fa:"\f028"}.fa-users-rays{--fa:"\e593"}.fa-wallet{--fa:"\f555"}.fa-clipboard-check{--fa:"\f46c"}.fa-file-audio{--fa:"\f1c7"}.fa-burger,.fa-hamburger{--fa:"\f805"}.fa-wrench{--fa:"\f0ad"}.fa-bugs{--fa:"\e4d0"}.fa-rupee,.fa-rupee-sign{--fa:"\f156"}.fa-file-image{--fa:"\f1c5"}.fa-circle-question,.fa-question-circle{--fa:"\f059"}.fa-plane-departure{--fa:"\f5b0"}.fa-handshake-slash{--fa:"\e060"}.fa-book-bookmark{--fa:"\e0bb"}.fa-code-branch{--fa:"\f126"}.fa-hat-cowboy{--fa:"\f8c0"}.fa-bridge{--fa:"\e4c8"}.fa-phone-alt,.fa-phone-flip{--fa:"\f879"}.fa-truck-front{--fa:"\e2b7"}.fa-cat{--fa:"\f6be"}.fa-anchor-circle-exclamation{--fa:"\e4ab"}.fa-truck-field{--fa:"\e58d"}.fa-route{--fa:"\f4d7"}.fa-clipboard-question{--fa:"\e4e3"}.fa-panorama{--fa:"\e209"}.fa-comment-medical{--fa:"\f7f5"}.fa-teeth-open{--fa:"\f62f"}.fa-file-circle-minus{--fa:"\e4ed"}.fa-tags{--fa:"\f02c"}.fa-wine-glass{--fa:"\f4e3"}.fa-fast-forward,.fa-forward-fast{--fa:"\f050"}.fa-face-meh-blank,.fa-meh-blank{--fa:"\f5a4"}.fa-parking,.fa-square-parking{--fa:"\f540"}.fa-house-signal{--fa:"\e012"}.fa-bars-progress,.fa-tasks-alt{--fa:"\f828"}.fa-faucet-drip{--fa:"\e006"}.fa-cart-flatbed,.fa-dolly-flatbed{--fa:"\f474"}.fa-ban-smoking,.fa-smoking-ban{--fa:"\f54d"}.fa-terminal{--fa:"\f120"}.fa-mobile-button{--fa:"\f10b"}.fa-house-medical-flag{--fa:"\e514"}.fa-basket-shopping,.fa-shopping-basket{--fa:"\f291"}.fa-tape{--fa:"\f4db"}.fa-bus-alt,.fa-bus-simple{--fa:"\f55e"}.fa-eye{--fa:"\f06e"}.fa-face-sad-cry,.fa-sad-cry{--fa:"\f5b3"}.fa-audio-description{--fa:"\f29e"}.fa-person-military-to-person{--fa:"\e54c"}.fa-file-shield{--fa:"\e4f0"}.fa-user-slash{--fa:"\f506"}.fa-pen{--fa:"\f304"}.fa-tower-observation{--fa:"\e586"}.fa-file-code{--fa:"\f1c9"}.fa-signal,.fa-signal-5,.fa-signal-perfect{--fa:"\f012"}.fa-bus{--fa:"\f207"}.fa-heart-circle-xmark{--fa:"\e501"}.fa-home-lg,.fa-house-chimney{--fa:"\e3af"}.fa-window-maximize{--fa:"\f2d0"}.fa-face-frown,.fa-frown{--fa:"\f119"}.fa-prescription{--fa:"\f5b1"}.fa-shop,.fa-store-alt{--fa:"\f54f"}.fa-floppy-disk,.fa-save{--fa:"\f0c7"}.fa-vihara{--fa:"\f6a7"}.fa-balance-scale-left,.fa-scale-unbalanced{--fa:"\f515"}.fa-sort-asc,.fa-sort-up{--fa:"\f0de"}.fa-comment-dots,.fa-commenting{--fa:"\f4ad"}.fa-plant-wilt{--fa:"\e5aa"}.fa-diamond{--fa:"\f219"}.fa-face-grin-squint,.fa-grin-squint{--fa:"\f585"}.fa-hand-holding-dollar,.fa-hand-holding-usd{--fa:"\f4c0"}.fa-chart-diagram{--fa:"\e695"}.fa-bacterium{--fa:"\e05a"}.fa-hand-pointer{--fa:"\f25a"}.fa-drum-steelpan{--fa:"\f56a"}.fa-hand-scissors{--fa:"\f257"}.fa-hands-praying,.fa-praying-hands{--fa:"\f684"}.fa-arrow-right-rotate,.fa-arrow-rotate-forward,.fa-arrow-rotate-right,.fa-redo{--fa:"\f01e"}.fa-biohazard{--fa:"\f780"}.fa-location,.fa-location-crosshairs{--fa:"\f601"}.fa-mars-double{--fa:"\f227"}.fa-child-dress{--fa:"\e59c"}.fa-users-between-lines{--fa:"\e591"}.fa-lungs-virus{--fa:"\e067"}.fa-face-grin-tears,.fa-grin-tears{--fa:"\f588"}.fa-phone{--fa:"\f095"}.fa-calendar-times,.fa-calendar-xmark{--fa:"\f273"}.fa-child-reaching{--fa:"\e59d"}.fa-head-side-virus{--fa:"\e064"}.fa-user-cog,.fa-user-gear{--fa:"\f4fe"}.fa-arrow-up-1-9,.fa-sort-numeric-up{--fa:"\f163"}.fa-door-closed{--fa:"\f52a"}.fa-shield-virus{--fa:"\e06c"}.fa-dice-six{--fa:"\f526"}.fa-mosquito-net{--fa:"\e52c"}.fa-file-fragment{--fa:"\e697"}.fa-bridge-water{--fa:"\e4ce"}.fa-person-booth{--fa:"\f756"}.fa-text-width{--fa:"\f035"}.fa-hat-wizard{--fa:"\f6e8"}.fa-pen-fancy{--fa:"\f5ac"}.fa-digging,.fa-person-digging{--fa:"\f85e"}.fa-trash{--fa:"\f1f8"}.fa-gauge-simple,.fa-gauge-simple-med,.fa-tachometer-average{--fa:"\f629"}.fa-book-medical{--fa:"\f7e6"}.fa-poo{--fa:"\f2fe"}.fa-quote-right,.fa-quote-right-alt{--fa:"\f10e"}.fa-shirt,.fa-t-shirt,.fa-tshirt{--fa:"\f553"}.fa-cubes{--fa:"\f1b3"}.fa-divide{--fa:"\f529"}.fa-tenge,.fa-tenge-sign{--fa:"\f7d7"}.fa-headphones{--fa:"\f025"}.fa-hands-holding{--fa:"\f4c2"}.fa-hands-clapping{--fa:"\e1a8"}.fa-republican{--fa:"\f75e"}.fa-arrow-left{--fa:"\f060"}.fa-person-circle-xmark{--fa:"\e543"}.fa-ruler{--fa:"\f545"}.fa-align-left{--fa:"\f036"}.fa-dice-d6{--fa:"\f6d1"}.fa-restroom{--fa:"\f7bd"}.fa-j{--fa:"\4a"}.fa-users-viewfinder{--fa:"\e595"}.fa-file-video{--fa:"\f1c8"}.fa-external-link-alt,.fa-up-right-from-square{--fa:"\f35d"}.fa-table-cells,.fa-th{--fa:"\f00a"}.fa-file-pdf{--fa:"\f1c1"}.fa-bible,.fa-book-bible{--fa:"\f647"}.fa-o{--fa:"\4f"}.fa-medkit,.fa-suitcase-medical{--fa:"\f0fa"}.fa-user-secret{--fa:"\f21b"}.fa-otter{--fa:"\f700"}.fa-female,.fa-person-dress{--fa:"\f182"}.fa-comment-dollar{--fa:"\f651"}.fa-briefcase-clock,.fa-business-time{--fa:"\f64a"}.fa-table-cells-large,.fa-th-large{--fa:"\f009"}.fa-book-tanakh,.fa-tanakh{--fa:"\f827"}.fa-phone-volume,.fa-volume-control-phone{--fa:"\f2a0"}.fa-hat-cowboy-side{--fa:"\f8c1"}.fa-clipboard-user{--fa:"\f7f3"}.fa-child{--fa:"\f1ae"}.fa-lira-sign{--fa:"\f195"}.fa-satellite{--fa:"\f7bf"}.fa-plane-lock{--fa:"\e558"}.fa-tag{--fa:"\f02b"}.fa-comment{--fa:"\f075"}.fa-birthday-cake,.fa-cake,.fa-cake-candles{--fa:"\f1fd"}.fa-envelope{--fa:"\f0e0"}.fa-angle-double-up,.fa-angles-up{--fa:"\f102"}.fa-paperclip{--fa:"\f0c6"}.fa-arrow-right-to-city{--fa:"\e4b3"}.fa-ribbon{--fa:"\f4d6"}.fa-lungs{--fa:"\f604"}.fa-arrow-up-9-1,.fa-sort-numeric-up-alt{--fa:"\f887"}.fa-litecoin-sign{--fa:"\e1d3"}.fa-border-none{--fa:"\f850"}.fa-circle-nodes{--fa:"\e4e2"}.fa-parachute-box{--fa:"\f4cd"}.fa-indent{--fa:"\f03c"}.fa-truck-field-un{--fa:"\e58e"}.fa-hourglass,.fa-hourglass-empty{--fa:"\f254"}.fa-mountain{--fa:"\f6fc"}.fa-user-doctor,.fa-user-md{--fa:"\f0f0"}.fa-circle-info,.fa-info-circle{--fa:"\f05a"}.fa-cloud-meatball{--fa:"\f73b"}.fa-camera,.fa-camera-alt{--fa:"\f030"}.fa-square-virus{--fa:"\e578"}.fa-meteor{--fa:"\f753"}.fa-car-on{--fa:"\e4dd"}.fa-sleigh{--fa:"\f7cc"}.fa-arrow-down-1-9,.fa-sort-numeric-asc,.fa-sort-numeric-down{--fa:"\f162"}.fa-hand-holding-droplet,.fa-hand-holding-water{--fa:"\f4c1"}.fa-water{--fa:"\f773"}.fa-calendar-check{--fa:"\f274"}.fa-braille{--fa:"\f2a1"}.fa-prescription-bottle-alt,.fa-prescription-bottle-medical{--fa:"\f486"}.fa-landmark{--fa:"\f66f"}.fa-truck{--fa:"\f0d1"}.fa-crosshairs{--fa:"\f05b"}.fa-person-cane{--fa:"\e53c"}.fa-tent{--fa:"\e57d"}.fa-vest-patches{--fa:"\e086"}.fa-check-double{--fa:"\f560"}.fa-arrow-down-a-z,.fa-sort-alpha-asc,.fa-sort-alpha-down{--fa:"\f15d"}.fa-money-bill-wheat{--fa:"\e52a"}.fa-cookie{--fa:"\f563"}.fa-arrow-left-rotate,.fa-arrow-rotate-back,.fa-arrow-rotate-backward,.fa-arrow-rotate-left,.fa-undo{--fa:"\f0e2"}.fa-hard-drive,.fa-hdd{--fa:"\f0a0"}.fa-face-grin-squint-tears,.fa-grin-squint-tears{--fa:"\f586"}.fa-dumbbell{--fa:"\f44b"}.fa-list-alt,.fa-rectangle-list{--fa:"\f022"}.fa-tarp-droplet{--fa:"\e57c"}.fa-house-medical-circle-check{--fa:"\e511"}.fa-person-skiing-nordic,.fa-skiing-nordic{--fa:"\f7ca"}.fa-calendar-plus{--fa:"\f271"}.fa-plane-arrival{--fa:"\f5af"}.fa-arrow-alt-circle-left,.fa-circle-left{--fa:"\f359"}.fa-subway,.fa-train-subway{--fa:"\f239"}.fa-chart-gantt{--fa:"\e0e4"}.fa-indian-rupee,.fa-indian-rupee-sign,.fa-inr{--fa:"\e1bc"}.fa-crop-alt,.fa-crop-simple{--fa:"\f565"}.fa-money-bill-1,.fa-money-bill-alt{--fa:"\f3d1"}.fa-left-long,.fa-long-arrow-alt-left{--fa:"\f30a"}.fa-dna{--fa:"\f471"}.fa-virus-slash{--fa:"\e075"}.fa-minus,.fa-subtract{--fa:"\f068"}.fa-chess{--fa:"\f439"}.fa-arrow-left-long,.fa-long-arrow-left{--fa:"\f177"}.fa-plug-circle-check{--fa:"\e55c"}.fa-street-view{--fa:"\f21d"}.fa-franc-sign{--fa:"\e18f"}.fa-volume-off{--fa:"\f026"}.fa-american-sign-language-interpreting,.fa-asl-interpreting,.fa-hands-american-sign-language-interpreting,.fa-hands-asl-interpreting{--fa:"\f2a3"}.fa-cog,.fa-gear{--fa:"\f013"}.fa-droplet-slash,.fa-tint-slash{--fa:"\f5c7"}.fa-mosque{--fa:"\f678"}.fa-mosquito{--fa:"\e52b"}.fa-star-of-david{--fa:"\f69a"}.fa-person-military-rifle{--fa:"\e54b"}.fa-cart-shopping,.fa-shopping-cart{--fa:"\f07a"}.fa-vials{--fa:"\f493"}.fa-plug-circle-plus{--fa:"\e55f"}.fa-place-of-worship{--fa:"\f67f"}.fa-grip-vertical{--fa:"\f58e"}.fa-hexagon-nodes{--fa:"\e699"}.fa-arrow-turn-up,.fa-level-up{--fa:"\f148"}.fa-u{--fa:"\55"}.fa-square-root-alt,.fa-square-root-variable{--fa:"\f698"}.fa-clock,.fa-clock-four{--fa:"\f017"}.fa-backward-step,.fa-step-backward{--fa:"\f048"}.fa-pallet{--fa:"\f482"}.fa-faucet{--fa:"\e005"}.fa-baseball-bat-ball{--fa:"\f432"}.fa-s{--fa:"\53"}.fa-timeline{--fa:"\e29c"}.fa-keyboard{--fa:"\f11c"}.fa-caret-down{--fa:"\f0d7"}.fa-clinic-medical,.fa-house-chimney-medical{--fa:"\f7f2"}.fa-temperature-3,.fa-temperature-three-quarters,.fa-thermometer-3,.fa-thermometer-three-quarters{--fa:"\f2c8"}.fa-mobile-android-alt,.fa-mobile-screen{--fa:"\f3cf"}.fa-plane-up{--fa:"\e22d"}.fa-piggy-bank{--fa:"\f4d3"}.fa-battery-3,.fa-battery-half{--fa:"\f242"}.fa-mountain-city{--fa:"\e52e"}.fa-coins{--fa:"\f51e"}.fa-khanda{--fa:"\f66d"}.fa-sliders,.fa-sliders-h{--fa:"\f1de"}.fa-folder-tree{--fa:"\f802"}.fa-network-wired{--fa:"\f6ff"}.fa-map-pin{--fa:"\f276"}.fa-hamsa{--fa:"\f665"}.fa-cent-sign{--fa:"\e3f5"}.fa-flask{--fa:"\f0c3"}.fa-person-pregnant{--fa:"\e31e"}.fa-wand-sparkles{--fa:"\f72b"}.fa-ellipsis-v,.fa-ellipsis-vertical{--fa:"\f142"}.fa-ticket{--fa:"\f145"}.fa-power-off{--fa:"\f011"}.fa-long-arrow-alt-right,.fa-right-long{--fa:"\f30b"}.fa-flag-usa{--fa:"\f74d"}.fa-laptop-file{--fa:"\e51d"}.fa-teletype,.fa-tty{--fa:"\f1e4"}.fa-diagram-next{--fa:"\e476"}.fa-person-rifle{--fa:"\e54e"}.fa-house-medical-circle-exclamation{--fa:"\e512"}.fa-closed-captioning{--fa:"\f20a"}.fa-hiking,.fa-person-hiking{--fa:"\f6ec"}.fa-venus-double{--fa:"\f226"}.fa-images{--fa:"\f302"}.fa-calculator{--fa:"\f1ec"}.fa-people-pulling{--fa:"\e535"}.fa-n{--fa:"\4e"}.fa-cable-car,.fa-tram{--fa:"\f7da"}.fa-cloud-rain{--fa:"\f73d"}.fa-building-circle-xmark{--fa:"\e4d4"}.fa-ship{--fa:"\f21a"}.fa-arrows-down-to-line{--fa:"\e4b8"}.fa-download{--fa:"\f019"}.fa-face-grin,.fa-grin{--fa:"\f580"}.fa-backspace,.fa-delete-left{--fa:"\f55a"}.fa-eye-dropper,.fa-eye-dropper-empty,.fa-eyedropper{--fa:"\f1fb"}.fa-file-circle-check{--fa:"\e5a0"}.fa-forward{--fa:"\f04e"}.fa-mobile,.fa-mobile-android,.fa-mobile-phone{--fa:"\f3ce"}.fa-face-meh,.fa-meh{--fa:"\f11a"}.fa-align-center{--fa:"\f037"}.fa-book-dead,.fa-book-skull{--fa:"\f6b7"}.fa-drivers-license,.fa-id-card{--fa:"\f2c2"}.fa-dedent,.fa-outdent{--fa:"\f03b"}.fa-heart-circle-exclamation{--fa:"\e4fe"}.fa-home,.fa-home-alt,.fa-home-lg-alt,.fa-house{--fa:"\f015"}.fa-calendar-week{--fa:"\f784"}.fa-laptop-medical{--fa:"\f812"}.fa-b{--fa:"\42"}.fa-file-medical{--fa:"\f477"}.fa-dice-one{--fa:"\f525"}.fa-kiwi-bird{--fa:"\f535"}.fa-arrow-right-arrow-left,.fa-exchange{--fa:"\f0ec"}.fa-redo-alt,.fa-rotate-forward,.fa-rotate-right{--fa:"\f2f9"}.fa-cutlery,.fa-utensils{--fa:"\f2e7"}.fa-arrow-up-wide-short,.fa-sort-amount-up{--fa:"\f161"}.fa-mill-sign{--fa:"\e1ed"}.fa-bowl-rice{--fa:"\e2eb"}.fa-skull{--fa:"\f54c"}.fa-broadcast-tower,.fa-tower-broadcast{--fa:"\f519"}.fa-truck-pickup{--fa:"\f63c"}.fa-long-arrow-alt-up,.fa-up-long{--fa:"\f30c"}.fa-stop{--fa:"\f04d"}.fa-code-merge{--fa:"\f387"}.fa-upload{--fa:"\f093"}.fa-hurricane{--fa:"\f751"}.fa-mound{--fa:"\e52d"}.fa-toilet-portable{--fa:"\e583"}.fa-compact-disc{--fa:"\f51f"}.fa-file-arrow-down,.fa-file-download{--fa:"\f56d"}.fa-caravan{--fa:"\f8ff"}.fa-shield-cat{--fa:"\e572"}.fa-bolt,.fa-zap{--fa:"\f0e7"}.fa-glass-water{--fa:"\e4f4"}.fa-oil-well{--fa:"\e532"}.fa-vault{--fa:"\e2c5"}.fa-mars{--fa:"\f222"}.fa-toilet{--fa:"\f7d8"}.fa-plane-circle-xmark{--fa:"\e557"}.fa-cny,.fa-jpy,.fa-rmb,.fa-yen,.fa-yen-sign{--fa:"\f157"}.fa-rouble,.fa-rub,.fa-ruble,.fa-ruble-sign{--fa:"\f158"}.fa-sun{--fa:"\f185"}.fa-guitar{--fa:"\f7a6"}.fa-face-laugh-wink,.fa-laugh-wink{--fa:"\f59c"}.fa-horse-head{--fa:"\f7ab"}.fa-bore-hole{--fa:"\e4c3"}.fa-industry{--fa:"\f275"}.fa-arrow-alt-circle-down,.fa-circle-down{--fa:"\f358"}.fa-arrows-turn-to-dots{--fa:"\e4c1"}.fa-florin-sign{--fa:"\e184"}.fa-arrow-down-short-wide,.fa-sort-amount-desc,.fa-sort-amount-down-alt{--fa:"\f884"}.fa-less-than{--fa:"\3c"}.fa-angle-down{--fa:"\f107"}.fa-car-tunnel{--fa:"\e4de"}.fa-head-side-cough{--fa:"\e061"}.fa-grip-lines{--fa:"\f7a4"}.fa-thumbs-down{--fa:"\f165"}.fa-user-lock{--fa:"\f502"}.fa-arrow-right-long,.fa-long-arrow-right{--fa:"\f178"}.fa-anchor-circle-xmark{--fa:"\e4ac"}.fa-ellipsis,.fa-ellipsis-h{--fa:"\f141"}.fa-chess-pawn{--fa:"\f443"}.fa-first-aid,.fa-kit-medical{--fa:"\f479"}.fa-person-through-window{--fa:"\e5a9"}.fa-toolbox{--fa:"\f552"}.fa-hands-holding-circle{--fa:"\e4fb"}.fa-bug{--fa:"\f188"}.fa-credit-card,.fa-credit-card-alt{--fa:"\f09d"}.fa-automobile,.fa-car{--fa:"\f1b9"}.fa-hand-holding-hand{--fa:"\e4f7"}.fa-book-open-reader,.fa-book-reader{--fa:"\f5da"}.fa-mountain-sun{--fa:"\e52f"}.fa-arrows-left-right-to-line{--fa:"\e4ba"}.fa-dice-d20{--fa:"\f6cf"}.fa-truck-droplet{--fa:"\e58c"}.fa-file-circle-xmark{--fa:"\e5a1"}.fa-temperature-arrow-up,.fa-temperature-up{--fa:"\e040"}.fa-medal{--fa:"\f5a2"}.fa-bed{--fa:"\f236"}.fa-h-square,.fa-square-h{--fa:"\f0fd"}.fa-podcast{--fa:"\f2ce"}.fa-temperature-4,.fa-temperature-full,.fa-thermometer-4,.fa-thermometer-full{--fa:"\f2c7"}.fa-bell{--fa:"\f0f3"}.fa-superscript{--fa:"\f12b"}.fa-plug-circle-xmark{--fa:"\e560"}.fa-star-of-life{--fa:"\f621"}.fa-phone-slash{--fa:"\f3dd"}.fa-paint-roller{--fa:"\f5aa"}.fa-hands-helping,.fa-handshake-angle{--fa:"\f4c4"}.fa-location-dot,.fa-map-marker-alt{--fa:"\f3c5"}.fa-file{--fa:"\f15b"}.fa-greater-than{--fa:"\3e"}.fa-person-swimming,.fa-swimmer{--fa:"\f5c4"}.fa-arrow-down{--fa:"\f063"}.fa-droplet,.fa-tint{--fa:"\f043"}.fa-eraser{--fa:"\f12d"}.fa-earth,.fa-earth-america,.fa-earth-americas,.fa-globe-americas{--fa:"\f57d"}.fa-person-burst{--fa:"\e53b"}.fa-dove{--fa:"\f4ba"}.fa-battery-0,.fa-battery-empty{--fa:"\f244"}.fa-socks{--fa:"\f696"}.fa-inbox{--fa:"\f01c"}.fa-section{--fa:"\e447"}.fa-gauge-high,.fa-tachometer-alt,.fa-tachometer-alt-fast{--fa:"\f625"}.fa-envelope-open-text{--fa:"\f658"}.fa-hospital,.fa-hospital-alt,.fa-hospital-wide{--fa:"\f0f8"}.fa-wine-bottle{--fa:"\f72f"}.fa-chess-rook{--fa:"\f447"}.fa-bars-staggered,.fa-reorder,.fa-stream{--fa:"\f550"}.fa-dharmachakra{--fa:"\f655"}.fa-hotdog{--fa:"\f80f"}.fa-blind,.fa-person-walking-with-cane{--fa:"\f29d"}.fa-drum{--fa:"\f569"}.fa-ice-cream{--fa:"\f810"}.fa-heart-circle-bolt{--fa:"\e4fc"}.fa-fax{--fa:"\f1ac"}.fa-paragraph{--fa:"\f1dd"}.fa-check-to-slot,.fa-vote-yea{--fa:"\f772"}.fa-star-half{--fa:"\f089"}.fa-boxes,.fa-boxes-alt,.fa-boxes-stacked{--fa:"\f468"}.fa-chain,.fa-link{--fa:"\f0c1"}.fa-assistive-listening-systems,.fa-ear-listen{--fa:"\f2a2"}.fa-tree-city{--fa:"\e587"}.fa-play{--fa:"\f04b"}.fa-font{--fa:"\f031"}.fa-table-cells-row-lock{--fa:"\e67a"}.fa-rupiah-sign{--fa:"\e23d"}.fa-magnifying-glass,.fa-search{--fa:"\f002"}.fa-ping-pong-paddle-ball,.fa-table-tennis,.fa-table-tennis-paddle-ball{--fa:"\f45d"}.fa-diagnoses,.fa-person-dots-from-line{--fa:"\f470"}.fa-trash-can-arrow-up,.fa-trash-restore-alt{--fa:"\f82a"}.fa-naira-sign{--fa:"\e1f6"}.fa-cart-arrow-down{--fa:"\f218"}.fa-walkie-talkie{--fa:"\f8ef"}.fa-file-edit,.fa-file-pen{--fa:"\f31c"}.fa-receipt{--fa:"\f543"}.fa-pen-square,.fa-pencil-square,.fa-square-pen{--fa:"\f14b"}.fa-suitcase-rolling{--fa:"\f5c1"}.fa-person-circle-exclamation{--fa:"\e53f"}.fa-chevron-down{--fa:"\f078"}.fa-battery,.fa-battery-5,.fa-battery-full{--fa:"\f240"}.fa-skull-crossbones{--fa:"\f714"}.fa-code-compare{--fa:"\e13a"}.fa-list-dots,.fa-list-ul{--fa:"\f0ca"}.fa-school-lock{--fa:"\e56f"}.fa-tower-cell{--fa:"\e585"}.fa-down-long,.fa-long-arrow-alt-down{--fa:"\f309"}.fa-ranking-star{--fa:"\e561"}.fa-chess-king{--fa:"\f43f"}.fa-person-harassing{--fa:"\e549"}.fa-brazilian-real-sign{--fa:"\e46c"}.fa-landmark-alt,.fa-landmark-dome{--fa:"\f752"}.fa-arrow-up{--fa:"\f062"}.fa-television,.fa-tv,.fa-tv-alt{--fa:"\f26c"}.fa-shrimp{--fa:"\e448"}.fa-list-check,.fa-tasks{--fa:"\f0ae"}.fa-jug-detergent{--fa:"\e519"}.fa-circle-user,.fa-user-circle{--fa:"\f2bd"}.fa-user-shield{--fa:"\f505"}.fa-wind{--fa:"\f72e"}.fa-car-burst,.fa-car-crash{--fa:"\f5e1"}.fa-y{--fa:"\59"}.fa-person-snowboarding,.fa-snowboarding{--fa:"\f7ce"}.fa-shipping-fast,.fa-truck-fast{--fa:"\f48b"}.fa-fish{--fa:"\f578"}.fa-user-graduate{--fa:"\f501"}.fa-adjust,.fa-circle-half-stroke{--fa:"\f042"}.fa-clapperboard{--fa:"\e131"}.fa-circle-radiation,.fa-radiation-alt{--fa:"\f7ba"}.fa-baseball,.fa-baseball-ball{--fa:"\f433"}.fa-jet-fighter-up{--fa:"\e518"}.fa-diagram-project,.fa-project-diagram{--fa:"\f542"}.fa-copy{--fa:"\f0c5"}.fa-volume-mute,.fa-volume-times,.fa-volume-xmark{--fa:"\f6a9"}.fa-hand-sparkles{--fa:"\e05d"}.fa-grip,.fa-grip-horizontal{--fa:"\f58d"}.fa-share-from-square,.fa-share-square{--fa:"\f14d"}.fa-child-combatant,.fa-child-rifle{--fa:"\e4e0"}.fa-gun{--fa:"\e19b"}.fa-phone-square,.fa-square-phone{--fa:"\f098"}.fa-add,.fa-plus{--fa:"\2b"}.fa-expand{--fa:"\f065"}.fa-computer{--fa:"\e4e5"}.fa-close,.fa-multiply,.fa-remove,.fa-times,.fa-xmark{--fa:"\f00d"}.fa-arrows,.fa-arrows-up-down-left-right{--fa:"\f047"}.fa-chalkboard-teacher,.fa-chalkboard-user{--fa:"\f51c"}.fa-peso-sign{--fa:"\e222"}.fa-building-shield{--fa:"\e4d8"}.fa-baby{--fa:"\f77c"}.fa-users-line{--fa:"\e592"}.fa-quote-left,.fa-quote-left-alt{--fa:"\f10d"}.fa-tractor{--fa:"\f722"}.fa-trash-arrow-up,.fa-trash-restore{--fa:"\f829"}.fa-arrow-down-up-lock{--fa:"\e4b0"}.fa-lines-leaning{--fa:"\e51e"}.fa-ruler-combined{--fa:"\f546"}.fa-copyright{--fa:"\f1f9"}.fa-equals{--fa:"\3d"}.fa-blender{--fa:"\f517"}.fa-teeth{--fa:"\f62e"}.fa-ils,.fa-shekel,.fa-shekel-sign,.fa-sheqel,.fa-sheqel-sign{--fa:"\f20b"}.fa-map{--fa:"\f279"}.fa-rocket{--fa:"\f135"}.fa-photo-film,.fa-photo-video{--fa:"\f87c"}.fa-folder-minus{--fa:"\f65d"}.fa-hexagon-nodes-bolt{--fa:"\e69a"}.fa-store{--fa:"\f54e"}.fa-arrow-trend-up{--fa:"\e098"}.fa-plug-circle-minus{--fa:"\e55e"}.fa-sign,.fa-sign-hanging{--fa:"\f4d9"}.fa-bezier-curve{--fa:"\f55b"}.fa-bell-slash{--fa:"\f1f6"}.fa-tablet,.fa-tablet-android{--fa:"\f3fb"}.fa-school-flag{--fa:"\e56e"}.fa-fill{--fa:"\f575"}.fa-angle-up{--fa:"\f106"}.fa-drumstick-bite{--fa:"\f6d7"}.fa-holly-berry{--fa:"\f7aa"}.fa-chevron-left{--fa:"\f053"}.fa-bacteria{--fa:"\e059"}.fa-hand-lizard{--fa:"\f258"}.fa-notdef{--fa:"\e1fe"}.fa-disease{--fa:"\f7fa"}.fa-briefcase-medical{--fa:"\f469"}.fa-genderless{--fa:"\f22d"}.fa-chevron-right{--fa:"\f054"}.fa-retweet{--fa:"\f079"}.fa-car-alt,.fa-car-rear{--fa:"\f5de"}.fa-pump-soap{--fa:"\e06b"}.fa-video-slash{--fa:"\f4e2"}.fa-battery-2,.fa-battery-quarter{--fa:"\f243"}.fa-radio{--fa:"\f8d7"}.fa-baby-carriage,.fa-carriage-baby{--fa:"\f77d"}.fa-traffic-light{--fa:"\f637"}.fa-thermometer{--fa:"\f491"}.fa-vr-cardboard{--fa:"\f729"}.fa-hand-middle-finger{--fa:"\f806"}.fa-percent,.fa-percentage{--fa:"\25"}.fa-truck-moving{--fa:"\f4df"}.fa-glass-water-droplet{--fa:"\e4f5"}.fa-display{--fa:"\e163"}.fa-face-smile,.fa-smile{--fa:"\f118"}.fa-thumb-tack,.fa-thumbtack{--fa:"\f08d"}.fa-trophy{--fa:"\f091"}.fa-person-praying,.fa-pray{--fa:"\f683"}.fa-hammer{--fa:"\f6e3"}.fa-hand-peace{--fa:"\f25b"}.fa-rotate,.fa-sync-alt{--fa:"\f2f1"}.fa-spinner{--fa:"\f110"}.fa-robot{--fa:"\f544"}.fa-peace{--fa:"\f67c"}.fa-cogs,.fa-gears{--fa:"\f085"}.fa-warehouse{--fa:"\f494"}.fa-arrow-up-right-dots{--fa:"\e4b7"}.fa-splotch{--fa:"\f5bc"}.fa-face-grin-hearts,.fa-grin-hearts{--fa:"\f584"}.fa-dice-four{--fa:"\f524"}.fa-sim-card{--fa:"\f7c4"}.fa-transgender,.fa-transgender-alt{--fa:"\f225"}.fa-mercury{--fa:"\f223"}.fa-arrow-turn-down,.fa-level-down{--fa:"\f149"}.fa-person-falling-burst{--fa:"\e547"}.fa-award{--fa:"\f559"}.fa-ticket-alt,.fa-ticket-simple{--fa:"\f3ff"}.fa-building{--fa:"\f1ad"}.fa-angle-double-left,.fa-angles-left{--fa:"\f100"}.fa-qrcode{--fa:"\f029"}.fa-clock-rotate-left,.fa-history{--fa:"\f1da"}.fa-face-grin-beam-sweat,.fa-grin-beam-sweat{--fa:"\f583"}.fa-arrow-right-from-file,.fa-file-export{--fa:"\f56e"}.fa-shield,.fa-shield-blank{--fa:"\f132"}.fa-arrow-up-short-wide,.fa-sort-amount-up-alt{--fa:"\f885"}.fa-comment-nodes{--fa:"\e696"}.fa-house-medical{--fa:"\e3b2"}.fa-golf-ball,.fa-golf-ball-tee{--fa:"\f450"}.fa-chevron-circle-left,.fa-circle-chevron-left{--fa:"\f137"}.fa-house-chimney-window{--fa:"\e00d"}.fa-pen-nib{--fa:"\f5ad"}.fa-tent-arrow-turn-left{--fa:"\e580"}.fa-tents{--fa:"\e582"}.fa-magic,.fa-wand-magic{--fa:"\f0d0"}.fa-dog{--fa:"\f6d3"}.fa-carrot{--fa:"\f787"}.fa-moon{--fa:"\f186"}.fa-wine-glass-alt,.fa-wine-glass-empty{--fa:"\f5ce"}.fa-cheese{--fa:"\f7ef"}.fa-yin-yang{--fa:"\f6ad"}.fa-music{--fa:"\f001"}.fa-code-commit{--fa:"\f386"}.fa-temperature-low{--fa:"\f76b"}.fa-biking,.fa-person-biking{--fa:"\f84a"}.fa-broom{--fa:"\f51a"}.fa-shield-heart{--fa:"\e574"}.fa-gopuram{--fa:"\f664"}.fa-earth-oceania,.fa-globe-oceania{--fa:"\e47b"}.fa-square-xmark,.fa-times-square,.fa-xmark-square{--fa:"\f2d3"}.fa-hashtag{--fa:"\23"}.fa-expand-alt,.fa-up-right-and-down-left-from-center{--fa:"\f424"}.fa-oil-can{--fa:"\f613"}.fa-t{--fa:"\54"}.fa-hippo{--fa:"\f6ed"}.fa-chart-column{--fa:"\e0e3"}.fa-infinity{--fa:"\f534"}.fa-vial-circle-check{--fa:"\e596"}.fa-person-arrow-down-to-line{--fa:"\e538"}.fa-voicemail{--fa:"\f897"}.fa-fan{--fa:"\f863"}.fa-person-walking-luggage{--fa:"\e554"}.fa-arrows-alt-v,.fa-up-down{--fa:"\f338"}.fa-cloud-moon-rain{--fa:"\f73c"}.fa-calendar{--fa:"\f133"}.fa-trailer{--fa:"\e041"}.fa-bahai,.fa-haykal{--fa:"\f666"}.fa-sd-card{--fa:"\f7c2"}.fa-dragon{--fa:"\f6d5"}.fa-shoe-prints{--fa:"\f54b"}.fa-circle-plus,.fa-plus-circle{--fa:"\f055"}.fa-face-grin-tongue-wink,.fa-grin-tongue-wink{--fa:"\f58b"}.fa-hand-holding{--fa:"\f4bd"}.fa-plug-circle-exclamation{--fa:"\e55d"}.fa-chain-broken,.fa-chain-slash,.fa-link-slash,.fa-unlink{--fa:"\f127"}.fa-clone{--fa:"\f24d"}.fa-person-walking-arrow-loop-left{--fa:"\e551"}.fa-arrow-up-z-a,.fa-sort-alpha-up-alt{--fa:"\f882"}.fa-fire-alt,.fa-fire-flame-curved{--fa:"\f7e4"}.fa-tornado{--fa:"\f76f"}.fa-file-circle-plus{--fa:"\e494"}.fa-book-quran,.fa-quran{--fa:"\f687"}.fa-anchor{--fa:"\f13d"}.fa-border-all{--fa:"\f84c"}.fa-angry,.fa-face-angry{--fa:"\f556"}.fa-cookie-bite{--fa:"\f564"}.fa-arrow-trend-down{--fa:"\e097"}.fa-feed,.fa-rss{--fa:"\f09e"}.fa-draw-polygon{--fa:"\f5ee"}.fa-balance-scale,.fa-scale-balanced{--fa:"\f24e"}.fa-gauge-simple-high,.fa-tachometer,.fa-tachometer-fast{--fa:"\f62a"}.fa-shower{--fa:"\f2cc"}.fa-desktop,.fa-desktop-alt{--fa:"\f390"}.fa-m{--fa:"\4d"}.fa-table-list,.fa-th-list{--fa:"\f00b"}.fa-comment-sms,.fa-sms{--fa:"\f7cd"}.fa-book{--fa:"\f02d"}.fa-user-plus{--fa:"\f234"}.fa-check{--fa:"\f00c"}.fa-battery-4,.fa-battery-three-quarters{--fa:"\f241"}.fa-house-circle-check{--fa:"\e509"}.fa-angle-left{--fa:"\f104"}.fa-diagram-successor{--fa:"\e47a"}.fa-truck-arrow-right{--fa:"\e58b"}.fa-arrows-split-up-and-left{--fa:"\e4bc"}.fa-fist-raised,.fa-hand-fist{--fa:"\f6de"}.fa-cloud-moon{--fa:"\f6c3"}.fa-briefcase{--fa:"\f0b1"}.fa-person-falling{--fa:"\e546"}.fa-image-portrait,.fa-portrait{--fa:"\f3e0"}.fa-user-tag{--fa:"\f507"}.fa-rug{--fa:"\e569"}.fa-earth-europe,.fa-globe-europe{--fa:"\f7a2"}.fa-cart-flatbed-suitcase,.fa-luggage-cart{--fa:"\f59d"}.fa-rectangle-times,.fa-rectangle-xmark,.fa-times-rectangle,.fa-window-close{--fa:"\f410"}.fa-baht-sign{--fa:"\e0ac"}.fa-book-open{--fa:"\f518"}.fa-book-journal-whills,.fa-journal-whills{--fa:"\f66a"}.fa-handcuffs{--fa:"\e4f8"}.fa-exclamation-triangle,.fa-triangle-exclamation,.fa-warning{--fa:"\f071"}.fa-database{--fa:"\f1c0"}.fa-mail-forward,.fa-share{--fa:"\f064"}.fa-bottle-droplet{--fa:"\e4c4"}.fa-mask-face{--fa:"\e1d7"}.fa-hill-rockslide{--fa:"\e508"}.fa-exchange-alt,.fa-right-left{--fa:"\f362"}.fa-paper-plane{--fa:"\f1d8"}.fa-road-circle-exclamation{--fa:"\e565"}.fa-dungeon{--fa:"\f6d9"}.fa-align-right{--fa:"\f038"}.fa-money-bill-1-wave,.fa-money-bill-wave-alt{--fa:"\f53b"}.fa-life-ring{--fa:"\f1cd"}.fa-hands,.fa-sign-language,.fa-signing{--fa:"\f2a7"}.fa-calendar-day{--fa:"\f783"}.fa-ladder-water,.fa-swimming-pool,.fa-water-ladder{--fa:"\f5c5"}.fa-arrows-up-down,.fa-arrows-v{--fa:"\f07d"}.fa-face-grimace,.fa-grimace{--fa:"\f57f"}.fa-wheelchair-alt,.fa-wheelchair-move{--fa:"\e2ce"}.fa-level-down-alt,.fa-turn-down{--fa:"\f3be"}.fa-person-walking-arrow-right{--fa:"\e552"}.fa-envelope-square,.fa-square-envelope{--fa:"\f199"}.fa-dice{--fa:"\f522"}.fa-bowling-ball{--fa:"\f436"}.fa-brain{--fa:"\f5dc"}.fa-band-aid,.fa-bandage{--fa:"\f462"}.fa-calendar-minus{--fa:"\f272"}.fa-circle-xmark,.fa-times-circle,.fa-xmark-circle{--fa:"\f057"}.fa-gifts{--fa:"\f79c"}.fa-hotel{--fa:"\f594"}.fa-earth-asia,.fa-globe-asia{--fa:"\f57e"}.fa-id-card-alt,.fa-id-card-clip{--fa:"\f47f"}.fa-magnifying-glass-plus,.fa-search-plus{--fa:"\f00e"}.fa-thumbs-up{--fa:"\f164"}.fa-user-clock{--fa:"\f4fd"}.fa-allergies,.fa-hand-dots{--fa:"\f461"}.fa-file-invoice{--fa:"\f570"}.fa-window-minimize{--fa:"\f2d1"}.fa-coffee,.fa-mug-saucer{--fa:"\f0f4"}.fa-brush{--fa:"\f55d"}.fa-file-half-dashed{--fa:"\e698"}.fa-mask{--fa:"\f6fa"}.fa-magnifying-glass-minus,.fa-search-minus{--fa:"\f010"}.fa-ruler-vertical{--fa:"\f548"}.fa-user-alt,.fa-user-large{--fa:"\f406"}.fa-train-tram{--fa:"\e5b4"}.fa-user-nurse{--fa:"\f82f"}.fa-syringe{--fa:"\f48e"}.fa-cloud-sun{--fa:"\f6c4"}.fa-stopwatch-20{--fa:"\e06f"}.fa-square-full{--fa:"\f45c"}.fa-magnet{--fa:"\f076"}.fa-jar{--fa:"\e516"}.fa-note-sticky,.fa-sticky-note{--fa:"\f249"}.fa-bug-slash{--fa:"\e490"}.fa-arrow-up-from-water-pump{--fa:"\e4b6"}.fa-bone{--fa:"\f5d7"}.fa-table-cells-row-unlock{--fa:"\e691"}.fa-user-injured{--fa:"\f728"}.fa-face-sad-tear,.fa-sad-tear{--fa:"\f5b4"}.fa-plane{--fa:"\f072"}.fa-tent-arrows-down{--fa:"\e581"}.fa-exclamation{--fa:"\21"}.fa-arrows-spin{--fa:"\e4bb"}.fa-print{--fa:"\f02f"}.fa-try,.fa-turkish-lira,.fa-turkish-lira-sign{--fa:"\e2bb"}.fa-dollar,.fa-dollar-sign,.fa-usd{--fa:"\24"}.fa-x{--fa:"\58"}.fa-magnifying-glass-dollar,.fa-search-dollar{--fa:"\f688"}.fa-users-cog,.fa-users-gear{--fa:"\f509"}.fa-person-military-pointing{--fa:"\e54a"}.fa-bank,.fa-building-columns,.fa-institution,.fa-museum,.fa-university{--fa:"\f19c"}.fa-umbrella{--fa:"\f0e9"}.fa-trowel{--fa:"\e589"}.fa-d{--fa:"\44"}.fa-stapler{--fa:"\e5af"}.fa-masks-theater,.fa-theater-masks{--fa:"\f630"}.fa-kip-sign{--fa:"\e1c4"}.fa-hand-point-left{--fa:"\f0a5"}.fa-handshake-alt,.fa-handshake-simple{--fa:"\f4c6"}.fa-fighter-jet,.fa-jet-fighter{--fa:"\f0fb"}.fa-share-alt-square,.fa-square-share-nodes{--fa:"\f1e1"}.fa-barcode{--fa:"\f02a"}.fa-plus-minus{--fa:"\e43c"}.fa-video,.fa-video-camera{--fa:"\f03d"}.fa-graduation-cap,.fa-mortar-board{--fa:"\f19d"}.fa-hand-holding-medical{--fa:"\e05c"}.fa-person-circle-check{--fa:"\e53e"}.fa-level-up-alt,.fa-turn-up{--fa:"\f3bf"} +.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(./webfonts/fa-brands-400.woff2) format("woff2"),url(./webfonts/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-weight:400}.fa-monero{--fa:"\f3d0"}.fa-hooli{--fa:"\f427"}.fa-yelp{--fa:"\f1e9"}.fa-cc-visa{--fa:"\f1f0"}.fa-lastfm{--fa:"\f202"}.fa-shopware{--fa:"\f5b5"}.fa-creative-commons-nc{--fa:"\f4e8"}.fa-aws{--fa:"\f375"}.fa-redhat{--fa:"\f7bc"}.fa-yoast{--fa:"\f2b1"}.fa-cloudflare{--fa:"\e07d"}.fa-ups{--fa:"\f7e0"}.fa-pixiv{--fa:"\e640"}.fa-wpexplorer{--fa:"\f2de"}.fa-dyalog{--fa:"\f399"}.fa-bity{--fa:"\f37a"}.fa-stackpath{--fa:"\f842"}.fa-buysellads{--fa:"\f20d"}.fa-first-order{--fa:"\f2b0"}.fa-modx{--fa:"\f285"}.fa-guilded{--fa:"\e07e"}.fa-vnv{--fa:"\f40b"}.fa-js-square,.fa-square-js{--fa:"\f3b9"}.fa-microsoft{--fa:"\f3ca"}.fa-qq{--fa:"\f1d6"}.fa-orcid{--fa:"\f8d2"}.fa-java{--fa:"\f4e4"}.fa-invision{--fa:"\f7b0"}.fa-creative-commons-pd-alt{--fa:"\f4ed"}.fa-centercode{--fa:"\f380"}.fa-glide-g{--fa:"\f2a6"}.fa-drupal{--fa:"\f1a9"}.fa-jxl{--fa:"\e67b"}.fa-dart-lang{--fa:"\e693"}.fa-hire-a-helper{--fa:"\f3b0"}.fa-creative-commons-by{--fa:"\f4e7"}.fa-unity{--fa:"\e049"}.fa-whmcs{--fa:"\f40d"}.fa-rocketchat{--fa:"\f3e8"}.fa-vk{--fa:"\f189"}.fa-untappd{--fa:"\f405"}.fa-mailchimp{--fa:"\f59e"}.fa-css3-alt{--fa:"\f38b"}.fa-reddit-square,.fa-square-reddit{--fa:"\f1a2"}.fa-vimeo-v{--fa:"\f27d"}.fa-contao{--fa:"\f26d"}.fa-square-font-awesome{--fa:"\e5ad"}.fa-deskpro{--fa:"\f38f"}.fa-brave{--fa:"\e63c"}.fa-sistrix{--fa:"\f3ee"}.fa-instagram-square,.fa-square-instagram{--fa:"\e055"}.fa-battle-net{--fa:"\f835"}.fa-the-red-yeti{--fa:"\f69d"}.fa-hacker-news-square,.fa-square-hacker-news{--fa:"\f3af"}.fa-edge{--fa:"\f282"}.fa-threads{--fa:"\e618"}.fa-napster{--fa:"\f3d2"}.fa-snapchat-square,.fa-square-snapchat{--fa:"\f2ad"}.fa-google-plus-g{--fa:"\f0d5"}.fa-artstation{--fa:"\f77a"}.fa-markdown{--fa:"\f60f"}.fa-sourcetree{--fa:"\f7d3"}.fa-google-plus{--fa:"\f2b3"}.fa-diaspora{--fa:"\f791"}.fa-foursquare{--fa:"\f180"}.fa-stack-overflow{--fa:"\f16c"}.fa-github-alt{--fa:"\f113"}.fa-phoenix-squadron{--fa:"\f511"}.fa-pagelines{--fa:"\f18c"}.fa-algolia{--fa:"\f36c"}.fa-red-river{--fa:"\f3e3"}.fa-creative-commons-sa{--fa:"\f4ef"}.fa-safari{--fa:"\f267"}.fa-google{--fa:"\f1a0"}.fa-font-awesome-alt,.fa-square-font-awesome-stroke{--fa:"\f35c"}.fa-atlassian{--fa:"\f77b"}.fa-linkedin-in{--fa:"\f0e1"}.fa-digital-ocean{--fa:"\f391"}.fa-nimblr{--fa:"\f5a8"}.fa-chromecast{--fa:"\f838"}.fa-evernote{--fa:"\f839"}.fa-hacker-news{--fa:"\f1d4"}.fa-creative-commons-sampling{--fa:"\f4f0"}.fa-adversal{--fa:"\f36a"}.fa-creative-commons{--fa:"\f25e"}.fa-watchman-monitoring{--fa:"\e087"}.fa-fonticons{--fa:"\f280"}.fa-weixin{--fa:"\f1d7"}.fa-shirtsinbulk{--fa:"\f214"}.fa-codepen{--fa:"\f1cb"}.fa-git-alt{--fa:"\f841"}.fa-lyft{--fa:"\f3c3"}.fa-rev{--fa:"\f5b2"}.fa-windows{--fa:"\f17a"}.fa-wizards-of-the-coast{--fa:"\f730"}.fa-square-viadeo,.fa-viadeo-square{--fa:"\f2aa"}.fa-meetup{--fa:"\f2e0"}.fa-centos{--fa:"\f789"}.fa-adn{--fa:"\f170"}.fa-cloudsmith{--fa:"\f384"}.fa-opensuse{--fa:"\e62b"}.fa-pied-piper-alt{--fa:"\f1a8"}.fa-dribbble-square,.fa-square-dribbble{--fa:"\f397"}.fa-codiepie{--fa:"\f284"}.fa-node{--fa:"\f419"}.fa-mix{--fa:"\f3cb"}.fa-steam{--fa:"\f1b6"}.fa-cc-apple-pay{--fa:"\f416"}.fa-scribd{--fa:"\f28a"}.fa-debian{--fa:"\e60b"}.fa-openid{--fa:"\f19b"}.fa-instalod{--fa:"\e081"}.fa-files-pinwheel{--fa:"\e69f"}.fa-expeditedssl{--fa:"\f23e"}.fa-sellcast{--fa:"\f2da"}.fa-square-twitter,.fa-twitter-square{--fa:"\f081"}.fa-r-project{--fa:"\f4f7"}.fa-delicious{--fa:"\f1a5"}.fa-freebsd{--fa:"\f3a4"}.fa-vuejs{--fa:"\f41f"}.fa-accusoft{--fa:"\f369"}.fa-ioxhost{--fa:"\f208"}.fa-fonticons-fi{--fa:"\f3a2"}.fa-app-store{--fa:"\f36f"}.fa-cc-mastercard{--fa:"\f1f1"}.fa-itunes-note{--fa:"\f3b5"}.fa-golang{--fa:"\e40f"}.fa-kickstarter,.fa-square-kickstarter{--fa:"\f3bb"}.fa-grav{--fa:"\f2d6"}.fa-weibo{--fa:"\f18a"}.fa-uncharted{--fa:"\e084"}.fa-firstdraft{--fa:"\f3a1"}.fa-square-youtube,.fa-youtube-square{--fa:"\f431"}.fa-wikipedia-w{--fa:"\f266"}.fa-rendact,.fa-wpressr{--fa:"\f3e4"}.fa-angellist{--fa:"\f209"}.fa-galactic-republic{--fa:"\f50c"}.fa-nfc-directional{--fa:"\e530"}.fa-skype{--fa:"\f17e"}.fa-joget{--fa:"\f3b7"}.fa-fedora{--fa:"\f798"}.fa-stripe-s{--fa:"\f42a"}.fa-meta{--fa:"\e49b"}.fa-laravel{--fa:"\f3bd"}.fa-hotjar{--fa:"\f3b1"}.fa-bluetooth-b{--fa:"\f294"}.fa-square-letterboxd{--fa:"\e62e"}.fa-sticker-mule{--fa:"\f3f7"}.fa-creative-commons-zero{--fa:"\f4f3"}.fa-hips{--fa:"\f452"}.fa-css{--fa:"\e6a2"}.fa-behance{--fa:"\f1b4"}.fa-reddit{--fa:"\f1a1"}.fa-discord{--fa:"\f392"}.fa-chrome{--fa:"\f268"}.fa-app-store-ios{--fa:"\f370"}.fa-cc-discover{--fa:"\f1f2"}.fa-wpbeginner{--fa:"\f297"}.fa-confluence{--fa:"\f78d"}.fa-shoelace{--fa:"\e60c"}.fa-mdb{--fa:"\f8ca"}.fa-dochub{--fa:"\f394"}.fa-accessible-icon{--fa:"\f368"}.fa-ebay{--fa:"\f4f4"}.fa-amazon{--fa:"\f270"}.fa-unsplash{--fa:"\e07c"}.fa-yarn{--fa:"\f7e3"}.fa-square-steam,.fa-steam-square{--fa:"\f1b7"}.fa-500px{--fa:"\f26e"}.fa-square-vimeo,.fa-vimeo-square{--fa:"\f194"}.fa-asymmetrik{--fa:"\f372"}.fa-font-awesome,.fa-font-awesome-flag,.fa-font-awesome-logo-full{--fa:"\f2b4"}.fa-gratipay{--fa:"\f184"}.fa-apple{--fa:"\f179"}.fa-hive{--fa:"\e07f"}.fa-gitkraken{--fa:"\f3a6"}.fa-keybase{--fa:"\f4f5"}.fa-apple-pay{--fa:"\f415"}.fa-padlet{--fa:"\e4a0"}.fa-amazon-pay{--fa:"\f42c"}.fa-github-square,.fa-square-github{--fa:"\f092"}.fa-stumbleupon{--fa:"\f1a4"}.fa-fedex{--fa:"\f797"}.fa-phoenix-framework{--fa:"\f3dc"}.fa-shopify{--fa:"\e057"}.fa-neos{--fa:"\f612"}.fa-square-threads{--fa:"\e619"}.fa-hackerrank{--fa:"\f5f7"}.fa-researchgate{--fa:"\f4f8"}.fa-swift{--fa:"\f8e1"}.fa-angular{--fa:"\f420"}.fa-speakap{--fa:"\f3f3"}.fa-angrycreative{--fa:"\f36e"}.fa-y-combinator{--fa:"\f23b"}.fa-empire{--fa:"\f1d1"}.fa-envira{--fa:"\f299"}.fa-google-scholar{--fa:"\e63b"}.fa-gitlab-square,.fa-square-gitlab{--fa:"\e5ae"}.fa-studiovinari{--fa:"\f3f8"}.fa-pied-piper{--fa:"\f2ae"}.fa-wordpress{--fa:"\f19a"}.fa-product-hunt{--fa:"\f288"}.fa-firefox{--fa:"\f269"}.fa-linode{--fa:"\f2b8"}.fa-goodreads{--fa:"\f3a8"}.fa-odnoklassniki-square,.fa-square-odnoklassniki{--fa:"\f264"}.fa-jsfiddle{--fa:"\f1cc"}.fa-sith{--fa:"\f512"}.fa-themeisle{--fa:"\f2b2"}.fa-page4{--fa:"\f3d7"}.fa-hashnode{--fa:"\e499"}.fa-react{--fa:"\f41b"}.fa-cc-paypal{--fa:"\f1f4"}.fa-squarespace{--fa:"\f5be"}.fa-cc-stripe{--fa:"\f1f5"}.fa-creative-commons-share{--fa:"\f4f2"}.fa-bitcoin{--fa:"\f379"}.fa-keycdn{--fa:"\f3ba"}.fa-opera{--fa:"\f26a"}.fa-itch-io{--fa:"\f83a"}.fa-umbraco{--fa:"\f8e8"}.fa-galactic-senate{--fa:"\f50d"}.fa-ubuntu{--fa:"\f7df"}.fa-draft2digital{--fa:"\f396"}.fa-stripe{--fa:"\f429"}.fa-houzz{--fa:"\f27c"}.fa-gg{--fa:"\f260"}.fa-dhl{--fa:"\f790"}.fa-pinterest-square,.fa-square-pinterest{--fa:"\f0d3"}.fa-xing{--fa:"\f168"}.fa-blackberry{--fa:"\f37b"}.fa-creative-commons-pd{--fa:"\f4ec"}.fa-playstation{--fa:"\f3df"}.fa-quinscape{--fa:"\f459"}.fa-less{--fa:"\f41d"}.fa-blogger-b{--fa:"\f37d"}.fa-opencart{--fa:"\f23d"}.fa-vine{--fa:"\f1ca"}.fa-signal-messenger{--fa:"\e663"}.fa-paypal{--fa:"\f1ed"}.fa-gitlab{--fa:"\f296"}.fa-typo3{--fa:"\f42b"}.fa-reddit-alien{--fa:"\f281"}.fa-yahoo{--fa:"\f19e"}.fa-dailymotion{--fa:"\e052"}.fa-affiliatetheme{--fa:"\f36b"}.fa-pied-piper-pp{--fa:"\f1a7"}.fa-bootstrap{--fa:"\f836"}.fa-odnoklassniki{--fa:"\f263"}.fa-nfc-symbol{--fa:"\e531"}.fa-mintbit{--fa:"\e62f"}.fa-ethereum{--fa:"\f42e"}.fa-speaker-deck{--fa:"\f83c"}.fa-creative-commons-nc-eu{--fa:"\f4e9"}.fa-patreon{--fa:"\f3d9"}.fa-avianex{--fa:"\f374"}.fa-ello{--fa:"\f5f1"}.fa-gofore{--fa:"\f3a7"}.fa-bimobject{--fa:"\f378"}.fa-brave-reverse{--fa:"\e63d"}.fa-facebook-f{--fa:"\f39e"}.fa-google-plus-square,.fa-square-google-plus{--fa:"\f0d4"}.fa-web-awesome{--fa:"\e682"}.fa-mandalorian{--fa:"\f50f"}.fa-first-order-alt{--fa:"\f50a"}.fa-osi{--fa:"\f41a"}.fa-google-wallet{--fa:"\f1ee"}.fa-d-and-d-beyond{--fa:"\f6ca"}.fa-periscope{--fa:"\f3da"}.fa-fulcrum{--fa:"\f50b"}.fa-cloudscale{--fa:"\f383"}.fa-forumbee{--fa:"\f211"}.fa-mizuni{--fa:"\f3cc"}.fa-schlix{--fa:"\f3ea"}.fa-square-xing,.fa-xing-square{--fa:"\f169"}.fa-bandcamp{--fa:"\f2d5"}.fa-wpforms{--fa:"\f298"}.fa-cloudversify{--fa:"\f385"}.fa-usps{--fa:"\f7e1"}.fa-megaport{--fa:"\f5a3"}.fa-magento{--fa:"\f3c4"}.fa-spotify{--fa:"\f1bc"}.fa-optin-monster{--fa:"\f23c"}.fa-fly{--fa:"\f417"}.fa-square-bluesky{--fa:"\e6a3"}.fa-aviato{--fa:"\f421"}.fa-itunes{--fa:"\f3b4"}.fa-cuttlefish{--fa:"\f38c"}.fa-blogger{--fa:"\f37c"}.fa-flickr{--fa:"\f16e"}.fa-viber{--fa:"\f409"}.fa-soundcloud{--fa:"\f1be"}.fa-digg{--fa:"\f1a6"}.fa-tencent-weibo{--fa:"\f1d5"}.fa-letterboxd{--fa:"\e62d"}.fa-symfony{--fa:"\f83d"}.fa-maxcdn{--fa:"\f136"}.fa-etsy{--fa:"\f2d7"}.fa-facebook-messenger{--fa:"\f39f"}.fa-audible{--fa:"\f373"}.fa-think-peaks{--fa:"\f731"}.fa-bilibili{--fa:"\e3d9"}.fa-erlang{--fa:"\f39d"}.fa-x-twitter{--fa:"\e61b"}.fa-cotton-bureau{--fa:"\f89e"}.fa-dashcube{--fa:"\f210"}.fa-42-group,.fa-innosoft{--fa:"\e080"}.fa-stack-exchange{--fa:"\f18d"}.fa-elementor{--fa:"\f430"}.fa-pied-piper-square,.fa-square-pied-piper{--fa:"\e01e"}.fa-creative-commons-nd{--fa:"\f4eb"}.fa-palfed{--fa:"\f3d8"}.fa-superpowers{--fa:"\f2dd"}.fa-resolving{--fa:"\f3e7"}.fa-xbox{--fa:"\f412"}.fa-square-web-awesome-stroke{--fa:"\e684"}.fa-searchengin{--fa:"\f3eb"}.fa-tiktok{--fa:"\e07b"}.fa-facebook-square,.fa-square-facebook{--fa:"\f082"}.fa-renren{--fa:"\f18b"}.fa-linux{--fa:"\f17c"}.fa-glide{--fa:"\f2a5"}.fa-linkedin{--fa:"\f08c"}.fa-hubspot{--fa:"\f3b2"}.fa-deploydog{--fa:"\f38e"}.fa-twitch{--fa:"\f1e8"}.fa-flutter{--fa:"\e694"}.fa-ravelry{--fa:"\f2d9"}.fa-mixer{--fa:"\e056"}.fa-lastfm-square,.fa-square-lastfm{--fa:"\f203"}.fa-vimeo{--fa:"\f40a"}.fa-mendeley{--fa:"\f7b3"}.fa-uniregistry{--fa:"\f404"}.fa-figma{--fa:"\f799"}.fa-creative-commons-remix{--fa:"\f4ee"}.fa-cc-amazon-pay{--fa:"\f42d"}.fa-dropbox{--fa:"\f16b"}.fa-instagram{--fa:"\f16d"}.fa-cmplid{--fa:"\e360"}.fa-upwork{--fa:"\e641"}.fa-facebook{--fa:"\f09a"}.fa-gripfire{--fa:"\f3ac"}.fa-jedi-order{--fa:"\f50e"}.fa-uikit{--fa:"\f403"}.fa-fort-awesome-alt{--fa:"\f3a3"}.fa-phabricator{--fa:"\f3db"}.fa-ussunnah{--fa:"\f407"}.fa-earlybirds{--fa:"\f39a"}.fa-trade-federation{--fa:"\f513"}.fa-autoprefixer{--fa:"\f41c"}.fa-whatsapp{--fa:"\f232"}.fa-square-upwork{--fa:"\e67c"}.fa-slideshare{--fa:"\f1e7"}.fa-google-play{--fa:"\f3ab"}.fa-viadeo{--fa:"\f2a9"}.fa-line{--fa:"\f3c0"}.fa-google-drive{--fa:"\f3aa"}.fa-servicestack{--fa:"\f3ec"}.fa-simplybuilt{--fa:"\f215"}.fa-bitbucket{--fa:"\f171"}.fa-imdb{--fa:"\f2d8"}.fa-deezer{--fa:"\e077"}.fa-raspberry-pi{--fa:"\f7bb"}.fa-jira{--fa:"\f7b1"}.fa-docker{--fa:"\f395"}.fa-screenpal{--fa:"\e570"}.fa-bluetooth{--fa:"\f293"}.fa-gitter{--fa:"\f426"}.fa-d-and-d{--fa:"\f38d"}.fa-microblog{--fa:"\e01a"}.fa-cc-diners-club{--fa:"\f24c"}.fa-gg-circle{--fa:"\f261"}.fa-pied-piper-hat{--fa:"\f4e5"}.fa-kickstarter-k{--fa:"\f3bc"}.fa-yandex{--fa:"\f413"}.fa-readme{--fa:"\f4d5"}.fa-html5{--fa:"\f13b"}.fa-sellsy{--fa:"\f213"}.fa-square-web-awesome{--fa:"\e683"}.fa-sass{--fa:"\f41e"}.fa-wirsindhandwerk,.fa-wsh{--fa:"\e2d0"}.fa-buromobelexperte{--fa:"\f37f"}.fa-salesforce{--fa:"\f83b"}.fa-octopus-deploy{--fa:"\e082"}.fa-medapps{--fa:"\f3c6"}.fa-ns8{--fa:"\f3d5"}.fa-pinterest-p{--fa:"\f231"}.fa-apper{--fa:"\f371"}.fa-fort-awesome{--fa:"\f286"}.fa-waze{--fa:"\f83f"}.fa-bluesky{--fa:"\e671"}.fa-cc-jcb{--fa:"\f24b"}.fa-snapchat,.fa-snapchat-ghost{--fa:"\f2ab"}.fa-fantasy-flight-games{--fa:"\f6dc"}.fa-rust{--fa:"\e07a"}.fa-wix{--fa:"\f5cf"}.fa-behance-square,.fa-square-behance{--fa:"\f1b5"}.fa-supple{--fa:"\f3f9"}.fa-webflow{--fa:"\e65c"}.fa-rebel{--fa:"\f1d0"}.fa-css3{--fa:"\f13c"}.fa-staylinked{--fa:"\f3f5"}.fa-kaggle{--fa:"\f5fa"}.fa-space-awesome{--fa:"\e5ac"}.fa-deviantart{--fa:"\f1bd"}.fa-cpanel{--fa:"\f388"}.fa-goodreads-g{--fa:"\f3a9"}.fa-git-square,.fa-square-git{--fa:"\f1d2"}.fa-square-tumblr,.fa-tumblr-square{--fa:"\f174"}.fa-trello{--fa:"\f181"}.fa-creative-commons-nc-jp{--fa:"\f4ea"}.fa-get-pocket{--fa:"\f265"}.fa-perbyte{--fa:"\e083"}.fa-grunt{--fa:"\f3ad"}.fa-weebly{--fa:"\f5cc"}.fa-connectdevelop{--fa:"\f20e"}.fa-leanpub{--fa:"\f212"}.fa-black-tie{--fa:"\f27e"}.fa-themeco{--fa:"\f5c6"}.fa-python{--fa:"\f3e2"}.fa-android{--fa:"\f17b"}.fa-bots{--fa:"\e340"}.fa-free-code-camp{--fa:"\f2c5"}.fa-hornbill{--fa:"\f592"}.fa-js{--fa:"\f3b8"}.fa-ideal{--fa:"\e013"}.fa-git{--fa:"\f1d3"}.fa-dev{--fa:"\f6cc"}.fa-sketch{--fa:"\f7c6"}.fa-yandex-international{--fa:"\f414"}.fa-cc-amex{--fa:"\f1f3"}.fa-uber{--fa:"\f402"}.fa-github{--fa:"\f09b"}.fa-php{--fa:"\f457"}.fa-alipay{--fa:"\f642"}.fa-youtube{--fa:"\f167"}.fa-skyatlas{--fa:"\f216"}.fa-firefox-browser{--fa:"\e007"}.fa-replyd{--fa:"\f3e6"}.fa-suse{--fa:"\f7d6"}.fa-jenkins{--fa:"\f3b6"}.fa-twitter{--fa:"\f099"}.fa-rockrms{--fa:"\f3e9"}.fa-pinterest{--fa:"\f0d2"}.fa-buffer{--fa:"\f837"}.fa-npm{--fa:"\f3d4"}.fa-yammer{--fa:"\f840"}.fa-btc{--fa:"\f15a"}.fa-dribbble{--fa:"\f17d"}.fa-stumbleupon-circle{--fa:"\f1a3"}.fa-internet-explorer{--fa:"\f26b"}.fa-stubber{--fa:"\e5c7"}.fa-telegram,.fa-telegram-plane{--fa:"\f2c6"}.fa-old-republic{--fa:"\f510"}.fa-odysee{--fa:"\e5c6"}.fa-square-whatsapp,.fa-whatsapp-square{--fa:"\f40c"}.fa-node-js{--fa:"\f3d3"}.fa-edge-legacy{--fa:"\e078"}.fa-slack,.fa-slack-hash{--fa:"\f198"}.fa-medrt{--fa:"\f3c8"}.fa-usb{--fa:"\f287"}.fa-tumblr{--fa:"\f173"}.fa-vaadin{--fa:"\f408"}.fa-quora{--fa:"\f2c4"}.fa-square-x-twitter{--fa:"\e61a"}.fa-reacteurope{--fa:"\f75d"}.fa-medium,.fa-medium-m{--fa:"\f23a"}.fa-amilia{--fa:"\f36d"}.fa-mixcloud{--fa:"\f289"}.fa-flipboard{--fa:"\f44d"}.fa-viacoin{--fa:"\f237"}.fa-critical-role{--fa:"\f6c9"}.fa-sitrox{--fa:"\e44a"}.fa-discourse{--fa:"\f393"}.fa-joomla{--fa:"\f1aa"}.fa-mastodon{--fa:"\f4f6"}.fa-airbnb{--fa:"\f834"}.fa-wolf-pack-battalion{--fa:"\f514"}.fa-buy-n-large{--fa:"\f8a6"}.fa-gulp{--fa:"\f3ae"}.fa-creative-commons-sampling-plus{--fa:"\f4f1"}.fa-strava{--fa:"\f428"}.fa-ember{--fa:"\f423"}.fa-canadian-maple-leaf{--fa:"\f785"}.fa-teamspeak{--fa:"\f4f9"}.fa-pushed{--fa:"\f3e1"}.fa-wordpress-simple{--fa:"\f411"}.fa-nutritionix{--fa:"\f3d6"}.fa-wodu{--fa:"\e088"}.fa-google-pay{--fa:"\e079"}.fa-intercom{--fa:"\f7af"}.fa-zhihu{--fa:"\f63f"}.fa-korvue{--fa:"\f42f"}.fa-pix{--fa:"\e43a"}.fa-steam-symbol{--fa:"\f3f6"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(./webfonts/fa-regular-400.woff2) format("woff2"),url(./webfonts/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(./webfonts/fa-solid-900.woff2) format("woff2"),url(./webfonts/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(./webfonts/fa-brands-400.woff2) format("woff2"),url(./webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(./webfonts/fa-solid-900.woff2) format("woff2"),url(./webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(./webfonts/fa-regular-400.woff2) format("woff2"),url(./webfonts/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(./webfonts/fa-solid-900.woff2) format("woff2"),url(./webfonts/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(./webfonts/fa-brands-400.woff2) format("woff2"),url(./webfonts/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(./webfonts/fa-regular-400.woff2) format("woff2"),url(./webfonts/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(./webfonts/fa-v4compatibility.woff2) format("woff2"),url(./webfonts/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} \ No newline at end of file diff --git a/public/s/fa/webfonts/fa-brands-400.ttf b/public/s/fa/webfonts/fa-brands-400.ttf new file mode 100644 index 0000000..0f82a83 Binary files /dev/null and b/public/s/fa/webfonts/fa-brands-400.ttf differ diff --git a/public/s/fa/webfonts/fa-brands-400.woff2 b/public/s/fa/webfonts/fa-brands-400.woff2 new file mode 100644 index 0000000..3c5cf97 Binary files /dev/null and b/public/s/fa/webfonts/fa-brands-400.woff2 differ diff --git a/public/s/fa/webfonts/fa-regular-400.ttf b/public/s/fa/webfonts/fa-regular-400.ttf new file mode 100644 index 0000000..9ee1919 Binary files /dev/null and b/public/s/fa/webfonts/fa-regular-400.ttf differ diff --git a/public/s/fa/webfonts/fa-regular-400.woff2 b/public/s/fa/webfonts/fa-regular-400.woff2 new file mode 100644 index 0000000..57d9179 Binary files /dev/null and b/public/s/fa/webfonts/fa-regular-400.woff2 differ diff --git a/public/s/fa/webfonts/fa-solid-900.ttf b/public/s/fa/webfonts/fa-solid-900.ttf new file mode 100644 index 0000000..1c10972 Binary files /dev/null and b/public/s/fa/webfonts/fa-solid-900.ttf differ diff --git a/public/s/fa/webfonts/fa-solid-900.woff2 b/public/s/fa/webfonts/fa-solid-900.woff2 new file mode 100644 index 0000000..1672102 Binary files /dev/null and b/public/s/fa/webfonts/fa-solid-900.woff2 differ diff --git a/public/s/fa/webfonts/fa-v4compatibility.ttf b/public/s/fa/webfonts/fa-v4compatibility.ttf new file mode 100644 index 0000000..3bcb67f Binary files /dev/null and b/public/s/fa/webfonts/fa-v4compatibility.ttf differ diff --git a/public/s/fa/webfonts/fa-v4compatibility.woff2 b/public/s/fa/webfonts/fa-v4compatibility.woff2 new file mode 100644 index 0000000..fbafb22 Binary files /dev/null and b/public/s/fa/webfonts/fa-v4compatibility.woff2 differ diff --git a/public/s/fonts/Hack.woff b/public/s/fonts/Hack.woff new file mode 100644 index 0000000..e835381 Binary files /dev/null and b/public/s/fonts/Hack.woff differ diff --git a/public/s/fonts/Manrope-Bold.woff2 b/public/s/fonts/Manrope-Bold.woff2 new file mode 100644 index 0000000..84f7af5 Binary files /dev/null and b/public/s/fonts/Manrope-Bold.woff2 differ diff --git a/public/s/fonts/Manrope-ExtraBold.woff2 b/public/s/fonts/Manrope-ExtraBold.woff2 new file mode 100644 index 0000000..b5cd400 Binary files /dev/null and b/public/s/fonts/Manrope-ExtraBold.woff2 differ diff --git a/public/s/fonts/Manrope-ExtraLight.woff2 b/public/s/fonts/Manrope-ExtraLight.woff2 new file mode 100644 index 0000000..cc1dbe6 Binary files /dev/null and b/public/s/fonts/Manrope-ExtraLight.woff2 differ diff --git a/public/s/fonts/Manrope-Light.woff2 b/public/s/fonts/Manrope-Light.woff2 new file mode 100644 index 0000000..bf07ee0 Binary files /dev/null and b/public/s/fonts/Manrope-Light.woff2 differ diff --git a/public/s/fonts/Manrope-Medium.woff2 b/public/s/fonts/Manrope-Medium.woff2 new file mode 100644 index 0000000..70447ee Binary files /dev/null and b/public/s/fonts/Manrope-Medium.woff2 differ diff --git a/public/s/fonts/Manrope-Regular.woff2 b/public/s/fonts/Manrope-Regular.woff2 new file mode 100644 index 0000000..7eaaa95 Binary files /dev/null and b/public/s/fonts/Manrope-Regular.woff2 differ diff --git a/public/s/fonts/Manrope-SemiBold.woff2 b/public/s/fonts/Manrope-SemiBold.woff2 new file mode 100644 index 0000000..7cc09a3 Binary files /dev/null and b/public/s/fonts/Manrope-SemiBold.woff2 differ diff --git a/public/s/img/404.gif b/public/s/img/404.gif new file mode 100644 index 0000000..045b2ae Binary files /dev/null and b/public/s/img/404.gif differ diff --git a/public/s/img/audio.webp b/public/s/img/audio.webp new file mode 100644 index 0000000..1faff45 Binary files /dev/null and b/public/s/img/audio.webp differ diff --git a/public/s/img/favicon.gif b/public/s/img/favicon.gif new file mode 100644 index 0000000..e8a5669 Binary files /dev/null and b/public/s/img/favicon.gif differ diff --git a/public/s/img/iconset.svg b/public/s/img/iconset.svg new file mode 100644 index 0000000..74c8cf1 --- /dev/null +++ b/public/s/img/iconset.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/s/img/loool.webp b/public/s/img/loool.webp new file mode 100644 index 0000000..e1742b1 Binary files /dev/null and b/public/s/img/loool.webp differ diff --git a/public/s/img/music.webp b/public/s/img/music.webp new file mode 100644 index 0000000..52ca8c0 Binary files /dev/null and b/public/s/img/music.webp differ diff --git a/public/s/img/swf.png b/public/s/img/swf.png new file mode 100644 index 0000000..50bebc8 Binary files /dev/null and b/public/s/img/swf.png differ diff --git a/public/s/img/v0ck.svg b/public/s/img/v0ck.svg new file mode 100644 index 0000000..ad4e8ef --- /dev/null +++ b/public/s/img/v0ck.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/public/s/js/admin.js b/public/s/js/admin.js new file mode 100644 index 0000000..bb327bb --- /dev/null +++ b/public/s/js/admin.js @@ -0,0 +1,448 @@ +(async () => { + // Helper to get dynamic context + const getContext = () => { + const idLink = document.querySelector("a.id-link"); + if (!idLink) return null; + const tagsContainer = document.querySelector("#tags"); + const inner = tagsContainer.querySelector(".tags-inner") || tagsContainer; + const usernameEl = document.querySelector("a#a_username"); + return { + postid: +idLink.innerText, + // Prefer data-username (raw DB username) over innerText (may be a display name) + poster: usernameEl?.dataset?.username || usernameEl?.innerText?.trim() || null, + tags: [...inner.querySelectorAll(".badge")].map(t => t.innerText.slice(0, -2)) + }; + }; + + const queryapi = async (url, data, method = 'GET') => { + let req; + if (method == 'POST') { + req = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": window.f0ckSession?.csrf_token + }, + body: JSON.stringify(data) + }); + } + else { + let s = []; + for (const [key, val] of Object.entries(data)) + s.push(encodeURIComponent(key) + "=" + encodeURIComponent(val)); + req = await fetch(url + '?' + s.join('&')); + } + return await req.json(); + }; + + const get = async (url, data) => queryapi(url, data, 'GET'); + const post = async (url, data) => queryapi(url, data, 'POST'); + + const renderTags = (_tags, highlightTag = null) => { + const tagsContainer = document.querySelector("#tags"); + if (!tagsContainer) return; + const inner = tagsContainer.querySelector(".tags-inner") || tagsContainer; + + // Only remove existing dynamically generated tags + [...inner.querySelectorAll(".badge")].forEach(tag => { + // Don't remove the one containing the add/toggle buttons, and don't remove the autocomplete input itself + if (!tag.querySelector('#a_addtag') && !tag.querySelector('#a_toggle') && !tag.classList.contains('tag-ac-wrapper')) { + tag.parentElement.removeChild(tag); + } + }); + + _tags.reverse().forEach(tag => { + const a = document.createElement("a"); + a.href = `/tag/${tag.normalized}`; + a.style = "color: inherit !important"; + a.textContent = tag.tag; + + const span = document.createElement("span"); + span.classList.add("badge", "mr-2"); + if (highlightTag && (tag.tag === highlightTag || tag.normalized === highlightTag)) { + span.classList.add('new-tag-glow'); + } + span.setAttribute('tooltip', tag.display_name || tag.user); + + tag.badge.split(" ").forEach(b => span.classList.add(b)); + + const delbutton = document.createElement("a"); + delbutton.innerHTML = ''; + delbutton.href = "javascript:void(0)"; + // Class for delegation + delbutton.classList.add("admin-deltag", "removetag"); + + span.appendChild(a); + span.appendChild(document.createTextNode('\u00A0')); + span.appendChild(delbutton); + + inner.insertAdjacentElement("afterbegin", span); + }); + + // Handle show more/less toggle visibility and count + const allBadges = [...inner.querySelectorAll(".badge")]; + const realTags = allBadges.filter(b => !b.querySelector('#a_addtag') && !b.querySelector('#a_toggle') && !b.classList.contains('tag-ac-wrapper')); + + let toggle = tagsContainer.querySelector(".show-tags-toggle"); + + if (realTags.length > 10) { + if (!toggle) { + toggle = document.createElement("a"); + toggle.href = "#"; + toggle.className = "show-tags-toggle"; + tagsContainer.appendChild(toggle); + } + const hiddenCount = realTags.length - 10; + toggle.dataset.count = hiddenCount; + + // Auto-expand when rendering new tags (e.g. after adding one) as requested + tagsContainer.classList.add('tags-expanded'); + toggle.textContent = "show less"; + + } else if (toggle) { + toggle.remove(); + tagsContainer.classList.remove('tags-expanded'); + } + }; + + window.renderTags = renderTags; + + const deleteEvent = async e => { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + const ctx = getContext(); + if (!ctx) return; + const { postid } = ctx; + + let target = e.target; + if (target.nodeType === 3) target = target.parentElement; + + const badge = target.closest('.badge'); + if (!badge) return; + const tagLink = badge.querySelector('a[href*="/tag/"], a:first-of-type'); + const tagname = tagLink ? tagLink.innerText.trim() : null; + if (!tagname) return; + + if (typeof ModAction === 'undefined') return alert('Error: ModAction module not loaded'); + + ModAction.confirm((window.f0ckI18n && window.f0ckI18n.tag_delete_title) || 'Delete Tag', `${(window.f0ckI18n && window.f0ckI18n.tag_delete_confirm) || 'Are you sure you want to delete the tag'} ${tagname}?`, async (reason) => { + // Send reason via query param for DELETE request + const res = await (await fetch("/api/v2/tags/" + postid + "/" + encodeURIComponent(tagname) + (reason ? "?reason=" + encodeURIComponent(reason) : ""), { + method: 'DELETE', + headers: { "X-CSRF-Token": window.f0ckSession?.csrf_token } + })).json(); + + if (!res.success) { + throw new Error(res.msg || "Error deleting tag"); + } + renderTags(res.tags); + if (window.flashMessage) window.flashMessage((window.f0ckI18n?.tag_deleted_success) || 'Tag deleted', 2500, 'success'); + }, { allowEmpty: window.f0ckSession?.is_admin }); + }; + + const addtagClick = (e) => { + if (e) e.preventDefault(); + const ctx = getContext(); + if (!ctx) return; + const { postid, tags } = ctx; + const anchor = document.querySelector("a#a_addtag"); + if (!anchor) return; + + TagAutocomplete.open({ + postid, + existingTags: tags, + anchorEl: anchor, + onSubmit: async (tag) => post("/api/v2/tags/" + postid, { tagname: tag }), + renderTags + }); + }; + + + const toggleFavEvent = async (e) => { + const ctx = getContext(); + if (!ctx) return; + const { postid } = ctx; + + // Read state BEFORE the API call so we know which direction to toggle + const favoBtn = document.querySelector("#a_favo"); + const wasAlreadyFav = favoBtn && favoBtn.classList.contains('fa-solid'); + + const res = await post('/api/v2/togglefav', { + postid: postid + }); + if (res.success) { + // New state is the logical opposite of what it was before the API call + const isNowFav = !wasAlreadyFav; + + if (favoBtn) { + favoBtn.classList.toggle('fa-solid', isNowFav); + favoBtn.classList.toggle('fa-regular', !isNowFav); + } + + const favcontainer = document.querySelector('#favs'); + favcontainer.innerHTML = ""; + if (res.favs.length > 0) { + res.favs.forEach(f => { + const a = document.createElement('a'); + a.href = `/user/${f.user}`; + a.setAttribute('tooltip', f.display_name || f.user); + a.setAttribute('flow', 'up'); + + const img = document.createElement('img'); + img.src = f.avatar_file ? `/a/${f.avatar_file}` : (f.avatar ? `/t/${f.avatar}.webp` : '/a/default.png'); + img.style.height = "32px"; + img.style.width = "32px"; + if (f.username_color) img.style.borderColor = f.username_color; + + a.appendChild(img); + favcontainer.appendChild(a); + favcontainer.appendChild(document.createTextNode('\u00A0')); + }); + favcontainer.hidden = false; + } else { + favcontainer.hidden = true; + } + + window.flashMessage((window.f0ckI18n && (isNowFav ? window.f0ckI18n.fav_added : window.f0ckI18n.fav_removed)) || (isNowFav ? 'ADDED TO FAVORITES' : 'REMOVED FROM FAVORITES')); + if (navigator.vibrate) navigator.vibrate(50); + } + }; + + const deleteButtonEvent = async e => { + if (e) { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + } + const ctx = getContext(); + if (!ctx) return; + const { postid, poster } = ctx; + + if (typeof ModAction === 'undefined') return alert('Error: ModAction module not loaded'); + + const i18n = window.f0ckI18n || {}; + const confirmTitle = i18n.item_delete_title || 'Delete Item'; + const confirmMsg = (i18n.item_delete_confirm || 'Are you sure you want to delete item {id} by {user}?') + .replace('{id}', postid) + .replace('{user}', poster || 'unknown'); + + ModAction.confirm(confirmTitle, confirmMsg, async (reason) => { + // Flag immediately so the SSE delete_item handler skips navigation + window._adminJustDeletedItem = postid; + const res = await post("/api/v2/admin/deletepost", { + postid: postid, + reason: reason + }); + if (!res.success) { + window._adminJustDeletedItem = null; + throw new Error(res.msg || "Error deleting item"); + } + const mediaObj = document.querySelector('.media-object'); + if (mediaObj) { + mediaObj.innerHTML = '

Item Deleted

The item has been successfully removed.

'; + } + if (window.flashMessage) window.flashMessage((window.f0ckI18n?.item_deleted_success) || 'Item deleted', 2500, 'success'); + // Clear flag after a short delay (SSE has surely arrived by then) + setTimeout(() => { window._adminJustDeletedItem = null; }, 3000); + }, { allowEmpty: window.f0ckSession?.is_admin }); + }; + + let tmptt = null; + const editTagEvent = async e => { + e.preventDefault(); + if (e.detail === 2) { // Double click + clearTimeout(tmptt); + const old = e.target; + const parent = e.target.parentElement; + const oldtag = e.target.innerText; + + const textfield = document.createElement('input'); + textfield.value = e.target.innerText; + textfield.size = 10; + + parent.insertAdjacentElement('afterbegin', textfield); + textfield.focus(); + parent.removeChild(e.target); + // Hide delete button while editing + const delBtn = parent.querySelector('a:last-child'); + if (delBtn) delBtn.style.display = 'none'; + + textfield.addEventListener("keydown", async e => { + if (e.key === 'Enter' || e.keyCode === 13) { + parent.removeChild(textfield); + let res = await fetch('/api/v2/tags/rename/' + encodeURIComponent(oldtag), { + method: 'PUT', + headers: { + "Content-Type": "application/json", + "X-CSRF-Token": window.f0ckSession?.csrf_token + }, + body: JSON.stringify({ newtag: textfield.value }) + }); + const status = res.status; + res = await res.json(); + switch (status) { + case 200: + case 201: + parent.insertAdjacentElement('afterbegin', old); + if (delBtn) delBtn.style.display = ''; + old.href = `/tag/${res.tag}`; + old.innerText = res.tag.trim(); + break; + default: + console.log(res); + break; + } + } + else if (e.key === 'Escape') { + parent.removeChild(textfield); + parent.insertAdjacentElement('afterbegin', old); + if (delBtn) delBtn.style.display = ''; + } + }); + } + else + tmptt = setTimeout(() => location.href = e.target.href, 250); + return false; + }; + + // Event Delegation + document.addEventListener("click", e => { + const target = e.target.nodeType === 3 ? e.target.parentElement : e.target; + + if (target.closest("a#a_addtag")) { + addtagClick(e); + } else if (target.closest("#a_delete")) { + deleteButtonEvent(e); + } else if (target.matches('#tags .badge > a[href*="/tag/"]')) { + editTagEvent(e); + } else if (target.closest('.admin-deltag') || target.closest('.removetag')) { + deleteEvent(e); + } else if (target.closest("#a_pin")) { + pinButtonEvent(e); + } else if (target.closest("#a_favo")) { + toggleFavEvent(e); + } + }); + + const pinButtonEvent = async e => { + if (e) e.preventDefault(); + const ctx = getContext(); + if (!ctx) return; + const { postid } = ctx; + + const pinBtn = document.querySelector('#a_pin'); + if (!pinBtn) return; + + const isPinned = pinBtn.getAttribute('data-pinned') === 'true'; + const url = isPinned ? `/mod/unpin?id=${postid}` : `/mod/pin?id=${postid}`; + + try { + const res = await (await fetch(url, { + method: 'POST', + headers: { "X-CSRF-Token": window.f0ckSession?.csrf_token } + })).json(); + + if (res.success) { + const newState = res.pinned; + const title = newState ? 'Unpin from main' : 'Pin to main'; + const currentBtn = document.querySelector('#a_pin'); + if (currentBtn) { + currentBtn.setAttribute('data-pinned', newState); + currentBtn.setAttribute('title', title); + currentBtn.classList.toggle('active', newState); + } + window.flashMessage(newState ? 'ITEM PINNED' : 'ITEM UNPINNED'); + } else { + alert('Error: ' + res.msg); + } + } catch (err) { + console.error('Pin error:', err); + } + }; + + document.addEventListener("keyup", e => { + if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") return; + const ctx = getContext(); + if (!ctx) return; + + // 'f' and 'i' handled by f0ckm.js keybindings via programmatic click + if (e.key === "x") deleteButtonEvent(); + else if (e.key === "g") pinButtonEvent(); + }); + + window.adminSetPassword = async (btn) => { + const id = btn.dataset.id; + const name = btn.dataset.name; + const password = prompt(`Enter new password for ${name} (min 20 chars):`); + if (!password) return; + if (password.length < 20) return alert('Password must be at least 20 characters.'); + + if (!confirm(`Are you sure you want to set a new password for ${name}? This will invalidate all their existing sessions and force them to change it on next login.`)) return; + + try { + const data = await post('/api/v2/admin/users/set-password', { user_id: id, password }); + if (data.success) { + alert(data.msg); + } else { + alert(data.msg || 'Failed to set password'); + } + } catch (err) { + alert('Network error'); + } + }; + + window.adminDeleteUser = async (btn) => { + const id = btn.dataset.id; + const name = btn.dataset.name; + if (!confirm(`CRITICAL ACTION: Are you sure you want to PERMANENTLY DELETE user ${name}? All their uploads and comments will be reassigned to 'deleted_user'. This cannot be undone.`)) return; + + try { + const data = await post('/api/v2/admin/users/delete', { user_id: id }); + if (data.success) { + alert(data.msg); + document.getElementById(`user-row-${id}`)?.remove(); + } else { + alert(data.msg || 'Failed to delete user'); + } + } catch (err) { + alert('Network error'); + } + }; + + window.adminResetLoginAttempts = async (btn) => { + const username = btn.dataset.username; + if (!confirm(`Are you sure you want to reset login attempts for ${username}?`)) return; + + try { + const data = await post('/api/v2/admin/users/reset-login-attempts', { username }); + if (data.success) { + alert(data.msg); + window.location.reload(); // Quickest way to refresh badges + } else { + alert(data.msg || 'Failed to reset attempts'); + } + } catch (err) { + alert('Network error'); + } + }; + + window.adminBulkDeleteHalls = async (btn) => { + const id = btn.dataset.id; + const name = btn.dataset.name; + if (!confirm(`Are you sure you want to PERMANENTLY DELETE ALL HALLS for ${name}? This cannot be undone.`)) return; + + try { + const data = await post('/api/v2/admin/users/bulk-delete-halls', { user_id: id }); + if (data.success) { + alert(data.msg); + } else { + alert(data.msg || 'Failed to delete halls'); + } + } catch (err) { + alert('Network error'); + } + }; + +})(); + diff --git a/public/s/js/comments.js b/public/s/js/comments.js new file mode 100644 index 0000000..b0d4204 --- /dev/null +++ b/public/s/js/comments.js @@ -0,0 +1,2509 @@ +class CommentSystem { + constructor() { + this.container = document.getElementById('comments-container'); + this.itemId = this.container ? this.container.dataset.itemId : null; + this.user = this.container ? this.container.dataset.user : null; // logged in user? + this.isAdmin = this.container ? this.container.dataset.isAdmin === 'true' : false; + this.isLocked = this.container ? this.container.dataset.isLocked === 'true' : false; + this.sort = (document.body.classList.contains('layout-legacy') || document.body.classList.contains('legacy-view')) ? 'old' : 'new'; + this.customEmojis = CommentSystem.emojiCache || {}; + + this.icons = { + reply: ``, + pin: ``, + unpin: ``, + edit: ``, + delete: ``, + link: ``, + pinned: ``, + lock: ``, + unlock: `` + }; + + // Restore visibility state + if (this.container) { + const isHidden = localStorage.getItem('comments_hidden') === 'true'; + // Force show if hash is present + if (window.location.hash && window.location.hash.startsWith('#c')) { + this.container.classList.remove('faded-out'); + this.container.style.display = 'block'; + localStorage.setItem('comments_hidden', 'false'); + } else if (isHidden) { + this.container.classList.add('faded-out'); + this.container.style.display = 'none'; + const layout = this.container.closest('.item-layout-container'); + if (layout) layout.classList.add('sidebar-hidden'); + } + } + + this.initialLoadDone = false; + this.pendingSubmissions = new Set(); + this.isMainSubmitting = false; + this.scrollListenerAdded = false; + + if (this.itemId) { + this.init(); + } + } + + async init() { + if (!this.container) { + console.warn('[CommentSystem] Container not found during init'); + return; + } + if (this.container.dataset.commentSystemInit) { + console.log('[CommentSystem] Already initialized for this container'); + return; + } + this.container.dataset.commentSystemInit = 'true'; + console.log('[CommentSystem] Initializing for item:', this.itemId); + + this.loadEmojis(); // Don't await + this.loadComments(); + this.setupGlobalListeners(); + this.setupDelegatedEvents(); + this.startLiveTimestamps(); + this.setupScrollListener(); + } + + setupScrollListener() { + if (this.scrollListenerAdded) return; + if (!document.body.classList.contains('layout-legacy') && !document.body.classList.contains('legacy-view')) return; + + const updateBtn = () => { + const btn = document.querySelector('.scroll-to-bottom'); + if (!btn) return; + + const container = document.getElementById('comments-container'); + if (!container) return; + + const rect = container.getBoundingClientRect(); + // Detect if we are at the bottom of the comments section + const isAtBottom = rect.bottom < window.innerHeight + 100; + + if (isAtBottom) { + btn.classList.add('is-at-bottom'); + btn.setAttribute('title', 'Scroll to top'); + } else { + btn.classList.remove('is-at-bottom'); + btn.setAttribute('title', 'Scroll to bottom'); + } + }; + + this.scrollHandler = updateBtn; + window.addEventListener('scroll', this.scrollHandler, { passive: true }); + this.scrollListenerAdded = true; + + // Dynamic detection after potential renders + this.scrollTimer = setInterval(this.scrollHandler, 1000); + } + + /** + * Boot or reload the Danmaku engine with fresh comment data. + * Called after render() so the overlay is always in sync with visible comments. + */ + _loadDanmaku(comments) { + if (typeof Danmaku === 'undefined') return; + + const playerEl = document.querySelector('.v0ck') || document.getElementById('ruffle-container'); + if (!playerEl) return; + + // Ensure the container is positioned so the absolute overlay works + if (getComputedStyle(playerEl).position === 'static') { + playerEl.style.position = 'relative'; + } + + // Prefer real