annotepage-client 2.9.0 → 2.10.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 +23 -7
- package/dist/HASHES.txt +2 -1
- package/dist/annotepage.js +90 -31
- package/labels/fr.json +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Load the client once, on a page of the site, with `data-setup` and **without**
|
|
|
34
34
|
|
|
35
35
|
```html
|
|
36
36
|
<script src="https://<your-cdn>/annotepage-client@2.5.0/dist/annotepage.js"
|
|
37
|
-
integrity="sha384-
|
|
37
|
+
integrity="sha384-zopsMVKI88YIkKUGjIgfsajeAEnObuHRo3B1woLG1AabTT32SQmgywnWLkb6Z+6I"
|
|
38
38
|
crossorigin="anonymous"
|
|
39
39
|
data-server="https://<your-server>/annotepage/api.php"
|
|
40
40
|
data-setup
|
|
@@ -55,7 +55,7 @@ on the server. No network request is made at that point.
|
|
|
55
55
|
|
|
56
56
|
```html
|
|
57
57
|
<script src="https://<your-cdn>/annotepage-client@2.5.0/dist/annotepage.js"
|
|
58
|
-
integrity="sha384-
|
|
58
|
+
integrity="sha384-zopsMVKI88YIkKUGjIgfsajeAEnObuHRo3B1woLG1AabTT32SQmgywnWLkb6Z+6I"
|
|
59
59
|
crossorigin="anonymous"
|
|
60
60
|
data-server="https://<your-server>/annotepage/api.php"
|
|
61
61
|
data-project="7Qb1kZ3xNvA9dLpEqKf2Zt"
|
|
@@ -134,12 +134,28 @@ nothing decrypted, rather than guessing which of the two is the typo. A
|
|
|
134
134
|
`data-key` that is not 43 base64url characters is refused the same way, and
|
|
135
135
|
said on screen: somebody put that attribute there on purpose.
|
|
136
136
|
|
|
137
|
-
|
|
138
|
-
once at load time
|
|
137
|
+
**Which of the two a project runs in is said in the panel, at every draw** --
|
|
138
|
+
in BOTH modes, and not once at load time. It is one badge, one word, above the
|
|
139
|
+
notes:
|
|
139
140
|
|
|
140
|
-
>
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
> `Public` `Secure`
|
|
142
|
+
|
|
143
|
+
It is not a button and there is nothing to press. Point at it, or reach it with
|
|
144
|
+
Tab, and it gives the sentence -- the badge is focusable for exactly that
|
|
145
|
+
reason, so the explanation is not reserved to whoever has a pointer:
|
|
146
|
+
|
|
147
|
+
> **Public** -- End-to-end encrypted, and the key is in this page: anyone who
|
|
148
|
+
> can open the page can read these notes AND write them. The key gives both --
|
|
149
|
+
> this format has no reader-only role.
|
|
150
|
+
|
|
151
|
+
> **Secure** -- End-to-end encrypted, and the key is not in this page: each
|
|
152
|
+
> browser pastes it once, and the server never receives it.
|
|
153
|
+
|
|
154
|
+
The encryption is not written on the badge itself: it holds in both modes, at
|
|
155
|
+
all times, so a word that appears in both distinguishes nothing. It belongs to
|
|
156
|
+
the sentence, where somebody asking the question is. All four texts are labels
|
|
157
|
+
(`mode.public`, `mode.secure`, `mode.public_detail`, `mode.secure_detail`) and
|
|
158
|
+
translate like the rest.
|
|
143
159
|
|
|
144
160
|
### What a public key actually opens, and it is not mainly reading
|
|
145
161
|
|
package/dist/HASHES.txt
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.10.0 sha384-zopsMVKI88YIkKUGjIgfsajeAEnObuHRo3B1woLG1AabTT32SQmgywnWLkb6Z+6I 190880 bytes
|
|
2
|
+
2.9.0 sha384-dCrvpqNifPv65lOqhoAkMuBKygkA6zE52XyadW8eyJftw5chspr13le0rbiJ588M 190878 bytes
|
|
2
3
|
2.8.0 sha384-OJwckEoVZ2bTAdnTOon5j4gOXCZiHF6VQtf0efutPWJq4YOyLgweYrrntZfRmaum 182813 bytes
|
|
3
4
|
2.7.0 sha384-i7lpLUYLSVSOFMi+9W+EMhMbYDe7Nff/TF2vUuyQvVQ+XHph8rGJDXn2s4aNPSOT 175916 bytes
|
|
4
5
|
2.6.0 sha384-PVE6tjzC72/VHyRLdxHLpwMLIp9ji+VUBapD+j3vmoi69rpkXn0h+ZicxwORUdQt 167714 bytes
|
package/dist/annotepage.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
2
|
annotepage -- the annotation layer, browser side.
|
|
3
3
|
|
|
4
|
-
Package version : 2.
|
|
4
|
+
Package version : 2.10.0
|
|
5
5
|
Format version : 2 (see FORMAT.md)
|
|
6
6
|
Licence : MIT
|
|
7
7
|
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
/* Injected by the build: they come from package.json and from
|
|
17
17
|
src/styles.css, so that no value is written in two places and can
|
|
18
18
|
therefore diverge. */
|
|
19
|
-
const TOOL_VERSION = "2.
|
|
19
|
+
const TOOL_VERSION = "2.10.0";
|
|
20
20
|
const FORMAT = 2;
|
|
21
|
-
const STYLES = "/* ============================================================================\n styles.css -- THE STYLES OF THE TOOL, AND OF NO OTHER ELEMENT.\n\n This sheet is INLINED into the served file by the build, then put into the\n tool's shadow root -- as a constructed sheet when the browser can do it, in\n a <style> otherwise. It was loaded by a <link> in the original tool; the\n move to a CDN under SRI brought it inside the file, so that there is only\n one digest to keep up to date. The containment itself has not changed, and\n is still twofold:\n\n - from the tool towards the site: no rule from here can reach an element\n of the host site, the browser sees to that. That is what makes the\n claim \"the layer touches nothing\" checkable rather than promised;\n - from the site towards the tool: no rule of the site can reach an\n element here. A redesign of the site's stylesheet therefore cannot\n distort the tool, nor the other way round.\n\n The \"ap-\" prefix on every class is the third safeguard: the day somebody\n loads these styles WITHOUT a shadow root -- by mistake, or to debug --\n nothing would answer a selector of the site.\n\n NO RULE TARGETS html, body, * OR ANY SELECTOR OF THE SITE. That is the one\n absolute prohibition of this file.\n\n COLOURS: the tool has its OWN palette, defined on the shadow root. It\n reads neither the site's variables nor its theme attribute: it has no\n reason to know how the site names its colours, and it must stay readable\n on a light site as on a dark one. The switch follows the system\n preference, the only information the tool has without asking anyone.\n ============================================================================ */\n\n\n:host {\n --ap-bg: #ffffff;\n --ap-bg-soft: #f4f6f8;\n --ap-bg-raised: #e9edf2;\n --ap-text: #1a1d21;\n --ap-text-soft: #5b6570;\n --ap-border: #d5dbe2;\n --ap-accent: #2f6fed;\n --ap-accent-dark: #1d55c8;\n --ap-accent-text: #ffffff;\n --ap-accent-veil: rgba(47, 111, 237, 0.14);\n --ap-alert-bg: #fdeceb;\n --ap-alert-border: #e3a9a4;\n --ap-alert-text: #8a1f16;\n --ap-shadow: 0 6px 24px rgba(16, 24, 40, 0.18);\n --ap-radius: 10px;\n --ap-font: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\",\n Arial, sans-serif;\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n --ap-bg: #1d2126;\n --ap-bg-soft: #262b32;\n --ap-bg-raised: #323942;\n --ap-text: #e9ecf0;\n --ap-text-soft: #a4adb8;\n --ap-border: #3a424c;\n --ap-accent: #6d9bff;\n --ap-accent-dark: #8fb4ff;\n --ap-accent-text: #10151c;\n --ap-accent-veil: rgba(109, 155, 255, 0.18);\n --ap-alert-bg: #3a1f1c;\n --ap-alert-border: #7c3a33;\n --ap-alert-text: #ffb9b1;\n --ap-shadow: 0 6px 24px rgba(0, 0, 0, 0.55);\n }\n}\n\n/* ----------------------------------------------------------------------------\n The layer.\n\n It covers the viewport and receives NO click: that is what lets the page\n behave exactly as usual as long as the tool is not in annotation mode.\n Each widget re-enables clicks for itself alone.\n ---------------------------------------------------------------------------- */\n\n.ap-layer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n font-family: var(--ap-font);\n font-size: 14px;\n line-height: 1.45;\n color: var(--ap-text);\n text-align: left;\n -webkit-font-smoothing: antialiased;\n}\n\n.ap-layer button,\n.ap-layer input,\n.ap-layer textarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n color: inherit;\n margin: 0;\n box-sizing: border-box;\n}\n\n/* ----------------------------------------------------------------------------\n The button: the only thing visible when the tool is at rest.\n ---------------------------------------------------------------------------- */\n\n.ap-button {\n position: fixed;\n right: 16px;\n bottom: 16px;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 9px 14px;\n border: 1px solid var(--ap-border);\n border-radius: 999px;\n background: var(--ap-bg);\n color: var(--ap-text);\n box-shadow: var(--ap-shadow);\n cursor: pointer;\n pointer-events: auto;\n opacity: 0.92;\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ap-button:hover,\n.ap-button:focus-visible {\n opacity: 1;\n transform: translateY(-1px);\n}\n\n.ap-button:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 2px;\n}\n\n.ap-button[aria-pressed=\"true\"] {\n background: var(--ap-accent);\n border-color: var(--ap-accent);\n color: var(--ap-accent-text);\n opacity: 1;\n}\n\n.ap-button-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--ap-accent);\n flex: none;\n}\n\n.ap-button[aria-pressed=\"true\"] .ap-button-dot {\n background: var(--ap-accent-text);\n}\n\n.ap-button-count {\n padding: 1px 7px;\n border-radius: 999px;\n background: var(--ap-bg-raised);\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-button[aria-pressed=\"true\"] .ap-button-count {\n background: rgba(255, 255, 255, 0.22);\n color: var(--ap-accent-text);\n}\n\n/* ----------------------------------------------------------------------------\n The pointing highlight.\n\n It is DRAWN HERE, from the coordinates of the element being pointed at.\n Nothing is put on the element itself: no class, no attribute, no style. So\n the site cannot move by a single pixel because of the pointing.\n ---------------------------------------------------------------------------- */\n\n.ap-highlight {\n position: fixed;\n border: 2px solid var(--ap-accent);\n border-radius: 3px;\n background: var(--ap-accent-veil);\n pointer-events: none;\n display: none;\n}\n\n.ap-highlight-label {\n position: fixed;\n max-width: 320px;\n padding: 4px 8px;\n border-radius: 6px;\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: none;\n display: none;\n box-shadow: var(--ap-shadow);\n}\n\n/* ----------------------------------------------------------------------------\n The markers: \"there are already notes here\".\n ---------------------------------------------------------------------------- */\n\n.ap-marker {\n position: fixed;\n min-width: 22px;\n height: 22px;\n padding: 0 6px;\n border: 2px solid var(--ap-bg);\n border-radius: 999px;\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-size: 12px;\n font-weight: 700;\n line-height: 18px;\n text-align: center;\n cursor: pointer;\n pointer-events: auto;\n box-shadow: var(--ap-shadow);\n}\n\n.ap-marker:focus-visible {\n outline: 2px solid var(--ap-accent-dark);\n outline-offset: 2px;\n}\n\n/* ----------------------------------------------------------------------------\n The panel.\n ---------------------------------------------------------------------------- */\n\n.ap-panel {\n position: fixed;\n top: 12px;\n right: 12px;\n bottom: 72px;\n width: 360px;\n max-width: calc(100vw - 24px);\n display: none;\n flex-direction: column;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n box-shadow: var(--ap-shadow);\n pointer-events: auto;\n overflow: hidden;\n}\n\n.ap-panel.ap-open {\n display: flex;\n}\n\n/* The other side. Two positions, right and left, and no third one: in\n annotation mode the layer takes every click, so every pixel of panel is a\n pixel of page that can no longer be pointed at. A floating panel would\n only move that loss around.\n\n \"bottom\" IS NOT REDECLARED, on purpose: the panel keeps the same 72 px of\n clearance above the floating button either way, and that clearance is the\n same on both sides because the button moves WITH it.\n\n AND THE BUTTON MOVES WITH IT, which was decided the other way first. The\n argument for leaving it was that it is the tool's only visible trace at\n rest, so an anchor that moved would stop being one. The owner's answer,\n and it is the better one: \"move left\" is asked of the TOOL, not of half of\n it. A panel on one edge and the control that opens it on the other makes\n the eye cross the whole window for one gesture, and somebody who set the\n side set it for the tool.\n\n TWO THINGS THIS RULE DEPENDS ON, both fragile:\n - it is written with ONE class, so it weighs exactly what \".ap-panel\"\n weighs;\n - it is written BEFORE the narrow block below.\n Reverse either and it wins under 560 px, where the panel is a bottom band\n spanning the width -- and a panel pinned to the left there is the full-\n height panel whose defect at 375 px is described in that block. */\n.ap-left {\n right: auto;\n left: 12px;\n}\n/* The button follows, and it is put on the LAYER and not on the panel: the\n button is not inside the panel, so the side has to be readable from a\n common ancestor. Same offset mirrored, same bottom -- only the edge\n changes. */\n.ap-layer.ap-left-side .ap-button {\n right: auto;\n left: 16px;\n}\n\n.ap-panel-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--ap-border);\n background: var(--ap-bg-soft);\n}\n\n.ap-panel-title {\n font-size: 15px;\n font-weight: 600;\n flex: 1 1 auto;\n}\n\n.ap-panel-instructions {\n padding: 10px 14px;\n border-bottom: 1px solid var(--ap-border);\n color: var(--ap-text-soft);\n font-size: 13px;\n}\n\n.ap-panel-body {\n flex: 1 1 auto;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: 4px 14px 14px;\n}\n\n.ap-panel-footer {\n padding: 8px 14px;\n border-top: 1px solid var(--ap-border);\n background: var(--ap-bg-soft);\n color: var(--ap-text-soft);\n font-size: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ap-section-title {\n margin: 14px 0 6px;\n color: var(--ap-text-soft);\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.ap-section-help {\n margin: 0 0 8px;\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-empty {\n margin: 16px 0;\n color: var(--ap-text-soft);\n}\n\n/* The standing mention of a public key. Deliberately NOT the alert colours:\n this is not a failure and it is on screen for ever -- an alarm that never\n goes away stops being read. It is a fact about the project, stated in the\n panel's own tone, and it stays at the top of every draw. */\n/* The public-key notice, the \"a newer client exists\" line and the \"this\n server speaks another format\" line are the same object on screen: a\n standing statement about what one is looking at, above the notes and above\n the failures. One rule, so they cannot drift apart -- and the third one\n joins it rather than inventing a colour, precisely because a protocol\n disagreement is a fact about the project, not an incident. */\n.ap-public,\n.ap-upgrade,\n.ap-format {\n margin: 0 0 10px;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg-soft);\n color: var(--ap-text-soft);\n font-size: 12px;\n line-height: 1.45;\n}\n\n/* ----------------------------------------------------------------------------\n A note, and its replies.\n ---------------------------------------------------------------------------- */\n\n.ap-note {\n margin: 8px 0;\n padding: 10px 12px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n}\n\n.ap-note.ap-orphan {\n background: var(--ap-bg-soft);\n}\n\n.ap-note.ap-focused {\n border-color: var(--ap-accent);\n box-shadow: 0 0 0 3px var(--ap-accent-veil);\n}\n\n.ap-note-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-note-author {\n font-weight: 600;\n}\n\n.ap-note-date {\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-note-target {\n margin: 4px 0 0;\n color: var(--ap-text-soft);\n font-size: 12px;\n font-style: italic;\n overflow-wrap: anywhere;\n}\n\n.ap-note-text {\n margin: 6px 0 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n.ap-note-actions {\n margin-top: 8px;\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-replies {\n margin: 8px 0 0;\n padding-left: 10px;\n border-left: 2px solid var(--ap-border);\n}\n\n.ap-reply {\n margin: 8px 0 0;\n}\n\n/* ----------------------------------------------------------------------------\n The form, anchored near the element pointed at.\n ---------------------------------------------------------------------------- */\n\n.ap-form {\n position: fixed;\n width: 340px;\n max-width: calc(100vw - 24px);\n display: none;\n flex-direction: column;\n gap: 8px;\n padding: 14px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n box-shadow: var(--ap-shadow);\n pointer-events: auto;\n}\n\n.ap-form.ap-open {\n display: flex;\n}\n\n.ap-form-title {\n font-size: 15px;\n font-weight: 600;\n}\n\n.ap-form-target {\n color: var(--ap-text-soft);\n font-size: 12px;\n font-style: italic;\n overflow-wrap: anywhere;\n}\n\n.ap-label {\n display: block;\n margin-bottom: 3px;\n font-size: 12px;\n font-weight: 600;\n color: var(--ap-text-soft);\n}\n\n.ap-help {\n margin: 3px 0 0;\n font-size: 12px;\n color: var(--ap-text-soft);\n}\n\n.ap-field,\n.ap-area {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: 8px;\n background: var(--ap-bg-soft);\n color: var(--ap-text);\n}\n\n.ap-field:focus,\n.ap-area:focus {\n outline: 2px solid var(--ap-accent);\n outline-offset: 1px;\n}\n\n.ap-area {\n min-height: 92px;\n resize: vertical;\n}\n\n.ap-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-counter {\n margin-left: auto;\n font-size: 12px;\n color: var(--ap-text-soft);\n}\n\n/* ----------------------------------------------------------------------------\n Buttons.\n ---------------------------------------------------------------------------- */\n\n.ap-primary,\n.ap-secondary,\n.ap-link {\n border-radius: 8px;\n cursor: pointer;\n pointer-events: auto;\n}\n\n.ap-primary {\n padding: 8px 14px;\n border: 1px solid var(--ap-accent);\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-weight: 600;\n}\n\n.ap-primary:hover {\n background: var(--ap-accent-dark);\n border-color: var(--ap-accent-dark);\n}\n\n.ap-secondary {\n padding: 8px 14px;\n border: 1px solid var(--ap-border);\n background: var(--ap-bg);\n color: var(--ap-text);\n}\n\n.ap-secondary:hover {\n background: var(--ap-bg-raised);\n}\n\n.ap-link {\n padding: 2px 4px;\n border: 0;\n background: none;\n color: var(--ap-accent);\n text-decoration: underline;\n font-size: 13px;\n}\n\n.ap-primary:disabled,\n.ap-secondary:disabled,\n.ap-link:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.ap-primary:focus-visible,\n.ap-secondary:focus-visible,\n.ap-link:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 2px;\n}\n\n/* ----------------------------------------------------------------------------\n The failures.\n\n They are RED, at the top of the block concerned, and carry the message the\n server returned as it stands: that is how a non-technical team learns that\n its remark is not saved, instead of believing it is.\n ---------------------------------------------------------------------------- */\n\n.ap-error {\n margin: 8px 0;\n padding: 10px 12px;\n border: 1px solid var(--ap-alert-border);\n border-radius: var(--ap-radius);\n background: var(--ap-alert-bg);\n color: var(--ap-alert-text);\n}\n\n.ap-error-title {\n font-weight: 700;\n margin-bottom: 4px;\n}\n\n.ap-error-detail {\n margin: 6px 0 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: 13px;\n}\n\n.ap-error .ap-link {\n color: var(--ap-alert-text);\n}\n\n/* ----------------------------------------------------------------------------\n Narrow: the panel takes the full width, and so does the form.\n ---------------------------------------------------------------------------- */\n\n/* ----------------------------------------------------------------------------\n Narrow.\n\n DEFECT OBSERVED at 375 px wide: a panel taking the full height covers the\n whole page, and no element can be pointed at any more -- every click lands\n on the panel. So it becomes a bottom band, which leaves the top half of\n the viewport free; one scrolls the page there to bring the wanted element\n into view. The form, for its part, hides the panel while typing (see\n notes.js): on a screen that size, writing and reading the list at the same\n time does not hold.\n ---------------------------------------------------------------------------- */\n\n/* On a narrow screen the panel becomes a bottom band and the form takes the\n full width.\n\n THE WIDTH CEILING IS KEPT, and it comes from a measured defect: \"left: 8;\n right: 8\" sizes the element against its CONTAINING BLOCK, which the host\n site's horizontal overflow can make wider than the visible window.\n Measured, in mobile emulation at 390 px: the site overflows to 407 px\n (with the tool and without it), and the panel came out 391 px wide\n starting at 8, that is 9 px off screen. \"100vw\" is the window, not the\n containing block: the ceiling therefore does nothing when the site does\n not overflow, and pulls the width back when it does. */\n@media (max-width: 560px) {\n .ap-panel {\n top: auto;\n right: 8px;\n left: 8px;\n bottom: 66px;\n height: 52vh;\n width: auto;\n max-width: calc(100vw - 16px);\n }\n\n /* A band spanning the width has no side, so the choice is inert here and\n the button says so by not being there. Offering a control that visibly\n does nothing is worse than offering none. The stored side is untouched:\n it is waiting for the wide screen it was chosen on. */\n .ap-side-toggle {\n display: none;\n }\n\n .ap-form {\n left: 8px;\n right: 8px;\n width: auto;\n max-width: calc(100vw - 16px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ap-button {\n transition: none;\n }\n}\n\n/* The failure shows without opening the panel: the button's dot changes\n colour. A team that does not click must be able to see that something is\n wrong. */\n.ap-button.ap-failed .ap-button-dot {\n background: var(--ap-alert-text);\n}\n\n.ap-button.ap-failed {\n border-color: var(--ap-alert-border);\n}\n\n/* Signature reminder, in the note form.\n The name was shown at the foot of the panel only: invisible at the moment\n one writes. A user reported not knowing which name they were writing\n under. */\n.ap-form-signature {\n display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;\n margin: 0 0 .6rem; font-size: .85rem; opacity: .8;\n}\n\n/* Resolution state, said on the card.\n Two cases NOT to be confused: resolved and online, resolved but not\n deployed yet. The second keeps the defect on the reviewer's screen; hiding\n it or announcing it as fixed would cost them their trust in the tool. */\n.ap-state-mark {\n display: inline-block; margin: 0 0 .5rem;\n padding: .15rem .55rem; border-radius: 4px;\n font-size: .75rem; font-weight: 600; letter-spacing: .02em;\n}\n.ap-note.ap-resolved { opacity: .72; }\n.ap-note.ap-resolved .ap-state-mark {\n color: #0f7a52; background: rgba(16, 185, 129, .14);\n}\n.ap-note.ap-resolved-pending .ap-state-mark {\n color: #8a5a00; background: rgba(245, 158, 11, .16);\n}\n/* The \"it is fixed\" / \"reopen\" block, opened under the card. Same shape as\n the reply block: it is the same gesture, one answers a remark. */\n.ap-resolve,\n.ap-reply-form {\n margin-top: .6rem;\n padding-top: .6rem;\n border-top: 1px solid var(--ap-border);\n}\n\n/* The question asked before the key is dropped, at the foot of the list.\n Framed like the resolution block -- it is the same shape of gesture, one\n answers before something changes -- and set apart from the notes above it,\n because it is not about a note. */\n.ap-forget {\n margin-top: 1rem;\n padding-top: .6rem;\n border-top: 1px solid var(--ap-border);\n}\n\n.ap-forget .ap-actions {\n margin-top: .5rem;\n}\n\n.ap-history-toggle {\n display: block; width: 100%; margin: 1rem 0 .25rem;\n padding: .5rem .75rem; border: 1px dashed currentColor; border-radius: 6px;\n background: none; color: inherit; font: inherit; opacity: .7; cursor: pointer;\n}\n.ap-history-toggle:hover { opacity: 1; }\n\n\n/* ----------------------------------------------------------------------------\n Setup and pasting the salt.\n\n These are the only screens where something is copied by hand. Everything\n there is SELECTABLE and monospaced: a 43-character salt copied wrong\n cannot be recovered, and nothing helps less than a font that confuses I, l\n and 1.\n ---------------------------------------------------------------------------- */\n\n.ap-panel-wide {\n width: 560px;\n}\n\n.ap-copy {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin: 0 0 12px;\n}\n\n.ap-code {\n flex: 1 1 auto;\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: 8px;\n background: var(--ap-bg-soft);\n color: var(--ap-text);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\",\n monospace;\n font-size: 12.5px;\n line-height: 1.5;\n resize: vertical;\n white-space: pre;\n overflow-x: auto;\n}\n\n.ap-code:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 1px;\n}\n\n@media (max-width: 560px) {\n .ap-panel-wide {\n width: auto;\n }\n\n .ap-copy {\n flex-direction: column;\n }\n}\n";
|
|
21
|
+
const STYLES = "/* ============================================================================\n styles.css -- THE STYLES OF THE TOOL, AND OF NO OTHER ELEMENT.\n\n This sheet is INLINED into the served file by the build, then put into the\n tool's shadow root -- as a constructed sheet when the browser can do it, in\n a <style> otherwise. It was loaded by a <link> in the original tool; the\n move to a CDN under SRI brought it inside the file, so that there is only\n one digest to keep up to date. The containment itself has not changed, and\n is still twofold:\n\n - from the tool towards the site: no rule from here can reach an element\n of the host site, the browser sees to that. That is what makes the\n claim \"the layer touches nothing\" checkable rather than promised;\n - from the site towards the tool: no rule of the site can reach an\n element here. A redesign of the site's stylesheet therefore cannot\n distort the tool, nor the other way round.\n\n The \"ap-\" prefix on every class is the third safeguard: the day somebody\n loads these styles WITHOUT a shadow root -- by mistake, or to debug --\n nothing would answer a selector of the site.\n\n NO RULE TARGETS html, body, * OR ANY SELECTOR OF THE SITE. That is the one\n absolute prohibition of this file.\n\n COLOURS: the tool has its OWN palette, defined on the shadow root. It\n reads neither the site's variables nor its theme attribute: it has no\n reason to know how the site names its colours, and it must stay readable\n on a light site as on a dark one. The switch follows the system\n preference, the only information the tool has without asking anyone.\n ============================================================================ */\n\n\n:host {\n --ap-bg: #ffffff;\n --ap-bg-soft: #f4f6f8;\n --ap-bg-raised: #e9edf2;\n --ap-text: #1a1d21;\n --ap-text-soft: #5b6570;\n --ap-border: #d5dbe2;\n --ap-accent: #2f6fed;\n --ap-accent-dark: #1d55c8;\n --ap-accent-text: #ffffff;\n --ap-accent-veil: rgba(47, 111, 237, 0.14);\n --ap-alert-bg: #fdeceb;\n --ap-alert-border: #e3a9a4;\n --ap-alert-text: #8a1f16;\n --ap-shadow: 0 6px 24px rgba(16, 24, 40, 0.18);\n --ap-radius: 10px;\n --ap-font: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\",\n Arial, sans-serif;\n}\n\n@media (prefers-color-scheme: dark) {\n :host {\n --ap-bg: #1d2126;\n --ap-bg-soft: #262b32;\n --ap-bg-raised: #323942;\n --ap-text: #e9ecf0;\n --ap-text-soft: #a4adb8;\n --ap-border: #3a424c;\n --ap-accent: #6d9bff;\n --ap-accent-dark: #8fb4ff;\n --ap-accent-text: #10151c;\n --ap-accent-veil: rgba(109, 155, 255, 0.18);\n --ap-alert-bg: #3a1f1c;\n --ap-alert-border: #7c3a33;\n --ap-alert-text: #ffb9b1;\n --ap-shadow: 0 6px 24px rgba(0, 0, 0, 0.55);\n }\n}\n\n/* ----------------------------------------------------------------------------\n The layer.\n\n It covers the viewport and receives NO click: that is what lets the page\n behave exactly as usual as long as the tool is not in annotation mode.\n Each widget re-enables clicks for itself alone.\n ---------------------------------------------------------------------------- */\n\n.ap-layer {\n position: absolute;\n inset: 0;\n pointer-events: none;\n font-family: var(--ap-font);\n font-size: 14px;\n line-height: 1.45;\n color: var(--ap-text);\n text-align: left;\n -webkit-font-smoothing: antialiased;\n}\n\n.ap-layer button,\n.ap-layer input,\n.ap-layer textarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n color: inherit;\n margin: 0;\n box-sizing: border-box;\n}\n\n/* ----------------------------------------------------------------------------\n The button: the only thing visible when the tool is at rest.\n ---------------------------------------------------------------------------- */\n\n.ap-button {\n position: fixed;\n right: 16px;\n bottom: 16px;\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 9px 14px;\n border: 1px solid var(--ap-border);\n border-radius: 999px;\n background: var(--ap-bg);\n color: var(--ap-text);\n box-shadow: var(--ap-shadow);\n cursor: pointer;\n pointer-events: auto;\n opacity: 0.92;\n transition: opacity 0.15s ease, transform 0.15s ease;\n}\n\n.ap-button:hover,\n.ap-button:focus-visible {\n opacity: 1;\n transform: translateY(-1px);\n}\n\n.ap-button:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 2px;\n}\n\n.ap-button[aria-pressed=\"true\"] {\n background: var(--ap-accent);\n border-color: var(--ap-accent);\n color: var(--ap-accent-text);\n opacity: 1;\n}\n\n.ap-button-dot {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: var(--ap-accent);\n flex: none;\n}\n\n.ap-button[aria-pressed=\"true\"] .ap-button-dot {\n background: var(--ap-accent-text);\n}\n\n.ap-button-count {\n padding: 1px 7px;\n border-radius: 999px;\n background: var(--ap-bg-raised);\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-button[aria-pressed=\"true\"] .ap-button-count {\n background: rgba(255, 255, 255, 0.22);\n color: var(--ap-accent-text);\n}\n\n/* ----------------------------------------------------------------------------\n The pointing highlight.\n\n It is DRAWN HERE, from the coordinates of the element being pointed at.\n Nothing is put on the element itself: no class, no attribute, no style. So\n the site cannot move by a single pixel because of the pointing.\n ---------------------------------------------------------------------------- */\n\n.ap-highlight {\n position: fixed;\n border: 2px solid var(--ap-accent);\n border-radius: 3px;\n background: var(--ap-accent-veil);\n pointer-events: none;\n display: none;\n}\n\n.ap-highlight-label {\n position: fixed;\n max-width: 320px;\n padding: 4px 8px;\n border-radius: 6px;\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n pointer-events: none;\n display: none;\n box-shadow: var(--ap-shadow);\n}\n\n/* ----------------------------------------------------------------------------\n The markers: \"there are already notes here\".\n ---------------------------------------------------------------------------- */\n\n.ap-marker {\n position: fixed;\n min-width: 22px;\n height: 22px;\n padding: 0 6px;\n border: 2px solid var(--ap-bg);\n border-radius: 999px;\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-size: 12px;\n font-weight: 700;\n line-height: 18px;\n text-align: center;\n cursor: pointer;\n pointer-events: auto;\n box-shadow: var(--ap-shadow);\n}\n\n.ap-marker:focus-visible {\n outline: 2px solid var(--ap-accent-dark);\n outline-offset: 2px;\n}\n\n/* ----------------------------------------------------------------------------\n The panel.\n ---------------------------------------------------------------------------- */\n\n.ap-panel {\n position: fixed;\n top: 12px;\n right: 12px;\n bottom: 72px;\n width: 360px;\n max-width: calc(100vw - 24px);\n display: none;\n flex-direction: column;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n box-shadow: var(--ap-shadow);\n pointer-events: auto;\n overflow: hidden;\n}\n\n.ap-panel.ap-open {\n display: flex;\n}\n\n/* The other side. Two positions, right and left, and no third one: in\n annotation mode the layer takes every click, so every pixel of panel is a\n pixel of page that can no longer be pointed at. A floating panel would\n only move that loss around.\n\n \"bottom\" IS NOT REDECLARED, on purpose: the panel keeps the same 72 px of\n clearance above the floating button either way, and that clearance is the\n same on both sides because the button moves WITH it.\n\n AND THE BUTTON MOVES WITH IT, which was decided the other way first. The\n argument for leaving it was that it is the tool's only visible trace at\n rest, so an anchor that moved would stop being one. The owner's answer,\n and it is the better one: \"move left\" is asked of the TOOL, not of half of\n it. A panel on one edge and the control that opens it on the other makes\n the eye cross the whole window for one gesture, and somebody who set the\n side set it for the tool.\n\n TWO THINGS THIS RULE DEPENDS ON, both fragile:\n - it is written with ONE class, so it weighs exactly what \".ap-panel\"\n weighs;\n - it is written BEFORE the narrow block below.\n Reverse either and it wins under 560 px, where the panel is a bottom band\n spanning the width -- and a panel pinned to the left there is the full-\n height panel whose defect at 375 px is described in that block. */\n.ap-left {\n right: auto;\n left: 12px;\n}\n/* The button follows, and it is put on the LAYER and not on the panel: the\n button is not inside the panel, so the side has to be readable from a\n common ancestor. Same offset mirrored, same bottom -- only the edge\n changes. */\n.ap-layer.ap-left-side .ap-button {\n right: auto;\n left: 16px;\n}\n\n.ap-panel-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n padding: 12px 14px;\n border-bottom: 1px solid var(--ap-border);\n background: var(--ap-bg-soft);\n}\n\n.ap-panel-title {\n font-size: 15px;\n font-weight: 600;\n flex: 1 1 auto;\n}\n\n.ap-panel-instructions {\n padding: 10px 14px;\n border-bottom: 1px solid var(--ap-border);\n color: var(--ap-text-soft);\n font-size: 13px;\n}\n\n.ap-panel-body {\n flex: 1 1 auto;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: 4px 14px 14px;\n}\n\n.ap-panel-footer {\n padding: 8px 14px;\n border-top: 1px solid var(--ap-border);\n background: var(--ap-bg-soft);\n color: var(--ap-text-soft);\n font-size: 12px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.ap-section-title {\n margin: 14px 0 6px;\n color: var(--ap-text-soft);\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n}\n\n.ap-section-help {\n margin: 0 0 8px;\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-empty {\n margin: 16px 0;\n color: var(--ap-text-soft);\n}\n\n/* ----------------------------------------------------------------------------\n THE MODE BADGE: one word, drawn in both modes, at the top of every draw.\n\n It replaced a paragraph that only the public mode ever drew. Two things\n were wrong with that: secure mode said nothing, so the panel never told a\n reviewer which of the two they were in; and a paragraph pinned to the top\n for ever is skipped after the second reading, while a single word is not.\n\n IT IS NOT A BUTTON AND MUST NOT LOOK LIKE ONE. No shadow, no pressed\n state, cursor stays default: there is nothing to press. What it does have\n is a focus ring, because it takes focus -- the description below has to be\n reachable without a pointer, and that is the only reason it is focusable.\n ---------------------------------------------------------------------------- */\n\n.ap-mode {\n /* The frame the description is positioned against. It spans the width of\n the panel body, so a description pinned to its two edges cannot spill\n sideways out of a body that scrolls -- and a scrolling body clips both\n axes, not just the one it scrolls. */\n position: relative;\n margin: 4px 0 10px;\n}\n\n.ap-mode-chip {\n display: inline-block;\n padding: 2px 9px 3px;\n border: 1px solid var(--ap-border);\n border-radius: 999px;\n background: var(--ap-bg-soft);\n color: var(--ap-text-soft);\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.05em;\n text-transform: uppercase;\n cursor: default;\n}\n\n/* The public badge is the one that carries a consequence, so it is the one\n that is coloured -- in the tool's own accent, not in the alert colours: a\n public key is a choice somebody made on purpose, not an incident. Secure\n keeps the quiet frame: it is the case where nothing is being said. */\n/* The same ring as every other focusable thing in the panel. It is the only\n visual proof, for somebody arriving by Tab, that this word is where their\n focus went. */\n.ap-mode-chip:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 2px;\n}\n\n.ap-mode-public .ap-mode-chip {\n border-color: var(--ap-accent);\n background: var(--ap-accent-veil);\n color: var(--ap-accent-dark);\n}\n\n/* The description. Hidden by DEFAULT and shown on demand, and hidden in a way\n that leaves it readable to a screen reader: it is the badge's\n aria-describedby target, and an accessible description is computed from the\n referenced element whether or not it is painted. It is taken out of the\n layout entirely (absolute) so that showing it moves not one pixel of the\n list underneath -- a tooltip that pushes the notes down is a tooltip that\n makes people lose their place. */\n.ap-mode-tip {\n position: absolute;\n z-index: 2;\n top: calc(100% + 6px);\n left: 0;\n right: 0;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg-raised);\n color: var(--ap-text);\n box-shadow: var(--ap-shadow);\n font-size: 12px;\n font-weight: 400;\n letter-spacing: normal;\n text-transform: none;\n line-height: 1.45;\n opacity: 0;\n visibility: hidden;\n transform: translateY(-3px);\n transition: opacity 0.12s ease, transform 0.12s ease, visibility 0s linear 0.12s;\n pointer-events: none;\n}\n\n/* BOTH, and the keyboard one is not the afterthought: :focus-visible is what\n makes the sentence reachable by somebody who never touches a pointer. */\n.ap-mode-chip:hover ~ .ap-mode-tip,\n.ap-mode-chip:focus-visible ~ .ap-mode-tip {\n opacity: 1;\n visibility: visible;\n transform: none;\n transition: opacity 0.12s ease, transform 0.12s ease, visibility 0s;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ap-mode-tip {\n transform: none;\n transition: none;\n }\n .ap-mode-chip:hover ~ .ap-mode-tip,\n .ap-mode-chip:focus-visible ~ .ap-mode-tip {\n transition: none;\n }\n}\n\n/* The \"a newer client exists\" line and the \"this server speaks another\n format\" line are the same object on screen: a standing statement about what\n one is looking at, above the notes and above the failures. One rule, so\n they cannot drift apart -- and the second one joins it rather than\n inventing a colour, precisely because a protocol disagreement is a fact\n about the project, not an incident.\n\n Deliberately NOT the alert colours: neither is a failure, and an alarm that\n never goes away stops being read. */\n.ap-upgrade,\n.ap-format {\n margin: 0 0 10px;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg-soft);\n color: var(--ap-text-soft);\n font-size: 12px;\n line-height: 1.45;\n}\n\n/* ----------------------------------------------------------------------------\n A note, and its replies.\n ---------------------------------------------------------------------------- */\n\n.ap-note {\n margin: 8px 0;\n padding: 10px 12px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n}\n\n.ap-note.ap-orphan {\n background: var(--ap-bg-soft);\n}\n\n.ap-note.ap-focused {\n border-color: var(--ap-accent);\n box-shadow: 0 0 0 3px var(--ap-accent-veil);\n}\n\n.ap-note-header {\n display: flex;\n align-items: baseline;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-note-author {\n font-weight: 600;\n}\n\n.ap-note-date {\n color: var(--ap-text-soft);\n font-size: 12px;\n}\n\n.ap-note-target {\n margin: 4px 0 0;\n color: var(--ap-text-soft);\n font-size: 12px;\n font-style: italic;\n overflow-wrap: anywhere;\n}\n\n.ap-note-text {\n margin: 6px 0 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n.ap-note-actions {\n margin-top: 8px;\n display: flex;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-replies {\n margin: 8px 0 0;\n padding-left: 10px;\n border-left: 2px solid var(--ap-border);\n}\n\n.ap-reply {\n margin: 8px 0 0;\n}\n\n/* ----------------------------------------------------------------------------\n The form, anchored near the element pointed at.\n ---------------------------------------------------------------------------- */\n\n.ap-form {\n position: fixed;\n width: 340px;\n max-width: calc(100vw - 24px);\n display: none;\n flex-direction: column;\n gap: 8px;\n padding: 14px;\n border: 1px solid var(--ap-border);\n border-radius: var(--ap-radius);\n background: var(--ap-bg);\n box-shadow: var(--ap-shadow);\n pointer-events: auto;\n}\n\n.ap-form.ap-open {\n display: flex;\n}\n\n.ap-form-title {\n font-size: 15px;\n font-weight: 600;\n}\n\n.ap-form-target {\n color: var(--ap-text-soft);\n font-size: 12px;\n font-style: italic;\n overflow-wrap: anywhere;\n}\n\n.ap-label {\n display: block;\n margin-bottom: 3px;\n font-size: 12px;\n font-weight: 600;\n color: var(--ap-text-soft);\n}\n\n.ap-help {\n margin: 3px 0 0;\n font-size: 12px;\n color: var(--ap-text-soft);\n}\n\n.ap-field,\n.ap-area {\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: 8px;\n background: var(--ap-bg-soft);\n color: var(--ap-text);\n}\n\n.ap-field:focus,\n.ap-area:focus {\n outline: 2px solid var(--ap-accent);\n outline-offset: 1px;\n}\n\n.ap-area {\n min-height: 92px;\n resize: vertical;\n}\n\n.ap-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n\n.ap-counter {\n margin-left: auto;\n font-size: 12px;\n color: var(--ap-text-soft);\n}\n\n/* ----------------------------------------------------------------------------\n Buttons.\n ---------------------------------------------------------------------------- */\n\n.ap-primary,\n.ap-secondary,\n.ap-link {\n border-radius: 8px;\n cursor: pointer;\n pointer-events: auto;\n}\n\n.ap-primary {\n padding: 8px 14px;\n border: 1px solid var(--ap-accent);\n background: var(--ap-accent);\n color: var(--ap-accent-text);\n font-weight: 600;\n}\n\n.ap-primary:hover {\n background: var(--ap-accent-dark);\n border-color: var(--ap-accent-dark);\n}\n\n.ap-secondary {\n padding: 8px 14px;\n border: 1px solid var(--ap-border);\n background: var(--ap-bg);\n color: var(--ap-text);\n}\n\n.ap-secondary:hover {\n background: var(--ap-bg-raised);\n}\n\n.ap-link {\n padding: 2px 4px;\n border: 0;\n background: none;\n color: var(--ap-accent);\n text-decoration: underline;\n font-size: 13px;\n}\n\n.ap-primary:disabled,\n.ap-secondary:disabled,\n.ap-link:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.ap-primary:focus-visible,\n.ap-secondary:focus-visible,\n.ap-link:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 2px;\n}\n\n/* ----------------------------------------------------------------------------\n The failures.\n\n They are RED, at the top of the block concerned, and carry the message the\n server returned as it stands: that is how a non-technical team learns that\n its remark is not saved, instead of believing it is.\n ---------------------------------------------------------------------------- */\n\n.ap-error {\n margin: 8px 0;\n padding: 10px 12px;\n border: 1px solid var(--ap-alert-border);\n border-radius: var(--ap-radius);\n background: var(--ap-alert-bg);\n color: var(--ap-alert-text);\n}\n\n.ap-error-title {\n font-weight: 700;\n margin-bottom: 4px;\n}\n\n.ap-error-detail {\n margin: 6px 0 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: 13px;\n}\n\n.ap-error .ap-link {\n color: var(--ap-alert-text);\n}\n\n/* ----------------------------------------------------------------------------\n Narrow: the panel takes the full width, and so does the form.\n ---------------------------------------------------------------------------- */\n\n/* ----------------------------------------------------------------------------\n Narrow.\n\n DEFECT OBSERVED at 375 px wide: a panel taking the full height covers the\n whole page, and no element can be pointed at any more -- every click lands\n on the panel. So it becomes a bottom band, which leaves the top half of\n the viewport free; one scrolls the page there to bring the wanted element\n into view. The form, for its part, hides the panel while typing (see\n notes.js): on a screen that size, writing and reading the list at the same\n time does not hold.\n ---------------------------------------------------------------------------- */\n\n/* On a narrow screen the panel becomes a bottom band and the form takes the\n full width.\n\n THE WIDTH CEILING IS KEPT, and it comes from a measured defect: \"left: 8;\n right: 8\" sizes the element against its CONTAINING BLOCK, which the host\n site's horizontal overflow can make wider than the visible window.\n Measured, in mobile emulation at 390 px: the site overflows to 407 px\n (with the tool and without it), and the panel came out 391 px wide\n starting at 8, that is 9 px off screen. \"100vw\" is the window, not the\n containing block: the ceiling therefore does nothing when the site does\n not overflow, and pulls the width back when it does. */\n@media (max-width: 560px) {\n .ap-panel {\n top: auto;\n right: 8px;\n left: 8px;\n bottom: 66px;\n height: 52vh;\n width: auto;\n max-width: calc(100vw - 16px);\n }\n\n /* A band spanning the width has no side, so the choice is inert here and\n the button says so by not being there. Offering a control that visibly\n does nothing is worse than offering none. The stored side is untouched:\n it is waiting for the wide screen it was chosen on. */\n .ap-side-toggle {\n display: none;\n }\n\n .ap-form {\n left: 8px;\n right: 8px;\n width: auto;\n max-width: calc(100vw - 16px);\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .ap-button {\n transition: none;\n }\n}\n\n/* The failure shows without opening the panel: the button's dot changes\n colour. A team that does not click must be able to see that something is\n wrong. */\n.ap-button.ap-failed .ap-button-dot {\n background: var(--ap-alert-text);\n}\n\n.ap-button.ap-failed {\n border-color: var(--ap-alert-border);\n}\n\n/* Signature reminder, in the note form.\n The name was shown at the foot of the panel only: invisible at the moment\n one writes. A user reported not knowing which name they were writing\n under. */\n.ap-form-signature {\n display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;\n margin: 0 0 .6rem; font-size: .85rem; opacity: .8;\n}\n\n/* Resolution state, said on the card.\n Two cases NOT to be confused: resolved and online, resolved but not\n deployed yet. The second keeps the defect on the reviewer's screen; hiding\n it or announcing it as fixed would cost them their trust in the tool. */\n.ap-state-mark {\n display: inline-block; margin: 0 0 .5rem;\n padding: .15rem .55rem; border-radius: 4px;\n font-size: .75rem; font-weight: 600; letter-spacing: .02em;\n}\n.ap-note.ap-resolved { opacity: .72; }\n.ap-note.ap-resolved .ap-state-mark {\n color: #0f7a52; background: rgba(16, 185, 129, .14);\n}\n.ap-note.ap-resolved-pending .ap-state-mark {\n color: #8a5a00; background: rgba(245, 158, 11, .16);\n}\n/* The \"it is fixed\" / \"reopen\" block, opened under the card. Same shape as\n the reply block: it is the same gesture, one answers a remark. */\n.ap-resolve,\n.ap-reply-form {\n margin-top: .6rem;\n padding-top: .6rem;\n border-top: 1px solid var(--ap-border);\n}\n\n/* The question asked before the key is dropped, at the foot of the list.\n Framed like the resolution block -- it is the same shape of gesture, one\n answers before something changes -- and set apart from the notes above it,\n because it is not about a note. */\n.ap-forget {\n margin-top: 1rem;\n padding-top: .6rem;\n border-top: 1px solid var(--ap-border);\n}\n\n.ap-forget .ap-actions {\n margin-top: .5rem;\n}\n\n.ap-history-toggle {\n display: block; width: 100%; margin: 1rem 0 .25rem;\n padding: .5rem .75rem; border: 1px dashed currentColor; border-radius: 6px;\n background: none; color: inherit; font: inherit; opacity: .7; cursor: pointer;\n}\n.ap-history-toggle:hover { opacity: 1; }\n\n\n/* ----------------------------------------------------------------------------\n Setup and pasting the salt.\n\n These are the only screens where something is copied by hand. Everything\n there is SELECTABLE and monospaced: a 43-character salt copied wrong\n cannot be recovered, and nothing helps less than a font that confuses I, l\n and 1.\n ---------------------------------------------------------------------------- */\n\n.ap-panel-wide {\n width: 560px;\n}\n\n.ap-copy {\n display: flex;\n align-items: flex-start;\n gap: 8px;\n margin: 0 0 12px;\n}\n\n.ap-code {\n flex: 1 1 auto;\n width: 100%;\n padding: 8px 10px;\n border: 1px solid var(--ap-border);\n border-radius: 8px;\n background: var(--ap-bg-soft);\n color: var(--ap-text);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\",\n monospace;\n font-size: 12.5px;\n line-height: 1.5;\n resize: vertical;\n white-space: pre;\n overflow-x: auto;\n}\n\n.ap-code:focus-visible {\n outline: 2px solid var(--ap-accent);\n outline-offset: 1px;\n}\n\n@media (max-width: 560px) {\n .ap-panel-wide {\n width: auto;\n }\n\n .ap-copy {\n flex-direction: column;\n }\n}\n";
|
|
22
22
|
|
|
23
23
|
/* ==== 00-preamble.js ==== */
|
|
24
24
|
|
|
@@ -559,16 +559,37 @@
|
|
|
559
559
|
+ 'can still get hold of the key before confirming: nobody, not even '
|
|
560
560
|
+ 'the server, can hand it back.',
|
|
561
561
|
|
|
562
|
-
/* --
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
562
|
+
/* -- WHICH OF THE TWO MODES THIS PROJECT RUNS IN ------------------
|
|
563
|
+
One word, drawn in BOTH modes, where the notes are and at every draw.
|
|
564
|
+
It replaces a paragraph that only the public mode ever showed: a
|
|
565
|
+
reviewer in secure mode had no way of telling which of the two they
|
|
566
|
+
were in, and a mention that appears in one case only is read as an
|
|
567
|
+
alarm rather than as a state.
|
|
568
|
+
|
|
569
|
+
The two words are the site's own: "Public" and "Secure", not a third
|
|
570
|
+
vocabulary invented here.
|
|
571
|
+
|
|
572
|
+
ENCRYPTION IS NOT ON THE BADGE. It holds in both modes, always, so
|
|
573
|
+
repeating it on screen at every draw says nothing that distinguishes
|
|
574
|
+
anything -- it belongs to the sentence one asks for, below.
|
|
575
|
+
|
|
576
|
+
Those sentences are the whole explanation, and they are reachable by
|
|
577
|
+
pointer AND by keyboard: the badge is not a button, it is focusable
|
|
578
|
+
so that the description can be asked for without a mouse. */
|
|
579
|
+
'mode.public': 'Public',
|
|
580
|
+
'mode.secure': 'Secure',
|
|
581
|
+
/* The write half is the one nobody expects -- the key gives both, and
|
|
582
|
+
this format has no reader-only role. Shortened from the paragraph it
|
|
583
|
+
replaces, and not one claim lighter. */
|
|
584
|
+
'mode.public_detail':
|
|
585
|
+
'End-to-end encrypted, and the key is in this page: anyone who can '
|
|
586
|
+
+ 'open the page can read these notes AND write them. The key gives '
|
|
587
|
+
+ 'both -- this format has no reader-only role.',
|
|
588
|
+
/* The mirror answer, which had never been written down anywhere: what
|
|
589
|
+
the other mode actually costs the server. */
|
|
590
|
+
'mode.secure_detail':
|
|
591
|
+
'End-to-end encrypted, and the key is not in this page: each browser '
|
|
592
|
+
+ 'pastes it once, and the server never receives it.',
|
|
572
593
|
|
|
573
594
|
/* -- A newer client exists, and this copy is not going to fetch it -
|
|
574
595
|
Shown ONLY when the file is served by the site itself: a copy served
|
|
@@ -2193,26 +2214,64 @@
|
|
|
2193
2214
|
return block;
|
|
2194
2215
|
};
|
|
2195
2216
|
|
|
2217
|
+
/**
|
|
2218
|
+
* WHICH MODE THIS PROJECT RUNS IN, SAID IN BOTH OF THEM.
|
|
2219
|
+
*
|
|
2220
|
+
* What was here before was a paragraph, drawn only when the key was in the
|
|
2221
|
+
* page. It had the right content and the wrong shape, twice over:
|
|
2222
|
+
*
|
|
2223
|
+
* - a reviewer in SECURE mode saw nothing at all, so the panel never told
|
|
2224
|
+
* them which of the two modes they were in. The one case that says
|
|
2225
|
+
* something is the case that is already the more alarming: silence was
|
|
2226
|
+
* doing the reassuring, and silence is also what a broken build looks
|
|
2227
|
+
* like;
|
|
2228
|
+
* - a paragraph that is on screen for ever gets skipped after the second
|
|
2229
|
+
* read. A word is read every time, because there is nothing to skip.
|
|
2230
|
+
*
|
|
2231
|
+
* So: one badge, same place, both modes, one word -- "Public" or "Secure",
|
|
2232
|
+
* the site's own two words and not a third vocabulary. The sentence that
|
|
2233
|
+
* explains it is asked for, and it is asked for BY POINTER OR BY KEYBOARD:
|
|
2234
|
+
* the badge takes focus (tabindex) although it is not a button and does
|
|
2235
|
+
* nothing when pressed, because a description reachable only by hovering is
|
|
2236
|
+
* a description half the people who need it cannot reach. The text is tied
|
|
2237
|
+
* to the badge with aria-describedby, so it is the badge's accessible
|
|
2238
|
+
* description whether or not it is on screen -- a native title= would be
|
|
2239
|
+
* neither of those things reliably.
|
|
2240
|
+
*
|
|
2241
|
+
* ENCRYPTION IS NOT ON THE BADGE. It is true in both modes, at all times, so
|
|
2242
|
+
* a word that appears in both says nothing about either. It belongs to the
|
|
2243
|
+
* sentence, which is where somebody asking "and what does that mean" is.
|
|
2244
|
+
*/
|
|
2245
|
+
const modeBadge = () => {
|
|
2246
|
+
const isPublic = PUBLIC_KEY;
|
|
2247
|
+
const block = create('div', 'ap-mode ' + (isPublic ? 'ap-mode-public' : 'ap-mode-secure'));
|
|
2248
|
+
|
|
2249
|
+
/* The id is drawn, because the panel is redrawn: two badges alive at the
|
|
2250
|
+
same instant during a redraw must not both answer to one id, or the
|
|
2251
|
+
description would resolve to the older one. Same trick, same reason as
|
|
2252
|
+
the name field above. */
|
|
2253
|
+
const id = 'ap-mode-' + Math.random().toString(36).slice(2, 8);
|
|
2254
|
+
|
|
2255
|
+
const chip = create('span', 'ap-mode-chip', T(isPublic ? 'mode.public' : 'mode.secure'));
|
|
2256
|
+
chip.setAttribute('role', 'note');
|
|
2257
|
+
chip.setAttribute('tabindex', '0');
|
|
2258
|
+
chip.setAttribute('aria-describedby', id);
|
|
2259
|
+
|
|
2260
|
+
const tip = create('span', 'ap-mode-tip',
|
|
2261
|
+
T(isPublic ? 'mode.public_detail' : 'mode.secure_detail'));
|
|
2262
|
+
tip.id = id;
|
|
2263
|
+
tip.setAttribute('role', 'tooltip');
|
|
2264
|
+
|
|
2265
|
+
block.appendChild(chip);
|
|
2266
|
+
block.appendChild(tip);
|
|
2267
|
+
return block;
|
|
2268
|
+
};
|
|
2269
|
+
|
|
2196
2270
|
const drawPanel = () => {
|
|
2197
2271
|
empty(ui.body);
|
|
2198
2272
|
empty(ui.footer);
|
|
2199
2273
|
|
|
2200
|
-
|
|
2201
|
-
where the notes are, at every draw and not once at startup.
|
|
2202
|
-
|
|
2203
|
-
It sits above everything else, failures included, because it is not an
|
|
2204
|
-
event: it describes what the notes underneath ARE. A one-off message
|
|
2205
|
-
at load time would be read by whoever happened to be looking, once,
|
|
2206
|
-
and by nobody who opens this panel a week later.
|
|
2207
|
-
|
|
2208
|
-
The half that has to survive being skim-read is the WRITE half: the
|
|
2209
|
-
key gives read and write, this format has no reader role, so a page
|
|
2210
|
-
anybody can open is a page anybody can post to. */
|
|
2211
|
-
if (PUBLIC_KEY) {
|
|
2212
|
-
const notice = create('div', 'ap-public', T('public.notice'));
|
|
2213
|
-
notice.setAttribute('role', 'note');
|
|
2214
|
-
ui.body.appendChild(notice);
|
|
2215
|
-
}
|
|
2274
|
+
ui.body.appendChild(modeBadge());
|
|
2216
2275
|
|
|
2217
2276
|
/* A NEWER CLIENT EXISTS, AND WE ARE NOT GOING TO FETCH IT. This copy is
|
|
2218
2277
|
served by the site itself -- somebody took the file off a CDN on
|
|
@@ -2220,7 +2279,7 @@
|
|
|
2220
2279
|
BY a CDN never gets here: it replaced itself before the panel existed.
|
|
2221
2280
|
|
|
2222
2281
|
Said at every draw and not once at load, for the same reason as the
|
|
2223
|
-
|
|
2282
|
+
badge above: a message shown at load time is read by whoever happened
|
|
2224
2283
|
to be looking, and by nobody who opens this panel a week later. */
|
|
2225
2284
|
if (upgradeAvailable) {
|
|
2226
2285
|
const notice = create('div', 'ap-upgrade',
|
|
@@ -2231,7 +2290,7 @@
|
|
|
2231
2290
|
|
|
2232
2291
|
/* THE SERVER AND THIS CLIENT DO NOT SPEAK THE SAME PROTOCOL NUMBER, and
|
|
2233
2292
|
that is a standing property of what is on screen, not an event: same
|
|
2234
|
-
register as the
|
|
2293
|
+
register as the badge and the notice above, same place, at every draw
|
|
2235
2294
|
(40-api, section 8bis).
|
|
2236
2295
|
|
|
2237
2296
|
Which of the two sentences is drawn is the whole asymmetry. A server
|
package/labels/fr.json
CHANGED
|
@@ -88,7 +88,10 @@
|
|
|
88
88
|
"key.replace": "Coller une autre clé",
|
|
89
89
|
"key.forget": "Oublier la clé sur ce navigateur",
|
|
90
90
|
"key.forget_confirm": "Ce navigateur cessera de retenir la clé de ce projet : l’outil la redemandera ici, et sur toutes les pages de ce site. Les notes ne sont pas touchées — elles restent sur le serveur, chiffrées, et reviennent dès que la clé est recollée. Assurez-vous de pouvoir encore vous procurer la clé avant de valider : personne, pas même le serveur, ne peut la redonner.",
|
|
91
|
-
"public
|
|
91
|
+
"mode.public": "Public",
|
|
92
|
+
"mode.secure": "Sécurisé",
|
|
93
|
+
"mode.public_detail": "Chiffré de bout en bout, et la clé est dans cette page : quiconque peut ouvrir la page peut lire ces notes ET en écrire. La clé donne les deux — ce format n’a pas de rôle lecteur seul.",
|
|
94
|
+
"mode.secure_detail": "Chiffré de bout en bout, et la clé n’est pas dans cette page : chaque navigateur la colle une fois, et le serveur ne la reçoit jamais.",
|
|
92
95
|
"upgrade.available": "Un client annotepage plus récent existe : {version}. Cette page exécute la version {current}, servie par le site lui-même — rien n’a été chargé pour la remplacer, et le moment de mettre à jour le fichier appartient au propriétaire.",
|
|
93
96
|
"format.server_newer": "Ce serveur parle le format annotepage {server} ; ce client parle le format {ours}. L’écriture est refusée depuis cette page tant que c’est le cas : une remarque scellée au format {ours} serait rangée dans une enveloppe que ce serveur ne sait pas relire, et rien n’est jamais supprimé dans cet outil. Les notes restent affichées, et ce qui n’a pas pu être lu est compté plus bas. Pour corriger : mettre à jour le fichier client annotepage chargé par cette page vers une version qui parle le format {server} — c’est la décision de la personne qui s’occupe du site.",
|
|
94
97
|
"format.server_older": "Ce serveur parle le format annotepage {server} ; ce client parle le format {ours}. Rien n’est bloqué : les notes de format {server} sont lues ici, et les remarques peuvent toujours être écrites. C’est le SERVEUR qui est en retard — demandez sa mise à jour vers le format {ours} à la personne qui s’en occupe.",
|
package/package.json
CHANGED