diff --git a/REPORT-facebook-cover-r919.md b/REPORT-facebook-cover-r919.md index ba7d6519..9fa64096 100644 --- a/REPORT-facebook-cover-r919.md +++ b/REPORT-facebook-cover-r919.md @@ -214,6 +214,52 @@ under the profile circle on all three covers, and the renders were looked at at 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. +## 12. Second measurement: a phone has TWO views (2026-10-09) + +The operator checked the Page signed out on a phone and found the cover **not** cropped at the sides — the +opposite of what §4 says — and then checked it signed in on his real phone, where it **is** cropped. Both +observations are right. My R-919 measurement was not wrong; it was **one view, generalised to "the phone"**. + +**Signed in, on the operator's real phone (Chrome on Android, 1080 px).** Measured from the screenshot, not +by eye: the cover band is y 401–1111 = 708 px tall across the full 1080 px screen = **1.525 : 1** against the +file's 2.628 : 1, so the width is cropped at full height. Solving the laptop frame's left edge (cover x 814, +screen x 528) against the scale gives a visible window of **x 351 .. 1298**; R-919's emulator said +**351 .. 1289**. The left edge agrees to the pixel, and the predicted screen position of the text's left edge +(x 75) matches the picture. **R-919 is confirmed on real hardware.** Its circle: x 649–1028 from y 450, +against the emulator's 637–1030 from y 369. + +**Signed out.** Also measured from the picture: the cover box is 412 × 132 = **3.121 : 1**, wider than the +file, so here the **height** is cut, not the width — and the file's blue top rule is still visible at the top +edge (rgb 0,121,199 at the first two rows), which puts the crop at the **bottom**: the top **525 px** of 624 +survives, the bottom 99 px does not. The profile circle is far bigger and higher: **x 486–1150 from y 232**, +41 % of the width. + +**What that means for a cover.** The sides are cut for one visitor and the bottom for the other, so the two +constraints have to be met together: SAFE is now **(391, 40)–(1249, 485)** and the binding circle is the +signed-out one. Two changes made the result workable rather than merely safe: + +- **The circles are DISCS.** They were modelled as rectangles running to the bottom of the cover. Near its + top a disc is only a few pixels wide, so the rectangle was discarding most of the lower cover for + nothing — a large part of why the frame looked empty. +- **Two layers.** The READ layer (catchphrase, wordmark) must survive every view; the check fails on it. + The DECOR layer (the laptop, the home motif) may be cropped or covered, and the build **reports** the + cost instead of forbidding it — cover B loses 39 % of its decor in some view, cover C 62 %. Before the + split, one check governed both, which is why no laptop big enough to read could ever pass. + +**Red-proof, again.** Before redrawing anything, the two-view geometry was run against the covers as they +then stood: **all three convicted** — cover A 908 content pixels under a circle (its wordmark sat inside the +signed-out circle), cover B 1715 plus content past the cut bottom, cover C 1962. The permanent +`control_old_window` control still convicts the original pre-R-919 layout too, so the build now carries two +controls. + +**The redraw.** Cover C is the operator's own idea: catchphrase and wordmark on the left, the dashboard on +the right at **640 px wide** (was 370) running off the right edge — readable for the first time. Cover B's +home motif grew the same way. Cover A is unchanged in kind, lifted into the tighter band. All three keep +every capital above the 25 px floor (48, 45, 43 px). + +**Still unmeasured: the Facebook app.** Three views are now measured and they disagree with each other and +with Meta's help page. The app is the fourth and the most used. R-919 stays **VERIFY** for that reason. + ## 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 3395268c..a4e20c51 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. **-- 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 | +| **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. **-- 2026-10-09 (second measurement):** a phone has TWO views and they disagree. SIGNED IN the sides are cut (confirmed on the operator's REAL phone, Chrome/Android: the window solves to x 351..1298 against the emulator's 351..1289 - the left edge to the pixel). SIGNED OUT the FULL width is shown but the cover is top-anchored and only the top 525 px survives (the bottom 99 px is cut; the file's blue top rule is still visible, which is how the side was established), with a much bigger, higher circle at x 486..1150 from y 232. `build.py` now carries both views, models the circles as DISCS rather than rectangles running to the bottom, and splits the artwork into a READ layer (must survive every view) and a DECOR layer (may be cropped or covered; the build reports the cost - B 39 %, C 62 %). The two-view geometry convicted all three then-current covers before the redraw (A 908 px under a circle, B 1715, C 1962). Covers redrawn: C is the operator's laptop idea with the dashboard at 640 px (was 370), B's motif grown to match. | **VERIFY -- rebuilt on `main` 2026-10-09; three views measured, the Facebook APP still is not.** The three measured views disagree with each other and with Meta's help page, so the app is the one that decides. | — | Operator: upload the rebuilt cover (and the profile picture) by hand, 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 crops differently again, send a screenshot - the last two were solved straight out of the picture - and move `PHONE_IN_X` / `PHONE_OUT_Y` / `PHONE_CIRCLES`; the designs follow them | CC | ## Process & tooling — 23 rows (P3 3, P4 20) diff --git a/marketing/CHANGELOG.md b/marketing/CHANGELOG.md index 9838e16e..90f9511b 100644 --- a/marketing/CHANGELOG.md +++ b/marketing/CHANGELOG.md @@ -1,5 +1,38 @@ # marketing — CHANGELOG +## facebook — a phone has TWO views of the cover, not one; the covers fill the frame again (2026-10-09) + +The operator saw the cover UNCROPPED on a signed-out phone and asked why, after R-919 had said the sides +are cut. Both are true, and the first measurement was one view generalised. + +- **Measured, signed out** (from the operator's screenshot, solved against the picture): Facebook shows + the **full width** — nothing is cut at the sides — but the cover is **top-anchored and only the top + 525 px survives**; the bottom 99 px is cut. The blue rule from the top of the file is still visible at + the top edge, which is how the crop's side was established. The profile circle is much bigger and much + higher than in the signed-in view: **x 486–1150 from y 232**. +- **Measured, signed in, on the operator's REAL phone** (Chrome on Android, 1080 px): the crop is real. + Solving the screenshot against the laptop frame put the window at **x 351..1298** where the emulated + Pixel 9 had said **351..1289** — the left edge to the pixel. R-919's number is confirmed, not replaced. +- **So the sides are cut for a signed-in visitor and the bottom for a signed-out one**, and a cover has to + survive both. `build.py` now carries both views (`PHONE_IN_X`, `PHONE_OUT_Y`, `PHONE_CIRCLES`) instead of + one, and SAFE is their intersection: **(391, 40)–(1249, 485)**. +- **The profile circles are modelled as DISCS now, not rectangles reaching the bottom.** Near its top a + disc is only a few pixels wide; the rectangle threw away most of the cover for nothing and is part of + why the covers looked empty. +- **Two layers, because a cover has two kinds of pixel.** The READ layer (catchphrase + wordmark) must + survive every view and the check fails on it. The DECOR layer (laptop, home motif) may be cropped or + covered — and the build now REPORTS how much is lost rather than forbidding it: cover B 39 %, cover C + 62 %. Without that split the check forbade a laptop big enough to see, which is what left the frame bare. +- **The covers were redrawn.** C is the operator's laptop idea: catchphrase and wordmark left, the + dashboard now **640 px wide** (was 370) and running off the right edge — legible at last. B's home motif + grew the same way. A is unchanged in kind, lifted into the tighter band. +- **A second red-proof, run before the redraw:** the two-view geometry convicted all three covers as they + stood — cover A 908 content pixels under a circle, B 1715 plus content past the bottom, C 1962. The + permanent `control_old_window` still convicts the pre-R-919 layout as well. +- Profile pictures untouched; they are not in the diff. Still **R-919 = VERIFY**: the Facebook **app** is + the one view nobody has measured. + + ## 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 diff --git a/marketing/facebook/build.py b/marketing/facebook/build.py index 3e62fbab..23adc8f2 100644 --- a/marketing/facebook/build.py +++ b/marketing/facebook/build.py @@ -85,36 +85,47 @@ LOGO_R = 0.40 # where the logo's farthest pixel is placed (inside the che CW, CH = 1640, 624 # cover, 2 x the 820 x 312 computer display SCALE = CW / 820.0 -# --- the phone view: MEASURED, never read off Meta's help page (R-919) ------------------------------- -# Measured 2026-10-08 on the live Page in Chrome DevTools device mode, Pixel 9 (412 x 924, mobile user -# agent, AFTER a reload — without one Facebook keeps serving the desktop bundle). The Page's mobile -# header renders 412 x 274 CSS px = 1,504 : 1, so Facebook keeps the cover's FULL HEIGHT and shows only -# the centre CW_PHONE pixels of its width. Evidence and the recipe to re-measure: -# documentation/audits/facebook-page-setup-2026-10-08/ (README.md, C2-phone-headline-cut-closeup.png). -# GRADE: measured on ONE device, in the mobile WEBSITE. NOT measured in the Facebook app and not at any -# other width — which is why MARGIN below is deliberate and why the operator's app check is the -# acceptance step, not this build. -PHONE_HDR = (412.0, 274.0) -CW_PHONE = CH * PHONE_HDR[0] / PHONE_HDR[1] # 938.3 of 1640 — about 351 px lost from EACH side -PHONE_X0 = (CW - CW_PHONE) / 2.0 +# --- the phone views: MEASURED, and there are TWO of them (R-919) ------------------------------------ +# A phone does NOT show the cover one way. Both of these are measured on the live Page: +# +# SIGNED IN 2026-10-08 in Chrome DevTools device mode (Pixel 9, mobile UA, after a reload - without +# one Facebook keeps serving the desktop bundle), and CONFIRMED 2026-10-09 on the +# operator's own phone (Chrome on Android, 1080 px). Facebook keeps the cover's FULL +# HEIGHT and shows only the centre ~948 px of its width. Solving the real-phone shot +# against the laptop frame put the window at x 351..1298 where the emulator said +# 351..1289 - the left edge to the pixel. Profile circle: x 637..1030 from y 369 +# (emulated), x 649..1028 from y 450 (real phone). +# SIGNED OUT 2026-10-09, measured from the operator's screenshot. The FULL width is shown - nothing +# is cut at the sides - but the cover is top-anchored and only the TOP 525 px survives; +# the bottom 99 px is cut. The circle is much bigger and much higher: x 486..1150 from +# y 232. +# +# So the SIDES are cut for a signed-in visitor and the BOTTOM for a signed-out one, and a cover has to +# survive both. Meta's own help page describes neither. Evidence and the recipe to re-measure: +# documentation/audits/facebook-page-setup-2026-10-08/. +# STILL NOT MEASURED: the Facebook app. That is why MARGIN is deliberate and why the operator's check +# in the app remains the acceptance step rather than this build. +PHONE_IN_X = (351.0, 1289.0) # signed in: the visible width, at full height +PHONE_OUT_Y = 525.0 # signed out: the visible height, at full width MARGIN = 40 -SAFE = (int(math.ceil(PHONE_X0 + MARGIN)), MARGIN, int(CW - PHONE_X0 - MARGIN), CH - MARGIN) +SAFE = (int(math.ceil(PHONE_IN_X[0] + MARGIN)), MARGIN, + int(PHONE_IN_X[1] - MARGIN), int(PHONE_OUT_Y - MARGIN)) PAD = 24 # x, y, diameter in cover pixels. The COMPUTER circle is UNCHANGED (second-hand, and left-anchored: it # sits against the cover's left edge, so everything to its left is under it too). On 2026-10-08 the -# computer circle was measured to sit 16 px BELOW the cover and to overlap nothing — this stays as a +# computer circle was measured to sit 16 px BELOW the cover and to overlap nothing - this stays as a # deliberate conservatism, not because it is still true. DESK_CIRCLE = (16 * SCALE, 176 * SCALE, 176 * SCALE) -# The PHONE circle is MEASURED and CENTRED: 172 display px over the bottom middle of the header, which -# maps to source x 637..1030, y 369..bottom. A centred circle hides its own box only — never the whole -# width to its left, which is why QUIET can no longer be one formula for both. -PHONE_CIRCLE = (637.0, 369.0, 393.0) -QUIET = [(0, DESK_CIRCLE[1] - PAD, DESK_CIRCLE[0] + DESK_CIRCLE[2] + PAD, CH), - (PHONE_CIRCLE[0] - PAD, PHONE_CIRCLE[1] - PAD, PHONE_CIRCLE[0] + PHONE_CIRCLE[2] + PAD, CH)] -# The band every cover draws in: inside SAFE, clear of the computer circle on the left, above the phone -# circle. Derived, so a re-measurement moves the designs with it. +# The two phone circles, as MEASURED. They are discs, and modelling them as rectangles that reach the +# bottom threw away most of the cover for nothing: near its top a disc is only a few pixels wide. +PHONE_CIRCLES = [(637.0, 369.0, 393.0), # signed in + (486.0, 232.0, 669.0)] # signed out - bigger and higher, so it is the binding one +DISCS = [(c[0] + c[2] / 2.0, c[1] + c[2] / 2.0, c[2] / 2.0 + PAD) for c in PHONE_CIRCLES] +QUIET = [(0, DESK_CIRCLE[1] - PAD, DESK_CIRCLE[0] + DESK_CIRCLE[2] + PAD, CH)] +# The band a CENTRED block may use: inside SAFE, clear of the computer circle, above the higher of the +# two phone circles. A block that stays to one side may go lower - the disc test is the real arbiter. BAND = (max(SAFE[0], int(math.ceil(DESK_CIRCLE[0] + DESK_CIRCLE[2] + PAD))), SAFE[1] + 8, - SAFE[2], int(PHONE_CIRCLE[1] - PAD) - 5) + SAFE[2], int(min(d[1] - d[2] for d in DISCS))) CAP_MIN = int(round(0.04 * CH)) # a headline capital is at least 4 % of the cover height (25 px at 624) # What this file believed before R-919: that a phone shows 640 x 360 of the cover. Kept ONLY so the # control below can convict it - a check that has never been seen failing is a wish, not a check. @@ -321,15 +332,15 @@ def background(glow_at=(0.78, 0.25)): def content_a(): - """Calm: the one line, centred in the band, with the domain under it.""" + """Calm: the one line, centred in the band, with the wordmark under it. No decor by design.""" c = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) mid = (BAND[0] + BAND[2]) / 2.0 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 + x = headline(c, x, 118, L1 + " ", size, TEXT1) + headline(c, x, 118, L2, size, BLUE_BRIGHT) + wordmark(c, mid, 134, round(size * 0.95), align="center") + return c, None, size def rack(d, x, y, w, h, fill, dot): @@ -375,23 +386,25 @@ def content_b(): c = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) d = ImageDraw.Draw(c) tx = BAND[0] + 12 - 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"), - (928, 262, "folder"), (1155, 425, "photo"), (1110, 540, "doc")] + size = headline_fit(L2, 430, 58) # the READ column; the motif gets the rest + headline(c, tx, 100, L1, size, TEXT1) + headline(c, tx, 100 + size + 12, L2, size, BLUE_BRIGHT) + wordmark(c, tx, 100 + size + 26, round(size * 0.72)) + # DECOR: the home with its server and its apps, free to run past the crop and under the circles + dec = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) + d = ImageDraw.Draw(dec) + hx, hy, hw = 1080, 210, 230 # house body: left, top, width + tiles = [(960, 96, "photo"), (1150, 70, "doc"), (1400, 150, "play"), (1430, 360, "lock"), + (905, 300, "folder"), (1230, 520, "photo"), (980, 505, "doc")] for x, y, _ in tiles: - d.line((hx + hw / 2, hy + 70, x, y), fill=(46, 168, 245, 110), width=4) - d.rectangle((hx, hy, hx + hw, hy + 130), fill=BG1, outline=BLUE_BRIGHT, width=6) - d.polygon([(hx - 30, hy + 4), (hx + hw / 2, hy - 85), (hx + hw + 30, hy + 4)], fill=BG1, outline=BLUE_BRIGHT) - d.line([(hx - 30, hy + 4), (hx + hw / 2, hy - 85), (hx + hw + 30, hy + 4)], fill=BLUE_BRIGHT, width=6, joint="curve") - rack(d, hx + 28, hy + 28, hw - 56, 75, BLUE, TEXT1) + d.line((hx + hw / 2, hy + 110, x, y), fill=(46, 168, 245, 110), width=5) + d.rectangle((hx, hy, hx + hw, hy + 200), fill=BG1, outline=BLUE_BRIGHT, width=7) + d.polygon([(hx - 46, hy + 5), (hx + hw / 2, hy - 130), (hx + hw + 46, hy + 5)], fill=BG1, outline=BLUE_BRIGHT) + d.line([(hx - 46, hy + 5), (hx + hw / 2, hy - 130), (hx + hw + 46, hy + 5)], fill=BLUE_BRIGHT, width=7, joint="curve") + rack(d, hx + 42, hy + 42, hw - 84, 116, BLUE, TEXT1) for x, y, kind in tiles: - tile(d, x, y, 62, kind) - return c, size + tile(d, x, y, 92, kind) + return c, dec, size def content_c(): @@ -400,23 +413,26 @@ def content_c(): c = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) d = ImageDraw.Draw(c) tx = BAND[0] + 12 - 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)) + size = headline_fit(L2, 400, 58) + headline(c, tx, 104, L1, size, TEXT1) + headline(c, tx, 104 + size + 12, L2, size, BLUE_BRIGHT) + wordmark(c, tx, 104 + size + 26, round(size * 0.80)) + # DECOR: the dashboard, now big enough to read, running off the right edge and under the circles + dec = Image.new("RGBA", (CW, CH), (0, 0, 0, 0)) + d = ImageDraw.Draw(dec) 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")) - sw = 370 + sw = 640 sh = int(round(sw * shot.height / shot.width)) shot = shot.resize((sw, sh), Image.LANCZOS) - fx, fy, b = 814, 56, 14 # frame left/top, bezel + fx, fy, b = 880, 46, 24 # frame left/top, bezel d.rectangle((fx, fy, fx + sw + 2 * b, fy + sh + 2 * b), fill=BG2, outline=LINE, width=3) - c.alpha_composite(shot, (fx + b, fy + b)) + dec.alpha_composite(shot, (fx + b, fy + b)) base_y = fy + sh + 2 * b - d.polygon([(fx - 30, base_y + 22), (fx + sw + 2 * b + 30, base_y + 22), (fx + sw + 2 * b, base_y), + d.polygon([(fx - 52, base_y + 38), (fx + sw + 2 * b + 52, base_y + 38), (fx + sw + 2 * b, base_y), (fx, base_y)], fill=LINE) - return c, size + return c, dec, size def cap_height(size): @@ -435,6 +451,27 @@ def control_old_window(): return x0 < SAFE[0], x0 +def decor_cost(dec): + """How much of the decor layer a visitor never sees: cut by the signed-in crop, cut by the + signed-out bottom, or behind a profile circle. Reported, never failed - decor is allowed to go + there, and that is the whole point of having a second layer.""" + if dec is None: + return 0, 0 + pts = alpha_points(dec.resize((CW // 4, CH // 4)), thr=8) + if not pts: + return 0, 0 + tot = len(pts) + lost = 0 + for q in pts: + x, y = q[0] * 4, q[1] * 4 + if x < PHONE_IN_X[0] or x > PHONE_IN_X[1] or y > PHONE_OUT_Y: + lost += 1 + continue + if any((x - cx) ** 2 + (y - cy) ** 2 <= r * r for cx, cy, r in DISCS): + lost += 1 + return tot, lost + + def check_safe(name, content, headline): pts = alpha_points(content.resize((CW // 4, CH // 4)), thr=8) xs = [p[0] * 4 for p in pts] @@ -442,18 +479,22 @@ def check_safe(name, content, headline): bbox = (min(xs), min(ys), max(xs) + 3, max(ys) + 3) inside = bbox[0] >= SAFE[0] and bbox[1] >= SAFE[1] and bbox[2] <= SAFE[2] and bbox[3] <= SAFE[3] check(inside, "%s: content box %s inside the safe area %s" % (name, bbox, SAFE)) - hits = [] - for q in QUIET: - hits.append(sum(1 for x, y in zip(xs, ys) if q[0] <= x <= q[2] and q[1] <= y <= q[3])) - check(sum(hits) == 0, "%s: %d content pixels under the computer circle, %d under the phone circle" - % (name, hits[0], hits[1])) + q = QUIET[0] + desk = sum(1 for x, y in zip(xs, ys) if q[0] <= x <= q[2] and q[1] <= y <= q[3]) + phone = 0 + for cx, cy, r in DISCS: + phone += sum(1 for x, y in zip(xs, ys) if (x - cx) ** 2 + (y - cy) ** 2 <= r * r) + check(desk + phone == 0, "%s: %d content pixels under the computer circle, %d under a phone circle" + % (name, desk, phone)) cap = cap_height(headline) check(cap >= CAP_MIN, "%s: headline size %d, capital height %d px (at least %d)" % (name, headline, cap, CAP_MIN)) -def build_cover(name, content, glow): +def build_cover(name, content, dec, glow): img = background(glow) + if dec is not None: + img.alpha_composite(dec) img.alpha_composite(content) path = os.path.join(OUT, name) img.convert("RGB").save(path, optimize=True) @@ -468,15 +509,25 @@ def b64(path): def preview(profiles, covers): - dk, ph = DESK_CIRCLE, PHONE_CIRCLE + dk = DESK_CIRCLE sx0, sy0, sx1, sy1 = SAFE pct = lambda v, tot: "%.3f%%" % (100.0 * v / tot) # noqa: E731 prof_html = "".join( '
' - '
%s — 176 px, 96 px, 40 px (the mark only)
' + '
%s \u2014 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] + + def phone_panel(src, vx0, vx1, vy1, circ): + """One phone view: the window (vx0..vx1 of the width, 0..vy1 of the height) with its circle.""" + vw, vh = vx1 - vx0, vy1 + return ('
' + '' + '
' + % (vw, vh, src, CW / vw * 100.0, vx0 / vw * 100.0, CH / vh * 100.0, b64(rec), + pct(circ[0] - vx0, vw), pct(circ[1], vh), pct(circ[2], vw))) + for label, p in covers: src = b64(p) safe = ('
' @@ -484,38 +535,42 @@ def preview(profiles, covers): desk = ('
%s
' % (src, safe, b64(rec), pct(dk[0], CW), pct(dk[1], CH), pct(dk[2], CW))) - crop_l = PHONE_X0 / CW_PHONE * 100 - phone = ('
' - '' - '
' - % (CW_PHONE, CH, src, CW / CW_PHONE * 100, crop_l, b64(rec), - pct(ph[0] - PHONE_X0, CW_PHONE), pct(ph[1], CH), pct(ph[2], CW_PHONE))) - cov.append('

%s

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

%s' - '

Phone \u2014 MEASURED (R-919): the centre %d px of %d at full height, and the ' - 'profile circle %d cover-pixels across - %d px on a %d px phone screen - centred. ' - 'NOT 640 x 360, and NOT Meta\u2019s 2,4:1.

%s
' - % (label, desk, round(CW_PHONE), CW, round(ph[2]), - round(ph[2] * PHONE_HDR[0] / CW_PHONE), round(PHONE_HDR[0]), phone)) + pin = phone_panel(src, PHONE_IN_X[0], PHONE_IN_X[1], CH, PHONE_CIRCLES[0]) + pout = phone_panel(src, 0.0, float(CW), PHONE_OUT_Y, PHONE_CIRCLES[1]) + cov.append('

%s

' + '

Computer \u2014 the whole picture, and the profile circle sits BELOW it. ' + 'Dashed = the safe area (preview only).

%s' + '
' + '
Phone, signed in \u2014 the centre %d px of %d, full height' + '
%s
' + '
Phone, signed out \u2014 the full width, but only the top %d px' + '
%s
' + % (label, desk, round(PHONE_IN_X[1] - PHONE_IN_X[0]), CW, pin, round(PHONE_OUT_Y), pout)) html = """ Facebook Page pictures

Felhom.eu - Facebook pictures

-

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.

+

A phone does NOT show the cover one way, so each cover is shown twice below. Both are MEASURED +on the live Page (R-919). Signed in: the centre %d px of the %d-pixel width, at full height - confirmed +on a real phone, where the window solved to x 351..1298 against the emulator's 351..1289. Signed out: +the full width, but top-anchored and only the top %d px - the bottom %d px is cut - with a bigger, higher +circle. So the sides are cut for one visitor and the bottom for the other. The Facebook APP is still not +measured - please check there.

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 @@ -524,7 +579,7 @@ covers below show the dark one.

Profile picture

%s %s
-""" % (round(CW_PHONE), CW, round(PHONE_CIRCLE[2]), +""" % (round(PHONE_IN_X[1] - PHONE_IN_X[0]), CW, round(PHONE_OUT_Y), CH - round(PHONE_OUT_Y), 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: @@ -550,21 +605,26 @@ def main(): n = outside_circle(Image.open(p), bg, CIRCLE_R) check(n == 0, "%s: %d logo pixels outside the circle" % (name, n)) built.append(p) - print("safe-area check (computer %d x %d; the phone shows the centre %d px of %d - MEASURED, R-919)" - % (CW, CH, round(CW_PHONE), CW)) + print("safe-area check (computer %d x %d; signed in a phone shows x %d..%d at full height, signed" + % (CW, CH, round(PHONE_IN_X[0]), round(PHONE_IN_X[1]))) + print(" out the full width but only the top %d px - all MEASURED, R-919)" % round(PHONE_OUT_Y)) print(" safe %s, band %s, both profile circles kept clear" % (SAFE, BAND)) convicted, old_x0 = control_old_window() print(" the pre-R-919 layout put the headline at x %d. The phone's crop edge is x %d, so %d px of it" - % (old_x0, round(PHONE_X0), round(PHONE_X0) - old_x0)) + % (old_x0, round(PHONE_IN_X[0]), round(PHONE_IN_X[0]) - old_x0)) print(" were CUT on every phone; the measured safe edge is x %d, %d px further in (MARGIN %d)." % (SAFE[0], SAFE[0] - old_x0, MARGIN)) check(convicted, "the safe check convicts the pre-R-919 layout (control)") covers = [("cover-a.png", content_a(), (0.50, 0.78)), ("cover-b.png", content_b(), (0.68, 0.70)), ("cover-c.png", content_c(), (0.30, 0.74))] cpaths = [] - for name, (content, headline), glow in covers: + for name, (content, dec, headline), glow in covers: check_safe(name, content, headline) - cpaths.append(build_cover(name, content, glow)) + tot, lost = decor_cost(dec) + if tot: + print(" %s: decor %d px, %d (%d %%) cropped or behind a circle in some view - allowed" + % (name, tot, lost, round(100.0 * lost / tot))) + cpaths.append(build_cover(name, content, dec, glow)) except RuntimeError as e: print("RENDER FAILED: %s" % e) return 2 diff --git a/marketing/facebook/out/cover-a.png b/marketing/facebook/out/cover-a.png index a76d1825..c28f0cec 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 6ed6ab2d..2280a7ae 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 02de4205..5327629e 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 9cf18e99..d5e9b2e7 100644 --- a/marketing/facebook/out/preview.html +++ b/marketing/facebook/out/preview.html @@ -4,25 +4,30 @@

Felhom.eu - Facebook pictures

-

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.

+

A phone does NOT show the cover one way, so each cover is shown twice below. Both are MEASURED +on the live Page (R-919). Signed in: the centre 938 px of the 1640-pixel width, at full height - confirmed +on a real phone, where the window solved to x 351..1298 against the emulator's 351..1289. Signed out: +the full width, but top-anchored and only the top 525 px - the bottom 99 px is cut - with a bigger, higher +circle. So the sides are cut for one visitor and the bottom for the other. The Facebook APP is still not +measured - please check there.

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.

+

A - calm

Computer — the whole picture, and the profile circle sits BELOW it. Dashed = the safe area (preview only).

Phone, signed in — the centre 938 px of 1640, full height
Phone, signed out — the full width, but only the top 525 px

B - the idea

Computer — the whole picture, and the profile circle sits BELOW it. Dashed = the safe area (preview only).

Phone, signed in — the centre 938 px of 1640, full height
Phone, signed out — the full width, but only the top 525 px

C - the product

Computer — the whole picture, and the profile circle sits BELOW it. Dashed = the safe area (preview only).

Phone, signed in — the centre 938 px of 1640, full height
Phone, signed out — the full width, but only the top 525 px