38 Commits

Author SHA1 Message Date
a2058f08d4 Bump version to 1.3.4 2025-06-26 17:18:55 -07:00
2d879a93ed Add about us section to Colormatic Studios page 2025-06-26 17:16:39 -07:00
d37f880894 Fix metadata image on Zakarya page
Not really sure how that happened
2025-06-20 15:47:57 -07:00
3eadb3df36 Revert "Ignore that selector"
We don't need that ignore statement anymore
2025-06-20 15:39:30 -07:00
2ef189b7f9 Make the scroll arrow code more Svelty 2025-06-20 15:36:15 -07:00
0041aa4e22 Hide the vertical pipe in the footer on mobile 2025-06-20 15:16:30 -07:00
1c37c310e7 Ignore that selector 2025-06-20 15:13:02 -07:00
a544504b42 Move some specific styles from main css to where they belong 2025-06-17 22:42:39 -07:00
130637dbc3 Remove the janky video player
Temporarily, the links to youtube are used
2025-06-17 22:39:39 -07:00
94a2e51784 The links in the navigation modal need the hover effect too
Gosh I'm just missing everything
2025-06-17 22:32:02 -07:00
701dc6683e The modal close button needs the hover effect too 2025-06-17 22:29:45 -07:00
caa296e11a How did it take this long for me to catch this issue 2025-06-17 22:12:17 -07:00
36ec03be09 All click targets should have the hover animation
I will regret this code in the future
2025-06-17 22:11:30 -07:00
0f3f26f4f3 Proper link colors
These match the Zakarya Blue colors
How did it take THIS LONG for me to get around to doing this
2025-06-17 21:56:28 -07:00
2475b9aee2 Change comment to be ambiguous because it was the wrong value
I had changed the value in the code after writing that comment
2025-06-16 11:12:49 -07:00
4f4c85c85a Bump version to 1.3.3 2025-06-11 21:34:28 -07:00
40bf1f6169 Improve theme settings interface 2025-06-11 21:26:56 -07:00
1ebb927c66 Zakarya page: add link to Colormatic Git profile 2025-06-11 21:25:33 -07:00
52662a6e4e Zakarya page: break up bio 2025-06-11 21:24:29 -07:00
5d9bd0f369 Remove period from construction notice
It did not play well with the formatter
2025-06-11 21:22:46 -07:00
12a51f18d2 Image optimizations 2025-06-11 21:22:05 -07:00
ea0cc34ba8 Require use of Svelte Runes 2025-06-11 21:18:37 -07:00
7fe92c971b Format bg.svelte 2025-06-11 21:17:43 -07:00
6df7d0565b Merge branch 'main' of git.colormatic.org:ColormaticStudios/Colormatic-Website
Merge commit oopsies
2025-06-11 16:38:18 -07:00
7958230d34 BG: Only update entities if animated is true 2025-06-11 16:37:10 -07:00
35ef8877ab Fix output data structure
This naming is very bad and it confused me
oops
2025-05-23 11:16:40 -07:00
f81fa98851 Disable BG animation
I've made the tough decision to disable the BG animation by default.
It's just to heavy on performance and is somewhat distracting for some
people. I'm planning on making a toggle on it, but I'm not sure that
will be done by the next release.
2025-05-16 19:40:36 -07:00
d23558cbfd I accidentally committed the release archive
I made a lot of mistakes with this release, what's next?
2025-05-09 20:26:58 -07:00
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
22 changed files with 629 additions and 454 deletions

View File

