diff --git a/docs.json b/docs.json index 19ffd9d..21406c1 100644 --- a/docs.json +++ b/docs.json @@ -62,8 +62,8 @@ "group": "Compose", "icon": "film", "expanded": true, + "root": "jobs/compose", "pages": [ - "jobs/compose/index", "jobs/compose/timeline", "jobs/compose/framing", "jobs/compose/transitions", diff --git a/jobs/compose/index.mdx b/jobs/compose.mdx similarity index 100% rename from jobs/compose/index.mdx rename to jobs/compose.mdx diff --git a/jobs/compose/reference/shape.mdx b/jobs/compose/reference/shape.mdx index a118ea7..f228f7e 100644 --- a/jobs/compose/reference/shape.mdx +++ b/jobs/compose/reference/shape.mdx @@ -23,7 +23,7 @@ Set `asset.type` to `"shape"`. These are its own fields, on top of the [clip fie | `viewBox` | string | The coordinate space `path` is written in, like "0 0 24 24" for a pasted icon. Defaults to "0 0 100 100", so a path written in percentages needs no viewBox at all. | | `radius` | number \| object[] | Corner rounding on a rect. 0 is square, 1 is fully round, which gives a pill on a wide box and a circle on a square one. Keyframes round it off or square it up. Rectangles only, and not combinable with a turn or `softness`. default `0`. | | `softness` | number | Feathers the edge; 0 is a hard edge. 0.1 to 0.3 makes a soft spotlight or vignette instead of a hard-edged shape. Not available on a rect that has a `radius`. default `0`, min 0, max 1. | -| `shadow` | object | A drop shadow behind the shape. Every field has a default, so "shadow": {} is a sensible one. It follows the shape, including its outline, its turn and its animation. | +| `shadow` | object | A drop shadow behind the shape. Every field has a default, so "shadow": \{\} is a sensible one. It follows the shape, including its outline, its turn and its animation. | | `shadow.color` | string | Shadow colour, usually black with alpha like "#00000080". default `"#00000080"`, pattern `^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$`. | | `shadow.blur` | number | Blur radius in pixels. 8-20 reads as a soft drop shadow; 0 is a hard offset copy. default `12`, min 0. | | `shadow.x` | number | Horizontal offset in pixels, positive to the right. default `6`. | diff --git a/jobs/compose/reference/timeline.mdx b/jobs/compose/reference/timeline.mdx index bf3d834..6c7d780 100644 --- a/jobs/compose/reference/timeline.mdx +++ b/jobs/compose/reference/timeline.mdx @@ -27,7 +27,7 @@ The shape of a compose request, from the outside in. Each asset kind adds its ow | `output.crf` | integer | Advanced: constant rate factor (lower = higher quality/larger file). Overrides `quality`. h264 accepts 0-51, vp9 0-63. Does not apply to gif/mp3/jpg/png. min 0, max 63. | | `output.encoderPreset` | `"ultrafast"` \| `"superfast"` \| `"veryfast"` \| `"faster"` \| `"fast"` \| `"medium"` \| `"slow"` \| `"slower"` \| `"veryslow"` | Advanced: x264 speed/compression preset (slower = smaller file, same quality). Overrides `quality`. h264 only. | | `output.range` | object | Which part of the timeline to render: a preview, or one section checked without paying for the whole render. A jpg or png is the single frame at `start`, the first frame when there is no range. | -| `timeline` * | object | The edit itself, in ONE of two shapes: { tracks } for precise multi-track control (layers, exact timing), or { scenes, overlays } for sequential scene-by-scene authoring. Not both. | +| `timeline` * | object | The edit itself, in ONE of two shapes: \{ tracks \} for precise multi-track control (layers, exact timing), or \{ scenes, overlays \} for sequential scene-by-scene authoring. Not both. | | `prompt` | string | The natural-language request this timeline was generated from, when AI assist was used. | | `assets` | string[] | The asset URLs supplied to AI assist, when it was used. | @@ -49,7 +49,7 @@ The shape of a compose request, from the outside in. Each asset kind adds its ow | `overlays.asset` * | object | | | `overlays.start` | number | When the clip begins, in seconds from the start of the video. Omit it and the clip starts after everything already placed on its track, pulled back by any transition sitting immediately before it. The first clip on a track starts at 0. min 0. | | `overlays.length` | number \| `"end"` | How long the clip plays. Omit it and the clip runs as long as its CONTENT does: the trim span, the source duration divided by speed, or a composition's own end. Use "end" and it runs to the end of the video instead, whatever the source says, which is what a music bed wants. Text and images have no duration of their own, so omitting is the same as "end" for them and a title with no length stays up. A clip that runs to the end must be the last on its track. | -| `overlays.opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [{time, value, easing}] for animated fades. Fade a clip in with [{time:0,value:0},{time:1,value:1}]. default `1`. | +| `overlays.opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [\{time, value, easing\}] for animated fades. Fade a clip in with [\{time:0,value:0\},\{time:1,value:1\}]. default `1`. | | `overlays.transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns). Omit for full-frame. | | `overlays.crop` | object | Trim edges off the frame by percent; the kept region scales back up to fill. Use to reframe landscape footage for a vertical canvas, and `radius` to round its corners. | | `overlays.speed` | number | Playback rate: 0.5 = slow motion, 2 = timelapse. Pitch shifts with speed. Not allowed on composition clips. Avoid slow motion on the OUTGOING side of a transition (it can run out of frames). over 0. | @@ -74,14 +74,14 @@ Every clip takes these, whatever its asset. | --- | --- | --- | | `start` | number | When the clip begins, in seconds from the start of the video. Omit it and the clip starts after everything already placed on its track, pulled back by any transition sitting immediately before it. The first clip on a track starts at 0. min 0. | | `length` | number \| `"end"` | How long the clip plays. Omit it and the clip runs as long as its CONTENT does: the trim span, the source duration divided by speed, or a composition's own end. Use "end" and it runs to the end of the video instead, whatever the source says, which is what a music bed wants. Text and images have no duration of their own, so omitting is the same as "end" for them and a title with no length stays up. A clip that runs to the end must be the last on its track. | -| `opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [{time, value, easing}] for animated fades. Fade a clip in with [{time:0,value:0},{time:1,value:1}]. default `1`. | +| `opacity` | number \| object[] | Clip opacity: a constant 0..1, or keyframes [\{time, value, easing\}] for animated fades. Fade a clip in with [\{time:0,value:0\},\{time:1,value:1\}]. default `1`. | | `transform` | object | Position/scale/rotate the clip within the frame (picture-in-picture, Ken Burns). Omit for full-frame. | -| `transform.position` | object \| object[] | Where to place the clip, in pixels like {x:1600,y:900} or percent like {x:"75%",y:"75%"}, or keyframes of them to move it. Omit it and the clip is centred, so a scale of 0.5 alone is a centred picture-in-picture. | -| `transform.scale` | number \| object[] | Size relative to the frame: 1 = full frame, 0.3 = a corner PiP, above 1 zooms in (crops edges). Keyframes animate it: [{time:"0%",value:1,easing:"easeInOutSine"},{time:"100%",value:1.15}] is a Ken Burns push-in. A keyframe's easing shapes the move into the next one. default `1`. | +| `transform.position` | object \| object[] | Where to place the clip, in pixels like \{x:1600,y:900\} or percent like \{x:"75%",y:"75%"\}, or keyframes of them to move it. Omit it and the clip is centred, so a scale of 0.5 alone is a centred picture-in-picture. | +| `transform.scale` | number \| object[] | Size relative to the frame: 1 = full frame, 0.3 = a corner PiP, above 1 zooms in (crops edges). Keyframes animate it: [\{time:"0%",value:1,easing:"easeInOutSine"\},\{time:"100%",value:1.15\}] is a Ken Burns push-in. A keyframe's easing shapes the move into the next one. default `1`. | | `transform.rotate` | number \| object[] | Rotation in degrees, clockwise. Keyframes spin it. default `0`. | | `transform.anchor` | `"center"` \| `"topLeft"` | What `position` points at: the clip's center (default) or its top-left corner. It is also what the clip turns about. With no position the clip is centred either way. On text, x follows style.align, so anchor only decides whether y is the text's middle or its top edge. default `"center"`. | | `transform.fit` | `"contain"` \| `"cover"` \| `"fill"` \| `"blur"` | How a picture fills its box when their shapes differ: contain shows all of it, cover fills the box and cuts what overflows (a vertical phone clip in a 16:9 frame with no bars), fill stretches it, blur shows all of it over an enlarged, blurred copy that fills the bars, for a clip that fills the frame. The box is `size`, or the frame. default `"contain"`. | -| `transform.size` | object \| object[] | The clip's box, in pixels like {width:480,height:480} or a percent of the frame. Omit it and the box is the whole frame. A picture fills it the way `fit` says: {width:400,height:400} with fit "cover" is a square cut from 16:9 footage. A shape is drawn to it: {width:"100%",height:"12%"} is a lower-third bar. Give one side and the clip keeps its own shape, which for a shape is SQUARE IN PIXELS, the only way to a true circle on a 16:9 frame. `scale` then multiplies the box, for zooms. Keyframes resize it: a width from "0%" to "80%" is a progress bar. | +| `transform.size` | object \| object[] | The clip's box, in pixels like \{width:480,height:480\} or a percent of the frame. Omit it and the box is the whole frame. A picture fills it the way `fit` says: \{width:400,height:400\} with fit "cover" is a square cut from 16:9 footage. A shape is drawn to it: \{width:"100%",height:"12%"\} is a lower-third bar. Give one side and the clip keeps its own shape, which for a shape is SQUARE IN PIXELS, the only way to a true circle on a 16:9 frame. `scale` then multiplies the box, for zooms. Keyframes resize it: a width from "0%" to "80%" is a progress bar. | | `transform.corners` | object \| object[] | Pins the clip's four corners to four points, in pixels or percent: a picture on a screen, a sign seen in perspective. It is the other form of a transform, so it is not combined with position, scale, rotate or fit. A corner may sit up to a frame beyond each edge. Keyframes move the corners. | | `crop` | object | Trim edges off the frame by percent; the kept region scales back up to fill. Use to reframe landscape footage for a vertical canvas, and `radius` to round its corners. | | `crop.top` | string | Percent of the frame height to remove from the top. default `"0%"`, pattern `^-?\d+(\.\d+)?%$`. | diff --git a/package.json b/package.json index 2262e70..6f6b4d2 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "validate:nav": "node scripts/validate-nav-and-redirects.mjs", "validate:plan-limits": "node scripts/check-plan-limits.mjs", "validate:webhooks": "node scripts/check-webhooks-guide.mjs", - "validate:templates": "node scripts/check-template-links.mjs" + "validate:templates": "node scripts/check-template-links.mjs", + "validate:build": "mint validate" } } diff --git a/scripts/validate-nav-and-redirects.mjs b/scripts/validate-nav-and-redirects.mjs index 54d02e2..dc01a3f 100644 --- a/scripts/validate-nav-and-redirects.mjs +++ b/scripts/validate-nav-and-redirects.mjs @@ -115,35 +115,7 @@ function main() { } } - // --- 3. A page file sitting beside a directory of the same name --- - // - // `jobs/compose.mdx` next to `jobs/compose/` shipped on 2026-09-24 and broke - // EVERY page under that directory in production: all 24 children served the - // parent's content. The sitemap and llms.txt listed them, so the build saw - // them; only the routing collapsed. `mint dev` does not reproduce it, which - // is why it reached the live site. - // - // The working shape is `/index.mdx`, which is what `storage/` and - // `automation/` already use. `/docs/` still serves it, so no URL moves. - for (const slug of [...pageSlugs].sort()) { - const asDir = join(ROOT, slug); - let isDir = false; - try { - isDir = statSync(asDir).isDirectory(); - } catch { - // No directory of that name: nothing to collide with. - } - if (isDir) { - violations.push( - `"${slug}.mdx" sits beside the directory "${slug}/". On Mintlify's production build ` + - `every page under that directory serves "${slug}" instead of its own content, and ` + - `\`mint dev\` does not reproduce it. Move it to "${slug}/index.mdx" and list that ` + - `as the group's first page (see storage/index and automation/index).` - ); - } - } - - // --- 4. Nav entries with no file behind them --- + // --- 3. Nav entries with no file behind them --- for (const slug of [...navSlugs].sort()) { if (!pageSlugs.has(slug)) { violations.push(`Navigation lists "${slug}", but no matching .md/.mdx file exists.`);