code:
'use strict';

const Mongo = require(__dirname+'/db.js')
	, { isIP } = require('net')
	, { DAY } = require(__dirname+'/../lib/converter/timeutils.js')
	, Boards = require(__dirname+'/boards.js')
	, Stats = require(__dirname+'/stats.js')
	, { Permissions } = require(__dirname+'/../lib/permission/permissions.js')
	, { randomBytes } = require('crypto')
	, randomBytesAsync = require('util').promisify(randomBytes)
	, db = Mongo.db.collection('posts')
	, config = require(__dirname+'/../lib/misc/config.js');

module.exports = {

	db,

	getThreadPage: async (board, thread) => {
		const threadsBefore = await db.aggregate([
			{
				'$match': {
					'thread': null,
					'board': board,
				}
			}, {
				'$project': {
					'sticky': 1,
					'bumped': 1,
					'postId': 1,
					'board': 1,
					'thread': 1
				}
			}, {
				'$sort': {
					'sticky': -1,
					'bumped': -1
				}
			}
		]).toArray();
		//is there a way to do this in the db with an aggregation stage, instead of in js?
		const threadIndex = threadsBefore.findIndex((e) => e.postId === thread);
		const threadPage = Math.max(1, Math.ceil((threadIndex+1)/10));
		return threadPage;
	},

	getBoardRecent: async (offset=0, limit=20, ip, board, permissions) => {
		const query = {};
		if (board) {
			query['board'] = board;
		}
		const projection = {
			'salt': 0,
			'password': 0,
		};
		if (!board) {
			projection['reports'] = 0;
		} else {
			projection['globalreports'] = 0;
		}
		if (ip != null) {
			if (isIP(ip)) {
				query['ip.raw'] = ip;
			} else {
				query['ip.cloak'] = ip;
			}
		}
		if (!permissions.get(Permissions.VIEW_RAW_IP)) {
			projection['ip.raw'] = 0;
			//MongoError, why cant i just projection['reports.ip.raw'] = 0;
			if (board) {
				projection['reports'] = { ip: { raw: 0 } };
			} else {
				projection['globalreports'] = { ip: { raw: 0 } };
			}
		}
		const posts = await db.find(query, {
			projection
		}).sort({
			'_id': -1
		}).skip(offset).limit(limit).toArray();
		return posts;
	},

	getRecent: async (board, page, limit=10, getSensitive=false, sortSticky=true) => {
		// get all thread posts (posts with null thread id)
		const projection = {
			'salt': 0,
			'password': 0,
			'reports': 0,
			'globalreports': 0,
		};
		if (!getSensitive) {
			projection['ip'] = 0;
		}
		const threadsQuery = {
			'thread': null,
		};
		if (board) {
			if (Array.isArray(board)) {
				//array for overboard
				threadsQuery['board'] = {
					'$in': board
				};
			} else {
				threadsQuery['board'] = board;
			}
		}
		let threadsSort = {
			'bumped': -1,
		};
		if (sortSticky === true) {
			threadsSort = {
				'sticky': -1,
				'bumped': -1
			};
		}
		const threads = await db.find(threadsQuery, {
			projection
		})
			.sort(threadsSort)
			.skip(10*(page-1))
			.limit(limit)
			.toArray();

		// add last n posts in reverse order to preview
		await Promise.all(threads.map(async thread => {
			const { stickyPreviewReplies, previewReplies } = config.get;
			const previewRepliesLimit = thread.sticky ? stickyPreviewReplies : previewReplies;
			const replies = previewRepliesLimit === 0 ? [] : await db.find({
				'thread': thread.postId,
				'board': thread.board
			},{
				projection
			}).sort({
				'postId': -1
			}).limit(previewRepliesLimit).toArray();

			//reverse order for board page
			thread.replies = replies.reverse();

			//if enough replies, show omitted count
			if (thread.replyposts > previewRepliesLimit) {
				//dont show all backlinks on OP for previews on index page
				thread.previewbacklinks = [];
				if (previewRepliesLimit > 0) {
					const firstPreviewId = thread.replies[0].postId;
					const latestPreviewBacklink = thread.backlinks.find(bl => { return bl.postId >= firstPreviewId; });
					if (latestPreviewBacklink != null) {
						const latestPreviewIndex = thread.backlinks.map(bl => bl.postId).indexOf(latestPreviewBacklink.postId);
						thread.previewbacklinks = thread.backlinks.slice(latestPreviewIndex);
					}
				}
				//count omitted image and posts
				const numPreviewFiles = replies.reduce((acc, post) => { return acc + post.files.length; }, 0);
				thread.omittedfiles = thread.replyfiles - numPreviewFiles;
				thread.omittedposts = thread.replyposts - replies.length;
			}
		}));
		return threads;

	},

	resetThreadAggregates: (ors) => {
		return db.aggregate([
			{
				'$match': {
					'$or': ors
				}
			}, {
				'$set': {
					'replyposts': 0,
					'replyfiles': 0,
					'bumped': '$date'
				}
			}, {
				'$project': {
					'_id': 1,
					'board': 1,
					'replyposts': 1,
					'replyfiles': 1,
					'bumped': 1
				}
			}
		]).toArray();
	},

	getThreadAggregates: (ors) => {
		return db.aggregate([
			{
				'$match': {
					'$or': ors
				}
			}, {
				'$group': {
					'_id': {
						'thread': '$thread',
						'board': '$board'
					},
					'replyposts': {
						'$sum': 1
					},
					'replyfiles': {
						'$sum': {
							'$size': '$files'
						}
					},
					'bumped': {
						'$max': {
							'$cond': [
								{ '$ne': [ '$email', 'sage' ] },
								'$date',
								0 //still need to improve this to ignore bump limitthreads
							]
						}
					}
				}
			}
		]).toArray();
	},

	getPages: (board) => {
		return db.countDocuments({
			'board': board,
			'thread': null
		});
	},

	getThread: async (board, id, getSensitive=false) => {
		// get thread post and potential replies concurrently
		const projection = {
			'salt': 0,
			'password': 0,
			'reports': 0,
			'globalreports': 0,
		};
		if (!getSensitive) {
			projection['ip'] = 0;
		}
		const [thread, replies] = await Promise.all([
			db.findOne({
				'postId': id,
				'board': board,
				'thread': null,
			}, {
				projection,
			}),
			module.exports.getThreadPosts(board, id, projection)
		]);
		// attach the replies to the thread post
		if (thread && replies) {
			thread.replies = replies;
		}
		return thread;
	},

	getThreadPosts: (board, id, projection) => {
		// all posts within a thread
		return db.find({
			'thread': id,
			'board': board
		}, {
			projection
		}).sort({
			'postId': 1
		}).toArray();
	},

	getMultipleThreadPosts: (board, ids) => {
		//all posts from multiple threads in a single board
		return db.find({
			'board': board,
			'thread': {
				'$in': ids
			}
		}, {
			'projection': {
				'salt': 0 ,
				'password': 0,
				'ip': 0,
				'reports': 0,
				'globalreports': 0,
			}
		}).toArray();
	},

	getCatalog: (board, sortSticky=true, catalogLimit=0) => {

		const threadsQuery = {
			thread: null,
		};
		if (board) {
			if (Array.isArray(board)) {
				//array for overboard catalog
				threadsQuery['board'] = {
					'$in': board
				};
			} else {
				threadsQuery['board'] = board;
			}
		}
		let threadsSort = {
			'bumped': -1,
		};
		if (sortSticky === true) {
			threadsSort = {
				'sticky': -1,
				'bumped': -1
			};
		}
		// get all threads for catalog
		return db.find(threadsQuery, {
			'projection': {
				'salt': 0,
				'password': 0,
				'ip': 0,
				'reports': 0,
				'globalreports': 0,
			}
		})
			.limit(catalogLimit)
			.sort(threadsSort)
			.toArray();

	},

	getPost: (board, id, getSensitive=false) => {

		// get a post
		if (getSensitive) {
			return db.findOne({
				'postId': id,
				'board': board
			});
		}

		return db.findOne({
			'postId': id,
			'board': board
		}, {
			'projection': {
				'salt': 0,
				'password': 0,
				'ip': 0,
				'reports': 0,
				'globalreports': 0,
			}
		});

	},

	checkExistingMessage: async (board, thread = null, hash) => {
		const query = {
			'board': board,
			'messagehash': hash,
		};
		if (thread !== null) {
			query['$or'] = [
				{ 'thread': thread },
				{ 'postId': thread },
			];
		}
		const postWithExistingMessage = await db.findOne(query, {
			'projection': {
				'messagehash': 1,
			}
		});
		return postWithExistingMessage;
	},

	checkExistingFiles: async (board, thread = null, hashes) => {
		const query = {
			'board': board,
			'files.hash': {
				'$in': hashes
			}
		};
		if (thread !== null) {
			query['$or'] = [
				{ 'thread': thread },
				{ 'postId': thread },
			];
		}
		const postWithExistingFiles = await db.findOne(query, {
			'projection': {
				'files.hash': 1,
			}
		});
		return postWithExistingFiles;
	},

	allBoardPosts: (board) => {
		return db.find({
			'board': board
		}).toArray();
	},

	//takes array "ids" of post ids
	getPosts: (board, ids, getSensitive=false) => {

		if (getSensitive) {
			return db.find({
				'postId': {
					'$in': ids
				},
				'board': board
			}).toArray();
		}

		return db.find({
			'postId': {
				'$in': ids
			},
			'board': board
		}, {
			'projection': {
				'salt': 0,
				'password': 0,
				'ip': 0,
				'reports': 0,
				'globalreports': 0,
			}
		}).toArray();

	},

	// get only thread and post id for use in quotes
	getPostsForQuotes: (queryOrs) => {
		const { quoteLimit } = config.get;
		return db.find({
			'$or': queryOrs
		}, {
			'projection': {
				'postId': 1,
				'board': 1,
				'thread': 1,
			}
		}).limit(quoteLimit).toArray();
	},

	//takes array "ids" of mongo ids to get posts from any board
	globalGetPosts: (ids) => {
		return db.find({
			'_id': {
				'$in': ids
			},
		}).toArray();
	},

	insertOne: async (board, data, thread, anonymizer) => {
		const sageEmail = data.email === 'sage';
		const bumpLocked = thread && thread.bumplocked === 1;
		const bumpLimited = thread && thread.replyposts >= board.settings.bumpLimit;
		const cyclic = thread && thread.cyclic === 1;
		const saged = sageEmail || bumpLocked || (bumpLimited && !cyclic);
		if (data.thread !== null) {
			const filter = {
				'postId': data.thread,
				'board': board._id
			};
			//update thread reply and reply file count
			const query = {
				'$inc': {
					'replyposts': 1,
					'replyfiles': data.files.length
				}
			};
			//if post email is not sage, and thread not bumplocked, set bump date
			if (!saged) {
				query['$set'] = {
					'bumped': new Date()
				};
			} else if (bumpLimited && !cyclic) {
				query['$set'] = {
					'bumplocked': 1
				};
			}
			//update the thread
			await db.updateOne(filter, query);
		} else {
			//this is a new thread so just set the bump date
			data.bumped = new Date();
		}

		//get the postId and add it to the post
		const postId = await Boards.getNextId(board._id, saged);
		data.postId = postId;

		//insert the post itself
		const postMongoId = await db.insertOne(data).then(result => result.insertedId); //_id of post

		const statsIp = (config.get.statsCountAnonymizers === false && anonymizer === true) ? null : data.ip.cloak;
		await Stats.updateOne(board._id, statsIp, data.thread == null);

		//add backlinks to the posts this post quotes
		if (data.thread && data.quotes.length > 0) {
			await db.updateMany({
				'_id': {
					'$in': data.quotes.map(q => q._id)
				}
			}, {
				'$push': {
					'backlinks': { _id: postMongoId, postId: postId }
				}
			});
		}

		return { postMongoId, postId };

	},

	getBoardReportCounts: (boards) => {
		return db.aggregate([
			{
				'$match': {
					'board': {
						'$in': boards
					},
					'reports.0': {
						'$exists': true
					},
				}
			}, {
				'$group': {
					'_id': '$board',
					'count': {
						'$sum': 1
					}
				}
			}
		]).toArray();
	},

	getGlobalReportsCount: () => {
		return db.countDocuments({
			'globalreports.0': {
				'$exists': true
			}
		});
	},

	getReports: async (board, permissions) => {
		const projection = {
			'salt': 0,
			'password': 0,
			'globalreports': 0,
		};
		if (!permissions.get(Permissions.VIEW_RAW_IP)) {
			projection['ip.raw'] = 0;
			projection['reports'] = { ip: { raw: 0 } };
		}
		const posts = await db.find({
			'reports.0': {
				'$exists': true
			},
			'board': board
		}, { projection }).toArray();
		return posts;
	},

	getGlobalReports: async (offset=0, limit, ip, permissions) => {
		const projection = {
			'salt': 0,
			'password': 0,
			'reports': 0,
		};
		if (!permissions.get(Permissions.VIEW_RAW_IP)) {
			projection['ip.raw'] = 0;
			projection['globalreports'] = { ip: { raw: 0 } };
		}
		const query = {
			'globalreports.0': {
				'$exists': true
			}
		};
		if (ip != null) {
			if (isIP(ip)) {
				query['$or'] = [
					{ 'ip.raw': ip },
					{ 'globalreports.ip.raw': ip }
				];
			} else {
				query['$or'] = [
					{ 'ip.cloak': ip },
					{ 'globalreports.ip.cloak': ip }
				];
			}
		}
		const posts = await db.find(query, { projection }).skip(offset).limit(limit).toArray();
		return posts;
	},

	deleteOne: (board, options) => {
		return db.deleteOne(options);
	},

	pruneThreads: async (board) => {

		//get threads that have been bumped off last page
		const oldThreads = await db.find({
			'thread': null,
			'board': board._id
		}).sort({
			'sticky': -1,
			'bumped': -1
		}).skip(board.settings.threadLimit).toArray();

		let early404Threads = [];
		if (board.settings.early404 === true) {
			early404Threads = await db.aggregate([
				{
					//get all the threads for a board
					'$match': {
						'thread': null,
						'board': board._id
					}
				}, {
					//in bump date order
					'$sort': {
						'sticky': -1,
						'bumped': -1
					}
				}, {
					//skip the first (board.settings.threadLimit/early404Fraction)
					'$skip': Math.ceil(board.settings.threadLimit/config.get.early404Fraction)
				}, {
					//then any that have less than early404Replies replies get matched again
					'$match': {
						'sticky':0,
						'replyposts': {
							'$lt': config.get.early404Replies
						}
					}
				}
			]).toArray();
		}

		return oldThreads.concat(early404Threads);
	},

	getMinimalThreads: (boards) => {
		return db.aggregate([
			{
				'$match': {
					'thread': null,
					'board': {
						'$in': boards,
					}
				}
			}, {
				'$project': {
					'sticky': 1,
					'bumped': 1,
					'postId': 1,
					'board': 1,
					'thread': 1,
				}
			}, {
				'$sort': {
					'sticky': -1,
					'bumped': -1,
				}
			}, {
				'$group': {
					'_id': '$board',
					'posts': {
						'$push': '$$CURRENT',
					}
				}
			}, {
				'$group': {
					'_id': null,
					'posts': {
						'$push': {
							'k': '$_id',
							'v': '$posts',
						}
					}
				}
			}, {
				'$replaceRoot': {
					'newRoot': {
						'$arrayToObject': '$posts',
					}
				}
			}
		]).toArray().then(r => r[0]);
	},

	fixLatest: (boards) => {
		return db.aggregate([
			{
				'$match': {
					//going to match against thread bump date instead
					'thread': null,
					'board': {
						'$in': boards
					},
				}
			}, {
				'$group': {
					'_id': '$board',
					'lastPostTimestamp': {
						'$max':'$bumped'
					}
				}
			}, {
				'$merge': {
					'into': 'boards'
				}
			}
		]).toArray();
	},

	hotThreads: async () => {
		const { hotThreadsLimit, hotThreadsThreshold, hotThreadsMaxAge } = config.get;
		if (hotThreadsLimit === 0) { //0 limit = no limit in mongodb
			return [];
		}
		const listedBoards = await Boards.getLocalListed();
		const potentialHotThreads = await db.find({
			'board': {
				'$in': listedBoards
			},
			'thread': null,
			'date': { //created in last month
				'$gte': new Date(Date.now() - hotThreadsMaxAge)
			},
			'bumped': { //bumped in last 7 days
				'$gte': new Date(Date.now() - (DAY * 7))
			},
			'replyposts': {
				'$gte': hotThreadsThreshold,
			}
		}).toArray();
		if (potentialHotThreads.length === 0) {
			return [];
		}
		const hotThreadReplyOrs = potentialHotThreads
			.map(t => ({ board: t.board, thread: t.postId }));
		const hotThreadScores = await db.aggregate([
			{
				'$match': {
					'$and': [
						{
							'$or': hotThreadReplyOrs
						},
						{
							'date': {
								'$gte': new Date(Date.now() - (DAY * 7))
							}
						},
					],
				},
			}, {
				'$group': {
					'_id': {
						'board': '$board',
						'thread': '$thread',
					},
					'score': {
						'$sum': 1,
					},
				},
			},
		]).toArray();
		//Welcome to improve into a pipeline if possible, but reducing to these maps isnt thaaat bad
		const hotThreadBiasMap = potentialHotThreads
			.reduce((acc, t) => {
				//(1 - (thread age / age limit)) = bias multiplier
				const threadAge = Date.now() - t.u;
				acc[`${t.board}-${t.postId}`] = Math.max(0, 1 - (threadAge / hotThreadsMaxAge)); //(0,1)
				return acc;
			}, {});
		const hotThreadScoreMap = hotThreadScores.reduce((acc, ht) => {
			acc[`${ht._id.board}-${ht._id.thread}`] = ht.score * hotThreadBiasMap[`${ht._id.board}-${ht._id.thread}`];
			return acc;
		}, {});
		const hotThreadsWithScore = potentialHotThreads.map(ht => {
			ht.score = hotThreadScoreMap[`${ht.board}-${ht.postId}`];
			return ht;
		}).sort((a, b) => {
			return b.score - a.score;
		}).slice(0, hotThreadsLimit);
		return hotThreadsWithScore;
	},

	deleteMany: (ids) => {
		return db.deleteMany({
			'_id': {
				'$in': ids
			}
		});
	},

	deleteAll: () => {
		return db.deleteMany();
	},

	move: async (postMongoIds, crossBoard, destinationThreadId, destinationBoard) => {
		let bulkWrites = []
			, newDestinationThreadId = destinationThreadId;
		if (crossBoard) {
			//postIds need to be adjusted if moving to a different board
			const lastId = await Boards.getNextId(destinationBoard, false, postMongoIds.length);
			//if moving board and no destination thread, pick the starting ID of the amount we incremented
			if (!destinationThreadId) {
				newDestinationThreadId = lastId;
			}
			bulkWrites = postMongoIds.map((postMongoId, index) => ({
				'updateOne': {
					'filter': {
						'_id': postMongoId,
					},
					'update': {
						'$set': {
							'postId': lastId + index,
						}
					}
				}
			}));
		}
		bulkWrites.push({
			'updateMany': {
				'filter': {
					'_id': {
						'$in': postMongoIds,
					}
				},
				'update': {
					'$set': {
						'board': destinationBoard,
						'thread': newDestinationThreadId,
					},
					'$unset': {
						'replyposts': '',
						'replyfiles': '',
						'sticky': '',
						'locked': '',
						'bumplocked': '',
						'cyclic': '',
					}
				}
			}
		});
		if (!destinationThreadId) {
			//No destination thread i.e we are maing a new thread from the selected posts, make one the OP
			bulkWrites.push({
				'updateOne': {
					'filter': {
						'_id': postMongoIds[0],
					},
					'update': {
						'$set': {
							'thread': null,
							'replyposts': 0,
							'replyfiles': 0,
							'sticky': 0,
							'locked': 0,
							'bumplocked': 0,
							'cyclic': 0,
							'salt': (await randomBytesAsync(128)).toString('base64'),
						}
					}
				}
			});
		}
		// console.log(JSON.stringify(bulkWrites, null, 4));
		const movedPosts = await db.bulkWrite(bulkWrites).then(result => result.modifiedCount);
		return { movedPosts, destinationThreadId: newDestinationThreadId };
	},

	threadExistsMiddleware: async (req, res, next) => {
		const thread = await module.exports.getPost(req.params.board, req.params.id);
		if (!thread) {
			return res.status(404).render('404');
		}
		res.locals.thread = thread;
		next();
	},

	postExists: (board, postId) => {
		return db.findOne({
			'board': board,
			'postId': postId,
		}, {
			'projection': {
				'salt': 0 ,
				'password': 0,
				'ip': 0,
				'reports': 0,
				'globalreports': 0,
			}
		});
	},

	postExistsMiddleware: async (req, res, next) => {
		const post = await module.exports.postExists(req.params.board, req.params.id);
		if (!post) {
			return res.status(404).render('404');
		}
		res.locals.post = post;
		next();
	},

};


