From 4b8ff451a15416f6b9eddce4fea7e41faf3c5e74 Mon Sep 17 00:00:00 2001 From: epalosh Date: Thu, 28 May 2026 14:14:43 -0500 Subject: [PATCH] Repo polish: README badges, social preview, FUNDING placeholder, blank-issue redirect --- .github/FUNDING.yml | 9 ++ .github/ISSUE_TEMPLATE/config.yml | 16 ++++ .github/social-preview.png | Bin 0 -> 25178 bytes README.md | 6 ++ tools/build_social_preview.py | 136 ++++++++++++++++++++++++++++++ 5 files changed, 167 insertions(+) create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/social-preview.png create mode 100644 tools/build_social_preview.py diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..41c0a3d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,9 @@ +# Optional. Sponsor button on the repo page wires here. +# Uncomment whichever platforms you've set up and put your handle in. +# Leaving everything commented means no Sponsor button shows — fine +# for now; flip on when you're ready to accept support. + +# github: epalosh # https://github.com/sponsors enrolled? +# ko_fi: epalosh # https://ko-fi.com/epalosh +# patreon: epalosh +# custom: ["https://your-site"] # any other URL (e.g. PayPal.me) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..f334ab4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,16 @@ +# Hide the "Open a blank issue" link at the bottom of the New Issue +# picker. Users who don't fit either template land in Discussions +# (questions/help) or the dedicated security path instead — keeps the +# issue tracker focused on actionable bug reports and feature requests. +blank_issues_enabled: false + +contact_links: + - name: Question or general help + url: https://github.com/epalosh/openfov/discussions/categories/q-a + about: Ask in Discussions if you're not sure something's a bug. Faster help, and the answer helps other users too. + - name: Show off your setup + url: https://github.com/epalosh/openfov/discussions/categories/show-and-tell + about: Got OpenFOV working with a specific webcam, monitor, or rig? Share what worked so others can follow. + - name: Security vulnerability + url: https://github.com/epalosh/openfov/security/advisories/new + about: Please report security issues privately, not as public bugs. See SECURITY.md for what's in scope. diff --git a/.github/social-preview.png b/.github/social-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..71e4e944568a8f10fa1b53cfab0b641a6cbbc3f0 GIT binary patch literal 25178 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 literal 0 HcmV?d00001 diff --git a/README.md b/README.md index b3d2233..5861787 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # OpenFOV +[![CI](https://github.com/epalosh/openfov/actions/workflows/ci.yml/badge.svg)](https://github.com/epalosh/openfov/actions/workflows/ci.yml) +[![Latest release](https://img.shields.io/github/v/release/epalosh/openfov?display_name=tag&sort=semver)](https://github.com/epalosh/openfov/releases) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Python: 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue)](https://www.python.org/downloads/) +[![Platform: Windows](https://img.shields.io/badge/Platform-Windows%2010%2F11-blue)](#install) + Webcam head tracking for iRacing. One installer, one launch — head tracking working. diff --git a/tools/build_social_preview.py b/tools/build_social_preview.py new file mode 100644 index 0000000..65d4f66 --- /dev/null +++ b/tools/build_social_preview.py @@ -0,0 +1,136 @@ +"""Generate the 1280×640 social preview banner for GitHub / X / Discord. + +GitHub uses this exact size; it's also what most other link-preview +consumers crop to. The banner has the FOV-cone mark on the left, the +project name + one-line tagline in the middle, and the repo URL in the +bottom-right corner. Mid-grey background matches the icon tile so the +brand stays consistent across the icon, taskbar, and social shares. + +Saved to `.github/social-preview.png`. Upload via repo Settings → +General → Social preview → Edit. + +Run via: + python tools/build_social_preview.py +""" + +from __future__ import annotations + +from pathlib import Path + +from PIL import Image, ImageDraw, ImageFont + + +# Output size — GitHub's recommended dimensions for repo social preview. +_W, _H = 1280, 640 + +# Match the icon palette. +_BG = (82, 90, 100, 255) # #525a64 — same tile color as the .ico +_HEAD_FILL = (10, 13, 16, 255) +_HEAD_STROKE = (255, 255, 255, 255) +_CONE_FILL = (52, 174, 158, 255) # solid teal +_CONE_STROKE = (31, 119, 104, 255) # darker teal +_TITLE_FG = (240, 244, 248, 255) +_TAGLINE_FG = (180, 188, 196, 255) +_FOOTER_FG = (120, 130, 140, 255) + + +def _try_font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont: + """Find a sensible system font. Falls back to Pillow's default if + nothing is available (rare on Windows). We try Segoe UI first to + match the in-app Qt look.""" + candidates = [ + "C:/Windows/Fonts/segoeuib.ttf" if bold else "C:/Windows/Fonts/segoeui.ttf", + "C:/Windows/Fonts/arialbd.ttf" if bold else "C:/Windows/Fonts/arial.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold + else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", + ] + for p in candidates: + if Path(p).exists(): + return ImageFont.truetype(p, size) + return ImageFont.load_default() + + +def _draw_logo(img: Image.Image, cx: int, cy: int, scale: float) -> None: + """Render the FOV-cone mark centered on (cx, cy). Geometry mirrors + build_icon.py but at a different scale; we keep them in lockstep + by hand since the icon canvas and the banner have different + aspect ratios and absolute size.""" + + # Coordinates in the original 256-px icon viewBox. + head_cx, head_cy, head_r = 86, 128, 52 + cone_apex = (99, 128) + cone_top = (226, 45) + cone_bot = (226, 211) + # Center the design at (128, 128) within the viewBox; offset to (cx, cy). + def s(x: float, y: float) -> tuple[float, float]: + return (cx + (x - 128) * scale, cy + (y - 128) * scale) + + cone = Image.new("RGBA", img.size, (0, 0, 0, 0)) + d = ImageDraw.Draw(cone) + apex = s(*cone_apex) + top = s(*cone_top) + bot = s(*cone_bot) + d.polygon([apex, top, bot], fill=_CONE_FILL) + stroke_w = max(3, int(round(8 * scale))) + d.line([apex, top], fill=_CONE_STROKE, width=stroke_w) + d.line([apex, bot], fill=_CONE_STROKE, width=stroke_w) + + head = Image.new("RGBA", img.size, (0, 0, 0, 0)) + d = ImageDraw.Draw(head) + hx, hy = s(head_cx, head_cy) + hr = head_r * scale + head_stroke_w = max(3, int(round(7 * scale))) + d.ellipse( + (hx - hr, hy - hr, hx + hr, hy + hr), + fill=_HEAD_FILL, + outline=_HEAD_STROKE, + width=head_stroke_w, + ) + + img.alpha_composite(cone) + img.alpha_composite(head) + + +def main() -> None: + img = Image.new("RGBA", (_W, _H), _BG) + + # Left third: FOV-cone logo. Scale 1.3 makes it ~440 px wide, + # leaving ~770 px for text on the right. + _draw_logo(img, cx=320, cy=320, scale=1.6) + + # Right two-thirds: title + tagline. Anchored at x=620 so the text + # column has consistent left alignment. + d = ImageDraw.Draw(img) + text_x = 620 + + title = "OpenFOV" + title_font = _try_font(124, bold=True) + d.text((text_x, 200), title, fill=_TITLE_FG, font=title_font) + + # Tagline: one line, sized to comfortably fit the column to the + # right of the logo. 32pt with Segoe UI fits "Webcam head tracking + # for iRacing" in well under 600 px. + tagline = "Webcam head tracking for iRacing" + tagline_font = _try_font(34) + d.text((text_x, 370), tagline, fill=_TAGLINE_FG, font=tagline_font) + + # Footer URL — small, lower-right. + footer_font = _try_font(24) + footer = "github.com/epalosh/openfov" + footer_bbox = d.textbbox((0, 0), footer, font=footer_font) + footer_w = footer_bbox[2] - footer_bbox[0] + d.text( + (_W - footer_w - 40, _H - 50), + footer, + fill=_FOOTER_FG, + font=footer_font, + ) + + out = Path(__file__).resolve().parent.parent / ".github" / "social-preview.png" + out.parent.mkdir(parents=True, exist_ok=True) + img.convert("RGB").save(out, format="PNG", optimize=True) + print(f"Wrote {out} ({out.stat().st_size} bytes)") + + +if __name__ == "__main__": + main()