From 1cfb6109385d701183762aeb0750e3e46605ee66 Mon Sep 17 00:00:00 2001 From: Declan Chidlow Date: Fri, 28 Jun 2024 20:05:33 +0800 Subject: [PATCH] Move from Prism to Shiki for syntax highlighing --- docs/scripts/figureview.js | 36 +++ docs/scripts/postsrefine.js | 35 +++ docs/scripts/puddle.js | 262 ++++++++++++++++++++++ docs/scripts/scrollfade.js | 24 ++ docs/scripts/syntax-highlighting.js | 39 ++++ docs/styles/animations/fade_in_up.css | 14 ++ docs/styles/base/body.css | 31 +++ docs/styles/base/reset.css | 53 +++++ docs/styles/components/article_header.css | 49 ++++ docs/styles/components/figures.css | 75 +++++++ docs/styles/components/footer.css | 33 +++ docs/styles/components/input.css | 38 ++++ docs/styles/components/navbar.css | 65 ++++++ docs/styles/components/section.css | 23 ++ docs/styles/components/seperator.css | 7 + docs/styles/pages/index.css | 50 +++++ docs/styles/pages/portfolio.css | 47 ++++ docs/styles/pages/posts.css | 82 +++++++ docs/styles/pages/services.css | 19 ++ docs/styles/pages/support.css | 7 + docs/styles/style.css | 26 +++ docs/styles/type/alerts.css | 44 ++++ docs/styles/type/code.css | 24 ++ docs/styles/type/faces.css | 19 ++ docs/styles/type/headers.css | 41 ++++ docs/styles/type/links.css | 35 +++ docs/styles/type/type.css | 37 +++ docs/styles/variables/colours.css | 33 +++ 28 files changed, 1248 insertions(+) create mode 100644 docs/scripts/figureview.js create mode 100644 docs/scripts/postsrefine.js create mode 100644 docs/scripts/puddle.js create mode 100644 docs/scripts/scrollfade.js create mode 100644 docs/scripts/syntax-highlighting.js create mode 100644 docs/styles/animations/fade_in_up.css create mode 100644 docs/styles/base/body.css create mode 100644 docs/styles/base/reset.css create mode 100644 docs/styles/components/article_header.css create mode 100644 docs/styles/components/figures.css create mode 100644 docs/styles/components/footer.css create mode 100644 docs/styles/components/input.css create mode 100644 docs/styles/components/navbar.css create mode 100644 docs/styles/components/section.css create mode 100644 docs/styles/components/seperator.css create mode 100644 docs/styles/pages/index.css create mode 100644 docs/styles/pages/portfolio.css create mode 100644 docs/styles/pages/posts.css create mode 100644 docs/styles/pages/services.css create mode 100644 docs/styles/pages/support.css create mode 100755 docs/styles/style.css create mode 100644 docs/styles/type/alerts.css create mode 100644 docs/styles/type/code.css create mode 100644 docs/styles/type/faces.css create mode 100644 docs/styles/type/headers.css create mode 100644 docs/styles/type/links.css create mode 100644 docs/styles/type/type.css create mode 100644 docs/styles/variables/colours.css diff --git a/docs/scripts/figureview.js b/docs/scripts/figureview.js new file mode 100644 index 0000000..0544cf7 --- /dev/null +++ b/docs/scripts/figureview.js @@ -0,0 +1,36 @@ +document.addEventListener("DOMContentLoaded", function () { + const images = document.querySelectorAll("figure img"), + modal = document.getElementById("fullscreenModal"), + modalImg = document.getElementById("fullscreenImg"), + captionText = document.getElementById("caption"), + altText = document.getElementById("altText"), + closeButton = document.getElementById("close"); + + Array.from(images).forEach((img) => { + img.style.cursor = "zoom-in"; + img.addEventListener("click", () => openFullscreen(img)); + }); + + closeButton.addEventListener("click", closeFullscreen); + + modal.addEventListener("click", (event) => { + if (event.target === modal) { + closeFullscreen(); + } + }); + + function openFullscreen(imgElement) { + modal.style.display = "block"; + modalImg.src = imgElement.currentSrc || imgElement.src; + altText.textContent = imgElement.alt; + + let caption = imgElement.nextElementSibling; + if (caption && caption.tagName.toLowerCase() === "figcaption") { + captionText.textContent = caption.textContent; + } + } + + function closeFullscreen() { + modal.style.display = "none"; + } +}); diff --git a/docs/scripts/postsrefine.js b/docs/scripts/postsrefine.js new file mode 100644 index 0000000..e7fb034 --- /dev/null +++ b/docs/scripts/postsrefine.js @@ -0,0 +1,35 @@ +// Show refine bar if user has JS enabled +document.getElementById("refine-posts").style.display = "flex"; + +const postItems = document.getElementById("post-items"), + postsSearch = document.getElementById("posts-search"), + postsFilter = document.getElementById("posts-filter"), + posts = postItems.querySelectorAll(".post"); + +postsSearch.addEventListener("keyup", searchPosts); +postsFilter.addEventListener("change", filterPosts); + +document.addEventListener("keydown", function (event) { + if (event.key === "/") { + event.preventDefault(); + postsSearch.focus(); + } +}); + +function searchPosts() { + const postsSearchSanitised = postsSearch.value.toUpperCase(); + + posts.forEach((a) => { + const txtValue = a.textContent.trim().toUpperCase(); + a.style.display = txtValue.includes(postsSearchSanitised) ? "" : "none"; + }); +} + +function filterPosts() { + const postsFilterSanitised = postsFilter.value.toUpperCase(); + + posts.forEach((a) => { + const postType = a.querySelector("p").textContent.toUpperCase(); + a.style.display = postsFilterSanitised === "ALL" || postType === postsFilterSanitised ? "" : "none"; + }); +} diff --git a/docs/scripts/puddle.js b/docs/scripts/puddle.js new file mode 100644 index 0000000..ab24d40 --- /dev/null +++ b/docs/scripts/puddle.js @@ -0,0 +1,262 @@ +// Based on Puddle.js +// https://batmannair.com/puddle.js + +class AsciiNode { + constructor(xx, yy, data) { + // Node coordinates and data + this.xx = xx; + this.yy = yy; + this.data = data; + + // Functions for node update and force computation + this.updateNode = this.updateNodeHelias; + this.computeForceAndDrawNode = this.computeForceAndDrawNodeHelias; + + // Forces and direction variables + this.currentForce = 0; + this.nextForce = 0; + + // Flags for update + this.isAddedToUpdate = false; + this.isMoveForceDelayComplete = true; + + // ASCII shades and their corresponding threshold values + this.asciiShades = [..." .,:-=+*#%@"]; + this.asciiThreshold = this.asciiShades.map((el, index) => (index * 100.0) / (this.asciiShades.length - 1)); + } + + // Attach click and mousemove listeners + applyListeners() { + this.element.onclick = () => this.startRipple(); + this.element.onmousemove = () => { + this.isMoveForceDelayComplete = false; + this.startRipple(); + setTimeout(() => (this.isMoveForceDelayComplete = true), 500); + }; + } + + // Create the HTML element representing the node + getNodeElement() { + this.element = document.createElement("span"); + this.element.style.cssText = "display: block; width: 100%; height: 100%;"; + this.drawNode(0); + this.applyListeners(); + return this.element; + } + + // Start a ripple effect from this node + startRipple(rippleStrength = 0) { + if (!rippleStrength) rippleStrength = this.data.maxRippleStrength; + this.currentForce = rippleStrength; + + this.drawNode(rippleStrength); + + // Update neighboring nodes + for (let yChange = -1; yChange <= 1; ++yChange) { + for (let xChange = -1; xChange <= 1; ++xChange) { + this.data.addToUpdateQueue(this.xx + xChange, this.yy + yChange); + } + } + } + + // Update node's force based on the Helias algorithm + updateNodeHelias() { + // Helias math mode taken from https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm + // Fetch forces from neighboring nodes + let nodeUp = this.data.getNode(this.xx, this.yy - 1); + let nodeUpForce = nodeUp ? nodeUp.currentForce : 0; + let nodeDown = this.data.getNode(this.xx, this.yy + 1); + let nodeDownForce = nodeDown ? nodeDown.currentForce : 0; + let nodeRight = this.data.getNode(this.xx + 1, this.yy); + let nodeRightForce = nodeRight ? nodeRight.currentForce : 0; + let nodeLeft = this.data.getNode(this.xx - 1, this.yy); + let nodeLeftForce = nodeLeft ? nodeLeft.currentForce : 0; + + // Calculate next force based on neighboring forces + this.nextForce = (nodeUpForce + nodeDownForce + nodeRightForce + nodeLeftForce) / 2 - this.nextForce; + this.nextForce = this.nextForce * this.data.forceDampeningRatio; + + this.data.addToDrawQueue(this.xx, this.yy); + } + + // Draw the node based on force magnitude + drawNode(forceMagnitude) { + forceMagnitude = Math.min(100, Math.max(0, forceMagnitude)); + let index = this.asciiThreshold.findIndex((el) => el >= forceMagnitude); + this.element.innerText = this.asciiShades[index]; + } + + // Compute force and update node's visual representation + computeForceAndDrawNodeHelias() { + if (Math.abs(this.nextForce) < this.data.forceCutOff) this.nextForce = 0; + this.drawNode(this.nextForce); + let temp = this.currentForce; + this.currentForce = this.nextForce; + this.nextForce = temp; + + // Update neighboring nodes for force calculation + this.data.addToUpdateQueue(this.xx - 1, this.yy); + this.data.addToUpdateQueue(this.xx + 1, this.yy); + this.data.addToUpdateQueue(this.xx, this.yy - 1); + this.data.addToUpdateQueue(this.xx, this.yy + 1); + } +} + +// Class managing the data for the puddle simulation +class PuddleData { + constructor(numRows, numCols) { + // Initialize data structures + this.nodeList = []; + this.updateQueue = []; + this.drawQueue = []; + this.isUpdateDone = true; + this.numRows = numRows; + this.numCols = numCols; + + // Default simulation parameters + this.maxRippleStrength = 100.0; + this.forceDampeningRatio = 0.85; // Force dampening percent + this.forceCutOff = 2; // Axial force less than this is set to 0 + } + + // Refresh and set new dimensions for the simulation + refresh(numRows, numCols) { + this.nodeList = []; + this.updateQueue = []; + this.drawQueue = []; + this.isUpdateDone = true; + this.numRows = numRows; + this.numCols = numCols; + } + + // Add node to the simulation grid + appendNode(node) { + this.nodeList.push(node); + } + + // Get node at specific coordinates in the grid + getNode(xx, yy) { + if (xx < 0 || xx >= this.numCols || yy < 0 || yy >= this.numRows) return undefined; + return this.nodeList[yy * this.numCols + xx]; + } + + // Add node to the update queue if not already added + addToUpdateQueue(xx, yy) { + if (xx < 0 || xx >= this.numCols || yy < 0 || yy >= this.numRows) return; + let index = yy * this.numCols + xx; + if (!this.nodeList[index].isAddedToUpdate) { + this.updateQueue.push(index); + this.nodeList[index].isAddedToUpdate = true; + } + } + + // Add node to the draw queue + addToDrawQueue(xx, yy) { + // Adding to draw is only done on self and so no errors should pop up + let index = yy * this.numCols + xx; + this.drawQueue.push(index); + } + + // Draw elements in the draw queue + drawElements() { + let drawList = this.drawQueue; + this.drawQueue = []; + for (const index of drawList) { + let node = this.nodeList[index]; + node.computeForceAndDrawNode(); + } + } + + // Update elements in the update queue + updateElements() { + this.isUpdateDone = false; + let updateList = this.updateQueue; + this.updateQueue = []; + for (const index of updateList) { + let node = this.nodeList[index]; + node.isAddedToUpdate = false; + } + for (const index of updateList) { + let node = this.nodeList[index]; + node.updateNode(); + } + this.drawElements(); + this.isUpdateDone = true; + } +} + +// Main class managing the entire puddle simulation +class Puddle { + constructor(queryElement, updateInterval = 100) { + // Initialize parent node and simulation data + this.parentNode = document.querySelector(queryElement); + this.data = new PuddleData(this.numRows, this.numCols); + this.updateInterval = updateInterval; + this.nodeStyle = AsciiNode; + this.nodeSize = 14; + this.updateLoop = undefined; + this.setupDefaultOptions(); + + // Event listener for window resize + window.addEventListener("resize", () => this.resizeHandler()); + } + + // Set the style of nodes + setNodeStyle() { + this.nodeStyle = AsciiNode; + this.setupGrid(); + } + + // Set default options for the simulation based on parent node dimensions + setupDefaultOptions() { + this.elementWidth = this.parentNode.scrollWidth; + this.elementHeight = this.parentNode.scrollHeight; + let lesserDimension = this.elementHeight < this.elementWidth ? this.elementHeight : this.elementWidth; + this.nodeSize = (lesserDimension * 3) / 100; + if (this.elementHeight) { + this.numRows = Math.floor(this.elementHeight / this.nodeSize); + this.numCols = Math.floor(this.elementWidth / this.nodeSize); + } + } + + // Resize the grid based on parent node dimensions + resizeGrid() { + this.elementWidth = this.parentNode.scrollWidth; + this.elementHeight = this.parentNode.scrollHeight; + if (this.elementHeight) { + this.numRows = Math.floor(this.elementHeight / this.nodeSize); + this.numCols = Math.floor(this.elementWidth / this.nodeSize); + } + this.setupGrid(); + } + + // Function to handle resizing of the grid + resizeHandler() { + this.resizeGrid(); + } + + // Set up the grid structure and initialize nodes for the simulation + setupGrid() { + clearInterval(this.updateLoop); + this.data.refresh(this.numRows, this.numCols); + + this.parentNode.innerHTML = ""; + this.parentNode.style.cssText = `font-family: "Fira Code, monospace"; display: grid; grid-template-columns: repeat(${this.numCols}, ${this.nodeSize}px); grid-template-rows: repeat(${this.numRows}, ${this.nodeSize}px);`; + + for (let yy = 0; yy < this.numRows; ++yy) { + for (let xx = 0; xx < this.numCols; ++xx) { + let node = new this.nodeStyle(xx, yy, this.data); + this.data.appendNode(node); + this.parentNode.appendChild(node.getNodeElement()); + } + } + + this.updateLoop = setInterval(() => this.tryUpdateElements(), this.updateInterval); + } + + // Try to update elements in the simulation + tryUpdateElements() { + if (this.data.isUpdateDone) this.data.updateElements(); + else console.log("Previous update not completed, skipping update"); + } +} \ No newline at end of file diff --git a/docs/scripts/scrollfade.js b/docs/scripts/scrollfade.js new file mode 100644 index 0000000..010bae6 --- /dev/null +++ b/docs/scripts/scrollfade.js @@ -0,0 +1,24 @@ +if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + const elements = document.querySelectorAll(".post"); + + function fadeInIfInView() { + elements.forEach((el) => { + if (isInViewport(el)) { + el.classList.add("fade-in-up"); + } + }); + } + + function isInViewport(element) { + const rect = element.getBoundingClientRect(); + return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth); + } + + fadeInIfInView(); // Check initially on page load + + window.addEventListener("scroll", () => { + elements.forEach((el) => { + el.classList.toggle("fade-in-up", window.scrollY >= el.offsetTop - window.innerHeight); + }); + }); +} diff --git a/docs/scripts/syntax-highlighting.js b/docs/scripts/syntax-highlighting.js new file mode 100644 index 0000000..8971f39 --- /dev/null +++ b/docs/scripts/syntax-highlighting.js @@ -0,0 +1,39 @@ +// Based on https://brandur.org/fragments/shiki + +import { codeToHtml } from "https://esm.sh/shiki@1.0.0"; + +const htmlEscapes = new Map([ + ["&", "&"], + ["<", "<"], + [">", ">"], +]); + +function unescapeHTMLEntities(str) { + for (const [escaped, unescaped] of htmlEscapes) { + str = str.replaceAll(escaped, unescaped); + } + return str; +} + +const processCodeBlock = async (codeBlock) => { + const classNames = codeBlock.getAttribute("class"); + if (!classNames) return; + + const language = classNames + .split(" ") + .map((c) => c.split("-")) + .find(([prefix]) => prefix === "language") + ?.slice(1) + .join("-") + .toLowerCase(); + + if (!language) return; + + const code = unescapeHTMLEntities(codeBlock.innerHTML); + codeBlock.parentElement.outerHTML = await codeToHtml(code, { + lang: language, + theme: "vitesse-dark", + }); +}; + +await Promise.all(Array.from(document.querySelectorAll("pre code")).map(processCodeBlock)); diff --git a/docs/styles/animations/fade_in_up.css b/docs/styles/animations/fade_in_up.css new file mode 100644 index 0000000..703a0e7 --- /dev/null +++ b/docs/styles/animations/fade_in_up.css @@ -0,0 +1,14 @@ +@keyframes fadeInUp { + from { + transform: translateY(50px); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +.fade-in-up { + animation: 0.5s both fadeInUp; +} diff --git a/docs/styles/base/body.css b/docs/styles/base/body.css new file mode 100644 index 0000000..4c3476a --- /dev/null +++ b/docs/styles/base/body.css @@ -0,0 +1,31 @@ +html { + scroll-padding-top: 5rem; +} + +body { + min-height: 100lvh; + margin: 0 3rem 3rem; + color: light-dark(var(--black), var(--bright_white)); + background: light-dark(var(--bright_white), var(--black)); +} + +main { + margin-top: 5rem; + display: flex; + gap: 3rem; + + div { + width: 100%; + } +} + +@media (max-width: 700px) { + body { + margin: 0 0.8rem 0.8rem; + } + + main { + flex-direction: column; + gap: 0; + } +} diff --git a/docs/styles/base/reset.css b/docs/styles/base/reset.css new file mode 100644 index 0000000..67d6fcf --- /dev/null +++ b/docs/styles/base/reset.css @@ -0,0 +1,53 @@ +/* Use a more-intuitive box-sizing model. */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +/* Remove default margin */ +* { + padding: 0; + margin: 0; +} + +/* Allow percentage-based heights in the application */ +html, +body { + height: 100%; +} + +/* Typographic tweaks */ +body { + line-height: calc(1rem + 0.5rem); + -webkit-font-smoothing: antialiased; +} + +/* Improve media defaults */ +img, +picture, +video, +canvas, +svg { + display: block; + max-width: 100%; +} + +/* Remove built-in form typography styles */ +input, +button, +textarea, +select { + font: inherit; +} + +/* Avoid text overflows */ +p, +h1, +h2, +h3, +h4, +h5, +h6 { + overflow-wrap: break-word; +} diff --git a/docs/styles/components/article_header.css b/docs/styles/components/article_header.css new file mode 100644 index 0000000..99b127b --- /dev/null +++ b/docs/styles/components/article_header.css @@ -0,0 +1,49 @@ +article { + margin-left: 3rem; + width: 100%; + + header { + position: relative; + text-transform: uppercase; + font-size: 1.2rem; + margin: 10rem 0 3rem 0; + + ul { + all: unset; + } + + li { + display: inline; + } + + li:not(:last-child)::after { + content: " |"; + } + } + + header::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: calc(100% + 6rem); + height: calc(100% + 20rem); + margin: -15rem 0 -3rem -3rem; + background: linear-gradient(var(--header_background), transparent); + z-index: -1; + } +} + +@media (max-width: 700px) { + article { + margin-left: 0; + + header::after { + width: calc(100% + 3rem + 0.8rem); + } + } + + span#section { + display: none; + } +} diff --git a/docs/styles/components/figures.css b/docs/styles/components/figures.css new file mode 100644 index 0000000..e54958b --- /dev/null +++ b/docs/styles/components/figures.css @@ -0,0 +1,75 @@ +figure { + width: 500px; +} + +figcaption { + font-size: 0.8rem; + font-style: italic; + text-align: center; + color: light-dark(var(--grey), var(--white)); +} + +.left { + float: left; + margin: 0.8rem 0.8rem 0.8rem 0; +} + +.right { + float: right; + margin: 0.8rem 0 0.8rem 0.8rem; +} + +/* Fullscreen modal styling */ +#fullscreenModal { + display: none; /* Set to block with JS */ + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + padding: 3rem; + overflow: auto; + background-color: light-dark(rgba(255, 234, 208, 0.9), rgba(0, 0, 0, 0.9)); + z-index: 1001; + + #fullscreenImg { + width: 100%; + max-width: 70rem; + margin: auto; + } + + #caption, + #altText { + text-align: center; + margin: 0.8rem; + } + + #altText::before { + content: "Alt: "; + } + + #close { + position: absolute; + top: 0; + right: 0; + margin: 0.8rem; + cursor: pointer; + font-size: 2rem; + } +} + +@media (max-width: 700px) { + #fullscreenModal { + padding: 3rem 0.8rem; + } +} + +@media (max-width: 900px) { + figure, + .right, + .left { + width: 100%; + margin: 0.8rem 0; + float: none; + } +} diff --git a/docs/styles/components/footer.css b/docs/styles/components/footer.css new file mode 100644 index 0000000..be88e0f --- /dev/null +++ b/docs/styles/components/footer.css @@ -0,0 +1,33 @@ +footer { + display: flex; + flex-direction: column; + gap: 0.3rem; + text-align: center; + margin: 2rem 0 1rem; + + #socials { + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; + gap: 0.5rem; + + img { + height: 1.5rem; + width: auto; + } + } + + a:link, + a:visited, + a:hover, + a:active { + color: light-dark(var(--orange), var(--yellow)); + text-decoration: none; + padding: 0 0.5rem; + } + + a:not([href*="vale.rocks"]):not([href^="#"]):not([href^="/"]):not([href*="mailto"]):not([href*="tel"]):not([name]):after { + content: none; + } +} diff --git a/docs/styles/components/input.css b/docs/styles/components/input.css new file mode 100644 index 0000000..1aeb0db --- /dev/null +++ b/docs/styles/components/input.css @@ -0,0 +1,38 @@ +button, +select, +input, +textarea { + color: light-dark(var(--black), var(--bright_white)); + padding: 0.2rem 0.5rem; + background: var(--grey); + border: none; + border-radius: 0; + text-overflow: ellipsis; + -webkit-appearance: none; +} + +button:focus, +select:focus, +input:focus, +textarea:focus { + color: inherit; +} + +button, +select { + cursor: pointer; +} + +textarea { + display: block; +} + +::placeholder { + opacity: 1; + color: light-dark(var(--bright_grey), var(--white)); +} + +:focus { + outline: var(--blue) solid 2px; + color: var(--blue); +} diff --git a/docs/styles/components/navbar.css b/docs/styles/components/navbar.css new file mode 100644 index 0000000..e7ec996 --- /dev/null +++ b/docs/styles/components/navbar.css @@ -0,0 +1,65 @@ +header#navbar { + height: 5rem; + width: 100%; + padding: 0 3rem; + display: flex; + align-items: center; + justify-content: flex-end; + gap: 1rem; + position: fixed; + top: 0; + left: 0; + text-transform: uppercase; + font-size: 1.3rem; + pointer-events: none; + z-index: 1000; + + #navlanding { + font-weight: 700; + } + + a { + color: inherit; + text-decoration: none; + cursor: pointer; + pointer-events: all; + } + + nav ul { + display: flex; + flex-wrap: wrap; + gap: 1rem; + list-style: none; + } + + .skip-link { + position: absolute; + top: 0; + left: 0; + padding: 0.5rem; + margin: 1rem; + background: light-dark(var(--white), var(--grey)); + transform: translateY(calc(-100% - 1rem)); + } + + .skip-link:focus { + transform: translateY(0%); + } +} + +header#navbar::after { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(light-dark(var(--bright_white), var(--black)), transparent); + z-index: -1; +} + +@media (max-width: 700px) { + header#navbar { + padding: 0 0.8rem; + } +} diff --git a/docs/styles/components/section.css b/docs/styles/components/section.css new file mode 100644 index 0000000..28c67f5 --- /dev/null +++ b/docs/styles/components/section.css @@ -0,0 +1,23 @@ +#section { + font-size: 5rem; + line-height: 4rem; + font-weight: 900; + text-transform: uppercase; + text-align: right; + transform: scale(-1); + writing-mode: vertical-lr; + user-select: none; + -webkit-user-select: none; +} + +@media (max-width: 700px) { + #section { + font-size: 3rem; + line-height: 3.2rem; + text-align: inherit; + transform: inherit; + writing-mode: inherit; + user-select: inherit; + -webkit-user-select: inherit; + } +} diff --git a/docs/styles/components/seperator.css b/docs/styles/components/seperator.css new file mode 100644 index 0000000..74616df --- /dev/null +++ b/docs/styles/components/seperator.css @@ -0,0 +1,7 @@ +hr { + all: unset; + display: block; + height: 2px; + background: var(--bright_grey); + margin-bottom: 1rem; +} diff --git a/docs/styles/pages/index.css b/docs/styles/pages/index.css new file mode 100644 index 0000000..66ac43e --- /dev/null +++ b/docs/styles/pages/index.css @@ -0,0 +1,50 @@ +main { + display: block; +} + +#landingbody, +footer { + position: relative; + z-index: 1; +} + +#hero { + height: 85dvh; + font-size: 2.5rem; + line-height: 2.5rem; + display: flex; + align-items: center; +} + +#herotext { + pointer-events: none; + z-index: 1000; + + h1 { + font-size: 6rem; + line-height: 6rem; + } +} + +#puddle-container { + color: var(--bright_grey); + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100vh; + pointer-events: auto; + cursor: default; + user-select: none; +} + +#puddle-container::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 50%; + background: linear-gradient(transparent, light-dark(var(--bright_white), var(--black))); + pointer-events: none; +} diff --git a/docs/styles/pages/portfolio.css b/docs/styles/pages/portfolio.css new file mode 100644 index 0000000..fcefc27 --- /dev/null +++ b/docs/styles/pages/portfolio.css @@ -0,0 +1,47 @@ +.portfolio-items { + display: flex; + flex-wrap: wrap; + gap: 2%; + justify-content: space-between; + + .item { + border-bottom: 2px solid var(--bright_grey); + flex-basis: 48%; + padding: 0.5rem 0; + } + + .item:focus { + border-color: var(--blue); + } + + h3 { + margin: 0; + } + + p { + text-transform: uppercase; + margin: 0; + } + + a { + text-decoration: none; + } + + a:hover { + color: var(--blue); + } +} + +.portfolio-items:not(:last-child) { + margin-bottom: 10rem; +} + +@media (max-width: 1000px) { + .portfolio-items { + flex-direction: column; + + .item { + width: 100%; + } + } +} diff --git a/docs/styles/pages/posts.css b/docs/styles/pages/posts.css new file mode 100644 index 0000000..2c6c79f --- /dev/null +++ b/docs/styles/pages/posts.css @@ -0,0 +1,82 @@ +#post-items { + display: flex; + flex-wrap: wrap; + gap: 2%; + justify-content: space-between; + + .post { + border-bottom: 2px solid var(--bright_grey); + flex-basis: 48%; + height: 8.5rem; + padding: 0.5rem 0; + overflow: hidden; + text-overflow: ellipsis; + } + + .post:focus { + border-color: var(--blue); + } + + h2 { + margin: 0; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + word-wrap: break-word; + text-overflow: ellipsis; + overflow: hidden; + } + + p { + text-transform: uppercase; + margin: 0; + } + + a { + text-decoration: none; + } + + a:hover { + color: var(--blue); + } +} + +#refine-posts { + /* Set to display: flex with postsrefine.js */ + display: none; + gap: 2%; + margin: 1rem 0; + + input, + select { + background: none; + border: none; + border-bottom: 2px solid var(--bright_grey); + padding: 0.2rem 0; + } + + input { + flex: 1; + min-width: none; + } + + input:focus, + select:focus { + border-color: var(--blue); + } +} + +@media (max-width: 1000px) { + #post-items { + flex-direction: column; + + .post { + width: 100%; + height: auto; + + h2 { + display: initial; + } + } + } +} diff --git a/docs/styles/pages/services.css b/docs/styles/pages/services.css new file mode 100644 index 0000000..4a441a5 --- /dev/null +++ b/docs/styles/pages/services.css @@ -0,0 +1,19 @@ +#services { + section { + border-bottom: 2px solid var(--bright_grey); + padding: 0.5rem 0; + } + + h2 { + margin: 0; + } + + p { + text-transform: uppercase; + margin: 0; + } + + p#description { + text-transform: none; + } +} diff --git a/docs/styles/pages/support.css b/docs/styles/pages/support.css new file mode 100644 index 0000000..510c916 --- /dev/null +++ b/docs/styles/pages/support.css @@ -0,0 +1,7 @@ +iframe { + border: none; + filter: invert(93.5%) hue-rotate(180deg); + width: 100%; + min-height: 50rem; + height: 30vh; +} diff --git a/docs/styles/style.css b/docs/styles/style.css new file mode 100755 index 0000000..fd89e78 --- /dev/null +++ b/docs/styles/style.css @@ -0,0 +1,26 @@ +/* Importing variable styles */ +@import url("/styles/variables/colours.css"); + +/* Importing base styles */ +@import url("/styles/base/reset.css"); +@import url("/styles/base/body.css"); + +/* Importing type styles */ +@import url("/styles/type/alerts.css"); +@import url("/styles/type/code.css"); +@import url("/styles/type/headers.css"); +@import url("/styles/type/links.css"); +@import url("/styles/type/type.css"); +@import url("/styles/type/faces.css"); + +/* Importing component styles */ +@import url("/styles/components/footer.css"); +@import url("/styles/components/figures.css"); +@import url("/styles/components/input.css"); +@import url("/styles/components/navbar.css"); +@import url("/styles/components/article_header.css"); +@import url("/styles/components/section.css"); +@import url("/styles/components/seperator.css"); + +/* Importing animation styles */ +@import url("/styles/animations/fade_in_up.css"); diff --git a/docs/styles/type/alerts.css b/docs/styles/type/alerts.css new file mode 100644 index 0000000..41c7b8b --- /dev/null +++ b/docs/styles/type/alerts.css @@ -0,0 +1,44 @@ +.markdown-alert-note { + border-color: var(--blue); +} + +.markdown-alert-note::before { + content: "Note:"; + color: var(--blue); +} + +.markdown-alert-tip { + border-color: var(--green); +} + +.markdown-alert-tip::before { + content: "Tip:"; + color: var(--green); +} + +.markdown-alert-important { + border-color: var(--magenta); +} + +.markdown-alert-important::before { + content: "Important:"; + color: var(--magenta); +} + +.markdown-alert-warning { + border-color: var(--yellow); +} + +.markdown-alert-warning::before { + content: "Warning:"; + color: var(--yellow); +} + +.markdown-alert-caution { + border-color: var(--red); +} + +.markdown-alert-caution::before { + content: "Caution:"; + color: var(--red); +} diff --git a/docs/styles/type/code.css b/docs/styles/type/code.css new file mode 100644 index 0000000..df5046c --- /dev/null +++ b/docs/styles/type/code.css @@ -0,0 +1,24 @@ +pre { + overflow-x: scroll; + white-space: pre; + display: inline-block; + padding: 0.8rem; + width: 100%; + background: light-dark(var(--bright_grey), var(--grey)); +} + +code { + font-family: "Fira Code", monospace; + background: light-dark(var(--bright_grey), var(--grey)); + color: var(--bright_white); + padding: 0 0.2rem; + overflow: inherit; + tab-size: 4; + word-spacing: normal; + word-break: normal; + word-wrap: normal; +} + +pre code { + background: none; +} diff --git a/docs/styles/type/faces.css b/docs/styles/type/faces.css new file mode 100644 index 0000000..eda7208 --- /dev/null +++ b/docs/styles/type/faces.css @@ -0,0 +1,19 @@ +@font-face { + font-family: "Lexend"; + src: url("/assets/typefaces/Lexend/Lexend-VariableFont_wght.ttf"); +} + +@font-face { + font-family: "Fira Code"; + src: url("/assets/typefaces/Fira_Code/FiraCode-VariableFont_wght.ttf"); +} + +@font-face { + font-family: "Material Symbols"; + src: url("/assets/typefaces/Material_Symbols_Sharp/MaterialSymbolsSharp-Regular.ttf"); +} + +.material-icons { + font-family: "Material Symbols"; + user-select: none; +} diff --git a/docs/styles/type/headers.css b/docs/styles/type/headers.css new file mode 100644 index 0000000..1373cb8 --- /dev/null +++ b/docs/styles/type/headers.css @@ -0,0 +1,41 @@ +h1, +h2, +h3, +h4, +h5, +h6 { + text-transform: uppercase; + text-wrap: balance; + font-weight: 700; + margin-bottom: 0.4rem; +} + +h1 { + font-size: 3rem; + line-height: 3.2rem; +} + +h2 { + font-size: 2rem; + line-height: 2.2rem; +} + +h3 { + font-size: 1.8rem; + line-height: 2rem; +} + +h4 { + font-size: 1rem; + line-height: 1.2rem; +} + +h5 { + font-size: 0.8rem; + line-height: 1rem; +} + +h6 { + font-size: 0.6rem; + line-height: 0.8rem; +} diff --git a/docs/styles/type/links.css b/docs/styles/type/links.css new file mode 100644 index 0000000..1b33a75 --- /dev/null +++ b/docs/styles/type/links.css @@ -0,0 +1,35 @@ +a { + color: inherit; + text-decoration: underline 0.1rem light-dark(var(--cyan), var(--blue)); + -webkit-text-decoration-line: underline; + -webkit-text-decoration-color: light-dark(var(--cyan), var(--blue)); + text-decoration-skip-ink: none; + text-underline-offset: 0.2rem; +} + +a:hover, +a:active { + text-decoration-thickness: 0.7rem; + text-underline-offset: -0.4rem; +} + +a:after { + font-family: "Material Symbols"; + margin-left: 0.1rem; + font-size: 0.5rem; + display: inline-block; + vertical-align: super; + line-height: 0; +} + +a[href^="#"]:after { + content: "arrow_downward"; +} + +a[href^="mailto"]:after { + content: "mail"; +} + +a[href]:not([href*="vale.rocks"]):not([href^="/"]):not([href^="#"]):not([href^="mailto"]):after { + content: "open_in_new"; +} diff --git a/docs/styles/type/type.css b/docs/styles/type/type.css new file mode 100644 index 0000000..b5b2f09 --- /dev/null +++ b/docs/styles/type/type.css @@ -0,0 +1,37 @@ +body { + font-family: "Lexend", sans-serif; +} + +p { + margin-bottom: 0.8rem; +} + +.readable_width { + max-width: 80ch; +} + +blockquote { + padding: 0.5rem 1rem; + background: light-dark(var(--white), var(--dark_grey)); + border-left: 0.2rem solid var(--grey); + margin-bottom: 0.8rem; + + p { + margin: 0; + } +} + +li { + list-style-position: inside; + line-height: 1.4rem; + margin-bottom: 0.3rem; +} + +abbr, +kbd { + font-variant-caps: all-small-caps; +} + +::selection { + background: var(--blue); +} diff --git a/docs/styles/variables/colours.css b/docs/styles/variables/colours.css new file mode 100644 index 0000000..94192b7 --- /dev/null +++ b/docs/styles/variables/colours.css @@ -0,0 +1,33 @@ +:root { + color-scheme: light dark; + + --black: #131111; + + --dark_grey: #221e1e; + --grey: #3e3c3c; + --bright_grey: #5e5a5a; + + --white: #c1ada0; + --bright_white: #ffead0; + + --red: #f13731; + --bright_red: #f45f5a; + + --orange: #f56e00; + --bright_orange: #ec934e; + + --yellow: #ffad1f; + --bright_yellow: #fed06e; + + --green: #58a551; + --bright_green: #74b270; + + --blue: #3578c1; + --bright_blue: #6d9bcd; + + --cyan: #2bb7bc; + --bright_cyan: #3fcfd4; + + --magenta: #e23672; + --bright_magenta: #e6467b; +}