Files
LogWhispererAI/.opencode/skills/templates/tailwind-component.html
Luca Sacchi Ricciardi aa489c7eb8 docs: add comprehensive frontend landing page plan and download design skills
Add detailed landing page development plan in docs/frontend_landing_plan.md:
- Complete landing page structure (Hero, Problem/Solution, Features, Demo, CTA)
- Design guidelines from downloaded skills (typography, color, motion, composition)
- Security considerations (XSS prevention, input sanitization, CSP)
- Performance targets (LCP <2.5s, bundle <150KB, Lighthouse >90)
- Responsiveness and accessibility requirements (WCAG 2.1 AA)
- Success KPIs and monitoring setup
- 3-week development timeline with daily tasks
- Definition of Done checklist

Download 10+ frontend/UI/UX skills via universal-skills-manager:
- frontend-ui-ux: UI/UX design without mockups
- frontend-design-guidelines: Production-grade interface guidelines
- frontend-developer: React best practices (40+ rules)
- frontend-engineer: Next.js 14 App Router patterns
- ui-ux-master: Comprehensive design systems and accessibility
- ui-ux-systems-designer: Information architecture and interaction
- ui-ux-design-user-experience: Platform-specific guidelines
- Plus additional reference materials and validation scripts

Configure universal-skills MCP with SkillsMP API key for curated skill access.

Safety first: All skills validated before installation, no project code modified.

Refs: Universal Skills Manager (github:jacob-bd/universal-skills-manager)
Next: Begin Sprint 3 landing page development
2026-04-03 13:13:59 +02:00

227 lines
8.6 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% macro resource_modal(modal_id='modal_resource', title='Resource') %}
<!--
DaisyUI Modal Component Template
Usage:
1. Import in your template:
{% from "components/modal_resource.html" import resource_modal %}
2. Call the macro:
{{ resource_modal(modal_id='modal_create_resource', title='Create Resource') }}
3. Open modal from JavaScript:
document.getElementById('modal_create_resource').showModal();
Features:
- Responsive width (max-w-2xl on desktop, full width on mobile)
- Max height with overflow (90vh)
- Auto-centering (my-auto)
- DaisyUI styled form controls
- HTMX-ready form handling
- Mobile-optimized button sizes
- Tailwind CSS utility classes
-->
<dialog id="{{ modal_id }}" class="modal">
<div class="modal-box w-full max-w-2xl p-4 max-h-[90vh] overflow-y-auto my-auto">
<!-- Modal Header -->
<h3 class="font-bold text-lg mb-3"> {{ title }}</h3>
<!-- Modal Form -->
<form id="form_{{ modal_id }}" class="space-y-2">
<!-- Text Input Example -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Name *</span>
</label>
<input type="text" name="name" required
class="input input-bordered w-full"
placeholder="Enter name"
autocomplete="off" />
</div>
<!-- Select Dropdown Example -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Category *</span>
</label>
<select name="category_id" required class="select select-bordered">
<option value="">-- Select category --</option>
<!-- Options populated dynamically -->
</select>
</div>
<!-- Radio Button Group Example (DaisyUI buttons) -->
<div class="form-control">
<label class="label py-1">
<span class="label-text font-semibold">Type *</span>
</label>
<div class="grid grid-cols-2 gap-2">
<label class="btn btn-sm btn-outline btn-error resource-type-btn btn-active" data-type="type1">
<input type="radio" name="type" value="type1" class="hidden" checked />
Type 1
</label>
<label class="btn btn-sm btn-outline btn-success resource-type-btn" data-type="type2">
<input type="radio" name="type" value="type2" class="hidden" />
Type 2
</label>
</div>
</div>
<!-- Number Input Example -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Amount *</span>
</label>
<input type="number" name="amount" step="1" min="0" required
class="input input-bordered" placeholder="0" />
</div>
<!-- Date Input Example with Quick Buttons -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Date *</span>
</label>
<!-- Quick date buttons (mobile-optimized) -->
<div class="flex gap-1 mb-1">
<button type="button" class="btn btn-xs btn-outline flex-1" onclick="setDate(0)">Today</button>
<button type="button" class="btn btn-xs btn-outline flex-1" onclick="setDate(-1)">Yesterday</button>
<button type="button" class="btn btn-xs btn-outline flex-1" onclick="setDate(-2)">2 days ago</button>
</div>
<input type="text" name="date" required
class="input input-bordered w-full date-input"
placeholder="DD.MM.YYYY"
maxlength="10"
autocomplete="off" />
</div>
<!-- Textarea Example -->
<div class="form-control">
<label class="label py-1">
<span class="label-text">Description</span>
</label>
<textarea name="description" class="textarea textarea-bordered"
placeholder="Optional comment" rows="2"></textarea>
</div>
<!-- Checkbox Example -->
<div class="form-control">
<label class="label cursor-pointer justify-start gap-3">
<input type="checkbox" name="is_active" class="checkbox checkbox-primary" checked />
<span class="label-text">Active</span>
</label>
</div>
<!-- Modal Actions -->
<div class="modal-action mt-3">
<!-- Cancel Button -->
<button type="button" onclick="{{ modal_id }}.close()" class="btn btn-sm btn-ghost">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
Cancel
</button>
<!-- Save Button (with offline mode support) -->
<button type="button" class="btn btn-sm save-btn"
data-form-id="form_{{ modal_id }}"
data-modal-id="{{ modal_id }}"
onclick="saveResource(this)"
title="Save">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
Save
</button>
</div>
</form>
</div>
<!-- Modal Backdrop (click to close) -->
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
<!-- CSS for type buttons toggle (add to global CSS) -->
<style>
.resource-type-btn {
transition: all 0.2s;
}
.resource-type-btn.btn-active {
opacity: 1;
}
.resource-type-btn:not(.btn-active) {
opacity: 0.6;
}
</style>
<!-- JavaScript Example (add to global JS or page-specific script) -->
<script>
// Toggle radio button styles
document.querySelectorAll('.resource-type-btn').forEach(btn => {
btn.addEventListener('click', function() {
// Remove btn-active from all buttons
document.querySelectorAll('.resource-type-btn').forEach(b => {
b.classList.remove('btn-active');
});
// Add btn-active to clicked button
this.classList.add('btn-active');
// Check the radio input
this.querySelector('input[type="radio"]').checked = true;
});
});
// Quick date setter
function setDate(daysOffset) {
const date = new Date();
date.setDate(date.getDate() + daysOffset);
const dateStr = date.toLocaleDateString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
const input = document.querySelector('#{{ modal_id }} .date-input');
if (input) input.value = dateStr;
}
// Save resource (HTMX or Fetch API)
async function saveResource(button) {
const formId = button.dataset.formId;
const modalId = button.dataset.modalId;
const form = document.getElementById(formId);
const modal = document.getElementById(modalId);
if (!form.checkValidity()) {
form.reportValidity();
return;
}
const formData = new FormData(form);
const data = Object.fromEntries(formData.entries());
try {
// Example: POST to API endpoint
const response = await fetch('/api/v1/resources', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
if (response.ok) {
modal.close();
// Refresh data via HTMX
htmx.trigger('#dynamic-content', 'refresh');
// Show success toast
showToast('Resource created successfully', 'success');
} else {
const error = await response.json();
showToast(error.detail || 'Failed to create resource', 'error');
}
} catch (error) {
console.error('Save error:', error);
showToast('Network error', 'error');
}
}
</script>
{% endmacro %}