Files
dickloads/src/routes/+page.svelte
Falkan 7ca0fc4032 highlight: inline equation layout with responsive stacked fallback
- From and result now on one line: '1.00 Units = 0.09 OtherUnits'
- Same font size and color for both sides (pico-color, 600 weight)
- Container query switches to over-under at <420px with '= ' prefix on result line
- Separator '=' shown inline; muted color/normal weight to visually anchor the equation
- Removed old two-tier font sizing (small grey from, large blue result)
2026-03-17 10:47:27 -04:00

138 lines
5.1 KiB
Svelte
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script lang="ts">
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
import { page } from '$app/state';
import { florpUnits } from '$lib/units/definitions';
import { convertById } from '$lib/convert';
import { FRACTIONAL_DIGITS } from '$lib/config';
import { formatBig } from '$lib/format';
import Big from 'big.js';
import type { Unit, ResultItem } from '$lib/types';
import ConverterCard from '../components/ConverterCard.svelte';
/**
* Converter page — owns all reactive state.
* Phase 2: live conversion, URL state, highlight interaction.
* Phase 3 will add NL input.
*/
const units: Unit[] = florpUnits;
// ── Reactive state ──────────────────────────────────────────────────────────
let inputValue = $state<number>(1);
let fromUnitId = $state<string>(units[0].id);
let highlightedUnitId = $state<string | null>(null);
// ── Initialize from URL params on first client-side load ───────────────────
if (browser) {
const params = page.url.searchParams;
const pValue = params.get('value');
const pFrom = params.get('from');
const pHighlight = params.get('highlight');
if (pValue !== null) {
const n = parseFloat(pValue);
if (!isNaN(n)) inputValue = n;
}
if (pFrom !== null && units.some((u) => u.id === pFrom)) {
fromUnitId = pFrom;
}
if (pHighlight !== null && units.some((u) => u.id === pHighlight)) {
highlightedUnitId = pHighlight;
}
}
// ── Derived: results for ALL units in definition order ─────────────────────
// convert.ts already handles the same-unit identity (from.id === to.id → return value),
// so no special-casing needed here for the from-unit.
let results = $derived<ResultItem[]>(
units.map((u) => ({
unit: u,
convertedValue: convertById(inputValue, fromUnitId, u.id, units)
}))
);
// ── Derived: highlighted result (unit + value) sourced from full results ────
// Results now includes all units, so from-unit can be highlighted too.
let highlightedResult = $derived(
highlightedUnitId !== null
? (results.find((r) => r.unit.id === highlightedUnitId) ?? null)
: null
);
let fromUnit = $derived(units.find(u => u.id === fromUnitId) ?? units[0]);
// ── URL sync: update URL only when serialized params actually change ────────
$effect(() => {
if (!browser) return;
const params = new URLSearchParams();
params.set('value', String(inputValue));
params.set('from', fromUnitId);
if (highlightedUnitId !== null) {
params.set('highlight', highlightedUnitId);
}
const next = params.toString();
if (next !== window.location.search.slice(1)) {
goto(`?${next}`, { replaceState: true, noScroll: true });
}
});
// ── Event handlers ──────────────────────────────────────────────────────────
function handleUnitChange(id: string) {
fromUnitId = id;
// Clear highlight if the selected unit becomes the new from-unit.
if (highlightedUnitId === id) {
highlightedUnitId = null;
}
}
function handleHighlight(id: string) {
highlightedUnitId = id === highlightedUnitId ? null : id;
}
function handleSetInput(id: string) {
// Shift+click: promote result unit to from-unit.
// inputValue is NOT changed — outputs recalculate from the same inputValue with the new fromUnitId.
// highlightedUnitId is NOT cleared — the two states are independent.
fromUnitId = id;
}
function handleCtrlClick(id: string, convertedValue: Big) {
// Ctrl+click: adopt this tile's displayed value as the new inputValue and promote it to from-unit.
// This is the "pivot" gesture.
inputValue = parseFloat(convertedValue.toFixed(FRACTIONAL_DIGITS));
fromUnitId = id;
}
</script>
<svelte:head>
<title>Humor Units — Converter</title>
</svelte:head>
<div class="highlighted-result" class:has-highlight={highlightedResult !== null} aria-live="polite">
{#if highlightedResult !== null}
<button class="highlight-dismiss" onclick={() => (highlightedUnitId = null)} aria-label="Remove highlight">×</button>
<div class="highlighted-equation">
<span class="highlighted-eq-from">{formatBig(new Big(inputValue))} {fromUnit.labelPlural}</span>
<span class="highlighted-eq-sep">=</span>
<span class="highlighted-eq-to">{formatBig(highlightedResult.convertedValue)} {highlightedResult.unit.labelPlural}</span>
</div>
<span class="highlighted-symbol">{highlightedResult.unit.symbol}</span>
{:else}
<span class="highlighted-placeholder">Click a result to highlight it</span>
{/if}
</div>
<ConverterCard
{units}
{inputValue}
{fromUnitId}
{highlightedUnitId}
{results}
oninputchange={(v) => (inputValue = v)}
onunitchange={handleUnitChange}
onhighlight={handleHighlight}
onsetinput={handleSetInput}
onctrlclick={handleCtrlClick}
/>