/**
* OPTICS hairline (Axiom 15): a divider means "one device pixel", not "one
* CSS pixel". On high-DPI web screens a 1px CSS divider paints 2–3 device
* pixels and reads heavy; 0.5px paints a single device pixel on 2x displays.
*
* Engine notes (probed against Chromium 147 / WebKit / Gecko):
* - Blink floors sub-1px BORDER widths up to 1 CSS px, but honors fractional
* HEIGHT/WIDTH — so standalone rules use the filled `line`/`vline`
* treatments (crisp everywhere), while container-owned edge borders
* (`bottom`/`top`/`right`/`left`) render true hairlines on WebKit/Gecko
* and gracefully stay 1px on Blink.
* - Low-DPI screens (1dppx) get a 1px fallback via the resolution media
* query below, so hairlines never fade or vanish there.
*
* Scope: DIVIDERS ONLY — separators between rows/sections, menu separators,
* table head rules, full-bleed panel rules. Control borders (inputs,
* buttons, control groups) keep the knob-driven `borderWidth` semantics
* from `resolveKnobs` and MUST NOT use this token.
*
* Native twin: `hairline.native.ts` (react-native StyleSheet.hairlineWidth,
* no CSS classes).
*/
/** Divider line width: half a CSS pixel = one device pixel on 2x displays. */
export const hairlineWidth: number = 0.5;
/**
* Spreadable divider treatments. Each entry pairs the 0.5px style with the
* fallback class the low-DPI media query targets:
*
* // horizontal rule element
* // vertical rule element
* // already-filled 1px line
* // container-owned edge rule
*/
export const hairline = {
/**
* Standalone horizontal rule as a FILLED line (tamagui Separator draws a
* border, which Blink cannot paint below 1 CSS px — flip it to a filled
* fractional-height line, crisp in every engine). Consumers may override
* `backgroundColor` after the spread for non-default divider colors.
*/
line: {
height: hairlineWidth,
maxHeight: hairlineWidth,
// Separator is a `flex: 1` item, so in a stack the main-axis size comes
// from flex-basis (height alone is ignored):
flexBasis: hairlineWidth,
borderBottomWidth: 0,
backgroundColor: "$borderColor",
className: "mp-hairline-h",
},
/** Standalone vertical rule as a filled line (see `line`). */
vline: {
width: hairlineWidth,
maxWidth: hairlineWidth,
flexBasis: hairlineWidth,
borderRightWidth: 0,
backgroundColor: "$borderColor",
className: "mp-hairline-w",
},
/** Already-filled 1px line elements (e.g. menu separator views). */
height: { height: hairlineWidth, className: "mp-hairline-h" },
// Container-owned edge rules. True hairline on WebKit/Gecko; Blink rounds
// sub-1px borders up to 1 CSS px (the pre-hairline status quo there).
bottom: { borderBottomWidth: hairlineWidth, className: "mp-hairline-b" },
top: { borderTopWidth: hairlineWidth, className: "mp-hairline-t" },
right: { borderRightWidth: hairlineWidth, className: "mp-hairline-r" },
left: { borderLeftWidth: hairlineWidth, className: "mp-hairline-l" },
} as const;
const STYLE_TAG_ID = "mp-hairline-styles";
/**
* 1px fallback for screens where 0.5px cannot map to a whole device pixel.
* (`-webkit-max-device-pixel-ratio` covers Safari versions without
* `resolution` media-query support.) Exported for unit tests.
*/
export const hairlineFallbackCss = `@media (max-resolution: 1.49dppx), (-webkit-max-device-pixel-ratio: 1.49) {
.mp-hairline-b { border-bottom-width: 1px !important; }
.mp-hairline-t { border-top-width: 1px !important; }
.mp-hairline-r { border-right-width: 1px !important; }
.mp-hairline-l { border-left-width: 1px !important; }
.mp-hairline-h { height: 1px !important; max-height: 1px !important; flex-basis: 1px !important; }
.mp-hairline-w { width: 1px !important; max-width: 1px !important; flex-basis: 1px !important; }
.mp-hairline-ie { border-inline-end-width: 1px !important; }
}`;
/** Idempotently mount the low-DPI fallback stylesheet (no-op off-DOM). */
export function ensureHairlineFallback(): void {
if (typeof document === "undefined") return;
if (document.getElementById(STYLE_TAG_ID)) return;
const tag = document.createElement("style");
tag.id = STYLE_TAG_ID;
tag.textContent = hairlineFallbackCss;
document.head.appendChild(tag);
}
// The fallback is static CSS (no knob/theme dependency), so mount it as soon
// as the module loads in a DOM environment.
ensureHairlineFallback();