From 760cc991979ff8ba1209642a98cc4cefa48d2b87 Mon Sep 17 00:00:00 2001 From: Leandro Cavalcanti Date: Fri, 20 Mar 2026 14:03:02 -0300 Subject: [PATCH 1/6] Extract logo art into its own module --- qualytics/cli/__init__.py | 15 +-------------- qualytics/cli/logo.py | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 14 deletions(-) create mode 100644 qualytics/cli/logo.py diff --git a/qualytics/cli/__init__.py b/qualytics/cli/__init__.py index 47f068b..21c6c38 100644 --- a/qualytics/cli/__init__.py +++ b/qualytics/cli/__init__.py @@ -10,6 +10,7 @@ from rich import print from ..config import __version__ +from .logo import LOGO # Qualytics brand color BRAND = "#FF9933" @@ -52,20 +53,6 @@ def resolve_command(self, ctx: click.Context, args: list[str]): raise -# fmt: off -# Wordmark traced from official SVG (qualytics-word-mark.svg). -# Each letter rendered independently to guarantee vertical alignment. -LOGO = [ - " ▄▄███▀ ▄▄▄▄", - " ██▀ ▀██▄ ██ ███ ▀█", - " ██ ██ ██ ██ ▄█▀▀▀▀███ ██ ▀█▄ ▄██▀▀███▀▀ ██ ██▀▀▀██▄ ▄██▀▀██▄", - " ██▄ ▄██ ██ ██ ██ ██ ██ ██▄ ██ ███ ██ ██ ▀▀ ▀██▄▄▄▄", - " ▀██▄▄▄▄▄▄▄██▀ ▀█▄▄ ▄██ ▀█▄▄ ▄▄██ ██ ██▄██ ███ ██ ▀█▄▄ ▄██ ██▄ ▄██", - " ▀▀▀▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀ ▀██▀ ▀▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀▀▀▀▀", - " ▄█▀", -] -# fmt: on - def print_banner(subtitle: str | None = None) -> None: """Print the Qualytics logo banner. diff --git a/qualytics/cli/logo.py b/qualytics/cli/logo.py new file mode 100644 index 0000000..b05178e --- /dev/null +++ b/qualytics/cli/logo.py @@ -0,0 +1,15 @@ +"""Qualytics ASCII logo.""" + +# fmt: off +# Wordmark traced from official logo, compact half-block rendering. +LOGO = [ + " ▄█████████▄ ▄ ▄ ▄", + " ██▀ ▀██ ██ ██ ▀▀", + " ██ ██ ██ ██ ▄████▄██ ██ ██ ██ ██████ ██ ▄██▀▀██▄ ▄█████▄", + " ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ▀▀ ██▄▄▄", + " ██▄ ▄██ ██ ██ ██ ██ ██ ██▄▄██ ██ ██ ██ ▄▄ ▀▀▀██", + " ▀██████████▄▄ ▀█████▀██ ▀████▀██ ██ ▄█▀ ██ ██ ▀██▄▄██▀ ▀█████▀", + " ▄█▀", + " ▀▀", +] +# fmt: on From 904ebbc34afdb2faa4b58e71b6d674eda2867685 Mon Sep 17 00:00:00 2001 From: Leandro Cavalcanti Date: Fri, 20 Mar 2026 14:21:39 -0300 Subject: [PATCH 2/6] Add two-tone coloring to logo banner Q icon in brand orange, wordmark in default foreground so it works on both dark and light terminal themes. --- qualytics/cli/__init__.py | 18 +++++++++++------- qualytics/cli/logo.py | 23 +++++++++++++++++++++-- 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/qualytics/cli/__init__.py b/qualytics/cli/__init__.py index 21c6c38..f34162c 100644 --- a/qualytics/cli/__init__.py +++ b/qualytics/cli/__init__.py @@ -10,7 +10,7 @@ from rich import print from ..config import __version__ -from .logo import LOGO +from .logo import logo_lines # Qualytics brand color BRAND = "#FF9933" @@ -83,12 +83,16 @@ def print_banner(subtitle: str | None = None) -> None: display_url = url.rstrip("/") if url else "" subtitle = f"[{BRAND}]✓[/{BRAND}] [dim]Connected to[/dim] {display_url}" - print() - for line in LOGO: - print(f"[bold {BRAND}]{line}[/bold {BRAND}]") - print() - print(f" [bold]v{__version__}[/bold] [dim]·[/dim] {subtitle}") - print() + from rich.console import Console + + console = Console() + + console.print() + for line in logo_lines(): + console.print(line) + console.print() + console.print(f" [bold]v{__version__}[/bold] [dim]·[/dim] {subtitle}") + console.print() def add_suggestion_callback(app: typer.Typer, group_name: str) -> None: diff --git a/qualytics/cli/logo.py b/qualytics/cli/logo.py index b05178e..31bd1ef 100644 --- a/qualytics/cli/logo.py +++ b/qualytics/cli/logo.py @@ -1,9 +1,14 @@ """Qualytics ASCII logo.""" +from rich.text import Text + +# Column where the Q icon ends and the wordmark begins. +_SPLIT = 18 + # fmt: off # Wordmark traced from official logo, compact half-block rendering. -LOGO = [ - " ▄█████████▄ ▄ ▄ ▄", +_LINES = [ + " ▄████▀ ▄██▄ ▄ ▄ ▄", " ██▀ ▀██ ██ ██ ▀▀", " ██ ██ ██ ██ ▄████▄██ ██ ██ ██ ██████ ██ ▄██▀▀██▄ ▄█████▄", " ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ▀▀ ██▄▄▄", @@ -13,3 +18,17 @@ " ▀▀", ] # fmt: on + +# Qualytics brand color +BRAND = "#FF9933" + + +def logo_lines() -> list[Text]: + """Return the logo as a list of Rich Text objects with two-tone coloring.""" + result = [] + for line in _LINES: + t = Text(line) + t.stylize(f"bold {BRAND}", 0, _SPLIT) + t.stylize("bold", _SPLIT) + result.append(t) + return result From 6a68309f0ac4a8f059fe23d842c259a85920a762 Mon Sep 17 00:00:00 2001 From: Leandro Cavalcanti Date: Fri, 20 Mar 2026 14:34:01 -0300 Subject: [PATCH 3/6] Apply horizontal gradient to Q icon in logo banner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-character gradient across the Q icon columns: #B83200 → #F96719 → #FF9933 → light warm tone, fading into the terminal's default foreground for the wordmark. --- qualytics/cli/logo.py | 40 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/qualytics/cli/logo.py b/qualytics/cli/logo.py index 31bd1ef..3683ecc 100644 --- a/qualytics/cli/logo.py +++ b/qualytics/cli/logo.py @@ -8,7 +8,7 @@ # fmt: off # Wordmark traced from official logo, compact half-block rendering. _LINES = [ - " ▄████▀ ▄██▄ ▄ ▄ ▄", + " ▄█████ ▄██▄ ▄ ▄ ▄", " ██▀ ▀██ ██ ██ ▀▀", " ██ ██ ██ ██ ▄████▄██ ██ ██ ██ ██████ ██ ▄██▀▀██▄ ▄█████▄", " ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ▀▀ ██▄▄▄", @@ -22,13 +22,45 @@ # Qualytics brand color BRAND = "#FF9933" +# Horizontal gradient stops for the Q icon (left → right). +# #B83200 → #F96719 → brand → near-white to suggest fade into terminal default. +_STOPS = [ + (0xB8, 0x32, 0x00), # #B83200 + (0xF9, 0x67, 0x19), # #F96719 + (0xFF, 0x99, 0x33), # #FF9933 (brand) + (0xFF, 0xCC, 0x88), # light warm tone – bridges brand to default +] + + +def _gradient_color(t: float) -> str: + """Map t (0..1) to a color along the multi-stop gradient.""" + if t <= 0: + r, g, b = _STOPS[0] + elif t >= 1: + r, g, b = _STOPS[-1] + else: + # Scale t to the number of segments. + seg = t * (len(_STOPS) - 1) + i = int(seg) + f = seg - i + a, b_ = _STOPS[i], _STOPS[min(i + 1, len(_STOPS) - 1)] + r = int(a[0] + (b_[0] - a[0]) * f) + g = int(a[1] + (b_[1] - a[1]) * f) + b = int(a[2] + (b_[2] - a[2]) * f) + return f"#{r:02x}{g:02x}{b:02x}" + def logo_lines() -> list[Text]: - """Return the logo as a list of Rich Text objects with two-tone coloring.""" + """Return the logo with a horizontal gradient on the Q icon. + + Q icon: left-to-right #B83200 → #F96719 → #FF9933 → light warm tone. + Wordmark: terminal default foreground. + """ result = [] for line in _LINES: t = Text(line) - t.stylize(f"bold {BRAND}", 0, _SPLIT) - t.stylize("bold", _SPLIT) + for col in range(min(_SPLIT, len(line))): + color = _gradient_color(col / max(_SPLIT - 1, 1)) + t.stylize(f"bold {color}", col, col + 1) result.append(t) return result From 9b603e6fab459fda41c749421ddaff3210361ca6 Mon Sep 17 00:00:00 2001 From: Leandro Cavalcanti Date: Fri, 20 Mar 2026 14:39:22 -0300 Subject: [PATCH 4/6] Fix banner tests to match updated logo art --- qualytics/cli/__init__.py | 1 - tests/test_cli.py | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/qualytics/cli/__init__.py b/qualytics/cli/__init__.py index f34162c..a1df1d9 100644 --- a/qualytics/cli/__init__.py +++ b/qualytics/cli/__init__.py @@ -53,7 +53,6 @@ def resolve_command(self, ctx: click.Context, args: list[str]): raise - def print_banner(subtitle: str | None = None) -> None: """Print the Qualytics logo banner. diff --git a/tests/test_cli.py b/tests/test_cli.py index 4853c07..67f632e 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -261,9 +261,8 @@ def test_banner_shows_logo_and_version(self, cli_runner, monkeypatch): assert result.exit_code == 0 output = _strip_ansi(result.output) # SVG-traced wordmark: Q logomark + lowercase ualytics - assert "▄▄███▀" in output # Q top - assert "▀██▄▄▄▄▄▄▄██▀" in output # Q bottom - assert "▀▀▀▀▀▀▀▀" in output # baseline + assert "▄█████" in output # Q top + assert "▀██████████▄▄" in output # Q bottom # Version below wordmark assert f"v{__version__}" in output @@ -510,4 +509,4 @@ def test_doctor_shows_banner_with_doctor_label( result = cli_runner.invoke(app, ["doctor"]) output = _strip_ansi(result.output) assert "Doctor" in output - assert "▄▄███▀" in output # Wordmark present + assert "▄█████" in output # Wordmark present From 5c7c458989e0045ab2740317bdeeca647951a3f8 Mon Sep 17 00:00:00 2001 From: Leandro Cavalcanti Date: Fri, 20 Mar 2026 15:23:58 -0300 Subject: [PATCH 5/6] Add compact block-letter fallback logo for narrow terminals Full-word "qualytics" in 3-line block letters (~37 cols) shown when terminal is narrower than 89 columns. Update tests to accept either logo variant. --- qualytics/cli/logo.py | 28 ++++++++++++++++++++++++++++ tests/test_cli.py | 7 +++---- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/qualytics/cli/logo.py b/qualytics/cli/logo.py index 3683ecc..a42a31b 100644 --- a/qualytics/cli/logo.py +++ b/qualytics/cli/logo.py @@ -50,6 +50,10 @@ def _gradient_color(t: float) -> str: return f"#{r:02x}{g:02x}{b:02x}" +# Minimum terminal width needed for the full ASCII logo. +MIN_LOGO_WIDTH = max(len(line) for line in _LINES) + 2 + + def logo_lines() -> list[Text]: """Return the logo with a horizontal gradient on the Q icon. @@ -64,3 +68,27 @@ def logo_lines() -> list[Text]: t.stylize(f"bold {color}", col, col + 1) result.append(t) return result + + +# fmt: off +# Compact block-letter wordmark (~37 cols wide). +_COMPACT_Q_SPLIT = 5 # width of the "Q" character +_COMPACT_LINES = [ + " ▄▀▀▄ █ █ ▄▀▀█ █ █ █ ▄█▄ ▀ ▄▀▀ ▄▀▀", + " █ █ █ █ █ █ █ ▀▄▀ █ █ █ ▀▄", + " ▀▄▄█▄ ▀▄▄█ ▀▄▄█ █ █ █ █ ▀▄▄ ▄▄▀", +] +# fmt: on + + +def compact_logo() -> list[Text]: + """Return a compact block-letter logo for narrow terminals.""" + result = [] + for line in _COMPACT_LINES: + t = Text(line) + for col in range(min(_COMPACT_Q_SPLIT, len(line))): + color = _gradient_color(col / max(_COMPACT_Q_SPLIT - 1, 1)) + t.stylize(f"bold {color}", col, col + 1) + t.stylize("bold", _COMPACT_Q_SPLIT) + result.append(t) + return result diff --git a/tests/test_cli.py b/tests/test_cli.py index 67f632e..1d123b1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -260,9 +260,8 @@ def test_banner_shows_logo_and_version(self, cli_runner, monkeypatch): result = cli_runner.invoke(app, []) assert result.exit_code == 0 output = _strip_ansi(result.output) - # SVG-traced wordmark: Q logomark + lowercase ualytics - assert "▄█████" in output # Q top - assert "▀██████████▄▄" in output # Q bottom + # Logo present (full or compact depending on terminal width) + assert "▄█████" in output or "▄▀▀▄" in output # Version below wordmark assert f"v{__version__}" in output @@ -509,4 +508,4 @@ def test_doctor_shows_banner_with_doctor_label( result = cli_runner.invoke(app, ["doctor"]) output = _strip_ansi(result.output) assert "Doctor" in output - assert "▄█████" in output # Wordmark present + assert "▄█████" in output or "▄▀▀▄" in output # Logo present From d9d043a175e1972b90a44439a65fe339b21ec7ad Mon Sep 17 00:00:00 2001 From: Leandro Cavalcanti Date: Fri, 20 Mar 2026 15:25:32 -0300 Subject: [PATCH 6/6] Use compact logo fallback when terminal is too narrow --- qualytics/cli/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qualytics/cli/__init__.py b/qualytics/cli/__init__.py index a1df1d9..9b780bb 100644 --- a/qualytics/cli/__init__.py +++ b/qualytics/cli/__init__.py @@ -10,7 +10,7 @@ from rich import print from ..config import __version__ -from .logo import logo_lines +from .logo import MIN_LOGO_WIDTH, compact_logo, logo_lines # Qualytics brand color BRAND = "#FF9933" @@ -87,7 +87,7 @@ def print_banner(subtitle: str | None = None) -> None: console = Console() console.print() - for line in logo_lines(): + for line in logo_lines() if console.width >= MIN_LOGO_WIDTH else compact_logo(): console.print(line) console.print() console.print(f" [bold]v{__version__}[/bold] [dim]·[/dim] {subtitle}")