code:
'use strict';

const Mongo = require(__dirname+'/db.js')
	, db = Mongo.db.collection('ratelimit');

module.exports = {

	db,

	resetQuota: (identifier, action) => {
		return db.deleteOne({ '_id': `${identifier}-${action}` });
	},

	incrmentQuota: (identifier, action, amount) => {
		return db.findOneAndUpdate(
			{
				'_id': `${identifier}-${action}`
			},
			{
				'$inc': {
					'sequence_value': amount
				},
				'$setOnInsert': {
					'expireAt': new Date()
				}
			},
			{
				'upsert': true
			}
		).then(r => { return r.value ? r.value.sequence_value : 0; });
	},

	deleteAll: () => {
		return db.deleteMany({});
	},

};

repo_name: Marzisoft/jschan

license: mit


code:
'use strict';

const Mongo = require(__dirname+'/db.js')
	, db = Mongo.db.collection('roles')
	, cache = require(__dirname+'/../lib/redis/redis.js');

module.exports = {

	db,

	findOne: async (id) => {
		//is there any point even caching
		let role = await cache.get(`role:${id}`);
		if (role) {
			return role;
		} else {
			role = await db.findOne({ '_id': id });
			if (role) {
				role.permissions = role.permissions.toString('base64');
				cache.set(`role:${id}`, role);
			}
		}
		return role;
	},

	find: () => {
		return db.find({}).toArray();
	},

	updateOne: async (id, permissions) => {
		const res = await db.updateOne({
			'_id': id
		}, {
			'$set': {
				'permissions': Mongo.Binary(permissions.array),
			},
		});
		cache.del(`role:${id}`);
		return res;
	},

	deleteAll: () => {
		return db.deleteMany({});
	},

};


code:
'use strict';

const Mongo = require(__dirname+'/db.js')
	, db = Mongo.db.collection('poststats');

module.exports = {

	db,

	updateOne: (board, ip, thread) => {
		return db.updateOne({
			'board': board,
			'hour': new Date().getHours()
		}, {
			'$inc': {
				'pph': 1,
				'tph': thread ? 1 : 0
			},
			'$addToSet': {
				'ips': ip
			}
		}, {
			'upsert': true
		});
	},

	getHourPosts: (board) => {
		return db.findOne({
			'board': board,
			'hour': new Date().getHours()
		}, {
			'projection': {
				'_id': 0,
				'pph': 1,
				'tph': 1
			}
		});
	},

	updateBoards: () => {
		return db.aggregate([
			{
				'$group': {
					'_id': '$board',
					'ppd': {
						'$sum': '$pph'
					},
					'pph': {
						'$push': { hour: '$hour', pph: '$pph' },
					},
					'ips': {
						'$addToSet': '$ips'
					}
				}
			}, {
				'$project': {
					'board': '$_id',
					'ips': 1,
					'ppd': 1,
					'pph': {
						$first: {
							$filter: {
								input: '$pph',
								as: 'hr',
								cond: {
									$eq: [ '$$hr.hour', (new Date().getHours()||24)-1 ]
								},
							}
						}
					}
				}
			}, {
				'$unwind': {
					'path': '$ips',
					'preserveNullAndEmptyArrays': true
				}
			}, {
				'$unwind': {
					'path': '$ips',
					'preserveNullAndEmptyArrays': true
				}
			}, {
				'$group': {
					'_id': '$_id',
					'ppd': {
						'$first': '$ppd'
					},
					'pph': {
						'$first': '$pph'
					},
					'ips': {
						'$addToSet': '$ips'
					}
				}
			}, {
				'$project': {
					'ips': {
						'$size': '$ips'
					},
					'ppd': 1,
					'pph': {
						'$ifNull': ['$pph.pph', 0]
					}
				}
			}, {
				'$merge': {
					'into': 'boards'
				}
			}
		]).toArray();
	},

	//reset stats, used at start of each hour
	resetStats: () => {
		return Promise.all([
			db.updateMany({
				'hour': new Date().getHours()
			}, {
				'$set': {
					'ips': [],
					'pph': 0,
					'tph': 0,
				}
			}),
		]);
	},

	deleteBoard: (board) => {
		return db.deleteMany({
			'board': board
		});
	},

	deleteAll: () => {
		return db.deleteMany({});
	},

};


code:
'use strict';

const Mongo = require(__dirname+'/db.js')
	, db = Mongo.db.collection('webring');

module.exports = {

	db,

	boardSort: (skip=0, limit=50, sort={ ips:-1, pph:-1, sequence_value:-1 }, filter={}) => {
		const addedFilter = {};
		if (filter.search) {
			addedFilter['$or'] = [
				{ uri: filter.search },
				{ tags: filter.search }
			];
		}
		const addedSort = {};
		if (sort.ips) {
			addedSort['uniqueUsers'] = sort.ips;
		}
		if (sort.pph) {
			addedSort['postsPerHour'] = sort.pph;
		}
		if (sort.sequence_value) {
			addedSort['totalPosts'] = sort.sequence_value;
		}
		if (sort.lastPostTimestamp) {
			addedSort['lastPostTimestamp'] = sort.lastPostTimestamp;
		}
		return db.find(addedFilter)
			.sort(addedSort)
			.skip(skip)
			.limit(limit)
			.toArray();
	},

	count: (filter) => {
		const addedFilter = {};
		if (filter.search) {
			addedFilter['$or'] = [
				{ uri: filter.search },
				{ tags: filter.search }
			];
		}
		return db.countDocuments(addedFilter);
	},

	deleteAll: () => {
		return db.deleteMany({});
	},

};


code:
version: "3.5"
services:

  redis:
    command: redis-server --requirepass changeme
    image: redis:alpine

  mongodb:
    image: mongo:latest
    environment:
      - MONGO_INITDB_ROOT_USERNAME=jschan
      - MONGO_INITDB_ROOT_PASSWORD=changeme

  nginx:
    build:
      context: .
      dockerfile: ./docker/nginx/Dockerfile
      args:
        ENABLED_MODULES: geoip
    ports:
      - "80:80"
    volumes:
      - ./docker/static/:/path/to/jschan/static/
    depends_on:
      - jschan

  jschan:
    build:
      context: .
      dockerfile: ./docker/jschan/Dockerfile
      network: default
    environment:
      - NODE_ENV=development
      - JSCHAN_IP=0.0.0.0
      - NO_CAPTCHA=1
      - MONGO_USERNAME=jschan
      - MONGO_PASSWORD=changeme
      - REDIS_PASSWORD=changeme
      - COOKIE_SECRET=changeme
      - TRIPCODE_SECRET=changeme
      - IP_HASH_SECRET=changeme
      - POST_PASSWORD_SECRET=changeme
      - GOOGLE_SITEKEY=changeme
      - GOOGLE_SECRETKEY=changeme
      - HCAPTCHA_SITEKEY=10000000-ffff-ffff-ffff-000000000001
      - HCAPTCHA_SECRETKEY=0x0000000000000000000000000000000000000000
    volumes:
      - ./docker/static:/opt/static/
    depends_on:
      - redis
      - mongodb

  jschan-reset:
    build:
      context: .
      dockerfile: ./docker/jschan/Dockerfile-reset
      network: default
    environment:
      - MONGO_USERNAME=jschan
      - MONGO_PASSWORD=changeme
      - REDIS_PASSWORD=changeme
    volumes:
      - ./docker/static:/opt/static/
    depends_on:
      - redis
      - mongodb

networks:
  default:
    name: jschan_default


code:
FROM node:16

