Redesign gradient control as ASCII diverging-fill scale
Replace the click-anywhere ASCII bar (whose hit-mapping was offset from the displayed marker, so taps registered the wrong value) with a track of nine discrete, exact tap targets that fill from the center outward to the selection: [----==#--]. Direction and distance from center read at a glance. - Exact per-position selection on touch and desktop; no more position math - Keyboard support (arrows/Home/End), scoped so it doesn't trigger the app-level screen navigation - Center the bar and slim mobile padding so the full track fits narrow viewports - Apply the same control to the manual analysis gradient; render the automated ones as a matching read-only, dimmed display - Add a subtle gray "Value: #" indicator below the buttons on the diagnostic gradients, matching the analysis screens - Move the "auto-calculated" note under the ANALYSIS header instead of after the value Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -569,6 +569,10 @@
|
||||
<div class="focused-screen gradient-screen">
|
||||
<div class="screen-category">ANALYSIS</div>
|
||||
|
||||
{#if screen.gradient.automated}
|
||||
<div class="analysis-auto-note">auto-calculated</div>
|
||||
{/if}
|
||||
|
||||
{#if isFirstAnalysisScreen}
|
||||
<AnalysisTransitionBanner
|
||||
recommendation={formRecommendation}
|
||||
@@ -820,6 +824,15 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.analysis-auto-note {
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
font-size: 0.85rem;
|
||||
font-style: italic;
|
||||
margin-top: -1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.gradient-focused {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
|
||||
@@ -11,8 +11,39 @@
|
||||
notes: string;
|
||||
}>();
|
||||
|
||||
const scaleValues = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
const CENTER = 5;
|
||||
|
||||
let showNotes = false;
|
||||
let notesText = gradient.notes || '';
|
||||
let scaleEl: HTMLDivElement;
|
||||
|
||||
// Shared with GradientSlider: the track fills from the center (=) out to
|
||||
// the chosen position (#), so distance from the middle reads at a glance.
|
||||
type CellKind = 'empty' | 'center' | 'fill' | 'marker';
|
||||
|
||||
function cellKind(cell: number, value: number | null): CellKind {
|
||||
if (value === null) return cell === CENTER ? 'center' : 'empty';
|
||||
if (cell === value) return 'marker';
|
||||
if (value > CENTER && cell >= CENTER && cell < value) return 'fill';
|
||||
if (value < CENTER && cell > value && cell <= CENTER) return 'fill';
|
||||
return 'empty';
|
||||
}
|
||||
|
||||
function cellGlyph(kind: CellKind): string {
|
||||
switch (kind) {
|
||||
case 'marker': return '#';
|
||||
case 'fill': return '=';
|
||||
case 'center': return '+';
|
||||
default: return '-';
|
||||
}
|
||||
}
|
||||
|
||||
function selectValue(value: number) {
|
||||
if (gradient.automated) return;
|
||||
dispatch('change', value);
|
||||
scaleEl?.focus();
|
||||
}
|
||||
|
||||
function handleNotApplicable() {
|
||||
dispatch('change', null);
|
||||
@@ -23,82 +54,35 @@
|
||||
showNotes = false;
|
||||
}
|
||||
|
||||
function handleScaleClick(event: MouseEvent) {
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
if (gradient.automated) return;
|
||||
|
||||
const target = event.currentTarget as HTMLElement;
|
||||
const rect = target.getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const width = rect.width;
|
||||
const current = gradient.value;
|
||||
let next: number | null = null;
|
||||
|
||||
// Account for button padding to get actual text area
|
||||
const styles = window.getComputedStyle(target);
|
||||
const paddingLeft = parseFloat(styles.paddingLeft);
|
||||
const paddingRight = parseFloat(styles.paddingRight);
|
||||
|
||||
const contentWidth = width - paddingLeft - paddingRight;
|
||||
const xInContent = x - paddingLeft;
|
||||
|
||||
// Clamp to content area and get ratio
|
||||
const ratio = Math.max(0, Math.min(1, xInContent / contentWidth));
|
||||
|
||||
// Map to 1-9 with proper centering on each character position
|
||||
const value = Math.min(9, Math.max(1, Math.floor(ratio * 9) + 1));
|
||||
|
||||
dispatch('change', value);
|
||||
}
|
||||
|
||||
function handleScaleTouch(event: TouchEvent) {
|
||||
if (gradient.automated) return;
|
||||
if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
|
||||
next = current === null ? CENTER : Math.max(1, current - 1);
|
||||
} else if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
|
||||
next = current === null ? CENTER : Math.min(9, current + 1);
|
||||
} else if (event.key === 'Home') {
|
||||
next = 1;
|
||||
} else if (event.key === 'End') {
|
||||
next = 9;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const target = event.currentTarget as HTMLElement;
|
||||
const rect = target.getBoundingClientRect();
|
||||
const touch = event.touches[0];
|
||||
const x = touch.clientX - rect.left;
|
||||
const width = rect.width;
|
||||
|
||||
// Account for button padding to get actual text area
|
||||
const styles = window.getComputedStyle(target);
|
||||
const paddingLeft = parseFloat(styles.paddingLeft);
|
||||
const paddingRight = parseFloat(styles.paddingRight);
|
||||
|
||||
const contentWidth = width - paddingLeft - paddingRight;
|
||||
const xInContent = x - paddingLeft;
|
||||
|
||||
// Clamp to content area and get ratio
|
||||
const ratio = Math.max(0, Math.min(1, xInContent / contentWidth));
|
||||
|
||||
// Map to 1-9 with proper centering on each character position
|
||||
const value = Math.min(9, Math.max(1, Math.floor(ratio * 9) + 1));
|
||||
|
||||
dispatch('change', value);
|
||||
event.stopPropagation();
|
||||
dispatch('change', next);
|
||||
}
|
||||
|
||||
function renderBar(value: number | null): string {
|
||||
// Fixed scale with 9 positions using ||||#||||
|
||||
if (value === null) {
|
||||
return '||||+||||';
|
||||
}
|
||||
// Value is 1-9, position the # marker at the right spot
|
||||
const positions = [
|
||||
'#||||||||',
|
||||
'|#|||||||',
|
||||
'||#||||||',
|
||||
'|||#|||||',
|
||||
'||||#||||',
|
||||
'|||||#|||',
|
||||
'||||||#||',
|
||||
'|||||||#|',
|
||||
'||||||||#'
|
||||
];
|
||||
return positions[value - 1];
|
||||
}
|
||||
|
||||
$: barDisplay = renderBar(gradient.value);
|
||||
</script>
|
||||
|
||||
<div class="analysis-gradient">
|
||||
{#if gradient.automated && !focusedMode}
|
||||
<div class="auto-note">auto-calculated</div>
|
||||
{/if}
|
||||
|
||||
<div class="gradient-row" class:focused={focusedMode}>
|
||||
{#if !focusedMode}
|
||||
<div class="term left">
|
||||
@@ -109,22 +93,53 @@
|
||||
{/if}
|
||||
|
||||
<div class="bar-container">
|
||||
<button
|
||||
class="bar"
|
||||
class:interactive={!gradient.automated}
|
||||
class:automated={gradient.automated}
|
||||
on:click={handleScaleClick}
|
||||
on:touchstart={handleScaleTouch}
|
||||
role={gradient.automated ? 'img' : 'slider'}
|
||||
aria-label={gradient.automated ? `Analysis showing value ${gradient.value || 'not calculated'}` : `Gradient scale between ${gradient.term_left} and ${gradient.term_right}`}
|
||||
aria-valuemin={gradient.automated ? undefined : 1}
|
||||
aria-valuemax={gradient.automated ? undefined : 9}
|
||||
aria-valuenow={gradient.automated ? undefined : gradient.value}
|
||||
title={gradient.automated ? 'Auto-calculated' : 'Click or tap on the scale to set value'}
|
||||
disabled={gradient.automated}
|
||||
>
|
||||
{barDisplay}
|
||||
</button>
|
||||
{#if gradient.automated}
|
||||
<!-- Read-only display of the auto-calculated value -->
|
||||
<div
|
||||
class="scale automated"
|
||||
role="img"
|
||||
aria-label="Analysis showing value {gradient.value ?? 'not calculated'}"
|
||||
title="Auto-calculated"
|
||||
>
|
||||
<span class="bracket" aria-hidden="true">[</span>
|
||||
{#each scaleValues as n}
|
||||
{@const kind = cellKind(n, gradient.value)}
|
||||
<span class="cell {kind}">{cellGlyph(kind)}</span>
|
||||
{/each}
|
||||
<span class="bracket" aria-hidden="true">]</span>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Interactive scale (same control as the diagnostic gradients) -->
|
||||
<div
|
||||
class="scale"
|
||||
bind:this={scaleEl}
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-label="Gradient scale between {gradient.term_left} and {gradient.term_right}"
|
||||
aria-valuemin="1"
|
||||
aria-valuemax="9"
|
||||
aria-valuenow={gradient.value}
|
||||
aria-valuetext={gradient.value === null ? 'Not set' : String(gradient.value)}
|
||||
on:keydown={handleKeydown}
|
||||
>
|
||||
<span class="bracket" aria-hidden="true">[</span>
|
||||
{#each scaleValues as n}
|
||||
{@const kind = cellKind(n, gradient.value)}
|
||||
<button
|
||||
type="button"
|
||||
class="cell {kind}"
|
||||
tabindex="-1"
|
||||
aria-label="Set value {n}"
|
||||
aria-pressed={gradient.value === n}
|
||||
title="{n}"
|
||||
on:click={() => selectValue(n)}
|
||||
>
|
||||
{cellGlyph(kind)}
|
||||
</button>
|
||||
{/each}
|
||||
<span class="bracket" aria-hidden="true">]</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if !focusedMode}
|
||||
@@ -136,13 +151,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="value-display">
|
||||
Value: {gradient.value !== null ? gradient.value : ''}
|
||||
{#if gradient.automated}
|
||||
<span class="auto-label">(auto-calculated)</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
{#if !gradient.automated}
|
||||
<div class="button-row">
|
||||
@@ -176,6 +184,10 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="value-display">
|
||||
Value: {gradient.value !== null ? gradient.value : ''}
|
||||
</div>
|
||||
|
||||
{#if showNotes}
|
||||
<div class="notes-editor">
|
||||
<textarea
|
||||
@@ -210,15 +222,6 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gradient-row.focused .bar-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gradient-row.focused .bar {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.term {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
@@ -232,59 +235,99 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* The bar is always centered within its container, on every screen size. */
|
||||
.bar-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bar {
|
||||
.scale {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 1.8rem;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.2rem;
|
||||
padding: 0.5rem 1rem;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
min-width: 240px;
|
||||
text-align: center;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--fg-color);
|
||||
border: 2px solid var(--border-color);
|
||||
padding: 0.25rem 0.4rem;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.bar.automated {
|
||||
cursor: default;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.bar.interactive {
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.bar.interactive:hover {
|
||||
.scale:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--hover-color);
|
||||
}
|
||||
|
||||
.bar.interactive:active {
|
||||
background-color: var(--input-bg);
|
||||
border-color: var(--fg-color);
|
||||
.scale.automated {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.bracket {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.cell {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: 1;
|
||||
background: transparent;
|
||||
color: var(--fg-color);
|
||||
border: none;
|
||||
padding: 0.35rem 0.3rem;
|
||||
min-width: auto;
|
||||
min-height: auto;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
/* Read-only cells in the automated display are not buttons */
|
||||
span.cell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
/* Opacity tiers reinforce the gradient feel: faint track, brighter
|
||||
fill, fully solid marker. */
|
||||
.cell.empty {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.cell.center {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.cell.fill {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.cell.marker {
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button.cell:hover {
|
||||
opacity: 1;
|
||||
color: var(--hover-color);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.value-display {
|
||||
text-align: center;
|
||||
margin: 0.5rem 0;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.analysis-gradient .value-display {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.auto-label {
|
||||
opacity: 0.6;
|
||||
font-size: 0.8rem;
|
||||
.auto-note {
|
||||
text-align: center;
|
||||
color: #888888;
|
||||
font-size: 0.85rem;
|
||||
font-style: italic;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
@@ -383,10 +426,15 @@
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
.bar {
|
||||
font-size: 1.4rem;
|
||||
min-width: 200px;
|
||||
padding: 0.5rem;
|
||||
.scale {
|
||||
font-size: 1.5rem;
|
||||
padding: 0.25rem 0.2rem;
|
||||
}
|
||||
|
||||
/* Slim horizontal padding keeps the full track inside narrow
|
||||
viewports; vertical padding preserves a tall tap target. */
|
||||
.cell {
|
||||
padding: 0.5rem 0.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,8 +11,40 @@
|
||||
notes: string;
|
||||
}>();
|
||||
|
||||
const scaleValues = [1, 2, 3, 4, 5, 6, 7, 8, 9];
|
||||
const CENTER = 5;
|
||||
|
||||
let showNotes = false;
|
||||
let notesText = gradient.notes || '';
|
||||
let scaleEl: HTMLDivElement;
|
||||
|
||||
// Glyph shown in each cell, given the current value.
|
||||
// The track fills from the center (=) out to the chosen position (#),
|
||||
// so direction and distance from the middle read at a glance.
|
||||
type CellKind = 'empty' | 'center' | 'fill' | 'marker';
|
||||
|
||||
function cellKind(cell: number, value: number | null): CellKind {
|
||||
if (value === null) return cell === CENTER ? 'center' : 'empty';
|
||||
if (cell === value) return 'marker';
|
||||
if (value > CENTER && cell >= CENTER && cell < value) return 'fill';
|
||||
if (value < CENTER && cell > value && cell <= CENTER) return 'fill';
|
||||
return 'empty';
|
||||
}
|
||||
|
||||
function cellGlyph(kind: CellKind): string {
|
||||
switch (kind) {
|
||||
case 'marker': return '#';
|
||||
case 'fill': return '=';
|
||||
case 'center': return '+';
|
||||
default: return '-';
|
||||
}
|
||||
}
|
||||
|
||||
function selectValue(value: number) {
|
||||
dispatch('change', value);
|
||||
// Keep focus on the scale so arrow keys continue to adjust the value
|
||||
scaleEl?.focus();
|
||||
}
|
||||
|
||||
function handleNotApplicable() {
|
||||
dispatch('change', null);
|
||||
@@ -23,75 +55,28 @@
|
||||
showNotes = false;
|
||||
}
|
||||
|
||||
function handleScaleClick(event: MouseEvent) {
|
||||
const target = event.currentTarget as HTMLElement;
|
||||
const rect = target.getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const width = rect.width;
|
||||
function handleKeydown(event: KeyboardEvent) {
|
||||
const current = gradient.value;
|
||||
let next: number | null = null;
|
||||
|
||||
// Account for button padding to get actual text area
|
||||
const styles = window.getComputedStyle(target);
|
||||
const paddingLeft = parseFloat(styles.paddingLeft);
|
||||
const paddingRight = parseFloat(styles.paddingRight);
|
||||
|
||||
const contentWidth = width - paddingLeft - paddingRight;
|
||||
const xInContent = x - paddingLeft;
|
||||
|
||||
// Clamp to content area and get ratio
|
||||
const ratio = Math.max(0, Math.min(1, xInContent / contentWidth));
|
||||
|
||||
// Map to 1-9 with proper centering on each character position
|
||||
const value = Math.min(9, Math.max(1, Math.floor(ratio * 9) + 1));
|
||||
|
||||
dispatch('change', value);
|
||||
}
|
||||
|
||||
function handleScaleTouch(event: TouchEvent) {
|
||||
event.preventDefault();
|
||||
const target = event.currentTarget as HTMLElement;
|
||||
const rect = target.getBoundingClientRect();
|
||||
const touch = event.touches[0];
|
||||
const x = touch.clientX - rect.left;
|
||||
const width = rect.width;
|
||||
|
||||
// Account for button padding to get actual text area
|
||||
const styles = window.getComputedStyle(target);
|
||||
const paddingLeft = parseFloat(styles.paddingLeft);
|
||||
const paddingRight = parseFloat(styles.paddingRight);
|
||||
|
||||
const contentWidth = width - paddingLeft - paddingRight;
|
||||
const xInContent = x - paddingLeft;
|
||||
|
||||
// Clamp to content area and get ratio
|
||||
const ratio = Math.max(0, Math.min(1, xInContent / contentWidth));
|
||||
|
||||
// Map to 1-9 with proper centering on each character position
|
||||
const value = Math.min(9, Math.max(1, Math.floor(ratio * 9) + 1));
|
||||
|
||||
dispatch('change', value);
|
||||
}
|
||||
|
||||
function renderBar(value: number | null): string {
|
||||
// Slider-style visualization with brackets and value number
|
||||
if (value === null) {
|
||||
return '[----+----]';
|
||||
if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') {
|
||||
next = current === null ? CENTER : Math.max(1, current - 1);
|
||||
} else if (event.key === 'ArrowRight' || event.key === 'ArrowUp') {
|
||||
next = current === null ? CENTER : Math.min(9, current + 1);
|
||||
} else if (event.key === 'Home') {
|
||||
next = 1;
|
||||
} else if (event.key === 'End') {
|
||||
next = 9;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
// Value is 1-9, show the number at its position along the slider
|
||||
const bars = [
|
||||
'[1--------]', // value 1
|
||||
'[-2-------]', // value 2
|
||||
'[--3------]', // value 3
|
||||
'[---4-----]', // value 4
|
||||
'[----5----]', // value 5 (centered)
|
||||
'[-----6---]', // value 6
|
||||
'[------7--]', // value 7
|
||||
'[-------8-]', // value 8
|
||||
'[--------9]' // value 9
|
||||
];
|
||||
return bars[value - 1];
|
||||
}
|
||||
|
||||
$: barDisplay = renderBar(gradient.value);
|
||||
// Handle the key here and keep it from bubbling to the app-level
|
||||
// screen navigation (which also listens for arrow keys).
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
dispatch('change', next);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="gradient-container">
|
||||
@@ -105,19 +90,35 @@
|
||||
{/if}
|
||||
|
||||
<div class="bar-container">
|
||||
<button
|
||||
class="bar"
|
||||
on:click={handleScaleClick}
|
||||
on:touchstart={handleScaleTouch}
|
||||
<div
|
||||
class="scale"
|
||||
bind:this={scaleEl}
|
||||
role="slider"
|
||||
tabindex="0"
|
||||
aria-label="Gradient scale between {gradient.term_left} and {gradient.term_right}"
|
||||
aria-valuemin="1"
|
||||
aria-valuemax="9"
|
||||
aria-valuenow={gradient.value}
|
||||
title="Click or tap on the scale to set value"
|
||||
aria-valuetext={gradient.value === null ? 'Not set' : String(gradient.value)}
|
||||
on:keydown={handleKeydown}
|
||||
>
|
||||
{barDisplay}
|
||||
</button>
|
||||
<span class="bracket" aria-hidden="true">[</span>
|
||||
{#each scaleValues as n}
|
||||
{@const kind = cellKind(n, gradient.value)}
|
||||
<button
|
||||
type="button"
|
||||
class="cell {kind}"
|
||||
tabindex="-1"
|
||||
aria-label="Set value {n}"
|
||||
aria-pressed={gradient.value === n}
|
||||
title="{n}"
|
||||
on:click={() => selectValue(n)}
|
||||
>
|
||||
{cellGlyph(kind)}
|
||||
</button>
|
||||
{/each}
|
||||
<span class="bracket" aria-hidden="true">]</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if !focusedMode}
|
||||
@@ -151,6 +152,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="value-display">
|
||||
Value: {gradient.value !== null ? gradient.value : ''}
|
||||
</div>
|
||||
|
||||
{#if showNotes}
|
||||
<div class="notes-editor">
|
||||
<textarea
|
||||
@@ -185,15 +190,6 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.gradient-row.focused .bar-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.gradient-row.focused .bar {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.term {
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
@@ -207,37 +203,79 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* The bar is always centered within its container, on every screen size. */
|
||||
.bar-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bar {
|
||||
.scale {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
font-size: 1.8rem;
|
||||
white-space: nowrap;
|
||||
letter-spacing: 0.2rem;
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
min-width: 240px;
|
||||
text-align: center;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--fg-color);
|
||||
border: 2px solid var(--border-color);
|
||||
transition: border-color 0.2s;
|
||||
padding: 0.25rem 0.4rem;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.bar:hover {
|
||||
.scale:focus-visible {
|
||||
outline: none;
|
||||
border-color: var(--hover-color);
|
||||
background-color: var(--bg-color);
|
||||
color: var(--fg-color);
|
||||
}
|
||||
|
||||
.bar:active {
|
||||
background-color: var(--input-bg);
|
||||
border-color: var(--fg-color);
|
||||
.bracket {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.cell {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: 1;
|
||||
background: transparent;
|
||||
color: var(--fg-color);
|
||||
border: none;
|
||||
padding: 0.35rem 0.3rem;
|
||||
min-width: auto;
|
||||
min-height: auto;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
/* Opacity tiers reinforce the gradient feel: faint track, brighter
|
||||
fill, fully solid marker. */
|
||||
.cell.empty {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.cell.center {
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.cell.fill {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.cell.marker {
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.cell:hover {
|
||||
opacity: 1;
|
||||
color: var(--hover-color);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.value-display {
|
||||
text-align: center;
|
||||
margin-top: 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
color: #888888;
|
||||
}
|
||||
|
||||
.controls {
|
||||
@@ -336,10 +374,15 @@
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
.bar {
|
||||
font-size: 1.4rem;
|
||||
min-width: 200px;
|
||||
padding: 0.5rem;
|
||||
.scale {
|
||||
font-size: 1.5rem;
|
||||
padding: 0.25rem 0.2rem;
|
||||
}
|
||||
|
||||
/* Slim horizontal padding keeps the full track inside narrow
|
||||
viewports; vertical padding preserves a tall tap target. */
|
||||
.cell {
|
||||
padding: 0.5rem 0.2rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user