feat(setup): Variant B full-bleed hero on 13.3 panel

Adopts the hero treatment Matt picked from the /tmp/setup-mockups
gallery: a 1200×460 harbour photo banner with the WeVisto wordmark at
200pt overlaid centred, then a 70-px accent band carrying the section
title. Replaces the prior 130-tall single band where the 110×110 logo
card couldn't render the Camogli photo recognisably under the 6-colour
palette.

Implementation notes:
- compose_hero_banner() crops from the hi-res IMG_2524.jpg (so we don't
  upsample the 900-square version), composites the SVG black-fade
  gradient, then Floyd-Steinberg dithers to the Spectra-6 palette so the
  photo reads as continuous tone instead of nearest-neighbour colour
  fields. Wordmark composited after the dither to keep text edges crisp.
- Compact orientation diagrams + smaller manual QR (box_size=5) so the
  AP screen's left column still fits the 4 steps + diagrams + help QR
  inside the 1070-px body left below the taller hero.
- Setup QR cell shrunk 16 → 14 (656 → 574 px) so the setup screen fits
  the QR + MAC chip + progress bar below the hero.
- Redundant two-line "Scan the QR to link this frame / to your
  wevisto.com account." dropped from setup screen — heading + label
  above the QR + MAC chip below it cover the same ground without
  crowding the post-hero body.
- epd_driver.cpp QR overlay coords updated to match: AP 230→590,
  setup (272,490,16) → (313,750,14).

compose_logo() (square card) kept for any future use; not currently
called by gen_ap/gen_setup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 23:16:43 -04:00
parent 3420ec56f5
commit b0ea1ce216
8 changed files with 157 additions and 96 deletions
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 251 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 240 KiB

