From f760298ca97a2d22981fe0d686e6c68ef1f2afb5 Mon Sep 17 00:00:00 2001 From: Youssef Date: Fri, 24 Jul 2026 14:33:25 +0100 Subject: [PATCH] Image hook (Together) + canonical examples for the 4 new themes - scripts/imagegen.mjs: opt-in generated-still hook via Together's images endpoint (FLUX.1.1-pro default, gemini-3-pro-image optional), cache by prompt hash, provenance sidecar. No key -> graceful exit 3, falls back to CSS-art / placeholder (verified). Wired into tier-e-generated.md + SKILL Step 4 as opt-in, never auto-fires. - site/examples/{grid,field,ledger,arcade}-01: canonical builds from the proposal mockups, all sloplint 0-FAIL, referenced by each theme spec. --- site/examples/arcade-01/index.html | 438 ++++++++++++++++ site/examples/field-01/index.html | 492 ++++++++++++++++++ site/examples/grid-01/index.html | 469 +++++++++++++++++ site/examples/ledger-01/index.html | 450 ++++++++++++++++ skills/hallmark/SKILL.md | 2 +- .../references/craft/tier-e-generated.md | 10 + skills/hallmark/scripts/imagegen.mjs | 100 ++++ 7 files changed, 1960 insertions(+), 1 deletion(-) create mode 100644 site/examples/arcade-01/index.html create mode 100644 site/examples/field-01/index.html create mode 100644 site/examples/grid-01/index.html create mode 100644 site/examples/ledger-01/index.html create mode 100644 skills/hallmark/scripts/imagegen.mjs diff --git a/site/examples/arcade-01/index.html b/site/examples/arcade-01/index.html new file mode 100644 index 0000000..db331ed --- /dev/null +++ b/site/examples/arcade-01/index.html @@ -0,0 +1,438 @@ + + + + + +Summer Jam 26 · a 48-hour game jam for people with day jobs + + + + + + + + + +
+
+ FREE PLAY + + EVENT 07 + + SELECT / 1 CREDIT +
+
+ +
+ +
+
+

NOW ACCEPTING PLAYERS

+

+ SUMMER + JAM 26 +

+

A 48-hour game jam for people with day jobs. One theme drops Friday night, teams of any size ship by Sunday, and Monday you go back to your inbox like nothing happened.

+ +
+
+ +
+
+

HOW THE WEEKEND RUNS

+

Three moves, one weekend

+

No pre-built engines, no head start. Everybody opens the same blank screen at the same minute and figures it out from there.

+
    +
  1. + 01 +

    Theme drops

    +

    Friday at 6pm the theme goes live. You find out what you are making at the exact moment everyone else does.

    +
  2. +
  3. + 02 +

    Build all weekend

    +

    48 hours to make something playable. Sleep is optional, snacks are not, and the help pit stays open the whole time.

    +
  4. +
  5. + 03 +

    Ship and play

    +

    Sunday at 6pm hands come off the keyboard. Every team demos, everyone plays everyone, and the room picks its favorites.

    +
  6. +
+
+
+ +
+
+

RUN OF SHOW

+

The 48-hour clock

+
+ + + + + + + + + + + + +
Summer Jam 26 weekend schedule
WhenSlotWhat happens
FRI 18:00KICKOFFTheme reveal, the ground rules, and the starting buzzer.
FRI 19:30TEAM UPFind collaborators or commit to flying solo. Any size counts.
SAT ALL DAYBUILDHeads down. Mentors roam the floor, coffee keeps flowing.
SUN 15:00SUBMITTools down, upload the build, and write a readme humans can follow.
SUN 18:00OPEN PLAYThe arcade opens. Everyone plays everything, everyone votes.
+
+
+
+ +
+
+

PICK YOUR TRACK

+

Four ways to enter

+
+
+ SOLO RUN +

One person, one build, start to finish. Prove you do not need a team to make something worth playing.

