···11+// the css side of pierre theming lives in src/pierre.css
22+33+import {
44+ getSharedHighlighter,
55+ registerCustomCSSVariableTheme,
66+ type FileDiffOptions,
77+ type FileOptions
88+} from "@pierre/diffs";
99+import { browser } from "$app/environment";
1010+1111+// pierre's two diff layouts, threaded from the ?diff= url param down to the
1212+// FileDiff instances
1313+export type DiffStyle = NonNullable<FileDiffOptions<undefined>["diffStyle"]>;
1414+1515+// pierre's theming hook is a registered shiki css-variable theme. the token
1616+// colors come from --diffs-token-* properties in pierre.css, they flip
1717+// with the media query. the name is pinned by app.css's @utility or tailwind
1818+// tree-shakes those rules away
1919+export const PIERRE_THEME_NAME = "tangled";
2020+2121+registerCustomCSSVariableTheme(PIERRE_THEME_NAME, {
2222+ // pierre.css defines the real tokens on diffs-container, these are just
2323+ // fallbacks for the generated bindings
2424+ background: "#ffffff",
2525+ foreground: "#111827",
2626+ "token-comment": "#6b7280",
2727+ "token-string": "#16a34a",
2828+ "token-string-expression": "#16a34a",
2929+ "token-keyword": "#c084fc",
3030+ "token-function": "#60a5fa",
3131+ "token-constant": "#f59e0b",
3232+ "token-parameter": "#111827",
3333+ "token-punctuation": "#4b5563",
3434+ "token-link": "#4338ca",
3535+ "token-inserted": "#16a34a",
3636+ "token-deleted": "#dc2626",
3737+ "token-changed": "#d97706"
3838+});
3939+4040+// pierre skips highlighting lines past 1e3 chars by default.
4141+// lockfiles however do not care...
4242+export const PIERRE_TOKENIZE_MAX_LINE_LENGTH = 5000;
4343+4444+// "line-info" separators carry pierre's "N unmodified lines" text, bare
4545+// "simple" ones get a "···"
4646+export const PIERRE_SEPARATOR_CSS =
4747+ // flush against our own file header, pierre's is disabled
4848+ ":host{--diffs-gap-block:0}" +
4949+ // overflow auto so the bottom scrollbar only exists when a line overflows
5050+ ":host{--diffs-overflow-override:auto}" +
5151+ "[data-separator]{block-size:21px;font-size:14px;line-height:21px;" +
5252+ "color:var(--diffs-fg-number-override,#9ca3af)}" +
5353+ // kill pierre's block gap, padding and rounded chip, the separator reads
5454+ // as one full-width row
5555+ '[data-separator="line-info"]{margin-block:0}' +
5656+ '[data-unified] [data-separator="line-info"] [data-separator-wrapper]{padding-inline:0}' +
5757+ '[data-gutter] [data-separator="line-info"] [data-separator-wrapper]{padding-left:0}' +
5858+ // pierre full-widths the unified separator and the split additions bar but
5959+ // not the split deletions one, that one carries the count text and clips in
6060+ // the 44px gutter
6161+ '[data-deletions] [data-gutter] [data-separator="line-info"] [data-separator-wrapper]{width:100cqi}' +
6262+ '[data-separator="line-info"] [data-separator-content]{border-radius:0}' +
6363+ '[data-separator="line-info"] [data-expand-button]{border-radius:0}' +
6464+ '[data-additions] [data-gutter] [data-separator="line-info"] [data-separator-wrapper]{border-radius:0}' +
6565+ "[data-separator-content]{justify-content:center}" +
6666+ '[data-separator="simple"]{display:flex;align-items:center;justify-content:center}' +
6767+ '[data-separator="simple"]::after{content:"···"}';
6868+6969+export const PIERRE_BASE_OPTIONS = {
7070+ theme: PIERRE_THEME_NAME,
7171+ themeType: "system",
7272+ tokenizeMaxLineLength: PIERRE_TOKENIZE_MAX_LINE_LENGTH
7373+} as const;
7474+7575+// shared by the components and the ssr preloads, the prerendered markup is
7676+// only adoptable when both sides build the same options
7777+export const pierreDiffOptions = (style: DiffStyle): FileDiffOptions<undefined> => ({
7878+ ...PIERRE_BASE_OPTIONS,
7979+ diffStyle: style,
8080+ diffIndicators: "classic",
8181+ lineDiffType: "word-alt",
8282+ // "line-info" separators size the bar to span the whole diff.
8383+ // "line-info-basic" never gets that, its count text clips in the 44px
8484+ // gutter cell
8585+ hunkSeparators: "line-info",
8686+ disableFileHeader: true,
8787+ overflow: "scroll",
8888+ unsafeCSS: PIERRE_SEPARATOR_CSS
8989+});
9090+9191+export const pierreFileOptions = (wrap: boolean): FileOptions<undefined> => ({
9292+ ...PIERRE_BASE_OPTIONS,
9393+ disableFileHeader: true,
9494+ overflow: wrap ? "wrap" : "scroll"
9595+});
9696+9797+// a renderer with a missing language paints plain text first and swaps in
9898+// the highlight, but only once the shared highlighter is up. before that the
9999+// first render waits on the shiki import and paints nothing, so warm it here
100100+if (browser) void getSharedHighlighter({ themes: [PIERRE_THEME_NAME], langs: [] });
···11+/* pierre's -override properties must land on the diffs-container element
22+ itself, its :host stylesheet beats inheriting from a wrapper. these rules
33+ must stay UNLAYERED: pierre injects its :host stylesheet unlayered, which
44+ beats anything in @layer. the doubled class lifts specificity above it
55+ regardless of order */
66+77+diffs-container.diffs-container {
88+ --diffs-font-family: var(--font-mono);
99+ --diffs-font-size: 0.875rem;
1010+ --diffs-line-height: 1.5;
1111+ /* the "tangled" theme (pierre.ts) binds --diffs-bg/--diffs-fg to these and
1212+ forces color-scheme: dark. without them the bg chain goes
1313+ guaranteed-invalid and light-dark() is stuck on the dark branch */
1414+ --diffs-background: var(--color-background-default);
1515+ --diffs-foreground: var(--color-foreground-default);
1616+ color-scheme: light dark;
1717+ --diffs-light-bg: var(--color-background-default);
1818+ --diffs-dark-bg: var(--color-background-default);
1919+ --diffs-bg-separator-override: var(--color-background-inset);
2020+ --diffs-fg-number-override: var(--color-foreground-placeholder);
2121+2222+ /* pierre blends line backgrounds 88/12 (light) toward these, so they must
2323+ be the saturated tokens. the *-color overrides drive the indicators and
2424+ number gutter */
2525+ --diffs-bg-addition-override: var(--color-foreground-success);
2626+ --diffs-bg-deletion-override: var(--color-foreground-danger);
2727+ --diffs-addition-color-override: var(--color-foreground-success-strong);
2828+ --diffs-deletion-color-override: var(--color-foreground-danger-strong);
2929+3030+ /* syntax palette for the "tangled" theme registered in pierre.ts */
3131+ --diffs-token-comment: var(--color-foreground-placeholder);
3232+ --diffs-token-string: var(--color-foreground-success);
3333+ --diffs-token-string-expression: var(--color-foreground-success);
3434+ --diffs-token-keyword: #c084fc;
3535+ --diffs-token-function: #60a5fa;
3636+ --diffs-token-constant: #f59e0b;
3737+ --diffs-token-parameter: var(--color-foreground-default);
3838+ --diffs-token-punctuation: var(--color-foreground-muted);
3939+ --diffs-token-link: var(--color-foreground-info);
4040+ --diffs-token-inserted: var(--color-foreground-success);
4141+ --diffs-token-deleted: var(--color-foreground-danger);
4242+ --diffs-token-changed: var(--color-foreground-warning);
4343+}
4444+4545+@media (prefers-color-scheme: dark) {
4646+ diffs-container.diffs-container {
4747+ --diffs-fg-number-override: var(--color-foreground-subtle);
4848+4949+ /* the dark blend is 80/20, hence the subtle backgrounds */
5050+ --diffs-bg-addition-override: var(--color-background-success-subtle);
5151+ --diffs-bg-deletion-override: var(--color-background-danger-subtle);
5252+ --diffs-addition-color-override: var(--color-foreground-success);
5353+ --diffs-deletion-color-override: var(--color-foreground-danger);
5454+ }
5555+}