Files
pictureFrame-webApp/templates/setup/index.html.twig
T
football2801 77c51586e8
CI / test (push) Has been cancelled
feat(brand): V-viewfinder 3a — the approved rebrand (was: split-W)
The V-viewfinder was approved during the favicon-v2 picker at
/v2/, then deployed prematurely (e7b9756) and reverted (81effca) after
the 'design pick is not a deploy command' lesson. Deploying it now with
explicit go-ahead.

Files: yellow V outline with the Camogli harbor visible inside, navy
field outside. Replaces the split-W (two Vs forming a W) across:
- favicon-16/32/64
- apple-touch-icon (180)
- icon-192 + icon-512 manifest icons
- icon-512-maskable (V at 65% safe zone)
- favicon.svg vector
- favicon.ico multi-res
- root-level apple-touch-icon{,-precomposed}.png for iOS fallback paths

Cache-bust query bumped to ?v=20260515-vviewfinder so browsers refetch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-15 14:42:00 -04:00

154 lines
8.7 KiB
Twig
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en" data-design="{{ app.request.cookies.get('wevisto_design')|default('v1') }}">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/svg+xml" href="/build/favicon.svg?v=20260515-vviewfinder">
<link rel="icon" type="image/png" sizes="32x32" href="/build/icons/favicon-32.png?v=20260515-vviewfinder">
<link rel="icon" type="image/png" sizes="16x16" href="/build/icons/favicon-16.png?v=20260515-vviewfinder">
<link rel="apple-touch-icon" sizes="180x180" href="/build/icons/apple-touch-icon.png?v=20260515-vviewfinder">
<title>Set up your frame — WeVisto</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; min-height: 100dvh; background: #fdf6ee; color: #3a2e22;
display: flex; align-items: flex-start; justify-content: center; padding: 2rem 1rem; }
.card { width: 100%; max-width: 400px; }
h1 { font-size: 1.4rem; font-weight: 700; margin-bottom: .25rem; }
.subtitle { font-size: .875rem; color: #8a7060; margin-bottom: 1.5rem; }
.tabs { display: flex; border-bottom: 1px solid #e8d9c4; margin-bottom: 1.5rem; }
.tab { flex: 1; padding: .75rem; text-align: center; font-weight: 700; font-size: .9rem;
color: #8a7060; text-decoration: none; border-bottom: 2px solid transparent; transition: color .15s; }
.tab.active { color: #c97c3a; border-bottom-color: #c97c3a; }
.panel { display: none; } .panel.active { display: block; }
.field { margin-bottom: 1rem; }
label { display: block; font-size: .8125rem; font-weight: 600; color: #8a7060; margin-bottom: .375rem; }
input[type="email"], input[type="password"], input[type="text"] {
width: 100%; min-height: 44px; padding: 0 .875rem; border: 1px solid #e8d9c4;
border-radius: 10px; background: #fff; font-size: 1rem; color: #3a2e22; }
input[aria-invalid="true"] { border-color: #c0392b; }
.field-error { margin-top: .25rem; font-size: .8125rem; color: #c0392b; }
.btn { display: flex; align-items: center; justify-content: center; width: 100%; min-height: 44px;
margin-top: 1.25rem; background: #c97c3a; color: #fff; border: none; border-radius: 9999px;
font-size: 1rem; font-weight: 700; cursor: pointer; }
.claim-banner { background: #fff5e8; border: 1px solid #f0c987; border-radius: 10px;
padding: .75rem .875rem; margin-bottom: 1.25rem; font-size: .875rem; line-height: 1.4;
color: #5c3f1c; }
.claim-banner strong { display: block; margin-bottom: .25rem; }
.claim-check { display: flex; align-items: flex-start; gap: .625rem; margin-top: 1rem;
font-size: .875rem; line-height: 1.35; cursor: pointer; }
.claim-check input[type="checkbox"] { width: 18px; height: 18px; flex: 0 0 auto;
margin-top: 2px; accent-color: #c97c3a; cursor: pointer; }
.logo-badge { display: block; width: 80px; height: 80px; margin: 0 auto 1rem; border-radius: 14px; overflow: hidden; border: 1px solid #e8d9c4; box-shadow: 0 2px 10px rgba(0,0,0,.06); }
.logo-badge img { display: block; width: 100%; height: 100%; }
</style>
</head>
<body>
<div class="card">
<a href="/" class="logo-badge" aria-label="WeVisto"><img src="/build/logo.svg" alt=""></a>
<h1>Link this frame</h1>
<p class="subtitle">Create an account, or sign in if you already have one. The frame will link to whichever account you use here.</p>
{% if already_claimed %}
<p class="claim-banner" role="status">
<strong>This frame is already linked to another account.</strong>
If youre taking it over, tick the box below — the previous
owners photos and history for this frame will be permanently
removed.
</p>
{% if claim_error %}
<p class="field-error" role="alert" style="margin-bottom:1rem">{{ claim_error }}</p>
{% endif %}
{% endif %}
<div class="tabs">
<a href="#register" class="tab {% if not login_error %}active{% endif %}" data-tab="register">Create account</a>
<a href="#login" class="tab {% if login_error %}active{% endif %}" data-tab="login">Sign in</a>
</div>
{# ── Register panel ────────────────────────────────────────────────────── #}
<div id="register" class="panel {% if not login_error %}active{% endif %}">
{{ form_start(reg_form, {action: path('setup_register', {mac: mac}), attr: {novalidate: 'novalidate'}}) }}
<div class="field">
{{ form_label(reg_form.email) }}
{{ form_widget(reg_form.email, {attr: {
id: 'reg-email',
'aria-invalid': reg_form.email.vars.errors|length > 0 ? 'true' : 'false'
}}) }}
{% for error in reg_form.email.vars.errors %}
<p class="field-error" role="alert">{{ error.message }}</p>
{% endfor %}
</div>
<div class="field">
{{ form_label(reg_form.plainPassword.first) }}
{{ form_widget(reg_form.plainPassword.first, {attr: {
id: 'reg-pass',
autocomplete: 'new-password',
'aria-invalid': reg_form.plainPassword.first.vars.errors|length > 0 ? 'true' : 'false'
}}) }}
{% for error in reg_form.plainPassword.first.vars.errors %}
<p class="field-error" role="alert">{{ error.message }}</p>
{% endfor %}
</div>
<div class="field">
{{ form_label(reg_form.plainPassword.second) }}
{{ form_widget(reg_form.plainPassword.second, {attr: {
id: 'reg-pass-confirm',
autocomplete: 'new-password',
'aria-invalid': reg_form.plainPassword.vars.errors|length > 0 ? 'true' : 'false'
}}) }}
{% for error in reg_form.plainPassword.vars.errors %}
<p class="field-error" role="alert">{{ error.message }}</p>
{% endfor %}
</div>
{% if already_claimed %}
<label class="claim-check">
<input type="checkbox" name="claim_device" value="1" required>
<span>Claim this frame as my own (deletes the previous owners photos and history)</span>
</label>
{% endif %}
<button type="submit" class="btn">Create account &amp; link frame</button>
{{ form_end(reg_form) }}
</div>
{# ── Login panel ───────────────────────────────────────────────────────── #}
<div id="login" class="panel {% if login_error %}active{% endif %}">
<form method="post" action="{{ path('setup_login', {mac: mac}) }}" novalidate>
{% if login_error %}
<p class="field-error" role="alert" style="margin-bottom:1rem">{{ login_error }}</p>
{% endif %}
<div class="field">
<label for="login-email">Email address</label>
<input type="email" id="login-email" name="_username" autocomplete="email" min-height="44px">
</div>
<div class="field">
<label for="login-pass">Password</label>
<input type="password" id="login-pass" name="_password" autocomplete="current-password">
</div>
{% if already_claimed %}
<label class="claim-check">
<input type="checkbox" name="claim_device" value="1" required>
<span>Claim this frame as my own (deletes the previous owners photos and history)</span>
</label>
{% endif %}
<button type="submit" class="btn">Sign in &amp; link frame</button>
</form>
</div>
</div>
<script>
(function () {
var tabs = document.querySelectorAll('.tab');
var panels = document.querySelectorAll('.panel');
tabs.forEach(function (tab) {
tab.addEventListener('click', function (e) {
e.preventDefault();
var target = tab.dataset.tab;
tabs.forEach(function (t) { t.classList.toggle('active', t.dataset.tab === target); });
panels.forEach(function (p) { p.classList.toggle('active', p.id === target); });
});
});
}());
</script>
</body>
</html>