feat: badge v2 auto-scaling text with Element_fit_text
Adds binary-search font auto-scaling for badge text fields, replacing
the character-count heuristic in v1. New files:
- action_fit_text.ts: Svelte action using binary search + MutationObserver
+ ResizeObserver. Pass null to disable (manual override mode).
- element_fit_text.svelte: Component wrapper with min/max/manual_size/
height/width props. height prop required for overflow detection to work.
- ae_comp__badge_obj_view_v2.svelte: Badge render using Element_fit_text
for name/title/affiliations/location in display mode. font_size_* props
default to undefined (auto-scale) instead of numeric defaults.
fit_heights derived object provides layout-aware section heights for
badge_3.5x5.5_pvc, badge_4x5_fanfold, and badge_4x6_fanfold layouts.
flex_justify() maps shorthand ('around','between','even') to CSS values.
Edit mode uses plain divs — inputs are never auto-scaled.
print/+page.svelte: Added v1/v2 toggle button in header. V1 preserved
as fallback. font_size_* passed as null (not ?? undefined) to v2 so
auto-scaling is active by default; manual override from print controls
still disables it per-field.
Docs: PROJECT__AE_Events_Badges_Review_Print.md updated with kiosk
workflow design intent, email address rule (always event_badge.email),
permission model alignment gap (TASK 4.0), and v2 implementation status.
TODO__Agents.md: completed items removed, badge polish tasks updated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
# PROJECT: AE Events Badges — Review Form & Print Font Controls
|
||||
|
||||
**Created:** 2026-02-27
|
||||
**Last Updated:** 2026-03-02
|
||||
**Last Updated:** 2026-03-12
|
||||
**Branch:** `ae_app_3x_llm`
|
||||
**Priority:** HIGH — first live event is Axonius, NYC, mid-April 2026
|
||||
**Owner:** Scott Idem / One Sky IT
|
||||
@@ -9,9 +9,72 @@
|
||||
|
||||
---
|
||||
|
||||
## Design Intent — Two Complementary Flows
|
||||
|
||||
### Flow 1: Remote Badge Review (email link)
|
||||
- Staff emails a review link to the attendee before the event.
|
||||
- Attendee opens the link on their own device, reviews their badge info, and edits permitted fields.
|
||||
- **Email address rule:** Always send to `event_badge.email` — never `email_override`.
|
||||
`email_override` is a display/badge field only. It cannot be trusted as a delivery address
|
||||
(attendee may have changed it to something different for badge display purposes).
|
||||
- Component: `ae_comp__badge_review_form.svelte` — plain form, no badge render.
|
||||
- Route: `/events/[event_id]/badges/[badge_id]/review/`
|
||||
|
||||
### Flow 2: Kiosk / Onsite Badge Station (print page)
|
||||
- Hardware: a laptop + badge printer (Epson fanfold or Zebra PVC card) at the check-in table.
|
||||
- At the event, an attendee walks up to a badge station (check-in kiosk).
|
||||
- A staff member or volunteer pulls up the attendee's badge on the print page.
|
||||
- The **print page is a kiosk tool**, not just a print queue:
|
||||
- Attendee reviews their badge info and can edit permitted fields **in real time**,
|
||||
with the live badge render updating as they make changes.
|
||||
- Staff/volunteers are present to assist with any questions.
|
||||
- Once satisfied, staff prints the badge.
|
||||
- The key differentiator vs the review form: **the live badge render** shows exactly how
|
||||
the badge will print. Attendees and staff can see changes immediately.
|
||||
- Component: `ae_comp__badge_obj_view.svelte` / `ae_comp__badge_obj_view_v2.svelte`
|
||||
- Route: `/events/[event_id]/badges/[badge_id]/print/`
|
||||
|
||||
### Permission Model — Same Logic, Both Flows
|
||||
Both flows should respect the same permission model:
|
||||
- **Attendee-level** (basic Authenticated access): can edit `pronouns_override`,
|
||||
`full_name_override`, `professional_title_override`, `affiliations_override`,
|
||||
`location_override`, `phone_override`, `email_override`, `allow_tracking`, `agree_to_tc`.
|
||||
- **Staff-level** (trusted_access+): all attendee fields + `email`, `badge_type_code_override`,
|
||||
`badge_type_override`, `hide`, `priority`, `notes`, and font size controls.
|
||||
- Permissions are configured per-event in `event.mod_badges_json.edit_permissions`.
|
||||
Hardcoded defaults are used until that config is implemented.
|
||||
|
||||
**Current gap (TASK 4):** The print page edit button is currently gated to trusted_access only.
|
||||
It needs to be accessible to attendees at the kiosk (with appropriate field-level gating),
|
||||
matching the permission model already implemented in `ae_comp__badge_review_form.svelte`.
|
||||
|
||||
---
|
||||
|
||||
## Next Up for Badges (TASK 4)
|
||||
|
||||
### 1. QR Code on Badge Front — `ae_comp__badge_obj_view.svelte`
|
||||
### 0. Kiosk Editing — Print Page Permission Model Alignment
|
||||
**This is the most important gap before the first live event.**
|
||||
|
||||
Currently the print page edit button is staff-only (trusted_access gate). At the kiosk,
|
||||
attendees need to be able to edit their own fields (same attendee-level permissions as the
|
||||
review form), with staff-only fields gated appropriately.
|
||||
|
||||
Work needed:
|
||||
- Wire the same `can_edit_fields` / `can_edit(field)` permission logic into the print page
|
||||
that `ae_comp__badge_review_form.svelte` already uses.
|
||||
- The edit panel on the print page should show attendee-editable fields to all authenticated
|
||||
users, and staff-only fields to trusted_access+.
|
||||
- The badge render (v1 or v2) should update live as the attendee edits fields.
|
||||
- Consider whether the print page needs its own inline edit panel (sidebar or overlay)
|
||||
or whether it should share/reuse the review form component alongside the badge render.
|
||||
- **Do NOT use `email_override` as the send-to address** — always use `event_badge.email`.
|
||||
|
||||
### 1. Auto-Scaling Badge Text (v2) — In Progress
|
||||
`ae_comp__badge_obj_view_v2.svelte` using `element_fit_text.svelte` (binary search auto-scale).
|
||||
Toggle between v1 (heuristic) and v2 (auto-scale) on the print page via the `v1`/`v2` header button.
|
||||
Heights tuned per layout in `fit_heights` derived object. Still needs visual tuning with real badges.
|
||||
|
||||
### 2. QR Code on Badge Front — `ae_comp__badge_obj_view.svelte`
|
||||
The badge template has a `show_qr` flag (or similar). When toggled on, the QR code should
|
||||
appear on the front face of the printed badge. Currently QR is only shown on the review form.
|
||||
|
||||
@@ -30,18 +93,38 @@ appear on the front face of the printed badge. Currently QR is only shown on the
|
||||
- Must be hidden on `ae_comp__badge_obj_view.svelte` when `show_qr` is falsy.
|
||||
|
||||
### 2. Badge Print Controls — UX Improvements (ae_comp__badge_print_controls.svelte)
|
||||
- Scott has identified areas for improvement — TBD next session
|
||||
- Consider: keyboard shortcuts (+ / -) for font sizing while a field is active
|
||||
- Consider: "Apply to all badges" workflow for font size presets
|
||||
|
||||
### 3. Leads Module
|
||||
### 4. Leads Module
|
||||
Next major work after badge polish. See `documentation/MODULE__AE_Events_Leads.md` (if it
|
||||
exists) for context. Exhibitor lead scanning via QR code.
|
||||
exists) for context. Exhibitor lead scanning via QR code at exhibitor booth → capture attendee
|
||||
badge data, gated by `allow_tracking` on the badge.
|
||||
|
||||
---
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### ⏳ TASK 4.0: Kiosk Editing — NOT STARTED (2026-03-12)
|
||||
Print page edit access needs to be opened to attendee-level permissions, not just trusted_access.
|
||||
The permission model, field list, and `can_edit()` helper from `ae_comp__badge_review_form.svelte`
|
||||
should be the reference. See Design Intent section above.
|
||||
|
||||
### ⏳ TASK 4.1: Auto-Scaling Badge Text v2 — IN PROGRESS (2026-03-12)
|
||||
**Files created:**
|
||||
- `src/lib/elements/action_fit_text.ts` — Svelte action: binary-search font scaling with
|
||||
MutationObserver + ResizeObserver + requestAnimationFrame.
|
||||
- `src/lib/elements/element_fit_text.svelte` — Component wrapper. Key prop: `height` (required
|
||||
for binary search to work — without it, offsetHeight == scrollHeight always).
|
||||
- `src/routes/events/.../ae_comp__badge_obj_view_v2.svelte` — V2 badge render using
|
||||
Element_fit_text for name/title/affiliations/location in display mode.
|
||||
`fit_heights` derived object provides layout-aware heights per field per badge layout.
|
||||
`font_size_*` props default to `undefined` (auto-scale) rather than numeric defaults (v1 behavior).
|
||||
Manual overrides from print controls still work — any number disables auto-scale for that field.
|
||||
|
||||
**Toggle:** `v1`/`v2` button in print page header. V1 preserved as fallback.
|
||||
**Status:** Working — heights in `fit_heights` still need visual tuning with real badge stock.
|
||||
|
||||
### ✅ TASK 3: Badge Print Controls Panel — COMPLETE (2026-03-02)
|
||||
|
||||
**Files created/modified:**
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
> Use this file to track steps for complex features or bug fixes.
|
||||
> **Status:** <20> Stable — ongoing development.
|
||||
|
||||
## 📋 Open: Security
|
||||
- [x] **PUBLIC_AE_API_SECRET_KEY Audit:** Completed 2026-03-11. Key is `PUBLIC_*` by design (always in client bundle). Highest-risk anonymous path now uses limited-permission `PUBLIC_AE_BOOTSTRAP_KEY`. Full server-side migration would require a major API proxy refactor — not justified given JWT + account_id auth layers. `manifest.webmanifest/+server.ts` is a minor cleanup candidate (could use bootstrap key instead), but no security urgency. Current state is acceptable.
|
||||
|
||||
## 🚧 Upcoming High Priority
|
||||
|
||||
@@ -46,27 +44,10 @@ lead record look like in the DB?
|
||||
- **`window.print()` for badge print button:** Wire the existing `handle_print_badge()` to trigger `window.print()`. Browser print works well across Chrome/Chromium/Firefox — no Electron needed.
|
||||
- **Input Field Audit:** Several input fields are missing `name`/`id` attributes or `data-testid`. Known examples: badge override fields in `ae_comp__badge_obj_view.svelte`; template name input in `ae_comp__badge_template_form.svelte`. Matters for: accessibility, autofill, label associations, and test targeting. (For tests, use `getByLabel()` rather than `input[value*=...]` which only checks the HTML attribute, not the Svelte-bound DOM property.)
|
||||
|
||||
### [UX] Session Expired & Access Denied (identified 2026-03-10)
|
||||
|
||||
Two related UX gaps to handle together:
|
||||
|
||||
**1. Session Expired banner (API 401/403 mid-session):**
|
||||
- `flag_expired` in root `+layout.svelte` is declared but never set — it was always intended for this
|
||||
- Add a small writable store or custom event (e.g., `ae_auth_error` in `ae_stores`) that API helpers (`api_get_object.ts`, `api_post_object.ts`, `api_patch_object.ts`) can fire when they get a 401 or 403
|
||||
- Root layout watches the store and sets `flag_expired = true`
|
||||
- Render a non-blocking dismissible banner (not full-screen): "Session expired. Please sign in again." with a link to the sign-in control
|
||||
- Especially relevant for Launcher (event staff on tablets may not notice silent failures)
|
||||
|
||||
**2. Standardize Access Denied UI (non-IDAA routes only — IDAA layout is intentionally custom):**
|
||||
- Currently inconsistent across the app:
|
||||
- Root layout: full-screen `flag_denied` (site access key gate — keep this, it's correct)
|
||||
- `/core` layout: silent redirect to home — should show a brief message instead
|
||||
- `/events/[event_id]/settings`: inline raw text string — should use a consistent banner component
|
||||
- `/events/.../badges/.../review`: inline `<h3>Access Denied</h3>` with no context or action
|
||||
- Create a reusable `element_access_denied.svelte` component (small: icon + message + optional action button)
|
||||
- Swap the ad-hoc patterns to use it consistently
|
||||
|
||||
## ✅ Completed (2026-03)
|
||||
- [x] **[Security]** `PUBLIC_AE_API_SECRET_KEY` audit complete. Key is `PUBLIC_*` by design (always in client bundle). Highest-risk anonymous path uses limited-permission `PUBLIC_AE_BOOTSTRAP_KEY`. Full server-side migration not justified given JWT + account_id auth layers. Current state acceptable. (2026-03-11)
|
||||
- [x] **[UX]** Session Expired banner — `ae_auth_error` store wired to API helpers; root layout sets `flag_expired` on 401/403; non-blocking dismissible banner rendered. (2026-03-12)
|
||||
- [x] **[UX]** Access Denied UI standardized — `element_access_denied.svelte` created; `/core` layout, `/events/settings`, and `/events/badges/review` updated to use it. (2026-03-12)
|
||||
- [x] **[Build]** Rollup/Vite circular dependency warnings eliminated — `manualChunks` in `vite.config.ts` colocates all `svelte/*` internals into a single `svelte-vendor` chunk, preventing `runtime.js` / `index-client.js` split (~35 warnings gone). (2026-03-11)
|
||||
- [x] **[Refactor]** `try_cache` audit + sponsorship/event_file/hosted_file SWR alignment — removed vestigial `try_cache` params from `generate_qr_code`, `ae_core_functions` wrappers; added SWR fast/slow path to sponsorship loaders; changed `event_file` and `hosted_file` single-object loader defaults from `false` → `true` for consistency. (2026-03-11)
|
||||
- [x] **[DevOps]** Frontend + Backend unified into single `aether_container_env` Docker Compose. `ae_app` service live with healthcheck, single exposed port (`AE_APP_NODE_PORT`), internal `ae_api` networking. Deploy scripts in `package.json` both target `../aether_container_env/docker-compose.yml`. (2026-03-10)
|
||||
|
||||
87
src/lib/elements/action_fit_text.ts
Normal file
87
src/lib/elements/action_fit_text.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* action_fit_text.ts
|
||||
*
|
||||
* Svelte action that auto-scales text inside a fixed-size container using
|
||||
* binary search. Reacts to content changes (MutationObserver) and container
|
||||
* resize (ResizeObserver).
|
||||
*
|
||||
* Usage:
|
||||
* <div use:fit_text={{ min: 14, max: 48 }}>Some text</div>
|
||||
*
|
||||
* Pass null/undefined to disable (e.g. when a manual font size is active):
|
||||
* <div use:fit_text={manual_size != null ? null : { min: 14, max: 48 }}>
|
||||
*/
|
||||
|
||||
export interface FitTextParams {
|
||||
min?: number;
|
||||
max?: number;
|
||||
}
|
||||
|
||||
export function fit_text(
|
||||
node: HTMLElement,
|
||||
params: FitTextParams | null | undefined
|
||||
) {
|
||||
if (!params) return {};
|
||||
|
||||
let { min = 16, max = 80 } = params;
|
||||
|
||||
// overflow:hidden is required so scrollWidth/scrollHeight accurately reflect overflow
|
||||
const prev_overflow = node.style.overflow;
|
||||
node.style.overflow = 'hidden';
|
||||
|
||||
function fits(): boolean {
|
||||
return node.scrollWidth <= node.offsetWidth && node.scrollHeight <= node.offsetHeight;
|
||||
}
|
||||
|
||||
function fit() {
|
||||
if (!params) return;
|
||||
|
||||
// Try max first — if it fits, no search needed
|
||||
node.style.fontSize = max + 'px';
|
||||
if (fits()) return;
|
||||
|
||||
// Binary search between min and max
|
||||
let lo = min;
|
||||
let hi = max;
|
||||
while (lo < hi - 1) {
|
||||
const mid = Math.floor((lo + hi) / 2);
|
||||
node.style.fontSize = mid + 'px';
|
||||
if (fits()) {
|
||||
lo = mid;
|
||||
} else {
|
||||
hi = mid;
|
||||
}
|
||||
}
|
||||
node.style.fontSize = lo + 'px';
|
||||
}
|
||||
|
||||
// Re-fit when text content changes (handles {@html} updates)
|
||||
const mutation_observer = new MutationObserver(fit);
|
||||
mutation_observer.observe(node, { childList: true, subtree: true, characterData: true });
|
||||
|
||||
// Re-fit when the container is resized (e.g. window resize, panel open/close)
|
||||
const resize_observer = new ResizeObserver(fit);
|
||||
resize_observer.observe(node);
|
||||
|
||||
// Defer initial fit to after the DOM has laid out
|
||||
requestAnimationFrame(fit);
|
||||
|
||||
return {
|
||||
update(new_params: FitTextParams | null | undefined) {
|
||||
if (!new_params) {
|
||||
node.style.overflow = prev_overflow;
|
||||
return;
|
||||
}
|
||||
params = new_params;
|
||||
min = new_params.min ?? 16;
|
||||
max = new_params.max ?? 80;
|
||||
node.style.overflow = 'hidden';
|
||||
requestAnimationFrame(fit);
|
||||
},
|
||||
destroy() {
|
||||
mutation_observer.disconnect();
|
||||
resize_observer.disconnect();
|
||||
node.style.overflow = prev_overflow;
|
||||
}
|
||||
};
|
||||
}
|
||||
98
src/lib/elements/element_fit_text.svelte
Normal file
98
src/lib/elements/element_fit_text.svelte
Normal file
@@ -0,0 +1,98 @@
|
||||
<script lang="ts">
|
||||
/**
|
||||
* element_fit_text.svelte
|
||||
*
|
||||
* Wrapper component for the fit_text Svelte action. Renders a div that
|
||||
* auto-scales its font size to fill the available space (binary search).
|
||||
*
|
||||
* CRITICAL — height must be constrained for auto-scaling to work:
|
||||
* The action checks scrollHeight <= offsetHeight to detect overflow.
|
||||
* If the wrapper div has no explicit height it expands to fit content,
|
||||
* making scrollHeight == offsetHeight always — so the binary search
|
||||
* returns max font immediately (appears broken / no scaling).
|
||||
*
|
||||
* Set height via the `height` prop (CSS string, e.g. "1.5in", "3rem", "80px"),
|
||||
* OR apply a Tailwind height class via the `class` prop (e.g. "h-[1.5in]"),
|
||||
* OR let a parent flex container with a defined size control the height
|
||||
* and pass class="h-full" to fill it.
|
||||
*
|
||||
* Props:
|
||||
* min — minimum font size in px (default: 16)
|
||||
* max — maximum font size in px (default: 80)
|
||||
* manual_size — when set, disables auto-scaling and applies this font size directly
|
||||
* disabled — when true, neither auto-scaling nor manual_size is applied
|
||||
* height — explicit CSS height for the wrapper div (e.g. "1.5in", "3rem")
|
||||
* width — explicit CSS width for the wrapper div (rarely needed; usually inherits)
|
||||
* class — extra Tailwind/CSS classes for the wrapper div
|
||||
* style — extra inline styles for the wrapper div
|
||||
*
|
||||
* Example — auto-scale to fill an explicitly sized region:
|
||||
* <Element_fit_text min={20} max={80} height="1.5in" class="leading-none">
|
||||
* {attendee_name}
|
||||
* </Element_fit_text>
|
||||
*
|
||||
* Example — auto-scale filling a flex parent (parent must have a defined height):
|
||||
* <!-- parent: flex-1 min-h-0 overflow-hidden -->
|
||||
* <Element_fit_text min={20} max={80} class="h-full leading-none">
|
||||
* {attendee_name}
|
||||
* </Element_fit_text>
|
||||
*
|
||||
* Example — manual override (disables auto-scale, applies fixed size):
|
||||
* <Element_fit_text min={20} max={80} manual_size={font_size_name}>
|
||||
* {attendee_name}
|
||||
* </Element_fit_text>
|
||||
*/
|
||||
|
||||
import { fit_text } from './action_fit_text';
|
||||
import type { Snippet } from 'svelte';
|
||||
|
||||
interface Props {
|
||||
min?: number;
|
||||
max?: number;
|
||||
/** When set, disables auto-scaling and applies this size directly via inline style. */
|
||||
manual_size?: number | null;
|
||||
/** When true, neither auto-scaling nor manual_size is applied. */
|
||||
disabled?: boolean;
|
||||
/**
|
||||
* Explicit CSS height for the wrapper div (e.g. "1.5in", "80px", "3rem").
|
||||
* REQUIRED for auto-scaling unless height is controlled via class or a flex parent.
|
||||
* Without a constrained height, offsetHeight == scrollHeight always → max font returned.
|
||||
*/
|
||||
height?: string;
|
||||
/** Explicit CSS width for the wrapper div. Usually not needed — inherits from parent. */
|
||||
width?: string;
|
||||
class?: string;
|
||||
style?: string;
|
||||
children?: Snippet;
|
||||
}
|
||||
|
||||
let {
|
||||
min = 16,
|
||||
max = 80,
|
||||
manual_size = null,
|
||||
disabled = false,
|
||||
height,
|
||||
width,
|
||||
class: extra_class = '',
|
||||
style: extra_style = '',
|
||||
children
|
||||
}: Props = $props();
|
||||
|
||||
// Pass null to the action when auto-scaling should be suppressed
|
||||
let action_params = $derived(disabled || manual_size != null ? null : { min, max });
|
||||
|
||||
// Compose the final inline style.
|
||||
// Priority: manual_size → height/width → extra_style (caller's additional styles)
|
||||
let computed_style = $derived(() => {
|
||||
const parts: string[] = [];
|
||||
if (manual_size != null) parts.push(`font-size: ${manual_size}px`);
|
||||
if (height) parts.push(`height: ${height}`);
|
||||
if (width) parts.push(`width: ${width}`);
|
||||
if (extra_style) parts.push(extra_style);
|
||||
return parts.join('; ');
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class={extra_class} style={computed_style()} use:fit_text={action_params}>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,8 +17,13 @@
|
||||
import { ArrowLeft, Eye, LoaderCircle, Mail, Printer } from 'lucide-svelte';
|
||||
|
||||
import Comp_badge_obj_view from '../ae_comp__badge_obj_view.svelte';
|
||||
import Comp_badge_obj_view_v2 from '../ae_comp__badge_obj_view_v2.svelte';
|
||||
import Comp_badge_print_controls from '../ae_comp__badge_print_controls.svelte';
|
||||
|
||||
// V2 toggle: temporary — lets staff compare auto-scaling text (v2) vs heuristic sizing (v1).
|
||||
// Remove once v2 is verified and v1 is retired.
|
||||
let use_v2 = $state(false);
|
||||
|
||||
let event_badge_id = $derived(page.params.badge_id);
|
||||
let event_id = $derived(page.params.event_id);
|
||||
|
||||
@@ -171,6 +176,16 @@
|
||||
<!-- Right: Action buttons -->
|
||||
<div class="flex flex-row gap-1 items-center shrink-0">
|
||||
|
||||
<!-- V1/V2 toggle: temporary comparison tool — remove once v2 is verified -->
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-sm preset-tonal-surface flex items-center gap-1 font-mono"
|
||||
onclick={() => (use_v2 = !use_v2)}
|
||||
title={use_v2 ? 'Switch to v1 (heuristic sizing)' : 'Switch to v2 (auto-scaling text)'}
|
||||
>
|
||||
{use_v2 ? 'v2' : 'v1'}
|
||||
</button>
|
||||
|
||||
<!-- 1. Print Now: Trusted+, not printed OR Edit Mode (reprint) -->
|
||||
{#if is_trusted && (!is_printed || is_edit_mode)}
|
||||
<button
|
||||
@@ -221,17 +236,33 @@
|
||||
pr-64 offsets the badge area so it's not hidden under the fixed controls panel.
|
||||
On print, pr-64 is cleared and the fixed controls panel is hidden. -->
|
||||
<div class="print:pr-0 pr-64">
|
||||
<Comp_badge_obj_view
|
||||
event_id={$lq__event_badge_obj.event_id as string}
|
||||
event_badge_id={event_badge_id as string}
|
||||
{lq__event_badge_obj}
|
||||
{lq__event_badge_template_obj}
|
||||
is_review_mode={false}
|
||||
font_size_name={font_size_name ?? undefined}
|
||||
font_size_title={font_size_title ?? undefined}
|
||||
font_size_affiliations={font_size_affiliations ?? undefined}
|
||||
font_size_location={font_size_location ?? undefined}
|
||||
/>
|
||||
{#if use_v2}
|
||||
<!-- V2: pass null directly (not ?? undefined) so auto-scaling is active by default.
|
||||
null → Element_fit_text auto-scales; a number → manual override from print controls. -->
|
||||
<Comp_badge_obj_view_v2
|
||||
event_id={$lq__event_badge_obj.event_id as string}
|
||||
event_badge_id={event_badge_id as string}
|
||||
{lq__event_badge_obj}
|
||||
{lq__event_badge_template_obj}
|
||||
is_review_mode={false}
|
||||
font_size_name={font_size_name}
|
||||
font_size_title={font_size_title}
|
||||
font_size_affiliations={font_size_affiliations}
|
||||
font_size_location={font_size_location}
|
||||
/>
|
||||
{:else}
|
||||
<Comp_badge_obj_view
|
||||
event_id={$lq__event_badge_obj.event_id as string}
|
||||
event_badge_id={event_badge_id as string}
|
||||
{lq__event_badge_obj}
|
||||
{lq__event_badge_template_obj}
|
||||
is_review_mode={false}
|
||||
font_size_name={font_size_name ?? undefined}
|
||||
font_size_title={font_size_title ?? undefined}
|
||||
font_size_affiliations={font_size_affiliations ?? undefined}
|
||||
font_size_location={font_size_location ?? undefined}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Controls panel: fixed to the right edge of the viewport — screen-only.
|
||||
|
||||
Reference in New Issue
Block a user