Repository navigation
Modernize README branding and rendering showcase #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| output/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
| <PropertyGroup> | ||
| <OutputType>Exe</OutputType> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <ProjectReference Include="../../MarkupString/MarkupString.csproj" /> | ||
| <ProjectReference Include="../../MarkupString.Ansi/MarkupString.Ansi.csproj" /> | ||
| <ProjectReference Include="../../MarkupString.Html/MarkupString.Html.csproj" /> | ||
| </ItemGroup> | ||
| </Project> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| using System.Net; | ||
| using MarkupString; | ||
| using MarkupString.Ansi; | ||
| using MarkupString.Html; | ||
| using MarkupString.Layout; | ||
|
|
||
| var registry = MarkupRegistry.Empty | ||
| .WithAnsi() | ||
| .WithHtml() | ||
| .WithLayoutImages(_ => true); | ||
|
|
||
| var output = Path.Combine("docs", "showcase", "output"); | ||
| Directory.CreateDirectory(output); | ||
|
|
||
| var box = new Stack( | ||
| [ | ||
| new Fields( | ||
| [ | ||
| new Field(P("Value"), P("Immutable styled text")), | ||
| new Field(P("Width"), P("Display cells")), | ||
| new Field(P("Targets"), P("ANSI · HTML · MXP")), | ||
| new Field(P("Unicode"), P("Grapheme-safe")), | ||
| ]) { Columns = 2, Gap = 4, Leader = P("·") }, | ||
| new Rule(P("OUTPUT HEALTH")), | ||
| new Gauge(92, 100) { Label = P("Layout"), Show = GaugeShow.Percent }, | ||
| new Rule(P("ONE VALUE · MANY FORMATS")), | ||
| new Flex( | ||
| [ | ||
| P("Terminal\nbox drawing").ToBlock().Sized(BlockSize.Percent(50), min: 16), | ||
| P("Browser\nsemantic HTML").ToBlock().Sized(BlockSize.Percent(50), min: 16), | ||
| ]) { Separator = P(" │ ") }, | ||
| ]) | ||
| .Bordered(P("MarkupString · BlockLayout"), BorderStyle.Double) | ||
| .Themed(new LayoutTheme | ||
| { | ||
| Border = BorderStyle.Double, | ||
| BorderColor = AnsiCodeParser.Parse("#8b5cf6"), | ||
| TitleColor = AnsiCodeParser.Parse("h#22d3ee"), | ||
| LabelColor = AnsiCodeParser.Parse("#fb7185"), | ||
| SeparatorColor = AnsiCodeParser.Parse("#64748b"), | ||
| GaugeFilledColor = AnsiCodeParser.Parse("#22d3ee"), | ||
| GaugeEmptyColor = AnsiCodeParser.Parse("#334155"), | ||
| }); | ||
|
|
||
| var boxValue = BlockLayout.Build(box, 62); | ||
| WritePage( | ||
| Path.Combine(output, "box-drawing.html"), | ||
| "Box drawing adapts to its renderer", | ||
| "A single block tree becomes precise terminal cells or responsive browser structure.", | ||
| boxValue, | ||
| registry); | ||
|
|
||
| var figure = new Figure( | ||
| new ImageMarkup("../../assets/markupstring-logo.png", "MarkupString logo", Width: 220), | ||
| MarkupText.Wrap(AnsiCodeParser.Parse("h#22d3ee"), | ||
| " ╭────────╮\n" + | ||
| " >──┤ M ├──≡\n" + | ||
| " ╰────────╯")) | ||
| { | ||
| Float = FigureFloat.Left, | ||
| Gap = 3, | ||
| Beside = MarkupText.Concat( | ||
| [ | ||
| MarkupText.Wrap(AnsiCodeParser.Parse("h#8b5cf6"), "One value, chosen at the boundary. "), | ||
| P("A terminal receives compact text art and readable copy. A browser receives the real image, alt text, and content that flows around it.") | ||
| ]), | ||
| }; | ||
|
|
||
| var figureValue = BlockLayout.Build(figure, 58); | ||
| WritePage( | ||
| Path.Combine(output, "image-drawing.html"), | ||
| "Images degrade into useful terminal art", | ||
| "The Figure keeps one intent while each renderer chooses the native representation.", | ||
| figureValue, | ||
| registry); | ||
|
|
||
| static MarkupText P(string text) => MarkupText.Plain(text); | ||
|
|
||
| static void WritePage(string path, string title, string subtitle, MarkupText value, MarkupRegistry registry) | ||
| { | ||
| var ansi = value.Render(MarkupFormat.Ansi, registry); | ||
| var ansiForBrowser = AnsiEscapeParser.Parse(ansi).Render(MarkupFormat.Html, registry); | ||
| var html = value.Render(MarkupFormat.Html, registry); | ||
| var escapedAnsi = WebUtility.HtmlEncode(ansi.Replace("\u001b", "\\e", StringComparison.Ordinal)); | ||
|
|
||
| File.WriteAllText(path, $$""" | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="utf-8"> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | ||
| <title>{{WebUtility.HtmlEncode(title)}}</title> | ||
| <style> | ||
| {{AnsiCss.Fixed}} | ||
| {{LayoutCss.Fixed}} | ||
| :root { color-scheme: dark; font-family: "DejaVu Sans", sans-serif; } | ||
| * { box-sizing: border-box; } | ||
| body { margin: 0; width: 1164px; height: 639px; overflow: hidden; color: #e2e8f0; background: radial-gradient(circle at 50% -20%, #312e81 0, #111827 42%, #070b14 100%); } | ||
| main { height: 100%; padding: 36px 30px 30px; } | ||
| header { display: flex; align-items: end; justify-content: space-between; gap: 28px; margin-bottom: 24px; } | ||
| h1 { margin: 0 0 7px; color: #f8fafc; font-size: 30px; letter-spacing: -0.035em; } | ||
| .subtitle { margin: 0; color: #94a3b8; font-size: 16px; } | ||
| .flow { display: flex; align-items: center; gap: 8px; color: #67e8f9; font: 700 13px "DejaVu Sans Mono", monospace; white-space: nowrap; } | ||
| .flow span { color: #64748b; } | ||
| .comparison { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; height: 478px; } | ||
| .panel { min-width: 0; overflow: hidden; border: 1px solid #334155; border-radius: 16px; background: rgba(15, 23, 42, .88); box-shadow: 0 24px 70px rgba(0, 0, 0, .3); } | ||
| .panel-head { height: 48px; display: flex; align-items: center; gap: 10px; padding: 0 17px; border-bottom: 1px solid #334155; background: rgba(30, 41, 59, .72); } | ||
| .dot { width: 10px; height: 10px; border-radius: 50%; background: #fb7185; box-shadow: 17px 0 #fbbf24, 34px 0 #22d3ee; margin-right: 32px; } | ||
| .label { font: 700 12px "DejaVu Sans Mono", monospace; letter-spacing: .08em; color: #cbd5e1; text-transform: uppercase; } | ||
| .native { margin-left: auto; padding: 4px 8px; border: 1px solid #475569; border-radius: 99px; color: #94a3b8; font: 600 10px "DejaVu Sans Mono", monospace; } | ||
| .terminal { height: calc(100% - 48px); margin: 0; padding: 25px 22px; overflow: hidden; color: #e2e8f0; background: #080d18; font: 13px/1.55 "DejaVu Sans Mono", monospace; white-space: pre; } | ||
| .browser { height: calc(100% - 48px); padding: 28px; overflow: hidden; color: #cbd5e1; background: #080d18; font: 15px/1.45 "DejaVu Sans Mono", monospace; } | ||
| .browser .ms-layout { margin-inline: auto; } | ||
| .browser .ms-box { border-color: #8b5cf6; background: #0f172a; box-shadow: 0 14px 32px rgba(0, 0, 0, .32); } | ||
| .browser .ms-box-title, .browser .ms-rule-title { color: #a78bfa; font-weight: 800; } | ||
| .browser .ms-gauge meter { accent-color: #0891b2; } | ||
| .browser .ms-figure-image { max-width: 220px; margin: 0 20px 14px 0; filter: drop-shadow(0 10px 20px rgba(0, 0, 0, .35)); } | ||
| .browser .ms-figure { font-family: "DejaVu Sans", sans-serif; } | ||
| .browser .ms-text { line-height: 1.65; } | ||
| .source { display: none; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <main> | ||
| <header> | ||
| <div><h1>{{WebUtility.HtmlEncode(title)}}</h1><p class="subtitle">{{WebUtility.HtmlEncode(subtitle)}}</p></div> | ||
| <div class="flow">MarkupText <span>→</span> renderer <span>→</span> native output</div> | ||
| </header> | ||
| <section class="comparison"> | ||
| <article class="panel"> | ||
| <div class="panel-head"><i class="dot"></i><span class="label">ANSI terminal</span><span class="native">SGR + cells</span></div> | ||
| <pre class="terminal">{{ansiForBrowser}}</pre> | ||
| </article> | ||
| <article class="panel"> | ||
| <div class="panel-head"><i class="dot"></i><span class="label">HTML browser</span><span class="native">semantic elements</span></div> | ||
| <div class="browser">{{html}}</div> | ||
| </article> | ||
| </section> | ||
| <pre class="source">{{escapedAnsi}}</pre> | ||
| </main> | ||
| </body> | ||
| </html> | ||
| """); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # README showcase captures | ||
|
|
||
| The two README screenshots are generated from MarkupString values rather than recreated by hand. | ||
| The checked-in captures use this supported Linux x64 environment: | ||
|
|
||
| - the .NET SDK selected by the repository's `global.json`; | ||
| - Python Playwright 1.59.0 with Chromium v1217 (`Chrome for Testing 147.0.7727.15`); | ||
| - ImageMagick 7.1.2-32; and | ||
| - DejaVu Sans and DejaVu Sans Mono 2.37. | ||
|
|
||
| Install Playwright and its browser with: | ||
|
|
||
| ```sh | ||
| uv tool install playwright==1.59.0 | ||
| playwright install chromium | ||
| ``` | ||
|
|
||
| ImageMagick, Fontconfig, and the two DejaVu fonts come from the operating system. The capture script | ||
| checks every prerequisite and stops with a specific error when the supported environment is not | ||
| present. After restoring the repository once, run from its root: | ||
|
|
||
| ```sh | ||
| docs/showcase/capture.sh | ||
| ``` | ||
|
|
||
| Each page places the same value's ANSI rendering beside its semantic HTML rendering. The ANSI side | ||
| is parsed back only to make its terminal styling visible in the browser capture; its text and SGR | ||
| styling come from `Render(MarkupFormat.Ansi)`. The capture script applies rounded alpha masks and | ||
| transparent padding so the dark cards sit cleanly on both GitHub README themes. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| #!/usr/bin/env bash | ||
| set -euo pipefail | ||
|
|
||
| fail() { | ||
| echo "capture: $*" >&2 | ||
| exit 1 | ||
| } | ||
|
|
||
| for tool in dotnet playwright magick fc-match fc-query awk; do | ||
| command -v "$tool" >/dev/null || fail "required tool '$tool' was not found" | ||
| done | ||
|
|
||
| [[ $(playwright --version) == "Version 1.59.0" ]] || | ||
| fail "Playwright 1.59.0 is required; install it with 'uv tool install playwright==1.59.0'" | ||
|
|
||
| magick_version=$(magick --version) | ||
| [[ ${magick_version%%$'\n'*} == "Version: ImageMagick 7.1.2-32 "* ]] || | ||
| fail "ImageMagick 7.1.2-32 is required" | ||
|
|
||
| for font in "DejaVu Sans" "DejaVu Sans Mono"; do | ||
| font_path=$(fc-match --format='%{file}' "$font" 2>/dev/null) | ||
| [[ -n $font_path ]] || fail "$font was not found" | ||
| font_version=$(fc-query --format='%{fontversion}' "$font_path") | ||
| [[ $font_version == "155320" ]] || fail "$font 2.37 is required" | ||
| done | ||
|
|
||
| browser_plan=$(playwright install --dry-run chromium) | ||
| [[ $browser_plan == *"Chrome for Testing 147.0.7727.15 (playwright chromium v1217)"* ]] || | ||
| fail "Playwright Chromium v1217 is required; run 'playwright install chromium'" | ||
| headless_dir=$(awk '/Chrome Headless Shell 147[.]0[.]7727[.]15/{found=1; next} found && /Install location:/{sub(/^[^:]+:[[:space:]]*/, ""); print; exit}' <<<"$browser_plan") | ||
| [[ -x $headless_dir/chrome-headless-shell-linux64/chrome-headless-shell ]] || | ||
| fail "Playwright Chromium v1217 is not installed; run 'playwright install chromium'" | ||
|
|
||
| repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) | ||
| output_dir="$repo_root/docs/showcase/output" | ||
| asset_dir="$repo_root/docs/assets" | ||
| capture_dir=$(mktemp -d) | ||
| trap 'rm -rf "$capture_dir"' EXIT | ||
|
|
||
| mask="$capture_dir/rounded-mask.png" | ||
| magick -size 1164x639 xc:black -fill white \ | ||
| -draw 'roundrectangle 0,0 1163,638 24,24' \ | ||
| "$mask" | ||
|
|
||
| cd "$repo_root" | ||
| dotnet run --project docs/showcase --no-restore | ||
|
|
||
| capture() { | ||
| local page=$1 | ||
| local asset=$2 | ||
| local raw="$capture_dir/$asset" | ||
| local clipped="$capture_dir/clipped-$asset" | ||
|
|
||
| playwright screenshot \ | ||
| --viewport-size="1164,639" \ | ||
| --color-scheme=dark \ | ||
| "file://$output_dir/$page" \ | ||
| "$raw" | ||
|
|
||
| magick composite -compose CopyOpacity "$mask" "$raw" "$clipped" | ||
| magick "$clipped" -bordercolor none -border 18 "$asset_dir/$asset" | ||
| } | ||
|
|
||
| capture box-drawing.html showcase-box-drawing.png | ||
| capture image-drawing.html showcase-image-drawing.png | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Verify the matched font family.
If
DejaVu Sans Monois absent,fc-matchcan return a different installed font. The version check can still pass, so the script can produce a capture with a substituted font. Check the matched family as well as its version before capture. (fontconfig.pages.freedesktop.org)🤖 Prompt for AI Agents