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
38 changes: 38 additions & 0 deletions .vortex/docs/.utils/VideoRecorder.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,42 @@ final class VideoRecorder {

public const LINE_HEIGHT = '1.1';

/**
* Height the renderer draws a character at, in column widths.
*
* Passed to the renderer, so this is the value it uses rather than a copy of
* it.
*/
public const FONT_SIZE = '1.67';

/**
* Return the rows that give a terminal of the given width a wanted shape.
*
* The renderer draws a cell one column wide and FONT_SIZE * LINE_HEIGHT
* column widths tall, so a terminal's rendered aspect is
* cols / (rows * FONT_SIZE * LINE_HEIGHT). Solving that for rows fixes the
* shape of every recording to one number rather than to a pair that has to
* be kept consistent by hand.
*
* @param int $cols
* Columns the terminal is recorded at.
* @param float $aspect
* Wanted width divided by height.
*
* @return int
* Rows to record, rounded to the nearest whole row.
*/
public static function rowsForAspect(int $cols, float $aspect): int {
if ($cols < 1) {
throw new InvalidArgumentException('Columns must be positive, got ' . $cols);
}
if ($aspect <= 0) {
throw new InvalidArgumentException('Aspect must be positive, got ' . $aspect);
}

return max(1, (int) round($cols / ($aspect * (float) self::FONT_SIZE * (float) self::LINE_HEIGHT)));
}

public function __construct(
public readonly string $project_root,
public readonly string $docs_static_dir,
Expand Down Expand Up @@ -405,6 +441,7 @@ public function renderSvg(string $cast_path, string $svg_path): void {
$cast_path,
$svg_path,
'--line-height', self::LINE_HEIGHT,
'--font-size', self::FONT_SIZE,
]);

if (!is_file($svg_path)) {
Expand Down Expand Up @@ -466,6 +503,7 @@ public function renderPng(string $cast_path, string $png_path, ?int $at_ms = NUL
$cast_path,
$frame_svg,
'--line-height', self::LINE_HEIGHT,
'--font-size', self::FONT_SIZE,
'--at', (string) $at,
]);

Expand Down
9 changes: 8 additions & 1 deletion .vortex/docs/.utils/svg-term-render.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Options:
* --at <ms> Timestamp of frame to render
* --line-height <n> Line height multiplier (default: 1.0)
* --font-size <n> Character height in column widths (default: 1.67)
* --font-family <s> Font family (default: Consolas, monospace)
*/

Expand All @@ -30,6 +31,7 @@ if (args.length < 2 || args.includes('--help')) {
console.log('Options:');
console.log(' --at <ms> Timestamp of frame to render');
console.log(' --line-height <n> Line height multiplier (default: 1.0)');
console.log(' --font-size <n> Character height in column widths (default: 1.67)');
console.log(' --font-family <s> Font family (default: Consolas, monospace)');
process.exit(args.includes('--help') ? 0 : 1);
}
Expand All @@ -40,6 +42,7 @@ const outputFile = args[1];
// Parse options.
let at = null;
let lineHeight = 1.0;
let fontSize = 1.67;
let fontFamily = 'Consolas, "Courier New", Courier, "Liberation Mono", monospace';

for (let i = 2; i < args.length; i++) {
Expand All @@ -49,6 +52,9 @@ for (let i = 2; i < args.length; i++) {
} else if (args[i] === '--line-height' && i + 1 < args.length) {
lineHeight = parseFloat(args[i + 1]);
i++;
} else if (args[i] === '--font-size' && i + 1 < args.length) {
fontSize = parseFloat(args[i + 1]);
i++;
} else if (args[i] === '--font-family' && i + 1 < args.length) {
fontFamily = args[i + 1];
i++;
Expand Down Expand Up @@ -129,7 +135,7 @@ const theme = {
text: [171, 178, 191], // #abb2bf
cursor: [82, 139, 255], // #528bff
bold: [171, 178, 191], // #abb2bf
fontSize: 1.67,
fontSize: fontSize,
lineHeight: lineHeight,
fontFamily: fontFamily,
};
Expand All @@ -150,6 +156,7 @@ try {
fs.writeFileSync(outputFile, svg, 'utf8');
console.log(`SVG rendered successfully: ${outputFile}`);
console.log(` lineHeight: ${lineHeight}`);
console.log(` fontSize: ${fontSize}`);
console.log(` fontFamily: ${fontFamily}`);
if (at !== null) {
console.log(` at: ${at}ms`);
Expand Down
24 changes: 12 additions & 12 deletions .vortex/docs/.utils/update-videos.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@
* php update-videos.php --keep lint # reuse workspace, record lint only
*/

/**
* Shape every recording is given: the 2560x1600 of a MacBook screen.
*
* A terminal is configured by its columns alone; VideoRecorder::rowsForAspect()
* turns that width and this ratio into the rows to record at.
*/
const ASPECT_RATIO = 2560 / 1600;

const PROMPT_DELAY = 1;

const WORKSPACE_REL = '.artifacts/tmp/videos-workspace';
Expand All @@ -39,7 +47,8 @@
* - command: the command executed inside the recording. NULL means the
* installer expect script is used instead.
* - speed: playback speed multiplier. 1.0 = recorded speed, 2.0 = 2x faster.
* - cols/rows: terminal dimensions passed to asciinema and used for renders.
* - cols: columns the terminal is recorded at. The rows follow from
* ASPECT_RATIO, so a recording cannot end up an arbitrary shape.
* - poster_ms: cast timestamp (ms) at which the PNG poster frame is taken.
* NULL means use the last frame of the cast.
* - typer: wrap the command with the simulated-typing intro from
Expand All @@ -51,71 +60,62 @@
'command' => NULL,
'speed' => 1.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => 2000,
'typer' => FALSE,
],
'build' => [
'command' => 'ahoy build',
'speed' => 1.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => 2000,
'typer' => TRUE,
],
'provision' => [
'command' => 'ahoy provision',
'speed' => 1.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => NULL,
'typer' => TRUE,
],
'lint' => [
'command' => 'ahoy lint',
'speed' => 2.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => NULL,
'typer' => TRUE,
],
'test' => [
'command' => 'ahoy test',
'speed' => 2.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => 2000,
'typer' => TRUE,
],
'test-bdd' => [
'command' => 'ahoy test-bdd',
'speed' => 1.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => 2000,
'typer' => TRUE,
],
'info' => [
'command' => 'ahoy info',
'speed' => 1.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => NULL,
'typer' => TRUE,
],
'doctor' => [
'command' => 'ahoy doctor',
'speed' => 1.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => NULL,
'typer' => TRUE,
],
'doctor-info' => [
'command' => 'ahoy doctor info',
'speed' => 1.0,
'cols' => 80,
'rows' => 42,
'poster_ms' => NULL,
'typer' => TRUE,
],
Expand Down Expand Up @@ -310,7 +310,7 @@ function record_installer(VideoRecorder $recorder, string $workspace, string $pr
command: $expect_script,
title: 'Vortex Installer Demo',
cols: (int) $cfg['cols'],
rows: (int) $cfg['rows'],
rows: VideoRecorder::rowsForAspect((int) $cfg['cols'], ASPECT_RATIO),
);

render_video($recorder, 'installer', $workspace, $docs_static_dir);
Expand Down Expand Up @@ -339,7 +339,7 @@ function record_command_video(VideoRecorder $recorder, string $name, string $pro
title: "Vortex $cmd Demo",
env: $env,
cols: (int) $cfg['cols'],
rows: (int) $cfg['rows'],
rows: VideoRecorder::rowsForAspect((int) $cfg['cols'], ASPECT_RATIO),
);

render_video($recorder, $name, $workspace, $docs_static_dir);
Expand Down
10 changes: 10 additions & 0 deletions .vortex/docs/src/components/AsciinemaPlayer/AsciinemaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const AsciinemaPlayer = ({
loop = false,
preload = true,
controls = true,
fit,
theme = 'asciinema',
terminalLineHeight = 1.0,
terminalFontFamily = 'Consolas, "Courier New", Courier, "Liberation Mono", monospace',
Expand Down Expand Up @@ -54,6 +55,10 @@ const AsciinemaPlayer = ({
options.startAt = startAt;
}

if (fit !== undefined) {
options.fit = fit;
}

window.AsciinemaPlayer.create(src, containerRef.current, options);
}
};
Expand All @@ -78,6 +83,10 @@ const AsciinemaPlayer = ({
options.startAt = startAt;
}

if (fit !== undefined) {
options.fit = fit;
}

window.AsciinemaPlayer.create(src, containerRef.current, options);
}
}
Expand All @@ -95,6 +104,7 @@ const AsciinemaPlayer = ({
loop,
preload,
controls,
fit,
theme,
terminalLineHeight,
terminalFontFamily,
Expand Down
69 changes: 44 additions & 25 deletions .vortex/docs/src/css/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -378,27 +378,6 @@ html[data-theme='dark'] .vtx-home .ic::after {
.vtx-home .hero-media {
min-width: 0;
}
.vtx-home .hero-badge {
display: inline-flex;
align-items: center;
gap: 9px;
font-size: 0.78rem;
font-weight: 600;
color: var(--teal);
background: color-mix(in srgb, var(--teal) 11%, transparent);
border: 1px solid color-mix(in srgb, var(--teal) 30%, transparent);
padding: 6px 14px;
border-radius: 100px;
margin-bottom: 24px;
}
.vtx-home .hero-badge .pulse {
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--teal);
box-shadow: 0 0 0 0 color-mix(in srgb, var(--teal) 60%, transparent);
animation: vtx-pulse 2.4s var(--ease) infinite;
}
@keyframes vtx-pulse {
0% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--teal) 55%, transparent);
Expand Down Expand Up @@ -647,11 +626,12 @@ html[data-theme='dark'] .vtx-home .btn-primary:hover {
background: #0a0e15;
line-height: 0;
}
/* No ratio of its own: the tile takes the poster's, so it hugs the image and
follows it whenever the recording is re-shaped. */
.vtx-home .media-screen img {
display: block;
width: 100%;
height: auto;
aspect-ratio: 1280 / 705;
object-fit: cover;
}
.vtx-home .media-foot {
display: flex;
Expand Down Expand Up @@ -1395,8 +1375,27 @@ html[data-theme='dark'] .vtx-home .step-num {
opacity: 1;
}
}
/* The panel spans most of the screen and takes its height from the player, so
the terminal grows with the viewport and the panel hugs whatever the player
drew. Only the width is computed here, and only to keep the panel inside the
viewport: '--vtx-demo-max' stops a wide desktop blowing the terminal up past
the width its poster is rendered at, and the last term stops a wide panel
from growing taller than the screen. That term needs the shape the player
draws, which is neither the SVG's nor the poster's - all three are rendered
with different cell metrics - and it only has to be close, since the height
itself comes from the player rather than from this number.
'--vtx-demo-chrome' is the title bar plus the body's padding, which sit
outside the terminal. */
.vtx-home .vtx-modal-panel {
width: min(960px, 100%);
--vtx-demo-ratio: 1.513;
--vtx-demo-chrome: 89px;
--vtx-demo-max: 1280px;
--vtx-demo-width: min(
80vw,
var(--vtx-demo-max),
calc((90vh - var(--vtx-demo-chrome)) * var(--vtx-demo-ratio) + 32px)
);
width: var(--vtx-demo-width);
max-height: 90vh;
display: flex;
flex-direction: column;
Expand All @@ -1407,6 +1406,21 @@ html[data-theme='dark'] .vtx-home .step-num {
box-shadow: 0 40px 90px -30px rgba(0, 0, 0, 0.85);
animation: vtx-pop 260ms var(--ease);
}
/* On a narrow screen there is no room to spare, so the dialog takes the whole
width and drops the frame that would only eat into it. */
@media (max-width: 860px) {
.vtx-home .vtx-modal {
padding: 0;
}
.vtx-home .vtx-modal-panel {
--vtx-demo-width: min(
100vw,
calc((100vh - var(--vtx-demo-chrome)) * var(--vtx-demo-ratio) + 32px)
);
border-width: 0;
border-radius: 0;
}
}
@keyframes vtx-pop {
from {
opacity: 0;
Expand Down Expand Up @@ -1454,14 +1468,19 @@ html[data-theme='dark'] .vtx-home .step-num {
width: 18px;
height: 18px;
}
/* The player fills the width and works out its own height, so the body takes
that height rather than imposing one. That is what makes the dialog hug the
terminal exactly at every size, since the player's rendered shape shifts
slightly as its font size rounds to whole pixels. */
.vtx-home .vtx-modal-body {
padding: 16px;
overflow: auto;
overflow: hidden;
background: #0a0e15;
}
.vtx-home .vtx-modal-body .asciinema-player,
.vtx-home .vtx-cast {
width: 100%;
height: auto;
}

@media (prefers-reduced-motion: reduce) {
Expand Down
Loading