more hardcodes are done

This commit is contained in:
Priec
2026-06-17 21:52:43 +02:00
parent 1d747d9960
commit 0754e014a3
26 changed files with 522 additions and 222 deletions

File diff suppressed because one or more lines are too long

View File

@@ -120,54 +120,7 @@
<!-- settings (language + theme) dropdown --> <!-- settings (language + theme) dropdown -->
<div x-data="{ open: false }" @keydown.escape="open = false" class="relative ml-auto"> <div x-data="{ open: false }" @keydown.escape="open = false" class="relative ml-auto">
<button type="button" @click="open = !open" :aria-expanded="open" {% include "partials/settings_dropdown.html" %}
aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}"
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<div x-show="open" x-cloak @click.outside="open = false" x-transition.origin.top.right
class="absolute right-0 mt-2 w-56 rounded-radius border border-outline bg-surface p-2 shadow-lg dark:border-outline-dark dark:bg-surface-dark-alt">
<form method="post" action="/lang" hx-boost="false">
<p class="px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
{{ t(key="settings-language", lang=lang | default(value='sk')) }}
</p>
<button type="submit" name="lang" value="en"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>English</span>
{% if lang | default(value='sk') == "en" %}<span class="text-primary dark:text-primary-dark"></span>{% endif %}
</button>
<button type="submit" name="lang" value="sk"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>Slovenčina</span>
{% if lang | default(value='sk') == "sk" %}<span class="text-primary dark:text-primary-dark"></span>{% endif %}
</button>
</form>
<p class="mt-1 px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
{{ t(key="settings-theme", lang=lang | default(value='sk')) }}
</p>
<div x-data="{ theme: currentTheme() }" @theme:changed.document="theme = $event.detail">
<button type="button" @click="setTheme('system')"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>{{ t(key="theme-system", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'system'" class="text-primary dark:text-primary-dark"></span>
</button>
<button type="button" @click="setTheme('light')"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>{{ t(key="theme-light", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'light'" class="text-primary dark:text-primary-dark"></span>
</button>
<button type="button" @click="setTheme('dark')"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>{{ t(key="theme-dark", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'dark'" class="text-primary dark:text-primary-dark"></span>
</button>
</div>
</div>
</div> </div>
</header> </header>

View File

@@ -1,4 +1,5 @@
{% extends "admin/base.html" %} {% extends "admin/base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ t(key="admin-categories", lang=lang | default(value='sk')) }}{% endblock title %} {% block title %}{{ t(key="admin-categories", lang=lang | default(value='sk')) }}{% endblock title %}
{% block crumb %}{{ t(key="admin-categories", lang=lang | default(value='sk')) }}{% endblock crumb %} {% block crumb %}{{ t(key="admin-categories", lang=lang | default(value='sk')) }}{% endblock crumb %}
@@ -9,10 +10,7 @@
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="admin-categories", lang=lang | default(value='sk')) }}</h1> <h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="admin-categories", lang=lang | default(value='sk')) }}</h1>
<p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-categories-desc", lang=lang | default(value='sk')) }}</p> <p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-categories-desc", lang=lang | default(value='sk')) }}</p>
</div> </div>
<a href="/admin/catalog/categories/new" {{ ui::button_primary(label=t(key="new-category", lang=lang | default(value='sk')), href="/admin/catalog/categories/new") }}
class="inline-flex items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">
{{ t(key="new-category", lang=lang | default(value='sk')) }}
</a>
</div> </div>
<div class="mt-6 overflow-hidden rounded-radius border border-outline dark:border-outline-dark"> <div class="mt-6 overflow-hidden rounded-radius border border-outline dark:border-outline-dark">
@@ -38,9 +36,9 @@
<td class="px-4 py-3 tabular-nums">{{ row.product_count }}</td> <td class="px-4 py-3 tabular-nums">{{ row.product_count }}</td>
<td class="px-4 py-3"> <td class="px-4 py-3">
{% if row.category.published %} {% if row.category.published %}
<span class="inline-flex rounded-full bg-success/15 px-2 py-0.5 text-xs font-medium text-success">{{ t(key="published", lang=lang | default(value='sk')) }}</span> {{ ui::badge(label=t(key="published", lang=lang | default(value='sk')), variant="success") }}
{% else %} {% else %}
<span class="inline-flex rounded-full bg-surface-alt px-2 py-0.5 text-xs font-medium text-on-surface/70 dark:bg-surface-dark-alt dark:text-on-surface-dark/70">{{ t(key="draft", lang=lang | default(value='sk')) }}</span> {{ ui::badge(label=t(key="draft", lang=lang | default(value='sk')), variant="neutral") }}
{% endif %} {% endif %}
</td> </td>
<td class="px-4 py-3"> <td class="px-4 py-3">
@@ -60,10 +58,7 @@
{% else %} {% else %}
<div class="flex flex-col items-center gap-3 px-4 py-16 text-center"> <div class="flex flex-col items-center gap-3 px-4 py-16 text-center">
<p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-no-categories", lang=lang | default(value='sk')) }}</p> <p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-no-categories", lang=lang | default(value='sk')) }}</p>
<a href="/admin/catalog/categories/new" {{ ui::button_primary(label=t(key="new-category", lang=lang | default(value='sk')), href="/admin/catalog/categories/new") }}
class="inline-flex items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">
{{ t(key="new-category", lang=lang | default(value='sk')) }}
</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@@ -1,4 +1,5 @@
{% extends "admin/base.html" %} {% extends "admin/base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{% if category %}{{ t(key="edit-category", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-category", lang=lang | default(value='sk')) }}{% endif %}{% endblock title %} {% block title %}{% if category %}{{ t(key="edit-category", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-category", lang=lang | default(value='sk')) }}{% endif %}{% endblock title %}
{% block crumb %}{{ t(key="admin-categories", lang=lang | default(value='sk')) }}{% endblock crumb %} {% block crumb %}{{ t(key="admin-categories", lang=lang | default(value='sk')) }}{% endblock crumb %}
@@ -8,8 +9,7 @@
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong"> <h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">
{% if category %}{{ t(key="edit-category", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-category", lang=lang | default(value='sk')) }}{% endif %} {% if category %}{{ t(key="edit-category", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-category", lang=lang | default(value='sk')) }}{% endif %}
</h1> </h1>
<a href="/admin/catalog/categories" {{ ui::button_outline(label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/categories") }}
class="inline-flex items-center rounded-radius border border-outline px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="cancel", lang=lang | default(value='sk')) }}</a>
</div> </div>
<form method="post" enctype="multipart/form-data" <form method="post" enctype="multipart/form-data"
@@ -71,12 +71,8 @@
</label> </label>
<div class="flex gap-3 pt-2"> <div class="flex gap-3 pt-2">
<button type="submit" {{ ui::button_primary(label=t(key="save", lang=lang | default(value='sk')), type="submit") }}
class="inline-flex items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark"> {{ ui::button_outline(label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/categories") }}
{{ t(key="save", lang=lang | default(value='sk')) }}
</button>
<a href="/admin/catalog/categories"
class="inline-flex items-center rounded-radius border border-outline px-4 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="cancel", lang=lang | default(value='sk')) }}</a>
</div> </div>
</form> </form>
{% endblock content %} {% endblock content %}

View File

@@ -1,4 +1,5 @@
{% extends "admin/base.html" %} {% extends "admin/base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{% if product %}{{ t(key="edit-product", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-product", lang=lang | default(value='sk')) }}{% endif %}{% endblock title %} {% block title %}{% if product %}{{ t(key="edit-product", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-product", lang=lang | default(value='sk')) }}{% endif %}{% endblock title %}
{% block crumb %}{{ t(key="admin-products", lang=lang | default(value='sk')) }}{% endblock crumb %} {% block crumb %}{{ t(key="admin-products", lang=lang | default(value='sk')) }}{% endblock crumb %}
@@ -8,8 +9,7 @@
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong"> <h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">
{% if product %}{{ t(key="edit-product", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-product", lang=lang | default(value='sk')) }}{% endif %} {% if product %}{{ t(key="edit-product", lang=lang | default(value='sk')) }}{% else %}{{ t(key="new-product", lang=lang | default(value='sk')) }}{% endif %}
</h1> </h1>
<a href="/admin/catalog/products" {{ ui::button_outline(label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/products") }}
class="inline-flex items-center rounded-radius border border-outline px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="cancel", lang=lang | default(value='sk')) }}</a>
</div> </div>
<form method="post" enctype="multipart/form-data" <form method="post" enctype="multipart/form-data"
@@ -89,12 +89,8 @@
</label> </label>
<div class="flex gap-3 pt-2"> <div class="flex gap-3 pt-2">
<button type="submit" {{ ui::button_primary(label=t(key="save", lang=lang | default(value='sk')), type="submit") }}
class="inline-flex items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark"> {{ ui::button_outline(label=t(key="cancel", lang=lang | default(value='sk')), href="/admin/catalog/products") }}
{{ t(key="save", lang=lang | default(value='sk')) }}
</button>
<a href="/admin/catalog/products"
class="inline-flex items-center rounded-radius border border-outline px-4 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="cancel", lang=lang | default(value='sk')) }}</a>
</div> </div>
</form> </form>
{% endblock content %} {% endblock content %}

View File

@@ -1,4 +1,5 @@
{% extends "admin/base.html" %} {% extends "admin/base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ t(key="admin-products", lang=lang | default(value='sk')) }}{% endblock title %} {% block title %}{{ t(key="admin-products", lang=lang | default(value='sk')) }}{% endblock title %}
{% block crumb %}{{ t(key="admin-products", lang=lang | default(value='sk')) }}{% endblock crumb %} {% block crumb %}{{ t(key="admin-products", lang=lang | default(value='sk')) }}{% endblock crumb %}
@@ -9,10 +10,7 @@
<h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="admin-products", lang=lang | default(value='sk')) }}</h1> <h1 class="text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="admin-products", lang=lang | default(value='sk')) }}</h1>
<p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-products-desc", lang=lang | default(value='sk')) }}</p> <p class="text-sm text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-products-desc", lang=lang | default(value='sk')) }}</p>
</div> </div>
<a href="/admin/catalog/products/new" {{ ui::button_primary(label=t(key="new-product", lang=lang | default(value='sk')), href="/admin/catalog/products/new") }}
class="inline-flex items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">
{{ t(key="new-product", lang=lang | default(value='sk')) }}
</a>
</div> </div>
<div class="mt-6 overflow-hidden rounded-radius border border-outline dark:border-outline-dark"> <div class="mt-6 overflow-hidden rounded-radius border border-outline dark:border-outline-dark">
@@ -47,9 +45,9 @@
<td class="px-4 py-3 tabular-nums">{{ product.stock }}</td> <td class="px-4 py-3 tabular-nums">{{ product.stock }}</td>
<td class="px-4 py-3"> <td class="px-4 py-3">
{% if product.published %} {% if product.published %}
<span class="inline-flex rounded-full bg-success/15 px-2 py-0.5 text-xs font-medium text-success">{{ t(key="published", lang=lang | default(value='sk')) }}</span> {{ ui::badge(label=t(key="published", lang=lang | default(value='sk')), variant="success") }}
{% else %} {% else %}
<span class="inline-flex rounded-full bg-surface-alt px-2 py-0.5 text-xs font-medium text-on-surface/70 dark:bg-surface-dark-alt dark:text-on-surface-dark/70">{{ t(key="draft", lang=lang | default(value='sk')) }}</span> {{ ui::badge(label=t(key="draft", lang=lang | default(value='sk')), variant="neutral") }}
{% endif %} {% endif %}
</td> </td>
<td class="px-4 py-3"> <td class="px-4 py-3">
@@ -71,10 +69,7 @@
{% else %} {% else %}
<div class="flex flex-col items-center gap-3 px-4 py-16 text-center"> <div class="flex flex-col items-center gap-3 px-4 py-16 text-center">
<p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-no-products", lang=lang | default(value='sk')) }}</p> <p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="admin-no-products", lang=lang | default(value='sk')) }}</p>
<a href="/admin/catalog/products/new" {{ ui::button_primary(label=t(key="new-product", lang=lang | default(value='sk')), href="/admin/catalog/products/new") }}
class="inline-flex items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">
{{ t(key="new-product", lang=lang | default(value='sk')) }}
</a>
</div> </div>
{% endif %} {% endif %}
</div> </div>

View File

@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ t(key="login-title", lang=lang | default(value='sk')) }}{% endblock title %} {% block title %}{{ t(key="login-title", lang=lang | default(value='sk')) }}{% endblock title %}
@@ -11,10 +12,7 @@
<span class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong"> <span class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">
{{ t(key="nav-admin", lang=lang | default(value='sk')) }} {{ t(key="nav-admin", lang=lang | default(value='sk')) }}
</span> </span>
<span {{ ui::badge(label=t(key="auth", lang=lang | default(value='sk')), variant="danger") }}
class="rounded-radius border border-danger/40 px-2 py-0.5 text-xs font-medium text-danger">
{{ t(key="auth", lang=lang | default(value='sk')) }}
</span>
</div> </div>
<div class="p-5"> <div class="p-5">
@@ -23,11 +21,7 @@
</h1> </h1>
{% if error %} {% if error %}
<div {{ ui::alert_danger(message=t(key="login-error", lang=lang | default(value='sk')), extra="mt-3") }}
class="mt-3 rounded-radius border border-danger/40 bg-danger/10 px-3 py-2 text-sm text-danger"
role="alert">
✗ {{ t(key="login-error", lang=lang | default(value='sk')) }}
</div>
{% endif %} {% endif %}
<form method="post" action="/admin/login" hx-boost="false" class="mt-4 flex flex-col gap-4"> <form method="post" action="/admin/login" hx-boost="false" class="mt-4 flex flex-col gap-4">
@@ -51,10 +45,7 @@
class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-none focus:ring-2 focus:ring-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark dark:focus:ring-primary-dark"> class="w-full rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-none focus:ring-2 focus:ring-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark dark:focus:ring-primary-dark">
</div> </div>
<button type="submit" {{ ui::button_primary(label=t(key="login-auth", lang=lang | default(value='sk')), type="submit", extra="mt-1 w-full") }}
class="mt-1 w-full rounded-radius bg-primary px-4 py-2 text-sm font-semibold text-on-primary transition hover:opacity-90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 focus:ring-offset-surface-alt dark:bg-primary-dark dark:text-on-primary-dark dark:focus:ring-primary-dark dark:focus:ring-offset-surface-dark-alt">
{{ t(key="login-auth", lang=lang | default(value='sk')) }}
</button>
</form> </form>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,5 @@
{% extends "admin/base.html" %} {% extends "admin/base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ t(key="admin-orders", lang=lang | default(value='sk')) }}{% endblock title %} {% block title %}{{ t(key="admin-orders", lang=lang | default(value='sk')) }}{% endblock title %}
{% block crumb %}{{ t(key="admin-orders", lang=lang | default(value='sk')) }}{% endblock crumb %} {% block crumb %}{{ t(key="admin-orders", lang=lang | default(value='sk')) }}{% endblock crumb %}
@@ -24,7 +25,7 @@
<td class="px-4 py-3 font-mono font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ order.order_number }}</td> <td class="px-4 py-3 font-mono font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ order.order_number }}</td>
<td class="px-4 py-3">{{ order.email }}</td> <td class="px-4 py-3">{{ order.email }}</td>
<td class="px-4 py-3"> <td class="px-4 py-3">
<span class="inline-flex rounded-full bg-surface-alt px-2 py-0.5 text-xs font-medium text-on-surface/80 dark:bg-surface-dark-alt dark:text-on-surface-dark/80">{{ t(key="order-status-" ~ order.status, lang=lang | default(value='sk')) }}</span> {{ ui::badge(label=t(key="order-status-" ~ order.status, lang=lang | default(value='sk')), variant="neutral") }}
</td> </td>
<td class="px-4 py-3 text-right tabular-nums">{{ order.total }} {{ order.currency }}</td> <td class="px-4 py-3 text-right tabular-nums">{{ order.total }} {{ order.currency }}</td>
<td class="px-4 py-3 text-right"> <td class="px-4 py-3 text-right">

View File

@@ -1,4 +1,5 @@
{% extends "admin/base.html" %} {% extends "admin/base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ order.order_number }}{% endblock title %} {% block title %}{{ order.order_number }}{% endblock title %}
{% block crumb %}{{ t(key="admin-orders", lang=lang | default(value='sk')) }}{% endblock crumb %} {% block crumb %}{{ t(key="admin-orders", lang=lang | default(value='sk')) }}{% endblock crumb %}
@@ -6,13 +7,11 @@
{% block content %} {% block content %}
<div class="flex flex-wrap items-center justify-between gap-3"> <div class="flex flex-wrap items-center justify-between gap-3">
<h1 class="font-mono text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ order.order_number }}</h1> <h1 class="font-mono text-2xl font-bold text-on-surface-strong dark:text-on-surface-dark-strong">{{ order.order_number }}</h1>
<a href="/admin/orders" class="inline-flex items-center rounded-radius border border-outline px-3 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="admin-orders", lang=lang | default(value='sk')) }}</a> {{ ui::button_outline(label=t(key="admin-orders", lang=lang | default(value='sk')), href="/admin/orders") }}
</div> </div>
{% if ship_error %} {% if ship_error %}
<div class="mt-4 rounded-radius border border-danger/40 bg-danger/10 px-4 py-3 text-sm font-medium text-danger"> {{ ui::alert_danger(message=ship_error, extra="mt-4") }}
{{ ship_error }}
</div>
{% endif %} {% endif %}
<div class="mt-6 grid gap-6 lg:grid-cols-3"> <div class="mt-6 grid gap-6 lg:grid-cols-3">
@@ -95,10 +94,9 @@
<p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="order-send-hint", lang=lang | default(value='sk')) }}</p> <p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="order-send-hint", lang=lang | default(value='sk')) }}</p>
<form method="post" action="/admin/orders/{{ order.id }}/ship" <form method="post" action="/admin/orders/{{ order.id }}/ship"
onsubmit="return confirm('{{ t(key="order-send-confirm", lang=lang | default(value='sk')) }}')"> onsubmit="return confirm('{{ t(key="order-send-confirm", lang=lang | default(value='sk')) }}')">
<button type="submit" {% set carrier_up = carrier | upper %}
class="inline-flex w-full items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark"> {% set ship_label = t(key="order-send-to-carrier", lang=lang | default(value='sk')) ~ " " ~ carrier_up %}
{{ t(key="order-send-to-carrier", lang=lang | default(value='sk')) }} {{ carrier | upper }} {{ ui::button_primary(label=ship_label, type="submit", extra="w-full") }}
</button>
</form> </form>
{% endif %} {% endif %}
</div> </div>
@@ -111,7 +109,7 @@
<option value="{{ status }}" {% if order.status == status %}selected{% endif %}>{{ t(key="order-status-" ~ status, lang=lang | default(value='sk')) }}</option> <option value="{{ status }}" {% if order.status == status %}selected{% endif %}>{{ t(key="order-status-" ~ status, lang=lang | default(value='sk')) }}</option>
{% endfor %} {% endfor %}
</select> </select>
<button type="submit" class="inline-flex w-full items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">{{ t(key="order-update-status", lang=lang | default(value='sk')) }}</button> {{ ui::button_primary(label=t(key="order-update-status", lang=lang | default(value='sk')), type="submit", extra="w-full") }}
</form> </form>
</aside> </aside>
</div> </div>

View File

@@ -1,4 +1,5 @@
{% extends "admin/base.html" %} {% extends "admin/base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ t(key="admin-shipping", lang=lang | default(value='sk')) }}{% endblock title %} {% block title %}{{ t(key="admin-shipping", lang=lang | default(value='sk')) }}{% endblock title %}
{% block crumb %}{{ t(key="admin-shipping", lang=lang | default(value='sk')) }}{% endblock crumb %} {% block crumb %}{{ t(key="admin-shipping", lang=lang | default(value='sk')) }}{% endblock crumb %}
@@ -27,10 +28,7 @@
class="size-4 rounded border-outline text-primary focus:ring-primary dark:border-outline-dark"> class="size-4 rounded border-outline text-primary focus:ring-primary dark:border-outline-dark">
<span class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="shipping-enabled", lang=lang | default(value='sk')) }}</span> <span class="text-sm font-medium text-on-surface-strong dark:text-on-surface-dark-strong">{{ t(key="shipping-enabled", lang=lang | default(value='sk')) }}</span>
</label> </label>
<button type="submit" {{ ui::button_primary(label=t(key="save", lang=lang | default(value='sk')), type="submit", extra="ml-auto") }}
class="ml-auto inline-flex items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">
{{ t(key="save", lang=lang | default(value='sk')) }}
</button>
</form> </form>
{% endfor %} {% endfor %}
</div> </div>

View File

@@ -111,56 +111,7 @@
</a> </a>
<!-- settings (language + theme) dropdown --> <!-- settings (language + theme) dropdown -->
<div x-data="{ open: false }" @keydown.escape="open = false" class="relative"> <div x-data="{ open: false }" @keydown.escape="open = false" class="relative">
<button type="button" @click="open = !open" :aria-expanded="open" {% include "partials/settings_dropdown.html" %}
aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}"
title="{{ t(key='settings', lang=lang | default(value='sk')) }}"
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<div x-show="open" x-cloak @click.outside="open = false"
x-transition.origin.top.right
class="absolute right-0 mt-2 w-56 rounded-radius border border-outline bg-surface p-2 shadow-lg dark:border-outline-dark dark:bg-surface-dark-alt">
<form method="post" action="/lang" hx-boost="false">
<p class="px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
{{ t(key="settings-language", lang=lang | default(value='sk')) }}
</p>
<button type="submit" name="lang" value="en"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>English</span>
{% if lang | default(value='sk') == "en" %}<span class="text-primary dark:text-primary-dark"></span>{% endif %}
</button>
<button type="submit" name="lang" value="sk"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>Slovenčina</span>
{% if lang | default(value='sk') == "sk" %}<span class="text-primary dark:text-primary-dark"></span>{% endif %}
</button>
</form>
<p class="mt-1 px-2 py-1 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
{{ t(key="settings-theme", lang=lang | default(value='sk')) }}
</p>
<div x-data="{ theme: currentTheme() }" @theme:changed.document="theme = $event.detail">
<button type="button" @click="setTheme('system')"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>{{ t(key="theme-system", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'system'" class="text-primary dark:text-primary-dark"></span>
</button>
<button type="button" @click="setTheme('light')"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>{{ t(key="theme-light", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'light'" class="text-primary dark:text-primary-dark"></span>
</button>
<button type="button" @click="setTheme('dark')"
class="flex w-full items-center justify-between rounded-radius px-2 py-1.5 text-sm text-on-surface transition hover:bg-surface-alt dark:text-on-surface-dark dark:hover:bg-surface-dark">
<span>{{ t(key="theme-dark", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'dark'" class="text-primary dark:text-primary-dark"></span>
</button>
</div>
</div>
</div> </div>
<!-- mobile hamburger --> <!-- mobile hamburger -->

View File

@@ -0,0 +1,67 @@
{# Reusable UI macros adapted from vendored Penguin UI components.
These are OUR adaptation layer; the byte-for-byte upstream sources live under
assets/views/penguinui/. Tailwind sees the full literal class strings here
(assets/css/app.css has @source "../views"), so every branch must spell its
classes out in full — never build class names by concatenation.
Usage:
{% import "macros/ui.html" as ui %}
{{ ui::button_primary(label=t(key="save", lang=lang)) }}
{{ ui::button_primary(label="Add", attrs='hx-post="/x"' | safe) }}
{{ ui::button_outline(label="Cancel", href="/back") }}
{{ ui::badge(label="Published", variant="success") }}
Notes:
- Macros can't see template context vars (e.g. `lang`); pass already-translated
strings as `label`.
- `attrs` is injected raw (caller must pass it through `| safe`); use it for
htmx / name / value / @click etc. For buttons whose attrs carry nested
quotes (e.g. hx-on with toast(...)), keep them inline instead.
- Source: penguinui/buttons/{default,outline,ghost}-button.html and
penguinui/badge/soft-color-badge.html. Upstream's color-button typos
(text-onDanger etc.) are fixed to our real tokens (text-on-danger). #}
{% macro button_primary(label, type="button", href="", attrs="", extra="") -%}
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius border border-primary bg-primary px-4 py-2 text-center text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 dark:border-primary-dark dark:bg-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark {{ extra }}" {{ attrs | safe }}>{{ label }}</{% if href %}a{% else %}button{% endif %}>
{%- endmacro button_primary %}
{% macro button_outline(label, type="button", href="", attrs="", extra="") -%}
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius border border-outline bg-surface-alt px-4 py-2 text-center text-sm font-medium tracking-wide text-on-surface transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-outline-strong active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 dark:border-outline-dark dark:bg-surface-dark-alt dark:text-on-surface-dark dark:focus-visible:outline-outline-dark-strong {{ extra }}" {{ attrs | safe }}>{{ label }}</{% if href %}a{% else %}button{% endif %}>
{%- endmacro button_outline %}
{% macro button_danger(label, type="button", href="", attrs="", extra="") -%}
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius border border-danger bg-danger px-4 py-2 text-center text-sm font-medium tracking-wide text-on-danger transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 dark:focus-visible:outline-danger {{ extra }}" {{ attrs | safe }}>{{ label }}</{% if href %}a{% else %}button{% endif %}>
{%- endmacro button_danger %}
{% macro button_ghost(label, type="button", href="", attrs="", extra="") -%}
{% if href %}<a href="{{ href }}"{% else %}<button type="{{ type }}"{% endif %} class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-radius bg-transparent px-4 py-2 text-center text-sm font-medium tracking-wide text-primary transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 disabled:cursor-not-allowed disabled:opacity-75 dark:text-primary-dark dark:focus-visible:outline-primary-dark {{ extra }}" {{ attrs | safe }}>{{ label }}</{% if href %}a{% else %}button{% endif %}>
{%- endmacro button_ghost %}
{# Compact danger alert (form/inline errors). Adapted from
penguinui/alert/default-alert.html (danger variant), trimmed to a single line
with the danger icon. #}
{% macro alert_danger(message, extra="") -%}
<div class="flex w-full items-center gap-2 overflow-hidden rounded-radius border border-danger bg-danger/10 px-3 py-2 text-sm text-danger {{ extra }}" role="alert">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5 shrink-0" aria-hidden="true">
<path fill-rule="evenodd" d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z" clip-rule="evenodd" />
</svg>
<span>{{ message }}</span>
</div>
{%- endmacro alert_danger %}
{# Soft-color badge. variant ∈ success | danger | warning | info | primary | neutral #}
{% macro badge(label, variant="neutral") -%}
{% if variant == "success" -%}
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-success bg-surface text-xs font-medium text-success dark:bg-surface-dark"><span class="bg-success/10 px-2 py-1">{{ label }}</span></span>
{%- elif variant == "danger" -%}
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-danger bg-surface text-xs font-medium text-danger dark:bg-surface-dark"><span class="bg-danger/10 px-2 py-1">{{ label }}</span></span>
{%- elif variant == "warning" -%}
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-warning bg-surface text-xs font-medium text-warning dark:bg-surface-dark"><span class="bg-warning/10 px-2 py-1">{{ label }}</span></span>
{%- elif variant == "info" -%}
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-info bg-surface text-xs font-medium text-info dark:bg-surface-dark"><span class="bg-info/10 px-2 py-1">{{ label }}</span></span>
{%- elif variant == "primary" -%}
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-primary bg-surface text-xs font-medium text-primary dark:border-primary-dark dark:bg-surface-dark dark:text-primary-dark"><span class="bg-primary/10 px-2 py-1 dark:bg-primary-dark/10">{{ label }}</span></span>
{%- else -%}
<span class="inline-flex w-fit overflow-hidden rounded-radius border border-outline bg-surface text-xs font-medium text-on-surface dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark"><span class="bg-surface-alt/40 px-2 py-1 dark:bg-surface-dark-alt/40">{{ label }}</span></span>
{%- endif %}
{%- endmacro badge %}

View File

@@ -0,0 +1,63 @@
{# Settings dropdown (language + theme). Shared by base.html and admin/base.html
to kill the former ~100-line copy-paste duplication.
Adapted from the vendored Penguin UI component
assets/views/penguinui/dropdowns/dropdown-with-click.html: Penguin's dropdown
menu container + item treatment. Deviations: kept our gear icon-only trigger
and our core-Alpine open / @click.outside toggle (upstream's x-trap / $focus
need the Alpine Focus plugin, which we don't bundle); item hover uses
bg-primary/5 to stay consistent with the rest of our Penguin-ified UI.
The host template provides the wrapper
<div x-data="{ open: false }" @keydown.escape="open = false" class="relative ...">
so it controls its own positioning (e.g. ml-auto in admin). #}
<button type="button" @click="open = !open" :aria-expanded="open"
aria-label="{{ t(key='settings', lang=lang | default(value='sk')) }}"
title="{{ t(key='settings', lang=lang | default(value='sk')) }}"
class="inline-flex size-9 items-center justify-center rounded-radius text-on-surface transition hover:bg-surface-alt focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-outline-strong dark:text-on-surface-dark dark:hover:bg-surface-dark-alt dark:focus-visible:outline-outline-dark-strong">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" class="size-5">
<path stroke-linecap="round" stroke-linejoin="round"
d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</button>
<div x-show="open" x-cloak @click.outside="open = false" x-transition.origin.top.right
class="absolute right-0 mt-2 flex w-56 flex-col overflow-hidden rounded-radius border border-outline bg-surface-alt py-1 shadow-lg dark:border-outline-dark dark:bg-surface-dark-alt"
role="menu">
<form method="post" action="/lang" hx-boost="false">
<p class="px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
{{ t(key="settings-language", lang=lang | default(value='sk')) }}
</p>
<button type="submit" name="lang" value="en" role="menuitem"
class="flex w-full items-center justify-between px-4 py-2 text-sm text-on-surface transition hover:bg-primary/5 hover:text-on-surface-strong focus-visible:bg-primary/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">
<span>English</span>
{% if lang | default(value='sk') == "en" %}<span class="text-primary dark:text-primary-dark"></span>{% endif %}
</button>
<button type="submit" name="lang" value="sk" role="menuitem"
class="flex w-full items-center justify-between px-4 py-2 text-sm text-on-surface transition hover:bg-primary/5 hover:text-on-surface-strong focus-visible:bg-primary/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">
<span>Slovenčina</span>
{% if lang | default(value='sk') == "sk" %}<span class="text-primary dark:text-primary-dark"></span>{% endif %}
</button>
</form>
<p class="mt-1 px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-on-surface/60 dark:text-on-surface-dark/60">
{{ t(key="settings-theme", lang=lang | default(value='sk')) }}
</p>
<div x-data="{ theme: currentTheme() }" @theme:changed.document="theme = $event.detail">
<button type="button" @click="setTheme('system')" role="menuitem"
class="flex w-full items-center justify-between px-4 py-2 text-sm text-on-surface transition hover:bg-primary/5 hover:text-on-surface-strong focus-visible:bg-primary/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">
<span>{{ t(key="theme-system", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'system'" class="text-primary dark:text-primary-dark"></span>
</button>
<button type="button" @click="setTheme('light')" role="menuitem"
class="flex w-full items-center justify-between px-4 py-2 text-sm text-on-surface transition hover:bg-primary/5 hover:text-on-surface-strong focus-visible:bg-primary/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">
<span>{{ t(key="theme-light", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'light'" class="text-primary dark:text-primary-dark"></span>
</button>
<button type="button" @click="setTheme('dark')" role="menuitem"
class="flex w-full items-center justify-between px-4 py-2 text-sm text-on-surface transition hover:bg-primary/5 hover:text-on-surface-strong focus-visible:bg-primary/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:text-on-surface-dark dark:hover:bg-primary-dark/5 dark:hover:text-on-surface-dark-strong">
<span>{{ t(key="theme-dark", lang=lang | default(value='sk')) }}</span>
<span x-show="theme === 'dark'" class="text-primary dark:text-primary-dark"></span>
</button>
</div>
</div>

View File

@@ -0,0 +1,101 @@
<!-- info Alert -->
<div class="relative w-full overflow-hidden rounded-sm border border-sky-500 bg-surface text-on-surface dark:bg-surface-dark dark:text-on-surface-dark"
role="alert">
<div class="flex w-full items-center gap-2 bg-info/10 p-4">
<div class="bg-sky-500/15 text-sky-500 rounded-full p-1" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-6"
aria-hidden="true">
<path fill-rule="evenodd"
d="M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-7-4a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM9 9a.75.75 0 0 0 0 1.5h.253a.25.25 0 0 1 .244.304l-.459 2.066A1.75 1.75 0 0 0 10.747 15H11a.75.75 0 0 0 0-1.5h-.253a.25.25 0 0 1-.244-.304l.459-2.066A1.75 1.75 0 0 0 9.253 9H9Z"
clip-rule="evenodd" />
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-semibold text-info">Update Available</h3>
<p class="text-xs font-medium sm:text-sm">A new version is available. Please update to the latest version.
</p>
</div>
<button class="ml-auto" aria-label="dismiss alert">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor"
fill="none" stroke-width="2.5" class="size-4 shrink-0">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- success Alert -->
<div class="relative w-full overflow-hidden rounded-sm border border-green-500 bg-surface text-on-surface dark:bg-surface-dark dark:text-on-surface-dark"
role="alert">
<div class="flex w-full items-center gap-2 bg-success/10 p-4">
<div class="bg-green-500/15 text-green-500 rounded-full p-1" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-6"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16Zm3.857-9.809a.75.75 0 0 0-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 1 0-1.06 1.061l2.5 2.5a.75.75 0 0 0 1.137-.089l4-5.5Z"
clip-rule="evenodd" />
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-semibold text-success">Successfully Subscribed</h3>
<p class="text-xs font-medium sm:text-sm">Success! You've subscribed to our newsletter. Welcome aboard!</p>
</div>
<button class="ml-auto" aria-label="dismiss alert">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor"
fill="none" stroke-width="2.5" class="size-4 shrink-0">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- warning Alert -->
<div class="relative w-full overflow-hidden rounded-sm border border-amber-500 bg-surface text-on-surface dark:bg-surface-dark dark:text-on-surface-dark"
role="alert">
<div class="flex w-full items-center gap-2 bg-warning/10 p-4">
<div class="bg-amber-500/15 text-amber-500 rounded-full p-1" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-6"
aria-hidden="true">
<path fill-rule="evenodd"
d="M18 10a8 8 0 1 1-16 0 8 8 0 0 1 16 0Zm-8-5a.75.75 0 0 1 .75.75v4.5a.75.75 0 0 1-1.5 0v-4.5A.75.75 0 0 1 10 5Zm0 10a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
clip-rule="evenodd" />
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-semibold text-warning">Credit Card Expires Soon</h3>
<p class="text-xs font-medium sm:text-sm">Your credit card expires soon. Please update your payment
information.</p>
</div>
<button class="ml-auto" aria-label="dismiss alert">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor"
fill="none" stroke-width="2.5" class="size-4 shrink-0">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>
<!-- danger Alert -->
<div class="relative w-full overflow-hidden rounded-sm border border-red-500 bg-surface text-on-surface dark:bg-surface-dark dark:text-on-surface-dark"
role="alert">
<div class="flex w-full items-center gap-2 bg-danger/10 p-4">
<div class="bg-red-500/15 text-red-500 rounded-full p-1" aria-hidden="true">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-6"
aria-hidden="true">
<path fill-rule="evenodd"
d="M10 18a8 8 0 1 0 0-16 8 8 0 0 0 0 16ZM8.28 7.22a.75.75 0 0 0-1.06 1.06L8.94 10l-1.72 1.72a.75.75 0 1 0 1.06 1.06L10 11.06l1.72 1.72a.75.75 0 1 0 1.06-1.06L11.06 10l1.72-1.72a.75.75 0 0 0-1.06-1.06L10 8.94 8.28 7.22Z"
clip-rule="evenodd" />
</svg>
</div>
<div class="ml-2">
<h3 class="text-sm font-semibold text-danger">Invalid Email Address</h3>
<p class="text-xs font-medium sm:text-sm">The email address you entered is invalid. Please try again.</p>
</div>
<button class="ml-auto" aria-label="dismiss alert">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" stroke="currentColor"
fill="none" stroke-width="2.5" class="size-4 shrink-0">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
</div>

View File

@@ -0,0 +1,40 @@
<!-- default Badge -->
<span class="w-fit inline-flex overflow-hidden rounded-radius border border-outline bg-surface text-xs font-medium text-on-surface dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
<span class="px-2 py-1 bg-surface-alt/10 dark:bg-surface-dark-alt/10">Bagde</span>
</span>
<!-- inverse Badge -->
<span class="w-fit inline-flex overflow-hidden rounded-radius border border-outline-dark bg-surface text-xs font-medium text-on-surface dark:border-outline dark:bg-surface-dark dark:text-on-surface-dark">
<span class="px-2 py-1 bg-surface-dark-alt/10 dark:bg-surface-alt/10">Bagde</span>
</span>
<!-- primary Badge -->
<span class="w-fit inline-flex overflow-hidden rounded-radius border border-primary bg-surface text-xs font-medium text-primary dark:border-primary-dark dark:bg-surface-dark dark:text-primary-dark">
<span class="px-2 py-1 bg-primary/10 dark:bg-primary-dark/10">Bagde</span>
</span>
<!-- secondary Badge -->
<span class="w-fit inline-flex overflow-hidden rounded-radius border border-secondary bg-surface text-xs font-medium text-secondary dark:border-secondary-dark dark:bg-surface-dark dark:text-secondary-dark">
<span class="px-2 py-1 bg-secondary/10 dark:bg-secondary-dark/10">Bagde</span>
</span>
<!-- info Badge -->
<span class="w-fit inline-flex overflow-hidden rounded-radius border border-info bg-surface text-xs font-medium text-info dark:border-info dark:bg-surface-dark dark:text-info">
<span class="px-2 py-1 bg-info/10 dark:bg-info/10">Bagde</span>
</span>
<!-- success Badge -->
<span class="w-fit inline-flex overflow-hidden rounded-radius border border-success bg-surface text-xs font-medium text-success dark:border-success dark:bg-surface-dark dark:text-success">
<span class="px-2 py-1 bg-success/10 dark:bg-success/10">Bagde</span>
</span>
<!-- warning Badge -->
<span class="w-fit inline-flex overflow-hidden rounded-radius border border-warning bg-surface text-xs font-medium text-warning dark:border-warning dark:bg-surface-dark dark:text-warning">
<span class="px-2 py-1 bg-warning/10 dark:bg-warning/10">Bagde</span>
</span>
<!-- danger Badge -->
<span class="w-fit inline-flex overflow-hidden rounded-radius border border-danger bg-surface text-xs font-medium text-danger dark:border-danger dark:bg-surface-dark dark:text-danger">
<span class="px-2 py-1 bg-danger/10 dark:bg-danger/10">Bagde</span>
</span>

View File

@@ -0,0 +1,64 @@
<!-- primary Button with Icon -->
<button type="button" class="inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-radius bg-primary border border-primary dark:border-primary-dark px-4 py-2 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 text-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="size-5 fill-on-primary dark:fill-on-primary-dark" fill="currentColor">
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
</svg>
Create
</button>
<!-- secondary Button with Icon -->
<button type="button" class="inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-radius bg-secondary border border-secondary dark:border-secondary-dark px-4 py-2 text-sm font-medium tracking-wide text-on-secondary transition hover:opacity-75 text-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-secondary-dark dark:text-on-secondary-dark dark:focus-visible:outline-secondary-dark">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="size-5 fill-on-secondary dark:fill-on-secondary-dark" fill="currentColor">
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
</svg>
Create
</button>
<!-- alternate Button with Icon -->
<button type="button" class="inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-radius bg-surface-alt border border-surface-alt dark:border-surface-dark-alt px-4 py-2 text-sm font-medium tracking-wide text-on-surface-strong transition hover:opacity-75 text-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-surface-alt active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-surface-dark-alt dark:text-on-surface-dark-strong dark:focus-visible:outline-surface-dark-alt">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="size-5 fill-on-surface-strong dark:fill-on-surface-dark-strong" fill="currentColor">
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
</svg>
Create
</button>
<!-- inverse Button with Icon -->
<button type="button" class="inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-radius bg-surface-dark border border-surface-dark dark:border-surface px-4 py-2 text-sm font-medium tracking-wide text-on-surface-dark transition hover:opacity-75 text-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-surface-dark active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-surface dark:text-on-surface dark:focus-visible:outline-surface">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="size-5 fill-on-surface-dark dark:fill-on-surface" fill="currentColor">
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
</svg>
Create
</button>
<!-- info Button with Icon -->
<button type="button" class="inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-radius bg-info border border-info dark:border-info px-4 py-2 text-sm font-medium tracking-wide text-on-info transition hover:opacity-75 text-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-info dark:text-on-info dark:focus-visible:outline-info">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="size-5 fill-on-info dark:fill-on-info" fill="currentColor">
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
</svg>
Create
</button>
<!-- danger Button with Icon -->
<button type="button" class="inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-radius bg-danger border border-danger dark:border-danger px-4 py-2 text-sm font-medium tracking-wide text-on-danger transition hover:opacity-75 text-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-danger dark:text-on-danger dark:focus-visible:outline-danger">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="size-5 fill-on-danger dark:fill-on-danger" fill="currentColor">
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
</svg>
Create
</button>
<!-- warning Button with Icon -->
<button type="button" class="inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-radius bg-warning border border-warning dark:border-warning px-4 py-2 text-sm font-medium tracking-wide text-on-warning transition hover:opacity-75 text-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-warning active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-warning dark:text-on-warning dark:focus-visible:outline-warning">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="size-5 fill-on-warning dark:fill-on-warning" fill="currentColor">
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
</svg>
Create
</button>
<!-- success Button with Icon -->
<button type="button" class="inline-flex justify-center items-center gap-2 whitespace-nowrap rounded-radius bg-success border border-success dark:border-success px-4 py-2 text-sm font-medium tracking-wide text-on-success transition hover:opacity-75 text-center focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-success active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-success dark:text-on-success dark:focus-visible:outline-success">
<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="size-5 fill-on-success dark:fill-on-success" fill="currentColor">
<path fill-rule="evenodd" d="M12 3.75a.75.75 0 01.75.75v6.75h6.75a.75.75 0 010 1.5h-6.75v6.75a.75.75 0 01-1.5 0v-6.75H4.5a.75.75 0 010-1.5h6.75V4.5a.75.75 0 01.75-.75z" clip-rule="evenodd" />
</svg>
Create
</button>

View File

@@ -0,0 +1,24 @@
<!-- primary Button -->
<button type="button" class="whitespace-nowrap rounded-radius bg-primary border border-primary px-4 py-2 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-primary-dark dark:border-primary-dark dark:text-on-primary-dark dark:focus-visible:outline-primary-dark">Primary</button>
<!-- secondary Button -->
<button type="button" class="whitespace-nowrap rounded-radius bg-secondary border border-secondary px-4 py-2 text-sm font-medium tracking-wide text-on-secondary transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-secondary-dark dark:border-secondary-dark dark:text-on-secondary-dark dark:focus-visible:outline-secondary-dark">Secondary</button>
<!-- alternate Button -->
<button type="button" class="whitespace-nowrap rounded-radius bg-surface-alt border border-surface-alt px-4 py-2 text-sm font-medium tracking-wide text-on-surface-strong transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-surface-alt active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-surface-dark-alt dark:border-surface-dark-alt dark:text-on-surface-dark-strong dark:focus-visible:outline-surface-dark-alt">Alternate</button>
<!-- inverse Button -->
<button type="button" class="whitespace-nowrap rounded-radius bg-surface-dark border border-surface-dark px-4 py-2 text-sm font-medium tracking-wide text-on-surface-dark transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-surface-dark active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-surface dark:border-surface dark:text-on-surface dark:focus-visible:outline-surface">Inverse</button>
<!-- info Button -->
<button type="button" class="whitespace-nowrap rounded-radius bg-info border border-info px-4 py-2 text-sm font-medium tracking-wide text-onInfo transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-info dark:border-info dark:text-onInfo dark:focus-visible:outline-info">Info</button>
<!-- danger Button -->
<button type="button" class="whitespace-nowrap rounded-radius bg-danger border border-danger px-4 py-2 text-sm font-medium tracking-wide text-onDanger transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-danger dark:border-danger dark:text-onDanger dark:focus-visible:outline-danger">Danger</button>
<!-- warning Button -->
<button type="button" class="whitespace-nowrap rounded-radius bg-warning border border-warning px-4 py-2 text-sm font-medium tracking-wide text-onWarning transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-warning active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-warning dark:border-warning dark:text-onWarning dark:focus-visible:outline-warning">Warning</button>
<!-- success Button -->
<button type="button" class="whitespace-nowrap rounded-radius bg-success border border-success px-4 py-2 text-sm font-medium tracking-wide text-onSuccess transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-success active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:bg-success dark:border-success dark:text-onSuccess dark:focus-visible:outline-success">Success</button>

View File

@@ -0,0 +1,24 @@
<!-- primary Ghost Button -->
<button type="button" class="bg-transparent rounded-radius px-4 py-2 text-sm font-medium tracking-wide text-primary transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:text-primary-dark dark:focus-visible:outline-primary-dark">Primary</button>
<!-- secondary Ghost Button -->
<button type="button" class="bg-transparent rounded-radius px-4 py-2 text-sm font-medium tracking-wide text-secondary transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:text-secondary-dark dark:focus-visible:outline-secondary-dark">Secondary</button>
<!-- alternate Ghost Button -->
<button type="button" class="bg-transparent rounded-radius px-4 py-2 text-sm font-medium tracking-wide text-outline transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-outline active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:text-outline-dark dark:focus-visible:outline-outline-dark">Alternate</button>
<!-- inverse Ghost Button -->
<button type="button" class="bg-transparent rounded-radius px-4 py-2 text-sm font-medium tracking-wide text-surface-dark transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-surface-dark active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:text-surface dark:focus-visible:outline-surface">Inverse</button>
<!-- info Ghost Button -->
<button type="button" class="bg-transparent rounded-radius px-4 py-2 text-sm font-medium tracking-wide text-info transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:text-info dark:focus-visible:outline-info">Info</button>
<!-- danger Ghost Button -->
<button type="button" class="bg-transparent rounded-radius px-4 py-2 text-sm font-medium tracking-wide text-danger transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:text-danger dark:focus-visible:outline-danger">Danger</button>
<!-- warning Ghost Button -->
<button type="button" class="bg-transparent rounded-radius px-4 py-2 text-sm font-medium tracking-wide text-warning transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-warning active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:text-warning dark:focus-visible:outline-warning">Warning</button>
<!-- success Ghost Button -->
<button type="button" class="bg-transparent rounded-radius px-4 py-2 text-sm font-medium tracking-wide text-success transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-success active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:text-success dark:focus-visible:outline-success">Success</button>

View File

@@ -0,0 +1,24 @@
<!-- primary Outline Button -->
<button type="button" class="whitespace-nowrap bg-transparent rounded-radius border border-primary px-4 py-2 text-sm font-medium tracking-wide text-primary transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:border-primary-dark dark:text-primary-dark dark:focus-visible:outline-primary-dark">Primary</button>
<!-- secondary Outline Button -->
<button type="button" class="whitespace-nowrap bg-transparent rounded-radius border border-secondary px-4 py-2 text-sm font-medium tracking-wide text-secondary transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-secondary active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:border-secondary-dark dark:text-secondary-dark dark:focus-visible:outline-secondary-dark">Secondary</button>
<!-- alternate Outline Button -->
<button type="button" class="whitespace-nowrap bg-transparent rounded-radius border border-outline px-4 py-2 text-sm font-medium tracking-wide text-outline transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-outline active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:border-outline-dark dark:text-outline-dark dark:focus-visible:outline-outline-dark">Alternate</button>
<!-- inverse Outline Button -->
<button type="button" class="whitespace-nowrap bg-transparent rounded-radius border border-surface-dark px-4 py-2 text-sm font-medium tracking-wide text-surface-dark transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-surface-dark active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:border-surface dark:text-surface dark:focus-visible:outline-surface">Inverse</button>
<!-- info Outline Button -->
<button type="button" class="whitespace-nowrap bg-transparent rounded-radius border border-info px-4 py-2 text-sm font-medium tracking-wide text-info transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-info active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:border-info dark:text-info dark:focus-visible:outline-info">Info</button>
<!-- danger Outline Button -->
<button type="button" class="whitespace-nowrap bg-transparent rounded-radius border border-danger px-4 py-2 text-sm font-medium tracking-wide text-danger transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-danger active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:border-danger dark:text-danger dark:focus-visible:outline-danger">Danger</button>
<!-- warning Outline Button -->
<button type="button" class="whitespace-nowrap bg-transparent rounded-radius border border-warning px-4 py-2 text-sm font-medium tracking-wide text-warning transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-warning active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:border-warning dark:text-warning dark:focus-visible:outline-warning">Warning</button>
<!-- success Outline Button -->
<button type="button" class="whitespace-nowrap bg-transparent rounded-radius border border-success px-4 py-2 text-sm font-medium tracking-wide text-success transition hover:opacity-75 text-center focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-success active:opacity-100 active:outline-offset-0 disabled:opacity-75 disabled:cursor-not-allowed dark:border-success dark:text-success dark:focus-visible:outline-success">Success</button>

View File

@@ -0,0 +1,16 @@
<div x-data="{ isOpen: false, openedWithKeyboard: false }" class="relative w-fit" x-on:keydown.esc.window="isOpen = false, openedWithKeyboard = false">
<!-- Toggle Button -->
<button type="button" x-on:click="isOpen = ! isOpen" class="inline-flex items-center gap-2 whitespace-nowrap rounded-radius border border-outline bg-surface-alt px-4 py-2 text-sm font-medium tracking-wide transition hover:opacity-75 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-outline-strong dark:border-outline-dark dark:bg-surface-dark-alt dark:focus-visible:outline-outline-dark-strong" aria-haspopup="true" x-on:keydown.space.prevent="openedWithKeyboard = true" x-on:keydown.enter.prevent="openedWithKeyboard = true" x-on:keydown.down.prevent="openedWithKeyboard = true" x-bind:class="isOpen || openedWithKeyboard ? 'text-on-surface-strong dark:text-on-surface-dark-strong' : 'text-on-surface dark:text-on-surface-dark'" x-bind:aria-expanded="isOpen || openedWithKeyboard">
Actions Menu
<svg aria-hidden="true" fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4 rotate-0">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5"/>
</svg>
</button>
<!-- Dropdown Menu -->
<div x-cloak x-show="isOpen || openedWithKeyboard" x-transition x-trap="openedWithKeyboard" x-on:click.outside="isOpen = false, openedWithKeyboard = false" x-on:keydown.down.prevent="$focus.wrap().next()" x-on:keydown.up.prevent="$focus.wrap().previous()" class="absolute top-11 left-0 flex w-fit min-w-48 flex-col overflow-hidden rounded-radius border border-outline bg-surface-alt dark:border-outline-dark dark:bg-surface-dark-alt" role="menu">
<a href="#" class="bg-surface-alt px-4 py-2 text-sm text-on-surface hover:bg-surface-dark-alt/5 hover:text-on-surface-strong focus-visible:bg-surface-dark-alt/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:bg-surface-dark-alt dark:text-on-surface-dark dark:hover:bg-surface-alt/5 dark:hover:text-on-surface-dark-strong dark:focus-visible:bg-surface-alt/10 dark:focus-visible:text-on-surface-dark-strong" role="menuitem">Dashboard</a>
<a href="#" class="bg-surface-alt px-4 py-2 text-sm text-on-surface hover:bg-surface-dark-alt/5 hover:text-on-surface-strong focus-visible:bg-surface-dark-alt/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:bg-surface-dark-alt dark:text-on-surface-dark dark:hover:bg-surface-alt/5 dark:hover:text-on-surface-dark-strong dark:focus-visible:bg-surface-alt/10 dark:focus-visible:text-on-surface-dark-strong" role="menuitem">Subscription</a>
<a href="#" class="bg-surface-alt px-4 py-2 text-sm text-on-surface hover:bg-surface-dark-alt/5 hover:text-on-surface-strong focus-visible:bg-surface-dark-alt/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:bg-surface-dark-alt dark:text-on-surface-dark dark:hover:bg-surface-alt/5 dark:hover:text-on-surface-dark-strong dark:focus-visible:bg-surface-alt/10 dark:focus-visible:text-on-surface-dark-strong" role="menuitem">Settings</a>
<a href="#" class="bg-surface-alt px-4 py-2 text-sm text-on-surface hover:bg-surface-dark-alt/5 hover:text-on-surface-strong focus-visible:bg-surface-dark-alt/10 focus-visible:text-on-surface-strong focus-visible:outline-hidden dark:bg-surface-dark-alt dark:text-on-surface-dark dark:hover:bg-surface-alt/5 dark:hover:text-on-surface-dark-strong dark:focus-visible:bg-surface-alt/10 dark:focus-visible:text-on-surface-dark-strong" role="menuitem">Sign Out</a>
</div>
</div>

View File

@@ -1,6 +1,7 @@
{# Cart contents, swapped in via htmx on quantity change / removal so the page {# Cart contents, swapped in via htmx on quantity change / removal so the page
never does a full reload. Rendered inside <div id="cart-body"> in cart.html never does a full reload. Rendered inside <div id="cart-body"> in cart.html
and returned on its own by /cart/update and /cart/remove. #} and returned on its own by /cart/update and /cart/remove. #}
{% import "macros/ui.html" as ui %}
{% if items | length > 0 %} {% if items | length > 0 %}
<div class="overflow-hidden rounded-radius border border-outline dark:border-outline-dark"> <div class="overflow-hidden rounded-radius border border-outline dark:border-outline-dark">
<table class="w-full text-left text-sm"> <table class="w-full text-left text-sm">
@@ -60,12 +61,12 @@
</div> </div>
<div class="mt-6 flex flex-wrap justify-between gap-3"> <div class="mt-6 flex flex-wrap justify-between gap-3">
<a href="/shop" class="inline-flex items-center rounded-radius border border-outline px-4 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="cart-continue", lang=lang | default(value='sk')) }}</a> {{ ui::button_outline(label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop") }}
<a href="/checkout" class="inline-flex items-center justify-center rounded-radius bg-primary px-5 py-2 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">{{ t(key="cart-checkout", lang=lang | default(value='sk')) }}</a> {{ ui::button_primary(label=t(key="cart-checkout", lang=lang | default(value='sk')), href="/checkout") }}
</div> </div>
{% else %} {% else %}
<div class="rounded-radius border border-outline px-6 py-16 text-center dark:border-outline-dark"> <div class="rounded-radius border border-outline px-6 py-16 text-center dark:border-outline-dark">
<p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="cart-empty", lang=lang | default(value='sk')) }}</p> <p class="text-on-surface/70 dark:text-on-surface-dark/70">{{ t(key="cart-empty", lang=lang | default(value='sk')) }}</p>
<a href="/shop" class="mt-4 inline-flex items-center justify-center rounded-radius bg-primary px-4 py-2 text-sm font-medium text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">{{ t(key="cart-continue", lang=lang | default(value='sk')) }}</a> {{ ui::button_primary(label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop", extra="mt-4") }}
</div> </div>
{% endif %} {% endif %}

View File

@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ t(key="cart-title", lang=lang | default(value='sk')) }}{% endblock title %} {% block title %}{{ t(key="cart-title", lang=lang | default(value='sk')) }}{% endblock title %}

View File

@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ t(key="checkout-title", lang=lang | default(value='sk')) }}{% endblock title %} {% block title %}{{ t(key="checkout-title", lang=lang | default(value='sk')) }}{% endblock title %}
@@ -211,10 +212,7 @@
<span>{{ t(key="cart-total", lang=lang | default(value='sk')) }}</span> <span>{{ t(key="cart-total", lang=lang | default(value='sk')) }}</span>
<span class="tabular-nums text-primary dark:text-primary-dark" x-text="fmt(subtotal + carrierPrice) + ' {{ currency }}'"></span> <span class="tabular-nums text-primary dark:text-primary-dark" x-text="fmt(subtotal + carrierPrice) + ' {{ currency }}'"></span>
</div> </div>
<button type="submit" :disabled="!canSubmit" {{ ui::button_primary(label=t(key="checkout-place-order", lang=lang | default(value='sk')), type="submit", attrs=':disabled="!canSubmit"', extra="w-full disabled:opacity-40") }}
class="inline-flex w-full items-center justify-center rounded-radius bg-primary px-6 py-2.5 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 disabled:cursor-not-allowed disabled:opacity-40 dark:bg-primary-dark dark:text-on-primary-dark">
{{ t(key="checkout-place-order", lang=lang | default(value='sk')) }}
</button>
</aside> </aside>
</form> </form>
{% endblock content %} {% endblock content %}

View File

@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ t(key="order-confirmed-title", lang=lang | default(value='sk')) }}{% endblock title %} {% block title %}{{ t(key="order-confirmed-title", lang=lang | default(value='sk')) }}{% endblock title %}
@@ -55,7 +56,7 @@
{% endif %} {% endif %}
<div class="text-center"> <div class="text-center">
<a href="/shop" class="inline-flex items-center justify-center rounded-radius border border-outline px-5 py-2 text-sm font-medium text-on-surface transition hover:bg-surface-alt dark:border-outline-dark dark:text-on-surface-dark dark:hover:bg-surface-dark-alt">{{ t(key="cart-continue", lang=lang | default(value='sk')) }}</a> {{ ui::button_outline(label=t(key="cart-continue", lang=lang | default(value='sk')), href="/shop") }}
</div> </div>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@@ -1,4 +1,5 @@
{% extends "base.html" %} {% extends "base.html" %}
{% import "macros/ui.html" as ui %}
{% block title %}{{ product.name }}{% endblock title %} {% block title %}{{ product.name }}{% endblock title %}
@@ -47,10 +48,7 @@
<input id="quantity" name="quantity" type="number" min="1" max="{{ product.stock }}" value="1" <input id="quantity" name="quantity" type="number" min="1" max="{{ product.stock }}" value="1"
class="w-24 rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark"> class="w-24 rounded-radius border border-outline bg-surface px-3 py-2 text-sm text-on-surface focus:outline-2 focus:outline-primary dark:border-outline-dark dark:bg-surface-dark dark:text-on-surface-dark">
</div> </div>
<button type="submit" {{ ui::button_primary(label=t(key="add-to-cart", lang=lang | default(value='sk')), type="submit") }}
class="inline-flex items-center justify-center rounded-radius bg-primary px-5 py-2 text-sm font-medium tracking-wide text-on-primary transition hover:opacity-75 dark:bg-primary-dark dark:text-on-primary-dark">
{{ t(key="add-to-cart", lang=lang | default(value='sk')) }}
</button>
</form> </form>
<p class="text-sm text-on-surface/60 dark:text-on-surface-dark/60">{{ t(key="in-stock", lang=lang | default(value='sk')) }}: {{ product.stock }}</p> <p class="text-sm text-on-surface/60 dark:text-on-surface-dark/60">{{ t(key="in-stock", lang=lang | default(value='sk')) }}: {{ product.stock }}</p>
{% else %} {% else %}

View File

@@ -87,20 +87,18 @@ and then use it (instead of hand-rolling):
--- ---
## 4. Dropdown (Settings) ## 4. Dropdown (Settings) — ✅ DONE
**Penguin UI: `dropdown-menu/` (dropdown-with-click.html, dropdown-with-icons.html)** **Penguin UI: `dropdowns/dropdown-with-click.html`**
| # | Location | What it is | Size | - Exact upstream mirror at `assets/views/penguinui/dropdowns/dropdown-with-click.html` (reference only)
|---|----------|------------|------| - **De-duplicated**: the ~103-line copy-paste is now one shared partial
| 7 | `assets/views/base.html:110-162` | Language + theme settings dropdown (gear icon trigger) | ~53 lines | `assets/views/partials/settings_dropdown.html`, included by both `base.html`
| 8 | `assets/views/admin/base.html:117-166` | **100% duplicate** of the same dropdown | ~50 lines | and `admin/base.html` (each host keeps its own positioning wrapper
`<div x-data="{ open:false }" class="relative [ml-auto]">`).
**Details:** - Adopts Penguin's dropdown menu container + item treatment. Deviations: kept our
- Both dropdowns are **identical** (copy-paste), totaling ~103 lines of duplicated code gear icon-only trigger and core-Alpine open/@click.outside toggle (upstream's
- Alpine `x-data="{ open: false }"` + `@click.outside` `x-trap`/`$focus` need the Alpine Focus plugin we don't bundle); item hover
- Language switcher: English / Slovenčina buttons in a `<form method="post" action="/lang">` uses `bg-primary/5` for consistency with the rest of the UI.
- Theme tristate: system / light / dark with `setTheme()` and `currentTheme()` from inline JS
- Gear cog inline SVG icon
--- ---
@@ -261,51 +259,57 @@ This is copy-pasted 5 times.
--- ---
## 15. Alert / Error Banner ## 15. Alert / Error Banner — ✅ DONE
**Penguin UI: `alert/` (6 variants)** **Penguin UI: `alert/default-alert.html`**
| # | Location | What it is | Size | - Exact upstream mirror at `assets/views/penguinui/alert/default-alert.html` (reference only)
|---|----------|------------|------| - Adapted into the `ui::alert_danger(message, extra="")` macro in
| 39 | `assets/views/admin/login.html:25-30` | Login error alert (`role="alert"`, danger border) | ~6 lines | `assets/views/macros/ui.html` (compact one-line danger alert + danger icon).
| 40 | `assets/views/admin/orders/show.html:13-15` | Ship error alert (danger border) | ~3 lines | - Adopted at both sites: `admin/login.html` (login error) and
`admin/orders/show.html` (ship error).
**Pattern:** `rounded-radius border border-danger/40 bg-danger/10 px-3 py-2 text-sm text-danger`
--- ---
## 16. Badge / Status Pill ## 16. Badge / Status Pill — ✅ DONE
**Penguin UI: `badge/` (7 variants)** **Penguin UI: `badge/soft-color-badge.html`**
| # | Location | What it is | Size | - Exact upstream mirror at `assets/views/penguinui/badge/soft-color-badge.html` (reference only)
|---|----------|------------|------| - Adapted into the `ui::badge(label, variant)` macro in `assets/views/macros/ui.html`
| 41 | `assets/views/base.html:107-108` | Cart item-count badge (absolute-positioned, number) | ~2 lines | (variants: success | danger | warning | info | primary | neutral).
| 42 | `assets/views/admin/login.html:14-16` | "Auth" badge on login card header | ~3 lines | - Adopted at the status-pill sites: "Auth" badge (`admin/login.html`), order status
| 43 | `assets/views/admin/orders/index.html:27` | Order status inline pill | ~1 line | (`orders/index.html`, neutral), Published/Draft pills (`products.html` +
| 44 | `assets/views/admin/catalog/products.html:49-53` | Published/Draft status pill | ~5 lines | `categories.html`, success/neutral).
| 45 | `assets/views/admin/catalog/categories.html:40-44` | Published/Draft status pill | ~5 lines | - Intentionally left inline (not soft-color pills): the cart item-count **notification**
| 46 | `assets/views/shop/_card.html:27` | "Out of stock" badge (danger background) | ~1 line | badge in `base.html` (count bubble, a different Penguin badge type) and the
block-style "out of stock" notice in `_card.html`.
--- ---
## 17. Buttons ## 17. Buttons — ✅ DONE (canonical macros + adopted)
**Penguin UI: `buttons/` (6 variants)** **Penguin UI: `buttons/default-button.html`, `outline-button.html`, `ghost-button.html`, `button-with-icon.html`**
| # | Location | What it is | Style | Count | - Exact upstream mirrors at `assets/views/penguinui/buttons/*.html` (reference only).
|---|----------|------------|-------|-------| - Canonical button macros in `assets/views/macros/ui.html`:
| 47 | Across all templates | Primary button | `bg-primary text-on-primary rounded-radius px-4 py-2` | 15+ | `ui::button_primary`, `ui::button_outline` (neutral secondary), `ui::button_danger`,
| 48 | Across all templates | Outline/secondary button | `border border-outline text-on-surface rounded-radius px-3 py-1.5` | 20+ | `ui::button_ghost`. Each takes `label`, `type`, `href` (renders `<a>` vs `<button>`),
| 49 | `base.html:86-88`, `admin/base.html:92-95` | Danger button (logout, delete) | `text-danger` with hover bg | 4 | `attrs` (raw — htmx / `:disabled` / name / value), `extra` (extra classes).
| 50 | `base.html:84` | Warning-colored button (admin link) | `text-warning` | 1 | Upstream's color-button token typos (`text-onDanger` etc.) are fixed to our real
| 51 | `base.html:112-113`, `admin/base.html:118-119`, ... | Icon-only ghost button (gear, hamburger, chevron) | `size-9 inline-flex items-center justify-center` | 10+ | tokens (`text-on-danger`).
| 52 | `_cart_body.html:46` | Text/link button (Remove) | `text-danger hover:underline` | 1 | - Adopted across every standard filled/outline/submit button: login, product &
category forms (save/cancel), products/categories "new" + empty-state CTAs,
orders detail (back/ship/status), shipping save, cart (continue/checkout/empty),
checkout place-order (`:disabled` via `attrs`), product detail add-to-cart,
order-confirmed continue.
- Intentionally left inline (different components, not the standard button):
icon-only ghost buttons (#51 gear/hamburger/chevron), compact table row-action
buttons (`edit`/`view`/`delete`, `px-3 py-1.5 text-xs`), the `text-danger`
"Remove" text link (#52), the file-input button (#13), and nav menu links.
Summary of button variants handcoded: > Gotcha for future macro use: Tera renders `{% include %}` in the **includer's**
- `bg-primary` (solid primary) > macro scope, so a template that includes a partial which calls `ui::` must also
- `border border-outline` (outline) > `{% import "macros/ui.html" as ui %}` itself (see `shop/cart.html` →
- `file:bg-primary` (file input button) > `shop/_cart_body.html`). In an `{% extends %}` child the import must sit
- `text-danger` / `text-warning` (semantic) > directly after `{% extends %}` with no comment/content before it.
- `hover:opacity-75` vs `hover:opacity-90` — inconsistent hover effects
- `tracking-wide` on some, not others
--- ---