feat(badges): configurable header bottom border and padding per template
Replaces hardcoded border-bottom/padding-bottom on badge_header div with cfg_json fields: header_border_color, header_border_width, header_padding_bottom. Empty color = no border. Template form exposes all three in Header & Branding. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -39,10 +39,19 @@ export interface BadgeTemplateCfg {
|
|||||||
// Header image vertical offset. CSS length applied as margin-top on the badge_header div.
|
// Header image vertical offset. CSS length applied as margin-top on the badge_header div.
|
||||||
// Default (unset) = "2rem" (matches the prior hardcoded mt-8).
|
// Default (unset) = "2rem" (matches the prior hardcoded mt-8).
|
||||||
// Negative values shift the image toward the top edge; larger values push it down.
|
// Negative values shift the image toward the top edge; larger values push it down.
|
||||||
// Useful when a background image's designed zone doesn't align with the default position.
|
|
||||||
// Any CSS length works: "-0.5in", "1rem", "8px".
|
// Any CSS length works: "-0.5in", "1rem", "8px".
|
||||||
header_margin_top?: string;
|
header_margin_top?: string;
|
||||||
|
|
||||||
|
// Border drawn below the badge header image. Set header_border_color to enable.
|
||||||
|
// Unset = no border (default). Any valid CSS hex color.
|
||||||
|
header_border_color?: string;
|
||||||
|
// Thickness of the header bottom border. Any CSS length. Default "2px" when color is set.
|
||||||
|
header_border_width?: string;
|
||||||
|
// Padding below the header image (inside the badge_header div, above the border).
|
||||||
|
// Useful for creating visual space between the image and the body.
|
||||||
|
// Any CSS length: "0.5in", "1rem". Unset = no extra padding.
|
||||||
|
header_padding_bottom?: string;
|
||||||
|
|
||||||
// Allow arbitrary extra keys to preserve forward-compatibility.
|
// Allow arbitrary extra keys to preserve forward-compatibility.
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,6 +326,21 @@ let header_margin_top = $derived.by(() => {
|
|||||||
return v && typeof v === 'string' && v.trim() ? v.trim() : '2rem';
|
return v && typeof v === 'string' && v.trim() ? v.trim() : '2rem';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
let header_div_style = $derived.by(() => {
|
||||||
|
const parts: string[] = [`margin-top: ${header_margin_top}`];
|
||||||
|
const color = (template_cfg?.header_border_color ?? '').trim();
|
||||||
|
if (color) {
|
||||||
|
const width = (template_cfg?.header_border_width ?? '').trim() || '2px';
|
||||||
|
parts.push(`border-bottom: ${width} solid ${color}`);
|
||||||
|
}
|
||||||
|
const pb = (template_cfg?.header_padding_bottom ?? '').trim();
|
||||||
|
if (pb) parts.push(`padding-bottom: ${pb}`);
|
||||||
|
return parts.join('; ');
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Layout-aware section heights for Element_fit_text.
|
* Layout-aware section heights for Element_fit_text.
|
||||||
*
|
*
|
||||||
@@ -676,7 +691,7 @@ const code_to_icon: {
|
|||||||
p-2
|
p-2
|
||||||
hover:outline-2 hover:outline-gray-500/75 hover:outline-dashed
|
hover:outline-2 hover:outline-gray-500/75 hover:outline-dashed
|
||||||
"
|
"
|
||||||
style="margin-top: {header_margin_top};">
|
style={header_div_style}>
|
||||||
<img
|
<img
|
||||||
class="header_image"
|
class="header_image"
|
||||||
class:header_full_width={banner_full_width}
|
class:header_full_width={banner_full_width}
|
||||||
@@ -691,9 +706,11 @@ const code_to_icon: {
|
|||||||
alt="check badge logo" />
|
alt="check badge logo" />
|
||||||
<div class="banner_text">
|
<div class="banner_text">
|
||||||
<div class="row_one">
|
<div class="row_one">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.header_row_1}
|
{@html $lq__event_badge_template_obj.header_row_1}
|
||||||
</div>
|
</div>
|
||||||
<div class="row_two">
|
<div class="row_two">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.header_row_2}
|
{@html $lq__event_badge_template_obj.header_row_2}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -732,7 +749,7 @@ const code_to_icon: {
|
|||||||
"
|
"
|
||||||
style="height: {fit_heights.grp_name_title}; justify-content: {flex_justify(
|
style="height: {fit_heights.grp_name_title}; justify-content: {flex_justify(
|
||||||
fit_heights.grp_name_title_flex
|
fit_heights.grp_name_title_flex
|
||||||
)}">
|
)};">
|
||||||
<!--
|
<!--
|
||||||
V2: Element_fit_text wraps display mode only — no inline edit inputs.
|
V2: Element_fit_text wraps display mode only — no inline edit inputs.
|
||||||
manual_size={font_size_name ?? null} disables auto-scaling when the
|
manual_size={font_size_name ?? null} disables auto-scaling when the
|
||||||
@@ -785,6 +802,7 @@ const code_to_icon: {
|
|||||||
flex items-center justify-start
|
flex items-center justify-start
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
<div style="text-align: {align_title};">{@html display_title}</div>
|
<div style="text-align: {align_title};">{@html display_title}</div>
|
||||||
</Element_fit_text>
|
</Element_fit_text>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -822,6 +840,7 @@ const code_to_icon: {
|
|||||||
flex items-center justify-start
|
flex items-center justify-start
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
<div style="text-align: {align_affiliations};">{@html display_affiliations}</div>
|
<div style="text-align: {align_affiliations};">{@html display_affiliations}</div>
|
||||||
</Element_fit_text>
|
</Element_fit_text>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -837,6 +856,7 @@ const code_to_icon: {
|
|||||||
manual_size={font_size_location ?? null}
|
manual_size={font_size_location ?? null}
|
||||||
height={fit_heights.location}
|
height={fit_heights.location}
|
||||||
class="location leading-none hover:bg-pink-100/50">
|
class="location leading-none hover:bg-pink-100/50">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
<div class="city state_province country" style="text-align: {align_location};">{@html display_location}</div>
|
<div class="city state_province country" style="text-align: {align_location};">{@html display_location}</div>
|
||||||
</Element_fit_text>
|
</Element_fit_text>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -987,9 +1007,11 @@ const code_to_icon: {
|
|||||||
alt="check badge logo" />
|
alt="check badge logo" />
|
||||||
<div class="banner_text">
|
<div class="banner_text">
|
||||||
<div class="row_one">
|
<div class="row_one">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.header_row_1}
|
{@html $lq__event_badge_template_obj.header_row_1}
|
||||||
</div>
|
</div>
|
||||||
<div class="row_two">
|
<div class="row_two">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.header_row_2}
|
{@html $lq__event_badge_template_obj.header_row_2}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1079,26 +1101,31 @@ const code_to_icon: {
|
|||||||
<ul>
|
<ul>
|
||||||
{#if $lq__event_badge_obj.ticket_1_code}
|
{#if $lq__event_badge_obj.ticket_1_code}
|
||||||
<li>
|
<li>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.ticket_1_text}
|
{@html $lq__event_badge_template_obj.ticket_1_text}
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $lq__event_badge_obj.ticket_2_code}
|
{#if $lq__event_badge_obj.ticket_2_code}
|
||||||
<li>
|
<li>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.ticket_2_text}
|
{@html $lq__event_badge_template_obj.ticket_2_text}
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $lq__event_badge_obj.ticket_3_code}
|
{#if $lq__event_badge_obj.ticket_3_code}
|
||||||
<li>
|
<li>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.ticket_3_text}
|
{@html $lq__event_badge_template_obj.ticket_3_text}
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $lq__event_badge_obj.ticket_4_code}
|
{#if $lq__event_badge_obj.ticket_4_code}
|
||||||
<li>
|
<li>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.ticket_4_text}
|
{@html $lq__event_badge_template_obj.ticket_4_text}
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $lq__event_badge_obj.ticket_5_code}
|
{#if $lq__event_badge_obj.ticket_5_code}
|
||||||
<li>
|
<li>
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
{@html $lq__event_badge_template_obj.ticket_5_text}
|
{@html $lq__event_badge_template_obj.ticket_5_text}
|
||||||
</li>
|
</li>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -67,6 +67,11 @@ let cfg_body_text_color = $state('#000000');
|
|||||||
let cfg_bleed = $state('');
|
let cfg_bleed = $state('');
|
||||||
// Header image vertical offset (CSS length). Empty = default 2rem. Negative = shift up.
|
// Header image vertical offset (CSS length). Empty = default 2rem. Negative = shift up.
|
||||||
let cfg_header_margin_top = $state('');
|
let cfg_header_margin_top = $state('');
|
||||||
|
// Header bottom border. Empty color = no border.
|
||||||
|
let cfg_header_border_color = $state('');
|
||||||
|
let cfg_header_border_width = $state('');
|
||||||
|
// Padding below the header image (above the border line). Empty = no extra padding.
|
||||||
|
let cfg_header_padding_bottom = $state('');
|
||||||
// Alignment overrides: 'left' | 'center' | 'right' | 'justify'
|
// Alignment overrides: 'left' | 'center' | 'right' | 'justify'
|
||||||
let cfg_align_name = $state('center');
|
let cfg_align_name = $state('center');
|
||||||
let cfg_align_title = $state('center');
|
let cfg_align_title = $state('center');
|
||||||
@@ -163,6 +168,9 @@ async function load_template(id: string) {
|
|||||||
// Background bleed (CSS length, e.g. "0.125in"). Empty = no bleed.
|
// Background bleed (CSS length, e.g. "0.125in"). Empty = no bleed.
|
||||||
cfg_bleed = parsed_cfg.bleed ?? '';
|
cfg_bleed = parsed_cfg.bleed ?? '';
|
||||||
cfg_header_margin_top = parsed_cfg.header_margin_top ?? '';
|
cfg_header_margin_top = parsed_cfg.header_margin_top ?? '';
|
||||||
|
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 ?? '';
|
||||||
|
|
||||||
// Alignment overrides (nested under cfg_json.align and cfg_json.qr_alignment)
|
// Alignment overrides (nested under cfg_json.align and cfg_json.qr_alignment)
|
||||||
cfg_align_name = parsed_cfg?.align?.name ?? parsed_cfg.align_name ?? 'center';
|
cfg_align_name = parsed_cfg?.align?.name ?? parsed_cfg.align_name ?? 'center';
|
||||||
@@ -248,6 +256,26 @@ async function handle_submit() {
|
|||||||
delete cfg_obj.header_margin_top;
|
delete cfg_obj.header_margin_top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Header bottom border: save color + width if color is set, remove both if cleared
|
||||||
|
if (cfg_header_border_color.trim()) {
|
||||||
|
cfg_obj.header_border_color = cfg_header_border_color.trim();
|
||||||
|
if (cfg_header_border_width.trim()) {
|
||||||
|
cfg_obj.header_border_width = cfg_header_border_width.trim();
|
||||||
|
} else {
|
||||||
|
delete cfg_obj.header_border_width;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
delete cfg_obj.header_border_color;
|
||||||
|
delete cfg_obj.header_border_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header padding below image
|
||||||
|
if (cfg_header_padding_bottom.trim()) {
|
||||||
|
cfg_obj.header_padding_bottom = cfg_header_padding_bottom.trim();
|
||||||
|
} else {
|
||||||
|
delete cfg_obj.header_padding_bottom;
|
||||||
|
}
|
||||||
|
|
||||||
const data_to_save: key_val = {
|
const data_to_save: key_val = {
|
||||||
name,
|
name,
|
||||||
background_image_path,
|
background_image_path,
|
||||||
@@ -361,6 +389,30 @@ function toggle_cfg_controls_auth_editable(key: string) {
|
|||||||
Any CSS length works. Leave blank to use the default (2rem).
|
Any CSS length works. Leave blank to use the default (2rem).
|
||||||
</p>
|
</p>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<p class="text-sm font-medium">Header Bottom Border</p>
|
||||||
|
<label class="label">
|
||||||
|
<span>Border Color (hex) — leave blank to disable border</span>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<input type="color" bind:value={cfg_header_border_color} class="w-10 h-8 p-0"
|
||||||
|
oninput={(e) => { if ((e.target as HTMLInputElement).value === '#000000' && !cfg_header_border_color) cfg_header_border_color = ''; }} />
|
||||||
|
<input type="text" bind:value={cfg_header_border_color} class="input w-36" placeholder="e.g. #FE6111 or empty" />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<span>Border Thickness</span>
|
||||||
|
<input type="text" bind:value={cfg_header_border_width} class="input" placeholder="e.g. 2px, 0.5mm (default: 2px)" />
|
||||||
|
</label>
|
||||||
|
<label class="label">
|
||||||
|
<span>Padding Below Header Image</span>
|
||||||
|
<input type="text" bind:value={cfg_header_padding_bottom} class="input" placeholder="e.g. 1.45in, 2rem — leave blank for none" />
|
||||||
|
<p class="text-xs text-surface-400 italic">
|
||||||
|
Creates space between the header image and the border line.
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label class="label">
|
<label class="label">
|
||||||
<span>Logo Path (URL, if no Header Path)</span>
|
<span>Logo Path (URL, if no Header Path)</span>
|
||||||
<input type="text" bind:value={logo_path} class="input" />
|
<input type="text" bind:value={logo_path} class="input" />
|
||||||
|
|||||||
Reference in New Issue
Block a user