Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 119 additions & 4 deletions biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
"$schema": "https://biomejs.dev/schemas/2.4.4/schema.json",
"assist": {
"actions": {
"source": {
Expand All @@ -10,21 +10,136 @@
"linter": {
"enabled": true,
"rules": {
"recommended": true
"recommended": true,
"nursery": {
"recommended": true,
"noShadow": "error",
"useExplicitType": "warn",
"noEqualsToNull": "error",
"noReturnAssign": "error",
"useArraySortCompare": "error",
"useFind": "warn",
"useSpread": "warn"
},
"correctness": {
"recommended": true,
"noUnusedImports": "error",
"noUnusedVariables": "error",
"noUnusedPrivateClassMembers": "error",
"noUndeclaredVariables": "error",
"noUnreachable": "error",
"noInvalidUseBeforeDeclaration": "error"
},
"style": {
"recommended": true,
"noDefaultExport": "off",
"noParameterAssign": "error",
"useBlockStatements": "error",
"useCollapsedElseIf": "warn",
"useConsistentBuiltinInstantiation": "error",
"useDefaultParameterLast": "error",
"useExplicitLengthCheck": "warn",
"useForOf": "warn",
"useFragmentSyntax": "warn",
"useImportType": "error",
"useShorthandAssign": "warn",
"useShorthandFunctionType": "warn",
"useThrowNewError": "error",
"useThrowOnlyError": "error",
"noCommonJs": "error",
"noExportedImports": "off",
"useNamingConvention": {
"level": "warn",
"options": {
"conventions": [
{
"selector": { "kind": "variable" },
"formats": ["camelCase", "CONSTANT_CASE", "PascalCase"]
},
{
"selector": { "kind": "function" },
"formats": ["camelCase", "PascalCase"]
},
{
"selector": { "kind": "typeLike" },
"formats": ["PascalCase"]
},
{
"selector": { "kind": "enumMember" },
"formats": ["CONSTANT_CASE", "PascalCase"]
}
]
}
},
"useFilenamingConvention": {
"level": "warn",
"options": {
"requireAscii": true,
"filenameCases": ["PascalCase", "camelCase"]
}
}
},
"suspicious": {
"recommended": true,
"noConsole": "warn",
"noEmptyBlockStatements": "error",
"noExplicitAny": "warn",
"useAwait": "error",
"useErrorMessage": "error",
"useGuardForIn": "error",
"useIsArray": "error",
"noIrregularWhitespace": "off"
},
"complexity": {
"recommended": true,
"noExcessiveCognitiveComplexity": {
"level": "warn",
"options": {
"maxAllowedComplexity": 25
}
},
"noForEach": "warn",
"noStaticOnlyClass": "error",
"noUselessStringConcat": "error",
"noUselessUndefinedInitialization": "error",
"noVoid": "error",
"useArrowFunction": "warn",
"useDateNow": "error",
"useFlatMap": "warn",
"useSimplifiedLogicExpression": "warn"
},
"performance": {
"recommended": true,
"noAccumulatingSpread": "warn",
"noBarrelFile": "warn",
"noDelete": "warn",
"noReExportAll": "warn"
},
"a11y": {
"recommended": true
},
"security": {
"recommended": true,
"noGlobalEval": "error"
}
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2
"indentWidth": 2,
"lineWidth": 120
},
"files": {
"includes": ["**/*.ts", "**/*.js", "**/*.json", "**/*.html", "!dist"]
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always"
"semicolons": "always",
"trailingCommas": "all",
"arrowParentheses": "always",
"bracketSameLine": false
}
}
}
28 changes: 9 additions & 19 deletions scripts/bouncingBallToSVG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,7 @@ const FLOOR_WIDTH = 0.8; // stroke-width
* T_n ∝ sqrt(h_n). We normalise so the total "time" = 1.
*/
function buildBounces(): Array<{ tStart: number; tEnd: number; h: number }> {
const heights = Array.from(
{ length: NUM_BOUNCES },
(_, n) => INITIAL_HEIGHT * RESTITUTION ** n,
);
const heights = Array.from({ length: NUM_BOUNCES }, (_, n) => INITIAL_HEIGHT * RESTITUTION ** n);

// flight time ∝ sqrt(h)
const durations = heights.map((h) => Math.sqrt(h));
Expand All @@ -59,9 +56,7 @@ function computeSnapshots(): Point[] {

// x positions of bounce endpoints (floor contacts) spaced proportionally
// to flight time (= proportional to normalised duration)
const xContacts: number[] = bounces.map(
(b) => X_START + b.tStart * (X_END - X_START),
);
const xContacts: number[] = bounces.map((b) => X_START + b.tStart * (X_END - X_START));
xContacts.push(X_END); // final landing

for (let i = 0; i < TOTAL_SNAPSHOTS; i++) {
Expand All @@ -70,7 +65,9 @@ function computeSnapshots(): Point[] {

// find which bounce this snapshot falls in
const bounce = bounces.find((b) => tGlobal >= b.tStart && tGlobal < b.tEnd);
if (!bounce) continue; // shouldn't happen
if (!bounce) {
continue; // shouldn't happen
}

const bounceIdx = bounces.indexOf(bounce);
// local time within this bounce, in [0, 1]
Expand All @@ -93,13 +90,9 @@ function computeSnapshots(): Point[] {
return points;
}

function buildSVG(points: Point[]): string {
const circles = points
.map(
(p) =>
` <circle cx="${p.x}" cy="${p.y}" r="${BALL_RADIUS}" ` +
`fill="${BALL_COLOR}" opacity="${BALL_OPACITY}"/>`,
)
function buildSvg(snapshots: Point[]): string {
const circles = snapshots
.map((p) => ` <circle cx="${p.x}" cy="${p.y}" r="${BALL_RADIUS}" fill="${BALL_COLOR}" opacity="${BALL_OPACITY}"/>`)
.join("\n");

return `<svg xmlns="http://www.w3.org/2000/svg" \
Expand All @@ -113,12 +106,9 @@ ${circles}
}

const points = computeSnapshots();
const svg = buildSVG(points);
const svg = buildSvg(points);

// Write to public/icons/icon.svg (source for PNG icon generation)
const outputPath = "public/icons/icon.svg";
fs.mkdirSync("public/icons", { recursive: true });
fs.writeFileSync(outputPath, svg, "utf8");
console.log(
`✓ Generated ${outputPath} with ${points.length} bouncing ball snapshots`,
);
6 changes: 2 additions & 4 deletions scripts/generate-icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { fileURLToPath } from "node:url";
import pngToIco from "png-to-ico";
import sharp from "sharp";

const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "..");
const Dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(Dirname, "..");

const svgBuffer = readFileSync(resolve(root, "public/icons/icon.svg"));

Expand All @@ -19,7 +19,6 @@ const pngIcons = [
for (const { name, size } of pngIcons) {
const dest = resolve(root, "public", name);
await sharp(svgBuffer).resize(size, size).png().toFile(dest);
console.log(`Generated ${name} (${size}x${size})`);
}

// Generate multi-size favicon.ico (16, 32, 48, 64)
Expand All @@ -34,4 +33,3 @@ for (const size of faviconSizes) {
const icoBuffer = await pngToIco(faviconPngs);
const faviconDest = resolve(root, "public", "favicon.ico");
writeFileSync(faviconDest, icoBuffer);
console.log(`Generated favicon.ico (16x16, 32x32, 48x48, 64x64)`);
Loading