Skip to content
Open
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
48 changes: 48 additions & 0 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Deploy website

on:
push:
branches: [master]
paths:
- 'website/**'
- '.github/workflows/website.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: pages
cancel-in-progress: true

jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: website
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: yarn
cache-dependency-path: website/yarn.lock
- run: yarn install --frozen-lockfile
- run: yarn generate
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: website/.output/public

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@v4
11 changes: 11 additions & 0 deletions website/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
node_modules
.nuxt
.output
.data
dist
.env
*.log
.DS_Store
_nm_root_junk
_junk_.nuxt
_junk_yarn.lock
18 changes: 18 additions & 0 deletions website/app.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup lang="ts">
const head = useLocaleHead()

useHead(() => ({
htmlAttrs: {
lang: head.value.htmlAttrs?.lang,
dir: head.value.htmlAttrs?.dir,
},
link: head.value.link,
meta: head.value.meta,
}))
</script>

<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
114 changes: 114 additions & 0 deletions website/assets/css/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
@import 'tailwindcss';

@font-face {
font-family: 'Atkynson Mono';
src: url('/fonts/AtkynsonMonoNerdFontMono-Regular.otf') format('opentype');
font-weight: 400;
font-style: normal;
font-display: swap;
}

@theme {
--font-sans:
'Hanken Grotesk Variable', ui-sans-serif, system-ui, -apple-system,
'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
--font-mono:
'Atkynson Mono', ui-monospace, 'SFMono-Regular', 'JetBrains Mono', Menlo,
monospace;

--color-canvas: #0b0b12;
--color-canvas-raised: #101019;
--color-panel: #14141f;
--color-panel-raised: #1a1a28;
--color-hairline: #23233440;
--color-border: #2a2a3c;

--color-ink: #e6e7f2;
--color-ink-muted: #a6adc8;
--color-ink-faint: #6c7086;

--color-mauve: #cba6f7;
--color-mauve-deep: #b48ef0;
--color-blush: #f5c2e7;
--color-mint: #a6e3a1;
--color-sky: #89dceb;
--color-gold: #f9e2af;

--shadow-frame: 0 40px 80px -30px rgba(0, 0, 0, 0.85);
--shadow-soft: 0 24px 60px -32px rgba(0, 0, 0, 0.7);
}

@layer base {
html {
scroll-behavior: smooth;
background-color: var(--color-canvas);
font-optical-sizing: auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}

body {
color: var(--color-ink);
font-family: var(--font-sans);
font-synthesis: none;
line-height: 1.6;
letter-spacing: -0.006em;
}

::selection {
background: color-mix(in srgb, var(--color-mauve) 30%, transparent);
color: #fff;
}

h1,
h2,
h3 {
text-wrap: balance;
}

h1 {
letter-spacing: -0.035em;
line-height: 1.03;
}

h2 {
letter-spacing: -0.028em;
line-height: 1.08;
}

h3 {
letter-spacing: -0.018em;
line-height: 1.2;
}

p {
text-wrap: pretty;
}
}

@utility container-page {
width: 100%;
max-width: 76rem;
margin-inline: auto;
padding-inline: 1.5rem;
}

@utility kicker {
display: inline-flex;
align-items: center;
gap: 0.55rem;
font-family: var(--font-mono);
font-size: 0.72rem;
font-weight: 500;
letter-spacing: 0.24em;
text-transform: uppercase;
color: var(--color-mauve);

&::before {
content: '';
width: 1.6rem;
height: 1px;
background: color-mix(in srgb, var(--color-mauve) 60%, transparent);
}
}
18 changes: 18 additions & 0 deletions website/components/LanguageSwitcher.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup lang="ts">
const { locale, locales } = useI18n()
const switchLocalePath = useSwitchLocalePath()
</script>

<template>
<div class="inline-flex items-center rounded-full border border-border p-0.5 text-xs">
<NuxtLink
v-for="l in locales"
:key="l.code"
:to="switchLocalePath(l.code)"
class="rounded-full px-2.5 py-1 font-medium transition-colors"
:class="l.code === locale ? 'bg-ink text-canvas' : 'text-ink-muted hover:text-ink'"
>
{{ l.name }}
</NuxtLink>
</div>
</template>
53 changes: 53 additions & 0 deletions website/components/PhoneFrame.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<script setup lang="ts">
withDefaults(
defineProps<{
src?: string
video?: string
poster?: string
alt?: string
loading?: 'lazy' | 'eager'
}>(),
{ loading: 'lazy' },
)
</script>

