banner: the framed NX wordmark, in the colours it already had
Browse filesNew letterforms, from the art you sent: a compact NX built from half-blocks,
inside a box rule. Seven rows -- frame, five rows of wordmark, frame.
THE PALETTE IS THE ONE IT HAD
Cyan, ice, white, pink, magenta, and the neutral. Nothing new was mixed. What
changed is where the neutral goes: it is the frame now, not the last row of the
letterforms, so the wordmark ends on magenta instead of fading to grey at the
baseline. The five hues land one per wordmark row.
banner() bands by row index, so the frame is painted by position rather than by
being a special case, and the wordmark is banded by its position WITHIN the
wordmark -- adding or removing a frame row cannot shift the hues by one.
THE RAMP IS NUMBERS NOW
`BANNER_RAMP` and `BANNER_FRAME` hold the 256-colour indices, and the escape
codes are built from them. That is not tidiness. Under NO_COLOR every code in
this file is the empty string, so a test asserting the wordmark is cyan-to-
magenta passes against any ramp at all -- "" == "" for every row. Asserting the
numbers is an assertion that can fail.
Four tests: both spellings are the same height, the ASCII fallback is actually
ASCII, the ramp is the five hues with the neutral held back for the frame and no
two rows sharing a step, and the rendered codes come from the ramp rather than
from a second hand-kept list beside it.
The ASCII fallback is seven rows too, for the same reason it has always had to
match: a fallback of a different height would paint a frame row as a letterform.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- nexa.py +45 -15
- tests/test_cli.py +45 -0
|
@@ -88,25 +88,44 @@ CYAN, ICE, MAG, PINK = c(51), c(87), c(201), c(213)
|
|
| 88 |
AMBER, GOLD, GREEN, RED = c(214), c(220), c(48), c(203)
|
| 89 |
DIM, GREY, WHITE = c(240), c(245), c(255)
|
| 90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
BANNER_UNICODE = """
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
ββ
|
| 95 |
-
β
|
| 96 |
-
ββ
|
| 97 |
-
|
|
|
|
| 98 |
"""
|
| 99 |
|
| 100 |
-
# The same
|
|
|
|
|
|
|
| 101 |
BANNER_ASCII = """
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
|
|
|
| 108 |
"""
|
| 109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
BANNER = BANNER_UNICODE if UNICODE else BANNER_ASCII
|
| 111 |
|
| 112 |
# Glyphs used throughout, degraded together with the banner.
|
|
@@ -116,8 +135,12 @@ ARROW = "βΈ" if UNICODE else ">"
|
|
| 116 |
FILLED, HOLLOW = ("β", "β") if UNICODE else ("*", "o")
|
| 117 |
MIDDOT = "Β·" if UNICODE else "-"
|
| 118 |
|
| 119 |
-
# Per-line gradient, top to bottom.
|
|
|
|
|
|
|
| 120 |
GRADIENT = [CYAN, ICE, WHITE, PINK, MAG, DIM]
|
|
|
|
|
|
|
| 121 |
|
| 122 |
|
| 123 |
_BANNER_SHOWN = False
|
|
@@ -137,7 +160,14 @@ def banner(subtitle=True, once=False):
|
|
| 137 |
lines = BANNER.strip("\n").split("\n")
|
| 138 |
out = [""]
|
| 139 |
for i, line in enumerate(lines):
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
out.append("")
|
| 142 |
out.append(" {0}{1}NEXA{2}{3} validation intelligence {4}v{5}{6}".format(
|
| 143 |
BOLD, AMBER, RESET, GREY, DIM, VERSION, RESET))
|
|
|
|
| 88 |
AMBER, GOLD, GREEN, RED = c(214), c(220), c(48), c(203)
|
| 89 |
DIM, GREY, WHITE = c(240), c(245), c(255)
|
| 90 |
|
| 91 |
+
# The wordmark's ramp as COLOUR NUMBERS, top to bottom, with the frame's neutral
|
| 92 |
+
# last. The escape codes above are "" whenever colour is off, so a palette
|
| 93 |
+
# written only as codes cannot be asserted about under NO_COLOR -- every claim
|
| 94 |
+
# becomes "" == "" and passes whatever the ramp says. The numbers are the design;
|
| 95 |
+
# the codes are how it reaches a terminal.
|
| 96 |
+
BANNER_RAMP = (51, 87, 255, 213, 201)
|
| 97 |
+
BANNER_FRAME = 240
|
| 98 |
+
|
| 99 |
+
# Five rows of wordmark inside a frame. The frame is the first and last line, so
|
| 100 |
+
# banner() -- which bands by row index -- paints it from the ends of GRADIENT and
|
| 101 |
+
# the letterforms from the middle. See BANNER_ROWS.
|
| 102 |
BANNER_UNICODE = """
|
| 103 |
+
βββββββββββββββββ
|
| 104 |
+
β β β βββ’ β β
|
| 105 |
+
β β’ββββ βββββͺ β
|
| 106 |
+
β βββββ Β·ββΒ· β
|
| 107 |
+
β ββββββͺββΒ·ββ β
|
| 108 |
+
β ββ ββͺβ’ββ ββ β
|
| 109 |
+
βββββββββββββββββ
|
| 110 |
"""
|
| 111 |
|
| 112 |
+
# The same seven rows in plain ASCII, for a console stuck on a legacy code page.
|
| 113 |
+
# Same height on purpose: banner() bands by row index, so a fallback of a
|
| 114 |
+
# different height would colour the frame as a letterform.
|
| 115 |
BANNER_ASCII = """
|
| 116 |
+
+---------------+
|
| 117 |
+
| N N X X |
|
| 118 |
+
| NN N XX |
|
| 119 |
+
| N NN XX |
|
| 120 |
+
| N N X X |
|
| 121 |
+
| = == == = |
|
| 122 |
+
+---------------+
|
| 123 |
"""
|
| 124 |
|
| 125 |
+
# The wordmark rows, frame excluded. banner() paints these from the middle of
|
| 126 |
+
# GRADIENT so the frame gets the ends of it.
|
| 127 |
+
BANNER_ROWS = 5
|
| 128 |
+
|
| 129 |
BANNER = BANNER_UNICODE if UNICODE else BANNER_ASCII
|
| 130 |
|
| 131 |
# Glyphs used throughout, degraded together with the banner.
|
|
|
|
| 135 |
FILLED, HOLLOW = ("β", "β") if UNICODE else ("*", "o")
|
| 136 |
MIDDOT = "Β·" if UNICODE else "-"
|
| 137 |
|
| 138 |
+
# Per-line gradient, top to bottom. The same six colours the wordmark has
|
| 139 |
+
# always used; the frame takes the neutral one and the five letterform rows
|
| 140 |
+
# take the hues, so nothing fades to grey at the baseline.
|
| 141 |
GRADIENT = [CYAN, ICE, WHITE, PINK, MAG, DIM]
|
| 142 |
+
FRAME_COLOUR = c(BANNER_FRAME)
|
| 143 |
+
WORDMARK_GRADIENT = [c(code) for code in BANNER_RAMP]
|
| 144 |
|
| 145 |
|
| 146 |
_BANNER_SHOWN = False
|
|
|
|
| 160 |
lines = BANNER.strip("\n").split("\n")
|
| 161 |
out = [""]
|
| 162 |
for i, line in enumerate(lines):
|
| 163 |
+
# First and last row are the frame. Everything between is wordmark,
|
| 164 |
+
# banded by its position WITHIN the wordmark rather than within the
|
| 165 |
+
# block, so adding or removing a frame cannot shift the hues.
|
| 166 |
+
if i == 0 or i == len(lines) - 1:
|
| 167 |
+
out.append(FRAME_COLOUR + line + RESET)
|
| 168 |
+
else:
|
| 169 |
+
out.append(WORDMARK_GRADIENT[(i - 1) % len(WORDMARK_GRADIENT)]
|
| 170 |
+
+ line + RESET)
|
| 171 |
out.append("")
|
| 172 |
out.append(" {0}{1}NEXA{2}{3} validation intelligence {4}v{5}{6}".format(
|
| 173 |
BOLD, AMBER, RESET, GREY, DIM, VERSION, RESET))
|
|
@@ -167,3 +167,48 @@ def test_subprocess_output_is_clean_when_not_a_terminal():
|
|
| 167 |
code, out = shell("rules", "onchange")
|
| 168 |
assert code == OK
|
| 169 |
assert "\033" not in out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
code, out = shell("rules", "onchange")
|
| 168 |
assert code == OK
|
| 169 |
assert "\033" not in out
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
# ------------------------------------------------- the wordmark and its frame
|
| 173 |
+
|
| 174 |
+
def test_both_spellings_are_the_same_height():
|
| 175 |
+
"""banner() bands by row index, so a fallback of a different height would
|
| 176 |
+
paint a frame row as a letterform and vice versa."""
|
| 177 |
+
unicode_rows = nexa.BANNER_UNICODE.strip("\n").split("\n")
|
| 178 |
+
ascii_rows = nexa.BANNER_ASCII.strip("\n").split("\n")
|
| 179 |
+
assert len(unicode_rows) == len(ascii_rows)
|
| 180 |
+
# Frame top and bottom, wordmark between.
|
| 181 |
+
assert len(unicode_rows) == nexa.BANNER_ROWS + 2
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def test_the_ascii_fallback_is_actually_ascii():
|
| 185 |
+
"""It exists for a console on a legacy code page. A block-drawing character
|
| 186 |
+
surviving into it defeats the entire point."""
|
| 187 |
+
assert nexa.BANNER_ASCII.isascii()
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def test_the_wordmark_takes_the_hues_and_the_frame_takes_the_neutral():
|
| 191 |
+
"""The ramp is the palette the banner already had -- cyan, ice, white, pink,
|
| 192 |
+
magenta -- with the neutral reserved for the frame.
|
| 193 |
+
|
| 194 |
+
Asserted against the NUMBERS, not the escape codes. Under NO_COLOR every code
|
| 195 |
+
is the empty string, so a test written against codes would pass for any ramp
|
| 196 |
+
at all, which is worse than not testing it.
|
| 197 |
+
"""
|
| 198 |
+
assert nexa.BANNER_RAMP == (51, 87, 255, 213, 201)
|
| 199 |
+
assert nexa.BANNER_FRAME == 240
|
| 200 |
+
assert nexa.BANNER_FRAME not in nexa.BANNER_RAMP, "the frame's neutral is not one of the wordmark's hues"
|
| 201 |
+
assert len(set(nexa.BANNER_RAMP)) == len(nexa.BANNER_RAMP), "two rows sharing a colour is a ramp with a step missing"
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def test_the_rendered_palette_is_built_from_the_ramp():
|
| 205 |
+
"""And the codes that reach the terminal come from those numbers rather than
|
| 206 |
+
from a second, hand-kept list that can drift from them."""
|
| 207 |
+
assert nexa.WORDMARK_GRADIENT == [nexa.c(n) for n in nexa.BANNER_RAMP]
|
| 208 |
+
assert nexa.FRAME_COLOUR == nexa.c(nexa.BANNER_FRAME)
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
def test_the_wordmark_row_count_matches_the_hue_count():
|
| 212 |
+
"""One hue per letterform row. If they drift, the banding wraps and two rows
|
| 213 |
+
share a colour."""
|
| 214 |
+
assert len(nexa.WORDMARK_GRADIENT) == nexa.BANNER_ROWS
|