Merge remote-tracking branch 'origin/staging' into staging
This commit is contained in:
@@ -119,6 +119,11 @@
|
||||
--space-24: 96px;
|
||||
--composer-reserve: 148px;
|
||||
--ease-out: cubic-bezier(.22, 1, .36, 1);
|
||||
/* The personal report's paper surface. Declared here so the dark blocks can
|
||||
override it; see the note on .personal-report-reader. */
|
||||
--report-accent: #85432f;
|
||||
--report-paper: #f8f5ee;
|
||||
--report-rule: #c9c2b7;
|
||||
/* Hairlines and sheens that are dark-on-light here and light-on-dark there. */
|
||||
--ring-hairline: oklch(0 0 0 / .1);
|
||||
--sheen: rgba(255, 255, 255, .54);
|
||||
@@ -3169,9 +3174,10 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
scroll boundary because the chat shell intentionally locks html/body.
|
||||
============================================================ */
|
||||
.personal-report-reader {
|
||||
--report-accent: #85432f;
|
||||
--report-paper: #f8f5ee;
|
||||
--report-rule: #c9c2b7;
|
||||
/* The report palette lives on :root, not here. A custom property declared on
|
||||
this element wins over :root no matter what a media query says, so keeping
|
||||
it local made the theme override dead: dark mode kept the light paper while
|
||||
--color-ink went light with the theme, leaving white text on white paper. */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
@@ -3309,6 +3315,29 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* Paper is always paper. Without this, printing while the screen is in dark
|
||||
mode kept the light-forced backgrounds below but left every ink light,
|
||||
which prints as a blank page. */
|
||||
:root {
|
||||
color-scheme: light;
|
||||
--color-ink: #1d1d1f;
|
||||
--color-ink-strong: #32322f;
|
||||
--color-ink-secondary: #5f5f59;
|
||||
--color-ink-tertiary: #6a6963;
|
||||
--color-canvas: #fbfaf7;
|
||||
--color-canvas-soft: #f3f2ee;
|
||||
--color-border: #d8d6cf;
|
||||
--color-border-strong: #b8b5ad;
|
||||
--color-action: #85432f;
|
||||
--color-action-hover: #6f3627;
|
||||
--color-focus: #85432f;
|
||||
--color-danger: #9a2f2f;
|
||||
--color-success: #28633e;
|
||||
--color-warning: #b07b22;
|
||||
--report-accent: #85432f;
|
||||
--report-paper: #f8f5ee;
|
||||
--report-rule: #c9c2b7;
|
||||
}
|
||||
html, body { width: auto !important; height: auto !important; min-height: 0 !important; overflow: visible !important; background: #fff !important; }
|
||||
* { -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
|
||||
.personal-report-screen-only { display: none !important; }
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import test from "node:test";
|
||||
|
||||
const globalStyles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||||
|
||||
const luminance = (hex: string) => {
|
||||
const channel = (value: number) => {
|
||||
const c = value / 255;
|
||||
return c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
||||
};
|
||||
const [r, g, b] = [1, 3, 5].map((i) => parseInt(hex.slice(i, i + 2), 16));
|
||||
return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
|
||||
};
|
||||
const contrast = (a: string, b: string) => {
|
||||
const [hi, lo] = [luminance(a), luminance(b)].sort((x, y) => y - x);
|
||||
return (hi + 0.05) / (lo + 0.05);
|
||||
};
|
||||
|
||||
function blockTokens(opener: string): Map<string, string> {
|
||||
const start = globalStyles.indexOf(opener);
|
||||
assert.notEqual(start, -1, `missing block: ${opener}`);
|
||||
const end = globalStyles.indexOf("\n}", start);
|
||||
const body = globalStyles.slice(start, end);
|
||||
return new Map([...body.matchAll(/--([\w-]+)\s*:\s*([^;]+);/g)]
|
||||
.map((hit) => [hit[1], hit[2].trim()]));
|
||||
}
|
||||
|
||||
test("the report palette is declared on :root, never on the reader element", () => {
|
||||
// A custom property declared on .personal-report-reader wins over :root no
|
||||
// matter what a media query says. Keeping it local made the dark override
|
||||
// dead: the paper stayed light while --color-ink went light with the theme,
|
||||
// which is white text on white paper.
|
||||
const reader = globalStyles.slice(globalStyles.indexOf(".personal-report-reader {"));
|
||||
const readerBody = reader.slice(0, reader.indexOf("\n}"));
|
||||
assert.doesNotMatch(readerBody, /--report-(paper|rule|accent)\s*:/);
|
||||
for (const token of ["report-paper", "report-rule", "report-accent"]) {
|
||||
assert.ok(blockTokens(":root {\n color-scheme: light;").has(token), `--${token} must live on :root`);
|
||||
}
|
||||
});
|
||||
|
||||
test("report ink clears AA on report paper in both themes", () => {
|
||||
const light = blockTokens(":root {\n color-scheme: light;");
|
||||
const dark = blockTokens(':root[data-theme="dark"] {');
|
||||
for (const [name, palette] of [["light", light], ["dark", dark]] as const) {
|
||||
const paper = palette.get("report-paper")!;
|
||||
assert.match(paper, /^#[0-9a-f]{6}$/i);
|
||||
for (const ink of ["color-ink", "color-ink-strong", "color-ink-secondary", "color-ink-tertiary", "report-accent"]) {
|
||||
const value = palette.get(ink)!;
|
||||
const ratio = contrast(value, paper);
|
||||
assert.ok(
|
||||
ratio >= 4.5,
|
||||
`${name}: --${ink} (${value}) on --report-paper (${paper}) is ${ratio.toFixed(2)}:1`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("print pins the palette back to light so paper is always paper", () => {
|
||||
// Printing while the screen is dark forced white backgrounds but left every
|
||||
// ink light, which comes out of the printer blank.
|
||||
const print = globalStyles.slice(globalStyles.indexOf("@media print {"));
|
||||
assert.match(print, /:root \{\s*\n\s*color-scheme: light;/);
|
||||
const printRoot = blockTokens("@media print {\n /* Paper is always paper");
|
||||
for (const token of ["color-ink", "color-ink-strong", "report-paper", "report-accent"]) {
|
||||
assert.ok(printRoot.has(token), `@media print must reset --${token}`);
|
||||
}
|
||||
assert.equal(printRoot.get("color-ink"), "#1d1d1f");
|
||||
assert.equal(printRoot.get("report-paper"), "#f8f5ee");
|
||||
});
|
||||
Reference in New Issue
Block a user