Commit Graph

21 Commits

Author SHA1 Message Date
Falkan
15b5871eb9 fix: tooltip positioning without rAF — measure synchronously via ready class 2026-03-19 00:16:49 -04:00
Falkan
65f760a254 fix: tooltip positioning — measure after rAF, shift up minimally to fit 2026-03-19 00:05:09 -04:00
Falkan
c13db8b826 fix: tooltip flips above cursor when it would clip viewport bottom 2026-03-18 23:58:40 -04:00
Falkan
61d1d49f4b feat: sort-alpha button per group, settings in header, sortPosition pin
- ⇅ button on each group header: sorts units alpha (labelPlural + symbol tiebreak) via order API
- ⚙ Settings moved to admin-header alongside Log out
- Unit type gets sortPosition?: number (1-based, optional)
- Converter sort: partitions pinned/floating, inserts pins at requested slots
  Pins > length append at end; gaps fill naturally with floating items
- Admin unit form: Force position number input
2026-03-18 22:16:44 -04:00
Falkan
b020b74c31 feat: yoloVisibility config, alwaysShowLabel on groups, site settings panel
- AppConfig gains yoloVisibility: 'auto' | 'never' (default: auto)
- Group gains alwaysShowLabel?: boolean
- YOLO toggle only renders when showYoloToggle is true (auto + >1 group)
- Group divider shows when >1 group OR alwaysShowLabel is set
- New /admin/api/config GET+PUT endpoint for yolo settings
- Admin: alwaysShowLabel checkbox in group form
- Admin: ⚙ Settings panel with YOLO label, description, and visibility controls
2026-03-18 20:15:45 -04:00
Falkan
2c27edc487 fix: cycle references resolve to Label (symbol) instead of empty
When a {id:description} reference forms a cycle, the back-reference
now expands to 'Label (symbol)' rather than being stripped.
So pound ↔ dickload renders as 'See dickload (dl)' and 'See pound (lb)'.
2026-03-18 19:59:33 -04:00
Falkan
d332cc135e fix: description placeholder sentinel leaking to output
Two bugs:
1. Sentinel used double-underscore delimiters (__DESC:id__) — fragile regex
   [^_]+ would break on IDs containing underscores, and Pass-1 output
   containing sentinels would leak through Pass 2 unstripped.
2. After Pass 2 substitution, sentinels inside substituted text (from
   Pass-1 output of the referenced id) were never cleaned up.

Fix: switch to null-byte delimiters (\x00DESC:id\x00) which cannot appear
in user text, and add a second .replace(SENTINEL_RE, '') pass to strip any
sentinels that survive after substitution (self-refs, nested cycles).
2026-03-18 19:51:01 -04:00
Falkan
6a9ecf9d46 fix: custom tooltip (no wiggle required), add ⓘ indicator for described units
- Replace native title-based tooltip with custom positioned div
  Native title only renders on mousemove; custom div fires on mouseenter
  after 500ms delay as intended
