17 Commits

Author SHA1 Message Date
4bffe03f04 Bump version 2025-05-09 10:48:51 -07:00
98ec534d2e Light theme nav modal bugfix 2025-05-09 10:47:37 -07:00
b502fd518a The theme option will now be saved to a cookie 2025-05-09 10:28:19 -07:00
df55dcbd07 BG: Particle speed changes 2025-05-08 11:26:44 -07:00
9194ad98db "Correct" element grabbing 2025-05-08 10:54:12 -07:00
71e7662408 Add a manual light/dark mode switcher
Despite the simplicity of the commit title, this was a pretty big
change. The styling used to just go off of the system's color scheme,
but that can't be overridden. Instead, I have made a variable that
determines whether dark theme is active and made a small panel with some
buttons to change the theme. I had to change a lot of code to achieve
this and lost a lot of hair (I metaphorically pulled it out) from
writing this code.

I also changed things from legacy mode to rune mode (Svelte 4 to 5)
while I was at it, that wasn't too big.
2025-05-07 21:17:52 -07:00
e6dd87427b Homepage: Scroll arrow event listener bugfix
When you navigate away from the homepage then navigate back, it will
assign a new event listener to scroll without removing the old one.

This change makes it so the event listener is removed when you navigate
away, and is put back normally when you navigate back.
2025-05-05 16:59:17 -07:00
3c1aa4fbe4 Use OffscreenCanvas instead of HTMLCanvasElement for buffers 2025-04-02 20:35:09 -07:00
9b58e864a2 Proper image loading 2025-04-02 20:30:57 -07:00
4b99a1c26e Fix oversights in bg renderer
So it turns out the performance improvements were completelty neglected
because my code is bad. The buffer wasn't getting rendered to the canvas
in the best way, and there were too many particles for smaller screens.
I have now added a simple math equasion to the init function that
decides how many particles are needed based on the display size. It
still needs a lot of tweaking, and a more complex function is probably
necessary for good results. I'm still trying to get this background to
not suck and actually look good.
2025-04-02 19:49:37 -07:00
2ce9e6955f Bump version to v1.2.1 2025-04-02 18:56:24 -07:00
a3a774b964 Change particle color and ammount 2025-03-31 20:08:30 -07:00
ac3d579254 Massive performance improvement in background rendering
Turns out the performance bottleneck was actually rendering the
gradients every frame, so they are now rendering to a buffer when the
page loads once and that buffer is being rendered as an image every
frame. No functionality has been changed; but it runs so much faster and
is much more efficient on mobile devices.
2025-03-31 19:59:16 -07:00
db0ed8cb42 Add page metadata for each page and enable prettier-plugin-svelte 2025-03-31 16:36:32 -07:00
2c21504279 Add prettier-plugin-svelte 2025-03-31 16:11:31 -07:00
4b05bb519e Bump package.json version to the right number 2025-03-31 15:54:51 -07:00
e7aba82655 Welcome, Prettier. 2025-03-31 15:51:57 -07:00
20 changed files with 688 additions and 227 deletions

49
.prettierrc.json Normal file
View File

@ -0,0 +1,49 @@
{
"plugins": ["prettier-plugin-svelte"],
"singleQuote": false,
"tabWidth": 2,
"useTabs": false,
"htmlWhitespaceSensitivity": "ignore",
"overrides": [
{
"files": "*.html",
"options": {
"tabWidth": 2,
"useTabs": false
}
},
{
"files": "*.scss",
"options": {
"tabWidth": 2,
"useTabs": false
}
},
{
"files": "*.json",
"options": {
"tabWidth": 2,
"useTabs": false
}
},
{
"files": "*.svelte",
"options": {
"tabWidth": 2,
"useTabs": false
}
},
{
"files": "*.js",
"options": {
"useTabs": true
}
},
{
"files": "*.ts",
"options": {
"useTabs": true
}
}
]
}

View File

