summaryrefslogtreecommitdiff
path: root/apps/web/src/lib/masks/definitions/cinematic-bars.ts
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2026-04-13 04:40:48 +0200
committerMaze Winther <mazewinther@gmail.com>2026-04-13 05:01:51 +0200
commit5a2aaf3601b6b050603f51e487a31fb07862a21f (patch)
tree1c1acdfb3a6c8210d90af181af62975624999155 /apps/web/src/lib/masks/definitions/cinematic-bars.ts
parent108d3f4ecad4c0ead14941c2067a21546750fedc (diff)
downloadopencut-5a2aaf3601b6b050603f51e487a31fb07862a21f.tar.gz
feat: cinematic bars, diamond, heart, and star masks
Made-with: Cursor
Diffstat (limited to 'apps/web/src/lib/masks/definitions/cinematic-bars.ts')
-rw-r--r--apps/web/src/lib/masks/definitions/cinematic-bars.ts132
1 files changed, 132 insertions, 0 deletions
diff --git a/apps/web/src/lib/masks/definitions/cinematic-bars.ts b/apps/web/src/lib/masks/definitions/cinematic-bars.ts
new file mode 100644
index 00000000..1eb65e71
--- /dev/null
+++ b/apps/web/src/lib/masks/definitions/cinematic-bars.ts
@@ -0,0 +1,132 @@
+import type {
+ MaskDefaultContext,
+ MaskDefinition,
+ RectangleMaskParams,
+} from "@/lib/masks/types";
+import {
+ BOX_LIKE_MASK_PARAMS,
+ computeBoxMaskParamUpdate,
+ getDefaultBaseMaskParams,
+ getStrokeOffset,
+ rotatePoint,
+} from "./box-like";
+
+function getDefaultCinematicBarsMaskParams({
+ elementSize,
+}: MaskDefaultContext): RectangleMaskParams {
+ const absWidth = Math.abs(elementSize?.width ?? 0);
+ const absHeight = Math.abs(elementSize?.height ?? 0);
+ const diagonal =
+ absWidth > 0 && absHeight > 0
+ ? Math.sqrt(absWidth ** 2 + absHeight ** 2)
+ : 0;
+ const fullSpanWidth =
+ absWidth > 0 ? diagonal / absWidth : Math.SQRT2;
+
+ return {
+ ...getDefaultBaseMaskParams(),
+ centerX: 0,
+ centerY: 0,
+ width: Math.max(fullSpanWidth, 1),
+ height: 0.6,
+ rotation: 0,
+ scale: 1,
+ };
+}
+
+function buildBandPath({
+ centerX,
+ centerY,
+ halfWidth,
+ halfHeight,
+ rotationRad,
+}: {
+ centerX: number;
+ centerY: number;
+ halfWidth: number;
+ halfHeight: number;
+ rotationRad: number;
+}): Path2D {
+ const corners = [
+ { x: centerX - halfWidth, y: centerY - halfHeight },
+ { x: centerX + halfWidth, y: centerY - halfHeight },
+ { x: centerX + halfWidth, y: centerY + halfHeight },
+ { x: centerX - halfWidth, y: centerY + halfHeight },
+ ].map((point) =>
+ rotatePoint({
+ ...point,
+ centerX,
+ centerY,
+ rotationRad,
+ }),
+ );
+
+ const path = new Path2D();
+ path.moveTo(corners[0].x, corners[0].y);
+ for (const corner of corners.slice(1)) {
+ path.lineTo(corner.x, corner.y);
+ }
+ path.closePath();
+ return path;
+}
+
+export const cinematicBarsMaskDefinition: MaskDefinition<RectangleMaskParams> = {
+ type: "cinematic-bars",
+ name: "Cinematic Bars",
+ overlayShape: "box",
+ buildOverlayPath({ width, height }) {
+ return `M 0,0 H ${width} V ${height} H 0 Z`;
+ },
+ features: {
+ hasPosition: true,
+ hasRotation: true,
+ sizeMode: "height-only",
+ },
+ params: BOX_LIKE_MASK_PARAMS,
+ buildDefault(context) {
+ return {
+ type: "cinematic-bars",
+ params: getDefaultCinematicBarsMaskParams(context),
+ };
+ },
+ computeParamUpdate: computeBoxMaskParamUpdate,
+ renderer: {
+ buildPath({ resolvedParams, width, height }) {
+ const params = resolvedParams as RectangleMaskParams;
+ const centerX = width / 2 + params.centerX * width;
+ const centerY = height / 2 + params.centerY * height;
+ const maskWidth = Math.max(params.width * width, width);
+ const maskHeight = Math.max(params.height, 0.01) * height;
+ const rotationRad = (params.rotation * Math.PI) / 180;
+
+ return buildBandPath({
+ centerX,
+ centerY,
+ halfWidth: maskWidth / 2,
+ halfHeight: maskHeight / 2,
+ rotationRad,
+ });
+ },
+ buildStrokePath({ resolvedParams, width, height }) {
+ const params = resolvedParams as RectangleMaskParams;
+ const centerX = width / 2 + params.centerX * width;
+ const centerY = height / 2 + params.centerY * height;
+ const rotationRad = (params.rotation * Math.PI) / 180;
+ const offset = getStrokeOffset({
+ strokeAlign: params.strokeAlign,
+ strokeWidth: params.strokeWidth,
+ });
+
+ return buildBandPath({
+ centerX,
+ centerY,
+ halfWidth: Math.max((Math.max(params.width * width, width) / 2) + offset, 1),
+ halfHeight: Math.max(
+ (Math.max(params.height, 0.01) * height) / 2 + offset,
+ 1,
+ ),
+ rotationRad,
+ });
+ },
+ },
+};