RUN apt-get update -y
RUN apt-get install ffmpeg imagemagick graphicsmagick -y

WORKDIR /opt

COPY . .

RUN npm install

RUN npm install -g pm2 gulp

COPY ./docker/jschan/secrets.js ./configs/secrets.js

#i fucking hate docker
ENV MONGO_USERNAME jschan
ENV MONGO_PASSWORD changeme
ENV REDIS_PASSWORD changeme

RUN gulp generate-favicon

CMD ["/bin/sh", "-c", "gulp; pm2-runtime start ecosystem.config.js"]

path: docker/jschan/Dockerfile


code:
FROM node:16

WORKDIR /opt

COPY . .

RUN npm install

RUN npm i -g pm2 gulp

COPY ./docker/jschan/secrets.js ./configs/secrets.js

#i fucking hate docker
ENV MONGO_USERNAME jschan
ENV MONGO_PASSWORD changeme
ENV REDIS_PASSWORD changeme

RUN gulp generate-favicon

CMD ["/bin/sh", "-c", "gulp reset; gulp"]

path: docker/jschan/Dockerfile-reset


code:
module.exports = {

	//mongodb connection string
	dbURL: `mongodb://${process.env.MONGO_USERNAME}:${process.env.MONGO_PASSWORD}@mongodb:27017`,

	//database name
	dbName: 'jschan',

	//redis connection info
	redis: {
		host: 'redis',
		port: '6379',
		password: process.env.REDIS_PASSWORD,
	},

	//backend webserver port
	port: 7000,

	//secrets/salts for various things
	cookieSecret: process.env.COOKIE_SECRET,
	tripcodeSecret: process.env.TRIPCODE_SECRET,
	ipHashSecret: process.env.IP_HASH_SECRET,
	postPasswordSecret: process.env.POST_PASSWORD_SECRET,

	//keys for google recaptcha
	google: {
		siteKey: process.env.GOOGLE_SITEKEY,
		secretKey: process.env.GOOGLE_SECRETKEY,
	},

	//keys for hcaptcha
	hcaptcha: {
		siteKey: process.env.HCAPTCHA_SITEKEY,
		secretKey: process.env.HCAPTCHA_SECRETKEY,
	},

	//keys for yandex smartcaptcha
	yandex: {
		siteKey: process.env.YANDEX_CAPTCHA_SITEKEY,
		secretKey: process.env.YANDEX_CAPTCHA_SECRETKEY,
	},

	//enable debug logging
	debugLogs: true,

};


code:
FROM nginx:mainline as builder

ARG ENABLED_MODULES

RUN set -ex \
    && if [ "$ENABLED_MODULES" = "" ]; then \
        echo "No additional modules enabled, exiting"; \
        exit 1; \
    fi

#COPY ./ /modules/

RUN set -ex \
  && apt update \
    && apt install -y --no-install-suggests --no-install-recommends \
                patch make wget mercurial devscripts debhelper dpkg-dev \
                quilt lsb-release build-essential libxml2-utils xsltproc \
                equivs git g++ libparse-recdescent-perl \
    && XSLSCRIPT_SHA512="f7194c5198daeab9b3b0c3aebf006922c7df1d345d454bd8474489ff2eb6b4bf8e2ffe442489a45d1aab80da6ecebe0097759a1e12cc26b5f0613d05b7c09ffa *stdin" \
    && wget -O /tmp/xslscript.pl https://hg.nginx.org/xslscript/raw-file/01dc9ba12e1b/xslscript.pl \
    && if [ "$(cat /tmp/xslscript.pl | openssl sha512 -r)" = "$XSLSCRIPT_SHA512" ]; then \
        echo "XSLScript checksum verification succeeded!"; \
        chmod +x /tmp/xslscript.pl; \
        mv /tmp/xslscript.pl /usr/local/bin/; \
    else \
        echo "XSLScript checksum verification failed!"; \
        exit 1; \
    fi \
    && hg clone -r ${NGINX_VERSION}-${PKG_RELEASE%%~*} https://hg.nginx.org/pkg-oss/ \
    && cd pkg-oss \
    && mkdir /tmp/packages \
    && for module in $ENABLED_MODULES; do \
        echo "Building $module for nginx-$NGINX_VERSION"; \
        if [ -d /modules/$module ]; then \
            echo "Building $module from user-supplied sources"; \
            # check if module sources file is there and not empty
            if [ ! -s /modules/$module/source ]; then \
                echo "No source file for $module in modules/$module/source, exiting"; \
                exit 1; \
            fi; \
            # some modules require build dependencies
            if [ -f /modules/$module/build-deps ]; then \
                echo "Installing $module build dependencies"; \
                apt update && apt install -y --no-install-suggests --no-install-recommends $(cat /modules/$module/build-deps | xargs); \
            fi; \
            # if a module has a build dependency that is not in a distro, provide a
            # shell script to fetch/build/install those
            # note that shared libraries produced as a result of this script will
            # not be copied from the builder image to the main one so build static
            if [ -x /modules/$module/prebuild ]; then \
                echo "Running prebuild script for $module"; \
                /modules/$module/prebuild; \
            fi; \
            /pkg-oss/build_module.sh -v $NGINX_VERSION -f -y -o /tmp/packages -n $module $(cat /modules/$module/source); \
            BUILT_MODULES="$BUILT_MODULES $(echo $module | tr '[A-Z]' '[a-z]' | tr -d '[/_\-\.\t ]')"; \
        elif make -C /pkg-oss/debian list | grep -P "^$module\s+\d" > /dev/null; then \
            echo "Building $module from pkg-oss sources"; \
            cd /pkg-oss/debian; \
            make rules-module-$module BASE_VERSION=$NGINX_VERSION NGINX_VERSION=$NGINX_VERSION; \
            mk-build-deps --install --tool="apt-get -o Debug::pkgProblemResolver=yes --no-install-recommends --yes" debuild-module-$module/nginx-$NGINX_VERSION/debian/control; \
            make module-$module BASE_VERSION=$NGINX_VERSION NGINX_VERSION=$NGINX_VERSION; \
            find ../../ -maxdepth 1 -mindepth 1 -type f -name "*.deb" -exec mv -v {} /tmp/packages/ \;; \
            BUILT_MODULES="$BUILT_MODULES $module"; \
        else \
            echo "Don't know how to build $module module, exiting"; \
            exit 1; \
        fi; \
    done \
    && echo "BUILT_MODULES=\"$BUILT_MODULES\"" > /tmp/packages/modules.env

FROM nginx:mainline
COPY --from=builder /tmp/packages /tmp/packages
RUN set -ex \
    && apt update \
    && apt-get install wget -y \
    && . /tmp/packages/modules.env \
    && for module in $BUILT_MODULES; do \
           apt install --no-install-suggests --no-install-recommends -y /tmp/packages/nginx-module-${module}_${NGINX_VERSION}*.deb; \
       done \
    && rm -rf /tmp/packages \
    && rm -rf /var/lib/apt/lists/

RUN mkdir /usr/share/GeoIP
RUN wget https://dl.miyuru.lk/geoip/dbip/country/dbip.dat.gz
RUN gunzip dbip.dat.gz
RUN mv dbip.dat /usr/share/GeoIP/GeoIP.dat

RUN rm /etc/nginx/conf.d/default.conf

COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf

COPY ./docker/nginx/jschan.conf /etc/nginx/conf.d/

COPY ./configs/nginx/snippets/ /etc/nginx/snippets/

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

path: docker/nginx/Dockerfile


code:
upstream chan {
	server jschan:7000;
}

server {
	server_name _;
	client_max_body_size 0;

	listen 80;
	listen [::]:80;

	include /etc/nginx/snippets/security_headers.conf;
	include /etc/nginx/snippets/error_pages.conf;
	include /etc/nginx/snippets/jschan_clearnet_routes.conf;
	include /etc/nginx/snippets/jschan_common_routes.conf;
}


code:
load_module /etc/nginx/modules/ngx_http_geoip_module-debug.so;

worker_processes auto;
pid /run/nginx.pid;

events {
	worker_connections 1000;
}

http {
	include /etc/nginx/mime.types;
	default_type application/octet-stream;
	geoip_country /usr/share/GeoIP/GeoIP.dat;

	map_hash_max_size 4096;
	map_hash_bucket_size 256;

	aio threads;
	sendfile on;
	tcp_nopush on;
	tcp_nodelay on;
	server_tokens off;
	types_hash_max_size 2048;
	server_names_hash_bucket_size 128;
	client_max_body_size 0;

	#proxy_request_buffering off;

	log_format custom '[$time_local] $remote_addr $status "$request" "$http_referer" "$http_user_agent" $bytes_sent';
	access_log /var/log/nginx/access.log custom;
	error_log /var/log/nginx/error.log;

	gzip on;
	#gzip_vary off;
	gzip_comp_level 6;
	gzip_proxied any;
	gzip_types text/plain text/css text/js text/xml text/javascript image/x-icon application/javascript application/json application/xml application/rss+xml image/svg+xml;

	ssl_protocols TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
	ssl_prefer_server_ciphers on;

	include /etc/nginx/conf.d/*;

}


code:
const numCpus = require('os').cpus().length;
module.exports = {
	// Options reference: https://pm2.io/doc/en/runtime/reference/ecosystem-file/
	apps : [{
		name: 'build-worker',
		script: 'worker.js',
		instances: Math.ceil(numCpus*0.75),
		autorestart: true,
		watch: false,
		max_memory_restart: '1G',
		log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS',
		env: {
			NODE_ENV: 'development'
		},
		env_development: {
			NODE_ENV: 'development'
		},
		env_production: {
			NODE_ENV: 'production'
		}
	}, {
		name: 'chan',
		script: 'server.js',
		instances: Math.ceil(numCpus*0.75),
		autorestart: true,
		watch: false,
		max_memory_restart: '1G',
		log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS',
		wait_ready: true,
		kill_timeout: 5000,
		env: {
			NODE_ENV: 'development'
		},
		env_production: {
			NODE_ENV: 'production'
		}
	}, {
		name: 'schedules',
		script: 'schedules/index.js',
		instances: 1,
		autorestart: true,
		watch: false,
		max_memory_restart: '1G',
		log_date_format: 'YYYY-MM-DD HH:mm:ss.SSS',
		env: {
			NODE_ENV: 'development'
		},
		env_development: {
			NODE_ENV: 'development'
		},
		env_production: {
			NODE_ENV: 'production'
		}
	}]
};


code:
img {
	width: var(--captcha-w);
	height: var(--captcha-h);
	image-rendering: crisp-edges;
	margin: 0 auto;
}
input {
	position: fixed;
	left: -3px;
	bottom: 0;
	opacity: 0.9;
	border: none;
	background: none;
	font-size: 18px;
	cursor: pointer;
	text-shadow: rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 1px, rgb(0, 0, 0) 0px 0px 1px;
	color: white;
}
body {
	font-family: arial, helvetica, sans-serif;
	font-size: 10pt;
	margin: 0;
	padding: 0;
}
.flexcenter {
	display: flex;
	flex-direction: column;
	align-items: center;
}


code:
.tegaki-icon:before {
  font-style: normal;
  font-weight: normal;
  speak: none;
  display: inline-block;
  text-align: center;
  font-variant: normal;
  text-transform: none;
  line-height: 1em;
}

.tegaki-cancel:before { content: '❌'; }
.tegaki-plus:before { content: '➕'; }
.tegaki-minus:before { content: '➖'; }
.tegaki-pen:before { content: '🖊'; } /* '' */
.tegaki-down-open:before { content: '⬇'; } /* '' */
.tegaki-up-open:before { content: '⬆'; } /* '' */
.tegaki-level-down:before { content: '⤵'; } /* '' */
.tegaki-pencil:before { content: '✏'; } /* '' */
.tegaki-play:before { content: '▶'; } /* '' */
.tegaki-bucket:before { content: '🪣'; } /* '' */
.tegaki-pause:before { content: '⏸'; } /* '' */
.tegaki-blur:before { content: '👆'; } /* '' */
.tegaki-to-start:before { content: '⏮'; } /* '' */
.tegaki-watercolor:before { content: '\e80d'; } /* '' */
.tegaki-tone:before { content: '🕶'; } /* '' */
.tegaki-airbrush:before { content: '💨'; } /* '' */
.tegaki-fast-fw:before { content: '⏩'; } /* '' */
.tegaki-fast-bw:before { content: '⏪'; } /* '' */
.tegaki-left-open:before { content: '⬅'; } /* '' */
.tegaki-right-open:before { content: '➡'; } /* '' */
.tegaki-eraser:before { content: '✏'; transform: rotate(180deg); } /* '' */
.tegaki-pipette:before { content: '💉'; } /* '' */

.tegaki-disabled,
.tegaki-disabled::after,
.tegaki-disabled::before {
  opacity: 0.35;
}

.tegaki-hidden {
  display: none !important;
}

.tegaki-invis {
  visibility: hidden !important;
  width: 0 !important;
  height: 0 !important;
}

.tegaki-replay-mode #tegaki-tools-cnt,
.tegaki-replay-mode #tegaki-toolmode-bar,
.tegaki-replay-mode .tegaki-ctrlgrp,
.tegaki-replay-mode .tegaki-layers-cell,
.tegaki-replay-mode #tegaki-layers-ctrl {
  pointer-events: none;
}

.tegaki-replay-mode #tegaki-ctrlgrp-zoom,
.tegaki-replay-mode #tegaki-ctrlgrp-layers {
  pointer-events: auto;
}

#tegaki {
  position: relative;
  width: 100%;
  /* height: 100%; */
  height: calc(100% - 24px);
  top: 0;
  left: 0;
  background-color: var(--post-color);
  color: var(--font-color);
  font-family: arial, sans-serif;
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  user-select: none;
  z-index: 9999;
  display: grid;
  grid-template-columns: 40px 1fr 160px;
  grid-template-rows: 24px 1fr 18px;
  grid-gap: 2px;
}

#tegaki input {
  color: inherit;
}

#tegaki > div {
  background-color: var(--background-rest);
}

#tegaki-menu-cnt {
  grid-area: 1 / 1 / 2 / 4;
  white-space: nowrap;
  overflow: hidden;
  display: flex;
}

#tegaki-tools-cnt {
  grid-area: 2 / 1 / 4 / 2;
  padding: 4px;
}

