tessera-learn 0.0.11 → 0.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +1 -0
  2. package/dist/audit-BBJpQGqb.js +204 -0
  3. package/dist/audit-BBJpQGqb.js.map +1 -0
  4. package/dist/plugin/a11y-cli.d.ts +1 -0
  5. package/dist/plugin/a11y-cli.js +36 -0
  6. package/dist/plugin/a11y-cli.js.map +1 -0
  7. package/dist/plugin/cli.js +2 -1
  8. package/dist/plugin/cli.js.map +1 -1
  9. package/dist/plugin/index.d.ts +16 -1
  10. package/dist/plugin/index.d.ts.map +1 -1
  11. package/dist/plugin/index.js +85 -10
  12. package/dist/plugin/index.js.map +1 -1
  13. package/dist/{validation-D9DXlqNP.js → validation-B-xTvM9B.js} +342 -18
  14. package/dist/validation-B-xTvM9B.js.map +1 -0
  15. package/package.json +17 -2
  16. package/src/components/Accordion.svelte +3 -1
  17. package/src/components/AccordionItem.svelte +1 -5
  18. package/src/components/Audio.svelte +17 -3
  19. package/src/components/Callout.svelte +5 -1
  20. package/src/components/Carousel.svelte +24 -8
  21. package/src/components/DefaultLayout.svelte +41 -12
  22. package/src/components/FillInTheBlank.svelte +16 -6
  23. package/src/components/Image.svelte +12 -3
  24. package/src/components/LockedBanner.svelte +2 -1
  25. package/src/components/Matching.svelte +48 -19
  26. package/src/components/MediaTracks.svelte +21 -0
  27. package/src/components/MultipleChoice.svelte +33 -13
  28. package/src/components/Quiz.svelte +61 -20
  29. package/src/components/ResultIcon.svelte +20 -4
  30. package/src/components/RevealModal.svelte +25 -22
  31. package/src/components/Sorting.svelte +61 -26
  32. package/src/components/Transcript.svelte +37 -0
  33. package/src/components/Video.svelte +21 -18
  34. package/src/components/util.ts +3 -1
  35. package/src/components/video-embed.ts +25 -0
  36. package/src/index.ts +2 -7
  37. package/src/plugin/a11y/audit.ts +299 -0
  38. package/src/plugin/a11y/contrast.ts +67 -0
  39. package/src/plugin/a11y-cli.ts +35 -0
  40. package/src/plugin/cli.ts +4 -1
  41. package/src/plugin/export.ts +42 -14
  42. package/src/plugin/index.ts +216 -44
  43. package/src/plugin/manifest.ts +62 -22
  44. package/src/plugin/validation.ts +736 -122
  45. package/src/runtime/App.svelte +119 -48
  46. package/src/runtime/LoadingBar.svelte +12 -3
  47. package/src/runtime/Sidebar.svelte +24 -8
  48. package/src/runtime/access.ts +15 -3
  49. package/src/runtime/adapters/cmi5.ts +55 -33
  50. package/src/runtime/adapters/index.ts +22 -10
  51. package/src/runtime/adapters/retry.ts +25 -20
  52. package/src/runtime/adapters/scorm-base.ts +19 -15
  53. package/src/runtime/adapters/scorm12.ts +7 -8
  54. package/src/runtime/adapters/scorm2004.ts +11 -14
  55. package/src/runtime/adapters/web.ts +1 -1
  56. package/src/runtime/hooks.svelte.ts +152 -326
  57. package/src/runtime/interaction-format.ts +30 -12
  58. package/src/runtime/interaction.ts +44 -11
  59. package/src/runtime/navigation.svelte.ts +27 -11
  60. package/src/runtime/persistence.ts +2 -2
  61. package/src/runtime/progress.svelte.ts +13 -9
  62. package/src/runtime/quiz-engine.svelte.ts +361 -0
  63. package/src/runtime/quiz-policy.ts +9 -3
  64. package/src/runtime/types.ts +24 -2
  65. package/src/runtime/xapi/agent-rules.ts +4 -1
  66. package/src/runtime/xapi/client.ts +5 -5
  67. package/src/runtime/xapi/derive-actor.ts +2 -2
  68. package/src/runtime/xapi/publisher.ts +32 -29
  69. package/src/runtime/xapi/setup.ts +18 -15
  70. package/src/runtime/xapi/validation.ts +15 -6
  71. package/src/virtual.d.ts +4 -1
  72. package/styles/base.css +32 -11
  73. package/styles/layout.css +39 -18
  74. package/styles/theme.css +15 -3
  75. package/dist/validation-D9DXlqNP.js.map +0 -1