- Tooltip styled dark with fade-in; Dan Mode gets yellow-on-black with magenta glow
- Units with descriptions show a small superscript ⓘ on the result label
2026-03-18 19:33:14 -04:00
Falkan
6e38522a89 feat: domain-based branding (humorunits.com / dickloads.com)
- src/lib/brand.ts: Brand config map, getBrand() reads hostname server-side
- src/routes/+layout.server.ts: new, passes brand to layout
- +layout.svelte: site name and footer driven by brand config
- humorunits.com footer: 'invented conversions for ridiculous people'
- dickloads.com footer: placeholder (pending Shannon's pick)
2026-03-18 19:27:49 -04:00
Falkan
d088e61006 feat: two-pass description placeholder resolver with cycle handling
Pass 1 resolves all field tokens ({id}, {id:label}, {id:plural}, {id:symbol})
and replaces {id:description} tokens with sentinels.
Pass 2 replaces sentinels with Pass 1 output for the referenced id — one level
deep, so mutual references expand once then stop cleanly.

ConverterCard now uses resolveAllDescriptions() for efficiency (one map
build per reactive update instead of per-unit).
2026-03-18 17:37:00 -04:00
Falkan
998cda0bb3 feat: hidden flag, description placeholders, drag handle contrast fix
- Unit and Group get hidden?: boolean — filtered server-side before render
- Hidden group also suppresses all its units from the converter
- New resolveDescription() utility in src/lib/description.ts
  supports {id}, {id:label}, {id:plural}, {id:symbol} placeholders
- ConverterCard pre-resolves descriptions and passes result to ConversionResult
- ConversionResult accepts description prop instead of reading unit.description
- Admin forms: Hidden checkbox added for both units and groups
- drag-handle color overridden to --pico-primary-inverse on selected/drag-over rows
  (white in light/dark themes, black on Dan Mode yellow)
2026-03-18 16:53:34 -04:00
Falkan
ae6a073b7e feat: two-line cards, responsive grid, real uniform font size
- ConversionResult: already two-line layout (value + label/symbol)
- app.css: responsive grid (1/2/3 cols at <480/480-899/≥900px)
- uniformFontSize.ts: JS-measured uniform font size per grid
- config.ts: remove MIN_COLUMN_WIDTH, grid is now CSS-only
- BACKLOG.md: remove resolved 'allow renaming unit/group IDs' item

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:17:09 -04:00
Falkan
af83263520 feat: two-line cards, responsive grid, real uniform font size 2026-03-17 20:13:57 -04:00
Falkan
1317f5e8fb feat: per-group sort order (defined/alpha) in group form and converter 2026-03-17 19:59:02 -04:00
Falkan
15b763e062 feat: unit descriptions with tooltip action and admin textarea 2026-03-17 19:55:09 -04:00
Falkan
e2e53ca44a feat: YOLO toggle inline layout; yoloLabel/yoloDescription configurable via config.json 2026-03-17 19:50:59 -04:00
Falkan
78e24017a7 fix: uncontrolled input — stop re-setting value prop on every keystroke, killing focus 2026-03-17 19:27:07 -04:00
Falkan
aeeae1e7ce refactor: simplify admin code — extract shared helpers, fix quality issues
- Extract KEBAB_RE, SESSION_COOKIE, SESSION_MAX_AGE_MS, COOKIE_MAX_AGE,
  authRequest(), and sessionResponse() to auth.ts — eliminating 4 copies
  of the auth() wrapper and 3 copies of KEBAB_RE across admin API routes
- Remove timing side-channel: drop a.length !== b.length early-return
  before timingSafeEqual in verifySession (SHA-256 HMAC always 64 chars)
- Fix YOLO localStorage write-before-read race using initialization guard
- Fix admin layout /api/ bypass to return {} instead of {authenticated:true}
- Extract extractError() helper in admin page — removes 4 inline patterns
- Parallel Promise.all in admin loadData() — halves round-trip latency
- Parallel Promise.all in moveCheckedUnits() + surface per-request errors
- Hoist Big(inputValue) above .map() in converter — one alloc per recompute
- O(n) single-pass orderedResults using Map instead of O(n*groups) filters
- Fix expandedGroups.add() mutation — use Set assignment for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 14:04:33 -04:00
Falkan
d1f51c141e feat: admin page with dynamic converter loading
Implements full admin interface for managing units and groups.
Migrates converter from static imports to server-side data loading.

- Switch adapter-static → adapter-node
- Add bcryptjs session auth with HMAC-signed cookies
- Add data/units.json and data/config.json data layer
- Add atomic file writes via temp-file rename
- Add public GET /api/units endpoint
- Add auth-gated admin CRUD API for units and groups
- Add two-panel admin UI with group tree and edit forms
- Add YOLO mode toggle for cross-group conversions
- Add visual group dividers in converter results grid
- Update ResultItem type for nullable convertedValue and isYolo flag
- Group deletion supports reassign/orphan with toBase recalculation

Rollback point: 067fd44

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 13:55:11 -04:00
Falkan
8f835c35d2 Phase 2: core converter, live conversion, URL state, highlight interaction 2026-03-16 21:34:16 -04:00
Falkan
65f8bc1b9f Phase 1: scaffold, types, schema, layout shell
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:22:34 -04:00