Skip to content
This repository was archived by the owner on Mar 20, 2026. It is now read-only.
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
108 changes: 108 additions & 0 deletions apps/website/components/Accordion.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<template>
<div class="accordion">
<button
class="accordion-trigger"
@click="toggle"
:aria-expanded="isOpen"
>
<slot name="icon" />
<span class="accordion-title">
<slot name="title" />
</span>
<Icon
name="heroicons:chevron-down"
:class="['accordion-chevron', { 'accordion-chevron-open': isOpen }]"
/>
</button>
<div v-show="isOpen" class="accordion-content">
<slot />
</div>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

interface Props {
defaultOpen?: boolean
}

const props = withDefaults(defineProps<Props>(), {
defaultOpen: false
})

const isOpen = ref(props.defaultOpen)

function toggle() {
isOpen.value = !isOpen.value
}
</script>

<style scoped>
.accordion {
background-color: var(--color-white);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
border: 1px solid var(--color-gray-200);
overflow: hidden;
}

body.dark .accordion {
background-color: var(--color-gray-900);
border-color: var(--color-gray-700);
}

.accordion-trigger {
width: 100%;
padding: var(--spacing-xl);
font-size: var(--font-size-xl);
font-weight: 600;
color: var(--color-gray-900);
display: flex;
align-items: center;
gap: var(--spacing-sm);
background: none;
border: none;
cursor: pointer;
text-align: left;
transition: background-color var(--transition-base);
}

.accordion-trigger:hover {
background-color: var(--color-gray-50);
}

body.dark .accordion-trigger {
color: var(--color-white);
}

body.dark .accordion-trigger:hover {
background-color: var(--color-gray-800);
}

.accordion-title {
flex: 1;
}

.accordion-chevron {
width: 1.25rem;
height: 1.25rem;
color: var(--color-gray-400);
flex-shrink: 0;
transition: transform var(--transition-base);
}

.accordion-chevron-open {
transform: rotate(180deg);
}

.accordion-content {
padding: 0 var(--spacing-xl) var(--spacing-xl) var(--spacing-xl);
color: var(--color-gray-600);
line-height: 1.7;
}

body.dark .accordion-content {
color: var(--color-gray-300);
}
</style>
142 changes: 142 additions & 0 deletions apps/website/components/FaqSection.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
<template>
<section id="faq" class="section faq-section">
<div class="container-custom">
<div class="section-header">
<h2 class="section-title">
Frequently Asked Questions
</h2>
<p class="section-description">
Common questions and answers about NoteX
</p>
</div>

<div class="faq-container">
<Accordion v-for="(item, index) in faqItems" :key="index">
<template #icon>
<Icon name="heroicons:question-mark-circle" class="faq-icon" />
</template>
<template #title>{{ item.question }}</template>
<template v-for="(content, contentIndex) in item.answer" :key="contentIndex">
<p v-if="content.type === 'text'">{{ content.value }}</p>
<InlineCodeBlock v-else-if="content.type === 'code'" :code="content.value" />
<p v-else-if="content.type === 'note'" class="faq-note">
<Icon name="heroicons:light-bulb" class="faq-note-icon" />
{{ content.value }}
</p>
</template>
</Accordion>
</div>
</div>
</section>
</template>

<script setup lang="ts">
interface FaqContent {
type: 'text' | 'code' | 'note'
value: string
}

interface FaqItem {
question: string
answer: FaqContent[]
}

const faqItems: FaqItem[] = [
{
question: "Why does macOS say the app is damaged or from an unidentified developer?",
answer: [
{
type: 'text',
value: 'NoteX is a free, open-source app and we don\'t pay the $99/year Apple developer fee for code signing. This causes macOS to block the app by default.'
},
{
type: 'text',
value: 'To fix this, run the following command in Terminal before opening the DMG:'
},
{
type: 'code',
value: 'xattr -d com.apple.quarantine ~/Downloads/NoteX_0.7.1_aarch64.dmg'
},
{
type: 'note',
value: 'Replace the filename with the actual DMG file you downloaded.'
}
]
}
]
</script>

<style scoped>
.faq-section {
background-color: var(--color-gray-50);
}

body.dark .faq-section {
background-color: var(--color-gray-800);
}

.section-header {
text-align: center;
margin-bottom: var(--spacing-3xl);
}

.section-title {
font-size: var(--font-size-4xl);
font-weight: 700;
color: var(--color-gray-900);
margin-bottom: var(--spacing-md);
}

body.dark .section-title {
color: var(--color-white);
}

.section-description {
font-size: var(--font-size-xl);
color: var(--color-gray-600);
max-width: 42rem;
margin-left: auto;
margin-right: auto;
}

body.dark .section-description {
color: var(--color-gray-300);
}

.faq-container {
max-width: 48rem;
margin-left: auto;
margin-right: auto;
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}

.faq-icon {
width: 1.5rem;
height: 1.5rem;
color: var(--color-primary-600);
flex-shrink: 0;
}

.faq-note {
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
}

.faq-note-icon {
width: 1rem;
height: 1rem;
flex-shrink: 0;
margin-top: 0.125rem;
}

:deep(.accordion-content p) {
margin-bottom: var(--spacing-md);
}

:deep(.accordion-content p:last-child) {
margin-bottom: 0;
}
</style>
83 changes: 83 additions & 0 deletions apps/website/components/InlineCodeBlock.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<template>
<div class="code-block">
<code>{{ code }}</code>
<button
class="copy-button"
@click="copyToClipboard"
:title="copied ? 'Copied!' : 'Copy to clipboard'"
>
<Icon :name="copied ? 'heroicons:check' : 'heroicons:clipboard-document'" class="copy-icon" />
</button>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

const props = defineProps<{
code: string
}>()

const copied = ref(false)

async function copyToClipboard() {
try {
await navigator.clipboard.writeText(props.code)
copied.value = true
setTimeout(() => {
copied.value = false
}, 2000)
} catch (err) {
console.error('Failed to copy:', err)
}
}
</script>

<style scoped>
.code-block {
background-color: var(--color-gray-900);
color: var(--color-gray-100);
padding: var(--spacing-md) var(--spacing-lg);
border-radius: var(--radius-md);
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
font-size: var(--font-size-sm);
overflow-x: auto;
margin: var(--spacing-md) 0;
display: flex;
align-items: center;
gap: var(--spacing-md);
}

:global(body.dark) .code-block {
background-color: var(--color-gray-950);
}

.code-block code {
white-space: nowrap;
flex: 1;
}

.copy-button {
background: none;
border: none;
padding: var(--spacing-xs);
cursor: pointer;
color: var(--color-gray-400);
border-radius: var(--radius-sm);
transition: color var(--transition-base), background-color var(--transition-base);
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}

.copy-button:hover {
color: var(--color-gray-100);
background-color: var(--color-gray-700);
}

.copy-icon {
width: 1.25rem;
height: 1.25rem;
}
</style>
Loading