diff --git a/package.json b/package.json index 711f7e1..42721dc 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,9 @@ }, "dependencies": { "@ethersproject/sha2": "5.8.0", + "@fontsource-variable/inter": "^5.3.0", + "@fontsource-variable/jetbrains-mono": "^5.3.0", + "@lucide/svelte": "^1.51.0", "@reown/appkit": "^1.8.24", "@reown/appkit-adapter-ethers5": "^1.8.24", "@sentry/sveltekit": "^11.4.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e895a71..3413b6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,15 @@ importers: '@ethersproject/sha2': specifier: 5.8.0 version: 5.8.0 + '@fontsource-variable/inter': + specifier: ^5.3.0 + version: 5.3.0 + '@fontsource-variable/jetbrains-mono': + specifier: ^5.3.0 + version: 5.3.0 + '@lucide/svelte': + specifier: ^1.51.0 + version: 1.51.0(svelte@5.57.1(@typescript-eslint/types@8.71.0)) '@reown/appkit': specifier: ^1.8.24 version: 1.8.24(@types/react@19.3.0)(debug@4.4.3(supports-color@8.1.1))(supports-color@8.1.1)(typescript@6.0.3)(zod@3.25.76) @@ -268,6 +277,12 @@ packages: '@floating-ui/utils@0.2.12': resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} + '@fontsource-variable/inter@5.3.0': + resolution: {integrity: sha512-OupL48va4JNofb97w6NYeF9S7W/kHNKM0Er8Dem5nqi4jeOLrVJDoE8tZEpnMJmtkvNbB1EIPPwHcdkF6b1oUA==} + + '@fontsource-variable/jetbrains-mono@5.3.0': + resolution: {integrity: sha512-F32xpS2NsGYoQi2ADSkKTgpJj7ozajsGgDJ8woTnqjmIB+dxDIqImjl4pXZVEExu8UFZ2ndhmX18EBS/hdz3Lw==} + '@humanfs/core@0.19.2': resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} engines: {node: '>=18.18.0'} @@ -322,6 +337,11 @@ packages: '@lit/reactive-element@2.1.2': resolution: {integrity: sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==} + '@lucide/svelte@1.51.0': + resolution: {integrity: sha512-FNoBtfrnQS1LV7yRm8T8pF6OH72BzCyPOWrOmVcpqGGLFmQZndRB4h0qhzUKqPq2JNkSfF7f4tUaiCwdY4WFfg==} + peerDependencies: + svelte: ^5 + '@mapbox/node-pre-gyp@2.0.3': resolution: {integrity: sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==} engines: {node: '>=18'} @@ -3651,6 +3671,10 @@ snapshots: '@floating-ui/utils@0.2.12': {} + '@fontsource-variable/inter@5.3.0': {} + + '@fontsource-variable/jetbrains-mono@5.3.0': {} + '@humanfs/core@0.19.2': dependencies: '@humanfs/types': 0.15.0 @@ -3705,6 +3729,10 @@ snapshots: dependencies: '@lit-labs/ssr-dom-shim': 1.6.0 + '@lucide/svelte@1.51.0(svelte@5.57.1(@typescript-eslint/types@8.71.0))': + dependencies: + svelte: 5.57.1(@typescript-eslint/types@8.71.0) + '@mapbox/node-pre-gyp@2.0.3(supports-color@8.1.1)': dependencies: consola: 3.4.2 diff --git a/src/app.css b/src/app.css index fad9c4d..766e659 100644 --- a/src/app.css +++ b/src/app.css @@ -1,11 +1,69 @@ @import 'tailwindcss'; @import '@skeletonlabs/skeleton'; -@import '@skeletonlabs/skeleton/themes/legacy'; @import '@skeletonlabs/skeleton-svelte'; +@import './theme.css'; @custom-variant dark (&:where(.dark, .dark *)); +@theme { + --font-mono: 'JetBrains Mono Variable', ui-monospace, SFMono-Regular, Menlo, monospace; +} + html, body { @apply h-full; } + +@layer base { + /* visible keyboard focus on every interactive element */ + :where( + a, + button, + summary, + [role='button'], + [role='radio'], + input[type='checkbox'] + ):focus-visible { + outline: 2px solid var(--color-primary-500); + outline-offset: 2px; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + ::before, + ::after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + } +} + +body { + /* soft brand glow behind the content */ + background-image: + radial-gradient( + 60rem 30rem at 90% -10%, + color-mix(in oklab, var(--color-primary-500) 12%, transparent), + transparent 70% + ), + radial-gradient( + 50rem 25rem at -10% 110%, + color-mix(in oklab, var(--color-secondary-500) 10%, transparent), + transparent 70% + ); + background-attachment: fixed; + + @variant dark { + background-image: + radial-gradient( + 60rem 30rem at 90% -10%, + color-mix(in oklab, var(--color-primary-500) 22%, transparent), + transparent 70% + ), + radial-gradient( + 50rem 25rem at -10% 110%, + color-mix(in oklab, var(--color-secondary-500) 10%, transparent), + transparent 70% + ); + } +} diff --git a/src/app.html b/src/app.html index 5008903..a7a1043 100644 --- a/src/app.html +++ b/src/app.html @@ -1,5 +1,5 @@ - + @@ -8,8 +8,8 @@ - - + + %sveltekit.head% - +
%sveltekit.body%
diff --git a/src/lib/badge.ts b/src/lib/badge.ts new file mode 100644 index 0000000..a10d5a6 --- /dev/null +++ b/src/lib/badge.ts @@ -0,0 +1,44 @@ +import type { Badge, BadgeStyle } from '#lib/types.ts'; + +const baseURL = 'https://img.shields.io/badge'; + +export const badgeStyles: { value: BadgeStyle; label: string }[] = [ + { value: 'flat', label: 'Flat' }, + { value: 'flat-square', label: 'Flat square' }, + { value: 'plastic', label: 'Plastic' }, + { value: 'for-the-badge', label: 'Big' }, + { value: 'social', label: 'Social' } +]; + +// shields.io static badge path segments use `-` as separator: escape `-` and `_` as `--` and `__` +function escapeSegment(value: string) { + return encodeURIComponent(value.replaceAll('-', '--').replaceAll('_', '__')); +} + +/** shields.io image URL of the badge, or undefined while the content or the color is missing */ +export function badgeImageURL(badge: Badge): string | undefined { + if (!badge.badgeContent || !badge.color) { + return undefined; + } + const u = new URL( + `${baseURL}/${escapeSegment(badge.badgeContent)}-${encodeURIComponent(badge.color)}` + ); + Object.entries(badge).forEach(([key, value]) => { + if (key === 'badgeContent' || key === 'color') { + return; + } + if (value) { + u.searchParams.set(key, String(value)); + } + }); + return u.toString(); +} + +/** CommitKudos URL the badge links to, with the repository prefilled */ +export function badgeLink(repository: string, reward = 1): string { + const u = new URL('https://commitkudos.com'); + u.searchParams.set('repository', repository); + u.searchParams.set('contributor', '5'); + u.searchParams.set('reward', reward.toString()); + return u.toString(); +} diff --git a/src/lib/clipboard.ts b/src/lib/clipboard.ts new file mode 100644 index 0000000..80684f4 --- /dev/null +++ b/src/lib/clipboard.ts @@ -0,0 +1,10 @@ +import { toaster } from '#lib/toaster.ts'; + +export async function copyToClipboard(text: string, title = 'Copied to clipboard') { + try { + await navigator.clipboard.writeText(text); + toaster.success({ title, duration: 1500 }); + } catch { + toaster.error({ title: 'Could not access the clipboard' }); + } +} diff --git a/src/lib/components/BadgeCode.svelte b/src/lib/components/BadgeCode.svelte index 618c6dc..251446a 100644 --- a/src/lib/components/BadgeCode.svelte +++ b/src/lib/components/BadgeCode.svelte @@ -1,39 +1,38 @@ (tab = e.value)}> - HTML Markdown + HTML {#each Object.entries(snippets) as [name, code] (name)}
-
{code}
+
{code}
diff --git a/src/lib/components/BadgeForm.svelte b/src/lib/components/BadgeForm.svelte index 67f60dc..190b53b 100644 --- a/src/lib/components/BadgeForm.svelte +++ b/src/lib/components/BadgeForm.svelte @@ -1,59 +1,126 @@ -
e.preventDefault()}> - - - - - - - +{#snippet field(id: string, label: string)} + +{/snippet} + + e.preventDefault()}> +
+ + Content + +
+ {@render field('badge-repository', 'Repository')} +
+ github.com/ + +
+
+
+ {@render field('badge-label', 'Label')} + +
+
+ +
+ + Style + +
+ {#each badgeStyles as style (style.value)} + {@const url = badgeImageURL({ + ...badge, + badgeContent: badge.badgeContent || 'owner/name', + style: style.value + })} + + {/each} +
+
+ {@render field('badge-logo', 'Logo')} + +

+ Any icon name from simpleicons.org. +

+
+
+ +
+ + Colors + +
+
+ {@render field('badge-color', 'Background')} + +
+
+ {@render field('badge-label-color', 'Label')} + +
+
+ {@render field('badge-logo-color', 'Logo')} + +
+
+
diff --git a/src/lib/components/BadgeGenerator.svelte b/src/lib/components/BadgeGenerator.svelte index ca27c3f..8c5c1e2 100644 --- a/src/lib/components/BadgeGenerator.svelte +++ b/src/lib/components/BadgeGenerator.svelte @@ -1,66 +1,68 @@ -
-
+
+
-
- + +
+
+ {#each [{ bg: 'bg-white', theme: 'light' }, { bg: 'bg-[#0d1117]', theme: 'dark' }] as readme (readme.theme)} +
+ {#if previewURL} + Badge preview on a {readme.theme} README + {/if} +
+ {/each} +

+ Preview on a light and a dark GitHub README +

+
+ {#if imageURL} - - badge - + + {:else} +

+ Enter your repository{badge.color ? '' : ' and a background color'} to get the code to paste in + your README. +

{/if}
diff --git a/src/lib/components/Balance.svelte b/src/lib/components/Balance.svelte index 3ec4a47..95dd2df 100644 --- a/src/lib/components/Balance.svelte +++ b/src/lib/components/Balance.svelte @@ -8,10 +8,18 @@ let { token = $bindable(), - amount = $bindable(0) + amount = $bindable(0), + id, + disabled = false, + recipients = 1 }: { token?: Balance; amount?: number; + /** id of the amount input, to attach a
diff --git a/src/lib/components/Step.svelte b/src/lib/components/Step.svelte new file mode 100644 index 0000000..bfdee87 --- /dev/null +++ b/src/lib/components/Step.svelte @@ -0,0 +1,67 @@ + + + + +
+ {#if !last} + + {/if} +
+ {#if status === 'done'} + + {:else} + {number} + {/if} +
+
+

{title}

+ {@render aside?.()} +
+
+ {#if status === 'pending'} +

{hint}

+ {:else} +
+ {@render children()} +
+ {/if} +
+
diff --git a/src/lib/components/Web3Modal.svelte b/src/lib/components/Web3Modal.svelte index 89e16bb..366ef77 100644 --- a/src/lib/components/Web3Modal.svelte +++ b/src/lib/components/Web3Modal.svelte @@ -4,14 +4,23 @@ {#if wallet.isConnected && wallet.chain} - {/if} - + +{:else} + +{/if} diff --git a/src/lib/logo-mark.png b/src/lib/logo-mark.png new file mode 100644 index 0000000..50f5978 Binary files /dev/null and b/src/lib/logo-mark.png differ diff --git a/src/lib/repository.ts b/src/lib/repository.ts new file mode 100644 index 0000000..1c0c720 --- /dev/null +++ b/src/lib/repository.ts @@ -0,0 +1,38 @@ +import type { RepoInfo } from '#lib/types.ts'; + +export type RepoResult = + { status: 'ok'; repo: RepoInfo } | { status: 'not-found' } | { status: 'error'; message: string }; + +/** + * Accepts `owner/name` as well as a pasted GitHub URL (`https://github.com/owner/name.git`) + * and returns `owner/name`. + */ +export function normalizeRepository(input: string): string { + return input + .trim() + .replace(/^(https?:\/\/)?(www\.)?github\.com\//i, '') + .replace(/\.git$/, '') + .split('/') + .slice(0, 2) + .join('/'); +} + +export function isRepositoryName(repository: string): boolean { + const [owner, name] = repository.split('/', 2); + return !!owner && !!name; +} + +export async function loadRepository(repository: string): Promise { + try { + const resp = await fetch(`/api/github/repo?${new URLSearchParams({ repository })}`); + if (resp.status === 404) { + return { status: 'not-found' }; + } + if (!resp.ok) { + return { status: 'error', message: `GitHub answered with status ${resp.status}` }; + } + return { status: 'ok', repo: await resp.json() }; + } catch (err) { + return { status: 'error', message: (err as Error).message }; + } +} diff --git a/src/lib/services/github.ts b/src/lib/services/github.ts index 87d81f0..7fdf1e9 100644 --- a/src/lib/services/github.ts +++ b/src/lib/services/github.ts @@ -1,25 +1,30 @@ import { GITHUB_TOKEN } from '$app/env/private'; -import type { CommitDetail } from '../types'; +import type { CommitDetail, RepoInfo } from '../types'; + +async function githubGet(url: URL | string): Promise { + return fetch(url, { + headers: { + Accept: 'application/vnd.github+json', + ...(GITHUB_TOKEN ? { Authorization: `Bearer ${GITHUB_TOKEN}` } : {}) + } + }); +} + +const repoURL = (owner: string, name: string) => + `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`; export async function listCommits( owner: string, name: string, since?: string ): Promise { - const url = new URL( - `https://api.github.com/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/commits` - ); + const url = new URL(`${repoURL(owner, name)}/commits`); url.searchParams.set('per_page', '100'); if (since) { url.searchParams.set('since', since); } - const resp = await fetch(url, { - headers: { - Accept: 'application/vnd.github+json', - ...(GITHUB_TOKEN ? { Authorization: `Bearer ${GITHUB_TOKEN}` } : {}) - } - }); + const resp = await githubGet(url); if (resp.status == 404) { throw new Error(`repository ${owner}/${name} not found`); @@ -31,3 +36,25 @@ export async function listCommits( } return await resp.json(); } + +export async function getRepository(owner: string, name: string): Promise { + const resp = await githubGet(repoURL(owner, name)); + + if (resp.status == 404) { + throw new Error(`repository ${owner}/${name} not found`); + } + + if (resp.status != 200) { + const text = await resp.text(); + throw new Error(`failed to fetch repository ${text}`); + } + const repo = await resp.json(); + return { + fullName: repo.full_name, + description: repo.description ?? '', + avatarUrl: repo.owner?.avatar_url ?? '', + htmlUrl: repo.html_url, + stars: repo.stargazers_count ?? 0, + forks: repo.forks_count ?? 0 + }; +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 9e2069f..bf0f015 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -77,6 +77,15 @@ export interface Contributor { reward?: { amount: number; token?: Balance }; } +export interface RepoInfo { + fullName: string; + description: string; + avatarUrl: string; + htmlUrl: string; + stars: number; + forks: number; +} + export interface Parent { sha: string; url: string; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index c3e38b0..eefec0f 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,20 +1,45 @@ + + + + + + + + + {#snippet children(toast)} @@ -29,23 +54,68 @@ {/snippet} -
- - - - - CommitKudos - - - - +
+
+
+ + + + CommitKudos + + + +
- - - -
+
+
+
+ +
{@render children()}
+ +
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index e65a1b2..bf4696d 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,37 +1,64 @@ -
-
-

- {#key index} - {greetings[index]} - {/key} + + CommitKudos · Reward your top open-source contributors + + + +
+
+

+ Reward + your top contributors -

-
e.preventDefault()}> -
- https://github.com/ - +

+

+ Pick a GitHub repository, choose who to thank, and send each contributor a crypto reward they + claim with a single link. +

+
+ + e.preventDefault()} + > + +
+
+ + +
+ {#if repoState.status === 'loading'} +
+
+
+
+
+
+
+ {:else if repoState.status === 'ok'} + + {:else if repoState.status === 'not-found'} +

+

+ {:else if repoState.status === 'error'} +

+

+ {/if}
- + + + {#snippet aside()} +
+ Top + {#each presets as n (n)} + + {/each} + (count = e.currentTarget.valueAsNumber || null)} + disabled={locked} + /> +
+ {/snippet} + + {#if repoState.status === 'loading'} +
    + {#each { length: 4 }, i (i)} +
  • +
    +
    +
    +
    +
    +
    +
  • + {/each} +
+ {:else if top.length === 0} +
+
+ {:else} +
    + {#each top as contributor, i (contributor.login || contributor.email)} +
  • + +
  • + {/each} +
+
+ {selected.length} of {top.length} selected · commits of the last 30 days + {#if !locked} + + {/if} +
+ {/if} +
+ + + + - {#if top.length > 0} - -
- Top contributors -
    - {#each top as contributor (contributor.login || contributor.email)} -
  • -
    - {#if contributor.avatarUrl} - avatar - {/if} -
    - {contributor.name} ({contributor.email}) - 0} - /> -
  • - {/each} -
+ + {#if !wallet.isConnected} +

+

+ {:else if selectedToken} +
+ + {selected.length} × {formatAmount(rewardAmount || 0)} + {selectedToken.symbol} + {#if wallet.chain}on {wallet.chain.name}{/if} + + + Total {formatAmount(total)} {selectedToken.symbol} + {#if totalPrice !== undefined} + ≈ {formatPrice(totalPrice, prices.currency)} + {/if} +
+ {#if insufficient} +

+

+ {/if} {/if} - {#if top.length > 0 && !links.length} + {#if !locked} - {:else if links.length > 0} - -
- Reward links -
    - {#each links as link, i (link)} -
  • {rewarded[i]?.name}: {link}
  • - {/each} -
-
{/if} - -
+
- + +
+

+ Your reward links are ready. Anyone holding a link can claim its reward, so only share + each link with its contributor. +

+ + {#if privateEmails > 0} +

+

+ {/if} +
+ + +
+
+
+
diff --git a/src/routes/api/github/repo/+server.ts b/src/routes/api/github/repo/+server.ts new file mode 100644 index 0000000..7768889 --- /dev/null +++ b/src/routes/api/github/repo/+server.ts @@ -0,0 +1,21 @@ +import { error } from '@sveltejs/kit'; + +import { getRepository } from '#lib/services/github.ts'; + +import type { RequestHandler } from './$types'; + +export const GET: RequestHandler = async ({ url }) => { + const repository = url.searchParams.get('repository') ?? ''; + const [owner, name] = repository.split('/', 2); + if (!owner || !name) { + return Response.json({ error: 'repository name format invalid' }, { status: 400 }); + } + + try { + return Response.json(await getRepository(owner, name)); + } catch (err) { + const message = (err as Error).message; + console.log(message); + error(message.includes('not found') ? 404 : 500, message); + } +}; diff --git a/src/routes/badges/+page.svelte b/src/routes/badges/+page.svelte index 3287c5a..eca8123 100644 --- a/src/routes/badges/+page.svelte +++ b/src/routes/badges/+page.svelte @@ -2,7 +2,17 @@ import BadgeGenerator from '#lib/components/BadgeGenerator.svelte'; -
-

Generate your README badge

+ + README badge · CommitKudos + + + +
+
+

README badge

+

+ Add a badge to your repository so its users can reward your top contributors in one click. +

+
diff --git a/src/routes/dashboard/+page.svelte b/src/routes/dashboard/+page.svelte index f620283..28634e1 100644 --- a/src/routes/dashboard/+page.svelte +++ b/src/routes/dashboard/+page.svelte @@ -1,45 +1,118 @@ -
-
-
e.preventDefault()}> + + Advanced rewards · CommitKudos + + + +
+ + +
+ {#if contributors.length > 0} +
+
+
+ + +
+ {/if} + + {#if repoState.status === 'loading'} +
+ {#each { length: 6 }, i (i)} +
+
+
+
+
+
+
+
{/each} - -
-
-
- {#if !repository} -
-
No repository selected
-
Specify the name of a repository to start
{:else if contributors.length === 0} -
-
No contributors found
-
Try another repository
+
+
+ {:else if visible.length === 0} +

No contributor matches “{filter}”.

{:else} -
- {#each contributors as contributor, i (contributor.login || contributor.email)} - +
+ {#each visible as { contributor, index } (contributor.login || contributor.email)} + {/each}
{/if}
-
diff --git a/src/theme.css b/src/theme.css new file mode 100644 index 0000000..52a4ff8 --- /dev/null +++ b/src/theme.css @@ -0,0 +1,211 @@ +/* + * CommitKudos theme for Skeleton, derived from the logo (src/lib/logo.png): + * primary = cat blue, secondary = circuit mint, tertiary = background teal, surface = outline navy. + * Only the tokens that differ from Skeleton's base theme are set here. + */ +[data-theme='commitkudos'] { + --spacing: 0.25rem; + --text-scaling: 1.067; + + --typo-base--font-family: 'Inter Variable', Inter, system-ui, sans-serif; + --typo-base--color-light: var(--color-surface-950); + --typo-base--color-dark: var(--color-surface-50); + --typo-heading--font-weight: 700; + --typo-heading--letter-spacing: -0.02em; + --typo-anchor--color-light: var(--color-primary-600); + --typo-anchor--color-dark: var(--color-primary-300); + --typo-anchor--text-decoration-line: none; + --typo-anchor--hover--text-decoration-line: underline; + + --radius-base: 0.5rem; + --radius-container: 1rem; + --default-border-width: 1px; + --default-outline-width: 1px; + --default-ring-width: 1px; + + --color-root-bg-light: var(--color-surface-50); + --color-root-bg-dark: var(--color-surface-950); + --color-brand-light: var(--color-primary-500); + --color-brand-contrast-light: var(--color-primary-contrast-500); + --color-brand-dark: var(--color-primary-400); + --color-brand-contrast-dark: var(--color-primary-contrast-400); + + /* primary: cat blue */ + --color-primary-50: oklch(97% 0.02 258deg); + --color-primary-100: oklch(93% 0.04 258deg); + --color-primary-200: oklch(86% 0.08 258deg); + --color-primary-300: oklch(77% 0.12 258deg); + --color-primary-400: oklch(67% 0.17 258deg); + --color-primary-500: oklch(58% 0.2 258deg); + --color-primary-600: oklch(51% 0.19 258deg); + --color-primary-700: oklch(45% 0.17 258deg); + --color-primary-800: oklch(38% 0.14 258deg); + --color-primary-900: oklch(32% 0.11 258deg); + --color-primary-950: oklch(25% 0.08 258deg); + --color-primary-contrast-dark: var(--color-surface-950); + --color-primary-contrast-light: oklch(100% 0 0deg); + --color-primary-contrast-50: var(--color-primary-contrast-dark); + --color-primary-contrast-100: var(--color-primary-contrast-dark); + --color-primary-contrast-200: var(--color-primary-contrast-dark); + --color-primary-contrast-300: var(--color-primary-contrast-dark); + --color-primary-contrast-400: var(--color-primary-contrast-dark); + --color-primary-contrast-500: var(--color-primary-contrast-light); + --color-primary-contrast-600: var(--color-primary-contrast-light); + --color-primary-contrast-700: var(--color-primary-contrast-light); + --color-primary-contrast-800: var(--color-primary-contrast-light); + --color-primary-contrast-900: var(--color-primary-contrast-light); + --color-primary-contrast-950: var(--color-primary-contrast-light); + + /* secondary: circuit mint */ + --color-secondary-50: oklch(97% 0.03 162deg); + --color-secondary-100: oklch(94% 0.06 162deg); + --color-secondary-200: oklch(91% 0.1 162deg); + --color-secondary-300: oklch(88% 0.14 162deg); + --color-secondary-400: oklch(84% 0.16 162deg); + --color-secondary-500: oklch(79% 0.17 162deg); + --color-secondary-600: oklch(70% 0.15 162deg); + --color-secondary-700: oklch(60% 0.13 162deg); + --color-secondary-800: oklch(50% 0.1 162deg); + --color-secondary-900: oklch(41% 0.08 162deg); + --color-secondary-950: oklch(32% 0.06 162deg); + --color-secondary-contrast-dark: var(--color-surface-950); + --color-secondary-contrast-light: oklch(100% 0 0deg); + --color-secondary-contrast-50: var(--color-secondary-contrast-dark); + --color-secondary-contrast-100: var(--color-secondary-contrast-dark); + --color-secondary-contrast-200: var(--color-secondary-contrast-dark); + --color-secondary-contrast-300: var(--color-secondary-contrast-dark); + --color-secondary-contrast-400: var(--color-secondary-contrast-dark); + --color-secondary-contrast-500: var(--color-secondary-contrast-dark); + --color-secondary-contrast-600: var(--color-secondary-contrast-dark); + --color-secondary-contrast-700: var(--color-secondary-contrast-light); + --color-secondary-contrast-800: var(--color-secondary-contrast-light); + --color-secondary-contrast-900: var(--color-secondary-contrast-light); + --color-secondary-contrast-950: var(--color-secondary-contrast-light); + + /* tertiary: background teal */ + --color-tertiary-50: oklch(97% 0.02 215deg); + --color-tertiary-100: oklch(93% 0.04 215deg); + --color-tertiary-200: oklch(87% 0.07 215deg); + --color-tertiary-300: oklch(80% 0.1 215deg); + --color-tertiary-400: oklch(72% 0.12 215deg); + --color-tertiary-500: oklch(64% 0.13 215deg); + --color-tertiary-600: oklch(56% 0.12 215deg); + --color-tertiary-700: oklch(48% 0.1 215deg); + --color-tertiary-800: oklch(40% 0.08 215deg); + --color-tertiary-900: oklch(33% 0.06 215deg); + --color-tertiary-950: oklch(26% 0.05 215deg); + --color-tertiary-contrast-dark: var(--color-surface-950); + --color-tertiary-contrast-light: oklch(100% 0 0deg); + --color-tertiary-contrast-50: var(--color-tertiary-contrast-dark); + --color-tertiary-contrast-100: var(--color-tertiary-contrast-dark); + --color-tertiary-contrast-200: var(--color-tertiary-contrast-dark); + --color-tertiary-contrast-300: var(--color-tertiary-contrast-dark); + --color-tertiary-contrast-400: var(--color-tertiary-contrast-dark); + --color-tertiary-contrast-500: var(--color-tertiary-contrast-dark); + --color-tertiary-contrast-600: var(--color-tertiary-contrast-light); + --color-tertiary-contrast-700: var(--color-tertiary-contrast-light); + --color-tertiary-contrast-800: var(--color-tertiary-contrast-light); + --color-tertiary-contrast-900: var(--color-tertiary-contrast-light); + --color-tertiary-contrast-950: var(--color-tertiary-contrast-light); + + --color-success-50: oklch(97% 0.03 150deg); + --color-success-100: oklch(93% 0.06 150deg); + --color-success-200: oklch(87% 0.1 150deg); + --color-success-300: oklch(80% 0.14 150deg); + --color-success-400: oklch(73% 0.16 150deg); + --color-success-500: oklch(66% 0.17 150deg); + --color-success-600: oklch(58% 0.15 150deg); + --color-success-700: oklch(50% 0.13 150deg); + --color-success-800: oklch(42% 0.1 150deg); + --color-success-900: oklch(35% 0.08 150deg); + --color-success-950: oklch(27% 0.06 150deg); + --color-success-contrast-dark: var(--color-surface-950); + --color-success-contrast-light: oklch(100% 0 0deg); + --color-success-contrast-50: var(--color-success-contrast-dark); + --color-success-contrast-100: var(--color-success-contrast-dark); + --color-success-contrast-200: var(--color-success-contrast-dark); + --color-success-contrast-300: var(--color-success-contrast-dark); + --color-success-contrast-400: var(--color-success-contrast-dark); + --color-success-contrast-500: var(--color-success-contrast-dark); + --color-success-contrast-600: var(--color-success-contrast-light); + --color-success-contrast-700: var(--color-success-contrast-light); + --color-success-contrast-800: var(--color-success-contrast-light); + --color-success-contrast-900: var(--color-success-contrast-light); + --color-success-contrast-950: var(--color-success-contrast-light); + + --color-warning-50: oklch(97% 0.03 80deg); + --color-warning-100: oklch(94% 0.06 80deg); + --color-warning-200: oklch(90% 0.1 80deg); + --color-warning-300: oklch(86% 0.14 80deg); + --color-warning-400: oklch(82% 0.16 80deg); + --color-warning-500: oklch(77% 0.16 75deg); + --color-warning-600: oklch(68% 0.15 70deg); + --color-warning-700: oklch(58% 0.13 65deg); + --color-warning-800: oklch(48% 0.11 60deg); + --color-warning-900: oklch(40% 0.09 60deg); + --color-warning-950: oklch(31% 0.07 60deg); + --color-warning-contrast-dark: var(--color-surface-950); + --color-warning-contrast-light: oklch(100% 0 0deg); + --color-warning-contrast-50: var(--color-warning-contrast-dark); + --color-warning-contrast-100: var(--color-warning-contrast-dark); + --color-warning-contrast-200: var(--color-warning-contrast-dark); + --color-warning-contrast-300: var(--color-warning-contrast-dark); + --color-warning-contrast-400: var(--color-warning-contrast-dark); + --color-warning-contrast-500: var(--color-warning-contrast-dark); + --color-warning-contrast-600: var(--color-warning-contrast-dark); + --color-warning-contrast-700: var(--color-warning-contrast-light); + --color-warning-contrast-800: var(--color-warning-contrast-light); + --color-warning-contrast-900: var(--color-warning-contrast-light); + --color-warning-contrast-950: var(--color-warning-contrast-light); + + --color-error-50: oklch(97% 0.02 25deg); + --color-error-100: oklch(93% 0.04 25deg); + --color-error-200: oklch(86% 0.08 25deg); + --color-error-300: oklch(78% 0.13 25deg); + --color-error-400: oklch(70% 0.18 25deg); + --color-error-500: oklch(62% 0.21 25deg); + --color-error-600: oklch(55% 0.2 25deg); + --color-error-700: oklch(48% 0.17 25deg); + --color-error-800: oklch(41% 0.14 25deg); + --color-error-900: oklch(34% 0.11 25deg); + --color-error-950: oklch(27% 0.08 25deg); + --color-error-contrast-dark: var(--color-surface-950); + --color-error-contrast-light: oklch(100% 0 0deg); + --color-error-contrast-50: var(--color-error-contrast-dark); + --color-error-contrast-100: var(--color-error-contrast-dark); + --color-error-contrast-200: var(--color-error-contrast-dark); + --color-error-contrast-300: var(--color-error-contrast-dark); + --color-error-contrast-400: var(--color-error-contrast-dark); + --color-error-contrast-500: var(--color-error-contrast-light); + --color-error-contrast-600: var(--color-error-contrast-light); + --color-error-contrast-700: var(--color-error-contrast-light); + --color-error-contrast-800: var(--color-error-contrast-light); + --color-error-contrast-900: var(--color-error-contrast-light); + --color-error-contrast-950: var(--color-error-contrast-light); + + /* surface: outline navy */ + --color-surface-50: oklch(98.5% 0.004 262deg); + --color-surface-100: oklch(96% 0.008 262deg); + --color-surface-200: oklch(91% 0.014 262deg); + --color-surface-300: oklch(83% 0.02 262deg); + --color-surface-400: oklch(69% 0.028 262deg); + --color-surface-500: oklch(56% 0.034 262deg); + --color-surface-600: oklch(45% 0.036 262deg); + --color-surface-700: oklch(35% 0.036 262deg); + --color-surface-800: oklch(27% 0.034 262deg); + --color-surface-900: oklch(21% 0.03 262deg); + --color-surface-950: oklch(16.5% 0.026 262deg); + --color-surface-contrast-dark: var(--color-surface-950); + --color-surface-contrast-light: var(--color-surface-50); + --color-surface-contrast-50: var(--color-surface-contrast-dark); + --color-surface-contrast-100: var(--color-surface-contrast-dark); + --color-surface-contrast-200: var(--color-surface-contrast-dark); + --color-surface-contrast-300: var(--color-surface-contrast-dark); + --color-surface-contrast-400: var(--color-surface-contrast-dark); + --color-surface-contrast-500: var(--color-surface-contrast-light); + --color-surface-contrast-600: var(--color-surface-contrast-light); + --color-surface-contrast-700: var(--color-surface-contrast-light); + --color-surface-contrast-800: var(--color-surface-contrast-light); + --color-surface-contrast-900: var(--color-surface-contrast-light); + --color-surface-contrast-950: var(--color-surface-contrast-light); +} diff --git a/static/og-image.png b/static/og-image.png new file mode 100644 index 0000000..6e6f0ee Binary files /dev/null and b/static/og-image.png differ diff --git a/static/site.webmanifest b/static/site.webmanifest index 9591150..25145bf 100644 --- a/static/site.webmanifest +++ b/static/site.webmanifest @@ -1,6 +1,6 @@ { - "name": "", - "short_name": "", + "name": "CommitKudos", + "short_name": "CommitKudos", "icons": [ { "src": "/android-chrome-192x192.png", @@ -13,7 +13,7 @@ "type": "image/png" } ], - "theme_color": "#ffffff", - "background_color": "#ffffff", + "theme_color": "#0a0f1a", + "background_color": "#0a0f1a", "display": "standalone" }