diff --git a/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte b/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte index 4c31af8b..2a7a4d87 100644 --- a/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte +++ b/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_obj_view.svelte @@ -43,6 +43,13 @@ * Set false via the controls panel to see the image at its natural size (dev/testing). */ banner_full_width?: boolean; + /** + * When true (default), the person's name is split at the last space and rendered + * on two lines. Gives short names like "Scott Idem" the same two-line visual weight + * as long names. Set false for names that genuinely read better on one line. + * Toggle lives in the controls panel; persisted in localStorage per workstation. + */ + name_two_lines?: boolean; log_lvl?: number; } @@ -58,6 +65,7 @@ font_size_location, preview_overrides = null, banner_full_width = true, + name_two_lines = true, }: Props = $props(); // Badge layout CSS — compiled in, hot-reloads in dev. @@ -102,6 +110,19 @@ let display_name = $derived( eff_badge?.full_name_override ?? eff_badge?.full_name ?? '' ); + + // Two-line name padding: when name_two_lines is on, apply horizontal padding scaled + // to name length so CSS word-wrap does the break naturally — no hard
needed. + // Short names (≤12 chars) get heavy padding to force a break at the space. + // Progressively less padding as names get longer; very long names wrap on their own. + // Breakpoints are character-count heuristics — adjust if specific names look off. + // name_pad_* — three tiers of horizontal padding to coax short names into two lines. + // Svelte scopes component style rules via compile-time class name hashing; dynamic class + // strings are invisible to the compiler so the hash never matches. class:name={cond} is the + // correct pattern — the literal class names appear in the template for the compiler to find. + let name_pad_short = $derived(name_two_lines && display_name.trim().length <= 12); + let name_pad_mid = $derived(name_two_lines && display_name.trim().length > 12 && display_name.trim().length <= 20); + let name_pad_long = $derived(name_two_lines && display_name.trim().length > 20 && display_name.trim().length <= 28); let display_title = $derived( eff_badge?.professional_title_override ?? eff_badge?.professional_title ?? '' ); @@ -258,20 +279,20 @@ const [bg, fg] = palettes[effective_badge_type_code?.toLowerCase() ?? ''] ?? ['#f1f5f9', '#64748b']; // Each entry: [svg string, tile size]. Swirls use a larger tile for smoother repeats. const patterns: [string, string][] = [ - // 1: diagonal stripes - [``, '24px 24px'], - // 2: polka dots - [``, '24px 24px'], - // 3: diamonds - [``, '24px 24px'], - // 4: grid - [``, '24px 24px'], - // 5: swirls — two crossing S-curves (yin-yang tiling) + echo curves for depth. - [``, '64px 64px'], - // 6: Inch Calibration Grid (1 inch squares, 0.25 inch subdivisions). - [`1in`, '96px 96px'], - // 7: Metric Calibration Grid (10mm squares, 1mm subdivisions). + // 1: Metric Calibration Grid (10mm squares, 1mm subdivisions). [`1cm`, '37.795px 37.795px'], + // 2: Inch Calibration Grid (1 inch squares, 0.25 inch subdivisions). + [`1in`, '96px 96px'], + // 3: swirls — two crossing S-curves (yin-yang tiling) + echo curves for depth. + [``, '64px 64px'], + // 4: diagonal stripes + [``, '24px 24px'], + // 5: polka dots + [``, '24px 24px'], + // 6: diamonds + [``, '24px 24px'], + // 7: grid + [``, '24px 24px'], ]; const [svg, size] = patterns[(demo_bg_state - 1) % DEMO_BG_PATTERNS]; return `background-image: url("data:image/svg+xml,${encodeURIComponent(svg)}"); background-repeat: repeat; background-size: ${size};`; @@ -336,7 +357,7 @@ onclick={cycle_demo_bg} title="Cycle demo background pattern — edit mode only" > - 🎨 {demo_bg_state > 0 ? ['stripes','dots','diamonds','grid','swirls','in-grid','mm-grid'][demo_bg_state - 1] : 'demo bg'} + 🎨 {demo_bg_state > 0 ? ['mm-grid','in-grid','swirls','stripes','dots','diamonds','grid'][demo_bg_state - 1] : 'demo bg'} {/if} @@ -348,7 +369,6 @@ flex flex-row flex-wrap gap-4 items-stretch justify-center p-2 mx-auto - outline-2 outline-dashed outline-blue-500 min-h-[6.0in] max-w-[8.5in] overflow-visible " @@ -367,7 +387,7 @@ p-0 m-0 overflow-visible text-center relative - outline-4 outline-red-500/50 hover:outline-red-700/75 + hover:outline-2 hover:outline-dashed hover:outline-red-500/75 group " style={demo_bg_style} @@ -455,15 +475,20 @@ max={80} manual_size={font_size_name ?? null} height={fit_heights.name} - class="full_name_override_all hover:bg-pink-100/50" + class="full_name_override_all hover:bg-pink-100/50 leading-none" > - +
{#if display_name} - {@html display_name.trim()} + {display_name.trim()} {:else} -- no name -- {/if} - +
{#if display_title} @@ -476,7 +501,7 @@ max={38} manual_size={font_size_title ?? null} height={fit_heights.title} - class="professional_title italic hover:bg-pink-100/50" + class="professional_title italic hover:bg-pink-100/50 leading-none" > {@html display_title} @@ -508,7 +533,7 @@ max={40} manual_size={font_size_affiliations ?? null} height={fit_heights.affiliations} - class="affiliations hover:bg-pink-100/50" + class="affiliations hover:bg-pink-100/50 leading-none" > {@html display_affiliations} @@ -524,7 +549,7 @@ max={34} manual_size={font_size_location ?? null} height={fit_heights.location} - class="location hover:bg-pink-100/50" + class="location hover:bg-pink-100/50 leading-none" > {@html display_location} @@ -974,6 +999,7 @@ .badge_front, .badge_back { outline: none !important; + box-shadow: none !important; } } @@ -981,8 +1007,37 @@ .badge_back { background-color: white; color: #1a1a1a; + /* + * Standard badge/card corner radius: 3.18mm = 1/8 inch = ~12px at 96dpi. + * Matches the physical die-cut corners of the printed card stock. + */ + border-radius: 12px; + /* + * Layered card shadow: gives each badge face a strong physical-card feel on screen. + * Three layers: crisp base edge → medium spread → wide ambient — creates + * convincing depth without looking like a UI box-shadow. + * Suppressed on print — no decoration on the physical output. + */ + box-shadow: + 0 1px 3px rgba(0, 0, 0, 0.20), + 0 6px 20px rgba(0, 0, 0, 0.14), + 0 16px 48px rgba(0, 0, 0, 0.08); } + /* + * Two-line name padding: applied to the Element_fit_text wrapper div (not the inner span). + * Squeezes the available text width so CSS word-wrap breaks the name at its natural space + * — no hard
needed. Padding on the block div also ensures text-align:center works + * correctly for all wrapped lines (inline-element padding breaks centering on each line). + * Short names (≤12 chars, e.g. "Scott Idem") need heavy padding to force the break. + * Progressively less padding as names get longer; very long names wrap on their own. + * Percentages are relative to the Element_fit_text div width, so the effect scales + * correctly regardless of badge template or font size. + */ + .name_pad_short { padding-left: 18%; padding-right: 18%; } + .name_pad_mid { padding-left: 8%; padding-right: 8%; } + .name_pad_long { padding-left: 2%; padding-right: 2%; } + /* * Header image: center horizontally within the badge. * defaults to display:inline, which left-aligns any image narrower than diff --git a/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_print_controls.svelte b/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_print_controls.svelte index 26d716fb..410dca67 100644 --- a/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_print_controls.svelte +++ b/src/routes/events/[event_id]/(badges)/badges/[badge_id]/ae_comp__badge_print_controls.svelte @@ -60,6 +60,8 @@ * Stored in localStorage. */ banner_full_width?: boolean; + /** When true (default), name uses padding to prefer two-line layout. Stored in localStorage. */ + name_two_lines?: boolean; log_lvl?: number; } @@ -77,6 +79,7 @@ print_offset_y = $bindable(0), hide_chrome = $bindable(false), banner_full_width = $bindable(true), + name_two_lines = $bindable(true), log_lvl = 0 }: Props = $props(); @@ -929,6 +932,18 @@ + +
+ +
+
@@ -403,6 +409,7 @@ bind:print_offset_y bind:hide_chrome bind:banner_full_width + bind:name_two_lines /> diff --git a/static/ae-print-badge.css b/static/ae-print-badge.css index 05ed6c92..9a6a84a9 100644 --- a/static/ae-print-badge.css +++ b/static/ae-print-badge.css @@ -107,9 +107,10 @@ outline: 3px dashed purple !important; outline-offset: -12px !important; } - /* Cyan = the actual badge — should be dead-center on page */ + /* Red = the actual badge — should be dead-center on page. + Thick + high-contrast so misalignment vs. physical card stock is obvious. */ html.debug_outlines .event_badge_wrapper { - outline: 3px solid cyan !important; - outline-offset: 2px !important; + outline: 6px solid red !important; + outline-offset: 3px !important; } } diff --git a/static/badge_header_calibration.svg b/static/badge_header_calibration.svg index 5c5e25e5..ac795a20 100644 --- a/static/badge_header_calibration.svg +++ b/static/badge_header_calibration.svg @@ -1,6 +1,168 @@ - - - - CLIP SLOT DANGER ZONE + + + + + + + + + + + mm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10 + + 20 + + 30 + + 40 + + 50 + + 60 + + 70 + + 80 + + + + + + + + in + + + + + + + + + + + + + + + + + + 1 + + 2 + + 3 + + + + + + + + CLIP SLOT DANGER ZONE + + + + + + + +