sheleg-design-skill 1.41.0 → 1.42.0

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.
Files changed (41) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +5 -4
  3. package/bin/cli.js +6 -2
  4. package/cursor/rules/sheleg-design.mdc +6 -1
  5. package/kits/proscenium/.design-sync/config.json +14 -0
  6. package/kits/proscenium/.design-sync/conventions.md +50 -0
  7. package/kits/proscenium/README.md +27 -0
  8. package/kits/proscenium/package.json +29 -0
  9. package/kits/proscenium/src/Button.md +23 -0
  10. package/kits/proscenium/src/Button.tsx +33 -0
  11. package/kits/proscenium/src/Card.md +26 -0
  12. package/kits/proscenium/src/Card.tsx +24 -0
  13. package/kits/proscenium/src/Chip.md +18 -0
  14. package/kits/proscenium/src/Chip.tsx +25 -0
  15. package/kits/proscenium/src/Frame.md +23 -0
  16. package/kits/proscenium/src/Frame.tsx +25 -0
  17. package/kits/proscenium/src/Heading.md +20 -0
  18. package/kits/proscenium/src/Heading.tsx +19 -0
  19. package/kits/proscenium/src/Rule.md +16 -0
  20. package/kits/proscenium/src/Rule.tsx +18 -0
  21. package/kits/proscenium/src/Skeleton.md +17 -0
  22. package/kits/proscenium/src/Skeleton.tsx +17 -0
  23. package/kits/proscenium/src/Stage.md +22 -0
  24. package/kits/proscenium/src/Stage.tsx +15 -0
  25. package/kits/proscenium/src/Stat.md +17 -0
  26. package/kits/proscenium/src/Stat.tsx +17 -0
  27. package/kits/proscenium/src/StatusDot.md +20 -0
  28. package/kits/proscenium/src/StatusDot.tsx +19 -0
  29. package/kits/proscenium/src/index.ts +23 -0
  30. package/kits/proscenium/src/styles.css +589 -0
  31. package/kits/proscenium/tsconfig.json +15 -0
  32. package/package.json +2 -2
  33. package/plugins/sheleg-design/.claude-plugin/plugin.json +2 -2
  34. package/plugins/sheleg-design/commands/sheleg-design.md +1 -1
  35. package/plugins/sheleg-design/skills/sheleg-design/DESIGN_SYNC_BRIDGE.md +1 -1
  36. package/plugins/sheleg-design/skills/sheleg-design/MOBILE_SURFACES.md +1 -1
  37. package/plugins/sheleg-design/skills/sheleg-design/SKILL.md +5 -4
  38. package/plugins/sheleg-design/skills/sheleg-design/SURFACE_COMPOSITION.md +1 -1
  39. package/plugins/sheleg-design/skills/sheleg-design/styles/proscenium.md +312 -0
  40. package/plugins/sheleg-design/skills/sheleg-design/styles/showroom.md +9 -0
  41. package/plugins/sheleg-design/skills/sheleg-design/styles/tokens/proscenium.css +225 -0
