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
70 changes: 60 additions & 10 deletions cella/skills/screenshots/shots-config.mjs
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
// The screenshots this app ships, for `shot-driver.mjs`. This file belongs to the app: list your own pages and states
// here, and a sync never overwrites it (it is pinned in cella/cella.config.ts). The driver and SKILL.md stay upstream.

/**
* The file type of every shot: 'webp' or 'png'. WebP is written lossless by the `cwebp` encoder (`brew install webp`),
* at under a third of the PNG's weight. The slides in `marketing-config.tsx` name the files by extension, and so does
* cella's own `marketing-config.tsx` for the two phone shots, so a change here is a change in both.
*/
export const format = 'webp';

/**
* Viewport and scale per device. The ratios are the frames in `frontend/src/modules/marketing/device-mockup-frame.tsx`,
* and the carousel renders a slide `object-contain`, so a shot that misses its ratio is letterboxed inside the mockup.
* Scale 2 is a retina shot: a 1280px-wide app, drawn at 2560px.
* Scale 2 is a retina shot: a 375px-wide app, drawn at 750px. The mockup draws a slide far smaller than the app, so
* the narrowest viewport that holds the page keeps its text readable.
*/
export const devices = {
// 1600 wide because the menu sheet only pushes the content beside it from 2xl up (`isDesktop` in app-nav.tsx), and
// overlaps the table below that. 1.5x is still more pixels than the mockup's ~735 CSS px ever draws.
pc: { width: 1600, height: 900, scale: 1.5 }, // aspect-video
// 1.5, not 2: a card's gradient and the blur behind a task sheet do not compress lossless, and 1920px is still more
// than the About page's mockup draws on a retina screen (54vw, 85% of it the screen).
pc: { width: 1280, height: 720, scale: 1.5 }, // aspect-video
tablet: { width: 768, height: 1024, scale: 2 }, // aspect-3/4
mobile: { width: 375, height: 667, scale: 2 }, // aspect-9/16
};
Expand All @@ -34,7 +42,12 @@ const shotWorkspace = async (call) => {
if (!organization) return null;
const { items: workspaces } = await call(`/workspaces?organizationId=${organization.id}&limit=50`);
const workspace = workspaces[0];
return workspace ? { ...workspace, path: `/${organization.tenantId}/${organization.slug}/workspace/${workspace.slug}` } : null;
if (!workspace) return null;
return {
...workspace,
path: `/${organization.tenantId}/${organization.slug}/workspace/${workspace.slug}`,
api: `/${organization.tenantId}/${organization.id}`,
};
};

export const placeholders = {
Expand All @@ -44,7 +57,7 @@ export const placeholders = {
task: async (call) => {
const workspace = await shotWorkspace(call);
if (!workspace) return null;
const { items: tasks } = await call(`/tasks?workspaceId=${workspace.id}&sort=createdAt&order=desc&limit=1`);
const { items: tasks } = await call(`${workspace.api}/tasks?workspaceId=${workspace.id}&sort=createdAt&order=desc&limit=1`);
return tasks[0]?.id ?? null;
},
};
Expand All @@ -56,29 +69,66 @@ export const suppress = {
};

/**
* Every entry writes `<out>.png` and `<out>-dark.png`. `path` is a route, with `{name}` filled from the placeholders
* above. `open` brings the page into the state the shot wants, after the route has rendered and settled.
*
* The three images the marketing carousel shows (`marketing-config.tsx`), in its order.
* Brings a board or table to the state a returning user sees. Waits for the first task: `settle` gives up after five
* seconds, and a cold dev server is still compiling the board then. Dismisses "Getting started", an empty first panel
* for an organization without a welcome text, and hides the debug button a development build adds to the sidebar.
* Both through the DOM, never by role: an open task sheet takes the page behind it out of the accessibility tree.
*/
const tidyBoard = async (page) => {
await page.locator('[data-task-card-id], .rdg-row').first().waitFor();
// The dismissal lives in the per-user IndexedDB alert store, so it cannot be seeded through localStorage
const explainer = page.locator('button', { hasText: /^Don't show again$/ });
if (await explainer.count()) {
await explainer.first().evaluate((button) => button.click());
await explainer.first().waitFor({ state: 'detached' });
}
await page.locator('button[aria-label="toggle debug toolbar"]').evaluateAll((buttons) => {
for (const button of buttons) button.style.visibility = 'hidden';
});
};

/**
* Every entry writes `<out>.<format>` and `<out>-dark.<format>`. `path` is a route, with `{name}` filled from the
* placeholders above. `open` brings the page into the state the shot wants, after the route has rendered and settled.
*/
export const shots = [
// The three images the marketing carousel shows (`marketing-config.tsx`), in its order.
{
id: 'board',
device: 'pc',
path: '{workspace}',
out: 'frontend/public/static/marketing/screenshots/board',
open: tidyBoard,
},
{
id: 'table',
device: 'pc',
path: '{workspace}?view=table',
out: 'frontend/public/static/marketing/screenshots/table',
open: tidyBoard,
},
{
id: 'task',
device: 'pc',
// The sheet over the board it was opened from, which is what a reader of the carousel sees when they click a card.
path: '{workspace}?taskSheetId={task}',
out: 'frontend/public/static/marketing/screenshots/task',
open: tidyBoard,
},
// raak on a phone, for the showcase on cella's About page. raak ships neither: they land in the gitignored `.temp`,
// and are copied from there to `frontend/public/static/marketing/showcases/` in the cella checkout.
{
id: 'showcase-board',
device: 'mobile',
path: '{workspace}',
out: '.temp/showcases/raak-1',
open: tidyBoard,
},
{
id: 'showcase-task',
device: 'mobile',
path: '{workspace}?taskSheetId={task}',
out: '.temp/showcases/raak-2',
open: tidyBoard,
},
];
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 6 additions & 6 deletions frontend/src/modules/marketing/marketing-config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,16 @@ export const whyItems = [{ id: 'simple' }, { id: 'automation' }, { id: 'instant'
// Slides for light and dark themes
/** Lists the light-theme slides for the product-benefits section. */
export const whyLightSlides = [
{ id: nanoid(), url: '/static/marketing/screenshots/board.png' },
{ id: nanoid(), url: '/static/marketing/screenshots/table.png' },
{ id: nanoid(), url: '/static/marketing/screenshots/task.png' },
{ id: nanoid(), url: '/static/marketing/screenshots/board.webp' },
{ id: nanoid(), url: '/static/marketing/screenshots/table.webp' },
{ id: nanoid(), url: '/static/marketing/screenshots/task.webp' },
];

/** Lists the dark-theme slides for the product-benefits section. */
export const whyDarkSlides = [
{ id: nanoid(), url: '/static/marketing/screenshots/board-dark.png' },
{ id: nanoid(), url: '/static/marketing/screenshots/table-dark.png' },
{ id: nanoid(), url: '/static/marketing/screenshots/task-dark.png' },
{ id: nanoid(), url: '/static/marketing/screenshots/board-dark.webp' },
{ id: nanoid(), url: '/static/marketing/screenshots/table-dark.webp' },
{ id: nanoid(), url: '/static/marketing/screenshots/task-dark.webp' },
];

/*************************************************************************************************
Expand Down
Loading