@nadicodeai/design-system 12.1.181 → 12.1.182

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/DESIGN.md CHANGED
@@ -1215,7 +1215,7 @@ The company is **nadicode**, lowercase. Its logo is the n, and the name drawn in
1215
1215
  3. **Icon.** The mark on a tile with rounded corners, in one of three fields. The brand tile, the white n on `{colors.identity-cobalto}`, is for favicons, avatars and the website's app icon, and is the one place the brand colour fills a logo's field. Where the platform crops the picture itself, a profile picture cut to a circle or a square and a phone's home screen, the tile has square corners and the platform draws its shape: the avatar is the brand tile, or the light or dark tile where a profile wants white or black, with the n across 46% of the side, so a circle cut keeps it whole with its clear space. The mark is never a profile picture: it has no field, and its n runs to the edges a crop cuts. The light tile, the `{colors.ink}` n on white, is a desktop app's icon, which keeps it in the system's dark appearance. The dark tile, the white n on `{colors.identity-ink}`, is the icon wherever else it sits on a dark ground: a dark slide, an app's own dark screen.
1216
1216
  4. **Signature form.** The light tile with its construction guides in `{colors.logo-guide}`: a dotted circle around the arch whose radius is the golden section of the n's width, the two centre axes, and the n's width and height, each measured 1.000. It is drawn from 128 px up, on light grounds: Nadia's desktop icon, a cover or closing slide, a poster, print; on a dark ground the dark tile stands in for it. Below 128 px a guide is a fraction of a pixel, so the icon drops them; drawn on a screen as a logo, such as at the top of an entry scene, it drops them at any size.
1217
1217
 
1218
- Alveare's mark is its own, chosen by the founder: six flat honeycomb cells, pointed at the top, stacked one, two and three into a pyramid, in the identity colours row by row from the top: cobalto in its dark step (`{colors.dark-logo-dot}`), arancio, verde, rosso, giallo, azzurro. Six cells of one comb are the hive. It sits on the dark tile everywhere, the app icon included, since Alveare opens onto black, and on a black screen it stands alone without its tile. Alveare Server's mark is a server rack of six bands, each with its light, in the same colours in the same order from the top, drawn a tenth smaller than the cells so the solid rack weighs what the open cells do beside it. Both are drawn in `apps/alveare/brand/icons/`.
1218
+ Alveare's mark is its own, chosen by the founder: six flat honeycomb cells, pointed at the top, stacked one, two and three into a pyramid, in the identity colours row by row from the top: cobalto in its dark step (`{colors.dark-logo-dot}`), arancio, verde, rosso, giallo, azzurro. Six cells of one comb are the hive. It sits on the dark tile everywhere, the app icon included, since Alveare opens onto black, and on a black screen it stands alone without its tile. Alveare Server's mark is a server rack of six bands, each with its light, in the same colours in the same order from the top, drawn a tenth smaller than the cells so the solid rack weighs what the open cells do beside it. Both are drawn from one geometry source, `@nadicodeai/design-system/assets/alveare-geometry`: Alveare's app icons and marks and `PlatformMark` render it, and nothing copies its paths.
1219
1219
 
1220
1220
  The mark and the wordmark never stand side by side: the wordmark already begins with the n, so together they read "n nadicode". Each place takes one of them.
1221
1221
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nadicodeai/design-system",
3
- "version": "12.1.181",
3
+ "version": "12.1.182",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -206,6 +206,11 @@
206
206
  "import": "./src/assets/logo-geometry.js",
207
207
  "default": "./src/assets/logo-geometry.js"
208
208
  },
