@dstackai/sqircle 0.1.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.
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/SquircleEditor.d.ts +24 -0
- package/dist/SquircleScene.d.ts +3 -0
- package/dist/codeExport.d.ts +10 -0
- package/dist/geometry.d.ts +42 -0
- package/dist/index.d.ts +8 -0
- package/dist/palettes.d.ts +181 -0
- package/dist/sqircle.js +1339 -0
- package/dist/sqircle.js.map +1 -0
- package/dist/style.css +2 -0
- package/dist/types.d.ts +71 -0
- package/docs/README.md +26 -0
- package/docs/design/README.md +20 -0
- package/docs/design/colors.md +150 -0
- package/docs/design/geometry.md +118 -0
- package/docs/design/rendering.md +116 -0
- package/docs/design/single-squircle-states.md +121 -0
- package/docs/react/README.md +168 -0
- package/package.json +54 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Rendering Contract
|
|
2
|
+
|
|
3
|
+
This file documents gradients, edge sharpness, top-plane annotations, and the text-label technique. The current demo text spells `GPU`, but reusable React config calls the feature `text`.
|
|
4
|
+
|
|
5
|
+
For exact variant stops and CSS color variables, see [colors.md](./colors.md).
|
|
6
|
+
|
|
7
|
+
## Gradient Bboxes
|
|
8
|
+
|
|
9
|
+
All face gradients use `gradientUnits="userSpaceOnUse"` and coordinates derived from generated points:
|
|
10
|
+
|
|
11
|
+
- Top gradient bbox: `x1 = 0`, `y1 = 0`, `x2 = 800`, `y2 = 291.18`
|
|
12
|
+
- Text surface gradient: `x1 = -425.63`, `y1 = -0.1`, `x2 = 425.6`, `y2 = 0.07`, with the same stops as the matching top gradient
|
|
13
|
+
- Side gradient bbox: `x1 = 0`, `y1 = 0`, `x2 = 800`, `y2 = 301.18`, computed from top face plus side wall
|
|
14
|
+
|
|
15
|
+
Top gradients span only the top face. Side gradients span the full top-plus-wall silhouette so the shadow ramp continues in the same projected coordinate space instead of restarting on the wall.
|
|
16
|
+
|
|
17
|
+
React uses `text-surface-*` gradients to remap the top face stops into the label's local projected plane. Static fixtures still use `gpu-surface-*` ids because those pages render the GPU example glyph. These gradients are used for `textStyle: "solid"` on wireframe material, where filled text should read as top-surface paint rather than wire stroke color.
|
|
18
|
+
|
|
19
|
+
Wireframe mode uses the top gradient as the stroke gradient so upper and lower curves match. This intentionally avoids fake lighting on wireframes.
|
|
20
|
+
|
|
21
|
+
## Sharpness Edge
|
|
22
|
+
|
|
23
|
+
Every filled face has a tiny in-family edge stroke:
|
|
24
|
+
|
|
25
|
+
- `--top-edge` on the top face
|
|
26
|
+
- `--side-edge` on the side wall
|
|
27
|
+
- `--face-edge-width: 0.35`
|
|
28
|
+
- `--face-edge-opacity: 0.72`
|
|
29
|
+
|
|
30
|
+
These edge colors are set per variant and must stay in the same color family. Never use black for prism edges; it flattens the gradients.
|
|
31
|
+
|
|
32
|
+
## Draw Order
|
|
33
|
+
|
|
34
|
+
Each prism definition follows this order:
|
|
35
|
+
|
|
36
|
+
1. Side wall polygon
|
|
37
|
+
2. Top face polygon
|
|
38
|
+
|
|
39
|
+
The top face is drawn after the side wall, so it hides the back half of the extrusion without masks or z-buffering.
|
|
40
|
+
|
|
41
|
+
## Inlay
|
|
42
|
+
|
|
43
|
+
The middle-layer dashed squircle is generated from the same superellipse at `0.6 * a` and projected at `z = h`, so it lies on the top plane. The static copies are:
|
|
44
|
+
|
|
45
|
+
- `#top-inlay` for solid/transparent rendering
|
|
46
|
+
- `#top-inlay-wire` for wireframe rendering
|
|
47
|
+
|
|
48
|
+
By default, solid and transparent inlays use `stroke: var(--label-fill)`, the same contrast token as the filled text label. Wireframe inlays use `stroke: var(--top-fill)`, the same gradient token as the wireframe text outline.
|
|
49
|
+
|
|
50
|
+
`constructor.html` can override dash color per layer. `dashColor: "contrast"` keeps the default token for the material's dash symbol, while `dashColor: "white"` and `dashColor: "black"` intentionally use fixed stroke colors. Dash symbol choice follows material: solid and transparent states use `#top-inlay`; wireframe states use `#top-inlay-wire`.
|
|
51
|
+
|
|
52
|
+
## Label Source
|
|
53
|
+
|
|
54
|
+
React renders top-plane text as one live SVG `<text>` element, so component configs can pass arbitrary strings such as `text: "GPU"`, `text: "CUDA"`, or `text: "AI"`. The static fixtures still store the current `GPU` demo glyph as `#label-gpu` in `<defs>` because those pages are fixed examples.
|
|
55
|
+
|
|
56
|
+
Current label parameters:
|
|
57
|
+
|
|
58
|
+
- Label outline source: `/System/Library/Fonts/Supplemental/Arial.ttf`
|
|
59
|
+
- Label outline size: `62px`
|
|
60
|
+
- Source bbox before centering: `x = 3.2998..129.3584`, `y = -45.1377..0.7568`
|
|
61
|
+
- Centering translation before projection: `x = -66.3291`, `y = 22.1904`
|
|
62
|
+
- Centered bbox: `x = -63.0293..63.0293`, `y = -22.9473..22.9473`
|
|
63
|
+
- Label wire stroke: `1.1`
|
|
64
|
+
|
|
65
|
+
For static fixture glyphs, generate the path with `opentype.js@2.0.0`:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
const font = opentype.parse(fs.readFileSync("/System/Library/Fonts/Supplemental/Arial.ttf").buffer);
|
|
69
|
+
const path = font.getPath("GPU", 0, 0, 62, { kerning: true });
|
|
70
|
+
const box = path.getBoundingBox();
|
|
71
|
+
const tx = -((box.x1 + box.x2) / 2);
|
|
72
|
+
const ty = -((box.y1 + box.y2) / 2);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Apply `tx` and `ty` to every path command coordinate before writing the static `d` attribute. Keep `fill-rule="evenodd"` and `clip-rule="evenodd"` on the path so counters, such as the hole inside `P`, remain true holes.
|
|
76
|
+
|
|
77
|
+
## Label Transform And Paint
|
|
78
|
+
|
|
79
|
+
Every visible static label is a single `<use>` of `#label-gpu`, and every React label is a single `<text>` element. Both are transformed onto the top plane with:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
matrix(cosA, sinA, -cosA, sinA, cx, cy - h)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For the current geometry:
|
|
86
|
+
|
|
87
|
+
```text
|
|
88
|
+
matrix(0.94, 0.342, -0.94, 0.342, 400, 145.6)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The transform never changes between solid and wireframe modes; only paint changes. React and constructor defaults intentionally mirror the original generated GPU path: `textSize: 62`, `textFontFamily: "Arial, Helvetica, sans-serif"`, and `textFontWeight: 400`.
|
|
92
|
+
|
|
93
|
+
Solid label style:
|
|
94
|
+
|
|
95
|
+
- `.plane-label` uses `fill: var(--label-fill)` and `stroke: none` when `textColor` is `contrast`.
|
|
96
|
+
- `#top-inlay` uses `stroke: var(--label-fill)` so dashed inlays match the filled label color.
|
|
97
|
+
- `15 Alpha` uses `#f7fbff` because its top gradient is dark.
|
|
98
|
+
- Lighter variants use dark in-family label fills.
|
|
99
|
+
|
|
100
|
+
Wireframe label style:
|
|
101
|
+
|
|
102
|
+
- `textStyle: "wireframe"` uses the same live text element as filled mode.
|
|
103
|
+
- It sets `fill:none !important` and uses `stroke: var(--gpu-wire-stroke)`.
|
|
104
|
+
- On solid/transparent material, `--gpu-wire-stroke` comes from `textColor`; `contrast` resolves to `--label-fill`, matching filled text and solid dash.
|
|
105
|
+
- On wireframe material, `--gpu-wire-stroke` resolves to the label-local `--gpu-wire-fill` gradient.
|
|
106
|
+
- `#top-inlay-wire` also uses `stroke: var(--top-fill)` so dashed inlays match the wireframe label and prism wires.
|
|
107
|
+
- It must remain an outline around the font figures, not a single-stroke lettering replacement.
|
|
108
|
+
- Keep the outline stroke thin relative to text size. The default `labelWire` is `1.1` at `textSize: 62`, safely below the ratio where counters start merging.
|
|
109
|
+
|
|
110
|
+
Do not sample the full-face `--top-fill` ramp directly for the text outline on wireframe material; it is too large for the label geometry and makes the color read wrong. Use one text element for all text styles. Use `textColor` for solid/transparent outline paint and the label-local wire gradient for wireframe-material outline paint.
|
|
111
|
+
|
|
112
|
+
Constructor `gpuColor: "white"` and `gpuColor: "black"` remain in the static GPU example schema and map to React `textColor` in generated code. They override label paint only on solid/transparent material. On wireframe material, React `textStyle: "solid"` renders a fully opaque filled label using the text surface gradient, and `textStyle: "wireframe"` renders an outlined label using the text wire gradient.
|
|
113
|
+
|
|
114
|
+
Do not introduce `textStyle: "transparent"` as a third paint control. Legacy static configs with `gpuStyle: "transparent"` should normalize to `solid`.
|
|
115
|
+
|
|
116
|
+
Do not build filled letters from `<rect>`, `<line>`, or separate stem/bowl paths. Do not add a second label copy and do not replace the text with monoline lettering for outline mode.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Single-Squircle State Constructors
|
|
2
|
+
|
|
3
|
+
This file documents the `Single Squircle States` drawer as reusable constructors. Use these recipes when another agent wants to compose a single squircle outside the full three-layer composition.
|
|
4
|
+
|
|
5
|
+
## Shared Setup
|
|
6
|
+
|
|
7
|
+
Each single-state card uses:
|
|
8
|
+
|
|
9
|
+
```html
|
|
10
|
+
<svg class="single-squircle" viewBox="-6 -6 812 314" aria-label="...">
|
|
11
|
+
...
|
|
12
|
+
</svg>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The viewBox frames exactly one prism plus a small margin for hairline strokes. Single-state cards inherit the currently selected variant colors from `.single-drawer`, which receives the same CSS variables as `.hero-card`:
|
|
16
|
+
|
|
17
|
+
- `--top-fill`
|
|
18
|
+
- `--side-fill`
|
|
19
|
+
- `--label-fill`
|
|
20
|
+
- `--top-edge`
|
|
21
|
+
- `--side-edge`
|
|
22
|
+
- `--label-wire-width`
|
|
23
|
+
- `--face-edge-width`
|
|
24
|
+
- `--face-edge-opacity`
|
|
25
|
+
|
|
26
|
+
The global transparent/wireframe switch does not affect this drawer. The drawer always shows all constructor states at once.
|
|
27
|
+
|
|
28
|
+
## Building Blocks
|
|
29
|
+
|
|
30
|
+
| Part | SVG use | CSS class | Purpose |
|
|
31
|
+
| --- | --- | --- | --- |
|
|
32
|
+
| Solid prism | `<use href="#prism-active" />` | `.single-active` | Filled, selected-looking prism with top and side gradients |
|
|
33
|
+
| Transparent prism | `<use href="#prism-ghost" />` | `.single-ghost` inside `.single-transparent` | Translucent filled prism |
|
|
34
|
+
| Wireframe prism | `<use href="#prism-ghost" />` | `.single-ghost` inside `.single-wire` | Gradient outline with transparent faces |
|
|
35
|
+
| Solid/transparent dash | `<use href="#top-inlay" />` | `.single-inlay` | Dashed top-plane squircle using `--label-fill` |
|
|
36
|
+
| Wireframe dash | `<use href="#top-inlay-wire" />` | `.single-wire-inlay.wire-inlay` | Gradient dashed top-plane squircle |
|
|
37
|
+
| Text label | `<use href="#label-gpu" />` | `.single-label.plane-label` | Compound path label on the top plane; the current static glyph spells `GPU` |
|
|
38
|
+
|
|
39
|
+
The label always uses this transform:
|
|
40
|
+
|
|
41
|
+
```html
|
|
42
|
+
transform="matrix(0.94,0.342,-0.94,0.342,400,145.6)"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Constructor Matrix
|
|
46
|
+
|
|
47
|
+
| State | Card class | SVG children in order | Paint behavior |
|
|
48
|
+
| --- | --- | --- | --- |
|
|
49
|
+
| `Solid` | `.single-card` | `.single-active` | Filled prism using selected variant top/side gradients |
|
|
50
|
+
| `Solid Text` | `.single-card` | `.single-active`, `.single-label.plane-label` | Solid prism plus filled contrast label |
|
|
51
|
+
| `Solid Dash` | `.single-card` | `.single-active`, `.single-inlay` | Solid prism plus contrast dashed inlay |
|
|
52
|
+
| `Solid Text + Dash` | `.single-card` | `.single-active`, `.single-inlay`, `.single-label.plane-label` | Solid prism plus dashed inlay and filled label |
|
|
53
|
+
| `Transparent` | `.single-card.single-transparent` | `.single-ghost` | Translucent prism at `0.38` opacity |
|
|
54
|
+
| `Transparent Text` | `.single-card.single-transparent` | `.single-ghost`, `.single-label.plane-label` | Translucent prism plus filled label at `0.62` opacity |
|
|
55
|
+
| `Transparent Dash` | `.single-card.single-transparent` | `.single-ghost`, `.single-inlay` | Translucent prism plus contrast dashed inlay |
|
|
56
|
+
| `Transparent Text + Dash` | `.single-card.single-transparent` | `.single-ghost`, `.single-inlay`, `.single-label.plane-label` | Translucent prism plus dashed inlay and filled label |
|
|
57
|
+
| `Wireframe` | `.single-card.single-wire` | `.single-ghost` | Transparent faces, gradient wire strokes |
|
|
58
|
+
| `Wireframe Text` | `.single-card.single-wire` | `.single-ghost`, `.single-label.plane-label` | Wireframe prism plus outlined gradient label |
|
|
59
|
+
| `Wireframe Dash` | `.single-card.single-wire` | `.single-ghost`, `.single-wire-inlay.wire-inlay` | Wireframe prism plus gradient dashed inlay |
|
|
60
|
+
| `Wireframe Text + Dash` | `.single-card.single-wire` | `.single-ghost`, `.single-wire-inlay.wire-inlay`, `.single-label.plane-label` | Wireframe prism plus gradient dashed inlay and gradient label |
|
|
61
|
+
|
|
62
|
+
## Copy-Paste Recipes
|
|
63
|
+
|
|
64
|
+
Solid with text:
|
|
65
|
+
|
|
66
|
+
```html
|
|
67
|
+
<svg class="single-squircle" viewBox="-6 -6 812 314" aria-label="Solid single squircle with text">
|
|
68
|
+
<use class="single-active" href="#prism-active" />
|
|
69
|
+
<use class="single-label plane-label" href="#label-gpu" transform="matrix(0.94,0.342,-0.94,0.342,400,145.6)" />
|
|
70
|
+
</svg>
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Transparent with text and dash:
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<svg class="single-squircle" viewBox="-6 -6 812 314" aria-label="Transparent single squircle with text and dashed inlay">
|
|
77
|
+
<use class="single-ghost" href="#prism-ghost" />
|
|
78
|
+
<use class="single-inlay" href="#top-inlay" />
|
|
79
|
+
<use class="single-label plane-label" href="#label-gpu" transform="matrix(0.94,0.342,-0.94,0.342,400,145.6)" />
|
|
80
|
+
</svg>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Wireframe with text and dash:
|
|
84
|
+
|
|
85
|
+
```html
|
|
86
|
+
<svg class="single-squircle" viewBox="-6 -6 812 314" aria-label="Wireframe single squircle with text and dashed inlay">
|
|
87
|
+
<use class="single-ghost" href="#prism-ghost" />
|
|
88
|
+
<use class="single-wire-inlay wire-inlay" href="#top-inlay-wire" />
|
|
89
|
+
<use class="single-label plane-label" href="#label-gpu" transform="matrix(0.94,0.342,-0.94,0.342,400,145.6)" />
|
|
90
|
+
</svg>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
The text + dash states are reusable variants that combine the top-plane text path with a dashed inlay.
|
|
94
|
+
|
|
95
|
+
In the static single-state drawer, solid and transparent text + dash states use `--label-fill` for both the filled text path and dashed inlay. Wireframe text + dash states use `--top-fill` for both the outlined text path and dashed inlay. `constructor.html` can override GPU color, GPU style, and dash color per layer because it is the GPU example; React generated code maps those controls to `textColor`, `textStyle`, and `dashColor`.
|
|
96
|
+
|
|
97
|
+
## Ordering Rules
|
|
98
|
+
|
|
99
|
+
- Draw the prism first.
|
|
100
|
+
- Draw the inlay after the prism.
|
|
101
|
+
- Draw the label last.
|
|
102
|
+
- Use `#top-inlay` for solid/transparent states.
|
|
103
|
+
- Use `#top-inlay-wire` for wireframe states.
|
|
104
|
+
- Do not use two label copies. The same `.plane-label` object changes paint through CSS.
|
|
105
|
+
|
|
106
|
+
## Color Inheritance
|
|
107
|
+
|
|
108
|
+
The drawer must be a sibling after the hidden variant radios. The selected variant selectors target both `.hero-card` and `.single-drawer`, e.g.:
|
|
109
|
+
|
|
110
|
+
```css
|
|
111
|
+
#variant-15:checked ~ .hero-card,
|
|
112
|
+
#variant-15:checked ~ .single-drawer {
|
|
113
|
+
--top-fill: url("#top-15");
|
|
114
|
+
--side-fill: url("#side-15");
|
|
115
|
+
--label-fill: #f7fbff;
|
|
116
|
+
--top-edge: #7c5fd0;
|
|
117
|
+
--side-edge: #2d1466;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
See [colors.md](./colors.md) for the full palette contract.
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# React Component Guide
|
|
2
|
+
|
|
3
|
+
The maintainable implementation lives in `src/squircle`. Static HTML pages remain useful visual fixtures, but new constructor work should prefer the React component.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `src/squircle/SquircleScene.tsx`: renders the SVG scene.
|
|
8
|
+
- `src/squircle/SquircleEditor.tsx`: reusable editor UI that owns layer editing, preview, React code output, and optional controlled state.
|
|
9
|
+
- `src/squircle/codeExport.ts`: serializes the current editor state into a copyable `SquircleScene` React component snippet.
|
|
10
|
+
- `src/squircle/geometry.ts`: generates superellipse prism, hidden edge, inlay, label transform, and gradient bounds from constants.
|
|
11
|
+
- `src/squircle/palettes.ts`: exports palette constants.
|
|
12
|
+
- `src/squircle/types.ts`: public config and prop types.
|
|
13
|
+
- Top-plane text is rendered by `SquircleScene` as live SVG text projected with the same isometric matrix as the top face. The constructor demo uses `GPU` as example text, but component configs can pass any string.
|
|
14
|
+
- `src/App.tsx`: tiny app entry that renders `SquircleEditor`.
|
|
15
|
+
|
|
16
|
+
## Renderer Component
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
<SquircleScene
|
|
20
|
+
theme="dark"
|
|
21
|
+
layers={[
|
|
22
|
+
{
|
|
23
|
+
id: "bottom",
|
|
24
|
+
offset: { y: 176 },
|
|
25
|
+
base: { material: "wireframe", paletteId: "15" },
|
|
26
|
+
hover: { material: "solid", paletteId: "20" },
|
|
27
|
+
stroke: { wire: 1.6, face: 0.35, labelWire: 1.1 }
|
|
28
|
+
}
|
|
29
|
+
]}
|
|
30
|
+
onLayerSelect={(id) => setSelectedId(id)}
|
|
31
|
+
/>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`layers` can contain 0-N items. The array order is draw order: first item is lowest/backmost, last item is topmost/frontmost. A layer with `visible: false` is skipped without changing the other layer offsets.
|
|
35
|
+
|
|
36
|
+
## Editor Component
|
|
37
|
+
|
|
38
|
+
Use `SquircleEditor` when you want the complete constructor UI:
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
<SquircleEditor
|
|
42
|
+
initialLayers={createSquircleLayers(5)}
|
|
43
|
+
onChange={(layers) => console.log(layers)}
|
|
44
|
+
/>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The editor uses `SquircleScene` for the center preview. It can be uncontrolled with `initialLayers`, or controlled with `value` plus `onChange`. It supports 0-N layers, add/delete/clear, layer visibility, preview click selection, base/hover variant editing, stroke controls, and a copyable Code panel through `showCode`.
|
|
48
|
+
|
|
49
|
+
`showConfig` remains as a deprecated compatibility alias for `showCode`; new callers should use `showCode`. The Code panel shows actual TSX: it imports `SquircleScene`, defines a typed `SquircleLayerConfig[]`, includes the active `theme`, and renders a ready-to-use component. The icon button copies that React code to the clipboard.
|
|
50
|
+
|
|
51
|
+
Both public components accept `theme="light" | "dark"`. `SquircleScene` keeps the SVG transparent and palette-driven, but adds `.sq-theme-light` / `.sq-theme-dark` classes plus `data-theme` for host styling. `SquircleEditor` applies the theme to the whole constructor shell and passes it into the preview scene.
|
|
52
|
+
|
|
53
|
+
The editor can own theme state or be controlled:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
<SquircleEditor
|
|
57
|
+
defaultTheme="dark"
|
|
58
|
+
showThemeSwitch
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<SquircleEditor
|
|
62
|
+
theme={theme}
|
|
63
|
+
onThemeChange={setTheme}
|
|
64
|
+
/>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The constructor page shows a Light/Dark segmented control in the top bar by default. Hide it with `showThemeSwitch={false}` when the host app owns theme elsewhere.
|
|
68
|
+
|
|
69
|
+
The generated React code includes the active `theme` next to `layers` so a handoff can recreate the same constructor appearance.
|
|
70
|
+
|
|
71
|
+
Use the exporter directly when you need the same code string outside the full editor:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { createSquircleReactCode } from "./squircle";
|
|
75
|
+
|
|
76
|
+
const code = createSquircleReactCode({
|
|
77
|
+
theme: "light",
|
|
78
|
+
layers,
|
|
79
|
+
componentName: "CustomSquircle"
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Layer Variant Fields
|
|
84
|
+
|
|
85
|
+
Each layer has a required `base` variant and an optional `hover` variant. Hover is still a state swap: the component renders a `.sq-hover` group only when the resolved hover variant differs from base. If hover is absent or identical, the layer has no `.sq-has-hover`, no hover copy, and no crossfade blink.
|
|
86
|
+
|
|
87
|
+
Variant fields:
|
|
88
|
+
|
|
89
|
+
| Field | Values | Meaning |
|
|
90
|
+
| --- | --- | --- |
|
|
91
|
+
| `material` | `solid`, `transparent`, `wireframe` | Prism rendering mode |
|
|
92
|
+
| `paletteId` | `13` through `20` | Palette from `SQUIRCLE_PALETTES` |
|
|
93
|
+
| `text` | string, boolean | Render top-plane text. Pass a string such as `"GPU"` or `"CUDA"`; `true` is a compatibility shorthand for `"GPU"` |
|
|
94
|
+
| `dash` | boolean | Render the dashed inlay |
|
|
95
|
+
| `textStyle` | `solid`, `wireframe` | Filled or outlined text |
|
|
96
|
+
| `textColor` | `contrast`, `auto`, `white`, `black` | Text paint for solid/transparent material |
|
|
97
|
+
| `textSize` | number | Text font size, default `62` |
|
|
98
|
+
| `textFontFamily` | string | SVG text font family, default `Arial, Helvetica, sans-serif` |
|
|
99
|
+
| `textFontWeight` | string, number | SVG text font weight, default `400` |
|
|
100
|
+
| `dashColor` | `contrast`, `auto`, `white`, `black` | Dash paint for solid/transparent material |
|
|
101
|
+
| `stroke` | partial `SquircleStrokeConfig` | Per-variant stroke overrides |
|
|
102
|
+
|
|
103
|
+
`auto` is accepted as a friendly alias for `contrast`. `gpu`, `gpuStyle`, and `gpuColor` are deprecated aliases accepted only for older snippets; new configs and generated code should use `text`, `textStyle`, and `textColor`.
|
|
104
|
+
|
|
105
|
+
## Stroke Parameters
|
|
106
|
+
|
|
107
|
+
Stroke widths and opacities are explicit data, not hard-coded CSS side effects:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
{
|
|
111
|
+
face: 0.35,
|
|
112
|
+
faceOpacity: 0.72,
|
|
113
|
+
wire: 1.6,
|
|
114
|
+
wireOpacity: 0.88,
|
|
115
|
+
hidden: 1.2,
|
|
116
|
+
hiddenOpacity: 0.28,
|
|
117
|
+
dash: 2.2,
|
|
118
|
+
wireDash: 1.6,
|
|
119
|
+
labelWire: 1.1
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Set `layer.stroke` for layer-wide defaults. Set `base.stroke` or `hover.stroke` only when a particular state needs to override that layer. Geometry does not move when strokes change.
|
|
124
|
+
|
|
125
|
+
## Palettes
|
|
126
|
+
|
|
127
|
+
`SQUIRCLE_PALETTES` is the source of truth for React:
|
|
128
|
+
|
|
129
|
+
- `top`: top-face gradient stops
|
|
130
|
+
- `side`: side-wall gradient stops
|
|
131
|
+
- `textWire`: text-local wireframe gradient stops
|
|
132
|
+
- `labelFill`: automatic annotation color
|
|
133
|
+
- `topEdge` and `sideEdge`: hairline edge strokes
|
|
134
|
+
- `swatch`: UI preview colors
|
|
135
|
+
|
|
136
|
+
Do not invent colors inside a layer config. Add new palettes to `palettes.ts` and update [colors.md](../design/colors.md).
|
|
137
|
+
|
|
138
|
+
## Geometry
|
|
139
|
+
|
|
140
|
+
`createSquircleGeometry()` implements the math from [geometry.md](../design/geometry.md):
|
|
141
|
+
|
|
142
|
+
- superellipse exponent `n = 12`
|
|
143
|
+
- samples `N = 160`
|
|
144
|
+
- projection angle `20deg`
|
|
145
|
+
- prism height `10`
|
|
146
|
+
- inlay scale `0.6`
|
|
147
|
+
|
|
148
|
+
The component generates polygons at render time. Do not paste generated point lists into React source.
|
|
149
|
+
|
|
150
|
+
## 0-N Layer Helpers
|
|
151
|
+
|
|
152
|
+
Use `createSquircleLayers(count)` for quick default layers and `reflowLayerOffsets(layers, gap)` when adding/removing layers in an editor. These helpers preserve the convention that the top layer has `offset.y = 0` and lower layers move downward by `gap`.
|
|
153
|
+
|
|
154
|
+
## Verification
|
|
155
|
+
|
|
156
|
+
Run:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
npm run typecheck
|
|
160
|
+
npm run build
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Then open `/react.html` from the Vite dev server and verify:
|
|
164
|
+
|
|
165
|
+
- empty layer arrays render an empty SVG without crashing.
|
|
166
|
+
- adding layers keeps array order as draw order.
|
|
167
|
+
- no-op hover layers do not blink.
|
|
168
|
+
- wireframe text outline uses the text-local gradient, not a single-stroke replacement.
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dstackai/sqircle",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React components for precise isometric squircle-prism SVG compositions.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/dstackai/sqircle.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/dstackai/sqircle#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/dstackai/sqircle/issues"
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": [
|
|
16
|
+
"**/*.css"
|
|
17
|
+
],
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md",
|
|
21
|
+
"docs/react",
|
|
22
|
+
"docs/design"
|
|
23
|
+
],
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/sqircle.js"
|
|
28
|
+
},
|
|
29
|
+
"./style.css": "./dist/style.css"
|
|
30
|
+
},
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"module": "./dist/sqircle.js",
|
|
33
|
+
"scripts": {
|
|
34
|
+
"dev": "vite --host 127.0.0.1",
|
|
35
|
+
"build": "npm run typecheck && npm run build:lib",
|
|
36
|
+
"build:demo": "tsc --noEmit && vite build",
|
|
37
|
+
"build:lib": "vite build --config vite.lib.config.ts && npm run build:types",
|
|
38
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"react": ">=18.0.0",
|
|
43
|
+
"react-dom": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/react": "^19.2.17",
|
|
47
|
+
"@types/react-dom": "^19.2.3",
|
|
48
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
49
|
+
"react": "^19.0.0",
|
|
50
|
+
"react-dom": "^19.0.0",
|
|
51
|
+
"typescript": "^6.0.3",
|
|
52
|
+
"vite": "^8.1.0"
|
|
53
|
+
}
|
|
54
|
+
}
|