preview-card — Generic karta s obrázkem, titulkem, metadaty, badges a footerem. Pro výpisy produktů, článků, referencí.

app/components/Card/preview-card.latte
{*
	Preview Card — generic card s obrázkem, titulkem, metadaty a footerem.
	Vhodné pro výpisy produktů, článků, referencí, kategorií...

	Parametry:
	- $href      (string|null)  link na detail (pokud zadán, celá karta klik.)
	- $image     (string|null)  URL obrázku
	- $alt       (string)       alt text obrázku (default '')
	- $title     (string)       titulek (povinné)
	- $meta      (string|null)  podtitulek/meta řádek (popis, kategorie, datum...)
	- $badges    (array)        pole [['text' => 'Novinka', 'modifier' => 'new'], ...]
	- $footer    (string|null)  HTML obsah footeru (cena + button apod.)
	- $class     (string|null)  extra třída
*}
{var $href = $href ?? null}
{var $image = $image ?? null}
{var $alt = $alt ?? ''}
{var $title = $title ?? ''}
{var $meta = $meta ?? null}
{var $badges = $badges ?? []}
{var $footer = $footer ?? null}
{var $class = $class ?? null}

{if $href}<a href="{$href}" class="preview-card{if $class} {$class}{/if}">{else}<div class="preview-card{if $class} {$class}{/if}">{/if}
	{if $image}
		<span class="preview-card__image">
			{if $badges}
				<span class="preview-card__badges">
					{foreach $badges as $badge}
						<span class="preview-card__badge{if isset($badge['modifier'])} preview-card__badge--{$badge['modifier']}{/if}">
							{$badge['text']}
						</span>
					{/foreach}
				</span>
			{/if}
			<img src="{$image}" alt="{$alt}" loading="lazy">
		</span>
	{/if}

	<div class="preview-card__body">
		<h3 class="preview-card__title">{$title}</h3>

		{if $meta}
			<div class="preview-card__meta">{$meta|noescape}</div>
		{/if}

		{if $footer}
			<div class="preview-card__footer">{$footer|noescape}</div>
		{/if}
	</div>
{if $href}</a>{else}</div>{/if}
resources/sass/components/_preview-card.scss
// ─── Configurable variables (override before @import) ──────────────────
$cgui-preview-card-bg:              #fff !default;
$cgui-preview-card-border:          1px solid $cgui-color-border !default;
$cgui-preview-card-radius:          $cgui-radius !default;
$cgui-preview-card-padding:         24px !default;
$cgui-preview-card-shadow:          none !default;
$cgui-preview-card-shadow-hover:    0 4px 16px rgba(0, 0, 0, 0.08) !default;
$cgui-preview-card-transition:      box-shadow 0.2s, transform 0.2s !default;
$cgui-preview-card-image-bg:        $cgui-color-bg-gray-light !default;
$cgui-preview-card-image-aspect:    1 / 1 !default;
$cgui-preview-card-image-radius:    calc(#{$cgui-preview-card-radius} - 6px) !default;
$cgui-preview-card-title-size:      26px !default;
$cgui-preview-card-title-weight:    500 !default;
$cgui-preview-card-title-color:     $cgui-color-text !default;
$cgui-preview-card-meta-size:       21px !default;
$cgui-preview-card-meta-color:      $cgui-color-text-gray !default;
$cgui-preview-card-gap:             13px !default;
// ───────────────────────────────────────────────────────────────────────

.preview-card {
	display: flex;
	flex-direction: column;
	background: $cgui-preview-card-bg;
	border: $cgui-preview-card-border;
	border-radius: $cgui-preview-card-radius;
	padding: $cgui-preview-card-padding;
	box-shadow: $cgui-preview-card-shadow;
	transition: $cgui-preview-card-transition;
	color: $cgui-preview-card-title-color;
	text-decoration: none;
	height: 100%;

	&:hover,
	&:focus-within {
		box-shadow: $cgui-preview-card-shadow-hover;
		transform: translateY(-2px);
	}

	&__image {
		display: block;
		width: 100%;
		aspect-ratio: $cgui-preview-card-image-aspect;
		background: $cgui-preview-card-image-bg;
		border-radius: $cgui-preview-card-image-radius;
		overflow: hidden;
		margin-bottom: $cgui-preview-card-gap;
		position: relative;

		img {
			width: 100%;
			height: 100%;
			object-fit: cover;
			transition: transform 0.3s;
		}
	}

	&:hover &__image img {
		transform: scale(1.04);
	}

	&__badges {
		position: absolute;
		top: 13px;
		left: 13px;
		display: flex;
		flex-direction: column;
		gap: 6px;
		z-index: 1;
	}

	&__badge {
		display: inline-flex;
		align-items: center;
		padding: 5px 13px;
		font-size: 19px;
		font-weight: 600;
		line-height: 1.2;
		color: #fff;
		background: $cgui-color-primary;
		border-radius: 6px;

		&--discount { background: #E91E63; }
		&--new      { background: $cgui-color-brand; }
		&--sale     { background: $cgui-color-yellow; color: $cgui-color-text; }
	}

	&__body {
		display: flex;
		flex-direction: column;
		gap: $cgui-preview-card-gap;
		flex: 1;
		min-width: 0;
	}

	&__title {
		font-size: $cgui-preview-card-title-size;
		font-weight: $cgui-preview-card-title-weight;
		color: $cgui-preview-card-title-color;
		margin: 0;
		line-height: 1.3;
		text-decoration: none;

		a {
			color: inherit;
			text-decoration: none;

			@include cgui-hover() {
				&:hover, &:focus { color: $cgui-color-primary; }
			}
		}
	}

	&__meta {
		font-size: $cgui-preview-card-meta-size;
		color: $cgui-preview-card-meta-color;
		line-height: 1.4;
	}

	&__footer {
		margin-top: auto;
		display: flex;
		align-items: center;
		justify-content: space-between;
		gap: $cgui-preview-card-gap;
		padding-top: $cgui-preview-card-gap;
	}
}