#tegaki-canvas-cnt {
  grid-area: 2 / 2 / 3 / 3;
  overflow: auto;
  display: flex;
  touch-action: none;
}

#tegaki-ctrl-cnt {
  grid-area: 2 / 3 / 4 / 4;
  padding: 6px;
  overflow: hidden auto;
  z-index: 1000; /* works when over canvas */
}

#tegaki-status-cnt {
  grid-area: 3 / 2 / 4 / 3;
  line-height: 18px;
  display: flex;
}

#tegaki-status-cnt > div {
  padding: 0 4px;
}

#tegaki-status-replay {
  color: #a61930;
}

#tegaki-status-output {
  font-size: 11px;
  font-weight: bold;
}

#tegaki-status-version {
  color: var(--post-color);
  font-size: 11px;
  margin-left: auto;
}

#tegaki-menu-bar {
  font-size: 12px;
  padding-left: 4px;
  padding-right: 18px;
  border-right: 2px solid var(--post-color);
}

.tegaki-replay-mode #tegaki-menu-bar {
  padding-right: 4px;
}

.tegaki-menu-lbl {
  margin: 0 2px;
  vertical-align: middle;
}

#tegaki-replay-controls {
  padding-right: 10px;
  padding-left: 10px;
  border-right: 2px solid var(--post-color);
  font-size: 11px;
}

#tegaki-replay-timeline {
  display: inline-block;
  width: 100px;
  height: 24px;
  margin: 0 4px;
  border-left: 1px solid var(--post-color);
  border-right: 1px solid var(--post-color);
  /*background-color: rgba(0, 0, 0, 0.25);*/
}

#tegaki-replay-timeline-fill {
  display: inline-block;
  width: 36px;
  height: 100%;
  background-color: var(--post-color);
}

#tegaki-replay-controls > span {
  vertical-align: middle;
}

#tegaki-replay-controls .tegaki-ui-cb-w {
  margin-right: 4px;
}

#tegaki-replay-speed-lbl {
  width: 28px;
  display: inline-block;
  text-align: center;
}

#tegaki-replay-speed-lbl::before {
  content: '×';
}

#tegaki-replay-now-lbl,
#tegaki-replay-end-lbl {
  display: inline-block;
  max-width: 50px;
  min-width: 30px;
  overflow: hidden;
  text-align: center;
  margin: 0 4px;
}

#tegaki-toolmode-bar {
  font-size: 11px;
  margin-left: 4px;
  line-height: 24px;
}

.tegaki-toolmode-lbl {
  margin-right: 6px;
}

.tegaki-toolmode-lbl::after {
  content: ':';
}

.tegaki-toolmode-grp {
  border-left: 1px solid var(--post-color);
  padding: 0 18px;
}

#tegaki canvas {
  image-rendering: optimizeSpeed;
  image-rendering: -moz-crisp-edges;
  image-rendering: -webkit-optimize-contrast;
  image-rendering: pixelated;
  -ms-interpolation-mode: nearest-neighbor;
}

#tegaki .tegaki-smooth-layers #tegaki-cursor-layer {
  image-rendering: auto;
}

.tegaki-tool-active {
  color: #f2f3f4;
}

.tegaki-tool-btn {
  width: 32px;
  height: 32px;
  display: block;
  margin: auto;
}

.tegaki-tool-btn:hover {
  background-color: var(--darken);
}

.tegaki-tool-btn:before {
  font-size: 20px;
  width: 32px;
  height: 32px;
  line-height: 32px;
}

.tegaki-mb-btn {
  cursor: default;
  text-decoration: none;
  display: inline-block;
  padding: 0 6px;
  word-spacing: -1px;
  position: relative;
  line-height: 24px;
  height: 24px;
}

.tegaki-mb-btn:hover:not(.tegaki-disabled),
.tegaki-ui-btn:hover:not(.tegaki-disabled) {
  background-color: rgba(0, 0, 0, 0.10);
}

.tegaki-sw-btn {
  display: inline;
  padding: 2px 6px;
  margin: 0 2px;
  box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.15), -1px -1px 0 rgba(255, 255, 255, 0.15);
}

.tegaki-sw-btn:hover:not(.tegaki-sw-btn-a) {
  background-color: rgba(0, 0, 0, 0.05);
}

.tegaki-sw-btn-a {
  background-color: rgba(0, 0, 0, 0.1);
  box-shadow: -1px -1px 0 rgba(0, 0, 0, 0.15), 1px 1px 0 rgba(255, 255, 255, 0.15);
}

#tegaki-toolmode-bar .tegaki-mb-btn-a {
  color: inherit;
  background-color: rgba(0, 0, 0, 0.10);
}

#tegaki-toolmode-bar .tegaki-mb-btn.tegaki-mb-btn-a:hover {
  color: inherit;
}

#tegaki-debug {
  position: absolute;
  left: 0;
  top: 0;
}

#tegaki-debug canvas {
  width: 75px;
  height: 75px;
  display: block;
  border: 1px solid black;
}

.tegaki-backdrop {
  overflow: hidden;
}

.tegaki-hidden {
  display: none !important;
}

.tegaki-strike {
  text-decoration: line-through;
}

#tegaki-layers {
  position: relative;
  font-size: 0;
  box-shadow: 0 0 8px 2px rgba(0, 0, 0, 0.25);
  contain: content;
}

#tegaki-layers canvas {
  width: 100%;
  height: 100%;
}

#tegaki-layers:empty {
  display: none;
}

#tegaki-layers-wrap {
  margin: auto;
  padding: 50px;
}

#tegaki-layers canvas {
  position: absolute;
  left: 0;
  top: 0;
}

#tegaki-finish-btn {
  font-weight: bold;
}

#tegaki-cursor-layer {
  position: absolute;
  mix-blend-mode: difference;
  touch-action: none;
  top: 50%;
  left: 50%;
  transform: translate(calc(-50% - 50px), -50%);
}

/* generic ui */
.tegaki-alpha-bg,
.tegaki-alpha-bg-xs {
  background-color: #fefefe;
  background-image:
    linear-gradient(45deg, #cacaca 25%, transparent 25%, transparent 75%, #cacaca 75%, #cacaca),
    linear-gradient(45deg, #cacaca 25%, transparent 25%, transparent 75%, #cacaca 75%, #cacaca);
}

.tegaki-alpha-bg {
  background-size: 16px 16px;
  background-position: 0 0, 8px 8px;
}

.tegaki-alpha-bg-xs {
  background-size: 6px 6px;
  background-position: 0 0, 3px 3px;
}

.tegaki-ellipsis {
  text-overflow: ellipsis;
  white-space: nowrap;
  overflow: hidden;
}

.tegaki-ui-cb {
  display: inline-block;
  vertical-align: middle;
  width: 10px;
  height: 10px;
  border: 1px solid #222;
  cursor: default;
  margin-right: 4px;
}

.tegaki-ui-cb::after,
.tegaki-ui-cb-a::after {
  display: block;
  content: ' ';
  width: 6px;
  height: 6px;
  margin-top: 2px;
  margin-left: 2px;
}

.tegaki-ui-cb-a::after {
  background-color: #222;
}

.tegaki-ui-cb-w:hover .tegaki-ui-cb::after,
.tegaki-ui-cb:hover::after {
  background-color: #555;
}

.tegaki-ui-ellipsis::after {
  content: '...';
  letter-spacing: -1px;
}

.tegaki-ui-borderless {
  border: none;
}

.tegaki-ui-btn {
  display: inline-block;
}

.tegaki-ui-btn:before {
  height: 24px;
  width: 24px;
  line-height: 24px;
  font-size: 14px;
}

.tegaki-stealth-input {
  border: 0;
  margin: 0;
  padding: 0;
  background: none;
}

.tegaki-stealth-input:hover:not(.tegaki-disabled) {
  background-color: rgba(0, 0, 0, 0.1);
}

.tegaki-range-lbl,
.tegaki-range-lbl-xs {
  display: inline-block;
  text-align: center;
  vertical-align: top;
}

.tegaki-range-lbl {
  width: 28px;
  font-size: 12px;
  margin-left: 4px;
}

.tegaki-range-lbl-xs {
  width: 20px;
  font-size: 10px;
}

.tegaki-label-xs {
  font-size: 10px;
  vertical-align: top;
}

.tegaki-lbl-c::after {
  content: ':';
}

.tegaki-lbl-p::after {
  content: '%';
  margin-left: 1px;
}

.tegaki-drag-lbl:not(.tegaki-disabled) {
  cursor: ew-resize;
}

.tegaki-disabled .tegaki-drag-lbl {
  cursor: auto;
}

/* control groups */
.tegaki-ctrlgrp {
  margin-bottom: 10px;
}

.tegaki-ctrlgrp:last-child {
  margin-bottom: 0;
}

.tegaki-ctrlgrp-title {
  font-size: 12px;
  font-weight: bold;
  margin-bottom: 6px;
  background-color: var(--post-color);
  padding: 1px 4px;
}

.tegaki-ctrlrow {
  font-size: 11px;
}

.tegaki-ctrlrow:not(:last-child) {
  margin-bottom: 6px;
}

.tegaki-ctrl-range {
  width: calc(100% - 34px);
  padding: 0;
  margin: 0;
  height: 14px;
}

/* zoom ctrl group */
#tegaki-zoom-lbl {
  display: inline-block;
  font-size: 12px;
  float: right;
  height: 24px;
  line-height: 24px;
}

/* color ctrl group */
#tegaki-color-ctrl {
  display: flex;
}

#tegaki-palette-switcher {
  align-self: center;
  margin-left: auto;
}

.tegaki-color-grid {
  display: grid;
  grid-gap: 4px;
  margin-top: 6px;
}

.tegaki-color-grid-20 {
  grid-template-columns: repeat(auto-fill, 20px);
  grid-auto-rows: 20px;
}

.tegaki-color-grid-15 {
  grid-template-columns: repeat(auto-fill, 15px);
  grid-auto-rows: 15px;
}

.tegaki-color-btn {
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.5);
}

#tegaki-color,
#tegaki-colorpicker {
  padding: 0;
  border: 0;
  display: block;
  width: 28px;
  height: 28px;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
}

/* layers ctrl group */
#tegaki-ctrlgrp-layers {
  position: relative;
}

#tegaki-layers-opts {
  height: 18px;
  display: flex;
}

#tegaki-layer-alpha-cell {
  margin-left: auto;
}

#tegaki-layers-ctrl {
  margin-top: 4px;
}

#tegaki-layers-grid {
  height: 84px;
  min-height: 84px;
  overflow: auto;
  background-color: var(--post-color);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--post-color);
  resize: vertical;
}

.tegaki-layers-cell {
  box-sizing: border-box;
  box-shadow: 0 1px 0 0px var(--post-color);
  padding: 0;
  height: 28px;
  flex-shrink: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
}

.tegaki-layers-cell-s,
.tegaki-layers-cell-a {
  background-color: var(--post-color);
}

.tegaki-layers-cell-a {
  font-weight: bold;
}

.tegaki-layers-cell-v {
  margin: 0 6px 0 4px;
}

.tegaki-layers-cell-v .tegaki-ui-cb {
  vertical-align: unset;
  margin: 0;
}

.tegaki-layers-cell-p {
  margin-right: 6px;
}

.tegaki-layers-cell-p canvas {
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35);
  vertical-align: middle;
}

.tegaki-layers-cell-s .tegaki-layers-cell-p canvas {
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 1.0);
}

.tegaki-layers-cell-n {
  font-size: 11px;
  margin-right: 1px;
  min-width: 20px;
}

.tegaki-layers-cell-d {
  box-shadow: inset 0 -2px 0 0 #000;
  z-index: 2;
}

#tegaki-layers-grid.tegaki-layers-cell-d {
  box-shadow: 0 -2px 0 0px #000;
}

#tegaki-layers-cell-dx {
  position: absolute;
  background: transparent;
  width: 100%;
  height: 32px;
  margin-top: -32px;
}

/* Improved mobile compatibility by moving the tools to
   the bottom of the screen in portrait mode*/
@media only screen and (max-width:600px) {
	#tegaki {
		grid-template-columns: 40px 1fr 0px;
		grid-template-rows: 24px 1fr 300px;
	}
	#tegaki-ctrl-cnt {
		grid-area: 3/2/3/2;
		z-index: 1;
	}
	#tegaki-layers-wrap {
		padding: 10px;
	}
}


code:
:root {
	--icon-color: invert(17%) sepia(89%) saturate(7057%) hue-rotate(2deg) brightness(93%) contrast(120%);
	--alt-label-color: #313340;
	--alt-font-color: black;
	--label-color: #313340;
	--box-border-color: #000;
	--font-color: black;
	--name-color: #117743;
	--link-color: #176f6d;
	--capcode-color: #f00;
	--subject-color: #8C489F;
	--darken: #00000010;
	--hr: #ffffff;
	--accent-color: #313340;
	--input-borders: #a9a9a9;
	--input-color: #000;
	--input-background: white;
	--dice-color: darkorange;
	--title-color: #f12e37;
	--greentext-color: green;
	--pinktext-color: #E0727F;
	--post-link-color: hsl(51deg 71% 42%);
	--link-hover: hsl(50deg 68% 31%);
	--background-top: #f9e272;
	--background-rest: #f9e272;
	--navbar-color: #2d303e;
	--post-color: #fafbe7;
	--post-outline-color: hsl(179deg 26% 69%);
	--highlighted-post-color: #fafbe7;
	--highlighted-post-outline-color: hsl(179deg 26% 69%);
	--board-title: #663E11;
}

body {
	background: linear-gradient(#f9e272,rgba(255,255,255,0) 400px calc(100% - 200px),#f9e272),url(/css/themes/assets/56chan-bg.png),hsl(178 40% 80%)
}

.board-title {
	text-shadow: 1px 1px 1px rgba(167,170,168,.6)
}

.navbar a {
	color: #d1ba47
}

.noselect,.post-quoters a {
	color: #ef589c
}

.post-quoters a:hover {
	color: #002c58!important
}

.noselect:hover {
	color: #002c58!important
}

.post-quoters {
	font-weight: 700
}

.postmenu {
	border-left: calc(2 * .5em * .866) solid #b7a01f
}

.label,.rlabel {
	color: #fff
}

.container {
	padding: 20px
}

.jsonly.postform-style.filelabel,input#submitpost {
	background-color: #176f6d;
	border: 1px solid hsl(63deg 71% 95%)!important;
	color: #fff;
	font-weight: 700
}

