merge in new changes
This commit is contained in:
50
src/support/animation.ts
Normal file
50
src/support/animation.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Add smooth reveal animations for content after loading
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const animateContent = () => {
|
||||
// Animate group 1
|
||||
const smoothReveal = document.querySelectorAll('.smooth-reveal');
|
||||
smoothReveal.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal');
|
||||
},
|
||||
50 + index * 100
|
||||
);
|
||||
});
|
||||
|
||||
// Animate group 2
|
||||
const smoothReveal2 = document.querySelectorAll('.smooth-reveal-2');
|
||||
smoothReveal2.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal');
|
||||
},
|
||||
200 + index * 250
|
||||
);
|
||||
});
|
||||
|
||||
// Animate topic cards with staggered delay
|
||||
const smoothRevealCards = document.querySelectorAll('.smooth-reveal-cards');
|
||||
smoothRevealCards.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal');
|
||||
},
|
||||
400 + index * 250
|
||||
);
|
||||
});
|
||||
|
||||
// Animate with just fade in with staggered delay
|
||||
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
|
||||
smoothRevealFade.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal-fade');
|
||||
},
|
||||
100 + index * 250
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
animateContent();
|
||||
});
|
||||
66
src/support/image.ts
Normal file
66
src/support/image.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import fs from 'node:fs/promises';
|
||||
|
||||
export interface BlurImageMetadata {
|
||||
/**
|
||||
* The width of the origin image
|
||||
*/
|
||||
width: number;
|
||||
/**
|
||||
* The height of the origin image
|
||||
*/
|
||||
height: number;
|
||||
/**
|
||||
* blurDataURL of the image
|
||||
*/
|
||||
blurDataURL: string;
|
||||
/**
|
||||
* blur image width
|
||||
*/
|
||||
blurWidth: number;
|
||||
/**
|
||||
* blur image height
|
||||
*/
|
||||
blurHeight: number;
|
||||
}
|
||||
|
||||
export async function blurStyle(filePath: string) {
|
||||
const image = await blurImageMetadata(filePath);
|
||||
const svg = blurImageSVG(image);
|
||||
return {
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: '50% 50%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundImage: `url("data:image/svg+xml;charset=utf-8,${svg}")`,
|
||||
};
|
||||
}
|
||||
|
||||
function blurImageSVG(image: BlurImageMetadata): string {
|
||||
const { blurDataURL, blurWidth, blurHeight, width, height } = image;
|
||||
|
||||
const std = 20;
|
||||
const svgWidth = blurWidth ? blurWidth * 40 : width;
|
||||
const svgHeight = blurHeight ? blurHeight * 40 : height;
|
||||
|
||||
const viewBox = svgWidth && svgHeight ? `viewBox='0 0 ${svgWidth} ${svgHeight}'` : '';
|
||||
|
||||
return `%3Csvg xmlns='http://www.w3.org/2000/svg' ${viewBox}%3E%3Cfilter id='b' color-interpolation-filters='sRGB'%3E%3CfeGaussianBlur stdDeviation='${std}'/%3E%3CfeColorMatrix values='1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 100 -1' result='s'/%3E%3CfeFlood x='0' y='0' width='100%25' height='100%25'/%3E%3CfeComposite operator='out' in='s'/%3E%3CfeComposite in2='SourceGraphic'/%3E%3CfeGaussianBlur stdDeviation='${std}'/%3E%3C/filter%3E%3Cimage width='100%25' height='100%25' x='0' y='0' preserveAspectRatio='xMidYMid slice' style='filter: url(%23b);' href='${blurDataURL}'/%3E%3C/svg%3E`;
|
||||
}
|
||||
|
||||
async function blurImageMetadata(filepath: string): Promise<BlurImageMetadata> {
|
||||
const { default: sharp } = await import('sharp');
|
||||
const buffer = await fs.readFile(filepath);
|
||||
|
||||
const img = sharp(buffer);
|
||||
const { width, height } = await img.metadata();
|
||||
if (width == null || height == null) {
|
||||
throw new Error(`Invalid image path: ${filepath}`);
|
||||
}
|
||||
|
||||
const aspectRatio = width / height;
|
||||
const blurWidth = 8;
|
||||
const blurHeight = Math.round(blurWidth / aspectRatio);
|
||||
const blurImage = await img.resize(blurWidth, blurHeight).webp({ quality: 10 }).toBuffer();
|
||||
const blurDataURL = `data:image/webp;base64,${blurImage.toString('base64')}`;
|
||||
|
||||
return { blurDataURL, blurHeight, blurWidth, width, height };
|
||||
}
|
||||
19
src/support/paths.ts
Normal file
19
src/support/paths.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { join } from 'node:path';
|
||||
|
||||
export function resolveFilePath(path: string) {
|
||||
if (path.startsWith('/')) {
|
||||
return resolveFilePathPublic(path);
|
||||
}
|
||||
|
||||
return resolveFilePathInternal(path);
|
||||
}
|
||||
|
||||
export function resolveFilePathPublic(path: string) {
|
||||
return join(process.cwd(), path);
|
||||
}
|
||||
|
||||
export function resolveFilePathInternal(path: string) {
|
||||
const normalizePath = path.startsWith('@') ? path.replace('@', '') : path;
|
||||
|
||||
return join(process.cwd(), 'src/', normalizePath);
|
||||
}
|
||||
49
src/support/time.ts
Normal file
49
src/support/time.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { format, register } from 'timeago.js';
|
||||
|
||||
const TimeAgoConfiguration: string[][] = [
|
||||
['today', 'today'],
|
||||
['%s seconds ago', 'in %s seconds'],
|
||||
['1 minute ago', 'in 1 minute'],
|
||||
['%s minutes ago', 'in %s minutes'],
|
||||
['1 hour ago', 'in 1 hour'],
|
||||
['%s hours ago', 'in %s hours'],
|
||||
['1 day ago', 'in 1 day'],
|
||||
['%s days ago', 'in %s days'],
|
||||
['1 week ago', 'in 1 week'],
|
||||
['%s weeks ago', 'in %s weeks'],
|
||||
['1 month ago', 'in 1 month'],
|
||||
['%s months ago', 'in %s months'],
|
||||
['1 year ago', 'in 1 year'],
|
||||
['%s years ago', 'in %s years'],
|
||||
];
|
||||
|
||||
function formatDate(date: Date): string {
|
||||
const year = new Date(date).getFullYear();
|
||||
const month = String(new Date(date).getMonth() + 1).padStart(2, '0');
|
||||
const day = String(new Date(date).getDate()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
function formatDateTime(date: Date): string {
|
||||
const hours = String(new Date(date).getHours()).padStart(2, '0');
|
||||
const minutes = String(new Date(date).getMinutes()).padStart(2, '0');
|
||||
|
||||
return `${formatDate(date)} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
function timeago(date?: Date): string {
|
||||
if (!date) {
|
||||
return 'today';
|
||||
}
|
||||
|
||||
const localeFunc = (number: number, index: number, _?: number): [string, string] => {
|
||||
return TimeAgoConfiguration[index] as [string, string];
|
||||
};
|
||||
|
||||
register('timeago', localeFunc);
|
||||
|
||||
return format(date, 'timeago');
|
||||
}
|
||||
|
||||
export { formatDate, timeago, formatDateTime };
|
||||
Reference in New Issue
Block a user