{"id":3319,"date":"2026-01-28T17:14:00","date_gmt":"2026-01-28T17:14:00","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2026\/01\/28\/from-pixels-to-characters-the-engineering-behind-github-copilot-clis-animated-ascii-banner\/"},"modified":"2026-01-28T17:14:00","modified_gmt":"2026-01-28T17:14:00","slug":"from-pixels-to-characters-the-engineering-behind-github-copilot-clis-animated-ascii-banner","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2026\/01\/28\/from-pixels-to-characters-the-engineering-behind-github-copilot-clis-animated-ascii-banner\/","title":{"rendered":"From pixels to characters: The engineering behind GitHub Copilot CLI\u2019s animated ASCII banner"},"content":{"rendered":"<p>Most people think ASCII art is simple, and a nostalgic remnant of the early internet. But when the GitHub Copilot CLI team asked for a small entrance banner for the new command-line experience, they discovered the opposite: An ASCII animation in a real-world terminal is one of the most constrained UI engineering problems you can take on.<\/p>\n<p>Part of what makes this even more interesting is the moment we\u2019re in. Over the past year, CLIs have seen a surge of investment as AI-assisted and agentic workflows move directly into the terminal. But unlike the web\u2014where design systems, accessibility standards, and rendering models are well-established\u2014the CLI world is still fragmented. Terminals behave differently, have few shared standards, and offer almost no consistent accessibility guidelines. That reality shaped every engineering decision in this project.<\/p>\n<p>Different terminals interpret ANSI color codes differently. Screen readers treat fast-changing characters as noise. Layout engines vary. Buffers flicker. Some users override global colors for accessibility. Others throttle redraw speed. There is no canvas, no compositor, no consistent rendering model, and no standard animation framework.<\/p>\n<aside data-color-mode=\"light\" data-dark-theme=\"dark\" data-light-theme=\"light_dimmed\" class=\"wp-block-group post-aside--large p-4 p-md-6 is-style-light-dimmed has-global-padding is-layout-constrained wp-block-group-is-layout-constrained is-style-light-dimmed--1\">\n<h3 class=\"wp-block-heading h5-mktg gh-aside-title is-typography-preset-h5\">By the numbers<\/h3>\n<ul class=\"wp-block-list\">\n<li>3 seconds of animation<\/li>\n<li>~20 frames<\/li>\n<li>~6,000 lines of TypeScript<\/li>\n<li>Dozens of terminal + theme combinations tested<\/li>\n<\/ul>\n<\/aside>\n<p>So when an animated Copilot mascot flying into the terminal appeared, it looked playful. But behind it was serious engineering work, unexpected complexity, a custom design toolchain, and a tight pairing between a designer and a long-time CLI engineer.<\/p>\n<p>That complexity only became fully visible once the system was built. In the end, animating a three-second ASCII banner required over 6,000 lines of TypeScript\u2014most of it dedicated not to visuals, but to handling terminal inconsistencies, accessibility constraints, and maintainable rendering logic.<\/p>\n<p>This is the technical story of how it came together.<\/p>\n<figure class=\"wp-block-embed is-type-video is-provider-youtube wp-block-embed-youtube wp-embed-aspect-16-9 wp-has-aspect-ratio\">\n<div class=\"wp-block-embed__wrapper\">\n<\/div>\n<\/figure>\n<aside data-color-mode=\"light\" data-dark-theme=\"dark\" data-light-theme=\"light_dimmed\" class=\"wp-block-group post-aside--large p-4 p-md-6 is-style-light-dimmed has-global-padding is-layout-constrained wp-block-group-is-layout-constrained is-style-light-dimmed--2\">\n<h2 class=\"wp-block-heading h5-mktg gh-aside-title is-typography-preset-h5\">\ud83d\udce6 What\u2019s new in GitHub Copilot CLI<\/h2>\n<p>GitHub Copilot CLI brings agentic workflows directly into your terminal\u2014letting you plan projects, modify files, run commands, use custom agents, and delegate tasks to the cloud, all without leaving the CLI.<\/p>\n<p>Since its introduction, Copilot CLI has expanded to support richer, more flexible agentic workflows:<\/p>\n<ul class=\"wp-block-list\">\n<li><strong>Works the way you do<\/strong> with persistent memory, infinite sessions, and intelligent compaction<\/li>\n<li><strong>Helps you think<\/strong> using explore, plan, and review workflows where you can choose the model at each step<\/li>\n<li><strong>Executes on your behalf<\/strong> with custom agents, agent skills, full MCP support, and async task delegation<\/li>\n<\/ul>\n<p><strong>Want to bring these same agentic capabilities into your own tools or products?<\/strong> <a href=\"https:\/\/github.com\/github\/copilot-sdk\/\">The GitHub Copilot SDK<\/a> exposes the same execution loop that powers Copilot CLI, so you can embed agents into any application using your Copilot subscription or your own model keys.<\/p>\n<p><a href=\"https:\/\/github.blog\/news-insights\/company-news\/build-an-agent-into-any-app-with-the-github-copilot-sdk\/?utm_source=blog-announcement-cli-sdk&amp;utm_medium=&amp;utm_campaign=cli-sdk-jan-2026\"><strong>Learn more about the Copilot SDK &gt;<\/strong><\/a><\/p>\n<\/aside>\n<h2 class=\"wp-block-heading\">Why animated ASCII is a hard engineering problem<\/h2>\n<figure class=\"wp-block-video\"><video height=\"1024\" width=\"1492\" controls poster=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/cli.png\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/Banner.mp4\" preload=\"none\"><\/video><\/figure>\n<p>Before diving into the build process, it\u2019s worth calling out why this problem space is more advanced than it looks.<\/p>\n<h3 class=\"wp-block-heading\">Terminals don\u2019t have a canvas<\/h3>\n<p>Unlike browsers (DOM), native apps (views), or graphics frameworks (GPU surfaces), terminals treat output as a <em>stream of characters<\/em>. There\u2019s no native concept of:<\/p>\n<ul class=\"wp-block-list\">\n<li>Frames<\/li>\n<li>Sprites<\/li>\n<li>Z-index<\/li>\n<li>Rasterized pixels<\/li>\n<li>Animation tick rates<\/li>\n<\/ul>\n<p>Because of this, every \u201cframe\u201d has to be manually repainted using cursor movements and redraw commands. There\u2019s no compositor smoothing anything over behind the scenes. Everything is stdout writes + ANSI control sequences.<\/p>\n<h3 class=\"wp-block-heading\">ANSI escape codes are inconsistent, and terminal color is its own engineering challenge<\/h3>\n<p>ANSI escape codes like <code>x1b[35m<\/code> (bright magenta) or <code>x1b[H<\/code> (cursor home) behave differently across terminals\u2014not just in how they render, but in <em>whether they\u2019re supported at all<\/em>. Some environments (like Windows Command Prompt or older versions of PowerShell) have limited or no ANSI support without extra configuration.<\/p>\n<p>But even in terminals that do support ANSI, the hardest part isn\u2019t the cursor movement. It\u2019s the colors.<\/p>\n<p>When you\u2019re building a CLI, you realistically have three approaches:<\/p>\n<ol class=\"wp-block-list\">\n<li><strong>Use no color at all. <\/strong>This guarantees broad compatibility, but makes it harder to highlight meaning or guide users\u2019 attention\u2014especially in dense CLI output.<\/li>\n<li><strong>Use richer color modes (3-bit, 4-bit, 8-bit, or truecolor)<\/strong> that aren\u2019t uniformly supported or customizable. This introduces a maintenance headache: Different terminals, themes, and accessibility profiles render the same color codes differently, and users often disagree about what \u201cgood\u201d colors look like.<\/li>\n<li><strong>Use a minimal, customizable palette (usually 4-bit colors)<\/strong> that most terminals allow users to override in their preferences. This is the safest path, but it limits how accurately you can represent a brand palette\u2014and it forces you to design for environments with widely varying contrast and theme choices.<\/li>\n<\/ol>\n<p>For the Copilot CLI animation, this meant treating color as a <em>semantic<\/em> system, not a literal one: Instead of committing specific RGB values, the team mapped high-level \u201croles\u201d (eyes, goggles, shadow, border) to ANSI colors that degrade gracefully across different terminals and accessibility settings.<\/p>\n<h3 class=\"wp-block-heading\">Accessibility is a first-class concern<\/h3>\n<p>Terminals are used by developers with a wide range of visual abilities\u2014not just blind users with screen readers, but also low-vision users, color-blind users, and anyone working in high-contrast or customized themes.<\/p>\n<p>That means:<\/p>\n<ul class=\"wp-block-list\">\n<li>Rapid re-renders can create auditory clutter for screen readers<\/li>\n<li>Color-based meaning must degrade safely, since bold, dim, or subtle hues may not be perceivable<\/li>\n<li>Low-vision users may not see contrast differences that designers expect<\/li>\n<li>Animations must be opt-in, not automatic<\/li>\n<li>Clearing sequences must avoid confusing assistive technologies<\/li>\n<\/ul>\n<p>This is also why the Copilot CLI animation ended up behind an opt-in flag early on\u2014accessibility constraints shaped the architecture from the start.\u00a0<\/p>\n<p>These constraints guided every decision in the Copilot CLI animation. The banner had to work when colors were overridden, when contrast was limited, and even when the animation itself wasn\u2019t visible.<\/p>\n<h3 class=\"wp-block-heading\">Ink (React for the terminal) helps, but it\u2019s not an animation engine<\/h3>\n<p><a href=\"https:\/\/github.com\/vadimdemedes\/ink\">Ink<\/a> lets you build terminal interfaces using React components, but:<\/p>\n<ul class=\"wp-block-list\">\n<li>It re-renders on every state change<\/li>\n<li>It doesn\u2019t manage frame deltas<\/li>\n<li>It doesn\u2019t synchronize with terminal paint cycles<\/li>\n<li>It doesn\u2019t solve flicker or cursor ghosting<\/li>\n<\/ul>\n<p>Which meant animation logic had to be handcrafted.<\/p>\n<h3 class=\"wp-block-heading\">Frame-based ASCII animation has no existing workflow for designers<\/h3>\n<p>There are tools for ASCII art, but virtually none for:<\/p>\n<ul class=\"wp-block-list\">\n<li>Frame-by-frame editing<\/li>\n<li>Multi-color ANSI previews<\/li>\n<li>Exporting color roles<\/li>\n<li>Generating Ink-ready components<\/li>\n<li>Testing contrast and accessibility<\/li>\n<\/ul>\n<p>Even existing ANSI preview tools don\u2019t simulate how different terminals remap colors or handle cursor updates, which makes accurate design iteration almost impossible without custom tooling. So the team had to build one.<\/p>\n<h2 class=\"wp-block-heading\">Part 1: A request that didn\u2019t fit any workflow<\/h2>\n<p>Cameron Foxly (<a href=\"https:\/\/github.com\/cameronfoxly\">@cameronfoxly<\/a>), a brand designer at GitHub with a background in animation, was asked to create a banner for the Copilot CLI.<\/p>\n<p>\u201cNormally, I\u2019d build something in After Effects and hand off assets,\u201d Cameron said. \u201cBut engineers didn\u2019t have the time to manually translate animation frames into a CLI. And honestly, I wanted something more fun.\u201d<\/p>\n<p>He\u2019d seen the static ASCII intro in Claude Code and knew Copilot deserved more personality.<\/p>\n<p>The 3D Copilot mascot flying in to reveal the CLI logo felt right. But after attempting to create just <em>one<\/em> frame manually, the idea quickly ran into reality.<\/p>\n<p>\u201cIt was a nightmare,\u201d Cameron said. \u201cIf this is going to exist, I need to build my own tool.\u201d<\/p>\n<h2 class=\"wp-block-heading\">Part 2: Building an ASCII animation editor from scratch<\/h2>\n<p>Cameron opened an empty repository in VS Code, and began asking GitHub Copilot for help scaffolding an animation MVP that could:<\/p>\n<ul class=\"wp-block-list\">\n<li>Read text files as frames<\/li>\n<li>Render them sequentially<\/li>\n<li>Control timing<\/li>\n<li>Clear the screen without flicker<\/li>\n<li>Add a primitive \u201cUI\u201d<\/li>\n<\/ul>\n<p>Within an hour, he had a working prototype that was monochrome, but functional.<\/p>\n<h3 class=\"wp-block-heading\">Simplified early animation loop<\/h3>\n<p>Below is a simplified example variation of the frame loop logic Cameron prototyped:<\/p>\n<pre class=\"wp-block-code\"><code>import fs from \"fs\";\nimport readline from \"readline\";\n\n\/**\n * Load ASCII frames from a directory.\n *\/\nconst frames = fs\n  .readdirSync(\".\/frames\")\n  .filter(f =&gt; f.endsWith(\".txt\"))\n  .map(f =&gt; fs.readFileSync(`.\/frames\/${f}`, \"utf8\"));\n\nlet current = 0;\n\nfunction render() {\n  \/\/ Move cursor to top-left of terminal\n  readline.cursorTo(process.stdout, 0, 0);\n\n  \/\/ Clear the screen below the cursor\n  readline.clearScreenDown(process.stdout);\n\n  \/\/ Write the current frame\n  process.stdout.write(frames[current]);\n\n  \/\/ Advance to next frame\n  current = (current + 1) % frames.length;\n}\n\n\/\/ 75ms = ~13fps. Higher can cause flicker in some terminals.\nsetInterval(render, 75);<\/code><\/pre>\n<p>This introduced the first major obstacle: color. The prototype worked in monochrome, but the moment color was added, inconsistencies across terminals\u2014and accessibility constraints\u2014became the dominant engineering problem.<\/p>\n<h2 class=\"wp-block-heading\">Part 3: ANSI color theory and the real-world limitations<\/h2>\n<p>The Copilot brand palette is vibrant and high-contrast, which is great for web but exceptionally challenging for terminals.<\/p>\n<p>ANSI terminals support:<\/p>\n<ul class=\"wp-block-list\">\n<li>16-color mode (standard)<\/li>\n<li>256-color mode (extended)<\/li>\n<li>Sometimes truecolor (\u201c24-bit\u201d) but inconsistently<\/li>\n<\/ul>\n<p>Even in 256-color mode, terminals remap colors based on:<\/p>\n<ul class=\"wp-block-list\">\n<li>User themes<\/li>\n<li>Accessibility settings<\/li>\n<li>High-contrast modes<\/li>\n<li>Light\/dark backgrounds<\/li>\n<li>OS-level overrides<\/li>\n<\/ul>\n<p>Which means you can\u2019t rely on exact hues. You have to design with variability in mind.<\/p>\n<p>Cameron needed a way to paint characters with ANSI color roles while previewing how they look in different terminals.<\/p>\n<p>He took a screenshot of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/ANSI_escape_code#Colors\">Wikipedia ANSI table<\/a>, handed it to Copilot, and asked it to scaffold a palette UI for his tool.<\/p>\n<h2 class=\"wp-block-heading\">Adding a color \u201cbrush\u201d tool<\/h2>\n<p>A simplified version:<\/p>\n<pre class=\"wp-block-code\"><code>function applyColor(char, color) {\n  \/\/ Minimal example: real implementation needed support for roles,\n  \/\/ contrast testing, and multiple ANSI modes.\n  const codes = {\n    magenta: \"x1b[35m\",\n    cyan: \"x1b[36m\",\n    white: \"x1b[37m\"\n  };\n\n  return `${codes[color]}${char}x1b[0m`; \/\/ Reset after each char\n}<\/code><\/pre>\n<p>This enabled Cameron to paint ANSI-colored ASCII like you would in Photoshop, one character at a time.<\/p>\n<figure class=\"wp-block-video\"><video height=\"1728\" width=\"2176\" controls poster=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/app1-poster.png\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/firstApp.mov\" preload=\"none\"><\/video><\/figure>\n<p>But now he had to export it into the real Copilot CLI codebase.<\/p>\n<h2 class=\"wp-block-heading\">Part 4: Exporting to Ink (React for the terminal)<\/h2>\n<p><a href=\"https:\/\/github.com\/vadimdemedes\/ink\">Ink<\/a> is a React renderer for building CLIs using JSX components. Instead of writing to the DOM, components render to stdout.<\/p>\n<p>Cameron asked Copilot to help generate an Ink component that would:<\/p>\n<ul class=\"wp-block-list\">\n<li>Accept frames<\/li>\n<li>Render them line-by-line<\/li>\n<li>Animate them with state updates<\/li>\n<li>Integrate cleanly into the CLI codebase<\/li>\n<\/ul>\n<h3 class=\"wp-block-heading\">Simplified Ink frame renderer<\/h3>\n<pre class=\"wp-block-code\"><code>import React from \"react\";\nimport { Box, Text } from \"ink\";\n\n\/**\n * Render a single ASCII frame.\n *\/\nexport const CopilotBanner = ({ frame }) =&gt; (\n  &lt;Box flexDirection=\"column\"&gt;\n    {frame.split(\"n\").map((line, i) =&gt; (\n      &lt;Text key={i}&gt;{line}&lt;\/Text&gt;\n    ))}\n  &lt;\/Box&gt;\n);<\/code><\/pre>\n<p>And a minimal animation wrapper:<\/p>\n<pre class=\"wp-block-code\"><code>export const AnimatedBanner = () =&gt; {\n  const [i, setI] = React.useState(0);\n\n  React.useEffect(() =&gt; {\n    const id = setInterval(() =&gt; setI(x =&gt; (x + 1) % frames.length), 75);\n    return () =&gt; clearInterval(id);\n  }, []);\n\n  return &lt;CopilotBanner frame={frames[i]} \/&gt;;\n};<\/code><\/pre>\n<p>This gave Cameron the confidence to open a pull request (his first engineering pull request in nine years at GitHub).<\/p>\n<p>\u201cCopilot filled in syntax I didn\u2019t know,\u201d Cameron said. \u201cBut I still made all the architectural decisions.\u201d<\/p>\n<p>Now it was time for the engineering team to turn a prototype into something production-worthy.<\/p>\n<h2 class=\"wp-block-heading\">Part 5: Terminal animation isn\u2019t solved technology<\/h2>\n<p>Andy Feller (<a href=\"https:\/\/github.com\/andyfeller\">@andyfeller<\/a>), a long-time GitHub engineer behind the GitHub CLI, partnered with Cameron to bring the animation into the Copilot CLI codebase.<\/p>\n<p>Unlike browsers\u2014which share rendering engines, accessibility APIs, and standards like WCAG\u2014terminal environments are a patchwork of behaviors inherited from decades-old hardware like the VT100. There\u2019s no DOM, no semantic structure, and only partial agreement on capabilities across terminals. This makes even \u201csimple\u201d UI design problems in the terminal uniquely challenging, especially as AI-driven workflows push CLIs into daily use for more developers.<\/p>\n<p>\u201cThere\u2019s no framework for terminal animations,\u201d Andy explained. \u201cWe had to figure out how to do this without flickering, without breaking accessibility, and across wildly different terminals.\u201d<\/p>\n<p>Andy broke the engineering challenges into four broad categories:<\/p>\n<h3 class=\"wp-block-heading\">Challenge 1: From banner to ready without flickering<\/h3>\n<p>Most terminals repaint the entire viewport when new content arrives. At the same time, CLIs come with a strict usability expectation: when developers run a command, they want to get to work immediately. Any animation that flickers, blocks input, or lingers too long actively degrades the experience.<\/p>\n<p>This created a core tension the team had to resolve: how to introduce a brief, animated banner without slowing startup, stealing focus, or destabilizing the terminal render loop.<\/p>\n<p>In practice, this was complicated by the fact that terminals behave differently under load. Some:<\/p>\n<ul class=\"wp-block-list\">\n<li>Throttle fast writes<\/li>\n<li>Reveal cleared frames momentarily<\/li>\n<li>Buffer output differently<\/li>\n<li>Repaint the cursor region inconsistently<\/li>\n<\/ul>\n<p>To avoid flicker while keeping the CLI responsive across popular terminals like iTerm2, Windows Terminal, and VS Code, the team had to carefully coordinate several interdependent concerns:<\/p>\n<ul class=\"wp-block-list\">\n<li>Keeping the animation under three seconds so it never delayed user interaction<\/li>\n<li>Separating static and non-static components to minimize unnecessary redraws<\/li>\n<li>Initializing MCP servers, custom agents, and user setup without blocking render<\/li>\n<li>Working within Ink\u2019s asynchronous re-rendering model<\/li>\n<\/ul>\n<p>The result was an animation treated as a non-blocking, best-effort enhancement\u2014visible when it could be rendered safely, but never at the expense of startup performance or usability.<\/p>\n<h3 class=\"wp-block-heading\">Challenge 2: Brand color mapping in ANSI<\/h3>\n<p>\u201cANSI color consistency simply doesn\u2019t exist,\u201d Andy said.\u00a0<\/p>\n<p>Most modern terminals support 8-bit color, allowing CLIs to choose from 256 colors. However, how those colors are actually rendered varies widely based on terminal themes, OS settings, and user accessibility overrides. In practice, CLIs can\u2019t rely on exact hues\u2014or even consistent contrast\u2014across environments.<\/p>\n<p>The Copilot banner introduced an additional complexity: although it\u2019s rendered using text characters, the block-letter Copilot logo functions as a <strong>graphical object<\/strong>, not readable body text. Under accessibility guidelines, non-text graphical elements have different contrast requirements than text, and they must remain perceivable without relying on fine detail or precise color matching.<\/p>\n<p>To account for this, the team deliberately chose a minimal 4-bit ANSI palette\u2014one of the few color modes most terminals allow users to customize\u2014to ensure the animation remained legible under high-contrast themes, low-vision settings, and color overrides.<\/p>\n<p>This meant the team had to:<\/p>\n<ul class=\"wp-block-list\">\n<li>Treat the Copilot wordmark as non-text graphical content with appropriate contrast requirements<\/li>\n<li>Select ANSI color codes that <em>approximate<\/em> the Copilot palette without relying on exact hues<\/li>\n<li>Satisfy WCAG contrast guidance for both text and non-text elements<\/li>\n<li>Ensure the animation remained legible in light and dark terminals<\/li>\n<li>Degrade gracefully when users override terminal colors for accessibility<\/li>\n<li>Test color combinations across multiple terminal emulators and theme configurations<\/li>\n<\/ul>\n<p>Rather than encoding brand colors directly, the animation maps semantic roles\u2014such as borders, eyes, highlights, and text\u2014to ANSI color slots that terminals can reinterpret safely. This allows the banner to remain recognizable without assuming control over the user\u2019s color environment.<\/p>\n<figure class=\"wp-block-image size-full\"><img data-opt-id=1889180884  fetchpriority=\"high\" decoding=\"async\" data-recalc-dims=\"1\" width=\"872\" height=\"246\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/banner1.png?resize=872%2C246\" alt=\"Dark mode version of the GitHub Copilot CLI banner.\" class=\"wp-image-93470\" \/><\/figure>\n<figure class=\"wp-block-image size-full\"><img data-opt-id=924253276  fetchpriority=\"high\" decoding=\"async\" data-recalc-dims=\"1\" width=\"869\" height=\"242\" src=\"https:\/\/github.blog\/wp-content\/uploads\/2026\/01\/banner2.png?resize=869%2C242\" alt=\"Light mode version of the GitHub Copilot CLI banner.\" class=\"wp-image-93471\" \/><\/figure>\n<h3 class=\"wp-block-heading\">Challenge 3: Making the animation maintainable<\/h3>\n<p>Cameron\u2019s prototype was a great starting point for Andy to incorporate into the Copilot CLI but it wasn\u2019t without its challenges:<\/p>\n<ul class=\"wp-block-list\">\n<li>Banner consisted of ~20 animation frames covering an 11\u00d778 area<\/li>\n<li>There are ~10 animation elements to stylize in any given frame<\/li>\n<li>Needed a way to separate the text of the frame from the colors involved<\/li>\n<li>Each frame mapped hard coded colors to row and column coordinates<\/li>\n<li>Each frame required precise timing to display Cameron\u2019s vision<\/li>\n<\/ul>\n<p>First, the animation was broken down into distinct animation elements that could be used to create separate light and dark themes:<\/p>\n<pre class=\"wp-block-code\"><code>type AnimationElements =\n    | \"block_text\"\n    | \"block_shadow\"\n    | \"border\"\n    | \"eyes\"\n    | \"head\"\n    | \"goggles\"\n    | \"shine\"\n    | \"stars\"\n    | \"text\";\n\ntype AnimationTheme = Record&lt;AnimationElements, ANSIColors&gt;;\n\nconst ANIMATION_ANSI_DARK: AnimationTheme = {\n    block_text: \"cyan\",\n    block_shadow: \"white\",\n    border: \"white\",\n    eyes: \"greenBright\",\n    head: \"magentaBright\",\n    goggles: \"cyanBright\",\n    shine: \"whiteBright\",\n    stars: \"yellowBright\",\n    text: \"whiteBright\",\n};\n\nconst ANIMATION_ANSI_LIGHT: AnimationTheme = {\n    block_text: \"blue\",\n    block_shadow: \"blackBright\",\n    border: \"blackBright\",\n    eyes: \"green\",\n    head: \"magenta\",\n    goggles: \"cyan\",\n    shine: \"whiteBright\",\n    stars: \"yellow\",\n    text: \"black\",\n};<\/code><\/pre>\n<p>Next, the overall animation and subsequent frames would capture content, color, duration needed to animate the banner:<\/p>\n<pre class=\"wp-block-code\"><code>interface AnimationFrame {\n    title: string;\n    duration: number;\n    content: string;\n    colors?: Record&lt;string, AnimationElements&gt;; \/\/ Map of \"row,col\" positions to animation elements\n}\n\ninterface Animation {\n    metadata: {\n        id: string;\n        name: string;\n        description: string;\n    };\n    frames: AnimationFrame[];\n}<\/code><\/pre>\n<p>Then, each animation frame was captured to separate frame content from stylistic and animation details, resulting in over 6,000 lines of TypeScript to safely animate three seconds of the Copilot logo across terminals with wildly different rendering and accessibility behaviors:<\/p>\n<pre class=\"wp-block-code\"><code>    const frames: AnimationFrame[] = [\n        {\n            title: \"Frame 1\",\n            duration: 80,\n            content: `\n\u250c\u2510\n\u2502\u2502\n\n\n\n\n\n\n\n\u2502\u2502\n\u2514\u2518`,\n            colors: {\n                \"1,0\": \"border\",\n                \"1,1\": \"border\",\n                \"2,0\": \"border\",\n                \"2,1\": \"border\",\n                \"10,0\": \"border\",\n                \"10,1\": \"border\",\n                \"11,0\": \"border\",\n                \"11,1\": \"border\",\n            },\n        },\n        {\n            title: \"Frame 2\",\n            duration: 80,\n            content: `\n\u250c\u2500\u2500     \u2500\u2500\u2510\n\u2502         \u2502\n \u2588\u2584\u2584\u2584\n \u2588\u2588\u2588\u2580\u2588\n \u2588\u2588\u2588 \u2590\u258c\n \u2588\u2588\u2588 \u2590\u258c\n   \u2580\u2580\u2588\u258c\n   \u2590 \u258c\n    \u2590\n\u2502\u2588\u2584\u2584\u258c     \u2502\n\u2514\u2580\u2580\u2580    \u2500\u2500\u2518`,\n            colors: {\n                \"1,0\": \"border\",\n                \"1,1\": \"border\",\n                \"1,2\": \"border\",\n                \"1,8\": \"border\",\n                \"1,9\": \"border\",\n                \"1,10\": \"border\",\n                \"2,0\": \"border\",\n                \"2,10\": \"border\",\n                \"3,1\": \"head\",\n                \"3,2\": \"head\",\n                \"3,3\": \"head\",\n                \"3,4\": \"head\",\n                \"4,1\": \"head\",\n                \"4,2\": \"head\",\n                \"4,3\": \"goggles\",\n                \"4,4\": \"goggles\",\n                \"4,5\": \"goggles\",\n                \"5,1\": \"head\",\n                \"5,2\": \"goggles\",\n                \"5,3\": \"goggles\",\n                \"5,5\": \"goggles\",\n                \"5,6\": \"goggles\",\n                \"6,1\": \"head\",\n                \"6,2\": \"goggles\",\n                \"6,3\": \"goggles\",\n                \"6,5\": \"goggles\",\n                \"6,6\": \"goggles\",\n                \"7,3\": \"goggles\",\n                \"7,4\": \"goggles\",\n                \"7,5\": \"goggles\",\n                \"7,6\": \"goggles\",\n                \"8,3\": \"eyes\",\n                \"8,5\": \"head\",\n                \"9,4\": \"head\",\n                \"10,0\": \"border\",\n                \"10,1\": \"head\",\n                \"10,2\": \"head\",\n                \"10,3\": \"head\",\n                \"10,4\": \"head\",\n                \"10,10\": \"border\",\n                \"11,0\": \"border\",\n                \"11,1\": \"head\",\n                \"11,2\": \"head\",\n                \"11,3\": \"head\",\n                \"11,8\": \"border\",\n                \"11,9\": \"border\",\n                \"11,10\": \"border\",\n            },\n        },<\/code><\/pre>\n<p>Finally, each animation frame is rendered building segments of text based on consecutive color usage with the necessary ANSI escape codes:<\/p>\n<pre class=\"wp-block-code\"><code>           {frameContent.map((line, rowIndex) =&gt; {\n                const truncatedLine = line.length &gt; 80 ? line.substring(0, 80) : line;\n                const coloredChars = Array.from(truncatedLine).map((char, colIndex) =&gt; {\n                    const color = getCharacterColor(rowIndex, colIndex, currentFrame, theme, hasDarkTerminalBackground);\n                    return { char, color };\n                });\n\n                \/\/ Group consecutive characters with the same color\n                const segments: Array&lt;{ text: string; color: string }&gt; = [];\n                let currentSegment = { text: \"\", color: coloredChars[0]?.color || theme.COPILOT };\n\n                coloredChars.forEach(({ char, color }) =&gt; {\n                    if (color === currentSegment.color) {\n                        currentSegment.text += char;\n                    } else {\n                        if (currentSegment.text) segments.push(currentSegment);\n                        currentSegment = { text: char, color };\n                    }\n                });\n                if (currentSegment.text) segments.push(currentSegment);\n\n                return (\n                    &lt;Text key={rowIndex} wrap=\"truncate\"&gt;\n                        {segments.map((segment, segIndex) =&gt; (\n                            &lt;Text key={segIndex} color={segment.color}&gt;\n                                {segment.text}\n                            &lt;\/Text&gt;\n                        ))}\n                    &lt;\/Text&gt;\n                );\n            })}<\/code><\/pre>\n<h2 class=\"wp-block-heading\">Challenge 4: Accessibility-first design<\/h2>\n<p>The engineering team approached the banner with the same philosophy as the <a href=\"https:\/\/github.blog\/engineering\/user-experience\/building-a-more-accessible-github-cli\/\">GitHub CLI\u2019s accessibility work<\/a>:<\/p>\n<ul class=\"wp-block-list\">\n<li>Respect global color overrides both in terminal and system preferences<\/li>\n<li>After the first use, avoid animations unless explicitly enabled via the Copilot CLI configuration file<\/li>\n<li>Minimize ANSI instructions that can confuse assistive tech<\/li>\n<\/ul>\n<p>\u201cCLI accessibility is under researched,\u201d Andy noted. \u201cWe\u2019ve learned a lot from users who are blind as well as users with low vision, and those lessons shaped this project.\u201d<\/p>\n<p>Because of this, the animation is opt-in and gated behind its own flag\u2014so it\u2019s not something developers see by default. And when developers run the CLI in \u2013screen-reader mode, the banner is automatically skipped so no decorative characters or motion are sent to assistive technologies.<\/p>\n<h2 class=\"wp-block-heading\">Part 6: An architecture built to scale<\/h2>\n<p>By the end of the refactor, the team had:<\/p>\n<ul class=\"wp-block-list\">\n<li>Frames stored as plain text<\/li>\n<li>Animation elements<\/li>\n<li>Themes as simple mappings<\/li>\n<li>A runtime colorization step<\/li>\n<li>Ink-driven timing and rendering<\/li>\n<li>A maintainable foundation for future animations<\/li>\n<\/ul>\n<p>This pattern\u2014storing frames as plain text, layering semantic roles, and applying themes at runtime\u2014isn\u2019t specific to Copilot. It\u2019s a reusable approach for anyone building terminal UIs or animations.<\/p>\n<h2 class=\"wp-block-heading\">Part 7: What this project reveals about building for the terminal<\/h2>\n<p>A \u201csimple ASCII banner\u201d turned into:<\/p>\n<ul class=\"wp-block-list\">\n<li>A frame-based animation tool that didn\u2019t exist<\/li>\n<li>A custom ANSI color palette strategy<\/li>\n<li>A new Ink component<\/li>\n<li>A maintainable rendering architecture<\/li>\n<li>Accessibility-first CLI design choices<\/li>\n<li>A designer\u2019s first engineering contribution<\/li>\n<li>Real-world testing across diverse terminals<\/li>\n<li>Open source contributions from the community<\/li>\n<\/ul>\n<p>\u201cThe most rewarding part was stepping into open source for the first time,\u201d Cameron said. \u201cWith Copilot, I was able to build out\u00a0 my MVP ASCII animation tool into a full open source app at <a href=\"http:\/\/ascii-motion.app\/\">ascii-motion.app<\/a>,. Someone fixed a typo in my <a href=\"https:\/\/github.com\/CameronFoxly\/Ascii-Motion\">README<\/a>, and it made my day.\u201d<\/p>\n<p>As Andy pointed out, building accessible experiences for CLIs is still largely unexplored territory and far behind the tooling and standards available for the web.<\/p>\n<p>Today, developers are already contributing to Cameron\u2019s ASCII Motion tool, and the Copilot CLI team can ship new animations without rebuilding the system.<\/p>\n<p>This is what building for the terminal demands: deep understanding of constraints, discipline around accessibility, and the willingness to invent tooling where none exists.<\/p>\n<h3 class=\"wp-block-heading\">Use GitHub Copilot in your terminal<\/h3>\n<p>The GitHub Copilot CLI brings AI-assisted workflows directly into your terminal \u2014 including commands for explaining code, generating files, refactoring, testing, and navigating unfamiliar projects.<\/p>\n<div class=\"wp-block-group post-content-cta has-global-padding is-layout-constrained wp-block-group-is-layout-constrained\">\n<p><a href=\"https:\/\/github.com\/features\/copilot\">Try GitHub Copilot CLI &gt;<\/a><\/p>\n<\/div>\n<p>The post <a href=\"https:\/\/github.blog\/engineering\/from-pixels-to-characters-the-engineering-behind-github-copilot-clis-animated-ascii-banner\/\">From pixels to characters: The engineering behind GitHub Copilot CLI\u2019s animated ASCII banner<\/a> appeared first on <a href=\"https:\/\/github.blog\/\">The GitHub Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>Most people think ASCII art is simple, and a nostalgic remnant of the early internet. But when the GitHub Copilot [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3320,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[8],"tags":[],"class_list":["post-3319","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-github-engineering"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/3319","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=3319"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/3319\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media\/3320"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=3319"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=3319"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=3319"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}