.captcha {
	filter: invert(17%) sepia(10%) saturate(7057%) hue-rotate(170deg) brightness(93%) contrast(120%)
}

.post-message a {
	color: hsl(51deg 71% 42%)
}


code:
/*tomorrow*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #282a2e;
	--alt-font-color: #c5c8c6;
	--background-top: #1d1f21;
	--background-rest: #1d1f21;
	--navbar-color: #282a2e;
	--post-color: #282a2e;
	--post-outline-color: #282a2e;
	--label-color: #282a2e;
	--box-border-color: #111;
	--darken: #ffffff10;
	--highlighted-post-color: #2b1d1f;
	--highlighted-post-outline-color: #111;
	--board-title: #c5c8c6;
	--hr: #282a2e;
	--font-color: #c5c8c6;
	--name-color: #c5c8c6;
	--capcode-color: #f00;
	--subject-color: #b294bb;
	--link-color: #c5c8c6;
	--post-link-color: #5f89ac;
	--link-hover: #81a2be;
	--accent-color: #c5c8c6;
	--input-borders: #111;
	--input-color: #c5c8c6;
	--input-background: #1d1d21;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: green;
	--pinktext-color:#E0727F;
}

.anchor:target + .post-container, .post-container.highlighted {
	border: 1px solid var(--highlighted-post-outline-color) ;
}

#float .post-container, #livetext, #threadstats, .navbar, .catalog-tile, .pages, .post-container, .stickynav, .collapse, .bottom-reply {
	border-width: 1px;
	border-color: black;
}

.navbar{
	box-shadow: 0 0 3px 0px #00000030;
}

.post-container.op {
	border-color: transparent;
}

table {
	border-spacing: 0px;
}

tr:nth-child(2n+1) {
	background: unset;
}

.captcha {
	filter: invert(90%);
}

.box-wrap, .tabs li a {
	border: 1px solid #000;
}

@media only screen and (max-width: 600px) {

	.post-info{
		background: none;
		border-bottom: none;
	}

	.anchor:target + .post-container .post-info, .post-container.highlighted .post-info {
		border-bottom: none;
	}

}

@media (prefers-color-scheme: light) {
	/*clear*/
	:root {
		--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
		--alt-label-color: #f5f5f5;
		--alt-font-color: #333;
		--background-top: #EEE;
		--background-rest: #EEE;
		--navbar-color: #DDD;
		--post-color: #DDD;
		--post-outline-color: #d2d2d2;
		--label-color: #f5f5f5;
		--box-border-color: #e0e0e0;
		--darken: #0000000f;
		--highlighted-post-color: #EEDACB;
		--highlighted-post-outline-color: #d2d2d2;
		--board-title: #b90000;
		--hr: #DDD;
		--font-color: #333;
		--name-color: #333;
		--capcode-color: #f00;
		--subject-color: #004A99;
		--link-color: black;
		--post-link-color: darkblue;
		--link-hover: #a74300;
		--accent-color: #cacaca;
		--input-borders: #d2d2d2;
		--input-color: #333;
		--input-background: #f5f5f5;
		--dice-color: maroon;
		--title-color: #d70000;
		--greentext-color: #789922;
		--pinktext-color:#E0727F;
	}
	.captcha {
		filter: invert(5%);
	}
	.post-info, .post-container:target .post-info, .post-container.highlighted .post-info {
		background:none;
		border:none;
	}
	#float .post-container, #livetext, #threadstats, .navbar, .catalog-tile, .pages, .post-container, .stickynav, .collapse, .bottom-reply {
		border-color: var(--post-outline-color);
	}
}


code:
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color:#040404;
	--alt-font-color:#c5c8c6;
	--background-top:#000;
	--background-rest:#000;
	--navbar-color:#040404;
	--post-color:#000;
	--post-outline-color:#111;
	--label-color:#040404;
	--box-border-color:#111;
	--darken:#040404;
	--highlighted-post-color:#000;
	--highlighted-post-outline-color:#444;
	--board-title:#c5c8c6;
	--hr:#040404;
	--font-color:#c5c8c6;
	--name-color:#c5c8c6;
	--capcode-color:#f00;
	--subject-color:#b294bb;
	--link-color:#c5c8c6;
	--post-link-color:#5f89ac;
	--link-hover:#81a2be;
	--accent-color:#111;
	--input-borders:#111;
	--input-color:#c5c8c6;
	--input-background:#040404;
	--dice-color:darkorange;
	--title-color:#d70000;
	--greentext-color:green;
	--pinktext-color:#E0727F;
}
.spoiler	{
	background:gray;
	color:gray;
}
.spoiler:not(:hover) * {
	color: gray;
	background: gray!important;
}
.anchor:target + .post-container,
.post-container.highlighted,
.anchor:target + table tbody tr th,
.anchor:target + table {
/*	border-style: dashed var(--highlighted-post-outline-color);*/
}
.post-info, .post-container:target .post-info, .post-container.highlighted .post-info {
	background:none!important;border:none!important
}


code:
:root {
	--icon-color:invert(17%) sepia(89%) saturate(7057%) hue-rotate(2deg) brightness(93%) contrast(120%);
	--alt-label-color:#6e7e46;
	--alt-font-color:black;
	--background-top:#2a401b;
	--background-rest:#5b7744;
	--navbar-color:#2a401b;
	--post-color:#4e5e36;
	--post-outline-color:#1a401b;
	--label-color:#6e7e46;
	--box-border-color:#1a401b;
	--darken:#00000030;
	--highlighted-post-color:#4e4e26;
	--highlighted-post-outline-color:#411;
	--board-title:#af0a0f;
	--hr:#c2c888;
	--font-color:#fff;
	--name-color:#dd0;
	--capcode-color:#f00;
	--subject-color:#ff0;
	--post-link-color:#d2d888;
	--link-color:#d2d888;
	--link-hover:#fff;
	--accent-color:#000;
	--input-borders:#a9a9a9;
	--input-color:#000;
	--input-background:white;
	--dice-color:darkorange;
	--title-color:#fd0;
	--greentext-color:#de6;
	--pinktext-color:#E0727F;
}
.postform-style {
	color: #000;
}


code:
:root {

	--label-color: #1DA1F2;
	--alt-label-color: #1DA1F2;
	--background-top: #E6ECF0;
	--background-rest: #E6ECF0;
	--navbar-color: white;
	--post-color: white;
	--highlighted-post-color: white;
	--highlighted-post-outline-color: #1DA1F2;
	--link-color: #1da1f2;
	--accent-color: #1DA1F2;
	--board-title: #1DA1F2;
	--font-color: black;
}
body, .navbar {
	font-size: unset;
	margin: 0 auto;
}
@media screen and (min-width: 800px) {
	body, .navbar {
		width: 50vw;
	}
}
.post-container.op {
	background:var(--post-color);
}
.stickynav{display:none}
.post-container, .pages {
	padding: 10px;
	width: 100%;
}
.post-name::before {
	content:"👤";
	font-size: 30px;
	justify-content: center;
	align-items: center;
	display: inline-flex;
	width: 70px;
	height: 70px;
	background: lightgray;
	border-radius: 50%;
	margin: 10px
}
.post-icons, .post-subject, .post-check, .board-banner, .postmenu, .user-id, .flag {
	display: none;
}
.post-container, .catalog-tile, .navbar, .pages, #action-menu, #threadstats, #livetext, .collapse, .bottom-reply  {
	border: none;
	border-radius: 10px;
}
.highlighted, .hoverhighlighted {
	border: 1px solid var(--highlighted-post-outline-color)!important;
}
.anchor{
	background: var(--post-color);
	height: 30px;
	width: 10px;
	margin-left: 50px;
	margin-top: 0
}
#float  {
	pointer-events: none;
	border: none;
	box-shadow: none;
}
.label, .rlabel, table {
	border:none;
}


code:
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color:#001010;
	--alt-font-color:#00CCCC;
	--background-top:#000505;
	--background-rest:#000505;
	--navbar-color:#001010;
	--post-color:#001010;
	--post-outline-color:#B7C5D9;
	--label-color:#001010;
	--box-border-color:#00AAAA;
	--darken:#00000010;
	--highlighted-post-color:#003030;
	--highlighted-post-outline-color:#FF0000;
	--board-title:#af0a0f;
	--hr:lightgray;
	--font-color:#00CCCC;
	--name-color:#FF0000;
	--capcode-color:#f00;
	--subject-color:#FF0000;
	--link-color:#FF0000;
	--post-link-color:#FF0000;
	--link-hover:#d00;
	--accent-color:#00CCCC;
	--input-borders:#00AAAA;
	--input-color:#00CCCC;
	--input-background:#000505;
	--dice-color:darkorange;
	--title-color:#d70000;
	--greentext-color:#FF0000;
	--pinktext-color:#E0727F;
}
#livetext,
#threadstats,
#float	.post-container,
.catalog-tile,
.live,
.modal,
.pages,
.collapse,
.bottom-reply,
.post-container,
.stickynav,
#action-menu,
input[type=file],
input[type=number],
input[type=password],
input[type=submit],
input[type=text],
select,
textarea,
.label,
.postform-style,
.close,
table,
.board-banner,
#postform {
	border:	1px	dotted;
}
hr {
	border-top:	1px	dotted;
}
.navbar {
	border-bottom:	1px	dotted;
}
a .post-name:hover,
a:hover {
	background:var(--link-hover)!important;
	color:black!important;
}
.anchor:target + .post-container .post-info, .post-container.highlighted .post-info {
	border-bottom: none;
}
@media only screen and (max-width: 600px) {
	.post-info{
		background: none;
		border-bottom: none;
	}
}


code:
/*tomorrow*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #42200f;
	--alt-font-color: #ff7e83;
	--background-top: #2f1903;
	--background-rest: #261602;
	--navbar-color: #302016;
	--post-color: #302016;
	--post-outline-color: #523830;
	--label-color: #42200f;
	--box-border-color: #4c332c;
	--darken: #ffffff10;
	--highlighted-post-color: #572a1b;
	--highlighted-post-outline-color: #784134;
	--board-title: #ff7e83;
	--hr: #4c332c;
	--font-color: #ff7e83;
	--name-color: #ff7e83;
	--capcode-color: #ff5252;
	--subject-color: #ff5252;
	--link-color: #a2a2ff;
	--post-link-color: #a2a2ff;
	--link-hover: #ff4d4b;
	--accent-color: #784134;
	--input-borders: #535353;
	--input-color: #fff;
	--input-background: #000;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: green;
	--pinktext-color:#E0727F;
}


code:
/*clear*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #f5f5f5;
	--alt-font-color: #333;
	--background-top: #EEE;
	--background-rest: #EEE;
	--navbar-color: #DDD;
	--post-color: #DDD;
	--post-outline-color: #d2d2d2;
	--label-color: #f5f5f5;
	--box-border-color: #e0e0e0;
	--darken: #0000000f;
	--highlighted-post-color: #EEDACB;
	--highlighted-post-outline-color: #d2d2d2;
	--board-title: maroon;
	--hr: #DDD;
	--font-color: #333;
	--name-color: #333;
	--capcode-color: #f00;
	--subject-color: #004A99;
	--link-color: black;
	--post-link-color: darkblue;
	--link-hover: #a74300;
	--accent-color: #cacaca;
	--input-borders: #d2d2d2;
	--input-color: #333;
	--input-background: #f5f5f5;
	--dice-color: maroon;
	--title-color: #d70000;
	--greentext-color: #789922;
	--pinktext-color:#E0727F;
}
.post-info, .post-container:target .post-info, .post-container.highlighted .post-info {
	background:none!important;border:none!important
}


code:
/* with love from ptchan */

:root {
    /* light theme colors */
    --bg: #f7f8fa;
    --bg-light: #fff;
    --bg-dark: #f3f4f6;
    --fg: #292d31;
    --fg-dark: #24292e;
    --fg-light: #6e7781;
    --selection: #d0d7de;
    --selection-dark: #cdd0d4;
    --blue: #0969da;
    --blue-light: #0072f3;
    --green: #2da44e;
    --orange: #e36209;
    --pink: #ff5fba;
    --red: #d73a49;
    --red-light: #da4a59;
    --yellow: #e3ca09;

    /* navbar and green btn colors, dark and light themes share these elements */
    --bg-navbar: #24292f;
    --fg-navbar: #fff;
    --fg-navbar-hover: #b1bac4;
    --bg-btn-primary: #2da44e;
    --bg-btn-primary-hover: #2c974b;
    --text-btn-primary: #fff;
    --border-btn-primary: rgba(27, 31, 36, 0.15);
    --shadow-btn-primary: 0 1px 0 rgba(27, 31, 36, 0.1);
    --shadow-inset-btn-primary: inset 0 1px 0 rgba(255, 255, 255, 0.03);

    /* main colors assignment */
    --darken: var(--bg-dark);
    --hr: var(--selection);
    --navbar-color: var(--bg-navbar);
    --background-top: var(--bg);
    --background-rest: var(--bg);
    --post-color: var(--bg-light);
    --post-outline-color: var(--selection);
    --highlighted-post-color: var(--bg-light);
    --highlighted-post-outline-color: var(--selection);
    --label-color: var(--bg-dark);
    --box-border-color: var(--selection);
    --alt-label-color: var(--bg);
    --alt-font-color: var(--fg);
    --accent-color: var(--selection);
    --input-color: var(--fg);
    --input-borders: var(--selection);
    --input-background: var(--bg-light);
    --board-title: var(--fg-dark);
    --title-color: var(--red-light);
    --font-color: var(--fg);
    --name-color: var(--blue);
    --subject-color: var(--fg-dark);
    --link-color: var(--fg-dark);
    --link-hover: var(--blue);
    --post-link-color: var(--fg-dark);
    --dice-color: var(--orange);
    --greentext-color: var(--green);
    --pinktext-color: var(--pink);
    --capcode-color: var(--red);
    --icon-color: saturate(100%) hue-rotate(2deg) brightness(95%) contrast(100%);
}

@media (prefers-color-scheme: dark) {
    :root {
        /* dark theme colors */
        --bg: #010409;
        --bg-light: #0d1117;
        --bg-dark: #21262d;
        --fg: #ededf0;
        --fg-dark: #fff;
        --fg-light: #b1bac4;
        --selection: #30363d;
        --blue: #58a6ff;
        --blue-light: #79c0ff;
        --green: #56d364;
        --orange: #d29922;
        --pink: #d2a8ff;
        --red: #ff7b72;
        --red-light: #ffa198;
        --yellow: #e3b341;
    }
}

