Skip to content

Animate the About logo with a passing glint - #7768

Merged
dhh merged 12 commits into
quattrofrom
about-logo-animation
Aug 22, 2026
Merged

Animate the About logo with a passing glint#7768
dhh merged 12 commits into
quattrofrom
about-logo-animation

Conversation

@omarchybot

@omarchybot omarchybot commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

The About screen has always drawn its logo once and left it there. fastfetch has no animation of its own — 2.67.0 has no --logo-animate, no GIF support, and nothing matching anim, gif or fps in its help or in its binary — but omarchy-launch-about is our own repaint loop around fastfetch, so the animation is ours to draw.

A band of light leans across the logo at 45 degrees, the logo rests for four seconds, and it goes again. Every frame is composed once up front and a tick writes one of them, repainting only the cells fastfetch drew the logo on: 40fps for under 1% of a core, stopping six columns short of the module panel. bin/omarchy-branding-about-animation holds the effect and knows nothing about About; the launcher hands it where the logo sits, what colour to give those cells back, and how much room it has, and keeps for itself the decision of when a frame may be written, because that is inseparable from how the window closes and resizes.

The logo is handed back exactly as it arrived. The frames carry the colour fastfetch drew it in rather than a plain reset, so a glint that has passed leaves the screen it found, and between glints the window is byte-for-byte the one it was before this change. Every frame, with the escape codes stripped, is byte-identical to the logo text, so the sweep can only recolour the logo and never rewrite it.

Where the logo on screen might not be the text in the file, it stays still rather than guess. Most of that is one question — the frames slice a row by character and a terminal draws it by column, so one character has to be one cell — and asking it catches a tab or an escape the renderer expanded itself, a double-width glyph, a combining mark, a joined emoji, and a shell whose locale is counting bytes rather than characters. The rest are About's own: a fastfetch config in any of the directories searched ahead of Omarchy's own, a logo built from $1 colour placeholders, a window too small for the layout to fit without scrolling, and a session that set NO_COLOR. A resize moves those cells, so a WINCH trap ends the sweep on the frame the signal is seen rather than up to a poll interval later.

Testing it against replacement logos, and a review of it, turned up bugs worth their own commits. The frames slice by character while the terminal draws by column, so a double-width glyph, a combining mark or a joined emoji — all of which a user can type into the logo — would have pushed the rest of the row into the module column; the row's character count is now checked against its width in columns, which is the assumption itself rather than a list of the glyphs that break it. The fit predicted the content height from the logo and the module column, but once the logo is the taller of the two fastfetch writes a row more than that arithmetic expects, so every logo of 27 rows or more got a window one row short and scrolled its top padding away — the shipped logo is 26 rows, which is why that one never showed. fastfetch honours NO_COLOR for a terminal but not for the substitution that measures it, so the colour to restore was measured as green while the logo on screen had none. And the config-path listing was split on whitespace, losing any path containing a space, and read a failed listing as "no custom config".

fastfetch has no animation of its own, so the sweep is ours. Every frame is composed once up front and a tick writes one of them, repainting only the cells fastfetch drew the logo on, which holds 40fps for under 1% of a core and never reaches the module column six columns to its right.

The logo is handed back exactly as it arrived: frames carry the colour fastfetch drew it in rather than a plain reset, so between glints the window is byte-for-byte the one it was before this change. Where the logo on screen might not be the text in the file, it stays still rather than guess — a fastfetch config in any directory searched ahead of Omarchy's own, a logo built from $1 colour placeholders or from a tab fastfetch expands itself, a window too small for the layout to fit without scrolling, and a shell whose locale is counting bytes instead of characters.

A resize moves those cells, so a WINCH trap ends the sweep on the frame it happens rather than up to a poll later, which measured 10ms against 830ms. The polling stays as the backstop for a signal that arrived while it could not be taken.

🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
Copilot AI balanced review requested due to automatic review settings August 22, 2026 12:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a periodic glint animation to the fastfetch About logo while preserving safe static fallbacks.

Changes:

  • Precomposes and renders animated logo frames.
  • Adds comprehensive shell coverage.
  • Documents the animation.

Tip

If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.

File Description
bin/omarchy-launch-about Implements animation, layout checks, and resize handling.
test/shell.d/about-sheen-test.sh Tests frame composition and safety fallbacks.
manual/41-branding.md Documents the About-logo animation.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread manual/41-branding.md Outdated
omarchybot and others added 11 commits August 22, 2026 05:34
The launcher was carrying two things: what a glint looks like, and what it is drawn over. Only the second is about fastfetch. bin/omarchy-logo-sheen now knows how to lean a band of light across an ASCII logo and nothing about About, and it is handed where the logo sits, what colour to give its cells back, and how much room it has. When a frame may be written stays with the launcher, because that is inseparable from how the window closes and resizes.

