Skip to content

Commit d91a00b

Browse files
StewAlexander-comClaudeclaude
authored
feat(site): full SEO + social-share meta, favicons, OG/Twitter images (#15)
Make the GitHub Pages start page instantly shareable on LinkedIn, Facebook, Messenger, iMessage, Slack, and X. Adds a complete favicon package, a PWA manifest, and a redesigned 1200×630 OG card plus a 1200×1200 square variant. - index.html: add robots, keywords, color-scheme, full OG set (site_name, locale, image:secure_url, image:type, image:alt) and full Twitter card set (image:alt, summary_large_image). og:image and twitter:image use absolute https:// URLs so scrapers can resolve them. Adds JSON-LD SoftwareApplication for Google rich results. - assets: new favicon-16/32 PNGs, multi-res favicon.ico, apple-touch-icon 180×180, icon-192/512 for PWA, redesigned og-image.png (1200×630, dark/amber, large text, no microtext), new og-image-square.png 1200×1200. - site.webmanifest with all icons and theme/background colors. - scripts/check_site.sh: validate the full meta package, require absolute https:// URLs for social images, assert PNG dimensions, parse manifest JSON and resolve every icon. - site/README.md: document the social-preview assets and how to validate with the FB / LinkedIn / X debuggers. Co-authored-by: Claude <claude@anthropic.local> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a85b067 commit d91a00b

12 files changed

Lines changed: 233 additions & 16 deletions

scripts/check_site.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,110 @@ need() {
2323
grep -q -- "$1" "$HTML" || fail "site/index.html missing: $1"
2424
}
2525
need "<title>Python Tutor"
26+
need 'name="description"'
27+
need 'name="robots"'
28+
need 'name="theme-color"'
29+
need 'rel="canonical"'
2630
need 'property="og:title"'
31+
need 'property="og:description"'
32+
need 'property="og:type"'
33+
need 'property="og:url"'
34+
need 'property="og:site_name"'
2735
need 'property="og:image"'
36+
need 'property="og:image:secure_url"'
37+
need 'property="og:image:type"'
38+
need 'property="og:image:width"'
39+
need 'property="og:image:height"'
40+
need 'property="og:image:alt"'
2841
need 'name="twitter:card"'
42+
need 'name="twitter:title"'
43+
need 'name="twitter:description"'
44+
need 'name="twitter:image"'
45+
need 'name="twitter:image:alt"'
46+
need 'rel="apple-touch-icon"'
47+
need 'rel="manifest"'
2948
need 'id="why"'
3049
need 'id="loop"'
3150
need 'id="screens"'
3251
need 'id="start"'
3352
ok "required <head> and section anchors present"
3453

54+
# Open Graph / Twitter image must be an absolute URL (most scrapers reject relative).
55+
grep -qE 'property="og:image"[^>]*content="https://' "$HTML" \
56+
|| fail "og:image must use an absolute https:// URL"
57+
grep -qE 'name="twitter:image"[^>]*content="https://' "$HTML" \
58+
|| fail "twitter:image must use an absolute https:// URL"
59+
ok "og:image and twitter:image use absolute URLs"
60+
61+
# Social-share asset files must exist on disk at the right sizes.
62+
need_file() { [ -f "$1" ] || fail "missing asset: $1"; }
63+
need_file "$SITE/assets/og-image.png"
64+
need_file "$SITE/assets/og-image-square.png"
65+
need_file "$SITE/assets/favicon.svg"
66+
need_file "$SITE/assets/favicon.ico"
67+
need_file "$SITE/assets/favicon-16.png"
68+
need_file "$SITE/assets/favicon-32.png"
69+
need_file "$SITE/assets/apple-touch-icon.png"
70+
need_file "$SITE/assets/icon-192.png"
71+
need_file "$SITE/assets/icon-512.png"
72+
need_file "$SITE/site.webmanifest"
73+
ok "favicon, manifest, and social-share assets present"
74+
75+
# Validate critical image dimensions where we can.
76+
python3 - "$SITE" <<'PY'
77+
import sys, struct, os
78+
site = sys.argv[1]
79+
def png_size(p):
80+
with open(p, "rb") as f:
81+
head = f.read(24)
82+
if head[:8] != b"\x89PNG\r\n\x1a\n":
83+
return None
84+
w, h = struct.unpack(">II", head[16:24])
85+
return w, h
86+
expected = {
87+
"assets/og-image.png": (1200, 630),
88+
"assets/og-image-square.png": (1200, 1200),
89+
"assets/apple-touch-icon.png": (180, 180),
90+
"assets/favicon-16.png": (16, 16),
91+
"assets/favicon-32.png": (32, 32),
92+
"assets/icon-192.png": (192, 192),
93+
"assets/icon-512.png": (512, 512),
94+
}
95+
bad = []
96+
for rel, want in expected.items():
97+
p = os.path.join(site, rel)
98+
got = png_size(p)
99+
if got != want:
100+
bad.append(f"{rel}: got {got}, want {want}")
101+
if bad:
102+
print("✗ wrong image dimensions:", file=sys.stderr)
103+
for b in bad: print(" " + b, file=sys.stderr)
104+
sys.exit(1)
105+
print("✓ all PNG asset dimensions correct")
106+
PY
107+
108+
# webmanifest must reference real icon files and be valid JSON.
109+
python3 - "$SITE" <<'PY'
110+
import json, os, sys
111+
site = sys.argv[1]
112+
mf = os.path.join(site, "site.webmanifest")
113+
data = json.load(open(mf))
114+
icons = data.get("icons", [])
115+
if not icons:
116+
print("✗ site.webmanifest has no icons", file=sys.stderr); sys.exit(1)
117+
missing = []
118+
for ic in icons:
119+
src = ic.get("src", "")
120+
rel = src[2:] if src.startswith("./") else src
121+
if not os.path.exists(os.path.join(site, rel)):
122+
missing.append(src)
123+
if missing:
124+
print("✗ manifest icons missing on disk:", file=sys.stderr)
125+
for m in missing: print(" " + m, file=sys.stderr)
126+
sys.exit(1)
127+
print(f"✓ site.webmanifest valid JSON with {len(icons)} resolvable icons")
128+
PY
129+
35130
# Start-page install content must be visible — this page is the entry point
36131
# to the repo, so the clone/install/run commands have to be there literally.
37132
need "git clone https://github.com/StewAlexander-com/python-tutor.git"

site/README.md

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,54 @@ on every push to `main` that touches `site/`.
1313

1414
```
1515
site/
16-
├── index.html # the landing page
16+
├── index.html # the landing page (full SEO + social meta)
1717
├── style.css # design tokens mirror frontend/base.css
18+
├── site.webmanifest # PWA manifest, references the icons below
1819
└── assets/
19-
├── favicon.svg
20-
├── og-image.png # 1200×630 social card (reused from frontend)
20+
├── favicon.svg # vector favicon, primary
21+
├── favicon.ico # 16/32/48 multi-res ICO for legacy clients
22+
├── favicon-16.png
23+
├── favicon-32.png
24+
├── apple-touch-icon.png # 180×180, full-bleed dark
25+
├── icon-192.png # PWA / Android home-screen
26+
├── icon-512.png # PWA / Android home-screen
27+
├── og-image.png # 1200×630 — Facebook, LinkedIn, Messenger, X
28+
├── og-image-square.png # 1200×1200 — square share / iMessage previews
2129
└── screenshots/ # six UI screenshots, lazy-loaded
2230
```
2331

32+
## Social preview & SEO
33+
34+
`index.html` includes:
35+
36+
- standard SEO: `<title>`, `description`, `keywords`, `robots`, canonical
37+
- Open Graph (Facebook / LinkedIn / Messenger / iMessage / Slack):
38+
`og:type`, `og:site_name`, `og:title`, `og:description`, `og:url`,
39+
`og:image` (+ `secure_url`, `type`, `width`, `height`, `alt`)
40+
- Twitter / X: `twitter:card=summary_large_image` plus title, description,
41+
image, and `twitter:image:alt`
42+
- JSON-LD `SoftwareApplication` for Google rich results
43+
- a full favicon set + `site.webmanifest` for PWA installs
44+
45+
`og:image` and `twitter:image` use **absolute** `https://` URLs (most
46+
social scrapers reject relative paths). All other assets use relative
47+
paths so the page works under the `/python-tutor/` GitHub Pages subpath
48+
and under `file://` previews.
49+
50+
### Validate the social preview
51+
52+
After deploy, paste the live URL into one of these debuggers — they
53+
fetch the page server-side and show what each platform will render:
54+
55+
- Facebook / Messenger: <https://developers.facebook.com/tools/debug/>
56+
- LinkedIn: <https://www.linkedin.com/post-inspector/>
57+
- X / Twitter: <https://cards-dev.twitter.com/validator> (or just paste
58+
into a draft tweet)
59+
- Generic: <https://www.opengraph.xyz/>
60+
61+
If you change the OG image, click "scrape again" in the FB debugger to
62+
bust the cache; LinkedIn caches for ~7 days and has no manual flush.
63+
2464
## Preview locally
2565