@@ -127,7 +127,7 @@ export class XAPIPublisher {
127
127
  }
128
128
  if (!/^https?:\/\//i.test(opts.endpoint)) {
129
129
  throw new XAPIConfigError(
130
- 'XAPIPublisher: endpoint must be an absolute http(s) URL'
130
+ 'XAPIPublisher: endpoint must be an absolute http(s) URL',
131
131
  );
132
132
  }
133
133
  if (!opts.activityId) {
@@ -173,7 +173,7 @@ export class XAPIPublisher {
173
173
  resolved = await this.#actorValue();
174
174
  } catch (err) {
175
175
  throw new XAPIConfigError(
176
- `xapi.actor resolver threw: ${err instanceof Error ? err.message : String(err)}`
176
+ `xapi.actor resolver threw: ${err instanceof Error ? err.message : String(err)}`,
177
177
  );
178
178
  }
179
179
  const err = validateAgent(resolved);
@@ -199,7 +199,7 @@ export class XAPIPublisher {
199
199
  getActor(): XAPIAgent {
200
200
  if (!this.#cachedActor) {
201
201
  throw new XAPIConfigError(
202
- 'XAPIPublisher.getActor() called before init() resolved. Await publisher.init() before reading the actor.'
202
+ 'XAPIPublisher.getActor() called before init() resolved. Await publisher.init() before reading the actor.',
203
203
  );
204
204
  }
205
205
  return this.#cachedActor;
@@ -231,7 +231,7 @@ export class XAPIPublisher {
231
231
  buildStatement(partial: PartialStatement, opts?: { id?: string }): Statement {
232
232
  if (!this.#cachedActor) {
233
233
  throw new XAPIConfigError(
234
- 'XAPIPublisher.buildStatement() called before init() resolved.'
234
+ 'XAPIPublisher.buildStatement() called before init() resolved.',
235
235
  );
236
236
  }
237
237
  const userCtx = partial.context ?? {};
@@ -274,7 +274,8 @@ export class XAPIPublisher {
274
274
  timestamp: new Date().toISOString(),
275
275
  };
276
276
  if (partial.result !== undefined) statement.result = partial.result;
277
- if (partial.attachments !== undefined) statement.attachments = partial.attachments;
277
+ if (partial.attachments !== undefined)
278
+ statement.attachments = partial.attachments;
278
279
  return statement;
279
280
  }
280
281
 
@@ -289,7 +290,7 @@ export class XAPIPublisher {
289
290
  */
290
291
  sendStatement(
291
292
  partial: PartialStatement,
292
- options?: SendStatementOptions & { id?: string }
293
+ options?: SendStatementOptions & { id?: string },
293
294
  ): Promise<SendStatementResult> {
294
295
  if (this.#unavailableReason) {
295
296
  return Promise.reject(this.#unavailableReason());
@@ -306,8 +307,8 @@ export class XAPIPublisher {
306
307
  if (!this.#cachedActor) {
307
308
  return Promise.reject(
308
309
  new XAPIConfigError(
309
- 'XAPIPublisher.sendStatement() called before init() resolved.'
310
- )
310
+ 'XAPIPublisher.sendStatement() called before init() resolved.',
311
+ ),
311
312
  );
312
313
  }
313
314
  const statement = this.buildStatement(partial, { id: options?.id });
@@ -326,7 +327,7 @@ export class XAPIPublisher {
326
327
  */
327
328
  enqueueBuilt(
328
329
  statementOrBatch: Statement | Statement[],
329
- options?: SendStatementOptions
330
+ options?: SendStatementOptions,
330
331
  ): Promise<DestinationOutcome> {
331
332
  if (this.#unavailableReason) {
332
333
  return Promise.reject(this.#unavailableReason());
@@ -336,7 +337,7 @@ export class XAPIPublisher {
336
337
  endpoint: this.#endpoint,
337
338
  ok: false,
338
339
  error: new XAPIConfigError(
339
- `XAPIPublisher queue saturated (${this.#queueDepth} in-flight); refusing further sends until the LRS catches up.`
340
+ `XAPIPublisher queue saturated (${this.#queueDepth} in-flight); refusing further sends until the LRS catches up.`,
340
341
  ),
341
342
  });
342
343
  }
@@ -345,7 +346,7 @@ export class XAPIPublisher {
345
346
  console.warn(
346
347
  `Tessera: xAPI publisher queue depth ${this.#queueDepth} (>= ${QUEUE_DEPTH_WARN}). ` +
347
348
  `Each pending statement is retained in the promise chain's closure until it drains; ` +
348
- `consider rate-limiting authoring sends or batching before sendStatement.`
349
+ `consider rate-limiting authoring sends or batching before sendStatement.`,
349
350
  );
350
351
  }
351
352
  this.#queueDepth++;
@@ -367,7 +368,7 @@ export class XAPIPublisher {
367
368
  status: outcome.status,
368
369
  error: outcome.error,
369
370
  });
370
- })
371
+ }),
371
372
  );
372
373
  return outcomePromise;
373
374
  }
@@ -385,7 +386,9 @@ export class XAPIPublisher {
385
386
  resolveTask = r;
386
387
  });
387
388
  this.#queue = this.#queue.then(() =>
388
- fn().catch(() => {}).then(() => resolveTask())
389
+ fn()
390
+ .catch(() => {})
391
+ .then(() => resolveTask()),
389
392
  );
390
393
  return taskPromise;
391
394
  }