@ -1,6 +1,6 @@
{
"name": "colormatic-website",
"version": "1.2.1",
"version": "1.3.4",
"type": "module",
"scripts": {
"dev": "vite dev",

View File

@ -2,63 +2,76 @@
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 animated = false;
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,
};
onMount(() => {
canvas = document.getElementById("bg-canvas") as HTMLCanvasElement;
ctx = canvas.getContext("2d") as CanvasRenderingContext2D;
particleImages.circle = new Image() as HTMLImageElement;
particleImages.circle.src = "/img/bg-shapes/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;
for (let i = 0; i < gradients.length; i++) {
// This re-renders each gradient so their new color will be correct
let gradient = gradients[i];
gradient.color = getRandomColor();
gradient.prepareBuffer();
}
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", (event) => {
dark_theme = event.matches;
for (let i = 0; i < gradientsArray.length; i++) {
let gradient = gradientsArray[i];
gradient.color = getRandomColor();
gradient.prepareBuffer();
}
if (!animated && ctx) {
// Don't try to render if ctx hasn't initialized yet
render(false);
}
});
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;
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 = 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 {};
});
Promise.all(Object.values(imagePromises)).then(() => {
render(false);
});
resize();
init();
render(false);
animate();
window.addEventListener("resize", resize);
@ -69,6 +82,10 @@
canvas.height = window.innerHeight;
canvasDpiScaler(canvas, ctx);
if (!animated) {
render(false);
}
}
const clamp = (val: number, min: number, max: number) =>
@ -83,14 +100,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) {
@ -166,17 +183,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
@ -189,7 +206,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.15)" : "invert(0.8)";
ctx.filter = darkTheme ? "invert(0.1)" : "invert(0.9)";
// Draw center of rotation
// ctx.beginPath();
@ -204,7 +221,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);
@ -221,30 +238,31 @@
radius: number;
color: string;
alpha: number;
renderingBuffer: HTMLCanvasElement;
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 = document.createElement(
"canvas",
) as HTMLCanvasElement;
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,
);
// canvasDpiScaler(
// this.renderingBuffer,
// this.renderingBuffer.getContext("2d") as CanvasRenderingContext2D,
// );
this.prepareBuffer();
}
prepareBuffer() {
let bctx = this.renderingBuffer.getContext(
"2d",
) as CanvasRenderingContext2D;
) as OffscreenCanvasRenderingContext2D;
bctx.clearRect(
0,
@ -262,7 +280,7 @@
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)`);
@ -276,48 +294,61 @@
}
draw() {
ctx.drawImage(
this.renderingBuffer,
this.x - this.radius,
this.y - this.radius,
);
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 < 50; i++) {
particlesArray.push(new Particle());
let isMobile = /Mobile|Android|iPhone/i.test(navigator.userAgent);
let particleCount: number;
if (isMobile) {
particleCount = 25;
} else {
particleCount = 40;
}
gradientsArray = [];
particles = [];
for (let i = 0; i < particleCount; i++) {
particles.push(new Particle());
}
gradients = [];
for (let i = 0; i < 10; i++) {
gradientsArray.push(new Gradient());
gradients.push(new Gradient());
}
}
function render(update: boolean) {
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
for (let i_gradient = 0; i_gradient < gradients.length; i_gradient++) {
let gradient = gradients[i_gradient];
if (update) gradient.update();
gradient.draw();
}
for (let i_particle = 0; i_particle < particles.length; i_particle++) {
let particle = particles[i_particle];
if (update) particle.update();
particle.draw();
}
}
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];
gradient.update();
gradient.draw();
}
for (let i_particle = 0; i_particle < particlesArray.length; i_particle++) {
let particle = particlesArray[i_particle];
particle.update();
particle.draw();
if (animated) {
render(true);
}
requestAnimationFrame(animate);
}
</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

@ -2,8 +2,8 @@
<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">contact@colormatic.org</a>
<span class="responsive-hidden">|</span>
<a href="mailto:contact@colormatic.org">contact@colormatic.org</a>
</p>
</footer>
@ -28,10 +28,15 @@
footer {
flex-direction: column;
}
footer p {
text-align: center;
padding: 4px;
margin: 4px;
}
.responsive-hidden {
display: none;
}
}
</style>

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>
@ -33,7 +37,7 @@
>
<i class="bi bi-git"></i>
</a>
<button on:click={toggleModalMenu} class="menu-button" aria-label="menu">
<button onclick={toggleModalMenu} class="menu-button" aria-label="menu">
<i class="bi bi-list"></i>
</button>
</div>
@ -45,10 +49,10 @@ 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">
<!-- 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>
@ -148,6 +152,12 @@ Svelte modal example, https://svelte.dev/playground/modal
nav button.menu-button {
background: none;
border: none;
transition-duration: 0.2s;
}
nav button.menu-button:hover {
color: #21afff;
}
nav .menu-button i {
@ -236,6 +246,12 @@ Svelte modal example, https://svelte.dev/playground/modal
font-size: 200%;
color: global.$text-color;
transition-duration: 0.2s;
}
span.modalbg div button.close:hover {
color: #21afff;
}
span.modalbg div ul {
@ -255,15 +271,17 @@ Svelte modal example, https://svelte.dev/playground/modal
font-size: 120%;
}
span.modalbg div ul li a:hover {
color: #21afff;
}
@media screen and (max-width: global.$mobile-width) {
span.modalbg div {
width: 30ch;
}
}
@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,172 @@
<script lang="ts">
import { getContext, onMount } from "svelte";
import { themes } from "../script/theme.ts";
import { setCookie, getCookie } from "../script/cookie.ts";
let panelRef = $state() as HTMLElement;
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();
let expanded = $state(false);
let currentIcon = $state("bi-circle-half");
let darkTheme: CallableFunction = getContext("darkTheme");
function setThemeOption(newThemeOption: string) {
expanded = false;
setCookie("theme", newThemeOption);
themeOption = newThemeOption;
switch (newThemeOption) {
case themes.LIGHT:
currentIcon = "bi-sun-fill";
break;
case themes.DARK:
currentIcon = "bi-moon-fill";
break;
case themes.AUTO:
currentIcon = "bi-circle-half";
break;
default:
console.error("Invalid theme option");
}
}
onMount(() => {
let themeCookie = getCookie("theme");
if (Object.values(themes).includes(themeCookie)) {
setThemeOption(themeCookie);
}
function handleClickOutside(event: MouseEvent) {
if (panelRef && !panelRef.contains(event.target as HTMLElement)) {
expanded = false;
}
}
document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
});
function togglePanel() {
expanded = !expanded;
}
</script>
<div
bind:this={panelRef}
class="theme-toggle panel {darkTheme() ? 'dark-theme' : ''}"
>
<button
class="toggle-button"
onclick={togglePanel}
aria-label="Toggle Theme Selector"
>
<i class={`bi ${currentIcon}`}></i>
</button>
<!-- Unfortunately, we have to hard-code the pixel count because CSS won't animate `auto` or `min-content` -->
<div class="button-group" style:height={expanded ? "68px" : "0px"}>
<!-- Don't show the button if it is currently selected (it will be shown as the dropdown icon) -->
{#if themeOption !== themes.DARK}
<button
aria-label="Dark Theme"
bind:this={darkButton}
onclick={() => setThemeOption(themes.DARK)}
>
<i bind:this={darkButtonIcon} class="bi bi-moon"></i>
</button>
{/if}
{#if themeOption !== themes.LIGHT}
<button
aria-label="Light Theme"
bind:this={lightButton}
onclick={() => setThemeOption(themes.LIGHT)}
>
<i bind:this={lightButtonIcon} class="bi bi-sun"></i>
</button>
{/if}
{#if themeOption !== themes.AUTO}
<button
aria-label="Auto Theme"
bind:this={autoButton}
onclick={() => setThemeOption(themes.AUTO)}
>
<i bind:this={autoButtonIcon} class="bi bi-circle-half"></i>
</button>
{/if}
</div>
</div>
<style lang="scss">
@use "../style/global.scss";
div.theme-toggle {
padding: 4px;
position: fixed;
bottom: 20px;
right: 20px;
display: flex;
flex-direction: column-reverse;
align-items: flex-end;
}
button.toggle-button {
font-size: 1.3rem;
background: transparent;
border: none;
cursor: pointer;
color: global.$text-color;
aspect-ratio: 1 / 1;
}
div.button-group {
overflow: hidden;
transition: height 0.3s ease;
display: flex;
flex-direction: column;
}
div.button-group button {
margin: 2px 0;
font-size: 1.3rem;
background: transparent;
border: none;
color: global.$text-color;
cursor: pointer;
}
@media screen and (max-width: global.$mobile-width) {
div.button-group button {
margin: 4px 0;
}
}
// Click target color animation
button {
transition-duration: 0.2s;
}
button:hover {
color: #21afff !important;
/*/
* Cascading styles was a mistake
* I hate this code
* But it works
/*/
}
</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 necessary 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,5 @@
export const prerender = true;
export const csr = true;
export const trailingSlash = "always";
// This will output directories containing index.html

View File

@ -1,23 +1,25 @@
<script lang="ts">
import { onMount } from "svelte";
import { getContext, onMount } from "svelte";
let atTop = $state(true);
function checkScrollPos() {
if (window.scrollY === 0) {
atTop = true;
} else {
atTop = false;
}
}
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", checkScrollPos);
return () => {
window.removeEventListener("scroll", checkScrollPos);
};
});
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<svelte:head>
@ -42,16 +44,16 @@
<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 class="scroll-arrow {atTop ? '' : 'hide'}">
<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">
@ -95,7 +97,6 @@
>
ColormaticStudios/Colormatic-Website
</a>
.
</p>
</div>
</main>
@ -131,4 +132,23 @@
font-size: 200%;
}
}
main div.scroll-arrow {
text-align: center;
font-size: 200%;
width: 100%;
position: fixed;
bottom: 64px;
opacity: 1;
visibility: visible;
transition: opacity 0.25s ease-in;
}
main div.scroll-arrow.hide {
opacity: 0;
visibility: hidden;
transition:
visibility 0s 0.25s,
opacity 0.25s ease-out;
}
</style>

View File

@ -1,3 +1,9 @@
<script lang="ts">
import { getContext } from "svelte";
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<svelte:head>
<title>Colormatic - About</title>
<meta
@ -22,7 +28,7 @@
<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,3 +1,9 @@
<script lang="ts">
import { getContext } from "svelte";
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<svelte:head>
<title>Colormatic Studios</title>
<meta
@ -29,7 +35,7 @@
<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">
@ -97,6 +103,20 @@
</div>
</div>
<div class="hero panel">
<h1>About Us</h1>
<p class="justify">
Colormatic Studios is a creative studio dedicated to giving life to
Colormatic's ambitious future.
</p>
<p class="justify">
We are currently just a passionate team of volunteers working to build
inspiring, intuitive and innovative creative works. We don't have many
projects right now, but we're working hard behind the scenes on ventures
we'll be introducing later.
</p>
</div>
<div class="hero panel">
<h1>Links:</h1>
<div class="double-linktree">
@ -226,6 +246,11 @@
color: global.$text-color;
}
// Bad code
div.project-grid-container div.project-grid-box h1 a:hover {
color: #21afff;
}
div.project-grid-container
div.project-grid-box
div.project-grid-box-contents {

View File

@ -1,245 +0,0 @@
<script lang="ts">
import { onMount } from "svelte";
onMount(() => {
let channel = getParam("c");
let video = getParam("v");
// Don't request an empty string
if (channel && video) {
getVideo(channel, video);
}
});
const BASEURL = "https://files.colormatic.org/";
export function getParam(paramName: string) {
var params = new URLSearchParams(window.location.search);
let t_param = params.get(paramName);
return t_param ? t_param : ""; // Return empty string if null
}
async function getJSON(url: string) {
const response = await fetch(url);
if (!response.ok) throw new Error(response.statusText);
const data = response.json();
return data;
}
export function getVideo(cname: string, vname: string) {
var videoplayer = document.getElementById("videoplayer") as HTMLElement;
var videotitle = document.getElementById("videotitle") as HTMLElement;
var videodescription = document.getElementById(
"videodescription",
) as HTMLElement;
var videodownload = document.getElementById("videodownload") as HTMLElement;
var sourcedownload = document.getElementById(
"sourcedownload",
) as HTMLElement;
getJSON(BASEURL + cname + "/videos/data/" + vname + ".json")
.then((data) => {
let videoURL =
BASEURL +
cname +
"/videos/raw/" +
data.video_file +
"." +
data.video_format;
videoplayer.setAttribute(
"poster",
BASEURL + cname + "/videos/thumbnail/" + data.thumbnail,
);
var videosource = document.createElement("source");
videosource.setAttribute("src", videoURL);
videosource.setAttribute("type", "video/" + data.video_format);
videoplayer.appendChild(videosource);
document.title = data.title;
videotitle.innerText = data.title;
data.description.forEach((iter: number) => {
// TODO: Detect if one of these lines contains a link and if so, make it a link
videodescription.appendChild(
document.createTextNode(iter.toString()),
);
videodescription.appendChild(document.createElement("br"));
});
videodownload.setAttribute(
"href",
BASEURL +
cname +
"/videos/raw/" +
data.video_file +
"." +
data.video_format,
);
videodownload.setAttribute(
"download",
data.video_file + "." + data.video_format,
);
sourcedownload.setAttribute(
"href",
BASEURL +
cname +
"/videos/source/" +
data.source_file +
"." +
data.source_format,
);
sourcedownload.setAttribute(
"download",
data.source_file + "." + data.source_format,
);
})
.catch((error) => {
videotitle.innerText = "Failed to load video.";
console.error(error);
});
}
</script>
<svelte:head>
<title>Video Player</title>
<meta property="og:type" content="video" />
</svelte:head>
<spacer></spacer>
<main>
<div class="video container">
<!-- Video elements are set by a script -->
<!-- svelte-ignore a11y_media_has_caption -->
<video id="videoplayer" controls></video>
<div class="videoobjects">
<div class="videodetails">
<h1 id="videotitle">Video Player</h1>
<p id="videodescription" class="justify"></p>
</div>
<div class="dropdown-container">
<div class="download-dropdown">
<i class="bi bi-download"></i>
<div class="dropdown-content">
<ul>
<li><a id="videodownload">Download Video</a></li>
<li><a id="sourcedownload">Download Source</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
</main>
<spacer></spacer>
<style lang="scss">
@use "../../style/global.scss";
div.video.container {
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 {
flex-grow: 1;
border-radius: 12px;
height: auto;
max-width: 55%;
}
div.video.container div.videoobjects {
display: grid;
padding: 24px;
}
div.video.container div.videodetails h1#videotitle {
padding: 0 12px;
}
div.dropdown-container {
display: flex;
flex-direction: column-reverse;
}
div.video.container div.download-dropdown {
position: relative;
display: inline-block;
padding: 12px;
width: 25px;
height: 25px;
background-color: #21afff;
box-shadow: 1px 1px 8px #00000033;
border-radius: 50px;
transition-duration: 0.35s;
font-size: 120%;
text-align: center;
}
div.video.container div.download-dropdown:hover {
box-shadow: 1px 1px 8px #00000088;
}
div.video.container div.download-dropdown div.dropdown-content {
display: none;
position: absolute;
font-size: 80%;
min-width: 160px;
background-color: #edeeee;
box-shadow: 1px 1px 8px #00000033;
border-radius: 8px;
text-align: center;
}
div.video.container div.download-dropdown:hover div.dropdown-content {
display: block;
}
div.video.container 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 {
padding: 4px;
cursor: pointer;
}
div.video.container div.download-dropdown div.dropdown-content ul li:hover {
background-color: #dcdfdf;
}
div.video.container 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 {
display: block;
}
div.video.container video#videoplayer {
width: 100%;
max-width: none;
}
div.video.container div.download-dropdown {
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 12px;
}
}
@media (prefers-color-scheme: dark) {
div.video.container div.download-dropdown div.dropdown-content {
background-color: #444444;
}
}
</style>

View File

@ -1,3 +1,9 @@
<script lang="ts">
import { getContext } from "svelte";
let darkTheme: CallableFunction = getContext("darkTheme");
</script>
<svelte:head>
<title>Colormatic - Zakarya</title>
<meta
@ -17,36 +23,54 @@
/>
<meta
property="og:image"
content="https://colormatic.org/img/zakarya-icon.svg"
content="https://colormatic.org/img/colormatic_logo.svg"
/>
<meta property="og:url" content="https://colormatic.org/zakarya/" />
<meta property="og:type" content="website" />
</svelte:head>
<main>
<img class="banner" src="/img/zakarya-banner.png" alt="Zakarya Banner" />
<main class={darkTheme() ? "dark-theme" : ""}>
<img
class="banner"
src="/img/zakarya-banner.webp"
alt="Zakarya Banner"
srcset="/img/zakarya-banner.webp 960w, /img/zakarya-banner@2x.webp 1920w"
/>
<div class="hero panel profile">
<div class="nameplate">
<img
src="/img/zakarya-icon.png"
src="/img/zakarya-icon.webp"
class="zakarya-icon"
alt="Zakarya Icon"
srcset="/img/zakarya-icon.webp 540w, /img/zakarya-icon@2x.webp 1080w"
/>
<span class="name-title">Zakarya</span>
</div>
<p>
I am a software and game developer, I run Colormatic and Colormatic
Studios, and I primarily study computer science, psychology, and
linguistics.
<br />
I have an intrinsic urge to create, and that's what Colormatic is all about.
My works include world building, music, videos, 3D modeling, video games, websites,
programs, and more.
</p>
<div class="bio">
<p>
I am a software and game developer, I run Colormatic and Colormatic
Studios, and I primarily study computer science, psychology, and
linguistics.
</p>
<p>
I have an intrinsic urge to create, and that's what Colormatic is all
about. My works include world building, music, videos, 3D modeling,
video games, websites, programs, and more.
</p>
</div>
<div class="linktree-container">
<ul class="linktree">
<li>
<a
href="https://git.colormatic.org/zakarya"
target="_blank"
rel="noopener noreferrer"
>
Colormatic Git
</a>
</li>
<li>
<a
href="https://mstdn.party/@zakarya"
@ -92,7 +116,11 @@
<h1>Featured Videos:</h1>
<ul class="videolist">
<li>
<a href="/video?c=zakarya&v=the-way-forward">
<a
href="https://www.youtube.com/watch?v=FWGCPIEM_-o"
target="_blank"
rel="noopener noreferrer"
>
<img
src="https://files.colormatic.org/zakarya/videos/thumbnail/wayforward.png"
alt="Video Thumbnail"
@ -101,7 +129,11 @@
</a>
</li>
<li>
<a href="/video?c=zakarya&v=hello-world">
<a
href="https://www.youtube.com/watch?v=OPD8NqNu0nE"
target="_blank"
rel="noopener noreferrer"
>
<img
src="https://files.colormatic.org/zakarya/videos/thumbnail/helloworld.png"
alt="Video Thumbnail"
@ -150,7 +182,7 @@
font-size: 200%;
}
main div.profile p {
main div.profile div.bio {
font-size: 120%;
max-width: 50%;
padding: 16px;
@ -179,7 +211,7 @@
margin: 24px auto;
}
main div.profile p {
main div.profile div.bio {
max-width: unset;
}
@ -206,9 +238,35 @@
}
}
@media (prefers-color-scheme: dark) {
main div.profile p {
border-color: #ffffff55;
}
main.dark-theme div.profile div.bio {
border-color: #ffffff55;
}
ul.videolist {
list-style-type: none;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding-left: 0;
}
ul.videolist li {
padding: 8px;
}
ul.videolist li a {
text-decoration: none;
}
ul.videolist li a img {
width: 250px;
height: auto;
border-radius: 8px;
}
ul.videolist li a span {
display: block;
color: global.$text-color;
font-size: 120%;
}
</style>

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

@ -0,0 +1,27 @@
// The magic cookie system is so stupid
export function setCookie(
cname: string,
cvalue: string,
sameSite: string = "Lax",
) {
document.cookie =
cname + "=" + cvalue + ";" + "SameSite" + "=" + sameSite + ";";
}
// 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,26 +4,46 @@
--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%;
}
a {
transition-duration: 0.2s;
}
a:link {
color: #2194ff;
}
a:visited {
color: #2178ff;
}
a:link:hover {
color: #21afff;
}
a:visited:hover {
color: #21afff;
}
a.btn,
button.btn {
padding: 8px 18px;
@ -60,10 +80,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,25 +104,6 @@ main div.hero {
}
}
main div#scroll-arrow {
text-align: center;
font-size: 200%;
width: 100%;
position: fixed;
bottom: 64px;
opacity: 1;
visibility: visible;
transition: opacity 0.25s ease-in;
}
main div#scroll-arrow.scroll-arrow-hide {
opacity: 0;
visibility: hidden;
transition:
visibility 0s 0.25s,
opacity 0.25s ease-out;
}
main div.hero h1 a {
color: global.$text-color;
}
@ -179,34 +182,6 @@ div.double-linktree ul.linktree {
margin: 0;
}
ul.videolist {
list-style-type: none;
display: flex;
flex-wrap: wrap;
justify-content: center;
padding-left: 0;
}
ul.videolist li {
padding: 8px;
}
ul.videolist li a {
text-decoration: none;
}
ul.videolist li a img {
width: 250px;
height: auto;
border-radius: 8px;
}
ul.videolist li a span {
display: block;
color: global.$text-color;
font-size: 120%;
}
img.pixelart {
image-rendering: pixelated;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 722 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 542 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

View File

@ -7,6 +7,9 @@ const config = {
kit: {
adapter: adapter(),
},
compilerOptions: {
runes: true,
},
};
export default config;