Guess The Number
A scoped sv-ui-state combines derived and persistent stores with bind-* controls.
The bindings are intentionally complex to stress the expression parser. Keep production expressions simple.
Guess My Number 1 to 100
Set the slider, then press Guess Higher — you guessed Lower — you guessed
tries
remaining
best
Code
The following example assumes that you have imported the component and set up the theme.
<div class="guess-number-game"> <sv-ui-state id="guess-number-game-state"> <sv-store type="number" name="$gameSeed" value="0"></sv-store> <sv-store type="number" name="$currentGuessInput" value="50"></sv-store> <sv-store type="number" name="$lastGuess" value="0"></sv-store> <sv-store type="number" name="$attemptCount" value="0"></sv-store> <sv-store type="number" name="$minimumPossibleValue" value="1"></sv-store> <sv-store type="number" name="$maximumPossibleValue" value="100"></sv-store> <sv-store type="number" name="$bestScore" persistent value="0"></sv-store>
<sv-store derive="(Date.now() % 100000 + $gameSeed * 37) % 100 + 1" name="$targetNumber"></sv-store> <sv-store derive="$lastGuess > 0 && $lastGuess === $targetNumber" name="$hasWon"></sv-store>
<div class="guess-number-game-header"> <span class="guess-number-game-title">Guess My Number</span> <span class="guess-number-game-subtitle">1 to 100</span> </div>
<div class="guess-number-game-feedback" bind-class="'guess-number-game-feedback-' + ($attemptCount === 0 ? 'idle' : ($hasWon ? 'success' : ($lastGuess < $targetNumber ? 'higher' : 'lower')))" > <span bind-show="$attemptCount === 0">Set the slider, then press Guess</span>
<span bind-show="$attemptCount > 0 && !$hasWon && $lastGuess < $targetNumber"> Higher — you guessed <b bind-text="$lastGuess"></b> </span>
<span bind-show="$attemptCount > 0 && !$hasWon && $lastGuess > $targetNumber"> Lower — you guessed <b bind-text="$lastGuess"></b> </span>
<span bind-show="$hasWon" bind-text="'Got it in ' + $attemptCount + ($attemptCount === 1 ? ' try' : ' tries') + '!'"></span> </div>
<div class="guess-number-game-picker"> <span class="guess-number-game-current-value" bind-text="$currentGuessInput"></span>
<input type="range" class="guess-number-game-slider" aria-label="Pick a number" bind-attr="min:$minimumPossibleValue; max:$maximumPossibleValue" bind-attr-toggle="disabled:$hasWon" bind-event="input:$currentGuessInput(Number(EVENT.target.value))" bind-prop="value:$currentGuessInput" />
<div class="guess-number-game-range-bar" bind-style=" --range-start:($minimumPossibleValue - 1) / 99 * 100; --range-end:($maximumPossibleValue - 1) / 99 * 100 " > <div class="guess-number-game-range-bar-fill"></div> </div>
<div class="guess-number-game-range-labels"> <span bind-text="$minimumPossibleValue"></span> <span bind-text="$maximumPossibleValue"></span> </div> </div>
<div class="guess-number-game-actions"> <button type="button" class="guess-number-game-guess-button" bind-attr-toggle="disabled:$hasWon" bind-event=" click: $lastGuess($currentGuessInput), $attemptCount($attemptCount + 1), $lastGuess < $targetNumber ? $minimumPossibleValue($lastGuess + 1) : ( $lastGuess > $targetNumber ? $maximumPossibleValue($lastGuess - 1) : $bestScore($bestScore === 0 || $attemptCount < $bestScore ? $attemptCount: $bestScore) ), $lastGuess < $targetNumber ? $currentGuessInput($lastGuess + 1) : ($lastGuess > $targetNumber ? $currentGuessInput($lastGuess - 1) : null) " > Guess </button>
<button type="button" class="guess-number-game-new-game-button" bind-event=" click: $gameSeed($gameSeed + 1), $lastGuess(0), $attemptCount(0), $minimumPossibleValue(1), $maximumPossibleValue(100), $currentGuessInput(50) " > New game </button> </div>
<div class="guess-number-game-statistics"> <div class="guess-number-game-statistic"> <span class="guess-number-game-statistic-label">tries</span> <span class="guess-number-game-statistic-value" bind-text="$attemptCount"></span> </div>
<div class="guess-number-game-statistic"> <span class="guess-number-game-statistic-label">remaining</span> <span class="guess-number-game-statistic-value" bind-text="$maximumPossibleValue - $minimumPossibleValue + 1"></span> </div>
<div class="guess-number-game-statistic"> <span class="guess-number-game-statistic-label">best</span> <span class="guess-number-game-statistic-value" bind-text="$bestScore > 0 ? $bestScore : '—'"></span> </div> </div> </sv-ui-state></div>
206 collapsed lines
<style> .guess-number-game { max-inline-size: 400px; margin: 0 auto; font-family: var(--sb-md-font); color: var(--sb-color-text); }
.guess-number-game-header { display: flex; align-items: baseline; justify-content: space-between; padding-block-end: 1rem; margin-block-end: 0.75rem; border-block-end: 1px solid var(--sb-color-gray-6); }
.guess-number-game-title { font-size: var(--sb-text-xl); font-weight: 700; color: var(--sb-heading-color); }
.guess-number-game-subtitle { font-size: var(--sb-text-sm); color: var(--sb-color-gray-3); }
.guess-number-game-feedback { display: flex; align-items: center; min-block-size: 48px; padding: 0.5rem 1rem; margin-block-end: 0.75rem; font-size: var(--sb-text-sm); background: var(--sb-color-gray-7); border: 2px solid var(--sb-color-gray-5); border-radius: var(--sv-bdr-rad-md); transition: background 0.15s, border-color 0.15s, color 0.15s;
b { font-weight: 700; font-variant-numeric: tabular-nums; } }
.guess-number-game-feedback-higher { color: var(--sb-note-text); background: var(--sb-note-background); border-color: var(--sb-note-border); }
.guess-number-game-feedback-lower { color: var(--sb-warning-text); background: var(--sb-warning-background); border-color: var(--sb-warning-border); }
.guess-number-game-feedback-success { font-weight: 600; color: var(--sb-tip-text); background: var(--sb-tip-background); border-color: var(--sb-tip-border); }
.guess-number-game-picker { padding: 1.25rem 1rem 0.75rem; margin-block-end: 0.75rem; text-align: center; background: var(--sb-color-gray-7); border: 1px solid var(--sb-color-gray-6); border-radius: var(--sv-bdr-rad-md); }
.guess-number-game-current-value { display: block; margin-block-end: 1rem; font-size: var(--sb-text-5xl); font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1; color: var(--sb-color-accent-high); letter-spacing: -0.03em; }
.guess-number-game-slider { display: block; inline-size: 99%; accent-color: var(--sb-color-accent); cursor: pointer;
&:disabled { cursor: not-allowed; opacity: 0.35; } }
.guess-number-game-range-bar { position: relative; block-size: 6px; margin-block: 2em 0.4rem; margin-inline: 3px; overflow: hidden; background: var(--sb-color-gray-5); border-radius: 3px; }
.guess-number-game-range-bar-fill { position: absolute; inset: 0; inset-inline: calc(var(--range-start, 0) * 1%) calc((100 - var(--range-end, 100)) * 1%); background: var(--sb-color-accent); transition: left 0.15s ease, right 0.15s ease; }
.guess-number-game-range-labels { display: flex; justify-content: space-between; font-size: var(--sb-text-xs); font-variant-numeric: tabular-nums; color: var(--sb-color-gray-3); }
.guess-number-game-actions { display: grid; grid-template-columns: 1fr auto; gap: 0.5rem; margin-block-end: 0.75rem; }
.guess-number-game-guess-button, .guess-number-game-new-game-button { padding: 0.75rem 1.25rem; font-family: var(--sb-md-font); font-size: var(--sb-text-sm); font-weight: 600; letter-spacing: 0.03em; cursor: pointer; border: none; border-radius: var(--sv-bdr-rad-md); transition: opacity 0.1s, background 0.1s; }
.guess-number-game-guess-button { color: var(--sv-accent-content); background: var(--sb-color-accent);
&:disabled { cursor: default; opacity: 0.3; }
&:hover:not(:disabled) { opacity: 0.85; } }
.guess-number-game-new-game-button { color: var(--sb-color-gray-1); background: var(--sb-color-gray-6); border: 1px solid var(--sb-color-gray-5);
&:hover { background: var(--sb-color-gray-5); } }
.guess-number-game-statistics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1px; overflow: hidden; background: var(--sb-color-gray-5); border-radius: var(--sv-bdr-rad-md); }
.guess-number-game-statistic { display: flex; flex-direction: column; gap: 0.1rem; align-items: center; padding: 0.625rem 0.5rem; background: var(--sb-color-gray-7); }
.guess-number-game-statistic-label { font-size: var(--sb-text-2xs); color: var(--sb-color-gray-3); text-transform: uppercase; letter-spacing: 0.1em; }
.guess-number-game-statistic-value { font-size: var(--sb-text-xl); font-weight: 700; font-variant-numeric: tabular-nums; color: var(--sb-color-gray-1); }</style>