feat(badges): layout CSS system — data-layout attribute, @page injection, style_href

Two compiled layout CSS files in src/lib/ae_events/badges/css/:
  - badge_layout_epson_4x5_fanfold.css — 4"×5" per side, Epson ColorWorks C3500
    fanfold duplex; preferred for general conference use (ISHLT, demos)
  - badge_layout_zebra_zc10l_pvc.css — 3.5"×5.5" PVC card, Zebra ZC10L,
    single-sided (pair with duplex=0 on template)

Rules scoped under [data-layout="..."] on the wrapper — beats Tailwind utility
class specificity without !important. Vite hot-reloads these in dev.

Badge component: add data-layout attribute from template.layout field; import
both layout CSS files (falls back to Tailwind 4"×6" defaults if layout unset).

Print page svelte:head: inject @page paper-size rule per layout code
(@page cannot use attribute selectors so it must be dynamic). Also wire
style_href as a <link> for per-event external client branding CSS.

db_events.ts Badge_template interface: add style_href and duplex fields.
MODULE doc: update layout codes table with badge_4x5_fanfold entry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scott Idem
2026-03-02 16:53:32 -05:00
parent 51cfcbf2d6
commit 32e9550ca2
6 changed files with 129 additions and 8 deletions

View File

@@ -176,15 +176,19 @@ to avoid bleeding into the rest of the app. The classes used in
The `layout` field encodes physical badge stock dimensions. Standard codes to use:
| Code | Dimensions | Description |
|---|---|---|
| `badge_3.5x5.5_pvc` | 3.5" × 5.5" (88.9 × 139.7mm) | PVC card, Zebra ZC10L |
| `badge_4x6_fanfold` | 4" × 6" (101.6 × 152.4mm) | Fanfold paper badge |
| `badge_4x6_fanfold_tickets` | 4" × 6" + tear-offs | Fanfold with ticket stubs |
| Code | Dimensions | CSS file | Description |
| --- | --- | --- | --- |
| `badge_4x5_fanfold` | 4" × 5" (101.6 × 127mm) | `badge_layout_epson_4x5_fanfold.css` | Epson ColorWorks C3500 / ExpoBadge fanfold — preferred for general conference use (ISHLT, demos) |
| `badge_3.5x5.5_pvc` | 3.5" × 5.5" (88.9 × 139.7mm) | `badge_layout_zebra_zc10l_pvc.css` | PVC card, Zebra ZC10L — single-sided, set `duplex=0` |
| `badge_4x6_fanfold` | 4" × 6" (101.6 × 152.4mm) | *(none — Tailwind defaults)* | Generic fanfold fallback; dimensions match the hardcoded Tailwind values |
| `badge_4x6_fanfold_tickets` | 4" × 6" + tear-offs | *(pending)* | Fanfold with ticket stubs |
The current badge component is hard-coded to `w-[4in]` / `min-h-[6.0in]`. These
dimensions will need to be made template-driven once the `layout` field is wired up.
For now, override via print CSS in the `style_href` stylesheet.
Layout CSS files live in `src/lib/ae_events/badges/css/` and are imported by
`ae_comp__badge_obj_view.svelte`. Rules are scoped under `[data-layout="..."]` on the
wrapper so multiple layouts can coexist in the bundle without conflict.
`@page` paper size rules are injected per-layout from `print/+page.svelte <svelte:head>`
(attribute selectors cannot scope `@page` rules, so they're handled dynamically).
---

View File

@@ -0,0 +1,42 @@
/* =============================================================================
Badge Layout: Epson ColorWorks C3500/C4000 / ExpoBadge 4" × 5" Fanfold (Duplex)
layout code: badge_4x5_fanfold
Badge stock: 4in wide × 5in per side, double-sided, continuous fanfold
Used for: ISHLT, general conference use, preferred demo layout
Print behavior:
Front (5") and back (5") render stacked on screen. When printed, the browser
sends a 4" × 10" strip to the Epson ColorWorks driver. @page size is injected
dynamically by print/+page.svelte <svelte:head> based on the layout field.
CSS scope:
All rules scoped under [data-layout="badge_4x5_fanfold"] to avoid conflicts
with other layouts rendered on the same page. These override the Tailwind
utility classes (w-[4in], min-h-[6.0in], etc.) that are hardcoded on the
badge sections — attribute + class selectors win over single class selectors.
============================================================================= */
/* --- Badge front --- */
[data-layout="badge_4x5_fanfold"] .badge_front {
width: 4in;
min-height: 5in;
max-height: 5in;
}
/* Body area: 5in total ~1in header ~0.5in footer = ~3.5in for content */
[data-layout="badge_4x5_fanfold"] .badge_body {
max-height: 3.5in;
}
/* --- Badge back --- */
[data-layout="badge_4x5_fanfold"] .badge_back {
width: 4in;
min-height: 5in;
max-height: 5in;
}
[data-layout="badge_4x5_fanfold"] .badge_back_content {
max-height: 4.5in;
}

View File

@@ -0,0 +1,29 @@
/* =============================================================================
Badge Layout: Zebra ZC10L — PVC Card 3.5" × 5.5"
layout code: badge_3.5x5.5_pvc
Badge stock: 3.5in × 5.5in PVC card (CR80 portrait equivalent)
Used for: Axonius NYC (April 2026), single-sided events
Print behavior:
Single-sided only. Set duplex=0 on the template — the badge_back section
will not render at all. @page size (3.5in × 5.5in) is injected dynamically
by print/+page.svelte <svelte:head> based on the layout field.
CSS scope:
All rules scoped under [data-layout="badge_3.5x5.5_pvc"] to avoid conflicts.
These override the hardcoded Tailwind utility classes on the badge sections.
============================================================================= */
/* --- Badge front --- */
[data-layout="badge_3.5x5.5_pvc"] .badge_front {
width: 3.5in;
min-height: 5.5in;
max-height: 5.5in;
}
/* Body area: 5.5in total ~1in header ~0.5in footer = ~4in for content.
Same as the current Tailwind default — no body override needed. */
/* No .badge_back rules — duplex=0 on the template means the back section
is not rendered at all (see show_badge_back in ae_comp__badge_obj_view.svelte). */

View File

@@ -251,6 +251,8 @@ export interface Badge_template {
layout?: null | string;
style_filename?: null | string;
style_href?: null | string;
duplex?: null | number | boolean;
enable?: null | boolean;
hide?: null | boolean;

View File

@@ -33,6 +33,12 @@
log_lvl = 0
}: Props = $props();
// Badge layout CSS — compiled in, hot-reloads in dev.
// Each file scopes rules under [data-layout="..."] on the wrapper section.
// @page rules (paper size) are injected per-template in print/+page.svelte.
import '$lib/ae_events/badges/css/badge_layout_epson_4x5_fanfold.css';
import '$lib/ae_events/badges/css/badge_layout_zebra_zc10l_pvc.css';
// *** Import Svelte specific
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
@@ -561,6 +567,7 @@ onkeypress={() => {
<section
id="event_badge_{$lq__event_badge_obj?.event_badge_id}"
data-layout={$lq__event_badge_template_obj?.layout ?? 'badge_4x6_fanfold'}
class="event_badge_wrapper event_badge print_area
flex flex-row flex-wrap gap-4
items-stretch justify-center

View File

@@ -122,6 +122,43 @@
{$lq__event_badge_obj?.full_name_override ?? $lq__event_badge_obj?.full_name ?? '—'}
{$events_loc?.title ? ` — ${$events_loc.title}` : ''}
</title>
<!--
@page rule sets paper size for the browser print dialog.
Scoped per layout code from the badge template.
Epson 4×5 fanfold: front (5") + back (5") = 10" strip, 4" wide.
Zebra ZC10L PVC: single card, 3.5" × 5.5", single-sided (duplex=0).
Default (4×6): front (6") + back (6") = 12" strip, 4" wide.
@page cannot use selector scoping — it must be top-level CSS — so we inject
it dynamically here based on the template's layout field rather than putting
it in the compiled layout CSS files.
-->
{#if $lq__event_badge_template_obj?.layout === 'badge_3.5x5.5_pvc'}
<style>
@page { size: 3.5in 5.5in; margin: 0; }
@media print { body { margin: 0; padding: 0; } .event_badge_wrapper { gap: 0 !important; padding: 0 !important; } }
</style>
{:else if $lq__event_badge_template_obj?.layout === 'badge_4x5_fanfold'}
<style>
@page { size: 4in 10in; margin: 0; }
@media print { body { margin: 0; padding: 0; } .event_badge_wrapper { gap: 0 !important; padding: 0 !important; } }
</style>
{:else}
<!-- Default: badge_4x6_fanfold or layout not yet set -->
<style>
@page { size: 4in 12in; margin: 0; }
@media print { body { margin: 0; padding: 0; } .event_badge_wrapper { gap: 0 !important; padding: 0 !important; } }
</style>
{/if}
<!-- External client CSS: brand colors, logo sizing, footer stripe colors per event.
Set style_href on the badge template. Edit the file on the static server and
refresh to update mid-event without any deployment. -->
{#if $lq__event_badge_template_obj?.style_href}
<link rel="stylesheet" href={$lq__event_badge_template_obj.style_href} />
{/if}
</svelte:head>
{#if $lq__event_badge_obj && $lq__event_badge_obj.event_id && event_badge_id}