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:
Dorian
2026-05-10 10:39:08 +01:00
parent 302c8aae83
commit 78cfb14a77
2 changed files with 116 additions and 17 deletions

View File

@@ -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
}

View File

@@ -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. */