diff --git a/REPORT-facebook-cover-r919.md b/REPORT-facebook-cover-r919.md index 1c030a07..ba7d6519 100644 --- a/REPORT-facebook-cover-r919.md +++ b/REPORT-facebook-cover-r919.md @@ -177,6 +177,43 @@ Two notes on how that was read, because the obvious ways are both wrong here: icon is attributed to a neighbour. Reading the icon off a neighbouring row is exactly how a red run gets reported green, so it is written down. +## 11. Operator refinements, same day (2026-10-09) + +After looking at the rebuilt pictures the operator asked for two things. **No geometry changed** — R-919's +measured constants, its band and all its checks stand, and the red-proof still convicts the old layout. + +**1. The profile picture carries the logo mark only.** The observation was right: the mark plus the +„felhom.eu" lettering was too much for 176 px, and smaller than what the Page carried before this work — +because `build.py` shrinks the artwork until it fits inside the circle, where the original was simply +cropped by it. Dropping the lettering lets the mark grow from **69 % to 76 %** of the circle; the canvas +goes 932 → **648 px** and the floor in `profile_width` 720 → **640** (twice Meta's recommended 320 source; +720 was above what the mark alone needs and would have shrunk it for no gain). `logo.png` is split at a +**measured** row — ink y 5–289, blank band y 290–295, wordmark y 296–403 — not at a guessed fraction. +Both backgrounds now read at 40 px, which was not true before, so the preview's „dark recommended" line +lost its old reason and now states the real trade-off. + +**2. The covers use the website's own headline setting, and the real wordmark.** Read out of +`website/assets/site.css`, `.page-index .hero-text h1` is `font-weight: 700; letter-spacing: -0.03em`. +`build.py` was using **ExtraBold (800) with no tracking** — so the cover did not in fact match the page. +It now sets `HEADLINE_WEIGHT = "Bold"` and `HEADLINE_TRACK = -0.03`, with genuine per-glyph spacing. +Bold is narrower, so the headlines grew: A 57 → 62, B 58 → 63, C 47 → 50 (capitals 48, 49, 39 px against +the 25 px floor). „felhom.eu" is no longer typed at all: `wordmark()` draws the logo's own lettering, which +is set in „M+ 2c" / „Vremena Grotesk" — fonts this machine does not have (R-916), so any typed version was +a look-alike. The website hero shows the same `logo.png` on the same dark background, so the covers now +match the page exactly. The `DOMAIN` constant was removed rather than left as dead code implying otherwise. + +**One mistake worth recording, caught before it shipped.** The preview page's new profile paragraph added a +fourth `%d` and I left the argument tuple in the old order, so the phone paragraph rendered „the centre +**76** px of the **938**-pixel width, with a profile circle **1640** cover-pixels across." Every number was a +real number from the file, just the wrong one in each slot — which is exactly why it read as plausible +instead of crashing. Found by reading the rendered paragraph back rather than trusting the build's exit +code; fixed, and both paragraphs re-read afterwards. + +**Rebuilt and verified:** all checks pass on DooPlex, the phone simulation still shows 0 px cut and 0 px +under the profile circle on all three covers, and the renders were looked at at 1640 px, at phone width and +at 176/96/40 px. New profile hashes: `db05dae5…` dark, `a44849e4…` white — they changed because changing +them was the request. R-919 stays **VERIFY**: the operator still uploads and checks in the Facebook app. + ## 10. Teardown Nothing on any host, the hub or Facebook. The work ran in the two git working trees only; the throwaway diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index da215f1b..3395268c 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -278,7 +278,7 @@ stopping line that lies. | **R-915** | Business & legal | P4 | **The Meta app `felhom.eu` is in development mode, so posts it makes are seen only by people with a role on the app.** READ 2026-10-08 (Meta docs, cited in the spike); not measured. MEASURED: development mode does not refuse posting (both test writes HTTP 200). Live needs display name, contact e-mail, a Terms of Service URL, an app icon, a category and the app purpose (privacy-policy and data-deletion URLs listed beside them). The robot's Page assignment, missing at first, was done by the operator the same day. | **WAITING-ON-OPERATOR** | R-813 (the Terms of Service URL) | Before real public posts: switch the app to Live in the Meta developer page. If nothing is done: posts stay invisible to the public. After Live: CC adds the Page link to the website footer (not before — a link to a Page nobody can read is worse than none) | operator | | **R-916** | Business & legal | P4 | **The logo has no usable vector master: `website/assets/logo.svg` sets „felhom.eu" as live text in the fonts „M+ 2c" and „Vremena Grotesk", which DooPlex does not have, so every renderer here draws other letters.** SEEN 2026-10-08 (Facebook pictures task): librsvg drew the lettering in DejaVu; `fc-match` resolves the family to DejaVu Sans. The PNG (645 x 408) is the only faithful copy, which caps every picture made from it at about that size (`marketing/facebook/README.md`). | **WAITING-ON-OPERATOR** | the machine with the fonts | In Inkscape on that machine: select the lettering → Path → Object to Path → save as `website/assets/logo-master.svg`; then `marketing/facebook/build.py` can use it. If nothing is done: the PNG stays the master; larger prints will be soft | operator | | **R-917** | Business & legal | P4 | **`COPY.md` §2, the Page's longer description (867 characters), has nowhere to go: Facebook's current Pages experience has no long-description field at all.** FOUND 2026-10-08 (Page setup task, `audits/facebook-page-setup-2026-10-08/`). Looked in four places, all on the live Page as its admin: the Page's „Névjegy" tab (Rövid áttekintés / Személyes adatok / Részletek — only a 255-character „Bemutatkozás" and the pinned category), Business Suite's „Oldal módosítása" dialog (profile picture, cover, Bemutatkozás, category, phone, e-mail, address, website, social links — and nothing else), Facebook settings → „Oldal adatai" (redirects to the same Névjegy tab) and settings → „Oldal beállítása" (name, access, type, history, status, recommendation, messaging, data sharing). MEASURED by Graph with the Page token: `description`, `general_info` and `bio` all read `null`. Writing `description` by API would need `pages_manage_metadata`; the robot key's scopes are `read_insights, pages_show_list, business_management, pages_read_engagement, pages_read_user_content, pages_manage_posts, pages_manage_engagement, public_profile`, and the task's fences forbid adding a permission. So §2 is written, reviewed and unplaceable. **Options for the operator:** (a) leave §2 unused and let the 99-character intro plus the website carry it; (b) shorten §2 to ≤ 255 characters and make it the „Bemutatkozás" instead of §1 — but §1 was written for exactly that slot, so this is really „rewrite one of the two"; (c) publish §2 as the Page's first pinned post once the app is Live (R-915), which is where a long text actually gets read; (d) ask Meta support whether the field still exists for this Page type. Recommended (c) — the text reads like a post already. | **WAITING-ON-OPERATOR** | the choice a/b/c/d; (c) also waits on R-915 | Pick a/b/c/d. If nothing is done: §2 stays in `COPY.md` unused and the Page carries only the 99-character intro | operator | -| **R-919** | Business & legal | P3 | **On a phone the Facebook Page cuts the left edge of the cover: the „s” of „saját szabályaid” and the „f” of „felhom.eu” are gone.** MEASURED 2026-10-08 on the live Page in Chrome DevTools device mode, Pixel 9 (412 × 924, mobile user agent, after a reload so Facebook serves the mobile bundle): `audits/facebook-page-setup-2026-10-08/C2-phone-headline-cut-closeup.png`. The mobile Page header is **412 × 274 = 1,504:1**, so Facebook keeps our cover's full height and shows only the centre **938 px of its 1640 px width (57,2 %)** — **351 px cut from each side**. `marketing/facebook/build.py` builds to a safe area of the centre **1028 × 544** (306 px clear of each edge), which is **45 px wider per side than the phone actually shows**; the light text in `out/cover-c.png` runs from x 333 to x 997, and the left crop edge is x 351, so the first **18 px** of the text are cut. Covers A and B are built from the same safe area and will have the same edge. Two further facts this measurement establishes: **Meta's own help page is wrong about its own rendering** — it states the mobile cover is 2,4:1 where the Page header measures 1,504:1 — and the mobile profile circle is far bigger than assumed (172 px, centred, overlapping the bottom 112 px of the 274 px cover, i.e. source x 637–1030 × y 369–624 is hidden). Not re-cropped on Facebook, per the task's fence. **-- 2026-10-09, FIXED in the build:** `marketing/facebook/build.py` now takes the phone view from the measurement (`PHONE_HDR = (412, 274)` -> the centre 938 px), so SAFE is (391, 40)-(1249, 584) and the phone profile circle is the measured CENTRED box (637, 369, 393) rather than a left-anchored one. Every cover is drawn in a derived `BAND` (408, 48)-(1249, 340), and a `cap` check holds each headline's capital at >= 4 % of the cover height. The red-proof runs on EVERY build (`control_old_window`): it draws the headline where the old 640 x 360 assumption put it, x 328, and the check must reject it -- the phone's crop edge is x 351, so 23 px were cut. Against the three covers as committed at `a76207945e` the new check convicted 3 of 3 (A 23/22 px over the left/right edges, B 63/58 px plus 1017 content pixels under the phone circle, C 63/82 px plus 1778). The profile pictures are untouched -- sha256 identical before and after. | **VERIFY -- rebuilt on `main` 2026-10-09; NOT proven until the operator looks in the Facebook app.** The phone geometry is measured on ONE device (Pixel 9 emulated, 412 px, the mobile WEBSITE); the app may crop differently. | — | Operator: upload the rebuilt cover by hand (same option as now unless `out/preview.html` suggests otherwise -- the pictures cannot be set by API, R-914), then open the Page in the Facebook APP on a phone and say whether the headline and „felhom.eu” are whole. Close on that word. If the app still cuts them, re-measure there and move `PHONE_HDR`; the build follows it. If nothing is done: the Page keeps today's cover, which IS cut on phones | CC | +| **R-919** | Business & legal | P3 | **On a phone the Facebook Page cuts the left edge of the cover: the „s” of „saját szabályaid” and the „f” of „felhom.eu” are gone.** MEASURED 2026-10-08 on the live Page in Chrome DevTools device mode, Pixel 9 (412 × 924, mobile user agent, after a reload so Facebook serves the mobile bundle): `audits/facebook-page-setup-2026-10-08/C2-phone-headline-cut-closeup.png`. The mobile Page header is **412 × 274 = 1,504:1**, so Facebook keeps our cover's full height and shows only the centre **938 px of its 1640 px width (57,2 %)** — **351 px cut from each side**. `marketing/facebook/build.py` builds to a safe area of the centre **1028 × 544** (306 px clear of each edge), which is **45 px wider per side than the phone actually shows**; the light text in `out/cover-c.png` runs from x 333 to x 997, and the left crop edge is x 351, so the first **18 px** of the text are cut. Covers A and B are built from the same safe area and will have the same edge. Two further facts this measurement establishes: **Meta's own help page is wrong about its own rendering** — it states the mobile cover is 2,4:1 where the Page header measures 1,504:1 — and the mobile profile circle is far bigger than assumed (172 px, centred, overlapping the bottom 112 px of the 274 px cover, i.e. source x 637–1030 × y 369–624 is hidden). Not re-cropped on Facebook, per the task's fence. **-- 2026-10-09, FIXED in the build:** `marketing/facebook/build.py` now takes the phone view from the measurement (`PHONE_HDR = (412, 274)` -> the centre 938 px), so SAFE is (391, 40)-(1249, 584) and the phone profile circle is the measured CENTRED box (637, 369, 393) rather than a left-anchored one. Every cover is drawn in a derived `BAND` (408, 48)-(1249, 340), and a `cap` check holds each headline's capital at >= 4 % of the cover height. The red-proof runs on EVERY build (`control_old_window`): it draws the headline where the old 640 x 360 assumption put it, x 328, and the check must reject it -- the phone's crop edge is x 351, so 23 px were cut. Against the three covers as committed at `a76207945e` the new check convicted 3 of 3 (A 23/22 px over the left/right edges, B 63/58 px plus 1017 content pixels under the phone circle, C 63/82 px plus 1778). The profile pictures are untouched -- sha256 identical before and after. **-- 2026-10-09 (operator refinements, same day):** the profile picture now carries the logo MARK only (the lettering was unreadable at 176 px; the mark grew 69 % -> 76 % of the circle, canvas 932 -> 648), and the covers set the headline the way `site.css` sets `.page-index .hero-text h1` (Bold 700, letter-spacing -0.03em, not ExtraBold 800 untracked) with „felhom.eu” drawn from the logo's OWN lettering instead of typed. No geometry changed; every R-919 check and the red-proof stand. | **VERIFY -- rebuilt on `main` 2026-10-09; NOT proven until the operator looks in the Facebook app.** The phone geometry is measured on ONE device (Pixel 9 emulated, 412 px, the mobile WEBSITE); the app may crop differently. | — | Operator: upload the rebuilt cover by hand (same option as now unless `out/preview.html` suggests otherwise -- the pictures cannot be set by API, R-914), then open the Page in the Facebook APP on a phone and say whether the headline and „felhom.eu” are whole. Close on that word. If the app still cuts them, re-measure there and move `PHONE_HDR`; the build follows it. If nothing is done: the Page keeps today's cover, which IS cut on phones | CC | ## Process & tooling — 23 rows (P3 3, P4 20) diff --git a/marketing/CHANGELOG.md b/marketing/CHANGELOG.md index fe4be2c4..9838e16e 100644 --- a/marketing/CHANGELOG.md +++ b/marketing/CHANGELOG.md @@ -1,5 +1,39 @@ # marketing — CHANGELOG +## facebook — the brand lockup: the mark alone on the profile, the real lettering on the covers (2026-10-09) + +Operator refinements on the same day, after looking at the rebuilt pictures. No geometry changed; R-919's +measured constants and all its checks stand. + +- **The profile picture is the logo MARK only.** The mark plus the „felhom.eu" lettering left both too small + to read at the 176 px Facebook shows, and smaller than the shape the Page carried before. Dropping the + lettering lets the mark grow from **69 % to 76 %** of the circle, and the canvas drops 932 → **648 px** + (still twice Meta's recommended 320 source, so the floor in `profile_width` went 720 → 640; 720 was above + what the mark alone needs and would have shrunk it for nothing). At 40 px the cloud, the house and the + keyhole now read. +- **Both backgrounds read now.** The preview recommended dark because the *lettering* faded on white — a + reason that no longer exists. It now says what is actually true: dark matches the covers and the Page as it + stands, white separates the circle from the dark cover, and the operator picks. +- **The covers set the headline the way the WEBSITE sets its h1.** `site.css` + `.page-index .hero-text h1` is `font-weight: 700; letter-spacing: -0.03em`; this file used **ExtraBold (800) + with no tracking**. Now `HEADLINE_WEIGHT = "Bold"` and `HEADLINE_TRACK = -0.03`, with real per-glyph + letter-spacing rather than faked by scaling. Bold is narrower, so the headlines grew: A 57 → 62, B 58 → 63, + C 47 → 50. +- **„felhom.eu" is no longer typed.** It is the logo's **own lettering**, cropped out of `logo.png` at a + measured split (mark y 5–289, blank band y 290–295, wordmark y 296–403) and scaled. That lettering is set + in „M+ 2c" / „Vremena Grotesk", fonts this machine does not have (R-916), so typing it in Plus Jakarta Sans + was a different wordmark. The website hero shows the same `logo.png` on the same dark background, so the + covers now match the page. The `DOMAIN` constant is gone with it. +- **Caught before it shipped:** the preview page's new profile paragraph added a fourth `%d`, and the + argument tuple was left in the old order — so the phone paragraph rendered „the centre **76** px of the + **938**-pixel width, with a profile circle **1640** cover-pixels across". All three numbers were real + numbers from the file, which is why it read as plausible rather than as a crash. Fixed and both paragraphs + re-read. +- Checks unchanged and all passing; the cap check now measures the weight the headline is actually set in. + Profile pictures necessarily changed (that was the point), so their sha256 moved: `db05dae5…` dark, + `a44849e4…` white. + + ## facebook — the covers rebuilt to the phone view we measured (R-919) (2026-10-09) R-919's fix, from an operator brief (no TASK file in the repo). The covers shipped on 2026-10-08 were cut diff --git a/marketing/facebook/README.md b/marketing/facebook/README.md index 201839ca..8ca80c97 100644 --- a/marketing/facebook/README.md +++ b/marketing/facebook/README.md @@ -22,7 +22,7 @@ pixel or two differently and rewrites all six files. Measured 2026-10-09. Brand | File | Where on the Page | |---|---| -| `out/profile-dark.png` (recommended) or `out/profile-white.png` | Profile picture | +| `out/profile-dark.png` (recommended) or `out/profile-white.png` | Profile picture — the logo **mark only**, no lettering (see below) | | `out/cover-a.png` / `cover-b.png` / `cover-c.png` — pick one | Cover photo | | `COPY.md` §1 | Intro / Bemutatkozás (≤ 100 characters) | | `COPY.md` §2 | **Nowhere today — the field is gone** (R-917). Facebook's current Pages experience has no long-description field: not in the Page's „Névjegy" tab, not in Business Suite's „Oldal módosítása", not in Page settings; the Graph `description` reads `null` and writing it needs `pages_manage_metadata`, which the robot key does not have. Checked 2026-10-08. | @@ -49,7 +49,7 @@ fasturtle.com) appear **nowhere on Meta's page**; only the 176 px profile size m assumed, 24 px, is smaller than the ~40 px Meta states. What we build: the cover at 2 x, **1640 x 624**, well above both stated minimums, as PNG; the profile picture -932 x 932, larger than Meta's 320 x 320 recommendation and therefore downscaled, not enlarged. +648 x 648, twice Meta's 320 x 320 recommendation and therefore downscaled, not enlarged. **MEASURED on the live Page 2026-10-08, both widths:** @@ -93,7 +93,7 @@ limit bites. `website/assets/logo.svg` is **not** usable as a master: its „felhom.eu" lettering is live text in the fonts „M+ 2c" and „Vremena Grotesk", which DooPlex does not have, so any renderer here draws other letters. The PNG -(645 x 408) is placed unscaled, so the profile picture is 932 x 932 — sharp, not enlarged. A vector master with the +(645 x 408) is placed unscaled. A vector master with the lettering converted to paths (Inkscape: Path → Object to Path, on the machine that has the fonts) is R-916. ## Cover C diff --git a/marketing/facebook/build.py b/marketing/facebook/build.py index 4a63de7c..3e62fbab 100644 --- a/marketing/facebook/build.py +++ b/marketing/facebook/build.py @@ -5,6 +5,14 @@ Run: python3 marketing/facebook/build.py Exit: 0 every image built and every check passed · 1 a check failed · 2 a render failed +The brand lockup. The headline is set the way the WEBSITE sets its own h1 - `site.css` +`.page-index .hero-text h1` is `font-weight: 700; letter-spacing: -0.03em` - so HEADLINE_WEIGHT is Bold, not +ExtraBold, and HEADLINE_TRACK is -0.03 em. „felhom.eu" is NOT typed in Plus Jakarta Sans: it is the logo's own +lettering, cropped out of logo.png, because that lettering is set in „M+ 2c" / „Vremena Grotesk" (fonts this +machine does not have, R-916) and typing it in another face is a different wordmark. The website hero shows the +same logo.png on the same dark background, so the cover matches the page. The PROFILE picture carries the MARK +only - at 176 px the mark plus the wordmark left both too small to read (operator, 2026-10-09). + Renderer: Pillow only (shapes, text, compositing). Text is set in the website's own Plus Jakarta Sans (website/assets/fonts/*.woff2), which Pillow reads through FreeType. NOT librsvg: tried 2026-10-08 — pango fell back to DejaVu for the woff2 fonts, and logo.svg's own lettering is live in 'M+ 2c' / 'Vremena Grotesk', fonts @@ -63,8 +71,15 @@ WHITE = hexc("#FFFFFF") L1 = "Saj\u00e1t felh\u0151d," L2 = "saj\u00e1t szab\u00e1lyaid" SLOGAN = L1 + " " + L2 -DOMAIN = "felhom.eu" +# No DOMAIN constant: „felhom.eu" is never typed. wordmark() draws the logo's own lettering, so the +# brand name on a cover is the same artwork as on the website, not a look-alike in another face. +# The website's own h1, read from website/assets/site.css (.page-index .hero-text h1). +HEADLINE_WEIGHT = "Bold" # font-weight: 700 - NOT ExtraBold, which is what this file used to use +HEADLINE_TRACK = -0.03 # letter-spacing: -0.03em +# logo.png is one image holding two things: the mark, then a blank band, then the „felhom.eu" lettering. +# MEASURED on the file 2026-10-09: ink runs y 5..289, the band is y 290..295, the wordmark y 296..403. +LOGO_SPLIT_Y = 293 CIRCLE_R = 0.42 # brief §7 A: the check radius, as a fraction of the width LOGO_R = 0.40 # where the logo's farthest pixel is placed (inside the check radius) CW, CH = 1640, 624 # cover, 2 x the 820 x 312 computer display @@ -144,26 +159,64 @@ def text_width(text, size, weight): return sum(font(size, weight, e).getlength(s) for e, s in runs(text)) -def draw_text(img, x, y, text, size, weight, fill, align="left"): - """Draw at baseline y; x is the left edge, or the centre with align='center'. Returns the width.""" - w = text_width(text, size, weight) +def text_width_tracked(text, size, weight, track=0.0): + """Width with letter-spacing, the way the site puts -0.03em on its h1. `track` is in pixels.""" + if not track: + return text_width(text, size, weight) + w = 0.0 + for e, s in runs(text): + f = font(size, weight, e) + for ch in s: + w += f.getlength(ch) + track + return w - track + + +def draw_text(img, x, y, text, size, weight, fill, align="left", track=0.0): + """Draw at baseline y; x is the left edge, or the centre with align='center'. Returns the width. + With `track` the glyphs are placed one at a time, so letter-spacing is real and not faked by scaling.""" + w = text_width_tracked(text, size, weight, track) if align == "center": x -= w / 2.0 d = ImageDraw.Draw(img) for e, s in runs(text): f = font(size, weight, e) - d.text((x, y), s, font=f, fill=fill, anchor="ls") - x += f.getlength(s) + if track: + for ch in s: + d.text((x, y), ch, font=f, fill=fill, anchor="ls") + x += f.getlength(ch) + track + else: + d.text((x, y), s, font=f, fill=fill, anchor="ls") + x += f.getlength(s) return w -def fit_size(text, weight, max_w, start): +def fit_size(text, weight, max_w, start, track_em=0.0): + """The largest size at or below `start` whose tracked width fits max_w.""" size = start - while text_width(text, size, weight) > max_w: + while text_width_tracked(text, size, weight, track_em * size) > max_w: size -= 1 return size +def headline(c, x, y, text, size, fill, align="left"): + """One run of headline text in the website's weight and tracking. Returns the PEN x after it, so the + second, blue half of the line starts exactly where the first ended - tracking makes `x + width` wrong + by one letter-space.""" + tr = HEADLINE_TRACK * size + w = draw_text(c, x, y, text, size, HEADLINE_WEIGHT, fill, align=align, track=tr) + if align == "center": + x -= w / 2.0 + return x + w + tr + + +def headline_fit(text, max_w, start): + return fit_size(text, HEADLINE_WEIGHT, max_w, start, HEADLINE_TRACK) + + +def headline_width(text, size): + return text_width_tracked(text, size, HEADLINE_WEIGHT, HEADLINE_TRACK * size) + + # ------------------------------------------------------------------ the logo def alpha_points(img, thr=16): @@ -188,6 +241,31 @@ def outside_circle(img, bg, r_frac): return n +_logo_parts = [] + + +def logo_parts(): + """logo.png split into (mark, wordmark), each tightened to its own ink. The split row is MEASURED.""" + if _logo_parts: + return _logo_parts[0] + src = Image.open(LOGO_PNG).convert("RGBA") + out = [] + for box in ((0, 0, src.width, LOGO_SPLIT_Y), (0, LOGO_SPLIT_Y, src.width, src.height)): + part = src.crop(box) + out.append(part.crop(part.getchannel("A").point(lambda v: 255 if v > 16 else 0).getbbox())) + _logo_parts.append((out[0], out[1])) + return _logo_parts[0] + + +def wordmark(c, x, y, height, align="left"): + """Draw „felhom.eu" as the logo's own lettering, scaled to `height`. Returns its width.""" + art = logo_parts()[1] + w = int(round(art.width * float(height) / art.height)) + art = art.resize((w, int(height)), Image.LANCZOS) + c.alpha_composite(art, (int(round(x - w / 2.0)) if align == "center" else int(round(x)), int(round(y)))) + return w + + def today_shape(): """The Page today: logo.png at full size, unscaled, centred on a white square of its own width.""" src = Image.open(LOGO_PNG).convert("RGBA") @@ -198,18 +276,21 @@ def today_shape(): def profile_width(): - """The square size at which logo.png fits radius LOGO_R x width UNSCALED (never upsampled), at least 720.""" - src = Image.open(LOGO_PNG).convert("RGBA") + """The square size at which the MARK fits radius LOGO_R x width UNSCALED (never upsampled). + Floor 640 = twice the 320 x 320 Meta recommends as a source, so there is resolution to spare at the + 176 / 196 px Facebook actually shows. It used to be 720, which was above what the mark alone needs and + would have shrunk the mark inside the circle for no gain.""" + src = logo_parts()[0] pts = alpha_points(src) xs, ys = [p[0] for p in pts], [p[1] for p in pts] cx, cy = (min(xs) + max(xs)) / 2.0, (min(ys) + max(ys)) / 2.0 dmax = max(math.hypot(x - cx, y - cy) for x, y in pts) + 1 - return max(720, int(math.ceil(dmax / LOGO_R / 2.0)) * 2), (cx, cy) + return max(640, int(math.ceil(dmax / LOGO_R / 2.0)) * 2), (cx, cy) def build_profile(bg, name): w, (cx, cy) = profile_width() - logo = Image.open(LOGO_PNG).convert("RGBA") + logo = logo_parts()[0] img = Image.new("RGBA", (w, w), bg) img.alpha_composite(logo, (int(round(w / 2.0 - cx)), int(round(w / 2.0 - cy)))) path = os.path.join(OUT, name) @@ -243,13 +324,11 @@ def content_a(): """Calm: the one line, centred in the band, with the domain under it.""" c = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) mid = (BAND[0] + BAND[2]) / 2.0 - size = fit_size(SLOGAN, "ExtraBold", (BAND[2] - BAND[0]) - 20, 86) - w1 = text_width(L1 + " ", size, "ExtraBold") - w = text_width(SLOGAN, size, "ExtraBold") - x0 = mid - w / 2.0 - draw_text(c, x0, 222, L1 + " ", size, "ExtraBold", TEXT1) - draw_text(c, x0 + w1, 222, L2, size, "ExtraBold", BLUE_BRIGHT) - draw_text(c, mid, 284, DOMAIN, 34, "SemiBold", TEXT2, align="center") + size = headline_fit(SLOGAN, (BAND[2] - BAND[0]) - 20, 86) + x = mid - headline_width(SLOGAN, size) / 2.0 + x = headline(c, x, 212, L1 + " ", size, TEXT1) + headline(c, x, 212, L2, size, BLUE_BRIGHT) + wordmark(c, mid, 246, round(size * 0.95), align="center") return c, size @@ -296,10 +375,10 @@ def content_b(): c = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) d = ImageDraw.Draw(c) tx = BAND[0] + 12 - size = fit_size(L2, "ExtraBold", 456, 64) # the text column; the motif gets the rest - draw_text(c, tx, 170, L1, size, "ExtraBold", TEXT1) - draw_text(c, tx, 170 + size + 14, L2, size, "ExtraBold", BLUE_BRIGHT) - draw_text(c, tx, 170 + size + 14 + 46, DOMAIN, 30, "SemiBold", TEXT2) + size = headline_fit(L2, 456, 64) # the text column; the motif gets the rest + headline(c, tx, 170, L1, size, TEXT1) + headline(c, tx, 170 + size + 14, L2, size, BLUE_BRIGHT) + wordmark(c, tx, 170 + size + 30, round(size * 0.9)) # the home with its server, on the right, scaled to the band's height hx, hy, hw = 1000, 190, 150 # house body: left, top, width tiles = [(935, 95, "photo"), (1065, 84, "doc"), (1195, 140, "play"), (1200, 278, "lock"), @@ -321,10 +400,10 @@ def content_c(): c = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) d = ImageDraw.Draw(c) tx = BAND[0] + 12 - size = fit_size(L2, "ExtraBold", 366, 56) - draw_text(c, tx, 180, L1, size, "ExtraBold", TEXT1) - draw_text(c, tx, 180 + size + 12, L2, size, "ExtraBold", BLUE_BRIGHT) - draw_text(c, tx, 180 + size + 12 + 42, DOMAIN, 30, "SemiBold", TEXT2) + size = headline_fit(L2, 366, 56) + headline(c, tx, 180, L1, size, TEXT1) + headline(c, tx, 180 + size + 12, L2, size, BLUE_BRIGHT) + wordmark(c, tx, 180 + size + 28, round(size * 0.9)) shot = Image.open(SHOT).convert("RGBA") # The domain chip next to the title names the demo box; covered with the page colour (copy only, source unchanged). ImageDraw.Draw(shot).rectangle((684, 32, 826, 70), fill=hexc("#0B1322")) @@ -342,7 +421,7 @@ def content_c(): def cap_height(size): """The height of a capital letter at this size, read from the font rather than assumed.""" - bb = font(size, "ExtraBold", 0).getbbox("S") + bb = font(size, HEADLINE_WEIGHT, 0).getbbox("S") return bb[3] - bb[1] @@ -394,7 +473,7 @@ def preview(profiles, covers): pct = lambda v, tot: "%.3f%%" % (100.0 * v / tot) # noqa: E731 prof_html = "".join( '
' - '
%s — 176 px, 96 px, 40 px
' + '
%s — 176 px, 96 px, 40 px (the mark only)
' % (b64(p), b64(p), b64(p), label) for label, p in profiles) cov = [] rec = profiles[0][1] @@ -437,12 +516,16 @@ main{max-width:860px;margin:0 auto}h1{font-size:22px;margin:0 0 4px}h2{font-size

