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
51 changes: 51 additions & 0 deletions app/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,25 @@
content: none;
}

/* Shared GitHub token palette. Persisted inline colours remain supported. */
.shj-kwd { color: #cf222e; --shiki-dark: #ff7b72; }
.shj-oper { color: #cf222e; --shiki-dark: #ff7b72; }
.shj-err { color: #82071e; --shiki-dark: #ffa198; }
.shj-deleted { color: #82071e; --shiki-dark: #ffa198; }
.shj-class { color: #953800; --shiki-dark: #ffa657; }
.shj-cmnt { color: #6e7781; --shiki-dark: #8b949e; }
.shj-bracket { color: #57606a; --shiki-dark: #8b949e; }
.shj-num { color: #0550ae; --shiki-dark: #79c0ff; }
.shj-bool { color: #0550ae; --shiki-dark: #79c0ff; }
.shj-type { color: #0550ae; --shiki-dark: #79c0ff; }
.shj-section { color: #0550ae; --shiki-dark: #79c0ff; }
.shj-var { color: #0550ae; --shiki-dark: #79c0ff; }
.shj-str { color: #0a3069; --shiki-dark: #a5d6ff; }
.shj-func { color: #8250df; --shiki-dark: #d2a8ff; }
.shj-insert { color: #116329; --shiki-dark: #7ee787; }
.shj-esc { color: #1f2328; --shiki-dark: #e6edf3; }
.dark :is(.shj-esc, .shj-kwd, .shj-oper, .shj-err, .shj-deleted, .shj-class, .shj-cmnt, .shj-bracket, .shj-num, .shj-bool, .shj-type, .shj-section, .shj-var, .shj-str, .shj-func, .shj-insert) { color: var(--shiki-dark); }

/* Highlighted code (rangi). The light colour is inlined on each token and the
dark one rides along as `--shiki-dark`, so dark mode has to win over an
inline style. */
Expand Down Expand Up @@ -1235,3 +1254,35 @@
opacity: 0.28;
pointer-events: none;
}


/* The email preview shares one card across both colour modes. */
.weekly-preview {
--email-page: #f5f4f2;
--email-surface: #ffffff;
--email-border: #e7e5e4;
--email-borderStrong: #d6d3d1;
--email-text: #1c1917;
--email-body: #44403c;
--email-muted: #6b625c;
--email-faint: #6b625c;
--email-accent: #be123c;
--email-onAccent: #ffffff;
--email-quote: #fafaf9;
--email-mark: #fb7185;
}

.dark .weekly-preview {
--email-page: #14110d;
--email-surface: #1c1917;
--email-border: #3a342c;
--email-borderStrong: #4d453b;
--email-text: #ede9e4;
--email-body: #cdc6bd;
--email-muted: #9c9389;
--email-faint: #7a726a;
--email-accent: #fb7185;
--email-onAccent: #1c1917;
--email-quote: #211d18;
--email-mark: #fb7185;
}
12 changes: 4 additions & 8 deletions app/components/WeeklyEmailPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,16 @@ import type { DigestDemoResponse } from '#layers/identity/server/api/digest/demo
// Only the card and its row count reach the payload. The answer also carries
// the whole email as `html`, which this preview never renders.
const { data: demo } = await useFetch('/api/digest/demo', {
key: 'home-digest-demo-v1',
key: 'home-digest-demo-v2',
transform: ({ card, rowCount }: DigestDemoResponse) => ({ card, rowCount }),
})

const hasDemo = computed(() => !!demo.value?.card && (demo.value?.rowCount ?? 0) > 0)

/**
* The preview follows the page's colour mode. Both cards ship in the payload,
* so switching themes costs no request.
* CSS changes the card's palette. Switching themes costs no request.
*/
const colorMode = useColorMode()
const card = computed(() =>
colorMode.value === 'dark' ? demo.value?.card.dark : demo.value?.card.light,
)
const card = computed(() => demo.value?.card)
</script>

<template>
Expand All @@ -38,7 +34,7 @@ const card = computed(() =>
<p class="data-label">
Example Skill updates Β· real commits
</p>
<div class="home-weekly-frame" aria-hidden="true" inert>
<div class="weekly-preview home-weekly-frame" aria-hidden="true" inert>
<!-- eslint-disable-next-line vue/no-v-html -->
<div class="home-weekly-card" v-html="card" />
</div>
Expand Down
2 changes: 2 additions & 0 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,11 @@ const [
] = await Promise.all([
withHomeDataTiming('home-updates', useFetch<RecentUpdatesResponse>('/api/feed/recent-updates', {
key: 'home-recent-updates-v2',
transform: feed => ({ ...feed, items: feed.items.slice(0, 5) }),
})),
withHomeDataTiming('home-publishes', useFetch<RecentPublishesResponse>('/api/feed/recent-publishes', {
key: 'home-recent-publishes-v2',
transform: feed => ({ ...feed, items: feed.items.slice(0, 5) }),
})),
// Server-rendered rather than lazy: the section is one of the few places on
// the homepage whose content changes hourly, and the endpoint is cached at
Expand Down
21 changes: 17 additions & 4 deletions layers/identity/server/api/digest/demo.get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import {
publicDigestSkillSchema,
publicDigestUpdatesSchema,
} from '../../utils/public-digest'
import { renderWeekly } from '../../utils/weekly-template'
import { renderWeekly, renderWeeklyPreview } from '../../utils/weekly-template'

/** A sample watch list built from real public changes, never personal subscriptions. */
export interface DigestDemoResponse {
card: { light: string, dark: string }
card: string
html: string
subject: string
windowEnd: number
Expand Down Expand Up @@ -49,10 +49,23 @@ export default defineCachedEventHandler(defineApiHandler({ handler: async () =>
})
const light = render('light')
return {
card: { light: light.card, dark: render('dark').card },
card: renderWeeklyPreview({
edition: 'digest',
changeSource: 'watches',
login: null,
windowStart,
windowEnd,
likedChanges: changes,
likedOverflow: 0,
trackedCount: changes.length,
trending: [],
siteUrl: 'https://skilld.dev',
settingsUrl: 'https://skilld.dev/me',
unsubscribeUrl: 'https://skilld.dev/me',
}),
html: light.html,
subject: light.subject,
windowEnd,
rowCount: changes.length,
}
} }), { maxAge: 3600, swr: true, name: 'public-digest-demo-v4' })
} }), { maxAge: 3600, swr: true, name: 'public-digest-demo-v5' })
10 changes: 8 additions & 2 deletions layers/identity/server/utils/weekly-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ const PALETTE: Record<WeeklyTheme, Tokens> = {
},
}

/** One theme-aware card for the website. Email keeps its fixed inline colours. */
export function renderWeeklyPreview(input: WeeklyRenderInput): string {
const tokens = Object.fromEntries(Object.keys(PALETTE.light).map(key => [key, `var(--email-${key})`])) as unknown as Tokens
return renderWeekly(input, tokens).card.replace(/>\s+</g, '><')
}

const SANS = `'Plus Jakarta Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif`
const MONO = `'IBM Plex Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace`
/**
Expand Down Expand Up @@ -698,9 +704,9 @@ function trackedLine(count: number): string {
return `${count} ${plural(count, 'Skill', 'Skills')} tracked, no updates this week`
}

export function renderWeekly(input: WeeklyRenderInput): WeeklyRender {
export function renderWeekly(input: WeeklyRenderInput, tokens?: Tokens): WeeklyRender {
input = { ...input, trending: input.trending.slice(0, MAX_WEEKLY_TRENDING) }
const t = PALETTE[input.theme ?? 'light']
const t = tokens ?? PALETTE[input.theme ?? 'light']
const now = input.windowEnd
const window = formatWindow(input.windowStart, input.windowEnd)
const subject = subjectFor(input)
Expand Down
6 changes: 3 additions & 3 deletions layers/marketing/app/components/FrameworkSkillsDirectory.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<script setup lang="ts">
import type { TagProfile } from '#layers/registry/server/api/tags/[slug].get'
import type { FrameworkDirectoryProfile } from '../utils/framework-directory'

type FrameworkDirectoryView
= | { _tag: 'loading' }
| { _tag: 'error' }
| { _tag: 'empty' }
| { _tag: 'ready', data: TagProfile }
| { _tag: 'ready', data: FrameworkDirectoryProfile }

const {
data,
Expand All @@ -20,7 +20,7 @@ const {
websiteLabel,
syncedAgo,
} = defineProps<{
data?: TagProfile | null
data?: FrameworkDirectoryProfile | null
status: 'idle' | 'pending' | 'success' | 'error'
error?: unknown
framework: string
Expand Down
5 changes: 3 additions & 2 deletions layers/marketing/app/pages/frameworks/react.vue
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
<script setup lang="ts">
import type { TagProfile } from '#layers/registry/server/api/tags/[slug].get'
import FrameworkSkillsDirectory from '../../components/FrameworkSkillsDirectory.vue'
import { frameworkDirectory } from '../../utils/framework-directory'

const { isBot } = useBotDetection()
const { data, status, error, refresh } = useFetch<TagProfile>(
const { data, status, error, refresh } = useFetch(
() => '/api/tags/react?view=data',
{ key: 'tag-react', lazy: !isBot.value },
{ key: 'tag-react', lazy: !isBot.value, transform: (profile: TagProfile) => frameworkDirectory(profile) },
)

const siteOrigin = 'https://skilld.dev'
Expand Down
22 changes: 22 additions & 0 deletions layers/marketing/app/utils/framework-directory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { TagProfile } from '#layers/registry/server/api/tags/[slug].get'

export type FrameworkDirectoryProfile = Omit<TagProfile, 'skills'> & {
skills: Pick<TagProfile['skills'][number], 'owner' | 'repo' | 'name' | 'slug' | 'registryPath' | 'description' | 'stars' | 'authorName'>[]
}

/** The directory keeps every result and only the fields its cards read. */
export function frameworkDirectory(profile: TagProfile): FrameworkDirectoryProfile {
return {
...profile,
skills: profile.skills.map(({ owner, repo, name, slug, registryPath, description, stars, authorName }) => ({
owner,
repo,
name,
slug,
registryPath,
description,
stars,
authorName,
})),
}
}
Loading
Loading