+
+
+ COUCH CO-OP +

Two to five people sharing a table, a whiteboard, and a deadline. The classic weekend crew.

+
+
+ THEME TAKEOVER +

Lean the hardest into the theme. Judged purely on how far you push the prompt. Weird is rewarded.

+
+
+ ROOKIE CABINET +

First jam ever? This track is scored on its own curve so a debut never competes against a veteran.

+
+
+ +
+

ON THE MARQUEE

+

What you are playing for

+
+
+ 1UP +

Best in show

+

The build the whole room crowds around. Overall pick of the weekend.

+
+
+ 2UP +

Crowd favorite

+

Chosen by every player in the arcade, not by a panel behind a curtain.

+
+
+ 3UP +

Tech flex

+

For the clever hack under the hood that nobody in the room saw coming.

+
+
+
+
+
+ +
+
+

INSERT COIN

+

Ready
player 1?

+

Slots are free and the theme stays secret until the buzzer. Grab yours, wrangle a team or do not, and clear one weekend on the calendar.

+ Register your team +

48 HOURS · ANY TEAM SIZE · ONE THEME · FREE TO ENTER

+
+
+ +
+ + + + + diff --git a/site/examples/field-01/index.html b/site/examples/field-01/index.html new file mode 100644 index 0000000..df83d44 --- /dev/null +++ b/site/examples/field-01/index.html @@ -0,0 +1,492 @@ + + + + + +Fieldnote - meetings that end with a plan + + + + + + + + + + +
+ + +
+
+
+

Meetings that end with a plan.

+

Fieldnote listens to the conversation and turns the talk into owners, dates, and next steps, so the plan is written down before everyone stands up.

+
+ + See how it works + + +
+
+ +
+ + + + +
+
+ Monday standup + 9:02 +
+
    +
  • + + Owner + Priya M. +
  • +
  • + + Due + Thursday +
  • +
  • + + Decision logged + Ship the beta +
  • +
+
+
+
+
+ + +
+
+
+

How it works

+

From talk to plan, in three moves.

+

No bot in the meeting asking people to repeat themselves. Fieldnote stays quiet and does the writing.

+
+ +
+ + +
+ 01 + +

It listens

+

Add Fieldnote to the call. It follows the conversation and keeps its head down while people talk.

+
+ +
+ 02 + +

It sorts

+

Every commitment becomes an owner, a date, and a decision, written the moment it is said out loud.

+
+ +
+ 03 + +

It hands off

+

The plan lands in your inbox and your task tools before the room clears. Nobody re-types the notes.

+
+
+
+
+ + +
+
+
+

What you get

+

A record the room can trust.

+
+ +
+ +
+
+ Owners, not action items +

Every next step has a name on it.

+

A task with no owner is a task nobody does. Fieldnote assigns each next step to the person who spoke up for it, and asks in the room if it is not obvious. You leave with a list of people, not a pile of verbs.

+
+
+
+ +
+
+
+ +
+
+ Dates it can defend +

Due dates tied to what was actually said.

+

When someone says "by end of week," Fieldnote writes the date, not the phrase, and links it back to the sentence it came from. If a date was never agreed, it says so plainly instead of inventing one.

+
+
+
+ +
+
+
+ +
+
+ Decisions, kept +

The moment a call was made, saved for later.

+

Two weeks on, when someone asks why you went with the smaller launch, the answer is one search away. Fieldnote keeps the decision and the reasoning next to it, so the plan holds up when memory does not.

+
+
+
+ +
+
+
+ +
+
+
+ + +
+
+

Try it on one meeting

+

Bring your next standup. We will write the plan.

+

Fieldnote is new, and still small. Start free, point it at one real meeting, and tell us where it gets things wrong. We are a few people who read every note that comes back.

