feat(taff): page /trouver-du-taf + types + JSON + PlatformeTaffCard sur main
Cherry-pick depuis feat/aep-taff-v1 — 24 plateformes scorées, page Jobs complète. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
472
pages/trouver-du-taf.vue
Normal file
472
pages/trouver-du-taf.vue
Normal file
@@ -0,0 +1,472 @@
|
||||
<template>
|
||||
<div class="taff-page" style="background: var(--nav-bg); min-height: 100%;">
|
||||
|
||||
<!-- ── Intro ─────────────────────────────────────────────────────── -->
|
||||
<div class="taff-header">
|
||||
<div class="taff-header-inner">
|
||||
<h1 class="taff-title">Trouver du taf en archi</h1>
|
||||
<p class="taff-subtitle">
|
||||
Annuaire critique des plateformes de mise en relation archi ↔ particulier.
|
||||
Évaluées sur 5 axes éthiques — rémunération, transparence, pratiques pro, écologie, qualité du matching.
|
||||
Cible : archi freelance indépendant en France.
|
||||
</p>
|
||||
<div class="taff-stats">
|
||||
<span class="taff-stat" style="color: #3d5534;">
|
||||
<span class="taff-stat-dot" style="background: #5a7a4a;"></span>
|
||||
{{ stats.recommande }} Recommandé{{ stats.recommande > 1 ? 's' : '' }} AEP
|
||||
</span>
|
||||
<span class="taff-stat" style="color: #7a5f2a;">
|
||||
<span class="taff-stat-dot" style="background: #c4a472;"></span>
|
||||
{{ stats.sous_reserve }} Sous réserve
|
||||
</span>
|
||||
<span class="taff-stat" style="color: #7a3322;">
|
||||
<span class="taff-stat-dot" style="background: #a85d3e;"></span>
|
||||
{{ stats.a_eviter }} À éviter
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Filtres ─────────────────────────────────────────────────── -->
|
||||
<div class="taff-filters-bar">
|
||||
<div class="taff-filters-inner">
|
||||
|
||||
<!-- Onglets B2C / AO publics -->
|
||||
<div class="taff-tabs">
|
||||
<button
|
||||
type="button"
|
||||
class="taff-tab"
|
||||
:class="{ 'taff-tab--active': activeTab === 'b2c' }"
|
||||
@click="activeTab = 'b2c'; resetFilters()"
|
||||
>
|
||||
Plateformes B2C
|
||||
<span class="taff-tab-count">{{ b2cCount }}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="taff-tab"
|
||||
:class="{ 'taff-tab--active': activeTab === 'ao' }"
|
||||
@click="activeTab = 'ao'; resetFilters()"
|
||||
>
|
||||
Appels d'offres publics
|
||||
<span class="taff-tab-count">{{ aoCount }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filtres tag global -->
|
||||
<div class="taff-filter-group">
|
||||
<button
|
||||
v-for="t in TAG_OPTIONS"
|
||||
:key="t.value"
|
||||
type="button"
|
||||
class="taff-filter-btn"
|
||||
:class="{ 'taff-filter-btn--active': filterTag === t.value }"
|
||||
:style="filterTag === t.value
|
||||
? `background: ${t.bg}; color: ${t.text}; border-color: ${t.accent};`
|
||||
: ''"
|
||||
@click="filterTag = filterTag === t.value ? '' : t.value"
|
||||
>
|
||||
{{ t.emoji }} {{ t.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Filtre secteur (B2C uniquement) -->
|
||||
<div v-if="activeTab === 'b2c'" class="taff-filter-group">
|
||||
<button
|
||||
v-for="s in SECTEUR_OPTIONS"
|
||||
:key="s.value"
|
||||
type="button"
|
||||
class="taff-filter-btn"
|
||||
:class="{ 'taff-filter-btn--active': filterSecteur === s.value }"
|
||||
@click="filterSecteur = filterSecteur === s.value ? '' : s.value"
|
||||
>
|
||||
{{ s.label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Recherche -->
|
||||
<label class="taff-search">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="color: var(--nav-text-muted); flex-shrink: 0;">
|
||||
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
||||
</svg>
|
||||
<input
|
||||
v-model="search"
|
||||
type="search"
|
||||
placeholder="Rechercher..."
|
||||
class="taff-search-input"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button v-if="search" type="button" @click.stop="search = ''" class="taff-search-clear" aria-label="Effacer">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
</label>
|
||||
|
||||
<!-- Compteur + reset -->
|
||||
<div class="flex items-center gap-3 ml-auto">
|
||||
<span class="text-xs font-bold uppercase tracking-widest" style="color: var(--nav-text-muted);">
|
||||
{{ filtered.length }} résultat{{ filtered.length > 1 ? 's' : '' }}
|
||||
</span>
|
||||
<button
|
||||
v-if="hasFilters"
|
||||
type="button"
|
||||
class="text-xs underline hover:opacity-70"
|
||||
style="color: var(--nav-text-muted);"
|
||||
@click="resetFilters"
|
||||
>Effacer</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Grille ─────────────────────────────────────────────────── -->
|
||||
<div class="taff-grid-wrap">
|
||||
<div v-if="filtered.length === 0" class="taff-empty">
|
||||
<p style="color: var(--nav-text-muted);">Aucune plateforme ne correspond à ces filtres.</p>
|
||||
<button type="button" class="taff-reset-btn" @click="resetFilters">Réinitialiser les filtres</button>
|
||||
</div>
|
||||
<div v-else class="taff-grid">
|
||||
<PlatformeTaffCard
|
||||
v-for="p in filtered"
|
||||
:key="p.id"
|
||||
:plateforme="p"
|
||||
@open="openModal"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Note juridique ────────────────────────────────────────── -->
|
||||
<div class="taff-disclaimer">
|
||||
<p>
|
||||
Évaluations basées sur des données publiques (CGV, Trustpilot, presse spécialisée) collectées en mai 2026.
|
||||
AEP est un méta-annuaire critique, pas un opérateur. Les fiches « À éviter ❌ » sont validées manuellement avant publication.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- ── Modal ─────────────────────────────────────────────────── -->
|
||||
<Teleport to="body">
|
||||
<Transition name="taff-backdrop">
|
||||
<div
|
||||
v-if="modalPlateforme"
|
||||
class="fixed inset-0 z-[1500]"
|
||||
style="background: rgba(26,34,56,0.55);"
|
||||
@click="closeModal"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Transition>
|
||||
<Transition name="taff-modal">
|
||||
<div
|
||||
v-if="modalPlateforme"
|
||||
class="fixed z-[1501] top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col"
|
||||
style="width: min(760px, 92vw); max-height: 90vh; background: var(--nav-bg); border-radius: 16px; box-shadow: 0 16px 64px rgba(26,34,56,0.28); overflow: hidden;"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
:aria-label="modalPlateforme.nom"
|
||||
tabindex="-1"
|
||||
@keydown.esc="closeModal"
|
||||
>
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between px-5 py-3 shrink-0" style="background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt);">
|
||||
<div class="flex items-center gap-2 min-w-0">
|
||||
<span
|
||||
class="inline-flex items-center gap-1 px-2.5 py-1 rounded-full text-sm font-semibold shrink-0"
|
||||
:style="`background: ${modalTagConfig.bg}; color: ${modalTagConfig.text};`"
|
||||
>{{ modalTagConfig.emoji }} {{ modalTagConfig.label }}</span>
|
||||
<span class="font-semibold text-base truncate" style="color: var(--nav-text);">{{ modalPlateforme.nom }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 shrink-0 ml-3">
|
||||
<a
|
||||
:href="modalPlateforme.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-xs font-medium transition-opacity hover:opacity-70"
|
||||
style="background: var(--nav-bg-alt); color: var(--nav-text);"
|
||||
>
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>
|
||||
<polyline points="15 3 21 3 21 9"/>
|
||||
<line x1="10" y1="14" x2="21" y2="3"/>
|
||||
</svg>
|
||||
Visiter
|
||||
</a>
|
||||
<button
|
||||
@click="closeModal"
|
||||
class="w-8 h-8 rounded-lg flex items-center justify-center hover:opacity-70 transition-opacity"
|
||||
style="background: var(--nav-bg-alt); color: var(--nav-text-muted);"
|
||||
aria-label="Fermer"
|
||||
>
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
|
||||
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="overflow-y-auto flex-1 px-5 py-5 space-y-5">
|
||||
|
||||
<!-- Scoring axes -->
|
||||
<div>
|
||||
<div class="modal-label">Évaluation AEP — 5 axes</div>
|
||||
<div class="modal-axes">
|
||||
<div
|
||||
v-for="axe in AXES"
|
||||
:key="axe.id"
|
||||
v-show="modalPlateforme.scoring[axe.id] !== null"
|
||||
class="modal-axe"
|
||||
:style="`background: ${axeScoreBg(modalPlateforme.scoring[axe.id] as string)};`"
|
||||
>
|
||||
<span class="text-xl leading-none">{{ axe.icon }}</span>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xs font-bold uppercase tracking-wider" style="color: var(--nav-text-muted);">{{ axe.label }}</span>
|
||||
<span class="text-lg leading-none" :style="`color: ${axeScoreText(modalPlateforme.scoring[axe.id] as string)};`">
|
||||
{{ modalPlateforme.scoring[axe.id] }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-3 text-sm leading-relaxed italic rounded-lg px-3 py-2.5" style="color: var(--nav-text-muted); background: var(--nav-bg-alt);">
|
||||
{{ modalPlateforme.scoring.justification_tag }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<div>
|
||||
<div class="modal-label">Fiche détaillée</div>
|
||||
<div class="space-y-3">
|
||||
<div v-for="section in parsedDescription" :key="section.title">
|
||||
<h5 class="text-sm font-bold mb-1" style="color: var(--nav-text);">{{ section.title }}</h5>
|
||||
<p class="text-sm leading-relaxed" style="color: var(--nav-text-muted);">{{ section.body }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Infos pratiques -->
|
||||
<div>
|
||||
<div class="modal-label">Infos pratiques</div>
|
||||
<div class="modal-meta-grid">
|
||||
<div class="modal-meta-item">
|
||||
<span class="modal-meta-key">Type</span>
|
||||
<span class="modal-meta-val">{{ modalPlateforme.type === 'b2c-mise-en-relation' ? 'Plateforme B2C' : 'Appels d\'offres publics' }}</span>
|
||||
</div>
|
||||
<div class="modal-meta-item">
|
||||
<span class="modal-meta-key">Coût d'entrée</span>
|
||||
<span class="modal-meta-val">{{ coutLabel(modalPlateforme.cout_entree) }}</span>
|
||||
</div>
|
||||
<div class="modal-meta-item">
|
||||
<span class="modal-meta-key">Zone</span>
|
||||
<span class="modal-meta-val">France entière</span>
|
||||
</div>
|
||||
<div class="modal-meta-item">
|
||||
<span class="modal-meta-key">Secteurs</span>
|
||||
<span class="modal-meta-val">{{ modalPlateforme.secteurs_servis.map(s => SECTEUR_LABELS[s] ?? s).join(', ') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer meta -->
|
||||
<div class="flex items-center gap-2 text-xs flex-wrap pb-1" style="color: var(--nav-text-muted);">
|
||||
<span>Fiche créée le {{ modalPlateforme.date_creation_fiche }}</span>
|
||||
<span>·</span>
|
||||
<span>{{ modalPlateforme.source_donnees.length }} source{{ modalPlateforme.source_donnees.length > 1 ? 's' : '' }}</span>
|
||||
<span v-if="modalPlateforme.flag_validation_jules" class="font-semibold" style="color: #7a5f2a;">
|
||||
· ⚠️ En attente de validation avant publication
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PlateformeTaff } from '~/types/plateforme-taff'
|
||||
|
||||
useHead({
|
||||
title: 'Trouver du taf en archi — AEP',
|
||||
meta: [
|
||||
{ name: 'description', content: "Annuaire critique des plateformes B2C archi–particulier. Évaluations éthiques sur 5 axes : rémunération, transparence, pratiques pro, écologie, matching." }
|
||||
]
|
||||
})
|
||||
|
||||
const { data } = await useAsyncData('plateformes-taff', () =>
|
||||
$fetch<{ meta: any; plateformes: PlateformeTaff[] }>('/data/plateformes-taff.json')
|
||||
)
|
||||
|
||||
const allPlateformes = computed(() => data.value?.plateformes ?? [])
|
||||
const stats = computed(() => data.value?.meta?.repartition ?? { recommande: 0, sous_reserve: 0, a_eviter: 0 })
|
||||
const b2cCount = computed(() => allPlateformes.value.filter(p => p.type === 'b2c-mise-en-relation').length)
|
||||
const aoCount = computed(() => allPlateformes.value.filter(p => p.type === 'appel-offre-public').length)
|
||||
|
||||
// Filtres
|
||||
const activeTab = ref<'b2c' | 'ao'>('b2c')
|
||||
const filterTag = ref('')
|
||||
const filterSecteur = ref('')
|
||||
const search = ref('')
|
||||
const hasFilters = computed(() => !!(filterTag.value || filterSecteur.value || search.value))
|
||||
|
||||
function resetFilters() {
|
||||
filterTag.value = ''
|
||||
filterSecteur.value = ''
|
||||
search.value = ''
|
||||
}
|
||||
|
||||
const filtered = computed(() => {
|
||||
let list = allPlateformes.value.filter(p =>
|
||||
activeTab.value === 'b2c'
|
||||
? p.type === 'b2c-mise-en-relation'
|
||||
: p.type === 'appel-offre-public'
|
||||
)
|
||||
if (filterTag.value)
|
||||
list = list.filter(p => p.scoring.tag_global === filterTag.value)
|
||||
if (filterSecteur.value)
|
||||
list = list.filter(p => (p.secteurs_servis as string[]).includes(filterSecteur.value))
|
||||
if (search.value) {
|
||||
const q = search.value.toLowerCase()
|
||||
list = list.filter(p =>
|
||||
p.nom.toLowerCase().includes(q) ||
|
||||
p.description_courte.toLowerCase().includes(q)
|
||||
)
|
||||
}
|
||||
const ORDER: Record<string, number> = { 'recommande': 0, 'sous-reserve': 1, 'a-eviter': 2 }
|
||||
return [...list].sort((a, b) => (ORDER[a.scoring.tag_global] ?? 9) - (ORDER[b.scoring.tag_global] ?? 9))
|
||||
})
|
||||
|
||||
// Options filtres
|
||||
const TAG_OPTIONS = [
|
||||
{ value: 'recommande', emoji: '✅', label: 'Recommandé', bg: 'rgba(90,122,74,0.12)', text: '#3d5534', accent: '#5a7a4a' },
|
||||
{ value: 'sous-reserve', emoji: '⚠️', label: 'Sous réserve', bg: 'rgba(196,164,114,0.15)', text: '#7a5f2a', accent: '#c4a472' },
|
||||
{ value: 'a-eviter', emoji: '❌', label: 'À éviter', bg: 'rgba(168,93,62,0.12)', text: '#7a3322', accent: '#a85d3e' },
|
||||
]
|
||||
|
||||
const SECTEUR_OPTIONS = [
|
||||
{ value: 'renovation', label: 'Rénovation' },
|
||||
{ value: 'construction-neuve', label: 'Neuf' },
|
||||
{ value: 'architecture-interieure', label: 'Archi intérieure' },
|
||||
{ value: 'mar-conseil', label: 'MAR / Conseil' },
|
||||
{ value: 'urbanisme', label: 'Urbanisme' },
|
||||
{ value: 'paysage', label: 'Paysage' },
|
||||
{ value: 'transversal', label: 'Transversal' },
|
||||
]
|
||||
|
||||
const SECTEUR_LABELS: Record<string, string> = {
|
||||
'renovation': 'Rénovation', 'construction-neuve': 'Neuf',
|
||||
'architecture-interieure': 'Archi intérieure', 'urbanisme': 'Urbanisme',
|
||||
'paysage': 'Paysage', 'mar-conseil': 'MAR / Conseil', 'transversal': 'Transversal',
|
||||
}
|
||||
|
||||
const COUT_LABELS: Record<string, string> = {
|
||||
'gratuit': 'Gratuit', 'freemium': 'Freemium',
|
||||
'abonnement': 'Abonnement', 'lead-paye': 'Lead payant', 'commission': 'Commission',
|
||||
}
|
||||
function coutLabel(c: string) { return COUT_LABELS[c] ?? c }
|
||||
|
||||
// Axes
|
||||
const AXES = [
|
||||
{ id: 'remuneration' as const, icon: '🪙', label: 'Rémunération' },
|
||||
{ id: 'transparence' as const, icon: '🔍', label: 'Transparence' },
|
||||
{ id: 'pratiques' as const, icon: '⚖️', label: 'Pratiques pro' },
|
||||
{ id: 'ecologie' as const, icon: '🌿', label: 'Écologie' },
|
||||
{ id: 'matching' as const, icon: '🎯', label: 'Matching' },
|
||||
]
|
||||
|
||||
function axeScoreBg(score: string) {
|
||||
if (score === '✅') return 'rgba(90,122,74,0.1)'
|
||||
if (score === '⚠️') return 'rgba(196,164,114,0.15)'
|
||||
if (score === '❌') return 'rgba(168,93,62,0.1)'
|
||||
return 'var(--nav-bg-alt)'
|
||||
}
|
||||
function axeScoreText(score: string) {
|
||||
if (score === '✅') return '#3d5534'
|
||||
if (score === '⚠️') return '#7a5f2a'
|
||||
if (score === '❌') return '#7a3322'
|
||||
return 'var(--nav-text-muted)'
|
||||
}
|
||||
|
||||
// Modal
|
||||
const modalPlateforme = ref<PlateformeTaff | null>(null)
|
||||
function openModal(p: PlateformeTaff) { modalPlateforme.value = p }
|
||||
function closeModal() { modalPlateforme.value = null }
|
||||
|
||||
const TAG_CONFIG: Record<string, { emoji: string; label: string; bg: string; text: string }> = {
|
||||
'recommande': { emoji: '✅', label: 'Recommandé AEP', bg: 'rgba(90,122,74,0.12)', text: '#3d5534' },
|
||||
'sous-reserve': { emoji: '⚠️', label: 'Sous réserve', bg: 'rgba(196,164,114,0.15)', text: '#7a5f2a' },
|
||||
'a-eviter': { emoji: '❌', label: 'À éviter', bg: 'rgba(168,93,62,0.12)', text: '#7a3322' },
|
||||
}
|
||||
const modalTagConfig = computed(() =>
|
||||
modalPlateforme.value
|
||||
? (TAG_CONFIG[modalPlateforme.value.scoring.tag_global] ?? TAG_CONFIG['sous-reserve'])
|
||||
: TAG_CONFIG['sous-reserve']
|
||||
)
|
||||
|
||||
// Parse description (format "## Titre\nContenu\n\n## Titre2\nContenu2")
|
||||
const parsedDescription = computed(() => {
|
||||
if (!modalPlateforme.value) return []
|
||||
const raw = modalPlateforme.value.description
|
||||
const sections: { title: string; body: string }[] = []
|
||||
const parts = raw.split(/\n\n## /)
|
||||
parts.forEach((part, i) => {
|
||||
const text = i === 0 ? part.replace(/^## /, '') : part
|
||||
const nl = text.indexOf('\n')
|
||||
if (nl < 0) return
|
||||
sections.push({ title: text.slice(0, nl).trim(), body: text.slice(nl + 1).trim() })
|
||||
})
|
||||
return sections
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.taff-page { max-width: 1280px; margin: 0 auto; padding-bottom: 3rem; }
|
||||
|
||||
.taff-header { padding: 2.5rem 1.5rem 1.5rem; border-bottom: 1px solid var(--nav-bg-alt); }
|
||||
.taff-header-inner { max-width: 680px; }
|
||||
.taff-title { font-size: 1.875rem; font-weight: 800; color: var(--nav-text); margin-bottom: 0.5rem; letter-spacing: -0.02em; }
|
||||
.taff-subtitle { font-size: 0.9375rem; color: var(--nav-text-muted); line-height: 1.6; margin-bottom: 1rem; }
|
||||
.taff-stats { display: flex; gap: 1.25rem; flex-wrap: wrap; }
|
||||
.taff-stat { display: flex; align-items: center; gap: 0.375rem; font-size: 0.8125rem; font-weight: 600; }
|
||||
.taff-stat-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
||||
|
||||
.taff-filters-bar { position: sticky; top: 0; z-index: 100; background: var(--nav-surface); border-bottom: 1px solid var(--nav-bg-alt); padding: 0.75rem 1.5rem; box-shadow: 0 2px 8px rgba(26,34,56,0.06); }
|
||||
.taff-filters-inner { display: flex; align-items: center; gap: 0.625rem; flex-wrap: wrap; }
|
||||
|
||||
.taff-tabs { display: flex; border-radius: 8px; overflow: hidden; border: 1px solid var(--nav-bg-alt); flex-shrink: 0; }
|
||||
.taff-tab { display: flex; align-items: center; gap: 0.375rem; padding: 0.375rem 0.875rem; font-size: 0.8125rem; font-weight: 500; color: var(--nav-text-muted); background: var(--nav-bg); border: none; cursor: pointer; transition: background 0.15s; }
|
||||
.taff-tab:first-child { border-right: 1px solid var(--nav-bg-alt); }
|
||||
.taff-tab--active { background: var(--nav-primary-solid); color: var(--nav-text-on-primary); }
|
||||
.taff-tab-count { font-size: 0.6875rem; opacity: 0.7; font-weight: 700; }
|
||||
|
||||
.taff-filter-group { display: flex; gap: 0.375rem; flex-wrap: wrap; }
|
||||
.taff-filter-btn { padding: 0.3125rem 0.75rem; border-radius: 9999px; font-size: 0.8125rem; font-weight: 500; border: 1px solid var(--nav-bg-alt); background: var(--nav-bg); color: var(--nav-text-muted); cursor: pointer; transition: all 0.15s; white-space: nowrap; }
|
||||
.taff-filter-btn:hover { background: var(--nav-bg-alt); }
|
||||
.taff-filter-btn--active { font-weight: 600; }
|
||||
|
||||
.taff-search { display: flex; align-items: center; gap: 0.5rem; padding: 0.375rem 0.75rem; border-radius: 8px; border: 1px solid var(--nav-bg-alt); background: var(--nav-bg); flex: 1; min-width: 160px; max-width: 240px; }
|
||||
.taff-search-input { flex: 1; background: transparent; border: none; outline: none; font-size: 0.8125rem; color: var(--nav-text); min-width: 0; }
|
||||
.taff-search-input::placeholder { color: var(--nav-text-muted); }
|
||||
.taff-search-clear { color: var(--nav-text-muted); background: none; border: none; cursor: pointer; padding: 0; display: flex; }
|
||||
|
||||
.taff-grid-wrap { padding: 1.5rem; }
|
||||
.taff-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 1rem; }
|
||||
.taff-empty { text-align: center; padding: 3rem; }
|
||||
.taff-reset-btn { margin-top: 0.75rem; padding: 0.5rem 1.25rem; border-radius: 8px; background: var(--nav-bg-alt); color: var(--nav-text); font-size: 0.875rem; border: none; cursor: pointer; }
|
||||
.taff-reset-btn:hover { opacity: 0.7; }
|
||||
|
||||
.taff-disclaimer { margin: 0 1.5rem; padding: 0.875rem 1.25rem; border-radius: 10px; font-size: 0.8125rem; line-height: 1.55; color: var(--nav-text-muted); background: var(--nav-bg-alt); }
|
||||
|
||||
/* Modal body helpers */
|
||||
.modal-label { font-size: 0.6875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--nav-text-muted); margin-bottom: 0.75rem; }
|
||||
.modal-axes { display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 0.5rem; }
|
||||
.modal-axe { display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem 0.75rem; border-radius: 8px; }
|
||||
.modal-meta-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.5rem; }
|
||||
.modal-meta-item { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.6rem 0.875rem; border-radius: 8px; background: var(--nav-bg-alt); }
|
||||
.modal-meta-key { font-size: 0.6875rem; text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; color: var(--nav-text-muted); }
|
||||
.modal-meta-val { font-size: 0.875rem; font-weight: 500; color: var(--nav-text); }
|
||||
|
||||
/* Transitions */
|
||||
.taff-backdrop-enter-active, .taff-backdrop-leave-active { transition: opacity 0.2s; }
|
||||
.taff-backdrop-enter-from, .taff-backdrop-leave-to { opacity: 0; }
|
||||
.taff-modal-enter-active, .taff-modal-leave-active { transition: opacity 0.2s, transform 0.2s; }
|
||||
.taff-modal-enter-from, .taff-modal-leave-to { opacity: 0; transform: translate(-50%, calc(-50% + 12px)); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user