@ -1,4 +1,5 @@
# Colormatic Website
[colormatic.org](https://colormatic.org)
This project uses SvelteKit along with TypeScript and Sass. It's configured for static site generation (SSG) and prerendering at compile time to create a simple static website for Nginx to serve, with very little client-side rendering (CSR).
@ -6,22 +7,37 @@ This project uses SvelteKit along with TypeScript and Sass. It's configured for
The Colormatic website is developed with accordance to modern web standards, however a legacy website will be available in the future for older/less capable browsers.
To download the project, run:
```
git clone git@git.colormatic.org:ColormaticStudios/Colormatic-Website.git
cd Colormatic-Website
npm install
```
You can run the project locally with:
```
npm run dev
```
Or you can build the project for release with:
```
npm run build
```
After the project has been built, you can preview the release build with:
```
npm run preview
```
Bootstrap Icons are licensed under the [MIT](https://opensource.org/license/MIT) license.
Before submitting a push or pull request, run:
```
npm run format
```
To format your code according to the project code format. Remember to never fight the formatter.
[Bootstrap Icons](https://icons.getbootstrap.com/) are licensed under the [MIT](https://opensource.org/license/MIT) license.

View File

@ -1,26 +1,29 @@
{
"name": "colormatic-website",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"sass": "^1.83.4",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^6.0.0"
},
"dependencies": {
"bootstrap-icons": "^1.11.3"
}
"name": "colormatic-website",
"version": "1.3.1",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"format": "prettier -w .",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"devDependencies": {
"@sveltejs/adapter-static": "^3.0.8",
"@sveltejs/kit": "^2.16.0",
"@sveltejs/vite-plugin-svelte": "^5.0.0",
"prettier": "^3.5.3",
"prettier-plugin-svelte": "^3.3.3",
"sass": "^1.83.4",
"svelte": "^5.0.0",
"svelte-check": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^6.0.0"
},
"dependencies": {
"bootstrap-icons": "^1.11.3"
}
}

BIN
release.tar.gz Normal file

Binary file not shown.

View File

@ -2,57 +2,60 @@
import { canvasDpiScaler } from "../script/canvas_dpi_scaler.ts";
import { onMount } from "svelte";
let canvas: HTMLCanvasElement;
let canvas = $state() as HTMLCanvasElement;
let ctx: CanvasRenderingContext2D;
let dark_theme = false;
let time_scale = 1;
let { darkTheme = $bindable() } = $props();
let timeScale = 1;
let particlesArray: Array<Particle> = [];
let gradientsArray: Array<Gradient> = [];
$effect(() => {
darkTheme;
let particleImages = {
// This is horrible code
circle: {} as HTMLImageElement,
square: {} as HTMLImageElement,
triangle: {} as HTMLImageElement,
star: {} as HTMLImageElement,
wavyCircle: {} as HTMLImageElement,
};
for (let i = 0; i < gradients.length; i++) {
let gradient = gradients[i];
gradient.color = getRandomColor();
gradient.prepareBuffer();
}
});
onMount(() => {
canvas = document.getElementById("bg-canvas") as HTMLCanvasElement;
let particleImages: { [key: string]: HTMLImageElement } = {};
async function loadImg(src: string): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
let img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = src;
});
}
let particles: Particle[] = [];
let gradients: Gradient[] = [];
onMount(async () => {
ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
particleImages.circle = new Image() as HTMLImageElement;
particleImages.circle.src = "/img/bg-shapes/circle.svg";
let imagePromises = {
circle: loadImg("/img/bg-shapes/circle.svg"),
square: loadImg("/img/bg-shapes/square.svg"),
triangle: loadImg("/img/bg-shapes/triangle.svg"),
star: loadImg("/img/bg-shapes/star.svg"),
wavyCircle: loadImg("/img/bg-shapes/wavy-circle.svg"),
};
particleImages.square = new Image() as HTMLImageElement;
particleImages.square.src = "/img/bg-shapes/square.svg";
particleImages.triangle = new Image() as HTMLImageElement;
particleImages.triangle.src = "/img/bg-shapes/triangle.svg";
particleImages.star = new Image() as HTMLImageElement;
particleImages.star.src = "/img/bg-shapes/star.svg";
particleImages.wavyCircle = new Image() as HTMLImageElement;
particleImages.wavyCircle.src = "/img/bg-shapes/wavy-circle.svg";
if (
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches
) {
dark_theme = true;
}
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (event) => {
dark_theme = event.matches;
gradientsArray.forEach((gradient: Gradient) => {
gradient.color = getRandomColor();
});
particleImages = await Promise.all(Object.values(imagePromises))
.then((imgs) => {
return {
circle: imgs[0],
square: imgs[1],
triangle: imgs[2],
star: imgs[3],
wavyCircle: imgs[4],
};
})
.catch((error) => {
console.error("Error loading images:", error);
return {};
});
resize();
@ -81,14 +84,14 @@
constructor() {
this.x = Math.random() * window.innerWidth;
this.y = Math.random() * window.innerHeight;
this.speedX = (Math.random() - 0.5) * 0.2; // -0.1 to 0.1
this.speedY = (Math.random() - 0.5) * 0.2;
this.speedX = Math.random() - 0.5; // -0.5 to 0.5
this.speedY = Math.random() - 0.5;
this.growthSpeed = Math.random() * 0.02 + 0.01; // 0.01 to 0.03
}
update() {
this.x += this.speedX * time_scale;
this.y += this.speedY * time_scale;
this.x += this.speedX * timeScale;
this.y += this.speedY * timeScale;
// Reverse direction if particle hits edge
if (this.x <= 0 || this.x >= window.innerWidth) {
@ -164,17 +167,17 @@
Math.floor(Math.random() * 5)
](); // A very strange but effective way to pick a random shape
this.angle = Math.random() * 360;
this.rotationSpeed = Math.random() * 2 - 1; // -1 to 1
this.rotationSpeed = (Math.random() - 0.5) * 4; // -2 to 2
this.originalSize = Math.random() * 8 + 8; // 8 to 16
this.size = this.originalSize;
}
update() {
super.update();
this.angle += this.rotationSpeed * time_scale;
this.angle += this.rotationSpeed * timeScale;
// Breathing effect: oscillate size
this.size += this.growthSpeed * time_scale;
this.size += this.growthSpeed * timeScale;
if (
this.size >= this.originalSize * 1.25 ||
this.size <= this.originalSize * 0.75
@ -187,7 +190,7 @@
ctx.save();
// The source images are black, so we are inverting them
// different amounts to get different shades of gray
ctx.filter = dark_theme ? "invert(0.25)" : "invert(0.75)";
ctx.filter = darkTheme ? "invert(0.15)" : "invert(0.8)";
// Draw center of rotation
// ctx.beginPath();
@ -202,7 +205,7 @@
}
function getRandomColor() {
if (dark_theme) {
if (darkTheme) {
let r = Math.floor(Math.random() * 255 - 100);
let b = Math.floor(Math.random() * 255 - 100);
let g = Math.floor(Math.random() * 255 - 100);
@ -219,60 +222,100 @@
radius: number;
color: string;
alpha: number;
renderingBuffer: OffscreenCanvas;
constructor() {
super();
this.radius = Math.random() * 500 + 300;
this.color = getRandomColor();
this.alpha = Math.random() * 0.5 + 0.5; // Initial alpha between 0.5 and 1
this.renderingBuffer = new OffscreenCanvas(
this.radius * 2,
this.radius * 2,
);
// One-shot buffer adjustment
this.renderingBuffer.width = this.radius * 2;
this.renderingBuffer.height = this.radius * 2;
// canvasDpiScaler(
// this.renderingBuffer,
// this.renderingBuffer.getContext("2d") as CanvasRenderingContext2D,
// );
this.prepareBuffer();
}
draw() {
const gradient = ctx.createRadialGradient(
this.x,
this.y,
prepareBuffer() {
let bctx = this.renderingBuffer.getContext(
"2d",
) as OffscreenCanvasRenderingContext2D;
bctx.clearRect(
0,
this.x,
this.y,
0,
this.renderingBuffer.width,
this.renderingBuffer.height,
);
const gradient = ctx.createRadialGradient(
this.radius,
this.radius,
0,
this.radius,
this.radius,
this.radius,
);
gradient.addColorStop(0, this.color);
if (dark_theme) {
if (darkTheme) {
gradient.addColorStop(1, `rgba(0, 0, 0, 0)`);
} else {
gradient.addColorStop(1, `rgba(255, 255, 255, 0)`);
}
ctx.globalAlpha = this.alpha;
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.globalAlpha = 1.0;
bctx.globalAlpha = this.alpha;
bctx.fillStyle = gradient;
bctx.beginPath();
bctx.arc(this.radius, this.radius, this.radius, 0, Math.PI * 2);
bctx.closePath();
bctx.fill();
}
draw() {
ctx.save();
ctx.translate(this.x - this.radius, this.y - this.radius);
ctx.drawImage(this.renderingBuffer, 0, 0);
ctx.restore();
}
}
function init() {
particlesArray = [];
for (let i = 0; i < 20; i++) {
particlesArray.push(new Particle());
/*/
* Calculate the proper amount of particles
* 25920 is our constant, equal to x in (1080*1920)/x = 80
* Because the subjectively correct amount of particles for a 1080p
* display is 80, so to calculate the proper amount for any window size,
* just do (width * height) / 25920
/*/
let particleCount = (window.innerWidth * window.innerHeight) / 25920;
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
gradientsArray = [];
gradients = [];
for (let i = 0; i < 10; i++) {
gradientsArray.push(new Gradient());
gradients.push(new Gradient());
}
}
function animate() {
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
for (let i_gradient = 0; i_gradient < gradientsArray.length; i_gradient++) {
let gradient = gradientsArray[i_gradient];
for (let i_gradient = 0; i_gradient < gradients.length; i_gradient++) {
let gradient = gradients[i_gradient];
gradient.update();
gradient.draw();
}
for (let i_particle = 0; i_particle < particlesArray.length; i_particle++) {
let particle = particlesArray[i_particle];
for (let i_particle = 0; i_particle < particles.length; i_particle++) {
let particle = particles[i_particle];
particle.update();
particle.draw();
}
@ -281,10 +324,10 @@
}
</script>
<canvas id="bg-canvas"></canvas>
<canvas bind:this={canvas} class="bg-canvas"></canvas>
<style>
canvas#bg-canvas {
canvas.bg-canvas {
position: fixed;
top: 0;
left: 0;

View File

@ -1,7 +1,8 @@
<footer>
<p>© 2025 Colormatic Studios, All Rights Reserved.</p>
<p>
<a href="mailto:support@colormatic.org">support@colormatic.org</a> |
<a href="mailto:support@colormatic.org">support@colormatic.org</a>
|
<a href="mailto:support@colormatic.org">contact@colormatic.org</a>
</p>
</footer>

View File

@ -1,14 +1,18 @@
<script lang="ts">
import { getContext } from "svelte";
let pages = $state() as HTMLElement;
function toggleModalMenu() {
var pages = document.getElementById("pages") as HTMLElement;
pages.classList.toggle("hidden");
}
function modalMenuProcessClick(e: MouseEvent) {
var pages = document.getElementById("pages") as HTMLElement;
if (e.target == pages) {
pages.classList.toggle("hidden");
}
}
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<nav>
@ -29,11 +33,13 @@
class="git-icon"
target="_blank"
rel="noopener noreferrer"
aria-label="Colormatic Git"><i class="bi bi-git"></i></a
>
<button on:click={toggleModalMenu} class="menu-button" aria-label="menu"
><i class="bi bi-list"></i></button
aria-label="Colormatic Git"
>
<i class="bi bi-git"></i>
</a>
<button onclick={toggleModalMenu} class="menu-button" aria-label="menu">
<i class="bi bi-list"></i>
</button>
</div>
</nav>
@ -43,12 +49,12 @@ the only way to achieve a proper modal. They even do this in the
Svelte modal example, https://svelte.dev/playground/modal
-->
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
<span on:click={modalMenuProcessClick} id="pages" class="modalbg hidden">
<div>
<button on:click={toggleModalMenu} class="close" aria-label="Close"
><i class="bi bi-x"></i></button
>
<!-- svelte-ignore a11y_click_events_have_key_events, a11y_no_static_element_interactions -->
<span onclick={modalMenuProcessClick} bind:this={pages} class="modalbg hidden">
<div class={darkTheme() ? "dark-theme" : ""}>
<button onclick={toggleModalMenu} class="close" aria-label="Close">
<i class="bi bi-x"></i>
</button>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/zakarya">Zakarya</a></li>
@ -57,15 +63,19 @@ Svelte modal example, https://svelte.dev/playground/modal
<a
href="https://git.colormatic.org"
target="_blank"
rel="noopener noreferrer">Colormatic Git</a
rel="noopener noreferrer"
>
Colormatic Git
</a>
</li>
<li>
<a
href="https://auth.colormatic.org"
target="_blank"
rel="noopener noreferrer">Colormatic ID</a
rel="noopener noreferrer"
>
Colormatic ID
</a>
</li>
<li><a href="/about">About</a></li>
</ul>
@ -255,9 +265,7 @@ Svelte modal example, https://svelte.dev/playground/modal
}
}
@media (prefers-color-scheme: dark) {
span.modalbg div {
background-color: #000000bb;
}
span.modalbg div.dark-theme {
background-color: #000000bb;
}
</style>

View File

@ -0,0 +1,105 @@
<script lang="ts">
import { getContext, onMount } from "svelte";
import { themes } from "../script/theme.ts";
import { setCookie, getCookie } from "../script/cookie.ts";
let lightButtonIcon = $state() as HTMLElement;
let darkButtonIcon = $state() as HTMLElement;
let autoButtonIcon = $state() as HTMLElement;
// In case easy access to the buttons themselves is useful, they are provided here.
let lightButton = $state() as HTMLButtonElement;
let darkButton = $state() as HTMLButtonElement;
let autoButton = $state() as HTMLButtonElement;
let { themeOption = $bindable() } = $props();
function setThemeOption(newThemeOption: string) {
setCookie("theme", newThemeOption);
switch (newThemeOption) {
case themes.LIGHT:
themeOption = themes.LIGHT;
lightButtonIcon.classList.replace("bi-sun", "bi-sun-fill");
darkButtonIcon.classList.replace("bi-moon-fill", "bi-moon");
autoButtonIcon.classList.replace("bi-display-fill", "bi-display");
break;
case themes.DARK:
themeOption = themes.DARK;
lightButtonIcon.classList.replace("bi-sun-fill", "bi-sun");
darkButtonIcon.classList.replace("bi-moon", "bi-moon-fill");
autoButtonIcon.classList.replace("bi-display-fill", "bi-display");
break;
case themes.AUTO:
themeOption = themes.AUTO;
lightButtonIcon.classList.replace("bi-sun-fill", "bi-sun");
darkButtonIcon.classList.replace("bi-moon-fill", "bi-moon");
autoButtonIcon.classList.replace("bi-display", "bi-display-fill");
break;
default:
console.error("setThemeOption was passed a value that is not a theme");
}
}
onMount(() => {
let themeCookie = getCookie("theme");
if (Object.values(themes).includes(themeCookie)) {
setThemeOption(themeCookie);
}
});
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<div class="panel settings {darkTheme() ? 'dark-theme' : ''}">
<button
aria-label="Dark Theme"
bind:this={darkButton}
onclick={() => {
setThemeOption(themes.DARK);
}}
>
<i bind:this={darkButtonIcon} class="bi bi-moon"></i>
</button>
<button
aria-label="Light Theme"
bind:this={lightButton}
onclick={() => {
setThemeOption(themes.LIGHT);
}}
>
<i bind:this={lightButtonIcon} class="bi bi-sun"></i>
</button>
<button
aria-label="Auto Theme"
bind:this={autoButton}
onclick={() => {
setThemeOption(themes.AUTO);
}}
>
<i bind:this={autoButtonIcon} class="bi bi-display-fill"></i>
</button>
</div>
<style lang="scss">
@use "../style/global.scss";
div.panel.settings {
padding: 6px 8px;
position: fixed;
bottom: 20px;
right: 20px;
}
div.panel.settings button {
color: global.$text-color;
font-size: 110%;
cursor: pointer;
// Reset buton style
border: none;
background-color: #00000000;
border-radius: 0;
}
</style>

View File

@ -4,16 +4,79 @@
import Navbar from "../component/navbar.svelte";
import Footer from "../component/footer.svelte";
import Bg from "../component/bg.svelte";
import Settings from "../component/settings.svelte";
import { themes } from "../script/theme.ts";
import { onMount, setContext } from "svelte";
interface Props {
children?: import("svelte").Snippet;
}
let { children }: Props = $props();
let themeOption = $state(themes.AUTO);
let darkTheme = $state(false);
/*/
* This is necesarry for pages to read the theme,
* sucks that we have to use an anonymous function
* just to grab a variable
/*/
let darkThemeCallable = () => darkTheme;
setContext("darkTheme", darkThemeCallable);
function setAutoTheme() {
// I'm so sorry about this code (good luck reading/debugging this)
darkTheme =
window.matchMedia &&
window.matchMedia("(prefers-color-scheme: dark)").matches;
document.documentElement.style.setProperty(
"--text-color",
darkTheme ? "white" : "#383c3f",
);
document.body.style.backgroundColor = darkTheme ? "black" : "white";
}
$effect(() => {
themeOption;
switch (themeOption) {
case themes.LIGHT:
darkTheme = false;
document.documentElement.style.setProperty("--text-color", "#383c3f");
document.body.style.backgroundColor = "white";
break;
case themes.DARK:
darkTheme = true;
document.documentElement.style.setProperty("--text-color", "white");
document.body.style.backgroundColor = "black";
break;
case themes.AUTO:
setAutoTheme();
}
});
onMount(() => {
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", () => {
if (themeOption === themes.AUTO) {
setAutoTheme();
}
});
});
</script>
<svelte:head>
<link rel="icon" href="/img/colormatic_logo.svg" />
</svelte:head>
<Bg />
<Bg bind:darkTheme />
<Navbar />
<slot />
{@render children?.()}
<Settings bind:themeOption />
<Footer />

View File

@ -1,3 +1,2 @@
export const prerender = true;
export const csr = true;
export const trailingSlash = "always";

View File

@ -1,39 +1,63 @@
<script lang="ts">
import { onMount } from "svelte";
import { getContext, onMount } from "svelte";
let arrow = $state() as HTMLDivElement;
function checkArrow(e: Event) {
if (window.scrollY != 0) {
if (!arrow.classList.contains("scroll-arrow-hide")) {
arrow.classList.add("scroll-arrow-hide");
}
} else {
if (arrow.classList.contains("scroll-arrow-hide")) {
arrow.classList.remove("scroll-arrow-hide");
}
}
}
onMount(() => {
let arrow = document.getElementById("scroll-arrow") as HTMLDivElement;
if (arrow) {
// Arrow is not null
window.addEventListener("scroll", (e: Event) => {
if (window.scrollY != 0) {
if (!arrow.classList.contains("scroll-arrow-hide")) {
arrow.classList.add("scroll-arrow-hide");
}
} else {
if (arrow.classList.contains("scroll-arrow-hide")) {
arrow.classList.remove("scroll-arrow-hide");
}
}
});
}
window.addEventListener("scroll", checkArrow);
return () => {
window.removeEventListener("scroll", checkArrow);
};
});
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<svelte:head>
<title>Colormatic</title>
<meta
name="description"
content="Colormatic is a non-profit project by Zakarya dedicated to creation."
/>
<meta name="keywords" content="Open Source, Non Profit" />
<link rel="canonical" href="https://colormatic.org" />
<meta property="og:title" content="Colormatic" />
<meta
property="og:description"
content="Colormatic is a non-profit project by Zakarya dedicated to creation."
/>
<meta
property="og:image"
content="https://colormatic.org/img/colormatic_logo.svg"
/>
<meta property="og:url" content="https://colormatic.org" />
<meta property="og:type" content="website" />
</svelte:head>
<main>
<main class={darkTheme() ? "dark-theme" : ""}>
<div class="brand-heading">
<h1>Colormatic: A non-profit project for creation.</h1>
</div>
<div id="scroll-arrow">
<div bind:this={arrow} class="scroll-arrow">
<i class="bi bi-arrow-down-circle-fill"></i>
</div>
<div style="margin-top:25vh"></div>
<div style="margin-top:calc(100vh - 500px);"></div>
<div class="heading">Featured Colormatic Studios Projects:</div>
<div class="hero panel">
@ -41,8 +65,10 @@
<a
href="https://git.colormatic.org/ColormaticStudios/quality-godot-first-person"
target="_blank"
rel="noopener noreferrer">Quality First Person Controller</a
rel="noopener noreferrer"
>
Quality First Person Controller
</a>
</h1>
<p>An actually good first person controller for the Godot Engine.</p>
@ -52,8 +78,10 @@
<a
href="https://git.colormatic.org/ColormaticStudios/godot-bson"
target="_blank"
rel="noopener noreferrer">BSON for Godot</a
rel="noopener noreferrer"
>
BSON for Godot
</a>
</h1>
<p>A BSON serializer/deserializer for the Godot Engine</p>
</div>
@ -69,8 +97,11 @@
Check up on progress and changes at <a
href="https://git.colormatic.org/ColormaticStudios/Colormatic-Website"
target="_blank"
rel="noopener noreferrer">ColormaticStudios/Colormatic-Website</a
>.
rel="noopener noreferrer"
>
ColormaticStudios/Colormatic-Website
</a>
.
</p>
</div>
</main>

View File

@ -1,10 +1,34 @@
<script lang="ts">
import { getContext } from "svelte";
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<svelte:head>
<title>Colormatic - About</title>
<meta
name="description"
content="Colormatic is a non-profit project by Zakarya dedicated to creation."
/>
<meta name="keywords" content="Open Source, Non Profit" />
<link rel="canonical" href="https://colormatic.org/about/" />
<meta property="og:title" content="Colormatic - About" />
<meta
property="og:description"
content="Colormatic is a non-profit project by Zakarya dedicated to creation."
/>
<meta
property="og:image"
content="https://colormatic.org/img/colormatic_logo.svg"
/>
<meta property="og:url" content="https://colormatic.org/about/" />
<meta property="og:type" content="website" />
</svelte:head>
<spacer></spacer>
<main>
<main class={darkTheme() ? "dark-theme" : ""}>
<div class="hero panel">
<h1>Colormatic: A non-profit project for creation.</h1>
<p class="justify">

View File

@ -1,8 +1,41 @@
<script lang="ts">
import { getContext } from "svelte";
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<svelte:head>
<title>Colormatic Studios</title>
<meta
name="description"
content="Colormatic Studios is a creative studio dedicated to giving life to
Colormatic's projects. We are currently just a small group of passionate
volunteers working to build inspiring, intuitive and innovative creative
works."
/>
<meta
name="keywords"
content="Open Source, Not for Profit, Game Development, World Building"
/>
<link rel="canonical" href="https://colormatic.org/studios/" />
<meta property="og:title" content="Colormatic Studios" />
<meta
property="og:description"
content="Colormatic Studios is a creative studio dedicated to giving life to
Colormatic's projects. We are currently just a small group of passionate
volunteers working to build inspiring, intuitive and innovative creative
works."
/>
<meta
property="og:image"
content="https://colormatic.org/img/colormatic_logo.svg"
/>
<meta property="og:url" content="https://colormatic.org/studios/" />
<meta property="og:type" content="website" />
</svelte:head>
<main>
<main class={darkTheme() ? "dark-theme" : ""}>
<div class="cs-title"><h1>Colormatic Studios</h1></div>
<div class="project-grid-container">
@ -11,8 +44,10 @@
<a
href="https://git.colormatic.org/ColormaticStudios/quality-godot-first-person"
target="_blank"
rel="noopener noreferrer">Quality First Person Controller</a
rel="noopener noreferrer"
>
Quality First Person Controller
</a>
</h1>
<div class="project-grid-box-contents">
<img
@ -27,8 +62,10 @@
<a
href="https://git.colormatic.org/ColormaticStudios/godot-bson"
target="_blank"
rel="noopener noreferrer">BSON for Godot</a
rel="noopener noreferrer"
>
BSON for Godot
</a>
</h1>
<div class="project-grid-box-contents">
<img
@ -74,22 +111,28 @@
<a
href="https://mastodon.social/@colormaticstudios"
target="_blank"
rel="noopener noreferrer">Mastodon</a
rel="noopener noreferrer"
>
Mastodon
</a>
</li>
<li>
<a
href="https://www.instagram.com/colormaticstudios/"
target="_blank"
rel="noopener noreferrer">Instagram</a
rel="noopener noreferrer"
>
Instagram
</a>
</li>
<li>
<a
href="https://www.youtube.com/@colormaticstudios"
target="_blank"
rel="noopener noreferrer">Youtube</a
rel="noopener noreferrer"
>
Youtube
</a>
</li>
</ul>
<ul class="linktree">
@ -97,22 +140,28 @@
<a
href="https://git.colormatic.org/ColormaticStudios"
target="_blank"
rel="noopener noreferrer">Colormatic Git</a
rel="noopener noreferrer"
>
Colormatic Git
</a>
</li>
<li>
<a
href="https://github.com/ColormaticStudios"
target="_blank"
rel="noopener noreferrer">GitHub</a
rel="noopener noreferrer"
>
GitHub
</a>
</li>
<li>
<a
href="https://bsky.app/profile/colormaticstudios.bsky.social"
target="_blank"
rel="noopener noreferrer">Bluesky</a
rel="noopener noreferrer"
>
Bluesky
</a>
</li>
</ul>
</div>

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { onMount } from "svelte";
import { getContext, onMount } from "svelte";
onMount(() => {
let channel = getParam("c");
@ -10,6 +10,8 @@
}
});
let darkTheme: CallableFunction = getContext("darkTheme");
const BASEURL = "https://files.colormatic.org/";
export function getParam(paramName: string) {
@ -100,12 +102,13 @@
<svelte:head>
<title>Video Player</title>
<meta property="og:type" content="video" />
</svelte:head>
<spacer></spacer>
<main>
<div class="video container">
<main class={darkTheme() ? "dark-theme" : ""}>
<div class="video panel">
<!-- Video elements are set by a script -->
<!-- svelte-ignore a11y_media_has_caption -->
<video id="videoplayer" controls></video>
@ -134,32 +137,27 @@
<style lang="scss">
@use "../../style/global.scss";
div.video.container {
div.video {
display: flex;
color: global.$text-color;
width: 90%;
margin: 16px auto 16px auto;
border: solid 1px #00000033;
border-radius: 8px;
box-shadow: 1px 1px 8px #00000033;
padding: 16px;
background-color: #ffffff22;
backdrop-filter: blur(3px);
}
div.video.container video#videoplayer {
div.video video#videoplayer {
flex-grow: 1;
border-radius: 12px;
height: auto;
max-width: 55%;
}
div.video.container div.videoobjects {
div.video div.videoobjects {
display: grid;
padding: 24px;
}
div.video.container div.videodetails h1#videotitle {
div.video div.videodetails h1#videotitle {
padding: 0 12px;
}
@ -168,7 +166,7 @@
flex-direction: column-reverse;
}
div.video.container div.download-dropdown {
div.video div.download-dropdown {
position: relative;
display: inline-block;
padding: 12px;
@ -182,11 +180,11 @@
text-align: center;
}
div.video.container div.download-dropdown:hover {
div.video div.download-dropdown:hover {
box-shadow: 1px 1px 8px #00000088;
}
div.video.container div.download-dropdown div.dropdown-content {
div.video div.download-dropdown div.dropdown-content {
display: none;
position: absolute;
font-size: 80%;
@ -197,38 +195,38 @@
text-align: center;
}
div.video.container div.download-dropdown:hover div.dropdown-content {
div.video div.download-dropdown:hover div.dropdown-content {
display: block;
}
div.video.container div.download-dropdown div.dropdown-content ul {
div.video div.download-dropdown div.dropdown-content ul {
list-style-type: none;
padding-left: 0;
}
div.video.container div.download-dropdown div.dropdown-content ul li {
div.video div.download-dropdown div.dropdown-content ul li {
padding: 4px;
cursor: pointer;
}
div.video.container div.download-dropdown div.dropdown-content ul li:hover {
div.video div.download-dropdown div.dropdown-content ul li:hover {
background-color: #dcdfdf;
}
div.video.container div.download-dropdown div.dropdown-content ul li a {
div.video div.download-dropdown div.dropdown-content ul li a {
text-decoration: none;
color: global.$text-color;
}
@media screen and (max-width: global.$mobile-width) {
div.video.container {
div.video {
display: block;
}
div.video.container video#videoplayer {
div.video video#videoplayer {
width: 100%;
max-width: none;
}
div.video.container div.download-dropdown {
div.video div.download-dropdown {
display: block;
margin-left: auto;
margin-right: auto;
@ -236,8 +234,8 @@
}
}
@media (prefers-color-scheme: dark) {
div.video.container div.download-dropdown div.dropdown-content {
main.dark-theme {
div.video div.download-dropdown div.dropdown-content {
background-color: #444444;
}
}

View File

@ -1,8 +1,35 @@
<script lang="ts">
import { getContext } from "svelte";
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<svelte:head>
<title>Colormatic - Zakarya</title>
<meta
name="description"
content="I am a software and game developer, I run Colormatic and Colormatic
Studios, and I primarily study computer science, psychology, and
linguistics."
/>
<link rel="canonical" href="https://colormatic.org/zakarya/" />
<meta property="og:title" content="Colormatic - Zakarya" />
<meta
property="og:description"
content="I am a software and game developer, I run Colormatic and Colormatic
Studios, and I primarily study computer science, psychology, and
linguistics."
/>
<meta
property="og:image"
content="https://colormatic.org/img/zakarya-icon.svg"
/>
<meta property="og:url" content="https://colormatic.org/zakarya/" />
<meta property="og:type" content="website" />
</svelte:head>
<main>
<main class={darkTheme() ? "dark-theme" : ""}>
<img class="banner" src="/img/zakarya-banner.png" alt="Zakarya Banner" />
<div class="hero panel profile">
<div class="nameplate">
@ -30,29 +57,37 @@
<a
href="https://mstdn.party/@zakarya"
target="_blank"
rel="noopener noreferrer">Mastodon</a
rel="noopener noreferrer"
>
Mastodon
</a>
</li>
<li>
<a
href="https://ko-fi.com/zakarya"
target="_blank"
rel="noopener noreferrer">Ko-fi</a
rel="noopener noreferrer"
>
Ko-fi
</a>
</li>
<li>
<a
href="https://www.youtube.com/@czakarya"
target="_blank"
rel="noopener noreferrer">Youtube</a
rel="noopener noreferrer"
>
Youtube
</a>
</li>
<li>
<a
href="https://github.com/CZakarya"
target="_blank"
rel="noopener noreferrer">GitHub</a
rel="noopener noreferrer"
>
GitHub
</a>
</li>
<!--<li><a href="https://www.reddit.com/user/CZakarya/" target="_blank" rel="noopener noreferrer">Reddit</a></li>-->
</ul>
@ -177,9 +212,7 @@
}
}
@media (prefers-color-scheme: dark) {
main div.profile p {
border-color: #ffffff55;
}
main.dark-theme div.profile p {
border-color: #ffffff55;
}
</style>

22
src/script/cookie.ts Normal file
View File

@ -0,0 +1,22 @@
// The magic cookie system is so stupid
export function setCookie(cname: string, cvalue: string) {
document.cookie = cname + "=" + cvalue + ";";
}
// Credit: https://www.w3schools.com/js/js_cookies.asp
export function getCookie(cname: string): string {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(";");
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == " ") {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}

15
src/script/theme.ts Normal file
View File

@ -0,0 +1,15 @@
/*/
* I know that having a source file this small is a bit silly,
* but I think it's probably a good way to do this. Since multiple
* components need the theme enum, why not define it in one place.
* I also think it's funny for a source file to have more comments
* than code. This will probably be changed in a future refactor.
*
* Also JavaScript enums suck (hot take)
/*/
export var themes = {
LIGHT: "light",
DARK: "dark",
AUTO: "auto",
};

View File

@ -4,21 +4,21 @@
--text-color: #383c3f;
}
@media (prefers-color-scheme: dark) {
:root {
--text-color: white;
}
body {
background-color: black;
}
}
body {
font-family: "Noto Sans", sans-serif;
margin: 0;
color: global.$text-color;
}
@media (prefers-color-scheme: dark) {
body {
background-color: black; // Don't flashbang dark theme users
}
:root {
--text-color: white;
}
}
spacer {
display: block;
margin-top: 8%;
@ -60,10 +60,12 @@ div.panel {
backdrop-filter: blur(5px);
}
@media (prefers-color-scheme: dark) {
div.panel {
border-color: #ffffff33;
}
main.dark-theme div.panel {
border-color: #ffffff33;
}
div.panel.dark-theme {
border-color: #ffffff33;
}
main div.hero {
@ -82,7 +84,7 @@ main div.hero {
}
}
main div#scroll-arrow {
main div.scroll-arrow {
text-align: center;
font-size: 200%;
width: 100%;
@ -93,7 +95,7 @@ main div#scroll-arrow {
transition: opacity 0.25s ease-in;
}
main div#scroll-arrow.scroll-arrow-hide {
main div.scroll-arrow.scroll-arrow-hide {
opacity: 0;
visibility: hidden;
transition:

View File

@ -1,20 +1,20 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
// from the referenced tsconfig.json - TypeScript does not merge them in
}

View File

@ -1,6 +1,6 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { sveltekit } from "@sveltejs/kit/vite";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [sveltekit()]
plugins: [sveltekit()],
});