+
+ + No card. One workspace. Leave whenever you like. +
+
+
+ +
+ + + + + diff --git a/site/examples/grid-01/index.html b/site/examples/grid-01/index.html new file mode 100644 index 0000000..263fae0 --- /dev/null +++ b/site/examples/grid-01/index.html @@ -0,0 +1,469 @@ + + + + + +Büro Grid · Identity and signage systems + + + + + + +
+ + + + +
+
+ Büro Grid + Work + Method + Contact +
+ DE + + EN +
+
+ +
+
+

Identity & signage systems

+

schedule,
then ship

+
+

Büro Grid builds identity, wayfinding, and editorial systems for institutions that plan in decades. We draw the grid first, so every decision after it has somewhere to stand.

+

Basel, since 2009
Working in DE / EN

+
+
+ +
+
+

01 · Practice

+

Structure before surface

+
+
+
+

A

+

Practice

+

Identity systems, signage programmes, and editorial grids. Each project begins as a set of rules, never as a single picture.

+
+
+

B

+

Method

+

Fixed modules, one measure, a single accent. The system has to survive twenty years of new hands, new content, and new signage.

+
+
+

C

+

Standort

+

Based in Basel, working across the institutional landscape in German and English. On site whenever the building itself needs us.

+
+
+
+ +
+
+

02 · Selected work

+

Index

+
+
    +
  1. + + 01 + Kunsthalle Basel + Wayfinding system · 2024 + + +
  2. +
  3. + + 02 + Rheinhafen Authority + Identity & signage · 2023 + + +
  4. +
  5. + + 03 + Universität Basel Press + Editorial grid · 2023 + + +
  6. +
  7. + + 04 + Kantonsbibliothek + Identity system · 2022 + + +
  8. +
  9. + + 05 + Tramnetz Nord + Platform signage · 2021 + + +
  10. +
  11. + + 06 + Museum für Gestaltung + Type & grid manual · 2020 + + +
  12. +
+
+ +
+
+

Position

+
+
+

A grid is not decoration. It is a decision made once, in public, so that every decision after it can be smaller, calmer, and easier to defend.

+
+
+ +
+
+ +

Contact

+

Start something that outlasts its brief

+
+
+ +
+

Steinenvorstadt 12
4051 Basel, Switzerland

+

New commissions for 2026 open from September.

+
+
+
+
+ +
+
+

Büro Grid

+

Identity systems and signage, Basel.

+
+
+

studio@burogrid.example

+

+41 61 000 00 00

+
+
+

Steinenvorstadt 12
4051 Basel

+
+
+

DE / EN

+

© 2026 Büro Grid

+
+
+
+
+ + diff --git a/site/examples/ledger-01/index.html b/site/examples/ledger-01/index.html new file mode 100644 index 0000000..1dbf7d6 --- /dev/null +++ b/site/examples/ledger-01/index.html @@ -0,0 +1,450 @@ + + + + + +Meridian · Treasury, reconciled while you sleep + + + + + + + + + + + +
+ + +
+
+
+

Meridian · Treasury OS

+

Treasury, reconciled while you sleep.

+

Meridian pulls every bank feed, card network, and ledger entry into a single balance that stays reconciled around the clock. You wake up to a closed book, not a queue of exceptions.

+ +

SOC 2 Type II · dual-control transfers · immutable audit log

+
+ +
+
+
+ OPERATING · USD · ****4471 + LIVE +
+
+

+ $4,982,114.09 +

+

Reconciled balance · as of 06:00 UTC · +$18,204.55 today

+ +
+ +
30-DAY RECONCILED BALANCEUTC
+
+ +
+ + + + + + + + + + + + + +
Cleared transactions · live feed
TimeDescriptionAmount
06:00:07ACH credit · customer settlement matched+$182,004.12
05:58:22Wire out · monthly payroll matched-$96,540.00
05:41:09Card network settlement matched-$12,878.45
05:12:50Interest · overnight sweep matched+$1,204.77
04:47:31ACH credit · marketplace payout matched+$50,000.00
03:20:16FX settlement · EUR/USD matched+$8,912.30
+
+
+
+
+
+
+ + +
+
+
+