+149 -88
View File
@@ -80,8 +80,17 @@ F_URL = ttf("DejaVuSans.ttf", 22) # URL bar mono
# ── Layout constants ───────────────────────────────────────────────────────── # ── Layout constants ─────────────────────────────────────────────────────────
HEADER_H = 130 # header band height # Hero treatment: full-bleed harbour photo banner (460 tall) + slim accent
BODY_Y = HEADER_H # band (70 tall) carrying the section title. Replaces the prior 130-tall
# single header band — at 110×110 the harbour photo couldn't render
# recognisably under the panel's 6-colour palette, so the brand moment now
# takes the whole top of the screen. 320×320 logo card variant is kept in
# compose_logo() for web/email use.
HERO_BANNER_H = 460
HERO_BAND_H = 70
HERO_H = HERO_BANNER_H + HERO_BAND_H # 530
HEADER_H = HERO_H # alias kept for grep
BODY_Y = HERO_H
DIV_X = 600 # vertical divider between left + right columns DIV_X = 600 # vertical divider between left + right columns
LEFT_X = 0; LEFT_W = 600 LEFT_X = 0; LEFT_W = 600
RIGHT_X = 602; RIGHT_W = W - RIGHT_X # 598 RIGHT_X = 602; RIGHT_W = W - RIGHT_X # 598
@@ -91,17 +100,22 @@ RIGHT_PAD = 36
# ── QR overlay regions — MUST match the panel driver ───────────────────────── # ── QR overlay regions — MUST match the panel driver ─────────────────────────
# Dynamic QRs (left as WHITE rectangles in the .bin so firmware can overlay). # Dynamic QRs (left as WHITE rectangles in the .bin so firmware can overlay).
# Coords shifted to accommodate the taller HERO_H=530 (was HEADER_H=130);
# epd_driver.cpp epd_draw_*_screen calls must update to match.
AP_QR_MODS = 37 # version 5, ECC_LOW AP_QR_MODS = 37 # version 5, ECC_LOW
AP_QR_CELL = 14 # 37 × 14 = 518 px AP_QR_CELL = 14 # 37 × 14 = 518 px
AP_QR_PX = AP_QR_MODS * AP_QR_CELL AP_QR_PX = AP_QR_MODS * AP_QR_CELL
AP_QR_X = RIGHT_X + (RIGHT_W - AP_QR_PX) // 2 # 642 AP_QR_X = RIGHT_X + (RIGHT_W - AP_QR_PX) // 2 # 642
AP_QR_Y = BODY_Y + 100 # 230 AP_QR_Y = BODY_Y + 60 # 590
# Setup QR shrunk from 16 to 14 cells (656 → 574 px) so the post-hero body
# can still fit the QR + MAC chip + caption + progress bar within the
# remaining 1070 px below the hero.
SETUP_QR_MODS = 41 # version 6, ECC_LOW SETUP_QR_MODS = 41 # version 6, ECC_LOW
SETUP_QR_CELL = 16 # 41 × 16 = 656 px SETUP_QR_CELL = 14 # 41 × 14 = 574 px
SETUP_QR_PX = SETUP_QR_MODS * SETUP_QR_CELL SETUP_QR_PX = SETUP_QR_MODS * SETUP_QR_CELL
SETUP_QR_X = (W - SETUP_QR_PX) // 2 # 272 SETUP_QR_X = (W - SETUP_QR_PX) // 2 # 313
SETUP_QR_Y = BODY_Y + 360 # 490 SETUP_QR_Y = BODY_Y + 220 # 750
def text_center(draw, cx, y, text, font, fill): def text_center(draw, cx, y, text, font, fill):
@@ -139,70 +153,109 @@ def draw_qr_frame(draw, qx, qy, qp, accent):
draw.rectangle([qx - 4, qy - 4, qx + qp + 3, qy + qp + 3], outline=BK, width=4) draw.rectangle([qx - 4, qy - 4, qx + qp + 3, qy + qp + 3], outline=BK, width=4)
def draw_header(draw, accent, header_text): def draw_hero(img, accent, header_text):
"""Coloured band along the top with section title on the left. Logo """Full-bleed photo banner + wordmark + slim accent band carrying the
placeholder is rendered separately by draw_logo_placeholder() so the section title. The brand moment occupies the top 530 px of the panel."""
brand mark sits in the top right of every setup screen.""" banner = compose_hero_banner(W, HERO_BANNER_H)
draw.rectangle([0, 0, W - 1, HEADER_H - 1], fill=accent) img.paste(banner, (0, 0))
draw.text((LEFT_PAD, 40), header_text, font=F_BAR, fill=BK)
draw = ImageDraw.Draw(img)
band_y = HERO_BANNER_H
draw.rectangle([0, band_y, W - 1, HERO_H - 1], fill=accent)
# F_BAR is 34pt, ~38 px tall — vertically centred in the band.
text_y = band_y + (HERO_BAND_H - 38) // 2
draw.text((LEFT_PAD, text_y), header_text, font=F_BAR, fill=BK)
# ── Logo ───────────────────────────────────────────────────────────────────── # ── Brand assets ─────────────────────────────────────────────────────────────
# Composed mirror of webApp/frontend/public/logo.svg: harbour photo + # Source photo (5712×4284 original — gives us latitude for landscape crops
# black-fade gradient + "WeVisto" wordmark (V in yellow). Rendered in # without having to upscale the 900-square version). The square JPEG is
# pure PIL because cairosvg / rsvg / imagemagick aren't installed on the # used by compose_logo() for the web/email logo card; the hires original
# build host. Source photo lives in webApp/brand/. # is used by compose_hero_banner() for the panel hero banner.
LOGO_SIDE = 110 # square; fits within HEADER_H=130 with padding LOGO_SRC_SQUARE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
LOGO_X = W - LOGO_SIDE - LEFT_PAD # 1054
LOGO_Y = (HEADER_H - LOGO_SIDE) // 2 # 10
LOGO_SRC = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "..", "webApp", "brand", "..", "..", "webApp", "brand",
"IMG_2524-square900.jpg") "IMG_2524-square900.jpg")
LOGO_SRC_HIRES = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"..", "..", "webApp", "brand",
"IMG_2524.jpg")
def compose_logo(size): def _gradient_overlay(w, h):
"""Returns a size×size RGB PIL image of the WeVisto wordmark over the """Black-fade overlay matching webApp/frontend/public/logo.svg: 0%
Camogli harbour photo. Mirrors webApp/frontend/public/logo.svg (320×320 opaque at top/bottom, 45% in the middle 42-58% band. Used to anchor
viewBox). the wordmark legibly against varying photo content underneath."""
layer = Image.new("RGBA", (w, h), (0, 0, 0, 0))
The composition is done at SVG-native 320×320 then LANCZOS-downsampled od = ImageDraw.Draw(layer)
to `size` — gets the same anti-aliasing the browser would produce, much for y in range(h):
cleaner text than rendering directly at the small target size. t = y / max(1, h - 1)
stroke_width=2 fakes font-weight 900 (DejaVuSans-Bold is only weight
700; no Black face is installed on the build host)."""
SRC = 320
bg = Image.open(LOGO_SRC).convert("RGB").resize((SRC, SRC), Image.LANCZOS)
# Black overlay band: 45% opacity peak at 42-58% height, fading at edges.
overlay = Image.new("RGBA", (SRC, SRC), (0, 0, 0, 0))
od = ImageDraw.Draw(overlay)
for y in range(SRC):
t = y / (SRC - 1)
if t < 0.42: if t < 0.42:
a = 0.45 * (t / 0.42) a = 0.45 * (t / 0.42)
elif t < 0.58: elif t < 0.58:
a = 0.45 a = 0.45
else: else:
a = 0.45 * ((1 - t) / 0.42) a = 0.45 * ((1 - t) / 0.42)
od.line([(0, y), (SRC, y)], fill=(0, 0, 0, int(a * 255))) od.line([(0, y), (w, y)], fill=(0, 0, 0, int(a * 255)))
bg = Image.alpha_composite(bg.convert("RGBA"), overlay).convert("RGB") return layer
draw = ImageDraw.Draw(bg) def _render_wordmark(canvas, cx, cy, font_size, stroke=2):
font = ttf("DejaVuSans-Bold.ttf", 62) """We[white] V[yellow] isto[white] centred at (cx, cy). stroke_width
fakes font-weight 900 — DejaVuSans-Bold is only weight 700."""
draw = ImageDraw.Draw(canvas)
font = ttf("DejaVuSans-Bold.ttf", font_size)
parts = [("We", WH), ("V", YL), ("isto", WH)] parts = [("We", WH), ("V", YL), ("isto", WH)]
widths = [draw.textbbox((0, 0), t, font=font, stroke_width=2)[2] for t, _ in parts] widths = [draw.textbbox((0, 0), t, font=font, stroke_width=stroke)[2] for t, _ in parts]
total_w = sum(widths) total_w = sum(widths)
x = (SRC - total_w) // 2 x = cx - total_w // 2
y = 175 - 62 // 2 - 16 # matches SVG translate(160 175) baseline y = cy - font_size // 2 - max(2, font_size // 16)
for (t, fill), w in zip(parts, widths): for (t, fill), w in zip(parts, widths):
draw.text((x, y), t, font=font, fill=fill, draw.text((x, y), t, font=font, fill=fill,
stroke_width=2, stroke_fill=fill) stroke_width=stroke, stroke_fill=fill)
x += w x += w
return bg.resize((size, size), Image.LANCZOS) def compose_logo(size):
"""Square logo card (photo + gradient + wordmark) at `size`×`size`.
Composes at SVG-native 320×320 then LANCZOS-downsamples for clean
anti-aliasing. Kept for any future use that wants the square mark on
the panel; the active hero uses compose_hero_banner() instead."""
SRC = 320
bg = Image.open(LOGO_SRC_SQUARE).convert("RGB").resize((SRC, SRC), Image.LANCZOS)
bg = Image.alpha_composite(bg.convert("RGBA"), _gradient_overlay(SRC, SRC)).convert("RGB")
_render_wordmark(bg, SRC // 2, int(0.547 * SRC), 62, stroke=2)
return bg.resize((size, size), Image.LANCZOS) if size != SRC else bg
def draw_logo_placeholder(img): def compose_hero_banner(banner_w, banner_h):
"""Paste the composed WeVisto logo in the top-right of the header.""" """Wide hero banner: harbour photo cropped from the hi-res original to
img.paste(compose_logo(LOGO_SIDE), (LOGO_X, LOGO_Y)) the target aspect, gradient overlay for legibility, Floyd-Steinberg
dithered to the Spectra-6 palette (so the photo reads as continuous
tone instead of nearest-neighbour colour blocks), then a big WeVisto
wordmark composited on top in crisp palette-exact colours."""
src = Image.open(LOGO_SRC_HIRES).convert("RGB")
sw, sh = src.size
src_ar = sw / sh
tgt_ar = banner_w / banner_h
if src_ar > tgt_ar:
new_w = int(sh * tgt_ar)
x0 = (sw - new_w) // 2
src = src.crop((x0, 0, x0 + new_w, sh))
else:
new_h = int(sw / tgt_ar)
y0 = int(sh * 0.42 - new_h // 2)
y0 = max(0, min(y0, sh - new_h))
src = src.crop((0, y0, sw, y0 + new_h))
banner = src.resize((banner_w, banner_h), Image.LANCZOS)
banner = Image.alpha_composite(banner.convert("RGBA"),
_gradient_overlay(banner_w, banner_h)).convert("RGB")
# FS-dither the photo + gradient against the Spectra-6 palette. Without
# this, the harbour collapses to blocky nearest-neighbour colour fields
# when pack()'s nearest() runs over the final panel image.
pal = Image.new("P", (1, 1))
pal.putpalette([*BK, *WH, *YL, *RD, *BL, *GR, *([0, 0, 0] * 250)])
banner = banner.quantize(palette=pal, dither=Image.FLOYDSTEINBERG).convert("RGB")
# Wordmark rendered AFTER the dither so text edges stay crisp instead
# of getting stippled. Colours used (WH, YL) are already palette-exact.
_render_wordmark(banner, banner_w // 2, int(0.55 * banner_h), 200, stroke=4)
return banner
def draw_divider(draw): def draw_divider(draw):
@@ -244,7 +297,7 @@ def paste_rotated_text(img, text, font, fill, anchor_xy, ccw_degrees=90):
img.paste(rotated, anchor_xy, rotated) img.paste(rotated, anchor_xy, rotated)
def orientation_diagrams(img, cx, top_y, label_color=None): def orientation_diagrams(img, cx, top_y, label_color=None, compact=False):
""" """
Side-by-side PORTRAIT / LANDSCAPE diagrams illustrating the two ways Side-by-side PORTRAIT / LANDSCAPE diagrams illustrating the two ways
the user can hang the frame. Drawn in current portrait-view coords: the user can hang the frame. Drawn in current portrait-view coords:
@@ -273,12 +326,19 @@ def orientation_diagrams(img, cx, top_y, label_color=None):
# Same external dimensions for both diagrams (the LANDSCAPE is a 90°-CCW # Same external dimensions for both diagrams (the LANDSCAPE is a 90°-CCW
# rotation of an upright wide rect — its bounding box is square'd to # rotation of an upright wide rect — its bounding box is square'd to
# match portrait so the pair sits in a clean two-up grid). # match portrait so the pair sits in a clean two-up grid). `compact`
# shrinks the diagrams ~35% so they fit alongside the manual QR in the
# AP screen's tighter post-hero body.
if compact:
diag_w, diag_h = 90, 140
ribbon_thick = 10
pair_gap = 70
else:
diag_w, diag_h = 130, 200 diag_w, diag_h = 130, 200
ribbon_thick = 14 ribbon_thick = 14
pair_gap = 100 # extra room so the rotated label doesn't crowd the divider pair_gap = 100
pair_w = diag_w * 2 + pair_gap pair_w = diag_w * 2 + pair_gap
base_y = top_y + 100 base_y = top_y + 60 if compact else top_y + 100
pt_x = cx - pair_w // 2 pt_x = cx - pair_w // 2
ls_x = pt_x + diag_w + pair_gap ls_x = pt_x + diag_w + pair_gap
@@ -328,30 +388,31 @@ def gen_ap(accent=YL,
img = Image.new("RGB", (W, H), WH) img = Image.new("RGB", (W, H), WH)
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
draw_header(draw, accent, header_text) draw_hero(img, accent, header_text)
draw_logo_placeholder(img)
draw_divider(draw) draw_divider(draw)
# ── LEFT COLUMN ────────────────────────────────────────────────────────── # ── LEFT COLUMN ──────────────────────────────────────────────────────────
# Big "Connect to WiFi" heading with accent underline. # Single-line "Connect to WiFi" heading — was a two-line block under the
# old shallow header; the new 530-tall hero swallowed the vertical budget
# so the body collapses to one line at a smaller F_TITLE size.
head_y = BODY_Y + 30 head_y = BODY_Y + 30
draw.text((LEFT_PAD, head_y), "Connect to", font=F_HEAD, fill=BK) f_head = ttf("DejaVuSans-Bold.ttf", 60)
draw.text((LEFT_PAD, head_y + 90), "WiFi", font=F_HEAD, fill=BK) draw.text((LEFT_PAD, head_y), "Connect to WiFi", font=f_head, fill=BK)
bb = draw.textbbox((0, 0), "WiFi", font=F_HEAD) bb = draw.textbbox((0, 0), "Connect to WiFi", font=f_head)
underline_y = head_y + 90 + bb[3] + 6 underline_y = head_y + bb[3] + 6
draw.rectangle([LEFT_PAD, underline_y, draw.rectangle([LEFT_PAD, underline_y,
LEFT_PAD + bb[2] + 4, underline_y + 6], fill=accent) LEFT_PAD + bb[2] + 4, underline_y + 6], fill=accent)
# 5 numbered steps — same instructions as the 7.3" but with the larger # 4 numbered steps. step_pitch tightened from 112 → 108 so the steps +
# type that fits comfortably on a 13.3" portrait body. # orientation diagram + manual QR all fit in the post-hero 1070-px body.
steps = [ steps = [
("Turn on your WeVisto", ""), ("Turn on your WeVisto", ""),
("Unlock your phone", ""), ("Unlock your phone", ""),
("Scan QR 1", "This will connect your phone to the WeVisto"), ("Scan QR 1", "This will connect your phone to the WeVisto"),
("Scan QR 2", "This will open the WeVisto setup page"), ("Scan QR 2", "This will open the WeVisto setup page"),
] ]
step_y0 = head_y + 240 step_y0 = underline_y + 60
step_pitch = 112 step_pitch = 108
box = 50 # numbered black box size box = 50 # numbered black box size
text_x = LEFT_PAD + box + 22 text_x = LEFT_PAD + box + 22
text_max_w = DIV_X - text_x - 18 # don't cross the column divider text_max_w = DIV_X - text_x - 18 # don't cross the column divider
@@ -366,16 +427,19 @@ def gen_ap(accent=YL,
for j, line in enumerate(wrap_to_width(draw, l2, F_STEP, text_max_w)): for j, line in enumerate(wrap_to_width(draw, l2, F_STEP, text_max_w)):
draw.text((text_x, by + 32 + j * line_h), line, font=F_STEP, fill=BK) draw.text((text_x, by + 32 + j * line_h), line, font=F_STEP, fill=BK)
# Orientation diagrams — tucked between the steps and the manual QR so # Orientation diagrams — between the steps and the manual QR. Compact
# the user sees both possible hanging positions before they commit. # variant (diag_h=130) so the LEFT column fits inside the smaller body.
orientation_diagrams(img, LEFT_X + LEFT_W // 2, step_y0 + len(steps) * step_pitch + 60) orientation_diagrams(img, LEFT_X + LEFT_W // 2,
step_y0 + len(steps) * step_pitch + 30,
compact=True)
# Manual QR bottom-left — covers "captive portal didn't open" + general # Manual QR bottom-left — covers "captive portal didn't open" + general
# troubleshooting. Same intent as the 7.3" but bigger box, more room. # troubleshooting. Shrunk to box_size=5 (~205 px) to share the column
# with the orientation diagrams above it.
manual_qr = qrcode.QRCode( manual_qr = qrcode.QRCode(
version=None, version=None,
error_correction=qrcode.constants.ERROR_CORRECT_L, error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=7, box_size=5,
border=2, border=2,
) )
manual_qr.add_data(MANUAL_URL) manual_qr.add_data(MANUAL_URL)
@@ -383,15 +447,14 @@ def gen_ap(accent=YL,
manual_img = manual_qr.make_image(fill_color=BK, back_color=WH).convert("RGB") manual_img = manual_qr.make_image(fill_color=BK, back_color=WH).convert("RGB")
mw, mh = manual_img.size mw, mh = manual_img.size
manual_x = LEFT_PAD manual_x = LEFT_PAD
manual_y = H - mh - 60 manual_y = H - mh - 30
img.paste(manual_img, (manual_x, manual_y)) img.paste(manual_img, (manual_x, manual_y))
# Side label aligned with manual QR. label_x = manual_x + mw + 24
label_x = manual_x + mw + 28 label_y = manual_y + (mh - 64) // 2
label_y = manual_y + (mh - 80) // 2
draw.text((label_x, label_y), "Need help?", font=F_STEP_B, fill=BK) draw.text((label_x, label_y), "Need help?", font=F_STEP_B, fill=BK)
draw.text((label_x, label_y + 38), "Scan for setup", font=F_STEP, fill=BK) draw.text((label_x, label_y + 32), "Scan for setup", font=F_STEP, fill=BK)
draw.text((label_x, label_y + 70), "& troubleshoot", font=F_STEP, fill=BK) draw.text((label_x, label_y + 58), "& troubleshoot", font=F_STEP, fill=BK)
# ── RIGHT COLUMN ───────────────────────────────────────────────────────── # ── RIGHT COLUMN ─────────────────────────────────────────────────────────
rcx = RIGHT_X + RIGHT_W // 2 rcx = RIGHT_X + RIGHT_W // 2
@@ -448,29 +511,27 @@ def gen_setup():
img = Image.new("RGB", (W, H), WH) img = Image.new("RGB", (W, H), WH)
draw = ImageDraw.Draw(img) draw = ImageDraw.Draw(img)
draw_header(draw, GR, "WIFI CONNECTED — STEP 2 OF 2") draw_hero(img, GR, "WIFI CONNECTED — STEP 2 OF 2")
draw_logo_placeholder(img)
# Centered heading # Centered heading. Two-line instructions that used to sit beneath the
# underline were dropped in the redesign — the hero already establishes
# WeVisto, the "SCAN TO FINISH" label sits right above the QR, and the
# MAC chip below identifies the device. Three labels for one QR was
# redundant under the tighter post-hero body height.
text_center(draw, W // 2, BODY_Y + 30, "Almost ready", F_HEAD, BK) text_center(draw, W // 2, BODY_Y + 30, "Almost ready", F_HEAD, BK)
bb = draw.textbbox((0, 0), "Almost ready", font=F_HEAD) bb = draw.textbbox((0, 0), "Almost ready", font=F_HEAD)
underline_w = bb[2] + 4
text_w = bb[2] - bb[0] text_w = bb[2] - bb[0]
underline_x = (W - text_w) // 2 underline_x = (W - text_w) // 2
underline_y = BODY_Y + 30 + bb[3] + 6 underline_y = BODY_Y + 30 + bb[3] + 6
draw.rectangle([underline_x, underline_y, draw.rectangle([underline_x, underline_y,
underline_x + text_w, underline_y + 6], fill=GR) underline_x + text_w, underline_y + 6], fill=GR)
text_center(draw, W // 2, BODY_Y + 170, "Scan the QR to link this frame", F_STEP_B, BK)
text_center(draw, W // 2, BODY_Y + 210, "to your wevisto.com account.", F_STEP, BK)
# Setup QR with decorative border + green/black double frame # Setup QR with decorative border + green/black double frame
draw_qr_frame(draw, SETUP_QR_X, SETUP_QR_Y, SETUP_QR_PX, GR) draw_qr_frame(draw, SETUP_QR_X, SETUP_QR_Y, SETUP_QR_PX, GR)
leave_qr_white(draw, SETUP_QR_X, SETUP_QR_Y, SETUP_QR_PX) leave_qr_white(draw, SETUP_QR_X, SETUP_QR_Y, SETUP_QR_PX)
text_center(draw, W // 2, text_center(draw, W // 2, SETUP_QR_Y - 56,
SETUP_QR_Y - 56, "Scan the QR to link your wevisto.com account",
"SCAN TO FINISH", F_STEP_B, BK)
F_LABEL, BK)
# MAC chip below QR — frame's identifier so the user knows which device # MAC chip below QR — frame's identifier so the user knows which device
# they're claiming. Static placeholder until firmware writes text here. # they're claiming. Static placeholder until firmware writes text here.
+3 -3
View File
@@ -428,13 +428,13 @@ static void draw_from_lfs(const char* path, uint8_t fallback_color,
// firmware paints the live QR into it. Mismatch = the QR draws over // firmware paints the live QR into it. Mismatch = the QR draws over
// decorative borders or the QR placeholder shows through. // decorative borders or the QR placeholder shows through.
void epd_draw_ap_screen(QRCode* qr) { void epd_draw_ap_screen(QRCode* qr) {
draw_from_lfs("/ap_bg.bin", COLOR_YELLOW, qr, 642, 230, 14); draw_from_lfs("/ap_bg.bin", COLOR_YELLOW, qr, 642, 590, 14);
} }
void epd_draw_ap_screen_retry(QRCode* qr) { void epd_draw_ap_screen_retry(QRCode* qr) {
draw_from_lfs("/ap_bg_retry.bin", COLOR_RED, qr, 642, 230, 14); draw_from_lfs("/ap_bg_retry.bin", COLOR_RED, qr, 642, 590, 14);
} }
void epd_draw_setup_screen(QRCode* qr) { void epd_draw_setup_screen(QRCode* qr) {
draw_from_lfs("/setup_bg.bin", COLOR_GREEN, qr, 272, 490, 16); draw_from_lfs("/setup_bg.bin", COLOR_GREEN, qr, 313, 750, 14);
} }