feat(badges): configurable punch-out hole markers for badge clip slots
Adds cfg_json.punch_holes.{left,right,center} to mark pre-perforated badge
clip slots with X overlays. Slots are 5/8in x 1/8in, 1/4in from top,
3/8in from left/right edges. Markers print on the badge so attendees know
where to push out the perforations. Template form exposes checkboxes in
Header & Branding. Documented in MODULE__AE_Events_Badge_Templates.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -274,6 +274,24 @@ Per-layout height overrides for the auto-scaling text zones. Set any subset —
|
||||
| `affiliations` | Height of the affiliations text zone |
|
||||
| `location` | Height of the location text zone |
|
||||
|
||||
### Punch-Out Hole Markers (`punch_holes`)
|
||||
|
||||
Enables X overlays at the physical badge clip slot positions. Slots are pre-perforated on the badge stock — the markers print on the badge so attendees know where to push them out.
|
||||
|
||||
**Slot dimensions:** 5/8″ wide × 1/8″ tall, 1/4″ from top edge, 3/8″ from left/right edges. Center slot is horizontally centered.
|
||||
|
||||
```json
|
||||
"punch_holes": { "left": true, "right": true, "center": false }
|
||||
```
|
||||
|
||||
| Key | Default | Notes |
|
||||
| --- | --- | --- |
|
||||
| `punch_holes.left` | `false` | Left clip slot marker |
|
||||
| `punch_holes.right` | `false` | Right clip slot marker |
|
||||
| `punch_holes.center` | `false` | Center clip slot marker (less common) |
|
||||
|
||||
---
|
||||
|
||||
### Controls Panel (`controls_cfg`)
|
||||
|
||||
Controls which fields appear in the print controls panel for non-trusted users, and which fields authenticated users may edit. Trusted + Edit Mode always sees and can edit all fields regardless of this config.
|
||||
|
||||
@@ -52,6 +52,16 @@ export interface BadgeTemplateCfg {
|
||||
// Any CSS length: "0.5in", "1rem". Unset = no extra padding.
|
||||
header_padding_bottom?: string;
|
||||
|
||||
// Punch-out hole markers: show X overlays at the physical badge clip slot positions.
|
||||
// Slots are pre-perforated on the badge stock — markers guide attendees to push them out.
|
||||
// Hole dimensions: 5/8in wide × 1/8in tall, 1/4in from top, 3/8in from left/right edges.
|
||||
// Center hole: horizontally centered, same vertical position.
|
||||
punch_holes?: {
|
||||
left?: boolean;
|
||||
right?: boolean;
|
||||
center?: boolean;
|
||||
};
|
||||
|
||||
// Allow arbitrary extra keys to preserve forward-compatibility.
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
@@ -326,6 +326,12 @@ let header_margin_top = $derived.by(() => {
|
||||
return v && typeof v === 'string' && v.trim() ? v.trim() : '2rem';
|
||||
});
|
||||
|
||||
// Punch-out hole markers: which of the three physical badge clip slots to mark.
|
||||
// Holes are 5/8in × 1/8in, 1/4in from top, 3/8in from left/right edges (center: horizontally centered).
|
||||
let punch_holes_left = $derived(!!template_cfg?.punch_holes?.left);
|
||||
let punch_holes_right = $derived(!!template_cfg?.punch_holes?.right);
|
||||
let punch_holes_center = $derived(!!template_cfg?.punch_holes?.center);
|
||||
|
||||
// Full inline style string for the badge_header div.
|
||||
// Combines margin-top, optional bottom border (color + width), and optional bottom padding.
|
||||
// Unset border_color = no border drawn.
|
||||
@@ -662,6 +668,43 @@ const code_to_icon: {
|
||||
{#if show_badge_back}Front of badge{:else}Badge preview{/if}
|
||||
</span>
|
||||
|
||||
<!-- Punch-out hole markers: X overlays at physical badge clip slot positions.
|
||||
Slots: 5/8in wide × 1/8in tall, 1/4in from top, 3/8in from left/right edges.
|
||||
Center slot: horizontally centered. Markers print so attendees know to push them out. -->
|
||||
{#if punch_holes_left}
|
||||
<div class="punch_hole_marker pointer-events-none absolute"
|
||||
aria-hidden="true"
|
||||
style="top: 0.25in; left: 0.375in; width: 0.625in; height: 0.125in;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 50 10" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" y="0.5" width="49" height="9" fill="rgba(255,255,255,0.4)" stroke="#777" stroke-width="1" stroke-dasharray="4,2"/>
|
||||
<line x1="0" y1="0" x2="50" y2="10" stroke="#777" stroke-width="1"/>
|
||||
<line x1="50" y1="0" x2="0" y2="10" stroke="#777" stroke-width="1"/>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
{#if punch_holes_right}
|
||||
<div class="punch_hole_marker pointer-events-none absolute"
|
||||
aria-hidden="true"
|
||||
style="top: 0.25in; right: 0.375in; width: 0.625in; height: 0.125in;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 50 10" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" y="0.5" width="49" height="9" fill="rgba(255,255,255,0.4)" stroke="#777" stroke-width="1" stroke-dasharray="4,2"/>
|
||||
<line x1="0" y1="0" x2="50" y2="10" stroke="#777" stroke-width="1"/>
|
||||
<line x1="50" y1="0" x2="0" y2="10" stroke="#777" stroke-width="1"/>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
{#if punch_holes_center}
|
||||
<div class="punch_hole_marker pointer-events-none absolute"
|
||||
aria-hidden="true"
|
||||
style="top: 0.25in; left: 50%; transform: translateX(-50%); width: 0.625in; height: 0.125in;">
|
||||
<svg width="100%" height="100%" viewBox="0 0 50 10" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" y="0.5" width="49" height="9" fill="rgba(255,255,255,0.4)" stroke="#777" stroke-width="1" stroke-dasharray="4,2"/>
|
||||
<line x1="0" y1="0" x2="50" y2="10" stroke="#777" stroke-width="1"/>
|
||||
<line x1="50" y1="0" x2="0" y2="10" stroke="#777" stroke-width="1"/>
|
||||
</svg>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Background image bleed container.
|
||||
Absolutely positioned so it can extend past badge_front's edges by bleed_offset
|
||||
(e.g. "-0.125in" on all sides). z-index: -1 keeps it behind all badge content.
|
||||
|
||||
@@ -67,6 +67,10 @@ let cfg_body_text_color = $state('#000000');
|
||||
let cfg_bleed = $state('');
|
||||
// Header image vertical offset (CSS length). Empty = default 2rem. Negative = shift up.
|
||||
let cfg_header_margin_top = $state('');
|
||||
// Punch-out hole markers (left/right/center badge clip slots).
|
||||
let cfg_punch_holes_left = $state(false);
|
||||
let cfg_punch_holes_right = $state(false);
|
||||
let cfg_punch_holes_center = $state(false);
|
||||
// Header bottom border. Empty color = no border.
|
||||
let cfg_header_border_color = $state('');
|
||||
let cfg_header_border_width = $state('');
|
||||
@@ -168,6 +172,9 @@ async function load_template(id: string) {
|
||||
// Background bleed (CSS length, e.g. "0.125in"). Empty = no bleed.
|
||||
cfg_bleed = parsed_cfg.bleed ?? '';
|
||||
cfg_header_margin_top = parsed_cfg.header_margin_top ?? '';
|
||||
cfg_punch_holes_left = parsed_cfg?.punch_holes?.left ?? false;
|
||||
cfg_punch_holes_right = parsed_cfg?.punch_holes?.right ?? false;
|
||||
cfg_punch_holes_center = parsed_cfg?.punch_holes?.center ?? false;
|
||||
cfg_header_border_color = parsed_cfg.header_border_color ?? '';
|
||||
cfg_header_border_width = parsed_cfg.header_border_width ?? '';
|
||||
cfg_header_padding_bottom = parsed_cfg.header_padding_bottom ?? '';
|
||||
@@ -249,6 +256,17 @@ async function handle_submit() {
|
||||
delete cfg_obj.bleed;
|
||||
}
|
||||
|
||||
// Punch-out hole markers: save the nested object only when at least one is enabled
|
||||
if (cfg_punch_holes_left || cfg_punch_holes_right || cfg_punch_holes_center) {
|
||||
cfg_obj.punch_holes = {
|
||||
left: cfg_punch_holes_left,
|
||||
right: cfg_punch_holes_right,
|
||||
center: cfg_punch_holes_center
|
||||
};
|
||||
} else {
|
||||
delete cfg_obj.punch_holes;
|
||||
}
|
||||
|
||||
// Header image vertical offset: save if set, remove key when cleared (falls back to 2rem default)
|
||||
if (cfg_header_margin_top.trim()) {
|
||||
cfg_obj.header_margin_top = cfg_header_margin_top.trim();
|
||||
@@ -381,6 +399,28 @@ function toggle_cfg_controls_auth_editable(key: string) {
|
||||
<span>Header Path (URL) — top banner image (used when no background image)</span>
|
||||
<input type="text" bind:value={header_path} class="input" />
|
||||
</label>
|
||||
<div class="space-y-1">
|
||||
<p class="text-sm font-medium">Punch-Out Hole Markers</p>
|
||||
<p class="text-xs text-surface-400 italic">
|
||||
Show X overlays at the physical badge clip slot positions (5/8″ × 1/8″ slots, 1/4″ from top).
|
||||
Markers help attendees know where to push out the perforations.
|
||||
</p>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_punch_holes_left} class="checkbox" />
|
||||
<span>Left slot</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_punch_holes_right} class="checkbox" />
|
||||
<span>Right slot</span>
|
||||
</label>
|
||||
<label class="label flex items-center gap-2">
|
||||
<input type="checkbox" bind:checked={cfg_punch_holes_center} class="checkbox" />
|
||||
<span>Center slot</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="label">
|
||||
<span>Header Image Vertical Offset</span>
|
||||
<input type="text" bind:value={cfg_header_margin_top} class="input" placeholder="e.g. 0.5in, -4px, 1rem (default: 2rem)" />
|
||||
|
||||
Reference in New Issue
Block a user