relic-mcp 0.11.0 → 0.12.1
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/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +5 -3
- package/dist/cover/frame.html +2 -0
- package/dist/cover/styles.css +380 -66
- package/dist/relic-mcp.js +396 -51
- package/package.json +1 -1
- package/skills/relic/SKILL.md +26 -3
- package/src/cover/media.ts +45 -23
- package/src/publish.ts +5 -0
- package/src/republish.ts +14 -0
- package/src/resolve-anchor.ts +83 -25
- package/src/server.ts +249 -12
- package/src/share.ts +177 -0
|
@@ -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.
|
|
13
|
+
"version": "0.12.1",
|
|
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.
|
|
24
|
+
"version": "0.12.1"
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "relic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.1",
|
|
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": {
|
package/README.md
CHANGED
|
@@ -42,7 +42,8 @@ For any client that takes a JSON config:
|
|
|
42
42
|
this machine later. See [Republishing](#republishing). The service never
|
|
43
43
|
receives the key or the token plaintext; it keeps only a SHA-256 of the
|
|
44
44
|
token.
|
|
45
|
-
|
|
45
|
+
7. Returns a shareable URL whose fragment holds the decryption key. Fragments
|
|
46
|
+
are never sent to a server by a browser.
|
|
46
47
|
Call the `relic_describe_client` tool and it will tell you all of this itself,
|
|
47
48
|
without reading a file or making a request.
|
|
48
49
|
|
|
@@ -179,9 +180,10 @@ relic plaintext leaves the machine during rendering.
|
|
|
179
180
|
| `relic_lookup_source` | `path` | Reads local state to find whether this machine already published that file, and returns the exact `relic_republish` call. Calls no server. |
|
|
180
181
|
| `relic_list` | optional `limit`, `include_expired`, `verify`, `refresh` | Every relic this machine published, newest first. The only tool that enumerates: lookup answers only from the file a relic came from. A name this machine never recorded is recovered by decrypting the relic's own envelope in one 64 KB range request, or comes back `null` with the reason. A relic the service will not serve is listed with which of removed, expired, exhausted, or unreachable it is. Each row carries the share URL, fragment included, so each row is a credential. Reaching the service spends one of a relic's finite opens; recovered names are cached, so a repeat listing spends none. |
|
|
181
182
|
| `relic_show` | `relic_id`, optional `include_content` | One relic in detail, including how many versions the service holds against how many this machine recorded, and with `include_content` the current decrypted content. Read it before republishing anything you did not just write: republish replaces what the link serves, so without the read-back the edit is blind. Content that is not valid UTF-8 is reported as size and type rather than mangled text. |
|
|
182
|
-
| `relic_republish` | `relic_id`, `path`, optional `filename`, `title`, `cover`, `ttl_days` | Publishes a new version under the same key, so the share URL is unchanged. Replaces the plaintext title (defaults to the new filename; pass `""` to remove it). Replaces the cover image with a newly rendered cover, or clears it if `cover: false` is passed. Works only on the machine holding that relic's key and publish token; a taken-down relic can never be revived. |
|
|
183
|
+
| `relic_republish` | `relic_id`, `path`, optional `filename`, `title`, `cover`, `ttl_days`, `addresses`, `notify` | Publishes a new version under the same key, so the share URL is unchanged. Replaces the plaintext title (defaults to the new filename; pass `""` to remove it). Replaces the cover image with a newly rendered cover, or clears it if `cover: false` is passed. Sends a change notice email to everyone following the relic unless `notify: false` is passed. At most one email per person per relic per 10 minutes is sent, and one stop link stops every email about that relic. Works only on the machine holding that relic's key and publish token; a taken-down relic can never be revived. |
|
|
184
|
+
| `relic_share` | `relic_id`, `emails` | Send a relic this machine published to named people. Registers up to 20 email addresses with the service for change and comment notices, and returns a composed share email and mailto link to draft in the sharer's own mailbox. Addresses are stored in the clear by the service, linked to the relic id. A new comment emails everyone on the list except the author, naming the commenter's address (or 'The publisher'). At most one email per person per relic per 10 minutes, and one stop link stops every email about that relic. Relic never sends the share email. Readers holding the link can also share from the viewer once this client version has published or republished the relic. |
|
|
183
185
|
| `relic_read_comments` | `relic_id` | Returns the relic's comments oldest first, decrypted locally, with the author and a count of any that would not decrypt. Works only on the machine that published. |
|
|
184
|
-
| `relic_comment` | `relic_id`, `body`, optional `display_name` | Leaves a comment, encrypted on this machine, authorized by the publish token and attributed as `publisher`. Body caps at 4096 bytes of UTF-8. |
|
|
186
|
+
| `relic_comment` | `relic_id`, `body`, optional `display_name` | Leaves a comment, encrypted on this machine, authorized by the publish token and attributed as `publisher`. Warning: posting emails everyone following the relic, naming 'The publisher'. At most one email per person per relic per 10 minutes, and one stop link stops every email about that relic. Body caps at 4096 bytes of UTF-8. |
|
|
185
187
|
| `relic_describe_client` | none | Explains the encryption path. Reads nothing, sends nothing. |
|
|
186
188
|
|
|
187
189
|
## Environment
|
package/dist/cover/frame.html
CHANGED
|
@@ -10996,6 +10996,8 @@ function foldAliases(lowered) {
|
|
|
10996
10996
|
}
|
|
10997
10997
|
return out;
|
|
10998
10998
|
}
|
|
10999
|
+
// ../relic-format/src/share.ts
|
|
11000
|
+
var SHARE_PROOF_INFO = new TextEncoder().encode("relic/share-proof/v1");
|
|
10999
11001
|
// ../relic-viewer/src/sandbox.ts
|
|
11000
11002
|
var React3 = __toESM(require_react(), 1);
|
|
11001
11003
|
var import_client = __toESM(require_client(), 1);
|
package/dist/cover/styles.css
CHANGED
|
@@ -254,6 +254,10 @@ body {
|
|
|
254
254
|
position: absolute;
|
|
255
255
|
z-index: 8;
|
|
256
256
|
background: var(--surface);
|
|
257
|
+
/* Its own ink rather than whatever it inherits. Inside a fullscreen stage
|
|
258
|
+
the surface is dark and the page around it is not, and an inherited ink
|
|
259
|
+
put the comment's own words dark on dark. */
|
|
260
|
+
color: var(--ink);
|
|
257
261
|
border: 1px solid var(--rule-strong);
|
|
258
262
|
box-shadow: 0 6px 18px rgb(0 0 0 / 14%);
|
|
259
263
|
font-family: var(--sans);
|
|
@@ -332,6 +336,14 @@ body {
|
|
|
332
336
|
list-style: none;
|
|
333
337
|
}
|
|
334
338
|
|
|
339
|
+
/* The card lights the row it opened for, and the light runs edge to edge of
|
|
340
|
+
the row. The sidebar has margins around its rows; a card has none, so the
|
|
341
|
+
row brings its own and the author and the date stop touching the light. */
|
|
342
|
+
.popover-thread .comment {
|
|
343
|
+
padding-right: 0.6rem;
|
|
344
|
+
padding-left: 0.6rem;
|
|
345
|
+
}
|
|
346
|
+
|
|
335
347
|
.popover-target {
|
|
336
348
|
margin: 0 0 0.4rem;
|
|
337
349
|
color: var(--ink-soft);
|
|
@@ -367,6 +379,68 @@ body {
|
|
|
367
379
|
font-size: 0.72rem;
|
|
368
380
|
}
|
|
369
381
|
|
|
382
|
+
/* The comments that share one cluster on the timeline. Each row opens that
|
|
383
|
+
comment's own card, so a row is a button that reads as the sidebar's
|
|
384
|
+
comment head shrunk to one line plus the start of what was said. */
|
|
385
|
+
.popover-members {
|
|
386
|
+
margin: 0;
|
|
387
|
+
padding: 0;
|
|
388
|
+
list-style: none;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.popover-members li + li {
|
|
392
|
+
border-top: 1px solid var(--rule);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.popover-member {
|
|
396
|
+
display: grid;
|
|
397
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
398
|
+
column-gap: 0.6rem;
|
|
399
|
+
row-gap: 0.15rem;
|
|
400
|
+
width: 100%;
|
|
401
|
+
padding: 0.45rem 0.3rem;
|
|
402
|
+
background: none;
|
|
403
|
+
border: 0;
|
|
404
|
+
color: var(--ink);
|
|
405
|
+
font: inherit;
|
|
406
|
+
text-align: left;
|
|
407
|
+
cursor: pointer;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.popover-member:hover {
|
|
411
|
+
background: color-mix(in srgb, var(--patina) 12%, transparent);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
.popover-member:focus-visible {
|
|
415
|
+
outline: 2px solid var(--patina);
|
|
416
|
+
outline-offset: -2px;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.popover-member-time {
|
|
420
|
+
font-family: var(--mono);
|
|
421
|
+
font-size: 0.72rem;
|
|
422
|
+
color: var(--patina);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.popover-member-author {
|
|
426
|
+
overflow: hidden;
|
|
427
|
+
font-family: var(--mono);
|
|
428
|
+
font-size: 0.72rem;
|
|
429
|
+
color: var(--ink-soft);
|
|
430
|
+
white-space: nowrap;
|
|
431
|
+
text-overflow: ellipsis;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
.popover-member-body {
|
|
435
|
+
grid-column: 1 / -1;
|
|
436
|
+
display: -webkit-box;
|
|
437
|
+
overflow: hidden;
|
|
438
|
+
font-family: var(--serif);
|
|
439
|
+
font-size: 0.86rem;
|
|
440
|
+
-webkit-box-orient: vertical;
|
|
441
|
+
-webkit-line-clamp: 2;
|
|
442
|
+
}
|
|
443
|
+
|
|
370
444
|
/* ---------- the accession band ---------- */
|
|
371
445
|
|
|
372
446
|
.bar {
|
|
@@ -702,6 +776,16 @@ body {
|
|
|
702
776
|
margin: 0 auto;
|
|
703
777
|
}
|
|
704
778
|
|
|
779
|
+
/* A markdown relic is a document, so it gets the width of a document: a
|
|
780
|
+
letter page, the 8.5in sheet Google Docs lays out and a printer feeds. The
|
|
781
|
+
46rem column above is narrower than that page, and it read as a strip down
|
|
782
|
+
the middle of the window. The notice is widened with it so the explanation
|
|
783
|
+
above the document lines up with the document it explains. */
|
|
784
|
+
.stage-markdown .doc,
|
|
785
|
+
.stage-markdown .notice {
|
|
786
|
+
max-width: 8.5in;
|
|
787
|
+
}
|
|
788
|
+
|
|
705
789
|
.toggle-row {
|
|
706
790
|
display: flex;
|
|
707
791
|
justify-content: flex-end;
|
|
@@ -1148,6 +1232,14 @@ body {
|
|
|
1148
1232
|
white-space: nowrap;
|
|
1149
1233
|
}
|
|
1150
1234
|
|
|
1235
|
+
/* A panel open over the scrubber already names the time it is about, and it
|
|
1236
|
+
sits exactly where this readout does: the readout's lower edge would show
|
|
1237
|
+
under the panel as a stray notch. The rule and the comment button stay, so
|
|
1238
|
+
a reader can still aim somewhere else. */
|
|
1239
|
+
.stage-wrap:has(.popover) .hover-timecode {
|
|
1240
|
+
visibility: hidden;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1151
1243
|
.timeline-hover-comment-btn {
|
|
1152
1244
|
position: absolute;
|
|
1153
1245
|
top: calc(100% + 4px);
|
|
@@ -1285,6 +1377,10 @@ body {
|
|
|
1285
1377
|
.media-stage:fullscreen {
|
|
1286
1378
|
color-scheme: dark;
|
|
1287
1379
|
--surface: #171b1c;
|
|
1380
|
+
/* Below the band rather than above it: a comment field in a panel on the
|
|
1381
|
+
stage sits on the ground, and the page's light ground under the stage's
|
|
1382
|
+
light ink made what was typed there unreadable. */
|
|
1383
|
+
--ground: #0e1112;
|
|
1288
1384
|
--ink: #e6e9e7;
|
|
1289
1385
|
--ink-soft: #969e9d;
|
|
1290
1386
|
--rule: #3b4345;
|
|
@@ -3068,6 +3164,266 @@ canvas.relic-page {
|
|
|
3068
3164
|
}
|
|
3069
3165
|
}
|
|
3070
3166
|
|
|
3167
|
+
/* ---------- the share panel ---------- */
|
|
3168
|
+
|
|
3169
|
+
/* A sheet that drops from the band, under the action that opened it. It is a
|
|
3170
|
+
modal dialog rather than a third sidebar: sharing is a short errand with an
|
|
3171
|
+
end, the reading row already has a conversation beside it, and a dialog
|
|
3172
|
+
gives the keyboard its trap and Escape for nothing. Square, hairline, the
|
|
3173
|
+
patina rule on top like the composer, and the version list's shadow, so it
|
|
3174
|
+
reads as the same family of chrome as everything else that drops from the
|
|
3175
|
+
band. */
|
|
3176
|
+
.share-panel {
|
|
3177
|
+
position: fixed;
|
|
3178
|
+
inset: var(--share-top, var(--bar-height)) 0 auto auto;
|
|
3179
|
+
width: min(28rem, 100vw);
|
|
3180
|
+
max-width: 100vw;
|
|
3181
|
+
max-height: calc(100dvh - var(--share-top, var(--bar-height)));
|
|
3182
|
+
margin: 0;
|
|
3183
|
+
padding: 0;
|
|
3184
|
+
overflow-y: auto;
|
|
3185
|
+
overscroll-behavior: contain;
|
|
3186
|
+
color: var(--ink);
|
|
3187
|
+
font-family: var(--sans);
|
|
3188
|
+
background: var(--surface);
|
|
3189
|
+
border: 1px solid var(--rule-strong);
|
|
3190
|
+
border-top: 2px solid var(--patina);
|
|
3191
|
+
border-right: 0;
|
|
3192
|
+
box-shadow: 0 6px 18px rgb(0 0 0 / 18%);
|
|
3193
|
+
}
|
|
3194
|
+
|
|
3195
|
+
.share-panel::backdrop {
|
|
3196
|
+
background: rgb(16 19 20 / 28%);
|
|
3197
|
+
}
|
|
3198
|
+
|
|
3199
|
+
.share-head {
|
|
3200
|
+
position: sticky;
|
|
3201
|
+
top: 0;
|
|
3202
|
+
z-index: 1;
|
|
3203
|
+
display: flex;
|
|
3204
|
+
align-items: center;
|
|
3205
|
+
justify-content: space-between;
|
|
3206
|
+
gap: 0.75rem;
|
|
3207
|
+
min-height: 2.75rem;
|
|
3208
|
+
padding: 0 0.4rem 0 1.25rem;
|
|
3209
|
+
background: var(--surface);
|
|
3210
|
+
border-bottom: 1px solid var(--rule);
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
.share-title {
|
|
3214
|
+
margin: 0;
|
|
3215
|
+
font-size: 1rem;
|
|
3216
|
+
font-weight: 600;
|
|
3217
|
+
}
|
|
3218
|
+
|
|
3219
|
+
.share-title:focus {
|
|
3220
|
+
outline: none;
|
|
3221
|
+
}
|
|
3222
|
+
|
|
3223
|
+
.share-close {
|
|
3224
|
+
display: inline-flex;
|
|
3225
|
+
align-items: center;
|
|
3226
|
+
justify-content: center;
|
|
3227
|
+
width: 2.25rem;
|
|
3228
|
+
height: 2.25rem;
|
|
3229
|
+
padding: 0;
|
|
3230
|
+
color: var(--ink-soft);
|
|
3231
|
+
background: transparent;
|
|
3232
|
+
border: 0;
|
|
3233
|
+
cursor: pointer;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
.share-close:hover {
|
|
3237
|
+
color: var(--patina-ink);
|
|
3238
|
+
background: var(--ground);
|
|
3239
|
+
}
|
|
3240
|
+
|
|
3241
|
+
.share-close:focus-visible {
|
|
3242
|
+
outline: 2px solid var(--patina);
|
|
3243
|
+
outline-offset: -2px;
|
|
3244
|
+
}
|
|
3245
|
+
|
|
3246
|
+
.share-body {
|
|
3247
|
+
display: grid;
|
|
3248
|
+
gap: 0.75rem;
|
|
3249
|
+
padding: 1.1rem 1.25rem 1.4rem;
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
.share-body > * {
|
|
3253
|
+
min-width: 0;
|
|
3254
|
+
}
|
|
3255
|
+
|
|
3256
|
+
.share-form {
|
|
3257
|
+
display: grid;
|
|
3258
|
+
gap: 0.75rem;
|
|
3259
|
+
margin: 0;
|
|
3260
|
+
}
|
|
3261
|
+
|
|
3262
|
+
.share-form > .action.primary,
|
|
3263
|
+
.share-body > .action {
|
|
3264
|
+
justify-self: start;
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3267
|
+
.share-body .thread-note,
|
|
3268
|
+
.share-body .thread-policy {
|
|
3269
|
+
margin: 0;
|
|
3270
|
+
}
|
|
3271
|
+
|
|
3272
|
+
.share-body .compose-warning {
|
|
3273
|
+
margin: 0;
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
/* The two facts a sharer is owed before typing anybody's address, set as the
|
|
3277
|
+
composer sets its disclosure: at reading size, on the oxide rule. */
|
|
3278
|
+
.share-facts {
|
|
3279
|
+
display: grid;
|
|
3280
|
+
gap: 0.4rem;
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
.share-facts p {
|
|
3284
|
+
margin: 0;
|
|
3285
|
+
}
|
|
3286
|
+
|
|
3287
|
+
.share-addresses {
|
|
3288
|
+
min-height: 4.6rem;
|
|
3289
|
+
font-family: var(--mono);
|
|
3290
|
+
font-size: 0.8rem;
|
|
3291
|
+
line-height: 1.6;
|
|
3292
|
+
resize: vertical;
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
.share-addresses[aria-invalid="true"] {
|
|
3296
|
+
border-color: var(--oxide);
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
/* Inside the sheet every action is a bordered button: the band's divider
|
|
3300
|
+
rules mean nothing away from the band. */
|
|
3301
|
+
.share-panel .action {
|
|
3302
|
+
justify-content: center;
|
|
3303
|
+
padding: 0.5rem 0.85rem;
|
|
3304
|
+
border: 1px solid var(--rule-strong);
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
.share-panel .action.primary {
|
|
3308
|
+
border: 1px solid var(--patina);
|
|
3309
|
+
}
|
|
3310
|
+
|
|
3311
|
+
.share-panel .action:disabled {
|
|
3312
|
+
color: var(--ink-soft);
|
|
3313
|
+
background: var(--ground);
|
|
3314
|
+
border-color: var(--rule);
|
|
3315
|
+
cursor: default;
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
.share-panel .action span {
|
|
3319
|
+
display: inline;
|
|
3320
|
+
}
|
|
3321
|
+
|
|
3322
|
+
.share-panel .compose-foot .action.primary {
|
|
3323
|
+
margin-left: auto;
|
|
3324
|
+
}
|
|
3325
|
+
|
|
3326
|
+
.share-done {
|
|
3327
|
+
margin: 0;
|
|
3328
|
+
padding: 0.6rem 0.75rem;
|
|
3329
|
+
font-size: 0.84rem;
|
|
3330
|
+
line-height: 1.5;
|
|
3331
|
+
background: var(--diff-added);
|
|
3332
|
+
border-left: 2px solid var(--patina);
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3335
|
+
/* The email as the recipient will get it, so what is about to leave the
|
|
3336
|
+
sharer's own mailbox is read before it leaves. The body is set in the
|
|
3337
|
+
reading face because it is a letter, not chrome. */
|
|
3338
|
+
.share-email {
|
|
3339
|
+
margin: 0;
|
|
3340
|
+
background: var(--ground);
|
|
3341
|
+
border: 1px solid var(--rule);
|
|
3342
|
+
}
|
|
3343
|
+
|
|
3344
|
+
.share-email-head {
|
|
3345
|
+
display: grid;
|
|
3346
|
+
grid-template-columns: auto minmax(0, 1fr);
|
|
3347
|
+
gap: 0.3rem 0.75rem;
|
|
3348
|
+
margin: 0;
|
|
3349
|
+
padding: 0.65rem 0.8rem;
|
|
3350
|
+
font-size: 0.82rem;
|
|
3351
|
+
border-bottom: 1px solid var(--rule);
|
|
3352
|
+
}
|
|
3353
|
+
|
|
3354
|
+
.share-email-head dt {
|
|
3355
|
+
padding-top: 0.15rem;
|
|
3356
|
+
color: var(--ink-soft);
|
|
3357
|
+
font-family: var(--mono);
|
|
3358
|
+
font-size: 0.66rem;
|
|
3359
|
+
letter-spacing: 0.14em;
|
|
3360
|
+
text-transform: uppercase;
|
|
3361
|
+
}
|
|
3362
|
+
|
|
3363
|
+
.share-email-head dd {
|
|
3364
|
+
margin: 0;
|
|
3365
|
+
overflow-wrap: anywhere;
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
.share-email-body {
|
|
3369
|
+
margin: 0;
|
|
3370
|
+
padding: 0.75rem 0.8rem 0.85rem;
|
|
3371
|
+
font-family: var(--serif);
|
|
3372
|
+
font-size: 0.9rem;
|
|
3373
|
+
line-height: 1.55;
|
|
3374
|
+
white-space: pre-wrap;
|
|
3375
|
+
overflow-wrap: anywhere;
|
|
3376
|
+
}
|
|
3377
|
+
|
|
3378
|
+
.share-send {
|
|
3379
|
+
display: flex;
|
|
3380
|
+
flex-wrap: wrap;
|
|
3381
|
+
gap: 0.5rem;
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
.share-again {
|
|
3385
|
+
justify-self: start;
|
|
3386
|
+
padding: 0;
|
|
3387
|
+
color: var(--patina-ink);
|
|
3388
|
+
font: inherit;
|
|
3389
|
+
font-size: 0.8rem;
|
|
3390
|
+
text-decoration: underline;
|
|
3391
|
+
text-underline-offset: 2px;
|
|
3392
|
+
background: none;
|
|
3393
|
+
border: 0;
|
|
3394
|
+
cursor: pointer;
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
.share-again:focus-visible {
|
|
3398
|
+
outline: 2px solid var(--patina);
|
|
3399
|
+
outline-offset: 2px;
|
|
3400
|
+
}
|
|
3401
|
+
|
|
3402
|
+
.share-body .thread-refusal {
|
|
3403
|
+
margin: 0;
|
|
3404
|
+
}
|
|
3405
|
+
|
|
3406
|
+
/* A phone gets the whole width under the band, and the buttons stack so the
|
|
3407
|
+
one that opens the mail app is never a sliver. */
|
|
3408
|
+
@media (max-width: 34rem) {
|
|
3409
|
+
.share-panel {
|
|
3410
|
+
width: 100vw;
|
|
3411
|
+
border-left: 0;
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
.share-head {
|
|
3415
|
+
padding-left: 1rem;
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
.share-body {
|
|
3419
|
+
padding: 1rem 1rem 1.25rem;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
.share-send .action {
|
|
3423
|
+
flex: 1 1 100%;
|
|
3424
|
+
}
|
|
3425
|
+
}
|
|
3426
|
+
|
|
3071
3427
|
/* ---------- toast ---------- */
|
|
3072
3428
|
|
|
3073
3429
|
.toast {
|
|
@@ -3165,6 +3521,11 @@ canvas.relic-page {
|
|
|
3165
3521
|
pointer-events: none;
|
|
3166
3522
|
}
|
|
3167
3523
|
|
|
3524
|
+
/* Markers are drawn in ink inside a ring of the surface. The played bar is
|
|
3525
|
+
patina and the unplayed rail a light tint of the ink, so a patina marker
|
|
3526
|
+
vanished over everything already played. Ink stands off the tinted rail,
|
|
3527
|
+
the surface ring stands off the patina, and the pair holds in the dark
|
|
3528
|
+
fullscreen stage, where both tokens are redefined. */
|
|
3168
3529
|
.time-marker {
|
|
3169
3530
|
position: absolute;
|
|
3170
3531
|
top: 50%;
|
|
@@ -3173,20 +3534,21 @@ canvas.relic-page {
|
|
|
3173
3534
|
padding: 0;
|
|
3174
3535
|
border: none;
|
|
3175
3536
|
border-radius: 0;
|
|
3176
|
-
background: var(--
|
|
3537
|
+
background: var(--ink);
|
|
3177
3538
|
transform: translate(-50%, -50%);
|
|
3178
3539
|
pointer-events: auto;
|
|
3179
3540
|
cursor: pointer;
|
|
3180
3541
|
z-index: 2;
|
|
3181
|
-
box-shadow:
|
|
3542
|
+
box-shadow: 0 0 0 1px var(--surface);
|
|
3182
3543
|
}
|
|
3183
3544
|
|
|
3184
3545
|
.time-marker:hover,
|
|
3185
3546
|
.time-marker.is-active,
|
|
3186
3547
|
.time-marker:focus-visible {
|
|
3187
3548
|
width: 5px;
|
|
3188
|
-
background: var(--
|
|
3549
|
+
background: var(--ink);
|
|
3189
3550
|
outline: 1px solid var(--patina);
|
|
3551
|
+
outline-offset: 1px;
|
|
3190
3552
|
z-index: 4;
|
|
3191
3553
|
}
|
|
3192
3554
|
|
|
@@ -3195,16 +3557,18 @@ canvas.relic-page {
|
|
|
3195
3557
|
border-style: dashed;
|
|
3196
3558
|
}
|
|
3197
3559
|
|
|
3560
|
+
/* A span stands above and below the 4px rail, so its ring shows against the
|
|
3561
|
+
controls band as well as against the rail, played or not. */
|
|
3198
3562
|
.time-marker-span {
|
|
3199
|
-
height:
|
|
3200
|
-
top:
|
|
3563
|
+
height: 8px;
|
|
3564
|
+
top: 50%;
|
|
3201
3565
|
border-radius: 0;
|
|
3202
|
-
border-left: 2px solid var(--
|
|
3203
|
-
border-right: 2px solid var(--
|
|
3566
|
+
border-left: 2px solid var(--ink);
|
|
3567
|
+
border-right: 2px solid var(--ink);
|
|
3204
3568
|
border-top: none;
|
|
3205
3569
|
border-bottom: none;
|
|
3206
|
-
background:
|
|
3207
|
-
transform:
|
|
3570
|
+
background: var(--ink);
|
|
3571
|
+
transform: translateY(-50%);
|
|
3208
3572
|
min-width: 4px;
|
|
3209
3573
|
z-index: 2;
|
|
3210
3574
|
}
|
|
@@ -3212,9 +3576,10 @@ canvas.relic-page {
|
|
|
3212
3576
|
.time-marker-span:hover,
|
|
3213
3577
|
.time-marker-span.is-active,
|
|
3214
3578
|
.time-marker-span:focus-visible {
|
|
3215
|
-
transform:
|
|
3216
|
-
background:
|
|
3579
|
+
transform: translateY(-50%);
|
|
3580
|
+
background: var(--ink);
|
|
3217
3581
|
outline: 1px solid var(--patina);
|
|
3582
|
+
outline-offset: 1px;
|
|
3218
3583
|
z-index: 4;
|
|
3219
3584
|
}
|
|
3220
3585
|
|
|
@@ -3230,8 +3595,8 @@ canvas.relic-page {
|
|
|
3230
3595
|
padding: 0 3px;
|
|
3231
3596
|
border: 1px solid var(--surface);
|
|
3232
3597
|
border-radius: 0;
|
|
3233
|
-
background: var(--
|
|
3234
|
-
color:
|
|
3598
|
+
background: var(--ink);
|
|
3599
|
+
color: var(--surface);
|
|
3235
3600
|
font-family: var(--mono);
|
|
3236
3601
|
transform: translateY(-50%);
|
|
3237
3602
|
pointer-events: auto;
|
|
@@ -3250,63 +3615,12 @@ canvas.relic-page {
|
|
|
3250
3615
|
.time-marker-cluster.is-active,
|
|
3251
3616
|
.time-marker-cluster:has(.is-active),
|
|
3252
3617
|
.time-marker-cluster:focus-visible {
|
|
3253
|
-
background: var(--
|
|
3618
|
+
background: var(--ink);
|
|
3254
3619
|
outline: 1px solid var(--patina);
|
|
3620
|
+
outline-offset: 1px;
|
|
3255
3621
|
z-index: 5;
|
|
3256
3622
|
}
|
|
3257
3623
|
|
|
3258
|
-
.timeline-marker-tooltip {
|
|
3259
|
-
position: absolute;
|
|
3260
|
-
bottom: 100%;
|
|
3261
|
-
margin-bottom: 8px;
|
|
3262
|
-
transform: translateX(-50%);
|
|
3263
|
-
background: var(--surface);
|
|
3264
|
-
border: 1px solid var(--rule);
|
|
3265
|
-
padding: 0.35rem 0.5rem;
|
|
3266
|
-
font-family: var(--sans);
|
|
3267
|
-
font-size: 0.74rem;
|
|
3268
|
-
color: var(--ink);
|
|
3269
|
-
white-space: nowrap;
|
|
3270
|
-
pointer-events: none;
|
|
3271
|
-
z-index: 20;
|
|
3272
|
-
display: flex;
|
|
3273
|
-
flex-direction: column;
|
|
3274
|
-
gap: 0.2rem;
|
|
3275
|
-
}
|
|
3276
|
-
|
|
3277
|
-
.marker-tooltip-header {
|
|
3278
|
-
display: flex;
|
|
3279
|
-
align-items: center;
|
|
3280
|
-
gap: 0.5rem;
|
|
3281
|
-
}
|
|
3282
|
-
|
|
3283
|
-
.marker-tooltip-author {
|
|
3284
|
-
font-family: var(--mono);
|
|
3285
|
-
font-size: 0.72rem;
|
|
3286
|
-
font-weight: 600;
|
|
3287
|
-
color: var(--patina);
|
|
3288
|
-
}
|
|
3289
|
-
|
|
3290
|
-
.marker-tooltip-time {
|
|
3291
|
-
font-family: var(--mono);
|
|
3292
|
-
font-size: 0.7rem;
|
|
3293
|
-
color: var(--ink-soft);
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
.marker-tooltip-snippet {
|
|
3297
|
-
font-family: var(--sans);
|
|
3298
|
-
font-size: 0.72rem;
|
|
3299
|
-
color: var(--ink);
|
|
3300
|
-
max-width: 18rem;
|
|
3301
|
-
overflow: hidden;
|
|
3302
|
-
text-overflow: ellipsis;
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
.marker-tooltip-item {
|
|
3306
|
-
font-family: var(--sans);
|
|
3307
|
-
font-size: 0.7rem;
|
|
3308
|
-
color: var(--ink);
|
|
3309
|
-
}
|
|
3310
3624
|
.time-frame-box {
|
|
3311
3625
|
position: absolute;
|
|
3312
3626
|
border: 2px solid var(--patina);
|