diff --git a/crates/codegen/kigi-tui/assets/logo/logo04.txt b/crates/codegen/kigi-tui/assets/logo/logo04.txt deleted file mode 100644 index 33f5cab..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo04.txt +++ /dev/null @@ -1,4 +0,0 @@ -⠀⠀⣠⣤⢤⡄⢠⠀ -⠀⣼⠃⠀⢠⠖⢿⠀ -⠀⢻⠄⠀⠀⢀⡾⠀ -⠀⠁⠈⠛⠛⠉⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo05.txt b/crates/codegen/kigi-tui/assets/logo/logo05.txt deleted file mode 100644 index 45701a3..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo05.txt +++ /dev/null @@ -1,5 +0,0 @@ -⠀⠀⠀⣀⣤⣤⣀⠀⠀⡠ -⠀⢀⡾⠋⠁⠀⢁⢴⡎⠀ -⠀⢸⡇⠀⠀⠐⠁⢀⣿⠀ -⠀⢈⠗⢀⣀⣀⣠⡾⠃⠀ -⠐⠁⠀⠈⠉⠉⠉⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo06.txt b/crates/codegen/kigi-tui/assets/logo/logo06.txt deleted file mode 100644 index cdeb7c2..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo06.txt +++ /dev/null @@ -1,6 +0,0 @@ -⠀⠀⠀⠀⢀⣀⣀⣀⠀⠀⣠⠀ -⠀⠀⣠⡾⠛⠉⠉⠉⢁⣴⡇⠀ -⠀⢠⣿⠁⠀⠀⢀⠔⠉⢻⡇⠀ -⠀⠈⣿⡀⠀⠀⠁⠀⠀⣼⠇⠀ -⠀⢀⠟⠁⣤⣤⣤⣤⡾⠋⠀⠀ -⠀⠀⠀⠀⠀⠉⠉⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo07.txt b/crates/codegen/kigi-tui/assets/logo/logo07.txt deleted file mode 100644 index ef39cca..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo07.txt +++ /dev/null @@ -1,7 +0,0 @@ -⠀⠀⠀⠀⠀⠀⣀⣀⡀⠀⠀⠀⢀⠄ -⠀⠀⠀⣠⣾⠿⠛⠛⠛⠛⢀⡴⠁⠀ -⠀⠀⣼⡟⠁⠀⠀⠀⢀⡴⠻⣿⡀⠀ -⠀⠀⣿⡇⠀⠀⠀⠔⠁⠀⠀⣿⡇⠀ -⠀⠀⢹⣷⠀⠀⠀⠀⠀⢀⣴⡿⠀⠀ -⠀⢀⠞⠁⠠⢶⣶⣶⣶⠿⠋⠀⠀⠀ -⠐⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo08.txt b/crates/codegen/kigi-tui/assets/logo/logo08.txt deleted file mode 100644 index 721b095..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo08.txt +++ /dev/null @@ -1,8 +0,0 @@ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀ -⠀⠀⠀⠀⣠⣶⡿⠿⠿⠿⠷⠂⢀⣼⠁⠀ -⠀⠀⢀⣾⡟⠁⠀⠀⠀⠀⢀⣴⣿⣿⠀⠀ -⠀⠀⣸⣿⠀⠀⠀⠀⢀⠔⠋⠀⢸⣿⠀⠀ -⠀⠀⢸⣿⡀⠀⠀⠐⠁⠀⠀⠀⣸⣿⠀⠀ -⠀⠀⠈⣿⠗⠀⠀⠀⠀⠀⣀⣴⣿⠃⠀⠀ -⠀⢀⠜⠁⠀⠲⠿⣿⣿⡿⠿⠋⠁⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo09.txt b/crates/codegen/kigi-tui/assets/logo/logo09.txt deleted file mode 100644 index 5199ff5..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo09.txt +++ /dev/null @@ -1,9 +0,0 @@ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀ -⠀⠀⠀⠀⠀⣠⣶⣾⣿⣿⣿⣶⠦⠀⢀⣼⠁⠀ -⠀⠀⠀⣠⣾⡿⠋⠁⠀⠀⠀⠀⢀⣴⣿⡇⠀⠀ -⠀⠀⢠⣿⡟⠀⠀⠀⠀⠀⢀⡴⠛⠙⣿⣧⠀⠀ -⠀⠀⢸⣿⡇⠀⠀⠀⢀⠔⠉⠀⠀⠀⣿⣿⠀⠀ -⠀⠀⠈⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⣠⣿⡏⠀⠀ -⠀⠀⠀⣿⠟⠁⢀⣀⣀⣀⣀⣠⣶⣿⠏⠀⠀⠀ -⠀⢀⠜⠁⠀⠈⠛⠿⠿⠿⠿⠟⠋⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo10.txt b/crates/codegen/kigi-tui/assets/logo/logo10.txt deleted file mode 100644 index 22bab9d..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo10.txt +++ /dev/null @@ -1,10 +0,0 @@ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀ -⠀⠀⠀⠀⠀⠀⣠⣴⣶⣶⣶⣶⣶⣤⡀⠀⢀⡼⠁⠀ -⠀⠀⠀⠀⣰⣿⡿⠛⠉⠉⠉⠉⠉⠁⢀⣴⣿⡇⠀⠀ -⠀⠀⠀⣼⣿⠏⠀⠀⠀⠀⠀⠀⢀⣴⠿⢿⣿⡇⠀⠀ -⠀⠀⠀⣿⣿⠀⠀⠀⠀⠀⢀⡴⠋⠁⠀⢸⣿⣇⠀⠀ -⠀⠀⠀⣿⣿⠀⠀⠀⠀⠔⠁⠀⠀⠀⠀⢸⣿⡇⠀⠀ -⠀⠀⠀⢹⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⡿⠁⠀⠀ -⠀⠀⠀⣸⠟⠁⣀⣠⣤⣤⣀⣤⣴⣾⣿⠟⠁⠀⠀⠀ -⠀⠀⠞⠁⠀⠀⠈⠙⠛⠻⠿⠛⠛⠉⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo12.txt b/crates/codegen/kigi-tui/assets/logo/logo12.txt deleted file mode 100644 index 142ad5d..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo12.txt +++ /dev/null @@ -1,12 +0,0 @@ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⣤⣴⣶⣤⣤⣄⡀⠀⠀⠀⢀⡼⠁⠀ -⠀⠀⠀⠀⠀⢀⣴⣾⣿⡿⠿⠛⠛⠻⠿⠟⠛⠁⢀⣴⣿⠁⠀⠀ -⠀⠀⠀⠀⢠⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣏⠀⠀⠀ -⠀⠀⠀⢠⣿⣿⠋⠀⠀⠀⠀⠀⠀⠀⢀⣴⠿⠛⢻⣿⣿⠀⠀⠀ -⠀⠀⠀⢸⣿⡟⠀⠀⠀⠀⠀⠀⢀⡴⠛⠁⠀⠀⠈⣿⣿⡆⠀⠀ -⠀⠀⠀⢸⣿⣷⠀⠀⠀⠀⢀⠔⠉⠀⠀⠀⠀⠀⢠⣿⣿⠃⠀⠀ -⠀⠀⠀⠈⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⡟⠀⠀⠀ -⠀⠀⠀⠀⣸⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⣀⣴⣿⣿⠟⠀⠀⠀⠀ -⠀⠀⠀⣰⠟⠁⠀⠠⣶⣿⣿⣷⣶⣾⣿⣿⡿⠟⠁⠀⠀⠀⠀⠀ -⠀⡠⠊⠁⠀⠀⠀⠀⠀⠈⠉⠉⠛⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo16.txt b/crates/codegen/kigi-tui/assets/logo/logo16.txt deleted file mode 100644 index 5e272e1..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo16.txt +++ /dev/null @@ -1,16 +0,0 @@ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡜⠁⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⠀⠀⠀⢀⣴⡟⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⡿⠿⠛⠛⠛⠛⠛⠿⠟⠋⠁⠀⢀⣴⣿⣿⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⡇⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⣰⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⣿⣿⣿⣧⠀⠀⠀⠀ -⠀⠀⠀⠀⢠⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⠛⠁⠀⠘⣿⣿⣿⡀⠀⠀⠀ -⠀⠀⠀⠀⢸⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠟⠉⠀⠀⠀⠀⠀⣿⣿⣿⡇⠀⠀⠀ -⠀⠀⠀⠀⢸⣿⣿⣇⠀⠀⠀⠀⠀⠀⢀⠴⠋⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⠇⠀⠀⠀ -⠀⠀⠀⠀⠀⣿⣿⣿⡄⠀⠀⠀⠀⠐⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⡿⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠸⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⠃⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⢰⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣿⣿⣿⡟⠁⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⢀⡾⠛⠁⠀⠀⣤⣶⣿⣿⣿⣶⣶⣶⣾⣿⣿⣿⣿⠟⠋⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⢀⡴⠋⠀⠀⠀⠀⠀⠀⠉⠛⠻⠿⠿⠿⠿⠿⠟⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠠⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo20.txt b/crates/codegen/kigi-tui/assets/logo/logo20.txt deleted file mode 100644 index 472e1f2..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo20.txt +++ /dev/null @@ -1,20 +0,0 @@ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡜⠁⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣤⣶⣶⣶⣶⣶⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠏⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡦⠀⠀⠀⢀⣴⣿⠏⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⡿⠿⠛⠋⠉⠉⠉⠙⠛⠻⠛⠉⠀⠀⠀⢀⣴⣿⣿⡿⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⡏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠟⠉⠻⣿⣿⣿⣿⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⢀⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⠟⠉⠀⠀⠀⠀⢿⣿⣿⣿⡄⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⢸⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠿⠋⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⡇⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠸⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⡇⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⢀⠔⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⠁⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠸⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⠏⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⠆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣴⣾⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⣼⡿⠋⠁⠀⠀⣀⣤⣶⣿⣿⣿⣶⣶⣶⣶⣶⣿⣿⣿⣿⣿⣿⡿⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⢠⡾⠋⠀⠀⠀⠀⠀⠈⠙⠻⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⢀⠔⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/assets/logo/logo24.txt b/crates/codegen/kigi-tui/assets/logo/logo24.txt deleted file mode 100644 index fa59bab..0000000 --- a/crates/codegen/kigi-tui/assets/logo/logo24.txt +++ /dev/null @@ -1,24 +0,0 @@ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡜⠁⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠏⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣄⡀⠀⠀⠀⠀⠀⠀⢀⣴⣿⠃⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠓⠀⠀⠀⢀⣴⣿⣿⠇⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⡿⠿⠛⠋⠉⠉⠉⠉⠉⠉⠛⠛⠛⠉⠀⠀⠀⠀⢀⣴⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⡿⠿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⠟⠋⠀⠀⠈⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⡿⠋⠁⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⡿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⢀⠴⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⠃⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠐⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⡿⠟⠁⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⢠⣿⡿⠋⠀⠀⠀⠀⣀⣤⣴⣾⣿⣿⣷⣶⣶⣶⣶⣶⣶⣿⣿⣿⣿⣿⣿⣿⡿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⣴⡿⠋⠀⠀⠀⠀⠀⠈⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠻⠿⠿⠿⠿⠿⠿⠛⠛⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⣠⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠚⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ -⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ diff --git a/crates/codegen/kigi-tui/src/views/welcome/logo.rs b/crates/codegen/kigi-tui/src/views/welcome/logo.rs index da2fc19..0286024 100644 --- a/crates/codegen/kigi-tui/src/views/welcome/logo.rs +++ b/crates/codegen/kigi-tui/src/views/welcome/logo.rs @@ -1,4 +1,13 @@ -//! Logo component — renders the braille art logo. +//! Logo component — a procedurally generated braille moon that waxes and +//! wanes through a full lunation, echoing the Kimi CLI's moon-phase spinner. +//! +//! The disc is rasterized into the 2×4 dot grid of braille cells (dots are +//! close to square in a typical terminal cell), so the moon stays round at +//! any size and needs no art assets. The lit region follows the standard +//! phase model: the terminator is the ellipse `x = cos(2πp)·√(1−y²)`, with +//! sunlight arriving from the right while waxing and from the left while +//! waning. The dark limb keeps a faint outline ring so the silhouette never +//! disappears at new moon. //! //! Hidden entirely on legacy Windows consoles: the U+2800 braille block is //! not covered by the ConHost raster fonts and would render as tofu. @@ -12,51 +21,75 @@ use ratatui::widgets::{Paragraph, Widget}; use crate::render::color::blend_color; use crate::theme::Theme; -const LOGO: &str = include_str!("../../../assets/logo/logo07.txt"); -const LOGO_SMALL: &str = include_str!("../../../assets/logo/logo05.txt"); +/// Full-size moon (hero box and tall stacked layouts), in braille cells. +const FULL_COLS: u16 = 20; +const FULL_ROWS: u16 = 10; +/// Small moon for short windows, in braille cells. +const SMALL_COLS: u16 = 10; +const SMALL_ROWS: u16 = 5; -/// Height at or above which the small logo is shown (below it, no logo). +/// Height at or above which the small moon is shown (below it, no logo). const SMALL_LOGO_MIN_HEIGHT: u16 = 22; -/// Height at or above which the full logo is shown. +/// Height at or above which the full moon is shown. const FULL_LOGO_MIN_HEIGHT: u16 = 26; -fn pick_logo(window_height: u16) -> Option<&'static str> { +/// Seconds for one full lunation (new → full → new). Deliberately slow — +/// the welcome screen should breathe, not blink. +const LUNATION_SECS: f32 = 8.0; + +/// Redraw cadence in frames per second. The terminator moves slowly, so a +/// modest rate looks smooth while sparing the long-lived welcome screen +/// from full-rate repaints. +const MOON_FPS: f32 = 12.0; + +/// Brightness of lit dots (blend factor from the resting gray toward the +/// bright text color). +const LIT_OPACITY: f32 = 0.85; +/// Extra breathing applied to lit dots. +const PULSE: f32 = 0.10; +/// Breathing period in seconds. +const PULSE_SECS: f32 = 5.0; +/// Squared inner radius (normalized) of the dark-limb outline ring. +const RING_INNER_SQ: f32 = 0.82; + +/// One logo size tier, in braille cells. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +struct MoonSize { + cols: u16, + rows: u16, +} + +const FULL: MoonSize = MoonSize { + cols: FULL_COLS, + rows: FULL_ROWS, +}; +const SMALL: MoonSize = MoonSize { + cols: SMALL_COLS, + rows: SMALL_ROWS, +}; + +fn pick_logo(window_height: u16) -> Option { pick_logo_for(window_height, logo_hidden()) } /// Pure tier selection so tests can drive the legacy-console flag directly. -fn pick_logo_for(window_height: u16, hidden: bool) -> Option<&'static str> { +fn pick_logo_for(window_height: u16, hidden: bool) -> Option { if hidden || window_height < SMALL_LOGO_MIN_HEIGHT { None } else if window_height < FULL_LOGO_MIN_HEIGHT { - Some(LOGO_SMALL) + Some(SMALL) } else { - Some(LOGO) + Some(FULL) } } -/// The braille art has no ASCII stand-in; see the module doc. +/// The braille moon has no ASCII stand-in; see the module doc. fn logo_hidden() -> bool { crate::glyphs::is_legacy_windows_console() } -fn non_empty_lines(logo: &str) -> impl Iterator { - logo.lines().filter(|l| !l.is_empty()) -} - -fn count_lines(logo: &str) -> u16 { - non_empty_lines(logo).count() as u16 -} - -fn visual_width(logo: &str) -> u16 { - non_empty_lines(logo) - .map(unicode_width::UnicodeWidthStr::width) - .max() - .unwrap_or(24) as u16 -} - /// Animation phase in seconds since the first render. Wall-clock based so the -/// shimmer speed is independent of the frame rate. +/// lunation speed is independent of the frame rate. fn anim_phase_secs() -> f32 { use std::sync::OnceLock; use std::time::Instant; @@ -64,78 +97,115 @@ fn anim_phase_secs() -> f32 { START.get_or_init(Instant::now).elapsed().as_secs_f32() } -/// Shimmer redraw cadence in frames per second. The sweep is slow, so a few fps -/// looks smooth while sparing the long-lived welcome screen from full-rate -/// repaints. -const SHIMMER_FPS: f32 = 12.0; - -/// Quantized shimmer frame for the current wall-clock phase. The welcome screen -/// redraws only when this advances, throttling the animation to ~`SHIMMER_FPS` -/// rather than the full event-loop tick rate. Pinned to 0 when the logo is -/// hidden. +/// Quantized animation frame for the current wall-clock phase. The welcome +/// screen redraws only when this advances, throttling the animation to +/// ~[`MOON_FPS`] rather than the full event-loop tick rate. Pinned to 0 when +/// the logo is hidden. pub fn shimmer_frame() -> u64 { if logo_hidden() { return 0; } - (anim_phase_secs() * SHIMMER_FPS) as u64 + (anim_phase_secs() * MOON_FPS) as u64 } -/// Per-glyph shine opacity in `[0, 1]` at normalized diagonal position `diag` -/// (0 = bottom-left .. 1 = top-right) and animation time `secs`. A raised-cosine -/// band sweeps bottom-left → top-right and parks off-screen between sweeps; a -/// gentle global pulse breathes underneath it. 0 keeps the resting gray, 1 is -/// full bright. -fn shine_opacity(diag: f32, secs: f32) -> f32 { - const BAND: f32 = 0.38; // half-width of the shine band — wider = more gradual falloff - const CYCLE: f32 = 4.0; // seconds per sweep + rest - const SWEEP_FRAC: f32 = 0.32; // portion of the cycle spent sweeping (~1.3s glint, rest idles) - const SHINE: f32 = 0.33; // peak shine strength - const PULSE: f32 = 0.06; // global breathing amount - const PULSE_SECS: f32 = 5.0; // breathing period +/// Lunation phase in `[0, 1)`: 0 = new moon, 0.5 = full moon. +fn phase_now() -> f32 { + (anim_phase_secs() / LUNATION_SECS).fract() +} - let p = (secs % CYCLE) / CYCLE; - let q = (p / SWEEP_FRAC).min(1.0); // parks the band off-screen during the rest - let band_pos = -BAND + q * (1.0 + 2.0 * BAND); - let pulse = PULSE * (0.5 - 0.5 * (std::f32::consts::TAU * secs / PULSE_SECS).cos()); - - let d = (diag - band_pos).abs(); - let shine = if d < BAND { - 0.5 * (1.0 + (std::f32::consts::PI * d / BAND).cos()) +/// Whether the normalized disc point `(dx, dy)` is sunlit at phase `p`. +/// +/// `x_edge = √(1−dy²)` is the disc edge at that height; the terminator sits +/// at `k·x_edge` with `k = cos(2πp)`. Waxing light grows from the right, +/// waning light retreats to the left. +fn dot_lit(dx: f32, dy: f32, p: f32) -> bool { + let k = (std::f32::consts::TAU * p).cos(); + let x_edge = (1.0 - dy * dy).max(0.0).sqrt(); + if p < 0.5 { + dx >= k * x_edge } else { - 0.0 - }; - (pulse + SHINE * shine).clamp(0.0, 1.0) + dx <= -k * x_edge + } } -fn render_into(area: Rect, buf: &mut Buffer, theme: &Theme, logo: &str) { - let lines: Vec<&str> = non_empty_lines(logo).collect(); - let rows = lines.len().max(1) as f32; - let cols = lines - .iter() - .map(|l| l.chars().count()) - .max() - .unwrap_or(1) - .max(1) as f32; +/// One rendered braille cell of the moon: the glyph plus whether any of its +/// dots are sunlit (drives the cell color). +#[derive(Clone, Copy)] +struct MoonCell { + ch: char, + lit: bool, +} + +/// Rasterize the moon at `size` and phase `p` into rows of braille cells. +/// `None` cells are fully empty (outside the disc). +fn moon_cells(size: MoonSize, p: f32) -> Vec>> { + // Braille dot bit values by (dot_col, dot_row) within a cell. + const DOT_BITS: [[u32; 4]; 2] = [[0x01, 0x02, 0x04, 0x40], [0x08, 0x10, 0x20, 0x80]]; + + let dots_w = (size.cols * 2) as i32; + let dots_h = (size.rows * 4) as i32; + let cx = (dots_w as f32 - 1.0) / 2.0; + let cy = (dots_h as f32 - 1.0) / 2.0; + let r = (dots_w.min(dots_h) as f32) / 2.0 - 0.5; + + (0..size.rows as i32) + .map(|cell_row| { + (0..size.cols as i32) + .map(|cell_col| { + let mut mask = 0u32; + let mut lit = false; + for (dot_col, col_bits) in DOT_BITS.iter().enumerate() { + for (dot_row, bit) in col_bits.iter().enumerate() { + let x = cell_col * 2 + dot_col as i32; + let y = cell_row * 4 + dot_row as i32; + let dx = (x as f32 - cx) / r; + let dy = (y as f32 - cy) / r; + let d_sq = dx * dx + dy * dy; + if d_sq > 1.0 { + continue; + } + if dot_lit(dx, dy, p) { + mask |= bit; + lit = true; + } else if d_sq >= RING_INNER_SQ { + // Dark limb: keep the outline ring visible. + mask |= bit; + } + } + } + (mask != 0).then(|| MoonCell { + ch: char::from_u32(0x2800 + mask).expect("braille block"), + lit, + }) + }) + .collect() + }) + .collect() +} + +fn render_into(area: Rect, buf: &mut Buffer, theme: &Theme, size: MoonSize) { let secs = anim_phase_secs(); + let pulse = PULSE * (0.5 - 0.5 * (std::f32::consts::TAU * secs / PULSE_SECS).cos()); + let lit_opacity = (LIT_OPACITY + pulse).clamp(0.0, 1.0); - // Blend each glyph from the resting gray toward the bright text color by its - // shine opacity, so a sheen sweeps across the braille art. Adjacent glyphs - // that land on the same blended color share one Span to hold down the - // per-frame allocation. let base = theme.gray; let hilite = theme.text_primary; - let logo_lines: Vec = lines - .iter() - .enumerate() - .map(|(row, line)| { + let lit_color = blend_color(base, hilite, lit_opacity).unwrap_or(base); + + // Adjacent cells with the same color share one Span to hold down the + // per-frame allocation. + let logo_lines: Vec = moon_cells(size, phase_now()) + .into_iter() + .map(|row| { let mut spans: Vec = Vec::new(); let mut run = String::new(); let mut run_color: Option = None; - for (col, ch) in line.chars().enumerate() { - // Sweep along the bottom-left → top-right diagonal: the - // coordinate grows as col increases and row decreases. - let diag = (col as f32 + (rows - 1.0 - row as f32)) / (cols + rows); - let color = blend_color(base, hilite, shine_opacity(diag, secs)).unwrap_or(base); + for cell in row { + let (ch, color) = match cell { + Some(MoonCell { ch, lit: true }) => (ch, lit_color), + Some(MoonCell { ch, lit: false }) => (ch, base), + None => ('\u{2800}', base), + }; if run_color != Some(color) { if let Some(prev) = run_color { spans.push(Span::styled( @@ -157,29 +227,29 @@ fn render_into(area: Rect, buf: &mut Buffer, theme: &Theme, logo: &str) { } pub fn logo_line_count(window_height: u16) -> u16 { - pick_logo(window_height).map_or(0, count_lines) + pick_logo(window_height).map_or(0, |s| s.rows) } pub fn logo_visual_width(window_height: u16) -> u16 { - pick_logo(window_height).map_or(24, visual_width) + pick_logo(window_height).map_or(24, |s| s.cols) } pub fn render_logo(area: Rect, buf: &mut Buffer, theme: &Theme, window_height: u16) { - if let Some(logo) = pick_logo(window_height) { - render_into(area, buf, theme, logo); + if let Some(size) = pick_logo(window_height) { + render_into(area, buf, theme, size); } } -/// The hero box always shows the full logo: it is laid out beside the menu, so -/// it fits whenever the box does. These report and render that logo directly, -/// independent of the height-based [`pick_logo`] tiers used by the stacked -/// layout. When [`logo_hidden`], they report 0 and render nothing. +/// The hero box always shows the full moon: it is laid out beside the menu, +/// so it fits whenever the box does. These report and render that logo +/// directly, independent of the height-based [`pick_logo`] tiers used by the +/// stacked layout. When [`logo_hidden`], they report 0 and render nothing. pub fn full_logo_line_count() -> u16 { full_logo_line_count_for(logo_hidden()) } fn full_logo_line_count_for(hidden: bool) -> u16 { - if hidden { 0 } else { count_lines(LOGO) } + if hidden { 0 } else { FULL.rows } } pub fn full_logo_visual_width() -> u16 { @@ -187,30 +257,26 @@ pub fn full_logo_visual_width() -> u16 { } fn full_logo_visual_width_for(hidden: bool) -> u16 { - if hidden { 0 } else { visual_width(LOGO) } + if hidden { 0 } else { FULL.cols } } pub fn render_full_logo(area: Rect, buf: &mut Buffer, theme: &Theme) { if !logo_hidden() { - render_into(area, buf, theme, LOGO); + render_into(area, buf, theme, FULL); } } -/// Line count of the small logo used in minimal's committed welcome card -/// (0 on a legacy Windows console, where the braille art is suppressed). +/// Line count of the small moon used in minimal's committed welcome card +/// (0 on a legacy Windows console, where braille art is suppressed). pub fn compact_logo_line_count() -> u16 { - if logo_hidden() { - 0 - } else { - count_lines(LOGO_SMALL) - } + if logo_hidden() { 0 } else { SMALL.rows } } -/// Render the small braille logo (centered) into `area` for minimal's welcome -/// card. No-op when the logo is hidden. +/// Render the small moon (centered) into `area` for minimal's welcome card. +/// No-op when the logo is hidden. pub fn render_compact_logo(area: Rect, buf: &mut Buffer, theme: &Theme) { if !logo_hidden() { - render_into(area, buf, theme, LOGO_SMALL); + render_into(area, buf, theme, SMALL); } } @@ -218,21 +284,35 @@ pub fn render_compact_logo(area: Rect, buf: &mut Buffer, theme: &Theme) { mod tests { use super::*; + /// Count sunlit dots across the whole disc at phase `p`. + fn lit_dots(p: f32) -> usize { + let dots_w = (FULL.cols * 2) as i32; + let dots_h = (FULL.rows * 4) as i32; + let cx = (dots_w as f32 - 1.0) / 2.0; + let cy = (dots_h as f32 - 1.0) / 2.0; + let r = (dots_w.min(dots_h) as f32) / 2.0 - 0.5; + let mut count = 0; + for y in 0..dots_h { + for x in 0..dots_w { + let dx = (x as f32 - cx) / r; + let dy = (y as f32 - cy) / r; + if dx * dx + dy * dy <= 1.0 && dot_lit(dx, dy, p) { + count += 1; + } + } + } + count + } + #[test] fn logo_sizes_by_height() { assert!(pick_logo_for(SMALL_LOGO_MIN_HEIGHT - 1, false).is_none()); - assert_eq!( - pick_logo_for(SMALL_LOGO_MIN_HEIGHT, false), - Some(LOGO_SMALL) - ); - assert_eq!( - pick_logo_for(FULL_LOGO_MIN_HEIGHT - 1, false), - Some(LOGO_SMALL) - ); - assert_eq!(pick_logo_for(FULL_LOGO_MIN_HEIGHT, false), Some(LOGO)); + assert_eq!(pick_logo_for(SMALL_LOGO_MIN_HEIGHT, false), Some(SMALL)); + assert_eq!(pick_logo_for(FULL_LOGO_MIN_HEIGHT - 1, false), Some(SMALL)); + assert_eq!(pick_logo_for(FULL_LOGO_MIN_HEIGHT, false), Some(FULL)); } - // The braille art has no legacy-safe stand-in, so every height tier must + // The braille moon has no legacy-safe stand-in, so every height tier must // collapse to no logo when the legacy-console flag is set. #[test] fn logo_hidden_on_legacy_console_at_every_height() { @@ -243,12 +323,12 @@ mod tests { #[test] fn hero_box_always_uses_full_logo() { - // The box renders the full logo regardless of height (it's laid out + // The box renders the full moon regardless of height (it's laid out // beside the menu), and it's the large variant — never the small one. - assert_eq!(full_logo_line_count_for(false), count_lines(LOGO)); - assert_eq!(full_logo_visual_width_for(false), visual_width(LOGO)); - assert!(full_logo_line_count_for(false) > count_lines(LOGO_SMALL)); - assert!(full_logo_visual_width_for(false) > visual_width(LOGO_SMALL)); + assert_eq!(full_logo_line_count_for(false), FULL.rows); + assert_eq!(full_logo_visual_width_for(false), FULL.cols); + assert!(full_logo_line_count_for(false) > SMALL.rows); + assert!(full_logo_visual_width_for(false) > SMALL.cols); } #[test] @@ -258,61 +338,89 @@ mod tests { } #[test] - fn compact_logo_line_count_matches_small_logo_when_visible() { - // The minimal welcome card budgets exactly the small logo's rows. When - // the logo isn't hidden, the count equals the small art's line count and - // is strictly shorter than the full logo. + fn compact_logo_is_the_small_moon() { if !logo_hidden() { - assert_eq!(compact_logo_line_count(), count_lines(LOGO_SMALL)); - assert!(compact_logo_line_count() < count_lines(LOGO)); - assert!(compact_logo_line_count() > 0); + assert_eq!(compact_logo_line_count(), SMALL.rows); + assert!(compact_logo_line_count() < FULL.rows); } else { assert_eq!(compact_logo_line_count(), 0); } } #[test] - fn shine_opacity_stays_in_unit_range() { - let mut secs = 0.0; - while secs < 10.0 { - for i in 0..=20 { - let diag = i as f32 / 20.0; - let op = shine_opacity(diag, secs); - assert!( - (0.0..=1.0).contains(&op), - "opacity {op} out of range at diag {diag}, secs {secs}" - ); - } - secs += 0.13; + fn full_moon_lights_the_whole_disc_and_new_moon_none() { + let full = lit_dots(0.5); + assert!(full > 0, "full moon must light the disc"); + assert_eq!(lit_dots(0.0), 0, "new moon must be fully dark"); + // Quarter phases light about half the disc. + let quarter = lit_dots(0.25); + assert!( + (full / 3..=2 * full / 3).contains(&quarter), + "first quarter lit {quarter} should be near half of full {full}" + ); + } + + #[test] + fn illumination_waxes_then_wanes() { + // Monotone growth to full, then monotone decay — the terminator + // sweeps once across the disc per lunation. + let phases = [0.05, 0.15, 0.25, 0.35, 0.45]; + for w in phases.windows(2) { + assert!( + lit_dots(w[0]) < lit_dots(w[1]), + "waxing must grow: p={} vs p={}", + w[0], + w[1] + ); + } + let phases = [0.55, 0.65, 0.75, 0.85, 0.95]; + for w in phases.windows(2) { + assert!( + lit_dots(w[0]) > lit_dots(w[1]), + "waning must shrink: p={} vs p={}", + w[0], + w[1] + ); } } #[test] - fn shine_band_sweeps_across() { - // The brightest point along the diagonal advances left → right as the - // sweep progresses through its active phase. - let brightest = |secs: f32| -> f32 { - (0..=100) - .map(|i| i as f32 / 100.0) - .max_by(|a, b| { - shine_opacity(*a, secs) - .partial_cmp(&shine_opacity(*b, secs)) - .unwrap() - }) - .unwrap() - }; - let early = brightest(0.1); - let mid = brightest(0.4); - let late = brightest(0.7); - assert!(early < mid, "early {early} should precede mid {mid}"); - assert!(mid < late, "mid {mid} should precede late {late}"); + fn waxing_lights_the_right_limb_first() { + // Shortly after new moon the crescent must hang on the right side. + assert!(dot_lit(0.95, 0.0, 0.1), "right limb lit while waxing"); + assert!(!dot_lit(-0.95, 0.0, 0.1), "left limb dark while waxing"); + // And on the left side while waning. + assert!(dot_lit(-0.95, 0.0, 0.9), "left limb lit while waning"); + assert!(!dot_lit(0.95, 0.0, 0.9), "right limb dark while waning"); } #[test] - fn shine_rests_dim_between_sweeps() { - // During the rest phase the band is parked off-screen, so an interior - // glyph falls back to at most the gentle pulse — never full bright. - let op = shine_opacity(0.5, 6.0); // secs % 4.0 = 2.0 → past SWEEP_FRAC, in the rest phase - assert!(op < 0.2, "resting opacity {op} should stay dim"); + fn new_moon_keeps_a_silhouette_ring() { + // Even at p=0 the rasterizer must emit outline cells so the moon + // never vanishes from the welcome screen. + let cells = moon_cells(FULL, 0.0); + let drawn = cells.iter().flatten().flatten().count(); + assert!(drawn > 0, "new moon must keep an outline ring"); + assert!( + cells.iter().flatten().flatten().all(|c| !c.lit), + "no cell may be lit at new moon" + ); + } + + #[test] + fn moon_raster_is_round_and_fills_the_grid() { + // The disc must span (nearly) the whole cell grid in both axes at + // full moon — this is what "make it larger" bought us. + let cells = moon_cells(FULL, 0.5); + assert_eq!(cells.len(), FULL.rows as usize); + assert!(cells.iter().all(|r| r.len() == FULL.cols as usize)); + let first_row_drawn = cells.first().unwrap().iter().flatten().count(); + let mid_row_drawn = cells[FULL.rows as usize / 2].iter().flatten().count(); + assert!(first_row_drawn > 0, "top of the disc must be drawn"); + assert!( + mid_row_drawn > first_row_drawn, + "equator must be wider than the pole (round disc)" + ); + assert_eq!(mid_row_drawn, FULL.cols as usize, "equator spans the grid"); } } diff --git a/crates/codegen/kigi-tui/src/views/welcome/mod.rs b/crates/codegen/kigi-tui/src/views/welcome/mod.rs index c889043..e63053c 100644 --- a/crates/codegen/kigi-tui/src/views/welcome/mod.rs +++ b/crates/codegen/kigi-tui/src/views/welcome/mod.rs @@ -2759,11 +2759,11 @@ mod tests { #[test] fn hero_box_inactive_when_warning_would_overflow() { - // Regression: the box is forced to the full 7-row logo, so even a - // 3-item menu needs 11 box rows. A startup warning (error_height = 2) - // pushes the total past height 19, so the gate must fall back to the - // stacked layout instead of overflowing by a row. - let area = Rect::new(0, 0, 90, 19); + // Regression: the box is forced to the full 10-row moon, so even a + // 3-item menu needs 14 box rows (min content 20). A startup warning + // (error_height = 2, +1 gap) pushes the total past height 20, so the + // gate must fall back to the stacked layout instead of overflowing. + let area = Rect::new(0, 0, 90, 20); let with_warning = WelcomeLayout::compute(WelcomeLayoutInput { content_area: area, error_height: 2, @@ -2814,12 +2814,12 @@ mod tests { #[test] fn hero_box_does_not_overflow_with_tall_menu() { - // A 6-item menu makes the box 2 rows taller than the default-4 box, so - // the centering pad (derived from the default box) must be clamped or - // the box gets pushed down and the version row clips at exactly - // min_content_height. 19 == min_content_height(0, 6, 0, 0): a 13-row box - // + 1 flex gap + 5 fixed-below. - let area = Rect::new(0, 0, 100, 19); + // A tall menu can outgrow the default box, so the centering pad + // (derived from the default box) must be clamped or the box gets + // pushed down and the version row clips at exactly + // min_content_height. 20 == min_content_height(0, 6, 0, 0): a 14-row + // box (10-row moon) + 1 flex gap + 5 fixed-below. + let area = Rect::new(0, 0, 100, 20); let layout = WelcomeLayout::compute(WelcomeLayoutInput { content_area: area, menu_height: 6, @@ -2845,9 +2845,9 @@ mod tests { #[test] fn hero_box_height_accounts_for_borders_and_padding() { - // At h >= 26, logo07 is used (7 lines). With menu_height=3: - // right_col = 2 + 0 + 0 + 1 + 3 = 6, inner = max(7, 6) = 7. - // hero_box_height = 2 (borders) + 2 (v_pad) + 7 = 11. + // At h >= 26 the full moon is used (10 lines). With menu_height=3: + // right_col = 2 + 0 + 0 + 1 + 3 = 6, inner = max(10, 6) = 10. + // hero_box_height = 2 (borders) + 2 (v_pad) + 10 = 14. let area = Rect::new(0, 0, 100, 50); let layout = WelcomeLayout::compute(WelcomeLayoutInput { content_area: area, @@ -2855,7 +2855,7 @@ mod tests { ..Default::default() }); assert!(layout.has_hero_box()); - assert_eq!(layout.hero_box.height, 11); + assert_eq!(layout.hero_box.height, 14); } #[test] diff --git a/crates/codegen/kigi-tui/tests/pty_e2e/welcome_screen_braille_logo_renders_correctly.rs b/crates/codegen/kigi-tui/tests/pty_e2e/welcome_screen_braille_logo_renders_correctly.rs index 15a0dde..56fe5de 100644 --- a/crates/codegen/kigi-tui/tests/pty_e2e/welcome_screen_braille_logo_renders_correctly.rs +++ b/crates/codegen/kigi-tui/tests/pty_e2e/welcome_screen_braille_logo_renders_correctly.rs @@ -18,7 +18,7 @@ async fn welcome_screen_braille_logo_renders_correctly() { let content = ContentController::start().await.expect("start content"); let binary = pager_binary().expect("resolve pager binary"); - // Use a tall terminal so pick_logo() selects the 7-line logo (≥26 rows). + // Use a tall terminal so pick_logo() selects the full moon (≥26 rows). let mut harness = PtyHarness::spawn_with_content(&binary, DEFAULT_ROWS, DEFAULT_COLS, &content, &[]) .expect("spawn pager"); @@ -29,24 +29,22 @@ async fn welcome_screen_braille_logo_renders_correctly() { let screen = harness.screen_contents(); - // The logo contains distinctive Braille characters. If the writer - // thread sends raw UTF-8 bytes through a code-page-dependent API, - // these 3-byte characters would be mangled into 3 separate single- - // byte characters each (e.g. Cyrillic). Check for a few that only - // appear in the logo — not in any ASCII menu label. - // - // From logo07.txt line 2: ⣠⣾⠿⠛ + // The moon logo is drawn from Braille Pattern characters. If the writer + // thread sends raw UTF-8 bytes through a code-page-dependent API, these + // 3-byte characters would be mangled into 3 separate single-byte + // characters each (e.g. Cyrillic). The moon is procedurally generated, + // so assert on the character class rather than specific glyphs: a full + // interior cell (⣿) is present through most of the lunation, and there + // must be a healthy number of non-blank braille cells overall. + let braille_dots = screen + .chars() + .filter(|c| ('\u{2801}'..='\u{28FF}').contains(c)) + .count(); assert!( - screen.contains('⣾'), - "Braille character ⣾ (U+28FE) not found in screen — \ - logo may be garbled by code-page misinterpretation.\n\ - Screen contents:\n{screen}" - ); - assert!( - screen.contains('⣿'), - "Braille character ⣿ (U+28FF) not found in screen — \ - logo may be garbled.\n\ - Screen contents:\n{screen}" + braille_dots >= 20, + "expected the braille moon (>= 20 non-blank braille cells), found \ + {braille_dots} — logo may be garbled by code-page \ + misinterpretation.\nScreen contents:\n{screen}" ); harness.quit().expect("clean quit");