<template>
<div class="relative w-full max-w-[300px]">
<div
class="absolute -inset-6 -z-10 rounded-[3rem] bg-mauve/10 blur-3xl"
aria-hidden="true"
/>
<div
class="rounded-[2.6rem] border border-white/10 bg-[#050509] p-2.5 shadow-frame ring-1 ring-black/60"
>
<div
class="relative overflow-hidden rounded-[2.1rem] bg-canvas ring-1 ring-white/5"
>
<video
v-if="video"
class="block aspect-[9/19.5] w-full object-cover"
:poster="poster"
autoplay
muted
loop
playsinline
>
<source :src="video" type="video/mp4" />
</video>
<img
v-else-if="src"
:src="src"
:alt="alt ?? ''"
:loading="loading"
class="block aspect-[9/19.5] w-full object-cover"
decoding="async"
/>
<div
v-else
class="aspect-[9/19.5] w-full bg-canvas"
aria-hidden="true"
/>
</div>
</div>
</div>
</template>
23 changes: 23 additions & 0 deletions website/components/SiteLogo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<template>
<span class="inline-flex items-center gap-2.5 font-mono text-ink">
<svg
viewBox="0 0 1024 1024"
class="h-6 w-6"
fill="none"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<defs>
<linearGradient id="conduitSignal" x1="264" y1="662" x2="760" y2="354" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#36F9F6" />
<stop offset="0.48" stop-color="#7AA2F7" />
<stop offset="1" stop-color="#FF7EDB" />
</linearGradient>
</defs>
<path d="M270 738L510 512L270 286" stroke="#E7F5FF" stroke-width="104" />
<path d="M524 738L764 512L524 286" stroke="url(#conduitSignal)" stroke-width="104" opacity="0.96" />
</svg>
<span class="text-[0.98rem] font-medium tracking-tight">conduit</span>
</span>
</template>
21 changes: 21 additions & 0 deletions website/components/StoreButtons.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<script setup lang="ts">
const stores = useStores()
</script>

<template>
<div class="flex flex-wrap items-center gap-3">
<a
v-for="store in stores"
:key="store.name"
:href="store.href"
rel="noopener"
class="group inline-flex items-center gap-3 rounded-xl border border-border bg-panel px-5 py-3 transition-colors hover:border-mauve/50 hover:bg-panel-raised"
>
<StoreIcon :icon="store.icon" class="h-6 w-6" :class="store.accent" />
<span class="text-left leading-tight">
<span class="block text-[0.65rem] uppercase tracking-wide text-ink-faint">{{ store.caption }}</span>
<span class="block text-sm font-semibold text-ink">{{ store.name }}</span>
</span>
</a>
</div>
</template>
35 changes: 35 additions & 0 deletions website/components/StoreIcon.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script setup lang="ts">
defineProps<{ icon: 'apple' | 'fdroid' | 'obtainium' }>()
</script>

<template>
<svg
v-if="icon === 'apple'"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<path d="M16.365 1.43c0 1.14-.42 2.2-1.12 2.98-.79.9-2.06 1.6-3.14 1.51-.13-1.1.44-2.28 1.1-3.02.75-.86 2.09-1.5 3.16-1.47zM20.9 17.13c-.55 1.27-.82 1.83-1.53 2.95-1 1.56-2.4 3.5-4.15 3.52-1.55.02-1.95-1.01-4.05-1-2.1.01-2.54 1.02-4.09 1-1.75-.02-3.08-1.77-4.07-3.33C1.03 16.9.68 11.85 2.83 9.16c1-1.32 2.58-2.15 4.14-2.15 1.6 0 2.6 1.02 3.92 1.02 1.28 0 2.06-1.02 3.92-1.02 1.38 0 2.85.75 3.9 2.05-3.43 1.88-2.87 6.78.19 8.07z" />
</svg>
<svg
v-else-if="icon === 'fdroid'"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<path d="M12 2 3 7v10l9 5 9-5V7l-9-5zm0 2.3 6.8 3.78L12 11.86 5.2 8.08 12 4.3zM5 9.7l6 3.35v6.7L5 16.4V9.7zm14 0v6.7l-6 3.35v-6.7l6-3.35z" />
</svg>
<svg
v-else
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.8"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="M12 3v11m0 0 4-4m-4 4-4-4" />
<path d="M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3" />
</svg>
</template>
Loading