Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 9 additions & 14 deletions app/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export default defineAppConfig({
'next.config.ts': 'i-simple-icons-nextjs',
astro: 'i-simple-icons:astro',
nitro: 'i-unjs-nitro',
md: 'i-custom-comark',
mdc: 'i-custom-comark',
react: 'i-logos-react',
html: 'i-vscode-icons-file-type-html',
svelte: 'i-logos-svelte-icon',
Expand Down Expand Up @@ -165,19 +167,6 @@ export default defineAppConfig({
td: {
base: 'bg-white dark:bg-muted/80',
},

// callout: {
// variants: {
// color: {
// info: {
// base: 'border-muted bg-muted text-toned [&_a]:text-primary [&_a]:hover:border-primary [&_a]:outline-primary/25 [&_a]:focus-visible:outline-3 [&_a]:focus-visible:has-[>code]:outline-0 [&_code]:text-primary-600 dark:[&_code]:text-primary-300 [&_code]:border-primary/25 [&_a]:[&>code]:outline-primary/25 [&_a]:hover:[&>code]:border-primary [&_a]:hover:[&>code]:text-primary [&_a]:focus-visible:[&>code]:border-primary [&_a]:focus-visible:[&>code]:text-primary [&>ul]:marker:text-primary/50',
// },
// success: {
// base: 'border-muted bg-muted text-toned [&_a]:text-primary [&_a]:hover:border-primary [&_a]:outline-primary/25 [&_a]:focus-visible:outline-3 [&_a]:focus-visible:has-[>code]:outline-0 [&_code]:text-primary-600 dark:[&_code]:text-primary-300 [&_code]:border-primary/25 [&_a]:[&>code]:outline-primary/25 [&_a]:hover:[&>code]:border-primary [&_a]:hover:[&>code]:text-primary [&_a]:focus-visible:[&>code]:border-primary [&_a]:focus-visible:[&>code]:text-primary [&>ul]:marker:text-primary/50'
// }
// }
// }
// }
},
icons: {
// info: 'i-tabler-info-square-rounded-filled',
Expand All @@ -195,7 +184,13 @@ export default defineAppConfig({
dark: '',
},
// Sibling sites listed in the brand popover; empty = no popover.
ecosystem: [] as { mark?: string; to: string; label?: string }[],
ecosystem: [
{
mark: 'comark-content',
to: 'https://comark-content.com',
label: 'Comark Content',
},
] as { mark?: string; to: string; label?: string }[],
search: true,
// No `colorMode` key: the always-visible toggle is in AppFooter, the header's is a mobile-drawer duplicate.
// Main navigation tabs; empty = one tab per top-level content section.
Expand Down
2 changes: 1 addition & 1 deletion app/assets/icons/comark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions app/components/landing/LandingCta.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<script setup lang="ts">
defineProps<{
id?: string
}>()
</script>

<template>
<UPageSection
:id="id"
:ui="{
container: 'max-w-6xl py-16 sm:py-16 lg:py-16',
}"
>
<div class="rounded-2xl border border-default bg-elevated/40 px-8 py-16 sm:px-16 sm:py-20 text-center">
<h2
v-if="$slots.title"
class="text-3xl sm:text-4xl medium tracking-tight leading-tight text-highlighted"
>
<slot
name="title"
unwrap="p"
/>
</h2>
<div
v-if="$slots.description"
class="mt-4 text-base text-toned mx-auto max-w-xl"
>
<slot
name="description"
unwrap="p"
/>
</div>
<div
v-if="$slots.links"
class="mt-8 flex flex-wrap justify-center gap-3"
>
<slot name="links" />
</div>
</div>
</UPageSection>
</template>
69 changes: 69 additions & 0 deletions app/components/landing/LandingFaq.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
interface FaqItem {
label: string
content: string
}

const props = defineProps<{
id?: string
items: FaqItem[]
}>()

// FAQPage structured data mirrors the visible accordion.
useHead({
script: [
{
type: 'application/ld+json',
innerHTML: jsonLd({
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: props.items.map((item) => ({
'@type': 'Question',
name: item.label,
acceptedAnswer: { '@type': 'Answer', text: item.content },
})),
}),
},
],
})
</script>

<template>
<UPageSection
:id="id"
:ui="{
container: 'max-w-3xl py-16 sm:py-24',
}"
>
<div>
<div class="text-center">
<p
v-if="$slots.headline"
class="font-mono font-medium text-xs text-primary uppercase tracking-[0.12em]"
>
// <slot name="headline" /> //
</p>
<h2
v-if="$slots.title"
class="mt-6 text-3xl sm:text-4xl medium tracking-tight leading-tight text-highlighted"
>
<slot
name="title"
unwrap="p"
/>
</h2>
</div>

<UAccordion
:items="items"
class="mt-12"
:unmount-on-hide="false"
:ui="{
trigger: 'text-base',
body: 'text-base text-toned',
header: 'text-highlighted',
}"
/>
</div>
</UPageSection>
</template>
38 changes: 38 additions & 0 deletions app/components/landing/LandingFeatureCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script setup lang="ts">
defineProps<{
icon?: string
to?: string
}>()
</script>

<template>
<UPageCard
:icon="icon"
:to="to"
:ui="{
leadingIcon: 'mb-2 size-4 text-primary',
title: 'text-sm font-medium tracking-tight text-highlighted',
description: 'mt-1 text-sm leading-relaxed sm:line-clamp-2 lg:line-clamp-3 text-toned',
}"
>
<template
v-if="$slots.title"
#title
>
<slot
name="title"
unwrap="p"
/>
</template>

<template
v-if="$slots.description"
#description
>
<slot
name="description"
unwrap="p"
/>
</template>
</UPageCard>
</template>
43 changes: 43 additions & 0 deletions app/components/landing/LandingFeatures.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script setup lang="ts">
defineProps<{
id?: string
}>()
</script>

<template>
<UPageSection
:id="id"
:ui="{
container: 'max-w-6xl py-16 sm:py-24',
}"
>
<div class="text-center">
<p
v-if="$slots.headline"
class="font-mono font-medium text-xs text-primary uppercase tracking-[0.12em]"
>
// <slot name="headline" /> //
</p>
<h2
v-if="$slots.title"
class="mt-6 text-3xl sm:text-4xl font-medium tracking-tight leading-tight text-highlighted"
>
<slot
name="title"
unwrap="p"
/>
</h2>
<p
v-if="$slots.description"
class="mt-4 text-base text-dimmed mx-auto max-w-lg"
>
<slot
name="description"
unwrap="p"
/>
</p>
</div>

