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:
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 |
+154
-93
@@ -80,8 +80,17 @@ F_URL = ttf("DejaVuSans.ttf", 22) # URL bar mono
|
||||
|
||||
|
||||
# ── Layout constants ─────────────────────────────────────────────────────────
|
||||
HEADER_H = 130 # header band height
|
||||
BODY_Y = HEADER_H
|
||||
# Hero treatment: full-bleed harbour photo banner (460 tall) + slim accent
|
||||
# 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
|
||||
LEFT_X = 0; LEFT_W = 600
|
||||
RIGHT_X = 602; RIGHT_W = W - RIGHT_X # 598
|
||||
@@ -91,17 +100,22 @@ RIGHT_PAD = 36
|
||||
|
||||
# ── QR overlay regions — MUST match the panel driver ─────────────────────────
|
||||
# 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_CELL = 14 # 37 × 14 = 518 px
|
||||
AP_QR_PX = AP_QR_MODS * AP_QR_CELL
|
||||
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_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_X = (W - SETUP_QR_PX) // 2 # 272
|
||||
SETUP_QR_Y = BODY_Y + 360 # 490
|
||||
SETUP_QR_X = (W - SETUP_QR_PX) // 2 # 313
|
||||
SETUP_QR_Y = BODY_Y + 220 # 750
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def draw_header(draw, accent, header_text):
|
||||
"""Coloured band along the top with section title on the left. Logo
|
||||
placeholder is rendered separately by draw_logo_placeholder() so the
|
||||
brand mark sits in the top right of every setup screen."""
|
||||
draw.rectangle([0, 0, W - 1, HEADER_H - 1], fill=accent)
|
||||
draw.text((LEFT_PAD, 40), header_text, font=F_BAR, fill=BK)
|
||||
def draw_hero(img, accent, header_text):
|
||||
"""Full-bleed photo banner + wordmark + slim accent band carrying the
|
||||
section title. The brand moment occupies the top 530 px of the panel."""
|
||||
banner = compose_hero_banner(W, HERO_BANNER_H)
|
||||
img.paste(banner, (0, 0))
|
||||
|
||||
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 ─────────────────────────────────────────────────────────────────────
|
||||
# Composed mirror of webApp/frontend/public/logo.svg: harbour photo +
|
||||
# black-fade gradient + "WeVisto" wordmark (V in yellow). Rendered in
|
||||
# pure PIL because cairosvg / rsvg / imagemagick aren't installed on the
|
||||
# build host. Source photo lives in webApp/brand/.
|
||||
LOGO_SIDE = 110 # square; fits within HEADER_H=130 with padding
|
||||
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",
|
||||
"IMG_2524-square900.jpg")
|
||||
# ── Brand assets ─────────────────────────────────────────────────────────────
|
||||
# Source photo (5712×4284 original — gives us latitude for landscape crops
|
||||
# without having to upscale the 900-square version). The square JPEG is
|
||||
# used by compose_logo() for the web/email logo card; the hires original
|
||||
# is used by compose_hero_banner() for the panel hero banner.
|
||||
LOGO_SRC_SQUARE = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
"..", "..", "webApp", "brand",
|
||||
"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):
|
||||
"""Returns a size×size RGB PIL image of the WeVisto wordmark over the
|
||||
Camogli harbour photo. Mirrors webApp/frontend/public/logo.svg (320×320
|
||||
viewBox).
|
||||
|
||||
The composition is done at SVG-native 320×320 then LANCZOS-downsampled
|
||||
to `size` — gets the same anti-aliasing the browser would produce, much
|
||||
cleaner text than rendering directly at the small target size.
|
||||
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)
|
||||
def _gradient_overlay(w, h):
|
||||
"""Black-fade overlay matching webApp/frontend/public/logo.svg: 0%
|
||||
opaque at top/bottom, 45% in the middle 42-58% band. Used to anchor
|
||||
the wordmark legibly against varying photo content underneath."""
|
||||
layer = Image.new("RGBA", (w, h), (0, 0, 0, 0))
|
||||
od = ImageDraw.Draw(layer)
|
||||
for y in range(h):
|
||||
t = y / max(1, h - 1)
|
||||
if t < 0.42:
|
||||
a = 0.45 * (t / 0.42)
|
||||
elif t < 0.58:
|
||||
a = 0.45
|
||||
else:
|
||||
a = 0.45 * ((1 - t) / 0.42)
|
||||
od.line([(0, y), (SRC, y)], fill=(0, 0, 0, int(a * 255)))
|
||||
bg = Image.alpha_composite(bg.convert("RGBA"), overlay).convert("RGB")
|
||||
od.line([(0, y), (w, y)], fill=(0, 0, 0, int(a * 255)))
|
||||
return layer
|
||||
|
||||
draw = ImageDraw.Draw(bg)
|
||||
font = ttf("DejaVuSans-Bold.ttf", 62)
|
||||
def _render_wordmark(canvas, cx, cy, font_size, stroke=2):
|
||||
"""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)]
|
||||
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)
|
||||
x = (SRC - total_w) // 2
|
||||
y = 175 - 62 // 2 - 16 # matches SVG translate(160 175) baseline
|
||||
x = cx - total_w // 2
|
||||
y = cy - font_size // 2 - max(2, font_size // 16)
|
||||
for (t, fill), w in zip(parts, widths):
|
||||
draw.text((x, y), t, font=font, fill=fill,
|
||||
stroke_width=2, stroke_fill=fill)
|
||||
stroke_width=stroke, stroke_fill=fill)
|
||||
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):
|
||||
"""Paste the composed WeVisto logo in the top-right of the header."""
|
||||
img.paste(compose_logo(LOGO_SIDE), (LOGO_X, LOGO_Y))
|
||||
def compose_hero_banner(banner_w, banner_h):
|
||||
"""Wide hero banner: harbour photo cropped from the hi-res original to
|
||||
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):
|
||||
@@ -244,7 +297,7 @@ def paste_rotated_text(img, text, font, fill, anchor_xy, ccw_degrees=90):
|
||||
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
|
||||
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
|
||||
# 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).
|
||||
diag_w, diag_h = 130, 200
|
||||
ribbon_thick = 14
|
||||
pair_gap = 100 # extra room so the rotated label doesn't crowd the divider
|
||||
# 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
|
||||
ribbon_thick = 14
|
||||
pair_gap = 100
|
||||
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
|
||||
ls_x = pt_x + diag_w + pair_gap
|
||||
@@ -328,30 +388,31 @@ def gen_ap(accent=YL,
|
||||
img = Image.new("RGB", (W, H), WH)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
draw_header(draw, accent, header_text)
|
||||
draw_logo_placeholder(img)
|
||||
draw_hero(img, accent, header_text)
|
||||
draw_divider(draw)
|
||||
|
||||
# ── 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
|
||||
draw.text((LEFT_PAD, head_y), "Connect to", font=F_HEAD, fill=BK)
|
||||
draw.text((LEFT_PAD, head_y + 90), "WiFi", font=F_HEAD, fill=BK)
|
||||
bb = draw.textbbox((0, 0), "WiFi", font=F_HEAD)
|
||||
underline_y = head_y + 90 + bb[3] + 6
|
||||
f_head = ttf("DejaVuSans-Bold.ttf", 60)
|
||||
draw.text((LEFT_PAD, head_y), "Connect to WiFi", font=f_head, fill=BK)
|
||||
bb = draw.textbbox((0, 0), "Connect to WiFi", font=f_head)
|
||||
underline_y = head_y + bb[3] + 6
|
||||
draw.rectangle([LEFT_PAD, underline_y,
|
||||
LEFT_PAD + bb[2] + 4, underline_y + 6], fill=accent)
|
||||
|
||||
# 5 numbered steps — same instructions as the 7.3" but with the larger
|
||||
# type that fits comfortably on a 13.3" portrait body.
|
||||
# 4 numbered steps. step_pitch tightened from 112 → 108 so the steps +
|
||||
# orientation diagram + manual QR all fit in the post-hero 1070-px body.
|
||||
steps = [
|
||||
("Turn on your WeVisto", ""),
|
||||
("Unlock your phone", ""),
|
||||
("Scan QR 1", "This will connect your phone to the WeVisto"),
|
||||
("Scan QR 2", "This will open the WeVisto setup page"),
|
||||
]
|
||||
step_y0 = head_y + 240
|
||||
step_pitch = 112
|
||||
step_y0 = underline_y + 60
|
||||
step_pitch = 108
|
||||
box = 50 # numbered black box size
|
||||
text_x = LEFT_PAD + box + 22
|
||||
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)):
|
||||
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
|
||||
# the user sees both possible hanging positions before they commit.
|
||||
orientation_diagrams(img, LEFT_X + LEFT_W // 2, step_y0 + len(steps) * step_pitch + 60)
|
||||
# Orientation diagrams — between the steps and the manual QR. Compact
|
||||
# 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 + 30,
|
||||
compact=True)
|
||||
|
||||
# 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(
|
||||
version=None,
|
||||
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||
box_size=7,
|
||||
box_size=5,
|
||||
border=2,
|
||||
)
|
||||
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")
|
||||
mw, mh = manual_img.size
|
||||
manual_x = LEFT_PAD
|
||||
manual_y = H - mh - 60
|
||||
manual_y = H - mh - 30
|
||||
img.paste(manual_img, (manual_x, manual_y))
|
||||
|
||||
# Side label aligned with manual QR.
|
||||
label_x = manual_x + mw + 28
|
||||
label_y = manual_y + (mh - 80) // 2
|
||||
label_x = manual_x + mw + 24
|
||||
label_y = manual_y + (mh - 64) // 2
|
||||
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 + 70), "& troubleshoot", 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 + 58), "& troubleshoot", font=F_STEP, fill=BK)
|
||||
|
||||
# ── RIGHT COLUMN ─────────────────────────────────────────────────────────
|
||||
rcx = RIGHT_X + RIGHT_W // 2
|
||||
@@ -448,29 +511,27 @@ def gen_setup():
|
||||
img = Image.new("RGB", (W, H), WH)
|
||||
draw = ImageDraw.Draw(img)
|
||||
|
||||
draw_header(draw, GR, "WIFI CONNECTED — STEP 2 OF 2")
|
||||
draw_logo_placeholder(img)
|
||||
draw_hero(img, GR, "WIFI CONNECTED — STEP 2 OF 2")
|
||||
|
||||
# Centered heading
|
||||
text_center(draw, W // 2, BODY_Y + 30, "Almost ready", F_HEAD, BK)
|
||||
# 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)
|
||||
bb = draw.textbbox((0, 0), "Almost ready", font=F_HEAD)
|
||||
underline_w = bb[2] + 4
|
||||
text_w = bb[2] - bb[0]
|
||||
underline_x = (W - text_w) // 2
|
||||
underline_y = BODY_Y + 30 + bb[3] + 6
|
||||
draw.rectangle([underline_x, underline_y,
|
||||
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
|
||||
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)
|
||||
text_center(draw, W // 2,
|
||||
SETUP_QR_Y - 56,
|
||||
"SCAN TO FINISH",
|
||||
F_LABEL, BK)
|
||||
text_center(draw, W // 2, SETUP_QR_Y - 56,
|
||||
"Scan the QR to link your wevisto.com account",
|
||||
F_STEP_B, BK)
|
||||
|
||||
# MAC chip below QR — frame's identifier so the user knows which device
|
||||
# they're claiming. Static placeholder until firmware writes text here.
|
||||
|
||||
@@ -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
|
||||
// decorative borders or the QR placeholder shows through.
|
||||
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) {
|
||||
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) {
|
||||
draw_from_lfs("/setup_bg.bin", COLOR_GREEN, qr, 272, 490, 16);
|
||||
draw_from_lfs("/setup_bg.bin", COLOR_GREEN, qr, 313, 750, 14);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user