The phone view below is MEASURED on the live Page (Pixel 9 emulated, mobile website, 2026-10-08, R-919): Facebook keeps the full height and shows only the centre %d px of the %d-pixel width, with a profile circle over the bottom middle %d cover-pixels across. It is NOT measured in the Facebook app \u2014 check there too.

-

Profile picture: the DARK background is recommended. The logo's lettering is white with a thin navy edge, -so on white it fades at 40 px; on dark it reads solid, as on the website. The covers below show the dark one.

+

Profile picture: the logo MARK only \u2014 no \u201efelhom.eu\u201d lettering. At the 176 px Facebook shows (and +40 px in a comment) the mark plus the lettering left both too small to read, so the lettering was dropped and the +mark grew to %d %% of the circle. BOTH backgrounds now read at 40 px, which was not true while the lettering was +there: dark matches the covers and the Page as it stands, white separates the circle from the dark cover. The +covers below show the dark one.

Profile picture

%s %s -""" % (round(CW_PHONE), CW, round(PHONE_CIRCLE[2]), prof_html, "".join(cov)) +""" % (round(CW_PHONE), CW, round(PHONE_CIRCLE[2]), + round(100.0 * logo_parts()[0].width / profile_width()[0]), prof_html, "".join(cov)) path = os.path.join(OUT, "preview.html") with open(path, "w", encoding="utf-8") as fh: fh.write(html) diff --git a/marketing/facebook/out/cover-a.png b/marketing/facebook/out/cover-a.png index 1abbb1e8..a76d1825 100644 Binary files a/marketing/facebook/out/cover-a.png and b/marketing/facebook/out/cover-a.png differ diff --git a/marketing/facebook/out/cover-b.png b/marketing/facebook/out/cover-b.png index 06b47ceb..6ed6ab2d 100644 Binary files a/marketing/facebook/out/cover-b.png and b/marketing/facebook/out/cover-b.png differ diff --git a/marketing/facebook/out/cover-c.png b/marketing/facebook/out/cover-c.png index 6426cdb2..02de4205 100644 Binary files a/marketing/facebook/out/cover-c.png and b/marketing/facebook/out/cover-c.png differ diff --git a/marketing/facebook/out/preview.html b/marketing/facebook/out/preview.html index 486c8444..9cf18e99 100644 --- a/marketing/facebook/out/preview.html +++ b/marketing/facebook/out/preview.html @@ -18,8 +18,11 @@ main{max-width:860px;margin:0 auto}h1{font-size:22px;margin:0 0 4px}h2{font-size

The phone view below is MEASURED on the live Page (Pixel 9 emulated, mobile website, 2026-10-08, R-919): Facebook keeps the full height and shows only the centre 938 px of the 1640-pixel width, with a profile circle over the bottom middle 393 cover-pixels across. It is NOT measured in the Facebook app — check there too.

-

Profile picture: the DARK background is recommended. The logo's lettering is white with a thin navy edge, -so on white it fades at 40 px; on dark it reads solid, as on the website. The covers below show the dark one.

-

Profile picture

Dark background (recommended) — 176 px, 96 px, 40 px
White background — 176 px, 96 px, 40 px
-

A - calm

Computer (820 x 312), dashed = safe area (preview only)

Phone — MEASURED (R-919): the centre 938 px of 1640 at full height, and the profile circle 393 cover-pixels across - 173 px on a 412 px phone screen - centred. NOT 640 x 360, and NOT Meta’s 2,4:1.

B - the idea

Computer (820 x 312), dashed = safe area (preview only)

Phone — MEASURED (R-919): the centre 938 px of 1640 at full height, and the profile circle 393 cover-pixels across - 173 px on a 412 px phone screen - centred. NOT 640 x 360, and NOT Meta’s 2,4:1.

C - the product

Computer (820 x 312), dashed = safe area (preview only)

Phone — MEASURED (R-919): the centre 938 px of 1640 at full height, and the profile circle 393 cover-pixels across - 173 px on a 412 px phone screen - centred. NOT 640 x 360, and NOT Meta’s 2,4:1.

+

Profile picture: the logo MARK only — no „felhom.eu” lettering. At the 176 px Facebook shows (and +40 px in a comment) the mark plus the lettering left both too small to read, so the lettering was dropped and the +mark grew to 76 % of the circle. BOTH backgrounds now read at 40 px, which was not true while the lettering was +there: dark matches the covers and the Page as it stands, white separates the circle from the dark cover. The +covers below show the dark one.

+

Profile picture

Dark background (recommended) — 176 px, 96 px, 40 px (the mark only)
White background — 176 px, 96 px, 40 px (the mark only)
+

A - calm

Computer (820 x 312), dashed = safe area (preview only)

Phone — MEASURED (R-919): the centre 938 px of 1640 at full height, and the profile circle 393 cover-pixels across - 173 px on a 412 px phone screen - centred. NOT 640 x 360, and NOT Meta’s 2,4:1.

B - the idea

Computer (820 x 312), dashed = safe area (preview only)

Phone — MEASURED (R-919): the centre 938 px of 1640 at full height, and the profile circle 393 cover-pixels across - 173 px on a 412 px phone screen - centred. NOT 640 x 360, and NOT Meta’s 2,4:1.

C - the product

Computer (820 x 312), dashed = safe area (preview only)

Phone — MEASURED (R-919): the centre 938 px of 1640 at full height, and the profile circle 393 cover-pixels across - 173 px on a 412 px phone screen - centred. NOT 640 x 360, and NOT Meta’s 2,4:1.

diff --git a/marketing/facebook/out/profile-dark.png b/marketing/facebook/out/profile-dark.png index bb4b2607..134b832a 100644 Binary files a/marketing/facebook/out/profile-dark.png and b/marketing/facebook/out/profile-dark.png differ diff --git a/marketing/facebook/out/profile-white.png b/marketing/facebook/out/profile-white.png index 87774506..dae28624 100644 Binary files a/marketing/facebook/out/profile-white.png and b/marketing/facebook/out/profile-white.png differ