@lincyaw/design 1.0.0-rc.0 → 1.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  Presmium's design system, shared by its outputs (the web form and the slides) and its workbench (`presmium studio`): the tokens (`--pm-*` CSS custom properties, light and dark), the classes of the shared components (`pm-ui-*`), the fonts (IBM Plex Sans, IBM Plex Mono and Source Serif 4, SIL Open Font License 1.1, from Fontsource) and the line icons. It uses no framework.
4
4
 
5
- Part of [Presmium](https://github.com/Lincyaw/presmium), which turns lectures written in MyST Markdown into web pages, slides, Jupyter notebooks and narrated lectures.
5
+ Part of [Presmium](https://github.com/Lincyaw/presmium), which compiles canonical lecture.json teaching documents into web pages, slides, Jupyter notebooks and narrated playback.
6
6
 
7
7
  ## Usage
8
8
 
9
- Plugin components do not import it: `@lincyaw/host` loads it into every page and draws the components in Vue (`PmButton`, `PmIcon` and the others); the workbench's panels draw the same classes in React (`@lincyaw/workbench`). A page outside both loads the styles itself:
9
+ Plugin components do not import it: `@lincyaw/host` loads it into every page and draws the components in React (`PmButton`, `PmIcon` and the others); the workbench's panels draw the same classes in React (`@lincyaw/workbench`). A page outside both loads the styles itself:
10
10
 
11
11
  ```ts
12
12
  import '@lincyaw/design/design.css'
@@ -20,6 +20,9 @@ button.innerHTML = `${iconMarkup('play')} Run`
20
20
  | `@lincyaw/design/design.css` | the fonts, the tokens and the components' classes |
21
21
  | `@lincyaw/design/tokens.css`, `components.css`, `fonts.css` | each part alone |
22
22
  | `ICONS`, `iconMarkup(name, size?)` | the line icons, as shapes or as SVG markup |
23
+ | `brandMarkup()` | the existing Presmium P as decorative SVG; load `design.css` for its token colors and size |
23
24
  | `COLORS`, `color(name)`, `colorToken(name)` | the names of the colour tokens, as `var(--pm-color-<name>)` or the property |
24
25
 
26
+ `pm-ui-appbar` supplies the shared application chrome, `pm-ui-appbar__context` its muted context label, and `pm-ui-toolbar` the typography and minimum height of a tool group. `pm-ui-toolbar__group` and `pm-ui-toolbar__separator` group and separate public controls. Their host arranges the bars and supplies outer spacing; these classes do not replace toolbar keyboard behavior.
27
+
25
28
  The tokens and the components are described in `docs/architecture.md` (section 5.10) of the repository.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lincyaw/design",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.2",
4
4
  "description": "Presmium's design system, shared by its outputs and its workbench: the tokens, the classes of the shared components, the fonts and the line icons, bound to no framework",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -24,10 +24,12 @@
24
24
  },
25
25
  "exports": {
26
26
  ".": "./src/index.ts",
27
+ "./mascot": "./src/assets/mascot.ts",
27
28
  "./design.css": "./src/design.css",
28
29
  "./tokens.css": "./src/tokens.css",
29
30
  "./components.css": "./src/components.css",
30
- "./fonts.css": "./src/fonts.css"
31
+ "./fonts.css": "./src/fonts.css",
32
+ "./block-preview.css": "./src/block-preview.css"
31
33
  },
32
34
  "files": [
33
35
  "src"
@@ -37,5 +39,8 @@
37
39
  "@fontsource/ibm-plex-sans": "5.3.0",
38
40
  "@fontsource/source-serif-4": "5.3.1",
39
41
  "lucide": "1.59.0"
42
+ },
43
+ "scripts": {
44
+ "mascot": "node tools/mascot/build.ts"
40
45
  }
41
46
  }
@@ -0,0 +1,4 @@
1
+ /** Presmium's existing P mark. Decorative beside the product name; geometry is independent of its host. */
2
+ export function brandMarkup(): string {
3
+ return '<svg xmlns="http://www.w3.org/2000/svg" class="pm-ui-brand-mark" viewBox="0 0 32 32" aria-hidden="true" focusable="false"><rect width="32" height="32" rx="7" fill="var(--pm-color-accent)"/><path d="M11 8h7.5a6.5 6.5 0 0 1 0 13H15v4h-4zM15 12v5h3.5a2.5 2.5 0 0 0 0-5z" fill="var(--pm-color-bg)" fill-rule="evenodd"/></svg>'
4
+ }
@@ -0,0 +1,75 @@
1
+ {
2
+ "version": 1,
3
+ "owner": "@lincyaw/design",
4
+ "ownerRole": "Registry maintainer; not the copyright owner of third-party assets.",
5
+ "scope": "Application UI assets only; project materials and generated content are excluded.",
6
+ "assets": [
7
+ {
8
+ "id": "presmium-brand-mark",
9
+ "kind": "brand-mark",
10
+ "owner": "Presmium repository contributors",
11
+ "license": "Apache-2.0",
12
+ "licenseEvidence": "../../LICENSE",
13
+ "source": "https://github.com/Lincyaw/presmium/blob/main/packages/dsh/src/client/brand.tsx",
14
+ "entry": "./brand.ts",
15
+ "usage": "Existing Presmium P geometry shared by product and authentication surfaces through brandMarkup(); dimensions and appearance follow public design tokens.",
16
+ "variants": "One inline SVG for light and dark; accent fill and background-colored P follow the host tokens."
17
+ },
18
+ {
19
+ "id": "presmium-mascot",
20
+ "kind": "mascot",
21
+ "owner": "Presmium repository contributors",
22
+ "license": "Apache-2.0",
23
+ "licenseEvidence": "../../LICENSE",
24
+ "source": "../../tools/mascot/poses.ts",
25
+ "entry": "./mascot.ts",
26
+ "usage": "The Presmium kitten (a black Bombay silhouette) showing what the assistant and long tasks are doing; key poses drawn by tools/mascot/draw.ts, played by mountMascot().",
27
+ "variants": "mascot/cat.png (black cat, light backgrounds) and mascot/cat-light.png (white cat, dark backgrounds); both 3 px per drawing unit, frames 4:3, sized by --pm-size-mascot and --pm-size-mascot-sm."
28
+ },
29
+ {
30
+ "id": "semantic-icons",
31
+ "kind": "icon-library",
32
+ "sourcePackage": "lucide",
33
+ "license": "ISC",
34
+ "source": "https://lucide.dev",
35
+ "entry": "../icons.ts",
36
+ "usage": "Semantic UI icons; consumers use the shared icon map rather than copying SVG paths."
37
+ },
38
+ {
39
+ "id": "ui-font",
40
+ "kind": "font",
41
+ "sourcePackage": "@fontsource/ibm-plex-sans",
42
+ "license": "OFL-1.1",
43
+ "source": "https://fontsource.org/fonts/ibm-plex-sans",
44
+ "entry": "../fonts.css",
45
+ "usage": "Application interface typography with the declared CJK system fallbacks."
46
+ },
47
+ {
48
+ "id": "reading-font",
49
+ "kind": "font",
50
+ "sourcePackage": "@fontsource/source-serif-4",
51
+ "license": "OFL-1.1",
52
+ "source": "https://fontsource.org/fonts/source-serif-4",
53
+ "entry": "../fonts.css",
54
+ "usage": "Document reading typography."
55
+ },
56
+ {
57
+ "id": "code-font",
58
+ "kind": "font",
59
+ "sourcePackage": "@fontsource/ibm-plex-mono",
60
+ "license": "OFL-1.1",
61
+ "source": "https://fontsource.org/fonts/ibm-plex-mono",
62
+ "entry": "../fonts.css",
63
+ "usage": "Authored teaching code and technical surfaces."
64
+ }
65
+ ],
66
+ "newAssetRequirements": [
67
+ "Register source, license, owner, usage, local entry and required light/dark variants before use.",
68
+ "For new product assets, record the author or provider, required attribution, permitted modifications and the license evidence; registry maintenance does not imply ownership or extra permissions.",
69
+ "For new product assets, register the original source file, exported variants, applicable resolution or cropping rules, Figma node URL and consuming code locations.",
70
+ "Do not invent missing authorization details for existing dependency assets; keep their declared license and distribution pipeline, and resolve unknown rights before adding new product files.",
71
+ "Keep UI images and marks under this directory; reference a named asset instead of embedding an ad hoc data URL in a business component.",
72
+ "Keep font binaries and their licenses with the declared dependency/build pipeline.",
73
+ "Do not put user uploads, generated teaching assets or credentials in this registry."
74
+ ]
75
+ }
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ // The sprite sheets are imported as modules; a bundler resolves each to the address of the image.
2
+ declare module '*.png' {
3
+ const url: string
4
+ export default url
5
+ }
@@ -0,0 +1,2 @@
1
+ // Generated by tools/mascot/build.ts from the key poses in tools/mascot/poses.ts; do not edit.
2
+ export const SHEET = {"frame":[216,162],"columns":13,"rows":22,"tickMs":80,"ground":144,"unit":3,"actions":{"idle":{"row":0,"timeline":[[0,3],[1,3],[2,3],[3,3],[4,3],[5,3],[0,2],[6,1],[7,1],[8,1],[1,3],[2,2],[9,1],[10,1],[2,2],[3,3],[4,3],[11,2],[12,10],[11,2],[5,3]]},"walk":{"row":1,"timeline":[[0,2,1.9],[1,2,1.9],[2,2,1.9],[3,2,1.9],[4,2,1.9],[5,2,1.9],[6,2,1.9],[7,2,1.9]]},"run":{"row":2,"timeline":[[0,1,3.2],[1,1,3.2],[2,1,3.2],[3,1,3.2],[4,1,3.2]]},"sit":{"row":3,"timeline":[[0,8],[1,5],[0,3],[2,1],[3,2],[4,3],[3,2],[2,1],[0,5],[5,1],[6,1],[7,1],[1,5],[0,4],[8,1],[9,1],[0,5]]},"curious":{"row":4,"timeline":[[0,4],[1,1],[2,10],[1,1],[0,3],[3,1],[4,10],[3,1],[0,5]]},"meow":{"row":5,"timeline":[[0,8],[1,1],[2,6],[1,2],[0,10]]},"groom":{"row":6,"timeline":[[0,3],[1,1],[2,2],[3,2],[2,2],[3,2],[2,2],[3,2],[4,2],[5,3],[4,2],[5,3],[2,2],[1,1],[0,4]]},"stretch":{"row":7,"timeline":[[0,6],[1,2],[2,2],[3,14],[2,2],[1,2],[0,3],[4,2],[5,12],[4,2],[0,8]]},"loaf":{"row":8,"timeline":[[0,6],[1,6],[0,6],[2,2],[0,2],[1,6],[3,3],[0,6]]},"knead":{"row":9,"timeline":[[0,3],[1,3]]},"sleep":{"row":10,"timeline":[[0,6],[1,6],[2,6],[3,6]]},"play":{"row":11,"timeline":[[0,8],[1,2],[2,2],[3,2],[4,6],[5,6]]},"pounce":{"row":12,"timeline":[[0,6],[1,2],[2,2],[1,2],[2,2],[1,1],[2,1],[0,2],[3,2,3],[4,3,6],[5,2,3],[0,3,0.8],[6,2],[7,8]]},"jump":{"row":13,"timeline":[[0,6],[1,2],[2,3],[3,2],[4,1],[5,4],[6,2],[2,3],[7,2],[0,6]]},"scared":{"row":14,"timeline":[[0,1],[1,1]]},"scratch":{"row":15,"timeline":[[0,6],[1,1],[2,1],[1,1],[2,1],[1,1],[2,1],[1,1],[2,1],[3,6],[0,6]]},"lounge":{"row":16,"timeline":[[0,8],[1,2],[2,10],[1,2],[0,8]]},"think":{"row":17,"timeline":[[0,4],[1,4],[2,4],[3,6],[4,4],[5,1],[6,3]]},"read":{"row":18,"timeline":[[0,4],[1,3],[2,3],[3,4],[4,1],[5,3],[6,3],[7,3],[8,3],[9,3]]},"write":{"row":19,"timeline":[[0,2],[1,2],[2,2],[3,3],[4,1],[5,2]]},"ask":{"row":20,"timeline":[[0,4],[1,2],[2,3],[1,2],[2,3],[1,2],[3,1],[4,4],[0,6]]},"done":{"row":21,"timeline":[[0,3],[1,2],[2,4],[1,2],[3,10],[0,6]]}}} as const
@@ -0,0 +1,65 @@
1
+ // Presmium's mascot: a black Bombay kitten in silhouette. Each action is a few drawn key poses with a timeline of how
2
+ // many ticks each one holds, like a hand-drawn sprite; the sheets and timelines are generated by
3
+ // tools/mascot/build.ts. `mascotFrame` is a pure function of the time, so a film can seek it; `mountMascot` plays it
4
+ // in an element on the page's clock. The black cat shows on light backgrounds and a white one on dark.
5
+ import catSheet from './mascot/cat.png'
6
+ import whiteCatSheet from './mascot/cat-light.png'
7
+ import { SHEET } from './mascot/sheet.ts'
8
+
9
+ export type MascotAction = keyof typeof SHEET.actions
10
+
11
+ export const MASCOT_ACTIONS = Object.keys(SHEET.actions) as MascotAction[]
12
+
13
+ // Imported as modules: a bundler gives each sheet its address (Vite a file URL, the Studio client a data URL).
14
+ const SHEETS = { light: catSheet, dark: whiteCatSheet }
15
+
16
+ /** The pose shown at `ms` into an action, its column and row on the sheet, and how far (in frame widths) the cat
17
+ * has moved forward since the action began, for a host that carries it across the page. */
18
+ export function mascotFrame(action: MascotAction, ms: number): { column: number, row: number, travel: number } {
19
+ const { row, timeline } = SHEET.actions[action] as { row: number, timeline: readonly (readonly number[])[] }
20
+ const total = timeline.reduce((sum, [, ticks = 0]) => sum + ticks, 0)
21
+ const perLoop = timeline.reduce((sum, [, ticks = 0, move = 0]) => sum + ticks * move, 0)
22
+ const ticks = Math.max(0, Math.floor(ms / SHEET.tickMs))
23
+ let n = ticks % total, travel = Math.floor(ticks / total) * perLoop
24
+ for (const [column = 0, length = 0, move = 0] of timeline) {
25
+ if (n < length) return { column, row, travel: (travel + n * move) * SHEET.unit / SHEET.frame[0] }
26
+ n -= length; travel += length * move
27
+ }
28
+ return { column: 0, row, travel: 0 }
29
+ }
30
+
31
+ /** Shows one frame of the sheet in `el`; the `pm-ui-mascot` class sizes it and picks the sheet for the theme. */
32
+ function show(el: HTMLElement, column: number, row: number) {
33
+ el.style.setProperty('--pm-mascot-x', `${column / (SHEET.columns - 1) * 100}%`)
34
+ el.style.setProperty('--pm-mascot-y', `${row / (SHEET.rows - 1) * 100}%`)
35
+ }
36
+
37
+ /**
38
+ * Plays the mascot in `el` until stopped; `set` changes the action, starting it from its beginning. Under reduced
39
+ * motion it shows the first pose of each action and stays still.
40
+ */
41
+ export function mountMascot(el: HTMLElement, action: MascotAction = 'idle') {
42
+ el.classList.add('pm-ui-mascot')
43
+ el.setAttribute('aria-hidden', 'true')
44
+ el.style.setProperty('--pm-mascot-sheet', `url("${SHEETS.light}")`)
45
+ el.style.setProperty('--pm-mascot-sheet-dark', `url("${SHEETS.dark}")`)
46
+ el.style.setProperty('--pm-mascot-columns', String(SHEET.columns))
47
+ el.style.setProperty('--pm-mascot-rows', String(SHEET.rows))
48
+ const still = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches
49
+ let current = action, start = performance.now(), frame = 0, shown = ''
50
+ const tick = (now: number) => {
51
+ const { column, row } = mascotFrame(current, still ? 0 : now - start)
52
+ const key = `${column}:${row}`
53
+ if (key !== shown) { show(el, column, row); shown = key }
54
+ if (!still) frame = requestAnimationFrame(tick)
55
+ }
56
+ tick(start)
57
+ return {
58
+ set(next: MascotAction) {
59
+ if (next === current) return
60
+ current = next; start = performance.now()
61
+ if (still) tick(start)
62
+ },
63
+ stop() { cancelAnimationFrame(frame) },
64
+ }
65
+ }
@@ -0,0 +1,20 @@
1
+ /* A native editor's read-only content widget. Selection and saving stay in the parent editor. */
2
+ .pm-ui-block-preview { min-width: 0; overflow: hidden; margin-block: var(--pm-space-3); border: var(--pm-size-hairline) solid var(--pm-color-line); border-radius: var(--pm-radius-control); background: var(--pm-color-surface); color: var(--pm-color-ink); }
3
+ .pm-ui-block-preview__header { display: flex; align-items: center; justify-content: space-between; gap: var(--pm-space-2); padding: var(--pm-space-2) var(--pm-space-3); border-block-end: var(--pm-size-hairline) solid var(--pm-color-line); }
4
+ .pm-ui-block-preview__label { display: inline-flex; align-items: center; gap: var(--pm-space-2); min-width: 0; color: var(--pm-color-muted); font-size: var(--pm-text-sm); }
5
+ .pm-ui-block-preview__viewport, .pm-ui-block-preview__frame { display: block; width: 100%; height: var(--pm-block-content-height, var(--pm-block-preview-height)); min-height: var(--pm-block-preview-min-height); max-height: var(--pm-block-preview-max-height); border: 0; background: var(--pm-color-surface); }
6
+ .pm-ui-block-preview__viewport { overflow: auto; }
7
+ .pm-ui-block-preview__viewport:focus-visible { outline: var(--pm-space-px) solid var(--pm-color-accent); outline-offset: calc(var(--pm-space-px) * -1); }
8
+ .pm-ui-block-preview__frame[inert] { pointer-events: none; }
9
+ /* Scoped to the card so that an editor's own paragraph rules (BlockNote's `.bn-default-styles p`) do not reset it. */
10
+ .pm-ui-block-preview .pm-ui-block-preview__pending { margin: 0; padding: var(--pm-space-3); font-size: var(--pm-text-sm); color: var(--pm-color-muted); }
11
+ .pm-ui-block-preview[data-status="failed"] .pm-ui-block-preview__label { color: var(--pm-color-danger); }
12
+ .pm-ui-block-preview[data-status="failed"] .pm-ui-block-preview__viewport { height: auto; min-height: 0; }
13
+
14
+ .pm-ui-block-preview__actions { display: inline-flex; align-items: center; flex-wrap: wrap; justify-content: flex-end; gap: var(--pm-space-1); }
15
+
16
+ .pm-ui-block-preview__narration { display: grid; gap: var(--pm-space-2); padding: var(--pm-space-3); border-block-start: var(--pm-size-hairline) solid var(--pm-color-line); }
17
+ .pm-ui-block-preview__narration-header { display: flex; align-items: center; justify-content: space-between; gap: var(--pm-space-2); }
18
+ .pm-ui-block-preview__step-label { min-width: 0; color: var(--pm-color-ink); font-size: var(--pm-text-sm); overflow-wrap: anywhere; }
19
+ .pm-ui-block-preview__narration-fields { display: grid; gap: var(--pm-space-2); min-width: 0; }
20
+ .pm-ui-block-preview__caption { margin: 0; color: var(--pm-color-ink); white-space: pre-wrap; overflow-wrap: anywhere; font-size: var(--pm-text-sm); }
@@ -0,0 +1,27 @@
1
+ .pm-ui-caption {
2
+ box-sizing: border-box;
3
+ max-width: var(--pm-caption-max-width);
4
+ max-height: var(--pm-caption-max-height);
5
+ overflow: auto;
6
+ padding: var(--pm-space-2) var(--pm-space-4);
7
+ border: var(--pm-size-hairline) solid var(--pm-color-line);
8
+ border-radius: var(--pm-radius-control);
9
+ background: var(--pm-color-chrome);
10
+ color: var(--pm-color-ink);
11
+ font-family: var(--pm-font-sans);
12
+ font-size: var(--pm-text-lg);
13
+ line-height: var(--pm-leading-ui);
14
+ text-align: center;
15
+ overflow-wrap: anywhere;
16
+ white-space: pre-wrap;
17
+ }
18
+ .pm-ui-caption[data-overlay] {
19
+ max-height: min(var(--pm-caption-max-height), var(--pm-caption-overlay-max-height));
20
+ }
21
+ .pm-ui-caption .pm-ui-caption__line,
22
+ .pm-ui-caption .pm-ui-caption__hint { margin: 0; }
23
+ .pm-ui-caption .pm-ui-caption__line + .pm-ui-caption__line { margin-top: var(--pm-space-1); }
24
+ .pm-ui-caption .pm-ui-caption__hint {
25
+ color: var(--pm-color-muted);
26
+ font-size: var(--pm-text-xs);
27
+ }