init template
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import sharp from 'sharp';
|
||||
import { resolve, basename, extname, join, dirname } from 'path';
|
||||
import { existsSync, mkdirSync, readdirSync, statSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const PROJECT_ROOT = resolve(fileURLToPath(import.meta.url), '../../..');
|
||||
const DEFAULT_INPUT = resolve(PROJECT_ROOT, 'assets/items');
|
||||
const DEFAULT_OUTPUT = resolve(PROJECT_ROOT, 'assets/icons');
|
||||
const GRID = 4;
|
||||
const SUPPORTED_EXTENSIONS = /\.(jpe?g|png|webp|tiff?)$/i;
|
||||
|
||||
async function splitSpriteSheet(filePath, outputDir) {
|
||||
const name = basename(filePath, extname(filePath));
|
||||
|
||||
const { width, height } = await sharp(filePath).metadata();
|
||||
const cellWidth = Math.floor(width / GRID);
|
||||
const cellHeight = Math.floor(height / GRID);
|
||||
|
||||
const tasks = [];
|
||||
|
||||
for (let row = 0; row < GRID; row++) {
|
||||
for (let col = 0; col < GRID; col++) {
|
||||
const left = col * cellWidth;
|
||||
const top = row * cellHeight;
|
||||
const outputPath = join(outputDir, `${name}-${row}-${col}.png`);
|
||||
|
||||
tasks.push(
|
||||
sharp(filePath)
|
||||
.extract({ left, top, width: cellWidth, height: cellHeight })
|
||||
.png()
|
||||
.toFile(outputPath)
|
||||
.then(() => outputPath)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.all(tasks);
|
||||
}
|
||||
|
||||
function collectFiles(input) {
|
||||
if (statSync(input).isFile()) {
|
||||
return [input];
|
||||
}
|
||||
return readdirSync(input)
|
||||
.filter(f => SUPPORTED_EXTENSIONS.test(f))
|
||||
.map(f => join(input, f));
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const input = resolve(process.argv[2] || DEFAULT_INPUT);
|
||||
const outputDir = resolve(process.argv[3] || DEFAULT_OUTPUT);
|
||||
|
||||
if (!existsSync(input)) {
|
||||
console.error(`Not found: ${input}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
const files = collectFiles(input);
|
||||
|
||||
if (files.length === 0) {
|
||||
console.error(`No image files found in: ${input}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Processing ${files.length} sprite sheet(s)...`);
|
||||
|
||||
const allResults = await Promise.all(
|
||||
files.map(async (filePath) => {
|
||||
const results = await splitSpriteSheet(filePath, outputDir);
|
||||
console.log(` ${basename(filePath)} -> ${results.length} images`);
|
||||
return results;
|
||||
})
|
||||
);
|
||||
|
||||
const totalImages = allResults.reduce((sum, r) => sum + r.length, 0);
|
||||
console.log(`Done. ${totalImages} images saved to ${outputDir}`);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -60,6 +60,158 @@
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
@utility page-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@utility page-header-reverse {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
align-items: flex-start;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
@utility hero-image {
|
||||
width: 33%;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@utility header-text {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
@utility page-title {
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.02em;
|
||||
color: #1e1b4b;
|
||||
}
|
||||
|
||||
@utility page-subtitle {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
color: #6366f1;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
@utility page-meta {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.85rem;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
@utility problem-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border: 2px solid #e0e7ff;
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(135deg, #fefefe 0%, #f0f0ff 100%);
|
||||
box-shadow: 0 2px 8px rgba(99, 102, 241, 0.08);
|
||||
width: fit-content;
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
@utility problem-card-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex-shrink: 0;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
@utility problem-card-text {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 600;
|
||||
color: #1e1b4b;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@utility problem-card-answer {
|
||||
display: inline-block;
|
||||
width: 3.5rem;
|
||||
border-bottom: 2.5px solid #a5b4fc;
|
||||
text-align: center;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@utility problem-align-left {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
padding: 0.3rem 0;
|
||||
}
|
||||
|
||||
@utility problem-align-right {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 0.3rem 0;
|
||||
}
|
||||
|
||||
@utility problem-align-center {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 0.3rem 0;
|
||||
}
|
||||
|
||||
@utility problems-area {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@utility page-content-themed {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 10mm 15mm 85mm 15mm;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@utility page-footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 80mm;
|
||||
overflow: hidden;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@utility page-footer-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center top;
|
||||
}
|
||||
|
||||
@utility page-footer-fade {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 40%;
|
||||
background: linear-gradient(to bottom, white, transparent);
|
||||
pointer-events: none;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
@media print {
|
||||
body {
|
||||
margin: 0;
|
||||
|
||||
Reference in New Issue
Block a user