{# Flash messages partial - rendered via OOB swap #}
{% if flash_messages %}
<div class="flash-messages">
{% for flash in flash_messages %}
<div class="flash flash-{{ flash.level }}"
role="alert"
hx-on::load="setTimeout(() => this.remove(), 5000)">
<div class="flash-content">
{% match flash.level %}
{% when "success" %}
<svg class="flash-icon" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"/>
</svg>
{% when "error" %}
<svg class="flash-icon" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/>
</svg>
{% when "warning" %}
<svg class="flash-icon" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
</svg>
{% when "info" %}
<svg class="flash-icon" width="20" height="20" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd"/>
</svg>
{% endmatch %}
<span class="flash-message">{{ flash.message }}</span>
</div>
<button type="button" class="flash-close" aria-label="Dismiss" onclick="this.parentElement.remove()">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z"/>
</svg>
</button>
</div>
{% endfor %}
</div>
<style>
.flash-messages {
position: fixed;
top: 1rem;
right: 1rem;
z-index: 1000;
max-width: 400px;
}
.flash {
display: flex;
align-items: flex-start;
gap: 0.75rem;
padding: 1rem;
margin-bottom: 0.5rem;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.flash-content {
display: flex;
align-items: center;
gap: 0.5rem;
flex: 1;
}
.flash-icon {
flex-shrink: 0;
}
.flash-close {
background: none;
border: none;
cursor: pointer;
padding: 0;
opacity: 0.6;
transition: opacity 0.2s;
}
.flash-close:hover {
opacity: 1;
}
.flash-success {
background-color: #d4edda;
border-left: 4px solid #28a745;
color: #155724;
}
.flash-error {
background-color: #f8d7da;
border-left: 4px solid #dc3545;
color: #721c24;
}
.flash-warning {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
color: #856404;
}
.flash-info {
background-color: #d1ecf1;
border-left: 4px solid #17a2b8;
color: #0c5460;
}
</style>
{% endif %}