relic-mcp 0.14.4 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "relic",
12
12
  "description": "Publish a file from your machine as an encrypted, shareable link. The agent encrypts locally, uploads only ciphertext, and hands back a URL whose fragment holds the key, so the service stores something it cannot read.",
13
- "version": "0.14.4",
13
+ "version": "0.15.0",
14
14
  "source": "./",
15
15
  "author": {
16
16
  "name": "The Bushido Collective",
@@ -21,6 +21,6 @@
21
21
  }
22
22
  ],
23
23
  "metadata": {
24
- "version": "0.14.4"
24
+ "version": "0.15.0"
25
25
  }
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relic",
3
- "version": "0.14.4",
3
+ "version": "0.15.0",
4
4
  "description": "Publish a file from your machine as an encrypted, shareable link. The agent encrypts locally, uploads only ciphertext, and hands back a URL whose fragment holds the key, so the service stores something it cannot read.",
5
5
  "mcpServers": "./mcp-servers.json",
6
6
  "author": {
@@ -1202,6 +1202,68 @@ var require_build = __commonJS(function(exports) {
1202
1202
  exports.default = LinesAndColumns;
1203
1203
  });
1204
1204
 
1205
+ // ../relic-viewer/src/content.ts
1206
+ var CONTENT_BATCH_BYTES = 8 * 1024 * 1024;
1207
+ var CONTENT_HEAD_BYTES = 65536;
1208
+ var CONTENT_TAIL_BYTES = 65557;
1209
+ var MAX_CONTENT_READ_BYTES = 16 * 1024 * 1024;
1210
+ var DOCUMENT_RENDER_LIMIT_BYTES = 100 * 1024 * 1024;
1211
+
1212
+ class ContentStoreError extends Error {
1213
+ constructor(cause) {
1214
+ super("The browser could not hold the decrypted content.", { cause });
1215
+ this.name = "ContentStoreError";
1216
+ }
1217
+ }
1218
+ function sliceBlob(blob, start, end, type) {
1219
+ try {
1220
+ return blob.slice(start, end, type);
1221
+ } catch (cause) {
1222
+ throw new ContentStoreError(cause);
1223
+ }
1224
+ }
1225
+ async function readBlob(blob) {
1226
+ try {
1227
+ const bytes = new Uint8Array(await blob.arrayBuffer());
1228
+ if (bytes.length !== blob.size)
1229
+ throw new Error("short Blob read");
1230
+ return bytes;
1231
+ } catch (cause) {
1232
+ throw new ContentStoreError(cause);
1233
+ }
1234
+ }
1235
+ function contentFromParts(base, head, tail) {
1236
+ return {
1237
+ size: base.size,
1238
+ head,
1239
+ tail,
1240
+ blob(type) {
1241
+ return sliceBlob(base, 0, base.size, type);
1242
+ },
1243
+ async read(start, end) {
1244
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > base.size || end - start > MAX_CONTENT_READ_BYTES) {
1245
+ throw new RangeError("Invalid or oversized content read");
1246
+ }
1247
+ return readBlob(sliceBlob(base, start, end));
1248
+ },
1249
+ async readWhole(limit) {
1250
+ if (!Number.isSafeInteger(limit) || limit < 0 || limit > DOCUMENT_RENDER_LIMIT_BYTES) {
1251
+ throw new RangeError("Invalid whole-content read limit");
1252
+ }
1253
+ if (base.size > limit)
1254
+ return;
1255
+ return readBlob(base);
1256
+ }
1257
+ };
1258
+ }
1259
+ async function contentFromBlob(blob) {
1260
+ const [head, tail] = await Promise.all([
1261
+ readBlob(sliceBlob(blob, 0, Math.min(blob.size, CONTENT_HEAD_BYTES))),
1262
+ readBlob(sliceBlob(blob, Math.max(0, blob.size - CONTENT_TAIL_BYTES), blob.size))
1263
+ ]);
1264
+ return contentFromParts(blob, head, tail);
1265
+ }
1266
+
1205
1267
  // ../relic-viewer/src/markdown.ts
