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
20 changes: 19 additions & 1 deletion mascot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ by hand. Every piece comes in three forms:
| `walk/patch-walk-animated` (0.96 s loop) | `.svg` (6.9 MB), `.webp` 512 × 352 | `.webp` 256 × 176 | `.svg` (1.2 MB), `.svgz` (2.6 MB), `.webp` 256 × 176 |
| `walk/frames/patch-walk-0N` (eight frames) | `.svg` and `.png` 1024 × 704 | `.png` 256 × 176 | `.svg` and `.svgz` |

### Walking left (from the walk cycle)

The approved left-facing walk, for anything that moves Patch to the left (a pet that follows the
pointer, a loan heading back). It is the walk cycle mirrored exactly by the script, never by hand, so
it shows Patch's other side. Same sizes, frames and timing as the walk cycle:

| File | Full | Original | Compressed |
|---|---|---|---|
| `walk-left/patch-walk-left` (walking pose) | `.svg`, `.png` 1920 × 1440 | `.png` 480 × 360 | `.svg`, `.svgz` |
| `walk-left/patch-walk-left-animated` (0.96 s loop) | `.svg`, `.webp` 512 × 352 | `.webp` 256 × 176 | `.svg`, `.svgz`, `.webp` 256 × 176 |
| `walk-left/frames/patch-walk-left-0N` (eight frames) | `.svg` and `.png` 1024 × 704 | `.png` 256 × 176 | `.svg` and `.svgz` |

Frame N of `walk-left/` is frame N of `walk/` facing the other way, on the same ground line, so you
can switch direction mid-stride without a jump.

**Which one to use:**

- **Product UI** (apps, the web, chat): the animated **WebP**, or `compressed/` for the lightest.
Expand Down Expand Up @@ -96,6 +111,8 @@ node scripts/cut-mascot.mjs

Pass `concept` or `walk` to rebuild just one sheet. If a new sheet has a different layout, update its
boxes in `SHEETS` at the top of the script first. A higher-resolution sheet gives finer vectors.
Rebuilding the walk also rebuilds `walk-left/`; pass `walk-left` to rebuild only that, from the current
walk files, in a few seconds and without tracing.

**How faithful the vectors are.** The trace settings were chosen by rendering each candidate and scoring
it against the sheet pixel by pixel. At the chosen settings about 0.27% of pixels differ visibly. Going
Expand Down Expand Up @@ -160,7 +177,8 @@ Patch is the friendly face of the product. **The box symbol is still the logo.**
## Keep Patch intact

1. **Don't** recolour, filter or redraw Patch. Use these files as they are.
2. **Don't** stretch, skew or mirror Patch. The coat is asymmetric on purpose.
2. **Don't** stretch, skew or mirror Patch. The coat is asymmetric on purpose. To show Patch heading
left, use the approved `walk-left/` files; never flip the other files with CSS or an editor.
3. **Don't** add shadows, glows, gradients or outlines.
4. **Don't** make Patch fierce: no bared teeth, angry brows or hunting poses.
5. **Don't** scale up the PNGs or WebPs past their size. Use the full SVGs when you need bigger.
Expand Down
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/frames/patch-walk-left-01.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/frames/patch-walk-left-02.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/frames/patch-walk-left-03.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/frames/patch-walk-left-04.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/frames/patch-walk-left-05.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/frames/patch-walk-left-06.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/frames/patch-walk-left-07.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/frames/patch-walk-left-08.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/patch-walk-left-animated.svg
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.
1 change: 1 addition & 0 deletions mascot/compressed/walk-left/patch-walk-left.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mascot/compressed/walk-left/patch-walk-left.svgz
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.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mascot/original/walk-left/patch-walk-left.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mascot/walk-left/frames/patch-walk-left-01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions mascot/walk-left/frames/patch-walk-left-01.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mascot/walk-left/frames/patch-walk-left-02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions mascot/walk-left/frames/patch-walk-left-02.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added mascot/walk-left/frames/patch-walk-left-03.png
1 change: 1 addition & 0 deletions mascot/walk-left/frames/patch-walk-left-03.svg

Large diffs are not rendered by default.

Binary file added mascot/walk-left/frames/patch-walk-left-04.png
1 change: 1 addition & 0 deletions mascot/walk-left/frames/patch-walk-left-04.svg

Large diffs are not rendered by default.

Binary file added mascot/walk-left/frames/patch-walk-left-05.png
1 change: 1 addition & 0 deletions mascot/walk-left/frames/patch-walk-left-05.svg

Large diffs are not rendered by default.

Binary file added mascot/walk-left/frames/patch-walk-left-06.png
1 change: 1 addition & 0 deletions mascot/walk-left/frames/patch-walk-left-06.svg

Large diffs are not rendered by default.

