Store assets: App Store header, search results, iPhone 6.3 and foldable sizes
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:
Ayushya Amitabh
2026-10-07 22:58:49 -04:00
co-authored by Claude Sonnet 5.5
parent 5837464581
commit 660d0bc869
25 changed files with 261 additions and 1 deletions
+10
View File
@@ -34,8 +34,18 @@ Schibsted Grotesk / Instrument Sans), flat shapes, alternating light and dark.
| Google Play 7-inch tablet, landscape | `android/tablet-7in-landscape/` | 1920 × 1200 |
| Google Play 10-inch tablet, landscape | `android/tablet-10in-landscape/` | 2560 × 1600 |
| App Store iPad screenshots, landscape (13" slot) | `ios/ipad-landscape/` | 2752 × 2064 |
| App Store iPhone 6.1" / 6.3" screenshots | `ios/iphone-6.3in/` | 1206 × 2622 |
| App Store iPhone Duo (foldable), portrait | `ios/iphone-fold-portrait/` | 2007 × 2853 |
| App Store iPhone Duo (foldable), landscape | `ios/iphone-fold-landscape/` | 2853 × 2007 |
| App Store header | `ios/header/` | 5244 × 2950, 3840 × 1646 |
| App Store search results | `ios/search-results/` | 5244 × 2950, 3840 × 2560, 1920 × 1280 |
| iOS icon reference | `ios/icon-1024.png` | 1024 × 1024 |
The iPad 13" slot (2064 × 2752 and 2752 × 2064) is covered by `ios/ipad/` and
`ios/ipad-landscape/`. The header and search-results banners are one brand
design (name, tagline, iPad and iPhone) rendered at each size. App previews
(video) are not produced here.
Upload in numeric order. The App Store uses the icon embedded in the uploaded
build; `ios/icon-1024.png` is only a reference copy. Screenshots, the feature
graphic and the iOS icon are opaque RGB PNGs; the Play icon is 32-bit RGBA.
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 431 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 684 KiB

+180
View File
@@ -359,6 +359,186 @@
"mode": "RGB",
"story": "external"
},
{
"file": "ios/iphone-6.3in/01-accounts.png",
"size": [
1206,
2622
],
"mode": "RGB",
"story": "accounts"
},
{
"file": "ios/iphone-6.3in/02-locks.png",
"size": [
1206,
2622
],
"mode": "RGB",
"story": "locks"
},
{
"file": "ios/iphone-6.3in/03-sync.png",
"size": [
1206,
2622
],
"mode": "RGB",
"story": "sync"
},
{
"file": "ios/iphone-6.3in/04-sharing.png",
"size": [
1206,
2622
],
"mode": "RGB",
"story": "sharing"
},
{
"file": "ios/iphone-6.3in/05-external.png",
"size": [
1206,
2622
],
"mode": "RGB",
"story": "external"
},
{
"file": "ios/iphone-fold-portrait/01-accounts.png",
"size": [
2007,
2853
],
"mode": "RGB",
"story": "accounts"
},
{
"file": "ios/iphone-fold-portrait/02-locks.png",
"size": [
2007,
2853
],
"mode": "RGB",
"story": "locks"
},
{
"file": "ios/iphone-fold-portrait/03-sync.png",
"size": [
2007,
2853
],
"mode": "RGB",
"story": "sync"
},
{
"file": "ios/iphone-fold-portrait/04-sharing.png",
"size": [
2007,
2853
],
"mode": "RGB",
"story": "sharing"
},
{
"file": "ios/iphone-fold-portrait/05-external.png",
"size": [
2007,
2853
],
"mode": "RGB",
"story": "external"
},
{
"file": "ios/iphone-fold-landscape/01-accounts.png",
"size": [
2853,
2007
],
"mode": "RGB",
"story": "accounts"
},
{
"file": "ios/iphone-fold-landscape/02-locks.png",
"size": [
2853,
2007
],
"mode": "RGB",
"story": "locks"
},
{
"file": "ios/iphone-fold-landscape/03-sync.png",
"size": [
2853,
2007
],
"mode": "RGB",
"story": "sync"
},
{
"file": "ios/iphone-fold-landscape/04-sharing.png",
"size": [
2853,
2007
],
"mode": "RGB",
"story": "sharing"
},
{
"file": "ios/iphone-fold-landscape/05-external.png",
"size": [
2853,
2007
],
"mode": "RGB",
"story": "external"
},
{
"file": "ios/header/header-5244x2950.png",
"size": [
5244,
2950
],
"mode": "RGB",
"story": "banner"
},
{
"file": "ios/header/header-3840x1646.png",
"size": [
3840,
1646
],
"mode": "RGB",
"story": "banner"
},
{
"file": "ios/search-results/search-5244x2950.png",
"size": [
5244,
2950
],
"mode": "RGB",
"story": "banner"
},
{
"file": "ios/search-results/search-3840x2560.png",
"size": [
3840,
2560
],
"mode": "RGB",
"story": "banner"
},
{
"file": "ios/search-results/search-1920x1280.png",
"size": [
1920,
1280
],
"mode": "RGB",
"story": "banner"
},
{
"file": "android/icon-512.png",
"size": [
Binary file not shown.
@@ -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 [