1206
1268
  function escapeHtml(value) {
1207
1269
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -44726,6 +44788,81 @@ globalThis.pdfjsLib = {
44726
44788
  XfaLayer
44727
44789
  };
44728
44790
 
44791
+ // ../relic-viewer/src/pdf.ts
44792
+ var PDF_INITIAL_BYTES = 1024 * 1024;
44793
+ var PDF_RANGE_CHUNK_SIZE = 65536;
44794
+
44795
+ class RelicPdfRangeTransport extends PDFDataRangeTransport {
44796
+ content;
44797
+ onError;
44798
+ aborted = false;
44799
+ failed = false;
44800
+ constructor(content, initialData, onError) {
44801
+ super(content.size, initialData);
44802
+ this.content = content;
44803
+ this.onError = onError;
44804
+ }
44805
+ requestDataRange(begin, end) {
44806
+ if (this.aborted || this.failed)
44807
+ return;
44808
+ this.readRange(begin, end).then((bytes) => {
44809
+ if (!this.aborted && !this.failed)
44810
+ this.onDataRange(begin, bytes);
44811
+ }).catch((error) => {
44812
+ if (this.aborted || this.failed)
44813
+ return;
44814
+ this.failed = true;
44815
+ this.onError(error);
44816
+ });
44817
+ }
44818
+ abort() {
44819
+ this.aborted = true;
44820
+ }
44821
+ async readRange(begin, end) {
44822
+ if (!Number.isSafeInteger(begin) || !Number.isSafeInteger(end) || begin < 0 || end < begin || end > this.content.size) {
44823
+ throw new RangeError("Invalid PDF range");
44824
+ }
44825
+ const length = end - begin;
44826
+ if (length <= MAX_CONTENT_READ_BYTES) {
44827
+ const bytes = await this.content.read(begin, end);
44828
+ if (bytes.byteLength !== length)
44829
+ throw new Error("Short PDF range read");
44830
+ return bytes;
44831
+ }
44832
+ const bytes = new Uint8Array(length);
44833
+ for (let start = begin;start < end; start += MAX_CONTENT_READ_BYTES) {
44834
+ if (this.aborted)
44835
+ return new Uint8Array(0);
44836
+ const stop = Math.min(start + MAX_CONTENT_READ_BYTES, end);
44837
+ const chunk = await this.content.read(start, stop);
44838
+ if (chunk.byteLength !== stop - start)
44839
+ throw new Error("Short PDF range read");
44840
+ bytes.set(chunk, start - begin);
44841
+ }
44842
+ return bytes;
44843
+ }
44844
+ }
44845
+ async function createPdfLoadingTask(content, signal) {
44846
+ const initialData = await content.read(0, Math.min(content.size, PDF_INITIAL_BYTES));
44847
+ if (signal?.aborted)
44848
+ throw new Error("PDF loading cancelled");
44849
+ if (initialData.byteLength !== Math.min(content.size, PDF_INITIAL_BYTES))
44850
+ throw new Error("Short PDF initial read");
44851
+ const { promise: failure, reject: rejectFailure } = Promise.withResolvers();
44852
+ failure.catch(() => {
44853
+ return;
44854
+ });
44855
+ const range = new RelicPdfRangeTransport(content, initialData, rejectFailure);
44856
+ const task = getDocument({
44857
+ range,
44858
+ length: content.size,
44859
+ rangeChunkSize: PDF_RANGE_CHUNK_SIZE,
44860
+ disableAutoFetch: true,
44861
+ disableStream: true
44862
+ });
44863
+ return { task, failure };
44864
+ }
44865
+
44729
44866
  // ../relic-viewer/src/cover/pdf-trim.ts
44730
44867
  var PDF_GUTTER_PX = 20;
44731
44868
  var PDF_CANVAS_WIDTH = 600;
@@ -44778,21 +44915,21 @@ function trimPdfPage(ink, pageWidth) {
44778
44915
  // ../relic-viewer/src/cover/pdf.ts
44779
44916
  var PDF_CANVAS_HEIGHT = 315;
44780
44917
  var PDF_DEVICE_SCALE = 2;
44781
- var workerInitialized = false;
44782
44918
  function ensurePdfWorker() {
44783
- if (!workerInitialized) {
44784
- GlobalWorkerOptions.workerPort = new Worker("/pdf.worker.mjs", {
44785
- type: "module"
44786
- });
44787
- workerInitialized = true;
44788
- }
44919
+ GlobalWorkerOptions.workerSrc = "/pdf.worker.mjs";
44789
44920
  }
44790
- async function renderPdfCover(bytes, root) {
44921
+ async function renderPdfCover(content, root) {
44791
44922
  ensurePdfWorker();
44792
- let pdfDoc;
44793
44923
  try {
44794
- const loadingTask = getDocument({ data: bytes });
44795
- pdfDoc = await loadingTask.promise;
44924
+ const { task, failure } = await createPdfLoadingTask(content);
44925
+ try {
44926
+ const pdfDoc = await Promise.race([task.promise, failure]);
44927
+ return await renderPdfCoverDocument(pdfDoc, root, failure);
44928
+ } finally {
44929
+ await task.destroy().catch(() => {
44930
+ return;
44931
+ });
44932
+ }
44796
44933
  } catch (err) {
44797
44934
  return {
44798
44935
  ok: false,
@@ -44800,9 +44937,11 @@ async function renderPdfCover(bytes, root) {
44800
44937
  detail: err instanceof Error ? err.message : String(err)
44801
44938
  };
44802
44939
  }
44940
+ }
44941
+ async function renderPdfCoverDocument(pdfDoc, root, failure) {
44803
44942
  let page;
44804
44943
  try {
44805
- page = await pdfDoc.getPage(1);
44944
+ page = await Promise.race([pdfDoc.getPage(1), failure]);
44806
44945
  } catch (err) {
44807
44946
  return {
44808
44947
  ok: false,
@@ -44827,11 +44966,14 @@ async function renderPdfCover(bytes, root) {
44827
44966
  };
44828
44967
  }
44829
44968
  try {
44830
- await page.render({
44831
- canvasContext: measureCtx,
44832
- viewport: refViewport,
44833
- canvas: measureCanvas
44834
- }).promise;
44969
+ await Promise.race([
44970
+ page.render({
44971
+ canvasContext: measureCtx,
44972
+ viewport: refViewport,
44973
+ canvas: measureCanvas
44974
+ }).promise,
44975
+ failure
44976
+ ]);
44835
44977
  } catch (err) {
44836
44978
  return {
44837
44979
  ok: false,
@@ -44864,11 +45006,14 @@ async function renderPdfCover(bytes, root) {
44864
45006
  };
44865
45007
  }
44866
45008
  try {
44867
- await page.render({
44868
- canvasContext: renderCtx,
44869
- viewport: finalViewport,
44870
- canvas: renderCanvas
44871
- }).promise;
45009
+ await Promise.race([
45010
+ page.render({
45011
+ canvasContext: renderCtx,
45012
+ viewport: finalViewport,
45013
+ canvas: renderCanvas
45014
+ }).promise,
45015
+ failure
45016
+ ]);
44872
45017
  } catch (err) {
44873
45018
  return {
44874
45019
  ok: false,
@@ -44896,7 +45041,7 @@ async function fetchInputBlob(input) {
44896
45041
  if (!res.ok) {
44897
45042
  throw new Error(`Failed to fetch input part ${i}: ${res.status}`);
44898
45043
  }
44899
- parts.push(await res.arrayBuffer());
45044
+ parts.push(await res.blob());
44900
45045
  }
44901
45046
  return new Blob(parts, { type: input.mimetype });
44902
45047
  }
@@ -45084,8 +45229,7 @@ async function renderKind(request, root) {
45084
45229
  }
45085
45230
  }
45086
45231
  case "pdf": {
45087
- const arrayBuffer = await blob.arrayBuffer();
45088
- return renderPdfCover(new Uint8Array(arrayBuffer), root);
45232
+ return renderPdfCover(await contentFromBlob(blob), root);
45089
45233
  }
45090
45234
  }
45091
45235
  }
@@ -11390,15 +11390,91 @@ var MAX_HEADER_BYTES = 2 + (2 + MAX_FILENAME_BYTES + 2 + MAX_MIMETYPE_BYTES + 8
11390
11390
  var encoder = new TextEncoder;
11391
11391
  var decoder = new TextDecoder("utf-8", { fatal: false });
11392
11392
 
11393
+ // ../relic-format/src/limits.ts
11394
+ var PLAINTEXT_CAP_BYTES = 500 * 1024 * 1024;
11395
+
11393
11396
  // ../relic-format/src/rfc8188.ts
11394
11397
  var TAG_BYTES = 16;
11395
11398
  var DELIMITER_BYTES = 1;
11396
11399
  var RECORD_OVERHEAD = TAG_BYTES + DELIMITER_BYTES;
11397
11400
  var CEK_INFO = new TextEncoder().encode("Content-Encoding: aes128gcm\x00");
11398
11401
  var NONCE_INFO = new TextEncoder().encode("Content-Encoding: nonce\x00");
11399
-
11400
- // ../relic-format/src/container.ts
11401
- var PLAINTEXT_CAP_BYTES = 100 * 1024 * 1024;
11402
+ // ../relic-format/src/crc32c.ts
11403
+ var CRC32C_TABLE = new Uint32Array(256);
11404
+ for (let index = 0;index < 256; index++) {
11405
+ let value = index;
11406
+ for (let bit = 0;bit < 8; bit++) {
11407
+ value = value & 1 ? value >>> 1 ^ 2197175160 : value >>> 1;
11408
+ }
11409
+ CRC32C_TABLE[index] = value >>> 0;
11410
+ }
11411
+ // ../relic-format/src/sha256.ts
11412
+ var K = new Uint32Array([
11413
+ 1116352408,
11414
+ 1899447441,
11415
+ 3049323471,
11416
+ 3921009573,
11417
+ 961987163,
11418
+ 1508970993,
11419
+ 2453635748,
11420
+ 2870763221,
11421
+ 3624381080,
11422
+ 310598401,
11423
+ 607225278,
11424
+ 1426881987,
11425
+ 1925078388,
11426
+ 2162078206,
11427
+ 2614888103,
11428
+ 3248222580,
11429
+ 3835390401,
11430
+ 4022224774,
11431
+ 264347078,
11432
+ 604807628,
11433
+ 770255983,
11434
+ 1249150122,
11435
+ 1555081692,
11436
+ 1996064986,
11437
+ 2554220882,
11438
+ 2821834349,
11439
+ 2952996808,
11440
+ 3210313671,
11441
+ 3336571891,
11442
+ 3584528711,
11443
+ 113926993,
11444
+ 338241895,
11445
+ 666307205,
11446
+ 773529912,
11447
+ 1294757372,
11448
+ 1396182291,
11449
+ 1695183700,
11450
+ 1986661051,
11451
+ 2177026350,
11452
+ 2456956037,
11453
+ 2730485921,
11454
+ 2820302411,
11455
+ 3259730800,
11456
+ 3345764771,
11457
+ 3516065817,
11458
+ 3600352804,
11459
+ 4094571909,
11460
+ 275423344,
11461
+ 430227734,
11462
+ 506948616,
11463
+ 659060556,
11464
+ 883997877,
11465
+ 958139571,
11466
+ 1322822218,
11467
+ 1537002063,
11468
+ 1747873779,
11469
+ 1955562222,
11470
+ 2024104815,
11471
+ 2227730452,
11472
+ 2361852424,
11473
+ 2428436474,
11474
+ 2756734187,
11475
+ 3204031479,
11476
+ 3329325298
11477
+ ]);
11402
11478
  // ../relic-format/src/id.ts
11403
11479
  var RESERVED_SEGMENTS = [
11404
11480
  "abuse",
@@ -11431,7 +11507,10 @@ function foldAliases(lowered) {
11431
11507
  return out;
11432
11508
  }
11433
11509
  // ../relic-format/src/otio-zip.ts
11434
- var MAX_EXPANDED_ZIP_BYTES = 100 * 1024 * 1024;
11510
+ var MAX_INFLATED_ZIP_BYTES = 100 * 1024 * 1024;
11511
+ var OTIO_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
11512
+ var MAX_ZIP_DIRECTORY_BYTES = 16 * 1024 * 1024;
11513
+ var ZIP_READ_CHUNK_BYTES = 8 * 1024 * 1024;
11435
11514
  var MAX_OTIO_BUNDLE_MEDIA_ENTRIES = 65535 - 3;
11436
11515
  var CRC_TABLE = new Uint32Array(256);
11437
11516
  for (let i = 0;i < 256; i++) {
@@ -20,6 +20,11 @@
20
20
  * JavaScript code in this page
21
21
  */
22
22
 
23
+ /**
24
+ * Modified by The Bushido Collective: stop indirect-object lookahead at endobj
25
+ * to avoid scanning the following whitespace in range-loaded PDFs.
26
+ */
27
+
23
28
  /**
24
29
  * pdfjsVersion = 5.4.149
25
30
  * pdfjsBuild = 9e2e9e209
@@ -11481,6 +11486,71 @@ class Lexer {
11481
11486
  }
11482
11487
  }
11483
11488
  }
11489
+ class IndirectObjectLexer extends Lexer {
11490
+ // Stop before lookahead reads beyond an indirect object's top-level endobj.
11491
+ // Dictionary entries alternate between names and values; a closer in value
11492
+ // position is a value, and a mismatched closer never closes a container.
11493
+ #openers = [];
11494
+ #ended = false;
11495
+ #tracking = true;
11496
+ getObj() {
11497
+ if (this.#ended) {
11498
+ return EOF;
11499
+ }
11500
+ const obj = super.getObj();
11501
+ if (!this.#tracking) {
11502
+ return obj;
11503
+ }
11504
+ // Inline images have their own parser and delimiter rules.
11505
+ if (obj instanceof Cmd && obj.cmd === "BI") {
11506
+ this.#openers.length = 0;
11507
+ this.#tracking = false;
11508
+ return obj;
11509
+ }
11510
+ const top = this.#openers.at(-1);
11511
+ if (top === "<<") {
11512
+ if (obj instanceof Name) {
11513
+ this.#openers[this.#openers.length - 1] = "<<value";
11514
+ }
11515
+ if (!(obj instanceof Cmd) || obj.cmd !== ">>") {
11516
+ return obj;
11517
+ }
11518
+ } else if (top === "<<value") {
11519
+ this.#openers[this.#openers.length - 1] = "<<";
11520
+ if (obj instanceof Cmd && (obj.cmd === ">>" || obj.cmd === "]")) {
11521
+ return obj;
11522
+ }
11523
+ }
11524
+ if (obj instanceof Cmd) {
11525
+ switch (obj.cmd) {
11526
+ case "<<":
11527
+ case "[":
11528
+ // Fall back to the original lexer on excessive nesting.
11529
+ if (this.#openers.length === 1024) {
11530
+ this.#openers.length = 0;
11531
+ this.#tracking = false;
11532
+ } else {
11533
+ this.#openers.push(obj.cmd);
11534
+ }
11535
+ break;
11536
+ case ">>":
11537
+ if (top === "<<") {
11538
+ this.#openers.pop();
11539
+ }
11540
+ break;
11541
+ case "]":
11542
+ if (top === "[") {
11543
+ this.#openers.pop();
11544
+ }
11545
+ break;
11546
+ case "endobj":
11547
+ this.#ended = this.#openers.length === 0;
11548
+ break;
11549
+ }
11550
+ }
11551
+ return obj;
11552
+ }
11553
+ }
11484
11554
  class Linearization {
11485
11555
  static create(stream) {
11486
11556
  function getInt(linDict, name, allowZeroValue = false) {
@@ -11505,7 +11575,7 @@ class Linearization {
11505
11575
  throw new Error("Hint array in the linearization dictionary is invalid.");
11506
11576
  }
11507
11577
  const parser = new Parser({
11508
- lexer: new Lexer(stream),
11578
+ lexer: new IndirectObjectLexer(stream),
11509
11579
  xref: null
11510
11580
  });
11511
11581
  const obj1 = parser.getObj();
@@ -54884,7 +54954,7 @@ class XRef {
54884
54954
  startXRefParsedCache.add(startXRef);
54885
54955
  stream.pos = startXRef + stream.start;
54886
54956
  const parser = new Parser({
54887
- lexer: new Lexer(stream),
54957
+ lexer: new IndirectObjectLexer(stream),
54888
54958
  xref: this,
54889
54959
  allowStreams: true
54890
54960
  });
@@ -55002,7 +55072,7 @@ class XRef {
55002
55072
  }
55003
55073
  const stream = this.stream.makeSubStream(xrefEntry.offset + this.stream.start);
55004
55074
  const parser = new Parser({
55005
- lexer: new Lexer(stream),
55075
+ lexer: new IndirectObjectLexer(stream),
55006
55076
  xref: this,
55007
55077
  allowStreams: true
55008
55078
  });
@@ -760,7 +760,7 @@ body {
760
760
  width while the actions lost their labels, which pushed Report off the
761
761
  row at 420px. Its accessible name carries the meaning when the text
762
762
  goes. */
763
- .action span:not(.action-count, .decision-tally, .decision-tally > span):not(:is(.share-panel, .decision-panel, .copy-panel, .band-more-list, .card-actions) span) {
763
+ .action span:not(.action-count, .decision-tally, .decision-tally > span):not(:is(.share-panel, .decision-panel, .copy-panel, .band-more-list, .card-actions, .large-open) span) {
764
764
  position: absolute;
765
765
  width: 1px;
766
766
  height: 1px;
@@ -6252,3 +6252,153 @@ mark[data-concealed] {
6252
6252
  border-bottom-color: transparent !important;
6253
6253
  box-shadow: none !important;
6254
6254
  }
6255
+
6256
+ /* The existing source table, kept to one viewport rather than a giant canvas. */
6257
+ .stage:has(.text-window) {
6258
+ height: 100%;
6259
+ min-height: 20rem;
6260
+ display: flex;
6261
+ flex-direction: column;
6262
+ }
6263
+
6264
+ .text-window.doc {
6265
+ display: flex;
6266
+ flex-direction: column;
6267
+ flex: 1;
6268
+ min-height: 0;
6269
+ min-width: 0;
6270
+ max-width: none;
6271
+ width: 100%;
6272
+ margin: 0;
6273
+ padding: 0;
6274
+ }
6275
+
6276
+ .text-window-tools {
6277
+ display: flex;
6278
+ align-items: center;
6279
+ flex-wrap: wrap;
6280
+ gap: 0.5rem;
6281
+ padding: 0.5rem 0.75rem;
6282
+ border-bottom: 1px solid var(--rule);
6283
+ background: var(--surface);
6284
+ font-family: var(--mono);
6285
+ font-size: 0.75rem;
6286
+ }
6287
+
6288
+ .text-window-tools label {
6289
+ display: flex;
6290
+ align-items: center;
6291
+ gap: 0.5rem;
6292
+ }
6293
+
6294
+ .text-window-tools input {
6295
+ width: 7rem;
6296
+ max-width: 35vw;
6297
+ padding: 0.25rem;
6298
+ background: var(--ground);
6299
+ color: var(--ink);
6300
+ border: 1px solid var(--rule-strong);
6301
+ font: inherit;
6302
+ }
6303
+
6304
+ .text-window-tools output {
6305
+ min-width: 0;
6306
+ overflow-wrap: anywhere;
6307
+ color: var(--ink-soft);
6308
+ }
6309
+
6310
+ .text-window-body {
6311
+ display: flex;
6312
+ flex: 1;
6313
+ min-height: 0;
6314
+ min-width: 0;
6315
+ }
6316
+
6317
+ /* One column as wide as the widest row, so every row, and the line number
6318
+ stuck to its left edge, spans the whole horizontal scroll. */
6319
+ .text-window-page {
6320
+ display: grid;
6321
+ grid-template-columns: minmax(max-content, 1fr);
6322
+ align-content: start;
6323
+ flex: 1;
6324
+ min-width: 0;
6325
+ margin: 0;
6326
+ padding: 0 0.75rem 0 0;
6327
+ overflow-x: auto;
6328
+ overflow-y: hidden;
6329
+ font: 12px/20px var(--mono);
6330
+ tab-size: 4;
6331
+ white-space: pre;
6332
+ background: var(--surface);
6333
+ /* A revealed quote scrolls clear of the sticky line numbers: 8ch number,
6334
+ 2ch padding, 1ch margin and the 1px rule. */
6335
+ scroll-padding-inline: calc(11ch + 1px) 0.75rem;
6336
+ }
6337
+
6338
+ .text-window-row {
6339
+ display: block;
6340
+ min-height: 20px;
6341
+ }
6342
+
6343
+ .text-window-row::before {
6344
+ content: attr(data-line);
6345
+ display: inline-block;
6346
+ position: sticky;
6347
+ left: 0;
6348
+ width: 8ch;
6349
+ padding: 0 1ch;
6350
+ margin-right: 1ch;
6351
+ text-align: right;
6352
+ color: var(--ink-soft);
6353
+ background: var(--surface);
6354
+ border-right: 1px solid var(--rule);
6355
+ user-select: none;
6356
+ }
6357
+
6358
+ .text-window-scroll {
6359
+ writing-mode: vertical-lr;
6360
+ direction: ltr;
6361
+ width: 1.5rem;
6362
+ min-height: 0;
6363
+ margin: 0;
6364
+ accent-color: var(--patina);
6365
+ }
6366
+
6367
+ .text-window-page:focus-visible,
6368
+ .text-window-scroll:focus-visible,
6369
+ .text-window-tools input:focus-visible {
6370
+ outline: 2px solid var(--patina);
6371
+ outline-offset: -2px;
6372
+ }
6373
+
6374
+ .large-open {
6375
+ width: min(32rem, calc(100vw - 2rem));
6376
+ max-height: calc(100dvh - 2rem);
6377
+ padding: 1.5rem;
6378
+ border: 1px solid var(--rule-strong);
6379
+ background: var(--surface);
6380
+ color: var(--ink);
6381
+ }
6382
+
6383
+ .large-open::backdrop {
6384
+ background: rgb(0 0 0 / 45%);
6385
+ }
6386
+
6387
+ .large-open h1 {
6388
+ margin: 0;
6389
+ font: 1.4rem/1.3 var(--serif);
6390
+ overflow-wrap: anywhere;
6391
+ }
6392
+
6393
+ .large-open p {
6394
+ color: var(--ink-soft);
6395
+ font: 0.8rem/1.5 var(--mono);
6396
+ overflow-wrap: anywhere;
6397
+ }
6398
+
6399
+ .large-open-actions {
6400
+ display: flex;
6401
+ justify-content: flex-end;
6402
+ flex-wrap: wrap;
6403
+ gap: 0.5rem;
6404
+ }