initial commit
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
// Helper functions for color interpolation and palette generation
|
||||
|
||||
export type RGB = { r: number; g: number; b: number };
|
||||
|
||||
export function hexToRgb(hex: string): RGB | null {
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
return result
|
||||
? {
|
||||
r: parseInt(result[1], 16),
|
||||
g: parseInt(result[2], 16),
|
||||
b: parseInt(result[3], 16),
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
export function rgbToHex(r: number, g: number, b: number): string {
|
||||
return (
|
||||
"#" + [r, g, b].map((x) => x.toString(16).padStart(2, "0")).join("")
|
||||
);
|
||||
}
|
||||
|
||||
// Parse rgba() string to RGB + alpha
|
||||
export function parseRgba(
|
||||
rgba: string,
|
||||
): { r: number; g: number; b: number; a: number } | null {
|
||||
const match = rgba.match(
|
||||
/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)/,
|
||||
);
|
||||
if (!match) return null;
|
||||
return {
|
||||
r: parseInt(match[1], 10),
|
||||
g: parseInt(match[2], 10),
|
||||
b: parseInt(match[3], 10),
|
||||
a: match[4] ? parseFloat(match[4]) : 1,
|
||||
};
|
||||
}
|
||||
|
||||
// Convert RGB to HSL
|
||||
export function rgbToHsl(
|
||||
r: number,
|
||||
g: number,
|
||||
b: number,
|
||||
): { h: number; s: number; l: number } {
|
||||
r /= 255;
|
||||
g /= 255;
|
||||
b /= 255;
|
||||
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
const l = (max + min) / 2;
|
||||
let h = 0;
|
||||
let s = 0;
|
||||
|
||||
if (max !== min) {
|
||||
const d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
|
||||
switch (max) {
|
||||
case r:
|
||||
h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
||||
break;
|
||||
case g:
|
||||
h = ((b - r) / d + 2) / 6;
|
||||
break;
|
||||
case b:
|
||||
h = ((r - g) / d + 4) / 6;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return { h: h * 360, s: s * 100, l: l * 100 };
|
||||
}
|
||||
|
||||
// Convert HSL to RGB
|
||||
export function hslToRgb(
|
||||
h: number,
|
||||
s: number,
|
||||
l: number,
|
||||
): { r: number; g: number; b: number } {
|
||||
h /= 360;
|
||||
s /= 100;
|
||||
l /= 100;
|
||||
|
||||
let r: number, g: number, b: number;
|
||||
|
||||
if (s === 0) {
|
||||
r = g = b = l;
|
||||
} else {
|
||||
const hue2rgb = (p: number, q: number, t: number) => {
|
||||
if (t < 0) t += 1;
|
||||
if (t > 1) t -= 1;
|
||||
if (t < 1 / 6) return p + (q - p) * 6 * t;
|
||||
if (t < 1 / 2) return q;
|
||||
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
|
||||
return p;
|
||||
};
|
||||
|
||||
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
||||
const p = 2 * l - q;
|
||||
r = hue2rgb(p, q, h + 1 / 3);
|
||||
g = hue2rgb(p, q, h);
|
||||
b = hue2rgb(p, q, h - 1 / 3);
|
||||
}
|
||||
|
||||
return {
|
||||
r: Math.round(r * 255),
|
||||
g: Math.round(g * 255),
|
||||
b: Math.round(b * 255),
|
||||
};
|
||||
}
|
||||
|
||||
// Interpolate between two colors
|
||||
export function lerpColor(color1: RGB, color2: RGB, t: number): RGB {
|
||||
return {
|
||||
r: Math.round(color1.r + (color2.r - color1.r) * t),
|
||||
g: Math.round(color1.g + (color2.g - color1.g) * t),
|
||||
b: Math.round(color1.b + (color2.b - color1.b) * t),
|
||||
};
|
||||
}
|
||||
|
||||
// Generate a color at a specific position along a gradient
|
||||
function getGradientColor(color1: RGB, color2: RGB, position: number): RGB {
|
||||
return lerpColor(color1, color2, position);
|
||||
}
|
||||
|
||||
// Adjust lightness of a color
|
||||
function adjustLightness(color: RGB, amount: number): RGB {
|
||||
const hsl = rgbToHsl(color.r, color.g, color.b);
|
||||
hsl.l = Math.max(0, Math.min(100, hsl.l + amount));
|
||||
return hslToRgb(hsl.h, hsl.s, hsl.l);
|
||||
}
|
||||
|
||||
// Adjust saturation of a color
|
||||
function adjustSaturation(color: RGB, amount: number): RGB {
|
||||
const hsl = rgbToHsl(color.r, color.g, color.b);
|
||||
hsl.s = Math.max(0, Math.min(100, hsl.s + amount));
|
||||
return hslToRgb(hsl.h, hsl.s, hsl.l);
|
||||
}
|
||||
|
||||
// Format RGB as rgba string
|
||||
function toRgba(color: RGB, alpha: number): string {
|
||||
return `rgba(${color.r}, ${color.g}, ${color.b}, ${alpha})`;
|
||||
}
|
||||
|
||||
// Color palette type for the animated background
|
||||
export type ColorPalette = {
|
||||
keyword: string;
|
||||
string: string;
|
||||
function: string;
|
||||
variable: string;
|
||||
comment: string;
|
||||
punctuation: string;
|
||||
number: string;
|
||||
};
|
||||
|
||||
// Default palette for when no gradient colors are provided
|
||||
export const defaultPalette: ColorPalette = {
|
||||
keyword: "rgba(168, 85, 247, 0.7)", // Purple
|
||||
string: "rgba(34, 197, 94, 0.7)", // Green
|
||||
function: "rgba(59, 130, 246, 0.7)", // Blue
|
||||
variable: "rgba(239, 68, 68, 0.7)", // Red
|
||||
comment: "rgba(156, 163, 175, 0.6)", // Gray
|
||||
punctuation: "rgba(107, 114, 128, 0.5)", // Darker gray
|
||||
number: "rgba(249, 115, 22, 0.7)", // Orange
|
||||
};
|
||||
|
||||
// Generate a code color palette from two gradient colors
|
||||
export function generatePaletteFromGradient(
|
||||
color1: RGB,
|
||||
color2: RGB,
|
||||
): ColorPalette {
|
||||
// Create variations by interpolating between the two colors
|
||||
// and adjusting lightness/saturation
|
||||
|
||||
// Primary color (closer to color1)
|
||||
const primary = getGradientColor(color1, color2, 0.2);
|
||||
|
||||
// Secondary color (closer to color2)
|
||||
const secondary = getGradientColor(color1, color2, 0.8);
|
||||
|
||||
// Middle blend
|
||||
const middle = getGradientColor(color1, color2, 0.5);
|
||||
|
||||
// Lighter variation for highlights
|
||||
const light = adjustLightness(getGradientColor(color1, color2, 0.3), 15);
|
||||
|
||||
// Darker variation for comments
|
||||
const dark = adjustLightness(middle, -20);
|
||||
|
||||
// Desaturated for punctuation
|
||||
const desaturated = adjustSaturation(
|
||||
adjustLightness(middle, -10),
|
||||
-30,
|
||||
);
|
||||
|
||||
// Accent (shift towards color2 with more saturation)
|
||||
const accent = adjustSaturation(getGradientColor(color1, color2, 0.7), 10);
|
||||
|
||||
return {
|
||||
keyword: toRgba(primary, 0.8),
|
||||
string: toRgba(secondary, 0.75),
|
||||
function: toRgba(middle, 0.75),
|
||||
variable: toRgba(light, 0.7),
|
||||
comment: toRgba(dark, 0.5),
|
||||
punctuation: toRgba(desaturated, 0.5),
|
||||
number: toRgba(accent, 0.8),
|
||||
};
|
||||
}
|
||||
|
||||
// Parse gradient string and extract the two colors
|
||||
export function parseGradientColors(
|
||||
gradient: string,
|
||||
): { color1: RGB; color2: RGB } | null {
|
||||
// Match rgba colors in the gradient string
|
||||
const rgbaPattern =
|
||||
/rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*[\d.]+)?\s*\)/g;
|
||||
const matches = [...gradient.matchAll(rgbaPattern)];
|
||||
|
||||
if (matches.length >= 2) {
|
||||
return {
|
||||
color1: {
|
||||
r: parseInt(matches[0][1], 10),
|
||||
g: parseInt(matches[0][2], 10),
|
||||
b: parseInt(matches[0][3], 10),
|
||||
},
|
||||
color2: {
|
||||
r: parseInt(matches[1][1], 10),
|
||||
g: parseInt(matches[1][2], 10),
|
||||
b: parseInt(matches[1][3], 10),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export { stripHtmlFromMarkdown } from "./markdown";
|
||||
export {
|
||||
hexToRgb,
|
||||
rgbToHex,
|
||||
parseRgba,
|
||||
rgbToHsl,
|
||||
hslToRgb,
|
||||
lerpColor,
|
||||
generatePaletteFromGradient,
|
||||
parseGradientColors,
|
||||
defaultPalette,
|
||||
type RGB,
|
||||
type ColorPalette,
|
||||
} from "./color";
|
||||
@@ -0,0 +1,39 @@
|
||||
// Strip HTML tags from markdown content, preserving code blocks
|
||||
export function stripHtmlFromMarkdown(markdown: string): string {
|
||||
// First, normalize smart quotes (but NOT backticks - they're fine as-is)
|
||||
let result = markdown
|
||||
.replace(/[""]/g, '"') // Smart quotes to regular quotes
|
||||
.replace(/['']/g, "'"); // Smart single quotes
|
||||
|
||||
// Placeholder for code blocks
|
||||
const codeBlocks: string[] = [];
|
||||
|
||||
// Replace fenced code blocks (```...```) with placeholders
|
||||
result = result.replace(/```[\s\S]*?```/g, (match) => {
|
||||
codeBlocks.push(match);
|
||||
return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
|
||||
});
|
||||
|
||||
// Replace inline code (`...`) with placeholders
|
||||
const inlineCode: string[] = [];
|
||||
result = result.replace(/`[^`]+`/g, (match) => {
|
||||
inlineCode.push(match);
|
||||
return `__INLINE_CODE_${inlineCode.length - 1}__`;
|
||||
});
|
||||
|
||||
// Remove HTML tags (but not their content for simple tags)
|
||||
// This handles both self-closing and paired tags
|
||||
result = result.replace(/<[^>]+>/g, "");
|
||||
|
||||
// Restore inline code
|
||||
inlineCode.forEach((code, i) => {
|
||||
result = result.replace(`__INLINE_CODE_${i}__`, code);
|
||||
});
|
||||
|
||||
// Restore code blocks
|
||||
codeBlocks.forEach((block, i) => {
|
||||
result = result.replace(`__CODE_BLOCK_${i}__`, block);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user