Web app: new entities (Image, RenderedAsset, SharedImage, Token, DeviceImageHistory), enums, repositories, controllers, message handlers, migrations, tests, frontend upload/library/sticker UI, Vue components. Firmware: EPD background screen binaries + gen scripts, setup_bg header. Infra: ddev config, test bundle, gitignore coverage dir. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,971 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=800">
|
||||
<title>Setup QR Screen — pictureFrame mockup</title>
|
||||
<style>
|
||||
/*
|
||||
HARDWARE CONSTRAINTS: 800×480px. Six colors only.
|
||||
#1a1a1a BLACK
|
||||
#f5f5f0 WHITE
|
||||
#f0d000 YELLOW
|
||||
#c03020 RED
|
||||
#1840c0 BLUE
|
||||
#10a040 GREEN
|
||||
|
||||
No gradients. No drop shadows. No anti-aliasing. No other colors.
|
||||
No fonts below ~16px.
|
||||
This file is a design mockup — open at 100% zoom in browser.
|
||||
*/
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #888;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
|
||||
/* Outer frame — the physical bezel */
|
||||
.device-bezel {
|
||||
width: 840px;
|
||||
height: 520px;
|
||||
background: #1a1a1a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* The e-ink display surface — exactly 800×480 */
|
||||
.display {
|
||||
width: 800px;
|
||||
height: 480px;
|
||||
background: #f5f5f0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
GREEN STATUS BAR — signals connected / almost done
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
||||
.status-bar {
|
||||
width: 800px;
|
||||
height: 52px;
|
||||
background: #10a040;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.status-bar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* WiFi connected indicator — pixelated bars (no border-radius) */
|
||||
.wifi-icon {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: 3px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.wifi-bar {
|
||||
background: #f5f5f0;
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
.wifi-bar-1 { height: 8px; }
|
||||
.wifi-bar-2 { height: 13px; }
|
||||
.wifi-bar-3 { height: 18px; }
|
||||
.wifi-bar-4 { height: 22px; }
|
||||
|
||||
.status-bar-label {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: #f5f5f0;
|
||||
}
|
||||
|
||||
.status-bar-right {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: #f5f5f0;
|
||||
letter-spacing: 0.06em;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.ip-chip {
|
||||
display: inline-block;
|
||||
background: #f5f5f0;
|
||||
color: #10a040;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.06em;
|
||||
padding: 3px 9px;
|
||||
}
|
||||
|
||||
/* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
MAIN BODY
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */
|
||||
.body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
/* ── LEFT PANEL ───────────────────────────────────── */
|
||||
.panel-left {
|
||||
width: 340px;
|
||||
flex-shrink: 0;
|
||||
padding: 24px 20px 20px 28px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
border-right: 2px solid #1a1a1a;
|
||||
}
|
||||
|
||||
.main-heading {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.main-heading em {
|
||||
font-style: normal;
|
||||
border-bottom: 3px solid #10a040;
|
||||
}
|
||||
|
||||
.sub-heading {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 15px;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.5;
|
||||
margin-top: 14px;
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.step-list {
|
||||
list-style: none;
|
||||
margin-top: 18px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.step-num {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background: #10a040;
|
||||
color: #f5f5f0;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-text {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 15px;
|
||||
color: #1a1a1a;
|
||||
line-height: 1.35;
|
||||
padding-top: 3px;
|
||||
}
|
||||
|
||||
.step-text strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* URL hint bar */
|
||||
.url-bar {
|
||||
margin-top: 18px;
|
||||
background: #1a1a1a;
|
||||
padding: 8px 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.url-bar-label {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
color: #10a040;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.url-bar-value {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 12px;
|
||||
color: #f5f5f0;
|
||||
word-break: break-all;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* Progress tracker */
|
||||
.progress-track {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
color: #1a1a1a;
|
||||
opacity: 0.45;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.progress-steps {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.prog-step {
|
||||
height: 6px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.prog-step.done {
|
||||
background: #10a040;
|
||||
}
|
||||
|
||||
.prog-step.active {
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.prog-step.todo {
|
||||
background: #1a1a1a;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.prog-step-labels {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.prog-step-label {
|
||||
flex: 1;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 10px;
|
||||
color: #1a1a1a;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.prog-step-label.done {
|
||||
color: #10a040;
|
||||
opacity: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.prog-step-label.active {
|
||||
opacity: 1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* ── CENTER PANEL — orientation diagrams ──────────── */
|
||||
.panel-center {
|
||||
width: 164px;
|
||||
flex-shrink: 0;
|
||||
border-right: 2px solid #1a1a1a;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 16px 10px;
|
||||
}
|
||||
|
||||
.orient-section-title {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.25em;
|
||||
text-transform: uppercase;
|
||||
color: #1a1a1a;
|
||||
opacity: 0.45;
|
||||
text-align: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.orient-label {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.15em;
|
||||
text-transform: uppercase;
|
||||
color: #1a1a1a;
|
||||
text-align: center;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.orient-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Landscape: wide rect + bottom ribbon */
|
||||
.orient-landscape-frame {
|
||||
width: 100px;
|
||||
height: 60px;
|
||||
border: 3px solid #1a1a1a;
|
||||
background: #f5f5f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Inner screen hatching — suggests image content */
|
||||
.orient-landscape-frame::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px; left: 6px; right: 6px; bottom: 6px;
|
||||
border: 1px solid #1a1a1a;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
.orient-landscape-ribbon {
|
||||
width: 100px;
|
||||
height: 9px;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
/* Portrait: tall rect + left ribbon */
|
||||
.orient-portrait-wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.orient-portrait-ribbon {
|
||||
width: 9px;
|
||||
height: 96px;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.orient-portrait-frame {
|
||||
width: 58px;
|
||||
height: 96px;
|
||||
border: 3px solid #1a1a1a;
|
||||
background: #f5f5f0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.orient-portrait-frame::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 6px; left: 6px; right: 6px; bottom: 6px;
|
||||
border: 1px solid #1a1a1a;
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
/* Active state — green accent for connected/done feeling */
|
||||
.orient-active .orient-landscape-frame,
|
||||
.orient-active .orient-portrait-frame {
|
||||
border-color: #10a040;
|
||||
}
|
||||
|
||||
.orient-active .orient-landscape-ribbon,
|
||||
.orient-active .orient-portrait-ribbon {
|
||||
background: #10a040;
|
||||
}
|
||||
|
||||
.orient-active .orient-label {
|
||||
color: #10a040;
|
||||
}
|
||||
|
||||
.active-badge {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
background: #10a040;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 11px;
|
||||
font-weight: 900;
|
||||
color: #f5f5f0;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.orient-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #1a1a1a;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
/* ── RIGHT PANEL — QR code ────────────────────────── */
|
||||
.panel-right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px 20px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.qr-instruction {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
color: #1a1a1a;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-wrapper {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
background: #f5f5f0;
|
||||
border: 3px solid #1a1a1a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Green corner accent — signals connected state */
|
||||
.qr-wrapper::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -3px;
|
||||
left: -3px;
|
||||
right: -3px;
|
||||
bottom: -3px;
|
||||
border: 3px solid #10a040;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qr-sub {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 12px;
|
||||
color: #1a1a1a;
|
||||
text-align: center;
|
||||
line-height: 1.45;
|
||||
opacity: 0.6;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
/* MAC address chip */
|
||||
.mac-chip {
|
||||
display: inline-block;
|
||||
background: #1a1a1a;
|
||||
color: #f5f5f0;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 3px 9px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="device-bezel">
|
||||
<div class="display">
|
||||
|
||||
<!-- STATUS BAR -->
|
||||
<div class="status-bar">
|
||||
<div class="status-bar-left">
|
||||
<!-- WiFi icon: 3 arcs + dot -->
|
||||
<div class="wifi-icon">
|
||||
<div class="wifi-bar wifi-bar-1"></div>
|
||||
<div class="wifi-bar wifi-bar-2"></div>
|
||||
<div class="wifi-bar wifi-bar-3"></div>
|
||||
<div class="wifi-bar wifi-bar-4"></div>
|
||||
</div>
|
||||
<span class="status-bar-label">WiFi Connected — Step 2 of 2</span>
|
||||
</div>
|
||||
<span class="status-bar-right">
|
||||
IP: <span class="ip-chip">192.168.1.47</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- BODY -->
|
||||
<div class="body">
|
||||
|
||||
<!-- LEFT: instructions + progress -->
|
||||
<div class="panel-left">
|
||||
<div>
|
||||
<div class="main-heading">Almost<br><em>ready.</em></div>
|
||||
<div class="sub-heading">
|
||||
Scan the QR code to give this frame a name and link it to your account.
|
||||
</div>
|
||||
|
||||
<ul class="step-list">
|
||||
<li class="step-item">
|
||||
<div class="step-num">1</div>
|
||||
<div class="step-text">
|
||||
Scan the QR code with your phone's camera
|
||||
</div>
|
||||
</li>
|
||||
<li class="step-item">
|
||||
<div class="step-num">2</div>
|
||||
<div class="step-text">
|
||||
Sign in or create an account at <strong>pictureframe.edholm.me</strong>
|
||||
</div>
|
||||
</li>
|
||||
<li class="step-item">
|
||||
<div class="step-num">3</div>
|
||||
<div class="step-text">
|
||||
Name the frame. Choose orientation. Done.
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="url-bar">
|
||||
<span class="url-bar-label">URL</span>
|
||||
<span class="url-bar-value">pictureframe.edholm.me/setup/1C:C3:AB:D1:91:F8</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<!-- Progress tracker: WiFi done / Account next / Frame ready todo -->
|
||||
<div class="progress-track">
|
||||
<div class="progress-label">Setup progress</div>
|
||||
<div class="progress-steps">
|
||||
<div class="prog-step done"></div>
|
||||
<div class="prog-step active"></div>
|
||||
<div class="prog-step todo"></div>
|
||||
</div>
|
||||
<div class="prog-step-labels">
|
||||
<div class="prog-step-label done">WiFi</div>
|
||||
<div class="prog-step-label active">Account</div>
|
||||
<div class="prog-step-label todo">Frame ready</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CENTER: orientation diagrams -->
|
||||
<div class="panel-center">
|
||||
<div class="orient-section-title">Frame orientation</div>
|
||||
|
||||
<!-- Landscape (active) -->
|
||||
<div class="orient-block orient-active">
|
||||
<div class="orient-label">Landscape</div>
|
||||
<div class="orient-landscape-frame"></div>
|
||||
<div class="orient-landscape-ribbon"></div>
|
||||
<div class="active-badge">✓</div>
|
||||
</div>
|
||||
|
||||
<div class="orient-divider"></div>
|
||||
|
||||
<!-- Portrait -->
|
||||
<div class="orient-block">
|
||||
<div class="orient-label">Portrait</div>
|
||||
<div class="orient-portrait-wrapper">
|
||||
<div class="orient-portrait-ribbon"></div>
|
||||
<div class="orient-portrait-frame"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- RIGHT: QR code -->
|
||||
<div class="panel-right">
|
||||
<div class="qr-instruction">Scan to finish</div>
|
||||
|
||||
<div class="qr-wrapper">
|
||||
<!-- QR code — encodes https://pictureframe.edholm.me/setup/1C:C3:AB:D1:91:F8 -->
|
||||
<svg width="182" height="182" viewBox="0 0 41 41" xmlns="http://www.w3.org/2000/svg"
|
||||
shape-rendering="crispEdges">
|
||||
<rect width="41" height="41" fill="#f5f5f0"/>
|
||||
|
||||
<!-- TOP-LEFT FINDER -->
|
||||
<rect x="1" y="1" width="7" height="7" fill="#1a1a1a"/>
|
||||
<rect x="2" y="2" width="5" height="5" fill="#f5f5f0"/>
|
||||
<rect x="3" y="3" width="3" height="3" fill="#1a1a1a"/>
|
||||
|
||||
<!-- TOP-RIGHT FINDER -->
|
||||
<rect x="33" y="1" width="7" height="7" fill="#1a1a1a"/>
|
||||
<rect x="34" y="2" width="5" height="5" fill="#f5f5f0"/>
|
||||
<rect x="35" y="3" width="3" height="3" fill="#1a1a1a"/>
|
||||
|
||||
<!-- BOTTOM-LEFT FINDER -->
|
||||
<rect x="1" y="33" width="7" height="7" fill="#1a1a1a"/>
|
||||
<rect x="2" y="34" width="5" height="5" fill="#f5f5f0"/>
|
||||
<rect x="3" y="35" width="3" height="3" fill="#1a1a1a"/>
|
||||
|
||||
<!-- TIMING H -->
|
||||
<rect x="9" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="11" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="13" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="19" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="25" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="29" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="31" y="6" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- TIMING V -->
|
||||
<rect x="6" y="9" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="11" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="13" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="15" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="17" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="19" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="21" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="23" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="25" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="27" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="29" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="6" y="31" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- DATA — URL QR pattern (unique from AP screen) -->
|
||||
<!-- Row 9 -->
|
||||
<rect x="8" y="9" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="9" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="9" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="19" y="9" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="9" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="26" y="9" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="30" y="9" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 10 -->
|
||||
<rect x="9" y="10" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="10" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="16" y="10" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="20" y="10" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="25" y="10" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="29" y="10" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="32" y="10" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 11 -->
|
||||
<rect x="8" y="11" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="11" y="11" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="16" y="11" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="18" y="11" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="11" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="11" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="30" y="11" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 12 -->
|
||||
<rect x="9" y="12" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="13" y="12" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="12" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="12" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="24" y="12" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="29" y="12" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="33" y="12" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 13 -->
|
||||
<rect x="8" y="13" width="4" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="13" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="16" y="13" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="13" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="25" y="13" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="28" y="13" width="4" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 14 -->
|
||||
<rect x="9" y="14" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="14" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="16" y="14" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="19" y="14" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="14" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="28" y="14" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="31" y="14" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 15 -->
|
||||
<rect x="8" y="15" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="15" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="15" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="22" y="15" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="26" y="15" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="30" y="15" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="33" y="15" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 16 -->
|
||||
<rect x="9" y="16" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="16" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="18" y="16" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="16" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="24" y="16" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="29" y="16" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="33" y="16" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 17 -->
|
||||
<rect x="8" y="17" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="11" y="17" width="4" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="17" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="17" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="26" y="17" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="29" y="17" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 18 -->
|
||||
<rect x="9" y="18" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="13" y="18" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="18" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="20" y="18" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="24" y="18" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="18" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="32" y="18" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 19 -->
|
||||
<rect x="8" y="19" width="4" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="19" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="18" y="19" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="22" y="19" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="19" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="31" y="19" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 20 -->
|
||||
<rect x="9" y="20" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="20" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="20" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="20" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="24" y="20" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="28" y="20" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="31" y="20" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="33" y="20" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 21 -->
|
||||
<rect x="8" y="21" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="21" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="21" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="18" y="21" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="21" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="21" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="32" y="21" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 22 -->
|
||||
<rect x="9" y="22" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="22" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="16" y="22" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="20" y="22" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="22" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="26" y="22" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="30" y="22" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 23 -->
|
||||
<rect x="8" y="23" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="11" y="23" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="23" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="20" y="23" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="25" y="23" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="28" y="23" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="32" y="23" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 24 -->
|
||||
<rect x="9" y="24" width="4" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="24" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="18" y="24" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="22" y="24" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="26" y="24" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="31" y="24" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 25 -->
|
||||
<rect x="8" y="25" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="25" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="25" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="25" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="24" y="25" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="28" y="25" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="31" y="25" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 26 -->
|
||||
<rect x="9" y="26" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="26" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="26" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="20" y="26" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="25" y="26" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="29" y="26" width="4" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 27 -->
|
||||
<rect x="8" y="27" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="13" y="27" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="27" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="19" y="27" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="27" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="28" y="27" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="32" y="27" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 28 -->
|
||||
<rect x="9" y="28" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="13" y="28" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="16" y="28" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="20" y="28" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="28" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="26" y="28" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="31" y="28" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="33" y="28" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 29 -->
|
||||
<rect x="8" y="29" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="10" y="29" width="4" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="29" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="18" y="29" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="29" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="29" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="30" y="29" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 30 -->
|
||||
<rect x="9" y="30" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="30" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="30" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="30" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="26" y="30" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="30" y="30" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="33" y="30" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Row 31 -->
|
||||
<rect x="8" y="31" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="31" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="31" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="20" y="31" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="24" y="31" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="31" width="4" height="1" fill="#1a1a1a"/>
|
||||
<rect x="33" y="31" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Alignment pattern (bottom-right) -->
|
||||
<rect x="28" y="28" width="5" height="5" fill="#1a1a1a"/>
|
||||
<rect x="29" y="29" width="3" height="3" fill="#f5f5f0"/>
|
||||
<rect x="30" y="30" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<rect x="8" y="8" width="1" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<!-- Data rows below bottom-left finder (rows 34–39) -->
|
||||
<rect x="9" y="34" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="34" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="16" y="34" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="34" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="24" y="34" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="28" y="34" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<rect x="8" y="35" width="4" height="1" fill="#1a1a1a"/>
|
||||
<rect x="14" y="35" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="17" y="35" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="35" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="26" y="35" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="29" y="35" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<rect x="9" y="36" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="13" y="36" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="18" y="36" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="21" y="36" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="25" y="36" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="30" y="36" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<rect x="8" y="37" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="11" y="37" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="37" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="20" y="37" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="23" y="37" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="37" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="30" y="37" width="3" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<rect x="9" y="38" width="4" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="38" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="18" y="38" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="22" y="38" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="38" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="31" y="38" width="2" height="1" fill="#1a1a1a"/>
|
||||
|
||||
<rect x="8" y="39" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="12" y="39" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="15" y="39" width="2" height="1" fill="#1a1a1a"/>
|
||||
<rect x="19" y="39" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="22" y="39" width="3" height="1" fill="#1a1a1a"/>
|
||||
<rect x="27" y="39" width="1" height="1" fill="#1a1a1a"/>
|
||||
<rect x="29" y="39" width="4" height="1" fill="#1a1a1a"/>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="qr-sub">
|
||||
<span class="mac-chip">1C:C3:AB:D1:91:F8</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.body -->
|
||||
|
||||
</div><!-- /.display -->
|
||||
</div><!-- /.device-bezel -->
|
||||
|
||||
<!--
|
||||
DESIGNER NOTES
|
||||
──────────────────────────────────────────────────────────────────
|
||||
Screen: Setup QR — Step 2 of 2
|
||||
State: Frame joined home WiFi. Waiting for account link via scan.
|
||||
Accent: GREEN — signals progress, success, completion imminent.
|
||||
|
||||
Layout rationale:
|
||||
• Green bar immediately contrasts with AP screen's yellow — user
|
||||
registers "something changed, I'm further along." The WiFi
|
||||
icon in the bar confirms the network is live.
|
||||
• "Almost ready." is deliberately casual and warm — not "Device
|
||||
Provisioning Step 2/2." The sub-heading does the explaining.
|
||||
• URL bar at the bottom of instructions answers the fallback question
|
||||
(what if the QR doesn't scan?) without cluttering the main steps.
|
||||
• Progress track — three segments: WiFi done (green), Account
|
||||
(solid black = active), Frame ready (faint = todo) — gives the user
|
||||
a map without requiring them to read it.
|
||||
• Center panel reuses the orientation diagram pattern from screen 1,
|
||||
maintaining visual language across the two screens. The active
|
||||
orientation is green this time, consistent with the accent shift.
|
||||
• QR panel: green bracket border, MAC chip below the code. The MAC
|
||||
is shown because the URL contains it — the user may need to verify
|
||||
their device if they have multiple frames. Also tells the builder
|
||||
(Matt) what he's looking at during development.
|
||||
|
||||
Physical observation: if the frame is landscape (current mockup),
|
||||
the ribbon connector sits at the bottom of the frame, behind the
|
||||
stand or mount. The orientation diagram ribbon is bottom for
|
||||
landscape, left for portrait — this matches physical reality.
|
||||
|
||||
Firmware implementation notes:
|
||||
• This screen appears after STA mode connection confirmed.
|
||||
• The QR encodes the /setup/{mac} URL exactly.
|
||||
• The IP shown is the DHCP-assigned address — confirm in real code
|
||||
that the display format matches (colons in MAC, not dashes).
|
||||
• Frame should stay on this screen until the web app confirms
|
||||
account linkage — then reboot into image-cycling mode.
|
||||
──────────────────────────────────────────────────────────────────
|
||||
-->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user