Binary file added mascot/walk-left/frames/patch-walk-left-07.png
1 change: 1 addition & 0 deletions mascot/walk-left/frames/patch-walk-left-07.svg

Large diffs are not rendered by default.

Binary file added mascot/walk-left/frames/patch-walk-left-08.png
1 change: 1 addition & 0 deletions mascot/walk-left/frames/patch-walk-left-08.svg

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions mascot/walk-left/patch-walk-left-animated.svg

Large diffs are not rendered by default.

Binary file added mascot/walk-left/patch-walk-left-animated.webp
Binary file added mascot/walk-left/patch-walk-left.png
1 change: 1 addition & 0 deletions mascot/walk-left/patch-walk-left.svg

Large diffs are not rendered by default.

47 changes: 45 additions & 2 deletions site/scripts/cut-mascot.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
//
// The loops are built from the frames as animated SVGs (CSS only, plays in <img>) and animated WebPs.
//
// Run from site/ after replacing a sheet: node scripts/cut-mascot.mjs [concept|walk]
// walk-left/ is the approved left-facing walk: every walk file mirrored exactly (vectors, pixels and
// loops), so Patch can head left without anyone flipping the artwork by hand. It is rebuilt with the
// walk sheet, or on its own from the current walk files.
//
// Run from site/ after replacing a sheet: node scripts/cut-mascot.mjs [concept|walk|walk-left]
// (no argument rebuilds both sheets; tracing the concept sheet takes a few minutes)
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
import { mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { gzipSync } from 'node:zlib';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
Expand Down Expand Up @@ -294,3 +298,42 @@ for (const s of SHEETS.filter((s) => !only || s.sheet.includes(only))) {

console.log(`cut-mascot: ${s.sheet} -> ${s.hero.out}, ${s.frames.length} frames, ${s.animated}`);
}

// walk-left/: the walk files mirrored. SVGs get one mirroring group around their artwork, PNGs are
// flopped, and the loops are rebuilt from the flopped frames with the walk's own encoder settings.
const left = (out) => out.replace(/^walk\//, 'walk-left/').replace('patch-walk', 'patch-walk-left');
const mirrorSvg = (svg, title) =>
svg.replace(/^([\s\S]*?viewBox="0 0 (\d+) \d+"[\s\S]*?<title id="t">)[\s\S]*?(<\/title>(?:<style>[\s\S]*?<\/style>)?)([\s\S]*)<\/svg>\s*$/,
(_, head, width, close, body) => `${head}${title}${close}<g transform="matrix(-1 0 0 1 ${width} 0)">${body}</g></svg>\n`);

async function mirrorWalk(s) {
for (const root of [[], ['original'], ['compressed']]) rmSync(at(...root, 'walk-left'), { recursive: true, force: true });
const title = 'Patch, the Space Works mascot, walking left';
for (const out of [s.hero.out, ...s.frames.map(([f]) => f)]) {
const full = mirrorSvg(readFileSync(at(`${out}.svg`), 'utf8'), title);
write(at(`${left(out)}.svg`), full);
write(at(`${left(out)}.png`), await sharp(at(`${out}.png`)).flop().png({ compressionLevel: 9 }).toBuffer());
write(at('original', `${left(out)}.png`), await sharp(at('original', `${out}.png`)).flop().png({ compressionLevel: 9 }).toBuffer());
write(at('compressed', `${left(out)}.svg`), mirrorSvg(readFileSync(at('compressed', `${out}.svg`), 'utf8'), title));
write(at('compressed', `${left(out)}.svgz`), gzipSync(full, { level: 9 }));
}
const loopTitle = 'Patch, the Space Works mascot, walking left with a wrench';
const full = mirrorSvg(readFileSync(at(`${s.animated}.svg`), 'utf8'), loopTitle);
write(at(`${left(s.animated)}.svg`), full);
write(at('compressed', `${left(s.animated)}.svg`), mirrorSvg(readFileSync(at('compressed', `${s.animated}.svg`), 'utf8'), loopTitle));
write(at('compressed', `${left(s.animated)}.svgz`), gzipSync(full, { level: 9 }));
const [fw, fh] = s.frameSize;
const delay = s.timeline.map(([, ms]) => ms);
const sequence = (file) => Promise.all(s.timeline.map(([f]) => file(s.frames[f][0])));
const flopped = (scale) => sequence((out) => sharp(at(`${out}.png`)).flop().resize(fw * scale, fh * scale).png().toBuffer());
const webp = async (inputs, opts, file) => {
mkdirSync(dirname(file), { recursive: true });
await sharp(inputs, { join: { animated: true } }).webp({ loop: 0, delay, effort: 6, ...opts }).toFile(file);
};
await webp(await flopped(2), { quality: 92, alphaQuality: 100 }, at(`${left(s.animated)}.webp`));
await webp(await sequence((out) => sharp(at('original', `${out}.png`)).flop().png().toBuffer()), { lossless: true }, at('original', `${left(s.animated)}.webp`));
await webp(await flopped(1), { quality: 80, alphaQuality: 90 }, at('compressed', `${left(s.animated)}.webp`));
console.log(`cut-mascot: walk -> walk-left, ${s.frames.length} frames, ${left(s.animated)}`);
}

if (!only || only === 'walk' || only === 'walk-left') await mirrorWalk(SHEETS.find((s) => s.align === 'shadow'));
5 changes: 5 additions & 0 deletions site/src/components/MascotGallery.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const files = [
{ show: 'patch-animated.webp', name: 'Patch, sneeze loop', use: '7-second loop', surface: 'white', downloads: ['patch-animated.webp', 'patch-animated.svg', 'original/patch-animated.webp', 'compressed/patch-animated.svg'] },
{ show: 'walk/patch-walk.png', name: 'Patch, walking', use: 'On its way', surface: 'cream', downloads: ['walk/patch-walk.svg', 'walk/patch-walk.png', 'original/walk/patch-walk.png', 'compressed/walk/patch-walk.svg'] },
{ show: 'walk/patch-walk-animated.webp', name: 'Patch, walk cycle', use: '8 frames, 0.96 s', surface: 'sky', downloads: ['walk/patch-walk-animated.webp', 'walk/patch-walk-animated.svg', 'original/walk/patch-walk-animated.webp', 'compressed/walk/patch-walk-animated.svg'] },
{ show: 'walk-left/patch-walk-left-animated.webp', name: 'Patch, walking left', use: 'Heading left', surface: 'sky', downloads: ['walk-left/patch-walk-left-animated.webp', 'walk-left/patch-walk-left-animated.svg', 'original/walk-left/patch-walk-left-animated.webp', 'compressed/walk-left/patch-walk-left-animated.svg'] },
{ show: 'concept/patch-concept-sheet.webp', name: 'Concept sheet', use: 'Pose and sneeze loop', surface: 'cream', downloads: ['concept/patch-concept-sheet.webp'] },
{ show: 'concept/patch-walk-sheet.webp', name: 'Walk sheet', use: 'Walk cycle', surface: 'cream', downloads: ['concept/patch-walk-sheet.webp'] },
];
Expand All @@ -31,6 +32,10 @@ const loops = [
label: 'Walk cycle frames',
frames: [1, 2, 3, 4, 5, 6, 7, 8].map((n) => [`walk/frames/patch-walk-0${n}`, `${((n - 1) * 0.12).toFixed(2)} s`, `Step ${n}`]),
},
{
label: 'Walking left frames',
frames: [1, 2, 3, 4, 5, 6, 7, 8].map((n) => [`walk-left/frames/patch-walk-left-0${n}`, `${((n - 1) * 0.12).toFixed(2)} s`, `Step ${n}`]),
},
];
// "SVG", "PNG", "Original PNG", "Lite SVG".
const label = (f: string) => {
Expand Down
7 changes: 6 additions & 1 deletion site/src/content/docs/brand/mascot.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Nothing was redrawn. Each piece comes three ways:
| Sneeze loop | `patch-animated.svg`, `patch-animated.webp`, and eight frames in `frames/` |
| Walking pose | `walk/patch-walk.svg`, `walk/patch-walk.png` |
| Walk cycle | `walk/patch-walk-animated.svg`, `walk/patch-walk-animated.webp`, and eight frames in `walk/frames/` |
| Walking left | `walk-left/patch-walk-left.svg`, `walk-left/patch-walk-left-animated.webp`, and eight frames in `walk-left/frames/` |

**Walking left** is the approved way to move Patch to the left. The script mirrors the walk cycle
exactly, so it shows Patch's other side; frame N lines up with walk frame N for a mid-stride turn.

The same names sit in `original/` and `compressed/`. In product UI, use the animated WebPs; they're far
lighter than the animated SVGs. Browsers only show `.svgz` when the server sends it gzip-encoded, so use
Expand Down Expand Up @@ -104,7 +108,8 @@ symbol as a combined mark.
## Keep Patch intact

1. **Don't recolour, filter or redraw** Patch. Use these files as they are.
2. **Don't stretch, skew or mirror** Patch. The coat is asymmetric on purpose.
2. **Don't stretch, skew or mirror** Patch. The coat is asymmetric on purpose. To head left, use the
`walk-left/` files; never flip the others with CSS.
3. **Don't add effects** such as shadows, glows, gradients or outlines.
4. **Don't make Patch fierce.** No bared teeth, angry brows or hunting poses.
5. **Don't scale up the PNGs or WebPs.** Use the full SVGs when you need bigger.
Expand Down
Loading