Commit Graph

2538 Commits

Author SHA1 Message Date
Scott Idem
98e31f1528 docs: update docs to reflect events store migration completion
BOOTSTRAP__AI_Agent_Quickstart.md: rewrite store reactivity trap section
to distinguish events sub-stores (PersistedState, fine-grained) from
ae_loc/idaa_loc (svelte-persisted-store, coarse). Add import/read/write
syntax examples and pointer to migration doc.

PROJECT__Stores_Svelte5_Migration.md: rewritten to reflect events module
fully complete; documents established PersistedState pattern with canonical
examples; tables show all 5 sub-stores done + events_loc retired.

TODO__Agents.md: events migration marked complete (2026-06-11); idaa_loc
and ae_loc listed as remaining work; stale events_loc file_display_overrides
ref fixed.

tests/README.md: replace Leads-only store migration note with full events
sub-store table, localStorage keys, and explanation of PersistedState
deserialization (no __version guard needed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 16:50:06 -04:00
Scott Idem
573d20e574 feat(stores): retire events_loc — Svelte 4 persisted store fully replaced
events_loc has been completely removed after migrating all consumers:

- EVENTS_MODULE_TITLE constant replaces $events_loc.title (was always
  the static default 'OSIT\'s Æ Events', never written dynamically)
- events/+layout.svelte: qry__* writes moved to events_sess; stale-deploy
  ver check block removed (store_versions.ts handles this already)
- 3 stale pres_mgmt stragglers fixed: device_obj_li, location_page_menu,
  event_reports_page_menu now use pres_mgmt_loc.current directly
- testing/+page.svelte: missed launcher ref fixed (launcher_loc.current)
- events_loc export, events_local_data_struct, persisted import, and
  AE_EVENTS_LOC_VERSION import all removed from ae_events_stores.ts
- events_loc import cleaned from all consumer files

events_sess (in-memory writable) stays in ae_events_stores.ts unchanged.
store_versions.ts keeps its _check_and_wipe('ae_events_loc') entry to
clean lingering old data from users' browsers.

svelte-check: 0 errors, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 16:41:06 -04:00
Scott Idem
83c9b9fd4f feat(stores): promote events auth state to Svelte 5 PersistedState
Creates ae_events_stores__auth.svelte.ts with PersistedState keyed
'ae_events_auth_loc' for auth__person and auth__kv (presenter/session
sign-in state). Migrated 10 component files from $events_loc.auth__* to
events_auth_loc.current.auth__*.

Also fixed stale pres_mgmt stragglers: $events_loc.pres_mgmt.* refs in
presenter_obj_li.svelte, presenter_page_menu.svelte, and [presenter_id]/
+page.svelte now use pres_mgmt_loc.current.* directly.

show_details boolean moved from events_loc to leads_loc (it belongs in
the leads module — one bind in ae_tab__manage.svelte).

auth__person, auth__kv, show_details, events_cfg_json, event_id removed
from events_local_data_struct. events_loc now only carries ver, title,
and qry__* prefs.

svelte-check: 0 errors, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 16:19:49 -04:00
Scott Idem
27c775d816 feat(stores): promote launcher_loc to Svelte 5 PersistedState
Creates ae_events_stores__launcher.svelte.ts with PersistedState keyed
'ae_launcher_loc', following the same pattern as badges, leads, and
pres_mgmt. All 28 launcher component files migrated from
$events_loc.launcher.* to launcher_loc.current.*.

events_local_data_struct in ae_events_stores.ts now carries no sub-module
objects — all four sub-modules (badges, launcher, leads, pres_mgmt) are
authoritative in their own stores. Session state (events_sess.launcher)
is unchanged.

svelte-check: 0 errors, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 16:00:40 -04:00
Scott Idem
5823f18161 chore(stores): remove migrated sub-objects from events_loc persisted struct
badges, leads, and pres_mgmt have each been promoted to their own
PersistedState stores (ae_badges_loc, ae_leads_loc, ae_pres_mgmt_loc).
Remove them from events_local_data_struct and drop the now-unused
*_loc_defaults imports so events_loc only carries what it owns.

launcher stays — not yet migrated (244 active refs).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 15:41:54 -04:00
Scott Idem
94e4fad061 chore(stores): remove unused default properties from ae_loc, ae_sess, events_slct, idaa_loc
Audited all three legacy persisted-store files for properties with zero
references in src/. Removed ~119 lines of dead defaults:

- ae_loc: qr_scanner_version, ds, entire mod block (archives/events/journals/posts/sponsorships)
- ae_sess: hub, mod block (archives/events/journals/posts/sponsorships/testing), download
- events_loc: ds
- events_sess: ds_loaded
- events_slct: abstract_*, badge_template_*, device_*, exhibit_tracking_*, lq__presenter_obj
- idaa_loc: ds, idaa_cfg_json, top-level qry__* (each submodule has its own), novi_rate_limited_until, novi_*_base_url (read from site_cfg_json, not idaa_loc)

No version bumps needed — removing fields from defaults is backwards-compatible.
svelte-check passes: 0 errors, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 15:29:48 -04:00
Scott Idem
9a1ba02b59 Can not use these links within an iframe... 2026-06-11 15:08:50 -04:00
Scott Idem
05841350fe feat(idaa): add /idaa/clear-caches page for Novi iframe cache reset
Clears all IDB databases, localStorage, and sessionStorage for the
prod-idaa.oneskyit.com origin when loaded as an iframe inside www.idaa.org.
Targets the partitioned storage bucket used by IDAA Novi iframes — direct
navigation to the site clears a different partition and has no effect.

Uses Novi-safe styling (explicit bg/text surfaces, no bare h1 elements,
inline styles on links) to survive Bootstrap v3 CSS injected by Novi.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 13:01:34 -04:00
Scott Idem
a5beff4aa8 feat(reports): add created_on timestamps and detail links to file downloads report
Show upload timestamp for every file; bold the newest upload per session/presenter
group so staff can quickly identify the most recent version. Add Session/Presenter
navigation links in each card header for direct access without searching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 04:23:07 -04:00
Scott Idem
246d4f8ef3 fix(pres_mgmt): show session/presenter counts in File Downloads header
The summary was showing file counts by type (session-level files vs
presenter-level files), which made the session count look wrong (e.g.
6 when there are 40 sessions). Now shows unique session and presenter
counts from the grouped data, matching what the label implies.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 17:30:19 -04:00
Scott Idem
666b54bd36 fix(badges): add missing updated_on column to CSV export
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 16:36:25 -04:00
Scott Idem
89c1decf8d feat(badges): add CSV export to badge reports
Adds an "Export CSV" report to the badge reports page. Generates
a clean CSV client-side from Dexie cache — no backend call needed.

- 3 filter presets: Printed+Clean (default), Printed all, All badges
- Printed+Clean mirrors the manually-cleaned Axonius export (printed,
  non-hidden, non-test badge type)
- Timezone selector: Eastern (default), Local, UTC — addresses the
  UTC→Eastern conversion needed for post-event client exports
- 24 columns: identity fields, override pairs, print status, created_on
- UTF-8 BOM for direct Excel open without import wizard
- Auto-generated filename from event name + date + filter suffix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 16:00:18 -04:00
Scott Idem
b9d70b616f feat(reports): add two new filename format options to file downloads report
Adds 'Session Code + Date + Time + Name' and 'Session Code + Date +
Session Time + Presentation Time + Presenter Full Name' format presets.
The second format uses presentation start datetime for the pres_time
part and event_presenter_full_name for the full presenter name part.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 15:18:39 -04:00
Scott Idem
e8a49562a9 feat(pres_mgmt): prefix/suffix inputs, flex row layout, strip :443 from links
File Downloads report: add Prefix and Suffix inputs for filename customization
(e.g. "2026_06__"), longest-filename length indicator with warning above 120
chars, and switch file rows from table to flex layout so Download/Copy Link
buttons stay right-aligned regardless of filename length.

Strip redundant :443 from https download URLs in both the file downloads report
and the manage event file list clipboard links.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 15:15:16 -04:00
Scott Idem
e909c34874 Updated documentation. 2026-06-10 14:55:10 -04:00
Scott Idem
48bc52899f fix(pres_mgmt): switch direct download links to event_file endpoint
event_file ?key= auth fix is now deployed. Retire the hosted_file workaround
and use /v3/action/event_file/{event_file_id}/download?key=... as the canonical
form across the file downloads report, manage file list, and download button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 14:41:53 -04:00
Scott Idem
6b122a065e feat(pres_mgmt): File Downloads report with clean filename presets
New report at Pres Mgmt > Reports > File Downloads. Groups all event files
by session and presenter, with 10 filename format presets (original, session
code/date/name combos, presenter variants). Per-file Download button and Copy
Link button using hosted_file endpoint for ?key= auth support.

Also fixes direct download links in element_manage_event_file_li and the
hosted_files download button — event_file endpoint does not yet propagate
?key= auth internally, so all direct links now use hosted_file endpoint
which supports it today.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 14:19:25 -04:00
Scott Idem
1b81b8873c refactor(badges): move reports IDB prefetch to +page.ts load function
Replace the $effect-based background fetch with the canonical
if(browser) fire-and-forget pattern in +page.ts. Runs earlier
in the lifecycle, no store subscription overhead, and immune to
$ae_api store re-trigger side effects. Removes ae_api and
events_func imports from the component.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:36:31 -04:00
Scott Idem
4f74cf1353 fix(badges): reports background fetch guard used wrong field name
$ae_api.api_key does not exist — the field is api_secret_key.
The guard evaluated to true on every render, returning immediately
and never calling search__event_badge. Changed to base_url which
is the standard readiness check per the quickstart doc.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:32:49 -04:00
Scott Idem
6dc6be9926 fix(badges): reports background fetch was not writing to IDB
try_cache=false skips db_save_ae_obj_li__ae_obj, so the API fetch
completed but liveQuery never saw the data. Removing the override
lets it default to true so results are persisted to IDB as expected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:27:57 -04:00
Scott Idem
97c4c1cd6b fix(badges): Remote First uncheck stuck visually checked
e.preventDefault() was called for both enable and disable clicks.
On disable, it reverted the DOM back to checked before Svelte could
sync the store update, leaving it visually stuck. Only prevent default
when enabling (to hold the unchecked state during confirmation).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:15:39 -04:00
Scott Idem
b6481a3507 feat(badges): Remote First confirmation warning + title tooltips
Enabling Remote First now shows an inline warning explaining that it
bypasses the local cache and queries the server on every keystroke.
User must confirm before it activates; Cancel leaves the checkbox off.
Disabling still takes effect immediately. Label and checkbox also get
title tooltips. Active state gets a warning tonal highlight.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:12:28 -04:00
Scott Idem
7b45b548e4 fix(badges): reports page fetches all badges on load instead of IDB-only
Reports were IDB-read-only — no data appeared until the badge search page
had already populated the cache. Added a background search__event_badge
call (limit 5000, try_cache=false) so navigating directly to /reports
always gets a fresh full dataset; liveQuery updates reactively.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 12:06:13 -04:00
Scott Idem
a1057fd776 fix(badges): throughput — weekday-first date header, keyed each block
Separators now read "Tuesday · June 9 — EDT" so day-of-week is the
lead identifier, matching how conference staff refer to the schedule.
Added key (sz) to the window-size each block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 11:28:48 -04:00
Scott Idem
d0286f7868 feat(badges): show full date + browser timezone in throughput separators
Date separators now display "Monday, June 9 — EDT" instead of "Jun 9",
using the browser's local timezone abbreviation resolved at page load.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 11:24:25 -04:00
Scott Idem
1c541cd090 fix(badges): print throughput report — descending sort + UTC timezone fix
Buckets now display newest-first. Naive UTC datetime strings from the
backend are normalized with a Z suffix before parsing so times display
in local browser timezone, matching the badge list.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 11:16:14 -04:00
Scott Idem
868b4017f2 feat(badges): allow trusted staff to edit name without enabling edit mode
Trusted users can already edit title/affiliations in regular mode via the
auth_editable list. Name was locked to trusted+edit_mode only. Staff at the
badge table frequently need to fix a misspelled name mid-queue without
toggling the full edit mode for the whole app.

Added a targeted guard in field_editable(): is_trusted && field === 'name'
passes before the auth_editable list check, so trusted users get the edit
pencil for name in normal mode. Regular authenticated users (attendees)
are unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 09:38:48 -04:00
Scott Idem
3122725610 fix(badges): apply print_count-first sort to all three badge display paths
The default sort (unprinted first, then name) was only wired into the API
order_by_li. Two IDB-local paths were left behind:

1. SCENARIO 2 fallback (no active filters, no text query): used Dexie
   .sortBy('given_name') — bypassed entirely on initial page load.
   Fixed: fetch .toArray() then JS-sort by print_count ASC → given_name ASC.

2. Fast-path IDB sort default case: also sorted by given_name only, causing
   a visible flash of name-ordered results before the API response landed.
   Fixed: same print_count ASC → given_name ASC comparator.

All three paths (API, fast-path IDB, fallback IDB) now agree on sort order.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 09:13:47 -04:00
Scott Idem
6e04145514 fix(badges): display print timestamps in local browser time instead of UTC
The backend returns naive UTC datetime strings (no Z suffix). dayjs was treating
them as local time, so staff in ET saw UTC times. Added treat_as_utc param to
iso_datetime_formatter (default false, zero breakage to existing call sites) that
appends Z before parsing so dayjs converts to browser-local time on display.

Applied to all print timestamp call sites in badge list (first/last print visible
row + debug row) and to created_on/updated_on in the debug row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 07:56:46 -04:00
Scott Idem
b8ceed69d0 fix(badges): default sort puts unprinted badges first (print_count ASC, then name ASC)
Previously the default sort was name-only, mixing printed and unprinted badges
together. Staff at registration need unprinted badges at the top so they can
immediately see who still needs a badge without filtering.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 07:47:40 -04:00
Scott Idem
71aacb6346 fix(badges): sync events_slct.event_id from URL param on direct navigation
The badges page relied on events_slct.event_id being set by ae_acct.slct.event_id
(populated when the user clicks an event in the events list). Direct URL navigation
— bookmark, shared link, browser history — skipped that code path, leaving
events_slct.event_id null. Any call using that value (upload form, create form)
would fire with an empty event_id and get a 404.

Reads page.params.event_id on mount, same pattern used by the launcher and
session pages, so all entry paths are reliable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 07:18:05 -04:00
Scott Idem
04f3b82d59 fix(stores): merge defaults on PersistedState deserialize so new fields get defaults
runed PersistedState hydrates raw stored JSON without merging defaults, so any
field added after a user's first session is undefined rather than its default value.
Apply a shallow spread (defaults → stored) in the custom serializer for badges,
leads, and pres_mgmt stores so new fields always fall back to defaults for
existing sessions — without disrupting any previously saved preferences.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:47:25 -04:00
Scott Idem
cc04411d23 feat(badges): goto() after print with per-device fallback toggle
Switch post-print navigation from window.location.href to goto() for
faster SvelteKit client-side transition back to Badge Search (no full
reload). A nav_to_badges() helper in print controls branches on
badges_loc.print_nav_use_goto (default true).

Badges Config page gains a "Local Device Settings" section with a
checkbox to disable goto() and fall back to hard reload — stored in
localStorage per browser, not synced to the event. Useful as an
on-site escape hatch without a code deploy.

Also fixes the Templates button on the config page: adds the standard
border border-surface-300-700 so it looks like a button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:28:33 -04:00
Scott Idem
55f3e3a5a4 feat(badges): add Hole Marker Inset control to badge template form
Exposes punch_holes.inset_x_mm in the badge template config UI under
the Punch-Out Hole Markers section. Visible whenever at least one slot
is enabled. Number input (0–8, step 0.5) with a Reset button that
clears back to the 2mm default. Saved to cfg_json on submit; parsed
back on load; omitted from the payload when left at default (null).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:13:07 -04:00
Scott Idem
955d28d9c5 fix(badges): shrink punch-hole markers horizontally, add inset_x_mm config
Default horizontal inset increased from 1mm to 2mm per side (width shrinks
by 4mm total) so markers stay inside the physical hole slot on all printers
and badge stock with minor registration variance. Vertical stays at 1mm.

The inset is now driven by punch_holes_inset_x (template_cfg.punch_holes
.inset_x_mm, default 2) so it can be tuned per template without a code
change. Added inset_x_mm field to AeBadgeTemplateCfg type with doc comment.

All 9 slot marker divs (6 rainbow + 3 solid) updated via replace_all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 20:07:59 -04:00
Scott Idem
7c5cf53106 feat(badges): extend search result limit to 500 for trusted staff
Trusted users can now step up to 500 results (300 → 400 → 500) via the
existing ± stepper. Manager tier continues to 2500 as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 19:52:19 -04:00
Scott Idem
24b52b8027 style(badges): reports — consistent buttons, dark mode, 500-row cap
- Report selector now uses a tonal-primary container matching the pres_mgmt
  reports pattern, making Long Names / Print Throughput clearly clickable
- All hardcoded gray-* colors replaced with surface-*-* tokens and
  dark: variants for proper light/dark mode support
- Control buttons (field selector, threshold, window size) use btn-sm with
  visible borders matching the rest of the events module
- Long Names table uses surface tokens on rows, header, borders
- Print Throughput bar rows use surface tokens; expanded badge chips link
  to /print instead of /review
- Long Names caps display at 500 rows with a warning note when hit

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 19:51:31 -04:00
Scott Idem
6b3fb36926 fix(badges): long names report edit button links to print page with icon
Changed href from /review to /print, added UserRoundPen icon, and
extended the title to include badge ID and name for hover context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 19:43:23 -04:00
Scott Idem
5cad150b0a fix(badges): move Reports link to trusted+edit toolbar, add reprint count
Reports link moved out of the enable_mass_print gate into the main
trusted+edit-mode toolbar, to the right of Templates — visible whenever
staff are in edit mode regardless of mass-print config.

Reports page header now shows reprint count in parentheses when > 0,
e.g. "142 badges · 98 printed (7 reprints)".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 19:38:25 -04:00
Scott Idem
8a41f02f0d feat(badges): add Long Names and Print Throughput reports
Two IDB-backed reports under /badges/reports:
- Long Names: filter badges by given/family/full name length (threshold
  adjustable), colour-coded by severity, links to review page
- Print Throughput: bucket print_last_datetime into 5/15/30/60-min
  windows with a horizontal bar chart and expandable badge name list

Also adds a "Badge Reports" nav link on the badges main page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 19:32:53 -04:00
Scott Idem
88ab5b27d4 fix(badges): skip PATCH and print immediately when offline or API unreachable
When navigator.onLine is false or the account is ghost (API unreachable),
bypass the 20-second API timeout entirely and fire window.print() at once.
The existing error state ("Printed — count NOT saved") already covers this
case. Staff can correct the print count manually after connectivity returns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:19:27 -04:00
Scott Idem
b623557795 feat(layout): collapsible offline/API-error banner with improved messages
Replace the static full-width offline banner with a two-state toggle:
expanded banner (default) with a collapse button, and a small chip in
the top-right corner when collapsed. Adds a subtitle line explaining
what still works offline vs. what requires network. Changes "No API"
chip label to "API Error" and "Offline" title to "Device Offline".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:19:22 -04:00
Scott Idem
7d2b30b7ce fix(badges): open badge review access when no passcode is set
Badges without person_passcode are now viewable by anyone with the URL —
open access is granted on badge load. Previously this was explicitly
denied. The passcode entry form is only shown when the badge actually
has a passcode configured. Auto-validate effect expanded to cover the
no-passcode case.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:35:47 -04:00
Scott Idem
c9b0acfa06 feat(badges): two-step confirm before sending email review link
Replace the single alert() call with an inline confirmation row per badge.
Clicking "Email Link" shows "Send / Cancel" in place so accidental sends
are avoided. Only one badge can be in confirm state at a time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 18:35:40 -04:00
Scott Idem
29a24812f4 fix(badges): perceptually uniform rainbow via OKLCH + @property
Replaces filter:hue-rotate() with CSS @property --ph-hue/--ph-lit animated
as numbers, applied as oklch() colors on SVG rect/line children. OKLCH keeps
perceptual lightness constant across hue rotation — no more brown/dark-blue
variance between slots. Pulse mode animates --ph-lit 0.42→0.78 for breathing.
Adds slow_pulse cfg_json flag + form checkbox. @property inherits:true lets
the animated value cascade from the div to its SVG children without per-child
animation declarations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:47:43 -04:00
Scott Idem
70fda25c95 feat(badges): slow pulse mode for rainbow punch hole markers
Adds punch_holes.slow_pulse cfg_json flag. When enabled, replaces the fast
2.5s linear hue-rotate with a 6s ease-in-out breathing animation that dims
(0.55 brightness) to bright (1.30) while shifting 180° of hue and back.
Same 120° phase offsets apply (2s apart). Form shows a Slow Pulse checkbox
below the slot cards whenever at least one slot has rainbow enabled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:42:23 -04:00
Scott Idem
8f815b7033 fix(badges): tri-phase rainbow cycling for punch hole markers
Each slot gets a fixed animation-delay (left: 0s, right: -0.833s, center: -1.667s)
so they are 120° apart in the hue cycle — same speed, different start points.
Replaces the shared-wrapper approach (all same phase) with per-slot CSS classes
that encode the phase offset, giving a proper tri-phase RGB cycling effect.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:35:31 -04:00
Scott Idem
ba4a0dc828 feat(badges): static RGB gradient for punch holes on print, cycling on screen
Each rainbow-enabled slot now has a companion print-only div with an inline
SVG linearGradient (R→Y→G→C→B→M spectrum). On screen: the animated
hue-rotate div cycles. On print: CSS hides the animated div and shows the
static gradient instead. X lines print in semi-transparent black over the
gradient fill for visibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:28:56 -04:00
Scott Idem
35c1324824 feat(badges): rainbow animation option for punch-out hole markers
Adds left/right/center_rainbow to punch_holes cfg_json. When enabled,
applies a CSS hue-rotate animation (2.5s loop) to the marker div using
a saturated red base color so the full visible spectrum appears. Template
form shows a Rainbow checkbox per slot; hides color pickers when active.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:23:37 -04:00
Scott Idem
1a53a20995 feat(badges): per-side header padding configurable via template cfg_json
Adds header_padding_top/right/left alongside existing header_padding_bottom.
Removes hardcoded p-2 class — all four sides now set via inline style with
0.5rem default. Template form shows a 2×2 padding grid in Header & Branding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:17:04 -04:00