Files
justvitamin/src-editorial/views/PatternLibrary.vue
T
Omair Saleh 056c47581f feat: editorial review dashboard + elite-grade pilot batch (5 SKUs)
Ships the second dashboard surface — a Pattern Library + Preview Theatre — that
presents the 4-section PDP pilot batch back to Umar, compliance, and the board
in an editorial format. Adds the full data layer that drives it: 5 source-backed
per-SKU drafts at QA 100/100, 15 competitor PDP semantic extracts, PubMed
evidence packs, EFSA claims library extension, JV brand voice guide, hand-curated
product FAQs, and the Matrixify-ready CSV exports for Lewis.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-02 18:50:09 +08:00

956 lines
27 KiB
Vue

<template>
<div class="patterns">
<!-- HERO ------------------------------------------------------ -->
<section class="hero page">
<div class="hero-grid">
<div class="hero-text">
<span class="eyebrow">Section by section</span>
<h1 class="hero-title">
Pick the<br />
<em>pattern</em>
</h1>
<p class="hero-lede">
For each of the four new PDP sections, here are five Just Vitamins drafts
on the left and the references they were measured against on the right.
Choose the structural pattern you want carried across the catalogue.
</p>
<div class="hero-meta">
<span class="meta">5 pilots</span>
<span class="hero-dot"></span>
<span class="meta">15 references</span>
<span class="hero-dot"></span>
<span class="meta">QA 100/100</span>
</div>
</div>
<div class="hero-ornament">
<div class="ornament-stack">
<div class="ornament-rule"></div>
<div class="ornament-num">04</div>
<div class="ornament-label">sections in scope</div>
</div>
</div>
</div>
</section>
<!-- SECTION TABS --------------------------------------------- -->
<nav class="section-tabs page" aria-label="Section">
<div class="tabs-rule"></div>
<div class="tabs-row">
<button
v-for="(s, i) in SECTIONS"
:key="s.key"
class="section-tab"
:class="{ 'is-active': activeKey === s.key }"
@click="select(s.key)"
>
<span class="tab-num">{{ String(i + 1).padStart(2, '0') }}</span>
<span class="tab-name">{{ s.label }}</span>
<span class="tab-meta">
{{ jvCount(s.key) }} JV · {{ refCount(s.key) }} ref
</span>
</button>
</div>
</nav>
<!-- ACTIVE SECTION SPREAD ------------------------------------ -->
<section v-if="active" class="spread page">
<header class="spread-header">
<div>
<span class="eyebrow">Section {{ activeIndex + 1 }} of 4</span>
<h2 class="spread-title">{{ active.label }}</h2>
<p class="spread-blurb">{{ active.blurb }}</p>
</div>
<div class="spread-meta">
<span class="meta">Recommended template</span>
<strong class="spread-rec">{{ recommendedFor(active.key) }}</strong>
</div>
</header>
<div class="spread-rule"></div>
<div class="cols">
<!-- JV COLUMN ------------------------------------------ -->
<div class="col col-jv">
<div class="col-head">
<span class="col-num">A.</span>
<span class="col-title">Just Vitamins drafts</span>
<span class="col-rule"></span>
</div>
<div v-if="loading" class="loading">Loading drafts</div>
<article
v-else
v-for="pilot in pilots"
:key="pilot.sku"
class="specimen specimen-jv"
:class="{ 'is-focused': focusSku === pilot.sku }"
@click="focusSku = pilot.sku"
>
<header class="specimen-head">
<div>
<span class="meta">{{ pilot.sku }} · {{ pilot.category }}</span>
<h3 class="specimen-title">{{ pilot.name }}</h3>
</div>
<span class="pip sage" :title="`QA ${pilot.qa.overallScore}`"></span>
</header>
<div class="specimen-body">
<component :is="JvRenderers[active.key]" :pilot="pilot" />
</div>
<footer class="specimen-cite">
<span class="cite-label">Source</span>
<span class="cite">{{ shortCite(jvSource(pilot, active.key)) }}</span>
</footer>
</article>
</div>
<!-- REFERENCE COLUMN ------------------------------------ -->
<div class="col col-ref">
<div class="col-head">
<span class="col-num">B.</span>
<span class="col-title">Reference patterns</span>
<span class="col-rule"></span>
</div>
<div v-if="loading" class="loading">Loading references</div>
<article
v-else
v-for="comp in compsForActive"
:key="comp.handle"
class="specimen specimen-ref"
:class="{ 'is-recommended': isRecommended(comp.handle) }"
>
<header class="specimen-head">
<div>
<span class="meta">{{ comp.brand }} · {{ comp.category }}</span>
<h3 class="specimen-title specimen-ref-title">
{{ refTitle(comp, active.key) }}
</h3>
</div>
<span class="ref-tag" v-if="isRecommended(comp.handle)">
<em>our pick</em>
</span>
</header>
<div class="specimen-body">
<component :is="RefRenderers[active.key]" :extract="comp" />
</div>
<footer class="specimen-actions">
<a class="action-link" :href="comp.url" target="_blank" rel="noopener">
view source
</a>
<button
class="action-link is-action"
@click="lockPattern(comp)"
>
use as template
</button>
</footer>
</article>
</div>
</div>
</section>
<!-- LOCK CONFIRMATION TOAST -->
<transition name="fade">
<div v-if="locked" class="locked-toast">
<span class="eyebrow">Pattern locked</span>
<strong>{{ locked.brand }} {{ locked.sectionLabel }}</strong>
<span class="locked-meta">applied to {{ active.label.toLowerCase() }}</span>
</div>
</transition>
</div>
</template>
<script setup>
import { ref, computed, watch, h, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useEditorialApi } from '../composables/useEditorialApi.js'
const SECTIONS = [
{ key: 'scientificStudy', label: 'Scientific Studies', blurb: 'EFSA-authorised wording, peer-reviewed evidence, regulator-precise stat tiles.' },
{ key: 'timelineBlock', label: 'Results Timeline', blurb: 'Phase-by-phase narrative of what customers experience week-by-week or month-by-month, grounded in real reviews.' },
{ key: 'comparisonTable', label: 'Comparison Table', blurb: 'How Just Vitamins compares against the brands the customer is also weighing up.' },
{ key: 'faqBlock', label: 'FAQs', blurb: 'Three product-specific questions plus four standard pairs, in elite brand voice.' }
]
const RECOMMENDED = {
scientificStudy: { handle: 'nothing-fishy-collagen', brand: 'Nothing Fishy', sectionLabel: 'clinical citation depth' },
timelineBlock: { handle: 'heights-biotic', brand: 'Heights', sectionLabel: 'Month 1-4 cadence' },
comparisonTable: { handle: 'club-earlybird-brain-drops', brand: 'Club EarlyBird', sectionLabel: 'feature matrix' },
faqBlock: { handle: 'elavate-collagen', brand: 'Elavate', sectionLabel: 'depth of coverage' }
}
const route = useRoute()
const router = useRouter()
const api = useEditorialApi()
const loading = ref(true)
const pilots = ref([])
const competitors = ref([])
const focusSku = ref(null)
const locked = ref(null)
const activeKey = computed(() => {
const r = route.params.section
return SECTIONS.find(s => s.key === r) ? r : SECTIONS[0].key
})
const active = computed(() => SECTIONS.find(s => s.key === activeKey.value))
const activeIndex = computed(() => SECTIONS.findIndex(s => s.key === activeKey.value))
function select(key) {
router.push({ name: 'patterns-section', params: { section: key } })
}
onMounted(async () => {
loading.value = true
try {
pilots.value = await api.loadAllPilots()
competitors.value = await api.loadAllCompetitors()
focusSku.value = pilots.value[0]?.sku || null
} finally {
loading.value = false
}
})
// Counters
function jvCount(_key) { return pilots.value.length }
function refCount(key) {
return competitors.value.filter(c => sectionPresent(c, key)).length
}
function sectionPresent(comp, key) {
const map = {
scientificStudy: 'scientific_study',
timelineBlock: 'results_timeline',
comparisonTable: 'comparison_table',
faqBlock: 'faq_block'
}
return Boolean(comp?.sections?.[map[key]]?.present)
}
const compsForActive = computed(() => {
return competitors.value
.filter(c => sectionPresent(c, activeKey.value))
.sort((a, b) => Number(isRecommended(b.handle)) - Number(isRecommended(a.handle)))
})
function isRecommended(handle) {
return RECOMMENDED[activeKey.value]?.handle === handle
}
function recommendedFor(key) {
const r = RECOMMENDED[key]
return r ? `${r.brand}${r.sectionLabel}` : '—'
}
function lockPattern(comp) {
locked.value = {
brand: comp.brand,
sectionLabel: SECTIONS.find(s => s.key === activeKey.value)?.label
}
setTimeout(() => { locked.value = null }, 3200)
}
// ---------- Citation helpers ----------
function jvSource(pilot, key) {
const src = pilot[key]?.sources?.[0] || ''
return src
}
function shortCite(s) {
if (!s) return '—'
return s.replace('data/intelligence/', '…/').replace('data/sources/', '…/').slice(0, 80)
}
// ---------- Refs key mapping helpers ----------
function refTitle(comp, key) {
const map = {
scientificStudy: 'scientific_study',
timelineBlock: 'results_timeline',
comparisonTable: 'comparison_table',
faqBlock: 'faq_block'
}
const sec = comp.sections?.[map[key]] || {}
return sec.headline || sec.section_title || '—'
}
// ============================================================
// RENDERERS — JV and competitor versions of each section
// ============================================================
const JvRenderers = {
scientificStudy: {
props: ['pilot'],
setup(props) {
return () => {
const ss = props.pilot.scientificStudy
return h('div', { class: 'render render-sci' }, [
h('h4', { class: 'render-headline' }, ss.headline),
h('p', { class: 'render-body' }, ss.body_copy),
ss.stats?.length ? h('div', { class: 'stat-row' },
ss.stats.slice(0, 3).map(s =>
h('div', { class: 'stat-tile' }, [
h('div', { class: 'stat-value' }, s.value),
h('div', { class: 'stat-label' }, s.label)
])
)
) : null
])
}
}
},
timelineBlock: {
props: ['pilot'],
setup(props) {
return () => {
const tb = props.pilot.timelineBlock
return h('div', { class: 'render render-timeline' }, [
h('h4', { class: 'render-headline' }, tb.headline),
tb.subtitle ? h('div', { class: 'render-subtitle' }, tb.subtitle) : null,
h('div', { class: 'phase-grid' },
tb.phases?.slice(0, 4).map(p =>
h('div', { class: 'phase-card' }, [
h('div', { class: 'phase-label' }, p.phase_label),
h('div', { class: 'phase-body' }, p.body_copy)
])
)
)
])
}
}
},
comparisonTable: {
props: ['pilot'],
setup(props) {
return () => {
const ct = props.pilot.comparisonTable
const cols = ct.columns || []
const rows = (ct.rows || []).slice(0, 4)
return h('div', { class: 'render render-cmp' }, [
h('h4', { class: 'render-headline' }, ct.section_title),
h('table', { class: 'cmp-table' }, [
h('thead', null, h('tr', null,
[h('th', null, 'Feature'), ...cols.map(c =>
h('th', { class: { 'is-us': c.is_us } }, c.name)
)]
)),
h('tbody', null, rows.map(r =>
h('tr', null, [
h('td', { class: 'feat' }, r.feature_label),
...cols.map((_c, i) => {
const v = r.values?.[String(i)]?.value || ''
return h('td', null, v.slice(0, 60))
})
])
))
])
])
}
}
},
faqBlock: {
props: ['pilot'],
setup(props) {
return () => {
const fb = props.pilot.faqBlock
const pairs = (fb.pairs || []).slice(0, 3)
return h('div', { class: 'render render-faq' }, [
h('h4', { class: 'render-headline' }, fb.section_title),
h('dl', { class: 'faq-list' },
pairs.flatMap(p => [
h('dt', { class: 'faq-q' }, p.question),
h('dd', { class: 'faq-a' }, p.answer)
])
)
])
}
}
}
}
const RefRenderers = {
scientificStudy: {
props: ['extract'],
setup(props) {
return () => {
const s = props.extract.sections?.scientific_study || {}
return h('div', { class: 'render render-sci' }, [
s.body_copy ? h('p', { class: 'render-body' }, s.body_copy) : null,
s.stat_tiles?.length ? h('div', { class: 'stat-row' },
s.stat_tiles.slice(0, 3).map(t =>
h('div', { class: 'stat-tile stat-tile-quiet' }, [
h('div', { class: 'stat-value' }, t.value || '—'),
h('div', { class: 'stat-label' }, t.label)
])
)
) : null,
s.citations?.length ? h('div', { class: 'cite-row' }, [
h('span', { class: 'meta' }, `${s.citations.length} citation${s.citations.length === 1 ? '' : 's'}`)
]) : null
])
}
}
},
timelineBlock: {
props: ['extract'],
setup(props) {
return () => {
const t = props.extract.sections?.results_timeline || {}
return h('div', { class: 'render render-timeline' }, [
t.intro_copy ? h('div', { class: 'render-subtitle' }, t.intro_copy) : null,
h('div', { class: 'phase-grid' },
(t.phases || []).slice(0, 4).map(p =>
h('div', { class: 'phase-card phase-card-quiet' }, [
h('div', { class: 'phase-label' }, p.phase_label),
h('div', { class: 'phase-body' }, p.body_copy || '')
])
)
)
])
}
}
},
comparisonTable: {
props: ['extract'],
setup(props) {
return () => {
const c = props.extract.sections?.comparison_table || {}
const headers = c.column_headers || []
const rows = (c.rows || []).slice(0, 4)
if (!headers.length) return h('div', { class: 'render-body' }, c.notes || 'Comparison present but not extractable as table.')
return h('div', { class: 'render render-cmp' }, [
h('table', { class: 'cmp-table cmp-table-quiet' }, [
h('thead', null, h('tr', null, headers.map(hd => h('th', null, hd)))),
h('tbody', null, rows.map(r =>
h('tr', null, [
h('td', { class: 'feat' }, r.feature_label),
h('td', null, (r.us_value || '').slice(0, 60)),
...(r.competitor_values || []).slice(0, 2).map(cv => h('td', null, (cv || '').slice(0, 60)))
])
))
])
])
}
}
},
faqBlock: {
props: ['extract'],
setup(props) {
return () => {
const f = props.extract.sections?.faq_block || {}
const pairs = (f.pairs || []).slice(0, 3)
return h('div', { class: 'render render-faq' }, [
h('dl', { class: 'faq-list faq-list-quiet' },
pairs.flatMap(p => [
h('dt', { class: 'faq-q' }, p.question),
h('dd', { class: 'faq-a' }, p.answer)
])
)
])
}
}
}
}
</script>
<style scoped>
.patterns { padding-bottom: var(--s-9); }
/* HERO ----------------------------------------------------- */
.hero { padding-top: var(--s-7); padding-bottom: var(--s-7); }
.hero-grid {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: var(--s-8);
align-items: end;
}
.hero-title {
font-size: var(--t-5xl);
font-variation-settings: 'opsz' 144, 'SOFT' 80, 'WONK' 0;
line-height: 0.95;
letter-spacing: -0.04em;
margin-top: var(--s-3);
}
.hero-title em {
font-style: italic;
font-variation-settings: 'opsz' 144, 'SOFT' 100, 'WONK' 1;
color: var(--accent-sage);
}
.hero-lede {
margin-top: var(--s-5);
max-width: 56ch;
font-size: var(--t-lg);
line-height: var(--lh-relaxed);
color: var(--ink-soft);
}
.hero-meta {
margin-top: var(--s-6);
display: flex;
gap: var(--s-3);
align-items: center;
}
.hero-dot {
width: 4px;
height: 4px;
border-radius: 50%;
background: var(--ink-muted);
}
.hero-ornament {
display: flex;
justify-content: flex-end;
align-items: flex-end;
padding-bottom: var(--s-2);
}
.ornament-stack {
text-align: right;
display: flex;
flex-direction: column;
gap: var(--s-2);
align-items: flex-end;
}
.ornament-rule { width: 80px; height: 1px; background: var(--rule-strong); }
.ornament-num {
font-family: var(--font-display);
font-size: var(--t-6xl);
line-height: 0.8;
font-variation-settings: 'opsz' 144, 'SOFT' 100, 'WONK' 1, 'wght' 300;
color: var(--ink);
letter-spacing: -0.05em;
}
.ornament-label {
font-family: var(--font-display);
font-style: italic;
font-size: var(--t-sm);
color: var(--ink-muted);
letter-spacing: 0.01em;
font-variation-settings: 'opsz' 24, 'WONK' 1;
}
/* TABS ----------------------------------------------------- */
.section-tabs { margin-top: var(--s-7); }
.tabs-rule { height: 1px; background: var(--rule); }
.tabs-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
border-bottom: 1px solid var(--rule);
}
.section-tab {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: var(--s-2);
padding: var(--s-4) var(--s-4);
text-align: left;
position: relative;
background: transparent;
transition: background var(--dur) var(--ease);
}
.section-tab:hover { background: var(--bg-cream-alt); }
.section-tab::after {
content: '';
position: absolute;
left: 0;
right: 0;
bottom: -1px;
height: 2px;
background: var(--ink);
transform: scaleX(0);
transform-origin: left;
transition: transform var(--dur) var(--ease-out);
}
.section-tab.is-active::after { transform: scaleX(1); }
.tab-num {
font-family: var(--font-mono);
font-size: 10px;
color: var(--ink-muted);
letter-spacing: 0.1em;
}
.tab-name {
font-family: var(--font-display);
font-style: italic;
font-size: var(--t-xl);
font-variation-settings: 'opsz' 36, 'WONK' 1, 'SOFT' 20;
letter-spacing: -0.012em;
color: var(--ink);
}
.tab-meta {
font-size: var(--t-xs);
color: var(--ink-muted);
letter-spacing: 0.04em;
text-transform: uppercase;
}
/* SPREAD --------------------------------------------------- */
.spread { padding-top: var(--s-7); }
.spread-header {
display: grid;
grid-template-columns: 1.5fr 1fr;
align-items: end;
gap: var(--s-6);
margin-bottom: var(--s-4);
}
.spread-title {
font-size: var(--t-4xl);
font-variation-settings: 'opsz' 96, 'SOFT' 40;
letter-spacing: -0.03em;
margin-top: var(--s-2);
}
.spread-blurb {
max-width: 60ch;
margin-top: var(--s-3);
color: var(--ink-soft);
}
.spread-meta {
text-align: right;
display: flex;
flex-direction: column;
gap: var(--s-1);
}
.spread-rec {
font-family: var(--font-display);
font-style: italic;
font-variation-settings: 'opsz' 30, 'WONK' 1;
font-size: var(--t-lg);
color: var(--accent-sage);
font-weight: 400;
}
.spread-rule {
height: 1px;
background: var(--rule-strong);
margin: var(--s-5) 0 var(--s-6);
}
.cols {
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: var(--s-7);
align-items: start;
}
.col-head {
display: flex;
align-items: center;
gap: var(--s-3);
margin-bottom: var(--s-5);
}
.col-num {
font-family: var(--font-display);
font-style: italic;
font-variation-settings: 'opsz' 36, 'WONK' 1, 'SOFT' 30;
font-size: var(--t-2xl);
color: var(--ink);
line-height: 1;
}
.col-title {
font-family: var(--font-display);
font-size: var(--t-lg);
font-variation-settings: 'opsz' 30;
letter-spacing: -0.015em;
}
.col-rule {
flex: 1;
height: 1px;
background: var(--rule);
}
/* SPECIMEN ------------------------------------------------- */
.specimen {
display: flex;
flex-direction: column;
gap: var(--s-4);
padding: var(--s-5);
border: 1px solid var(--rule);
background: var(--bg-paper);
margin-bottom: var(--s-5);
transition: all var(--dur) var(--ease);
}
.specimen-jv {
cursor: pointer;
position: relative;
}
.specimen-jv::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background: transparent;
transition: background var(--dur) var(--ease);
}
.specimen-jv:hover {
border-color: var(--rule-strong);
}
.specimen-jv.is-focused::before { background: var(--ink); }
.specimen-jv.is-focused {
background: var(--bg-cream-alt);
border-color: var(--rule-strong);
}
.specimen-ref.is-recommended {
border-color: var(--accent-sage);
background: var(--bg-sage-soft);
background: linear-gradient(180deg, var(--bg-paper), var(--bg-sage-soft));
}
.specimen-ref.is-recommended .specimen-title {
color: var(--ink);
}
.specimen-head {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: var(--s-4);
}
.specimen-title {
font-size: var(--t-xl);
font-variation-settings: 'opsz' 36, 'SOFT' 20;
letter-spacing: -0.015em;
line-height: 1.15;
margin-top: var(--s-1);
}
.specimen-ref-title {
font-style: italic;
font-variation-settings: 'opsz' 36, 'WONK' 1, 'SOFT' 30;
color: var(--ink-soft);
}
.ref-tag {
font-family: var(--font-display);
font-style: italic;
font-size: var(--t-sm);
color: var(--accent-sage);
border: 1px solid var(--accent-sage);
padding: 2px 10px;
border-radius: 999px;
font-variation-settings: 'opsz' 24, 'WONK' 1;
}
.specimen-body { font-size: var(--t-base); }
.specimen-cite {
display: flex;
align-items: center;
gap: var(--s-2);
border-top: 1px dashed var(--rule);
padding-top: var(--s-3);
}
.cite-label {
font-size: var(--t-xs);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink-muted);
}
.specimen-actions {
display: flex;
justify-content: space-between;
align-items: center;
border-top: 1px dashed var(--rule);
padding-top: var(--s-3);
}
.action-link {
font-family: var(--font-sans);
font-size: var(--t-sm);
color: var(--ink-soft);
text-decoration: none;
letter-spacing: 0.01em;
transition: color var(--dur) var(--ease);
}
.action-link:hover { color: var(--ink); }
.action-link.is-action {
color: var(--accent-sage);
font-weight: 500;
border: none;
background: none;
cursor: pointer;
}
.action-link.is-action:hover { color: var(--ink); }
/* RENDERERS (shared subtle look) --------------------------- */
:deep(.render-headline) {
font-family: var(--font-display);
font-size: var(--t-lg);
font-variation-settings: 'opsz' 24, 'SOFT' 20;
letter-spacing: -0.012em;
margin-bottom: var(--s-2);
line-height: 1.2;
}
:deep(.render-body) {
font-size: var(--t-sm);
line-height: var(--lh-relaxed);
color: var(--ink-soft);
}
:deep(.render-subtitle) {
font-family: var(--font-display);
font-style: italic;
font-size: var(--t-sm);
color: var(--ink-muted);
margin-bottom: var(--s-3);
font-variation-settings: 'opsz' 24, 'WONK' 1;
}
:deep(.stat-row) {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--s-3);
margin-top: var(--s-4);
}
:deep(.stat-tile) {
border-top: 1px solid var(--ink);
padding-top: var(--s-2);
}
:deep(.stat-tile-quiet) { border-top-color: var(--rule); }
:deep(.stat-value) {
font-family: var(--font-display);
font-size: var(--t-xl);
font-variation-settings: 'opsz' 36, 'SOFT' 20;
letter-spacing: -0.02em;
line-height: 1;
}
:deep(.stat-label) {
font-size: 11px;
color: var(--ink-muted);
letter-spacing: 0.01em;
margin-top: var(--s-2);
line-height: 1.3;
}
:deep(.phase-grid) {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--s-3);
}
:deep(.phase-card) {
padding: var(--s-3) var(--s-4);
background: var(--bg-cream-alt);
border-radius: var(--r-sm);
}
:deep(.phase-card-quiet) {
background: transparent;
border: 1px dashed var(--rule);
}
:deep(.phase-label) {
font-family: var(--font-display);
font-style: italic;
font-size: var(--t-sm);
font-variation-settings: 'opsz' 24, 'WONK' 1;
color: var(--accent-sage);
margin-bottom: var(--s-2);
}
:deep(.phase-body) {
font-size: var(--t-xs);
line-height: 1.5;
color: var(--ink-soft);
}
:deep(.cmp-table) {
width: 100%;
font-size: var(--t-xs);
border-collapse: collapse;
margin-top: var(--s-3);
}
:deep(.cmp-table th),
:deep(.cmp-table td) {
text-align: left;
padding: 6px 8px;
border-bottom: 1px solid var(--rule-faint);
vertical-align: top;
letter-spacing: -0.005em;
}
:deep(.cmp-table th) {
font-family: var(--font-sans);
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--ink-muted);
font-weight: 600;
}
:deep(.cmp-table th.is-us) { color: var(--accent-sage); }
:deep(.cmp-table .feat) {
font-weight: 500;
color: var(--ink);
white-space: nowrap;
}
:deep(.faq-list) { display: flex; flex-direction: column; gap: var(--s-3); margin-top: var(--s-3); }
:deep(.faq-q) {
font-family: var(--font-display);
font-style: italic;
font-size: var(--t-sm);
font-variation-settings: 'opsz' 24, 'WONK' 1;
color: var(--ink);
}
:deep(.faq-a) {
font-size: var(--t-xs);
line-height: 1.55;
color: var(--ink-soft);
margin-left: var(--s-4);
border-left: 1px solid var(--rule);
padding-left: var(--s-3);
}
/* LOCK TOAST ----------------------------------------------- */
.locked-toast {
position: fixed;
bottom: var(--s-7);
left: 50%;
transform: translateX(-50%);
background: var(--bg-ink);
color: var(--ink-on-dark);
padding: var(--s-3) var(--s-5);
border-radius: 999px;
display: flex;
align-items: center;
gap: var(--s-3);
box-shadow: var(--shadow-lift);
z-index: 100;
}
.locked-toast .eyebrow {
color: var(--accent-sage);
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
font-style: normal;
font-family: var(--font-sans);
font-weight: 500;
}
.locked-toast strong {
font-family: var(--font-display);
font-variation-settings: 'opsz' 30, 'SOFT' 20;
font-weight: 400;
font-size: var(--t-base);
color: var(--ink-on-dark);
}
.locked-meta {
font-size: var(--t-xs);
color: rgba(244, 239, 230, 0.6);
}
.loading {
padding: var(--s-6);
color: var(--ink-muted);
font-style: italic;
font-family: var(--font-display);
}
/* Responsive ------------------------------------------------ */
@media (max-width: 1100px) {
.cols { grid-template-columns: 1fr; gap: var(--s-6); }
}
@media (max-width: 900px) {
.hero-grid { grid-template-columns: 1fr; }
.hero-ornament { display: none; }
.tabs-row { grid-template-columns: repeat(2, 1fr); }
.spread-header { grid-template-columns: 1fr; }
.spread-meta { text-align: left; }
}
</style>