@mrsf/marp-mrsf 0.4.8

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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts", "../../shared/src/comments.ts", "../src/rules/core.ts", "../src/rules/renderer.ts", "../src/shared.ts"],
4
+ "sourcesContent": ["import { readFileSync } from \"node:fs\";\nimport path from \"node:path\";\nimport { parseSidecarContent } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions } from \"./types.js\";\nimport { createMarpPlugin } from \"./shared.js\";\nimport type { MrsfMarpPlugin } from \"./shared.js\";\n\nexport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\nexport type { MrsfMarpPlugin, MarpitLike, MarpitPluginContext } from \"./shared.js\";\n\nexport const mrsfPlugin: MrsfMarpPlugin = createMarpPlugin((options: MrsfPluginOptions, env?: unknown) => {\n if (options.comments) {\n return options.comments;\n }\n\n if (options.loader) {\n try {\n return options.loader(options, env);\n } catch {\n return null;\n }\n }\n\n if (options.sidecarPath) {\n try {\n const abs = path.resolve(options.cwd || process.cwd(), options.sidecarPath);\n const content = readFileSync(abs, \"utf-8\");\n return parseSidecarContent(content, abs);\n } catch {\n return null;\n }\n }\n\n if (options.documentPath) {\n try {\n const cwd = options.cwd || process.cwd();\n const abs = path.resolve(cwd, options.documentPath);\n const yamlPath = abs + \".review.yaml\";\n const jsonPath = abs + \".review.json\";\n\n let raw: string | null = null;\n let hint: string | null = null;\n\n try {\n raw = readFileSync(yamlPath, \"utf-8\");\n hint = yamlPath;\n } catch {\n try {\n raw = readFileSync(jsonPath, \"utf-8\");\n hint = jsonPath;\n } catch {\n return null;\n }\n }\n\n if (!raw) {\n return null;\n }\n\n return parseSidecarContent(raw, hint!);\n } catch {\n return null;\n }\n }\n\n return null;\n});\n\nexport default mrsfPlugin;", "/**\n * Shared comment loading and grouping logic for MRSF rendering plugins.\n */\n\nimport type { MrsfDocument } from \"@mrsf/cli\";\nimport type { MrsfPluginOptions, SlimComment, CommentThread, LineMap, CommentLoader } from \"./types.js\";\n\nexport type { CommentLoader };\n\n/**\n * Convert an MrsfDocument into a slim comment array.\n */\nexport function toSlimComments(doc: MrsfDocument): SlimComment[] {\n return doc.comments.map((c) => ({\n id: c.id,\n author: c.author || \"Unknown\",\n text: c.text || \"\",\n line: c.line ?? null,\n x_page: typeof c.x_page === \"number\" ? c.x_page : null,\n end_line: c.end_line ?? null,\n start_column: c.start_column ?? null,\n end_column: c.end_column ?? null,\n selected_text: c.selected_text || null,\n resolved: !!c.resolved,\n reply_to: c.reply_to || null,\n severity: c.severity || null,\n type: c.type || null,\n timestamp: c.timestamp || null,\n }));\n}\n\n/**\n * Group comments by line number, threading replies under parents.\n */\nexport function groupByLine(comments: SlimComment[]): LineMap {\n const rootComments = comments.filter((c) => !c.reply_to && c.line != null);\n const replies = comments.filter((c) => c.reply_to);\n\n const replyMap = new Map<string, SlimComment[]>();\n for (const r of replies) {\n const list = replyMap.get(r.reply_to!) || [];\n list.push(r);\n replyMap.set(r.reply_to!, list);\n }\n\n const lineMap: LineMap = new Map();\n for (const c of rootComments) {\n const line = c.line!;\n const threads = lineMap.get(line) || [];\n threads.push({\n comment: c,\n replies: replyMap.get(c.id) || [],\n });\n lineMap.set(line, threads);\n }\n\n return lineMap;\n}\n\n/**\n * Resolve options into a filtered LineMap ready for rendering.\n * Returns null if there are no comments to render.\n */\nexport function resolveComments(\n loader: CommentLoader,\n options: MrsfPluginOptions,\n env?: unknown,\n): { lineMap: LineMap; comments: SlimComment[] } | null {\n const showResolved = options.showResolved ?? true;\n\n const doc = loader(options, env);\n if (!doc || !doc.comments || doc.comments.length === 0) {\n return null;\n }\n\n let comments = toSlimComments(doc);\n if (!showResolved) {\n comments = comments.filter((c) => !c.resolved);\n }\n\n if (comments.length === 0) return null;\n\n const lineMap = groupByLine(comments);\n return { lineMap, comments };\n}\n", "import type Token from \"markdown-it/lib/token.mjs\";\nimport type StateCore from \"markdown-it/lib/rules_core/state_core.mjs\";\nimport type { LineMap, CommentThread, SlimComment } from \"../types.js\";\n\nexport interface CoreRuleOptions {\n lineHighlight?: boolean;\n}\n\nexport interface ResolvedCommentData {\n lineMap: LineMap;\n comments: SlimComment[];\n}\n\nfunction buildPageLineMap(tokens: Token[]): Map<number, number> {\n const pageLineMap = new Map<number, number>();\n let pageNumber = 0;\n\n for (const token of tokens) {\n if (token.type !== \"marpit_slide_open\" || !token.map) {\n continue;\n }\n\n pageNumber += 1;\n pageLineMap.set(pageNumber, token.map[0] + 1);\n }\n\n return pageLineMap;\n}\n\nfunction mergePageScopedThreads(\n lineMap: LineMap,\n comments: SlimComment[],\n pageLineMap: Map<number, number>,\n): LineMap {\n const merged: LineMap = new Map(\n Array.from(lineMap.entries(), ([line, threads]) => [\n line,\n threads.map((thread) => ({\n comment: thread.comment,\n replies: [...thread.replies],\n })),\n ]),\n );\n const replyMap = new Map<string, SlimComment[]>();\n\n for (const comment of comments) {\n if (!comment.reply_to) {\n continue;\n }\n\n const replies = replyMap.get(comment.reply_to) || [];\n replies.push(comment);\n replyMap.set(comment.reply_to, replies);\n }\n\n for (const comment of comments) {\n if (comment.reply_to || comment.line != null || comment.x_page == null) {\n continue;\n }\n\n const displayLine = pageLineMap.get(comment.x_page);\n if (displayLine == null) {\n continue;\n }\n\n const threadComment: SlimComment = {\n ...comment,\n line: displayLine,\n };\n const threadReplies = (replyMap.get(comment.id) || []).map((reply) => ({\n ...reply,\n line: reply.line ?? displayLine,\n }));\n const threads = merged.get(displayLine) || [];\n\n threads.push({\n comment: threadComment,\n replies: threadReplies,\n });\n merged.set(displayLine, threads);\n }\n\n return merged;\n}\n\nexport function installCoreRule(\n md: { core: { ruler: { push: (name: string, fn: (state: StateCore) => void) => void } } },\n resolveComments: (state: StateCore) => ResolvedCommentData | null,\n options: CoreRuleOptions = {},\n): void {\n md.core.ruler.push(\"mrsf_inject\", (state: StateCore) => {\n const result = resolveComments(state);\n if (!result) return;\n\n const pageLineMap = buildPageLineMap(state.tokens);\n const lineMap = mergePageScopedThreads(result.lineMap, result.comments, pageLineMap);\n const tokens = state.tokens;\n const TokenCtor = state.Token;\n const processed = new Set<number>();\n\n for (let i = tokens.length - 1; i >= 0; i--) {\n const token = tokens[i];\n const map = token.map;\n if (!map) continue;\n\n if (token.type === \"inline\") continue;\n\n const startLine1 = map[0] + 1;\n const endLine1 = map[1];\n token.attrSet(\"data-mrsf-line\", String(startLine1));\n token.attrSet(\"data-mrsf-start-line\", String(startLine1));\n token.attrSet(\"data-mrsf-end-line\", String(endLine1));\n\n for (let line0 = map[0]; line0 < map[1]; line0++) {\n const line = line0 + 1;\n if (processed.has(line)) continue;\n processed.add(line);\n\n const threads = lineMap.get(line);\n if (threads && threads.length > 0) {\n if (options.lineHighlight) {\n const existingClass = token.attrGet(\"class\") || \"\";\n if (!existingClass.includes(\"mrsf-line-highlight\")) {\n token.attrSet(\n \"class\",\n existingClass ? `${existingClass} mrsf-line-highlight` : \"mrsf-line-highlight\",\n );\n }\n }\n token.attrSet(\"data-mrsf-line\", String(line));\n }\n }\n }\n\n const allThreads: CommentThread[] = [];\n for (const threads of lineMap.values()) {\n allThreads.push(...threads);\n }\n if (allThreads.length > 0) {\n const scriptToken = new TokenCtor(\"mrsf_data_script\", \"\", 0);\n scriptToken.meta = { threads: allThreads };\n tokens.push(scriptToken);\n }\n });\n}", "import type { CommentThread } from \"../types.js\";\n\nexport interface RendererRuleOptions {\n dataContainer?: \"script\" | \"element\";\n dataElementId?: string;\n}\n\nfunction escapeAttribute(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nexport function installRendererRules(\n md: { renderer: { rules: Record<string, ((...args: any[]) => string) | undefined> } },\n options: RendererRuleOptions = {},\n): void {\n md.renderer.rules[\"mrsf_data_script\"] = (\n tokens: { meta: { threads: CommentThread[] } }[],\n idx: number,\n ): string => {\n const { threads } = tokens[idx].meta;\n const payload = JSON.stringify({ threads });\n if (options.dataContainer === \"element\") {\n const elementId = options.dataElementId || \"mrsf-comment-data\";\n return `<div id=\"${escapeAttribute(elementId)}\" data-mrsf-json=\"${escapeAttribute(payload)}\" aria-hidden=\"true\"></div>`;\n }\n\n const data = payload.replace(/</g, \"\\\\u003c\");\n return `<script type=\"application/mrsf+json\">${data}</script>`;\n };\n}", "import type MarkdownIt from \"markdown-it\";\nimport { resolveComments } from \"@mrsf/plugin-shared\";\nimport type { CommentLoader, MrsfPluginOptions } from \"./types.js\";\nimport { installCoreRule } from \"./rules/core.js\";\nimport { installRendererRules } from \"./rules/renderer.js\";\n\nexport interface MarpitRenderResult {\n html: string | string[];\n css: string;\n comments?: string[][];\n}\n\nexport interface MarpitLike {\n render(markdown: string, env?: Record<string, unknown>): MarpitRenderResult;\n use(plugin: (context: unknown, ...params: unknown[]) => unknown, ...params: unknown[]): unknown;\n markdown?: MarkdownIt;\n}\n\nexport interface MarpitPluginContext {\n marpit?: MarpitLike;\n markdown?: MarkdownIt;\n}\n\nexport type MrsfMarpPlugin = (\n context: MarpitPluginContext,\n options?: MrsfPluginOptions,\n) => void;\n\nconst patchedRenderers = new WeakSet<MarpitLike>();\n\nexport function installMarkdownMrsfPlugin(\n md: MarkdownIt,\n options: MrsfPluginOptions,\n loader: CommentLoader,\n): void {\n installCoreRule(\n md,\n (state) => resolveComments(loader, options, state.env),\n { lineHighlight: options.lineHighlight ?? false },\n );\n installRendererRules(md, {\n dataContainer: options.dataContainer,\n dataElementId: options.dataElementId,\n });\n}\n\nexport function createMarpPlugin(loader: CommentLoader): MrsfMarpPlugin {\n return function mrsfPlugin(\n context: MarpitPluginContext,\n options: MrsfPluginOptions = {},\n ): void {\n const markdown = context.markdown ?? context.marpit?.markdown;\n if (markdown) {\n installMarkdownMrsfPlugin(markdown, options, loader);\n }\n\n if (!context.marpit) {\n return;\n }\n\n patchRender(context.marpit, options);\n };\n}\n\nfunction patchRender(marpit: MarpitLike, options: MrsfPluginOptions): void {\n if (patchedRenderers.has(marpit)) {\n return;\n }\n\n const originalRender = marpit.render.bind(marpit);\n marpit.render = (markdown: string, env?: Record<string, unknown>): MarpitRenderResult => {\n const result = originalRender(markdown, env);\n const fallbackHtml = Array.isArray(result.html)\n ? originalRender(markdown, { ...(env ?? {}), htmlAsArray: false }).html\n : null;\n\n return {\n ...result,\n html: normalizeRenderedHtml(result.html, options, typeof fallbackHtml === \"string\" ? fallbackHtml : null),\n };\n };\n\n patchedRenderers.add(marpit);\n}\n\nconst SCRIPT_RE = /<script type=\"application\\/mrsf\\+json\">[\\s\\S]*?<\\/script>/g;\n\nfunction normalizeRenderedHtml(\n html: string | string[],\n options: MrsfPluginOptions,\n fallbackHtml: string | null = null,\n): string | string[] {\n if (Array.isArray(html)) {\n let payload: string | null = null;\n\n const pages = html.map((pageHtml, index) => {\n const extracted = extractPayloads(pageHtml);\n if (extracted.payloads.length > 0) {\n payload = extracted.payloads[extracted.payloads.length - 1];\n }\n\n return annotatePageFragment(extracted.html, index + 1, options);\n });\n\n if (!payload && fallbackHtml) {\n const fallback = extractPayloads(fallbackHtml);\n payload = fallback.payloads[fallback.payloads.length - 1] ?? null;\n }\n\n if (payload && pages.length > 0) {\n pages[pages.length - 1] += payload;\n }\n\n return pages;\n }\n\n const extracted = extractPayloads(html);\n const annotated = annotateSectionTags(extracted.html, options);\n\n if (extracted.payloads.length === 0) {\n return annotated;\n }\n\n const payload = extracted.payloads[extracted.payloads.length - 1];\n return insertPayloadIntoContainer(annotated, payload);\n}\n\nfunction extractPayloads(html: string): { html: string; payloads: string[] } {\n const payloads = html.match(SCRIPT_RE) ?? [];\n return {\n html: html.replace(SCRIPT_RE, \"\"),\n payloads,\n };\n}\n\nfunction annotatePageFragment(\n html: string,\n pageNumber: number,\n options: MrsfPluginOptions,\n): string {\n const attrName = resolvePageAttributeName(options);\n return html.replace(/<([a-zA-Z][\\w:-]*)(\\s|>)/, `<$1 ${attrName}=\"${pageNumber}\"$2`);\n}\n\nfunction annotateSectionTags(html: string, options: MrsfPluginOptions): string {\n if (html.includes(\"<svg\") && html.includes(\"data-marpit-svg\")) {\n return annotateSvgPageTags(html, options);\n }\n\n const attrName = resolvePageAttributeName(options);\n let pageNumber = 0;\n\n return html.replace(/<section(\\s|>)/g, (_match, suffix: string) => {\n pageNumber += 1;\n return `<section ${attrName}=\"${pageNumber}\"${suffix}`;\n });\n}\n\nfunction annotateSvgPageTags(html: string, options: MrsfPluginOptions): string {\n const attrName = resolvePageAttributeName(options);\n let pageNumber = 0;\n\n return html.replace(/<svg\\b([^>]*)>/g, (_match, attrs: string) => {\n if (!attrs.includes(\"data-marpit-svg\")) {\n return `<svg${attrs}>`;\n }\n\n pageNumber += 1;\n return `<svg${attrs} ${attrName}=\"${pageNumber}\">`;\n });\n}\n\nfunction resolvePageAttributeName(_options: MrsfPluginOptions): string {\n return \"data-mrsf-page\";\n}\n\nfunction insertPayloadIntoContainer(html: string, payload: string): string {\n const closingTagIndex = html.lastIndexOf(\"</\");\n if (closingTagIndex === -1) {\n return html + payload;\n }\n\n return html.slice(0, closingTagIndex) + payload + html.slice(closingTagIndex);\n}"],
5
+ "mappings": "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",
6
+ "names": ["resolveComments", "mrsfPlugin", "payload", "extracted"]
7
+ }
@@ -0,0 +1,17 @@
1
+ import type StateCore from "markdown-it/lib/rules_core/state_core.mjs";
2
+ import type { LineMap, SlimComment } from "../types.js";
3
+ export interface CoreRuleOptions {
4
+ lineHighlight?: boolean;
5
+ }
6
+ export interface ResolvedCommentData {
7
+ lineMap: LineMap;
8
+ comments: SlimComment[];
9
+ }
10
+ export declare function installCoreRule(md: {
11
+ core: {
12
+ ruler: {
13
+ push: (name: string, fn: (state: StateCore) => void) => void;
14
+ };
15
+ };
16
+ }, resolveComments: (state: StateCore) => ResolvedCommentData | null, options?: CoreRuleOptions): void;
17
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1,10 @@
1
+ export interface RendererRuleOptions {
2
+ dataContainer?: "script" | "element";
3
+ dataElementId?: string;
4
+ }
5
+ export declare function installRendererRules(md: {
6
+ renderer: {
7
+ rules: Record<string, ((...args: any[]) => string) | undefined>;
8
+ };
9
+ }, options?: RendererRuleOptions): void;
10
+ //# sourceMappingURL=renderer.d.ts.map
@@ -0,0 +1,20 @@
1
+ import type MarkdownIt from "markdown-it";
2
+ import type { CommentLoader, MrsfPluginOptions } from "./types.js";
3
+ export interface MarpitRenderResult {
4
+ html: string | string[];
5
+ css: string;
6
+ comments?: string[][];
7
+ }
8
+ export interface MarpitLike {
9
+ render(markdown: string, env?: Record<string, unknown>): MarpitRenderResult;
10
+ use(plugin: (context: unknown, ...params: unknown[]) => unknown, ...params: unknown[]): unknown;
11
+ markdown?: MarkdownIt;
12
+ }
13
+ export interface MarpitPluginContext {
14
+ marpit?: MarpitLike;
15
+ markdown?: MarkdownIt;
16
+ }
17
+ export type MrsfMarpPlugin = (context: MarpitPluginContext, options?: MrsfPluginOptions) => void;
18
+ export declare function installMarkdownMrsfPlugin(md: MarkdownIt, options: MrsfPluginOptions, loader: CommentLoader): void;
19
+ export declare function createMarpPlugin(loader: CommentLoader): MrsfMarpPlugin;
20
+ //# sourceMappingURL=shared.d.ts.map
package/dist/style.css ADDED
@@ -0,0 +1,522 @@
1
+ /*
2
+ * Sidemark — MRSF plugin default styles (overlay gutter architecture).
3
+ *
4
+ * Include via: import "@mrsf/plugin-shared/style.css"
5
+ * or <link rel="stylesheet" href=".../@mrsf/plugin-shared/dist/style.css">
6
+ *
7
+ * Customise with CSS custom properties (--mrsf-*).
8
+ *
9
+ * Layout: The controller wraps the rendered markdown in an overlay root
10
+ * with position:relative, and creates absolute-positioned gutter columns
11
+ * on the left and/or right. The plugins only add data-mrsf-line
12
+ * attributes — no visual DOM injection.
13
+ */
14
+
15
+ /* ── Custom property defaults ────────────────────────────── */
16
+
17
+ :root {
18
+ --mrsf-accent: #3794ff;
19
+ --mrsf-badge-bg: #007acc;
20
+ --mrsf-badge-fg: #fff;
21
+ --mrsf-badge-resolved-bg: #388a34;
22
+ --mrsf-add-bg: rgba(0, 122, 204, 0.12);
23
+ --mrsf-add-fg: #007acc;
24
+ --mrsf-add-border: rgba(0, 122, 204, 0.28);
25
+ --mrsf-tooltip-bg: #252526;
26
+ --mrsf-tooltip-fg: #cccccc;
27
+ --mrsf-tooltip-border: #454545;
28
+ --mrsf-highlight-bg: rgba(255, 213, 79, 0.25);
29
+ --mrsf-highlight-border: rgba(255, 213, 79, 0.7);
30
+ --mrsf-severity-high: #e74c3c;
31
+ --mrsf-severity-medium: #f39c12;
32
+ --mrsf-severity-low: #3498db;
33
+ --mrsf-gutter-width: 36px;
34
+ --mrsf-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
35
+ }
36
+
37
+ @media (prefers-color-scheme: light) {
38
+ :root {
39
+ --mrsf-tooltip-bg: #f3f3f3;
40
+ --mrsf-tooltip-fg: #333333;
41
+ --mrsf-tooltip-border: #cccccc;
42
+ }
43
+ }
44
+
45
+ /* ── Overlay root (created by controller) ────────────────── */
46
+
47
+ .mrsf-overlay-root {
48
+ position: relative;
49
+ }
50
+
51
+ .mrsf-gutter {
52
+ position: absolute;
53
+ top: 0;
54
+ bottom: 0;
55
+ width: var(--mrsf-gutter-width);
56
+ pointer-events: none;
57
+ z-index: 10;
58
+ }
59
+
60
+ .mrsf-gutter.is-hidden,
61
+ .mrsf-line-highlight-layer.is-hidden {
62
+ display: none;
63
+ }
64
+
65
+ .mrsf-gutter-left {
66
+ left: calc(-1 * var(--mrsf-gutter-width) - 8px);
67
+ }
68
+
69
+ .mrsf-gutter-right {
70
+ right: calc(-1 * var(--mrsf-gutter-width) - 8px);
71
+ }
72
+
73
+ .mrsf-line-highlight-layer {
74
+ position: absolute;
75
+ inset: 0;
76
+ pointer-events: none;
77
+ z-index: 1;
78
+ }
79
+
80
+ .mrsf-line-highlight-overlay {
81
+ position: absolute;
82
+ left: 0;
83
+ right: 0;
84
+ border-left: 3px solid var(--mrsf-accent);
85
+ background: rgba(55, 148, 255, 0.06);
86
+ border-radius: 2px;
87
+ transition: background 0.15s ease;
88
+ }
89
+
90
+ .mrsf-line-highlight-overlay:hover {
91
+ background: rgba(55, 148, 255, 0.12);
92
+ }
93
+
94
+ /* ── Line highlight (CSS-only tinting for commented lines) ── */
95
+
96
+ .mrsf-line-highlight {
97
+ border-left: 3px solid var(--mrsf-accent);
98
+ padding-left: 8px;
99
+ background: rgba(55, 148, 255, 0.06);
100
+ border-radius: 2px;
101
+ transition: background 0.15s ease;
102
+ }
103
+
104
+ .mrsf-line-highlight:hover {
105
+ background: rgba(55, 148, 255, 0.12);
106
+ }
107
+
108
+ /* ── Gutter items (badges & add buttons, positioned by JS) ── */
109
+
110
+ .mrsf-gutter-item {
111
+ position: absolute;
112
+ left: 0;
113
+ right: 0;
114
+ display: flex;
115
+ align-items: center;
116
+ justify-content: center;
117
+ pointer-events: auto;
118
+ }
119
+
120
+ /* ── Badge ──────────────────────────────────────────────── */
121
+
122
+ .mrsf-badge,
123
+ .mrsf-gutter-add {
124
+ display: inline-flex;
125
+ align-items: center;
126
+ justify-content: center;
127
+ gap: 2px;
128
+ font-size: 11px;
129
+ font-weight: 600;
130
+ line-height: 1;
131
+ min-height: 20px;
132
+ padding: 3px 8px;
133
+ border-radius: 999px;
134
+ user-select: none;
135
+ white-space: nowrap;
136
+ transition: opacity 0.15s ease, background 0.15s ease, border-color 0.15s ease;
137
+ }
138
+
139
+ .mrsf-badge {
140
+ cursor: pointer;
141
+ color: var(--mrsf-badge-fg);
142
+ background: var(--mrsf-badge-bg);
143
+ border: 1px solid transparent;
144
+ border-left: 3px solid transparent;
145
+ }
146
+
147
+ .mrsf-badge-resolved {
148
+ background: var(--mrsf-badge-resolved-bg);
149
+ opacity: 0.7;
150
+ }
151
+
152
+ .mrsf-badge-severity-high {
153
+ border-left-color: var(--mrsf-severity-high);
154
+ }
155
+
156
+ .mrsf-badge-severity-medium {
157
+ border-left-color: var(--mrsf-severity-medium);
158
+ }
159
+
160
+ /* ── Add button (in gutter) ─────────────────────────────── */
161
+
162
+ .mrsf-gutter-add {
163
+ appearance: none;
164
+ opacity: 0.38;
165
+ pointer-events: auto;
166
+ border: 1px solid var(--mrsf-add-border);
167
+ background: var(--mrsf-add-bg);
168
+ color: var(--mrsf-add-fg);
169
+ cursor: pointer;
170
+ font-family: var(--mrsf-font-family);
171
+ }
172
+
173
+ @media (prefers-color-scheme: light) {
174
+ :root {
175
+ --mrsf-add-bg: rgba(0, 122, 204, 0.08);
176
+ --mrsf-add-border: rgba(0, 122, 204, 0.18);
177
+ }
178
+ }
179
+
180
+ .mrsf-gutter-add:hover,
181
+ .mrsf-gutter-add:focus-visible {
182
+ opacity: 1;
183
+ background: rgba(0, 122, 204, 0.18);
184
+ border-color: rgba(0, 122, 204, 0.38);
185
+ }
186
+
187
+ .mrsf-gutter-add:focus-visible,
188
+ .mrsf-badge:focus-visible {
189
+ outline: 2px solid var(--mrsf-accent);
190
+ outline-offset: 2px;
191
+ }
192
+
193
+ /* ── Tooltip (positioned by JS, appended to gutter item) ── */
194
+
195
+ .mrsf-tooltip {
196
+ display: none;
197
+ position: absolute;
198
+ top: 100%;
199
+ z-index: 1000;
200
+ min-width: 300px;
201
+ max-width: 480px;
202
+ max-height: 400px;
203
+ overflow-y: auto;
204
+ padding: 8px;
205
+ margin-top: 4px;
206
+ border-radius: 6px;
207
+
208
+ background: var(--mrsf-tooltip-bg);
209
+ border: 1px solid var(--mrsf-tooltip-border);
210
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
211
+
212
+ font-family: var(--mrsf-font-family);
213
+ font-size: 13px;
214
+ line-height: 1.5;
215
+ color: var(--mrsf-tooltip-fg);
216
+ }
217
+
218
+ /* Left gutter: tooltip opens to the right */
219
+ .mrsf-gutter-left .mrsf-tooltip {
220
+ left: 0;
221
+ right: auto;
222
+ }
223
+
224
+ /* Right gutter: tooltip opens to the left */
225
+ .mrsf-gutter-right .mrsf-tooltip {
226
+ left: auto;
227
+ right: 0;
228
+ }
229
+
230
+ .mrsf-tooltip.mrsf-tooltip-visible {
231
+ display: block;
232
+ animation: mrsf-fade-in 0.15s ease;
233
+ }
234
+
235
+ @keyframes mrsf-fade-in {
236
+ from { opacity: 0; transform: translateY(-4px); }
237
+ to { opacity: 1; transform: translateY(0); }
238
+ }
239
+
240
+ /* ── Thread / Comment ───────────────────────────────────── */
241
+
242
+ .mrsf-thread {
243
+ padding: 4px 0;
244
+ }
245
+
246
+ .mrsf-thread + .mrsf-thread {
247
+ border-top: 1px solid var(--mrsf-tooltip-border);
248
+ margin-top: 4px;
249
+ padding-top: 8px;
250
+ }
251
+
252
+ .mrsf-comment {
253
+ padding: 4px 0;
254
+ }
255
+
256
+ .mrsf-comment.mrsf-resolved {
257
+ border-left: 2px solid var(--mrsf-badge-resolved-bg);
258
+ padding-left: 6px;
259
+ }
260
+
261
+ /* ── Replies ────────────────────────────────────────────── */
262
+
263
+ .mrsf-replies {
264
+ margin-left: 16px;
265
+ padding-left: 8px;
266
+ border-left: 2px solid var(--mrsf-tooltip-border);
267
+ }
268
+
269
+ .mrsf-reply {
270
+ font-size: 12px;
271
+ }
272
+
273
+ /* ── Comment header ─────────────────────────────────────── */
274
+
275
+ .mrsf-comment-header {
276
+ display: flex;
277
+ align-items: center;
278
+ gap: 6px;
279
+ flex-wrap: wrap;
280
+ margin-bottom: 2px;
281
+ }
282
+
283
+ .mrsf-author {
284
+ font-weight: 600;
285
+ }
286
+
287
+ .mrsf-date {
288
+ font-size: 11px;
289
+ opacity: 0.6;
290
+ }
291
+
292
+ .mrsf-severity {
293
+ font-size: 10px;
294
+ font-weight: 600;
295
+ padding: 1px 5px;
296
+ border-radius: 8px;
297
+ text-transform: uppercase;
298
+ letter-spacing: 0.3px;
299
+ }
300
+
301
+ .mrsf-severity-high {
302
+ background: rgba(231, 76, 60, 0.2);
303
+ color: var(--mrsf-severity-high);
304
+ }
305
+
306
+ .mrsf-severity-medium {
307
+ background: rgba(243, 156, 18, 0.2);
308
+ color: var(--mrsf-severity-medium);
309
+ }
310
+
311
+ .mrsf-severity-low {
312
+ background: rgba(52, 152, 219, 0.2);
313
+ color: var(--mrsf-severity-low);
314
+ }
315
+
316
+ .mrsf-type {
317
+ font-size: 10px;
318
+ padding: 1px 5px;
319
+ border-radius: 8px;
320
+ background: rgba(127, 127, 127, 0.15);
321
+ opacity: 0.8;
322
+ }
323
+
324
+ .mrsf-resolved-badge {
325
+ font-size: 10px;
326
+ color: var(--mrsf-badge-resolved-bg);
327
+ font-weight: 600;
328
+ }
329
+
330
+ /* ── Comment body ───────────────────────────────────────── */
331
+
332
+ .mrsf-comment-body {
333
+ white-space: pre-wrap;
334
+ word-break: break-word;
335
+ }
336
+
337
+ /* ── Selected text quote in tooltip ─────────────────────── */
338
+
339
+ .mrsf-selected-text {
340
+ font-family: monospace;
341
+ font-size: 0.85em;
342
+ background: rgba(127, 127, 127, 0.1);
343
+ border-left: 3px solid rgba(127, 127, 127, 0.3);
344
+ margin: 4px 0;
345
+ border-radius: 2px;
346
+ }
347
+
348
+ .mrsf-selected-text-summary {
349
+ padding: 4px 8px;
350
+ cursor: pointer;
351
+ overflow: hidden;
352
+ text-overflow: ellipsis;
353
+ white-space: nowrap;
354
+ display: block;
355
+ list-style: none;
356
+ color: var(--mrsf-fg, inherit);
357
+ opacity: 0.7;
358
+ }
359
+
360
+ .mrsf-selected-text-summary::-webkit-details-marker {
361
+ display: none;
362
+ }
363
+
364
+ .mrsf-selected-text-summary::before {
365
+ content: "▶ ";
366
+ font-size: 0.7em;
367
+ vertical-align: middle;
368
+ margin-right: 2px;
369
+ }
370
+
371
+ .mrsf-selected-text[open] > .mrsf-selected-text-summary {
372
+ display: none;
373
+ }
374
+
375
+ .mrsf-selected-text-full {
376
+ display: block;
377
+ padding: 4px 8px;
378
+ white-space: pre-wrap;
379
+ word-break: break-word;
380
+ }
381
+
382
+ /* ── Inline text highlight (overlay positioned by JS) ───── */
383
+
384
+ .mrsf-highlight-overlay {
385
+ position: absolute;
386
+ background: var(--mrsf-highlight-bg);
387
+ border-bottom: 2px solid var(--mrsf-highlight-border);
388
+ border-radius: 2px;
389
+ pointer-events: none;
390
+ transition: background 0.15s ease;
391
+ }
392
+
393
+ /* ── Inline comment text highlight (<mark> by controller) ── */
394
+
395
+ .mrsf-inline-highlight {
396
+ background: var(--mrsf-highlight-bg);
397
+ border-bottom: 2px solid var(--mrsf-highlight-border);
398
+ border-radius: 2px;
399
+ cursor: pointer;
400
+ transition: background 0.15s ease;
401
+ }
402
+
403
+ .mrsf-inline-highlight:hover {
404
+ background: rgba(255, 213, 79, 0.4);
405
+ }
406
+
407
+ /* Inline tooltip (appended to document.body, positioned by JS) */
408
+ .mrsf-inline-tooltip {
409
+ position: fixed;
410
+ z-index: 1100;
411
+ min-width: 300px;
412
+ max-width: 480px;
413
+ max-height: 400px;
414
+ overflow-y: auto;
415
+ padding: 8px;
416
+ border-radius: 6px;
417
+ background: var(--mrsf-tooltip-bg);
418
+ border: 1px solid var(--mrsf-tooltip-border);
419
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
420
+ font-family: var(--mrsf-font-family);
421
+ font-size: 13px;
422
+ line-height: 1.5;
423
+ color: var(--mrsf-tooltip-fg);
424
+ white-space: normal;
425
+ animation: mrsf-fade-in 0.15s ease;
426
+ }
427
+
428
+ /* ── Interactive action buttons ─────────────────────────── */
429
+
430
+ .mrsf-actions {
431
+ display: none;
432
+ gap: 4px;
433
+ margin-top: 4px;
434
+ padding-top: 4px;
435
+ border-top: 1px solid var(--mrsf-tooltip-border);
436
+ }
437
+
438
+ .mrsf-interactive .mrsf-actions {
439
+ display: flex;
440
+ }
441
+
442
+ .mrsf-tooltip-actions {
443
+ display: flex;
444
+ justify-content: flex-end;
445
+ gap: 6px;
446
+ margin-top: 8px;
447
+ padding-top: 6px;
448
+ border-top: 1px solid var(--mrsf-tooltip-border);
449
+ }
450
+
451
+ .mrsf-action-btn {
452
+ font-size: 11px;
453
+ padding: 2px 8px;
454
+ border-radius: 4px;
455
+ border: 1px solid var(--mrsf-tooltip-border);
456
+ background: transparent;
457
+ color: var(--mrsf-tooltip-fg);
458
+ cursor: pointer;
459
+ font-family: var(--mrsf-font-family);
460
+ transition: background 0.15s ease;
461
+ }
462
+
463
+ .mrsf-action-btn.mrsf-action-danger {
464
+ color: #d13438;
465
+ border-color: rgba(209, 52, 56, 0.7);
466
+ }
467
+
468
+ .mrsf-action-btn:hover {
469
+ background: rgba(127, 127, 127, 0.2);
470
+ }
471
+
472
+ /* ── Floating "Add comment" button (selection-based) ────── */
473
+
474
+ /* ── Orphaned comments section (bottom of container) ────── */
475
+
476
+ .mrsf-orphaned-section {
477
+ margin-top: 24px;
478
+ padding-top: 16px;
479
+ border-top: 2px dashed var(--mrsf-tooltip-border);
480
+ }
481
+
482
+ .mrsf-orphaned-heading {
483
+ font-family: var(--mrsf-font-family);
484
+ font-size: 12px;
485
+ font-weight: 600;
486
+ text-transform: uppercase;
487
+ letter-spacing: 0.5px;
488
+ color: #999;
489
+ margin-bottom: 8px;
490
+ }
491
+
492
+ .mrsf-orphaned-thread {
493
+ padding: 8px;
494
+ margin-bottom: 6px;
495
+ border-radius: 6px;
496
+ border: 1px solid var(--mrsf-tooltip-border);
497
+ background: rgba(127, 127, 127, 0.06);
498
+ opacity: 0.7;
499
+ font-family: var(--mrsf-font-family);
500
+ font-size: 13px;
501
+ line-height: 1.5;
502
+ color: var(--mrsf-tooltip-fg);
503
+ }
504
+
505
+ .mrsf-add-inline-button {
506
+ position: absolute;
507
+ z-index: 1200;
508
+ display: none;
509
+ font-size: 12px;
510
+ padding: 6px 10px;
511
+ border-radius: 6px;
512
+ border: 1px solid var(--mrsf-tooltip-border);
513
+ background: var(--mrsf-tooltip-bg);
514
+ color: var(--mrsf-tooltip-fg);
515
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.25);
516
+ cursor: pointer;
517
+ font-family: var(--mrsf-font-family);
518
+ }
519
+
520
+ .mrsf-add-inline-button:hover {
521
+ background: rgba(127, 127, 127, 0.2);
522
+ }