209
+ "./assets/alveare-geometry": {
210
+ "types": "./src/assets/alveare-geometry.d.ts",
211
+ "import": "./src/assets/alveare-geometry.js",
212
+ "default": "./src/assets/alveare-geometry.js"
213
+ },
209
214
  "./assets/house-icons": {
210
215
  "types": "./src/assets/house-icons.d.ts",
211
216
  "import": "./src/assets/house-icons.js",
@@ -223,6 +228,7 @@
223
228
  "LICENSES",
224
229
  "src/assets/*.d.ts",
225
230
  "src/assets/logo-geometry.js",
231
+ "src/assets/alveare-geometry.js",
226
232
  "src/assets/house-icons.js",
227
233
  "src/assets/empty",
228
234
  "src/tokens",
@@ -0,0 +1,22 @@
1
+ export type AlveareToken =
2
+ | "dark-logo-dot"
3
+ | "identity-arancio"
4
+ | "identity-verde"
5
+ | "identity-rosso"
6
+ | "identity-giallo"
7
+ | "identity-azzurro";
8
+
9
+ export type AlveareGeometry = Readonly<{
10
+ viewBox: string;
11
+ nodes: readonly Readonly<{
12
+ element: "path";
13
+ attributes: Readonly<Record<string, string>>;
14
+ }>[];
15
+ }>;
16
+
17
+ /** The colour a consumer paints the shape that takes `token` with. */
18
+ export type AlvearePaint = (token: AlveareToken) => string;
19
+
20
+ export declare const ALVEARE_DRAWING: number;
21
+ export declare function alveareMarkGeometry(paint: AlvearePaint): AlveareGeometry;
22
+ export declare function alveareServerMarkGeometry(paint: AlvearePaint): AlveareGeometry;
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Framework-neutral SVG geometry for Alveare's and Alveare Server's marks.
3
+ *
4
+ * This module is the only drawing of either mark. Alveare's app icons, the marks
5
+ * its scenes and the website show, and the shared PlatformMark render these
6
+ * descriptors instead of copying paths.
7
+ *
8
+ * Both marks are drawn in one square of ALVEARE_DRAWING units, centred in it,
9
+ * so an icon places them alike; each view box crops to its own shapes. A
10
+ * consumer paints each shape by the design token it takes, which `paint`
11
+ * turns into a colour: a CSS variable in a page, a hex value in a file. The
12
+ * six tokens run from the top in the same order on both marks.
13
+ *
14
+ * Alveare's mark is six pointy-top cells of one comb, stacked one, two and
15
+ * three: on a grid of radius 31, each cell is 5 units from its neighbours.
16
+ * Alveare Server's is a rack 160 units square of six bands 6 apart, its outer
17
+ * corners of radius 18, each band with a light of radius 4.4 cut out 50 units
18
+ * right of centre; it is drawn at nine tenths, so the solid rack weighs what
19
+ * the open cells do beside it.
20
+ */
21
+
22
+ const freeze = (geometry) => {
23
+ geometry.nodes.forEach((node) => {
24
+ Object.freeze(node.attributes);
25
+ Object.freeze(node);
26
+ });
27
+ Object.freeze(geometry.nodes);
28
+ return Object.freeze(geometry);
29
+ };
30
+
31
+ const round2 = (value) => Number(value.toFixed(2)).toString();
32
+
33
+ export const ALVEARE_DRAWING = 200;
34
+ const CENTRE = ALVEARE_DRAWING / 2;
35
+
36
+ const TOKENS = [
37
+ "dark-logo-dot",
38
+ "identity-arancio",
39
+ "identity-verde",
40
+ "identity-rosso",
41
+ "identity-giallo",
42
+ "identity-azzurro",
43
+ ];
44
+
45
+ const viewBox = (left, top, right, bottom) =>
46
+ [left, top, right - left, bottom - top].map(round2).join(" ");
47
+
48
+ const GRID = 31;
49
+ const CELL_GAP = 5;
50
+ const CELL_RADIUS = GRID - CELL_GAP / Math.sqrt(3);
51
+ /** Each cell's place on the grid, in cells across and rows down from the centre. */
52
+ const CELLS = [
53
+ [0, -1],
54
+ [-0.5, 0],
55
+ [0.5, 0],
56
+ [-1, 1],
57
+ [0, 1],
58
+ [1, 1],
59
+ ];
60
+
61
+ const cellCorners = ([across, down]) => {
62
+ const x = CENTRE + across * Math.sqrt(3) * GRID;
63
+ const y = CENTRE + down * 1.5 * GRID;
64
+ return Array.from({ length: 6 }, (_, corner) => {
65
+ const angle = ((-90 + 60 * corner) * Math.PI) / 180;
66
+ return [
67
+ Number(round2(x + CELL_RADIUS * Math.cos(angle))),
68
+ Number(round2(y + CELL_RADIUS * Math.sin(angle))),
69
+ ];
70
+ });
71
+ };
72
+
73
+ /** Alveare's mark: the six cells, painted from the top. */
74
+ export const alveareMarkGeometry = (paint) => {
75
+ const cells = CELLS.map(cellCorners);
76
+ const xs = cells.flat().map(([x]) => x);
77
+ const ys = cells.flat().map(([, y]) => y);
78
+ return freeze({
79
+ viewBox: viewBox(Math.min(...xs), Math.min(...ys), Math.max(...xs), Math.max(...ys)),
80
+ nodes: cells.map((corners, index) => ({
81
+ element: "path",
82
+ attributes: {
83
+ fill: paint(TOKENS[index]),
84
+ d: `M${corners.map(([x, y]) => `${x} ${y}`).join("L")}Z`,
85
+ },
86
+ })),
87
+ });
88
+ };
89
+
90
+ const RACK_BALANCE = 0.9;
91
+ const RACK_SIDE = 160;
92
+ const BAND_GAP = 6;
93
+ const CORNER = 18;
94
+ const LIGHT = 4.4;
95
+ const LIGHT_RIGHT = 50;
96
+
97
+ /** Alveare Server's mark: the rack's six bands, painted from the top, each with its light. */
98
+ export const alveareServerMarkGeometry = (paint) => {
99
+ const at = (unit) => Number(round2(CENTRE + RACK_BALANCE * (unit - CENTRE)));
100
+ const size = (unit) => round2(RACK_BALANCE * unit);
101
+ const left = CENTRE - RACK_SIDE / 2;
102
+ const right = CENTRE + RACK_SIDE / 2;
103
+ const band = (RACK_SIDE - 5 * BAND_GAP) / 6;
104
+ const r = size(CORNER);
105
+ const corner = `A${r} ${r} 0 0 1`;
106
+ const light = size(LIGHT);
107
+ const lightX = CENTRE + LIGHT_RIGHT;
108
+ return freeze({
109
+ viewBox: viewBox(at(left), at(left), at(right), at(right)),
110
+ nodes: TOKENS.map((token, index) => {
111
+ const top = left + index * (band + BAND_GAP);
112
+ const bottom = top + band;
113
+ const middle = at(top + band / 2);
114
+ let body = `M${at(left)} ${at(top)}H${at(right)}V${at(bottom)}H${at(left)}Z`;
115
+ if (index === 0) {
116
+ body = `M${at(left)} ${at(bottom)}V${at(top + CORNER)}${corner} ${at(left + CORNER)} ${at(top)}H${at(right - CORNER)}${corner} ${at(right)} ${at(top + CORNER)}V${at(bottom)}Z`;
117
+ } else if (index === TOKENS.length - 1) {
118
+ body = `M${at(left)} ${at(top)}H${at(right)}V${at(bottom - CORNER)}${corner} ${at(right - CORNER)} ${at(bottom)}H${at(left + CORNER)}${corner} ${at(left)} ${at(bottom - CORNER)}Z`;
119
+ }
120
+ const hole = `M${at(lightX + LIGHT)} ${middle}A${light} ${light} 0 1 0 ${at(lightX - LIGHT)} ${middle}A${light} ${light} 0 1 0 ${at(lightX + LIGHT)} ${middle}Z`;
121
+ return {
122
+ element: "path",
123
+ attributes: { fill: paint(token), "fill-rule": "evenodd", d: `${body} ${hole}` },
124
+ };
125
+ }),
126
+ });
127
+ };