pipeup 0.9.1 → 1.0.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.
- package/README.md +3 -3
- package/cli/browser-host.mjs +43 -0
- package/cli/browser.mjs +144 -0
- package/cli/pipeup.mjs +54 -2
- package/cli/read.mjs +1 -1
- package/dist/addons.json +12 -12
- package/dist/pipeup.agents.js +1 -1
- package/dist/pipeup.core.js +1 -1
- package/dist/pipeup.esm.js +1 -1
- package/dist/pipeup.min.js +1 -1
- package/dist/types/anchor/clues.d.ts +16 -0
- package/dist/types/anchor/resolve.d.ts +2 -0
- package/dist/types/core.d.ts +2 -2
- package/dist/types/document.d.ts +6 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/model/ops.d.ts +3 -0
- package/dist/types/model/types.d.ts +22 -2
- package/dist/types/ui/addons.d.ts +13 -0
- package/dist/types/ui/app.d.ts +21 -0
- package/dist/types/ui/column.d.ts +2 -0
- package/dist/types/ui/context.d.ts +6 -0
- package/dist/types/ui/field.d.ts +22 -0
- package/dist/types/ui/host.d.ts +1 -1
- package/dist/types/ui/mount.d.ts +36 -0
- package/dist/types/ui/styles.d.ts +1 -1
- package/dist/types/ui/thread-view.d.ts +4 -0
- package/package.json +3 -2
- package/skills/pipeup-add/SKILL.md +2 -2
- package/skills/pipeup-apply/SKILL.md +14 -9
- package/skills/pipeup-integrate/SKILL.md +8 -8
- package/skills/pipeup-send-to-git-setup/SKILL.md +1 -1
- package/skills/pipeup-summarise/SKILL.md +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Anchor, AnchorKind } from "../model/types";
|
|
2
|
+
export declare function kindOf(el: Element): AnchorKind;
|
|
3
|
+
/** Elements of one kind under `base`, in page order. A box can be anything that isn't one of the other kinds. */
|
|
4
|
+
export declare function candidates(base: Element, kind: AnchorKind): Element[];
|
|
5
|
+
/** What the thing is called: its label, a picture's description or file name, a link's address. "" when nothing. */
|
|
6
|
+
export declare function nameOf(el: Element): string;
|
|
7
|
+
/** The headings `node` sits under, outermost first, from the page's outline before it. */
|
|
8
|
+
export declare function headsOf(node: Node, root: Element): string[];
|
|
9
|
+
/** Where `el` is down `root`, 0 to 1; undefined when the page has no layout to measure. */
|
|
10
|
+
export declare function yOf(el: Element, root: Element): number | undefined;
|
|
11
|
+
/** The clues kept with a comment on `el`, so a look-alike can be found when the page changes. */
|
|
12
|
+
export declare function cluesOf(el: Element, root: Element): Pick<Anchor, "kind" | "name" | "heads" | "y">;
|
|
13
|
+
/** How alike two texts are, 0 to 1 (Dice's coefficient on trigrams). */
|
|
14
|
+
export declare function similarity(a: string, b: string): number;
|
|
15
|
+
/** What a comment was on, in words: "it was a box under Pricing › Business". "" when it has no clues. */
|
|
16
|
+
export declare const wasOn: (a: Anchor) => string;
|
|
@@ -4,6 +4,8 @@ export interface Resolved {
|
|
|
4
4
|
state: AnchorState;
|
|
5
5
|
element: Element | null;
|
|
6
6
|
range: Range | null;
|
|
7
|
+
/** Found by looking alike rather than by its words or its place: someone should say whether it's right. */
|
|
8
|
+
alike?: boolean;
|
|
7
9
|
}
|
|
8
10
|
export interface ResolveOptions {
|
|
9
11
|
/** Try approximate matching when exact text isn't found. Default true. */
|
package/dist/types/core.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "1.0.0";
|
|
2
2
|
/** The day this version was made (set with the version): Pipeup says so, once, in the console when it is over a year old. */
|
|
3
|
-
export declare const BUILT = "2026-11-
|
|
3
|
+
export declare const BUILT = "2026-11-10";
|
|
4
4
|
export { PipeupDocument, UnsavedChangeError, type Listener, type OpenOptions } from "./document";
|
|
5
5
|
export { DEFAULT_PROFILE_NAME, MemoryStore, loadOrCreateProfile, type OpStore, type Profile, } from "./storage/store";
|
|
6
6
|
export { IndexedDbStore } from "./storage/indexeddb";
|
package/dist/types/document.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Identity } from "./crypto/identity";
|
|
2
|
-
import type { Anchor, Mention, SignedOp, Thread } from "./model/types";
|
|
2
|
+
import type { Anchor, Mention, PlaceHow, SignedOp, Thread } from "./model/types";
|
|
3
3
|
import type { OpStore } from "./storage/store";
|
|
4
4
|
export interface OpenOptions {
|
|
5
5
|
doc: string;
|
|
@@ -43,6 +43,11 @@ export declare class PipeupDocument {
|
|
|
43
43
|
ops(): readonly SignedOp[];
|
|
44
44
|
onChange(fn: Listener): () => void;
|
|
45
45
|
comment(anchor: Anchor, text: string, mentions?: Mention[]): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Sets a thread's place from now on, for everyone: `confirmed` keeps it where it was found (described again, so its
|
|
48
|
+
* clues follow the page), `moved` puts it somewhere else, `gone` makes it Lost on purpose.
|
|
49
|
+
*/
|
|
50
|
+
place(threadId: string, anchor: Anchor, how: PlaceHow): Promise<void>;
|
|
46
51
|
reply(parentId: string, text: string, mentions?: Mention[]): Promise<string>;
|
|
47
52
|
edit(commentId: string, text: string, mentions?: Mention[]): Promise<void>;
|
|
48
53
|
remove(commentId: string): Promise<void>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ export * from "./core";
|
|
|
2
2
|
export { comments, mount, type MountError, type MountErrorCode, type MountOptions, type PipeupInstance, type PipeupState, } from "./ui/mount";
|
|
3
3
|
export { setting } from "./ui/settings";
|
|
4
4
|
export type { Appearance } from "./ui/legible";
|
|
5
|
+
export type { Prefs } from "./ui/app";
|
|
6
|
+
export type { Field, FieldHost } from "./ui/field";
|
|
5
7
|
export { setPeople, type Person } from "./ui/mentions";
|
|
6
|
-
export { ADDON_API, addons, use, type AddonDocument, type AddonHost, type AddonInfo, type Capability, type ComposerHandle, type ComposerTool, type Dictation, type ItemHandle, type ListNote, type ReactionPicker, type MenuItem, type NetworkUse, type Off, type PanelHandle, type PanelOptions, type PipeupAddon, type PostOptions, type Status, type Teardown, type UiSnapshot, } from "./ui/addons";
|
|
8
|
+
export { ADDON_API, addons, use, type AddonDocument, type AddonHost, type AddonInfo, type Capability, type ComposerHandle, type ComposerTool, type Dictation, type ItemHandle, type Keep, type ListNote, type ReactionPicker, type MenuItem, type NetworkUse, type Off, type PanelHandle, type PanelOptions, type PipeupAddon, type PostOptions, type Status, type Teardown, type UiSnapshot, } from "./ui/addons";
|
|
7
9
|
export { onReveal, setViewState, type SlideHook, type ViewState } from "./ui/here";
|
|
@@ -19,6 +19,9 @@ export declare const LIMITS: {
|
|
|
19
19
|
readonly viewKey: 64;
|
|
20
20
|
readonly viewValue: 200;
|
|
21
21
|
readonly mentions: 20;
|
|
22
|
+
readonly name: 200;
|
|
23
|
+
readonly head: 120;
|
|
24
|
+
readonly heads: 3;
|
|
22
25
|
};
|
|
23
26
|
/** Who a mention names: a writer's key, or a person the page or an add-on supplied (`host:<id>`, `gh:<name>`). */
|
|
24
27
|
/** A way to reach someone that they chose to share: an email address, or a GitHub name (`gh:octocat`). */
|
|
@@ -22,8 +22,21 @@ export interface Anchor {
|
|
|
22
22
|
fingerprint: string;
|
|
23
23
|
/** A short plain-text excerpt of what the reviewer saw. */
|
|
24
24
|
snapshot: string;
|
|
25
|
+
/** What kind of thing it was on, so a look-alike can be found when the page changes. */
|
|
26
|
+
kind?: AnchorKind;
|
|
27
|
+
/** Its accessible name (a label, a picture's description or file name, a link's address), when it has one. */
|
|
28
|
+
name?: string;
|
|
29
|
+
/** The headings it sat under, outermost first. */
|
|
30
|
+
heads?: string[];
|
|
31
|
+
/** Where it was down the commentable area, 0 (top) to 1 (bottom). */
|
|
32
|
+
y?: number;
|
|
33
|
+
/** Someone said its place is gone: it is Lost on purpose and never re-found. */
|
|
34
|
+
lost?: true;
|
|
25
35
|
}
|
|
26
|
-
export type
|
|
36
|
+
export type AnchorKind = "h" | "p" | "img" | "button" | "link" | "field" | "media" | "box";
|
|
37
|
+
/** How a `place` op set a thread's place: kept where it was found, moved somewhere else, or gone. */
|
|
38
|
+
export type PlaceHow = "confirmed" | "moved" | "gone";
|
|
39
|
+
export type OpKind = "create" | "reply" | "edit" | "delete" | "resolve" | "reopen" | "addressed" | "react" | "unreact" | "seen" | "contact" | "uncontact" | "place";
|
|
27
40
|
/** A person named in a comment's words: `text.slice(start, end)` is the "@Name"; `ref` says who (a key, or a supplied person). */
|
|
28
41
|
export interface Mention {
|
|
29
42
|
start: number;
|
|
@@ -43,7 +56,7 @@ export interface OpBody {
|
|
|
43
56
|
text?: string;
|
|
44
57
|
/** create, reply and edit: the people named in the words. Signed with them, so it can't be altered. */
|
|
45
58
|
mentions?: Mention[];
|
|
46
|
-
/** create
|
|
59
|
+
/** create, and place (the thread's place from now on). */
|
|
47
60
|
anchor?: Anchor;
|
|
48
61
|
/** Author's public key. */
|
|
49
62
|
author: string;
|
|
@@ -100,4 +113,11 @@ export interface Thread {
|
|
|
100
113
|
name: string;
|
|
101
114
|
at: number;
|
|
102
115
|
};
|
|
116
|
+
/** Someone set this thread's place since it was made (the latest `place`); `anchor` is then that place. */
|
|
117
|
+
placed?: {
|
|
118
|
+
how: PlaceHow;
|
|
119
|
+
author: string;
|
|
120
|
+
name: string;
|
|
121
|
+
at: number;
|
|
122
|
+
};
|
|
103
123
|
}
|
|
@@ -138,6 +138,11 @@ export interface PostOptions {
|
|
|
138
138
|
/** The reviewer's own comment, deleted as Delete does (a thread's first comment takes its replies); `text` is unused. */
|
|
139
139
|
remove?: string;
|
|
140
140
|
}
|
|
141
|
+
/** Small values kept by whatever adds Pipeup (the browser extension's own storage), out of the site's reach. */
|
|
142
|
+
export interface Keep {
|
|
143
|
+
get(key: string): Promise<unknown>;
|
|
144
|
+
set(key: string, value: unknown): Promise<void>;
|
|
145
|
+
}
|
|
141
146
|
export interface AddonHost {
|
|
142
147
|
readonly api: number;
|
|
143
148
|
/** The core's version, for diagnostics only. */
|
|
@@ -147,6 +152,11 @@ export interface AddonHost {
|
|
|
147
152
|
readonly root: Element;
|
|
148
153
|
/** This browser can't keep comments (they are held in memory). */
|
|
149
154
|
readonly ephemeral: boolean;
|
|
155
|
+
/**
|
|
156
|
+
* Where this add-on keeps its choices when whatever added Pipeup keeps them (the browser extension), so the site can
|
|
157
|
+
* neither read nor set them. Absent on a page that loads Pipeup itself.
|
|
158
|
+
*/
|
|
159
|
+
readonly keep?: Keep;
|
|
150
160
|
/** Aborted at teardown: pass it to fetch, timers and sockets. */
|
|
151
161
|
readonly signal: AbortSignal;
|
|
152
162
|
has(c: Capability): boolean;
|
|
@@ -249,6 +259,8 @@ export declare class Surface {
|
|
|
249
259
|
private uiKey;
|
|
250
260
|
/** Where each add-on draws in the page: marked element → its <pipeup-place>. */
|
|
251
261
|
private readonly places;
|
|
262
|
+
/** Each place's shadow root (closed, as Pipeup's own, when the host keeps Pipeup from the page). */
|
|
263
|
+
private readonly placeRoots;
|
|
252
264
|
private aside;
|
|
253
265
|
private looked;
|
|
254
266
|
/** Where the reviewer's latest press or key happened: `post` is only for what they sent from the add-on's own UI. */
|
|
@@ -322,6 +334,7 @@ interface Live {
|
|
|
322
334
|
doc: PipeupDocument;
|
|
323
335
|
root: Element;
|
|
324
336
|
ephemeral: boolean;
|
|
337
|
+
keep?: Keep;
|
|
325
338
|
version: string;
|
|
326
339
|
active: boolean;
|
|
327
340
|
/** Keeps another identity for this browser, used from the next load. */
|
package/dist/types/ui/app.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type PipeupDocument } from "../document";
|
|
2
2
|
import type { Ctx, UiState, View } from "./context";
|
|
3
3
|
import { type Appearance } from "./legible";
|
|
4
|
+
import { type FieldHost } from "./field";
|
|
4
5
|
export interface AppOptions {
|
|
5
6
|
doc: PipeupDocument;
|
|
6
7
|
root: Element;
|
|
@@ -9,9 +10,29 @@ export interface AppOptions {
|
|
|
9
10
|
onRender?(): void;
|
|
10
11
|
/** Pipeup's stylesheet (passed in so the bundles can carry it last, where it compresses best). */
|
|
11
12
|
styles: string;
|
|
13
|
+
/** Where the reviewer's choices for this page are kept (look, Hide comments). Default: the page's localStorage. */
|
|
14
|
+
prefs?: Prefs;
|
|
15
|
+
/**
|
|
16
|
+
* A host shows Pipeup's threads itself (the browser extension's side panel): Pipeup opens none on the page, and tells
|
|
17
|
+
* the host when one is opened there. New comments are still written on the page, beside what they're about.
|
|
18
|
+
*/
|
|
19
|
+
host?: Host;
|
|
20
|
+
/** Pipeup's UI is in a closed shadow root, out of the page's scripts' reach (the browser extension). */
|
|
21
|
+
closed?: boolean;
|
|
22
|
+
/** A host's own stand-in for Pipeup's text fields, typed in where the page can't hear (the browser extension). */
|
|
23
|
+
field?: FieldHost;
|
|
12
24
|
}
|
|
25
|
+
/** What Pipeup tells a host that shows its threads. */
|
|
26
|
+
export interface Host {
|
|
27
|
+
/** A thread was opened on the page (its highlight clicked, or just written). */
|
|
28
|
+
open(id: string): void;
|
|
29
|
+
}
|
|
30
|
+
/** A place for the reviewer's small choices, read as Pipeup starts: localStorage, or the host's own. */
|
|
31
|
+
export type Prefs = Pick<Storage, "getItem" | "setItem" | "removeItem">;
|
|
13
32
|
export interface App {
|
|
14
33
|
readonly ctx: Ctx;
|
|
34
|
+
/** Goes to a thread's place: scrolls it into view and marks it, as choosing it in All comments does. */
|
|
35
|
+
goTo(id: string): void;
|
|
15
36
|
/** How Pipeup looks and why: its look, who chose it, and what the page's styles do to it. */
|
|
16
37
|
appearance(): Appearance;
|
|
17
38
|
destroy(): void;
|
|
@@ -13,5 +13,7 @@ export declare function columnSpot(root: Element, showing?: boolean): {
|
|
|
13
13
|
export declare function lost(ctx: Ctx, t: Thread): boolean;
|
|
14
14
|
/** The thread's content was found changed, or nearby: it is flagged Moved, for someone to confirm. */
|
|
15
15
|
export declare const moved: (ctx: Ctx, t: Thread) => boolean;
|
|
16
|
+
/** Found by looking alike: its card asks whether this is the right place. */
|
|
17
|
+
export declare const alike: (ctx: Ctx, t: Thread) => boolean;
|
|
16
18
|
/** Threads beside the document, level with what they're about; the open one aligns exactly. */
|
|
17
19
|
export declare function createColumn(ctx: Ctx): View;
|
|
@@ -42,6 +42,10 @@ export interface UiState {
|
|
|
42
42
|
menu: boolean;
|
|
43
43
|
/** Pin was tapped on a touch screen: the next tap on the page is where the pin goes, even on a highlight. */
|
|
44
44
|
pinning: boolean;
|
|
45
|
+
/** The open thread was opened by sending it: it shows, but the next click in comment mode goes on to the next block. */
|
|
46
|
+
sent: boolean;
|
|
47
|
+
/** The thread whose place is being chosen (Not here, Show where it belongs): comment mode's next pick moves it. */
|
|
48
|
+
picking: string | null;
|
|
45
49
|
}
|
|
46
50
|
/** Something that draws: rebuilt on render, repositioned every frame. */
|
|
47
51
|
export interface View {
|
|
@@ -112,6 +116,8 @@ export interface Ctx {
|
|
|
112
116
|
startDraft(d: Draft): void;
|
|
113
117
|
/** Moves the open draft to another block, keeping its words (starts one if none is open). */
|
|
114
118
|
moveDraft(d: Draft): void;
|
|
119
|
+
/** Comment mode picked the new place of the thread being placed: moves it there, for everyone. */
|
|
120
|
+
repoint(anchor: Anchor): void;
|
|
115
121
|
/** Nothing has been typed in the open draft. */
|
|
116
122
|
draftEmpty(): boolean;
|
|
117
123
|
/** Runs `fn`, then puts focus and the caret back in the open draft as they were. */
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A host's own stand-in for Pipeup's text fields (the browser extension's frame, `mount({ field })`): what the reviewer
|
|
3
|
+
* types there is typed in another document, so the page never hears it. Pipeup's field stays in the DOM, hidden, as
|
|
4
|
+
* the model the rest of Pipeup reads and writes; the host keeps the two in step and passes the reviewer's keys in with
|
|
5
|
+
* `pass`, as events that stay inside Pipeup's shadow root.
|
|
6
|
+
*/
|
|
7
|
+
export type Field = HTMLTextAreaElement | HTMLInputElement;
|
|
8
|
+
/** Given one of Pipeup's fields and a way to pass events into it, the element to show in its place (or null: keep it). */
|
|
9
|
+
export type FieldHost = (field: Field, pass: (event: Event) => void) => HTMLElement | null;
|
|
10
|
+
/** Set as Pipeup starts (null: the fields are Pipeup's own); `shadow` is where passed keys are heard. */
|
|
11
|
+
export declare function setFieldHost(h: FieldHost | null, shadow: ShadowRoot | null): void;
|
|
12
|
+
/** Offers a field Pipeup just made, already in its place, to the host, which may show its own in its place. */
|
|
13
|
+
export declare function offerField(field: Field): void;
|
|
14
|
+
/** The field behind what has focus: a field of Pipeup's, or the field a host's stand-in shows. */
|
|
15
|
+
export declare function fieldOf(a: Element | null | undefined): Field | null;
|
|
16
|
+
/** As `fieldOf`, a comment or reply box only. */
|
|
17
|
+
export declare function boxOf(a: Element | null | undefined): HTMLTextAreaElement | null;
|
|
18
|
+
/**
|
|
19
|
+
* Listens for keys on the window, as the page's own keys are heard, and also for the keys a host passes into a field
|
|
20
|
+
* (they never reach the window, so the page doesn't hear them). Returns how to stop.
|
|
21
|
+
*/
|
|
22
|
+
export declare function onWindowKey(type: "keydown" | "keyup", fn: (e: KeyboardEvent) => void, capture: boolean): () => void;
|
package/dist/types/ui/host.d.ts
CHANGED
|
@@ -11,4 +11,4 @@ export interface Host {
|
|
|
11
11
|
* z-index there is and comes after <body>, so a page's own bar at that z-index (a consent banner, say) still sits under
|
|
12
12
|
* it: of two equals, the later one is drawn on top.
|
|
13
13
|
*/
|
|
14
|
-
export declare function createHost(theme: Theme, styles: string, doc?: Document): Host;
|
|
14
|
+
export declare function createHost(theme: Theme, styles: string, doc?: Document, closed?: boolean): Host;
|
package/dist/types/ui/mount.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { PipeupDocument } from "../document";
|
|
2
2
|
import { type OpStore } from "../storage/store";
|
|
3
|
+
import { type Keep } from "./addons";
|
|
4
|
+
import { type Prefs } from "./app";
|
|
5
|
+
import type { LookChoice } from "./context";
|
|
6
|
+
import { type CopyAs } from "../export/format";
|
|
3
7
|
import { type SlideHook } from "./here";
|
|
4
8
|
import { type AgentComments } from "../export/agent";
|
|
5
9
|
import type { Appearance } from "./legible";
|
|
10
|
+
import type { FieldHost } from "./field";
|
|
6
11
|
export interface MountOptions {
|
|
7
12
|
/** The area people can comment on. Default: document.body. */
|
|
8
13
|
root?: Element;
|
|
@@ -22,6 +27,21 @@ export interface MountOptions {
|
|
|
22
27
|
select?: string;
|
|
23
28
|
/** "off": the page has its own way to start commenting, so Pipeup's control is hidden, as `data-pipeup-control`. */
|
|
24
29
|
control?: string;
|
|
30
|
+
/** Where the reviewer's choices (look, Hide comments) are kept, read as Pipeup starts. Default: localStorage. */
|
|
31
|
+
prefs?: Prefs;
|
|
32
|
+
/**
|
|
33
|
+
* "host": whatever adds Pipeup shows its threads (the browser extension's side panel). Pipeup opens none on the page,
|
|
34
|
+
* and says when one is opened there ("open"); new comments are still written on the page.
|
|
35
|
+
*/
|
|
36
|
+
ui?: string;
|
|
37
|
+
/** More page settings, as `data-pipeup-…` attributes would give them (for hosts that can't write the page's own). */
|
|
38
|
+
settings?: Record<string, string>;
|
|
39
|
+
/** Where add-ons keep their choices (`host.keep`). Default: none; each add-on keeps them in the page's own storage. */
|
|
40
|
+
keep?: Keep;
|
|
41
|
+
/** Pipeup's UI in a closed shadow root, out of the page's scripts' reach (for hosts apart from the page). */
|
|
42
|
+
closed?: boolean;
|
|
43
|
+
/** A host's own stand-in for Pipeup's text fields, where the page can't hear what is typed (the browser extension). */
|
|
44
|
+
field?: FieldHost;
|
|
25
45
|
}
|
|
26
46
|
/** What the page's code hears: comment mode and the reviewer's name ("" until they give one). */
|
|
27
47
|
export interface PipeupState {
|
|
@@ -58,6 +78,22 @@ export interface PipeupInstance {
|
|
|
58
78
|
openAllComments(): Promise<void>;
|
|
59
79
|
/** Calls `fn` whenever comment mode or the name changes, however it changed; returns a function that stops it. */
|
|
60
80
|
on(event: "change", fn: (state: PipeupState) => void): () => void;
|
|
81
|
+
/** With `ui: "host"`: a thread was opened on the page (its highlight clicked, or just written). */
|
|
82
|
+
on(event: "open", fn: (id: string) => void): () => void;
|
|
83
|
+
/** The open comments (`all`: and resolved ones), as Copy comments makes them: Markdown for AI, or plain text. */
|
|
84
|
+
commentsAs(as: CopyAs, options?: {
|
|
85
|
+
all?: boolean;
|
|
86
|
+
}): string;
|
|
87
|
+
/** The reviewer's look: "auto" follows the author, then the page. Kept like their other choices. */
|
|
88
|
+
readonly look: LookChoice;
|
|
89
|
+
setLook(look: LookChoice): Promise<void>;
|
|
90
|
+
/** Hide comments is on. */
|
|
91
|
+
readonly hidden: boolean;
|
|
92
|
+
setHidden(on: boolean): Promise<void>;
|
|
93
|
+
/** Goes to a thread's place on the page: scrolls it into view and marks it; with Pipeup's own panel, opens it. */
|
|
94
|
+
openThread(id: string): Promise<void>;
|
|
95
|
+
/** Opens the comment box on an element or on words (a Range), as choosing it in comment mode does. */
|
|
96
|
+
startComment(target: Element | Range): Promise<void>;
|
|
61
97
|
}
|
|
62
98
|
/** Adds Pipeup to the page. Calling it again returns the same instance. */
|
|
63
99
|
export declare function mount(options?: MountOptions): Promise<PipeupInstance>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Pipeup's stylesheet, scoped to its shadow root. Light by default; `.dark` follows a dark page. */
|
|
2
|
-
export declare const STYLES = "\n/* Inherited page styles are reset on .layer below; custom properties pass through all:initial. */\n:host{all:initial;direction:ltr;unicode-bidi:normal}\n*{box-sizing:border-box}\nbutton{appearance:none;font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}\nsvg{display:block}\n.layer{all:initial;direction:ltr;unicode-bidi:normal;display:block;position:fixed;inset:0;pointer-events:none;font:13px/1.5 var(--pu-font,system-ui,sans-serif);color:var(--k);\n--k:#2C2C2A;--mu:#5F5E5A;--fa:#6B6A64;--ln:#E5E3DA;--ru:#ECEAE2;--so:#FAFAF7;--su:#FFFFFF;--ho:#F1EFE8;\n--as:color-mix(in srgb,var(--pu-accent,#534AB7) 12%,transparent);\n--sh:0 8px 28px rgba(44,44,42,.10);\n--in:.26s;--mv:.34s;--out:.2s;--pl:1.2s;\n--eo:cubic-bezier(.22,.61,.36,1);--eio:cubic-bezier(.4,0,.2,1);--ei:cubic-bezier(.4,0,1,1);\n--pu-text:var(--k);--pu-muted:var(--mu);--pu-faint:var(--fa);--pu-line:var(--ln);--pu-surface:var(--su);--pu-soft:var(--so);--pu-hover:var(--ho);--pu-shadow:var(--sh);--pu-ease:var(--eo)}\n.layer{transition:opacity .24s var(--eo)}.layer.fading{opacity:0;transition-duration:.16s;transition-timing-function:var(--ei)}\n.layer.dark{--k:#ECEBE6;--mu:#B4B2A9;--fa:#9A9892;--ln:#3A3936;--ru:#34332F;--so:#22221F;--su:#1B1B19;--ho:#2C2C29;--sh:0 8px 28px rgba(0,0,0,.45)}\n.layer.hidden .bub,.layer.hidden .col,.layer.hidden .pop,.layer.hidden .tip,.layer.hidden .mark,.layer.hidden .selbar{opacity:0!important;pointer-events:none!important}\n/* While a phone's on-screen keyboard is up for a Pipeup box, the comment control and its messages fade out of the little\nroom left above it. */\n.layer.kb :is(.launch,.toast){opacity:0!important;pointer-events:none!important;transition:opacity var(--out) var(--ei)!important}\n\n.tx{font-size:14px;line-height:1.5;color:var(--k);white-space:pre-wrap;overflow-wrap:anywhere}\n.tx.del,.rest,.ctx,.ago{color:var(--fa)}\n.rest,.ago{font-size:12px}\n.rest{display:block;margin-top:3px}\n/* A card's top line: avatar, who (cut short on one line, never wrapped), how long ago, then its actions. */\n.hdr{position:relative;display:flex;align-items:center;gap:7px;min-height:22px}\n.who{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600;color:var(--k)}\n.ago{flex:none;white-space:nowrap}\n.ago::before{content:\"\u00B7\";margin-right:6px}\n.root>.tx,.it>.tx,.root>.ctx{margin-top:2px}\n.ctx[hidden]{display:none}\n.ctx{font-size:12px;margin-bottom:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.cd>.thread .tx,.clamp{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n/* A long comment at rest in the column: its first four lines, fading out, then \"more\"; opened, it eases out to all of it. */\n.th.long .root>.tx{max-height:var(--full);overflow:hidden;transition:max-height var(--mv) var(--eio)}\n.th.long:not(.on) .root>.tx{max-height:84px;mask-image:linear-gradient(#000 calc(100% - 21px),transparent)}\n.mr{display:grid;grid-template-rows:0fr;opacity:0;font-size:12px;color:var(--pu-accent);text-align:end;transition:grid-template-rows var(--mv) var(--eio),opacity var(--in) var(--eo)}\n.mr>span{min-height:0;overflow:hidden}\n.th.long:not(.on) .mr{grid-template-rows:1fr;opacity:1}\n.tx a{color:var(--pu-accent);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px}\n\n/* Actions sit at the card's top right: always on an open card, on hover (or focus) on one at rest. In All comments a\nclosed card keeps them while its \u22EF menu is open, and focus put there for the keyboard (not a click) shows them. After\nResolve (.still) the card that moves up under the pointer keeps them from it until it moves; keyboard focus shows them. */\n.acts{margin-left:auto;display:flex;flex:none;transition:opacity var(--in) var(--eo)}\n.th:not(.on):not(:hover):not(:focus-within) .acts,.cd:not(.open):not(:hover):not(:has(:focus-visible,.pm)) .acts,.still .th:not(.on):not(:has(:focus-visible)) .acts,.still .cd:not(.open):not(:has(:focus-visible,.pm)) .acts{opacity:0;pointer-events:none}\n/* On a closed card they lie over the end of the top line, on the card's own colour, so the name has the whole line\nwhile they're hidden and the card never changes size as they ease in. They take presses only once eased in: a click as\nthe pointer arrives, or a tap (which hovers as it lands), opens the card. */\n.th:not(.on) .acts,.cd:not(.open) .acts{position:absolute;right:0;padding-left:16px;background:linear-gradient(90deg,transparent,var(--bg) 14px);transition:opacity var(--in) var(--eo),pointer-events 0s var(--in);transition-behavior:allow-discrete}\n.th.on .rest{display:none}\n/* A comment's \u22EF menu: Copy thread, Edit, Delete (asked twice). */\n.pm{position:absolute;right:0;top:100%;z-index:5;display:flex;flex-direction:column;min-width:150px;padding:4px;border:1px solid var(--ln);border-radius:10px;background:var(--su);box-shadow:var(--sh)}\n.pm.in{animation:pu-in var(--in) var(--eo) both}\n.pm button{padding:6px 10px;border-radius:7px;text-align:left;font-size:13px;transition:background-color .2s var(--eo),color .2s var(--eo)}\n.pm button:hover,.pm button:focus-visible{background:var(--ho)}\n.pm .dl{color:#C9402F}.dark .pm .dl{color:#F08A7E}\n.edit{margin-top:2px}\n.ib{width:24px;height:22px;display:grid;place-items:center;border-radius:6px;color:var(--mu);transition:background-color .2s var(--eo),color .2s var(--eo)}\n.ib:hover,.ib:focus-visible{background:var(--ho);color:var(--k)}\n\n/* Replies: one level in, oldest first; the reply line ends the thread at the same indent. */\n.rps,.rbox{padding-left:14px}\n.rps .it{padding-top:8px}\n.more{display:grid;grid-template-rows:1fr;transition:grid-template-rows var(--mv) var(--eio)}\n.more>div{overflow:hidden;min-height:0}\n.th:not(.on) .more{grid-template-rows:0fr}\n\n/* Writing boxes: a rounded box that says what it's for, then Cancel and Send (named) while it is in use. */\n.row{position:relative;padding-top:6px}\n.line{display:flex;align-items:center;gap:6px;padding:3px 12px;border:1px solid var(--ln);border-radius:16px;cursor:text;transition:border-color var(--in) var(--eo),box-shadow var(--in) var(--eo)}\n.line:hover{border-color:var(--mu)}\n.line:focus-within{border-color:var(--pu-accent);box-shadow:0 0 0 1px var(--pu-accent)}\n.input{flex:1;min-width:0;height:24px;display:block;border:0;outline:none;resize:none;overflow:hidden;\nfont:inherit;font-size:14px;line-height:1.5;padding:1px 0;color:var(--k);background:transparent}\n.input::placeholder{color:var(--fa);opacity:1}\n.bts{display:flex;justify-content:flex-end;gap:4px;max-height:0;opacity:0;overflow:hidden;transition:max-height var(--in) var(--eo),opacity var(--in) var(--eo),padding var(--in) var(--eo)}\n.row:focus-within .bts,.row.has .bts{max-height:40px;opacity:1;padding-top:4px}\n.bts button{padding:2px 10px;border-radius:14px;font-size:13px;font-weight:600;color:var(--pu-accent);transition:background-color .2s var(--eo),color .2s var(--eo)}\n.cnl:hover,.cnl:focus-visible{background:var(--as)}\n.send{background:var(--pu-accent);color:var(--pu-on,#fff)!important}\n.send:not(.show){background:var(--ru);color:var(--fa)!important;cursor:default}\n.ed{display:flex;gap:10px;align-items:center;min-height:34px;padding:6px 10px}\n.ed .input,.nf .input{font-size:13px;height:22px;border-bottom:1px solid var(--ln)}\n.ed .input:focus,.nf .input:focus{border-bottom-color:var(--pu-accent)}\n\n/* Avatars: a squircle with the writer's animal (or initial) on a soft colour, at the start of the card's top line. */\n.av{flex:none;display:grid;place-items:center;width:22px;height:22px;border-radius:32%;\nfont-size:12px;font-weight:600;line-height:1;opacity:0;transform:scale(.85);transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.av.in{opacity:1;transform:none}\n/* The animal nearly fills its tint (its drawing has room round it in its 24 grid), in a lighter line so it stays thin. */\n.av svg{width:122%;height:122%;stroke-width:1.3}\n/* Ten colours (.c0 Red to .c9 Grey, as COLOURS): a soft tint and a dark drawing, swapped round on dark pages. */\n.av{--av-l:92%;--av-d:30%;--s:60%;background:hsl(var(--h) var(--s) var(--av-l));color:hsl(var(--h) var(--s) var(--av-d))}\n.dark .av{--av-l:20%;--av-d:78%}\n/* An AI's reply: a plain disc with a violet ring, all inside the avatar's own box so nothing is cropped. */\n.av.ai{position:relative;border-radius:50%;background:var(--su);color:var(--pu-accent);font-size:9px;letter-spacing:.02em}\n.av.ai::before{content:\"\";position:absolute;inset:3px;border-radius:50%;border:1.5px solid var(--pu-accent);pointer-events:none}\n.refs{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;white-space:normal}\n.refs .ref{display:inline-flex;align-items:center;gap:5px;max-width:160px;padding:1px 9px;border:1px solid var(--ln);border-radius:999px;background:var(--so);color:var(--mu);font:inherit;font-size:11.5px;line-height:1.6;text-decoration:none;cursor:pointer;transition:background-color .2s var(--eo),color .2s var(--eo),border-color .2s var(--eo)}\n.refs .ref:hover,.refs .ref:focus-visible{background:var(--as);color:var(--pu-accent);border-color:var(--pu-accent)}\n.ref span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.rxp{position:fixed;width:320px;height:340px;border-radius:12px;overflow:hidden;pointer-events:none;opacity:0;transform:translateY(4px);transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.rxp.show{opacity:1;transform:none;pointer-events:auto}\n.mn{border-radius:4px;padding:0 2px;background:var(--as);color:var(--pu-accent)}\n.mn.me{font-weight:600}\n.mnl{position:absolute;left:0;bottom:100%;margin:0 0 4px;padding:4px;list-style:none;min-width:180px;max-width:100%;border:1px solid var(--ln);border-radius:10px;background:var(--su);box-shadow:var(--sh);opacity:0;transform:translateY(4px);pointer-events:none;z-index:4;transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.mnl.show{opacity:1;transform:none;pointer-events:auto}\n.mnl li{display:flex;align-items:baseline;gap:8px;padding:5px 8px;border-radius:6px;font-size:13px;color:var(--k);cursor:pointer}\n.mnl li small{font-size:11px;color:var(--fa)}\n.mnl li.on,.mnl li:hover{background:var(--as);color:var(--pu-accent)}\n.dlg{position:fixed;left:50%;top:18vh;width:min(380px,calc(100vw - 32px));padding:16px 18px;border:1px solid var(--ln);border-radius:14px;background:var(--su);box-shadow:var(--sh);color:var(--k);opacity:0;transform:translate(-50%,6px);pointer-events:none;transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.dlg.show{opacity:1;transform:translate(-50%,0);pointer-events:auto}\n.toast.act{display:flex;align-items:center}.toast.show .ta{pointer-events:auto}\n/* Faded, its button is no longer a Tab stop (hidden once the fade is over). */\n.toast .ta{transition:visibility 0s .3s}.toast:not(.show) .ta{visibility:hidden}.toast.show .ta{transition-delay:0s}\n/* By touch, comment mode's hint is filled with the accent; once a tap has opened a box its words fold away (their column\neases to nothing) and Done stays. */\n.toast.tap{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;background:var(--pu-accent);border-color:var(--pu-accent);color:var(--pu-on,#fff);font-size:15px;font-weight:500;transition:opacity .3s var(--eo),transform .3s var(--eo),grid-template-columns var(--mv) var(--eio),padding var(--mv) var(--eio)}\n.toast.tap .tw{min-width:0;max-height:4em;overflow:hidden;text-wrap:balance;transition:opacity var(--out) var(--ei),max-height var(--mv) var(--eio)}\n.toast.tap .ta{color:inherit;font-weight:600}\n.toast.tap.taught{grid-template-columns:minmax(0,0fr) auto;padding-left:4px}\n.toast.tap.taught .tw{opacity:0;max-height:0}\n.ta{margin-left:4px;color:var(--pu-accent);text-decoration:underline;text-underline-offset:2px}\n.rx .n{font-size:11px;font-variant-numeric:tabular-nums}\n.rx.mine{background:var(--as);color:var(--pu-accent);border-color:var(--pu-accent)}\n.rx:disabled{cursor:default;opacity:.7}\n.rx{-webkit-touch-callout:none;-webkit-user-select:none}\n.rx.new,.rxq.new{animation:pu-in var(--mv) var(--eo) backwards}\n.rxs{align-items:center}\n.rxq{display:inline-flex;gap:2px}\n.rxo{padding:2px 5px;border-radius:8px;font-size:16px;line-height:1.3;transition:background-color .2s var(--eo)}\n.rxo:hover,.rxo:focus-visible,.rxo[aria-pressed=true]{background:var(--as)}\n.refs .ref{position:relative}.ref .rc{position:absolute;left:0;top:calc(100% + 6px);z-index:3;width:max-content;max-width:min(240px,70vw);padding:6px 9px;border:1px solid var(--ln);border-radius:8px;background:var(--su);box-shadow:var(--sh);color:var(--k);font-size:12px;line-height:1.45;white-space:normal;text-align:left;overflow:visible;opacity:0;visibility:hidden;transform:translateY(-3px);pointer-events:none;transition:opacity .2s var(--eo),transform .2s var(--eo),visibility 0s .2s}.ref:hover .rc,.ref:focus-visible .rc,.ref.held .rc{opacity:1;visibility:visible;transform:none;transition-delay:0s}\n.ref .sl{width:10px;height:7px;border:1.3px solid currentColor;border-radius:2px;flex:none}\n.seen{display:flex;align-items:center;gap:6px;padding:6px 0 0 14px;font-size:12px;color:var(--fa)}\n.cm{display:flex;align-items:center;gap:6px;max-height:20px;padding-top:4px;overflow:hidden;font-size:12px;color:var(--fa)}.hdr .cm{display:inline-grid;padding:0;flex:none}.cm.new{animation:pu-in var(--mv) var(--eo) both}.cm.out{animation:cm-out var(--mv) var(--eo) forwards}\n@keyframes cm-out{to{opacity:0;max-height:0;padding-top:0}}\n.seen button{margin-left:auto;padding:0 6px;border:0;border-radius:6px;background:none;color:var(--fa);font:inherit;cursor:pointer;transition:color .2s var(--eo),background-color .2s var(--eo)}.seen button:hover,.seen button:focus-visible{color:var(--pu-accent);background:var(--as)}\n.seen i{width:12px;height:12px;border-radius:50%;border:1.5px solid var(--pu-accent);flex:none}\n.c0{--h:0}.c1{--h:25}.c2{--h:50}.c3{--h:130}.c4{--h:175}.c5{--h:215}.c6{--h:270}.c7{--h:330}.c8{--h:28;--s:35%}.c9{--h:220;--s:8%}\nbutton.me{flex:none;display:grid;place-items:center}\nbutton.me>.av{grid-area:1/1}\n/* Who you are, at the top of a new comment; a name field eases open in its place. */\n.you{flex:1;display:grid;min-width:0;min-height:22px;font-size:12px;color:var(--fa)}\n.you .nm{font-size:13px;font-weight:600;color:var(--k)}\n.you>*{grid-area:1/1;display:flex;align-items:center;min-width:0;transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.say{white-space:pre;overflow:hidden}\n.nm{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.say button{flex:none}\n.say button{color:var(--mu);text-decoration:underline;text-decoration-color:var(--ln);text-underline-offset:2px;transition:color .2s var(--eo)}\n.say button:hover,.say button:focus-visible{color:var(--pu-accent)}\n.nf{opacity:0;transform:translateY(-4px);pointer-events:none}\n.nf .input{font-size:13px;height:24px}\n.you.naming .nf{opacity:1;transform:none;pointer-events:auto}\n.you.naming .say{opacity:0;pointer-events:none}\n\n/* Bubbles, tips, popovers, marks, pulses. */\n.bub{position:fixed;left:0;top:0;width:20px;height:20px;margin:-20px 0 0 -2px;border-radius:50% 50% 50% 0;background:var(--pu-accent);\nborder:2px solid var(--su);box-shadow:0 1px 4px rgba(0,0,0,.2);pointer-events:auto;opacity:0;transform:translateY(-6px) scale(.9);transform-origin:2px 20px;\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.bub.in{opacity:1;transform:none;transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.bub.in:hover,.bub.on{transform:scale(1.18)}\n.bub.done{background:var(--fa)}\n.bub.ghost{pointer-events:none}\n.tip,.pop,.menu,.all,.xp,.toast,.selbar,.namebar,.tt,.rxp{background:var(--su);border:1px solid var(--ln);box-shadow:var(--sh)}\n.tip{position:fixed;max-width:260px;border-radius:10px;padding:7px 10px;opacity:0;transform:translateY(3px);\ntransition:opacity .22s var(--eo),transform .26s var(--eo)}\n.tip.show{opacity:1;transform:none;pointer-events:auto;cursor:pointer;\ntransition:opacity .22s var(--eo),transform .26s var(--eo),left var(--mv) var(--eio),top var(--mv) var(--eio)}\n.pop{position:fixed;width:var(--pu-pop);border-radius:12px;padding:8px 10px 10px;pointer-events:none;opacity:0;transform:translateY(6px) scale(.98);\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.pop.show{opacity:1;transform:none;pointer-events:auto;transition:opacity var(--in) var(--eo),transform var(--mv) var(--eo)}\n.pop.show.glide{transition:opacity var(--in) var(--eo),transform var(--mv) var(--eo),left var(--mv) var(--eio),top var(--mv) var(--eio)}\n.mark{position:fixed;border:2px solid color-mix(in srgb,var(--pu-accent) 50%,transparent);background:var(--as);border-radius:8px;opacity:0;\ntransition:opacity .3s var(--eo)}\n.mark.show{opacity:1}\n.pulse{position:fixed;border:2px solid var(--pu-accent);border-radius:8px;background:var(--as);animation:pu-pulse var(--pl) var(--eio) forwards}\n@keyframes pu-pulse{0%{opacity:0;transform:scale(.985)}35%{opacity:.85;transform:scale(1)}100%{opacity:0;transform:scale(1.02)}}\n\n/* Document column. */\n.col{position:fixed;left:0;top:0;pointer-events:none;transition:opacity var(--in) var(--eo)}\n/* Each thread is a compact card; the open one lifts with a soft shadow. */\n.th,.cd{--bg:var(--su);background:var(--bg);opacity:var(--o,1)}\n.th{position:absolute;left:0;right:0;top:0;padding:8px 10px 10px;border:1px solid var(--ln);border-radius:10px;pointer-events:auto;cursor:pointer;\ntransition:transform var(--mv) var(--eio),box-shadow var(--in) var(--eo),border-color var(--in) var(--eo),opacity var(--in) var(--eo)}\n.th.hot{border-color:var(--mu)}\n.th.on{border-color:transparent;box-shadow:var(--sh);cursor:default}\n.th.dim{--o:.45}\n.th.resolved:not(.on):not(.hot){--o:.6}\n.th.lost .ctx{white-space:normal}\n.th.wait{opacity:0!important;pointer-events:none}\n.th:is(.out,.hop){opacity:0!important;pointer-events:none;transition:opacity var(--out) var(--ei)}\n/* A closed thread's button has no box of its own: the thread shows its focus. */\n.th:has(.opn:focus-visible){outline:2px solid var(--pu-accent);outline-offset:2px}\n\n/* Selection bar. */\n.selbar{position:fixed;display:flex;border-radius:10px;padding:2px;opacity:0;transform:translateY(4px);pointer-events:none;\ntransition:opacity var(--in) var(--eo),transform .3s var(--eo)}\n.selbar.show{opacity:1;transform:none;pointer-events:auto}\n.selbar button{width:32px;height:30px;display:grid;place-items:center;border-radius:8px;color:var(--pu-accent);transition:background-color .2s var(--eo)}\n.selbar button:hover,.selbar button:focus-visible{background:var(--as)}\n\n/* Comment mode's outline: glides between blocks; solid accent once chosen. */\n.pick{position:fixed;left:0;top:0;border:2px solid color-mix(in srgb,var(--pu-accent) 70%,transparent);background:color-mix(in srgb,var(--pu-accent) 6%,transparent);\nborder-radius:8px;pointer-events:none;opacity:0;transition:opacity var(--out) var(--ei),border-color var(--out) var(--ei),border-width var(--out) var(--ei),box-shadow var(--out) var(--ei)}\n.pick.show{opacity:1;transition-duration:var(--in);transition-timing-function:var(--eo)}\n.pick.show.glide{transition-property:all;transition-duration:var(--mv);transition-timing-function:var(--eio)}\n.pick.on{border-color:var(--pu-accent)}\n.pkw{position:fixed;left:0;top:0;border:2px dashed color-mix(in srgb,var(--pu-accent) 70%,transparent);border-radius:8px;pointer-events:none;opacity:0;transition:opacity var(--out) var(--ei)}\n.pkw.show{opacity:.6;transition:opacity var(--in) var(--eo)}\n.draft{transition:opacity var(--in) var(--eo)}.draft.wait:not(:focus-within){opacity:.6}\n/* The block cursor: its focus is drawn on the outline (3px accent with a surface halo, 3:1 on any page); the\nmarkers that hold focus are invisible. */\n.pick.kf{border:3px solid var(--pu-accent);box-shadow:0 0 0 2px var(--su)}\n.km{position:fixed;left:0;top:0;pointer-events:none;outline:none}\n.sr,.mn-sr,.hh{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n.namebar{position:fixed;left:0;top:0;display:flex;align-items:center;gap:2px;max-width:calc(100% - 16px);padding:2px 2px 2px 10px;border-radius:10px;font-size:12px;color:var(--mu);\nwhite-space:nowrap;pointer-events:none;opacity:0;transform:translateY(4px);\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.namebar.show{opacity:1;transform:none;pointer-events:auto;transition:opacity var(--in) var(--eo),transform .3s var(--eo)}\n.namebar.show.glide{transition-property:all;transition-duration:var(--mv);transition-timing-function:var(--eio)}\n/* It sits over the page's words beside its block: pointed at anywhere but its icons, it fades so they can be read. */\n.namebar:hover:not(:has(.nb:hover)){opacity:.15}\n.namebar .lbl{margin-right:6px;max-width:260px;overflow:hidden;text-overflow:ellipsis}\n.namebar .nb{padding:4px;display:grid;place-items:center;border-radius:7px;transition:background-color .2s var(--eo),color .2s var(--eo)}\n.namebar .nb:hover,.namebar .nb:focus-visible{background:var(--ho);color:var(--k)}\n.namebar .nb.on{background:var(--as);color:var(--pu-accent)}\n.namebar .nb[hidden]{display:none}\n\n/* The comment control: a small square of frosted glass tucked into the page's corner (flush right and bottom, its\ntop left rounded), with the open count inside a comment bubble. Pointed at, it reaches left just far enough to show\nits shortcut (and anything add-ons say). Its layers and numbers cross-fade in place. */\n.launch{position:fixed;right:0;bottom:var(--pu-lift);display:flex;flex-direction:row-reverse;align-items:center;border-top-left-radius:10px;pointer-events:auto;transform-origin:100% 100%;\nbackground:color-mix(in srgb,var(--su) 62%,transparent);-webkit-backdrop-filter:blur(10px) saturate(1.5);backdrop-filter:blur(10px) saturate(1.5);\ntransition:opacity var(--in) var(--eo),transform var(--mv) var(--eo),background-color .3s var(--eo),color .3s var(--eo),bottom var(--mv) var(--eio)}\n.launch:has(.mode.on){background:color-mix(in srgb,var(--pu-accent) 90%,transparent);color:var(--pu-on,#fff)}\n/* While All comments is open, the control is the panel's footer: it draws in, and the footer takes its place. */\n.layer.listing .launch{opacity:0;transform:scale(.6);pointer-events:none}\n.launch.off{display:none}\n.menu.inp{--b:60px;right:8px;z-index:1}\n.menu.inp :is([data-item=comment],[data-item=all]){display:none}\n.launch .mode{position:relative;display:grid;place-items:center;width:32px;height:32px;border-radius:10px 0 0}\n.mode>*,.cnt>*{grid-area:1/1}\n.mode>*{display:grid;place-items:center;transition:opacity var(--in) var(--eo)}\n.cnt,.has>.plus{opacity:0}\n.has>.cnt{opacity:1}\n.cnt>.n{font-size:11px;font-weight:500;line-height:1;margin:-1.2px 0 0 1.2px;font-variant-numeric:tabular-nums lining-nums;opacity:0;transition:opacity var(--in) var(--eo)}\n.w>.n{margin-top:-3.4px}\n.cnt>svg{transition:opacity var(--in) var(--eo)}\n.cnt>svg+svg,.cnt.w>svg{opacity:0}\n.cnt.w>svg+svg{opacity:1}\n.n.on{opacity:1}\n.launch .mode:focus-visible{outline:2px solid var(--pu-accent);outline-offset:-2px}\n/* Open threads on other slides or views: a small dot at the bubble's top right, easing in and out. */\n.mode>.dot{position:absolute;top:2px;right:2px;width:8px;height:8px;border-radius:50%;background:var(--pu-accent);opacity:0;transition:opacity var(--in) var(--eo),background-color .3s var(--eo)}\n.mode.else>.dot{opacity:1}\n.mode.on>.dot{background:var(--pu-on,#fff)}\n/* What it reaches out to show when pointed at: the shortcut, after anything add-ons say. Its width eases to exactly what\nthe words need (a 0fr to 1fr column), so it opens and folds evenly, and it folds at once when the pointer leaves. */\n.ttip{display:grid;grid-template-columns:minmax(0,0fr);overflow:hidden;font-size:12px;color:var(--mu);pointer-events:none;opacity:0;transition:grid-template-columns var(--out) var(--ei),opacity var(--out) var(--ei)}\n.ttip>span{min-width:0;white-space:nowrap;padding-left:10px}\n.ttip small{font-size:12px}\n.ttip small,.mi .kc{color:var(--fa)}\n.launch:has(.mode.on) :is(.ttip,.ttip small){color:inherit}\n.launch:hover .ttip{grid-template-columns:minmax(0,1fr);opacity:1;transition:grid-template-columns var(--mv) var(--eo) .4s,opacity var(--in) var(--eo) .4s}\n.launch:has(.mm[aria-expanded=true]) .ttip{grid-template-columns:minmax(0,0fr);opacity:0}\n/* \u22EF at the end of the reach opens the menu: it folds with the reach, and opens with it on hover or keyboard focus. */\n.launch .mm{display:grid;place-items:center;width:0;height:24px;margin-left:0;border-radius:6px;overflow:hidden;opacity:0;color:inherit;transition:width var(--out) var(--ei),margin var(--out) var(--ei),opacity var(--out) var(--ei),background-color .2s var(--eo)}\n.launch:is(:hover,:has(.mm:focus-visible)) .mm{width:26px;margin-left:4px;opacity:1;transition:width var(--mv) var(--eo) .4s,margin var(--mv) var(--eo) .4s,opacity var(--in) var(--eo) .4s,background-color .2s var(--eo)}\n.launch:has(.mm:focus-visible) .mm{transition-delay:0s}\n.launch .mm:hover{background:color-mix(in srgb,currentColor 12%,transparent)}\n.launch .mm:focus-visible{outline:2px solid var(--pu-accent);outline-offset:-2px}\n.launch:has(.mm[aria-expanded=true]) .mm{width:0;margin-left:0;opacity:0}\n.launch .mode{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}\n.launch:has(.mm:focus-visible) .ttip{grid-template-columns:minmax(0,1fr);opacity:1}\n@media (hover:none){.ttip,.tt,.cd>.thread .acts,.launch .mm{display:none}}\n.menu{--b:calc(40px + var(--pu-lift));position:fixed;right:8px;bottom:var(--b);width:290px;border-radius:12px;padding:4px;pointer-events:none;opacity:0;transform:translateY(6px);\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.menu.show{z-index:2;opacity:1;transform:none;pointer-events:auto;transition:opacity var(--in) var(--eo),transform var(--mv) var(--eo)}\n@media (max-height:600px){.menu{max-height:calc(100% - var(--b) - 8px);overflow-y:auto;overscroll-behavior:contain}}\n/* The open menu was asked for: it sits over a chosen block's naming bar and an open comment box (a phone has little room). */\n/* Menu rows: one line each; the explanation is a tooltip beside the menu that eases in after a moment. */\n.mi{position:relative;display:flex;gap:10px;align-items:center;width:100%;min-height:34px;text-align:left;padding:6px 10px;border-radius:8px;transition:background-color .2s var(--eo)}\n.mi:hover,.mi:focus-visible{background:var(--so)}\n.mi svg{color:var(--mu);flex:none}\n.mi small{display:block;color:var(--fa);font-size:12px}\n.lb{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.ma{flex:none;display:grid;margin:-2px 0}\n.ma .av{width:20px;height:20px;font-size:11px}\n.mi .av svg{color:inherit}\n.sep{height:1px;margin:4px 10px;background:var(--ru)}\n/* Copy comments opens to its two choices beneath it, set in under it; folding and unfolding ease. */\n.subs{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--in) var(--eo),opacity var(--in) var(--eo)}\n.subs.open{grid-template-rows:1fr;opacity:1}\n.subs>div{min-height:0;overflow:hidden}\n.subs .mi{padding-left:30px}\n.chev{display:grid;transition:transform var(--in) var(--eo)}\n[aria-expanded=true]>.chev{transform:rotate(90deg)}\n.tt{position:absolute;right:calc(100% + 14px);top:50%;translate:0 -50%;width:max-content;max-width:220px;padding:4px 9px;border-radius:8px;font-size:12px;line-height:1.4;\ncolor:var(--mu);pointer-events:none;opacity:0;transform:translateX(3px);\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.mi:hover>.tt,.mi:focus-visible>.tt{opacity:1;transform:none;transition:opacity var(--in) var(--eo) .5s,transform var(--in) var(--eo) .5s}\n@media (max-width:600px){.tt{right:8px;top:auto;bottom:100%;translate:none;transform:translateY(3px)}}\n/* Switches: a track and a knob that slides (cross-fades with reduced motion). */\n.sw{position:relative;flex:none;width:26px;height:16px;border-radius:8px;background:var(--ln);transition:background-color var(--mv) var(--eio)}\n.sw::before,.sw::after{content:\"\";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);\ntransition:transform var(--mv) var(--eio),opacity var(--mv) var(--eio)}\n.sw::before{opacity:0;transform:translateX(10px)}\n[aria-checked=true]>.sw{background:var(--pu-accent)}\n[aria-checked=true]>.sw::after{transform:translateX(10px)}\n.tools{display:flex;flex:none;gap:2px}\n.tools[hidden]{display:none}\n.tool[aria-pressed=true]{background:var(--as);color:var(--pu-accent)}\n.note{display:block;padding:0 0 4px;font-size:12px;color:var(--fa)}\n.bts .here{flex:none;display:grid;place-items:center;margin-right:auto;padding:0 2px;color:var(--fa);transition:opacity var(--in) var(--eo)}.here.off{opacity:0;pointer-events:none}\n.note[hidden]{display:none}\n.ov{position:absolute;inset:0;pointer-events:none;overflow:hidden}\n/* An add-on's panel (.xp) is the same full-height panel as All comments (.all), with its own body. */\n.xb{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:4px 16px 16px;font-size:13px}\n.xb p{margin:0 0 10px}\n/* All comments: a full-height panel on the right that slides in (fades, with reduced motion). */\n.all,.xp{position:fixed;top:0;right:0;bottom:0;width:var(--pu-panel);display:flex;flex-direction:column;border-width:0 0 0 1px;opacity:0;transform:translateX(100%);\ntransition:transform var(--out) var(--ei),opacity var(--out) var(--ei)}\n.all.show,.xp.show{opacity:1;transform:none;pointer-events:auto;transition:transform var(--mv) var(--eo),opacity var(--in) var(--eo)}\n.all.full{width:100%}\n/* The header: no title, two filters (an icon and a word in a pill: grey off, a soft accent fill on) and Close. */\n.hd{display:flex;align-items:center;gap:4px;padding:8px 8px 6px}\n.sp{flex:1}\n.tg{position:relative;display:flex;align-items:center;gap:5px;height:28px;padding:0 11px 0 9px;border-radius:99px;white-space:nowrap;line-height:1;color:var(--mu);transition:background-color .2s var(--eo),color .2s var(--eo)}\n.tg:hover{background:var(--ho);color:var(--k)}\n.tg[aria-pressed=true]{background:var(--as);color:var(--pu-accent)}\n.tg[aria-pressed=true]:hover{background:color-mix(in srgb,var(--pu-accent) 20%,transparent)}\n.tg:focus-visible{outline:2px solid var(--pu-accent);outline-offset:1px}\n/* What each filter does: a tooltip under it that eases in after a moment, on hover or focus. */\n.tg::after{content:attr(data-tip);position:absolute;left:0;top:calc(100% + 4px);z-index:3;width:max-content;max-width:220px;padding:4px 9px;border:1px solid var(--ln);border-radius:8px;background:var(--su);box-shadow:var(--sh);font-size:12px;line-height:1.4;color:var(--mu);pointer-events:none;opacity:0;transform:translateY(-3px);transition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.tg:not([aria-pressed=true]):is(:hover,:focus-visible)::after{opacity:1;transform:none;transition:opacity var(--in) var(--eo) .4s,transform var(--in) var(--eo) .4s}\n.list{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:4px 8px 8px;background:var(--so)}\n/* Threads in the panel are the same cards; opened, a card shows the thread beneath the words it's about. */\n.cd{position:relative;margin-top:6px;border:1px solid var(--ln);border-radius:10px}\n/* What filters take away or bring back folds out or in, and a card opening or closing eases to its new height. */\n.list :is(.cd,[role=listitem],.sec){transition:all var(--mv) var(--eio)}\n.list .out{opacity:0!important;margin-top:0;padding-block:0;border-width:0;overflow:hidden}\n.cd.open{border-color:transparent;box-shadow:var(--sh)}\n.cd.done{--o:.6}\n/* Closed, its button lies over it (choosing the card opens it), beneath its actions and what they open. */\n.cd>.mi{position:absolute;inset:0;border-radius:10px;background:none}\n.cd>.thread :is(.acts,.refs,.edit){position:relative;z-index:1}\n.cd:not(.open,.new):is(:hover,:has(>.mi:focus-visible)){--bg:var(--so)}\n.cd.open>.mi{display:none}\n.cd.open .root>.hdr{cursor:pointer}\n.cd.new,.xr,.cd>.thread{padding:8px 10px 10px}\n/* The footer: who you are, Comment (its shortcut and switch), and \u22EF for the menu. */\n.pf{display:flex;align-items:center;gap:8px;min-height:52px;padding:0 8px 0 14px;border-top:1px solid var(--ln)}\n.fs{display:flex;align-items:center;gap:6px;padding:4px 6px;border-radius:8px;font-size:13px;transition:background-color .2s var(--eo)}\n.fs:hover,.fs:focus-visible{background:var(--ho)}\n.fs small{font-size:12px;color:var(--fa)}\n.pf .ib{width:30px;height:30px}\n/* The line from an open thread in the panel to its place on the page. */\n.cx{position:fixed;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none;opacity:0;transition:opacity var(--in) var(--eo)}\n.cx.show{opacity:1}\n.cx path{fill:none;stroke:var(--pu-accent);stroke-width:1.5;stroke-linecap:round;opacity:.7}\n.cx circle{fill:var(--pu-accent)}\n.layer.listing .col{opacity:0;transition:opacity var(--out) var(--ei)}\n.sec{padding:8px 10px 2px;font-size:12px;color:var(--fa)}\n.fold{display:flex;align-items:center;gap:4px;width:100%;margin-top:6px;border-radius:6px;text-align:left;cursor:pointer}\n.fold:hover,.fold:focus-visible{color:var(--mu);outline:none}\n.fold svg{transition:transform var(--mv) var(--eio)}\n.fold[aria-expanded=true] svg{transform:rotate(90deg)}\n.ag{display:flex;align-items:center;gap:8px;padding:6px 14px;font-size:12px;color:var(--fa);border-bottom:1px solid var(--ln)}\n.ag[hidden]{display:none}\n.lns{display:flex;flex-direction:column}\n.ln{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--in) var(--eo),opacity var(--in) var(--eo)}\n.ln.show{grid-template-rows:1fr;opacity:1}\n.ln>.lw{overflow:hidden;min-height:0}\n.ag button{margin-left:auto;flex:none;padding:0 6px;border-radius:6px;color:var(--fa);transition:color .2s var(--eo),background-color .2s var(--eo)}.ag button:hover:not(:disabled),.ag button:focus-visible{color:var(--pu-accent);background:var(--as)}.ag button:disabled{opacity:.5;cursor:default}\n.adr{margin:8px 0 0 14px;padding:8px 10px;border-radius:8px;background:var(--so);font-size:13px;color:var(--k);animation:pu-in var(--mv) var(--eo) both}\n.adr small{display:block;font-size:12px;color:var(--fa)}\n.adr .go{display:flex;gap:6px;margin-top:6px}\n.adr .go button{padding:2px 8px;border-radius:6px;font-size:12px;color:var(--mu);transition:color .2s var(--eo),background-color .2s var(--eo)}.adr .go button:hover,.adr .go button:focus-visible{color:var(--pu-accent);background:var(--as)}\n@keyframes pu-in{0%{opacity:0;transform:translateY(3px)}100%{opacity:1;transform:none}}\n.sec b{margin-left:6px;font-weight:500;color:var(--pu-accent)}\n/* Messages sit beside the comment control, never over the page's own bottom bar; beside the panel while it is open,\nand above its footer when it fills the screen. */\n.toast{--r:40px;position:fixed;right:var(--r);bottom:calc(4px + var(--pu-lift));max-width:min(320px,calc(100vw - var(--r) - 16px));border-radius:10px;padding:8px 14px;font-size:13px;opacity:0;transform:translateY(6px);transition:.3s var(--eo)}\n.toast.show{opacity:1;transform:none}\n/* While All comments is open, messages sit inside the panel above its footer, on top of it: never over the page,\nnor a deck's own controls in the space beside it. */\n.layer:is(.listing,:has(.xp.show)) .toast{--r:12px;bottom:64px;max-width:min(320px,calc(var(--pu-panel) - 24px));z-index:2}\n/* While the menu is open, beside it (above it on a narrow screen), never under it. */\n.layer:not(.listing):has(.menu.show) .toast{--r:314px}\n@media (max-width:640px){.layer:not(.listing):has(.menu.show) .toast{--r:12px;bottom:auto;top:12px}}\n\n/* Touch: everything a finger presses is at least 44 px across. A bubble keeps its small look round a larger hit area.\nThe control is 44 px, so the menu and messages sit further from it. Shortcuts aren't shown where there is no keyboard. */\n@media (pointer:coarse){\n.mi,.ib,.rxo,.refs .ref,.mnl li,.bts button,.tg,.fs,.pm button,.ta,.nb,.selbar button,button.me,.say button,.line,.seen button,.ag button,.go button{min-height:44px}\n.ib,.rxo,.refs .ref,.nb,.selbar button,button.me,.ta{min-width:44px}\n.bub::before{content:\"\";position:absolute;inset:-14px;border-radius:50%}\n.row:focus-within .bts,.row.has .bts{max-height:52px}\n.hd{padding:4px 8px}\n.toast{--r:52px;display:flex;align-items:center;min-height:44px;padding:4px 14px;line-height:1.25}\n.ta{display:grid;place-items:center;margin:-5px -10px -5px 6px;padding:0 10px}\n.launch .mode{width:44px;height:44px}\n.menu{--b:calc(52px + var(--pu-lift))}\n}\n@media (pointer:coarse) and (hover:none){.ks{display:none}}\n\n/* Keyboard focus is always plainly marked: a 2 px ring in the accent, which reads at 4.5 to 1 on Pipeup's surfaces. */\n.layer :is(button,a):not(.mi,.opn,.mode,.tg):focus-visible{outline:2px solid var(--pu-accent);outline-offset:1px}\n.layer .mi:focus-visible{outline:2px solid var(--pu-accent);outline-offset:-2px}\n/* A look that differs from the page's keeps its see-through parts solid: a dimmed card dims its words, not the card. */\n.flip .launch{background:var(--su)}\n.flip :is(.th,.cd){opacity:1}\n.flip :is(.th,.cd)>*{opacity:var(--o,1);transition:opacity var(--in) var(--eo)}\n\n@media (forced-colors: active){\n.pick.kf{border:3px solid Highlight}\n.layer :is(.ib,.mi,.nb,.tg,.fs,.selbar button,.say button,.xb button):focus-visible{outline:2px solid Highlight}\n/* Switches, the filters, your own reactions and comment mode draw in system colours, so every state shows. */\n.sw{forced-color-adjust:none;border:1px solid CanvasText;background:Canvas}\n.sw::before,.sw::after{top:1px;left:1px;background:CanvasText}\n[aria-checked=true]>.sw{background:Highlight;border-color:Highlight}\n[aria-checked=true]>.sw::before,[aria-checked=true]>.sw::after{background:HighlightText}\n.layer :is(.tg,.rxo,.rx)[aria-pressed=true],.launch:has(.mode.on){forced-color-adjust:none;background:Highlight;color:HighlightText;border-color:Highlight}\n}\n\n@media (prefers-reduced-motion: reduce){\n.ln,.subs{transition-property:opacity}\n@keyframes pu-in{0%{opacity:0}100%{opacity:1}}\n@keyframes cm-out{99%{opacity:0;max-height:20px;padding-top:4px}100%{opacity:0;max-height:0;padding-top:0}}\n.bub,.bub.in,.bub.in:hover,.bub.on,.tip,.pop,.selbar,.menu,.all,.xp,.tt,.toast,.ref .rc,.rxp,.mnl{transform:none!important}\n.ttip{transition-property:opacity!important}\n.dlg{transform:translate(-50%,0)!important}\n.sw::after{transform:none!important;transition-property:opacity}\n[aria-checked=true]>.sw::after{opacity:0}\n[aria-checked=true]>.sw::before{opacity:1}\n.th,.th.long .root>.tx,.mr,.list :is(.cd,[role=listitem],.sec){transition-property:opacity,box-shadow}\n.tip.show,.toast{transition-property:opacity}\n.pick,.pick.show{transition:opacity .16s ease-in-out}\n.all,.all.show,.xp,.xp.show{transition-property:opacity}\n.namebar,.namebar.show{transform:none!important;transition:opacity .16s ease-in-out}\n.av,.nf,.launch{transform:none!important}\n@keyframes pu-pulse{0%{opacity:0}35%{opacity:.85}100%{opacity:0}}\n}\n";
|
|
2
|
+
export declare const STYLES = "\n/* Inherited page styles are reset on .layer below; custom properties pass through all:initial. */\n:host{all:initial;direction:ltr;unicode-bidi:normal}\n*{box-sizing:border-box}\nbutton{appearance:none;font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}\nsvg{display:block}\n.layer{all:initial;direction:ltr;unicode-bidi:normal;display:block;position:fixed;inset:0;pointer-events:none;font:13px/1.5 var(--pu-font,system-ui,sans-serif);color:var(--k);\n--k:#2C2C2A;--mu:#5F5E5A;--fa:#6B6A64;--ln:#E5E3DA;--ru:#ECEAE2;--so:#FAFAF7;--su:#FFFFFF;--ho:#F1EFE8;\n--as:color-mix(in srgb,var(--pu-accent,#534AB7) 12%,transparent);\n--sh:0 8px 28px rgba(44,44,42,.10);\n--in:.26s;--mv:.34s;--out:.2s;--pl:1.2s;\n--eo:cubic-bezier(.22,.61,.36,1);--eio:cubic-bezier(.4,0,.2,1);--ei:cubic-bezier(.4,0,1,1);\n--pu-text:var(--k);--pu-muted:var(--mu);--pu-faint:var(--fa);--pu-line:var(--ln);--pu-surface:var(--su);--pu-soft:var(--so);--pu-hover:var(--ho);--pu-shadow:var(--sh);--pu-ease:var(--eo)}\n.layer{transition:opacity .24s var(--eo)}.layer.fading{opacity:0;transition-duration:.16s;transition-timing-function:var(--ei)}\n.layer.dark{--k:#ECEBE6;--mu:#B4B2A9;--fa:#9A9892;--ln:#3A3936;--ru:#34332F;--so:#22221F;--su:#1B1B19;--ho:#2C2C29;--sh:0 8px 28px rgba(0,0,0,.45)}\n.layer.hidden .bub,.layer.hidden .col,.layer.hidden .pop,.layer.hidden .tip,.layer.hidden .mark,.layer.hidden .selbar{opacity:0!important;pointer-events:none!important}\n/* While a phone's on-screen keyboard is up for a Pipeup box, the comment control and its messages fade out of the little\nroom left above it. */\n.layer.kb :is(.launch,.toast){opacity:0!important;pointer-events:none!important;transition:opacity var(--out) var(--ei)!important}\n\n.tx{font-size:14px;line-height:1.5;color:var(--k);white-space:pre-wrap;overflow-wrap:anywhere}\n.tx.del,.rest,.ctx,.ago{color:var(--fa)}\n.rest,.ago{font-size:12px}\n.rest{display:block;margin-top:3px}\n/* A card's top line: avatar, who (cut short on one line, never wrapped), how long ago, then its actions. */\n.hdr{position:relative;display:flex;align-items:center;gap:7px;min-height:22px}\n.who{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600;color:var(--k)}\n.ago{flex:none;white-space:nowrap}\n.ago::before{content:\"\u00B7\";margin-right:6px}\n.root>.tx,.it>.tx,.root>.ctx{margin-top:2px}\n.ctx[hidden]{display:none}\n.ctx{font-size:12px;margin-bottom:3px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.cd>.thread .tx,.clamp{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}\n/* A long comment at rest in the column: its first four lines, fading out, then \"more\"; opened, it eases out to all of it. */\n.th.long .root>.tx{max-height:var(--full);overflow:hidden;transition:max-height var(--mv) var(--eio)}\n.th.long:not(.on) .root>.tx{max-height:84px;mask-image:linear-gradient(#000 calc(100% - 21px),transparent)}\n.mr{display:grid;grid-template-rows:0fr;opacity:0;font-size:12px;color:var(--pu-accent);text-align:end;transition:grid-template-rows var(--mv) var(--eio),opacity var(--in) var(--eo)}\n.mr>span{min-height:0;overflow:hidden}\n.th.long:not(.on) .mr{grid-template-rows:1fr;opacity:1}\n.tx a{color:var(--pu-accent);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:2px}\n\n/* Actions sit at the card's top right: always on an open card, on hover (or focus) on one at rest. In All comments a\nclosed card keeps them while its \u22EF menu is open, and focus put there for the keyboard (not a click) shows them. After\nResolve (.still) the card that moves up under the pointer keeps them from it until it moves; keyboard focus shows them. */\n.acts{margin-left:auto;display:flex;flex:none;transition:opacity var(--in) var(--eo)}\n.th:not(.on):not(:hover):not(:focus-within) .acts,.cd:not(.open):not(:hover):not(:has(:focus-visible,.pm)) .acts,.still .th:not(.on):not(:has(:focus-visible)) .acts,.still .cd:not(.open):not(:has(:focus-visible,.pm)) .acts{opacity:0;pointer-events:none}\n/* On a closed card they lie over the end of the top line, on the card's own colour, so the name has the whole line\nwhile they're hidden and the card never changes size as they ease in. They take presses only once eased in: a click as\nthe pointer arrives, or a tap (which hovers as it lands), opens the card. */\n.th:not(.on) .acts,.cd:not(.open) .acts{position:absolute;right:0;padding-left:16px;background:linear-gradient(90deg,transparent,var(--bg) 14px);transition:opacity var(--in) var(--eo),pointer-events 0s var(--in);transition-behavior:allow-discrete}\n.th.on .rest{display:none}\n/* A comment's \u22EF menu: Copy thread, Edit, Delete (asked twice). */\n.pm{position:absolute;right:0;top:100%;z-index:5;display:flex;flex-direction:column;min-width:150px;padding:4px;border:1px solid var(--ln);border-radius:10px;background:var(--su);box-shadow:var(--sh)}\n.pm.in{animation:pu-in var(--in) var(--eo) both}\n.pm button{padding:6px 10px;border-radius:7px;text-align:left;font-size:13px;transition:background-color .2s var(--eo),color .2s var(--eo)}\n.pm button:hover,.pm button:focus-visible{background:var(--ho)}\n.pm .dl{color:#C9402F}.dark .pm .dl{color:#F08A7E}\n.edit{margin-top:2px}\n.ib{width:24px;height:22px;display:grid;place-items:center;border-radius:6px;color:var(--mu);transition:background-color .2s var(--eo),color .2s var(--eo)}\n.ib:hover,.ib:focus-visible{background:var(--ho);color:var(--k)}\n\n/* Replies: one level in, oldest first; the reply line ends the thread at the same indent. */\n.rps,.rbox{padding-left:14px}\n.rps .it{padding-top:8px}\n.more{display:grid;grid-template-rows:1fr;transition:grid-template-rows var(--mv) var(--eio)}\n.more>div{overflow:hidden;min-height:0}\n.th:not(.on) .more{grid-template-rows:0fr}\n\n/* Writing boxes: a rounded box that says what it's for, then Cancel and Send (named) while it is in use. */\n.row{position:relative;padding-top:6px}\n.line{display:flex;align-items:center;gap:6px;padding:3px 12px;border:1px solid var(--ln);border-radius:16px;cursor:text;transition:border-color var(--in) var(--eo),box-shadow var(--in) var(--eo)}\n.line:hover{border-color:var(--mu)}\n.line:focus-within{border-color:var(--pu-accent);box-shadow:0 0 0 1px var(--pu-accent)}\n.input{flex:1;min-width:0;height:24px;display:block;border:0;outline:none;resize:none;overflow:hidden;\nfont:inherit;font-size:14px;line-height:1.5;padding:1px 0;color:var(--k);background:transparent}\n.input::placeholder{color:var(--fa);opacity:1}\n.bts{display:flex;justify-content:flex-end;gap:4px;max-height:0;opacity:0;overflow:hidden;transition:max-height var(--in) var(--eo),opacity var(--in) var(--eo),padding var(--in) var(--eo)}\n.row:focus-within .bts,.row.has .bts{max-height:40px;opacity:1;padding-top:4px}\n.bts button{padding:2px 10px;border-radius:14px;font-size:13px;font-weight:600;color:var(--pu-accent);transition:background-color .2s var(--eo),color .2s var(--eo)}\n.cnl:hover,.cnl:focus-visible{background:var(--as)}\n.send{background:var(--pu-accent);color:var(--pu-on,#fff)!important}\n.send:not(.show){background:var(--ru);color:var(--fa)!important;cursor:default}\n.ed{display:flex;gap:10px;align-items:center;min-height:34px;padding:6px 10px}\n.ed .input,.nf .input{font-size:13px;height:22px;border-bottom:1px solid var(--ln)}\n.ed .input:focus,.nf .input:focus{border-bottom-color:var(--pu-accent)}\n\n/* Avatars: a squircle with the writer's animal (or initial) on a soft colour, at the start of the card's top line. */\n.av{flex:none;display:grid;place-items:center;width:22px;height:22px;border-radius:32%;\nfont-size:12px;font-weight:600;line-height:1;opacity:0;transform:scale(.85);transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.av.in{opacity:1;transform:none}\n/* The animal nearly fills its tint (its drawing has room round it in its 24 grid), in a lighter line so it stays thin. */\n.av svg{width:122%;height:122%;stroke-width:1.3}\n/* Ten colours (.c0 Red to .c9 Grey, as COLOURS): a soft tint and a dark drawing, swapped round on dark pages. */\n.av{--av-l:92%;--av-d:30%;--s:60%;background:hsl(var(--h) var(--s) var(--av-l));color:hsl(var(--h) var(--s) var(--av-d))}\n.dark .av{--av-l:20%;--av-d:78%}\n/* An AI's reply: a plain disc with a violet ring, all inside the avatar's own box so nothing is cropped. */\n.av.ai{position:relative;border-radius:50%;background:var(--su);color:var(--pu-accent);font-size:9px;letter-spacing:.02em}\n.av.ai::before{content:\"\";position:absolute;inset:3px;border-radius:50%;border:1.5px solid var(--pu-accent);pointer-events:none}\n.refs{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;white-space:normal}\n.refs .ref{display:inline-flex;align-items:center;gap:5px;max-width:160px;padding:1px 9px;border:1px solid var(--ln);border-radius:999px;background:var(--so);color:var(--mu);font:inherit;font-size:11.5px;line-height:1.6;text-decoration:none;cursor:pointer;transition:background-color .2s var(--eo),color .2s var(--eo),border-color .2s var(--eo)}\n.refs .ref:hover,.refs .ref:focus-visible{background:var(--as);color:var(--pu-accent);border-color:var(--pu-accent)}\n.ref span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.rxp{position:fixed;width:320px;height:340px;border-radius:12px;overflow:hidden;pointer-events:none;opacity:0;transform:translateY(4px);transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.rxp.show{opacity:1;transform:none;pointer-events:auto}\n.mn{border-radius:4px;padding:0 2px;background:var(--as);color:var(--pu-accent)}\n.mn.me{font-weight:600}\n.mnl{position:absolute;left:0;bottom:100%;margin:0 0 4px;padding:4px;list-style:none;min-width:180px;max-width:100%;border:1px solid var(--ln);border-radius:10px;background:var(--su);box-shadow:var(--sh);opacity:0;transform:translateY(4px);pointer-events:none;z-index:4;transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.mnl.show{opacity:1;transform:none;pointer-events:auto}\n.mnl li{display:flex;align-items:baseline;gap:8px;padding:5px 8px;border-radius:6px;font-size:13px;color:var(--k);cursor:pointer}\n.mnl li small{font-size:11px;color:var(--fa)}\n.mnl li.on,.mnl li:hover{background:var(--as);color:var(--pu-accent)}\n.dlg{position:fixed;left:50%;top:18vh;width:min(380px,calc(100vw - 32px));padding:16px 18px;border:1px solid var(--ln);border-radius:14px;background:var(--su);box-shadow:var(--sh);color:var(--k);opacity:0;transform:translate(-50%,6px);pointer-events:none;transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.dlg.show{opacity:1;transform:translate(-50%,0);pointer-events:auto}\n.toast.act{display:flex;align-items:center}.toast.show .ta{pointer-events:auto}\n/* Faded, its button is no longer a Tab stop (hidden once the fade is over). */\n.toast .ta{transition:visibility 0s .3s}.toast:not(.show) .ta{visibility:hidden}.toast.show .ta{transition-delay:0s}\n/* By touch, comment mode's hint is filled with the accent; once a tap has opened a box its words fold away (their column\neases to nothing) and Done stays. */\n.toast.tap{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;background:var(--pu-accent);border-color:var(--pu-accent);color:var(--pu-on,#fff);font-size:15px;font-weight:500;transition:opacity .3s var(--eo),transform .3s var(--eo),grid-template-columns var(--mv) var(--eio),padding var(--mv) var(--eio)}\n.toast.tap .tw{min-width:0;max-height:4em;overflow:hidden;text-wrap:balance;transition:opacity var(--out) var(--ei),max-height var(--mv) var(--eio)}\n.toast.tap .ta{color:inherit;font-weight:600}\n.toast.tap.taught{grid-template-columns:minmax(0,0fr) auto;padding-left:4px}\n.toast.tap.taught .tw{opacity:0;max-height:0}\n.ta{margin-left:4px;color:var(--pu-accent);text-decoration:underline;text-underline-offset:2px}\n.rx .n{font-size:11px;font-variant-numeric:tabular-nums}\n.rx.mine{background:var(--as);color:var(--pu-accent);border-color:var(--pu-accent)}\n.rx:disabled{cursor:default;opacity:.7}\n.rx{-webkit-touch-callout:none;-webkit-user-select:none}\n.rx.new,.rxq.new{animation:pu-in var(--mv) var(--eo) backwards}\n.rxs{align-items:center}\n.rxq{display:inline-flex;gap:2px}\n.rxo{padding:2px 5px;border-radius:8px;font-size:16px;line-height:1.3;transition:background-color .2s var(--eo)}\n.rxo:hover,.rxo:focus-visible,.rxo[aria-pressed=true]{background:var(--as)}\n.refs .ref{position:relative}.ref .rc{position:absolute;left:0;top:calc(100% + 6px);z-index:3;width:max-content;max-width:min(240px,70vw);padding:6px 9px;border:1px solid var(--ln);border-radius:8px;background:var(--su);box-shadow:var(--sh);color:var(--k);font-size:12px;line-height:1.45;white-space:normal;text-align:left;overflow:visible;opacity:0;visibility:hidden;transform:translateY(-3px);pointer-events:none;transition:opacity .2s var(--eo),transform .2s var(--eo),visibility 0s .2s}.ref:hover .rc,.ref:focus-visible .rc,.ref.held .rc{opacity:1;visibility:visible;transform:none;transition-delay:0s}\n.ref .sl{width:10px;height:7px;border:1.3px solid currentColor;border-radius:2px;flex:none}\n.seen{display:flex;align-items:center;gap:6px;padding:6px 0 0 14px;font-size:12px;color:var(--fa)}\n.cm{display:flex;align-items:center;gap:6px;max-height:20px;padding-top:4px;overflow:hidden;font-size:12px;color:var(--fa)}.hdr .cm{display:inline-grid;padding:0;flex:none}.cm.new{animation:pu-in var(--mv) var(--eo) both}.cm.out{animation:cm-out var(--mv) var(--eo) forwards}\n@keyframes cm-out{to{opacity:0;max-height:0;padding-top:0}}\n.seen button{margin-left:auto;padding:0 6px;border:0;border-radius:6px;background:none;color:var(--fa);font:inherit;cursor:pointer;transition:color .2s var(--eo),background-color .2s var(--eo)}.seen button:hover,.seen button:focus-visible{color:var(--pu-accent);background:var(--as)}\n.seen i{width:12px;height:12px;border-radius:50%;border:1.5px solid var(--pu-accent);flex:none}\n.c0{--h:0}.c1{--h:25}.c2{--h:50}.c3{--h:130}.c4{--h:175}.c5{--h:215}.c6{--h:270}.c7{--h:330}.c8{--h:28;--s:35%}.c9{--h:220;--s:8%}\nbutton.me{flex:none;display:grid;place-items:center}\nbutton.me>.av{grid-area:1/1}\n/* Who you are, at the top of a new comment; a name field eases open in its place. */\n.you{flex:1;display:grid;min-width:0;min-height:22px;font-size:12px;color:var(--fa)}\n.you .nm{font-size:13px;font-weight:600;color:var(--k)}\n.you>*{grid-area:1/1;display:flex;align-items:center;min-width:0;transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.say{white-space:pre;overflow:hidden}\n.nm{min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.say button{flex:none}\n.say button{color:var(--mu);text-decoration:underline;text-decoration-color:var(--ln);text-underline-offset:2px;transition:color .2s var(--eo)}\n.say button:hover,.say button:focus-visible{color:var(--pu-accent)}\n.nf{opacity:0;transform:translateY(-4px);pointer-events:none}\n.nf .input{font-size:13px;height:24px}\n.you.naming .nf{opacity:1;transform:none;pointer-events:auto}\n.you.naming .say{opacity:0;pointer-events:none}\n\n/* Bubbles, tips, popovers, marks, pulses. */\n.bub{position:fixed;left:0;top:0;width:20px;height:20px;margin:-20px 0 0 -2px;border-radius:50% 50% 50% 0;background:var(--pu-accent);\nborder:2px solid var(--su);box-shadow:0 1px 4px rgba(0,0,0,.2);pointer-events:auto;opacity:0;transform:translateY(-6px) scale(.9);transform-origin:2px 20px;\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.bub.in{opacity:1;transform:none;transition:opacity var(--in) var(--eo),transform var(--in) var(--eo)}\n.bub.in:hover,.bub.on{transform:scale(1.18)}\n.bub.done{background:var(--fa)}\n.bub.ghost{pointer-events:none}\n.tip,.pop,.menu,.all,.xp,.toast,.selbar,.namebar,.tt,.rxp{background:var(--su);border:1px solid var(--ln);box-shadow:var(--sh)}\n.tip{position:fixed;max-width:260px;border-radius:10px;padding:7px 10px;opacity:0;transform:translateY(3px);\ntransition:opacity .22s var(--eo),transform .26s var(--eo)}\n.tip.show{opacity:1;transform:none;pointer-events:auto;cursor:pointer;\ntransition:opacity .22s var(--eo),transform .26s var(--eo),left var(--mv) var(--eio),top var(--mv) var(--eio)}\n.pop{position:fixed;width:var(--pu-pop);border-radius:12px;padding:8px 10px 10px;pointer-events:none;opacity:0;transform:translateY(6px) scale(.98);\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.pop.show{opacity:1;transform:none;pointer-events:auto;transition:opacity var(--in) var(--eo),transform var(--mv) var(--eo)}\n.pop.show.glide{transition:opacity var(--in) var(--eo),transform var(--mv) var(--eo),left var(--mv) var(--eio),top var(--mv) var(--eio)}\n.mark{position:fixed;border:2px solid color-mix(in srgb,var(--pu-accent) 50%,transparent);background:var(--as);border-radius:8px;opacity:0;\ntransition:opacity .3s var(--eo)}\n.mark.show{opacity:1}\n.pulse{position:fixed;border:2px solid var(--pu-accent);border-radius:8px;background:var(--as);animation:pu-pulse var(--pl) var(--eio) forwards}\n@keyframes pu-pulse{0%{opacity:0;transform:scale(.985)}35%{opacity:.85;transform:scale(1)}100%{opacity:0;transform:scale(1.02)}}\n\n/* Document column. */\n.col{position:fixed;left:0;top:0;pointer-events:none;transition:opacity var(--in) var(--eo)}\n/* Each thread is a compact card; the open one lifts with a soft shadow. */\n.th,.cd{--bg:var(--su);background:var(--bg);opacity:var(--o,1)}\n.th{position:absolute;left:0;right:0;top:0;padding:8px 10px 10px;border:1px solid var(--ln);border-radius:10px;pointer-events:auto;cursor:pointer;\ntransition:transform var(--mv) var(--eio),box-shadow var(--in) var(--eo),border-color var(--in) var(--eo),opacity var(--in) var(--eo)}\n.th.hot{border-color:var(--mu)}\n.th.on{border-color:transparent;box-shadow:var(--sh);cursor:default}\n.th.dim{--o:.45}\n.th.resolved:not(.on):not(.hot){--o:.6}\n.th.lost .ctx{white-space:normal}\n.th.wait{opacity:0!important;pointer-events:none}\n.th:is(.out,.hop){opacity:0!important;pointer-events:none;transition:opacity var(--out) var(--ei)}\n/* A closed thread's button has no box of its own: the thread shows its focus. */\n.th:has(.opn:focus-visible){outline:2px solid var(--pu-accent);outline-offset:2px}\n\n/* Selection bar. */\n.selbar{position:fixed;display:flex;border-radius:10px;padding:2px;opacity:0;transform:translateY(4px);pointer-events:none;\ntransition:opacity var(--in) var(--eo),transform .3s var(--eo)}\n.selbar.show{opacity:1;transform:none;pointer-events:auto}\n.selbar button{width:32px;height:30px;display:grid;place-items:center;border-radius:8px;color:var(--pu-accent);transition:background-color .2s var(--eo)}\n.selbar button:hover,.selbar button:focus-visible{background:var(--as)}\n\n/* Comment mode's outline: glides between blocks; solid accent once chosen. */\n.pick{position:fixed;left:0;top:0;border:2px solid color-mix(in srgb,var(--pu-accent) 70%,transparent);background:color-mix(in srgb,var(--pu-accent) 6%,transparent);\nborder-radius:8px;pointer-events:none;opacity:0;transition:opacity var(--out) var(--ei),border-color var(--out) var(--ei),border-width var(--out) var(--ei),box-shadow var(--out) var(--ei)}\n.pick.show{opacity:1;transition-duration:var(--in);transition-timing-function:var(--eo)}\n.pick.show.glide{transition-property:all;transition-duration:var(--mv);transition-timing-function:var(--eio)}\n.pick.on{border-color:var(--pu-accent)}\n.pkw{position:fixed;left:0;top:0;border:2px dashed color-mix(in srgb,var(--pu-accent) 70%,transparent);border-radius:8px;pointer-events:none;opacity:0;transition:opacity var(--out) var(--ei)}\n.pkw.show{opacity:.6;transition:opacity var(--in) var(--eo)}\n.draft{transition:opacity var(--in) var(--eo)}.draft.wait:not(:focus-within){opacity:.6}\n/* The block cursor: its focus is drawn on the outline (3px accent with a surface halo, 3:1 on any page); the\nmarkers that hold focus are invisible. */\n.pick.kf{border:3px solid var(--pu-accent);box-shadow:0 0 0 2px var(--su)}\n.km{position:fixed;left:0;top:0;pointer-events:none;outline:none}\n.sr,.mn-sr,.hh{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}\n.namebar{position:fixed;left:0;top:0;display:flex;align-items:center;gap:2px;max-width:calc(100% - 16px);padding:2px 2px 2px 10px;border-radius:10px;font-size:12px;color:var(--mu);\nwhite-space:nowrap;pointer-events:none;opacity:0;transform:translateY(4px);\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.namebar.show{opacity:1;transform:none;pointer-events:auto;transition:opacity var(--in) var(--eo),transform .3s var(--eo)}\n.namebar.show.glide{transition-property:all;transition-duration:var(--mv);transition-timing-function:var(--eio)}\n/* It sits over the page's words beside its block: pointed at anywhere but its icons, it fades so they can be read. */\n.namebar:hover:not(:has(.nb:hover)){opacity:.15}\n.namebar .lbl{margin-right:6px;max-width:260px;overflow:hidden;text-overflow:ellipsis}\n.namebar .nb{padding:4px;display:grid;place-items:center;border-radius:7px;transition:background-color .2s var(--eo),color .2s var(--eo)}\n.namebar .nb:hover,.namebar .nb:focus-visible{background:var(--ho);color:var(--k)}\n.namebar .nb.on{background:var(--as);color:var(--pu-accent)}\n.namebar .nb[hidden]{display:none}\n\n/* The comment control: a small square of frosted glass tucked into the page's corner (flush right and bottom, its\ntop left rounded), with the open count inside a comment bubble. Pointed at, it reaches left just far enough to show\nits shortcut (and anything add-ons say). Its layers and numbers cross-fade in place. */\n.launch{position:fixed;right:0;bottom:var(--pu-lift);display:flex;flex-direction:row-reverse;align-items:center;border-top-left-radius:10px;pointer-events:auto;transform-origin:100% 100%;\nbackground:color-mix(in srgb,var(--su) 62%,transparent);-webkit-backdrop-filter:blur(10px) saturate(1.5);backdrop-filter:blur(10px) saturate(1.5);\ntransition:opacity var(--in) var(--eo),transform var(--mv) var(--eo),background-color .3s var(--eo),color .3s var(--eo),bottom var(--mv) var(--eio)}\n.launch:has(.mode.on){background:color-mix(in srgb,var(--pu-accent) 90%,transparent);color:var(--pu-on,#fff)}\n/* While All comments is open, the control is the panel's footer: it draws in, and the footer takes its place. */\n.layer.listing .launch{opacity:0;transform:scale(.6);pointer-events:none}\n.launch.off{display:none}\n.menu.inp{--b:60px;right:8px;z-index:1}\n.menu.inp :is([data-item=comment],[data-item=all]){display:none}\n.launch .mode{position:relative;display:grid;place-items:center;width:32px;height:32px;border-radius:10px 0 0}\n.mode>*,.cnt>*{grid-area:1/1}\n.mode>*{display:grid;place-items:center;transition:opacity var(--in) var(--eo)}\n.cnt,.has>.plus{opacity:0}\n.has>.cnt{opacity:1}\n.cnt>.n{font-size:11px;font-weight:500;line-height:1;margin:-1.2px 0 0 1.2px;font-variant-numeric:tabular-nums lining-nums;opacity:0;transition:opacity var(--in) var(--eo)}\n.w>.n{margin-top:-3.4px}\n.cnt>svg{transition:opacity var(--in) var(--eo)}\n.cnt>svg+svg,.cnt.w>svg{opacity:0}\n.cnt.w>svg+svg{opacity:1}\n.n.on{opacity:1}\n.launch .mode:focus-visible{outline:2px solid var(--pu-accent);outline-offset:-2px}\n/* Open threads on other slides or views: a small dot at the bubble's top right, easing in and out. */\n.mode>.dot{position:absolute;top:2px;right:2px;width:8px;height:8px;border-radius:50%;background:var(--pu-accent);opacity:0;transition:opacity var(--in) var(--eo),background-color .3s var(--eo)}\n.mode.else>.dot{opacity:1}\n.mode.on>.dot{background:var(--pu-on,#fff)}\n/* What it reaches out to show when pointed at: the shortcut, after anything add-ons say. Its width eases to exactly what\nthe words need (a 0fr to 1fr column), so it opens and folds evenly, and it folds at once when the pointer leaves. */\n.ttip{display:grid;grid-template-columns:minmax(0,0fr);overflow:hidden;font-size:12px;color:var(--mu);pointer-events:none;opacity:0;transition:grid-template-columns var(--out) var(--ei),opacity var(--out) var(--ei)}\n.ttip>span{min-width:0;white-space:nowrap;padding-left:10px}\n.ttip small{font-size:12px}\n.ttip small,.mi .kc{color:var(--fa)}\n.launch:has(.mode.on) :is(.ttip,.ttip small){color:inherit}\n.launch:hover .ttip{grid-template-columns:minmax(0,1fr);opacity:1;transition:grid-template-columns var(--mv) var(--eo) .4s,opacity var(--in) var(--eo) .4s}\n.launch:has(.mm[aria-expanded=true]) .ttip{grid-template-columns:minmax(0,0fr);opacity:0}\n/* \u22EF opens the menu: just left of the bubble, so however far the reach goes it eases in at the same place, beside the\nbubble, which never moves. It folds with the reach, and opens with it on hover or keyboard focus. */\n.launch .mm{display:grid;place-items:center;width:0;height:24px;margin-left:0;border-radius:6px;overflow:hidden;opacity:0;color:inherit;transition:width var(--out) var(--ei),margin var(--out) var(--ei),opacity var(--out) var(--ei),background-color .2s var(--eo)}\n.launch:is(:hover,:has(.mm:focus-visible)) .mm{width:26px;margin-left:4px;opacity:1;transition:width var(--mv) var(--eo) .4s,margin var(--mv) var(--eo) .4s,opacity var(--in) var(--eo) .4s,background-color .2s var(--eo)}\n.launch:has(.mm:focus-visible) .mm{transition-delay:0s}\n.launch .mm:hover{background:color-mix(in srgb,currentColor 12%,transparent)}\n.launch .mm:focus-visible{outline:2px solid var(--pu-accent);outline-offset:-2px}\n.launch:has(.mm[aria-expanded=true]) .mm{width:0;margin-left:0;opacity:0}\n.launch .mode{-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}\n.launch:has(.mm:focus-visible) .ttip{grid-template-columns:minmax(0,1fr);opacity:1}\n@media (hover:none){.ttip,.tt,.cd>.thread .acts,.launch .mm{display:none}}\n.menu{--b:calc(40px + var(--pu-lift));position:fixed;right:8px;bottom:var(--b);width:290px;border-radius:12px;padding:4px;pointer-events:none;opacity:0;transform:translateY(6px);\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.menu.show{z-index:2;opacity:1;transform:none;pointer-events:auto;transition:opacity var(--in) var(--eo),transform var(--mv) var(--eo)}\n@media (max-height:600px){.menu{max-height:calc(100% - var(--b) - 8px);overflow-y:auto;overscroll-behavior:contain}}\n/* The open menu was asked for: it sits over a chosen block's naming bar and an open comment box (a phone has little room). */\n/* Menu rows: one line each; the explanation is a tooltip beside the menu that eases in after a moment. */\n.mi{position:relative;display:flex;gap:10px;align-items:center;width:100%;min-height:34px;text-align:left;padding:6px 10px;border-radius:8px;transition:background-color .2s var(--eo)}\n.mi:hover,.mi:focus-visible{background:var(--so)}\n.mi svg{color:var(--mu);flex:none}\n.mi small{display:block;color:var(--fa);font-size:12px}\n.lb{flex:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n.ma{flex:none;display:grid;margin:-2px 0}\n.ma .av{width:20px;height:20px;font-size:11px}\n.mi .av svg{color:inherit}\n.sep{height:1px;margin:4px 10px;background:var(--ru)}\n/* Copy comments opens to its two choices beneath it, set in under it; folding and unfolding ease. */\n.subs{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--in) var(--eo),opacity var(--in) var(--eo)}\n.subs.open{grid-template-rows:1fr;opacity:1}\n.subs>div{min-height:0;overflow:hidden}\n.subs .mi{padding-left:30px}\n.chev{display:grid;transition:transform var(--in) var(--eo)}\n[aria-expanded=true]>.chev{transform:rotate(90deg)}\n.tt{position:absolute;right:calc(100% + 14px);top:50%;translate:0 -50%;width:max-content;max-width:220px;padding:4px 9px;border-radius:8px;font-size:12px;line-height:1.4;\ncolor:var(--mu);pointer-events:none;opacity:0;transform:translateX(3px);\ntransition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.mi:hover>.tt,.mi:focus-visible>.tt{opacity:1;transform:none;transition:opacity var(--in) var(--eo) .5s,transform var(--in) var(--eo) .5s}\n@media (max-width:600px){.tt{right:8px;top:auto;bottom:100%;translate:none;transform:translateY(3px)}}\n/* Switches: a track and a knob that slides (cross-fades with reduced motion). */\n.sw{position:relative;flex:none;width:26px;height:16px;border-radius:8px;background:var(--ln);transition:background-color var(--mv) var(--eio)}\n.sw::before,.sw::after{content:\"\";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:#fff;box-shadow:0 1px 2px rgba(0,0,0,.25);\ntransition:transform var(--mv) var(--eio),opacity var(--mv) var(--eio)}\n.sw::before{opacity:0;transform:translateX(10px)}\n[aria-checked=true]>.sw{background:var(--pu-accent)}\n[aria-checked=true]>.sw::after{transform:translateX(10px)}\n.tools{display:flex;flex:none;gap:2px}\n.tools[hidden]{display:none}\n.tool[aria-pressed=true]{background:var(--as);color:var(--pu-accent)}\n.note{display:block;padding:0 0 4px;font-size:12px;color:var(--fa)}\n.bts .here{flex:none;display:grid;place-items:center;margin-right:auto;padding:0 2px;color:var(--fa);transition:opacity var(--in) var(--eo)}.here.off{opacity:0;pointer-events:none}\n.note[hidden]{display:none}\n.ov{position:absolute;inset:0;pointer-events:none;overflow:hidden}\n/* An add-on's panel (.xp) is the same full-height panel as All comments (.all), with its own body. */\n.xb{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:4px 16px 16px;font-size:13px}\n.xb p{margin:0 0 10px}\n/* All comments: a full-height panel on the right that slides in (fades, with reduced motion). */\n.all,.xp{position:fixed;top:0;right:0;bottom:0;width:var(--pu-panel);display:flex;flex-direction:column;border-width:0 0 0 1px;opacity:0;transform:translateX(100%);\ntransition:transform var(--out) var(--ei),opacity var(--out) var(--ei)}\n.all.show,.xp.show{opacity:1;transform:none;pointer-events:auto;transition:transform var(--mv) var(--eo),opacity var(--in) var(--eo)}\n.all.full{width:100%}\n/* The header: no title, two filters (an icon and a word in a pill: grey off, a soft accent fill on) and Close. */\n.hd{display:flex;align-items:center;gap:4px;padding:8px 8px 6px}\n.sp{flex:1}\n.tg{position:relative;display:flex;align-items:center;gap:5px;height:28px;padding:0 11px 0 9px;border-radius:99px;white-space:nowrap;line-height:1;color:var(--mu);transition:background-color .2s var(--eo),color .2s var(--eo)}\n.tg:hover{background:var(--ho);color:var(--k)}\n.tg[aria-pressed=true]{background:var(--as);color:var(--pu-accent)}\n.tg[aria-pressed=true]:hover{background:color-mix(in srgb,var(--pu-accent) 20%,transparent)}\n.tg:focus-visible{outline:2px solid var(--pu-accent);outline-offset:1px}\n/* What each filter does: a tooltip under it that eases in after a moment, on hover or focus. */\n.tg::after{content:attr(data-tip);position:absolute;left:0;top:calc(100% + 4px);z-index:3;width:max-content;max-width:220px;padding:4px 9px;border:1px solid var(--ln);border-radius:8px;background:var(--su);box-shadow:var(--sh);font-size:12px;line-height:1.4;color:var(--mu);pointer-events:none;opacity:0;transform:translateY(-3px);transition:opacity var(--out) var(--ei),transform var(--out) var(--ei)}\n.tg:not([aria-pressed=true]):is(:hover,:focus-visible)::after{opacity:1;transform:none;transition:opacity var(--in) var(--eo) .4s,transform var(--in) var(--eo) .4s}\n.list{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:4px 8px 8px;background:var(--so)}\n/* Threads in the panel are the same cards; opened, a card shows the thread beneath the words it's about. */\n.cd{position:relative;margin-top:6px;border:1px solid var(--ln);border-radius:10px}\n/* What filters take away or bring back folds out or in, and a card opening or closing eases to its new height. */\n.list :is(.cd,[role=listitem],.sec){transition:all var(--mv) var(--eio)}\n.list .out{opacity:0!important;margin-top:0;padding-block:0;border-width:0;overflow:hidden}\n.cd.open{border-color:transparent;box-shadow:var(--sh)}\n.cd.done{--o:.6}\n/* Closed, its button lies over it (choosing the card opens it), beneath its actions and what they open. */\n.cd>.mi{position:absolute;inset:0;border-radius:10px;background:none}\n.cd>.thread :is(.acts,.refs,.edit){position:relative;z-index:1}\n.cd:not(.open,.new):is(:hover,:has(>.mi:focus-visible)){--bg:var(--so)}\n.cd.open>.mi{display:none}\n.cd.open .root>.hdr{cursor:pointer}\n.cd.new,.xr,.cd>.thread{padding:8px 10px 10px}\n/* The footer: who you are, Comment (its shortcut and switch), and \u22EF for the menu. */\n.pf{display:flex;align-items:center;gap:8px;min-height:52px;padding:0 8px 0 14px;border-top:1px solid var(--ln)}\n.fs{display:flex;align-items:center;gap:6px;padding:4px 6px;border-radius:8px;font-size:13px;transition:background-color .2s var(--eo)}\n.fs:hover,.fs:focus-visible{background:var(--ho)}\n.fs small{font-size:12px;color:var(--fa)}\n.pf .ib{width:30px;height:30px}\n/* The line from an open thread in the panel to its place on the page. */\n.cx{position:fixed;inset:0;width:100%;height:100%;overflow:visible;pointer-events:none;opacity:0;transition:opacity var(--in) var(--eo)}\n.cx.show{opacity:1}\n.cx path{fill:none;stroke:var(--pu-accent);stroke-width:1.5;stroke-linecap:round;opacity:.7}\n.cx circle{fill:var(--pu-accent)}\n.layer.listing .col{opacity:0;transition:opacity var(--out) var(--ei)}\n.sec{padding:8px 10px 2px;font-size:12px;color:var(--fa)}\n.fold{display:flex;align-items:center;gap:4px;width:100%;margin-top:6px;border-radius:6px;text-align:left;cursor:pointer}\n.fold:hover,.fold:focus-visible{color:var(--mu);outline:none}\n.fold svg{transition:transform var(--mv) var(--eio)}\n.fold[aria-expanded=true] svg{transform:rotate(90deg)}\n.ag{display:flex;align-items:center;gap:8px;padding:6px 14px;font-size:12px;color:var(--fa);border-bottom:1px solid var(--ln)}\n.ag[hidden]{display:none}\n.lns{display:flex;flex-direction:column}\n.ln{display:grid;grid-template-rows:0fr;opacity:0;transition:grid-template-rows var(--in) var(--eo),opacity var(--in) var(--eo)}\n.ln.show{grid-template-rows:1fr;opacity:1}\n.ln>.lw{overflow:hidden;min-height:0}\n.ag button{margin-left:auto;flex:none;padding:0 6px;border-radius:6px;color:var(--fa);transition:color .2s var(--eo),background-color .2s var(--eo)}.ag button:hover:not(:disabled),.ag button:focus-visible{color:var(--pu-accent);background:var(--as)}.ag button:disabled{opacity:.5;cursor:default}\n.adr{margin:8px 0 0 14px;padding:8px 10px;border-radius:8px;background:var(--so);font-size:13px;color:var(--k);animation:pu-in var(--mv) var(--eo) both}\n.adr small{display:block;font-size:12px;color:var(--fa)}\n.adr .go{display:flex;gap:6px;margin-top:6px}\n.adr .go button{padding:2px 8px;border-radius:6px;font-size:12px;color:var(--mu);transition:color .2s var(--eo),background-color .2s var(--eo)}.adr .go button:hover,.adr .go button:focus-visible{color:var(--pu-accent);background:var(--as)}\n@keyframes pu-in{0%{opacity:0;transform:translateY(3px)}100%{opacity:1;transform:none}}\n.sec b{margin-left:6px;font-weight:500;color:var(--pu-accent)}\n/* Messages sit beside the comment control, never over the page's own bottom bar; beside the panel while it is open,\nand above its footer when it fills the screen. */\n.toast{--r:40px;position:fixed;right:var(--r);bottom:calc(4px + var(--pu-lift));max-width:min(320px,calc(100vw - var(--r) - 16px));border-radius:10px;padding:8px 14px;font-size:13px;opacity:0;transform:translateY(6px);transition:.3s var(--eo)}\n.toast.show{opacity:1;transform:none}\n/* While All comments is open, messages sit inside the panel above its footer, on top of it: never over the page,\nnor a deck's own controls in the space beside it. */\n.layer:is(.listing,:has(.xp.show)) .toast{--r:12px;bottom:64px;max-width:min(320px,calc(var(--pu-panel) - 24px));z-index:2}\n/* While the menu is open, beside it (above it on a narrow screen), never under it. */\n.layer:not(.listing):has(.menu.show) .toast{--r:314px}\n@media (max-width:640px){.layer:not(.listing):has(.menu.show) .toast{--r:12px;bottom:auto;top:12px}}\n\n/* Touch: everything a finger presses is at least 44 px across. A bubble keeps its small look round a larger hit area.\nThe control is 44 px, so the menu and messages sit further from it. Shortcuts aren't shown where there is no keyboard. */\n@media (pointer:coarse){\n.mi,.ib,.rxo,.refs .ref,.mnl li,.bts button,.tg,.fs,.pm button,.ta,.nb,.selbar button,button.me,.say button,.line,.seen button,.ag button,.go button{min-height:44px}\n.ib,.rxo,.refs .ref,.nb,.selbar button,button.me,.ta{min-width:44px}\n.bub::before{content:\"\";position:absolute;inset:-14px;border-radius:50%}\n.row:focus-within .bts,.row.has .bts{max-height:52px}\n.hd{padding:4px 8px}\n.toast{--r:52px;display:flex;align-items:center;min-height:44px;padding:4px 14px;line-height:1.25}\n.ta{display:grid;place-items:center;margin:-5px -10px -5px 6px;padding:0 10px}\n.launch .mode{width:44px;height:44px}\n.menu{--b:calc(52px + var(--pu-lift))}\n}\n@media (pointer:coarse) and (hover:none){.ks{display:none}}\n\n/* Keyboard focus is always plainly marked: a 2 px ring in the accent, which reads at 4.5 to 1 on Pipeup's surfaces. */\n.layer :is(button,a):not(.mi,.opn,.mode,.tg):focus-visible{outline:2px solid var(--pu-accent);outline-offset:1px}\n.layer .mi:focus-visible{outline:2px solid var(--pu-accent);outline-offset:-2px}\n/* A look that differs from the page's keeps its see-through parts solid: a dimmed card dims its words, not the card. */\n.flip .launch{background:var(--su)}\n.flip :is(.th,.cd){opacity:1}\n.flip :is(.th,.cd)>*{opacity:var(--o,1);transition:opacity var(--in) var(--eo)}\n\n@media (forced-colors: active){\n.pick.kf{border:3px solid Highlight}\n.layer :is(.ib,.mi,.nb,.tg,.fs,.selbar button,.say button,.xb button):focus-visible{outline:2px solid Highlight}\n/* Switches, the filters, your own reactions and comment mode draw in system colours, so every state shows. */\n.sw{forced-color-adjust:none;border:1px solid CanvasText;background:Canvas}\n.sw::before,.sw::after{top:1px;left:1px;background:CanvasText}\n[aria-checked=true]>.sw{background:Highlight;border-color:Highlight}\n[aria-checked=true]>.sw::before,[aria-checked=true]>.sw::after{background:HighlightText}\n.layer :is(.tg,.rxo,.rx)[aria-pressed=true],.launch:has(.mode.on){forced-color-adjust:none;background:Highlight;color:HighlightText;border-color:Highlight}\n}\n\n@media (prefers-reduced-motion: reduce){\n.ln,.subs{transition-property:opacity}\n@keyframes pu-in{0%{opacity:0}100%{opacity:1}}\n@keyframes cm-out{99%{opacity:0;max-height:20px;padding-top:4px}100%{opacity:0;max-height:0;padding-top:0}}\n.bub,.bub.in,.bub.in:hover,.bub.on,.tip,.pop,.selbar,.menu,.all,.xp,.tt,.toast,.ref .rc,.rxp,.mnl{transform:none!important}\n.ttip{transition-property:opacity!important}\n.dlg{transform:translate(-50%,0)!important}\n.sw::after{transform:none!important;transition-property:opacity}\n[aria-checked=true]>.sw::after{opacity:0}\n[aria-checked=true]>.sw::before{opacity:1}\n.th,.th.long .root>.tx,.mr,.list :is(.cd,[role=listitem],.sec){transition-property:opacity,box-shadow}\n.tip.show,.toast{transition-property:opacity}\n.pick,.pick.show{transition:opacity .16s ease-in-out}\n.all,.all.show,.xp,.xp.show{transition-property:opacity}\n.namebar,.namebar.show{transform:none!important;transition:opacity .16s ease-in-out}\n.av,.nf,.launch{transform:none!important}\n@keyframes pu-pulse{0%{opacity:0}35%{opacity:.85}100%{opacity:0}}\n}\n";
|
|
@@ -20,6 +20,8 @@ export interface ThreadActions {
|
|
|
20
20
|
/** "Still not right" on an addressed thread: reopens it and puts the cursor in its reply line. */
|
|
21
21
|
notRight(threadId: string): Promise<void>;
|
|
22
22
|
copy(threadId: string): void;
|
|
23
|
+
/** Answers where a re-found or lost thread belongs: here, somewhere to pick, or gone (§8). */
|
|
24
|
+
place?(threadId: string, how: "confirmed" | "pick" | "gone"): void;
|
|
23
25
|
/** Cancel or Esc threw away the words in thread `threadId`'s reply line: offers them back. */
|
|
24
26
|
drop(threadId: string, words: string): void;
|
|
25
27
|
/** Esc in an empty reply line: closes the thread. */
|
|
@@ -40,6 +42,8 @@ export interface ThreadViewOptions {
|
|
|
40
42
|
place?: string | null;
|
|
41
43
|
/** Its content was found changed or nearby: flagged Moved, with what it was about, for someone to confirm. */
|
|
42
44
|
moved?: boolean;
|
|
45
|
+
/** Found by looking alike: it asks whether this is the right place. */
|
|
46
|
+
alike?: boolean;
|
|
43
47
|
/** A card's line about what it's on, as written (in place of the quote). */
|
|
44
48
|
note?: string | null;
|
|
45
49
|
/** A card's quiet last line (its replies and reactions), as written. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pipeup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Comments and feedback right on any HTML page: documents, slide decks and whole sites.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"comments",
|
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
"lint": "eslint src test e2e && prettier --check src test e2e",
|
|
59
59
|
"format": "prettier --write src test e2e",
|
|
60
60
|
"check": "tsc --noEmit && tsc -p tsconfig.e2e.json --noEmit && npm run lint && vitest run && npm run build && node scripts/size.mjs && npm run e2e",
|
|
61
|
-
"e2e": "playwright test"
|
|
61
|
+
"e2e": "playwright test",
|
|
62
|
+
"e2e:safari": "WEBKIT=all playwright test --project=webkit"
|
|
62
63
|
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@playwright/test": "^1.55.0",
|
|
@@ -5,7 +5,7 @@ description: Use when someone asks you to add comments to an HTML page, make it
|
|
|
5
5
|
|
|
6
6
|
# Add Pipeup to this page
|
|
7
7
|
|
|
8
|
-
Written for Pipeup 0.
|
|
8
|
+
Written for Pipeup 1.0.0 This skill is published at https://pipeup-ai.github.io/pipeup/skills/pipeup-add/SKILL.md. Its companions: [integrate](https://pipeup-ai.github.io/pipeup/skills/pipeup-integrate/SKILL.md) (the full reference: layouts, decks, tabs, every option), [apply](https://pipeup-ai.github.io/pipeup/skills/pipeup-apply/SKILL.md) (answering the comments) and [summarise](https://pipeup-ai.github.io/pipeup/skills/pipeup-summarise/SKILL.md).
|
|
9
9
|
|
|
10
10
|
Pipeup lets people comment on an HTML page in place. **When the page already exists, never change its layout, styling or behaviour to make room for Pipeup**: it draws in its own layer, and you only add attributes and one script tag. Edit the HTML file only; don't download, fetch or run anything yourself.
|
|
11
11
|
|
|
@@ -14,7 +14,7 @@ Pipeup lets people comment on an HTML page in place. **When the page already exi
|
|
|
14
14
|
1. **Pin the script.** Just before `</body>`:
|
|
15
15
|
|
|
16
16
|
```html
|
|
17
|
-
<script src="https://cdn.jsdelivr.net/npm/pipeup@0.
|
|
17
|
+
<script src="https://cdn.jsdelivr.net/npm/pipeup@1.0.0/dist/pipeup.min.js"
|
|
18
18
|
integrity="sha384-…" crossorigin="anonymous"></script>
|
|
19
19
|
```
|
|
20
20
|
|