How it works

+

Three steps, run continuously.

+

No overnight batch, no month-end scramble. Meridian keeps the loop closed every minute of the day.

+
+
+
+

01 / INGEST

+

Feeds sync every 60 seconds

+

Bank connections, card networks, and your general ledger stream in over signed APIs. Nothing is keyed by hand; nothing waits for a nightly file.

+
+
+

02 / MATCH

+

Deterministic rules pair entries

+

Each transaction is matched to its expected ledger entry by amount, date, and counterparty. Anything ambiguous drops into a review queue instead of a silent guess.

+
+
+

03 / CLOSE

+

Balances lock at 06:00 UTC

+

The book closes on schedule with every match carrying an immutable reference. You can audit any figure back to the transaction that moved it.

+
+
+
+
+ + +
+
+
+

Capabilities

+

A datasheet, not a pitch.

+

The specification your controller will actually read. Every line is a default you can change, not a feature you have to buy.

+
+
+
+
Reconciliation cadence
Continuous · 60s sync
+
Match engine
Deterministic + rule overrides
+
Entities per workspace
Unlimited books, one console
+
Close schedule
06:00 UTC lock, configurable
+
Ledger export
CSV · JSON · GAAP-mapped
+
Review queue
Exceptions only, per rule
+
Outbound transfers
Dual-control approval
+
Audit references
Immutable, per line
+
Feed connectors
Bank · card · ledger APIs
+
Access model
SSO / SAML · scoped keys
+
Data residency
US · EU regions
+
Retention
7-year append-only log
+
+
+
+
+ + +
+
+
+

Trust

+

Built for the people who sign off.

+

Treasury software fails closed. These are the controls that let your auditors, your board, and your bank take Meridian at its word.

+
+
+
+

Encryption

+

AES-256 at rest, TLS 1.3 in transit. Keys are managed in a hardware security module and rotated on a fixed schedule. No customer data leaves an encrypted boundary.

+
+
+

Movement

+

Dual-control on every outbound transfer. Initiation and approval are separated by role, and no single credential can move funds on its own.

+
+
+

Audit

+

Append-only ledger with per-line references. Every match, override, and export is written to an immutable log that reconstructs any balance on demand.

+
+
+

Access

+

SSO, SAML, and SCIM provisioning. Scoped API keys, granular roles, and session policies keep least-privilege the default rather than the exception.

+
+
+
+ SOC 2 Type II + AES-256 + TLS 1.3 + SSO / SAML + SCIM + US / EU residency +
+
+
+ + +
+
+
+

Get started

+

Open an account and close tonight.

+

Connect your first feed in an afternoon. By the next lock at 06:00 UTC, your balance is reconciled and your close is done.

+ +

No card to start · your data stays in your region