#action-menu,
#customflag,
#dragHandle,
#float,
#livetext,
#postform,
#threadstats,
#threadwatcher,
.bottom-reply,
.captcha,
.catalog-tile,
.close,
.collapse,
.modal,
.pages,
.post-container,
.stickynav,
input[type=submit] {
    border-radius: 6px !important
}

.file-thumb,
.post-file-src * {
    border-radius: 5px
}

#float {
    box-shadow: none !important
}

/* darker post-info */
.post-container.highlighted .post-info,
.post-container:target,
.post-info {
    background: var(--darken);
    margin: -6.5px -6.5px 5px -6.5px;
    border-bottom: 1px solid var(--post-outline-color);
    border-radius: 6px 6px 0 0
}

/* darker post-info */
@media screen and (min-width:900px) {
    .container {
        width: 78vw;
        margin: 0 auto
    }

    /* exceptions */
    .navbar,
    .main>.container {
        width: 100%
    }
}

a {
    font-style: italic
}

/* better highlight */
#float,
.anchor:target+.catalog-tile,
.anchor:target+.post-container,
.anchor:target+table,
.anchor:target+table tbody tr th,
.post-container.highlighted,
.post-container.hoverhighlighted {
    outline: 2px solid var(--blue-light)
}

input:focus,
select:focus,
textarea:focus {
    background: var(--highlighted-post-color);
    border: 2px solid var(--blue-light)
}

/* post menu fixes */
.postmenu {
    border: solid var(--font-color);
    border-width: 0 1px 1px 0;
    background: 0 0
}

.postmenu:hover {
    border: solid var(--link-hover);
    border-width: 0 1px 1px 0;
    background: 0 0
}

.postmenu:focus {
    border: solid var(--font-color);
    border-width: 0 1px 1px 0;
    background: 0 0
}

/* files box */
.filelabel {
    background: var(--highlighted-post-color);
    border: 2px dashed var(--blue-light)
}

/* big title */
.board-title {
    font-size: 55px
}

/* navbar */
.navbar {
    border: 0;
    box-shadow: none
}

.stickynav {
    background: var(--bg-navbar);
    border: 0;
    box-shadow: none
}

a.nav-item {
    color: var(--fg-navbar);

    font-weight: bold;
    /* overwrites italic from links */
    font-style: normal !important
}

a.nav-item:hover {
    color: var(--fg-navbar-hover) !important
}

/* primary buttons */
.bottom-reply.no-decoration,
.collapse.no-decoration,
.expand-omitted,
input[type=submit] {
    background: var(--bg-btn-primary);
    border-color: var(--border-btn-primary);
    color: var(--text-btn-primary);
    box-shadow: var(--shadow-btn-primary), var(--shadow-inset-btn-primary);
    transition: .2s cubic-bezier(.3, 0, .5, 1);
    transition-property: color, background-color, border-color;

    font-weight: bold;
    /* overwrites italic from links */
    font-style: normal !important
}

.bottom-reply.no-decoration:hover,
.collapse.no-decoration:hover,
.expand-omitted:hover,
input[type=submit]:hover {
    background: var(--bg-btn-primary-hover);
    /* overwrites blue from links hover */
    color: var(--text-btn-primary) !important
}

/* grey buttons */
#action-menu,
.close,
.pages,
.collapse {
    background: var(--bg-light);
    border-color: var(--selection);
    box-shadow: var(--shadow-btn-primary), var(--shadow-inset-btn-primary);
    transition: .2s cubic-bezier(.3, 0, .5, 1);
    transition-property: color, background-color, border-color;
}

#action-menu:hover,
.close:hover,
.pages:hover,
.collapse:hover {
    background: var(--bg-dark);
}

/* dimmed info display */
#livetext,
#threadstats {
    background: 0 0;
    border: none
}

#livetext,
#threadstats,
.board-description,
.cb,
.edited,
.footer,
.post-date,
.post-file-info,
.replies {
    color: var(--fg-light)
}

/* tables style */
table {
    border-collapse: separate;
    border-radius: 6px;
}

td {
    border-top: 1px solid var(--post-outline-color);
    border-left: 1px solid var(--post-outline-color);
}

td:first-child,
th:first-child {
    border-left: none;
}

/* table exceptions */
table.boardtable.w900 td:last-child {
    background-color: unset !important;
    text-shadow: unset;
    color: var(--font-color) !important;
}

#hotposts,
#hotposts td,
#hotposts tr {
    border: none;
    background: none;
}

/* forms (post form and threadwatcher) */
.modal,
#postform,
#threadwatcher {
    background-color: var(--bg-light);
    outline: 2px solid var(--blue-light);
}

.label {
    border-radius: 6px 0px 0px 6px !important;
}

input[type="text"],
input[type="password"],
select:not(.postmenu),
#message,
#postpassword,
.filelabel {
    border-radius: 0px 6px 6px 0px !important;
}

#postform-dragHandle,
#threadwatcher-dragHandle {
    margin-bottom: 4px;
    border-radius: 6px;
    background-color: var(--bg);
    padding: 5px;
}

#submitpost {
    margin-top: 6px;
}

.rlabel {
    border-radius: 0px 6px 6px 0px;
}


code:
:root {
	--background-top: rgb(40, 40, 46);
	--background-rest: rgb(40, 40, 46);
	--label-color: #1f1f1f;
	--box-border-color: #0099ff;
	--font-color: #eee;
	--alt-font-color: #fff;
	--alt-label-color: #800;
	--navbar-color: #3f3f3f;
	--darken: #00000010;
	--link-color: #0099ff;
	--post-link-color: #0099ff;
	--link-hover: #3333ff;
	--accent-color: #0099ff;
	--input-borders: #a9a9a9;
	--input-color: #fff;
	--input-background: black;
	--post-color: #1f1f1f;
	--post-outline-color: #0099ff;
	--highlighted-post-color: #2c2c3c;
	--highlighted-post-outline-color: #0555ff;
	--dice-color: darkorange;
	--title-color: #ff0000;
	--greentext-color: #55ff33;
	--pinktext-color:#E0727F;
	--board-title: #33ffff;
	--name-color: #0044ff;
	--capcode-color: #f00;
	--subject-color: #33ddff;
	--hr: #0099ff;
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
}
body {
	font-family: 'Hack', monospace, sans-serif;
	font-size: 9pt;
	color: #eee;
}
.bold {
	font-weight: bold;
	color: #fff;
}
.em {
	color: #FF6700;
}
.strikethrough {
	color: #e6e600;
}
a, a:visited, a .post-name {
	text-decoration: none;
	font-weight: bolder;
	font-size: 12px;
	color: var(--link-color);
}
/* prevent above rule making link in board titles small */
.board-title a {
    font-size: inherit;
}
.catalog-tile {
	padding: 10px;
	margin: 4px;
	height: 240px;
	border: 1px solid var(--post-outline-color);
	max-width: 250px;
}
.catalog-thumb {
	width: 80px;
	height: 80px;
}
.spoiler {
	cursor: help;
}


code:
:root {
    --icon-color: invert(97%)sepia(32%)saturate(5790%)hue-rotate(318deg)brightness(107%)contrast(93%);
    --hr: #F6D251;
    --link-color: #F6D251;
    --post-link-color: #F6D251;
    --label-color: #5A1687;
    --alt-label-color: #5A1687;
    --navbar-color: #5A1687;
    --font-color: #FAFAFA;
    --alt-font-color: #FAFAFA;
    --input-color: #FAFAFA;
    --background-top: #430070;
    --background-rest: #430070;
    --post-color: #35134C;
    --highlighted-post-color: #230D33;
    --box-border-color: #230D33;
    --post-outline-color: #230D33;
    --highlighted-post-outline-color: #230D33;
    --input-borders: #230D33;
    --darken: #230D339F ;
    --accent-color: #230D33;
    --board-title: #F85088;
    --title-color: #F85088;
    --link-hover: #F85088;
    --input-background: #5A1687;
    --subject-color: #F85088;
    --name-color: #70EF4C;
    --capcode-color: #F6D251;
    --dice-color: #FF770F;
    --greentext-color: #70EF4C;
    --pinktext-color: #F85088
}

body {
    font-size: 16px;
    text-shadow: 1px 1px #000;
    background-image: linear-gradient(45deg,#35134CEF,#35134CEF),url(/css/themes/assets/digi-bg1.png)
}

#livetext,#threadstats,.bottom-reply,.catalog-tile,.collapse,.pages,.post-container {
    border: outset;
    border-color: #35134c
}

.file-thumb {
    border: inset;
    border-color: #35134c
}

.op.post-container {
    max-width: max-content;
    border: outset;
    border-color: #35134c;
    background: #35134c
}

.board-title {
    text-shadow: 2px 2px #000
}

.board-description {
    text-shadow: 2px 2px #000
}

.footer {
    font-size: 16px;
    background: linear-gradient(to right,#00000000,#0000009F,#00000000)
}

.nav-item,.short-nav.nav-item {
    line-height: 2em
}

.close.mr-0 {
    padding-right: 1em
}

.text-center.no-m-p.post-message {
    max-width: max-content;
    margin: auto;
    font-weight: bold;
    padding: 10px;
    border: outset;
    border-color: #35134c;
    background: #35134c
}

hr {
    border-width: 5px;
    border-style: dotted none none;
    margin: 5px 0
}

* {
    cursor: url(/css/themes/assets/digi-cursor.png),default
}

.post-file-info {
    font-size: 16px
}


code:
/* with love from ptchan */

:root {
    --icon-color: invert(17%) sepia(89%) saturate(7057%) hue-rotate(2deg) brightness(93%) contrast(120%);
    --alt-label-color: #d6f0da;
    --alt-font-color: #333;
    --background-top: #eefff2;
    --background-rest: #eefff2;
    --navbar-color: #d6f0da;
    --post-color: #d6f0da;
    --post-outline-color: transparent;
    --label-color: #d6f0da;
    --box-border-color: #d6f0da;
    --darken: #0000000f;
    --highlighted-post-color: #d6bad0;
    --highlighted-post-outline-color: transparent;
    --board-title: #b90000;
    --hr: #DDD;
    --font-color: #333;
    --name-color: #117743;
    --capcode-color: #f00;
    --subject-color: #004A99;
    --link-color: #34345c;
    --post-link-color: #34345c;
    --link-hover: #8d0000;
    --accent-color: #d6f0da;
    --input-borders: #d2d2d2;
    --input-color: #333;
    --input-background: #f5f5f5;
    --dice-color: maroon;
    --title-color: #d70000;
    --greentext-color: #789922;
    --pinktext-color: #E0727F;
}

.post-container, #float .post-container, .stickynav, .pages, .toggle-summary, .catalog-tile, #livetext, #threadstats {
	border-width: 0 1px 1px 0;
}
a.quote {
    color:var(--post-link-color);
}

.alt {
    background: var(--label-color);
    color: var(--font-color);
}

td, th {
    padding: 5px;
    overflow-wrap: anywhere;
    padding: 6px;
}

table {
    background: var(--background-rest);
    border-radius: 5px;
}

#float .post-container, #livetext, #threadstats, .catalog-tile, .pages, .post-container, .stickynav, .toggle-summary {
    border-width: 1px 1px 1px 1px;
    border-radius: 5px;
}

.post-container {
    padding: 10px;
}

.toggle-summary {
    padding: 13px;
}

#postform {

    border-radius: 5px;
}

#dragHandle {

    border-radius: 5px;
}

.label, .rlabel {
	border: none;
    border-radius: 5px;
    padding: 5px;
}

.modal {

    border-radius: 5px;
    padding: 5px;
}

.post-date{
    opacity: 0.5;
}

img {
    border-radius: 5px;
}

video {
    border-radius: 5px;
}

@media only screen and (max-width: 600px) {
.post-info {
    background: var(--darken);
    margin: -6.5px -6.5px 5px -6.5px;
    border-bottom: 1px solid var(--post-outline-color);
    border-radius: 5px;
}

.container {
    padding: 5px;

}
}

.user-id {
    text-shadow: none;
    color: #fff;
    padding: 1px 5px;
    border: none;
    border-radius: 5px;
}

.bottom-reply {
    border-radius: 5px;
}

.collapse {
    border-radius: 5px;
}

body {
	background: url(/css/themes/assets/favela-bg.jpg) repeat 0 0 azure
}

hr {
    border-top: 1px solid var(--post-color);
}


code:
/*gurochan*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #e6cbc0;
	--alt-font-color: #000;
	--background-top: #EDDAD2;
	--background-rest: #EDDAD2;
	--navbar-color: #D9AF9E;
	--post-color: #D9AF9E;
	--post-outline-color: #CA927B;
	--label-color: #E6CBC0;
	--box-border-color: #CA927B;
	--darken: #0000000f;
	--highlighted-post-color: #EEDACB;
	--highlighted-post-outline-color: #CA927B;
	--board-title: #FF6600;
	--hr: #CA927B;
	--font-color: #000;
	--name-color: #000;
	--capcode-color: #f00;
	--subject-color: #004A99;
	--link-color: #34345C;
	--post-link-color: #34345C;
	--link-hover: #34345C;
	--accent-color: #E6CBC0;
	--input-borders: #CA927B;
	--input-color: #000;
	--input-background: #E6CBC0;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: #789922;
	--pinktext-color:#E0727F;
}


code:
:root {
	--icon-color: invert(15%) sepia(66%) saturate(7479%) hue-rotate(6deg) brightness(98%) contrast(124%);
	--alt-label-color: #282a2e;
	--alt-font-color: #f4c095;
	--background-top: #0d1a1d;
	--background-rest: #0d1a1d;
	--navbar-color: #222222eb;
	--post-color: #222222;
	--post-outline-color: #d2738a;
	--label-color: #282a2e;
	--box-border-color: #d2738a;
	--darken: #ffffff10;
	--highlighted-post-color: #6d99ba73;
	--highlighted-post-outline-color: #82cece;
	--board-title: #f4c095;
	--hr: #d2738a;
	--font-color: #dcdcdc;
	--name-color: #d2738a;
	--capcode-color: #15ff00;
	--subject-color: #f4c095;
	--link-color: #6d98ba;
	--post-link-color: #6d98ba;
	--link-hover: #bd0000;
	--accent-color: #d2738a;
	--input-borders: #d2738a;
	--input-color: #dcdcdc;
	--input-background: #282a2e;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: #88b319;
	--pinktext-color: #e0727f;
}

body {
	background: linear-gradient(#6d99BA50,#1c1c1c75 400px calc(100% - 200px),#d2738A90),url(/css/themes/assets/iwakura-bg.gif),#0d0d0d none repeat scroll 0 0
}

a.quote {
	color: #f4c095
}

table {
	border: 1px solid var(--box-border-color)
}

tr {
	background: var(--background-rest) url(/css/themes/assets/iwakura-bg.gif)
}

.captcha {
	filter: invert(90%)
}

#float .post-container, #livetext, #threadstats, .catalog-tile, .pages, .post-container, .collapse, .bottom-reply {
	border-style: none none none solid
}

hr {
	margin: 16px 0
}


code:
/*lainchan*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #333;
	--alt-font-color: #bbb;
	--background-top: #1E1E1E;
	--background-rest: #1E1E1E;
	--navbar-color: #333;
	--post-color: #333;
	--post-outline-color: #555;
	--label-color: #333;
	--box-border-color: #666;
	--darken: #ffffff0f;
	--highlighted-post-color: #32DD7220;
	--highlighted-post-outline-color: #32DD7250;
	--board-title: #32DD72;
	--hr: #333;
	--font-color: #bbb;
	--name-color: #32DD72;
	--capcode-color: #f00;
	--subject-color: #b294bb;
	--link-color: #fff;
	--post-link-color: #fff;
	--link-hover: #32DD72;
	--accent-color: #666;
	--input-borders: #666;
	--input-color: #CCCCCC;
	--input-background: #333;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: #B8D962;
	--pinktext-color:#E0727F;
}
.post-container, #float .post-container, .stickynav, .pages, #action-menu, .catalog-tile, #livetext, #threadstats, .collapse, .bottom-reply {
	border-width: 1px;
}
.captcha {
	filter: invert(90%);
}


code:
/*miku*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #B6DDDE;
	--alt-font-color: black;
	--background-top: #2e99cc70;
	--background-rest: #D2FFEE;
	--navbar-color: #B6DDDE;
	--post-color: #B6DDDE;
	--post-outline-color: #8FCCCD;
	--label-color: #B6DDDE;
	--box-border-color: #8FCCCD;
	--darken: #00000010;
	--highlighted-post-color: #a9d8ff;
	--highlighted-post-outline-color: #8FCCCD;
	--board-title: #800000;
	--hr: #B7D9C5;
	--font-color: black;
	--name-color: #117743;
	--capcode-color: #f00;
	--subject-color: #117743;
	--link-color: #00637B;
	--post-link-color: #d00;
	--link-hover: #d00;
	--accent-color: #B6DDDE;
	--input-borders: #a9a9a9;
	--input-color: #000;
	--input-background: white;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: #789922;
	--pinktext-color:#E0727F;
}
.post-container, #float .post-container, .stickynav, .pages, #action-menu, .catalog-tile, #livetext, #threadstats, .collapse, .bottom-reply {
	border-width: 0 1px 1px 0;
}


code:
:root {
	--icon-color:invert(15%) sepia(66%) saturate(7479%) hue-rotate(6deg) brightness(98%) contrast(124%);
	--alt-label-color: #182f3f;
	--alt-font-color: #acacac;
	--background-top: #0d1a1d;
	--background-rest: #0d1a1d;
	--navbar-color: #182f3f9e;
	--post-color: #182f3f;
	--post-outline-color: #82cece;
	--label-color: #182f3f;
	--box-border-color: #82cece;
	--darken: #ffffff10;
	--highlighted-post-color: #244860;
	--highlighted-post-outline-color: #82cece;
	--board-title: #82cece;
	--hr: #82cece;
	--font-color: #acacac;
	--name-color: #82cece;
	--capcode-color: #f00;
	--subject-color: #46b5b5;
	--link-color: #82cece;
	--post-link-color: #82cece;
	--link-hover: #acacac;
	--accent-color: #82cece;
	--input-borders: #82cece;
	--input-color: #acacac;
	--input-background: #182f3f;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: #789922;
	--pinktext-color: #e0727f;
}
body {
	background: var(--background-rest) url(/css/themes/assets/mushroom.png);
}
a.post-name {
	color: #a0ffff;
}
a.quote {
	color: #81a2be;
}
table {
	border: 1px solid var(--box-border-color);
}
tr {
	background: var(--background-rest) url(/css/themes/assets/mushroom.png);
}
.captcha {
	filter: invert(90%);
}


code:
/*tomorrow*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #1a1e34;
	--alt-font-color: #bfbfbf;
	--background-top: #080c19;
	--background-rest: #080c19;
	--navbar-color: #1a1e34;
	--post-color: #1a1e34;
	--post-outline-color: #2c3a4e;
	--label-color: #1a1e34;
	--box-border-color: #2c3a4e;
	--darken: #ffffff08;
	--highlighted-post-color: #44283e;
	--highlighted-post-outline-color: #664e5c;
	--board-title: #c10000;
	--hr: #303030;
	--font-color: #bfbfbf;
	--name-color: #67b38d;
	--capcode-color: #ff4141;
	--subject-color: #8f8dc9;
	--link-color: #8585a3;
	--post-link-color: #f65252;
	--link-hover: #f65252;
	--accent-color: #434343;
	--input-borders: #434343;
	--input-color: #bfbfbf;
	--input-background: #080c19;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: #0a0;
	--pinktext-color:#E0727F;
}


code:
/*pink*/
:root {
	--icon-color: invert(17%) sepia(89%) saturate(7057%) hue-rotate(2deg) brightness(93%) contrast(120%);
	--alt-label-color: #ffcccb;
	--alt-font-color:#80002c;
	--background-top:#ffe4e6;
	--background-rest:#ffe4e6;
	--navbar-color:#ffcccb;
	--post-color:#ffcccb;
	--post-outline-color:#80002c;
	--label-color:#ffcccb;
	--box-border-color:#80002c;
	--darken:#80002c54;
	--highlighted-post-color:#eab5b4;
	--highlighted-post-outline-color:#80002c;
	--board-title:#80002c;
	--hr:#80002c;
	--font-color:#80002c;
	--name-color:#b3003e;
	--capcode-color:#f00;
	--subject-color:#e60050;
	--link-color:#80002c;
	--post-link-color:#80002c;
	--link-hover:#c48899;
	--accent-color:#80002c;
	--input-borders:#80002c;
	--input-color:#80002c;
	--input-background:#ffe6ea;
	--dice-color:darkorange;
	--title-color:#d70000;
	--greentext-color:#789922;
	--pinktext-color:#FF00EF;
}
.post-container:not(.op), .stickynav, .pages, #action-menu, .catalog-tile, #livetext, #threadstats,#float, table, .collapse, .bottom-reply {
	box-shadow: 3px 3px 3px var(--darken);
}
hr {
	height: 0px;
	border-width: 2px medium medium;
	border-style: dashed none none;
	border-color: var(--post-outline-color);
}
.anchor:target + .post-container,
.post-container.highlighted,
.anchor:target + table tbody tr th,
.anchor:target + table {
	border: 1px dashed;
}

@media only screen and (max-width: 600px) {

	.post-info{
		background: none;
		border-bottom: none;
	}

	.anchor:target + .post-container .post-info, .post-container.highlighted .post-info {
		border-bottom: none;
	}

}


code:
/* from ptchan with love */

:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #800;
	--alt-font-color: #fff;
	--background-top: #fed6af90;
	--background-rest: #ffe;
	--navbar-color: #f0e0d6;
	--post-color: #f0e0d6;
	--post-outline-color: #d9bfb7;
	--label-color: #EA8;
	--box-border-color: #000;
	--darken: #ead6ca;
	--highlighted-post-color: #f0c0b0;
	--highlighted-post-outline-color: #d99f91;
	--board-title: #af0a0f;
	--hr: #D9BFB7;
	--font-color: #800000;
	--name-color: #117743;
	--capcode-color: #f00;
	--subject-color: #f00;
	--link-color: #800;
	--post-link-color: navy;
	--link-hover: #f00;
	--accent-color: #EA8;
	--input-borders: #a9a9a9;
	--input-color: #000;
	--input-background: white;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: #789922;
	--pinktext-color:#E0727F;
}

.post-container, #float .post-container, .stickynav, .pages, .toggle-summary, .catalog-tile, #livetext, #threadstats {
	border-width: 0 1px 1px 0;
}
a.quote {
    color:var(--post-link-color);
}

.alt {
    background: var(--label-color);
    color: var(--font-color);
}

td, th {
    padding: 5px;
    overflow-wrap: anywhere;
    padding: 6px;
}

table {
    background: var(--background-rest);
    border: 1px solid var(--highlighted-post-color);
    border-radius: 5px;
}

#float .post-container, #livetext, #threadstats, .catalog-tile, .pages, .post-container, .stickynav, .toggle-summary {
    border-width: 1px 1px 1px 1px;
    border-radius: 5px;
}

.post-container {
    padding: 10px;
}

.toggle-summary {
    padding: 13px;
}

#postform {

    border-radius: 5px;
}

#dragHandle {

    border-radius: 5px;
}

.label, .rlabel {
	border: none;
    border-radius: 5px;
    padding: 5px;
}

.modal {

    border-radius: 5px;
    padding: 5px;
}

.post-date{
    opacity: 0.5;
}

img {
    border-radius: 5px;
}

video {
    border-radius: 5px;
}

@media only screen and (max-width: 600px) {
.post-info {
    background: var(--darken);
    margin: -6.5px -6.5px 5px -6.5px;
    border-bottom: 1px solid var(--post-outline-color);
    border-radius: 5px;
}

.container {
    padding: 5px;

}
}

.user-id {
    text-shadow: none;
    color: #fff;
    padding: 1px 5px;
    border: none;
    border-radius: 5px;
}

.bottom-reply {
    border-radius: 5px;
}

.collapse {
    border-radius: 5px;
}

body {
    background: url(/css/themes/assets/ptchan-bg.png) repeat 0 0 #ffe
}


