1- import { describe , expect , test } from "bun:test" ;
1+ import { afterEach , describe , expect , test } from "bun:test" ;
2+ import { rgbToHex , type CapturedSpan } from "@opentui/core" ;
3+ import { defined } from "../../tests/helpers/defined.js" ;
24
35import {
46 LOCKUP_FADE_MS ,
@@ -13,7 +15,9 @@ import {
1315 STALL_BLINK_CYCLE_MS ,
1416 type RampPhase ,
1517} from "./ramp" ;
16- import { UI } from "./theme" ;
18+ import { withTestRenderer } from "./harness" ;
19+ import { createAppShell } from "./shell/index" ;
20+ import { corbitsLight , setTheme , UI } from "./theme" ;
1721
1822const idle = ( nowMs : number ) : LockupInput => ( {
1923 nowMs,
@@ -40,6 +44,28 @@ const live = (
4044
4145const still = ( nowMs = 0 ) => lockupCells ( idle ( nowMs ) ) ;
4246
47+ function colorHex ( color : CapturedSpan [ "fg" ] ) : string {
48+ return rgbToHex ( color ) . toLowerCase ( ) . slice ( 0 , 7 ) ;
49+ }
50+
51+ function luminance ( hex : string ) : number {
52+ const channel = ( offset : number ) : number => {
53+ const value = Number . parseInt ( hex . slice ( offset , offset + 2 ) , 16 ) / 255 ;
54+ return value <= 0.03928 ? value / 12.92 : ( ( value + 0.055 ) / 1.055 ) ** 2.4 ;
55+ } ;
56+ return 0.2126 * channel ( 1 ) + 0.7152 * channel ( 3 ) + 0.0722 * channel ( 5 ) ;
57+ }
58+
59+ function contrast ( foreground : string , background : string ) : number {
60+ const fg = luminance ( foreground ) ;
61+ const bg = luminance ( background ) ;
62+ return ( Math . max ( fg , bg ) + 0.05 ) / ( Math . min ( fg , bg ) + 0.05 ) ;
63+ }
64+
65+ afterEach ( ( ) => {
66+ setTheme ( "corbits-dark" ) ;
67+ } ) ;
68+
4369describe ( "brand lockup" , ( ) => {
4470 test ( "idle is the wordmark alone" , ( ) => {
4571 const cells = still ( ) ;
@@ -69,6 +95,32 @@ describe("brand lockup", () => {
6995 expect ( lockupWidth ( input ) ) . toBe ( lockupCells ( input ) . length ) ;
7096 } ) ;
7197
98+ test ( "light wordmark renders with readable paint-time chrome" , async ( ) => {
99+ setTheme ( "corbits-light" ) ;
100+ await withTestRenderer ( async ( h ) => {
101+ const shell = createAppShell ( h . renderer , {
102+ terminal : { columns : 80 , rows : 24 } ,
103+ wireKeys : false ,
104+ } ) ;
105+ try {
106+ await h . renderOnce ( ) ;
107+ await h . renderOnce ( ) ;
108+ const wordmark = h
109+ . captureSpans ( )
110+ . lines . flatMap ( ( line ) => line . spans )
111+ . find ( ( span ) => span . text . includes ( LOCKUP_WORDMARK ) ) ;
112+ const painted = defined ( wordmark , "rendered wordmark span" ) ;
113+ const foreground = colorHex ( painted . fg ) ;
114+ const background = colorHex ( painted . bg ) ;
115+ expect ( foreground ) . toBe ( corbitsLight . textDim ) ;
116+ expect ( background ) . toBe ( corbitsLight . ground ) ;
117+ expect ( contrast ( foreground , background ) ) . toBeGreaterThanOrEqual ( 4.5 ) ;
118+ } finally {
119+ shell . dispose ( ) ;
120+ }
121+ } ) ;
122+ } ) ;
123+
72124 test ( "the wordmark stays chrome-dim" , ( ) => {
73125 for ( const cell of still ( ) ) {
74126 expect ( cell . fg ) . toBe ( UI . textDim ) ;
0 commit comments