diff --git a/lang/en.json b/lang/en.json
index e69de29..bb81437 100644
--- a/lang/en.json
+++ b/lang/en.json
@@ -0,0 +1,7 @@
+{
+ "mb-cypher2": {
+ "embed": {
+ "noPermission": "Unauthorized Actor Embed"
+ }
+ }
+}
diff --git a/scripts/mb-cypher2.js b/scripts/mb-cypher2.js
index e69de29..3992715 100644
--- a/scripts/mb-cypher2.js
+++ b/scripts/mb-cypher2.js
@@ -0,0 +1,82 @@
+import {
+ preloadTemplatePaths,
+ normalizeTemplatePaths,
+ MODULE_ID,
+ log,
+} from "./module.js";
+
+const TEMPLATE_FILES = ["actor.hbs", "actor-inline.hbs"];
+const TEMPLATE_BASE = "embeds";
+const TEMPLATES = normalizeTemplatePaths(TEMPLATE_FILES, TEMPLATE_BASE);
+
+async function buildActorEmbedHTML(doc, content, config, options) {
+ // Types: community, pc, marker, npc, vehicle
+ // LIMITED access can see Name and Description
+ const noPermission = !doc.testUserPermission(
+ game.user,
+ CONST.DOCUMENT_OWNERSHIP_LEVELS.LIMITED,
+ );
+ if (noPermission) {
+ const broken = document.createElement("span");
+ broken.classList.add("broken", "content-embed");
+ const i = document.createElement("i");
+ i.classList.add("fa-solid", "fa-circle-exclamation");
+ i.inert = true;
+ broken.append(
+ i,
+ _loc(`${MODULE_ID}.embed.noPermission`, { uuid: doc.uuid }),
+ );
+ return broken;
+ }
+ let icon = "";
+ switch (doc.type) {
+ case "pc":
+ icon = "fa-solid fa-user";
+ break;
+ case "npc":
+ icon = "fa-solid fa-user-secret";
+ break;
+ case "community":
+ icon = "fa-solid fa-tree-city";
+ break;
+ case "marker":
+ icon = "fa-solid fa-thumbtack";
+ break;
+ case "vehicle":
+ icon = "fa-solid fa-car-side";
+ break;
+ default:
+ icon = "fa-solid fa-person";
+ }
+ const context = {
+ config: { ...config },
+ actor: doc,
+ basic: doc.system.basic,
+ pools: doc.system.pools,
+ options,
+ viewFullDocument: doc.testUserPermission(
+ game.user,
+ CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
+ ),
+ icon,
+ user: game.user,
+ };
+ const template = config.inline
+ ? TEMPLATES["actor-inline.hbs"]
+ : TEMPLATES["actor.hbs"];
+ const el = document.createElement(config.inline ? "span" : "div");
+ el.classList.add(`${MODULE_ID}-embed`, "content-embed");
+ let innerHTML = await renderTemplate(template, context);
+ el.innerHTML = innerHTML;
+ return el;
+}
+
+function initializeEmbeds() {
+ CONFIG.Actor.embedHandlers.push(buildActorEmbedHTML);
+}
+
+Hooks.once("init", async () => {
+ log(`Initializing ${MODULE_ID}`);
+ initializeEmbeds();
+ preloadTemplatePaths(TEMPLATES);
+});
diff --git a/scripts/module.js b/scripts/module.js
new file mode 100644
index 0000000..60cec2c
--- /dev/null
+++ b/scripts/module.js
@@ -0,0 +1,19 @@
+export const MODULE_ID = "mb-cypher2";
+
+export function log(...args) {
+ console.log(`${MODULE_ID} |`, ...args);
+}
+
+export function normalizeTemplatePaths(paths, baseDir = "") {
+ const templates = {};
+ log("finding templates", paths, baseDir);
+ const basePath = `modules/${MODULE_ID}/templates${baseDir ? "/" + baseDir : ""}`;
+ for (const file of paths) {
+ templates[file] = `${basePath}/${file}`;
+ }
+ return templates;
+}
+
+export async function preloadTemplatePaths(normalizedPaths) {
+ return loadTemplates(Object.values(normalizedPaths));
+}
diff --git a/styles/mb-cypher2.css b/styles/mb-cypher2.css
index e69de29..a354c1f 100644
--- a/styles/mb-cypher2.css
+++ b/styles/mb-cypher2.css
@@ -0,0 +1,29 @@
+:root {
+ --mbcypher2-journal-embed-background: #ccccaa;
+ --mbcypher2-journal-embed-border-color: #aaaa88;
+ --mbcypher2-journal-embed-font-color: #000000;
+}
+
+body.theme-dark {
+ --mbcypher2-journal-embed-background: #333300;
+ --mbcypher2-journal-embed-border-color: #666633;
+ --mbcypher2-journal-embed-font-color: #eeeecc;
+}
+
+.mb-cypher2-embed.content-embed {
+ background: var(--mbcypher2-journal-embed-background);
+ border: 1px solid var(--mbcypher2-journal-embed-border-color);
+ border-radius: 2px;
+}
+
+span.mb-cypher2-embed.content-embed {
+ margin: -1px 0;
+ padding: 1px 4px 0;
+ white-space: nowrap;
+ word-break: break-all;
+}
+
+figure.content-embed figcaption cite {
+ margin-left: 0.5em;
+}
+}
diff --git a/templates/embeds/actor-inline.hbs b/templates/embeds/actor-inline.hbs
new file mode 100644
index 0000000..57f9ef8
--- /dev/null
+++ b/templates/embeds/actor-inline.hbs
@@ -0,0 +1,32 @@
+
+{{actor.name}}
+{{#if (eq actor.type "pc") }}
+ {{ localize "CYPHERSYSTEM.IsA" }} {{ basic.descriptor }} {{ basic.type }}
+ {{ localize "CYPHERSYSTEM.Who" }} {{ basic.focus }} {{ basic.additionalSentence }}
+{{/if}}
+{{#if viewFullDocument}}
+ {{#if basic.rank}}
+ {{ localize "CYPHERSYSTEM.Rank" }} {{ basic.rank }}
+ {{/if}}
+ {{#if basic.level}}
+ {{ localize "CYPHERSYSTEM.Level" }} {{ basic.level }}
+ {{/if}}
+ {{#if basic.tier}}
+ {{ localize "CYPHERSYSTEM.Tier" }} {{ basic.tier }}
+ {{/if}}
+ {{#if pools.health}}
+ {{ localize "CYPHERSYSTEM.Health" }} {{ pools.health.value }}/{{ pools.health.max}}
+ {{/if}}
+ {{#if pools.infrastructure}}
+ {{ localize "CYPHERSYSTEM.Infrastructure" }} {{ pools.infrastructure.value }}/{{ pools.infrastructure.max}}
+ {{/if}}
+ {{#if pools.might}}
+ {{ localize "CYPHERSYSTEM.Might" }} {{ pools.might.value }}/{{ pools.might.max}}
+ {{/if}}
+ {{#if pools.speed}}
+ {{ localize "CYPHERSYSTEM.Speed" }} {{ pools.speed.value }}/{{ pools.speed.max}}
+ {{/if}}
+ {{#if pools.intellect}}
+ {{ localize "CYPHERSYSTEM.Intellect" }} {{ pools.intellect.value }}/{{ pools.intellect.max}}
+ {{/if}}
+{{/if}}
diff --git a/templates/embeds/actor.hbs b/templates/embeds/actor.hbs
new file mode 100644
index 0000000..e69de29