<UPageGrid class="mt-12"><slot /></UPageGrid>
</UPageSection>
</template>
139 changes: 139 additions & 0 deletions app/components/landing/LandingHeroDemo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<script setup lang="ts">
import shiki from '@comark/nuxt/plugins/shiki'

const props = defineProps<{
/** Markdown source: shown highlighted in the source tab, rendered live in the output tab. */
source: string
/** Optional URL for the "Open in playground" header button. */
playground?: string
}>()

const plugins = [shiki()]

// Four-backtick fence so fenced code blocks inside the demo source don't close it early.
const sourceAsCode = computed(() => ['````md', props.source, '````'].join('\n'))

const tabs = [
{ id: 'source', label: 'source.md' },
{ id: 'output', label: 'rendered output' },
]
const activeIndex = ref(0)

/** Stable, unique per instance so `aria-controls`/`aria-labelledby` can pair up. */
const uid = useId()
const tabId = (index: number) => `${uid}-tab-${index}`
const panelId = (index: number) => `${uid}-panel-${index}`

const tabRefs = useTemplateRef<HTMLButtonElement[]>('tabRefs')

function select(index: number) {
const next = (index + tabs.length) % tabs.length
activeIndex.value = next
tabRefs.value?.[next]?.focus()
}

function onKeydown(event: KeyboardEvent) {
const handlers: Record<string, () => void> = {
ArrowRight: () => select(activeIndex.value + 1),
ArrowLeft: () => select(activeIndex.value - 1),
Home: () => select(0),
End: () => select(tabs.length - 1),
}
const handler = handlers[event.key]
if (!handler) return
event.preventDefault()
handler()
}
</script>

<template>
<div class="not-prose overflow-hidden rounded-lg border border-muted bg-default text-left">
<div class="flex items-center justify-between gap-4 border-b border-muted bg-elevated/50 px-3 py-2">
<div class="flex min-w-0 items-center gap-3">
<div class="flex shrink-0 gap-1.5">
<div class="size-2.5 rounded-full bg-accented" />
<div class="size-2.5 rounded-full bg-accented" />
<div class="size-2.5 rounded-full bg-accented" />
</div>

<div
role="tablist"
aria-label="Markdown demo"
class="flex items-center gap-1"
@keydown="onKeydown"
>
<button
v-for="(tab, index) in tabs"
:id="tabId(index)"
:key="tab.id"
ref="tabRefs"
role="tab"
:aria-selected="activeIndex === index"
:aria-controls="panelId(index)"
:tabindex="activeIndex === index ? 0 : -1"
class="rounded-md px-2.5 py-1 font-mono text-xs transition-colors"
:class="activeIndex === index ? 'bg-accented/60 text-highlighted' : 'text-muted hover:text-highlighted'"
@click="select(index)"
>
{{ tab.label }}
</button>
</div>
</div>

<UButton
v-if="playground"
label="Open in playground"
trailing-icon="i-lucide-arrow-right"
variant="subtle"
color="neutral"
size="xs"
:to="playground"
/>
</div>

<div
v-show="activeIndex === 0"
:id="panelId(0)"
role="tabpanel"
:aria-labelledby="tabId(0)"
class="hero-demo-source h-80 overflow-y-auto p-4 md:h-96"
>
<Markdown
class="font-mono text-sm/6"
:value="sourceAsCode"
:plugins="plugins"
:components="{ pre: 'pre' }"
/>
</div>

<div
v-show="activeIndex === 1"
:id="panelId(1)"
role="tabpanel"
:aria-labelledby="tabId(1)"
class="h-80 overflow-y-auto p-4 text-sm md:h-96"
>
<Markdown
:value="source"
:plugins="plugins"
/>
</div>
</div>
</template>

<style scoped>
.hero-demo-source :deep(pre) {
margin: 0;
background: transparent !important;
white-space: pre-wrap;
word-break: break-word;
}

.hero-demo-source :deep(.line) {
display: inline;
}

.hero-demo-source :deep(span) {
background-color: transparent !important;
}
</style>
Loading
Loading