The tests split along the same seam, and the sheen's half no longer strips the launcher's tail to reach the code it tests.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The frames slice the logo by character and the terminal draws it by column, so the sheen only puts a row back where it found it while those two agree. A double-width glyph, a combining mark and a joined emoji each break that, and a cut through one of them renders as something else — pushing the rest of the row right, into the module column, with nothing on screen to say the logo moved. A user can put any of the three in the logo by editing the text.

Comparing the row's character count against its width in columns is the assumption itself, so it is what gets checked, rather than a list of the glyph classes that happen to break it today.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The fit predicted the content height from the logo and the module column, taking the taller of the two. Once the logo is the taller one fastfetch writes a row more than that arithmetic expects, so every logo of 27 rows or more got a window one row short and scrolled its top padding away. The shipped logo is 26 rows, which is why it never showed.

Ask fastfetch how tall the layout came out instead of predicting it. That measurement was already being taken for the sheen, which refuses to animate a layout that scrolled — so a tall logo used to lose the glint as well as the padding row.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
fastfetch drops the logo's colour when it writes to a terminal that set NO_COLOR, but not when it writes to the substitution that measures it. The colour to hand those cells back in was therefore measured as green while the logo on screen had none, and the first glint would have left the whole logo green. It stops suppressing autowrap there too, so a narrow window soft-wraps and scrolls where the measurement cannot see it and the frames go on addressing rows that moved.

Both follow from animating a screen drawn in a mode the measurement did not reproduce. A glint is colour besides, which is the thing NO_COLOR asks for none of.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
…swer

Splitting each listed path on whitespace dropped everything after the first space, so a config under a home directory containing one was missed and the fit and the sheen went ahead against a layout neither had measured. The marker fastfetch prints beside the config it settled on is not part of the path either.

A listing that failed was also indistinguishable from one that found nothing, because the status of a process substitution is discarded, so a fastfetch that could not enumerate read as "no custom config". Fall back to the directory fastfetch has always looked in first rather than take silence for an answer.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
The sweep read the WINCH flag before the grid rather than after it. A signal arriving during the two command substitutions that read the grid is delivered only once they finish, so the flag was still false when it was tested, the grid still compared equal, and one more frame went out at coordinates the resize had already moved. Read the flag last, after the check it could have landed during.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
Every builder the tests drive can be exercised while nothing on screen ever animates. Replacing the render loop's animated branch with the still one it replaced left all of them passing, so nothing was holding the animation onto the screen it belongs on.
🤖 Generated by Opus 5 in Claude Code. Reviewed by Codex XHigh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Codex XHigh <noreply@openai.com>
omarchy-logo-sheen sat in a group of its own, and a group whose only command is hidden still gets a header printed for it with nothing underneath. It belongs beside omarchy-branding-about, whose art it animates: the group already exists, the two halves of About branding are next to each other, and `omarchy branding about` still routes to the command rather than the helper now sharing its prefix.

The tests take the names of what they cover, the way the rest of the suite does.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Four questions about a logo turned out to be one. Whether the shell is counting characters or bytes, whether a tab or an escape is in the line, whether a glyph is double-width or a combining mark or a joined emoji — each was asking whether one character is one cell, so that is the only thing asked now. It is also less strict in the one case that deserves it: plain ASCII art animates in a C locale, where the locale probe used to refuse everything.

One band of light instead of a white core inside a green halo, which is three colour spans a row rather than five and no helper to clamp four cuts with. Only the left cut needs clamping at all: a slice starting past the end of a line is already empty, while a negative offset would count from the end of it.

One loop instead of two, because an empty frame list plays nothing. A logo that cannot be animated now waits in the loop the animated one rests in rather than in a second copy of it, and the build that failed leaves no frames behind, since that loop plays whatever it finds.

The logo's colour comes out of one match rather than a loop eating one escape at a time.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The line promised the glint to any art in the file, and the code does not: art whose characters are not one column wide is left still on purpose, as is the whole screen for anyone keeping a fastfetch config of their own. Somebody reading the old line and seeing a still logo would take deliberate behaviour for a broken feature.

Name the condition rather than the list of glyph classes behind it, and say that both modes of Set From Image meet it, because that is the path almost everyone is on.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A terminal can be told to render bold text in a brighter colour — foot's bold-text-in-bright, whose palette-based form brightens a bold regular colour into its bright counterpart. fastfetch draws the logo bold green, so under that setting the logo is already bright green, which is the colour the band was using: the glint came out the same green as the art and nothing appeared to happen at all. None of the four terminal configs set it, so it was waiting on whoever turned it on.

Bright white instead, because no regular colour brightens into it, and the band shows whatever the terminal does with bold. Narrower with it, since white against green needs less width to read than a second green did.

🤖 Generated by Opus 5 in Claude Code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dhh
dhh merged commit eca89f9 into quattro Aug 22, 2026
3 checks passed
@dhh
dhh deleted the about-logo-animation branch August 22, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants