init
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/404.gif
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/404.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 340 B  | 
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/apple-touch-icon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 28 KiB  | 
							
								
								
									
										1
									
								
								themes/duckquill/static/auto-render.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1 @@
 | 
			
		|||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("katex")):"function"==typeof define&&define.amd?define(["katex"],t):"object"==typeof exports?exports.renderMathInElement=t(require("katex")):e.renderMathInElement=t(e.katex)}("undefined"!=typeof self?self:this,(function(e){return function(){"use strict";var t={771:function(t){t.exports=e}},n={};function r(e){var o=n[e];if(void 0!==o)return o.exports;var i=n[e]={exports:{}};return t[e](i,i.exports,r),i.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)};var o={};return function(){r.d(o,{default:function(){return d}});var e=r(771),t=r.n(e);const n=function(e,t,n){let r=n,o=0;const i=e.length;for(;r<t.length;){const n=t[r];if(o<=0&&t.slice(r,r+i)===e)return r;"\\"===n?r++:"{"===n?o++:"}"===n&&o--,r++}return-1},i=/^\\begin{/;var a=function(e,t){let r;const o=[],a=new RegExp("("+t.map((e=>e.left.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"))).join("|")+")");for(;r=e.search(a),-1!==r;){r>0&&(o.push({type:"text",data:e.slice(0,r)}),e=e.slice(r));const a=t.findIndex((t=>e.startsWith(t.left)));if(r=n(t[a].right,e,t[a].left.length),-1===r)break;const l=e.slice(0,r+t[a].right.length),s=i.test(l)?l:e.slice(t[a].left.length,r);o.push({type:"math",data:s,rawData:l,display:t[a].display}),e=e.slice(r+t[a].right.length)}return""!==e&&o.push({type:"text",data:e}),o};const l=function(e,n){const r=a(e,n.delimiters);if(1===r.length&&"text"===r[0].type)return null;const o=document.createDocumentFragment();for(let e=0;e<r.length;e++)if("text"===r[e].type)o.appendChild(document.createTextNode(r[e].data));else{const i=document.createElement("span");let a=r[e].data;n.displayMode=r[e].display;try{n.preProcess&&(a=n.preProcess(a)),t().render(a,i,n)}catch(i){if(!(i instanceof t().ParseError))throw i;n.errorCallback("KaTeX auto-render: Failed to parse `"+r[e].data+"` with ",i),o.appendChild(document.createTextNode(r[e].rawData));continue}o.appendChild(i)}return o},s=function(e,t){for(let n=0;n<e.childNodes.length;n++){const r=e.childNodes[n];if(3===r.nodeType){let o=r.textContent,i=r.nextSibling,a=0;for(;i&&i.nodeType===Node.TEXT_NODE;)o+=i.textContent,i=i.nextSibling,a++;const s=l(o,t);if(s){for(let e=0;e<a;e++)r.nextSibling.remove();n+=s.childNodes.length-1,e.replaceChild(s,r)}else n+=a}else if(1===r.nodeType){const e=" "+r.className+" ";-1===t.ignoredTags.indexOf(r.nodeName.toLowerCase())&&t.ignoredClasses.every((t=>-1===e.indexOf(" "+t+" ")))&&s(r,t)}}};var d=function(e,t){if(!e)throw new Error("No element provided to render");const n={};for(const e in t)t.hasOwnProperty(e)&&(n[e]=t[e]);n.delimiters=n.delimiters||[{left:"$$",right:"$$",display:!0},{left:"\\(",right:"\\)",display:!1},{left:"\\begin{equation}",right:"\\end{equation}",display:!0},{left:"\\begin{align}",right:"\\end{align}",display:!0},{left:"\\begin{alignat}",right:"\\end{alignat}",display:!0},{left:"\\begin{gather}",right:"\\end{gather}",display:!0},{left:"\\begin{CD}",right:"\\end{CD}",display:!0},{left:"\\[",right:"\\]",display:!0}],n.ignoredTags=n.ignoredTags||["script","noscript","style","textarea","pre","code","option"],n.ignoredClasses=n.ignoredClasses||[],n.errorCallback=n.errorCallback||console.error,n.macros=n.macros||{},s(e,n)}}(),o=o.default}()}));
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/card.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 MiB  | 
							
								
								
									
										27
									
								
								themes/duckquill/static/closable.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
const closable = document.querySelectorAll("details.closable");
 | 
			
		||||
 | 
			
		||||
closable.forEach((detail) => {
 | 
			
		||||
	detail.addEventListener("toggle", () => {
 | 
			
		||||
		if (detail.open) setTargetDetail(detail);
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
function setTargetDetail(targetDetail) {
 | 
			
		||||
	closable.forEach((detail) => {
 | 
			
		||||
		if (detail !== targetDetail) {
 | 
			
		||||
			detail.open = false;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.addEventListener("click", function (event) {
 | 
			
		||||
	const isClickInsideDetail = [...closable].some((detail) =>
 | 
			
		||||
		detail.contains(event.target)
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	if (!isClickInsideDetail) {
 | 
			
		||||
		closable.forEach((detail) => {
 | 
			
		||||
			detail.open = false;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										406
									
								
								themes/duckquill/static/comments.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,406 @@
 | 
			
		|||
// Taken from https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon/
 | 
			
		||||
// Attachment, card, and spoiler code taken from https://github.com/cassidyjames/cassidyjames.github.io/blob/99782788a7e3ba3cc52d6803010873abd1b02b9e/_includes/comments.html#L251-L296
 | 
			
		||||
 | 
			
		||||
let blogPostAuthorText = document.getElementById("blog-post-author-text").textContent;
 | 
			
		||||
let boostsFromText = document.getElementById("boosts-from-text").textContent;
 | 
			
		||||
let dateLocale = document.getElementById("date-locale").textContent;
 | 
			
		||||
let favesFromText = document.getElementById("faves-from-text").textContent;
 | 
			
		||||
let host = document.getElementById("host").textContent;
 | 
			
		||||
let id = document.getElementById("id").textContent;
 | 
			
		||||
let lazyAsyncImage = document.getElementById("lazy-async-image").textContent;
 | 
			
		||||
let loadingText = document.getElementById("loading-text").textContent;
 | 
			
		||||
let noCommentsText = document.getElementById("no-comments-text").textContent;
 | 
			
		||||
let relAttributes = document.getElementById("rel-attributes").textContent;
 | 
			
		||||
let reloadText = document.getElementById("reload-text").textContent;
 | 
			
		||||
let sensitiveText = document.getElementById("sensitive-text").textContent;
 | 
			
		||||
let user = document.getElementById("user").textContent;
 | 
			
		||||
let viewCommentText = document.getElementById("view-comment-text").textContent;
 | 
			
		||||
let viewProfileText = document.getElementById("view-profile-text").textContent;
 | 
			
		||||
 | 
			
		||||
document.getElementById("load-comments").addEventListener("click", loadComments);
 | 
			
		||||
 | 
			
		||||
function escapeHtml(unsafe) {
 | 
			
		||||
	return unsafe
 | 
			
		||||
		.replace(/&/g, "&")
 | 
			
		||||
		.replace(/</g, "<")
 | 
			
		||||
		.replace(/>/g, ">")
 | 
			
		||||
		.replace(/"/g, """)
 | 
			
		||||
		.replace(/'/g, "'");
 | 
			
		||||
}
 | 
			
		||||
function emojify(input, emojis) {
 | 
			
		||||
	let output = input;
 | 
			
		||||
 | 
			
		||||
	emojis.forEach((emoji) => {
 | 
			
		||||
		let picture = document.createElement("picture");
 | 
			
		||||
 | 
			
		||||
		let source = document.createElement("source");
 | 
			
		||||
		source.setAttribute("srcset", escapeHtml(emoji.url));
 | 
			
		||||
		source.setAttribute("media", "(prefers-reduced-motion: no-preference)");
 | 
			
		||||
 | 
			
		||||
		let img = document.createElement("img");
 | 
			
		||||
		img.className = "emoji";
 | 
			
		||||
		img.setAttribute("src", escapeHtml(emoji.static_url));
 | 
			
		||||
		img.setAttribute("alt", `:${emoji.shortcode}:`);
 | 
			
		||||
		img.setAttribute("title", `:${emoji.shortcode}:`);
 | 
			
		||||
		if (lazyAsyncImage == "true") {
 | 
			
		||||
			img.setAttribute("decoding", "async");
 | 
			
		||||
			img.setAttribute("loading", "lazy");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		picture.appendChild(source);
 | 
			
		||||
		picture.appendChild(img);
 | 
			
		||||
 | 
			
		||||
		output = output.replace(`:${emoji.shortcode}:`, picture.outerHTML);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	return output;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function loadComments() {
 | 
			
		||||
	let commentsWrapper = document.getElementById("comments-wrapper");
 | 
			
		||||
	commentsWrapper.innerHTML = "";
 | 
			
		||||
 | 
			
		||||
	let loadCommentsButton = document.getElementById("load-comments");
 | 
			
		||||
	loadCommentsButton.innerHTML = loadingText;
 | 
			
		||||
	loadCommentsButton.disabled = true;
 | 
			
		||||
 | 
			
		||||
	fetch(`https://${host}/api/v1/statuses/${id}/context`)
 | 
			
		||||
		.then(function (response) {
 | 
			
		||||
			return response.json();
 | 
			
		||||
		})
 | 
			
		||||
		.then(function (data) {
 | 
			
		||||
			let descendants = data["descendants"];
 | 
			
		||||
			if (
 | 
			
		||||
				descendants &&
 | 
			
		||||
				Array.isArray(descendants) &&
 | 
			
		||||
				descendants.length > 0
 | 
			
		||||
			) {
 | 
			
		||||
				commentsWrapper.innerHTML = "";
 | 
			
		||||
 | 
			
		||||
				descendants.forEach(function (status) {
 | 
			
		||||
					console.log(descendants);
 | 
			
		||||
					if (status.account.display_name.length > 0) {
 | 
			
		||||
						status.account.display_name = escapeHtml(
 | 
			
		||||
							status.account.display_name
 | 
			
		||||
						);
 | 
			
		||||
						status.account.display_name = emojify(
 | 
			
		||||
							status.account.display_name,
 | 
			
		||||
							status.account.emojis
 | 
			
		||||
						);
 | 
			
		||||
					} else {
 | 
			
		||||
						status.account.display_name = status.account.username;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					let instance = "";
 | 
			
		||||
					if (status.account.acct.includes("@")) {
 | 
			
		||||
						instance = status.account.acct.split("@")[1];
 | 
			
		||||
					} else {
 | 
			
		||||
						instance = host;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					const isReply = status.in_reply_to_id !== id;
 | 
			
		||||
 | 
			
		||||
					let op = false;
 | 
			
		||||
					if (status.account.acct == user) {
 | 
			
		||||
						op = true;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					status.content = emojify(status.content, status.emojis);
 | 
			
		||||
 | 
			
		||||
					let comment = document.createElement("article");
 | 
			
		||||
					comment.id = `comment-${status.id}`;
 | 
			
		||||
					comment.className = isReply ? "comment comment-reply" : "comment";
 | 
			
		||||
					comment.setAttribute("itemprop", "comment");
 | 
			
		||||
					comment.setAttribute("itemtype", "http://schema.org/Comment");
 | 
			
		||||
 | 
			
		||||
					let avatarSource = document.createElement("source");
 | 
			
		||||
					avatarSource.setAttribute(
 | 
			
		||||
						"srcset",
 | 
			
		||||
						escapeHtml(status.account.avatar)
 | 
			
		||||
					);
 | 
			
		||||
					avatarSource.setAttribute(
 | 
			
		||||
						"media",
 | 
			
		||||
						"(prefers-reduced-motion: no-preference)"
 | 
			
		||||
					);
 | 
			
		||||
 | 
			
		||||
					let avatarImg = document.createElement("img");
 | 
			
		||||
					avatarImg.className = "avatar";
 | 
			
		||||
					avatarImg.setAttribute(
 | 
			
		||||
						"src",
 | 
			
		||||
						escapeHtml(status.account.avatar_static)
 | 
			
		||||
					);
 | 
			
		||||
					avatarImg.setAttribute(
 | 
			
		||||
						"alt",
 | 
			
		||||
						`@${status.account.username}@${instance} avatar`
 | 
			
		||||
					);
 | 
			
		||||
					if (lazyAsyncImage == "true") {
 | 
			
		||||
						avatarImg.setAttribute("decoding", "async");
 | 
			
		||||
						avatarImg.setAttribute("loading", "lazy");
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					let avatarPicture = document.createElement("picture");
 | 
			
		||||
					avatarPicture.appendChild(avatarSource);
 | 
			
		||||
					avatarPicture.appendChild(avatarImg);
 | 
			
		||||
 | 
			
		||||
					let avatar = document.createElement("a");
 | 
			
		||||
					avatar.className = "avatar-link";
 | 
			
		||||
					avatar.setAttribute("href", status.account.url);
 | 
			
		||||
					avatar.setAttribute("rel", relAttributes);
 | 
			
		||||
					avatar.setAttribute(
 | 
			
		||||
						"title",
 | 
			
		||||
						`${viewProfileText} @${status.account.username}@${instance}`
 | 
			
		||||
					);
 | 
			
		||||
					avatar.appendChild(avatarPicture);
 | 
			
		||||
					comment.appendChild(avatar);
 | 
			
		||||
 | 
			
		||||
					let instanceBadge = document.createElement("a");
 | 
			
		||||
					instanceBadge.className = "instance";
 | 
			
		||||
					instanceBadge.setAttribute("href", status.account.url);
 | 
			
		||||
					instanceBadge.setAttribute(
 | 
			
		||||
						"title",
 | 
			
		||||
						`@${status.account.username}@${instance}`
 | 
			
		||||
					);
 | 
			
		||||
					instanceBadge.setAttribute("rel", relAttributes);
 | 
			
		||||
					instanceBadge.textContent = instance;
 | 
			
		||||
 | 
			
		||||
					let display = document.createElement("span");
 | 
			
		||||
					display.className = "display";
 | 
			
		||||
					display.setAttribute("itemprop", "author");
 | 
			
		||||
					display.setAttribute("itemtype", "http://schema.org/Person");
 | 
			
		||||
					display.innerHTML = status.account.display_name;
 | 
			
		||||
 | 
			
		||||
					let header = document.createElement("header");
 | 
			
		||||
					header.className = "author";
 | 
			
		||||
					header.appendChild(display);
 | 
			
		||||
					header.appendChild(instanceBadge);
 | 
			
		||||
					comment.appendChild(header);
 | 
			
		||||
 | 
			
		||||
					let permalink = document.createElement("a");
 | 
			
		||||
					permalink.setAttribute("href", status.url);
 | 
			
		||||
					permalink.setAttribute("itemprop", "url");
 | 
			
		||||
					permalink.setAttribute("title", `${viewCommentText} ${instance}`);
 | 
			
		||||
					permalink.setAttribute("rel", relAttributes);
 | 
			
		||||
					permalink.textContent = new Date(
 | 
			
		||||
						status.created_at
 | 
			
		||||
					).toLocaleString(dateLocale, {
 | 
			
		||||
						dateStyle: "long",
 | 
			
		||||
						timeStyle: "short",
 | 
			
		||||
					});
 | 
			
		||||
 | 
			
		||||
					let timestamp = document.createElement("time");
 | 
			
		||||
					timestamp.setAttribute("datetime", status.created_at);
 | 
			
		||||
					timestamp.appendChild(permalink);
 | 
			
		||||
					permalink.classList.add("external");
 | 
			
		||||
					comment.appendChild(timestamp);
 | 
			
		||||
 | 
			
		||||
					let main = document.createElement("main");
 | 
			
		||||
					main.setAttribute("itemprop", "text");
 | 
			
		||||
 | 
			
		||||
					if (status.sensitive == true || status.spoiler_text != "") {
 | 
			
		||||
						let summary = document.createElement("summary");
 | 
			
		||||
						if (status.spoiler_text == "") {
 | 
			
		||||
							status.spoiler_text == sensitiveText;
 | 
			
		||||
						}
 | 
			
		||||
						summary.innerHTML = status.spoiler_text;
 | 
			
		||||
 | 
			
		||||
						let spoiler = document.createElement("details");
 | 
			
		||||
						spoiler.appendChild(summary);
 | 
			
		||||
						spoiler.innerHTML += status.content;
 | 
			
		||||
 | 
			
		||||
						main.appendChild(spoiler);
 | 
			
		||||
					} else {
 | 
			
		||||
						main.innerHTML = status.content;
 | 
			
		||||
					}
 | 
			
		||||
					comment.appendChild(main);
 | 
			
		||||
 | 
			
		||||
					let attachments = status.media_attachments;
 | 
			
		||||
					let SUPPORTED_MEDIA = ["image", "video", "gifv", "audio"];
 | 
			
		||||
					let media = document.createElement("div");
 | 
			
		||||
					media.className = "attachments";
 | 
			
		||||
					if (
 | 
			
		||||
						attachments &&
 | 
			
		||||
						Array.isArray(attachments) &&
 | 
			
		||||
						attachments.length > 0
 | 
			
		||||
					) {
 | 
			
		||||
						attachments.forEach((attachment) => {
 | 
			
		||||
							if (SUPPORTED_MEDIA.includes(attachment.type)) {
 | 
			
		||||
 | 
			
		||||
								let mediaElement;
 | 
			
		||||
								switch (attachment.type) {
 | 
			
		||||
									case "image":
 | 
			
		||||
										mediaElement = document.createElement("img");
 | 
			
		||||
										mediaElement.setAttribute("src", attachment.preview_url);
 | 
			
		||||
 | 
			
		||||
										if (attachment.description != null) {
 | 
			
		||||
											mediaElement.setAttribute("alt", attachment.description);
 | 
			
		||||
											mediaElement.setAttribute("title", attachment.description);
 | 
			
		||||
										}
 | 
			
		||||
 | 
			
		||||
										if (lazyAsyncImage == "true") {
 | 
			
		||||
											mediaElement.setAttribute("decoding", "async");
 | 
			
		||||
											mediaElement.setAttribute("loading", "lazy");
 | 
			
		||||
										}
 | 
			
		||||
 | 
			
		||||
										if (status.sensitive == true) {
 | 
			
		||||
											mediaElement.classList.add("spoiler");
 | 
			
		||||
										}
 | 
			
		||||
 | 
			
		||||
										media.appendChild(mediaElement);
 | 
			
		||||
										break;
 | 
			
		||||
 | 
			
		||||
									case "video":
 | 
			
		||||
										mediaElement = document.createElement("video");
 | 
			
		||||
										mediaElement.setAttribute("src", attachment.url);
 | 
			
		||||
										mediaElement.setAttribute("controls", "");
 | 
			
		||||
 | 
			
		||||
										if (attachment.description != null) {
 | 
			
		||||
											mediaElement.setAttribute("aria-title", attachment.description);
 | 
			
		||||
											mediaElement.setAttribute("title", attachment.description);
 | 
			
		||||
										}
 | 
			
		||||
 | 
			
		||||
										if (status.sensitive == true) {
 | 
			
		||||
											mediaElement.classList.add("spoiler");
 | 
			
		||||
										}
 | 
			
		||||
 | 
			
		||||
										media.appendChild(mediaElement);
 | 
			
		||||
										break;
 | 
			
		||||
 | 
			
		||||
									case "gifv":
 | 
			
		||||
										mediaElement = document.createElement("video");
 | 
			
		||||
										mediaElement.setAttribute("src", attachment.url);
 | 
			
		||||
										mediaElement.setAttribute("autoplay", "");
 | 
			
		||||
										mediaElement.setAttribute("playsinline", "");
 | 
			
		||||
										mediaElement.setAttribute("loop", "");
 | 
			
		||||
 | 
			
		||||
										if (attachment.description != null) {
 | 
			
		||||
											mediaElement.setAttribute("aria-title", attachment.description);
 | 
			
		||||
											mediaElement.setAttribute("title", attachment.description);
 | 
			
		||||
										}
 | 
			
		||||
 | 
			
		||||
										if (status.sensitive == true) {
 | 
			
		||||
											mediaElement.classList.add("spoiler");
 | 
			
		||||
										}
 | 
			
		||||
 | 
			
		||||
										media.appendChild(mediaElement);
 | 
			
		||||
										break;
 | 
			
		||||
 | 
			
		||||
									case "audio":
 | 
			
		||||
										mediaElement = document.createElement("audio");
 | 
			
		||||
										mediaElement.setAttribute("src", attachment.url);
 | 
			
		||||
										mediaElement.setAttribute("controls", "");
 | 
			
		||||
 | 
			
		||||
										if (attachment.description != null) {
 | 
			
		||||
											mediaElement.setAttribute("aria-title", attachment.description);
 | 
			
		||||
											mediaElement.setAttribute("title", attachment.description);
 | 
			
		||||
										}
 | 
			
		||||
 | 
			
		||||
										media.appendChild(mediaElement);
 | 
			
		||||
										break;
 | 
			
		||||
								}
 | 
			
		||||
 | 
			
		||||
								let mediaLink = document.createElement("a");
 | 
			
		||||
								mediaLink.setAttribute("href", attachment.url);
 | 
			
		||||
								mediaLink.setAttribute("rel", relAttributes);
 | 
			
		||||
								mediaLink.appendChild(mediaElement);
 | 
			
		||||
 | 
			
		||||
								media.appendChild(mediaLink);
 | 
			
		||||
							}
 | 
			
		||||
						});
 | 
			
		||||
 | 
			
		||||
						comment.appendChild(media);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					let interactions = document.createElement("footer");
 | 
			
		||||
 | 
			
		||||
					let boosts = document.createElement("a");
 | 
			
		||||
					boosts.className = "boosts";
 | 
			
		||||
					boosts.setAttribute("href", `${status.url}/reblogs`);
 | 
			
		||||
					boosts.setAttribute("title", `${boostsFromText}`.replace("$INSTANCE", instance));
 | 
			
		||||
 | 
			
		||||
					let boostsIcon = document.createElement("i");
 | 
			
		||||
					boostsIcon.className = "icon";
 | 
			
		||||
					boosts.appendChild(boostsIcon);
 | 
			
		||||
					boosts.insertAdjacentHTML('beforeend', ` ${status.reblogs_count}`);
 | 
			
		||||
					interactions.appendChild(boosts);
 | 
			
		||||
 | 
			
		||||
					let faves = document.createElement("a");
 | 
			
		||||
					faves.className = "faves";
 | 
			
		||||
					faves.setAttribute("href", `${status.url}/favourites`);
 | 
			
		||||
					faves.setAttribute("title", `${favesFromText}`.replace("$INSTANCE", instance));
 | 
			
		||||
 | 
			
		||||
					let favesIcon = document.createElement("i");
 | 
			
		||||
					favesIcon.className = "icon";
 | 
			
		||||
					faves.appendChild(favesIcon);
 | 
			
		||||
					faves.insertAdjacentHTML('beforeend', ` ${status.favourites_count}`);
 | 
			
		||||
					interactions.appendChild(faves);
 | 
			
		||||
					comment.appendChild(interactions);
 | 
			
		||||
 | 
			
		||||
					if (status.card != null) {
 | 
			
		||||
						let cardFigure = document.createElement("figure");
 | 
			
		||||
 | 
			
		||||
						if (status.card.image != null) {
 | 
			
		||||
							let cardImg = document.createElement("img");
 | 
			
		||||
							cardImg.setAttribute("src", status.card.image);
 | 
			
		||||
							cardImg.classList.add("no-hover");
 | 
			
		||||
							cardFigure.appendChild(cardImg);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						let cardCaption = document.createElement("figcaption");
 | 
			
		||||
 | 
			
		||||
						let cardTitle = document.createElement("strong");
 | 
			
		||||
						cardTitle.innerHTML = status.card.title;
 | 
			
		||||
						cardCaption.appendChild(cardTitle);
 | 
			
		||||
 | 
			
		||||
						if (status.card.description != null && status.card.description.length > 0) {
 | 
			
		||||
							let cardDescription = document.createElement("p");
 | 
			
		||||
							cardDescription.innerHTML = status.card.description;
 | 
			
		||||
							cardCaption.appendChild(cardDescription);
 | 
			
		||||
						}
 | 
			
		||||
 | 
			
		||||
						cardFigure.appendChild(cardCaption);
 | 
			
		||||
 | 
			
		||||
						let card = document.createElement("a");
 | 
			
		||||
						card.className = "card";
 | 
			
		||||
						card.setAttribute("href", status.card.url);
 | 
			
		||||
						card.setAttribute("rel", relAttributes);
 | 
			
		||||
						card.appendChild(cardFigure);
 | 
			
		||||
 | 
			
		||||
						comment.appendChild(card);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					if (op === true) {
 | 
			
		||||
						comment.classList.add("op");
 | 
			
		||||
 | 
			
		||||
						avatar.classList.add("op");
 | 
			
		||||
						avatar.setAttribute(
 | 
			
		||||
							"title",
 | 
			
		||||
							`${blogPostAuthorText}: ` + avatar.getAttribute("title")
 | 
			
		||||
						);
 | 
			
		||||
 | 
			
		||||
						instanceBadge.classList.add("op");
 | 
			
		||||
						instanceBadge.setAttribute(
 | 
			
		||||
							"title",
 | 
			
		||||
							`${blogPostAuthorText}: ` + instanceBadge.getAttribute("title")
 | 
			
		||||
						);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					commentsWrapper.innerHTML += comment.outerHTML;
 | 
			
		||||
				});
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			else {
 | 
			
		||||
				var statusText = document.createElement("p");
 | 
			
		||||
				statusText.innerHTML = noCommentsText;
 | 
			
		||||
				statusText.setAttribute("id", "comments-status");
 | 
			
		||||
				commentsWrapper.appendChild(statusText);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			loadCommentsButton.innerHTML = reloadText;
 | 
			
		||||
		})
 | 
			
		||||
		.catch(function (error) {
 | 
			
		||||
			console.error('Error loading comments:', error);
 | 
			
		||||
		})
 | 
			
		||||
		.finally(function () {
 | 
			
		||||
			loadCommentsButton.disabled = false;
 | 
			
		||||
		});
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								themes/duckquill/static/copy-button.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
// Based on https://www.roboleary.net/2022/01/13/copy-code-to-clipboard-blog.html
 | 
			
		||||
document.addEventListener("DOMContentLoaded", function () {
 | 
			
		||||
	let blocks = document.querySelectorAll("pre[class^='language-']");
 | 
			
		||||
 | 
			
		||||
	blocks.forEach((block) => {
 | 
			
		||||
		if (navigator.clipboard) {
 | 
			
		||||
			// Code block header title
 | 
			
		||||
			let title = document.createElement("span");
 | 
			
		||||
			let lang = block.getAttribute("data-lang");
 | 
			
		||||
			title.innerHTML = lang;
 | 
			
		||||
 | 
			
		||||
			// Copy button icon
 | 
			
		||||
			let icon = document.createElement("i");
 | 
			
		||||
			icon.classList.add("icon");
 | 
			
		||||
 | 
			
		||||
			// Copy button
 | 
			
		||||
			let button = document.createElement("button");
 | 
			
		||||
			let copyCodeText = document.getElementById("copy-code-text").textContent;
 | 
			
		||||
			button.setAttribute("title", copyCodeText)
 | 
			
		||||
			button.appendChild(icon);
 | 
			
		||||
 | 
			
		||||
			// Code block header
 | 
			
		||||
			let header = document.createElement("div");
 | 
			
		||||
			header.classList.add("header");
 | 
			
		||||
			header.appendChild(title);
 | 
			
		||||
			header.appendChild(button);
 | 
			
		||||
 | 
			
		||||
			// Container that holds header and the code block itself
 | 
			
		||||
			let container = document.createElement("div");
 | 
			
		||||
			container.classList.add("pre-container");
 | 
			
		||||
			container.appendChild(header);
 | 
			
		||||
 | 
			
		||||
			// Move code block into the container
 | 
			
		||||
			block.parentNode.insertBefore(container, block);
 | 
			
		||||
			container.appendChild(block);
 | 
			
		||||
 | 
			
		||||
			button.addEventListener("click", async () => {
 | 
			
		||||
				await copyCode(block, header, button); // Pass the button here
 | 
			
		||||
			});
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	async function copyCode(block, header, button) {
 | 
			
		||||
		let code = block.querySelector("code");
 | 
			
		||||
		let text = code.innerText;
 | 
			
		||||
 | 
			
		||||
		await navigator.clipboard.writeText(text);
 | 
			
		||||
 | 
			
		||||
		header.classList.add("active");
 | 
			
		||||
		button.setAttribute("disabled", true);
 | 
			
		||||
 | 
			
		||||
		header.addEventListener("animationend", () => {
 | 
			
		||||
			header.classList.remove("active");
 | 
			
		||||
			button.removeAttribute("disabled");
 | 
			
		||||
		}, { once: true });
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										271
									
								
								themes/duckquill/static/count.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,271 @@
 | 
			
		|||
// GoatCounter: https://www.goatcounter.com
 | 
			
		||||
// This file is released under the ISC license: https://opensource.org/licenses/ISC
 | 
			
		||||
;(function() {
 | 
			
		||||
	'use strict';
 | 
			
		||||
 | 
			
		||||
	if (window.goatcounter && window.goatcounter.vars)  // Compatibility with very old version; do not use.
 | 
			
		||||
		window.goatcounter = window.goatcounter.vars
 | 
			
		||||
	else
 | 
			
		||||
		window.goatcounter = window.goatcounter || {}
 | 
			
		||||
 | 
			
		||||
	// Load settings from data-goatcounter-settings.
 | 
			
		||||
	var s = document.querySelector('script[data-goatcounter]')
 | 
			
		||||
	if (s && s.dataset.goatcounterSettings) {
 | 
			
		||||
		try         { var set = JSON.parse(s.dataset.goatcounterSettings) }
 | 
			
		||||
		catch (err) { console.error('invalid JSON in data-goatcounter-settings: ' + err) }
 | 
			
		||||
		for (var k in set)
 | 
			
		||||
			if (['no_onload', 'no_events', 'allow_local', 'allow_frame', 'path', 'title', 'referrer', 'event'].indexOf(k) > -1)
 | 
			
		||||
				window.goatcounter[k] = set[k]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var enc = encodeURIComponent
 | 
			
		||||
 | 
			
		||||
	// Get all data we're going to send off to the counter endpoint.
 | 
			
		||||
	var get_data = function(vars) {
 | 
			
		||||
		var data = {
 | 
			
		||||
			p: (vars.path     === undefined ? goatcounter.path     : vars.path),
 | 
			
		||||
			r: (vars.referrer === undefined ? goatcounter.referrer : vars.referrer),
 | 
			
		||||
			t: (vars.title    === undefined ? goatcounter.title    : vars.title),
 | 
			
		||||
			e: !!(vars.event || goatcounter.event),
 | 
			
		||||
			s: [window.screen.width, window.screen.height, (window.devicePixelRatio || 1)],
 | 
			
		||||
			b: is_bot(),
 | 
			
		||||
			q: location.search,
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var rcb, pcb, tcb  // Save callbacks to apply later.
 | 
			
		||||
		if (typeof(data.r) === 'function') rcb = data.r
 | 
			
		||||
		if (typeof(data.t) === 'function') tcb = data.t
 | 
			
		||||
		if (typeof(data.p) === 'function') pcb = data.p
 | 
			
		||||
 | 
			
		||||
		if (is_empty(data.r)) data.r = document.referrer
 | 
			
		||||
		if (is_empty(data.t)) data.t = document.title
 | 
			
		||||
		if (is_empty(data.p)) data.p = get_path()
 | 
			
		||||
 | 
			
		||||
		if (rcb) data.r = rcb(data.r)
 | 
			
		||||
		if (tcb) data.t = tcb(data.t)
 | 
			
		||||
		if (pcb) data.p = pcb(data.p)
 | 
			
		||||
		return data
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check if a value is "empty" for the purpose of get_data().
 | 
			
		||||
	var is_empty = function(v) { return v === null || v === undefined || typeof(v) === 'function' }
 | 
			
		||||
 | 
			
		||||
	// See if this looks like a bot; there is some additional filtering on the
 | 
			
		||||
	// backend, but these properties can't be fetched from there.
 | 
			
		||||
	var is_bot = function() {
 | 
			
		||||
		// Headless browsers are probably a bot.
 | 
			
		||||
		var w = window, d = document
 | 
			
		||||
		if (w.callPhantom || w._phantom || w.phantom)
 | 
			
		||||
			return 150
 | 
			
		||||
		if (w.__nightmare)
 | 
			
		||||
			return 151
 | 
			
		||||
		if (d.__selenium_unwrapped || d.__webdriver_evaluate || d.__driver_evaluate)
 | 
			
		||||
			return 152
 | 
			
		||||
		if (navigator.webdriver)
 | 
			
		||||
			return 153
 | 
			
		||||
		return 0
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Object to urlencoded string, starting with a ?.
 | 
			
		||||
	var urlencode = function(obj) {
 | 
			
		||||
		var p = []
 | 
			
		||||
		for (var k in obj)
 | 
			
		||||
			if (obj[k] !== '' && obj[k] !== null && obj[k] !== undefined && obj[k] !== false)
 | 
			
		||||
				p.push(enc(k) + '=' + enc(obj[k]))
 | 
			
		||||
		return '?' + p.join('&')
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Show a warning in the console.
 | 
			
		||||
	var warn = function(msg) {
 | 
			
		||||
		if (console && 'warn' in console)
 | 
			
		||||
			console.warn('goatcounter: ' + msg)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get the endpoint to send requests to.
 | 
			
		||||
	var get_endpoint = function() {
 | 
			
		||||
		var s = document.querySelector('script[data-goatcounter]')
 | 
			
		||||
		if (s && s.dataset.goatcounter)
 | 
			
		||||
			return s.dataset.goatcounter
 | 
			
		||||
		return (goatcounter.endpoint || window.counter)  // counter is for compat; don't use.
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get current path.
 | 
			
		||||
	var get_path = function() {
 | 
			
		||||
		var loc = location,
 | 
			
		||||
			c = document.querySelector('link[rel="canonical"][href]')
 | 
			
		||||
		if (c) {  // May be relative or point to different domain.
 | 
			
		||||
			var a = document.createElement('a')
 | 
			
		||||
			a.href = c.href
 | 
			
		||||
			if (a.hostname.replace(/^www\./, '') === location.hostname.replace(/^www\./, ''))
 | 
			
		||||
				loc = a
 | 
			
		||||
		}
 | 
			
		||||
		return (loc.pathname + loc.search) || '/'
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Run function after DOM is loaded.
 | 
			
		||||
	var on_load = function(f) {
 | 
			
		||||
		if (document.body === null)
 | 
			
		||||
			document.addEventListener('DOMContentLoaded', function() { f() }, false)
 | 
			
		||||
		else
 | 
			
		||||
			f()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Filter some requests that we (probably) don't want to count.
 | 
			
		||||
	goatcounter.filter = function() {
 | 
			
		||||
		if ('visibilityState' in document && document.visibilityState === 'prerender')
 | 
			
		||||
			return 'visibilityState'
 | 
			
		||||
		if (!goatcounter.allow_frame && location !== parent.location)
 | 
			
		||||
			return 'frame'
 | 
			
		||||
		if (!goatcounter.allow_local && location.hostname.match(/(localhost$|^127\.|^10\.|^172\.(1[6-9]|2[0-9]|3[0-1])\.|^192\.168\.|^0\.0\.0\.0$)/))
 | 
			
		||||
			return 'localhost'
 | 
			
		||||
		if (!goatcounter.allow_local && location.protocol === 'file:')
 | 
			
		||||
			return 'localfile'
 | 
			
		||||
		if (localStorage && localStorage.getItem('skipgc') === 't')
 | 
			
		||||
			return 'disabled with #toggle-goatcounter'
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get URL to send to GoatCounter.
 | 
			
		||||
	window.goatcounter.url = function(vars) {
 | 
			
		||||
		var data = get_data(vars || {})
 | 
			
		||||
		if (data.p === null)  // null from user callback.
 | 
			
		||||
			return
 | 
			
		||||
		data.rnd = Math.random().toString(36).substr(2, 5)  // Browsers don't always listen to Cache-Control.
 | 
			
		||||
 | 
			
		||||
		var endpoint = get_endpoint()
 | 
			
		||||
		if (!endpoint)
 | 
			
		||||
			return warn('no endpoint found')
 | 
			
		||||
 | 
			
		||||
		return endpoint + urlencode(data)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Count a hit.
 | 
			
		||||
	window.goatcounter.count = function(vars) {
 | 
			
		||||
		var f = goatcounter.filter()
 | 
			
		||||
		if (f)
 | 
			
		||||
			return warn('not counting because of: ' + f)
 | 
			
		||||
		var url = goatcounter.url(vars)
 | 
			
		||||
		if (!url)
 | 
			
		||||
			return warn('not counting because path callback returned null')
 | 
			
		||||
 | 
			
		||||
		if (!navigator.sendBeacon(url)) {
 | 
			
		||||
			// This mostly fails due to being blocked by CSP; try again with an
 | 
			
		||||
			// image-based fallback.
 | 
			
		||||
			var img = document.createElement('img')
 | 
			
		||||
			img.src = url
 | 
			
		||||
			img.style.position = 'absolute'  // Affect layout less.
 | 
			
		||||
			img.style.bottom = '0px'
 | 
			
		||||
			img.style.width = '1px'
 | 
			
		||||
			img.style.height = '1px'
 | 
			
		||||
			img.loading = 'eager'
 | 
			
		||||
			img.setAttribute('alt', '')
 | 
			
		||||
			img.setAttribute('aria-hidden', 'true')
 | 
			
		||||
 | 
			
		||||
			var rm = function() { if (img && img.parentNode) img.parentNode.removeChild(img) }
 | 
			
		||||
			img.addEventListener('load', rm, false)
 | 
			
		||||
			document.body.appendChild(img)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get a query parameter.
 | 
			
		||||
	window.goatcounter.get_query = function(name) {
 | 
			
		||||
		var s = location.search.substr(1).split('&')
 | 
			
		||||
		for (var i = 0; i < s.length; i++)
 | 
			
		||||
			if (s[i].toLowerCase().indexOf(name.toLowerCase() + '=') === 0)
 | 
			
		||||
				return s[i].substr(name.length + 1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Track click events.
 | 
			
		||||
	window.goatcounter.bind_events = function() {
 | 
			
		||||
		if (!document.querySelectorAll)  // Just in case someone uses an ancient browser.
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		var send = function(elem) {
 | 
			
		||||
			return function() {
 | 
			
		||||
				goatcounter.count({
 | 
			
		||||
					event:    true,
 | 
			
		||||
					path:     (elem.dataset.goatcounterClick || elem.name || elem.id || ''),
 | 
			
		||||
					title:    (elem.dataset.goatcounterTitle || elem.title || (elem.innerHTML || '').substr(0, 200) || ''),
 | 
			
		||||
					referrer: (elem.dataset.goatcounterReferrer || elem.dataset.goatcounterReferral || ''),
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Array.prototype.slice.call(document.querySelectorAll("*[data-goatcounter-click]")).forEach(function(elem) {
 | 
			
		||||
			if (elem.dataset.goatcounterBound)
 | 
			
		||||
				return
 | 
			
		||||
			var f = send(elem)
 | 
			
		||||
			elem.addEventListener('click', f, false)
 | 
			
		||||
			elem.addEventListener('auxclick', f, false)  // Middle click.
 | 
			
		||||
			elem.dataset.goatcounterBound = 'true'
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Add a "visitor counter" frame or image.
 | 
			
		||||
	window.goatcounter.visit_count = function(opt) {
 | 
			
		||||
		on_load(function() {
 | 
			
		||||
			opt        = opt        || {}
 | 
			
		||||
			opt.type   = opt.type   || 'html'
 | 
			
		||||
			opt.append = opt.append || 'body'
 | 
			
		||||
			opt.path   = opt.path   || get_path()
 | 
			
		||||
			opt.attr   = opt.attr   || {width: '200', height: (opt.no_branding ? '60' : '80')}
 | 
			
		||||
 | 
			
		||||
			opt.attr['src'] = get_endpoint() + 'er/' + enc(opt.path) + '.' + enc(opt.type) + '?'
 | 
			
		||||
			if (opt.no_branding) opt.attr['src'] += '&no_branding=1'
 | 
			
		||||
			if (opt.style)       opt.attr['src'] += '&style=' + enc(opt.style)
 | 
			
		||||
			if (opt.start)       opt.attr['src'] += '&start=' + enc(opt.start)
 | 
			
		||||
			if (opt.end)         opt.attr['src'] += '&end='   + enc(opt.end)
 | 
			
		||||
 | 
			
		||||
			var tag = {png: 'img', svg: 'img', html: 'iframe'}[opt.type]
 | 
			
		||||
			if (!tag)
 | 
			
		||||
				return warn('visit_count: unknown type: ' + opt.type)
 | 
			
		||||
 | 
			
		||||
			if (opt.type === 'html') {
 | 
			
		||||
				opt.attr['frameborder'] = '0'
 | 
			
		||||
				opt.attr['scrolling']   = 'no'
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			var d = document.createElement(tag)
 | 
			
		||||
			for (var k in opt.attr)
 | 
			
		||||
				d.setAttribute(k, opt.attr[k])
 | 
			
		||||
 | 
			
		||||
			var p = document.querySelector(opt.append)
 | 
			
		||||
			if (!p)
 | 
			
		||||
				return warn('visit_count: append not found: ' + opt.append)
 | 
			
		||||
			p.appendChild(d)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Make it easy to skip your own views.
 | 
			
		||||
	if (location.hash === '#toggle-goatcounter') {
 | 
			
		||||
		if (localStorage.getItem('skipgc') === 't') {
 | 
			
		||||
			localStorage.removeItem('skipgc', 't')
 | 
			
		||||
			alert('GoatCounter tracking is now ENABLED in this browser.')
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			localStorage.setItem('skipgc', 't')
 | 
			
		||||
			alert('GoatCounter tracking is now DISABLED in this browser until ' + location + ' is loaded again.')
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!goatcounter.no_onload)
 | 
			
		||||
		on_load(function() {
 | 
			
		||||
			// 1. Page is visible, count request.
 | 
			
		||||
			// 2. Page is not yet visible; wait until it switches to 'visible' and count.
 | 
			
		||||
			// See #487
 | 
			
		||||
			if (!('visibilityState' in document) || document.visibilityState === 'visible')
 | 
			
		||||
				goatcounter.count()
 | 
			
		||||
			else {
 | 
			
		||||
				var f = function(e) {
 | 
			
		||||
					if (document.visibilityState !== 'visible')
 | 
			
		||||
						return
 | 
			
		||||
					document.removeEventListener('visibilitychange', f)
 | 
			
		||||
					goatcounter.count()
 | 
			
		||||
				}
 | 
			
		||||
				document.addEventListener('visibilitychange', f)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if (!goatcounter.no_events)
 | 
			
		||||
				goatcounter.bind_events()
 | 
			
		||||
		})
 | 
			
		||||
})();
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/favicon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 548 B  | 
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/InterVariable-Italic.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/InterVariable.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/JetBrainsMono-Italic.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/JetBrainsMono.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_AMS-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Caligraphic-Bold.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Caligraphic-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Fraktur-Bold.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Fraktur-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Main-Bold.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Main-BoldItalic.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Main-Italic.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Main-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Math-BoldItalic.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Math-Italic.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_SansSerif-Bold.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_SansSerif-Italic.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_SansSerif-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Script-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Size1-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Size2-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Size3-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Size4-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								themes/duckquill/static/fonts/KaTeX_Typewriter-Regular.woff2
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										9
									
								
								themes/duckquill/static/fuse.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										8
									
								
								themes/duckquill/static/katex-init.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,8 @@
 | 
			
		|||
document.addEventListener("DOMContentLoaded", function () {
 | 
			
		||||
	renderMathInElement(document.body, {
 | 
			
		||||
		delimiters: [
 | 
			
		||||
			{ left: "$$", right: "$$", display: true },
 | 
			
		||||
			{ left: "$", right: "$", display: false },
 | 
			
		||||
		],
 | 
			
		||||
	});
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										1209
									
								
								themes/duckquill/static/katex.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								themes/duckquill/static/katex.min.js
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 96 B  | 
| 
		 After Width: | Height: | Size: 103 KiB  | 
							
								
								
									
										209
									
								
								themes/duckquill/static/search-elasticlunr.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,209 @@
 | 
			
		|||
// Based on https://github.com/getzola/zola/blob/1ac1231de1e342bbaf4d7a51a8a9a40ea152e246/docs/static/search.js
 | 
			
		||||
function debounce(func, wait) {
 | 
			
		||||
	var timeout;
 | 
			
		||||
 | 
			
		||||
	return function () {
 | 
			
		||||
		var context = this;
 | 
			
		||||
		var args = arguments;
 | 
			
		||||
		clearTimeout(timeout);
 | 
			
		||||
 | 
			
		||||
		timeout = setTimeout(function () {
 | 
			
		||||
			timeout = null;
 | 
			
		||||
			func.apply(context, args);
 | 
			
		||||
		}, wait);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Taken from mdbook
 | 
			
		||||
// The strategy is as follows:
 | 
			
		||||
// First, assign a value to each word in the document:
 | 
			
		||||
//  Words that correspond to search terms (stemmer aware): 40
 | 
			
		||||
//  Normal words: 2
 | 
			
		||||
//  First word in a sentence: 8
 | 
			
		||||
// Then use a sliding window with a constant number of words and count the
 | 
			
		||||
// sum of the values of the words within the window. Then use the window that got the
 | 
			
		||||
// maximum sum. If there are multiple maximas, then get the last one.
 | 
			
		||||
// Enclose the terms in <b>.
 | 
			
		||||
function makeTeaser(body, terms) {
 | 
			
		||||
	var TERM_WEIGHT = 40;
 | 
			
		||||
	var NORMAL_WORD_WEIGHT = 2;
 | 
			
		||||
	var FIRST_WORD_WEIGHT = 8;
 | 
			
		||||
	var TEASER_MAX_WORDS = 30;
 | 
			
		||||
 | 
			
		||||
	var stemmedTerms = terms.map(function (w) {
 | 
			
		||||
		return elasticlunr.stemmer(w.toLowerCase());
 | 
			
		||||
	});
 | 
			
		||||
	var termFound = false;
 | 
			
		||||
	var index = 0;
 | 
			
		||||
	var weighted = []; // contains elements of ["word", weight, index_in_document]
 | 
			
		||||
 | 
			
		||||
	// split in sentences, then words
 | 
			
		||||
	var sentences = body.toLowerCase().split(". ");
 | 
			
		||||
 | 
			
		||||
	for (var i in sentences) {
 | 
			
		||||
		var words = sentences[i].split(" ");
 | 
			
		||||
		var value = FIRST_WORD_WEIGHT;
 | 
			
		||||
 | 
			
		||||
		for (var j in words) {
 | 
			
		||||
			var word = words[j];
 | 
			
		||||
 | 
			
		||||
			if (word.length > 0) {
 | 
			
		||||
				for (var k in stemmedTerms) {
 | 
			
		||||
					if (elasticlunr.stemmer(word).startsWith(stemmedTerms[k])) {
 | 
			
		||||
						value = TERM_WEIGHT;
 | 
			
		||||
						termFound = true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				weighted.push([word, value, index]);
 | 
			
		||||
				value = NORMAL_WORD_WEIGHT;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			index += word.length;
 | 
			
		||||
			index += 1;  // ' ' or '.' if last word in sentence
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		index += 1;  // because we split at a two-char boundary '. '
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (weighted.length === 0) {
 | 
			
		||||
		return body;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var windowWeights = [];
 | 
			
		||||
	var windowSize = Math.min(weighted.length, TEASER_MAX_WORDS);
 | 
			
		||||
	// We add a window with all the weights first
 | 
			
		||||
	var curSum = 0;
 | 
			
		||||
	for (var i = 0; i < windowSize; i++) {
 | 
			
		||||
		curSum += weighted[i][1];
 | 
			
		||||
	}
 | 
			
		||||
	windowWeights.push(curSum);
 | 
			
		||||
 | 
			
		||||
	for (var i = 0; i < weighted.length - windowSize; i++) {
 | 
			
		||||
		curSum -= weighted[i][1];
 | 
			
		||||
		curSum += weighted[i + windowSize][1];
 | 
			
		||||
		windowWeights.push(curSum);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If we didn't find the term, just pick the first window
 | 
			
		||||
	var maxSumIndex = 0;
 | 
			
		||||
	if (termFound) {
 | 
			
		||||
		var maxFound = 0;
 | 
			
		||||
		// backwards
 | 
			
		||||
		for (var i = windowWeights.length - 1; i >= 0; i--) {
 | 
			
		||||
			if (windowWeights[i] > maxFound) {
 | 
			
		||||
				maxFound = windowWeights[i];
 | 
			
		||||
				maxSumIndex = i;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var teaser = [];
 | 
			
		||||
	var startIndex = weighted[maxSumIndex][2];
 | 
			
		||||
	for (var i = maxSumIndex; i < maxSumIndex + windowSize; i++) {
 | 
			
		||||
		var word = weighted[i];
 | 
			
		||||
		if (startIndex < word[2]) {
 | 
			
		||||
			// missing text from index to start of `word`
 | 
			
		||||
			teaser.push(body.substring(startIndex, word[2]));
 | 
			
		||||
			startIndex = word[2];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// add <strong> around search terms
 | 
			
		||||
		if (word[1] === TERM_WEIGHT) {
 | 
			
		||||
			teaser.push("<strong>");
 | 
			
		||||
		}
 | 
			
		||||
		startIndex = word[2] + word[0].length;
 | 
			
		||||
		teaser.push(body.substring(word[2], startIndex));
 | 
			
		||||
 | 
			
		||||
		if (word[1] === TERM_WEIGHT) {
 | 
			
		||||
			teaser.push("</strong>");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	teaser.push("…");
 | 
			
		||||
	return teaser.join("");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function formatSearchResultItem(item, terms) {
 | 
			
		||||
	return '<div class="item">'
 | 
			
		||||
		+ `<a href="${item.ref}">${item.doc.title}</a>`
 | 
			
		||||
		+ `<span>${makeTeaser(item.doc.body, terms)}</span>`
 | 
			
		||||
		+ '</div>';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function initSearch() {
 | 
			
		||||
	var searchBar = document.getElementById("search-bar");
 | 
			
		||||
	var searchContainer = document.getElementById("search-container");
 | 
			
		||||
	var searchResults = document.getElementById("search-results");
 | 
			
		||||
	var MAX_ITEMS = 10;
 | 
			
		||||
 | 
			
		||||
	var options = {
 | 
			
		||||
		bool: "AND",
 | 
			
		||||
		fields: {
 | 
			
		||||
			title: { boost: 2 },
 | 
			
		||||
			body: { boost: 1 },
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	var currentTerm = "";
 | 
			
		||||
	var index;
 | 
			
		||||
 | 
			
		||||
	var initIndex = async function () {
 | 
			
		||||
		if (index === undefined) {
 | 
			
		||||
			let searchIndex = document.getElementById("search-index").textContent;
 | 
			
		||||
			index = fetch(searchIndex)
 | 
			
		||||
				.then(
 | 
			
		||||
					async function (response) {
 | 
			
		||||
						return await elasticlunr.Index.load(await response.json());
 | 
			
		||||
					}
 | 
			
		||||
				);
 | 
			
		||||
		}
 | 
			
		||||
		let res = await index;
 | 
			
		||||
		return res;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	searchBar.addEventListener("keyup", debounce(async function () {
 | 
			
		||||
		var term = searchBar.value.trim();
 | 
			
		||||
		if (term === currentTerm) {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		searchResults.style.display = term === "" ? "none" : "flex";
 | 
			
		||||
		searchResults.innerHTML = "";
 | 
			
		||||
		currentTerm = term;
 | 
			
		||||
		if (term === "") {
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		var results = (await initIndex()).search(term, options);
 | 
			
		||||
		if (results.length === 0) {
 | 
			
		||||
			searchResults.style.display = "none";
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for (var i = 0; i < Math.min(results.length, MAX_ITEMS); i++) {
 | 
			
		||||
			searchResults.innerHTML += formatSearchResultItem(results[i], term.split(" "));
 | 
			
		||||
		}
 | 
			
		||||
	}, 150));
 | 
			
		||||
 | 
			
		||||
	document.addEventListener("keydown", function (event) {
 | 
			
		||||
		if (event.key === "/") {
 | 
			
		||||
			event.preventDefault();
 | 
			
		||||
			toggleSearch();
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	document.getElementById("search-toggle").addEventListener("click", toggleSearch);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function toggleSearch() {
 | 
			
		||||
	var searchContainer = document.getElementById("search-container");
 | 
			
		||||
	var searchBar = document.getElementById("search-bar");
 | 
			
		||||
	searchContainer.classList.toggle("active");
 | 
			
		||||
	searchBar.toggleAttribute("disabled");
 | 
			
		||||
	searchBar.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (document.readyState === "complete" ||
 | 
			
		||||
	(document.readyState !== "loading" && !document.documentElement.doScroll)
 | 
			
		||||
) {
 | 
			
		||||
	initSearch();
 | 
			
		||||
} else {
 | 
			
		||||
	document.addEventListener("DOMContentLoaded", initSearch);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								themes/duckquill/static/search-fuse.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,127 @@
 | 
			
		|||
	// Based on https://codeberg.org/daudix/duckquill/issues/101#issuecomment-2377169
 | 
			
		||||
	let searchSetup = false;
 | 
			
		||||
	let fuse;
 | 
			
		||||
 | 
			
		||||
	async function initIndex() {
 | 
			
		||||
		if (searchSetup) return;
 | 
			
		||||
 | 
			
		||||
		const url = document.getElementById("search-index").textContent;
 | 
			
		||||
		const response = await fetch(url);
 | 
			
		||||
 | 
			
		||||
		if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
 | 
			
		||||
 | 
			
		||||
		const options = {
 | 
			
		||||
			includeScore: false,
 | 
			
		||||
			includeMatches: true,
 | 
			
		||||
			ignoreLocation: true,
 | 
			
		||||
			threshold: 0.15,
 | 
			
		||||
			keys: [
 | 
			
		||||
				{ name: "title", weight: 3 },
 | 
			
		||||
				{ name: "description", weight: 2 },
 | 
			
		||||
				{ name: "body", weight: 1 }
 | 
			
		||||
			]
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		fuse = new Fuse(await response.json(), options);
 | 
			
		||||
		searchSetup = true;
 | 
			
		||||
 | 
			
		||||
		console.log("Search index initialized successfully");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function toggleSearch() {
 | 
			
		||||
		initIndex();
 | 
			
		||||
		const searchBar = document.getElementById("search-bar");
 | 
			
		||||
		const searchContainer = document.getElementById("search-container");
 | 
			
		||||
		const searchResults = document.getElementById("search-results");
 | 
			
		||||
		searchContainer.classList.toggle("active");
 | 
			
		||||
		searchBar.toggleAttribute("disabled");
 | 
			
		||||
		searchBar.focus();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function debounce(actual_fn, wait) {
 | 
			
		||||
		let timeoutId;
 | 
			
		||||
	
 | 
			
		||||
		return (...args) => {
 | 
			
		||||
			clearTimeout(timeoutId);
 | 
			
		||||
	
 | 
			
		||||
			timeoutId = setTimeout(() => {
 | 
			
		||||
				actual_fn(...args);
 | 
			
		||||
			}, wait);
 | 
			
		||||
		};
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	function initSearch() {
 | 
			
		||||
		const searchBar = document.getElementById("search-bar");
 | 
			
		||||
		const searchResults = document.getElementById("search-results");
 | 
			
		||||
		const searchContainer = document.getElementById("search-container");
 | 
			
		||||
		const MAX_ITEMS = 10;
 | 
			
		||||
		const MAX_RESULTS = 4;
 | 
			
		||||
 | 
			
		||||
		let currentTerm = "";
 | 
			
		||||
 | 
			
		||||
		searchBar.addEventListener("keyup", (e) => {
 | 
			
		||||
			const searchVal = searchBar.value.trim();
 | 
			
		||||
			const results = fuse.search(searchVal, { limit: MAX_ITEMS });
 | 
			
		||||
 | 
			
		||||
			let html = "";
 | 
			
		||||
			for (const result of results) {
 | 
			
		||||
				html += makeTeaser(result, searchVal);
 | 
			
		||||
			}
 | 
			
		||||
			searchResults.innerHTML = html;
 | 
			
		||||
 | 
			
		||||
			if (html) {
 | 
			
		||||
				searchResults.style.display = "flex";
 | 
			
		||||
			} else {
 | 
			
		||||
				searchResults.style.display = "none";
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		function makeTeaser(result, searchVal) {
 | 
			
		||||
			const TEASER_SIZE = 20;
 | 
			
		||||
			let output = `<div class="search-result item"><a class="result-title" href=${result.item.url}>${result.item.title}</a>`;
 | 
			
		||||
 | 
			
		||||
			for (const match of result.matches) {
 | 
			
		||||
				if (match.key === "title") continue;
 | 
			
		||||
 | 
			
		||||
				const indices = match.indices.sort((a, b) => Math.abs(a[1] - a[0] - searchVal.length) - Math.abs(b[1] - b[0] - searchVal.length)).slice(0, MAX_RESULTS);
 | 
			
		||||
				const value = match.value;
 | 
			
		||||
 | 
			
		||||
				for (const ind of indices) {
 | 
			
		||||
					const start = Math.max(0, ind[0] - TEASER_SIZE);
 | 
			
		||||
					const end = Math.min(value.length - 1, ind[1] + TEASER_SIZE);
 | 
			
		||||
					output += "<span>"
 | 
			
		||||
						+ value.substring(start, ind[0])
 | 
			
		||||
						+ `<strong>${value.substring(ind[0], ind[1] + 1)}</strong>`
 | 
			
		||||
						+ value.substring(ind[1] + 1, end)
 | 
			
		||||
						+ "</span>";
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (match.indices.length > 4) {
 | 
			
		||||
                    const moreMatchesText = document.getElementById("more-matches-text").textContent;
 | 
			
		||||
					output += `<span class="more-matches">${moreMatchesText}</span>`.replace("$MATCHES", `+${match.indices.length - MAX_RESULTS}`);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			return output + "</div>";
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		/*window.addEventListener("click", function (event) {
 | 
			
		||||
			if (searchSetup && searchBar.getAttribute("disabled") === null && !searchContainer.contains(event.target)) {
 | 
			
		||||
				toggleSearch();
 | 
			
		||||
			}
 | 
			
		||||
		}, { passive: true });*/
 | 
			
		||||
 | 
			
		||||
		document.addEventListener("keydown", function(event) {
 | 
			
		||||
			if (event.key === "/") {
 | 
			
		||||
				event.preventDefault();
 | 
			
		||||
				toggleSearch();
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		document.getElementById("search-toggle").addEventListener("click", toggleSearch);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (document.readyState === "complete" ||
 | 
			
		||||
		(document.readyState !== "loading" && !document.documentElement.doScroll))
 | 
			
		||||
		initSearch();
 | 
			
		||||
	else
 | 
			
		||||
		document.addEventListener("DOMContentLoaded", initSearch);
 | 
			
		||||
							
								
								
									
										283
									
								
								themes/duckquill/static/syntax-theme-dark.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,283 @@
 | 
			
		|||
/*
 | 
			
		||||
 * theme "Solarized (dark)" generated by syntect
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
.z-code {
 | 
			
		||||
 color: #839496;
 | 
			
		||||
 background-color: #002b36;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.z-comment, .z-meta.z-documentation {
 | 
			
		||||
 color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-string {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-string.z-regexp {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-character.z-escape {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-numeric {
 | 
			
		||||
 color: #6c71c4;
 | 
			
		||||
}
 | 
			
		||||
.z-variable {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-function {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-language {
 | 
			
		||||
 color: #d33682;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-import .z-keyword, .z-keyword.z-control.z-import, .z-keyword.z-control.z-import.z-from, .z-keyword.z-other.z-import, .z-keyword.z-control.z-at-rule.z-include, .z-keyword.z-control.z-at-rule.z-import {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-operator.z-comparison, .z-keyword.z-operator.z-assignment, .z-keyword.z-operator.z-arithmetic {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
.z-storage {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-storage.z-modifier {
 | 
			
		||||
 color: #93a1a1;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-control.z-class, .z-entity.z-name, .z-entity.z-name.z-class, .z-entity.z-name.z-type.z-class {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-inherited-class {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-attribute-name {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-support, .z-support.z-type, .z-support.z-class {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-function {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-variable {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-constant, .z-constant.z-language, .z-meta.z-preprocessor {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-section {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-construct, .z-keyword.z-other.z-new {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-character, .z-constant.z-other {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-tag {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-tag.z-html, .z-punctuation.z-definition.z-tag.z-begin, .z-punctuation.z-definition.z-tag.z-end {
 | 
			
		||||
 color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-separator.z-continuation {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-storage.z-type {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-type.z-exception {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-other.z-special-method {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-invalid {
 | 
			
		||||
 background-color: #6e2e32;
 | 
			
		||||
}
 | 
			
		||||
.z-string.z-quoted.z-double, .z-string.z-quoted.z-single {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-string {
 | 
			
		||||
 color: #839496;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-brace.z-square, .z-punctuation.z-section.z-brackets {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-brace.z-round, .z-meta.z-brace.z-curly, .z-punctuation.z-section, .z-punctuation.z-section.z-block, .z-punctuation.z-definition.z-parameters, .z-punctuation.z-section.z-group {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-constant.z-color, .z-invalid.z-deprecated.z-color.z-w3c-non-standard-color-name.z-scss {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-selector.z-css {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-tag.z-css, .z-entity.z-name.z-tag.z-scss, .z-source.z-less .z-keyword.z-control.z-html.z-elements, .z-source.z-sass .z-keyword.z-control.z-untitled {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-attribute-name.z-class {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-attribute-name.z-id {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-attribute-name.z-pseudo-element, .z-entity.z-other.z-attribute-name.z-tag.z-pseudo-element, .z-entity.z-other.z-attribute-name.z-pseudo-class, .z-entity.z-other.z-attribute-name.z-tag.z-pseudo-class {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-text.z-html.z-basic .z-meta.z-tag.z-other.z-html, .z-text.z-html.z-basic .z-meta.z-tag.z-any.z-html, .z-text.z-html.z-basic .z-meta.z-tag.z-block.z-any, .z-text.z-html.z-basic .z-meta.z-tag.z-inline.z-any, .z-text.z-html.z-basic .z-meta.z-tag.z-structure.z-any.z-html, .z-text.z-html.z-basic .z-source.z-js.z-embedded.z-html, .z-punctuation.z-separator.z-key-value.z-html {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
.z-text.z-html.z-basic .z-entity.z-other.z-attribute-name.z-html, .z-meta.z-tag.z-xml .z-entity.z-other.z-attribute-name {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-other.z-special-method.z-ruby {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-other.z-constant.z-ruby {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-other.z-symbol.z-ruby {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-other.z-special-method.z-ruby {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-array .z-support.z-function.z-construct.z-php {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-function.z-preprocessor.z-c, .z-meta.z-preprocessor.z-c.z-include, .z-meta.z-preprocessor.z-macro.z-c {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-preprocessor.z-c.z-include .z-string.z-quoted.z-other.z-lt-gt.z-include.z-c, .z-meta.z-preprocessor.z-c.z-include .z-punctuation.z-definition.z-string.z-begin.z-c, .z-meta.z-preprocessor.z-c.z-include .z-punctuation.z-definition.z-string.z-end.z-c {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-other.z-package.z-exclude, .z-other.z-remove {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-other.z-add {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-section.z-group.z-tex, .z-punctuation.z-definition.z-arguments.z-begin.z-latex, .z-punctuation.z-definition.z-arguments.z-end.z-latex, .z-punctuation.z-definition.z-arguments.z-latex {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-group.z-braces.z-tex {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-string.z-other.z-math.z-tex {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-parameter.z-function.z-latex {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-constant.z-math.z-tex {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-text.z-tex.z-latex .z-constant.z-other.z-math.z-tex, .z-constant.z-other.z-general.z-math.z-tex, .z-constant.z-other.z-general.z-math.z-tex, .z-constant.z-character.z-math.z-tex {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-string.z-other.z-math.z-tex {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-string.z-begin.z-tex, .z-punctuation.z-definition.z-string.z-end.z-tex {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-control.z-label.z-latex, .z-text.z-tex.z-latex .z-constant.z-other.z-general.z-math.z-tex {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-parameter.z-definition.z-label.z-latex {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-be.z-latex {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-section.z-latex {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-general.z-tex {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-control.z-ref.z-latex {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-storage.z-type.z-class.z-python, .z-storage.z-type.z-function.z-python, .z-storage.z-modifier.z-global.z-python {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-type.z-exception.z-python {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-scope.z-for-in-loop.z-shell, .z-variable.z-other.z-loop.z-shell {
 | 
			
		||||
 color: #93a1a1;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-scope.z-case-block.z-shell, .z-meta.z-scope.z-case-body.z-shell {
 | 
			
		||||
 color: #93a1a1;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-logical-expression.z-shell {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-storage.z-modifier.z-c++ {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-perl {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-diff, .z-meta.z-diff.z-header {
 | 
			
		||||
 color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-diff.z-range {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-deleted {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-changed {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-inserted {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-warning {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-error {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-heading, .z-punctuation.z-definition.z-heading.z-markdown {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-quote {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-italic {
 | 
			
		||||
font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-bold {
 | 
			
		||||
font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-underline.z-link.z-markdown, .z-meta.z-link.z-reference .z-constant.z-other.z-reference.z-link.z-markdown {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-other.z-reference.z-link.z-markdown {
 | 
			
		||||
 color: #6c71c4;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-paragraph.z-markdown .z-meta.z-dummy.z-line-break {
 | 
			
		||||
 background-color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-brackethighlighter.z-all {
 | 
			
		||||
 color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-filename.z-find-in-files {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-numeric.z-line-number.z-find-in-files {
 | 
			
		||||
 color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-other.z-readwrite.z-js, .z-variable.z-other.z-object.z-js, .z-variable.z-other.z-constant.z-js {
 | 
			
		||||
 color: #839496;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										283
									
								
								themes/duckquill/static/syntax-theme-light.css
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,283 @@
 | 
			
		|||
/*
 | 
			
		||||
 * theme "Solarized (light)" generated by syntect
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
.z-code {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
 background-color: #fdf6e3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.z-comment, .z-meta.z-documentation {
 | 
			
		||||
 color: #93a1a1;
 | 
			
		||||
}
 | 
			
		||||
.z-string {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-string.z-regexp {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-character.z-escape {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-numeric {
 | 
			
		||||
 color: #6c71c4;
 | 
			
		||||
}
 | 
			
		||||
.z-variable {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-function {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-language {
 | 
			
		||||
 color: #d33682;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-import .z-keyword, .z-keyword.z-control.z-import, .z-keyword.z-control.z-import.z-from, .z-keyword.z-other.z-import, .z-keyword.z-control.z-at-rule.z-include, .z-keyword.z-control.z-at-rule.z-import {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-operator.z-comparison, .z-keyword.z-operator.z-assignment, .z-keyword.z-operator.z-arithmetic {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
.z-storage {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-storage.z-modifier {
 | 
			
		||||
 color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-control.z-class, .z-entity.z-name, .z-entity.z-name.z-class, .z-entity.z-name.z-type.z-class {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-inherited-class {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-attribute-name {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-support, .z-support.z-type, .z-support.z-class {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-function {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-variable {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-constant, .z-constant.z-language, .z-meta.z-preprocessor {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-section {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-construct, .z-keyword.z-other.z-new {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-character, .z-constant.z-other {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-tag {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-tag.z-html, .z-punctuation.z-definition.z-tag.z-begin, .z-punctuation.z-definition.z-tag.z-end {
 | 
			
		||||
 color: #93a1a1;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-separator.z-continuation {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-storage.z-type {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-type.z-exception {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-other.z-special-method {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-invalid {
 | 
			
		||||
 background-color: #ec9489;
 | 
			
		||||
}
 | 
			
		||||
.z-string.z-quoted.z-double, .z-string.z-quoted.z-single {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-string {
 | 
			
		||||
 color: #839496;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-brace.z-square, .z-punctuation.z-section.z-brackets {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-brace.z-round, .z-meta.z-brace.z-curly, .z-punctuation.z-section, .z-punctuation.z-section.z-block, .z-punctuation.z-definition.z-parameters, .z-punctuation.z-section.z-group {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-constant.z-color, .z-invalid.z-deprecated.z-color.z-w3c-non-standard-color-name.z-scss {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-selector.z-css {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-tag.z-css, .z-entity.z-name.z-tag.z-scss, .z-source.z-less .z-keyword.z-control.z-html.z-elements, .z-source.z-sass .z-keyword.z-control.z-untitled {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-attribute-name.z-class {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-attribute-name.z-id {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-other.z-attribute-name.z-pseudo-element, .z-entity.z-other.z-attribute-name.z-tag.z-pseudo-element, .z-entity.z-other.z-attribute-name.z-pseudo-class, .z-entity.z-other.z-attribute-name.z-tag.z-pseudo-class {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-text.z-html.z-basic .z-meta.z-tag.z-other.z-html, .z-text.z-html.z-basic .z-meta.z-tag.z-any.z-html, .z-text.z-html.z-basic .z-meta.z-tag.z-block.z-any, .z-text.z-html.z-basic .z-meta.z-tag.z-inline.z-any, .z-text.z-html.z-basic .z-meta.z-tag.z-structure.z-any.z-html, .z-text.z-html.z-basic .z-source.z-js.z-embedded.z-html, .z-punctuation.z-separator.z-key-value.z-html {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
.z-text.z-html.z-basic .z-entity.z-other.z-attribute-name.z-html, .z-meta.z-tag.z-xml .z-entity.z-other.z-attribute-name {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-other.z-special-method.z-ruby {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-other.z-constant.z-ruby {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-other.z-symbol.z-ruby {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-other.z-special-method.z-ruby {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-array .z-support.z-function.z-construct.z-php {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-function.z-preprocessor.z-c, .z-meta.z-preprocessor.z-c.z-include, .z-meta.z-preprocessor.z-macro.z-c {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-preprocessor.z-c.z-include .z-string.z-quoted.z-other.z-lt-gt.z-include.z-c, .z-meta.z-preprocessor.z-c.z-include .z-punctuation.z-definition.z-string.z-begin.z-c, .z-meta.z-preprocessor.z-c.z-include .z-punctuation.z-definition.z-string.z-end.z-c {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-other.z-package.z-exclude, .z-other.z-remove {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-other.z-add {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-section.z-group.z-tex, .z-punctuation.z-definition.z-arguments.z-begin.z-latex, .z-punctuation.z-definition.z-arguments.z-end.z-latex, .z-punctuation.z-definition.z-arguments.z-latex {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-group.z-braces.z-tex {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-string.z-other.z-math.z-tex {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-parameter.z-function.z-latex {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-constant.z-math.z-tex {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-text.z-tex.z-latex .z-constant.z-other.z-math.z-tex, .z-constant.z-other.z-general.z-math.z-tex, .z-constant.z-other.z-general.z-math.z-tex, .z-constant.z-character.z-math.z-tex {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-string.z-other.z-math.z-tex {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-string.z-begin.z-tex, .z-punctuation.z-definition.z-string.z-end.z-tex {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-control.z-label.z-latex, .z-text.z-tex.z-latex .z-constant.z-other.z-general.z-math.z-tex {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-parameter.z-definition.z-label.z-latex {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-be.z-latex {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-section.z-latex {
 | 
			
		||||
 color: #cb4b16;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-general.z-tex {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-keyword.z-control.z-ref.z-latex {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-storage.z-type.z-class.z-python, .z-storage.z-type.z-function.z-python, .z-storage.z-modifier.z-global.z-python {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-type.z-exception.z-python {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-scope.z-for-in-loop.z-shell, .z-variable.z-other.z-loop.z-shell {
 | 
			
		||||
 color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-scope.z-case-block.z-shell, .z-meta.z-scope.z-case-body.z-shell {
 | 
			
		||||
 color: #586e75;
 | 
			
		||||
}
 | 
			
		||||
.z-punctuation.z-definition.z-logical-expression.z-shell {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-storage.z-modifier.z-c++ {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-support.z-function.z-perl {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-diff, .z-meta.z-diff.z-header {
 | 
			
		||||
 color: #93a1a1;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-diff.z-range {
 | 
			
		||||
 color: #268bd2;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-deleted {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-changed {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-inserted {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-warning {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-error {
 | 
			
		||||
 color: #dc322f;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-heading, .z-punctuation.z-definition.z-heading.z-markdown {
 | 
			
		||||
 color: #b58900;
 | 
			
		||||
font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-quote {
 | 
			
		||||
 color: #859900;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-italic {
 | 
			
		||||
font-style: italic;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-bold {
 | 
			
		||||
font-weight: bold;
 | 
			
		||||
}
 | 
			
		||||
.z-markup.z-underline.z-link.z-markdown, .z-meta.z-link.z-reference .z-constant.z-other.z-reference.z-link.z-markdown {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-other.z-reference.z-link.z-markdown {
 | 
			
		||||
 color: #6c71c4;
 | 
			
		||||
}
 | 
			
		||||
.z-meta.z-paragraph.z-markdown .z-meta.z-dummy.z-line-break {
 | 
			
		||||
 background-color: #eee8d5;
 | 
			
		||||
}
 | 
			
		||||
.z-brackethighlighter.z-all {
 | 
			
		||||
 color: #93a1a1;
 | 
			
		||||
}
 | 
			
		||||
.z-entity.z-name.z-filename.z-find-in-files {
 | 
			
		||||
 color: #2aa198;
 | 
			
		||||
}
 | 
			
		||||
.z-constant.z-numeric.z-line-number.z-find-in-files {
 | 
			
		||||
 color: #93a1a1;
 | 
			
		||||
}
 | 
			
		||||
.z-variable.z-other.z-readwrite.z-js, .z-variable.z-other.z-object.z-js, .z-variable.z-other.z-constant.z-js {
 | 
			
		||||
 color: #657b83;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										104
									
								
								themes/duckquill/static/theme-switcher.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
				
			
			@ -0,0 +1,104 @@
 | 
			
		|||
// Theme Initialization
 | 
			
		||||
(function () {
 | 
			
		||||
	// Get the default theme from the HTML data-theme attribute.
 | 
			
		||||
	const defaultTheme = document.documentElement.getAttribute("data-theme");
 | 
			
		||||
 | 
			
		||||
	// Set the data-default-theme attribute only if defaultTheme is not null.
 | 
			
		||||
	if (defaultTheme) {
 | 
			
		||||
		document.documentElement.setAttribute("data-default-theme", defaultTheme);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Attempt to retrieve the current theme from the browser's local storage.
 | 
			
		||||
	const storedTheme = localStorage.getItem("theme");
 | 
			
		||||
 | 
			
		||||
	if (storedTheme && storedTheme !== "system") {
 | 
			
		||||
		document.documentElement.setAttribute("data-theme", storedTheme);
 | 
			
		||||
	} else if (defaultTheme && storedTheme !== "system") {
 | 
			
		||||
		document.documentElement.setAttribute("data-theme", defaultTheme);
 | 
			
		||||
	} else {
 | 
			
		||||
		// If no theme is found in local storage and no default theme is set, hand over control to the CSS.
 | 
			
		||||
		document.documentElement.removeAttribute("data-theme");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Expose defaultTheme to the outer scope.
 | 
			
		||||
	window.defaultTheme = defaultTheme;
 | 
			
		||||
})();
 | 
			
		||||
 | 
			
		||||
// Icon Update and Theme Switching
 | 
			
		||||
function setTheme(theme, saveToLocalStorage = false) {
 | 
			
		||||
	if (theme === "system") {
 | 
			
		||||
		document.documentElement.removeAttribute("data-theme");
 | 
			
		||||
	} else {
 | 
			
		||||
		document.documentElement.setAttribute("data-theme", theme);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (saveToLocalStorage) {
 | 
			
		||||
		localStorage.setItem("theme", theme);
 | 
			
		||||
	} else {
 | 
			
		||||
		localStorage.removeItem("theme");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Update icon class based on the selected theme.
 | 
			
		||||
	updateIconClass(theme);
 | 
			
		||||
 | 
			
		||||
	// Update the active button based on the selected theme.
 | 
			
		||||
	updateActiveButton(theme);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function resetTheme() {
 | 
			
		||||
	// Reset the theme to the default or system preference if no default is set.
 | 
			
		||||
	setTheme(window.defaultTheme || "system");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function switchTheme(theme) {
 | 
			
		||||
	if (theme === "system") {
 | 
			
		||||
		resetTheme();
 | 
			
		||||
	} else {
 | 
			
		||||
		setTheme(theme, true);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateIconClass(theme) {
 | 
			
		||||
	const iconElement = document.querySelector("#theme-switcher summary .icon");
 | 
			
		||||
 | 
			
		||||
	// Remove any existing theme classes
 | 
			
		||||
	iconElement.classList.remove("light", "dark");
 | 
			
		||||
 | 
			
		||||
	// Add the appropriate class based on the selected theme
 | 
			
		||||
	if (theme === "light") {
 | 
			
		||||
		iconElement.classList.add("light");
 | 
			
		||||
	} else if (theme === "dark") {
 | 
			
		||||
		iconElement.classList.add("dark");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function updateActiveButton(theme) {
 | 
			
		||||
	// Remove .active class from all buttons
 | 
			
		||||
	document.querySelectorAll('#theme-switcher button').forEach(button => {
 | 
			
		||||
		button.classList.remove('active');
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	// Add .active class to the button corresponding to the current theme
 | 
			
		||||
	const activeButton = document.querySelector(`#theme-${theme}`);
 | 
			
		||||
	if (activeButton) {
 | 
			
		||||
		activeButton.classList.add('active');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
document.getElementById("theme-light").addEventListener("click", function () {
 | 
			
		||||
	switchTheme("light");
 | 
			
		||||
});
 | 
			
		||||
document.getElementById("theme-dark").addEventListener("click", function () {
 | 
			
		||||
	switchTheme("dark");
 | 
			
		||||
});
 | 
			
		||||
document.getElementById("theme-system").addEventListener("click", function () {
 | 
			
		||||
	switchTheme("system");
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// Update icon class on page load based on current theme
 | 
			
		||||
const currentTheme = localStorage.getItem("theme") || window.defaultTheme || "system";
 | 
			
		||||
updateIconClass(currentTheme);
 | 
			
		||||
updateActiveButton(currentTheme);
 | 
			
		||||
 | 
			
		||||
// Make the switchTheme function accessible globally
 | 
			
		||||
window.switchTheme = switchTheme;
 | 
			
		||||