Initial commit
ci / docker (backend, backend/Dockerfile, backend) (push) Successful in 21s
ci / docker (frontend, frontend/Dockerfile, frontend) (push) Successful in 23s

This commit is contained in:
2025-10-17 20:19:47 +02:00
commit 2af2af3460
132 changed files with 35408 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
const HEX_COLOR_PATTERN = /^#?([0-9a-fA-F]{6})$/;
const clamp01 = (value) => Math.min(1, Math.max(0, value));
const gammaEncode = (channel) =>
channel <= 0.0031308 ? 12.92 * channel : 1.055 * Math.pow(channel, 1 / 2.4) - 0.055;
const oklchToHex = (l, c, h) => {
const hr = (h * Math.PI) / 180;
const a = Math.cos(hr) * c;
const b = Math.sin(hr) * c;
const l1 = l + 0.3963377774 * a + 0.2158037573 * b;
const m1 = l - 0.1055613458 * a - 0.0638541728 * b;
const s1 = l - 0.0894841775 * a - 1.291485548 * b;
const l3 = l1 ** 3;
const m3 = m1 ** 3;
const s3 = s1 ** 3;
const r = 4.0767416621 * l3 - 3.3077115913 * m3 + 0.2309699292 * s3;
const g = -1.2684380046 * l3 + 2.6097574011 * m3 - 0.3413193965 * s3;
const bLin = -0.0041960863 * l3 - 0.7034186147 * m3 + 1.707614701 * s3;
if ([r, g, bLin].some((channel) => channel < 0 || channel > 1)) {
return null;
}
const sr = Math.round(clamp01(gammaEncode(r)) * 255);
const sg = Math.round(clamp01(gammaEncode(g)) * 255);
const sb = Math.round(clamp01(gammaEncode(bLin)) * 255);
return `#${((sr << 16) | (sg << 8) | sb).toString(16).padStart(6, '0')}`;
};
const hslToHex = (h, s, l) => {
const normalizedH = ((h % 360) + 360) % 360;
const sat = clamp01(s);
const light = clamp01(l);
const chroma = (1 - Math.abs(2 * light - 1)) * sat;
const hPrime = normalizedH / 60;
const x = chroma * (1 - Math.abs((hPrime % 2) - 1));
let r1 = 0;
let g1 = 0;
let b1 = 0;
if (hPrime >= 0 && hPrime < 1) {
r1 = chroma;
g1 = x;
} else if (hPrime >= 1 && hPrime < 2) {
r1 = x;
g1 = chroma;
} else if (hPrime >= 2 && hPrime < 3) {
g1 = chroma;
b1 = x;
} else if (hPrime >= 3 && hPrime < 4) {
g1 = x;
b1 = chroma;
} else if (hPrime >= 4 && hPrime < 5) {
r1 = x;
b1 = chroma;
} else {
r1 = chroma;
b1 = x;
}
const m = light - chroma / 2;
const r = clamp01(r1 + m);
const g = clamp01(g1 + m);
const b = clamp01(b1 + m);
const sr = Math.round(r * 255);
const sg = Math.round(g * 255);
const sb = Math.round(b * 255);
return `#${((sr << 16) | (sg << 8) | sb).toString(16).padStart(6, '0')}`;
};
export const hexToRgb = (input) => {
if (!input) return null;
const match = HEX_COLOR_PATTERN.exec(input.trim());
if (!match) return null;
const value = parseInt(match[1], 16);
return {
r: (value >> 16) & 0xff,
g: (value >> 8) & 0xff,
b: value & 0xff,
hex: `#${match[1].toLowerCase()}`,
};
};
export const relativeLuminance = ({ r, g, b }) => {
const toLinear = (channel) => {
const normalized = channel / 255;
return normalized <= 0.03928
? normalized / 12.92
: ((normalized + 0.055) / 1.055) ** 2.4;
};
const [red, green, blue] = [toLinear(r), toLinear(g), toLinear(b)];
return 0.2126 * red + 0.7152 * green + 0.0722 * blue;
};
export const getReadableTextColor = (hex, { light = '#1f1f1f', dark = '#ffffff' } = {}) => {
const rgb = hexToRgb(hex);
if (!rgb) return dark;
const luminance = relativeLuminance(rgb);
return luminance > 0.6 ? light : dark;
};
export const getTagColorStyle = (hex) => {
const rgb = hexToRgb(hex);
if (!rgb) return null;
return {
backgroundColor: rgb.hex,
borderColor: rgb.hex,
color: getReadableTextColor(rgb.hex),
};
};
export const generateRandomTagColor = () => {
const bucketCount = 8;
const bucketWidth = 360 / bucketCount;
const bucket = Math.floor(Math.random() * bucketCount);
const baseHue = bucket * bucketWidth;
const hueJitter = bucketWidth * 0.35;
const hue = baseHue + (Math.random() * 2 - 1) * hueJitter;
const saturation = 0.45 + Math.random() * 0.2; // 0.45 - 0.65
const lightness = 0.55 + Math.random() * 0.1; // 0.55 - 0.65
return hslToHex(hue, saturation, lightness);
};
export { HEX_COLOR_PATTERN };
+18
View File
@@ -0,0 +1,18 @@
export const formatFileSize = (value) => {
const bytes = Number(value);
if (!Number.isFinite(bytes) || bytes <= 0) {
return '0 B';
}
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB'];
let index = 0;
let amount = bytes;
while (amount >= 1024 && index < units.length - 1) {
amount /= 1024;
index += 1;
}
return `${amount.toFixed(2)} ${units[index]}`;
};