+
+
+
+ +
+ + + + + diff --git a/skills/hallmark/SKILL.md b/skills/hallmark/SKILL.md index 20be2d7..21874db 100644 --- a/skills/hallmark/SKILL.md +++ b/skills/hallmark/SKILL.md @@ -244,7 +244,7 @@ A custom system is complete (palette + pairing + axes), never a colour swap; its Then run the image-need table at [`references/hero-enrichment.md` § Image-need detection](references/hero-enrichment.md). Default is typography-only. Photographic briefs without user assets use the placeholder strategy in [`references/assets.md`](references/assets.md); non-photographic briefs prefer [`references/imagery-kit.md`](references/imagery-kit.md). Never ship invented stock photos as final design. -**The enrichment hierarchy is non-negotiable:** typography only → pure CSS art → hand-built SVG → generated still → customised library asset → Lottie, last resort. Reaching for Lottie when CSS would build it is the new tell. (Construction guidance lives in `references/craft/`; its files are lettered by construction method - css, svg, animation, webgl, generated, lottie - so match by name.) State the decision in one sentence; it goes into the stamp. +**The enrichment hierarchy is non-negotiable:** typography only → pure CSS art → hand-built SVG → generated still → customised library asset → Lottie, last resort. Reaching for Lottie when CSS would build it is the new tell. (Construction guidance lives in `references/craft/`; its files are lettered by construction method - css, svg, animation, webgl, generated, lottie - so match by name.) The **generated-still** rung has an opt-in callable hook, `scripts/imagegen.mjs` (Together images, cache-by-hash), used only when the user asks for real imagery and `TOGETHER_API_KEY` is set; with no key it falls back to CSS-art / placeholder (see [`references/craft/tier-e-generated.md`](references/craft/tier-e-generated.md)). State the decision in one sentence; it goes into the stamp. ### 5. Preview diff --git a/skills/hallmark/references/craft/tier-e-generated.md b/skills/hallmark/references/craft/tier-e-generated.md index 3695418..a14ac6b 100644 --- a/skills/hallmark/references/craft/tier-e-generated.md +++ b/skills/hallmark/references/craft/tier-e-generated.md @@ -9,6 +9,16 @@ When characters or specific scenes are needed and hand-build is uneconomical (th | **[Midjourney v8](https://www.midjourney.com)** | ~$0.14 / image | Aesthetic beauty, artistic direction, atmospheric stills | PNG | | **[Flux 2](https://blackforestlabs.ai/)** | ~$0.03 / image | Photorealism, skin / fabric / product detail | PNG | +### The callable hook (opt-in) + +When `TOGETHER_API_KEY` is set and the user has asked for a generated image, you can fetch one instead of recommending a manual round-trip: + +``` +node /scripts/imagegen.mjs "" --size 1536x1024 [--model ...] [--out ] +``` + +Default model `black-forest-labs/FLUX.1.1-pro` (photoreal, ~$0.04/MP); `--model google/gemini-3-pro-image` for legible in-image text and brand consistency (carries a SynthID watermark). It caches by prompt hash under `.hallmark/img-cache/` and writes a provenance sidecar. **With no key it exits gracefully (code 3) and you fall back to CSS-art or a labelled placeholder** - the recommend-only behaviour below. Never auto-fire it; it serves image-heavy briefs on request, and every generated asset is still post-processed and provenance-stamped per the discipline below. + ### Discipline when generating - **Always post-process.** Add grain, asymmetric crop, hand-drawn overlays, colour grading. Raw model output reads as AI 100 % of the time. The post-process is what makes it ours. diff --git a/skills/hallmark/scripts/imagegen.mjs b/skills/hallmark/scripts/imagegen.mjs new file mode 100644 index 0000000..5bfdf99 --- /dev/null +++ b/skills/hallmark/scripts/imagegen.mjs @@ -0,0 +1,100 @@ +#!/usr/bin/env node +// Hallmark image hook. Generates a photographic / illustrative asset for an +// image-heavy brief via Together's images endpoint, caches by prompt hash, and +// stamps provenance. Zero dependencies (Node 18+ global fetch). OPT-IN: it only +// runs when the user asks for a generated image AND TOGETHER_API_KEY is set; +// with no key it prints a graceful fallback message and exits non-zero so the +// build falls back to CSS-art / placeholder (today's recommend-only behaviour). +// +// node imagegen.mjs "" [--model ] [--size 1024x1024] [--out ] +// [--n 1] [--steps 4] [--force] [--json] +// +// Default model: black-forest-labs/FLUX.1.1-pro (photoreal, ~$0.04/MP). +// Alternatives: --model google/gemini-3-pro-image (legible in-image text, brand +// consistency; carries a SynthID watermark), black-forest-labs/FLUX.1-dev. +// +// Cache: .hallmark/img-cache/.png (+ .json +// provenance). A cache hit re-uses the file and never calls the API. + +import { createHash } from "node:crypto"; +import { mkdirSync, existsSync, writeFileSync, readFileSync } from "node:fs"; +import { join } from "node:path"; + +const ENDPOINT = "https://api.together.xyz/v1/images/generations"; +const DEFAULT_MODEL = "black-forest-labs/FLUX.1.1-pro"; +const CACHE_DIR = join(process.cwd(), ".hallmark", "img-cache"); + +function parseArgs(argv) { + const a = { _: [] }; + for (let i = 0; i < argv.length; i++) { + const t = argv[i]; + if (t.startsWith("--")) { + const k = t.slice(2); + if (k === "force" || k === "json") a[k] = true; + else a[k] = argv[++i]; + } else a._.push(t); + } + return a; +} + +function out(obj, code = 0) { + if (args.json) process.stdout.write(JSON.stringify(obj) + "\n"); + else if (obj.path) console.log(obj.path); + else if (obj.message) console.error(obj.message); + process.exit(code); +} + +const args = parseArgs(process.argv.slice(2)); +const prompt = args._.join(" ").trim(); +if (!prompt) out({ error: 'usage: node imagegen.mjs "" [--model ...] [--size WxH] [--out path]' }, 2); + +const model = args.model ?? DEFAULT_MODEL; +const size = args.size ?? "1024x1024"; +const [w, h] = size.split("x").map((n) => parseInt(n, 10)); +if (!w || !h) out({ error: `bad --size ${size} (want WxH, e.g. 1536x1024)` }, 2); +const steps = parseInt(args.steps ?? "4", 10); + +const hash = createHash("sha256").update([model, prompt, size, steps].join("|")).digest("hex").slice(0, 16); +const cachePath = args.out ?? join(CACHE_DIR, `${hash}.png`); +const metaPath = cachePath.replace(/\.png$/, "") + ".json"; + +// --- cache hit ------------------------------------------------------------- +if (!args.force && existsSync(cachePath)) { + out({ path: cachePath, cached: true, model, prompt }); +} + +// --- key check: graceful fallback (this is the opt-in gate) ---------------- +const key = process.env.TOGETHER_API_KEY; +if (!key) { + out({ + error: "no-key", + message: + "TOGETHER_API_KEY not set. Image generation is opt-in; falling back to CSS-art / a labelled placeholder. " + + "Set TOGETHER_API_KEY to generate real imagery, or keep the typography-only / placeholder path.", + }, 3); +} + +// --- generate -------------------------------------------------------------- +try { + const res = await fetch(ENDPOINT, { + method: "POST", + headers: { "content-type": "application/json", authorization: `Bearer ${key}` }, + body: JSON.stringify({ model, prompt, width: w, height: h, steps, n: parseInt(args.n ?? "1", 10), response_format: "b64_json" }), + }); + if (!res.ok) { + const detail = (await res.text()).slice(0, 300); + out({ error: `together ${res.status}`, message: `image API ${res.status}: ${detail}` }, 1); + } + const data = await res.json(); + const b64 = data?.data?.[0]?.b64_json; + if (!b64) out({ error: "no-image", message: "image API returned no b64_json payload" }, 1); + + mkdirSync(CACHE_DIR, { recursive: true }); + if (args.out) mkdirSync(join(args.out, ".."), { recursive: true }); + writeFileSync(cachePath, Buffer.from(b64, "base64")); + const provenance = { model, prompt, size, steps, hash, generatedVia: "together", note: "generated asset; post-process before shipping; verify licensing", at: new Date().toISOString().slice(0, 10) }; + writeFileSync(metaPath, JSON.stringify(provenance, null, 2) + "\n"); + out({ path: cachePath, cached: false, model, prompt, provenance }); +} catch (e) { + out({ error: "fetch-failed", message: `image generation failed: ${String(e?.message ?? e)}. Falling back to placeholder.` }, 1); +}