@warp-drive/holodeck 0.0.1 → 0.0.3-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,18 +1,33 @@
1
1
  <p align="center">
2
2
  <img
3
3
  class="project-logo"
4
- src="./logos/NCC-1701-a-blue.svg#gh-light-mode-only"
4
+ src="./logos/logo-yellow-slab.svg"
5
5
  alt="WarpDrive"
6
- width="120px"
7
- title="WarpDrive" />
8
- <img
9
- class="project-logo"
10
- src="./logos/NCC-1701-a.svg#gh-dark-mode-only"
11
- alt="WarpDrive"
12
- width="120px"
13
- title="WarpDrive" />
6
+ width="180px"
7
+ title="WarpDrive"
8
+ />
14
9
  </p>
15
10
 
11
+ ![NPM Stable Version](https://img.shields.io/npm/v/ember-data/latest?label=version&style=flat&color=fdb155)
12
+ ![NPM Downloads](https://img.shields.io/npm/dm/ember-data.svg?style=flat&color=fdb155)
13
+ ![License](https://img.shields.io/github/license/warp-drive-data/warp-drive.svg?style=flat&color=fdb155)
14
+ [![EmberJS Discord Community Server](https://img.shields.io/badge/EmberJS-grey?logo=discord&logoColor=fdb155)](https://discord.gg/zT3asNS
15
+ )
16
+ [![WarpDrive Discord Server](https://img.shields.io/badge/WarpDrive-grey?logo=discord&logoColor=fdb155)](https://discord.gg/PHBbnWJx5S
17
+ )
18
+
19
+ <p align="center">
20
+ <br>
21
+ <a href="https://warp-drive.io">WarpDrive</a> is the lightweight data library for web apps &mdash;
22
+ <br>
23
+ universal, typed, reactive, and ready to scale.
24
+ <br/><br/>
25
+ </p>
26
+
27
+ ---
28
+
29
+ # @warp-drive/holodeck
30
+
16
31
  <h3 align="center">⚡️ Simple, Fast HTTP Mocking</h3>
17
32
  <p align="center">Ideal for Test Suites</p>
18
33
 
@@ -163,10 +178,10 @@ const MockHost = `https://${window.location.hostname}:${Number(window.location.p
163
178
  setConfig({ host: MockHost });
164
179
 
165
180
  QUnit.hooks.beforeEach(function (assert) {
166
- setTestId(assert.test.testId);
181
+ setTestId(this, assert.test.testId);
167
182
  });
168
183
  QUnit.hooks.afterEach(function (assert) {
169
- setTestId(null);
184
+ setTestId(this, null);
170
185
  });
171
186
  ```
172
187
 
@@ -225,7 +240,7 @@ holodeck can be launched and cleaned up using the lifecycle hooks in the launch
225
240
  for diagnostic in `diagnostic.js`:
226
241
 
227
242
  ```ts
228
- import launch from '@warp-drive/diagnostic/server/default-setup.js';
243
+ import { launch } from '@warp-drive/diagnostic/server';
229
244
  import holodeck from '@warp-drive/holodeck';
230
245
 
231
246
  await launch({
@@ -249,7 +264,7 @@ await launch({
249
264
  img.project-logo {
250
265
  padding: 0 5em 1em 5em;
251
266
  width: 100px;
252
- border-bottom: 2px solid #0969da;
267
+ border-bottom: 2px solid #bbb;
253
268
  margin: 0 auto;
254
269
  display: block;
255
270
  }
@@ -265,7 +280,7 @@ await launch({
265
280
  display: inline-block;
266
281
  padding: .2rem 0;
267
282
  color: #000;
268
- border-bottom: 3px solid #0969da;
283
+ border-bottom: 3px solid #bbb;
269
284
  }
270
285
 
271
286
  details > details {
@@ -0,0 +1,52 @@
1
+ import type { Handler, NextFn } from "@warp-drive/core/request";
2
+ import type { RequestContext, StructuredDataDocument } from "@warp-drive/core/types/request";
3
+ import type { Store } from "@warp-drive/legacy/store";
4
+ import type { ScaffoldGenerator } from "./mock.js";
5
+ /**
6
+ * @public
7
+ */
8
+ export declare function setConfig({ host }: {
9
+ host: string;
10
+ }): void;
11
+ /**
12
+ * @public
13
+ */
14
+ export declare function setTestId(context: object, str: string | null): void;
15
+ /**
16
+ * @public
17
+ */
18
+ export declare function setIsRecording(value: boolean): void;
19
+ /**
20
+ * @public
21
+ */
22
+ export declare function getIsRecording(): boolean;
23
+ /**
24
+ * A request handler that intercepts requests and routes them through
25
+ * the Holodeck mock server.
26
+ *
27
+ * This handler modifies the request URL to include test identifiers
28
+ * and manages request counts for accurate mocking.
29
+ *
30
+ * Requires that the test context be configured with a testId using `setTestId`.
31
+ *
32
+ * @param owner - the test context object used to retrieve the test ID.
33
+ */
34
+ export declare class MockServerHandler implements Handler {
35
+ owner: object;
36
+ constructor(owner: object);
37
+ request<T>(context: RequestContext, next: NextFn<T>): Promise<StructuredDataDocument<T>>;
38
+ }
39
+ /**
40
+ * Creates an adapterFor function that wraps the provided adapterFor function
41
+ * to override the adapter's _fetchRequest method to route requests through
42
+ * the Holodeck mock server.
43
+ *
44
+ * @param owner - The test context object used to retrieve the test ID.
45
+ */
46
+ export declare function installAdapterFor(owner: object, store: Store): void;
47
+ /**
48
+ * Mock a request by sending the scaffold to the mock server.
49
+ *
50
+ * @public
51
+ */
52
+ export declare function mock(owner: object, generate: ScaffoldGenerator, isRecording?: boolean): Promise<void>;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @public
3
+ */
4
+ export interface Scaffold {
5
+ status: number;
6
+ statusText?: string;
7
+ headers: Record<string, string>;
8
+ body: Record<string, string> | string | null;
9
+ method: string;
10
+ url: string;
11
+ response: Record<string, unknown>;
12
+ }
13
+ /**
14
+ * @public
15
+ */
16
+ export type ScaffoldGenerator = () => Scaffold;
17
+ /**
18
+ * @public
19
+ */
20
+ export type ResponseGenerator = () => Record<string, unknown>;
21
+ /**
22
+ * Sets up Mocking for a GET request on the mock server
23
+ * for the supplied url.
24
+ *
25
+ * The response body is generated by the supplied response function.
26
+ *
27
+ * Available options:
28
+ * - status: the status code to return (default: 200)
29
+ * - headers: the headers to return (default: {})
30
+ * - body: the body to match against for the request (default: null)
31
+ * - RECORD: whether to record the request (default: false)
32
+ *
33
+ * @param url the url to mock, relative to the mock server host (e.g. `users/1`)
34
+ * @param response a function which generates the response to return
35
+ * @param options status, headers for the response, body to match against for the request, and whether to record the request
36
+ * @return
37
+ */
38
+ export declare function GET(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
39
+ RECORD?: boolean;
40
+ }): Promise<void>;
41
+ /**
42
+ * Mock a POST request
43
+ */
44
+ export declare function POST(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
45
+ RECORD?: boolean;
46
+ }): Promise<void>;
47
+ /**
48
+ * mock a PUT request
49
+ */
50
+ export declare function PUT(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
51
+ RECORD?: boolean;
52
+ }): Promise<void>;
53
+ /**
54
+ * mock a PATCH request
55
+ *
56
+ */
57
+ export declare function PATCH(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
58
+ RECORD?: boolean;
59
+ }): Promise<void>;
60
+ /**
61
+ * mock a DELETE request
62
+ */
63
+ export declare function DELETE(owner: object, url: string, response: ResponseGenerator, options?: Partial<Omit<Scaffold, "response" | "url" | "method">> & {
64
+ RECORD?: boolean;
65
+ }): Promise<void>;
package/dist/index.js CHANGED
@@ -1,10 +1,26 @@
1
+ import { SHOULD_RECORD } from '@warp-drive/core/build-config/env';
2
+
3
+ /**
4
+ * @module
5
+ * @mergeModuleWith <project>
6
+ */
1
7
  const TEST_IDS = new WeakMap();
2
- let HOST = 'https://localhost:1135/';
8
+ let HOST = '/';
9
+
10
+ /**
11
+ * @public
12
+ */
13
+
3
14
  function setConfig({
4
15
  host
5
16
  }) {
6
17
  HOST = host.endsWith('/') ? host : `${host}/`;
7
18
  }
19
+
20
+ /**
21
+ * @public
22
+ */
23
+
8
24
  function setTestId(context, str) {
9
25
  if (str && TEST_IDS.has(context)) {
10
26
  throw new Error(`MockServerHandler is already configured with a testId.`);
@@ -12,37 +28,72 @@ function setTestId(context, str) {
12
28
  if (str) {
13
29
  TEST_IDS.set(context, {
14
30
  id: str,
15
- request: 0,
16
- mock: 0
31
+ mock: {
32
+ GET: {},
33
+ PUT: {},
34
+ PATCH: {},
35
+ DELETE: {},
36
+ POST: {},
37
+ QUERY: {},
38
+ OPTIONS: {},
39
+ HEAD: {},
40
+ CONNECT: {},
41
+ TRACE: {}
42
+ },
43
+ request: {
44
+ GET: {},
45
+ PUT: {},
46
+ PATCH: {},
47
+ DELETE: {},
48
+ POST: {},
49
+ QUERY: {},
50
+ OPTIONS: {},
51
+ HEAD: {},
52
+ CONNECT: {},
53
+ TRACE: {}
54
+ }
17
55
  });
18
56
  } else {
19
57
  TEST_IDS.delete(context);
20
58
  }
21
59
  }
22
- let IS_RECORDING = false;
60
+ const shouldRecord = SHOULD_RECORD ? true : false;
61
+ let IS_RECORDING = null;
62
+
63
+ /**
64
+ * @public
65
+ */
23
66
  function setIsRecording(value) {
24
- IS_RECORDING = Boolean(value);
67
+ IS_RECORDING = value === null ? value : Boolean(value);
25
68
  }
69
+
70
+ /**
71
+ * @public
72
+ */
26
73
  function getIsRecording() {
27
- return IS_RECORDING;
74
+ return IS_RECORDING === null ? shouldRecord : IS_RECORDING;
28
75
  }
76
+
77
+ /**
78
+ * A request handler that intercepts requests and routes them through
79
+ * the Holodeck mock server.
80
+ *
81
+ * This handler modifies the request URL to include test identifiers
82
+ * and manages request counts for accurate mocking.
83
+ *
84
+ * Requires that the test context be configured with a testId using `setTestId`.
85
+ *
86
+ * @param owner - the test context object used to retrieve the test ID.
87
+ */
29
88
  class MockServerHandler {
30
89
  constructor(owner) {
31
90
  this.owner = owner;
32
91
  }
33
92
  async request(context, next) {
34
- const test = TEST_IDS.get(this.owner);
35
- if (!test) {
36
- throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);
37
- }
38
- const request = Object.assign({}, context.request);
39
- const isRecording = request.url.endsWith('/__record');
40
- const firstChar = request.url.includes('?') ? '&' : '?';
41
- const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${isRecording ? test.mock++ : test.request++}`;
42
- request.url = request.url + queryForTest;
43
- request.mode = 'cors';
44
- request.credentials = 'omit';
45
- request.referrerPolicy = '';
93
+ const {
94
+ request,
95
+ queryForTest
96
+ } = setupHolodeckFetch(this.owner, Object.assign({}, context.request));
46
97
  try {
47
98
  const future = next(request);
48
99
  context.setStream(future.getStream());
@@ -55,17 +106,127 @@ class MockServerHandler {
55
106
  }
56
107
  }
57
108
  }
58
- async function mock(owner, generate, isRecording) {
109
+ function setupHolodeckFetch(owner, request) {
59
110
  const test = TEST_IDS.get(owner);
60
111
  if (!test) {
61
- throw new Error(`Cannot call "mock" before configuring a testId. Use setTestId to set the testId for each test`);
112
+ throw new Error(`MockServerHandler is not configured with a testId. Use setTestId to set the testId for each test`);
113
+ }
114
+ const url = request.url;
115
+ const firstChar = url.includes('?') ? '&' : '?';
116
+ const method = request.method?.toUpperCase() ?? 'GET';
117
+
118
+ // enable custom methods
119
+ if (!test.request[method]) {
120
+ // eslint-disable-next-line no-console
121
+ console.log(`⚠️ Using custom HTTP method ${method} for response to request ${url}`);
122
+ test.request[method] = {};
123
+ }
124
+ if (!(url in test.request[method])) {
125
+ test.request[method][url] = 0;
126
+ }
127
+ const queryForTest = `${firstChar}__xTestId=${test.id}&__xTestRequestNumber=${test.request[method][url]++}`;
128
+ request.url = url + queryForTest;
129
+ request.method = method;
130
+ request.mode = 'cors';
131
+ request.credentials = 'omit';
132
+ request.referrerPolicy = '';
133
+
134
+ // since holodeck currently runs on a separate port
135
+ // and we don't want to trigger cors pre-flight
136
+ // we convert PUT to POST to keep the request in the
137
+ // "simple" cors category.
138
+ // if (request.method === 'PUT') {
139
+ // request.method = 'POST';
140
+ // }
141
+
142
+ const headers = new Headers(request.headers);
143
+ if (headers.has('Content-Type')) {
144
+ // under the rules of simple-cors, content-type can only be
145
+ // one of three things, none of which are what folks typically
146
+ // set this to. Since holodeck always expects body to be JSON
147
+ // this "just works".
148
+ headers.set('Content-Type', 'text/plain');
149
+ request.headers = headers;
150
+ }
151
+ return {
152
+ request,
153
+ queryForTest
154
+ };
155
+ }
156
+ function upgradeStore(store) {
157
+ if (typeof store.adapterFor !== 'function') {
158
+ throw new Error('Store is not compatible with Holodeck. Missing adapterFor method.');
62
159
  }
63
- const testMockNum = test.mock++;
160
+ }
161
+
162
+ /**
163
+ * Creates an adapterFor function that wraps the provided adapterFor function
164
+ * to override the adapter's _fetchRequest method to route requests through
165
+ * the Holodeck mock server.
166
+ *
167
+ * @param owner - The test context object used to retrieve the test ID.
168
+ */
169
+ function installAdapterFor(owner, store) {
170
+ upgradeStore(store);
171
+ const fn = store.adapterFor;
172
+ function holodeckAdapterFor(modelName, _allowMissing) {
173
+ const adapter = fn.call(this, modelName, _allowMissing);
174
+ if (adapter) {
175
+ if (!adapter.hasOverriddenFetch) {
176
+ adapter.hasOverriddenFetch = true;
177
+ adapter.useFetch = true;
178
+ const originalFetch = adapter._fetchRequest?.bind(adapter);
179
+ adapter._fetchRequest = function (options) {
180
+ if (!originalFetch) {
181
+ throw new Error(`Adapter ${String(modelName)} does not implement _fetchRequest`);
182
+ }
183
+ const {
184
+ request
185
+ } = setupHolodeckFetch(owner, options);
186
+ return originalFetch(request);
187
+ };
188
+ }
189
+ }
190
+ return adapter;
191
+ }
192
+ store.adapterFor = holodeckAdapterFor;
193
+ }
194
+
195
+ /**
196
+ * Mock a request by sending the scaffold to the mock server.
197
+ *
198
+ * @public
199
+ */
200
+ async function mock(owner, generate, isRecording) {
64
201
  if (getIsRecording() || isRecording) {
202
+ const test = TEST_IDS.get(owner);
203
+ if (!test) {
204
+ throw new Error(`Cannot call "mock" before configuring a testId. Use setTestId to set the testId for each test`);
205
+ }
206
+ const requestToMock = generate();
207
+ const {
208
+ url: mockUrl,
209
+ method
210
+ } = requestToMock;
211
+ if (!mockUrl || !method) {
212
+ throw new Error(`MockError: Cannot mock a request without providing a URL and Method`);
213
+ }
214
+ const mockMethod = method?.toUpperCase() ?? 'GET';
215
+
216
+ // enable custom methods
217
+ if (!test.mock[mockMethod]) {
218
+ // eslint-disable-next-line no-console
219
+ console.log(`⚠️ Using custom HTTP method ${mockMethod} for response to request ${mockUrl}`);
220
+ test.mock[mockMethod] = {};
221
+ }
222
+ if (!(mockUrl in test.mock[mockMethod])) {
223
+ test.mock[mockMethod][mockUrl] = 0;
224
+ }
225
+ const testMockNum = test.mock[mockMethod][mockUrl]++;
65
226
  const url = `${HOST}__record?__xTestId=${test.id}&__xTestRequestNumber=${testMockNum}`;
66
227
  await fetch(url, {
67
228
  method: 'POST',
68
- body: JSON.stringify(generate()),
229
+ body: JSON.stringify(requestToMock),
69
230
  mode: 'cors',
70
231
  credentials: 'omit',
71
232
  referrerPolicy: ''
@@ -73,5 +234,4 @@ async function mock(owner, generate, isRecording) {
73
234
  }
74
235
  }
75
236
 
76
- export { MockServerHandler, getIsRecording, mock, setConfig, setIsRecording, setTestId };
77
- //# sourceMappingURL=index.js.map
237
+ export { MockServerHandler, getIsRecording, installAdapterFor, mock, setConfig, setIsRecording, setTestId };
package/dist/mock.js CHANGED
@@ -1,5 +1,17 @@
1
1
  import { mock, getIsRecording } from './index.js';
2
2
 
3
+ /**
4
+ * @public
5
+ */
6
+
7
+ /**
8
+ * @public
9
+ */
10
+
11
+ /**
12
+ * @public
13
+ */
14
+
3
15
  /**
4
16
  * Sets up Mocking for a GET request on the mock server
5
17
  * for the supplied url.
@@ -28,11 +40,81 @@ function GET(owner, url, response, options) {
28
40
  response: response()
29
41
  }), getIsRecording() || (options?.RECORD ?? false));
30
42
  }
31
- function POST() {}
32
- function PUT() {}
33
- function PATCH() {}
34
- function DELETE() {}
35
- function QUERY() {}
43
+ const STATUS_TEXT_FOR = new Map([[200, 'OK'], [201, 'Created'], [202, 'Accepted'], [203, 'Non-Authoritative Information'], [204, 'No Content'], [205, 'Reset Content'], [206, 'Partial Content'], [207, 'Multi-Status'], [208, 'Already Reported'], [226, 'IM Used'], [300, 'Multiple Choices'], [301, 'Moved Permanently'], [302, 'Found'], [303, 'See Other'], [304, 'Not Modified'], [307, 'Temporary Redirect'], [308, 'Permanent Redirect'], [400, 'Bad Request'], [401, 'Unauthorized'], [402, 'Payment Required'], [403, 'Forbidden'], [404, 'Not Found'], [405, 'Method Not Allowed'], [406, 'Not Acceptable'], [407, 'Proxy Authentication Required'], [408, 'Request Timeout'], [409, 'Conflict'], [410, 'Gone'], [411, 'Length Required'], [412, 'Precondition Failed'], [413, 'Payload Too Large'], [414, 'URI Too Long'], [415, 'Unsupported Media Type'], [416, 'Range Not Satisfiable'], [417, 'Expectation Failed'], [419, 'Page Expired'], [420, 'Enhance Your Calm'], [421, 'Misdirected Request'], [422, 'Unprocessable Entity'], [423, 'Locked'], [424, 'Failed Dependency'], [425, 'Too Early'], [426, 'Upgrade Required'], [428, 'Precondition Required'], [429, 'Too Many Requests'], [430, 'Request Header Fields Too Large'], [431, 'Request Header Fields Too Large'], [450, 'Blocked By Windows Parental Controls'], [451, 'Unavailable For Legal Reasons'], [500, 'Internal Server Error'], [501, 'Not Implemented'], [502, 'Bad Gateway'], [503, 'Service Unavailable'], [504, 'Gateway Timeout'], [505, 'HTTP Version Not Supported'], [506, 'Variant Also Negotiates'], [507, 'Insufficient Storage'], [508, 'Loop Detected'], [509, 'Bandwidth Limit Exceeded'], [510, 'Not Extended'], [511, 'Network Authentication Required']]);
44
+
45
+ /**
46
+ * Mock a POST request
47
+ */
48
+ function POST(owner, url, response, options) {
49
+ return mock(owner, () => {
50
+ const body = response();
51
+ const status = options?.status ?? (body ? 201 : 204);
52
+ return {
53
+ status: status,
54
+ statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',
55
+ headers: options?.headers ?? {},
56
+ body: options?.body ?? null,
57
+ method: 'POST',
58
+ url,
59
+ response: body
60
+ };
61
+ }, getIsRecording() || (options?.RECORD ?? false));
62
+ }
63
+
64
+ /**
65
+ * mock a PUT request
66
+ */
67
+ function PUT(owner, url, response, options) {
68
+ return mock(owner, () => {
69
+ const body = response();
70
+ const status = options?.status ?? (body ? 200 : 204);
71
+ return {
72
+ status: status,
73
+ statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',
74
+ headers: options?.headers ?? {},
75
+ body: options?.body ?? null,
76
+ method: 'PUT',
77
+ url,
78
+ response: body
79
+ };
80
+ }, getIsRecording() || (options?.RECORD ?? false));
81
+ }
82
+ /**
83
+ * mock a PATCH request
84
+ *
85
+ */
86
+ function PATCH(owner, url, response, options) {
87
+ return mock(owner, () => {
88
+ const body = response();
89
+ const status = options?.status ?? (body ? 200 : 204);
90
+ return {
91
+ status: status,
92
+ statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',
93
+ headers: options?.headers ?? {},
94
+ body: options?.body ?? null,
95
+ method: 'PATCH',
96
+ url,
97
+ response: body
98
+ };
99
+ }, getIsRecording() || (options?.RECORD ?? false));
100
+ }
101
+ /**
102
+ * mock a DELETE request
103
+ */
104
+ function DELETE(owner, url, response, options) {
105
+ return mock(owner, () => {
106
+ const body = response();
107
+ const status = options?.status ?? (body ? 200 : 204);
108
+ return {
109
+ status: status,
110
+ statusText: options?.statusText ?? STATUS_TEXT_FOR.get(status) ?? '',
111
+ headers: options?.headers ?? {},
112
+ body: options?.body ?? null,
113
+ method: 'DELETE',
114
+ url,
115
+ response: body
116
+ };
117
+ }, getIsRecording() || (options?.RECORD ?? false));
118
+ }
36
119
 
37
- export { DELETE, GET, PATCH, POST, PUT, QUERY };
38
- //# sourceMappingURL=mock.js.map
120
+ export { DELETE, GET, PATCH, POST, PUT };
package/logos/README.md CHANGED
@@ -1,4 +1,4 @@
1
1
  # Autogeneration Notice
2
2
 
3
- This directory is maintained in the root of the monorepo and sync'd to public
4
- repositories by running `bun sync-logos`
3
+ This directory is maintained in the root of the monorepo in `/logos/synced` and sync'd
4
+ to public repositories by running `bun sync-logos`
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1498" height="1047" fill="none" viewBox="0 0 1498 1047"><g filter="url(#a)"><path fill="url(#b)" d="M132 175.075C132 149.076 153.138 128 179.213 128H1318.23c26.08 0 47.22 21.076 47.22 47.075v688.85c0 25.999-21.14 47.075-47.22 47.075H179.213C153.138 911 132 889.924 132 863.925z"/><path fill="#201328" d="M1043.4 587.312c0-39.962 30.84-72.357 68.87-72.357h53.09c38.04 0 68.88-32.395 68.88-72.357v-92.651c0-39.962-30.84-72.358-68.88-72.358h-53.09c-38.01 0-68.82 32.351-68.87 72.275v92.734c0 39.962-30.83 72.357-68.869 72.357h-53.089c-38.036 0-68.871-32.395-68.871-72.357v-87.409c0-39.962-30.835-72.358-68.872-72.358h-51.654c-38.037 0-68.872 32.396-68.872 72.358v87.409c0 39.962-30.835 72.357-68.872 72.357h-53.088c-38.037 0-68.872-32.395-68.872-72.357v-92.652c0-39.961-30.835-72.357-68.872-72.357h-53.088c-38.037 0-68.872 32.396-68.872 72.357v92.652c0 39.962 30.835 72.357 68.872 72.357h53.088c38.037 0 68.872 32.395 68.872 72.357v101.741c0 39.962 30.835 72.358 68.872 72.358h53.088c38.037 0 68.872-32.396 68.872-72.358V587.312c0-39.962 30.835-72.357 68.872-72.357h51.654c38.037 0 68.872 32.395 68.872 72.357v101.741c0 39.962 30.835 72.358 68.871 72.358h53.089c38.039 0 68.869-32.396 68.869-72.358z"/></g><defs><linearGradient id="b" x1="748.724" x2="1167.5" y1="128" y2="1070.23" gradientUnits="userSpaceOnUse"><stop stop-color="#ffc474"/><stop offset="1" stop-color="#ff9809"/></linearGradient><filter id="a" width="1497.45" height="1047" x="0" y="0" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="4"/><feGaussianBlur stdDeviation="66"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0"/><feBlend in2="BackgroundImageFix" result="effect1_dropShadow_757_188"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="4"/><feGaussianBlur stdDeviation="18"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/><feBlend in2="effect1_dropShadow_757_188" result="effect2_dropShadow_757_188"/><feColorMatrix in="SourceAlpha" result="hardAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/><feOffset dy="4"/><feGaussianBlur stdDeviation="2"/><feComposite in2="hardAlpha" operator="out"/><feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0"/><feBlend in2="effect2_dropShadow_757_188" result="effect3_dropShadow_757_188"/><feBlend in="SourceGraphic" in2="effect3_dropShadow_757_188" result="shape"/></filter></defs></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="2081" height="200" fill="none" viewBox="0 0 2081 200"><path fill="#000" d="M2064.89 185.359c-15.57 5.16-31.15 8.846-46.72 11.058q-23.22 3.456-47.55 3.456c-20.73 0-39.21-2.212-55.43-6.635q-24.18-6.773-40.77-19.49c-11.06-8.478-19.49-18.844-25.3-31.1-5.8-12.256-8.71-26.125-8.71-41.606 0-14.375 2.91-27.69 8.71-39.947 5.9-12.348 14.19-23.037 24.88-32.068 10.78-9.123 23.78-16.218 38.98-21.286C1928.19 2.58 1945.14 0 1963.85 0c17.23 0 32.99 2.35 47.27 7.05 14.38 4.607 26.68 11.472 36.91 20.595q15.48 13.684 23.91 33.727c5.71 13.361 8.57 28.75 8.57 46.167v12.716h-187.16a48.3 48.3 0 0 0 8.16 16.449c3.87 4.976 9.08 9.215 15.62 12.717 6.54 3.501 14.56 6.22 24.05 8.155 9.58 1.935 21.01 2.903 34.28 2.903 9.4 0 18.61-.553 27.64-1.659 9.04-1.198 17.47-2.719 25.3-4.562q11.745-2.902 21.15-6.358c6.36-2.304 11.47-4.607 15.34-6.911zm-35.38-102.7c-.47-4.7-1.89-9.538-4.29-14.514-2.3-5.069-5.99-9.63-11.06-13.685s-11.7-7.371-19.9-9.952q-12.3-4.008-30.69-4.008-17.28 0-29.85 4.285-12.585 4.284-21.15 10.643c-5.62 4.239-10 8.8-13.13 13.684-3.14 4.884-5.16 9.4-6.09 13.546zm-284.63 112.099h-59.71l-96.06-189.92h57.22l69.11 142.924 68.98-142.924h57.22zm-238.29 0V4.838h51.28v189.92zm-32.63-146.932c-1.38-.461-3.4-1.06-6.08-1.797q-3.87-1.245-9.12-2.35-5.25-1.244-11.61-2.073c-4.15-.553-8.43-.83-12.86-.83-9.21 0-17.83 1.152-25.84 3.456q-11.895 3.317-22.26 8.984a125.3 125.3 0 0 0-19.35 12.717c-5.9 4.7-11.33 9.537-16.31 14.514v114.311h-51.28V4.838h51.28v30.824c6.17-4.608 12.49-9.03 18.94-13.27a163 163 0 0 1 20.32-11.472c7.09-3.318 14.56-5.944 22.39-7.88 7.83-2.026 16.12-3.04 24.88-3.04 3.32 0 6.68.184 10.09.553 3.5.276 6.86.645 10.09 1.106 3.32.46 6.4.967 9.26 1.52s5.35 1.106 7.46 1.659zM1263.71 99.66c0 12.164-1.33 22.991-4 32.483-2.68 9.399-6.45 17.6-11.34 24.604-4.79 7.003-10.6 12.947-17.42 17.831q-10.08 7.325-22.53 11.749c-8.29 2.948-17.23 5.114-26.81 6.496q-14.37 1.935-30 1.935h-120.53V4.838h120.26c10.41 0 20.41.691 29.99 2.073 9.59 1.29 18.52 3.41 26.82 6.359 8.38 2.948 15.99 6.865 22.8 11.749 6.82 4.791 12.63 10.735 17.42 17.83 4.89 7.004 8.66 15.205 11.34 24.605 2.67 9.399 4 20.134 4 32.206m-51.69 0q0-13.685-3.6-23.775c-2.3-6.727-6.08-12.256-11.33-16.587-5.16-4.423-11.93-7.694-20.32-9.814-8.38-2.211-18.61-3.317-30.68-3.317h-63.73v107.262h63.73c12.07 0 22.3-1.06 30.68-3.179 8.39-2.212 15.16-5.529 20.32-9.952 5.25-4.515 9.03-10.137 11.33-16.864q3.6-10.09 3.6-23.774M986.574 63.169q0 14.237-4.699 25.157-4.562 10.92-14.514 18.383-9.952 7.464-25.71 11.335-15.62 3.732-37.735 3.732h-85.285v72.982h-17.278V4.838h102.563q22.116 0 37.735 3.87 15.758 3.732 25.71 11.058t14.514 18.246q4.7 10.92 4.699 25.157m-17.831 0q0-13.546-4.561-21.84-4.423-8.294-13.823-12.716-9.26-4.562-23.636-6.082-14.237-1.52-33.865-1.52h-74.227v84.593h74.227q7.602 0 16.172.138 8.709 0 17.14-.83 8.432-.967 16.034-3.179 7.74-2.349 13.546-7.05 5.945-4.699 9.399-12.301 3.594-7.602 3.594-19.213M766.455 19.49q-4.01-1.244-11.197-2.627-7.049-1.52-18.245-1.52-15.481 0-29.166 3.87-13.545 3.732-25.295 10.229a126.3 126.3 0 0 0-21.563 15.205q-9.951 8.57-17.969 18.245v131.866h-17.278V4.838h17.278V43.54a166 166 0 0 1 20.043-17.555q10.92-8.017 22.945-13.684a125.8 125.8 0 0 1 25.157-8.985Q724.435 0 738.533 0q4.839 0 8.708.276 3.87.14 7.05.553 3.317.277 6.22.691 2.902.416 5.944.968zm-220.33 175.268v-21.01q-9.952 5.39-22.669 9.952-12.579 4.562-26.677 7.879-13.96 3.318-28.751 5.114-14.79 1.797-29.027 1.797-18.522 0-33.312-3.317-14.652-3.318-24.881-9.814-10.228-6.635-15.757-16.31-5.529-9.814-5.529-22.669 0-12.717 6.358-22.393 6.497-9.813 18.384-17.001 11.887-7.326 28.612-12.302 16.864-5.115 37.598-8.57 20.733-3.594 44.784-5.806 24.19-2.349 50.867-3.87V61.925q0-8.846-3.318-15.62-3.317-6.772-9.261-11.749-5.943-5.115-13.96-8.431-8.017-3.456-17.416-5.53-9.262-2.074-19.628-2.902a219 219 0 0 0-20.596-.968q-13.96 0-25.433 1.383-11.473 1.381-21.425 3.87-9.952 2.35-19.075 5.529a563 563 0 0 0-18.384 6.773V14.099q19.213-5.114 40.915-8.846 21.839-3.87 46.305-3.87 20.733 0 38.703 3.593 17.969 3.456 31.238 11.196 13.27 7.74 20.872 20.043 7.603 12.164 7.603 29.718v128.825zm0-102.977q-45.2 2.489-77.268 7.05-31.929 4.561-52.248 11.196-20.32 6.634-29.857 15.481-9.399 8.846-9.399 20.042 0 9.123 4.561 16.173 4.7 7.049 13.408 11.887 8.708 4.7 21.148 7.188 12.44 2.349 27.922 2.349 9.123 0 18.798-.967a276 276 0 0 0 19.49-2.903 319 319 0 0 0 19.075-4.423q9.4-2.627 17.693-5.667t15.066-6.497q6.912-3.455 11.611-7.049zM180.245 4.838l67.73 176.098L318.193 4.838h19.213l-76.438 189.92h-25.71L168.634 23.222l-66.486 171.536h-25.71L0 4.838h19.213l70.218 176.098L157.023 4.838z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="2081" height="200" fill="none" viewBox="0 0 2081 200"><path fill="#fff" d="M2064.89 185.359c-15.57 5.16-31.15 8.846-46.72 11.058q-23.22 3.456-47.55 3.456c-20.73 0-39.21-2.212-55.43-6.635q-24.18-6.773-40.77-19.49c-11.06-8.478-19.49-18.844-25.3-31.1-5.8-12.256-8.71-26.125-8.71-41.606 0-14.375 2.91-27.69 8.71-39.947 5.9-12.348 14.19-23.037 24.88-32.068 10.78-9.123 23.78-16.218 38.98-21.286C1928.19 2.58 1945.14 0 1963.85 0c17.23 0 32.99 2.35 47.27 7.05 14.38 4.607 26.68 11.472 36.91 20.595q15.48 13.684 23.91 33.727c5.71 13.361 8.57 28.75 8.57 46.167v12.716h-187.16a48.3 48.3 0 0 0 8.16 16.449c3.87 4.976 9.08 9.215 15.62 12.717 6.54 3.501 14.56 6.22 24.05 8.155 9.58 1.935 21.01 2.903 34.28 2.903 9.4 0 18.61-.553 27.64-1.659 9.04-1.198 17.47-2.719 25.3-4.562q11.745-2.902 21.15-6.358c6.36-2.304 11.47-4.607 15.34-6.911zm-35.38-102.7c-.47-4.7-1.89-9.538-4.29-14.514-2.3-5.069-5.99-9.63-11.06-13.685s-11.7-7.371-19.9-9.952q-12.3-4.008-30.69-4.008-17.28 0-29.85 4.285-12.585 4.284-21.15 10.643c-5.62 4.239-10 8.8-13.13 13.684-3.14 4.884-5.16 9.4-6.09 13.546zm-284.63 112.099h-59.71l-96.06-189.92h57.22l69.11 142.924 68.98-142.924h57.22zm-238.29 0V4.838h51.28v189.92zm-32.63-146.932c-1.38-.461-3.4-1.06-6.08-1.797q-3.87-1.245-9.12-2.35-5.25-1.244-11.61-2.073c-4.15-.553-8.43-.83-12.86-.83-9.21 0-17.83 1.152-25.84 3.456q-11.895 3.317-22.26 8.984a125.3 125.3 0 0 0-19.35 12.717c-5.9 4.7-11.33 9.537-16.31 14.514v114.311h-51.28V4.838h51.28v30.824c6.17-4.608 12.49-9.03 18.94-13.27a163 163 0 0 1 20.32-11.472c7.09-3.318 14.56-5.944 22.39-7.88 7.83-2.026 16.12-3.04 24.88-3.04 3.32 0 6.68.184 10.09.553 3.5.276 6.86.645 10.09 1.106 3.32.46 6.4.967 9.26 1.52s5.35 1.106 7.46 1.659zM1263.71 99.66c0 12.164-1.33 22.991-4 32.483-2.68 9.399-6.45 17.6-11.34 24.604-4.79 7.003-10.6 12.947-17.42 17.831q-10.08 7.325-22.53 11.749c-8.29 2.948-17.23 5.114-26.81 6.496q-14.37 1.935-30 1.935h-120.53V4.838h120.26c10.41 0 20.41.691 29.99 2.073 9.59 1.29 18.52 3.41 26.82 6.359 8.38 2.948 15.99 6.865 22.8 11.749 6.82 4.791 12.63 10.735 17.42 17.83 4.89 7.004 8.66 15.205 11.34 24.605 2.67 9.399 4 20.134 4 32.206m-51.69 0q0-13.685-3.6-23.775c-2.3-6.727-6.08-12.256-11.33-16.587-5.16-4.423-11.93-7.694-20.32-9.814-8.38-2.211-18.61-3.317-30.68-3.317h-63.73v107.262h63.73c12.07 0 22.3-1.06 30.68-3.179 8.39-2.212 15.16-5.529 20.32-9.952 5.25-4.515 9.03-10.137 11.33-16.864q3.6-10.09 3.6-23.774M986.574 63.169q0 14.237-4.699 25.157-4.562 10.92-14.514 18.383-9.952 7.464-25.71 11.335-15.62 3.732-37.735 3.732h-85.285v72.982h-17.278V4.838h102.563q22.116 0 37.735 3.87 15.758 3.732 25.71 11.058t14.514 18.246q4.7 10.92 4.699 25.157m-17.831 0q0-13.546-4.561-21.84-4.423-8.294-13.823-12.716-9.26-4.562-23.636-6.082-14.237-1.52-33.865-1.52h-74.227v84.593h74.227q7.602 0 16.172.138 8.709 0 17.14-.83 8.432-.967 16.034-3.179 7.74-2.349 13.546-7.05 5.945-4.699 9.399-12.301 3.594-7.602 3.594-19.213M766.455 19.49q-4.01-1.244-11.197-2.627-7.049-1.52-18.245-1.52-15.481 0-29.166 3.87-13.545 3.732-25.295 10.229a126.3 126.3 0 0 0-21.563 15.205q-9.951 8.57-17.969 18.245v131.866h-17.278V4.838h17.278V43.54a166 166 0 0 1 20.043-17.555q10.92-8.017 22.945-13.684a125.8 125.8 0 0 1 25.157-8.985Q724.435 0 738.533 0q4.839 0 8.708.276 3.87.14 7.05.553 3.317.277 6.22.691 2.902.416 5.944.968zm-220.33 175.268v-21.01q-9.952 5.39-22.669 9.952-12.579 4.562-26.677 7.879-13.96 3.318-28.751 5.114-14.79 1.797-29.027 1.797-18.522 0-33.312-3.317-14.652-3.318-24.881-9.814-10.228-6.635-15.757-16.31-5.529-9.814-5.529-22.669 0-12.717 6.358-22.393 6.497-9.813 18.384-17.001 11.887-7.326 28.612-12.302 16.864-5.115 37.598-8.57 20.733-3.594 44.784-5.806 24.19-2.349 50.867-3.87V61.925q0-8.846-3.318-15.62-3.317-6.772-9.261-11.749-5.943-5.115-13.96-8.431-8.017-3.456-17.416-5.53-9.262-2.074-19.628-2.902a219 219 0 0 0-20.596-.968q-13.96 0-25.433 1.383-11.473 1.381-21.425 3.87-9.952 2.35-19.075 5.529a563 563 0 0 0-18.384 6.773V14.099q19.213-5.114 40.915-8.846 21.839-3.87 46.305-3.87 20.733 0 38.703 3.593 17.969 3.456 31.238 11.196 13.27 7.74 20.872 20.043 7.603 12.164 7.603 29.718v128.825zm0-102.977q-45.2 2.489-77.268 7.05-31.929 4.561-52.248 11.196-20.32 6.634-29.857 15.481-9.399 8.846-9.399 20.042 0 9.123 4.561 16.173 4.7 7.049 13.408 11.887 8.708 4.7 21.148 7.188 12.44 2.349 27.922 2.349 9.123 0 18.798-.967a276 276 0 0 0 19.49-2.903 319 319 0 0 0 19.075-4.423q9.4-2.627 17.693-5.667t15.066-6.497q6.912-3.455 11.611-7.049zM180.245 4.838l67.73 176.098L318.193 4.838h19.213l-76.438 189.92h-25.71L168.634 23.222l-66.486 171.536h-25.71L0 4.838h19.213l70.218 176.098L157.023 4.838z"/></svg>