Skip to content

Commit ea9e144

Browse files
authored
Merge pull request #440 from samternent/stickers
Stickers
2 parents 6421f67 + 6ecb786 commit ea9e144

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+6869
-1304
lines changed

.changeset/dirty-oranges-sneeze.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"@ternent/concord-protocol": minor
3+
"ternent-api": minor
4+
"concord": minor
5+
---
6+
7+
Adds pixpax app

.github/workflows/deploy-ternent-api.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ jobs:
3333
with:
3434
username: ${{ secrets.DOCKERHUB_USERNAME }}
3535
password: ${{ secrets.DOCKERHUB_TOKEN }}
36+
37+
38+
- name: Build libs
39+
run: pnpm build:ternent-api-libs
40+
41+
- name: Build packs
42+
run: pnpm generate:pixpax:all
3643

3744
- name: Build Docker image
3845
run: pnpm build:ternent-api

apps/concord/index.html

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,43 @@
5151
<link rel="preconnect" href="https://fonts.googleapis.com" />
5252
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
5353
<link
54-
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Noto+Serif:ital,wght@0,100..900;1,100..900&family=JetBrains+Mono:ital,wght@0,200..800;1,200..800"
54+
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&family=Noto+Serif:ital,wght@0,100..900;1,100..900&family=JetBrains+Mono:ital,wght@0,200..800;1,200..800&Pixelify+Sans:wght@400;700"
5555
rel="stylesheet"
5656
/>
5757