package/CHANGELOG.md CHANGED
@@ -4,6 +4,57 @@ All notable changes to this project are documented in this file. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
5
5
  follow [SemVer](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [1.42.0] - 2026-08-17
8
+
9
+ **The twenty-eighth pack, and the first one measured for its tempo rather than its
10
+ surface.** `proscenium` is extracted from [mailmodo.com](https://www.mailmodo.com/), read
11
+ off live computed styles in a headless Chrome at 1440×1000 and at 390, 768 and 1024 for
12
+ the ramp. What it takes from the reference is the *cadence* — two acts, then the same call
13
+ to action, again, with one dark act at the middle — which is why it forks against
14
+ `showroom` on tempo rather than on look.
15
+
16
+ ### Added
17
+
18
+ - **`proscenium`**, from [mailmodo.com](https://www.mailmodo.com/) — a white field
19
+ carrying two cool acts and **one deep indigo act at the middle** (`#07061d` to `#2a0b78`
20
+ at 86.41%, stops measured), ink that is an indigo rather than a grey, an electric violet
21
+ at `#5a45fe` filling a control that stays **nearly square at 4px against cards at 16**,
22
+ one family at nine weights, and a framed product panel the fold cuts off. Widened
23
+ contract, addressable origin, every stated ratio recomputed by the palette gate.
24
+ - **A reference kit**, with the six-component spine plus `StatusDot`, `Skeleton`, `Frame`
25
+ and this pack's own `Stage` — the one dark act, which the kit's stylesheet teaches to
26
+ strip elevation from any card standing inside it.
27
+
28
+ ### Three values the reference has and this pack declines, each marked at its declaration
29
+
30
+ - **The rem base.** Mailmodo steps the root font size by viewport — 10px at 390 and 768,
31
+ 11px at 1024, 13px at 1440 — and lets every rem follow. It is coherent and it overrides
32
+ the reader's own text-size preference, so the pack takes the *endpoints* (27px→62px on
33
+ the display, 28px→62px on an act heading) and ships them as clamp slopes with a rem term
34
+ in the sum.
35
+ - **The heading ink.** The reference sets headings to pure black against an indigo body
36
+ ink. The move is worth keeping and the literal is not: pure black as a field or an ink is
37
+ banned library-wide as an unfinished default and the slop lint fails on it, so the pack
38
+ ships `#05041c` — 20.18:1 on the field against black's 21.00.
39
+ - **The amber.** `#9e7613` is 4.15:1 on white, under AA for body text. The hue is the
40
+ reference's; the step down to `#8a6510` (5.32 / 4.91 / 4.68) is the pack's.
41
+
42
+ `--ok`, `--danger` and the entire dark register are pack decisions rather than
43
+ measurements — the reference paints no success and no error state and has no dark mode. The
44
+ dark register is derived from the one dark thing the reference does have: the stage act's
45
+ own two stops carry the field and the panel.
46
+
47
+ ### Fixed
48
+
49
+ - **A self-test plant that had stopped finding its target.** The core-contract remainder
50
+ fixture matched `The other \w+ answer all four`; at the twenty-eighth pack the remainder
51
+ became "twenty-one", which a bare `\w+` cannot match, so the plant changed nothing and
52
+ the self-test still reported it as caught. A fixture that cannot find its own target is a
53
+ hole in the gate, and it opens on exactly the release the plant exists to catch.
54
+
55
+ ### Ratchet
56
+
57
+ `test/floors.json` raised 2422/1287/504 → 3222/1811/615.
7
58
  ## [1.41.0] - 2026-08-17
8
59
 
9
60
  **A style pack is a token layer and a set of rules. It does not ship a button** — and until
package/README.md CHANGED
@@ -11,7 +11,7 @@ problem — invented colors, six accent hues, dark mode retrofitted later.
11
11
 
12
12
  This skill is the taste layer. It gives a coding agent **one motion
13
13
  methodology** for cinematic, scroll-driven pages, **a motion doctrine** that
14
- decides whether to animate before it decides how, and **twenty-seven locked style
14
+ decides whether to animate before it decides how, and **twenty-eight locked style
15
15
  packs** with ready-made design tokens, so what it builds reads as one system
16
16
  instead of a pile of effects.
17
17
 
@@ -71,6 +71,7 @@ into the cinematic layer, and says so in its own *Motion flavor* section.
71
71
  | `notation` | a near-white page drawn **entirely in hairlines instead of cards**, radii of 2 and 4px, a slab serif held at weight 300 against a monospace, **no bold anywhere**, an ink primary that leaves the accent free to mark what can be read, and one chamfered corner per page | **developer and technical products sold on restraint** — open source front pages, workspaces for people who dislike being sold to, documentation homes (standalone) |
72
72
  | `almanac` | **oatmeal paper rather than white**, seams at 2px and 4px with **no 1px anywhere**, a 104px display at weight 500 with a line-height below one that locks its lines into a block, uppercase mono tags notched through the edges of drawn boxes, and one object per page floating on four stacked shadow stops | **pages that assert a category** — a manifesto, a company saying what this kind of thing is, a product whose argument is editorial rather than functional (standalone) |
73
73
  | `vitrine` | a white field drawn **entirely in hairlines**, a serif display over a sans body, an ink primary so the accent stays free to mark what can be read, a grey panel that groups without lifting, and **one framed record** with a 1px inset highlight carrying the page's evidence | **the front door of a product sold on trust** — B2B software under evaluation, security and compliance surfaces, specification and comparison pages (standalone) |
74
+ | `proscenium` | a white field carrying two cool acts and **one deep indigo act at the middle**, ink that is an indigo rather than a grey, an electric violet that fills a control staying **nearly square at 4px** against cards at 16, one family at nine weights, and a framed product panel the fold cuts off | **product-led marketing front doors whose argument is a demonstration** — SaaS home pages, launch and tour pages, any page with six or more acts that needs a repeated beat (standalone) |
74
75
  | `ledger` | warm cream paper where elevation is a **1px hairline at 12% ink** and no card casts a shadow, radii of 7.5/10/15/20 nested concentrically, an **ink** primary button, and a terracotta accent forbidden from filling any control — it labels, as a 10px monospace uppercase kicker — over 32px data rows, with a seal on every card stating how its number is known | the console of a product that answers questions **about data** — AI analysts, BI surfaces, query workspaces, agents that read a warehouse and write back a figure |
75
76
 
76
77
  Each pack locks palette, type, texture, motion tokens, signature motifs and
@@ -146,7 +147,7 @@ skills.
146
147
  | `DESIGN_SYNC_BRIDGE.md` | The Claude Design contract: what a pack sends to claude.ai/design and in what shape, the rule for each of the four reference types, and the border motion does not cross |
147
148
  | `FIGMA_BRIDGE.md` | The design↔code contract: how a pack's tokens map onto Figma variable collections and modes, how to implement a design without importing raw values, and what cannot cross the border |
148
149
  | `AI_PRODUCT_PATTERNS.md` | The surfaces a model drives: the five states of a call, streaming instead of spinners, latency, provenance and uncertainty, agent confirmations, and the bans that keep it honest |
149
- | `styles/*.md` | The twenty-seven style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
150
+ | `styles/*.md` | The twenty-eight style packs — palette, type, texture, motion tokens, motifs, bans, and the traps each one carries |
150
151
  | `styles/tokens/*.css` | The ready-made token layer per pack, copied verbatim instead of transcribed (`workbench` and `field-notes` each ship a light `:root` plus a `data-theme="dark"` twin) |
151
152
  | `styles/STYLE_PACK_TEMPLATE.md` | The pack contract as a skeleton, so a new style is authored against the same headings rather than improvised |
152
153
 
@@ -217,7 +218,7 @@ cd ./ds-workbench && npm install && npm run build
217
218
  then `/design-sync` in that directory, from Claude Code. Three layers cross: the
218
219
  pack's **bans** as the design system's own README, `styles.css` built from
219
220
  `tokens/<pack>.css` verbatim, and the components — a six-name spine that is
220
- identical in all twenty-seven kits, so switching packs swaps identity rather than API,
221
+ identical in all twenty-eight kits, so switching packs swaps identity rather than API,
221
222
  plus each pack's signature parts. **Motion does not cross**, exactly as it does
222
223
  not cross into Figma: a kit is the static half of a pack, and saying so is what
223
224
  stops an agent inventing motion to fill the silence.
@@ -263,7 +264,7 @@ a pack's four widened sections used to make two gates *quieter* and still green.
263
264
  One honest limit: the npx installer is checked by asserting its runtime bundle
264
265
  walker exists, not by reading a file list — it has none by design. What proves
265
266
  it ships the right files is CI, which installs the bundle through **both**
266
- installers and `diff -r`s the result against the source, then builds all twenty-seven
267
+ installers and `diff -r`s the result against the source, then builds all twenty-eight
267
268
  kits.
268
269
 
269
270
  `test/scenarios.md` (T1–T19) is the behavioral harness: fresh subagents given a
package/bin/cli.js CHANGED
@@ -234,7 +234,7 @@ ${c("bold", "What it installs")}
234
234
  DESIGN_SYNC_BRIDGE.md the Claude Design contract (what a pack sends, and
235
235
  what does not cross)
236
236
  AI_PRODUCT_PATTERNS.md chat / agent / streaming surfaces (honest state)
237
- styles/ twenty-seven style packs — instrument-console (dark console),
237
+ styles/ twenty-eight style packs — instrument-console (dark console),
238
238
  editorial-luxury (warm editorial), workbench (light/dark
239
239
  product UI), briefing-room (dark 16:9 presentation deck),
240
240
  atrium (warm cream consumer health), orchard (friendly
@@ -276,7 +276,11 @@ ${c("bold", "What it installs")}
276
276
  almanac (oatmeal paper at 2px with no 1px anywhere and a
277
277
  display set below a line-height of one), vitrine (a white
278
278
  hairline field with a serif display, an ink primary, and
279
- one framed record carrying the evidence) —
279
+ one framed record carrying the evidence),
280
+ proscenium (a white field with two cool acts and one deep
281
+ indigo act at the middle, a violet filling a control that
282
+ stays nearly square at 4px against cards at 16, and a
283
+ framed product panel the fold cuts off) —
280
284
  plus a ready-made token CSS per pack and
281
285
  STYLE_PACK_TEMPLATE.md for authoring more
282
286
  `);
@@ -86,7 +86,12 @@ four stacked shadow stops, for pages that assert a category;
86
86
  vitrine — a white field drawn entirely in hairlines, a serif display over a sans
87
87
  body, an ink primary, a grey panel that groups without lifting, and one framed
88
88
  record with a 1px inset highlight carrying the page's evidence, for the front
89
- door of a product sold on trust);
89
+ door of a product sold on trust;
90
+ proscenium — a white field carrying two cool acts and one deep indigo act at the
91
+ middle, an electric violet filling a control that stays nearly square at 4px
92
+ against cards at 16, one family at nine weights, and a framed product panel the
93
+ fold cuts off, for product-led marketing front doors whose argument is a
94
+ demonstration);
90
95
  otherwise follow the contract below (self-contained on purpose).
91
96
 
92
97
  ## Whether to animate at all — before how
@@ -0,0 +1,14 @@
1
+ {
2
+ "pkg": "@sheleg-design/proscenium",
3
+ "globalName": "ShelegProscenium",
4
+ "shape": "package",
5
+ "buildCmd": "npm run build",
6
+ "srcDir": "src",
7
+ "tsconfig": "tsconfig.json",
8
+ "cssEntry": "src/styles.css",
9
+ "docsDir": "src",
10
+ "readmeHeader": ".design-sync/conventions.md",
11
+ "guidelinesGlob": [
12
+ "guidelines/*.md"
13
+ ]
14
+ }
@@ -0,0 +1,50 @@
1
+ # Proscenium — the contract this design system ships under
2
+
3
+ **Register.** Choose Proscenium for **product-led marketing front doors whose
4
+ argument is a demonstration**: SaaS home pages, launch and tour pages, any page with
5
+ six or more acts that needs a repeated beat. Light is the default register and dark
6
+ is a first-class twin; both come from the same tokens, so build every screen against
7
+ `var(--…)` and never a literal.
8
+
9
+ **The page is a sequence of acts on a fixed beat.** Two acts, then the same call to
10
+ action, again. The reader is never more than two acts from the next one, and the
11
+ cadence — not a divider or a colour — is what keeps a long page legible.
12
+
13
+ **One dark act, at the middle.** `--stage` is a measured gradient and the pack allows
14
+ exactly one block of it per page. A second makes the page read as a section list, and
15
+ it is the most likely drift because the block is the easiest thing on the page to
16
+ like.
17
+
18
+ **The control stays nearly square.** Radius 4 on buttons against 16 on cards, both
19
+ measured. Closing that gap is the single fastest way to make a page in this pack look
20
+ like every other generated landing page.
21
+
22
+ **The product is on screen before any claim is made**, inside a frame the fold cuts
23
+ off. `Frame cropped` is the signature: the panel runs off the viewport rather than
24
+ sitting complete inside it.
25
+
26
+ **Three shadows, three jobs.** `--shadow-card` (94px of blur, violet-tinted) carries
27
+ an argument card; `--shadow-hair` (a hard 1px 2px, no blur) carries a container card;
28
+ `--shadow-control` belongs to the primary button. A control never takes the card's
29
+ shadow and a card never takes the control's. Inside the dark act there is no shadow
30
+ at all — at 9% on a gradient it reads as dirt.
31
+
32
+ **Status is never by colour alone.** Every state is a dot or an icon **plus a word**,
33
+ and `StatusDot` makes the label a required prop so the rule cannot be skipped by
34
+ omission.
35
+
36
+ **Two status colours and the whole dark register are pack decisions, not
37
+ measurements.** The reference paints no success and no error state and has no dark
38
+ mode; the token layer marks each substitution at its declaration. Read them as
39
+ decisions, and re-check them if the reference ever ships the real thing.
40
+
41
+ **Bans** (verbatim from the pack):
42
+
43
+ - No second dark act; no scroll clock, no scrub, no parallax.
44
+ - No status by colour alone.
45
+ - No card shadow on a control, and no control shadow on a card.
46
+ - No closing the 4/16 radius gap.
47
+ - No second family, and no swapping Inter for a system stack "for now".
48
+ - No logo wall and no invented counter — a page with no real logos builds its proof
49
+ rail from product facts and leaves no hole where one would go.
50
+ - No bloom under running text; no fixed CTA bar on a phone.
@@ -0,0 +1,27 @@
1
+ # @sheleg-design/proscenium
2
+
3
+ The React reference kit for the SHELEG **Proscenium** style pack — a white field
4
+ carrying two cool acts and one deep indigo act at the middle (light default, dark
5
+ twin), an electric violet filling a control that stays nearly square at 4px against
6
+ cards at 16, and a framed product panel the fold cuts off.
7
+
8
+ It is generated from the pack, not authored beside it: `src/styles.css` opens with
9
+ `styles/tokens/proscenium.css` byte for byte, and the rules the design agent must obey
10
+ are in [`.design-sync/conventions.md`](./.design-sync/conventions.md).
11
+
12
+ ```bash
13
+ npm install && npm run build
14
+ ```
15
+
16
+ ## The spine
17
+
18
+ `Button`, `Card`, `Chip`, `Stat`, `Heading`, `Rule` — identical names, props and
19
+ types in every SHELEG kit. Switching packs swaps identity, not API.
20
+
21
+ ## This pack's own
22
+
23
+ `Frame` is the signature element — the proscenium arch, and `cropped` is the prop
24
+ that carries it. `Stage` is the one dark act, and the pack allows exactly one per
25
+ page. `StatusDot` requires its label, because this pack states status is never by
26
+ colour alone. `Skeleton` is static. See
27
+ [`styles/proscenium.md`](../../plugins/sheleg-design/skills/sheleg-design/styles/proscenium.md).
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@sheleg-design/proscenium",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json"
21
+ },
22
+ "peerDependencies": {
23
+ "react": ">=18"
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.6.0",
27
+ "@types/react": "^18.3.0"
28
+ }
29
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ category: Actions
3
+ ---
4
+
5
+ Min-height 44, radius **4**, weight 600, label at 16 — and the radius is the
6
+ point. The reference's control is nearly square while its cards sit at 16, and
7
+ that gap is what keeps a roomy page from reading as soft. Rounding this to match
8
+ the card is the single most likely edit to make a page in this pack generic.
9
+
10
+ `primary` fills with the accent, carries `--shadow-control`, and states its
11
+ label colour on the rule rather than inheriting it, because an anchor reset that
12
+ says `color: inherit` will otherwise win the label and paint ink on accent.
13
+ `secondary` is the 1px `--edge` bordered one with no shadow; `ghost` carries no
14
+ fill and takes `--accent-deep` for its label.
15
+
16
+ **Active flattens the shadow instead of moving the control.** Nothing translates
17
+ and nothing scales — a button that pops on click has left this pack.
18
+
19
+ ```tsx
20
+ <Button onClick={book}>Book a walkthrough</Button>
21
+ <Button variant="secondary" onClick={tour}>See it in action</Button>
22
+ <Button variant="ghost" size="sm" onClick={dismiss}>Not now</Button>
23
+ ```
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface ButtonProps {
4
+ /** `primary` is the accent fill — the pack allows one per view. */
5
+ variant?: 'primary' | 'secondary' | 'ghost';
6
+ size?: 'sm' | 'md' | 'lg';
7
+ disabled?: boolean;
8
+ onClick?: () => void;
9
+ children: ReactNode;
10
+ className?: string;
11
+ }
12
+
13
+ export function Button({
14
+ variant = 'primary',
15
+ size = 'md',
16
+ disabled = false,
17
+ onClick,
18
+ children,
19
+ className,
20
+ }: ButtonProps) {
21
+ return (
22
+ <button
23
+ type="button"
24
+ className={['ps-btn', `ps-btn--${variant}`, `ps-btn--${size}`, className]
25
+ .filter(Boolean)
26
+ .join(' ')}
27
+ disabled={disabled}
28
+ onClick={onClick}
29
+ >
30
+ {children}
31
+ </button>
32
+ );
33
+ }
@@ -0,0 +1,26 @@
1
+ ---
2
+ category: Surfaces
3
+ ---
4
+
5
+ Radius 16, and **two kinds that are not interchangeable.** The default is the
6
+ container card: `--panel`, a 1px `--border`, and `--shadow-hair` — the measured
7
+ hard `1px 2px 0` with no blur at all. `--argument` drops the border and takes
8
+ `--shadow-card`, which is 94px of blur at 4px of offset in a violet-tinted
9
+ black. That one is for the card carrying the act's argument, not for every card
10
+ in a grid.
11
+
12
+ **A card is for a group.** A list of statements takes a seam and no box; boxing
13
+ prose is the fastest way to make this pack look like a template.
14
+
15
+ **Inside the dark act the shadow is gone**, not softened: `--shadow-card` at 9%
16
+ is invisible on the gradient and reads as dirt. The stylesheet already swaps a
17
+ card inside `.ps-stage` to `--stage-panel` with no shadow.
18
+
19
+ ```tsx
20
+ <Card title="Managed accounts" meta="4 held">
21
+ <AccountRows />
22
+ </Card>
23
+ <Card className="ps-card--argument" title="Know who holds every account">
24
+ <AccessTable />
25
+ </Card>
26
+ ```
@@ -0,0 +1,24 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface CardProps {
4
+ title?: string;
5
+ /** Right-aligned metadata on the title row: a count, a window, a state. */
6
+ meta?: string;
7
+ children: ReactNode;
8
+ className?: string;
9
+ }
10
+
11
+ export function Card({ title, meta, children, className }: CardProps) {
12
+ const head = title !== undefined || meta !== undefined;
13
+ return (
14
+ <section className={['ps-card', className].filter(Boolean).join(' ')}>
15
+ {head && (
16
+ <div className="ps-card__head">
17
+ {title !== undefined && <h3 className="ps-card__title">{title}</h3>}
18
+ {meta !== undefined && <span className="ps-card__meta">{meta}</span>}
19
+ </div>
20
+ )}
21
+ <div className="ps-card__body">{children}</div>
22
+ </section>
23
+ );
24
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ A pill at `--r-pill`, 13px, weight 500. `neutral` sits on `--panel` inside a
6
+ `--border-strong` hairline; `accent` takes `--accent-weak` with `--accent-deep`
7
+ as its word.
8
+
9
+ **Radius 16 on a chip is a mistake, not a variant.** A 28px-tall chip at
10
+ `--r-card` is a lozenge — the pack names this one explicitly because it is what
11
+ happens when a card token gets reused for a control.
12
+
13
+ `selected` keeps its state after the pointer leaves; hover does not.
14
+
15
+ ```tsx
16
+ <Chip>Client / Acme · 10:42</Chip>
17
+ <Chip tone="accent" selected>Needs attention</Chip>
18
+ ```
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface ChipProps {
4
+ children: ReactNode;
5
+ selected?: boolean;
6
+ tone?: 'neutral' | 'accent';
7
+ className?: string;
8
+ }
9
+
10
+ export function Chip({ children, selected = false, tone = 'neutral', className }: ChipProps) {
11
+ return (
12
+ <span
13
+ className={[
14
+ 'ps-chip',
15
+ `ps-chip--${tone}`,
16
+ selected ? 'ps-chip--selected' : undefined,
17
+ className,
18
+ ]
19
+ .filter(Boolean)
20
+ .join(' ')}
21
+ >
22
+ {children}
23
+ </span>
24
+ );
25
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ **The proscenium arch** — a translucent fill, a 1px `--frame-edge` hairline and
6
+ an inset white glow around a product view. Measured off the reference's hero
7
+ container, `--shadow-frame` and `--frame-fill` together.
8
+
9
+ `cropped` is the part that carries the pack. The panel is meant to be **cut off
10
+ by the fold**: it drops its bottom padding, its bottom border and its bottom
11
+ radii so the product runs off the viewport rather than sitting complete inside
12
+ it. A page in this pack is remembered as the one where the product was already
13
+ there, and a frame that closes politely above the fold is the version nobody
14
+ remembers.
15
+
16
+ `foot` is the honesty line: a frame that shows an interface without saying what
17
+ it is drawn from is a mock wearing evidence's clothes.
18
+
19
+ ```tsx
20
+ <Frame cropped caption="What needs attention" foot="Drawn from the product's own routes">
21
+ <ConsolePanel />
22
+ </Frame>
23
+ ```
@@ -0,0 +1,25 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface FrameProps {
4
+ caption?: string;
5
+ /** The honesty line under the panel: what this view is drawn from. */
6
+ foot?: string;
7
+ /** The fold cuts the panel off — the pack's signature. See Frame.md. */
8
+ cropped?: boolean;
9
+ children: ReactNode;
10
+ className?: string;
11
+ }
12
+
13
+ export function Frame({ caption, foot, cropped = false, children, className }: FrameProps) {
14
+ return (
15
+ <figure
16
+ className={['ps-frame', cropped ? 'ps-frame--cropped' : undefined, className]
17
+ .filter(Boolean)
18
+ .join(' ')}
19
+ >
20
+ {caption !== undefined && <figcaption className="ps-frame__cap">{caption}</figcaption>}
21
+ <div className="ps-frame__glass">{children}</div>
22
+ {foot !== undefined && !cropped && <p className="ps-frame__foot">{foot}</p>}
23
+ </figure>
24
+ );
25
+ }
@@ -0,0 +1,20 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ Three levels, all fluid, all one family: level 1 is `--t-hero` (27px at 390 →
6
+ 62px at 1024, both endpoints measured), level 2 is `--t-act` (28 → 62), level 3
7
+ is `--t-feature` (28 → 39).
8
+
9
+ The weights are the hierarchy, because the family never changes: 600 on the
10
+ display and the act heading, 700 on the feature heading. The reference loads
11
+ Inter at nine weights and no second face anywhere on the page.
12
+
13
+ Level 1 is capped at 32ch, which holds it to two lines at 1440. A headline that
14
+ reaches five lines is a broken hero, not a long one.
15
+
16
+ ```tsx
17
+ <Heading level={1}>Your company runs in Telegram</Heading>
18
+ <Heading>Everything around the conversation, in one place</Heading>
19
+ <Heading level={3}>Know who holds every account</Heading>
20
+ ```
@@ -0,0 +1,19 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface HeadingProps {
4
+ /** 1 = page title (30px), 2 = section (24px), 3 = card title (16px). */
5
+ level?: 1 | 2 | 3;
6
+ children: ReactNode;
7
+ className?: string;
8
+ }
9
+
10
+ export function Heading({ level = 2, children, className }: HeadingProps) {
11
+ const Tag = `h${level}` as 'h1' | 'h2' | 'h3';
12
+ return (
13
+ <Tag
14
+ className={['ps-heading', `ps-heading--${level}`, className].filter(Boolean).join(' ')}
15
+ >
16
+ {children}
17
+ </Tag>
18
+ );
19
+ }
@@ -0,0 +1,16 @@
1
+ ---
2
+ category: Foundations
3
+ ---
4
+
5
+ A 1px `--border` — the measured seam, and the reference's most-used border by a
6
+ factor of two. `strong` is `--border-strong` and belongs where a divider has to
7
+ be *seen*: between two groups of rows, not between two rows.
8
+
9
+ Unlike the hairline packs, a rule here is not the elevation model. This pack has
10
+ three named shadows doing three different jobs, and a rule is for structure
11
+ inside a surface that is already separated.
12
+
13
+ ```tsx
14
+ <Rule />
15
+ <Rule tone="strong" />
16
+ ```
@@ -0,0 +1,18 @@
1
+ export interface RuleProps {
2
+ tone?: 'hairline' | 'strong';
3
+ className?: string;
4
+ }
5
+
6
+ export function Rule({ tone = 'hairline', className }: RuleProps) {
7
+ return (
8
+ <hr
9
+ className={[
10
+ 'ps-rule',
11
+ tone === 'strong' ? 'ps-rule--strong' : undefined,
12
+ className,
13
+ ]
14
+ .filter(Boolean)
15
+ .join(' ')}
16
+ />
17
+ );
18
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ category: Surfaces
3
+ ---
4
+
5
+ The loading idiom, and it is **static**. Blocks at `--r-inner` filled
6
+ `--panel-2`, sized to the element they stand in for — same heights, same widths,
7
+ so nothing jumps when the data lands.
8
+
9
+ No spinner under 400ms of expected wait, and never a spinner inside a frame that
10
+ has already drawn its arch: the frame is the promise that something is coming,
11
+ and a spinner inside it says the same thing twice.
12
+
13
+ ```tsx
14
+ <Skeleton width="38%" />
15
+ <Skeleton width="72%" />
16
+ <Skeleton height={64} />
17
+ ```
@@ -0,0 +1,17 @@
1
+ export interface SkeletonProps {
2
+ /** Matches the real element's height, so the layout does not jump on load. */
3
+ height?: number;
4
+ /** A CSS width — a percentage for text lines, a length for blocks. */
5
+ width?: string;
6
+ className?: string;
7
+ }
8
+
9
+ export function Skeleton({ height = 14, width = '100%', className }: SkeletonProps) {
10
+ return (
11
+ <span
12
+ className={['ps-skeleton', className].filter(Boolean).join(' ')}
13
+ style={{ height: `${height}px`, width }}
14
+ aria-hidden="true"
15
+ />
16
+ );
17
+ }
@@ -0,0 +1,22 @@
1
+ ---
2
+ category: Signature
3
+ ---
4
+
5
+ **The one dark act.** A full-bleed `--stage` gradient block — measured stops,
6
+ `#07061d` to `#2a0b78` at 86.41% — placed once at the middle of the page.
7
+
8
+ **Once.** A second `--stage` block makes the page read as a section list rather
9
+ than as a design with a middle, and it is this pack's most likely drift because
10
+ the block is the easiest thing on the page to like. The measured reference has
11
+ exactly one.
12
+
13
+ Inside it, cards separate by `--stage-panel` and a seam rather than by
14
+ elevation; the stylesheet does that swap for any `.ps-card` inside `.ps-stage`,
15
+ so a card does not have to know where it is standing.
16
+
17
+ ```tsx
18
+ <Stage>
19
+ <Heading>One operating layer for every team working in Telegram</Heading>
20
+ <UseCaseCards />
21
+ </Stage>
22
+ ```
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export interface StageProps {
4
+ children: ReactNode;
5
+ className?: string;
6
+ }
7
+
8
+ /** The one dark act. One per page — see Stage.md. */
9
+ export function Stage({ children, className }: StageProps) {
10
+ return (
11
+ <section className={['ps-stage', className].filter(Boolean).join(' ')}>
12
+ <div className="ps-stage__inner">{children}</div>
13
+ </section>
14
+ );
15
+ }
@@ -0,0 +1,17 @@
1
+ ---
2
+ category: Data
3
+ ---
4
+
5
+ Label above at `--t-meta` uppercase, tracked `--track-wide`, in `--muted`;
6
+ figure at `--t-card` weight 600 in `--ink-strong`; source below in
7
+ `--font-data`.
8
+
9
+ The label comes first so the eye lands on the figure without hunting. `source`
10
+ is optional and should almost always be given — a figure with no provenance is
11
+ the thing this library's own doctrine bans, and on a page whose whole argument
12
+ is a demonstration an unsourced number is the one element that can make the
13
+ demonstration read as a mock.
14
+
15
+ ```tsx
16
+ <Stat label="Median response" value="2h 14m" source="past 30 days" />
17
+ ```