Store assets: App Store header, search results, iPhone 6.3 and foldable sizes
Check PR / check (pull_request) Successful in 59s
Check PR / check (pull_request) Successful in 59s
Adds ios/header, ios/search-results, ios/iphone-6.3in and ios/iphone-fold-* to compose.py and the README upload table. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
5837464581
commit
660d0bc869
@@ -60,6 +60,19 @@ TARGETS = [
|
||||
('android/tablet-7in-landscape', 'atabletl', (1920, 1200), 'landscape'),
|
||||
('android/tablet-10in-landscape', 'atabletl', (2560, 1600), 'landscape'),
|
||||
('ios/ipad-landscape', 'ipadl', (2752, 2064), 'landscape'),
|
||||
# Newer App Store slots. iPad 13" (2064x2752, 2752x2064) is the two above.
|
||||
('ios/iphone-6.3in', 'iphone', (1206, 2622), 'phone'),
|
||||
('ios/iphone-fold-portrait', 'ipad', (2007, 2853), 'tablet'),
|
||||
('ios/iphone-fold-landscape', 'ipadl', (2853, 2007), 'landscape'),
|
||||
]
|
||||
|
||||
# App Store product-page banners: (folder, file name, size).
|
||||
HEROES = [
|
||||
('ios/header', 'header-5244x2950.png', (5244, 2950)),
|
||||
('ios/header', 'header-3840x1646.png', (3840, 1646)),
|
||||
('ios/search-results', 'search-5244x2950.png', (5244, 2950)),
|
||||
('ios/search-results', 'search-3840x2560.png', (3840, 2560)),
|
||||
('ios/search-results', 'search-1920x1280.png', (1920, 1280)),
|
||||
]
|
||||
|
||||
|
||||
@@ -215,6 +228,56 @@ def story_canvas(story, size, kind, device):
|
||||
return im
|
||||
|
||||
|
||||
def hero_canvas(size):
|
||||
"""Brand banner: name and tagline left, iPad (landscape) and iPhone right."""
|
||||
t = THEMES['light']
|
||||
w, h = size
|
||||
im = background(size, t)
|
||||
d = ImageDraw.Draw(im)
|
||||
margin = round(min(w * .06, h * .13))
|
||||
colw = round(w * (.30 if w / h > 2 else .34))
|
||||
# Text column, vertically centered as a block.
|
||||
icon_px = round(h * .105)
|
||||
head = round(h * .092)
|
||||
sub_size = round(h * .034)
|
||||
block = icon_px + round(h * .05) + round(head * 2.35) + round(sub_size * 4.4)
|
||||
y = (h - block) // 2
|
||||
icon = Image.open(ROOT / 'assets/icon/app_icon.png').convert('RGBA').resize((icon_px, icon_px), Image.Resampling.LANCZOS)
|
||||
im.paste(icon, (margin, y), rounded_mask((icon_px, icon_px), round(icon_px * .24)))
|
||||
spaced(d, (margin + icon_px + round(h * .025), y + icon_px // 2 - round(h * .02)), 'NOOCLOUD',
|
||||
font(round(h * .034), 'Bold', 'InstrumentSans'), t['accent_text'], h * .006)
|
||||
y += icon_px + round(h * .05)
|
||||
for index, line in enumerate(('The Nextcloud app', 'that just works.')):
|
||||
fnt, _ = fit(d, line, font, head, colw)
|
||||
d.text((margin, y), line, font=fnt, fill=t['accent_text'] if index else t['fg'])
|
||||
y += round(head * 1.12)
|
||||
y += round(head * .2)
|
||||
fnt = body_font(sub_size, 'Regular')
|
||||
chars = max(16, int(colw / (sub_size * .52)))
|
||||
for line in textwrap.wrap('Multiple accounts, locks, offline sync, sharing and external storage.', chars):
|
||||
d.text((margin, y), line, font=fnt, fill=t['fg2'])
|
||||
y += round(sub_size * 1.4)
|
||||
# Devices: iPad landscape with the iPhone overlapping its bottom-right corner.
|
||||
x0 = margin + colw + round(w * .03)
|
||||
region_w = w - x0 - margin
|
||||
ipad_h = round(h * .72)
|
||||
ipad_w = round(ipad_h * 4 / 3)
|
||||
phone_h = round(ipad_h * 1.02)
|
||||
phone_w = round(phone_h * 0.4614)
|
||||
total = ipad_w + round(phone_w * .7)
|
||||
if total > region_w:
|
||||
k = region_w / total
|
||||
ipad_h, ipad_w, phone_h, phone_w = (round(v * k) for v in (ipad_h, ipad_w, phone_h, phone_w))
|
||||
total = ipad_w + round(phone_w * .7)
|
||||
x = x0 + (region_w - total) // 2
|
||||
top = (h - ipad_h) // 2 - round(h * .03)
|
||||
paste_panel(im, OUT / 'source/ipadl/external.png', (x, top, ipad_w, ipad_h), round(h * .028), t)
|
||||
paste_panel(im, OUT / 'source/iphone/offline.png',
|
||||
(x + ipad_w - round(phone_w * .3), top + ipad_h - phone_h + round(h * .07), phone_w, phone_h),
|
||||
round(h * .035), t)
|
||||
return im
|
||||
|
||||
|
||||
def check(path, size, mode):
|
||||
image = Image.open(path)
|
||||
assert image.size == tuple(size) and image.mode == mode, path
|
||||
@@ -234,6 +297,13 @@ for folder, device, size, kind in TARGETS:
|
||||
manifest.append({'file': str(path.relative_to(OUT)), 'size': list(size), 'mode': 'RGB',
|
||||
'story': story['key']})
|
||||
|
||||
for folder, name, size in HEROES:
|
||||
dest = OUT / folder
|
||||
dest.mkdir(parents=True, exist_ok=True)
|
||||
path = dest / name
|
||||
hero_canvas(size).save(path, optimize=True)
|
||||
manifest.append({'file': str(path.relative_to(OUT)), 'size': list(size), 'mode': 'RGB', 'story': 'banner'})
|
||||
|
||||
# Store icons: Play wants a 512 px PNG, App Store Connect takes the icon from
|
||||
# the uploaded build (this 1024 px copy is only a reference).
|
||||
for platform, px, source in [
|
||||
|
||||
Reference in New Issue
Block a user