@ox-content/vite-plugin 3.0.0-alpha.16 → 3.0.0-alpha.18

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.
Files changed (48) hide show
  1. package/dist/index.cjs +99 -69
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +23 -39
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +23 -39
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +38 -18
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/markdown-tables.d.cts +13 -16
  10. package/dist/markdown-tables.d.cts.map +1 -1
  11. package/dist/markdown-tables.d.mts +13 -16
  12. package/dist/markdown-tables.d.mts.map +1 -1
  13. package/dist/napi.cjs +44 -0
  14. package/dist/napi.cjs.map +1 -0
  15. package/dist/napi.mjs +34 -0
  16. package/dist/napi.mjs.map +1 -0
  17. package/dist/reader-chrome-client.cjs +103 -0
  18. package/dist/reader-chrome-client.d.cts +2 -0
  19. package/dist/reader-chrome-client.d.mts +2 -0
  20. package/dist/reader-chrome-client.mjs +102 -0
  21. package/dist/reader-chrome.cjs +109 -0
  22. package/dist/reader-chrome.cjs.map +1 -0
  23. package/dist/reader-chrome.d.cts +103 -0
  24. package/dist/reader-chrome.d.mts +103 -0
  25. package/dist/reader-chrome.mjs +98 -0
  26. package/dist/reader-chrome.mjs.map +1 -0
  27. package/dist/styles/all.css +2 -0
  28. package/dist/styles/core.css +53 -1
  29. package/dist/styles/reader-chrome.css +163 -0
  30. package/dist/styles/theme-transition.css +37 -0
  31. package/dist/styles/twitter-full.css +87 -13
  32. package/dist/theme-tokens.d.cts +18 -21
  33. package/dist/theme-tokens.d.cts.map +1 -1
  34. package/dist/theme-tokens.d.mts +18 -21
  35. package/dist/theme-tokens.d.mts.map +1 -1
  36. package/dist/theme-transition-client.cjs +161 -0
  37. package/dist/theme-transition-client.d.cts +21 -0
  38. package/dist/theme-transition-client.d.mts +21 -0
  39. package/dist/theme-transition-client.mjs +160 -0
  40. package/dist/twitter-client.cjs +84 -0
  41. package/dist/twitter-client.d.cts +11 -0
  42. package/dist/twitter-client.d.mts +11 -0
  43. package/dist/twitter-client.mjs +83 -0
  44. package/dist/vitepress.cjs +5 -43
  45. package/dist/vitepress.cjs.map +1 -1
  46. package/dist/vitepress.mjs +5 -31
  47. package/dist/vitepress.mjs.map +1 -1
  48. package/package.json +46 -4
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Per-control flags for `ssg.readerChrome`.
3
+ *
4
+ * Omitted fields stay on when the feature itself is enabled.
5
+ */
6
+ export interface ReaderChromeOptions {
7
+ /**
8
+ * Copy button on fenced code blocks. The clipboard is read in the browser,
9
+ * never at build time.
10
+ *
11
+ * @default true
12
+ */
13
+ copy?: boolean;
14
+
15
+ /**
16
+ * Icon and `rel="noopener noreferrer"` on outbound `http(s)` links.
17
+ * Relative, hash, and same-document links are left alone.
18
+ *
19
+ * @default true
20
+ */
21
+ externalLinks?: boolean;
22
+
23
+ /**
24
+ * Back-to-top control that appears after the page is scrolled.
25
+ *
26
+ * @default true
27
+ */
28
+ backToTop?: boolean;
29
+ }
30
+
31
+ /**
32
+ * Resolved reader chrome. `false` means no extra markup or JS.
33
+ */
34
+ export type ResolvedReaderChrome =
35
+ | false
36
+ | {
37
+ copy: boolean;
38
+ externalLinks: boolean;
39
+ backToTop: boolean;
40
+ };
41
+
42
+ type EnabledReaderChrome = Exclude<ResolvedReaderChrome, false>;
43
+
44
+ export type ReaderChromeInput =
45
+ | boolean
46
+ | ReaderChromeOptions
47
+ | EnabledReaderChrome
48
+ | undefined;
49
+
50
+ /**
51
+ * Resolve the public reader-chrome option shape.
52
+ */
53
+ export declare function resolveReaderChromeInput(
54
+ input?: ReaderChromeInput,
55
+ ): ResolvedReaderChrome;
56
+
57
+ /**
58
+ * Apply the same native reader-chrome HTML transform used by the built-in SSG.
59
+ */
60
+ export declare function applyReaderChromeHtml(
61
+ html: string,
62
+ input?: ReaderChromeInput,
63
+ ): string;
64
+
65
+ /**
66
+ * Root attributes consumed by the browser runtime.
67
+ */
68
+ export declare function readerChromeAttributes(
69
+ input?: ReaderChromeInput,
70
+ ): Readonly<Record<string, "">>;
71
+
72
+ /**
73
+ * Render the root attributes as a leading-space HTML fragment.
74
+ */
75
+ export declare function renderReaderChromeAttributes(input?: ReaderChromeInput): string;
76
+
77
+ /**
78
+ * CSS shared by the built-in SSG and custom hosts.
79
+ */
80
+ export declare function readerChromeCss(input?: ReaderChromeInput): string;
81
+
82
+ /**
83
+ * Auto-initializing script shared by the built-in SSG and custom hosts.
84
+ */
85
+ export declare function readerChromeScript(input?: ReaderChromeInput): string;
86
+
87
+ /**
88
+ * Inline stylesheet tag for hosts that do not import
89
+ * `@ox-content/vite-plugin/styles/reader-chrome.css`.
90
+ */
91
+ export declare function renderReaderChromeStyleTag(input?: ReaderChromeInput): string;
92
+
93
+ /**
94
+ * Inline auto-init script for static hosts that do not bundle
95
+ * `@ox-content/vite-plugin/reader-chrome/client`.
96
+ */
97
+ export declare function renderReaderChromeScriptTag(input?: ReaderChromeInput): string;
98
+
99
+ export declare function readerChromeIsEnabled(
100
+ chrome: ResolvedReaderChrome,
101
+ ): chrome is Exclude<ResolvedReaderChrome, false>;
102
+
103
+ export declare function readerChromeNeedsJs(chrome: ResolvedReaderChrome): boolean;
@@ -0,0 +1,98 @@
1
+ import { n as importNapiModuleSync } from "./napi.mjs";
2
+ //#region src/reader-chrome.ts
3
+ const EMPTY_ATTRIBUTES = Object.freeze({});
4
+ /**
5
+ * Resolve the public reader-chrome option shape.
6
+ *
7
+ * `true` mirrors `ssg.readerChrome: true`: copy, outbound-link decoration, and
8
+ * back-to-top are enabled. Object fields default on, so `{ copy: false }`
9
+ * disables only the copy button.
10
+ */
11
+ function resolveReaderChromeInput(input = true) {
12
+ if (!input) return false;
13
+ if (input === true) return {
14
+ copy: true,
15
+ externalLinks: true,
16
+ backToTop: true
17
+ };
18
+ return {
19
+ copy: input.copy ?? true,
20
+ externalLinks: input.externalLinks ?? true,
21
+ backToTop: input.backToTop ?? true
22
+ };
23
+ }
24
+ /**
25
+ * Apply the same native reader-chrome HTML transform used by the built-in SSG.
26
+ *
27
+ * This rewrites rendered article HTML. It wraps fenced `<pre>` blocks for copy
28
+ * controls and decorates outbound links when enabled. Back-to-top remains a
29
+ * document-shell control, so it is exposed through the asset helpers.
30
+ */
31
+ function applyReaderChromeHtml(html, input = true) {
32
+ const chrome = resolveReaderChromeInput(input);
33
+ if (!chrome || !chrome.copy && !chrome.externalLinks) return html;
34
+ return importNapiModuleSync().applySsgReaderChromeHtml(html, {
35
+ copy: chrome.copy,
36
+ externalLinks: chrome.externalLinks,
37
+ backToTop: chrome.backToTop
38
+ });
39
+ }
40
+ /**
41
+ * Root attributes consumed by the browser runtime.
42
+ */
43
+ function readerChromeAttributes(input = true) {
44
+ const chrome = resolveReaderChromeInput(input);
45
+ if (!readerChromeIsEnabled(chrome)) return EMPTY_ATTRIBUTES;
46
+ return {
47
+ "data-ox-reader-chrome": "",
48
+ ...chrome.copy ? { "data-ox-copy": "" } : {},
49
+ ...chrome.externalLinks ? { "data-ox-external-links": "" } : {},
50
+ ...chrome.backToTop ? { "data-ox-back-to-top": "" } : {}
51
+ };
52
+ }
53
+ /**
54
+ * Render the root attributes as a leading-space HTML fragment.
55
+ *
56
+ * Useful in template strings: `<article${renderReaderChromeAttributes()}>`.
57
+ */
58
+ function renderReaderChromeAttributes(input = true) {
59
+ return Object.keys(readerChromeAttributes(input)).map((name) => ` ${name}`).join("");
60
+ }
61
+ /**
62
+ * CSS shared by the built-in SSG and custom hosts.
63
+ */
64
+ function readerChromeCss(input = true) {
65
+ return readerChromeIsEnabled(resolveReaderChromeInput(input)) ? importNapiModuleSync().getSsgReaderChromeCss() : "";
66
+ }
67
+ /**
68
+ * Auto-initializing script shared by the built-in SSG and custom hosts.
69
+ */
70
+ function readerChromeScript(input = true) {
71
+ return readerChromeNeedsJs(resolveReaderChromeInput(input)) ? importNapiModuleSync().getSsgReaderChromeScript() : "";
72
+ }
73
+ /**
74
+ * Inline stylesheet tag for hosts that do not import
75
+ * `@ox-content/vite-plugin/styles/reader-chrome.css`.
76
+ */
77
+ function renderReaderChromeStyleTag(input = true) {
78
+ const css = readerChromeCss(input);
79
+ return css ? `<style data-ox-style="reader-chrome">\n${css}</style>` : "";
80
+ }
81
+ /**
82
+ * Inline auto-init script for static hosts that do not bundle
83
+ * `@ox-content/vite-plugin/reader-chrome/client`.
84
+ */
85
+ function renderReaderChromeScriptTag(input = true) {
86
+ const js = readerChromeScript(input);
87
+ return js ? `<script data-ox-script="reader-chrome">\n${js}<\/script>` : "";
88
+ }
89
+ function readerChromeIsEnabled(chrome) {
90
+ return Boolean(chrome && (chrome.copy || chrome.externalLinks || chrome.backToTop));
91
+ }
92
+ function readerChromeNeedsJs(chrome) {
93
+ return Boolean(chrome && (chrome.copy || chrome.backToTop));
94
+ }
95
+ //#endregion
96
+ export { applyReaderChromeHtml, readerChromeAttributes, readerChromeCss, readerChromeIsEnabled, readerChromeNeedsJs, readerChromeScript, renderReaderChromeAttributes, renderReaderChromeScriptTag, renderReaderChromeStyleTag, resolveReaderChromeInput };
97
+
98
+ //# sourceMappingURL=reader-chrome.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reader-chrome.mjs","names":[],"sources":["../src/reader-chrome.ts"],"sourcesContent":["import { importNapiModuleSync } from \"./napi\";\nimport type { ReaderChromeOptions, ResolvedReaderChrome } from \"./reader-chrome-options\";\n\nexport type { ReaderChromeOptions, ResolvedReaderChrome };\n\ntype EnabledReaderChrome = Exclude<ResolvedReaderChrome, false>;\n\nexport type ReaderChromeInput = boolean | ReaderChromeOptions | EnabledReaderChrome | undefined;\n\nconst EMPTY_ATTRIBUTES = Object.freeze({}) as Readonly<Record<string, \"\">>;\n\n/**\n * Resolve the public reader-chrome option shape.\n *\n * `true` mirrors `ssg.readerChrome: true`: copy, outbound-link decoration, and\n * back-to-top are enabled. Object fields default on, so `{ copy: false }`\n * disables only the copy button.\n */\nexport function resolveReaderChromeInput(input: ReaderChromeInput = true): ResolvedReaderChrome {\n if (!input) {\n return false;\n }\n if (input === true) {\n return { copy: true, externalLinks: true, backToTop: true };\n }\n return {\n copy: input.copy ?? true,\n externalLinks: input.externalLinks ?? true,\n backToTop: input.backToTop ?? true,\n };\n}\n\n/**\n * Apply the same native reader-chrome HTML transform used by the built-in SSG.\n *\n * This rewrites rendered article HTML. It wraps fenced `<pre>` blocks for copy\n * controls and decorates outbound links when enabled. Back-to-top remains a\n * document-shell control, so it is exposed through the asset helpers.\n */\nexport function applyReaderChromeHtml(html: string, input: ReaderChromeInput = true): string {\n const chrome = resolveReaderChromeInput(input);\n if (!chrome || (!chrome.copy && !chrome.externalLinks)) {\n return html;\n }\n return importNapiModuleSync().applySsgReaderChromeHtml(html, {\n copy: chrome.copy,\n externalLinks: chrome.externalLinks,\n backToTop: chrome.backToTop,\n });\n}\n\n/**\n * Root attributes consumed by the browser runtime.\n */\nexport function readerChromeAttributes(\n input: ReaderChromeInput = true,\n): Readonly<Record<string, \"\">> {\n const chrome = resolveReaderChromeInput(input);\n if (!readerChromeIsEnabled(chrome)) {\n return EMPTY_ATTRIBUTES;\n }\n\n return {\n \"data-ox-reader-chrome\": \"\",\n ...(chrome.copy ? { \"data-ox-copy\": \"\" as const } : {}),\n ...(chrome.externalLinks ? { \"data-ox-external-links\": \"\" as const } : {}),\n ...(chrome.backToTop ? { \"data-ox-back-to-top\": \"\" as const } : {}),\n };\n}\n\n/**\n * Render the root attributes as a leading-space HTML fragment.\n *\n * Useful in template strings: `<article${renderReaderChromeAttributes()}>`.\n */\nexport function renderReaderChromeAttributes(input: ReaderChromeInput = true): string {\n return Object.keys(readerChromeAttributes(input))\n .map((name) => ` ${name}`)\n .join(\"\");\n}\n\n/**\n * CSS shared by the built-in SSG and custom hosts.\n */\nexport function readerChromeCss(input: ReaderChromeInput = true): string {\n return readerChromeIsEnabled(resolveReaderChromeInput(input))\n ? importNapiModuleSync().getSsgReaderChromeCss()\n : \"\";\n}\n\n/**\n * Auto-initializing script shared by the built-in SSG and custom hosts.\n */\nexport function readerChromeScript(input: ReaderChromeInput = true): string {\n return readerChromeNeedsJs(resolveReaderChromeInput(input))\n ? importNapiModuleSync().getSsgReaderChromeScript()\n : \"\";\n}\n\n/**\n * Inline stylesheet tag for hosts that do not import\n * `@ox-content/vite-plugin/styles/reader-chrome.css`.\n */\nexport function renderReaderChromeStyleTag(input: ReaderChromeInput = true): string {\n const css = readerChromeCss(input);\n return css ? `<style data-ox-style=\"reader-chrome\">\\n${css}</style>` : \"\";\n}\n\n/**\n * Inline auto-init script for static hosts that do not bundle\n * `@ox-content/vite-plugin/reader-chrome/client`.\n */\nexport function renderReaderChromeScriptTag(input: ReaderChromeInput = true): string {\n const js = readerChromeScript(input);\n return js ? `<script data-ox-script=\"reader-chrome\">\\n${js}</script>` : \"\";\n}\n\nexport function readerChromeIsEnabled(\n chrome: ResolvedReaderChrome,\n): chrome is Exclude<ResolvedReaderChrome, false> {\n return Boolean(chrome && (chrome.copy || chrome.externalLinks || chrome.backToTop));\n}\n\nexport function readerChromeNeedsJs(chrome: ResolvedReaderChrome): boolean {\n return Boolean(chrome && (chrome.copy || chrome.backToTop));\n}\n"],"mappings":";;AASA,MAAM,mBAAmB,OAAO,OAAO,CAAC,CAAC;;;;;;;;AASzC,SAAgB,yBAAyB,QAA2B,MAA4B;CAC9F,IAAI,CAAC,OACH,OAAO;CAET,IAAI,UAAU,MACZ,OAAO;EAAE,MAAM;EAAM,eAAe;EAAM,WAAW;CAAK;CAE5D,OAAO;EACL,MAAM,MAAM,QAAQ;EACpB,eAAe,MAAM,iBAAiB;EACtC,WAAW,MAAM,aAAa;CAChC;AACF;;;;;;;;AASA,SAAgB,sBAAsB,MAAc,QAA2B,MAAc;CAC3F,MAAM,SAAS,yBAAyB,KAAK;CAC7C,IAAI,CAAC,UAAW,CAAC,OAAO,QAAQ,CAAC,OAAO,eACtC,OAAO;CAET,OAAO,qBAAqB,CAAC,CAAC,yBAAyB,MAAM;EAC3D,MAAM,OAAO;EACb,eAAe,OAAO;EACtB,WAAW,OAAO;CACpB,CAAC;AACH;;;;AAKA,SAAgB,uBACd,QAA2B,MACG;CAC9B,MAAM,SAAS,yBAAyB,KAAK;CAC7C,IAAI,CAAC,sBAAsB,MAAM,GAC/B,OAAO;CAGT,OAAO;EACL,yBAAyB;EACzB,GAAI,OAAO,OAAO,EAAE,gBAAgB,GAAY,IAAI,CAAC;EACrD,GAAI,OAAO,gBAAgB,EAAE,0BAA0B,GAAY,IAAI,CAAC;EACxE,GAAI,OAAO,YAAY,EAAE,uBAAuB,GAAY,IAAI,CAAC;CACnE;AACF;;;;;;AAOA,SAAgB,6BAA6B,QAA2B,MAAc;CACpF,OAAO,OAAO,KAAK,uBAAuB,KAAK,CAAC,CAAC,CAC9C,KAAK,SAAS,IAAI,MAAM,CAAC,CACzB,KAAK,EAAE;AACZ;;;;AAKA,SAAgB,gBAAgB,QAA2B,MAAc;CACvE,OAAO,sBAAsB,yBAAyB,KAAK,CAAC,IACxD,qBAAqB,CAAC,CAAC,sBAAsB,IAC7C;AACN;;;;AAKA,SAAgB,mBAAmB,QAA2B,MAAc;CAC1E,OAAO,oBAAoB,yBAAyB,KAAK,CAAC,IACtD,qBAAqB,CAAC,CAAC,yBAAyB,IAChD;AACN;;;;;AAMA,SAAgB,2BAA2B,QAA2B,MAAc;CAClF,MAAM,MAAM,gBAAgB,KAAK;CACjC,OAAO,MAAM,0CAA0C,IAAI,YAAY;AACzE;;;;;AAMA,SAAgB,4BAA4B,QAA2B,MAAc;CACnF,MAAM,KAAK,mBAAmB,KAAK;CACnC,OAAO,KAAK,4CAA4C,GAAG,cAAa;AAC1E;AAEA,SAAgB,sBACd,QACgD;CAChD,OAAO,QAAQ,WAAW,OAAO,QAAQ,OAAO,iBAAiB,OAAO,UAAU;AACpF;AAEA,SAAgB,oBAAoB,QAAuC;CACzE,OAAO,QAAQ,WAAW,OAAO,QAAQ,OAAO,UAAU;AAC5D"}
@@ -11,3 +11,5 @@
11
11
  @import "./graphviz.css";
12
12
  @import "./citations.css";
13
13
  @import "./not-by-ai.css";
14
+ @import "./reader-chrome.css";
15
+ @import "./theme-transition.css";
@@ -1963,11 +1963,52 @@ a:hover {
1963
1963
  .ox-breadcrumbs-item:not(:last-child)::after {
1964
1964
  margin-inline: 0.25rem;
1965
1965
  }
1966
+ /* The desktop rhythm scaled for a phone: body blocks 0.875rem apart, framed
1967
+ * blocks 1rem, headings still clearing more above than below. The steps keep
1968
+ * the same relationships, just sized for a 360-420px column. */
1966
1969
  .content h1 {
1967
1970
  font-size: 1.5rem;
1971
+ /* 1.08 is a display line-height for a title that never wraps. A mobile
1972
+ * title almost always wraps, and at 24px those lines collide. */
1973
+ line-height: 1.25;
1974
+ letter-spacing: -0.02em;
1975
+ margin-bottom: 0.75rem;
1968
1976
  }
1969
1977
  .content h2 {
1970
- font-size: 1.2rem;
1978
+ font-size: 1.25rem;
1979
+ margin-top: 2rem;
1980
+ margin-bottom: 0.6rem;
1981
+ }
1982
+ /* h2 was scaled down here and h3 was not, so an h3 rendered larger than the
1983
+ * h2 above it and the section hierarchy read backwards. */
1984
+ .content h3 {
1985
+ font-size: 1.0625rem;
1986
+ margin-top: 1.5rem;
1987
+ margin-bottom: 0.4rem;
1988
+ }
1989
+ .content h2 + h3 {
1990
+ margin-top: 1rem;
1991
+ }
1992
+ .content h4 {
1993
+ font-size: 0.9375rem;
1994
+ margin-top: 1.15rem;
1995
+ margin-bottom: 0.3rem;
1996
+ }
1997
+ .content p {
1998
+ margin-bottom: 0.875rem;
1999
+ }
2000
+ .content ul,
2001
+ .content ol {
2002
+ margin: 0.875rem 0;
2003
+ padding-left: 1.25rem;
2004
+ }
2005
+ .content blockquote,
2006
+ .content table,
2007
+ .content figure,
2008
+ /* The code wrapper is also styled by a sheet concatenated after this one, so
2009
+ * it needs the extra specificity to win. */
2010
+ .content .ox-code {
2011
+ margin-block: 1rem;
1971
2012
  }
1972
2013
  .content code {
1973
2014
  font-size: 0.82em;
@@ -2180,6 +2221,17 @@ a:hover {
2180
2221
  .content h1 {
2181
2222
  font-size: 1.35rem;
2182
2223
  }
2224
+ .content h2 {
2225
+ font-size: 1.15rem;
2226
+ margin-top: 1.75rem;
2227
+ }
2228
+ .content h3 {
2229
+ font-size: 1rem;
2230
+ margin-top: 1.25rem;
2231
+ }
2232
+ .content h4 {
2233
+ font-size: 0.9rem;
2234
+ }
2183
2235
  .content code {
2184
2236
  font-size: 0.8em;
2185
2237
  }
@@ -0,0 +1,163 @@
1
+ .ox-code {
2
+ --ox-copy-reserved-inline-size: max(
3
+ 2rem,
4
+ calc(var(--ox-copy-control-size, 1.75rem) + var(--ox-copy-inset, 0.5rem) - 0.25rem)
5
+ );
6
+ position: relative;
7
+ margin: 1.25rem 0;
8
+ }
9
+ .content .ox-code > pre,
10
+ .content .ox-code:has(ox-code-play) pre {
11
+ margin: 0;
12
+ padding-inline-end: calc(var(--ox-copy-reserved-inline-size) + 0.45rem);
13
+ }
14
+ .ox-code:has(> ox-code-play) .ox-code-play__toolbar {
15
+ padding-inline-end: calc(var(--ox-copy-reserved-inline-size) + 0.65rem);
16
+ }
17
+ .content .ox-code > pre[data-code-title]::before {
18
+ margin-inline-end: calc(-1 * (var(--ox-copy-reserved-inline-size) + 0.45rem));
19
+ padding-inline-end: calc(var(--ox-copy-reserved-inline-size) + 0.45rem);
20
+ }
21
+ .ox-copy {
22
+ position: absolute;
23
+ inset-block-start: var(--ox-copy-inset, 0.5rem);
24
+ inset-inline-end: var(--ox-copy-inset, 0.5rem);
25
+ z-index: 1;
26
+ display: grid;
27
+ width: var(--ox-copy-control-size, 1.75rem);
28
+ height: var(--ox-copy-control-size, 1.75rem);
29
+ padding: 0;
30
+ place-items: center;
31
+ border: 1px solid transparent;
32
+ border-radius: 4px;
33
+ background: transparent;
34
+ color: color-mix(in srgb, var(--octc-color-code-text) 70%, transparent);
35
+ cursor: pointer;
36
+ opacity: 0.68;
37
+ transition:
38
+ opacity 0.15s ease,
39
+ color 0.15s ease,
40
+ border-color 0.15s ease,
41
+ background-color 0.15s ease;
42
+ }
43
+ .ox-code > .ox-copy {
44
+ z-index: 2;
45
+ min-width: 0;
46
+ min-height: 0;
47
+ width: var(--ox-copy-control-size, 1.75rem);
48
+ height: var(--ox-copy-control-size, 1.75rem);
49
+ padding: 0;
50
+ font: inherit;
51
+ line-height: 1;
52
+ }
53
+ .ox-copy::before {
54
+ content: "";
55
+ width: var(--ox-copy-icon-size, 0.8125rem);
56
+ height: var(--ox-copy-icon-size, 0.8125rem);
57
+ background-color: currentColor;
58
+ -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='9' y='9' width='11' height='11' rx='2'/><path d='M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3'/></svg>")
59
+ center / contain no-repeat;
60
+ mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='9' y='9' width='11' height='11' rx='2'/><path d='M15 9V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v7a2 2 0 0 0 2 2h3'/></svg>")
61
+ center / contain no-repeat;
62
+ }
63
+ .ox-copy:hover,
64
+ .ox-copy:focus-visible {
65
+ color: var(--octc-color-code-text);
66
+ border-color: color-mix(
67
+ in srgb,
68
+ var(--octc-color-primary) 42%,
69
+ var(--octc-color-code-frame-border)
70
+ );
71
+ background: color-mix(in srgb, var(--octc-color-code-bg) 82%, var(--octc-color-bg) 18%);
72
+ opacity: 0.96;
73
+ }
74
+ .ox-copy[data-copy-state="copied"] {
75
+ color: var(--octc-color-primary);
76
+ border-color: color-mix(in srgb, var(--octc-color-primary) 36%, transparent);
77
+ background: color-mix(in srgb, var(--octc-color-code-bg) 86%, var(--octc-color-bg) 14%);
78
+ opacity: 0.96;
79
+ }
80
+ .ox-copy[data-copy-state="copied"]::before {
81
+ -webkit-mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><path d='m5 12 4 4L19 6'/></svg>");
82
+ mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'><path d='m5 12 4 4L19 6'/></svg>");
83
+ }
84
+ .ox-copy[data-copy-state="failed"] {
85
+ color: var(--octc-color-code-line-error-border);
86
+ }
87
+ .ox-copy-status {
88
+ position: absolute;
89
+ width: 1px;
90
+ height: 1px;
91
+ padding: 0;
92
+ margin: -1px;
93
+ overflow: hidden;
94
+ clip: rect(0, 0, 0, 0);
95
+ clip-path: inset(50%);
96
+ white-space: nowrap;
97
+ border: 0;
98
+ }
99
+ @media (any-hover: hover) and (any-pointer: fine) {
100
+ .ox-copy {
101
+ opacity: 0;
102
+ pointer-events: none;
103
+ }
104
+ .ox-code:hover > .ox-copy,
105
+ .ox-code:focus-within > .ox-copy {
106
+ opacity: 0.86;
107
+ pointer-events: auto;
108
+ }
109
+ }
110
+ .ox-external {
111
+ text-decoration-thickness: from-font;
112
+ }
113
+ .ox-external-icon {
114
+ display: inline-block;
115
+ width: 0.7em;
116
+ height: 0.7em;
117
+ margin-inline-start: 0.2em;
118
+ vertical-align: middle;
119
+ background-color: currentColor;
120
+ -webkit-mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/><polyline points='15 3 21 3 21 9'/><line x1='10' y1='14' x2='21' y2='3'/></svg>")
121
+ center / contain no-repeat;
122
+ mask: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6'/><polyline points='15 3 21 3 21 9'/><line x1='10' y1='14' x2='21' y2='3'/></svg>")
123
+ center / contain no-repeat;
124
+ }
125
+ .content .ox-magic-link .ox-external-icon {
126
+ width: 0.62em;
127
+ height: 0.62em;
128
+ margin-inline-start: 0.02em;
129
+ opacity: 0.72;
130
+ flex: 0 0 auto;
131
+ }
132
+ .ox-back-to-top {
133
+ position: fixed;
134
+ right: 1.25rem;
135
+ bottom: 5.5rem;
136
+ z-index: 8;
137
+ padding: 0.45rem 0.75rem;
138
+ border: 1px solid color-mix(in srgb, var(--octc-color-border) 80%, transparent);
139
+ border-radius: 999px;
140
+ background: color-mix(in srgb, var(--octc-color-bg) 92%, transparent);
141
+ color: var(--octc-color-text);
142
+ font: inherit;
143
+ font-size: 0.75rem;
144
+ cursor: pointer;
145
+ transition:
146
+ opacity 0.2s ease,
147
+ transform 0.2s ease;
148
+ }
149
+ .ox-back-to-top[hidden] {
150
+ display: none;
151
+ }
152
+ .ox-back-to-top:hover,
153
+ .ox-back-to-top:focus-visible {
154
+ color: var(--octc-color-primary);
155
+ border-color: var(--octc-color-primary);
156
+ }
157
+ @media (prefers-reduced-motion: reduce) {
158
+ .ox-reader-chrome--reduced-motion .ox-back-to-top,
159
+ .ox-back-to-top,
160
+ .ox-copy {
161
+ transition: none;
162
+ }
163
+ }
@@ -0,0 +1,37 @@
1
+ /* Same-document colour-scheme change. Every rule is scoped to an attribute the
2
+ runtime sets for the length of one theme toggle, so none of it can reach the
3
+ cross-document `@view-transition` used for MPA navigation — that transition
4
+ needs the UA cross-fade left alone. */
5
+
6
+ /* The circle is drawn by clipping one snapshot over the other, so the UA
7
+ cross-fade has to stop first: two half-opacity snapshots would show the clip
8
+ through the layer beneath it. */
9
+ :root[data-ox-theme-transition]::view-transition-old(root),
10
+ :root[data-ox-theme-transition]::view-transition-new(root) {
11
+ animation: none;
12
+ mix-blend-mode: normal;
13
+ }
14
+
15
+ /* Whichever snapshot the circle is drawn on has to sit on top of the other. */
16
+ :root[data-ox-theme-transition="expand"]::view-transition-old(root) {
17
+ z-index: 1;
18
+ }
19
+ :root[data-ox-theme-transition="expand"]::view-transition-new(root) {
20
+ z-index: 2;
21
+ }
22
+ :root[data-ox-theme-transition="shrink"]::view-transition-old(root) {
23
+ z-index: 2;
24
+ }
25
+ :root[data-ox-theme-transition="shrink"]::view-transition-new(root) {
26
+ z-index: 1;
27
+ }
28
+
29
+ /* Element-level colour transitions would animate underneath the snapshot and
30
+ bleed the old palette into the new one. The runtime holds this attribute
31
+ only while the theme is mutated, and drops it once both snapshots exist. */
32
+ :root[data-ox-theme-transition-suppress],
33
+ :root[data-ox-theme-transition-suppress] *,
34
+ :root[data-ox-theme-transition-suppress] *::before,
35
+ :root[data-ox-theme-transition-suppress] *::after {
36
+ transition: none !important;
37
+ }
@@ -25,6 +25,10 @@
25
25
  margin: var(--ox-tweet-container-margin);
26
26
  }
27
27
 
28
+ .ox-tweet.ox-tweet--full * {
29
+ box-sizing: border-box;
30
+ }
31
+
28
32
  .ox-tweet.ox-tweet--full :where(img, video, figure, picture, svg) {
29
33
  margin: 0;
30
34
  }
@@ -39,8 +43,13 @@
39
43
  }
40
44
 
41
45
  .ox-tweet.ox-tweet--full :where(a) {
46
+ padding: 0;
47
+ font-size: inherit;
42
48
  font-weight: inherit;
49
+ line-height: inherit;
43
50
  text-decoration: none;
51
+ background: transparent;
52
+ border: 0;
44
53
  }
45
54
 
46
55
  .ox-tweet.ox-tweet--full :where(code, pre, kbd, samp) {
@@ -114,7 +123,14 @@
114
123
  --ox-tweet-body-line-height: 1.5rem;
115
124
  --ox-tweet-quoted-body-font-size: 0.938rem;
116
125
  --ox-tweet-info-font-size: 0.9375rem;
117
- --ox-tweet-actions-font-size: 0.875rem;
126
+ --ox-tweet-actions-font-size: 1rem;
127
+ --ox-tweet-actions-line-height: 1.25rem;
128
+ --ox-tweet-actions-font-weight: 700;
129
+ --ox-tweet-actions-icon-size: 1.25em;
130
+ --ox-tweet-actions-icon-wrapper-size: 2rem;
131
+ --ox-tweet-replies-font-size: 0.875rem;
132
+ --ox-tweet-replies-line-height: 1rem;
133
+ --ox-tweet-replies-font-weight: 700;
118
134
  --ox-tweet-icon-size: 1.25em;
119
135
  --ox-tweet-border: 1px solid rgb(207, 217, 222);
120
136
  --ox-tweet-font-color: rgb(15, 20, 25);
@@ -133,9 +149,10 @@
133
149
  --ox-tweet-verified-gold: rgb(226, 179, 64);
134
150
  --ox-tweet-verified-gray: rgb(130, 154, 171);
135
151
  --ox-tweet-icon-x: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.727-8.83L1.254 2.25H8.08l4.257 5.622L18.244 2.25zm-1.161 17.52h1.833L7.084 4.126H5.117z'/%3E%3C/svg%3E");
136
- --ox-tweet-icon-like: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 21.638h-.014C9.403 21.59 1.95 14.856 1.95 8.478c0-3.064 2.525-5.754 5.403-5.754 2.29 0 3.83 1.58 4.646 2.73.814-1.148 2.354-2.73 4.645-2.73 2.88 0 5.404 2.69 5.404 5.755 0 6.376-7.454 13.11-10.037 13.157H12z'/%3E%3C/svg%3E");
137
- --ox-tweet-icon-reply: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M1.751 10c0-4.42 3.584-8 8.005-8h4.366c4.49 0 8.129 3.64 8.129 8.13 0 2.96-1.607 5.68-4.196 7.11l-8.054 4.46v-3.69h-.067c-4.49.1-8.183-3.51-8.183-8.01zm8.005-6c-3.317 0-6.005 2.69-6.005 6 0 3.37 2.77 6.08 6.138 6.01l.351-.01h1.761v2.3l5.087-2.81C17.04 14.42 18.25 12.37 18.25 10.14c0-3.39-2.744-6.13-6.129-6.13H9.756z'/%3E%3C/svg%3E");
138
- --ox-tweet-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M7 7.75A2.75 2.75 0 0 1 9.75 5h8.5A2.75 2.75 0 0 1 21 7.75v8.5A2.75 2.75 0 0 1 18.25 19h-8.5A2.75 2.75 0 0 1 7 16.25v-8.5zM9.75 7A.75.75 0 0 0 9 7.75v8.5c0 .41.34.75.75.75h8.5c.41 0 .75-.34.75-.75v-8.5a.75.75 0 0 0-.75-.75h-8.5zM3 4.75A2.75 2.75 0 0 1 5.75 2h8.5a1 1 0 1 1 0 2h-8.5a.75.75 0 0 0-.75.75v8.5a1 1 0 1 1-2 0v-8.5z'/%3E%3C/svg%3E");
152
+ --ox-tweet-icon-like: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M20.884 13.19c-1.351 2.48-4.001 5.12-8.379 7.67l-.503.3-.504-.3c-4.379-2.55-7.029-5.19-8.382-7.67-1.36-2.5-1.41-4.86-.514-6.67.887-1.79 2.647-2.91 4.601-3.01 1.651-.09 3.368.56 4.798 2.01 1.429-1.45 3.146-2.1 4.796-2.01 1.954.1 3.714 1.22 4.601 3.01.896 1.81.846 4.17-.514 6.67z'/%3E%3C/svg%3E");
153
+ --ox-tweet-icon-reply: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M1.751 10c0-4.42 3.584-8 8.005-8h4.366c4.49 0 8.129 3.64 8.129 8.13 0 2.96-1.607 5.68-4.196 7.11l-8.054 4.46v-3.69h-.067c-4.49.1-8.183-3.51-8.183-8.01z'/%3E%3C/svg%3E");
154
+ --ox-tweet-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M18.36 5.64c-1.95-1.96-5.11-1.96-7.07 0L9.88 7.05 8.46 5.64l1.42-1.42c2.73-2.73 7.16-2.73 9.9 0 2.73 2.74 2.73 7.17 0 9.9l-1.42 1.42-1.41-1.42 1.41-1.41c1.96-1.96 1.96-5.12 0-7.07zm-2.12 3.53l-7.07 7.07-1.41-1.41 7.07-7.07 1.41 1.41zm-12.02.71l1.42-1.42 1.41 1.42-1.41 1.41c-1.96 1.96-1.96 5.12 0 7.07 1.95 1.96 5.11 1.96 7.07 0l1.41-1.41 1.42 1.41-1.42 1.42c-2.73 2.73-7.16 2.73-9.9 0-2.73-2.74-2.73-7.17 0-9.9z'/%3E%3C/svg%3E");
155
+ --ox-tweet-icon-check: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M9.64 18.952l-5.55-4.861 1.317-1.504 3.951 3.459 8.459-10.948L19.4 6.32 9.64 18.952z'/%3E%3C/svg%3E");
139
156
  --ox-tweet-icon-info: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M13.5 8.5c0 .83-.67 1.5-1.5 1.5s-1.5-.67-1.5-1.5S11.17 7 12 7s1.5.67 1.5 1.5zM13 17v-5h-2v5h2zm-1 5.25c5.66 0 10.25-4.59 10.25-10.25S17.66 1.75 12 1.75 1.75 6.34 1.75 12 6.34 22.25 12 22.25zM20.25 12c0 4.56-3.69 8.25-8.25 8.25S3.75 16.56 3.75 12 7.44 3.75 12 3.75s8.25 3.69 8.25 8.25z'/%3E%3C/svg%3E");
140
157
  --ox-tweet-icon-badge: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.34 2.19c-1.4-.46-2.91-.2-3.91.81s-1.27 2.52-.81 3.91c-1.31.67-2.19 1.91-2.19 3.34s.88 2.67 2.19 3.34c-.46 1.39-.2 2.9.81 3.91s2.52 1.27 3.91.81c.67 1.31 1.91 2.19 3.34 2.19s2.67-.88 3.34-2.19c1.4.46 2.91.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z'/%3E%3C/svg%3E");
141
158
  box-sizing: border-box;
@@ -300,6 +317,11 @@
300
317
  mask-image: var(--ox-tweet-icon-copy);
301
318
  }
302
319
 
320
+ .ox-tweet--full .ox-tweet__action--copy[data-ox-tweet-copied] .ox-tweet__icon--copy {
321
+ -webkit-mask-image: var(--ox-tweet-icon-check);
322
+ mask-image: var(--ox-tweet-icon-check);
323
+ }
324
+
303
325
  .ox-tweet--full .ox-tweet__icon--info {
304
326
  -webkit-mask-image: var(--ox-tweet-icon-info);
305
327
  mask-image: var(--ox-tweet-icon-info);
@@ -453,22 +475,50 @@
453
475
  .ox-tweet--full .ox-tweet__action {
454
476
  display: flex;
455
477
  align-items: center;
456
- margin-right: 1.25rem;
478
+ min-width: var(--ox-tweet-actions-icon-wrapper-size);
479
+ min-height: var(--ox-tweet-actions-icon-wrapper-size);
480
+ padding: 0;
481
+ margin: 0 1.25rem 0 0;
457
482
  font-size: var(--ox-tweet-actions-font-size);
458
- font-weight: 700;
459
- line-height: 1rem;
483
+ font-weight: var(--ox-tweet-actions-font-weight);
484
+ line-height: var(--ox-tweet-actions-line-height);
485
+ background: transparent;
486
+ border: 0;
487
+ }
488
+
489
+ .ox-tweet--full .ox-tweet__action-icon {
490
+ box-sizing: border-box;
491
+ display: flex;
492
+ flex: 0 0 var(--ox-tweet-actions-icon-wrapper-size);
493
+ align-items: center;
494
+ justify-content: center;
495
+ width: var(--ox-tweet-actions-icon-wrapper-size);
496
+ height: var(--ox-tweet-actions-icon-wrapper-size);
497
+ margin: 0 0.25rem 0 -0.25rem;
498
+ border-radius: 9999px;
460
499
  }
461
500
 
462
501
  .ox-tweet--full .ox-tweet__action .ox-tweet__icon {
463
- margin-right: 0.25rem;
502
+ width: var(--ox-tweet-actions-icon-size);
503
+ height: var(--ox-tweet-actions-icon-size);
464
504
  }
465
505
 
466
- .ox-tweet--full .ox-tweet__action--like:hover {
506
+ .ox-tweet--full .ox-tweet__action--like:hover .ox-tweet__action-text {
467
507
  color: var(--ox-tweet-color-red);
508
+ text-decoration-line: underline;
468
509
  }
469
510
 
470
- .ox-tweet--full .ox-tweet__action--reply:hover {
511
+ .ox-tweet--full .ox-tweet__action--like:hover .ox-tweet__action-icon {
512
+ background-color: var(--ox-tweet-color-red-hover);
513
+ }
514
+
515
+ .ox-tweet--full .ox-tweet__action--reply:hover .ox-tweet__action-text {
471
516
  color: var(--ox-tweet-color-blue-text);
517
+ text-decoration-line: underline;
518
+ }
519
+
520
+ .ox-tweet--full .ox-tweet__action--reply:hover .ox-tweet__action-icon {
521
+ background-color: var(--ox-tweet-color-blue-hover);
472
522
  }
473
523
 
474
524
  .ox-tweet--full .ox-tweet__action--copy:hover,
@@ -476,10 +526,33 @@
476
526
  color: var(--ox-tweet-color-green);
477
527
  }
478
528
 
529
+ .ox-tweet--full .ox-tweet__action--copy:hover .ox-tweet__action-icon,
530
+ .ox-tweet--full .ox-tweet__action--copy[data-ox-tweet-copied] .ox-tweet__action-icon {
531
+ background-color: var(--ox-tweet-color-green-hover);
532
+ }
533
+
534
+ .ox-tweet--full .ox-tweet__action--copy:hover .ox-tweet__action-text,
535
+ .ox-tweet--full .ox-tweet__action--copy[data-ox-tweet-copied] .ox-tweet__action-text {
536
+ color: var(--ox-tweet-color-green);
537
+ text-decoration-line: underline;
538
+ }
539
+
479
540
  .ox-tweet--full .ox-tweet__copied-text {
480
541
  display: none;
481
542
  }
482
543
 
544
+ .ox-tweet--full .ox-tweet__copy-status {
545
+ position: absolute;
546
+ width: 1px;
547
+ height: 1px;
548
+ padding: 0;
549
+ margin: -1px;
550
+ overflow: hidden;
551
+ clip: rect(0, 0, 0, 0);
552
+ white-space: nowrap;
553
+ border: 0;
554
+ }
555
+
483
556
  .ox-tweet--full .ox-tweet__action--copy[data-ox-tweet-copied] .ox-tweet__copy-text {
484
557
  display: none;
485
558
  }
@@ -490,7 +563,7 @@
490
563
 
491
564
  .ox-tweet--full .ox-tweet__replies {
492
565
  padding: 0.25rem 0;
493
- margin: 0.5rem 0 0;
566
+ margin: 0;
494
567
  }
495
568
 
496
569
  .ox-tweet--full .ox-tweet__replies-link {
@@ -501,8 +574,9 @@
501
574
  width: 100%;
502
575
  min-height: 32px;
503
576
  padding: 0 1rem;
504
- font-size: 0.875rem;
505
- font-weight: 700;
577
+ font-size: var(--ox-tweet-replies-font-size);
578
+ font-weight: var(--ox-tweet-replies-font-weight);
579
+ line-height: var(--ox-tweet-replies-line-height);
506
580
  color: var(--ox-tweet-color-blue-text);
507
581
  border: var(--ox-tweet-border);
508
582
  border-radius: 9999px;