@@ -413,7 +416,7 @@ export class XAPIPublisher {
413
416
 
414
417
  #sendWithRetry(
415
418
  statementOrBatch: Statement | Statement[],
416
- options?: SendStatementOptions
419
+ options?: SendStatementOptions,
417
420
  ): Promise<SendOutcome> {
418
421
  const body = JSON.stringify(statementOrBatch);
419
422
  const retry = options?.retry !== false; // default: retry enabled
@@ -432,7 +435,7 @@ export class XAPIPublisher {
432
435
  `Tessera: xAPI ${count}-statement batch is ${body.length} bytes, ` +
433
436
  `over the 64 KiB keepalive cap. The browser may silently drop this ` +
434
437
  `request during unload. Reduce per-statement size or split sends ` +
435
- `before terminate.`
438
+ `before terminate.`,
436
439
  );
437
440
  }
438
441
  return this.#sendOnce(body, keepalive).then((outcome) => {
@@ -447,9 +450,9 @@ export class XAPIPublisher {
447
450
  return outcome;
448
451
  }
449
452
  if (isFinal) return outcome;
450
- return new Promise<void>((r) =>
451
- setTimeout(r, backoffMs(n))
452
- ).then(() => attempt(n + 1));
453
+ return new Promise<void>((r) => setTimeout(r, backoffMs(n))).then(() =>
454
+ attempt(n + 1),
455
+ );
453
456
  });
454
457
  };
455
458
 
@@ -461,7 +464,7 @@ export class XAPIPublisher {
461
464
  return Promise.resolve({
462
465
  ok: false,
463
466
  error: new XAPIConfigError(
464
- 'xapi.auth was rejected by the LRS twice in a row; refusing further sends for the publisher lifetime. Reload the runtime to retry.'
467
+ 'xapi.auth was rejected by the LRS twice in a row; refusing further sends for the publisher lifetime. Reload the runtime to retry.',
465
468
  ),
466
469
  });
467
470
  }
@@ -482,7 +485,7 @@ export class XAPIPublisher {
482
485
  #fetchWithToken(
483
486
  token: string,
484
487
  body: string,
485
- keepalive: boolean
488
+ keepalive: boolean,
486
489
  ): Promise<SendOutcome> {
487
490
  const headers = new Headers();
488
491
  if (token) headers.set('Authorization', `Basic ${token}`);
@@ -504,7 +507,7 @@ export class XAPIPublisher {
504
507
  #handleResponse(
505
508
  resp: Response,
506
509
  body: string,
507
- keepalive: boolean
510
+ keepalive: boolean,
508
511
  ): Promise<SendOutcome> | SendOutcome {
509
512
  if (resp.ok || resp.status === 409) {
510
513
  return { ok: true, status: resp.status };
@@ -540,7 +543,7 @@ export class XAPIPublisher {
540
543
  ok: false,
541
544
  status: 401,
542
545
  error: new Error(
543
- 'LRS rejected re-resolved auth (consecutive 401s); auth resolver marked dead'
546
+ 'LRS rejected re-resolved auth (consecutive 401s); auth resolver marked dead',
544
547
  ),
545
548
  };
546
549
  }
@@ -555,7 +558,7 @@ export class XAPIPublisher {
555
558
  ok: false,
556
559
  status: 401,
557
560
  error: err instanceof Error ? err : new Error(String(err)),
558
- })
561
+ }),
559
562
  );