5858
<style>
59+
:root {
60+
--brand-font: "Pixelify Sans", "Press Start 2P", ui-monospace, monospace;
61+
--bg-pixpax: radial-gradient(
62+
circle at top,
63+
color-mix(in oklch, var(--pixpax-warm) 70%, var(--ui-bg)) 0%,
64+
color-mix(in oklch, var(--pixpax-pink) 60%, var(--ui-bg)) 40%,
65+
color-mix(in oklch, var(--pixpax-blue) 50%, var(--ui-bg)) 100%
66+
);
67+
68+
--ui-bg: oklch(98% 0.01 95);
69+
--ui-fg: oklch(20% 0.02 260);
70+
71+
/* PixPax accent washes */
72+
--pixpax-warm: oklch(96% 0.05 85);
73+
--pixpax-pink: oklch(94% 0.06 10);
74+
--pixpax-blue: oklch(95% 0.04 230);
75+
76+
--font-serif: "Noto Serif", serif;
77+
--font-sans: "Noto Sans", sans-serif;
78+
--font-mono: "JetBrains Mono", "Courier New", monospace;
79+
}
80+
81+
[data-theme-palette="dark"] {
82+
--ui-bg: oklch(18% 0.02 260);
83+
--ui-fg: oklch(92% 0.01 95);
84+
85+
/* Same hues, lower lightness = restraint */
86+
--pixpax-warm: oklch(28% 0.04 85);
87+
--pixpax-pink: oklch(26% 0.05 10);
88+
--pixpax-blue: oklch(30% 0.04 230);
89+
}
90+
5991
html,
6092
body {
6193
font-family: "Noto Serif", serif;

apps/concord/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
"scripts": {
77
"dev": "vite",
88
"build": "vite-ssg build",
9-
"preview": "vite preview"
9+
"preview": "vite preview",
10+
"test": "vitest"
1011
},
1112
"dependencies": {
1213
"@inrupt/solid-client": "^3.0.0",
@@ -23,6 +24,7 @@
2324
"ternent-ui": "workspace:*",
2425
"ternent-utils": "workspace:*",
2526
"vite-plugin-ejs": "^1.7.0",
27+
"vite-svg-loader": "^5.1.0",
2628
"vue": "^3.5.24"
2729
},
2830
"devDependencies": {
@@ -34,6 +36,7 @@
3436
"vite": "npm:rolldown-vite@7.2.5",
3537
"vite-plugin-vue-markdown": "^0.23.8",
3638
"vite-ssg": "^28.2.2",
39+
"vitest": "^1.6.0",
3740
"vue-router": "^4.4.3"
3841
},
3942
"overrides": {

apps/concord/src/content/index.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,7 @@ version: 0.1
55
date: 2026-01-06
66
---
77

8-
# Concord Whitepaper
9-
10-
_A local‑first, verifiable ledger for application data_
8+
# A local‑first, verifiable ledger for application data
119

1210
---
1311

apps/concord/src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ watchEffect(() => {
4141
"data-theme",
4242
`${theme.value}-${themeMode.value || "light"}`
4343
);
44+
document.documentElement.setAttribute("data-theme-palette", themeMode.value);
4445

4546
// document.documentElement.classList.toggle(themeMode.value);
4647
});
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script setup lang="ts">
2+
import { ref, watch, onMounted } from "vue";
3+
import { useLedger } from "../ledger/useLedger";
4+
import { useIdentity } from "../identity/useIdentity";
5+
import { useProfile } from "../profile/useProfile";
6+
7+
const props = defineProps<{
8+
readOnly?: boolean;
9+
}>();
10+
11+
const { api, ledger } = useLedger();
12+
const { publicKey, privateKey, publicKeyPEM } = useIdentity();
13+
const profile = useProfile();
14+
15+
const lastAuthKey = ref("");
16+
17+
onMounted(async () => {
18+
if (!ledger.value) {
19+
await api.loadFromStorage();
20+
}
21+
});
22+
23+
watch(
24+
() =>
25+
[
26+
profile.ready.value,
27+
publicKey.value,
28+
privateKey.value,
29+
props.readOnly,
30+
] as const,
31+
async ([ready, pub, priv, isReadOnly]) => {
32+
if (isReadOnly) return;
33+
if (!ready || !pub || !priv) return;
34+
await profile.ensureProfileId();
35+
36+
const identityKey = publicKeyPEM.value || "";
37+
if (identityKey && identityKey === lastAuthKey.value) return;
38+
lastAuthKey.value = identityKey;
39+
40+
await api.auth(priv, pub);
41+
await api.replay();
42+
},
43+
{ immediate: true }
44+
);
45+
</script>
46+
47+
<template>
48+
<slot />
49+
</template>

apps/concord/src/module/app/WebLayout.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,25 +30,25 @@ import Logo from "../brand/Logo.vue";
3030
</div>
3131
<RouterLink
3232
to="/workspace"
33-
class="text-xs flex items-center py-1.5 px-3 rounded-md bg-[var(--ui-primary)]"
33+
class="text-xs flex items-center py-1.5 px-3 rounded-md bg-[var(--ui-primary)] text-[var(--ui-bg)] hover:bg-[var(--ui-primary-hover)] transition"
3434
>Demo</RouterLink
3535
>
3636
</nav>
3737
</div>
3838
</header>
39-
<main class="mx-auto max-w-[65ch] p-4"><slot /></main>
39+
<main class="mx-auto max-w-[65ch] py-6 lg:py-12 px-2"><slot /></main>
4040

4141
<footer
4242
class="flex justify-end items-center py-12 border-t-[1px] border-[var(--ui-border)] w-full"
4343
>
4444
<div class="max-w-[980px] mx-auto w-full flex justify-end group">
4545
<a
46-
href="mailto:concord@ternent.dev"
46+
href="mailto:sam@ternent.dev"
4747
class="flex items-end justify-center gap-3 opacity-80 hover:opacity-100"
4848
>
4949
<span
5050
class="text-xs font-mono font-thin -translate-y-1 group-hover:translate-y-0.5 group-hover:translate-x-1.5 transition-all duration-300"
51-
>concord@ternent.dev</span
51+
>sam@ternent.dev</span
5252
>
5353
<Logo
5454
class="size-8 group-hover:-rotate-6 transition-all duration-300"
Lines changed: 61 additions & 0 deletions
Loading
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<script setup lang="ts">
2+
import { computed } from "vue";
3+
import StickerFrame from "./StickerFrame.vue";
4+
import StickerPixelArt from "./StickerPixelArt.vue";
5+
import StickerKitArt from "./StickerKitArt.vue";
6+
7+
type GridArt = {
8+
kind: "grid";
9+
data: {
10+
creature: any;
11+
palettes: any[];
12+
};
13+
};
14+
15+
type KitArt = {
16+
kind: "kit";
17+
data: {
18+
sticker: any;
19+
palettes?: any[];
20+
kitId?: string;
21+
};
22+
};
23+
24+
type StickerProps = {
25+
art: GridArt | KitArt;
26+
rarity: string;
27+
size?: "compact" | "full";
28+
label?: string;
29+
sublabel?: string;
30+
status?: string;
31+
finish?: string;
32+
packId?: string;
33+
missing?: boolean;
34+
};
35+
36+
const props = defineProps<StickerProps>();
37+
38+
const isCompact = computed(() => props.size === "compact");
39+
const defaultLabel = computed(() => {
40+
if (props.label) return props.label;
41+
if (props.art.kind === "grid") return props.art.data.creature?.id || "";
42+
return props.art.data.sticker?.id || "";
43+
});
44+
const defaultSublabel = computed(() => {
45+
if (props.sublabel) return props.sublabel;
46+
if (props.art.kind === "grid") return props.art.data.creature?.rarity || "";
47+
return props.art.data.sticker?.rarity || "";
48+
});
49+
const seed = computed(() => {
50+
const base =
51+
props.art.kind === "grid"
52+
? props.art.data.creature?.id
53+
: props.art.data.sticker?.id;
54+
const normalized = base || defaultLabel.value || defaultSublabel.value || props.rarity;
55+
return props.packId ? `${props.packId}:${normalized}` : normalized;
56+
});
57+
</script>
58+
59+
<template>
60+
<StickerFrame
61+
:rarity="rarity"
62+
:compact="isCompact"
63+
:label="defaultLabel"
64+
:sublabel="defaultSublabel"
65+
:status="status"
66+
:missing="missing"
67+
:seed="seed"
68+
>
69+
<StickerPixelArt
70+
v-if="art.kind === 'grid'"
71+
:creature="art.data.creature"
72+
:palettes="art.data.palettes"
73+
:finish="finish"
74+
:pack-id="packId"
75+
:missing="missing"
76+
/>
77+
<StickerKitArt
78+
v-else
79+
:sticker="art.data.sticker"
80+
:palettes="art.data.palettes"
81+
:kit-id="art.data.kitId"
82+
:finish="finish"
83+
:pack-id="packId"
84+
:missing="missing"
85+
/>
86+
</StickerFrame>
87+
</template>

0 commit comments

Comments
 (0)