2666
The page is pure static HTML + CSS — no build step.
@@ -45,8 +85,14 @@ overview that points them at the repo and the two-command install.
4585

4686
`scripts/check_site.sh` runs from the repo root and verifies:
4787

48-
- referenced screenshots and OG image exist on disk
49-
- `<title>` and Open Graph tags are present
88+
- referenced screenshots and social assets exist on disk
89+
- complete `<head>` meta package: title, description, robots, canonical,
90+
theme-color, full Open Graph set, full Twitter card set
91+
- `og:image` and `twitter:image` are absolute `https://` URLs
92+
- favicon package (svg, ico, 16/32 png, apple-touch-icon 180×180,
93+
192 / 512 PWA icons) and `site.webmanifest` are present, with all
94+
PNGs at their declared dimensions
95+
- `site.webmanifest` is valid JSON and every icon resolves
5096
- no `localhost:` URLs are baked into hrefs/srcs
5197
- key sections (`#why`, `#loop`, `#screens`, `#start`) are wired up
5298

site/assets/apple-touch-icon.png

2.56 KB
Loading

site/assets/favicon-16.png

394 Bytes
Loading

site/assets/favicon-32.png

778 Bytes
Loading

site/assets/favicon.ico

1.35 KB
Binary file not shown.

site/assets/icon-192.png

2.62 KB
Loading

site/assets/icon-512.png

6.84 KB
Loading

site/assets/og-image-square.png

112 KB
Loading

site/assets/og-image.png

-36.3 KB
Loading

0 commit comments

Comments
 (0)