560
563
  }
561
564
  // Append the LRS body to the error message so callers see the
@@ -573,14 +576,14 @@ export class XAPIPublisher {
573
576
  ok: false,
574
577
  status: resp.status,
575
578
  error: new Error(
576
- `LRS responded ${resp.status}${respBody ? `: ${respBody.slice(0, 500)}` : ''}`
579
+ `LRS responded ${resp.status}${respBody ? `: ${respBody.slice(0, 500)}` : ''}`,
577
580
  ),
578
581
  }),
579
582
  (): SendOutcome => ({
580
583
  ok: false,
581
584
  status: resp.status,
582
585
  error: new Error(`LRS responded ${resp.status}`),
583
- })
586
+ }),
584
587
  );
585
588
  }
586
589
 
@@ -593,7 +596,8 @@ export class XAPIPublisher {
593
596
  // where the LMS fetch URL produced no token); otherwise revalidate.
594
597
  if (this.#authValue.length > 0) {
595
598
  const err = validateAuthCredential(this.#authValue);
596
- if (err) return Promise.reject(new XAPIConfigError(`xapi.auth: ${err}`));
599
+ if (err)
600
+ return Promise.reject(new XAPIConfigError(`xapi.auth: ${err}`));
597
601
  }
598
602
  this.#cachedAuth = this.#authValue;
599
603
  return Promise.resolve(this.#cachedAuth);
@@ -603,7 +607,7 @@ export class XAPIPublisher {
603
607
  .then((resolved) => {
604
608
  if (typeof resolved !== 'string' || !resolved) {
605
609
  throw new XAPIConfigError(
606
- 'xapi.auth resolver must return a non-empty string'
610
+ 'xapi.auth resolver must return a non-empty string',
607
611
  );
608
612
  }
609
613
  const err = validateAuthCredential(resolved);
@@ -614,9 +618,8 @@ export class XAPIPublisher {
614
618
  .catch((err) => {
615
619
  if (err instanceof XAPIConfigError) throw err;
616
620
  throw new XAPIConfigError(
617
- `xapi.auth resolver threw: ${err instanceof Error ? err.message : String(err)}`
621
+ `xapi.auth resolver threw: ${err instanceof Error ? err.message : String(err)}`,
618
622
  );
619
623
  });
620
624
  }
621
625
  }
622
-
@@ -34,8 +34,8 @@ class XAPIDevFallbackError extends Error {
34
34
  "Tessera xAPI: xapi.endpoint is 'lms' but no cmi5 launch parameters " +
35
35
  '(fetch / endpoint / activityId / actor) were present on the URL. ' +
36
36
  'Either launch this course from a real LMS / SCORM Cloud, or ' +
37
- "temporarily change xapi.endpoint to an explicit URL pointed at a " +
38
- 'local LRS (e.g. http://localhost:8080/data/xAPI/) for dev work.'
37
+ 'temporarily change xapi.endpoint to an explicit URL pointed at a ' +
38
+ 'local LRS (e.g. http://localhost:8080/data/xAPI/) for dev work.',
39
39
  );
40
40
  this.name = 'XAPIDevFallbackError';
41
41
  }
@@ -72,14 +72,14 @@ class XAPISCORMDevFallbackError extends Error {
72
72
  'destination. Either supply xapi.actor explicitly in course.config.js, or launch from ' +
73
73
  'a real LMS / SCORM Cloud where ' +
74
74
  (standard === 'scorm12' ? 'cmi.core.student_id' : 'cmi.learner_id') +
75
- ' is populated.'
75
+ ' is populated.',
76
76
  );
77
77
  this.name = 'XAPISCORMDevFallbackError';
78
78
  }
79
79
  }
80
80
 
81
81
  function makeSCORMDevFallbackPublisher(
82
- standard: 'scorm12' | 'scorm2004'
82
+ standard: 'scorm12' | 'scorm2004',
83
83
  ): XAPIPublisher {
84
84
  return makeRejectingPublisher(() => new XAPISCORMDevFallbackError(standard));
85
85
  }
@@ -93,13 +93,13 @@ function makeSCORMDevFallbackPublisher(
93
93
  function resolveDestination(
94
94
  entry: XAPIConfig,
95
95
  config: CourseConfig,
96
- adapter: PersistenceAdapter | null
96
+ adapter: PersistenceAdapter | null,
97
97
  ): DestinationSource | null {
98
98
  if (entry.endpoint === 'lms') {
99
99
  if (config.export?.standard !== 'cmi5') {
100
100
  // Build-time validator should reject this; defense in depth at runtime.
101
101
  console.warn(
102
- "Tessera xAPI: ignoring xapi entry with endpoint: 'lms' under non-cmi5 export."
102
+ "Tessera xAPI: ignoring xapi entry with endpoint: 'lms' under non-cmi5 export.",
103
103
  );
104
104
  return null;
105
105
  }
@@ -121,14 +121,17 @@ function resolveDestination(
121
121
  (actorOrResolver as { __scormDevFallback?: 'scorm12' | 'scorm2004' })
122
122
  .__scormDevFallback
123
123
  ) {
124
- const std = (actorOrResolver as { __scormDevFallback: 'scorm12' | 'scorm2004' })
125
- .__scormDevFallback;
124
+ const std = (
125
+ actorOrResolver as { __scormDevFallback: 'scorm12' | 'scorm2004' }
126
+ ).__scormDevFallback;
126
127
  return { kind: 'explicit', publisher: makeSCORMDevFallbackPublisher(std) };
127
128
  }
128
129
  const publisher = new XAPIPublisher({
129
130
  endpoint: explicit.endpoint,
130
131
  auth: explicit.auth,
131
- actor: actorOrResolver as XAPIAgent | (() => XAPIAgent | Promise<XAPIAgent>),
132
+ actor: actorOrResolver as
133
+ | XAPIAgent
134
+ | (() => XAPIAgent | Promise<XAPIAgent>),
132
135
  activityId: explicit.activityId,
133
136
  registration: explicit.registration,
134
137
  });
@@ -145,7 +148,7 @@ function resolveDestination(
145
148
  function resolveExplicitActor(
146
149
  explicit: XAPIExplicitConfig,
147
150
  config: CourseConfig,
148
- adapter: PersistenceAdapter | null
151
+ adapter: PersistenceAdapter | null,
149
152
  ):
150
153
  | XAPIAgent
151
154
  | (() => XAPIAgent | Promise<XAPIAgent>)
@@ -170,7 +173,7 @@ function resolveExplicitActor(
170
173
  return synthesizeSCORM12Actor(
171
174
  adapter.getAPI(),
172
175
  explicit.activityId,
173
- explicit.actorAccountHomePage
176
+ explicit.actorAccountHomePage,
174
177
  );
175
178
  }
176
179
  // Adapter is the WebAdapter dev fallback. Mirror the cmi5 'lms'
@@ -184,14 +187,14 @@ function resolveExplicitActor(
184
187
  return synthesizeSCORM2004Actor(
185
188
  adapter.getAPI(),
186
189
  explicit.activityId,
187
- explicit.actorAccountHomePage
190
+ explicit.actorAccountHomePage,
188
191
  );
189
192
  }
190
193
  return { __scormDevFallback: 'scorm2004' };
191
194
  }
192
195
  // Web export with no actor — build-time validator should have errored.
193
196
  console.warn(
194
- 'Tessera xAPI: explicit destination has no actor and no derivation source — skipping.'
197
+ 'Tessera xAPI: explicit destination has no actor and no derivation source — skipping.',
195
198
  );
196
199
  return null;
197
200
  }
@@ -206,7 +209,7 @@ function resolveExplicitActor(
206
209
  */
207
210
  export async function buildXAPIClient(
208
211
  config: CourseConfig,
209
- adapter: PersistenceAdapter | null
212
+ adapter: PersistenceAdapter | null,
210
213
  ): Promise<XAPIClient | null> {
211
214
  const raw = config.xapi;
212
215
  if (raw === undefined || raw === null) return null;
@@ -233,7 +236,7 @@ export async function buildXAPIClient(
233
236
  } catch (err) {
234
237
  console.warn(
235
238
  'Tessera xAPI: failed to initialize an explicit destination — skipping.',
236
- err
239
+ err,
237
240
  );
238
241
  }
239
242
  }
@@ -1,5 +1,9 @@
1
1
  import type { PartialStatement } from './types.js';
2
- export { validateAgent, validateAuthCredential, joinFieldError } from './agent-rules.js';
2
+ export {
3
+ validateAgent,
4
+ validateAuthCredential,
5
+ joinFieldError,
6
+ } from './agent-rules.js';
3
7
 
4
8
  /** Thrown for runtime-validation failures (auth/actor resolver misuse). */
5
9
  export class XAPIConfigError extends Error {
@@ -32,7 +36,7 @@ export function validatePartialStatement(partial: PartialStatement): void {
32
36
  if (!partial || typeof partial !== 'object') {
33
37
  throw new XAPIStatementError(
34
38
  'sendStatement: partial statement must be an object',
35
- partial
39
+ partial,
36
40
  );
37
41
  }
38
42
  if (
@@ -43,7 +47,7 @@ export function validatePartialStatement(partial: PartialStatement): void {
43
47
  ) {
44
48
  throw new XAPIStatementError(
45
49
  'sendStatement: verb.id is required and must be a non-empty string',
46
- partial
50
+ partial,
47
51
  );
48
52
  }
49
53
  if (partial.object !== undefined) {
@@ -55,16 +59,21 @@ export function validatePartialStatement(partial: PartialStatement): void {
55
59
  ) {
56
60
  throw new XAPIStatementError(
57
61
  'sendStatement: object.id must be a non-empty string when object is supplied',
58
- partial
62
+ partial,
59
63
  );
60
64
  }
61
65
  }
62
66
  const scaled = partial.result?.score?.scaled;
63
67
  if (scaled !== undefined) {
64
- if (typeof scaled !== 'number' || !Number.isFinite(scaled) || scaled < -1 || scaled > 1) {
68
+ if (
69
+ typeof scaled !== 'number' ||
70
+ !Number.isFinite(scaled) ||
71
+ scaled < -1 ||
72
+ scaled > 1
73
+ ) {
65
74
  throw new XAPIStatementError(
66
75
  `sendStatement: result.score.scaled must be a number in [-1, 1], got ${scaled}`,
67
- partial
76
+ partial,
68
77
  );
69
78
  }
70
79
  }
package/src/virtual.d.ts CHANGED
@@ -14,7 +14,10 @@ declare module 'virtual:tessera-xapi-setup' {
14
14
  import type { CourseConfig } from 'tessera-learn/runtime/types.js';
15
15
  import type { PersistenceAdapter } from 'tessera-learn/runtime/persistence.js';
16
16
  import type { XAPIClient } from 'tessera-learn/runtime/xapi/client.js';
17
- export function buildXAPIClient(config: CourseConfig, adapter: PersistenceAdapter): Promise<XAPIClient | null>;
17
+ export function buildXAPIClient(
18
+ config: CourseConfig,
19
+ adapter: PersistenceAdapter,
20
+ ): Promise<XAPIClient | null>;
18
21
  }
19
22
 
20
23
  interface ImportMetaEnv {
package/styles/base.css CHANGED
@@ -47,7 +47,12 @@ object {
47
47
  }
48
48
 
49
49
  /* ---- Typography ---- */
50
- h1, h2, h3, h4, h5, h6 {
50
+ h1,
51
+ h2,
52
+ h3,
53
+ h4,
54
+ h5,
55
+ h6 {
51
56
  font-weight: 700;
52
57
  line-height: 1.25;
53
58
  color: var(--tessera-text);
@@ -55,12 +60,24 @@ h1, h2, h3, h4, h5, h6 {
55
60
  margin-bottom: var(--tessera-spacing-md);
56
61
  }
57
62
 
58
- h1 { font-size: 2rem; }
59
- h2 { font-size: 1.625rem; }
60
- h3 { font-size: 1.375rem; }
61
- h4 { font-size: 1.125rem; }
62
- h5 { font-size: 1rem; }
63
- h6 { font-size: 0.875rem; }
63
+ h1 {
64
+ font-size: 2rem;
65
+ }
66
+ h2 {
67
+ font-size: 1.625rem;
68
+ }
69
+ h3 {
70
+ font-size: 1.375rem;
71
+ }
72
+ h4 {
73
+ font-size: 1.125rem;
74
+ }
75
+ h5 {
76
+ font-size: 1rem;
77
+ }
78
+ h6 {
79
+ font-size: 0.875rem;
80
+ }
64
81
 
65
82
  h1:first-child,
66
83
  h2:first-child,
@@ -90,16 +107,19 @@ a:focus-visible {
90
107
  border-radius: 2px;
91
108
  }
92
109
 
93
- strong, b {
110
+ strong,
111
+ b {
94
112
  font-weight: 700;
95
113
  }
96
114
 
97
- em, i {
115
+ em,
116
+ i {
98
117
  font-style: italic;
99
118
  }
100
119
 
101
120
  /* ---- Lists ---- */
102
- ul, ol {
121
+ ul,
122
+ ol {
103
123
  margin-bottom: var(--tessera-spacing-md);
104
124
  padding-left: var(--tessera-spacing-xl);
105
125
  }
@@ -170,7 +190,8 @@ table {
170
190
  font-size: 0.9375rem;
171
191
  }
172
192
 
173
- th, td {
193
+ th,
194
+ td {
174
195
  padding: var(--tessera-spacing-sm) var(--tessera-spacing-md);
175
196
  border: 1px solid var(--tessera-border);
176
197
  text-align: left;
package/styles/layout.css CHANGED
@@ -104,8 +104,9 @@
104
104
  background: none;
105
105
  cursor: pointer;
106
106
  text-align: left;
107
- transition: background-color var(--tessera-transition-fast),
108
- color var(--tessera-transition-fast);
107
+ transition:
108
+ background-color var(--tessera-transition-fast),
109
+ color var(--tessera-transition-fast);
109
110
  border-left: 3px solid transparent;
110
111
  }
111
112
 
@@ -114,19 +115,19 @@
114
115
  color: var(--tessera-text);
115
116
  }
116
117
 
117
- .tessera-nav-page[aria-current="page"] {
118
+ .tessera-nav-page[aria-current='page'] {
118
119
  background-color: var(--tessera-primary-light);
119
120
  color: var(--tessera-primary-dark);
120
121
  font-weight: 600;
121
122
  border-left-color: var(--tessera-primary);
122
123
  }
123
124
 
124
- .tessera-nav-page[aria-disabled="true"] {
125
+ .tessera-nav-page[aria-disabled='true'] {
125
126
  opacity: 0.45;
126
127
  cursor: not-allowed;
127
128
  }
128
129
 
129
- .tessera-nav-page[aria-disabled="true"]:hover {
130
+ .tessera-nav-page[aria-disabled='true']:hover {
130
131
  background-color: transparent;
131
132
  color: var(--tessera-text-light);
132
133
  }
@@ -176,8 +177,9 @@
176
177
  border: 1px solid var(--tessera-primary);
177
178
  border-radius: 6px;
178
179
  cursor: pointer;
179
- transition: background-color var(--tessera-transition-fast),
180
- color var(--tessera-transition-fast);
180
+ transition:
181
+ background-color var(--tessera-transition-fast),
182
+ color var(--tessera-transition-fast);
181
183
  }
182
184
 
183
185
  .tessera-page-nav-btn:hover:not(:disabled) {
@@ -250,8 +252,9 @@
250
252
  height: 2px;
251
253
  background-color: var(--tessera-text);
252
254
  border-radius: 1px;
253
- transition: transform var(--tessera-transition-fast),
254
- opacity var(--tessera-transition-fast);
255
+ transition:
256
+ transform var(--tessera-transition-fast),
257
+ opacity var(--tessera-transition-fast);
255
258
  }
256
259
 
257
260
  .tessera-hamburger-lines {
@@ -260,15 +263,21 @@
260
263
  gap: 5px;
261
264
  }
262
265
 
263
- .tessera-hamburger[aria-expanded="true"] .tessera-hamburger-lines .tessera-hamburger-line:nth-child(1) {
266
+ .tessera-hamburger[aria-expanded='true']
267
+ .tessera-hamburger-lines
268
+ .tessera-hamburger-line:nth-child(1) {
264
269
  transform: translateY(7px) rotate(45deg);
265
270
  }
266
271
 
267
- .tessera-hamburger[aria-expanded="true"] .tessera-hamburger-lines .tessera-hamburger-line:nth-child(2) {
272
+ .tessera-hamburger[aria-expanded='true']
273
+ .tessera-hamburger-lines
274
+ .tessera-hamburger-line:nth-child(2) {
268
275
  opacity: 0;
269
276
  }
270
277
 
271
- .tessera-hamburger[aria-expanded="true"] .tessera-hamburger-lines .tessera-hamburger-line:nth-child(3) {
278
+ .tessera-hamburger[aria-expanded='true']
279
+ .tessera-hamburger-lines
280
+ .tessera-hamburger-line:nth-child(3) {
272
281
  transform: translateY(-7px) rotate(-45deg);
273
282
  }
274
283
 
@@ -334,7 +343,9 @@
334
343
  cursor: pointer;
335
344
  min-height: 44px;
336
345
  font-family: var(--tessera-font-family);
337
- transition: background-color var(--tessera-transition-fast), opacity var(--tessera-transition-fast);
346
+ transition:
347
+ background-color var(--tessera-transition-fast),
348
+ opacity var(--tessera-transition-fast);
338
349
  }
339
350
 
340
351
  .tessera-btn-primary:hover:not(:disabled) {
@@ -401,7 +412,9 @@
401
412
  }
402
413
 
403
414
  .tessera-content {
404
- padding-top: calc(var(--tessera-spacing-xl) + 44px + var(--tessera-spacing-md));
415
+ padding-top: calc(
416
+ var(--tessera-spacing-xl) + 44px + var(--tessera-spacing-md)
417
+ );
405
418
  }
406
419
  }
407
420
 
@@ -409,7 +422,9 @@
409
422
  @media (max-width: 640px) {
410
423
  .tessera-content {
411
424
  padding: var(--tessera-spacing-md);
412
- padding-top: calc(var(--tessera-spacing-md) + 44px + var(--tessera-spacing-md));
425
+ padding-top: calc(
426
+ var(--tessera-spacing-md) + 44px + var(--tessera-spacing-md)
427
+ );
413
428
  }
414
429
 
415
430
  .tessera-page-nav {
@@ -423,7 +438,13 @@
423
438
  font-size: 1rem;
424
439
  }
425
440
 
426
- h1 { font-size: 1.625rem; }
427
- h2 { font-size: 1.375rem; }
428
- h3 { font-size: 1.125rem; }
441
+ h1 {
442
+ font-size: 1.625rem;
443
+ }
444
+ h2 {
445
+ font-size: 1.375rem;
446
+ }
447
+ h3 {
448
+ font-size: 1.125rem;
449
+ }
429
450
  }
package/styles/theme.css CHANGED
@@ -9,11 +9,23 @@
9
9
  --tessera-bg-secondary: #f9fafb;
10
10
  --tessera-border: #e5e7eb;
11
11
  --tessera-success: #16a34a;
12
- --tessera-success-bg: color-mix(in srgb, var(--tessera-success) 8%, transparent);
13
- --tessera-success-border: color-mix(in srgb, var(--tessera-success) 25%, transparent);
12
+ --tessera-success-bg: color-mix(
13
+ in srgb,
14
+ var(--tessera-success) 8%,
15
+ transparent
16
+ );
17
+ --tessera-success-border: color-mix(
18
+ in srgb,
19
+ var(--tessera-success) 25%,
20
+ transparent
21
+ );
14
22
  --tessera-error: #dc2626;
15
23
  --tessera-error-bg: color-mix(in srgb, var(--tessera-error) 8%, transparent);
16
- --tessera-error-border: color-mix(in srgb, var(--tessera-error) 25%, transparent);
24
+ --tessera-error-border: color-mix(
25
+ in srgb,
26
+ var(--tessera-error) 25%,
27
+ transparent
28
+ );
17
29
  --tessera-warning: #d97706;
18
30
 
19
31
  /* Typography */