Redo image audit: full 6-image analysis with per-image scores, coverage map, set analysis

This commit is contained in:
Omair Saleh
2026-04-28 18:18:36 +08:00
parent fc84c8f263
commit 9b79da92b1
2 changed files with 273 additions and 163 deletions
+100 -27
View File
@@ -3,49 +3,122 @@
"name": "Turmeric with BioPerine & Vitamin C 95% Curcuminoids",
"generatedAt": "2026-04-28",
"generatedBy": "claude-code-in-session",
"imageUrl": "https://images.justvitamins.co.uk/product-images/Normal/3c4e4cc8-4a6d-a8a2-e89577cfe1db-1.jpg",
"imageDescription": "Standing white resealable pouch on white background. Dark circular motif on label showing turmeric powder on a wooden spoon. Label text: 'Turmeric with BioPerine & Vitamin C, 95% Curcuminoids, Immune Bone & Cartilage Health, Vegetarian and Vegan'. Just Vitamins logo and compostable badge visible. 3-4 yellow tablets displayed at base of pouch.",
"imageUrl": "https://images.justvitamins.co.uk/product-images/Normal/3c4e9e20-5169-4a72-a016-a24622ba00fe-1.jpg",
"imageCount": 6,
"overallScore": {
"value": 3.1,
"value": 3.4,
"outOf": 5,
"confidence": "high",
"verdict": "Functional but not compelling. Worse than leading category competitors on lifestyle and perceived value dimensions."
"verdict": "A solid informational set that explains the science well but completely misses the emotional purchase driver — pain relief and restored mobility. No lifestyle image exists across all 6 shots."
},
"images": [
{
"index": 1,
"url": "https://images.justvitamins.co.uk/product-images/Normal/3c4e9e20-5169-4a72-a016-a24622ba00fe-1.jpg",
"role": "Hero — Product Identity",
"description": "White resealable pouch on plain background. Dark circular label shows turmeric powder on wooden spoon. Text: 'Turmeric with BioPerine® & Vitamin C, 95% Curcuminoids, Immune, Bone & Cartilage Health, Vegetarian and Vegan'. Just Vitamins logo and compostable badge. 3-4 yellow tablets at base.",
"score": 3.2,
"strengths": ["Clean, professional studio shot", "Strong product identity — branding and key ingredients are legible", "Compostable badge is a genuine differentiator", "Tablet visual confirms physical product"],
"weaknesses": ["White-on-white feels flat and lacks premium depth", "No lifestyle context — answers 'what is this' but not 'why do I need this'", "Pouch format reads as budget vs glass bottle competitors at same price", "No emotional connection to the pain relief outcome"]
},
{
"index": 2,
"url": "https://images.justvitamins.co.uk/product-images/Normal/3c4e9e20-5169-4a72-a016-a24622ba00fe-2.jpg",
"role": "Tablet Size Reference",
"description": "Single yellow tablet next to a metal ruler on white background. Tablet measures approximately 20mm x 12mm.",
"score": 2.8,
"strengths": ["Directly addresses the capsule size question — reviews mention size as a concern", "Honest and transparent — no overselling", "Shows the tablet is a manageable, easy-to-swallow size"],
"weaknesses": ["Cold and clinical aesthetic — metal ruler + white background is unappealing", "Tablet appears dull yellow, slightly unappealing up close", "No context — a tablet against a ruler conveys no benefit or aspiration", "Would perform better as an in-hand lifestyle shot showing the tablet's manageable size alongside something relatable"]
},
{
"index": 4,
"url": "https://images.justvitamins.co.uk/product-images/Normal/3c4e9e20-5169-4a72-a016-a24622ba00fe-4.jpg",
"role": "Social Proof + Feature List",
"description": "Warm turmeric ingredient background (root + powder in bowl). Two 5-star customer quotes. Red badge: '4 months supply, 120 tablets'. Six bullet features: 95% curcuminoids, genuine Indian turmeric, BioPerine absorption, 120 easy-to-swallow tablets, plastic-free compostable pouch, 100% money back guarantee. Body system icons at base. Product pouch visible.",
"score": 4.1,
"strengths": ["Strong conversion image — covers the main objections in one frame", "Customer quotes add authentic social proof", "100% money back guarantee prominently shown — reduces purchase risk", "Feature list is comprehensive and clearly hierarchy is correct", "Body system icons are a nice touch for benefit clarity", "Warm ingredient background creates appetite appeal"],
"weaknesses": ["Slightly text-heavy — small screens may render the bullet points unreadable", "The two customer quotes don't reference pain relief specifically — missed opportunity to anchor the #1 outcome", "Body system icons are very generic (gut, joint, brain) — could be more specific to joint/mobility outcome"]
},
{
"index": 5,
"url": "https://images.justvitamins.co.uk/product-images/Normal/3c4e9e20-5169-4a72-a016-a24622ba00fe-5.jpg",
"role": "Science Differentiation",
"description": "Dark background. Headline: 'Not a regular Turmeric powder'. Explains curcuminoids as active compounds. Bar chart: JustVitamins 95% vs turmeric powder up to 10%. 'Turmeric grown in SANGLI INDIA' with Indian map and farmer image. BioPerine section: 'increase bioavailability of Curcumin by up to 2,000%'. Fresh turmeric root and powder in frame.",
"score": 4.4,
"strengths": ["Best image in the set — directly addresses why educated buyers should pay premium over £3 supermarket turmeric", "95% vs 10% bar chart is a compelling, visual argument", "Sangli India provenance is a trust signal — sourcing transparency matters to supplement buyers", "2,000% bioavailability stat is memorable and differentiating", "Dark background gives this a scientific/premium feel", "Farmer image adds authentic supply chain story"],
"weaknesses": ["Text is dense — may be difficult to read on mobile thumbnails", "The disclaimer footnote (*bioavailability of Curcumin when co-administered...) is present but very small", "Missing: no connection between the science and the customer outcome (joint pain relief)"]
},
{
"index": 6,
"url": "https://images.justvitamins.co.uk/product-images/Normal/3c4e9e20-5169-4a72-a016-a24622ba00fe-6.jpg",
"role": "Brand Range + Trust Signals",
"description": "Orange brand background. Four JustVitamins pouches shown side by side: Multivitamins A-Z, Vegan Vitamin D3, Turmeric with BioPerine, Rosehip. Six brand badges: UK Made, Vegan Friendly, Plastic Free, Superb Customer Service, Max Strength, Premium Grade Ingredients. Footer: 'No questions asked 100% money back guarantee'.",
"score": 2.5,
"strengths": ["UK Made badge is a strong differentiator in the supplement market", "Brand badge row is visually clean and covers key purchase criteria", "Money back guarantee reinforces image 4 — consistent messaging"],
"weaknesses": ["This is a brand image, not a product image — it dilutes product focus", "Showing competitor products (Multivitamins, D3, Rosehip) within the Turmeric PDP is distracting", "Weakest image in the set for conversion — a buyer researching turmeric doesn't need to see D3 and Rosehip", "Should be replaced by a lifestyle image of the target customer (55-70, active, comfortable movement) which is the single biggest gap in this image set"]
},
{
"index": 7,
"url": "https://images.justvitamins.co.uk/product-images/Normal/3c4e9e20-5169-4a72-a016-a24622ba00fe-7.jpg",
"role": "Feefo Social Proof",
"description": "Close-up macro shot of turmeric powder being scooped with warm golden background. Bold text: '5* Rated, 21,000+ independent feefo reviews'. Feefo badge: 22,267 reviews, 5-star service and product rating. Just Vitamins logo.",
"score": 4.3,
"strengths": ["21,000+ reviews is a massive trust signal — more than most category competitors", "Feefo (independent verified) is more credible than Amazon star ratings", "Warm macro turmeric photography is visually beautiful — best aesthetic in the set", "Clean, bold typography — readable even at thumbnail size", "Answers the 'is this legit?' question definitively"],
"weaknesses": ["Could reference the number of turmeric-specific reviews rather than overall brand reviews for more product relevance", "No specific customer quote about joint pain — missed opportunity to anchor trust to the #1 outcome"]
}
],
"axes": {
"designQuality": {
"score": 3.0,
"score": 3.5,
"outOf": 5,
"label": "Needs work",
"notes": "Clean studio shot with good focus and lighting. The white-on-white background is safe but flat. The dark circular label motif is distinctive but feels dated (2018-era supplement aesthetic). No depth, shadow styling, or textural interest. Pouch format reads as budget vs the glass bottle positioning of premium competitors."
"label": "Lookin okay",
"notes": "Images 5 and 7 are genuinely strong design work with professional photography and clear visual hierarchy. Images 4 is well-composed. Image 2 (ruler shot) and Image 6 (brand range) drag the average down — both feel like afterthoughts. Overall set is more functional than aspirational."
},
"perceivedValue": {
"score": 3.2,
"outOf": 5,
"label": "Lookin okay",
"notes": "Image 5's science story and Image 7's 21,000+ Feefo reviews significantly raise perceived value vs a plain product shot. However the pouch format (Image 1) and clinical ruler (Image 2) undercut a premium read. Premium competitors (Wild Nutrition, Ancient+Brave) use dark glass and lifestyle photography that reads as worth more at the same price point."
},
"messageClarity": {
"score": 4.2,
"outOf": 5,
"label": "Lookin okay",
"notes": "Across 6 images the buyer now understands: what the product is, how big the tablets are, what makes the formula premium (95% curcuminoids, BioPerine, Indian sourcing), brand credentials, and social proof. The set answers most 'what' questions. What it does not answer is 'how will my life improve?' — the outcome message is absent."
},
"messageStrength": {
"score": 2.8,
"outOf": 5,
"label": "Needs work",
"notes": "The resealable pouch is a category-appropriate but not premium-signalling format. Wild Nutrition and Ancient+Brave use dark glass with minimal labelling at similar price points and read as worth more. The 'compostable' badge is a genuine positive but is small and easy to miss. No ingredient hero shot (turmeric root, black pepper) that would visually justify the science premium."
},
"messageClarity": {
"score": 4.0,
"outOf": 5,
"label": "Lookin okay",
"notes": "Strong on factual clarity. Product name, key ingredient (BioPerine), curcuminoid percentage, benefit category, and dietary flags are all legible. The circular thumbnail of turmeric powder on a spoon is an effective ingredient cue. Deducted for: benefit area ('Immune, Bone & Cartilage Health') is small text, and the BioPerine absorption story is label-only with no visual dramatisation."
},
"messageStrength": {
"score": 2.5,
"outOf": 5,
"label": "Needs work",
"notes": "The core emotional purchase driver — pain relief and restored mobility — is completely absent from the image. '847 customers reference joint pain relief' but the image shows a pouch of tablets with no connection to that outcome. No person, no motion, no before/after implication. The image answers 'what is this?' but not 'why do I need this?' or 'how will my life improve?'. This is the biggest single gap."
"notes": "This is the critical gap across the entire set. 847 reviews mention joint pain relief as the core purchase driver. Not a single image shows a person moving freely, a before/after implication, or any emotional connection to the outcome. The science and social proof are well-communicated but the emotional reason to buy — restored mobility, less pain, better quality of life — is invisible. This is the #1 priority fix."
}
},
"setAnalysis": {
"coverage": {
"productIdentity": true,
"tabletSizeReference": true,
"socialProof": true,
"scienceDifferentiation": true,
"brandTrust": true,
"outcomeLifestyle": false,
"timelineExpectation": false,
"ingredientHeroShot": false
},
"criticalGap": "No lifestyle image exists across all 6 images. The #1 purchase driver — joint pain relief and restored mobility — is completely unaddressed visually. This is the single highest-ROI addition to this image set.",
"imageToReplace": "Image 6 (brand range shot) should be replaced — it is the weakest converting image and directly removes the Turmeric product from the centre of attention. Replace with a lifestyle shot of target customer (55-70) in comfortable, active movement.",
"imageToImprove": "Image 2 (ruler shot) serves a valid purpose (size anxiety is real based on reviews) but needs a warmer execution. Replace the metal ruler and white background with an in-hand shot: tablet in an older adult's open palm, natural lighting, showing the manageable scale without the clinical feel."
},
"competitorBenchmark": {
"hollandAndBarrett": "H&B turmeric uses lifestyle images of active older adults alongside their product shots. Their PDP includes a turmeric root ingredient flat-lay. Score advantage: +0.6 on messageStrength.",
"naturesBest": "Nature's Best uses clean white bottle format with benefit-led headline copy. More premium read on perceivedValue. Score advantage: +0.5 on perceivedValue.",
"verdict": "JV Turmeric image is factually clear but emotionally flat versus top two competitors."
"hollandAndBarrett": "H&B's turmeric PDP includes a lifestyle image of an older adult alongside their product shots. Their feature image uses warm amber tones throughout. Advantage: +0.7 on messageStrength vs JV's current set.",
"naturesBest": "Nature's Best shows clean bottle format with benefit-led headline copy and a lifestyle shot. More premium read on perceivedValue. Advantage: +0.5 on perceivedValue.",
"wildNutrition": "Wild Nutrition uses dark studio photography with moody lighting, glass packaging, and minimal copy — reads as the most premium in category. Advantage: +0.8 on perceivedValue.",
"verdict": "JV's science and social proof images (5, 7) are category-leading. The gap is entirely in lifestyle photography. Competitors lead on emotional connection to the outcome, not on factual differentiation."
},
"priorityFixes": [
"Add lifestyle image: older adult (55-70) in active, comfortable movement — gardening, walking, climbing stairs",
"Add ingredient hero shot: fresh turmeric root + black peppercorns on natural surface (linen/slate)",
"Add '95% Curcuminoids + BioPerine' infographic panel explaining absorption advantage",
"Add social proof badge overlay: '2,000+ Five Star Reviews'"
"Add lifestyle image replacing Image 6: person aged 55-70 in comfortable, active movement — gardening, walking, climbing stairs. Implied mobility and ease. Warm natural lighting. This addresses the #1 purchase driver (847 reviews on joint relief) and fills the single biggest gap in the set.",
"Improve Image 2: replace cold ruler/white background with in-hand shot — tablet in open palm of an older adult. Same size-reference function, warmer execution. Directly addresses the capsule size anxiety mentioned in reviews.",
"Add timeline image (7th slot): '6-8 weeks: feel the difference' timeline infographic. 318 reviews mention onset time. Setting this expectation in the image set converts 'it's not working yet' abandoners into completers.",
"Update Image 4 quotes to include a joint pain reference: replace one generic quote with a specific outcome quote ('I can walk without pain for the first time in years'). The current quotes are positive but generic.",
"Consider Image 5 mobile optimisation: the science content is excellent but text-dense. A mobile-first version with larger type and simpler layout would improve readability in thumbnail and mobile PDP views."
]
}
+173 -136
View File
@@ -3,61 +3,95 @@
<div class="view-label">Image Audit</div>
<div class="view-header">
<h1>Objective feedback on your images</h1>
<p class="subtitle">Know exactly what's working and what needs improvement. Objective scores on how well your current images perform.</p>
<p class="subtitle">Scores across all {{ data?.imageCount || 0 }} product images design quality, perceived value, message clarity and strength.</p>
</div>
<div v-if="loading" class="loading">Loading</div>
<div v-else-if="!data" class="empty">No image audit data available for this product yet.</div>
<template v-else>
<div class="section-title">Image Audit Overview</div>
<!-- Score row -->
<!-- Overall score row -->
<div class="section-title">Overall Image Score</div>
<div class="score-row">
<!-- Main score card -->
<div class="main-score-card">
<div class="main-score-num">{{ data.overallScore?.value?.toFixed(1) }}</div>
<div class="main-score-label">out of {{ data.overallScore?.outOf || 5 }}</div>
<div class="main-score-verdict">"{{ data.overallScore?.verdict }}"</div>
</div>
<!-- Sub-scores -->
<div class="sub-scores-card">
<div class="sub-score-row" v-for="axis in axisRows" :key="axis.key">
<div class="sub-score-info">
<span class="sub-score-name">{{ axis.label }}</span>
<span class="sub-score-val">{{ axis.score?.toFixed(1) }} out of {{ axis.outOf || 5 }}</span>
<span class="sub-score-val">{{ axis.score?.toFixed(1) }} / {{ axis.outOf || 5 }}</span>
</div>
<div class="sub-score-track">
<div
class="sub-score-fill"
:class="scoreClass(axis.score)"
:style="{ width: ((axis.score || 0) / (axis.outOf || 5) * 100) + '%' }"
></div>
<div class="sub-score-fill" :class="scoreClass(axis.score)" :style="{ width: ((axis.score||0)/(axis.outOf||5)*100)+'%' }"></div>
</div>
<div class="sub-score-verdict" :class="scoreClass(axis.score)">{{ axis.label_text }}</div>
</div>
</div>
</div>
<!-- Image gallery -->
<div class="section-title" style="margin-top:24px">Product Images</div>
<div class="image-gallery">
<div v-for="(url, i) in galleryUrls" :key="i" class="gallery-item">
<img
:src="url"
:alt="`Product image ${i + 1}`"
class="gallery-img"
@error="onImgError($event)"
/>
<div class="gallery-num">{{ i + 1 }}</div>
<!-- Coverage map -->
<div v-if="data.setAnalysis" class="section-title" style="margin-top:24px">Image Set Coverage</div>
<div v-if="data.setAnalysis" class="coverage-row">
<div v-for="(val, key) in data.setAnalysis.coverage" :key="key" class="coverage-badge" :class="val ? 'covered' : 'missing'">
<span class="coverage-icon">{{ val ? '✓' : '✗' }}</span>
<span class="coverage-label">{{ coverageLabel(key) }}</span>
</div>
</div>
<div v-if="data.setAnalysis?.criticalGap" class="gap-banner">
<span class="gap-icon"></span>
<span>{{ data.setAnalysis.criticalGap }}</span>
</div>
<!-- Per-image gallery with scores -->
<div class="section-title" style="margin-top:24px">
Product Images <span class="count-badge">{{ galleryUrls.length }}</span>
</div>
<div class="image-grid">
<div
v-for="(url, i) in galleryUrls"
:key="i"
class="image-card"
:class="{ active: selected === i }"
@click="selected = selected === i ? null : i"
>
<div class="image-wrap">
<img :src="url" :alt="`Image ${i+1}`" class="gallery-img" />
<div class="image-index">{{ imageForUrl(url)?.index || i+1 }}</div>
<div v-if="imageForUrl(url)" class="image-score-badge" :class="scoreClass(imageForUrl(url).score)">
{{ imageForUrl(url).score?.toFixed(1) }}
</div>
</div>
<div class="image-role">{{ imageForUrl(url)?.role || '—' }}</div>
</div>
</div>
<!-- Image description -->
<div v-if="data.imageDescription" class="image-desc-card">
<div class="image-desc-label">Main Image Description</div>
<p class="image-desc-text">{{ data.imageDescription }}</p>
</div>
<!-- Selected image detail -->
<transition name="fade">
<div v-if="selected !== null && selectedImage" class="image-detail-card">
<div class="detail-header">
<span class="detail-role">{{ selectedImage.role }}</span>
<span class="detail-score" :class="scoreClass(selectedImage.score)">{{ selectedImage.score?.toFixed(1) }} / 5</span>
</div>
<p class="detail-desc">{{ selectedImage.description }}</p>
<div class="detail-cols">
<div class="detail-col">
<div class="detail-col-title strength-title">Strengths</div>
<ul class="detail-list">
<li v-for="s in selectedImage.strengths" :key="s" class="strength-item">{{ s }}</li>
</ul>
</div>
<div class="detail-col">
<div class="detail-col-title weakness-title">Weaknesses</div>
<ul class="detail-list">
<li v-for="w in selectedImage.weaknesses" :key="w" class="weakness-item">{{ w }}</li>
</ul>
</div>
</div>
</div>
</transition>
<!-- Priority fixes -->
<div class="section-title" style="margin-top:24px">Priority Fixes</div>
@@ -71,10 +105,12 @@
<!-- Competitor benchmark -->
<div v-if="data.competitorBenchmark" class="benchmark-card">
<div class="benchmark-title">Competitor Benchmark</div>
<p v-if="data.competitorBenchmark.hollandAndBarrett" class="benchmark-row"><strong>H&amp;B:</strong> {{ data.competitorBenchmark.hollandAndBarrett }}</p>
<p v-if="data.competitorBenchmark.naturesBest" class="benchmark-row"><strong>Nature's Best:</strong> {{ data.competitorBenchmark.naturesBest }}</p>
<p v-if="data.competitorBenchmark.verdict" class="benchmark-verdict">{{ data.competitorBenchmark.verdict }}</p>
<p v-for="(val, key) in data.competitorBenchmark" :key="key" class="benchmark-row">
<template v-if="key !== 'verdict'"><strong>{{ benchmarkLabel(key) }}:</strong> {{ val }}</template>
<em v-else>{{ val }}</em>
</p>
</div>
</template>
</div>
</template>
@@ -87,6 +123,7 @@ const route = useRoute()
const data = ref(null)
const loading = ref(false)
const galleryUrls = ref([])
const selected = ref(null)
const axisRows = computed(() => {
const axes = data.value?.axes || {}
@@ -98,6 +135,17 @@ const axisRows = computed(() => {
]
})
const selectedImage = computed(() => {
if (selected.value === null) return null
const url = galleryUrls.value[selected.value]
return imageForUrl(url)
})
function imageForUrl(url) {
if (!url || !data.value?.images) return null
return data.value.images.find(img => img.url === url) || null
}
function scoreClass(v) {
if (!v) return 'fair'
if (v >= 4) return 'good'
@@ -105,13 +153,29 @@ function scoreClass(v) {
return 'fair'
}
function onImgError(e) {
e.target.closest('.gallery-item').style.display = 'none'
function coverageLabel(key) {
const map = {
productIdentity: 'Product Identity',
tabletSizeReference: 'Size Reference',
socialProof: 'Social Proof',
scienceDifferentiation: 'Science Story',
brandTrust: 'Brand Trust',
outcomeLifestyle: 'Outcome Lifestyle',
timelineExpectation: 'Timeline Expectation',
ingredientHeroShot: 'Ingredient Hero'
}
return map[key] || key
}
function benchmarkLabel(key) {
const map = { hollandAndBarrett: 'H&B', naturesBest: "Nature's Best", wildNutrition: 'Wild Nutrition' }
return map[key] || key
}
async function load(sku) {
if (!sku) return
loading.value = true
selected.value = null
try {
const [auditRes, imgRes] = await Promise.all([
fetch(`/api/intelligence/${sku}/image-audit`),
@@ -131,39 +195,19 @@ watch(() => route.params.sku, load)
<style scoped>
.section-title { font-size: 18px; font-weight: 600; margin-bottom: 16px; }
.score-row {
display: flex;
gap: 20px;
margin-bottom: 20px;
flex-wrap: wrap;
align-items: stretch;
}
/* Score row */
.score-row { display: flex; gap: 20px; margin-bottom: 20px; flex-wrap: wrap; align-items: stretch; }
.main-score-card {
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 32px 36px;
text-align: center;
min-width: 180px;
display: flex;
flex-direction: column;
justify-content: center;
background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg);
padding: 32px 36px; text-align: center; min-width: 180px;
display: flex; flex-direction: column; justify-content: center;
}
.main-score-num { font-size: 56px; font-weight: 300; line-height: 1; color: var(--text-primary); }
.main-score-label { font-size: 13px; color: var(--text-muted); margin-top: 4px; }
.main-score-verdict { font-size: 11px; color: var(--text-muted); margin-top: 10px; font-style: italic; max-width: 160px; line-height: 1.4; }
.main-score-verdict { font-size: 11px; color: var(--text-muted); margin-top: 10px; font-style: italic; line-height: 1.4; }
.sub-scores-card {
flex: 1;
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 20px 24px;
display: flex;
flex-direction: column;
gap: 16px;
min-width: 260px;
flex: 1; background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg);
padding: 20px 24px; display: flex; flex-direction: column; gap: 16px; min-width: 260px;
}
.sub-score-row { display: flex; flex-direction: column; gap: 4px; }
.sub-score-info { display: flex; justify-content: space-between; align-items: baseline; }
@@ -179,92 +223,85 @@ watch(() => route.params.sku, load)
.sub-score-verdict.okay { color: #f59e0b; }
.sub-score-verdict.fair { color: var(--negative); }
/* Image gallery */
.image-gallery {
display: flex;
gap: 12px;
flex-wrap: wrap;
margin-bottom: 24px;
/* Coverage */
.coverage-row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.coverage-badge {
display: flex; align-items: center; gap: 5px;
padding: 4px 12px; border-radius: 20px; font-size: 11px; font-weight: 500;
}
.gallery-item {
position: relative;
width: 140px;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
background: #fff;
}
.gallery-img {
width: 100%;
height: 140px;
object-fit: contain;
display: block;
padding: 8px;
}
.gallery-num {
position: absolute;
top: 6px;
left: 6px;
width: 20px;
height: 20px;
background: rgba(0,0,0,0.45);
color: #fff;
border-radius: 50%;
font-size: 10px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
.coverage-badge.covered { background: var(--positive-bg); color: var(--positive); }
.coverage-badge.missing { background: var(--negative-bg); color: var(--negative); }
.coverage-icon { font-size: 10px; }
.gap-banner {
display: flex; align-items: flex-start; gap: 10px;
background: #fff7ed; border: 1px solid #fed7aa; border-radius: var(--radius-lg);
padding: 12px 16px; font-size: 13px; color: #c2410c; margin-bottom: 24px; line-height: 1.5;
}
.gap-icon { font-size: 14px; flex-shrink: 0; margin-top: 1px; }
.image-desc-card {
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 18px 22px;
margin-bottom: 24px;
/* Image grid */
.image-grid { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.image-card {
width: 148px; border: 2px solid var(--border); border-radius: var(--radius-lg);
overflow: hidden; background: #fff; cursor: pointer; transition: border-color 0.15s;
}
.image-desc-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted); margin-bottom: 8px; }
.image-desc-text { font-size: 13px; color: var(--text-secondary); line-height: 1.6; margin: 0; }
.image-card:hover { border-color: var(--accent); }
.image-card.active { border-color: var(--accent); }
.image-wrap { position: relative; }
.gallery-img { width: 100%; height: 148px; object-fit: contain; display: block; padding: 8px; }
.image-index {
position: absolute; top: 6px; left: 6px; width: 20px; height: 20px;
background: rgba(0,0,0,0.45); color: #fff; border-radius: 50%;
font-size: 10px; font-weight: 600; display: flex; align-items: center; justify-content: center;
}
.image-score-badge {
position: absolute; top: 6px; right: 6px;
padding: 2px 7px; border-radius: 10px; font-size: 11px; font-weight: 700;
}
.image-score-badge.good { background: var(--positive-bg); color: var(--positive); }
.image-score-badge.okay { background: #fef9c3; color: #a16207; }
.image-score-badge.fair { background: var(--negative-bg); color: var(--negative); }
.image-role { font-size: 11px; color: var(--text-muted); padding: 6px 8px 8px; text-align: center; line-height: 1.3; }
.fixes-list {
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 8px 0;
margin-bottom: 24px;
}
.fix-item {
display: flex;
align-items: flex-start;
gap: 14px;
padding: 12px 22px;
border-bottom: 1px solid var(--border);
/* Image detail */
.image-detail-card {
background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg);
padding: 20px 24px; margin-bottom: 24px;
}
.detail-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
.detail-role { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.detail-score { font-size: 14px; font-weight: 700; }
.detail-score.good { color: var(--positive); }
.detail-score.okay { color: #f59e0b; }
.detail-score.fair { color: var(--negative); }
.detail-desc { font-size: 12px; color: var(--text-secondary); line-height: 1.5; margin-bottom: 16px; }
.detail-cols { display: flex; gap: 20px; }
.detail-col { flex: 1; }
.detail-col-title { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 8px; }
.strength-title { color: var(--positive); }
.weakness-title { color: var(--negative); }
.detail-list { padding-left: 16px; }
.detail-list li { font-size: 12px; line-height: 1.5; margin-bottom: 4px; }
.strength-item { color: var(--text-secondary); }
.weakness-item { color: var(--text-secondary); }
/* Fixes */
.fixes-list { background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 8px 0; margin-bottom: 24px; }
.fix-item { display: flex; align-items: flex-start; gap: 14px; padding: 12px 22px; border-bottom: 1px solid var(--border); }
.fix-item:last-child { border-bottom: none; }
.fix-num {
width: 22px;
height: 22px;
background: var(--accent-light);
color: var(--accent);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
font-weight: 700;
flex-shrink: 0;
margin-top: 1px;
width: 22px; height: 22px; background: var(--accent-light); color: var(--accent);
border-radius: 50%; display: flex; align-items: center; justify-content: center;
font-size: 11px; font-weight: 700; flex-shrink: 0; margin-top: 1px;
}
.fix-text { font-size: 13px; color: var(--text-primary); line-height: 1.5; }
.benchmark-card {
background: #fff;
border: 1px solid var(--border);
border-radius: var(--radius-lg);
padding: 18px 22px;
}
/* Benchmark */
.benchmark-card { background: #fff; border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 18px 22px; }
.benchmark-title { font-size: 13px; font-weight: 600; margin-bottom: 10px; }
.benchmark-row { font-size: 12px; color: var(--text-secondary); line-height: 1.5; margin: 0 0 6px; }
.benchmark-verdict { font-size: 12px; color: var(--text-muted); font-style: italic; margin: 8px 0 0; }
/* Fade transition */
.fade-enter-active, .fade-leave-active { transition: opacity 0.2s; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
</style>