@meddleware/landing 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,280 @@
1
+ name: Publish
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ env:
9
+ IMAGE: landing
10
+
11
+ jobs:
12
+ # ── App: Docker image → quay.io + Docker Hub ────────────────────────────────
13
+ # Split into per-arch native builds + merge so each platform compiles on its
14
+ # own native runner. The arm64 Vite build runs on ubuntu-24.04-arm rather than
15
+ # QEMU emulation, eliminating the ~3-5× slowdown for npm install + tsc + vite.
16
+ #
17
+ # No VITE_* build args — the landing page has no wallet, no chain SDK, and no
18
+ # operator-configurable runtime values.
19
+
20
+ build-docker-amd64-public:
21
+ name: Docker amd64 (quay.io + Docker Hub)
22
+ runs-on: ubuntu-latest
23
+ permissions:
24
+ contents: read
25
+ steps:
26
+ - uses: actions/checkout@v7.0.1
27
+ - uses: docker/setup-buildx-action@v4.3.0
28
+
29
+ - name: Log in to quay.io
30
+ uses: docker/login-action@v4.6.0
31
+ with:
32
+ registry: quay.io
33
+ username: ${{ secrets.QUAY_USERNAME }}
34
+ password: ${{ secrets.QUAY_TOKEN }}
35
+
36
+ - name: Log in to Docker Hub
37
+ uses: docker/login-action@v4.6.0
38
+ with:
39
+ registry: docker.io
40
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
41
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
42
+
43
+ - name: Build and push (linux/amd64)
44
+ uses: docker/build-push-action@v7.3.0
45
+ with:
46
+ context: .
47
+ platforms: linux/amd64
48
+ push: true
49
+ tags: |
50
+ quay.io/${{ vars.QUAY_NAMESPACE }}/${{ env.IMAGE }}:${{ github.sha }}-amd64
51
+ docker.io/${{ vars.DOCKERHUB_NAMESPACE }}/${{ env.IMAGE }}:${{ github.sha }}-amd64
52
+ cache-from: type=gha,scope=amd64-public
53
+ cache-to: type=gha,scope=amd64-public,mode=max
54
+
55
+ build-docker-arm64-public:
56
+ name: Docker arm64 (quay.io + Docker Hub)
57
+ runs-on: ubuntu-24.04-arm
58
+ permissions:
59
+ contents: read
60
+ steps:
61
+ - uses: actions/checkout@v7.0.1
62
+ - uses: docker/setup-buildx-action@v4.3.0
63
+
64
+ - name: Log in to quay.io
65
+ uses: docker/login-action@v4.6.0
66
+ with:
67
+ registry: quay.io
68
+ username: ${{ secrets.QUAY_USERNAME }}
69
+ password: ${{ secrets.QUAY_TOKEN }}
70
+
71
+ - name: Log in to Docker Hub
72
+ uses: docker/login-action@v4.6.0
73
+ with:
74
+ registry: docker.io
75
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
76
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
77
+
78
+ - name: Build and push (linux/arm64)
79
+ uses: docker/build-push-action@v7.3.0
80
+ with:
81
+ context: .
82
+ platforms: linux/arm64
83
+ push: true
84
+ tags: |
85
+ quay.io/${{ vars.QUAY_NAMESPACE }}/${{ env.IMAGE }}:${{ github.sha }}-arm64
86
+ docker.io/${{ vars.DOCKERHUB_NAMESPACE }}/${{ env.IMAGE }}:${{ github.sha }}-arm64
87
+ cache-from: type=gha,scope=arm64-public
88
+ cache-to: type=gha,scope=arm64-public,mode=max
89
+
90
+ merge-docker-public:
91
+ name: Merge + sign (quay.io + Docker Hub)
92
+ needs: [build-docker-amd64-public, build-docker-arm64-public]
93
+ runs-on: ubuntu-latest
94
+ permissions:
95
+ contents: read
96
+ id-token: write
97
+ attestations: write
98
+ steps:
99
+ - uses: docker/setup-buildx-action@v4.3.0
100
+
101
+ - name: Log in to quay.io
102
+ uses: docker/login-action@v4.6.0
103
+ with:
104
+ registry: quay.io
105
+ username: ${{ secrets.QUAY_USERNAME }}
106
+ password: ${{ secrets.QUAY_TOKEN }}
107
+
108
+ - name: Log in to Docker Hub
109
+ uses: docker/login-action@v4.6.0
110
+ with:
111
+ registry: docker.io
112
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
113
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
114
+
115
+ - name: Compute final tags and labels
116
+ id: meta
117
+ uses: docker/metadata-action@v6.2.0
118
+ with:
119
+ images: |
120
+ quay.io/${{ vars.QUAY_NAMESPACE }}/${{ env.IMAGE }}
121
+ docker.io/${{ vars.DOCKERHUB_NAMESPACE }}/${{ env.IMAGE }}
122
+ tags: |
123
+ type=semver,pattern={{version}}
124
+ type=semver,pattern={{major}}.{{minor}}
125
+ flavor: |
126
+ latest=auto
127
+
128
+ - name: Merge multi-arch manifests
129
+ id: merge
130
+ env:
131
+ QUAY_NS: ${{ vars.QUAY_NAMESPACE }}
132
+ DOCKERHUB_NS: ${{ vars.DOCKERHUB_NAMESPACE }}
133
+ SHA: ${{ github.sha }}
134
+ run: |
135
+ QUAY_SRC="quay.io/${QUAY_NS}/${IMAGE}:${SHA}"
136
+ HUB_SRC="docker.io/${DOCKERHUB_NS}/${IMAGE}:${SHA}"
137
+
138
+ QUAY_FLAGS=""
139
+ HUB_FLAGS=""
140
+ while IFS= read -r tag; do
141
+ if [[ "$tag" == quay.io/* ]]; then
142
+ QUAY_FLAGS="$QUAY_FLAGS --tag $tag"
143
+ elif [[ "$tag" == docker.io/* ]]; then
144
+ HUB_FLAGS="$HUB_FLAGS --tag $tag"
145
+ fi
146
+ done <<< "${{ steps.meta.outputs.tags }}"
147
+
148
+ docker buildx imagetools create $QUAY_FLAGS "${QUAY_SRC}-amd64" "${QUAY_SRC}-arm64"
149
+ docker buildx imagetools create $HUB_FLAGS "${HUB_SRC}-amd64" "${HUB_SRC}-arm64"
150
+
151
+ PRIMARY=$(echo "${{ steps.meta.outputs.tags }}" | grep 'quay.io' | grep -v 'latest' | head -1)
152
+ DIGEST=$(docker buildx imagetools inspect "${PRIMARY}" --format '{{.Manifest.Digest}}')
153
+ echo "digest=${DIGEST}" >> "$GITHUB_OUTPUT"
154
+
155
+ - uses: sigstore/cosign-installer@v4.1.2
156
+
157
+ - name: Sign images (keyless)
158
+ env:
159
+ DIGEST: ${{ steps.merge.outputs.digest }}
160
+ QUAY_NS: ${{ vars.QUAY_NAMESPACE }}
161
+ DOCKERHUB_NS: ${{ vars.DOCKERHUB_NAMESPACE }}
162
+ run: |
163
+ cosign sign --yes "quay.io/${QUAY_NS}/${IMAGE}@${DIGEST}"
164
+ cosign sign --yes "docker.io/${DOCKERHUB_NS}/${IMAGE}@${DIGEST}"
165
+
166
+ - name: Attest build provenance (quay.io)
167
+ uses: actions/attest-build-provenance@v4.2.2
168
+ continue-on-error: true
169
+ with:
170
+ subject-name: quay.io/${{ vars.QUAY_NAMESPACE }}/${{ env.IMAGE }}
171
+ subject-digest: ${{ steps.merge.outputs.digest }}
172
+ push-to-registry: true
173
+
174
+ - name: Attest build provenance (Docker Hub)
175
+ uses: actions/attest-build-provenance@v4.2.2
176
+ continue-on-error: true
177
+ with:
178
+ subject-name: docker.io/${{ vars.DOCKERHUB_NAMESPACE }}/${{ env.IMAGE }}
179
+ subject-digest: ${{ steps.merge.outputs.digest }}
180
+ push-to-registry: true
181
+
182
+ - name: Summary
183
+ run: echo "Published & signed @ ${{ steps.merge.outputs.digest }}" >> "$GITHUB_STEP_SUMMARY"
184
+
185
+ # ── Self-hosted registry (best-effort) ─────────────────────────────────────
186
+
187
+ build-docker-amd64-private:
188
+ name: Docker amd64 (self-hosted registry)
189
+ runs-on: ubuntu-latest
190
+ continue-on-error: true
191
+ permissions:
192
+ contents: read
193
+ steps:
194
+ - uses: actions/checkout@v7.0.1
195
+ - uses: docker/setup-buildx-action@v4.3.0
196
+
197
+ - name: Log in to self-hosted registry
198
+ uses: docker/login-action@v4.6.0
199
+ with:
200
+ registry: ${{ vars.PRIVATE_REGISTRY }}
201
+ username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }}
202
+ password: ${{ secrets.PRIVATE_REGISTRY_TOKEN }}
203
+
204
+ - name: Build and push (linux/amd64)
205
+ uses: docker/build-push-action@v7.3.0
206
+ with:
207
+ context: .
208
+ platforms: linux/amd64
209
+ push: true
210
+ tags: ${{ vars.PRIVATE_REGISTRY }}/${{ vars.PRIVATE_NAMESPACE }}/${{ env.IMAGE }}:${{ github.sha }}-amd64
211
+ cache-from: type=gha,scope=amd64-private
212
+ cache-to: type=gha,scope=amd64-private,mode=max
213
+
214
+ build-docker-arm64-private:
215
+ name: Docker arm64 (self-hosted registry)
216
+ runs-on: ubuntu-24.04-arm
217
+ continue-on-error: true
218
+ permissions:
219
+ contents: read
220
+ steps:
221
+ - uses: actions/checkout@v7.0.1
222
+ - uses: docker/setup-buildx-action@v4.3.0
223
+
224
+ - name: Log in to self-hosted registry
225
+ uses: docker/login-action@v4.6.0
226
+ with:
227
+ registry: ${{ vars.PRIVATE_REGISTRY }}
228
+ username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }}
229
+ password: ${{ secrets.PRIVATE_REGISTRY_TOKEN }}
230
+
231
+ - name: Build and push (linux/arm64)
232
+ uses: docker/build-push-action@v7.3.0
233
+ with:
234
+ context: .
235
+ platforms: linux/arm64
236
+ push: true
237
+ tags: ${{ vars.PRIVATE_REGISTRY }}/${{ vars.PRIVATE_NAMESPACE }}/${{ env.IMAGE }}:${{ github.sha }}-arm64
238
+ cache-from: type=gha,scope=arm64-private
239
+ cache-to: type=gha,scope=arm64-private,mode=max
240
+
241
+ merge-docker-private:
242
+ name: Merge (self-hosted registry)
243
+ needs: [build-docker-amd64-private, build-docker-arm64-private]
244
+ runs-on: ubuntu-latest
245
+ continue-on-error: true
246
+ permissions:
247
+ contents: read
248
+ steps:
249
+ - uses: docker/setup-buildx-action@v4.3.0
250
+
251
+ - name: Log in to self-hosted registry
252
+ uses: docker/login-action@v4.6.0
253
+ with:
254
+ registry: ${{ vars.PRIVATE_REGISTRY }}
255
+ username: ${{ secrets.PRIVATE_REGISTRY_USERNAME }}
256
+ password: ${{ secrets.PRIVATE_REGISTRY_TOKEN }}
257
+
258
+ - name: Compute tags
259
+ id: meta
260
+ uses: docker/metadata-action@v6.2.0
261
+ with:
262
+ images: ${{ vars.PRIVATE_REGISTRY }}/${{ vars.PRIVATE_NAMESPACE }}/${{ env.IMAGE }}
263
+ tags: |
264
+ type=semver,pattern={{version}}
265
+ type=semver,pattern={{major}}.{{minor}}
266
+ flavor: |
267
+ latest=auto
268
+
269
+ - name: Merge multi-arch manifests
270
+ env:
271
+ SHA: ${{ github.sha }}
272
+ REG: ${{ vars.PRIVATE_REGISTRY }}
273
+ NS: ${{ vars.PRIVATE_NAMESPACE }}
274
+ run: |
275
+ SRC="${REG}/${NS}/${IMAGE}:${SHA}"
276
+ FLAGS=""
277
+ while IFS= read -r tag; do
278
+ FLAGS="$FLAGS --tag $tag"
279
+ done <<< "${{ steps.meta.outputs.tags }}"
280
+ docker buildx imagetools create $FLAGS "${SRC}-amd64" "${SRC}-arm64"
package/CLAUDE.md ADDED
@@ -0,0 +1,69 @@
1
+ # CLAUDE.md — @meddleware/landing
2
+
3
+ ## What this app is
4
+
5
+ A Vue 3 + Vite SPA serving as the corporate landing page at `meddleware.co.uk`. It presents
6
+ Meddleware's tool suite to first-time visitors and links them to the chain-agnostic dashboard
7
+ (`dash.meddleware.co.uk`) and standalone tool subdomains.
8
+
9
+ The landing page has **no wallet, no blockchain SDK, no accounting logic** — it is a pure
10
+ marketing/navigation page. Keep it that way.
11
+
12
+ ## Architecture
13
+
14
+ ```
15
+ src/
16
+ App.vue AppHeader (transparent) + RouterView + AppFooter (dark)
17
+ views/
18
+ HomeView.vue Composes HeroSection + FeaturesGrid + CtaSection
19
+ components/
20
+ HeroSection.vue Full-viewport hero with gradient, headline, CTAs
21
+ FeaturesGrid.vue Responsive grid of ToolCard entries — one per live tool
22
+ ToolCard.vue Emoji icon + title + description + optional link
23
+ CtaSection.vue Secondary CTA strip with dashboard and docs links
24
+ styles/
25
+ main.css Layout resets; imports after design-tokens + ui base.css
26
+ env.d.ts Vite client types + *.vue shim
27
+ ```
28
+
29
+ ## Styling rules
30
+
31
+ All colours and spacing come from `@meddleware/design-tokens` role tokens (`--bg`, `--surface`,
32
+ `--text`, `--muted`, `--accent`, `--border`, `--focus-ring`, `--space-*`, `--font-size-*`,
33
+ `--tracking-*`). Never hardcode hex/rem values — see `@meddleware/ui` CLAUDE.md for the full
34
+ theming contract.
35
+
36
+ The hero gradient uses `color-mix(in srgb, var(--accent) 12%, transparent)` to produce a
37
+ subtle radial wash without referencing any colour-named ramps.
38
+
39
+ ## Adding a new tool card
40
+
41
+ Add an entry to the `tools` array in `src/components/FeaturesGrid.vue`:
42
+
43
+ ```ts
44
+ {
45
+ icon: '⚙',
46
+ title: 'New Tool',
47
+ description: 'One or two sentences.',
48
+ href: 'https://new-tool.meddleware.co.uk',
49
+ }
50
+ ```
51
+
52
+ ## Deployment
53
+
54
+ Served by `static-server` in k8s namespace `apps`, service name `landing`, port 80.
55
+ Ingress: `meddleware.co.uk` → `post-bootstrap/landing/base/ingress.yaml`.
56
+ Cloudflare Tunnel: `meddleware.co.uk` → `https://nginx-ingress-controller.nginx-ingress.svc:443`
57
+ (No TLS Verify; HTTP Host Header `meddleware.co.uk`).
58
+
59
+ Legal files (`public/legal/`) must be copied from `repos/ui/public/legal/` before deploying
60
+ so the `CopyrightLine` component's symbol links resolve correctly.
61
+
62
+ ## Build
63
+
64
+ ```bash
65
+ npm install
66
+ npm run type-check # vue-tsc
67
+ npm run build # type-check + vite build → dist/
68
+ npm run dev # local dev server at http://localhost:5173
69
+ ```
package/Dockerfile ADDED
@@ -0,0 +1,32 @@
1
+ # ── build stage ───────────────────────────────────────────────────────────────
2
+ # Standalone build — the Docker context is this repo root. All @meddleware/*
3
+ # dependencies resolve from the npm registry, so they must be published before
4
+ # this image is built.
5
+ #
6
+ # docker build -t landing:<tag> .
7
+ #
8
+ # No VITE_* build args are required — the landing page has no wallet, no chain
9
+ # SDK, and no operator-configurable runtime values.
10
+
11
+ FROM node:24-slim AS build
12
+ WORKDIR /app
13
+
14
+ COPY package.json package-lock.json ./
15
+ RUN npm ci
16
+
17
+ COPY . .
18
+ RUN npm run build
19
+
20
+ # ── runtime stage ─────────────────────────────────────────────────────────────
21
+ # static-server is a minimal Go binary image — no shell, no package manager.
22
+ # See https://github.com/meddleware-org/static-server for configuration details.
23
+
24
+ FROM quay.io/meddleware-org/static-server:0.1.1
25
+
26
+ COPY --from=build /app/dist /app/public
27
+
28
+ ENV SERVE_DIR=/app/public \
29
+ SPA_FALLBACK=true \
30
+ CACHE_IMMUTABLE_PREFIX=/assets/
31
+
32
+ EXPOSE 8080
package/index.html ADDED
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="description" content="Meddleware — protocol-agnostic infrastructure, developer tools, and on-chain primitives. Currently live on Sui." />
7
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
8
+ <title>Meddleware — Tooling for the decentralised web</title>
9
+ </head>
10
+ <body>
11
+ <div id="app"></div>
12
+ <script type="module" src="/src/main.ts"></script>
13
+ </body>
14
+ </html>
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@meddleware/landing",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "vue-tsc --noEmit && vite build",
8
+ "preview": "vite preview",
9
+ "type-check": "vue-tsc --noEmit"
10
+ },
11
+ "dependencies": {
12
+ "@meddleware/design-tokens": "0.1.5",
13
+ "@meddleware/ui": "0.1.21",
14
+ "vue": "^3.5.43",
15
+ "vue-router": "^5.3.1"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "~24.12.2",
19
+ "@vitejs/plugin-vue": "^6.0.9",
20
+ "typescript": "^6.0.0",
21
+ "vite": "^8.3.0",
22
+ "vue-tsc": "~3.3.11"
23
+ },
24
+ "engines": {
25
+ "node": "^22.18.0 || >=24.12.0"
26
+ }
27
+ }
package/public/CNAME ADDED
@@ -0,0 +1 @@
1
+ meddleware.co.uk
@@ -0,0 +1,7 @@
1
+ /*
2
+ X-Frame-Options: DENY
3
+ X-Content-Type-Options: nosniff
4
+ Referrer-Policy: strict-origin-when-cross-origin
5
+ Permissions-Policy: camera=(), microphone=(), geolocation=()
6
+ Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https:
7
+ Strict-Transport-Security: max-age=31536000; includeSubDomains
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none">
2
+ <rect width="32" height="32" rx="8" fill="#2d1b1e"/>
3
+ <path d="M16 6 L26 12 L26 20 L16 26 L6 20 L6 12 Z" fill="#c0392b" opacity="0.9"/>
4
+ <path d="M16 10 L22 13.5 L22 20.5 L16 24 L10 20.5 L10 13.5 Z" fill="#2d1b1e"/>
5
+ <path d="M16 14 L19 15.75 L19 19.25 L16 21 L13 19.25 L13 15.75 Z" fill="#c0392b" opacity="0.7"/>
6
+ </svg>
@@ -0,0 +1,48 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Copyleft Notice</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light dark;
10
+ font-family:
11
+ system-ui,
12
+ -apple-system,
13
+ BlinkMacSystemFont,
14
+ "Segoe UI",
15
+ sans-serif;
16
+ }
17
+ body {
18
+ margin: 0;
19
+ padding: 2rem;
20
+ line-height: 1.6;
21
+ max-width: 48rem;
22
+ }
23
+ h1 {
24
+ margin-top: 0;
25
+ font-size: 1.5rem;
26
+ }
27
+ code {
28
+ font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
29
+ }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <h1>Copyleft Notice</h1>
34
+ <p>
35
+ This page is reserved for works distributed under a copyleft-style
36
+ permission structure.
37
+ </p>
38
+ <p>
39
+ Copyleft generally means that derivatives and redistributions must
40
+ preserve the same or compatible licensing terms, depending on the specific
41
+ licence in force.
42
+ </p>
43
+ <p>
44
+ The exact legal meaning depends on the licence or notice that accompanies
45
+ the relevant work.
46
+ </p>
47
+ </body>
48
+ </html>
@@ -0,0 +1,48 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Copyright Notice</title>
7
+ <style>
8
+ :root {
9
+ color-scheme: light dark;
10
+ font-family:
11
+ system-ui,
12
+ -apple-system,
13
+ BlinkMacSystemFont,
14
+ "Segoe UI",
15
+ sans-serif;
16
+ }
17
+ body {
18
+ margin: 0;
19
+ padding: 2rem;
20
+ line-height: 1.6;
21
+ max-width: 48rem;
22
+ }
23
+ h1 {
24
+ margin-top: 0;
25
+ font-size: 1.5rem;
26
+ }
27
+ code {
28
+ font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
29
+ }
30
+ </style>
31
+ </head>
32
+ <body>
33
+ <h1>Copyright Notice</h1>
34
+ <p>
35
+ Unless otherwise stated, the content and code in this application are
36
+ protected by copyright.
37
+ </p>
38
+ <p>
39
+ Copyright in the relevant work is retained by the applicable rights holder
40
+ and used under the permissions, licences, or agreements associated with
41
+ that work.
42
+ </p>
43
+ <p>
44
+ For the full application footer, see the main interface. This page exists
45
+ as a static legal reference.
46
+ </p>
47
+ </body>
48
+ </html>
package/src/App.vue ADDED
@@ -0,0 +1,101 @@
1
+ <script setup lang="ts">
2
+ import { RouterView } from 'vue-router'
3
+ import { AppHeader, AppFooter, CopyrightLine, useColorMode } from '@meddleware/ui'
4
+
5
+ const { mode } = useColorMode('dark')
6
+ </script>
7
+
8
+ <template>
9
+ <a class="skip-link" href="#main-content">Skip to main content</a>
10
+
11
+ <AppHeader variant="transparent" :sticky="true">
12
+ <template #brand>
13
+ <a href="/" class="brand-link" aria-label="Meddleware home">
14
+ <span class="brand-mark" aria-hidden="true">◆</span>
15
+ <span>Meddleware</span>
16
+ </a>
17
+ </template>
18
+ <template #actions>
19
+ <nav class="header-nav" aria-label="Site">
20
+ <a href="https://dash.meddleware.co.uk" class="header-nav__link">Dashboard</a>
21
+ <a href="https://docs.meddleware.co.uk" class="header-nav__link" target="_blank" rel="noopener noreferrer">Docs</a>
22
+ </nav>
23
+ </template>
24
+ </AppHeader>
25
+
26
+ <main id="main-content">
27
+ <RouterView />
28
+ </main>
29
+
30
+ <AppFooter variant="dark">
31
+ <template #start>
32
+ <CopyrightLine symbolVariant="kopimi" organisation-name="Meddleware" rightsStatement="jam" />
33
+ </template>
34
+ <template #end>
35
+ <a href="https://dash.meddleware.co.uk" class="footer-link">Dashboard</a>
36
+ <a href="https://docs.meddleware.co.uk" class="footer-link" target="_blank" rel="noopener noreferrer">Documentation</a>
37
+ <a href="https://dev.meddleware.co.uk" class="footer-link" target="_blank" rel="noopener noreferrer">Developer docs</a>
38
+ </template>
39
+ </AppFooter>
40
+ </template>
41
+
42
+ <style scoped>
43
+ .skip-link {
44
+ position: absolute;
45
+ top: -100%;
46
+ left: 0;
47
+ padding: var(--space-2xs) var(--space-xs);
48
+ background: var(--accent);
49
+ color: var(--accent-contrast);
50
+ font-size: var(--font-size-sm);
51
+ z-index: 100;
52
+ }
53
+
54
+ .skip-link:focus {
55
+ top: 0;
56
+ }
57
+
58
+ .brand-link {
59
+ display: flex;
60
+ align-items: center;
61
+ gap: var(--space-2xs);
62
+ color: inherit;
63
+ text-decoration: none;
64
+ font-weight: 650;
65
+ letter-spacing: var(--tracking-wide);
66
+ }
67
+
68
+ .brand-mark {
69
+ color: var(--accent);
70
+ }
71
+
72
+ .header-nav {
73
+ display: flex;
74
+ align-items: center;
75
+ gap: var(--space-sm);
76
+ }
77
+
78
+ .header-nav__link {
79
+ color: inherit;
80
+ text-decoration: none;
81
+ font-size: var(--font-size-sm);
82
+ opacity: 0.8;
83
+ transition: opacity var(--transition-base, 150ms ease);
84
+ }
85
+
86
+ .header-nav__link:hover {
87
+ opacity: 1;
88
+ }
89
+
90
+ .footer-link {
91
+ color: inherit;
92
+ text-decoration: none;
93
+ font-size: var(--font-size-sm);
94
+ opacity: 0.65;
95
+ transition: opacity var(--transition-base, 150ms ease);
96
+ }
97
+
98
+ .footer-link:hover {
99
+ opacity: 1;
100
+ }
101
+ </style>
@@ -0,0 +1,86 @@
1
+ <script setup lang="ts">
2
+ // Static CTA strip — no reactive state.
3
+ </script>
4
+
5
+ <template>
6
+ <section class="cta" aria-labelledby="cta-heading">
7
+ <div class="cta__inner">
8
+ <h2 id="cta-heading" class="cta__title">Ready to build on-chain?</h2>
9
+ <p class="cta__body">
10
+ All tools are live on Sui testnet and mainnet. Connect a wallet and get started.
11
+ </p>
12
+ <div class="cta__actions">
13
+ <a href="https://dash.meddleware.co.uk" class="cta__btn cta__btn--primary">
14
+ Open Dashboard
15
+ </a>
16
+ <a href="https://docs.meddleware.co.uk" class="cta__btn cta__btn--secondary"
17
+ target="_blank" rel="noopener noreferrer">
18
+ Read the docs ↗
19
+ </a>
20
+ </div>
21
+ </div>
22
+ </section>
23
+ </template>
24
+
25
+ <style scoped>
26
+ .cta {
27
+ padding: var(--space-xl) var(--space-sm);
28
+ background: var(--bg);
29
+ text-align: center;
30
+ }
31
+
32
+ .cta__inner {
33
+ max-width: 36rem;
34
+ margin-inline: auto;
35
+ }
36
+
37
+ .cta__title {
38
+ margin: 0 0 var(--space-xs);
39
+ font-size: var(--font-size-xl);
40
+ font-weight: 700;
41
+ line-height: var(--leading-tight, 1.2);
42
+ color: var(--text);
43
+ }
44
+
45
+ .cta__body {
46
+ margin: 0 0 var(--space-md);
47
+ font-size: var(--font-size-sm);
48
+ color: var(--muted);
49
+ line-height: var(--leading-relaxed, 1.6);
50
+ }
51
+
52
+ .cta__actions {
53
+ display: flex;
54
+ flex-wrap: wrap;
55
+ align-items: center;
56
+ justify-content: center;
57
+ gap: var(--space-xs);
58
+ }
59
+
60
+ .cta__btn {
61
+ display: inline-flex;
62
+ align-items: center;
63
+ padding: var(--space-2xs) var(--space-sm);
64
+ border-radius: var(--radius);
65
+ font-size: var(--font-size-sm);
66
+ font-weight: 600;
67
+ text-decoration: none;
68
+ transition: opacity var(--transition-base, 150ms ease), transform var(--transition-base, 150ms ease);
69
+ }
70
+
71
+ .cta__btn:hover {
72
+ opacity: 0.85;
73
+ transform: translateY(-1px);
74
+ }
75
+
76
+ .cta__btn--primary {
77
+ background: var(--accent);
78
+ color: var(--accent-contrast);
79
+ }
80
+
81
+ .cta__btn--secondary {
82
+ background: color-mix(in srgb, var(--text) 10%, transparent);
83
+ color: var(--text);
84
+ border: 1px solid var(--border);
85
+ }
86
+ </style>
@@ -0,0 +1,104 @@
1
+ <script setup lang="ts">
2
+ import ToolCard from './ToolCard.vue'
3
+
4
+ const tools = [
5
+ {
6
+ icon: '🗄',
7
+ title: 'Walrus Storage',
8
+ description:
9
+ 'Decentralised, tamper-proof blob storage on Sui. Upload, manage, and extend the lifetime of blobs — no backend required.',
10
+ href: 'https://sui-walrus.meddleware.co.uk',
11
+ },
12
+ {
13
+ icon: '🔒',
14
+ title: 'Sealed Storage',
15
+ description:
16
+ 'End-to-end encrypted blobs with Sui-based access control. Only holders of the matching on-chain NFT can decrypt.',
17
+ href: 'https://sui-seal.meddleware.co.uk',
18
+ },
19
+ {
20
+ icon: '🔐',
21
+ title: 'Access Gate',
22
+ description:
23
+ 'NFT-based access control for any HTTP service. Gate uploads, APIs, and content behind a soulbound Sui NFT.',
24
+ href: 'https://sui-access-gate.meddleware.co.uk',
25
+ },
26
+ {
27
+ icon: '🪙',
28
+ title: 'Token Deployer',
29
+ description:
30
+ 'Deploy your own Sui coin fully client-side in under a minute. Your wallet signs, your keys — no intermediaries.',
31
+ href: 'https://sui-token-deployer.meddleware.co.uk',
32
+ },
33
+ {
34
+ icon: '🏛',
35
+ title: 'DAO Console',
36
+ description:
37
+ 'Govern your vault on-chain. Propose, vote, and execute DAO actions through a clean browser interface.',
38
+ href: 'https://sui-dao.meddleware.co.uk',
39
+ },
40
+ ] as const
41
+ </script>
42
+
43
+ <template>
44
+ <section class="features" aria-labelledby="features-heading">
45
+ <div class="features__inner">
46
+ <header class="features__header">
47
+ <h2 id="features-heading" class="features__title">What's in the dashboard</h2>
48
+ <p class="features__subtitle">
49
+ Five on-chain tools — all accessible via a single wallet connection at
50
+ <a href="https://dash.meddleware.co.uk">dash.meddleware.co.uk</a>,
51
+ or as standalone micro-apps on their own subdomains.
52
+ </p>
53
+ </header>
54
+ <ul class="features__grid" role="list">
55
+ <li v-for="tool in tools" :key="tool.title">
56
+ <ToolCard v-bind="tool" />
57
+ </li>
58
+ </ul>
59
+ </div>
60
+ </section>
61
+ </template>
62
+
63
+ <style scoped>
64
+ .features {
65
+ padding: var(--space-xl) var(--space-sm);
66
+ background: var(--surface);
67
+ border-top: 1px solid var(--border);
68
+ border-bottom: 1px solid var(--border);
69
+ }
70
+
71
+ .features__inner {
72
+ max-width: 56rem;
73
+ margin-inline: auto;
74
+ }
75
+
76
+ .features__header {
77
+ text-align: center;
78
+ margin-bottom: var(--space-lg);
79
+ }
80
+
81
+ .features__title {
82
+ margin: 0 0 var(--space-xs);
83
+ font-size: var(--font-size-xl);
84
+ font-weight: 700;
85
+ line-height: var(--leading-tight, 1.2);
86
+ color: var(--text);
87
+ }
88
+
89
+ .features__subtitle {
90
+ margin: 0;
91
+ font-size: var(--font-size-sm);
92
+ color: var(--muted);
93
+ line-height: var(--leading-relaxed, 1.6);
94
+ }
95
+
96
+ .features__grid {
97
+ list-style: none;
98
+ margin: 0;
99
+ padding: 0;
100
+ display: grid;
101
+ grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
102
+ gap: var(--space-xs);
103
+ }
104
+ </style>
@@ -0,0 +1,142 @@
1
+ <script setup lang="ts">
2
+ // No reactive state — static hero section.
3
+ </script>
4
+
5
+ <template>
6
+ <section class="hero" aria-labelledby="hero-headline">
7
+ <div class="hero__content">
8
+ <p class="hero__eyebrow" aria-hidden="true">◆ On-chain tooling</p>
9
+ <h1 id="hero-headline" class="hero__headline">
10
+ Tooling for the<br />decentralised web
11
+ </h1>
12
+ <p class="hero__subheading">
13
+ Protocol-agnostic infrastructure, developer tools, and on-chain primitives.
14
+ <br class="hero__br" />
15
+ Currently live on Sui.
16
+ </p>
17
+ <div class="hero__actions">
18
+ <a href="https://dash.meddleware.co.uk" class="hero__cta hero__cta--primary">
19
+ Open Dashboard
20
+ </a>
21
+ <a href="https://docs.meddleware.co.uk" class="hero__cta hero__cta--secondary"
22
+ target="_blank" rel="noopener noreferrer">
23
+ Documentation ↗
24
+ </a>
25
+ </div>
26
+ </div>
27
+ <div class="hero__glyph" aria-hidden="true">◆</div>
28
+ </section>
29
+ </template>
30
+
31
+ <style scoped>
32
+ .hero {
33
+ position: relative;
34
+ min-height: calc(100dvh - var(--mw-header-height, 56px));
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ padding: var(--space-xl) var(--space-sm);
39
+ overflow: hidden;
40
+ background:
41
+ radial-gradient(ellipse 80% 60% at 50% 0%, color-mix(in srgb, var(--accent) 12%, transparent) 0%, transparent 70%),
42
+ var(--bg);
43
+ }
44
+
45
+ .hero__content {
46
+ position: relative;
47
+ z-index: 1;
48
+ max-width: 44rem;
49
+ text-align: center;
50
+ }
51
+
52
+ .hero__eyebrow {
53
+ margin: 0 0 var(--space-xs);
54
+ font-size: var(--font-size-sm);
55
+ font-weight: 600;
56
+ letter-spacing: var(--tracking-wide);
57
+ text-transform: uppercase;
58
+ color: var(--accent);
59
+ opacity: 0.85;
60
+ }
61
+
62
+ .hero__headline {
63
+ margin: 0 0 var(--space-sm);
64
+ font-size: clamp(var(--font-size-2xl), 7vw, var(--font-size-3xl));
65
+ font-weight: 700;
66
+ line-height: var(--leading-tight, 1.2);
67
+ letter-spacing: var(--tracking-tight);
68
+ color: var(--text);
69
+ }
70
+
71
+ .hero__subheading {
72
+ margin: 0 0 var(--space-md);
73
+ font-size: var(--font-size-md);
74
+ line-height: var(--leading-relaxed, 1.6);
75
+ color: var(--muted);
76
+ }
77
+
78
+ .hero__br {
79
+ display: none;
80
+ }
81
+
82
+ @media (min-width: 600px) {
83
+ .hero__br {
84
+ display: inline;
85
+ }
86
+ }
87
+
88
+ .hero__actions {
89
+ display: flex;
90
+ flex-wrap: wrap;
91
+ align-items: center;
92
+ justify-content: center;
93
+ gap: var(--space-xs);
94
+ }
95
+
96
+ .hero__cta {
97
+ display: inline-flex;
98
+ align-items: center;
99
+ padding: var(--space-2xs) var(--space-sm);
100
+ border-radius: var(--radius);
101
+ font-size: var(--font-size-sm);
102
+ font-weight: 600;
103
+ text-decoration: none;
104
+ transition: opacity var(--transition-base, 150ms ease), transform var(--transition-base, 150ms ease);
105
+ }
106
+
107
+ .hero__cta:hover {
108
+ opacity: 0.85;
109
+ transform: translateY(-1px);
110
+ }
111
+
112
+ .hero__cta--primary {
113
+ background: var(--accent);
114
+ color: var(--accent-contrast);
115
+ }
116
+
117
+ .hero__cta--secondary {
118
+ background: color-mix(in srgb, var(--text) 10%, transparent);
119
+ color: var(--text);
120
+ border: 1px solid var(--border);
121
+ }
122
+
123
+ /* Large decorative glyph — hidden on small screens to avoid clutter. */
124
+ .hero__glyph {
125
+ position: absolute;
126
+ right: -0.15em;
127
+ bottom: -0.2em;
128
+ font-size: 40vw;
129
+ font-weight: 700;
130
+ color: var(--accent);
131
+ opacity: 0.03;
132
+ pointer-events: none;
133
+ user-select: none;
134
+ line-height: 1;
135
+ }
136
+
137
+ @media (max-width: 480px) {
138
+ .hero__glyph {
139
+ display: none;
140
+ }
141
+ }
142
+ </style>
@@ -0,0 +1,89 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * ToolCard — one feature / tool card in the FeaturesGrid.
4
+ *
5
+ * Renders as a `<UiCard>`-styled article with an emoji icon, title, description,
6
+ * and an optional link. The link is applied to the whole card surface when provided.
7
+ */
8
+ defineProps<{
9
+ /** Emoji or single character used as the visual icon. Rendered `aria-hidden`. */
10
+ icon: string
11
+ /** Tool name — rendered as the card heading. */
12
+ title: string
13
+ /** One or two sentences describing the tool. */
14
+ description: string
15
+ /** Optional link URL. When set, the card title becomes an anchor. */
16
+ href?: string
17
+ }>()
18
+ </script>
19
+
20
+ <template>
21
+ <article class="tool-card">
22
+ <div class="tool-card__icon" aria-hidden="true">{{ icon }}</div>
23
+ <div class="tool-card__body">
24
+ <h3 class="tool-card__title">
25
+ <a v-if="href" :href="href" class="tool-card__link" target="_blank" rel="noopener noreferrer">
26
+ {{ title }} <span aria-hidden="true">↗</span>
27
+ </a>
28
+ <template v-else>{{ title }}</template>
29
+ </h3>
30
+ <p class="tool-card__description">{{ description }}</p>
31
+ </div>
32
+ </article>
33
+ </template>
34
+
35
+ <style scoped>
36
+ .tool-card {
37
+ display: flex;
38
+ gap: var(--space-xs);
39
+ padding: var(--space-sm);
40
+ border-radius: var(--radius);
41
+ border: 1px solid var(--border);
42
+ background: var(--surface);
43
+ transition: border-color var(--transition-base, 150ms ease);
44
+ }
45
+
46
+ .tool-card:has(.tool-card__link:hover) {
47
+ border-color: color-mix(in srgb, var(--accent) 50%, var(--border));
48
+ }
49
+
50
+ .tool-card__icon {
51
+ flex-shrink: 0;
52
+ font-size: var(--font-size-lg);
53
+ line-height: 1;
54
+ padding-top: 0.1em;
55
+ }
56
+
57
+ .tool-card__body {
58
+ min-width: 0;
59
+ }
60
+
61
+ .tool-card__title {
62
+ margin: 0 0 var(--space-3xs);
63
+ font-size: var(--font-size-sm);
64
+ font-weight: 650;
65
+ color: var(--text);
66
+ }
67
+
68
+ .tool-card__link {
69
+ color: inherit;
70
+ text-decoration: none;
71
+ }
72
+
73
+ .tool-card__link:hover {
74
+ color: var(--accent);
75
+ }
76
+
77
+ .tool-card__link:focus-visible {
78
+ outline: 2px solid var(--focus-ring);
79
+ outline-offset: 2px;
80
+ border-radius: 2px;
81
+ }
82
+
83
+ .tool-card__description {
84
+ margin: 0;
85
+ font-size: var(--font-size-sm);
86
+ line-height: var(--leading-relaxed, 1.6);
87
+ color: var(--muted);
88
+ }
89
+ </style>
package/src/env.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.vue' {
4
+ import type { DefineComponent } from 'vue'
5
+ const component: DefineComponent<Record<string, unknown>, Record<string, unknown>, unknown>
6
+ export default component
7
+ }
package/src/main.ts ADDED
@@ -0,0 +1,15 @@
1
+ import '@meddleware/design-tokens/tokens.css'
2
+ import '@meddleware/ui/base.css'
3
+ import './styles/main.css'
4
+
5
+ import { createApp } from 'vue'
6
+ import { createRouter, createWebHistory } from 'vue-router'
7
+ import App from './App.vue'
8
+ import HomeView from './views/HomeView.vue'
9
+
10
+ const router = createRouter({
11
+ history: createWebHistory(),
12
+ routes: [{ path: '/', component: HomeView }],
13
+ })
14
+
15
+ createApp(App).use(router).mount('#app')
@@ -0,0 +1,31 @@
1
+ /* Landing-page layout resets and base styles. */
2
+
3
+ *,
4
+ *::before,
5
+ *::after {
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html {
10
+ scroll-behavior: smooth;
11
+ }
12
+
13
+ body {
14
+ margin: 0;
15
+ background: var(--bg);
16
+ color: var(--text);
17
+ font-family: var(--mw-font-sans, system-ui, sans-serif);
18
+ line-height: var(--leading-normal, 1.5);
19
+ -webkit-font-smoothing: antialiased;
20
+ }
21
+
22
+ a {
23
+ color: var(--accent);
24
+ text-underline-offset: 0.2em;
25
+ }
26
+
27
+ img,
28
+ svg {
29
+ display: block;
30
+ max-width: 100%;
31
+ }
@@ -0,0 +1,11 @@
1
+ <script setup lang="ts">
2
+ import HeroSection from '../components/HeroSection.vue'
3
+ import FeaturesGrid from '../components/FeaturesGrid.vue'
4
+ import CtaSection from '../components/CtaSection.vue'
5
+ </script>
6
+
7
+ <template>
8
+ <HeroSection />
9
+ <FeaturesGrid />
10
+ <CtaSection />
11
+ </template>
package/tsconfig.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "Bundler",
6
+ "lib": ["ESNext", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "verbatimModuleSyntax": true,
13
+ "jsx": "preserve",
14
+ "types": ["node"],
15
+ "noEmit": true
16
+ },
17
+ "include": ["src/**/*.ts", "src/**/*.vue", "src/**/*.d.ts", "vite.config.ts"]
18
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { defineConfig } from 'vite'
2
+ import vue from '@vitejs/plugin-vue'
3
+
4
+ export default defineConfig({
5
+ base: '/',
6
+ plugins: [vue()],
7
+ build: {
8
+ target: 'esnext',
9
+ },
10
+ })