feat: budget total row + timeline polish + paint slider thumb + auto-advance
Budget panel - Removed the inner "Budget Allocation" subtitle and the inline total from bm-title — the outer panel header "Budget Plan" already names the section, the inner subtitle was redundant. - Added a Total Allocation row at the end of the breakdown using the same .bcat layout as the category lines so label + amount columns align. Top divider + heavier weight mark it as the summary line. DE: Gesamtbudget. The bar slot is collapsed to height:0 on the total row so only label + amount render. - Bumped .bm-title (when it existed) and .bcat-label / .bcat-cost so the budget figures read at a comfortable size. Timeline - Each .tl-item gets generous top + bottom padding (14px / 28px) so the steps read as distinct moments. Padding is consistent across every item so the connector positioning stays stable. - Connector line is now a light dotted left-border drawn on a zero-width pseudo, anchored top:46px / bottom:-14px so the dotted line crosses the gap between items and reaches each next dot. - .tl-when (step title) typeface switched from Space Mono to Barlow (matching the CTA buttons), weight 600, 0.16em tracking, dark green paint colour (#2a3010), vertically centred against the 32px dot via min-height + flex centring so the title sits at the dot's mid-line. Quiz - Slider thumb is now a CSS-painted dark green circle: linear gradient (#3a4220 → #2a3010 → #161a08) with an inset cream highlight and a soft drop shadow + halo ring, approximating the SVG paint gloss filter the CTA buttons use. Both -webkit and -moz pseudo elements styled. - Single-choice questions now auto-advance 220ms after a tap so the Continue button is no longer needed on those steps. renderQuestion drops the Continue button on single-choice, keeps Back when not on the first question. .quiz-footer:empty hides cleanly on the very first single-choice screen. Result panels - .result-panel-body top padding bumped from 0 to 20px so expanded content has clear breathing room below the summary header. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
34
src/App.vue
34
src/App.vue
@@ -1096,12 +1096,15 @@ function renderQuestion() {
|
||||
html += `</div>`
|
||||
|
||||
const isLast = currentQ === QUESTIONS.length - 1
|
||||
const nav = `<div class="q-nav">
|
||||
${currentQ > 0 ? `<button class="btn-back" onclick="prevQ()">${t('back_btn')}</button>` : ''}
|
||||
<button class="btn-next" id="btn-next" onclick="nextQ()" ${canProceed() ? '' : 'disabled'}>
|
||||
${isLast ? t('finish_btn') : t('continue_btn')}
|
||||
</button>
|
||||
</div>`
|
||||
// Single-choice steps auto-advance on tap, so the Continue button is
|
||||
// redundant — render only Back (if available) on those screens. Multi
|
||||
// and slider steps still need the explicit Continue.
|
||||
const showContinue = q.type !== 'single'
|
||||
const showBack = currentQ > 0
|
||||
const parts = []
|
||||
if (showBack) parts.push(`<button class="btn-back" onclick="prevQ()">${t('back_btn')}</button>`)
|
||||
if (showContinue) parts.push(`<button class="btn-next" id="btn-next" onclick="nextQ()" ${canProceed() ? '' : 'disabled'}>${isLast ? t('finish_btn') : t('continue_btn')}</button>`)
|
||||
const nav = parts.length ? `<div class="q-nav">${parts.join('')}</div>` : ''
|
||||
|
||||
document.getElementById('question-container').innerHTML = html
|
||||
document.getElementById('quiz-footer').innerHTML = nav
|
||||
@@ -1117,8 +1120,16 @@ function selectOption(qid, val, el) {
|
||||
answers[qid] = val
|
||||
document.querySelectorAll('.q-opt').forEach(o => o.classList.remove('selected'))
|
||||
el.classList.add('selected')
|
||||
document.getElementById('btn-next').disabled = false
|
||||
const nextBtn = document.getElementById('btn-next')
|
||||
if (nextBtn) nextBtn.disabled = false
|
||||
saveState()
|
||||
// Single-choice → auto-advance. Short delay so the user sees the green
|
||||
// selected state register before the screen flips. They can always go
|
||||
// Back to revise — the answer is preserved in `answers[qid]` and
|
||||
// re-applied as `selected` when renderQuestion runs again.
|
||||
setTimeout(() => {
|
||||
if (answers[qid] === val) nextQ()
|
||||
}, 220)
|
||||
}
|
||||
|
||||
function toggleMulti(qid, val, el) {
|
||||
@@ -1438,7 +1449,6 @@ function renderBudgetMeter() {
|
||||
{ label: t('bcat_sanitation'), pct: 5, color: '#D4A820' },
|
||||
]
|
||||
let html = `<div class="budget-meter">
|
||||
<div class="bm-title">${t('budget_title')} — ${lang==='de'?'Gesamt':'Total'}: €${budget.toLocaleString()}</div>
|
||||
<div class="budget-cats">`
|
||||
cats.forEach(c => {
|
||||
html += `<div class="bcat">
|
||||
@@ -1447,6 +1457,14 @@ function renderBudgetMeter() {
|
||||
<div class="bcat-cost">€${Math.round(budget*c.pct/100)}</div>
|
||||
</div>`
|
||||
})
|
||||
// Total row at the end of the table — same .bcat layout so alignment
|
||||
// matches the per-category lines.
|
||||
const totalLabel = lang === 'de' ? 'Gesamtbudget' : 'Total Allocation'
|
||||
html += `<div class="bcat bcat-total">
|
||||
<div class="bcat-label">${totalLabel}</div>
|
||||
<div class="bcat-bar-wrap" aria-hidden="true"></div>
|
||||
<div class="bcat-cost">€${budget.toLocaleString()}</div>
|
||||
</div>`
|
||||
html += `</div></div>`
|
||||
document.getElementById('budget-meter-container').innerHTML = html
|
||||
}
|
||||
|
||||
@@ -482,7 +482,33 @@ body.quiz-active .site-header { display: none; }
|
||||
.slider-labels { display: flex; justify-content: space-between; margin-bottom: 12px; }
|
||||
.slider-labels span { font-size: 12px; color: var(--text-dim); font-family: var(--font-mono); }
|
||||
input[type=range] { width: 100%; -webkit-appearance: none; appearance: none; height: 4px; background: var(--muted); border-radius: 99px; outline: none; cursor: pointer; }
|
||||
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px; height: 22px; border-radius: 50%; background: var(--red); cursor: pointer; box-shadow: 0 2px 16px rgba(90,154,120,0.3); }
|
||||
/* Slider thumb — dark green paint style. CSS-only approximation of the
|
||||
SVG paintGlossBtn filter the CTA buttons use: a #2a3010 base with a
|
||||
linear gradient simulating the embossed top-highlight / bottom-shadow,
|
||||
plus an inset top stroke and a soft drop shadow. */
|
||||
input[type=range]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none;
|
||||
width: 22px; height: 22px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(180deg, #3a4220 0%, #2a3010 55%, #161a08 100%);
|
||||
border: 1px solid rgba(0,0,0,0.40);
|
||||
cursor: pointer;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255,236,200,0.22),
|
||||
0 2px 6px rgba(0,0,0,0.30),
|
||||
0 0 0 4px rgba(42,48,16,0.10);
|
||||
}
|
||||
input[type=range]::-moz-range-thumb {
|
||||
width: 22px; height: 22px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(180deg, #3a4220 0%, #2a3010 55%, #161a08 100%);
|
||||
border: 1px solid rgba(0,0,0,0.40);
|
||||
cursor: pointer;
|
||||
box-shadow:
|
||||
inset 0 1px 0 rgba(255,236,200,0.22),
|
||||
0 2px 6px rgba(0,0,0,0.30),
|
||||
0 0 0 4px rgba(42,48,16,0.10);
|
||||
}
|
||||
.slider-val { text-align: center; font-family: var(--font-display); font-weight: 800; font-size: 32px; color: var(--white); margin-top: 16px; }
|
||||
.slider-val span { font-size: 18px; color: var(--text-dim); margin-left: 4px; }
|
||||
|
||||
@@ -703,22 +729,60 @@ input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px;
|
||||
itself is stripped to transparent in the panel-body override (line ~615),
|
||||
so only the inner element colours need adjusting here. */
|
||||
.budget-meter { background: var(--panel); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px; margin-bottom: 20px; animation: fadeUp 0.4s 0.2s ease both; }
|
||||
.bm-title { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--text); margin-bottom: 16px; }
|
||||
/* Inner budget title matches the panel header typography (.rp-title:
|
||||
var(--font-body), 700, 0.02em tracking) but a touch bigger so the
|
||||
total reads as the primary stat inside the panel. */
|
||||
.bm-title {
|
||||
font-family: var(--font-body);
|
||||
font-weight: 700;
|
||||
font-size: 19px;
|
||||
letter-spacing: 0.02em;
|
||||
line-height: 1.3;
|
||||
color: var(--text);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.budget-cats { display: flex; flex-direction: column; gap: 10px; }
|
||||
.bcat { display: flex; align-items: center; gap: 10px; }
|
||||
.bcat-label { font-size: 13px; color: var(--text-dim); width: 100px; flex-shrink: 0; }
|
||||
.bcat-label { font-size: 16px; color: var(--text-dim); width: 110px; flex-shrink: 0; }
|
||||
.bcat-bar-wrap { flex: 1; height: 6px; background: rgba(0,0,0,0.08); border-radius: 99px; overflow: hidden; }
|
||||
.bcat-bar { height: 100%; border-radius: 99px; transition: width 0.8s cubic-bezier(0.4,0,0.2,1); }
|
||||
.bcat-cost { font-family: var(--font-mono); font-size: 12px; color: var(--text); width: 60px; text-align: right; flex-shrink: 0; }
|
||||
.bcat-cost { font-family: var(--font-mono); font-size: 15px; color: var(--text); width: 72px; text-align: right; flex-shrink: 0; }
|
||||
/* Total row — sums the breakdown above. Same .bcat layout so the label
|
||||
and amount align with the category rows, plus a top divider and a
|
||||
touch more weight to set it apart as the summary line. */
|
||||
.bcat-total {
|
||||
margin-top: 6px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid rgba(0,0,0,0.08);
|
||||
}
|
||||
.bcat-total .bcat-label { color: var(--text); font-weight: 600; }
|
||||
.bcat-total .bcat-cost { color: var(--text); font-weight: 700; }
|
||||
.bcat-total .bcat-bar-wrap { background: transparent; height: 0; }
|
||||
|
||||
/* Timeline — same dark-on-light flip. Connector line uses a subtle dark
|
||||
border colour so it reads on the white-paint panel. */
|
||||
.timeline { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px; margin-bottom: 20px; animation: fadeUp 0.4s 0.25s ease both; }
|
||||
.tl-title { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--text); margin-bottom: 16px; }
|
||||
.tl-items { display: flex; flex-direction: column; gap: 0; }
|
||||
.tl-item { display: flex; gap: 16px; padding-bottom: 16px; position: relative; }
|
||||
.tl-item:last-child { padding-bottom: 0; }
|
||||
.tl-item:not(:last-child)::before { content: ''; position: absolute; left: 15px; top: 32px; bottom: 0; width: 2px; background: rgba(0,0,0,0.10); }
|
||||
/* Each step gets generous breathing room top + bottom so the timeline
|
||||
reads as a sequence of distinct moments rather than a tight list.
|
||||
Padding is consistent across every item (no first/last overrides) so
|
||||
the connector line positioning is stable from step to step. */
|
||||
.tl-item { display: flex; gap: 16px; padding: 14px 0 28px; position: relative; }
|
||||
.tl-item:last-child { padding-bottom: 4px; }
|
||||
/* Connector line — light dotted column drawn as a dotted left border on a
|
||||
zero-width pseudo. Top starts 46px from item-top (just under a 32px dot
|
||||
sat on 14px padding) and bottom extends -14px past the item so it
|
||||
reaches the next item's dot at its 14px padding offset. */
|
||||
.tl-item:not(:last-child)::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
top: 46px;
|
||||
bottom: -14px;
|
||||
width: 0;
|
||||
border-left: 2px dotted rgba(0,0,0,0.18);
|
||||
}
|
||||
/* Timeline dot — green-paint circle that matches the rec-icon chip but
|
||||
round. Same painted ::before with a slightly larger size so the icon
|
||||
inside reads at small viewport sizes. */
|
||||
@@ -742,7 +806,22 @@ input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px;
|
||||
filter: url(#paintGlossBtn);
|
||||
-webkit-filter: url(#paintGlossBtn);
|
||||
}
|
||||
.tl-when { font-family: var(--font-mono); font-size:14px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-dim); margin-bottom: 4px; }
|
||||
/* Step title — vertically centred against the 32px dot using min-height
|
||||
+ flex centring so the label sits at the dot's mid-line rather than
|
||||
its top edge. Dark-green-paint colour so the title matches the rest
|
||||
of the brand-paint elements. */
|
||||
.tl-when {
|
||||
font-family: var(--font-body);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
letter-spacing: 0.16em;
|
||||
text-transform: uppercase;
|
||||
color: #2a3010;
|
||||
margin-bottom: 6px;
|
||||
min-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.tl-action { font-size:16px; color: var(--text); line-height: 1.5; }
|
||||
.tl-cost { font-family: var(--font-mono); font-size:15px; color: var(--green-bright); margin-top: 4px; }
|
||||
|
||||
@@ -864,7 +943,9 @@ input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px;
|
||||
}
|
||||
.result-panel[open] .rp-chevron { transform: rotate(180deg); }
|
||||
.result-panel-body {
|
||||
padding: 0 18px 18px;
|
||||
/* Generous top padding so the expanded content has clear breathing
|
||||
room beneath the panel header (was 0 → 20px). */
|
||||
padding: 20px 18px 18px;
|
||||
}
|
||||
/* Strip box treatment from inner cards when wrapped in a panel — the panel
|
||||
is now the visible container. */
|
||||
|
||||
Reference in New Issue
Block a user