@xfey/tutti 0.1.31 → 0.1.33
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 +4 -3
- package/dist/server-shell/cli/args.d.ts +4 -0
- package/dist/server-shell/cli/args.js +10 -1
- package/dist/server-shell/cli/cli.js +22 -3
- package/dist/server-shell/cli/host-lifecycle.js +1 -0
- package/dist/server-shell/cli/host-server-runtime.js +2 -10
- package/dist/server-shell/cli/relay-registration.d.ts +1 -0
- package/dist/server-shell/cli/relay-registration.js +1 -0
- package/dist/server-shell/http/routes/project-api/project-timeline-projection.js +4 -4
- package/dist/server-shell/http/static-web.d.ts +1 -0
- package/dist/server-shell/http/static-web.js +14 -1
- package/dist/server-shell/local-console/browser-open.d.ts +5 -0
- package/dist/server-shell/local-console/browser-open.js +40 -0
- package/dist/server-shell/local-console/folder-picker.d.ts +21 -0
- package/dist/server-shell/local-console/folder-picker.js +109 -0
- package/dist/server-shell/local-console/index.d.ts +4 -0
- package/dist/server-shell/local-console/index.js +4 -0
- package/dist/server-shell/local-console/managed-console.d.ts +9 -0
- package/dist/server-shell/local-console/managed-console.js +94 -0
- package/dist/server-shell/local-console/project-service.d.ts +57 -0
- package/dist/server-shell/local-console/project-service.js +217 -0
- package/dist/server-shell/local-console/runtime-endpoint.d.ts +21 -0
- package/dist/server-shell/local-console/runtime-endpoint.js +74 -0
- package/dist/server-shell/local-console/server.d.ts +17 -0
- package/dist/server-shell/local-console/server.js +301 -0
- package/dist/server-shell/local-console/session.d.ts +33 -0
- package/dist/server-shell/local-console/session.js +116 -0
- package/node_modules/@tutti/relay-client/dist/tunnel-types.d.ts +3 -0
- package/node_modules/@tutti/relay-client/dist/tunnel.js +52 -0
- package/package.json +1 -1
- package/web/assets/beethoven-symphony-5-opening-ypVy5SB4.svg +62 -0
- package/web/assets/beethoven-symphony-7-allegretto-B3bc-cZD.svg +58 -0
- package/web/assets/dvorak-symphony-9-largo-BEgNL3HB.svg +50 -0
- package/web/assets/index-BcZpeSaX.css +1 -0
- package/web/assets/index-DKf4jz_E.js +29 -0
- package/web/assets/mozart-symphony-18-allegro-B5-MrLL1.svg +58 -0
- package/web/assets/tutti_avatar-BBhaZGi3.png +0 -0
- package/web/index.html +6 -3
- package/web/assets/index-BHRlhFwi.css +0 -1
- package/web/assets/index-Du5Txb6C.js +0 -29
- package/web/assets/tutti_avatar-DAVuzlig.png +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
export const LOCAL_CONSOLE_SESSION_COOKIE = "tutti_local_console_session";
|
|
3
|
+
const ACCESS_TOKEN_TTL_MS = 60_000;
|
|
4
|
+
const SESSION_TTL_MS = 12 * 60 * 60 * 1_000;
|
|
5
|
+
function createSecret() {
|
|
6
|
+
return randomBytes(32).toString("base64url");
|
|
7
|
+
}
|
|
8
|
+
function hashSecret(value) {
|
|
9
|
+
return createHash("sha256").update(value).digest("base64url");
|
|
10
|
+
}
|
|
11
|
+
function secretEquals(value, expectedHash) {
|
|
12
|
+
const actual = Buffer.from(hashSecret(value));
|
|
13
|
+
const expected = Buffer.from(expectedHash);
|
|
14
|
+
return actual.length === expected.length && timingSafeEqual(actual, expected);
|
|
15
|
+
}
|
|
16
|
+
export class LocalConsoleSessionRegistry {
|
|
17
|
+
#accessTokens = new Map();
|
|
18
|
+
#sessions = new Map();
|
|
19
|
+
#now;
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
this.#now = options.now ?? (() => new Date());
|
|
22
|
+
}
|
|
23
|
+
issueAccessToken(options) {
|
|
24
|
+
this.#purgeExpired();
|
|
25
|
+
const token = createSecret();
|
|
26
|
+
const expiresAt = this.#now().getTime() + ACCESS_TOKEN_TTL_MS;
|
|
27
|
+
this.#accessTokens.set(hashSecret(token), {
|
|
28
|
+
hash: hashSecret(token),
|
|
29
|
+
expiresAt,
|
|
30
|
+
currentDirectory: options.currentDirectory,
|
|
31
|
+
});
|
|
32
|
+
return { token, expires_at: new Date(expiresAt).toISOString() };
|
|
33
|
+
}
|
|
34
|
+
exchangeAccessToken(token) {
|
|
35
|
+
this.#purgeExpired();
|
|
36
|
+
const accessTokenHash = hashSecret(token);
|
|
37
|
+
const accessToken = this.#accessTokens.get(accessTokenHash);
|
|
38
|
+
if (accessToken === undefined ||
|
|
39
|
+
accessToken.expiresAt <= this.#now().getTime() ||
|
|
40
|
+
!secretEquals(token, accessToken.hash)) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
this.#accessTokens.delete(accessTokenHash);
|
|
44
|
+
const sessionToken = createSecret();
|
|
45
|
+
const expiresAt = this.#now().getTime() + SESSION_TTL_MS;
|
|
46
|
+
this.#sessions.set(hashSecret(sessionToken), {
|
|
47
|
+
hash: hashSecret(sessionToken),
|
|
48
|
+
csrfToken: createSecret(),
|
|
49
|
+
expiresAt,
|
|
50
|
+
currentDirectory: accessToken.currentDirectory,
|
|
51
|
+
});
|
|
52
|
+
const session = this.#sessions.get(hashSecret(sessionToken));
|
|
53
|
+
if (session === undefined) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
sessionToken,
|
|
58
|
+
csrfToken: session.csrfToken,
|
|
59
|
+
expires_at: new Date(expiresAt).toISOString(),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
readSession(token) {
|
|
63
|
+
this.#purgeExpired();
|
|
64
|
+
if (token === undefined || token.trim() === "") {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const session = this.#sessions.get(hashSecret(token));
|
|
68
|
+
if (session === undefined ||
|
|
69
|
+
session.expiresAt <= this.#now().getTime() ||
|
|
70
|
+
!secretEquals(token, session.hash)) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return session;
|
|
74
|
+
}
|
|
75
|
+
#purgeExpired() {
|
|
76
|
+
const now = this.#now().getTime();
|
|
77
|
+
for (const [key, value] of this.#accessTokens) {
|
|
78
|
+
if (value.expiresAt <= now) {
|
|
79
|
+
this.#accessTokens.delete(key);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
for (const [key, value] of this.#sessions) {
|
|
83
|
+
if (value.expiresAt <= now) {
|
|
84
|
+
this.#sessions.delete(key);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
export function parseCookieHeader(header) {
|
|
90
|
+
if (header === undefined) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
const cookies = {};
|
|
94
|
+
for (const part of header.split(";")) {
|
|
95
|
+
const separator = part.indexOf("=");
|
|
96
|
+
if (separator <= 0) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const name = part.slice(0, separator).trim();
|
|
100
|
+
const value = part.slice(separator + 1).trim();
|
|
101
|
+
if (name !== "") {
|
|
102
|
+
cookies[name] = value;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return cookies;
|
|
106
|
+
}
|
|
107
|
+
export function localConsoleSessionCookie(token, expiresAt) {
|
|
108
|
+
return [
|
|
109
|
+
`${LOCAL_CONSOLE_SESSION_COOKIE}=${token}`,
|
|
110
|
+
"Path=/",
|
|
111
|
+
"HttpOnly",
|
|
112
|
+
"SameSite=Strict",
|
|
113
|
+
`Expires=${new Date(expiresAt).toUTCString()}`,
|
|
114
|
+
].join("; ");
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -34,11 +34,13 @@ export type RelayHostTunnelRequestHandler = (request: RelayHostTunnelRequest) =>
|
|
|
34
34
|
export type RelayHostTunnelStreamRequestHandler = (request: RelayHostTunnelRequest, sink: RelayHostTunnelStreamSink) => Promise<void> | void;
|
|
35
35
|
export type RelayHostTunnelWebSocketLike = {
|
|
36
36
|
on(event: "open", listener: () => void): void;
|
|
37
|
+
on(event: "ping", listener: () => void): void;
|
|
37
38
|
on(event: "message", listener: (data: unknown) => void): void;
|
|
38
39
|
on(event: "close", listener: (code?: number, reason?: unknown) => void): void;
|
|
39
40
|
on(event: "error", listener: (error: unknown) => void): void;
|
|
40
41
|
send(data: string, callback?: (error?: Error) => void): void;
|
|
41
42
|
close(code?: number, reason?: string): void;
|
|
43
|
+
terminate(): void;
|
|
42
44
|
};
|
|
43
45
|
export type RelayHostTunnelConnector = (options: {
|
|
44
46
|
url: string;
|
|
@@ -58,6 +60,7 @@ export type ConnectRelayHostTunnelOptions = {
|
|
|
58
60
|
relayProjectRef: RelayProjectRef;
|
|
59
61
|
hostConnectionRef: HostConnectionRef;
|
|
60
62
|
hostConnectionToken: string;
|
|
63
|
+
heartbeatIntervalMs: number;
|
|
61
64
|
handleRequest: RelayHostTunnelRequestHandler;
|
|
62
65
|
handleStreamRequest?: RelayHostTunnelStreamRequestHandler;
|
|
63
66
|
connect?: RelayHostTunnelConnector;
|
|
@@ -2,6 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { redactAndTruncateText } from "@tutti/shared/utils";
|
|
3
3
|
import { chunkToBase64, internalErrorResponse, parseHostRequestFrame, parseHostStreamCancelFrame, sendFrame, sendResponse, } from "./tunnel-frames.js";
|
|
4
4
|
const loadNativeModule = createRequire(import.meta.url);
|
|
5
|
+
const HOST_LIVENESS_INTERVAL_MULTIPLIER = 4;
|
|
5
6
|
function defaultConnector(options) {
|
|
6
7
|
const wsModule = loadNativeModule("ws");
|
|
7
8
|
const WebSocketCtor = typeof wsModule === "function" ? wsModule : (wsModule.WebSocket ?? wsModule.default);
|
|
@@ -18,15 +19,23 @@ function tunnelUrl(options) {
|
|
|
18
19
|
return url.toString();
|
|
19
20
|
}
|
|
20
21
|
export function connectRelayHostTunnel(options) {
|
|
22
|
+
if (!Number.isFinite(options.heartbeatIntervalMs) || options.heartbeatIntervalMs <= 0) {
|
|
23
|
+
throw new TypeError("Relay heartbeat interval must be a positive finite number");
|
|
24
|
+
}
|
|
21
25
|
const socket = (options.connect ?? defaultConnector)({
|
|
22
26
|
url: tunnelUrl(options),
|
|
23
27
|
headers: {
|
|
24
28
|
authorization: `Bearer ${options.hostConnectionToken}`,
|
|
25
29
|
},
|
|
26
30
|
});
|
|
31
|
+
const livenessTimeoutMs = options.heartbeatIntervalMs * HOST_LIVENESS_INTERVAL_MULTIPLIER;
|
|
27
32
|
let resolveConnected;
|
|
28
33
|
let rejectConnected;
|
|
29
34
|
let connectedSettled = false;
|
|
35
|
+
let opened = false;
|
|
36
|
+
let ending = false;
|
|
37
|
+
let socketClosed = false;
|
|
38
|
+
let livenessTimer;
|
|
30
39
|
const connected = new Promise((resolve, reject) => {
|
|
31
40
|
resolveConnected = () => {
|
|
32
41
|
connectedSettled = true;
|
|
@@ -42,10 +51,38 @@ export function connectRelayHostTunnel(options) {
|
|
|
42
51
|
resolveClosed = resolve;
|
|
43
52
|
});
|
|
44
53
|
const streamControllers = new Map();
|
|
54
|
+
const clearLivenessDeadline = () => {
|
|
55
|
+
if (livenessTimer !== undefined) {
|
|
56
|
+
clearTimeout(livenessTimer);
|
|
57
|
+
livenessTimer = undefined;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const terminateSocket = () => {
|
|
61
|
+
if (ending || socketClosed) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
ending = true;
|
|
65
|
+
clearLivenessDeadline();
|
|
66
|
+
socket.terminate();
|
|
67
|
+
};
|
|
68
|
+
const refreshLivenessDeadline = () => {
|
|
69
|
+
if (!opened || ending || socketClosed) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
clearLivenessDeadline();
|
|
73
|
+
livenessTimer = setTimeout(terminateSocket, livenessTimeoutMs);
|
|
74
|
+
livenessTimer.unref?.();
|
|
75
|
+
};
|
|
45
76
|
socket.on("open", () => {
|
|
77
|
+
opened = true;
|
|
78
|
+
refreshLivenessDeadline();
|
|
46
79
|
resolveConnected();
|
|
47
80
|
});
|
|
81
|
+
socket.on("ping", () => {
|
|
82
|
+
refreshLivenessDeadline();
|
|
83
|
+
});
|
|
48
84
|
socket.on("message", (data) => {
|
|
85
|
+
refreshLivenessDeadline();
|
|
49
86
|
const cancelRequestId = parseHostStreamCancelFrame(data);
|
|
50
87
|
if (cancelRequestId !== null) {
|
|
51
88
|
streamControllers.get(cancelRequestId)?.abort();
|
|
@@ -122,9 +159,19 @@ export function connectRelayHostTunnel(options) {
|
|
|
122
159
|
socket.on("error", (error) => {
|
|
123
160
|
if (!connectedSettled) {
|
|
124
161
|
rejectConnected(error instanceof Error ? error : new Error("Relay host tunnel failed"));
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (opened) {
|
|
165
|
+
terminateSocket();
|
|
125
166
|
}
|
|
126
167
|
});
|
|
127
168
|
socket.on("close", (code, reason) => {
|
|
169
|
+
if (socketClosed) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
socketClosed = true;
|
|
173
|
+
ending = true;
|
|
174
|
+
clearLivenessDeadline();
|
|
128
175
|
for (const controller of streamControllers.values()) {
|
|
129
176
|
controller.abort();
|
|
130
177
|
}
|
|
@@ -148,6 +195,11 @@ export function connectRelayHostTunnel(options) {
|
|
|
148
195
|
connected,
|
|
149
196
|
closed,
|
|
150
197
|
close: (code = 1000, reason = "closed") => {
|
|
198
|
+
if (ending || socketClosed) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
ending = true;
|
|
202
|
+
clearLivenessDeadline();
|
|
151
203
|
socket.close(code, reason);
|
|
152
204
|
},
|
|
153
205
|
};
|
package/package.json
CHANGED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="2048" height="1180" viewBox="0 0 2048 1180" role="img" aria-labelledby="title description">
|
|
3
|
+
<title id="title">Beethoven — Symphony No. 5 / Opening — rhythmic modules</title>
|
|
4
|
+
<desc id="description">Phrase windows become a balanced, deterministically scattered set of blocks, circles, steps, dots and stripes.</desc>
|
|
5
|
+
<rect width="100%" height="100%" fill="#F8FAF9" />
|
|
6
|
+
<rect data-role="module-stripe" x="1137.35" y="691.93" width="183.59" height="21.35" rx="0.00" fill="#F0F2F1" opacity="0.822" />
|
|
7
|
+
<rect data-role="module-stripe" x="1137.35" y="728.65" width="183.59" height="21.35" rx="0.00" fill="#F0F2F1" opacity="0.822" />
|
|
8
|
+
<rect data-role="module-stripe" x="1137.35" y="765.37" width="183.59" height="21.35" rx="0.00" fill="#F0F2F1" opacity="0.822" />
|
|
9
|
+
<rect data-role="module-stripe" x="1137.35" y="802.09" width="183.59" height="21.35" rx="0.00" fill="#F0F2F1" opacity="0.822" />
|
|
10
|
+
<rect data-role="module-stripe" x="1137.35" y="838.81" width="183.59" height="21.35" rx="0.00" fill="#F0F2F1" opacity="0.822" />
|
|
11
|
+
<rect data-role="module-square" x="66.89" y="6.71" width="242.63" height="242.63" rx="0.00" fill="#E7EBE9" opacity="0.807" />
|
|
12
|
+
<ellipse data-role="module-dot" cx="1576.58" cy="708.56" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
13
|
+
<ellipse data-role="module-dot" cx="1636.88" cy="708.56" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
14
|
+
<ellipse data-role="module-dot" cx="1697.17" cy="708.56" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
15
|
+
<ellipse data-role="module-dot" cx="1576.58" cy="768.86" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
16
|
+
<ellipse data-role="module-dot" cx="1636.88" cy="768.86" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
17
|
+
<ellipse data-role="module-dot" cx="1697.17" cy="768.86" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
18
|
+
<ellipse data-role="module-dot" cx="1576.58" cy="829.16" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
19
|
+
<ellipse data-role="module-dot" cx="1636.88" cy="829.16" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
20
|
+
<ellipse data-role="module-dot" cx="1697.17" cy="829.16" rx="14.07" ry="14.07" fill="#E7EBE9" opacity="0.819" />
|
|
21
|
+
<ellipse data-role="module-circle" cx="1957.82" cy="533.45" rx="137.05" ry="137.05" fill="#DFE4E2" opacity="0.805" />
|
|
22
|
+
<polygon data-role="module-step" points="912.72,246.39 912.72,85.98 1036.91,85.98 1036.91,-12.34 1171.45,-12.34 1171.45,246.39" fill="#F0F2F1" opacity="0.807" />
|
|
23
|
+
<ellipse data-role="module-dot" cx="571.69" cy="32.03" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
24
|
+
<ellipse data-role="module-dot" cx="632.99" cy="32.03" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
25
|
+
<ellipse data-role="module-dot" cx="694.29" cy="32.03" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
26
|
+
<ellipse data-role="module-dot" cx="571.69" cy="93.34" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
27
|
+
<ellipse data-role="module-dot" cx="632.99" cy="93.34" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
28
|
+
<ellipse data-role="module-dot" cx="694.29" cy="93.34" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
29
|
+
<ellipse data-role="module-dot" cx="571.69" cy="154.64" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
30
|
+
<ellipse data-role="module-dot" cx="632.99" cy="154.64" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
31
|
+
<ellipse data-role="module-dot" cx="694.29" cy="154.64" rx="14.30" ry="14.30" fill="#F0F2F1" opacity="0.808" />
|
|
32
|
+
<rect data-role="module-square" x="1762.53" y="948.05" width="241.91" height="241.91" rx="0.00" fill="#EDF0EE" opacity="0.796" />
|
|
33
|
+
<rect data-role="module-square" x="811.20" y="520.41" width="241.45" height="241.45" rx="0.00" fill="#DFE4E2" opacity="0.796" />
|
|
34
|
+
<rect data-role="module-square" x="319.82" y="722.09" width="283.55" height="283.55" rx="0.00" fill="#DFE4E2" opacity="0.808" />
|
|
35
|
+
<polygon data-role="module-step" points="1832.59,134.41 1981.10,134.41 1981.10,249.39 2072.12,249.39 2072.12,373.94 1832.59,373.94" fill="#F0F2F1" opacity="0.805" />
|
|
36
|
+
<ellipse data-role="module-circle" cx="526.42" cy="417.47" rx="116.56" ry="116.56" fill="#E7EBE9" opacity="0.800" />
|
|
37
|
+
<polygon data-role="module-step" points="1410.90,1221.55 1259.09,1221.55 1259.09,1104.01 1166.04,1104.01 1166.04,976.69 1410.90,976.69" fill="#EDF0EE" opacity="0.803" />
|
|
38
|
+
<ellipse data-role="module-circle" cx="1404.78" cy="511.10" rx="120.93" ry="120.93" fill="#DFE4E2" opacity="0.803" />
|
|
39
|
+
<polygon data-role="module-step" points="213.74,991.79 213.74,1133.02 104.41,1133.02 104.41,1219.58 -14.04,1219.58 -14.04,991.79" fill="#E7EBE9" opacity="0.799" />
|
|
40
|
+
<ellipse data-role="module-dot" cx="1372.88" cy="71.41" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
41
|
+
<ellipse data-role="module-dot" cx="1440.03" cy="71.41" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
42
|
+
<ellipse data-role="module-dot" cx="1507.18" cy="71.41" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
43
|
+
<ellipse data-role="module-dot" cx="1372.88" cy="138.55" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
44
|
+
<ellipse data-role="module-dot" cx="1440.03" cy="138.55" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
45
|
+
<ellipse data-role="module-dot" cx="1507.18" cy="138.55" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
46
|
+
<ellipse data-role="module-dot" cx="1372.88" cy="205.70" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
47
|
+
<ellipse data-role="module-dot" cx="1440.03" cy="205.70" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
48
|
+
<ellipse data-role="module-dot" cx="1507.18" cy="205.70" rx="15.67" ry="15.67" fill="#EDF0EE" opacity="0.808" />
|
|
49
|
+
<rect data-role="module-stripe" x="8.18" y="441.70" width="22.25" height="229.64" rx="0.00" fill="#DFE4E2" opacity="0.802" />
|
|
50
|
+
<rect data-role="module-stripe" x="46.45" y="441.70" width="22.25" height="229.64" rx="0.00" fill="#DFE4E2" opacity="0.802" />
|
|
51
|
+
<rect data-role="module-stripe" x="84.73" y="441.70" width="22.25" height="229.64" rx="0.00" fill="#DFE4E2" opacity="0.802" />
|
|
52
|
+
<rect data-role="module-stripe" x="123.00" y="441.70" width="22.25" height="229.64" rx="0.00" fill="#DFE4E2" opacity="0.802" />
|
|
53
|
+
<rect data-role="module-stripe" x="161.27" y="441.70" width="22.25" height="229.64" rx="0.00" fill="#DFE4E2" opacity="0.802" />
|
|
54
|
+
<rect data-role="module-stripe" x="199.54" y="441.70" width="22.25" height="229.64" rx="0.00" fill="#DFE4E2" opacity="0.802" />
|
|
55
|
+
<rect data-role="module-stripe" x="746.51" y="966.35" width="24.83" height="213.57" rx="0.00" fill="#F0F2F1" opacity="0.819" />
|
|
56
|
+
<rect data-role="module-stripe" x="789.23" y="966.35" width="24.83" height="213.57" rx="0.00" fill="#F0F2F1" opacity="0.819" />
|
|
57
|
+
<rect data-role="module-stripe" x="831.94" y="966.35" width="24.83" height="213.57" rx="0.00" fill="#F0F2F1" opacity="0.819" />
|
|
58
|
+
<rect data-role="module-stripe" x="874.65" y="966.35" width="24.83" height="213.57" rx="0.00" fill="#F0F2F1" opacity="0.819" />
|
|
59
|
+
<rect data-role="module-stripe" x="917.37" y="966.35" width="24.83" height="213.57" rx="0.00" fill="#F0F2F1" opacity="0.819" />
|
|
60
|
+
<polygon data-role="edge-arc" points="0.00,1180.00 -0.00,822.29 23.40,823.05 46.69,825.35 69.79,829.16 92.58,834.48 114.98,841.27 136.89,849.52 158.21,859.18 178.86,870.21 198.73,882.57 217.76,896.21 235.86,911.06 252.94,927.06 268.94,944.14 283.79,962.24 297.43,981.27 309.79,1001.14 320.82,1021.79 330.48,1043.11 338.73,1065.02 345.52,1087.42 350.84,1110.21 354.65,1133.31 356.95,1156.60 357.71,1180.00" fill="#EDF0EE" opacity="0.640" />
|
|
61
|
+
<polygon data-role="edge-arc" points="2048.00,0.00 2048.00,381.79 2023.03,380.98 1998.17,378.53 1973.52,374.46 1949.18,368.79 1925.28,361.53 1901.89,352.73 1879.14,342.42 1857.10,330.64 1835.89,317.45 1815.58,302.90 1796.27,287.05 1778.03,269.97 1760.95,251.73 1745.10,232.42 1730.55,212.11 1717.36,190.90 1705.58,168.86 1695.27,146.11 1686.47,122.72 1679.21,98.82 1673.54,74.48 1669.47,49.83 1667.02,24.97 1666.21,0.00" fill="#DFE4E2" opacity="0.600" />
|
|
62
|
+
</svg>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="2048" height="1180" viewBox="0 0 2048 1180" role="img" aria-labelledby="title description">
|
|
3
|
+
<title id="title">Beethoven — Symphony No. 7 / Allegretto — rhythmic modules</title>
|
|
4
|
+
<desc id="description">Phrase windows become a balanced, deterministically scattered set of blocks, circles, steps, dots and stripes.</desc>
|
|
5
|
+
<rect width="100%" height="100%" fill="#F8FAF9" />
|
|
6
|
+
<ellipse data-role="module-dot" cx="4.75" cy="147.17" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
7
|
+
<ellipse data-role="module-dot" cx="60.50" cy="147.17" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
8
|
+
<ellipse data-role="module-dot" cx="116.25" cy="147.17" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
9
|
+
<ellipse data-role="module-dot" cx="4.75" cy="202.91" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
10
|
+
<ellipse data-role="module-dot" cx="60.50" cy="202.91" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
11
|
+
<ellipse data-role="module-dot" cx="116.25" cy="202.91" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
12
|
+
<ellipse data-role="module-dot" cx="4.75" cy="258.66" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
13
|
+
<ellipse data-role="module-dot" cx="60.50" cy="258.66" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
14
|
+
<ellipse data-role="module-dot" cx="116.25" cy="258.66" rx="13.01" ry="13.01" fill="#F0F2F1" opacity="0.774" />
|
|
15
|
+
<ellipse data-role="module-circle" cx="125.52" cy="712.10" rx="117.81" ry="117.81" fill="#E7EBE9" opacity="0.781" />
|
|
16
|
+
<ellipse data-role="module-dot" cx="1026.73" cy="531.40" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
17
|
+
<ellipse data-role="module-dot" cx="1075.79" cy="531.40" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
18
|
+
<ellipse data-role="module-dot" cx="1124.86" cy="531.40" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
19
|
+
<ellipse data-role="module-dot" cx="1026.73" cy="580.47" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
20
|
+
<ellipse data-role="module-dot" cx="1075.79" cy="580.47" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
21
|
+
<ellipse data-role="module-dot" cx="1124.86" cy="580.47" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
22
|
+
<ellipse data-role="module-dot" cx="1026.73" cy="629.54" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
23
|
+
<ellipse data-role="module-dot" cx="1075.79" cy="629.54" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
24
|
+
<ellipse data-role="module-dot" cx="1124.86" cy="629.54" rx="11.45" ry="11.45" fill="#E7EBE9" opacity="0.777" />
|
|
25
|
+
<ellipse data-role="module-circle" cx="565.46" cy="889.20" rx="95.25" ry="95.25" fill="#EDF0EE" opacity="0.785" />
|
|
26
|
+
<ellipse data-role="module-circle" cx="563.71" cy="302.72" rx="99.85" ry="99.85" fill="#DFE4E2" opacity="0.779" />
|
|
27
|
+
<rect data-role="module-stripe" x="895.25" y="910.83" width="25.17" height="216.47" rx="0.00" fill="#E7EBE9" opacity="0.795" />
|
|
28
|
+
<rect data-role="module-stripe" x="938.54" y="910.83" width="25.17" height="216.47" rx="0.00" fill="#E7EBE9" opacity="0.795" />
|
|
29
|
+
<rect data-role="module-stripe" x="981.83" y="910.83" width="25.17" height="216.47" rx="0.00" fill="#E7EBE9" opacity="0.795" />
|
|
30
|
+
<rect data-role="module-stripe" x="1025.13" y="910.83" width="25.17" height="216.47" rx="0.00" fill="#E7EBE9" opacity="0.795" />
|
|
31
|
+
<rect data-role="module-stripe" x="1068.42" y="910.83" width="25.17" height="216.47" rx="0.00" fill="#E7EBE9" opacity="0.795" />
|
|
32
|
+
<rect data-role="module-square" x="1367.27" y="637.80" width="203.02" height="203.02" rx="0.00" fill="#EDF0EE" opacity="0.786" />
|
|
33
|
+
<rect data-role="module-square" x="615.21" y="113.67" width="202.07" height="202.07" rx="0.00" fill="#EDF0EE" opacity="0.796" />
|
|
34
|
+
<rect data-role="module-square" x="1786.35" y="974.13" width="233.20" height="233.20" rx="0.00" fill="#DFE4E2" opacity="0.785" />
|
|
35
|
+
<rect data-role="module-stripe" x="470.19" y="7.84" width="24.41" height="209.88" rx="0.00" fill="#E7EBE9" opacity="0.794" />
|
|
36
|
+
<rect data-role="module-stripe" x="512.17" y="7.84" width="24.41" height="209.88" rx="0.00" fill="#E7EBE9" opacity="0.794" />
|
|
37
|
+
<rect data-role="module-stripe" x="554.15" y="7.84" width="24.41" height="209.88" rx="0.00" fill="#E7EBE9" opacity="0.794" />
|
|
38
|
+
<rect data-role="module-stripe" x="596.12" y="7.84" width="24.41" height="209.88" rx="0.00" fill="#E7EBE9" opacity="0.794" />
|
|
39
|
+
<rect data-role="module-stripe" x="638.10" y="7.84" width="24.41" height="209.88" rx="0.00" fill="#E7EBE9" opacity="0.794" />
|
|
40
|
+
<polygon data-role="module-step" points="1516.11,329.09 1350.08,329.09 1350.08,200.56 1248.33,200.56 1248.33,61.31 1516.11,61.31" fill="#E7EBE9" opacity="0.794" />
|
|
41
|
+
<rect data-role="module-stripe" x="1876.23" y="246.53" width="22.63" height="194.65" rx="0.00" fill="#EDF0EE" opacity="0.789" />
|
|
42
|
+
<rect data-role="module-stripe" x="1915.16" y="246.53" width="22.63" height="194.65" rx="0.00" fill="#EDF0EE" opacity="0.789" />
|
|
43
|
+
<rect data-role="module-stripe" x="1954.09" y="246.53" width="22.63" height="194.65" rx="0.00" fill="#EDF0EE" opacity="0.789" />
|
|
44
|
+
<rect data-role="module-stripe" x="1993.02" y="246.53" width="22.63" height="194.65" rx="0.00" fill="#EDF0EE" opacity="0.789" />
|
|
45
|
+
<rect data-role="module-stripe" x="2031.95" y="246.53" width="22.63" height="194.65" rx="0.00" fill="#EDF0EE" opacity="0.789" />
|
|
46
|
+
<polygon data-role="module-step" points="228.37,1057.21 79.79,1057.21 79.79,942.18 -11.27,942.18 -11.27,817.57 228.37,817.57" fill="#DFE4E2" opacity="0.792" />
|
|
47
|
+
<ellipse data-role="module-dot" cx="1356.55" cy="1063.59" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
48
|
+
<ellipse data-role="module-dot" cx="1400.83" cy="1063.59" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
49
|
+
<ellipse data-role="module-dot" cx="1445.11" cy="1063.59" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
50
|
+
<ellipse data-role="module-dot" cx="1356.55" cy="1107.87" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
51
|
+
<ellipse data-role="module-dot" cx="1400.83" cy="1107.87" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
52
|
+
<ellipse data-role="module-dot" cx="1445.11" cy="1107.87" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
53
|
+
<ellipse data-role="module-dot" cx="1356.55" cy="1152.15" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
54
|
+
<ellipse data-role="module-dot" cx="1400.83" cy="1152.15" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
55
|
+
<ellipse data-role="module-dot" cx="1445.11" cy="1152.15" rx="10.33" ry="10.33" fill="#F0F2F1" opacity="0.778" />
|
|
56
|
+
<polygon data-role="edge-arc" points="0.00,1180.00 -0.00,842.60 22.07,843.33 44.04,845.49 65.82,849.09 87.32,854.10 108.45,860.51 129.12,868.29 149.23,877.40 168.70,887.81 187.45,899.47 205.39,912.33 222.46,926.33 238.58,941.42 253.67,957.54 267.67,974.61 280.53,992.55 292.19,1011.30 302.60,1030.77 311.71,1050.88 319.49,1071.55 325.90,1092.68 330.91,1114.18 334.51,1135.96 336.67,1157.93 337.40,1180.00" fill="#EDF0EE" opacity="0.640" />
|
|
57
|
+
<polygon data-role="edge-arc" points="2048.00,0.00 2048.00,364.65 2024.15,363.87 2000.40,361.53 1976.86,357.64 1953.62,352.22 1930.79,345.29 1908.46,336.89 1886.72,327.04 1865.68,315.79 1845.41,303.19 1826.02,289.29 1807.57,274.16 1790.16,257.84 1773.84,240.43 1758.71,221.98 1744.81,202.59 1732.21,182.32 1720.96,161.28 1711.11,139.54 1702.71,117.21 1695.78,94.38 1690.36,71.14 1686.47,47.60 1684.13,23.85 1683.35,0.00" fill="#DFE4E2" opacity="0.600" />
|
|
58
|
+
</svg>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="2048" height="1180" viewBox="0 0 2048 1180" role="img" aria-labelledby="title description">
|
|
3
|
+
<title id="title">Dvořák — Symphony No. 9 / Largo — rhythmic modules</title>
|
|
4
|
+
<desc id="description">Phrase windows become a balanced, deterministically scattered set of blocks, circles, steps, dots and stripes.</desc>
|
|
5
|
+
<rect width="100%" height="100%" fill="#F8FAF9" />
|
|
6
|
+
<rect data-role="module-square" x="734.34" y="394.20" width="189.39" height="189.39" rx="0.00" fill="#F0F2F1" opacity="0.778" />
|
|
7
|
+
<rect data-role="module-stripe" x="1261.21" y="715.22" width="25.65" height="220.59" rx="0.00" fill="#F0F2F1" opacity="0.772" />
|
|
8
|
+
<rect data-role="module-stripe" x="1305.33" y="715.22" width="25.65" height="220.59" rx="0.00" fill="#F0F2F1" opacity="0.772" />
|
|
9
|
+
<rect data-role="module-stripe" x="1349.45" y="715.22" width="25.65" height="220.59" rx="0.00" fill="#F0F2F1" opacity="0.772" />
|
|
10
|
+
<rect data-role="module-stripe" x="1393.57" y="715.22" width="25.65" height="220.59" rx="0.00" fill="#F0F2F1" opacity="0.772" />
|
|
11
|
+
<rect data-role="module-stripe" x="1437.69" y="715.22" width="25.65" height="220.59" rx="0.00" fill="#F0F2F1" opacity="0.772" />
|
|
12
|
+
<ellipse data-role="module-circle" cx="92.51" cy="86.94" rx="128.49" ry="128.49" fill="#E7EBE9" opacity="0.767" />
|
|
13
|
+
<rect data-role="module-stripe" x="-36.14" y="795.14" width="25.70" height="221.05" rx="0.00" fill="#DFE4E2" opacity="0.781" />
|
|
14
|
+
<rect data-role="module-stripe" x="8.07" y="795.14" width="25.70" height="221.05" rx="0.00" fill="#DFE4E2" opacity="0.781" />
|
|
15
|
+
<rect data-role="module-stripe" x="52.28" y="795.14" width="25.70" height="221.05" rx="0.00" fill="#DFE4E2" opacity="0.781" />
|
|
16
|
+
<rect data-role="module-stripe" x="96.49" y="795.14" width="25.70" height="221.05" rx="0.00" fill="#DFE4E2" opacity="0.781" />
|
|
17
|
+
<rect data-role="module-stripe" x="140.70" y="795.14" width="25.70" height="221.05" rx="0.00" fill="#DFE4E2" opacity="0.781" />
|
|
18
|
+
<ellipse data-role="module-circle" cx="822.53" cy="920.14" rx="135.89" ry="135.89" fill="#F0F2F1" opacity="0.764" />
|
|
19
|
+
<polygon data-role="module-step" points="1383.27,386.80 1245.28,386.80 1245.28,279.98 1160.71,279.98 1160.71,164.25 1383.27,164.25" fill="#E7EBE9" opacity="0.777" />
|
|
20
|
+
<polygon data-role="module-step" points="1654.02,703.73 1654.02,569.62 1757.85,569.62 1757.85,487.43 1870.32,487.43 1870.32,703.73" fill="#E7EBE9" opacity="0.773" />
|
|
21
|
+
<ellipse data-role="module-dot" cx="592.14" cy="96.66" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
22
|
+
<ellipse data-role="module-dot" cx="652.84" cy="96.66" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
23
|
+
<ellipse data-role="module-dot" cx="713.54" cy="96.66" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
24
|
+
<ellipse data-role="module-dot" cx="592.14" cy="157.35" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
25
|
+
<ellipse data-role="module-dot" cx="652.84" cy="157.35" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
26
|
+
<ellipse data-role="module-dot" cx="713.54" cy="157.35" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
27
|
+
<ellipse data-role="module-dot" cx="592.14" cy="218.05" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
28
|
+
<ellipse data-role="module-dot" cx="652.84" cy="218.05" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
29
|
+
<ellipse data-role="module-dot" cx="713.54" cy="218.05" rx="14.16" ry="14.16" fill="#DFE4E2" opacity="0.764" />
|
|
30
|
+
<ellipse data-role="module-dot" cx="344.67" cy="651.92" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
31
|
+
<ellipse data-role="module-dot" cx="399.20" cy="651.92" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
32
|
+
<ellipse data-role="module-dot" cx="453.73" cy="651.92" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
33
|
+
<ellipse data-role="module-dot" cx="344.67" cy="706.45" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
34
|
+
<ellipse data-role="module-dot" cx="399.20" cy="706.45" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
35
|
+
<ellipse data-role="module-dot" cx="453.73" cy="706.45" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
36
|
+
<ellipse data-role="module-dot" cx="344.67" cy="760.98" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
37
|
+
<ellipse data-role="module-dot" cx="399.20" cy="760.98" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
38
|
+
<ellipse data-role="module-dot" cx="453.73" cy="760.98" rx="12.72" ry="12.72" fill="#F0F2F1" opacity="0.766" />
|
|
39
|
+
<ellipse data-role="module-circle" cx="1912.72" cy="156.82" rx="129.15" ry="129.15" fill="#DFE4E2" opacity="0.756" />
|
|
40
|
+
<polygon data-role="module-step" points="1747.33,961.00 1901.62,961.00 1901.62,1080.46 1996.18,1080.46 1996.18,1209.86 1747.33,1209.86" fill="#EDF0EE" opacity="0.789" />
|
|
41
|
+
<rect data-role="module-square" x="974.40" y="46.09" width="232.26" height="232.26" rx="0.00" fill="#EDF0EE" opacity="0.791" />
|
|
42
|
+
<rect data-role="module-square" x="299.88" y="414.22" width="217.38" height="217.38" rx="0.00" fill="#F0F2F1" opacity="0.788" />
|
|
43
|
+
<rect data-role="module-stripe" x="1729.70" y="315.79" width="25.15" height="216.28" rx="0.00" fill="#E7EBE9" opacity="0.789" />
|
|
44
|
+
<rect data-role="module-stripe" x="1772.96" y="315.79" width="25.15" height="216.28" rx="0.00" fill="#E7EBE9" opacity="0.789" />
|
|
45
|
+
<rect data-role="module-stripe" x="1816.21" y="315.79" width="25.15" height="216.28" rx="0.00" fill="#E7EBE9" opacity="0.789" />
|
|
46
|
+
<rect data-role="module-stripe" x="1859.47" y="315.79" width="25.15" height="216.28" rx="0.00" fill="#E7EBE9" opacity="0.789" />
|
|
47
|
+
<rect data-role="module-stripe" x="1902.72" y="315.79" width="25.15" height="216.28" rx="0.00" fill="#E7EBE9" opacity="0.789" />
|
|
48
|
+
<polygon data-role="edge-arc" points="2048.00,1180.00 1708.91,1180.00 1709.64,1157.82 1711.81,1135.74 1715.43,1113.85 1720.47,1092.24 1726.91,1071.00 1734.72,1050.24 1743.88,1030.03 1754.34,1010.46 1766.06,991.61 1778.98,973.58 1793.06,956.42 1808.23,940.23 1824.42,925.06 1841.58,910.98 1859.61,898.06 1878.46,886.34 1898.03,875.88 1918.24,866.72 1939.00,858.91 1960.24,852.47 1981.85,847.43 2003.74,843.81 2025.82,841.64 2048.00,840.91" fill="#EDF0EE" opacity="0.640" />
|
|
49
|
+
<polygon data-role="edge-arc" points="0.00,0.00 369.26,0.00 368.47,24.15 366.11,48.20 362.17,72.04 356.68,95.57 349.67,118.70 341.16,141.31 331.18,163.32 319.79,184.63 307.03,205.15 292.96,224.79 277.63,243.47 261.11,261.11 243.47,277.63 224.79,292.96 205.15,307.03 184.63,319.79 163.32,331.18 141.31,341.16 118.70,349.67 95.57,356.68 72.04,362.17 48.20,366.11 24.15,368.47 0.00,369.26" fill="#DFE4E2" opacity="0.600" />
|
|
50
|
+
</svg>
|