From 94766dca09dcc30c1bbbe0fd0f788a1a48866fdd Mon Sep 17 00:00:00 2001 From: PeterGuy326 <47820304+PeterGuy326@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:21:15 +0800 Subject: [PATCH] fix(#18): use the selected Open Herd icon for organization avatars --- brand/bytefolk/README.md | 56 +++++++++------- brand/bytefolk/avatar-1024.png | Bin 8309 -> 15097 bytes .../bytefolk-concept-c-open-herd-mark.svg | 13 ++++ .../bytefolk/bytefolk-concept-c-open-herd.svg | 35 ++++++++++ brand/bytefolk/render-avatar.sh | 11 ++-- scripts/validate-brand-assets.rb | 60 ++++++++++++++---- 6 files changed, 136 insertions(+), 39 deletions(-) create mode 100644 brand/bytefolk/bytefolk-concept-c-open-herd-mark.svg create mode 100644 brand/bytefolk/bytefolk-concept-c-open-herd.svg diff --git a/brand/bytefolk/README.md b/brand/bytefolk/README.md index 439b86f..437b0e2 100644 --- a/brand/bytefolk/README.md +++ b/brand/bytefolk/README.md @@ -1,40 +1,41 @@ -# ByteFolk brand mark +# ByteFolk organization avatar -The ByteFolk organization mark is the minimal one-color **horned B** approved in -[issue #18](https://github.com/bytefolk/.github/issues/18). It combines a capital `B`, -two modular horns, and an extended lower bowl that hints at the digital-employee mascot without -turning the logo into an illustration. +The current organization avatar uses the **Open Herd icon without the ByteFolk wordmark**, +as specified in [issue #18, R2](https://github.com/bytefolk/.github/issues/18). +Its only graphic source is `g#open-herd-mark` in +[`bytefolk-concept-c-open-herd.svg`](bytefolk-concept-c-open-herd.svg). +The mark-only SVG preserves that group's four paths, five rectangles, and colors exactly; +it only removes the horizontal lockup's translation and wordmark and fits the icon canvas. +R2 supersedes the earlier R1 one-color horned B avatar direction. ## Assets | File | Use | | --- | --- | -| `symbol.svg` | Primary mark on white or very light surfaces | -| `symbol-reversed.svg` | White mark on black or dark surfaces | -| `lockup.svg` | Horizontal symbol and `ByteFolk` wordmark | -| `avatar-1024.png` | Exact opaque upload artifact for the GitHub organization profile | +| `bytefolk-concept-c-open-herd.svg` | Unchanged original Open Herd horizontal lockup and source group | +| `bytefolk-concept-c-open-herd-mark.svg` | Exact icon-only extraction; the current avatar export source | +| `avatar-1024.png` | Opaque 1024×1024 Open Herd upload artifact for the GitHub organization profile | +| `symbol.svg` | Historical R1 black horned B; not an avatar source | +| `symbol-reversed.svg` | Historical R1 white horned B; not an avatar source | +| `lockup.svg` | Historical R1 horned B lockup; not an avatar source | -The full-body mascot is a separate expressive brand character. It must not replace the symbol in -organization avatars, favicons, repository ownership marks, or compact product navigation. +Historical R1 SVGs remain unchanged for reference. Their presence or earlier approval must not +be used to restore the black B organization avatar. The full-body mascot is also a separate asset. ## Usage -- Primary ink: `#141414`. -- Reversed ink: `#FFFFFF`. -- Keep clear space of at least one horn-stem width around the symbol. -- Keep every visible avatar pixel (any grayscale value below 250) inside the 496 px-radius circle +- Preserve the source palette: blue `#1677ff`, purple `#722ed1`, ink `#141414`, and white `#ffffff`. +- Use the icon without visible text for organization avatars and compact navigation. +- Keep every visible avatar pixel (any color channel below 250) inside the 496 px-radius circle centered at `(511.5, 511.5)` on the 1024 px canvas. This leaves 16 px inside GitHub's 512 px circular crop. -- Use the standalone symbol at 16 px or larger. -- Use the horizontal lockup at 120 px wide or larger. +- Use the standalone icon at 16 px or larger. Reserve the full lockup for wider brand surfaces. - Upload `avatar-1024.png` without recropping or recoloring it. The image already includes padding and a validated circular safe area for GitHub's presentation. -- On dark surfaces, use `symbol-reversed.svg`; do not place the primary black mark directly on a - dark background. +- Preserve the opaque white avatar background in both GitHub light and dark themes. -Do not add gradients, shadows, outlines, background frames, eyes, nostrils, mane tiles, extra -colors, text inside the mark, or product-specific decoration. Do not stretch, rotate, crop, or -rearrange the horns and B geometry. +Do not redraw, recolor, stretch, rotate, crop, or rearrange the source geometry. Do not add +gradients, shadows, outlines, text, or decoration. Preserve the existing white details and seams. ## Reproduce the GitHub avatar @@ -43,7 +44,14 @@ ImageMagick is required for deterministic raster export: ```bash brand/bytefolk/render-avatar.sh brand/bytefolk/render-avatar.sh --check +ruby scripts/validate-brand-assets.rb ``` -The first command rewrites `avatar-1024.png` from `symbol.svg`. The second renders to a temporary -file and verifies byte-for-byte equality with the tracked artifact. +The first command renders the mark-only SVG at density 768, scales proportionally to fit +900×900, centers it on an opaque white 1024×1024 canvas, and writes `avatar-1024.png`. +The second renders to a temporary file and verifies byte-for-byte equality with the tracked +artifact. The validator checks the approved source, icon geometry and palette, and avatar pixels. + +The tracked export was produced with ImageMagick 7.1.2-22. Other renderer versions may produce +different antialiasing or PNG bytes; do not replace the approved artifact without verifying the +source and visual result. Upload the tracked PNG, then verify GitHub's served pixels separately. diff --git a/brand/bytefolk/avatar-1024.png b/brand/bytefolk/avatar-1024.png index 82e530bc6a10615e5bd08c7b24d2e6828ff3dd74..a023ee161de8f71b3d2b43e8350f0060da117a57 100644 GIT binary patch literal 15097 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 literal 8309 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 IKl&#B3$ZS=!T + ByteFolk Open Herd organization mark + The mark-only organization icon. The ByteFolk wordmark is intentionally omitted. + + + + + + + + + + diff --git a/brand/bytefolk/bytefolk-concept-c-open-herd.svg b/brand/bytefolk/bytefolk-concept-c-open-herd.svg new file mode 100644 index 0000000..6e4e928 --- /dev/null +++ b/brand/bytefolk/bytefolk-concept-c-open-herd.svg @@ -0,0 +1,35 @@ + + ByteFolk Logo Concept C — Open Herd + Four independent contributors assemble into an abstract hybrid animal and human face beside an open technical wordmark. + + + + + + + + + + + + + + + + ByteFolk + diff --git a/brand/bytefolk/render-avatar.sh b/brand/bytefolk/render-avatar.sh index 511a90d..a35cd40 100755 --- a/brand/bytefolk/render-avatar.sh +++ b/brand/bytefolk/render-avatar.sh @@ -2,7 +2,7 @@ set -euo pipefail asset_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -source_svg="$asset_dir/symbol.svg" +source_svg="$asset_dir/bytefolk-concept-c-open-herd-mark.svg" tracked_png="$asset_dir/avatar-1024.png" mode="${1:-write}" @@ -15,9 +15,12 @@ render() { local destination="$1" magick \ -background '#ffffff' \ + -density 768 \ "$source_svg" \ - -filter box \ - -resize '1024x1024!' \ + -resize 900x900 \ + -gravity center \ + -background '#ffffff' \ + -extent 1024x1024 \ -alpha remove \ -alpha off \ -colorspace sRGB \ @@ -40,7 +43,7 @@ case "$mode" in echo "avatar-1024.png is stale; run brand/bytefolk/render-avatar.sh" >&2 exit 1 fi - echo "ByteFolk avatar matches symbol.svg" + echo "ByteFolk avatar matches the Open Herd mark-only source" ;; *) echo "Usage: brand/bytefolk/render-avatar.sh [write|--check]" >&2 diff --git a/scripts/validate-brand-assets.rb b/scripts/validate-brand-assets.rb index beda490..7ea98ac 100644 --- a/scripts/validate-brand-assets.rb +++ b/scripts/validate-brand-assets.rb @@ -3,10 +3,18 @@ require "pathname" require "rexml/document" require "zlib" +require "digest" ROOT = Pathname.new(File.expand_path("..", __dir__)).freeze ASSET_DIR = ROOT.join("brand", "bytefolk").freeze -SVG_NAMES = %w[symbol.svg symbol-reversed.svg lockup.svg].freeze +LEGACY_SVG_NAMES = %w[symbol.svg symbol-reversed.svg lockup.svg].freeze +SOURCE_NAME = "bytefolk-concept-c-open-herd.svg" +MARK_NAME = "bytefolk-concept-c-open-herd-mark.svg" +SVG_NAMES = (LEGACY_SVG_NAMES + [SOURCE_NAME, MARK_NAME]).freeze +# Issue #18 R2: preserve the owner's original source and approved rendered upload. +SOURCE_SHA256 = "160c6f108e25d5f6d2a06ae68bd3cc11715f4580c0a0d9d02060d016a4526e49" +AVATAR_SHA256 = "aefe9f3494bff30ff93809f929b04f709f5f36eedbfabdb5f5764efe78578461" +PALETTE = { "--bf-blue" => "#1677ff", "--bf-purple" => "#722ed1", "--bf-ink" => "#141414", "--bf-paper" => "#ffffff" }.freeze PROHIBITED_ELEMENTS = %w[filter foreignObject image linearGradient radialGradient script].freeze CIRCULAR_SAFETY_MARGIN = 16.0 INK_THRESHOLD = 250 @@ -14,6 +22,7 @@ errors = [] circular_metrics = nil geometry_signatures = {} +documents = {} SVG_NAMES.each do |name| path = ASSET_DIR.join(name) @@ -34,6 +43,7 @@ errors << "#{path.relative_path_from(ROOT)}: root element must be svg" next end + documents[name] = document errors << "#{path.relative_path_from(ROOT)}: viewBox must be present" if root.attributes["viewBox"].to_s.empty? %w[width height].each do |attribute| errors << "#{path.relative_path_from(ROOT)}: root #{attribute} is prohibited" if root.attributes[attribute] @@ -51,6 +61,8 @@ end end + next unless LEGACY_SVG_NAMES.include?(name) + symbol_group = REXML::XPath.first(document, "//*[starts-with(@id, 'bytefolk-symbol')]") if symbol_group child_geometry = symbol_group.elements.map do |element| @@ -63,15 +75,43 @@ end end -if geometry_signatures.length == SVG_NAMES.length && geometry_signatures.values.uniq.length != 1 +if geometry_signatures.length == LEGACY_SVG_NAMES.length && geometry_signatures.values.uniq.length != 1 errors << "ByteFolk primary, reversed, and lockup symbol geometry must remain identical" end +if documents[SOURCE_NAME] && documents[MARK_NAME] + unless Digest::SHA256.file(ASSET_DIR.join(SOURCE_NAME)).hexdigest == SOURCE_SHA256 + errors << "#{SOURCE_NAME}: original Open Herd source must remain unchanged" + end + source_group = REXML::XPath.first(documents[SOURCE_NAME], "//*[@id='open-herd-mark']") + mark = documents[MARK_NAME].root + unless mark.attributes["viewBox"] == "0 0 142 116" && + (mark.attributes.to_a.map(&:name) - %w[xmlns viewBox role aria-label]).empty? + errors << "#{MARK_NAME}: preserve the icon canvas and do not add root styling or transforms" + end + normalize = lambda do |element| + attributes = element.attributes.map do |key, value| + value = PALETTE.fetch(value[4...-1], value) if value.start_with?("var(") + value = "#ffffff" if value == "#fff" + [key, value] + end.sort + [element.name, attributes, element.elements.size] + end + source_geometry = source_group&.elements&.map { |element| normalize.call(element) } + icon_geometry = mark.elements.reject { |element| %w[title desc].include?(element.name) }.map { |element| normalize.call(element) } + unless source_geometry && icon_geometry == source_geometry + errors << "#{MARK_NAME}: icon geometry and palette must match the original group exactly, without text" + end +end + png_path = ASSET_DIR.join("avatar-1024.png") if !png_path.file? errors << "#{png_path.relative_path_from(ROOT)}: missing" else bytes = png_path.binread + unless Digest::SHA256.hexdigest(bytes) == AVATAR_SHA256 + errors << "#{png_path.relative_path_from(ROOT)}: does not match the approved R2 Open Herd upload" + end signature = "\x89PNG\r\n\x1A\n".b if bytes.byteslice(0, 8) != signature errors << "#{png_path.relative_path_from(ROOT)}: invalid PNG signature" @@ -107,7 +147,7 @@ if raw.bytesize == expected_size prior = Array.new(stride, 0) - pixels = [] + colors = {} cursor = 0 min_x = width min_y = height @@ -147,11 +187,8 @@ end row.each_slice(3).with_index do |(red, green, blue), x| - unless red == green && green == blue - errors << "#{png_path.relative_path_from(ROOT)}: avatar must remain grayscale" - break - end - next if red >= INK_THRESHOLD + colors[[red, green, blue]] = true + next if [red, green, blue].min >= INK_THRESHOLD min_x = [min_x, x].min min_y = [min_y, y].min @@ -161,12 +198,13 @@ max_ink_radius = [max_ink_radius, ink_radius].max outside_circle_ink += 1 if ink_radius > safe_radius end - pixels.concat(row) prior = row end - darkest = pixels.min - errors << "#{png_path.relative_path_from(ROOT)}: primary ink is not #141414" unless darkest == 20 + PALETTE.each_value do |hex| + rgb = hex.delete_prefix("#").scan(/../).map { |channel| channel.to_i(16) } + errors << "#{png_path.relative_path_from(ROOT)}: missing source color #{hex}" unless colors[rgb] + end margins = [min_x, min_y, width - 1 - max_x, height - 1 - max_y] if max_x.negative? || margins.any? { |margin| margin < 100 } errors << "#{png_path.relative_path_from(ROOT)}: insufficient rectangular padding #{margins.inspect}"