0489028486
CI / test (push) Has been cancelled
v2 tokens were duplicated: in design-v2.scss for the SPA, inlined in login.html.twig for Twig. Two places to keep in sync. Now: one shared /public/css/wevisto-design.css loaded by every Twig standalone template AND linked from the SPA index.html. It contains: - Brand constants (yellow / navy / fonts) - v2 tokens with per-theme dusk overrides - v2 base body bg + editorial typography defaults - v2 overrides for the .card / .btn / .field-error / .logo-badge patterns used across all Twig templates The SPA's design-v2.scss now holds only SPA-specific composition: side rail at desktop, frame card, theme swatch harbor preview, settings polish. No token duplication. Result: changing a v2 color in one file flows to every surface in both worlds. Adding v2 to another Twig template only requires the existing shared CSS link (already wired up to all 11 standalone templates). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
155 lines
8.7 KiB
Twig
155 lines
8.7 KiB
Twig
<!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">
|
||
<link rel="stylesheet" href="/css/wevisto-design.css">
|
||
<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 you’re taking it over, tick the box below — the previous
|
||
owner’s 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 owner’s photos and history)</span>
|
||
</label>
|
||
{% endif %}
|
||
<button type="submit" class="btn">Create account & 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 owner’s photos and history)</span>
|
||
</label>
|
||
{% endif %}
|
||
<button type="submit" class="btn">Sign in & 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>
|