code:
/*rei-zero*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #29373e;
	--alt-font-color: #d6d6d6;
	--background-top: #000E1C;
	--background-rest: #000E1C;
	--navbar-color: #29373E;
	--post-color: #29373E;
	--post-outline-color: #666666;
	--label-color: #29373E;
	--box-border-color: #000;
	--darken: #00000010;
	--highlighted-post-color: #193A3A;
	--highlighted-post-outline-color: #999999;
	--board-title: #193A3A;
	--hr: #474747;
	--font-color: #D6D6D6;
	--name-color: #117743;
	--capcode-color: #f00;
	--subject-color: #CCCCCC;
	--link-color: #5C99AD;
	--post-link-color: #00BfFF;
	--link-hover: #eeeeee;
	--accent-color: #29373E;
	--input-borders: #a9a9a9;
	--input-color: lightgray;
	--input-background: #001229;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: #789922;
	--pinktext-color:#E0727F;
}


code:
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color:#2d2d86;
	--alt-font-color:#f2f2f2;
	--background-top:#19194d;
	--background-rest:#19194d;
	--navbar-color:#2d2d86;
	--post-color:#262673;
	--post-outline-color:#262673;
	--label-color:#2d2d86;
	--box-border-color:#000000;
	--darken:#ffffff10;
	--highlighted-post-color:#3636a6;
	--highlighted-post-outline-color:#111;
	--board-title:#c5c8c6;
	--hr:#282a2e;
	--font-color:#f2f2f2;
	--name-color:#117743;
	--capcode-color:red;
	--subject-color:#6666CC;
	--link-color:white;
	--post-link-color:red;
	--link-hover:#f2f2f2;
	--accent-color:#2d2d86;
	--input-borders:black;
	--input-color:black;
	--input-background:white;
	--dice-color:darkorange;
	--title-color:#d70000;
	--greentext-color:#789922;
	--pinktext-color:#E0727F;
}
.post-message a:hover {
	color:var(--post-link-color)!important;
}
.postform-style {
	color:black
}
.post-info, .post-container:target .post-info, .post-container.highlighted .post-info {
	background:none!important;border:none!important
}


code:
:root {
	--icon-color: invert(17%) sepia(89%) saturate(7057%) hue-rotate(2deg) brightness(93%) contrast(120%);
	--alt-label-color: #800;
	--alt-font-color: #fff;
	--background-top: #002b36;
	--background-rest: #002b36;
	--navbar-color: #002b36;
	--post-color: #073642;
	--post-outline-color: #586e75;
	--label-color: #073642;
	--box-border-color: #586e75;
	--darken: #00000010;
	--highlighted-post-color: #002b36;
	--highlighted-post-outline-color: #cb4b16;
	--board-title: #dc322f;
	--hr: #93a1a1;
	--font-color: #839496;
	--name-color: #b58900;
	--capcode-color: #f00;
	--subject-color: #b58900;
	--link-color: #839496;
	--post-link-color: #268bd2;
	--link-hover: #2aa198;
	--accent-color: #073642;
	--input-borders: #586e75;
	--input-color: #839496;
	--input-background: #002b36;
	--dice-color: #D33682;
	--title-color: #dc322f;
	--greentext-color: #789922;
	--pinktext-color: #d33682;
}
.bold {
	color: #93a1a1;
}
.em {
	color: #586e75;
}
img[src='/file/dice.png'] {
	filter: hue-rotate(290deg)
}


code:
:root {
	--icon-color: invert(17%) sepia(89%) saturate(7057%) hue-rotate(2deg) brightness(93%) contrast(120%);
	--alt-label-color: #EEE8D5;
	--alt-font-color: #657B83;
	--background-top: #FDF6E3;
	--background-rest: #FDF6E3;
	--navbar-color: #EEE8D5;
	--post-color: #EEE8D5;
	--post-outline-color: #93A1A1;
	--label-color: #EEE8D5;
	--box-border-color: #93A1A1;
	--darken: #00000010;
	--highlighted-post-color: #FDF6E3;
	--highlighted-post-outline-color: #cb4b16;
	--board-title: #dc322f;
	--hr: #93a1a1;
	--font-color: #657B83;
	--name-color: #b58900;
	--capcode-color: #f00;
	--subject-color: #b58900;
	--link-color: #657B83;
	--post-link-color: #268bd2;
	--link-hover: #2aa198;
	--accent-color: #EEE8D5;
	--input-borders: #93A1A1;
	--input-color: #657B83;
	--input-background: #FDF6E3;
	--dice-color: #D33682;
	--title-color: #dc322f;
	--greentext-color: #789922;
	--pinktext-color: #d33682;
}
.bold {
	color: #93a1a1;
}
.em {
	color: #586e75;
}
img[src='/file/dice.png'] {
	filter: hue-rotate(290deg)
}


code:
:root {
	--accent-color: #cb9fac;
	--input-borders: #140e10;
	--input-color: #cb9fac;
	--input-background: #1e1415;
	--input-alt-borders: #99719c;
	--input-alt-color: hsl(342.3, 29.7%, 71%);
	--icon-color: invert(17%) sepia(89%) saturate(7057%) hue-rotate(295deg) brightness(93%) contrast(120%);
	--darken: #00000010;
	--capcode-color: #bd758d;
	--dice-color: darkorange;
	--pinktext-color: #E0727F;
	--label-color: #241c1e;
	--box-border-color: #140e10;
	--link-color: palevioletred;
	--post-link-color: palevioletred;
	--link-hover: pink;
	--board-title: rgba(0, 0, 0, 0);
	--subject-color: #aa00a9;
	--hr: #582d3d;
	--name-color: #bd758d;
	--font-color: hsl(342, 30%, 71%);
	--post-color: #301821;
	--post-outline-color: #40232e;
	--highlighted-post-color: #301821;
	--highlighted-post-outline-color: #40232e;
	--title-color: red;
	--greentext-color: #4f9234;
	--spoiler-bg-color: #120c0d;
	--spoiler-hover-color: #dcd9d9;
	--spoiler-bg-color-op: #584453;
}

body {
	background: #140e10 url(/css/themes/assets/tchan-bg.png);
	font-family: trebuchet ms,Tahoma,Verdana,Arial,sans-serif;
	font-size: 13.3333px!important
}

.postmenu {
	border: none;
	background: url(/css/themes/assets/tchan-star.png) no-repeat;
	display: inline-block;
	height: 19px;
	width: 17px;
	vertical-align: bottom;
	margin-left: 5px
}

a.post-name {
	color: red!important;
	font-size: 0;
	text-decoration: none
}

a.post-name:before {
	content: 'SAGE!';
	font-size: 13px;
	text-decoration: none
}

.you::after {
	margin-left: 3px;
	content: "(You)";
	font-weight: lighter;
	font-style: italic;
	font-size: 13px;
	color: var(--name-color)
}

.spoiler {
	background: var(--spoiler-bg-color);
	color: transparent
}

.spoiler:hover {
	color: var(--spoiler-hover-color)
}

.post-container.op .spoiler {
	background: var(--spoiler-bg-color-op)
}

.captcha {
	border-radius: 5px;
	filter: invert(80%) sepia(15%) saturate(557%) hue-rotate(280deg) brightness(80%) contrast(120%)
}

.post-capcode {
	color: #bd758d!important;
	font-weight: 700!important
}

.post-capcode:after {
	color: #bd758d!important;
	font-weight: 700!important;
	text-shadow: none;
	content: '';
	background: url(/css/themes/assets/tchan-capcode.png) 0 0 no-repeat;
	background-size: cover;
	width: 15px!important;
	height: 16px!important;
	display: inline-block;
	margin-left: 5px
}

.board-title {
	font-weight: 700;
	background-attachment: scroll;
	background-clip: text;
	-webkit-background-clip: text;
	background-color: rgba(0,0,0,0);
	background-image: linear-gradient(to right,red,#ffa600,#fff500,#06ff00,#00c9ff,#2300ff,#ff00fa,red);
	background-size: 200%;
	animation: heading 1s linear infinite
}

@keyframes heading {
	to {
		background-position: 200% center
	}
}

.navbar {
	background: 0 0;
	border: none;
	box-shadow: none;
	position: absolute;
	color: var(--font-color)
}

.navbar a {
	color: var(--link-color)
}

.detected {
	background: linear-gradient(to right,red,#ffa600,#fff500,#06ff00,#00c9ff,#2300ff,#ff00fa,red);
	background-size: 200% auto;
	color: #000;
	-webkit-background-clip: text;
	-webkit-text-fill-color: transparent;
	font-weight: 700;
	animation: heading 1s linear infinite!important;
	font-family: trebuchet ms,Tahoma,Verdana,Arial,sans-serif!important;
	font-size: 100%!important
}

.pinktext {
	font-weight: 400;
	font-family: trebuchet ms,Tahoma,Verdana,Arial,sans-serif!important;
	font-size: x-large;
	color: #cb9eac
}

.close.postform-style,input,select,textarea {
	font-family: trebuchet ms,Tahoma,Verdana,Arial,sans-serif!important;
	font-size: 13.3333px!important;
	border-radius: 5px!important
}

.postform-style,input,select,textarea {
	font-family: trebuchet ms,Tahoma,Verdana,Arial,sans-serif!important;
	font-size: 13.3333px!important;
	border-radius: 5px!important
}

.modal a.button {
	color: var(--link-color)!important
}

.modal a.button:hover {
	color: var(--link-hover)!important
}

input[type=submit] {
	padding: 3px 4px;
	min-height: unset!important
}

input[type=file],input[type=number],input[type=password],input[type=range],input[type=text],textarea {
	border-width: 2px!important
}

.close.postform-style,input[type=button],input[type=submit],select {
	border-width: 1px!important;
	border-color: var(--input-alt-borders)!important;
	color: var(--input-alt-color)!important
}

#float .post-container,#livetext,#postform,#threadstats,.catalog-tile,.modal,.pages,.post-container,.stickynav,#action-menu, .collapse, .bottom-reply {
	border-radius: 4px;
	border-style: solid none none solid;
	border-width: 1px;
	box-shadow: 2px 2px 1px #120c0d
}

.post-container.op {
	box-shadow: unset
}

#postform {
	padding: 4px
}

#postform-dragHandle {
	border-radius: 4px
}

.file-thumb,.post-file-src * {
	max-width: 90vw;
	border-radius: 5px
}

.file-thumb {
	box-shadow: 0 1px 4px rgba(0,0,0,.2)
}

.post-container {
	padding-right: 3em;
	padding-top: .3em;
	padding-bottom: .5em;
	padding-left: .7em
}

.post-file-info {
	font-size: smaller;
	text-align: left;
	max-width: 150px
}

.post-file {
	align-items: start
}

.filelabel,div.label,textarea {
	border-radius: 5px;
	border-width: 2px
}

tr {
	background: var(--input-background);
	color: var(--input-color)
}

.board-banner {
	border: 2px solid #cb9fac;
	border-radius: 10px
}

.code {
	background-color: var(--spoiler-bg-color)!important;
	border-radius: 5px;
	border: var(--spoiler-bg-color)!important
}

.spoiler:not(:hover) * {
	background: var(--spoiler-bg-color)!important;
	color: transparent
}

.spoiler {
	cursor: auto
}

.spoiler:hover a {
	color: var(--spoiler-hover-color)!important
}

.post-container.op .spoiler:not(:hover) * {
	background: var(--spoiler-bg-color-op)!important
}

.anchor:target+.catalog-tile,.anchor:target+.post-container,.anchor:target+table,.anchor:target+table tbody tr th,.post-container.highlighted,.post-container.hoverhighlighted {
	outline: 2px dashed #cb9fac
}


code:
/* from ptchan with love */

:root {
	--icon-color: invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #040404;
	--alt-font-color: #e8eaf6;
	--background-top: #000;
	--background-rest: #000;
	--navbar-color: #040404;
	--post-color: #000;
	--post-outline-color: #111;
	--label-color: #040404;
	--box-border-color: #111;
	--darken: #040404;
	--highlighted-post-color: #000;
	--highlighted-post-outline-color: #444;
	--board-title: #e8eaf6;
	--hr: #040404;
	--font-color: #e8eaf6;
	--name-color: #e8eaf6;
	--capcode-color: #f00;
	--subject-color: #b294bb;
	--link-color: white;
	--post-link-color: #5f89ac;
	--link-hover: #81a2be;
	--accent-color: #e8eaf6;
	--input-borders: #111;
	--input-color: #e8eaf6;
	--input-background: #040404;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: lime;
	--pinktext-color: magenta;
}

.spoiler {
	background: gray;
	color: azuregray;
}

.spoiler:not(:hover) * {
	color: gray;
	background: gray !important;
}

.post-info, .post-container:target .post-info, .post-container.highlighted .post-info {
	background: none !important;
    border: none !important
}

@font-face {
    font-family: VCR OSD Mono;
    src: url(/css/themes/assets/VCR-OSD-Mono.ttf);
}

* {
    font-family: 'VCR OSD Mono', sans-serif !important;
}

body {
    background: black !important;
    width: 80%;
    margin: auto;
    font-size: medium;
}

a {
    text-decoration: none !important;
	color: var(--link-color) !important;
}

th {
    text-transform: uppercase;
}

tr:nth-child(2n) * {
    color: yellow;
}

.bold {
	color: var(--link-color) !important;
}

.navbar {
    width: inherit;
}

.navbar .nav-item {
    text-transform: uppercase;
}

.board-title {
    background-color: black;
    color: white;
    text-transform: uppercase;
    border: 6px solid red;
    width: 60%;
    margin: auto;
    letter-spacing: 0.5em;
    padding: 10px;
}

.no-m-p  {
    background-color: blue;
    color: var(--font-color) !important;
    padding: 10px 10px;
}

.footer {
    width: 100%;
    display: flex;
    flex-flow: row wrap;
    align-content: space-between;
    justify-content: space-between;
    background-color: black;
    font-size: 0; /* This a trick used to hide the bullet points that aren't encapsulated by tags */
    line-height: normal;
}

.footer a {
    color: blue !important;
    flex: 1;
    text-transform: uppercase;
    font-size: initial;
	padding: 5px 0;
}

.footer > div {
    line-break: after;
    color: red;
    font-size: initial;
    padding: 10px 0;
}

.footer a:nth-child(4n + 1) {
    background-color: red;
    color: white !important;
}

.footer a:nth-child(4n + 2) {
    background-color: lime;
}

.footer a:nth-child(4n + 3) {
    background-color: yellow;
}

.footer a:nth-child(4n + 4) {
    background-color: cyan;
}


code:
:root {
	--icon-color: invert(17%) sepia(89%) saturate(7057%) hue-rotate(2deg) brightness(93%) contrast(120%);
	--alt-label-color: #260E22;
	--alt-font-color: #c5c8c6;
	--background-top: #260E22;
	--background-rest: #090A18;
	--navbar-color: #260E22;
	--post-color: #260E22;
	--post-outline-color: #090A18;
	--label-color: #260E22;
	--box-border-color: #111;
	--darken: #00000050;
	--highlighted-post-color: #54264C;
	--highlighted-post-outline-color: #111;
	--board-title: #c5c8c6;
	--hr: #282a2e;
	--font-color: #c5c8c6;
	--name-color: #c5c8c6;
	--capcode-color: #f00;
	--subject-color: #b294bb;
	--link-color: #c5c8c6;
	--post-link-color: #5f89ac;
	--link-hover: #81a2be;
	--accent-color: #c5c8c6;
	--input-borders: #111;
	--input-color: #c5c8c6;
	--input-background: #090A18;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: green;
	--pinktext-color: #E0727F;
}
#float .post-container, div#livetext, div#threadstats, .catalog-tile,
nav.pages, div.pages, .post-container:not(.op), nav.stickynav,
summary#action-menu, table, div.catalog-tile, div.highlighted,
div.hoverhighlighted, a#action-menu, .board-banner, .collapse, bottom-reply {
	border-width: 1px;
	border-radius: 2px;
}
input[type], textarea, .label, .rlabel, button, select:not(.postmenu),
.close, #settingsmodal, .postform-style, img, .spoiler, .code, .mono,
.detected, #postform {
	border-radius: 2px;
}
table {
	box-shadow: 0 0 3px #00000040;
}
.post-container.op {
	border-width: 1px;
}
.post-check {
	opacity: 0;
	margin-right: -1em;
	transition: opacity 100ms, margin-right 100ms;
}
.post-check:hover {
	opacity: 1;
	margin-right: unset;
}
.post-file-info {
	overflow-wrap: anywhere;
}
.spoiler {
	padding: 0 1px
}
.wrapbar + hr:first-of-type {
	margin: 1px 0 0 0;
}
body, small, .post-file-info, .quote, input, textarea {
	font-family: monospace!important;
	font-size: 9pt!important;
}


code:
/*tomorrow*/
:root {
	--icon-color:invert(17%)sepia(89%)saturate(7057%)hue-rotate(2deg)brightness(93%)contrast(120%);
	--alt-label-color: #282a2e;
	--alt-font-color: #c5c8c6;
	--background-top: #1d1f21;
	--background-rest: #1d1f21;
	--navbar-color: #282a2e;
	--post-color: #282a2e;
	--post-outline-color: #282a2e;
	--label-color: #282a2e;
	--box-border-color: #111;
	--darken: #ffffff10;
	--highlighted-post-color: #2b1d1f;
	--highlighted-post-outline-color: #111;
	--board-title: #c5c8c6;
	--hr: #282a2e;
	--font-color: #c5c8c6;
	--name-color: #c5c8c6;
	--capcode-color: #f00;
	--subject-color: #b294bb;
	--link-color: #c5c8c6;
	--post-link-color: #5f89ac;
	--link-hover: #81a2be;
	--accent-color: #c5c8c6;
	--input-borders: #111;
	--input-color: #c5c8c6;
	--input-background: #1d1d21;
	--dice-color: darkorange;
	--title-color: #d70000;
	--greentext-color: green;
	--pinktext-color:#E0727F;
}

table {
	border: none;
	border-spacing: 2px;
}

.captcha {
	filter: invert(90%);
}

.box-wrap, .tabs li a {
	border: 1px solid var(--box-border-color);
}

@media only screen and (max-width: 600px) {

	.post-info{
		border-bottom: none;
	}

	.anchor:target + .post-container .post-info, .post-container.highlighted .post-info {
		border-bottom: none;
	}

}