feat(copy): wrote some basic copy for the statis pages, landing, prices, products
This commit is contained in:
@@ -1,10 +1,14 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Blogs | Socialize',
|
||||
description: 'Practical articles on content review workflows, client approval, revision tracking, and publication handoff.',
|
||||
title: computed(() => t('public.blogs.meta.title')),
|
||||
description: computed(() => t('public.blogs.meta.description')),
|
||||
path: '/blogs',
|
||||
});
|
||||
</script>
|
||||
@@ -15,12 +19,9 @@
|
||||
|
||||
<main class="public-page-content">
|
||||
<section class="public-page-panel">
|
||||
<div class="eyebrow">Blogs</div>
|
||||
<h1>Practical notes on content review workflows.</h1>
|
||||
<p>
|
||||
Articles are coming soon. This area will cover approval operations, client review habits,
|
||||
revision tracking, and publication handoff.
|
||||
</p>
|
||||
<div class="eyebrow">{{ t('public.blogs.eyebrow') }}</div>
|
||||
<h1>{{ t('public.blogs.title') }}</h1>
|
||||
<p>{{ t('public.blogs.description') }}</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Guides | Socialize',
|
||||
description: 'Reusable guides for content intake, review rounds, approval decisions, and delivery readiness.',
|
||||
title: computed(() => t('public.guides.meta.title')),
|
||||
description: computed(() => t('public.guides.meta.description')),
|
||||
path: '/guides',
|
||||
});
|
||||
</script>
|
||||
@@ -15,12 +19,9 @@
|
||||
|
||||
<main class="public-page-content">
|
||||
<section class="public-page-panel">
|
||||
<div class="eyebrow">Guides</div>
|
||||
<h1>Reusable guides for managing review and approval work.</h1>
|
||||
<p>
|
||||
Guides are coming soon. This area will collect repeatable playbooks for content intake,
|
||||
review rounds, approval decisions, and delivery readiness.
|
||||
</p>
|
||||
<div class="eyebrow">{{ t('public.guides.eyebrow') }}</div>
|
||||
<h1>{{ t('public.guides.title') }}</h1>
|
||||
<p>{{ t('public.guides.description') }}</p>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -1,35 +1,50 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Socialize | Social media approval workflow',
|
||||
description: 'Socialize helps teams manage social media content review, revisions, approval decisions, and publication handoff in one workflow.',
|
||||
title: computed(() => t('public.landing.meta.title')),
|
||||
description: computed(() => t('public.landing.meta.description')),
|
||||
path: '/',
|
||||
});
|
||||
|
||||
const pillarKeys = ['singleSource', 'collaboration', 'ownership'];
|
||||
const focusMetricKeys = ['clients', 'campaigns', 'contentItems'];
|
||||
const proofKeys = ['reviews', 'handoff', 'traceability'];
|
||||
const mockupRowKeys = ['reel', 'launch', 'newsletter'];
|
||||
|
||||
const pillars = computed(() => [
|
||||
{
|
||||
eyebrow: 'Single source of truth',
|
||||
title: 'Comments, revisions, decisions, and due dates stay attached to one content item.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Built for agencies',
|
||||
title: 'Coordinate internal teams, providers, and client approvers without chasing email threads.',
|
||||
},
|
||||
{
|
||||
eyebrow: 'Google Drive first',
|
||||
title: 'Keep Drive as the asset owner when clients require it, while centralizing workflow in Socialize.',
|
||||
},
|
||||
...pillarKeys.map(key => ({
|
||||
eyebrow: t(`public.landing.pillars.${key}.eyebrow`),
|
||||
title: t(`public.landing.pillars.${key}.title`),
|
||||
})),
|
||||
]);
|
||||
|
||||
const workflow = computed(() => [
|
||||
'Create a content item with copy, targets, due dates, and review notes.',
|
||||
'Attach Google Drive assets and register revisions as feedback comes in.',
|
||||
'Request internal review, then client approval, with a clear audit trail.',
|
||||
'Mark the item ready for publishing handoff once approvals are complete.',
|
||||
]);
|
||||
const focusMetrics = computed(() =>
|
||||
focusMetricKeys.map(key => ({
|
||||
label: t(`public.landing.focus.${key}.label`),
|
||||
description: t(`public.landing.focus.${key}.description`),
|
||||
}))
|
||||
);
|
||||
|
||||
const proofPoints = computed(() =>
|
||||
proofKeys.map(key => ({
|
||||
label: t(`public.landing.proof.${key}.label`),
|
||||
value: t(`public.landing.proof.${key}.value`),
|
||||
}))
|
||||
);
|
||||
|
||||
const mockupRows = computed(() =>
|
||||
mockupRowKeys.map(key => ({
|
||||
title: t(`public.landing.mockup.${key}.title`),
|
||||
meta: t(`public.landing.mockup.${key}.meta`),
|
||||
status: t(`public.landing.mockup.${key}.status`),
|
||||
}))
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -42,32 +57,51 @@
|
||||
class="hero-card"
|
||||
>
|
||||
<div class="hero-copy">
|
||||
<div class="eyebrow">Social media approval workflow</div>
|
||||
<h1>Replace Drive links, scattered comments, and manual follow-up with one review system.</h1>
|
||||
<p>
|
||||
Socialize is being rebuilt as an agency workflow product for content review, revision tracking,
|
||||
client approval, and publication readiness.
|
||||
</p>
|
||||
<div class="eyebrow">{{ t('public.landing.hero.eyebrow') }}</div>
|
||||
<h1>{{ t('public.landing.hero.title') }}</h1>
|
||||
<p>{{ t('public.landing.hero.description') }}</p>
|
||||
<div class="proof-row">
|
||||
<div
|
||||
v-for="proof in proofPoints"
|
||||
:key="proof.label"
|
||||
>
|
||||
<strong>{{ proof.value }}</strong>
|
||||
<span>{{ proof.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hero-actions">
|
||||
<router-link to="/login">
|
||||
<button class="primary">Open the app</button>
|
||||
</router-link>
|
||||
<router-link to="/register">
|
||||
<button class="secondary">Create an internal account</button>
|
||||
<button class="primary">{{ t('public.landing.hero.primaryAction') }}</button>
|
||||
</router-link>
|
||||
</div>
|
||||
<span class="cta-note">{{ t('public.landing.hero.ctaNote') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="hero-panel">
|
||||
<div class="hero-panel-title">Version 1 workflow</div>
|
||||
<ol class="workflow-list">
|
||||
<li
|
||||
v-for="step in workflow"
|
||||
:key="step"
|
||||
<div
|
||||
class="workflow-preview"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div class="preview-bar">
|
||||
<span>{{ t('public.landing.mockup.title') }}</span>
|
||||
<strong>{{ t('public.landing.mockup.badge') }}</strong>
|
||||
</div>
|
||||
<div class="preview-list">
|
||||
<div
|
||||
v-for="row in mockupRows"
|
||||
:key="row.title"
|
||||
class="preview-row"
|
||||
>
|
||||
{{ step }}
|
||||
</li>
|
||||
</ol>
|
||||
<div>
|
||||
<strong>{{ row.title }}</strong>
|
||||
<span>{{ row.meta }}</span>
|
||||
</div>
|
||||
<em>{{ row.status }}</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="approval-card">
|
||||
<span>{{ t('public.landing.mockup.approvalLabel') }}</span>
|
||||
<strong>{{ t('public.landing.mockup.approvalValue') }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -84,37 +118,37 @@
|
||||
|
||||
<section class="focus-card">
|
||||
<div>
|
||||
<div class="eyebrow">Current build focus</div>
|
||||
<h2>Phase 1 into Phase 2: retire the creator product surface and install the workflow domain shell.</h2>
|
||||
<div class="eyebrow">{{ t('public.landing.focus.eyebrow') }}</div>
|
||||
<h2>{{ t('public.landing.focus.title') }}</h2>
|
||||
<router-link
|
||||
class="inline-cta"
|
||||
to="/register"
|
||||
>
|
||||
{{ t('public.landing.hero.primaryAction') }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="focus-metrics">
|
||||
<div>
|
||||
<strong>Clients</strong>
|
||||
<span>Brands and businesses under one workspace</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Campaigns</strong>
|
||||
<span>Grouped work tied to timelines, approvals, and delivery goals</span>
|
||||
</div>
|
||||
<div>
|
||||
<strong>Content items</strong>
|
||||
<span>Reviewable units with assets, copy, and approvals</span>
|
||||
<div
|
||||
v-for="metric in focusMetrics"
|
||||
:key="metric.label"
|
||||
>
|
||||
<strong>{{ metric.label }}</strong>
|
||||
<span>{{ metric.description }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
id="pricing"
|
||||
class="pricing-card"
|
||||
>
|
||||
<section class="final-cta">
|
||||
<div>
|
||||
<div class="eyebrow">Pricing</div>
|
||||
<h2>Workspace pricing for teams that manage content approvals with clients.</h2>
|
||||
<div class="eyebrow">{{ t('public.landing.finalCta.eyebrow') }}</div>
|
||||
<h2>{{ t('public.landing.finalCta.title') }}</h2>
|
||||
<p>{{ t('public.landing.finalCta.description') }}</p>
|
||||
</div>
|
||||
<router-link to="/login">
|
||||
<button class="secondary pricing-action">Open the app</button>
|
||||
<router-link to="/register">
|
||||
<button class="primary">{{ t('public.landing.hero.primaryAction') }}</button>
|
||||
</router-link>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
@@ -129,7 +163,7 @@
|
||||
}
|
||||
|
||||
.hero-card {
|
||||
@apply grid gap-6 rounded-[2rem] p-6 md:grid-cols-[1.4fr_0.9fr] md:p-10;
|
||||
@apply grid gap-8 overflow-hidden rounded-[2rem] p-6 md:grid-cols-[1fr_0.88fr] md:p-10;
|
||||
background: linear-gradient(145deg, #172033 0%, #25324b 65%, #314766 100%);
|
||||
color: #fffaf2;
|
||||
box-shadow: 0 30px 80px rgba(23, 32, 51, 0.18);
|
||||
@@ -157,23 +191,91 @@
|
||||
@apply flex flex-col gap-3 sm:flex-row;
|
||||
}
|
||||
|
||||
.hero-panel {
|
||||
@apply rounded-[1.5rem] p-6;
|
||||
background: rgba(255, 250, 242, 0.08);
|
||||
border: 1px solid rgba(255, 250, 242, 0.12);
|
||||
.hero-actions .primary,
|
||||
.final-cta .primary {
|
||||
@apply min-h-12 px-7 text-base;
|
||||
box-shadow: 0 18px 40px rgba(255, 138, 61, 0.28);
|
||||
}
|
||||
|
||||
.hero-panel-title {
|
||||
@apply mb-4 text-sm font-bold uppercase tracking-[0.22em];
|
||||
.cta-note {
|
||||
@apply text-sm font-semibold;
|
||||
color: rgba(255, 250, 242, 0.72);
|
||||
}
|
||||
|
||||
.proof-row {
|
||||
@apply grid max-w-2xl gap-3 sm:grid-cols-3;
|
||||
}
|
||||
|
||||
.proof-row div {
|
||||
@apply rounded-[1rem] border p-4;
|
||||
background: rgba(255, 250, 242, 0.08);
|
||||
border-color: rgba(255, 250, 242, 0.12);
|
||||
}
|
||||
|
||||
.proof-row strong {
|
||||
@apply block text-2xl font-black;
|
||||
color: #7dd3c7;
|
||||
}
|
||||
|
||||
.workflow-list {
|
||||
@apply flex list-decimal flex-col gap-4 pl-5;
|
||||
.proof-row span {
|
||||
@apply mt-1 block text-xs font-bold uppercase leading-5 tracking-[0.12em];
|
||||
color: rgba(255, 250, 242, 0.72);
|
||||
}
|
||||
|
||||
.workflow-list li {
|
||||
@apply text-sm leading-6 md:text-base;
|
||||
.workflow-preview {
|
||||
@apply flex flex-col gap-4 rounded-[1.5rem] p-4;
|
||||
background: rgba(255, 250, 242, 0.1);
|
||||
border: 1px solid rgba(255, 250, 242, 0.14);
|
||||
}
|
||||
|
||||
.preview-bar {
|
||||
@apply flex items-center justify-between gap-3 rounded-[1rem] bg-white/90 px-4 py-3;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.preview-bar span,
|
||||
.approval-card span {
|
||||
@apply text-xs font-black uppercase tracking-[0.18em];
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.preview-bar strong {
|
||||
@apply rounded-full px-3 py-1 text-xs font-black;
|
||||
background: rgba(255, 138, 61, 0.14);
|
||||
color: #b45309;
|
||||
}
|
||||
|
||||
.preview-list {
|
||||
@apply flex flex-col gap-3;
|
||||
}
|
||||
|
||||
.preview-row {
|
||||
@apply flex items-center justify-between gap-4 rounded-[1rem] bg-white/90 p-4;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.preview-row strong {
|
||||
@apply block text-sm font-black;
|
||||
}
|
||||
|
||||
.preview-row span {
|
||||
@apply mt-1 block text-xs font-semibold;
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.preview-row em {
|
||||
@apply rounded-full px-3 py-1 text-xs font-black not-italic;
|
||||
background: rgba(15, 118, 110, 0.1);
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.approval-card {
|
||||
@apply rounded-[1rem] bg-white/95 p-5;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.approval-card strong {
|
||||
@apply mt-2 block text-2xl font-black leading-tight;
|
||||
}
|
||||
|
||||
.pillars-grid {
|
||||
@@ -203,6 +305,16 @@
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.inline-cta {
|
||||
@apply mt-5 inline-flex h-11 items-center rounded-full px-5 text-sm font-black no-underline transition-colors;
|
||||
background: #172033;
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.inline-cta:hover {
|
||||
background: #0f766e;
|
||||
}
|
||||
|
||||
.focus-metrics {
|
||||
@apply grid gap-4 md:grid-cols-3;
|
||||
}
|
||||
@@ -222,19 +334,20 @@
|
||||
color: #3f4d63;
|
||||
}
|
||||
|
||||
.pricing-card {
|
||||
@apply flex flex-col gap-5 rounded-[1.75rem] p-6 md:flex-row md:items-center md:justify-between md:p-8;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
border: 1px solid rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
|
||||
.final-cta {
|
||||
@apply flex flex-col gap-6 rounded-[2rem] p-6 md:flex-row md:items-center md:justify-between md:p-10;
|
||||
background: #172033;
|
||||
color: #fffaf2;
|
||||
box-shadow: 0 30px 80px rgba(23, 32, 51, 0.18);
|
||||
}
|
||||
|
||||
.pricing-card h2 {
|
||||
@apply mt-3 max-w-3xl text-2xl font-black leading-tight md:text-3xl;
|
||||
color: #172033;
|
||||
.final-cta h2 {
|
||||
@apply mt-3 max-w-3xl text-3xl font-black leading-tight md:text-5xl;
|
||||
}
|
||||
|
||||
.pricing-action {
|
||||
@apply w-full sm:w-auto;
|
||||
.final-cta p {
|
||||
@apply mt-4 max-w-2xl text-base leading-7;
|
||||
color: rgba(255, 250, 242, 0.78);
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
const tierKeys = ['free', 'freelance', 'agency', 'enterprise'];
|
||||
const listIndexes = [0, 1, 2, 3, 4];
|
||||
const limitIndexes = [0, 1, 2, 3];
|
||||
|
||||
const tiers = computed(() =>
|
||||
tierKeys.map(key => ({
|
||||
key,
|
||||
isFeatured: key === 'agency',
|
||||
features: listIndexes.map(index => t(`public.pricing.tiers.${key}.features.${index}`)),
|
||||
limits: limitIndexes.map(index => t(`public.pricing.tiers.${key}.limits.${index}`)),
|
||||
}))
|
||||
);
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Pricing | Socialize',
|
||||
description: 'Socialize workspace pricing for teams managing social media content approvals with clients.',
|
||||
title: computed(() => t('public.pricing.meta.title')),
|
||||
description: computed(() => t('public.pricing.meta.description')),
|
||||
path: '/pricing',
|
||||
});
|
||||
</script>
|
||||
@@ -15,18 +31,65 @@
|
||||
|
||||
<main class="public-page-content">
|
||||
<section class="public-page-panel">
|
||||
<div class="eyebrow">Pricing</div>
|
||||
<h1>Simple workspace pricing for teams managing client approvals.</h1>
|
||||
<p>
|
||||
Pricing details are coming soon. For now, Socialize is focused on the core review workflow:
|
||||
workspaces, content items, assets, comments, and approval decisions.
|
||||
</p>
|
||||
<router-link
|
||||
class="pricing-login"
|
||||
to="/login"
|
||||
<div class="eyebrow">{{ t('public.pricing.eyebrow') }}</div>
|
||||
<h1>{{ t('public.pricing.title') }}</h1>
|
||||
<p>{{ t('public.pricing.description') }}</p>
|
||||
</section>
|
||||
|
||||
<section class="pricing-grid">
|
||||
<article
|
||||
v-for="tier in tiers"
|
||||
:key="tier.key"
|
||||
class="pricing-tier"
|
||||
:class="{ featured: tier.isFeatured }"
|
||||
>
|
||||
Login
|
||||
</router-link>
|
||||
<div class="tier-top">
|
||||
<span
|
||||
v-if="tier.isFeatured"
|
||||
class="tier-badge"
|
||||
>
|
||||
{{ t('public.pricing.bestValue') }}
|
||||
</span>
|
||||
<h2>{{ t(`public.pricing.tiers.${tier.key}.name`) }}</h2>
|
||||
<p>{{ t(`public.pricing.tiers.${tier.key}.description`) }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tier-price">
|
||||
<strong>{{ t(`public.pricing.tiers.${tier.key}.price`) }}</strong>
|
||||
<span>{{ t(`public.pricing.tiers.${tier.key}.priceNote`) }}</span>
|
||||
</div>
|
||||
|
||||
<router-link
|
||||
class="tier-action"
|
||||
to="/register"
|
||||
>
|
||||
{{ t(`public.pricing.tiers.${tier.key}.action`) }}
|
||||
</router-link>
|
||||
|
||||
<div class="tier-section">
|
||||
<h3>{{ t('public.pricing.includedLabel') }}</h3>
|
||||
<ul>
|
||||
<li
|
||||
v-for="feature in tier.features"
|
||||
:key="feature"
|
||||
>
|
||||
{{ feature }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="tier-section limits">
|
||||
<h3>{{ t('public.pricing.limitsLabel') }}</h3>
|
||||
<ul>
|
||||
<li
|
||||
v-for="limit in tier.limits"
|
||||
:key="limit"
|
||||
>
|
||||
{{ limit }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
@@ -38,7 +101,7 @@
|
||||
}
|
||||
|
||||
.public-page-content {
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col gap-8 px-5 py-8 md:px-8 md:py-12;
|
||||
}
|
||||
|
||||
.public-page-panel {
|
||||
@@ -63,13 +126,124 @@
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.pricing-login {
|
||||
@apply mt-8 inline-flex h-11 w-fit items-center rounded-full px-5 text-sm font-bold no-underline transition-colors;
|
||||
.pricing-grid {
|
||||
@apply grid gap-4 lg:grid-cols-4;
|
||||
}
|
||||
|
||||
.pricing-tier {
|
||||
@apply relative flex min-h-[42rem] flex-col gap-6 rounded-[1.5rem] border bg-white/85 p-5;
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
|
||||
}
|
||||
|
||||
.pricing-tier.featured {
|
||||
background: #172033;
|
||||
color: #fffaf2;
|
||||
border-color: rgba(255, 178, 107, 0.44);
|
||||
box-shadow: 0 28px 70px rgba(23, 32, 51, 0.2);
|
||||
}
|
||||
|
||||
.tier-badge {
|
||||
@apply mb-4 inline-flex w-fit rounded-full px-3 py-1 text-xs font-black uppercase tracking-[0.16em];
|
||||
background: #ffb26b;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.tier-top h2 {
|
||||
@apply text-2xl font-black;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.featured .tier-top h2 {
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.tier-top p {
|
||||
@apply mt-3 text-sm leading-6;
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.featured .tier-top p,
|
||||
.featured .tier-price span,
|
||||
.featured .tier-section li {
|
||||
color: rgba(255, 250, 242, 0.78);
|
||||
}
|
||||
|
||||
.tier-price {
|
||||
@apply flex items-end gap-2;
|
||||
}
|
||||
|
||||
.tier-price strong {
|
||||
@apply text-4xl font-black leading-none;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.featured .tier-price strong {
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.tier-price span {
|
||||
@apply pb-1 text-sm font-semibold;
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.tier-action {
|
||||
@apply flex h-11 items-center justify-center rounded-full px-5 text-sm font-black no-underline transition-colors;
|
||||
background: #172033;
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.pricing-login:hover {
|
||||
.tier-action:hover {
|
||||
background: #0f766e;
|
||||
}
|
||||
|
||||
.featured .tier-action {
|
||||
background: #ffb26b;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.featured .tier-action:hover {
|
||||
background: #fffaf2;
|
||||
}
|
||||
|
||||
.tier-section {
|
||||
@apply flex flex-col gap-3;
|
||||
}
|
||||
|
||||
.tier-section h3 {
|
||||
@apply text-xs font-black uppercase tracking-[0.18em];
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.featured .tier-section h3 {
|
||||
color: #ffb26b;
|
||||
}
|
||||
|
||||
.tier-section ul {
|
||||
@apply flex flex-col gap-2;
|
||||
}
|
||||
|
||||
.tier-section li {
|
||||
@apply text-sm leading-6;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.tier-section li::before {
|
||||
content: "✓";
|
||||
@apply mr-2 font-black;
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.featured .tier-section li::before {
|
||||
color: #ffb26b;
|
||||
}
|
||||
|
||||
.limits {
|
||||
@apply mt-auto border-t pt-5;
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
}
|
||||
|
||||
.featured .limits {
|
||||
border-color: rgba(255, 250, 242, 0.14);
|
||||
}
|
||||
</style>
|
||||
|
||||
200
frontend/src/features/landing/views/ProductFeaturePage.vue
Normal file
200
frontend/src/features/landing/views/ProductFeaturePage.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute } from 'vue-router';
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { getProductFeature, productFeatureItems } from '@/features/landing/productFeatures.js';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
const route = useRoute();
|
||||
const { t } = useI18n();
|
||||
|
||||
const feature = computed(() => getProductFeature(route.params.featureSlug));
|
||||
const featurePath = computed(() => `/product/${feature.value.slug}`);
|
||||
const relatedFeatures = computed(() =>
|
||||
productFeatureItems.filter(item => item.slug !== feature.value.slug).slice(0, 3)
|
||||
);
|
||||
const bullets = computed(() =>
|
||||
[0, 1, 2].map(index => t(`public.features.${feature.value.slug}.bullets.${index}`))
|
||||
);
|
||||
|
||||
usePublicPageMeta({
|
||||
title: computed(() => `${t(`public.features.${feature.value.slug}.title`)} | Socialize`),
|
||||
description: computed(() => t(`public.features.${feature.value.slug}.description`)),
|
||||
path: featurePath,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="public-page">
|
||||
<LandingSiteMenu />
|
||||
|
||||
<main class="feature-page-content">
|
||||
<section class="feature-hero">
|
||||
<span class="feature-icon">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path :d="feature.icon" />
|
||||
</svg>
|
||||
</span>
|
||||
<div class="eyebrow">{{ t('public.product.eyebrow') }}</div>
|
||||
<h1>{{ t(`public.features.${feature.slug}.detailTitle`) }}</h1>
|
||||
<p>{{ t(`public.features.${feature.slug}.detailDescription`) }}</p>
|
||||
</section>
|
||||
|
||||
<section class="feature-detail-panel">
|
||||
<div class="eyebrow">{{ t('public.product.featureEyebrow') }}</div>
|
||||
<ul>
|
||||
<li
|
||||
v-for="bullet in bullets"
|
||||
:key="bullet"
|
||||
>
|
||||
<span aria-hidden="true">✓</span>
|
||||
<strong>{{ bullet }}</strong>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="related-features">
|
||||
<div>
|
||||
<div class="eyebrow">{{ t('public.product.featureEyebrow') }}</div>
|
||||
<h2>{{ t('public.product.featureTitle') }}</h2>
|
||||
</div>
|
||||
<div class="related-grid">
|
||||
<router-link
|
||||
v-for="item in relatedFeatures"
|
||||
:key="item.slug"
|
||||
class="related-card"
|
||||
:to="`/product/${item.slug}`"
|
||||
>
|
||||
<span class="related-icon">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path :d="item.icon" />
|
||||
</svg>
|
||||
</span>
|
||||
<strong>{{ t(`public.features.${item.slug}.title`) }}</strong>
|
||||
<span>{{ t(`public.features.${item.slug}.description`) }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.public-page {
|
||||
@apply min-h-screen w-full;
|
||||
}
|
||||
|
||||
.feature-page-content {
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col gap-8 px-5 py-8 md:px-8 md:py-12;
|
||||
}
|
||||
|
||||
.feature-hero {
|
||||
@apply rounded-[2rem] p-6 md:p-10;
|
||||
background: rgba(255, 255, 255, 0.84);
|
||||
border: 1px solid rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.06);
|
||||
}
|
||||
|
||||
.feature-icon,
|
||||
.related-icon {
|
||||
@apply flex h-12 w-12 items-center justify-center rounded-[0.9rem];
|
||||
background: rgba(15, 118, 110, 0.1);
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.feature-icon svg,
|
||||
.related-icon svg {
|
||||
@apply h-5 w-5;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
@apply mt-5 text-xs font-bold uppercase tracking-[0.26em];
|
||||
color: #ff8a3d;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply mt-4 max-w-4xl text-4xl font-black leading-tight md:text-6xl;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.related-grid {
|
||||
@apply grid gap-4 md:grid-cols-3;
|
||||
}
|
||||
|
||||
.related-card {
|
||||
@apply rounded-[1.25rem] border bg-white/80 p-5;
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.05);
|
||||
}
|
||||
|
||||
.feature-detail-panel {
|
||||
@apply rounded-[1.5rem] border bg-white/80 p-5 md:p-7;
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.05);
|
||||
}
|
||||
|
||||
.feature-detail-panel .eyebrow {
|
||||
@apply mt-0;
|
||||
}
|
||||
|
||||
.feature-detail-panel ul {
|
||||
@apply mt-5 grid gap-3;
|
||||
}
|
||||
|
||||
.feature-detail-panel li {
|
||||
@apply grid grid-cols-[2rem_1fr] items-start gap-3 rounded-[1rem] p-4;
|
||||
background: rgba(15, 118, 110, 0.08);
|
||||
}
|
||||
|
||||
.feature-detail-panel li span {
|
||||
@apply flex h-8 w-8 items-center justify-center rounded-full text-sm font-black;
|
||||
background: #0f766e;
|
||||
color: #fffaf2;
|
||||
}
|
||||
|
||||
.feature-detail-panel li strong {
|
||||
@apply pt-1 text-base font-semibold leading-7;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.related-features {
|
||||
@apply flex flex-col gap-5;
|
||||
}
|
||||
|
||||
.related-features h2 {
|
||||
@apply mt-3 max-w-3xl text-2xl font-black leading-tight md:text-3xl;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.related-card {
|
||||
@apply flex min-h-48 flex-col gap-3 no-underline transition-colors;
|
||||
}
|
||||
|
||||
.related-card:hover {
|
||||
background: #fff;
|
||||
border-color: rgba(15, 118, 110, 0.28);
|
||||
}
|
||||
|
||||
.related-card strong {
|
||||
@apply text-base font-black;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.related-card span:last-child {
|
||||
@apply text-sm leading-6;
|
||||
color: #44516a;
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,15 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import LandingSiteMenu from '@/features/landing/components/LandingSiteMenu.vue';
|
||||
import { productFeatureItems } from '@/features/landing/productFeatures.js';
|
||||
import { usePublicPageMeta } from '@/features/landing/publicPageMeta.js';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
usePublicPageMeta({
|
||||
title: 'Product | Socialize',
|
||||
description: 'Socialize keeps content items, assets, revisions, comments, approval decisions, and publishing handoff details in one workspace.',
|
||||
title: computed(() => t('public.product.meta.title')),
|
||||
description: computed(() => t('public.product.meta.description')),
|
||||
path: '/product',
|
||||
});
|
||||
</script>
|
||||
@@ -15,12 +20,36 @@
|
||||
|
||||
<main class="public-page-content">
|
||||
<section class="public-page-panel">
|
||||
<div class="eyebrow">Product</div>
|
||||
<h1>Social media content approval, organized around the work itself.</h1>
|
||||
<p>
|
||||
Socialize keeps content items, assets, revisions, comments, approval decisions, and publishing
|
||||
handoff details in one workspace so teams do not have to coordinate review across scattered tools.
|
||||
</p>
|
||||
<div class="eyebrow">{{ t('public.product.eyebrow') }}</div>
|
||||
<h1>{{ t('public.product.title') }}</h1>
|
||||
<p>{{ t('public.product.description') }}</p>
|
||||
</section>
|
||||
|
||||
<section class="product-features">
|
||||
<div>
|
||||
<div class="eyebrow">{{ t('public.product.featureEyebrow') }}</div>
|
||||
<h2>{{ t('public.product.featureTitle') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="feature-grid">
|
||||
<router-link
|
||||
v-for="feature in productFeatureItems"
|
||||
:key="feature.slug"
|
||||
class="feature-card"
|
||||
:to="`/product/${feature.slug}`"
|
||||
>
|
||||
<span class="feature-icon">
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path :d="feature.icon" />
|
||||
</svg>
|
||||
</span>
|
||||
<strong>{{ t(`public.features.${feature.slug}.title`) }}</strong>
|
||||
<span>{{ t(`public.features.${feature.slug}.description`) }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
@@ -32,7 +61,7 @@
|
||||
}
|
||||
|
||||
.public-page-content {
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col px-5 py-8 md:px-8 md:py-12;
|
||||
@apply mx-auto flex w-full max-w-7xl flex-col gap-8 px-5 py-8 md:px-8 md:py-12;
|
||||
}
|
||||
|
||||
.public-page-panel {
|
||||
@@ -56,4 +85,49 @@
|
||||
@apply mt-5 max-w-3xl text-base leading-7 md:text-lg;
|
||||
color: #44516a;
|
||||
}
|
||||
|
||||
.product-features {
|
||||
@apply flex flex-col gap-5;
|
||||
}
|
||||
|
||||
.product-features h2 {
|
||||
@apply mt-3 max-w-3xl text-2xl font-black leading-tight md:text-3xl;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.feature-grid {
|
||||
@apply grid gap-4 md:grid-cols-3;
|
||||
}
|
||||
|
||||
.feature-card {
|
||||
@apply flex min-h-48 flex-col gap-3 rounded-[1.25rem] border bg-white/80 p-5 no-underline transition-colors;
|
||||
border-color: rgba(23, 32, 51, 0.08);
|
||||
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.05);
|
||||
}
|
||||
|
||||
.feature-card:hover {
|
||||
background: #fff;
|
||||
border-color: rgba(15, 118, 110, 0.28);
|
||||
}
|
||||
|
||||
.feature-icon {
|
||||
@apply flex h-11 w-11 items-center justify-center rounded-[0.85rem];
|
||||
background: rgba(15, 118, 110, 0.1);
|
||||
color: #0f766e;
|
||||
}
|
||||
|
||||
.feature-icon svg {
|
||||
@apply h-5 w-5;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.feature-card strong {
|
||||
@apply text-base font-black;
|
||||
color: #172033;
|
||||
}
|
||||
|
||||
.feature-card span:last-child {
|
||||
@apply text-sm leading-6;
|
||||
color: #44516a;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user