Update callista-odoo-demo skill (local edits: README, SKILL, field gotchas, templates)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DywdYrFTrWa4h1T3ZykPBX
This commit is contained in:
glenn
2026-09-21 14:30:07 +02:00
co-authored by Claude Opus 5
parent 9876f6ba10
commit 2fd559f1cc
8 changed files with 203 additions and 127 deletions
+6 -1
View File
@@ -26,9 +26,14 @@ follow-through framing. They do not restate the pipeline. See
## Install
```bash
git clone <this repo> ~/.claude/skills/callista-odoo-demo
git clone <this repo> ~/dev/callista-odoo-demo
ln -s ~/dev/callista-odoo-demo ~/.claude/skills/callista-odoo-demo
```
**Symlink it; do not clone straight into `~/.claude/skills/`.** A second working copy is a
second source of truth: you edit the skill in one, Claude loads the other, and the drift is
invisible until a build follows instructions you thought you had deleted.
Then ask Claude: *"build an Odoo demo for &lt;client&gt; from these notes."*
**Do not** install this as `odoo-demo-architect` — skills resolve by directory name, and a
+29 -15
View File
@@ -42,7 +42,7 @@ question whose answer is sitting in the file you just read.
| **Source brief location** | A single obvious candidate file in the working directory | Zero or several candidates — ask which, or paste |
| **Odoo UI language** | Language the brief is written in; client country; `l10n_*` in scope | Brief is English but the client clearly isn't anglophone — ask (nl_BE / fr_BE / en_US / other) |
| **Client website / company info** | URL or company name in the brief — fetch the site, mine it for what they build or sell, size, regions, branding | No name or URL anywhere in the source |
| **Odoo instance** | `demo_config.json`, prior session, the brief | Not found — always ask, never guess a URL or API key |
| **Odoo instance** | `scripts/creds.json` or `ODOO_URL`/`ODOO_KEY` in the environment, prior session, the brief | Not found — always ask, never guess a URL or API key |
| **Demo audience** | Attendee list in a transcript header or email thread | Source is notes with no names |
| **Rate card** | A rates file in the project | Only needed at Phase C — don't ask now |
@@ -182,11 +182,14 @@ button calls — not through `write` on `state` or any other status field:
| Do | Never |
|---|---|
| `x("sale.order", "action_confirm", [ids])` | `write(ids, {"state": "sale"})` |
| `x("account.move", "action_post", [ids])` | `write(ids, {"state": "posted"})` |
| `x("stock.picking", "action_assign", [ids])` then `"button_validate"` | `write(ids, {"state": "done"})` |
| `x("purchase.order", "button_confirm", [ids])` | `write(ids, {"state": "purchase"})` |
| `x("account.move", "button_draft", [ids])` before cancel | `write(ids, {"state": "draft"})` |
| `x("sale.order", "action_confirm", ids)` | `write(ids, {"state": "sale"})` |
| `x("account.move", "action_post", ids)` | `write(ids, {"state": "posted"})` |
| `x("stock.picking", "action_assign", ids)` then `"button_validate"` | `write(ids, {"state": "done"})` |
| `x("purchase.order", "button_confirm", ids)` | `write(ids, {"state": "purchase"})` |
| `x("account.move", "button_draft", ids)` before cancel | `write(ids, {"state": "draft"})` |
`ids` is already the list — `x()` wraps a bare int for you, so `[ids]` around a list sends a
nested list and the server rejects it.
This is also the only way to stay consistent: every JSON-2 call is a separate transaction, so
a hand-rolled sequence of writes can be interrupted halfway by a concurrent change and leave a
@@ -215,7 +218,7 @@ are heavy; allow several minutes.
Industry-appropriate, minimal but believable, generated from the brief — the client's own
partners, products, projects and orders, staged so each hero has a record to fire on.
Drive it from a `demo_config.json` (see `demo_config.example.json`) and
Drive it from `scripts/demo_config.json` (see `demo_config.example.json`) and
`scripts/build_demo.py`.
**`build_demo.py` is a per-engagement template, not an engine.** It implements one shape
@@ -329,8 +332,11 @@ are different claims, and only one of them survives the demo.
### Sales docs
From `templates/` (brand-themed, `{{placeholder}}`-driven), produce three self-contained
HTML files filled with the client's data:
From `templates/` (brand-themed, `{{placeholder}}`-driven), produce three single-file HTML
docs filled with the client's data. Inline `brand.css` into each; the **only** external
reference any doc may carry is the Lato webfont `@import`, which falls back to the system
stack offline (see `templates/README.md`). No other external asset, ever — these get
emailed, opened on a strange laptop, and printed in a room with no wifi.
- **run-of-show** — beats with exact click-paths + what to say.
- **one-pager** — before/after, what's automated, investment, path to go-live; branded for
@@ -350,8 +356,8 @@ Runs after the demo happened. Needs: the built demo, the verification log, and w
actually came up in the room. Ask the user for the room notes — objections raised, who
pushed back, what they asked for that wasn't there.
Generate four documents, reusing `templates/` branding and its self-contained-HTML rule
(no external assets, single file, prints to A4).
Generate four documents, reusing `templates/` branding and its single-file HTML rule
(inlined CSS, no external asset but the Lato webfont, prints to A4).
| Document | Contents |
|---|---|
@@ -385,11 +391,18 @@ override exactly these, and nothing else:
| **Demo data policy** | An override of what dataset gets loaded, or an instruction not to install a stock one | *Load demo data* |
| **Follow-through framing** | Vertical-specific objections and scope lines | Phase C |
Beyond those five, a vertical may **attach step-local notes** to any named step — the
domain detail that only makes sense for its industry (which settings default off after its
modules install, what a plausible staged number looks like, which chain *Test heroes* has
to exercise). Those change no mechanism, so they need no extension point; they name the
step they apply at and stop there.
Rules for the boundary:
- The vertical does **not** reimplement `scripts/`, `templates/`, or `demo_config.json` handling.
- It references steps by **name** (*Test heroes*, *Purge test residue*), never by number.
- Anything a vertical needs that isn't in the table above is a gap in *this* skill — fix it
here, so the next vertical gets it free.
- A vertical needing new *mechanism* — a step that doesn't exist, an override the table
doesn't offer — is a gap in *this* skill. Fix it here, so the next vertical gets it free.
Domain detail hung off an existing step is not a gap; it belongs in the vertical.
---
@@ -409,8 +422,9 @@ Rules for the boundary:
- Never send anything client-facing without the user reading it first.
- Read `reference/odoo19-field-gotchas.md` before touching Odoo 19/20 — several core fields
were renamed.
- `discovery_brief.md`, `demo_config.json` and the follow-through docs hold client data and
API keys — keep them in the project directory, never in a scratchpad you'd forget.
- `discovery_brief.md`, `scripts/demo_config.json` and the follow-through docs hold client
data, and `scripts/creds.json` holds the API key — keep them in the project directory,
never in a scratchpad you'd forget. Both json files are gitignored; keep it that way.
## Files
+18
View File
@@ -60,3 +60,21 @@ A custom module that sets `category_id` on `res.groups` or `groups_id` on `res.u
## Module install over RPC
- `x("ir.module.module","button_immediate_install", ids)`. Heavy — let it run minutes (`x()` defaults to a 600s timeout for this reason). On a failed XML-data load it rolls back cleanly (module stays uninstalled); fix and retry.
- After deploying a custom module to git (Odoo.sh), wait for the rebuild, then `x("ir.module.module","update_list")` and search for it before installing.
## Custom module on v19 — five that cost a build cycle each
- **`stock.move.name` is gone.** `_rec_name = 'reference'`. Passing `name` in move vals raises. Set `description_picking` if you need text on the line.
- **Search views: `<group expand="0" string="Group By">` no longer validates.** RelaxNG rejects the attributes (`ERROR:RELAXNGV:RELAXNG_ERR_NOELEM`, reported only as "Invalid view definition"). Use a bare `<group>` wrapping the group-by `<filter>`s, as core does.
- **Mixed stored/non-stored fields in one compute → `UserWarning: inconsistent 'store'`**, and the non-stored one silently recomputes the stored ones. Give non-stored fields their own compute method.
- `sale.order.line` uses **`product_uom_id`** in v19 while `stock.move` still uses **`product_uom`**. `_action_launch_stock_rule` is keyword-only: `def _action_launch_stock_rule(self, *, previous_product_uom_qty=False)`.
- Suppressing the automatic delivery on confirmation (blanket/call-off orders) is cleanest by overriding `sale.order.line._action_launch_stock_rule` to skip those lines — no picking is created at all, so nothing has to be cancelled afterwards.
## Local Odoo 19 Enterprise in Docker (no Odoo.sh, no sudo needed)
`postgres:16` + the official `odoo:19` image (it carries every Python dep), but **run the mounted 19.0 source, not the image's copy**:
```yaml
entrypoint: ["/bin/bash","-lc","rm -rf /usr/lib/python3/dist-packages/odoo && exec python3 /mnt/odoo/odoo-bin \"$$@\"","--"]
command: ["-c","/etc/odoo/odoo.conf"]
```
Without the `rm`, `odoo.addons` is a **namespace package spanning both trees**: core loads from the mount while half the addons load from dist-packages — a silent version mix. Verify with a traceback path or `python3 -c "import odoo"` (should fail).
- Config keys: `http_port` (not `xmlrpc_port`), `gevent_port`. `--without-demo=all` is now a boolean — pass `--without-demo=True`.
- Driving the build through `odoo-bin shell` (script on stdin) beats the external API **on a local box only**: full ORM, no named-argument footguns, and **nothing commits unless you call `env.cr.commit()`** — which makes a dry-run of the live demo beat trivially safe (`env.cr.rollback()`). It needs shell access to the container, so it is not an option on Odoo.sh or Odoo Online; there, JSON-2 remains the only route.
- **After a CLI `-u <module>`, restart the server container.** The upgrade runs in its own process: the DB gets the new fields and views, the long-running web process keeps its old registry. The browser then loads a view referencing a field that server doesn't know → `OwlError: "model"."field" field is undefined`, reported as an Owl lifecycle crash. `odoo-bin shell` scripts never see it (fresh registry per process), so **the whole test suite can be green while the UI is broken** — verify a renamed/added field with `fields_get` over JSON-2 against the *running* server, not only through shell.
+18 -3
View File
@@ -1,8 +1,23 @@
# Doc templates
Three self-contained HTML docs the skill produces in step 8. Inline `brand.css`
into each (one `<style>` block, no external deps except Google Fonts). Replace the
footer brand with the SELLING company. Fill `{{placeholders}}` from the engagement.
Three single-file HTML docs the skill produces at *Sales docs* (Phase B). Inline
`brand.css` into each (one `<style>` block). The Lato `@import` is the one external
reference allowed — `display=swap` plus the fallback stack in `brand.css` means an
offline doc still renders, just in the system font. Nothing else may load from the
network. Replace the footer brand with the SELLING company. Fill `{{placeholders}}`
from the engagement.
## House style
`brand.css` carries the Callista house style, lifted from www.callista.be (its Odoo
theme): gold `#D1AA7A` / `#CA9E67` with `#976B34` for gold text on light, warm cream
`#F8F2EB` page over `#F2E7D9` bands, white cards, black ink, **Lato**, Bootstrap radii
(`.4rem` / `1rem`). Hero bands get a 4px gold top rule; the dark call-out card is pure
black with gold headings, echoing the site's header.
If the selling company is not Callista, swap the palette in `brand.css` along with the
footer — everything downstream reads from those custom properties, so a doc only needs
the `:root` block replaced.
## 1. `run-of-show` (internal — the presenter drives from this)
See `run-of-show.template.html`. Sections, in order:
+35 -31
View File
@@ -1,40 +1,44 @@
/* Reusable brand theme for odoo-demo-architect docs.
Light, blue #005EFF, navy #191F2E, Inter + Space Grotesk. Inline this into each
self-contained HTML doc (no external deps except Google Fonts). Swap the brand
footer per the SELLING company. */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap');
:root{--blue:#005EFF;--blue-dark:#0046bd;--blue-light:rgba(0,94,255,0.08);--cyan:#00C6FF;
--navy:#191F2E;--txt:#191F2E;--dim:#414651;--faint:#6b7280;--bg:#F5F7FA;
--hero:radial-gradient(circle at 50% 0%,#E3F2FD 0%,#F8FAFC 70%);--surface:#fff;--line:#e5e9f0;
--ok:#16a34a;--warn:#d97706;--bad:#dc2626;--sh-sm:0 2px 4px rgba(0,0,0,.02);
--sh-md:0 12px 24px -6px rgba(0,0,0,.05);--r:16px;--r-sm:10px;}
/* Reusable brand theme for callista-odoo-demo docs — Callista house style.
Gold #D1AA7A on warm cream #F8F2EB / #F2E7D9, black ink, Lato. Taken from
www.callista.be (Odoo theme: o-color-1 #D1AA7A, o-color-2 #CA9E67, link #976B34,
body font Lato, --border-radius .4rem). Inline this into each single-file HTML doc.
The Lato @import below is the one external reference a doc may carry; it swaps in
and the stack below falls back offline. Swap the brand footer per the SELLING
company — and if that company is not Callista, swap this palette with it. */
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;0,900;1,400&display=swap');
:root{--gold:#D1AA7A;--gold-2:#CA9E67;--gold-ink:#976B34;--gold-tint:rgba(209,170,122,.16);
--ink:#000;--txt:#000;--dim:#4a4540;--faint:#8a8279;--bg:#F8F2EB;--band:#F2E7D9;
--hero:linear-gradient(180deg,#F2E7D9 0%,#EFE1CF 100%);--surface:#fff;--line:#E8DFD3;
--ok:#2E7D32;--warn:#B45309;--bad:#C0392B;--sh-sm:0 1px 2px rgba(70,50,25,.05);
--sh-md:0 12px 24px -8px rgba(70,50,25,.14);--r:1rem;--r-sm:.4rem;}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--txt);font:15px/1.55 Inter,system-ui,sans-serif;-webkit-font-smoothing:antialiased}
body{margin:0;background:var(--bg);color:var(--txt);font:15px/1.6 Lato,-apple-system,system-ui,sans-serif;-webkit-font-smoothing:antialiased}
.wrap{max-width:1000px;margin:0 auto;padding:36px 28px 60px}
h1,h2,h3{font-family:'Space Grotesk',sans-serif;color:var(--navy);letter-spacing:-.02em;line-height:1.2}
h1{font-size:32px;margin:0 0 6px;font-weight:700}
h2{font-size:20px;margin:34px 0 12px;font-weight:600;border-bottom:1px solid var(--line);padding-bottom:8px}
h3{font-size:15px;margin:18px 0 6px;font-weight:600;color:var(--blue)}
h1,h2,h3{font-family:Lato,sans-serif;color:var(--ink);line-height:1.2}
h1{font-size:34px;margin:0 0 6px;font-weight:900;letter-spacing:-.01em}
h2{font-size:21px;margin:34px 0 12px;font-weight:700;border-bottom:2px solid var(--gold);padding-bottom:8px}
h3{font-size:15px;margin:18px 0 6px;font-weight:700;color:var(--gold-ink);text-transform:uppercase;letter-spacing:.04em}
.sub{color:var(--dim);margin:0 0 4px;font-size:15px}.src{color:var(--faint);font-size:12px;margin:0 0 22px}
a{color:var(--blue);text-decoration:none}
.gradient-text{background:linear-gradient(135deg,var(--blue),var(--cyan));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.label{font-family:'Space Grotesk',sans-serif;font-size:11px;font-weight:700;color:var(--blue);text-transform:uppercase;letter-spacing:.1em;background:var(--blue-light);padding:4px 12px;border-radius:100px;display:inline-block;margin-bottom:14px}
.lead{background:#fff;border:1px solid var(--line);border-left:4px solid var(--blue);padding:14px 18px;border-radius:var(--r-sm);margin:16px 0;box-shadow:var(--sh-sm)}
a{color:var(--gold-ink);text-decoration:none}a:hover{text-decoration:underline}
.gradient-text{background:linear-gradient(135deg,var(--gold-2),var(--gold-ink));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.label{font-size:11px;font-weight:700;color:var(--gold-ink);text-transform:uppercase;letter-spacing:.1em;background:var(--gold-tint);padding:5px 14px;border-radius:50rem;display:inline-block;margin-bottom:14px}
.lead{background:#fff;border:1px solid var(--line);border-left:4px solid var(--gold);padding:14px 18px;border-radius:var(--r-sm);margin:16px 0;box-shadow:var(--sh-sm)}
.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:22px 0}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:18px;box-shadow:var(--sh-sm)}
.card .n{font-family:'Space Grotesk',sans-serif;font-size:27px;font-weight:700;color:var(--navy)}.card .n.blue{color:var(--blue)}.card .l{color:var(--dim);font-size:13px;margin-top:3px}
.card-dark{background:var(--navy);color:#fff;border-radius:var(--r);padding:22px 24px;box-shadow:var(--sh-md);margin:18px 0}.card-dark h3{color:#fff}.card-dark p{color:#cbd5e1;margin:6px 0 0}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:18px;box-shadow:var(--sh-sm);min-width:0}
.card .n{font-size:28px;font-weight:900;color:var(--ink);line-height:1.1;overflow-wrap:anywhere}.card .n.blue,.card .n.gold{color:var(--gold-ink)}.card .l{color:var(--dim);font-size:13px;margin-top:3px}
.card-dark{background:var(--ink);color:#fff;border-radius:var(--r-sm);padding:22px 24px;box-shadow:var(--sh-md);margin:18px 0}.card-dark h3{color:var(--gold)}.card-dark p{color:#ded7cd;margin:6px 0 0}.card-dark a{color:var(--gold)}
table{width:100%;border-collapse:collapse;font-size:14px;background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--faint);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.04em;background:#fafbfc}tr:last-child td{border-bottom:none}
.pill{display:inline-block;padding:2px 10px;border-radius:100px;font-size:11.5px;font-weight:600}.pill.live{background:#dcfce7;color:#15803d}.pill.bad{background:#fee2e2;color:#b91c1c}
.box{background:#fff;border:1px solid var(--line);border-left:4px solid var(--ok);border-radius:var(--r-sm);padding:12px 16px;margin:10px 0;box-shadow:var(--sh-sm)}.box.warn{border-left-color:var(--warn)}.box.info{border-left-color:var(--blue)}
th{color:var(--gold-ink);font-weight:700;font-size:11px;text-transform:uppercase;letter-spacing:.06em;background:var(--band)}tr:last-child td{border-bottom:none}
.pill{display:inline-block;padding:2px 10px;border-radius:50rem;font-size:11.5px;font-weight:700}.pill.live{background:#e3f1e4;color:#1f6d24}.pill.bad{background:#fbe4e1;color:#a3271b}
.box{background:#fff;border:1px solid var(--line);border-left:4px solid var(--ok);border-radius:var(--r-sm);padding:12px 16px;margin:10px 0;box-shadow:var(--sh-sm)}.box.warn{border-left-color:var(--warn)}.box.info{border-left-color:var(--gold)}
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:18px 0}
.step{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:16px;position:relative;box-shadow:var(--sh-sm)}.step.here{border-color:var(--blue);box-shadow:0 0 0 1px var(--blue) inset}
.step .num{font-family:'Space Grotesk',sans-serif;font-size:11px;color:var(--faint);font-weight:700;text-transform:uppercase}.step .ttl{font-family:'Space Grotesk',sans-serif;font-size:15px;font-weight:700;color:var(--navy);margin:4px 0 6px}.step .d{font-size:12.5px;color:var(--dim)}
.step.here .badge{position:absolute;top:-10px;right:10px;background:var(--blue);color:#fff;font-size:11px;font-weight:700;padding:2px 9px;border-radius:100px}
code{background:var(--blue-light);border:1px solid #d6e4ff;border-radius:5px;padding:1px 6px;font:12.5px ui-monospace,Consolas,monospace;color:var(--blue-dark)}
.step{background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:16px;position:relative;box-shadow:var(--sh-sm)}.step.here{border-color:var(--gold);box-shadow:0 0 0 2px var(--gold) inset}
.step .num{font-size:11px;color:var(--faint);font-weight:700;text-transform:uppercase;letter-spacing:.06em}.step .ttl{font-size:15px;font-weight:900;color:var(--ink);margin:4px 0 6px}.step .d{font-size:12.5px;color:var(--dim)}
.step.here .badge{position:absolute;top:-10px;right:10px;background:var(--gold);color:#000;font-size:11px;font-weight:700;padding:2px 10px;border-radius:50rem}
code{background:var(--band);border:1px solid var(--line);border-radius:var(--r-sm);padding:1px 6px;font:12.5px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--gold-ink)}
footer{margin-top:40px;color:var(--faint);font-size:12px;border-top:1px solid var(--line);padding-top:16px}
.hero{background:var(--hero);padding:44px 28px 32px}.hero .wrap{padding:0;max-width:1000px;margin:0 auto}
@media(max-width:760px){.grid,.steps{grid-template-columns:1fr 1fr}h1{font-size:25px}}
.hero{background:var(--hero);border-top:4px solid var(--gold);border-bottom:1px solid #E2D2BB;padding:44px 28px 34px}.hero .wrap{padding:0;max-width:1000px;margin:0 auto}
.hero .label{background:#fff;border:1px solid #E2D2BB}
@media(max-width:760px){.grid,.steps{grid-template-columns:1fr 1fr}h1{font-size:26px}}
@media print{body{background:#fff}.wrap{padding:0}@page{size:A4;margin:12mm}}
+17 -15
View File
@@ -5,24 +5,26 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{CLIENT}} Demo — Step-by-Step (1 page)</title>
<style>
/* >>> inline templates/brand.css here <<< plus the small extras below */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@600;700&display=swap');
:root{--blue:#005EFF;--cyan:#00C6FF;--navy:#191F2E;--txt:#191F2E;--dim:#414651;--faint:#6b7280;--bg:#F5F7FA;--line:#e5e9f0;--ok:#16a34a;--warn:#d97706;--bad:#dc2626;--blue-light:rgba(0,94,255,0.08)}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--txt);font:13px/1.45 Inter,system-ui,sans-serif}
/* >>> Callista house style (compact variant of templates/brand.css) <<< */
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;0,900;1,400&display=swap');
:root{--gold:#D1AA7A;--gold-2:#CA9E67;--gold-ink:#976B34;--gold-tint:rgba(209,170,122,.16);
--ink:#000;--txt:#000;--dim:#4a4540;--faint:#8a8279;--bg:#F8F2EB;--band:#F2E7D9;--line:#E8DFD3;
--ok:#2E7D32;--warn:#B45309;--bad:#C0392B}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--txt);font:13px/1.5 Lato,-apple-system,system-ui,sans-serif}
.wrap{max-width:920px;margin:0 auto;padding:22px 26px 30px}
h1{font-family:'Space Grotesk',sans-serif;font-size:23px;margin:0 0 2px;color:var(--navy);letter-spacing:-.02em}
h1{font-family:Lato,sans-serif;font-size:24px;font-weight:900;margin:0 0 2px;color:var(--ink);letter-spacing:-.01em}
.sub{color:var(--dim);font-size:13px;margin:0 0 2px}.src{color:var(--faint);font-size:11px;margin:0 0 12px}
.gradient-text{background:linear-gradient(135deg,var(--blue),var(--cyan));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.bar{display:flex;flex-wrap:wrap;gap:8px;background:#fff;border:1px solid var(--line);border-left:4px solid var(--blue);border-radius:8px;padding:9px 12px;margin:0 0 14px;font-size:12px}
.bar b{color:var(--navy)}code{background:var(--blue-light);border:1px solid #d6e4ff;border-radius:4px;padding:1px 5px;font:11.5px ui-monospace,Consolas,monospace;color:#0046bd}
table{width:100%;border-collapse:collapse;background:#fff;border:1px solid var(--line);border-radius:8px;overflow:hidden}
.gradient-text{background:linear-gradient(135deg,var(--gold-2),var(--gold-ink));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.bar{display:flex;flex-wrap:wrap;gap:8px;background:#fff;border:1px solid var(--line);border-left:4px solid var(--gold);border-radius:.4rem;padding:9px 12px;margin:0 0 14px;font-size:12px}
.bar b{color:var(--ink)}code{background:var(--band);border:1px solid var(--line);border-radius:.3rem;padding:1px 5px;font:11.5px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--gold-ink)}
table{width:100%;border-collapse:collapse;background:#fff;border:1px solid var(--line);border-radius:.4rem;overflow:hidden}
th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--faint);font-weight:600;font-size:10.5px;text-transform:uppercase;background:#fafbfc}tr:last-child td{border-bottom:none}
td.n{font-family:'Space Grotesk',sans-serif;font-weight:700;color:var(--blue);width:26px;text-align:center;font-size:15px}
td.do{width:46%}td.do b{color:var(--navy)}td.say{color:var(--dim);font-style:italic}
tr.hero td{background:#fffdf6}tr.hero td.n{color:var(--warn)}
.tag{display:inline-block;font-size:10px;font-weight:700;padding:1px 7px;border-radius:100px;margin-left:4px}.tag.block{background:#fee2e2;color:#b91c1c}.tag.ok{background:#dcfce7;color:#15803d}
.ref{display:flex;flex-wrap:wrap;gap:14px;margin-top:12px;font-size:11.5px;color:var(--dim)}.ref div{background:#fff;border:1px solid var(--line);border-radius:8px;padding:7px 10px}.ref b{color:var(--navy)}
th{color:var(--gold-ink);font-weight:700;font-size:10.5px;text-transform:uppercase;letter-spacing:.06em;background:var(--band)}tr:last-child td{border-bottom:none}
td.n{font-weight:900;color:var(--gold-ink);width:26px;text-align:center;font-size:15px}
td.do{width:46%}td.do b{color:var(--ink)}td.say{color:var(--dim);font-style:italic}
tr.hero td{background:#FCF6EE}tr.hero td.n{color:var(--warn)}
.tag{display:inline-block;font-size:10px;font-weight:700;padding:1px 8px;border-radius:50rem;margin-left:4px}.tag.block{background:#fbe4e1;color:#a3271b}.tag.ok{background:#e3f1e4;color:#1f6d24}
.ref{display:flex;flex-wrap:wrap;gap:14px;margin-top:12px;font-size:11.5px;color:var(--dim)}.ref div{background:#fff;border:1px solid var(--line);border-radius:.4rem;padding:7px 10px}.ref b{color:var(--ink)}
footer{margin-top:12px;color:var(--faint);font-size:10.5px;border-top:1px solid var(--line);padding-top:8px}
@media print{body{background:#fff}.wrap{padding:0}@page{size:A4;margin:12mm}}
</style>
+39 -32
View File
@@ -5,46 +5,53 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{SELLER}} × {{CLIENT}} — {{HEADLINE_SHORT}}</title>
<style>
/* >>> inline templates/brand.css here <<< plus the small extras below */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap');
:root{--blue:#005EFF;--blue-dark:#0046bd;--blue-light:rgba(0,94,255,0.08);--cyan:#00C6FF;
--navy:#191F2E;--txt:#191F2E;--dim:#414651;--faint:#6b7280;--bg:#F5F7FA;
--hero:radial-gradient(circle at 50% 0%,#E3F2FD 0%,#F8FAFC 70%);--surface:#fff;--line:#e5e9f0;
--ok:#16a34a;--warn:#d97706;--bad:#dc2626;--sh-sm:0 2px 4px rgba(0,0,0,.02);
--sh-md:0 12px 24px -6px rgba(0,0,0,.05);--r:16px;--r-sm:10px;}
/* Reusable brand theme for callista-odoo-demo docs — Callista house style.
Gold #D1AA7A on warm cream #F8F2EB / #F2E7D9, black ink, Lato. Taken from
www.callista.be (Odoo theme: o-color-1 #D1AA7A, o-color-2 #CA9E67, link #976B34,
body font Lato, --border-radius .4rem). Inline this into each single-file HTML doc.
The Lato @import below is the one external reference a doc may carry; it swaps in
and the stack below falls back offline. Swap the brand footer per the SELLING
company — and if that company is not Callista, swap this palette with it. */
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;0,900;1,400&display=swap');
:root{--gold:#D1AA7A;--gold-2:#CA9E67;--gold-ink:#976B34;--gold-tint:rgba(209,170,122,.16);
--ink:#000;--txt:#000;--dim:#4a4540;--faint:#8a8279;--bg:#F8F2EB;--band:#F2E7D9;
--hero:linear-gradient(180deg,#F2E7D9 0%,#EFE1CF 100%);--surface:#fff;--line:#E8DFD3;
--ok:#2E7D32;--warn:#B45309;--bad:#C0392B;--sh-sm:0 1px 2px rgba(70,50,25,.05);
--sh-md:0 12px 24px -8px rgba(70,50,25,.14);--r:1rem;--r-sm:.4rem;}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--txt);font:15px/1.55 Inter,system-ui,sans-serif;-webkit-font-smoothing:antialiased}
body{margin:0;background:var(--bg);color:var(--txt);font:15px/1.6 Lato,-apple-system,system-ui,sans-serif;-webkit-font-smoothing:antialiased}
.wrap{max-width:1000px;margin:0 auto;padding:36px 28px 60px}
h1,h2,h3{font-family:'Space Grotesk',sans-serif;color:var(--navy);letter-spacing:-.02em;line-height:1.2}
h1{font-size:32px;margin:0 0 6px;font-weight:700}
h2{font-size:20px;margin:34px 0 12px;font-weight:600;border-bottom:1px solid var(--line);padding-bottom:8px}
h3{font-size:15px;margin:18px 0 6px;font-weight:600;color:var(--blue)}
h1,h2,h3{font-family:Lato,sans-serif;color:var(--ink);line-height:1.2}
h1{font-size:34px;margin:0 0 6px;font-weight:900;letter-spacing:-.01em}
h2{font-size:21px;margin:34px 0 12px;font-weight:700;border-bottom:2px solid var(--gold);padding-bottom:8px}
h3{font-size:15px;margin:18px 0 6px;font-weight:700;color:var(--gold-ink);text-transform:uppercase;letter-spacing:.04em}
.sub{color:var(--dim);margin:0 0 4px;font-size:15px}.src{color:var(--faint);font-size:12px;margin:0 0 22px}
a{color:var(--blue);text-decoration:none}
.gradient-text{background:linear-gradient(135deg,var(--blue),var(--cyan));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.label{font-family:'Space Grotesk',sans-serif;font-size:11px;font-weight:700;color:var(--blue);text-transform:uppercase;letter-spacing:.1em;background:var(--blue-light);padding:4px 12px;border-radius:100px;display:inline-block;margin-bottom:14px}
.lead{background:#fff;border:1px solid var(--line);border-left:4px solid var(--blue);padding:14px 18px;border-radius:var(--r-sm);margin:16px 0;box-shadow:var(--sh-sm)}
a{color:var(--gold-ink);text-decoration:none}a:hover{text-decoration:underline}
.gradient-text{background:linear-gradient(135deg,var(--gold-2),var(--gold-ink));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.label{font-size:11px;font-weight:700;color:var(--gold-ink);text-transform:uppercase;letter-spacing:.1em;background:var(--gold-tint);padding:5px 14px;border-radius:50rem;display:inline-block;margin-bottom:14px}
.lead{background:#fff;border:1px solid var(--line);border-left:4px solid var(--gold);padding:14px 18px;border-radius:var(--r-sm);margin:16px 0;box-shadow:var(--sh-sm)}
.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:22px 0}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:18px;box-shadow:var(--sh-sm)}
.card .n{font-family:'Space Grotesk',sans-serif;font-size:27px;font-weight:700;color:var(--navy)}.card .n.blue{color:var(--blue)}.card .l{color:var(--dim);font-size:13px;margin-top:3px}
.card-dark{background:var(--navy);color:#fff;border-radius:var(--r);padding:22px 24px;box-shadow:var(--sh-md);margin:18px 0}.card-dark h3{color:#fff}.card-dark p{color:#cbd5e1;margin:6px 0 0}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:18px;box-shadow:var(--sh-sm);min-width:0}
.card .n{font-size:28px;font-weight:900;color:var(--ink);line-height:1.1;overflow-wrap:anywhere}.card .n.blue,.card .n.gold{color:var(--gold-ink)}.card .l{color:var(--dim);font-size:13px;margin-top:3px}
.card-dark{background:var(--ink);color:#fff;border-radius:var(--r-sm);padding:22px 24px;box-shadow:var(--sh-md);margin:18px 0}.card-dark h3{color:var(--gold)}.card-dark p{color:#ded7cd;margin:6px 0 0}.card-dark a{color:var(--gold)}
table{width:100%;border-collapse:collapse;font-size:14px;background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--faint);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.04em;background:#fafbfc}tr:last-child td{border-bottom:none}
.pill{display:inline-block;padding:2px 10px;border-radius:100px;font-size:11.5px;font-weight:600}.pill.live{background:#dcfce7;color:#15803d}
.box{background:#fff;border:1px solid var(--line);border-left:4px solid var(--ok);border-radius:var(--r-sm);padding:12px 16px;margin:10px 0;box-shadow:var(--sh-sm)}.box.warn{border-left-color:var(--warn)}.box.info{border-left-color:var(--blue)}
th{color:var(--gold-ink);font-weight:700;font-size:11px;text-transform:uppercase;letter-spacing:.06em;background:var(--band)}tr:last-child td{border-bottom:none}
.pill{display:inline-block;padding:2px 10px;border-radius:50rem;font-size:11.5px;font-weight:700}.pill.live{background:#e3f1e4;color:#1f6d24}.pill.bad{background:#fbe4e1;color:#a3271b}
.box{background:#fff;border:1px solid var(--line);border-left:4px solid var(--ok);border-radius:var(--r-sm);padding:12px 16px;margin:10px 0;box-shadow:var(--sh-sm)}.box.warn{border-left-color:var(--warn)}.box.info{border-left-color:var(--gold)}
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:18px 0}
.step{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:16px;position:relative;box-shadow:var(--sh-sm)}.step.here{border-color:var(--blue);box-shadow:0 0 0 1px var(--blue) inset}
.step .num{font-family:'Space Grotesk',sans-serif;font-size:11px;color:var(--faint);font-weight:700;text-transform:uppercase}.step .ttl{font-family:'Space Grotesk',sans-serif;font-size:15px;font-weight:700;color:var(--navy);margin:4px 0 6px}.step .d{font-size:12.5px;color:var(--dim)}
.step.here .badge{position:absolute;top:-10px;right:10px;background:var(--blue);color:#fff;font-size:11px;font-weight:700;padding:2px 9px;border-radius:100px}
code{background:var(--blue-light);border:1px solid #d6e4ff;border-radius:5px;padding:1px 6px;font:12.5px ui-monospace,Consolas,monospace;color:var(--blue-dark)}
.step{background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:16px;position:relative;box-shadow:var(--sh-sm)}.step.here{border-color:var(--gold);box-shadow:0 0 0 2px var(--gold) inset}
.step .num{font-size:11px;color:var(--faint);font-weight:700;text-transform:uppercase;letter-spacing:.06em}.step .ttl{font-size:15px;font-weight:900;color:var(--ink);margin:4px 0 6px}.step .d{font-size:12.5px;color:var(--dim)}
.step.here .badge{position:absolute;top:-10px;right:10px;background:var(--gold);color:#000;font-size:11px;font-weight:700;padding:2px 10px;border-radius:50rem}
code{background:var(--band);border:1px solid var(--line);border-radius:var(--r-sm);padding:1px 6px;font:12.5px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--gold-ink)}
footer{margin-top:40px;color:var(--faint);font-size:12px;border-top:1px solid var(--line);padding-top:16px}
.hero{background:var(--hero);padding:44px 28px 32px}.hero .wrap{padding:0;max-width:1000px;margin:0 auto}
.hero{background:var(--hero);border-top:4px solid var(--gold);border-bottom:1px solid #E2D2BB;padding:44px 28px 34px}.hero .wrap{padding:0;max-width:1000px;margin:0 auto}
.hero .label{background:#fff;border:1px solid #E2D2BB}
@media(max-width:760px){.grid,.steps{grid-template-columns:1fr 1fr}h1{font-size:26px}}
@media print{body{background:#fff}.wrap{padding:0}@page{size:A4;margin:12mm}}
/* one-pager extras */
.tbl-wrap{overflow-x:auto}
td.was{color:var(--dim)}td.now b{color:var(--navy)}
@media(max-width:760px){.grid,.steps{grid-template-columns:1fr 1fr}h1{font-size:25px}}
@media print{body{background:#fff}.wrap{padding:0}@page{size:A4;margin:12mm}}
td.was{color:var(--dim)}td.now b{color:var(--ink)}
</style>
</head>
<body>
@@ -85,7 +92,7 @@ td.was{color:var(--dim)}td.now b{color:var(--navy)}
<!-- Real figures from the built demo. If a number is not in the verification log,
it does not go on this page. -->
<div class="grid">
<div class="card"><div class="n blue">{{PROOF1_N}}</div><div class="l">{{PROOF1_L}}</div></div>
<div class="card"><div class="n gold">{{PROOF1_N}}</div><div class="l">{{PROOF1_L}}</div></div>
<div class="card"><div class="n">{{PROOF2_N}}</div><div class="l">{{PROOF2_L}}</div></div>
<div class="card"><div class="n">{{PROOF3_N}}</div><div class="l">{{PROOF3_L}}</div></div>
<div class="card"><div class="n">{{PROOF4_N}}</div><div class="l">{{PROOF4_L}}</div></div>
@@ -115,7 +122,7 @@ td.was{color:var(--dim)}td.now b{color:var(--navy)}
here becomes the number you are held to. -->
<h2>Investment and timing</h2>
<div class="grid">
<div class="card"><div class="n blue">{{PRICE}}</div><div class="l">Phase 1</div></div>
<div class="card"><div class="n gold">{{PRICE}}</div><div class="l">Phase 1</div></div>
<div class="card"><div class="n">{{ALL_PHASES}}</div><div class="l">All phases</div></div>
<div class="card"><div class="n">{{DURATION}}</div><div class="l">Duration</div></div>
<div class="card"><div class="n">{{PAYMENT_TERMS}}</div><div class="l">Payment terms</div></div>
+41 -30
View File
@@ -5,43 +5,54 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{CLIENT}} — Run of Show</title>
<style>
/* >>> inline templates/brand.css here <<< plus the small extras below */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap');
:root{--blue:#005EFF;--blue-dark:#0046bd;--blue-light:rgba(0,94,255,0.08);--cyan:#00C6FF;
--navy:#191F2E;--txt:#191F2E;--dim:#414651;--faint:#6b7280;--bg:#F5F7FA;
--hero:radial-gradient(circle at 50% 0%,#E3F2FD 0%,#F8FAFC 70%);--surface:#fff;--line:#e5e9f0;
--ok:#16a34a;--warn:#d97706;--bad:#dc2626;--sh-sm:0 2px 4px rgba(0,0,0,.02);
--sh-md:0 12px 24px -6px rgba(0,0,0,.05);--r:16px;--r-sm:10px;}
/* Reusable brand theme for callista-odoo-demo docs — Callista house style.
Gold #D1AA7A on warm cream #F8F2EB / #F2E7D9, black ink, Lato. Taken from
www.callista.be (Odoo theme: o-color-1 #D1AA7A, o-color-2 #CA9E67, link #976B34,
body font Lato, --border-radius .4rem). Inline this into each single-file HTML doc.
The Lato @import below is the one external reference a doc may carry; it swaps in
and the stack below falls back offline. Swap the brand footer per the SELLING
company — and if that company is not Callista, swap this palette with it. */
@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;0,900;1,400&display=swap');
:root{--gold:#D1AA7A;--gold-2:#CA9E67;--gold-ink:#976B34;--gold-tint:rgba(209,170,122,.16);
--ink:#000;--txt:#000;--dim:#4a4540;--faint:#8a8279;--bg:#F8F2EB;--band:#F2E7D9;
--hero:linear-gradient(180deg,#F2E7D9 0%,#EFE1CF 100%);--surface:#fff;--line:#E8DFD3;
--ok:#2E7D32;--warn:#B45309;--bad:#C0392B;--sh-sm:0 1px 2px rgba(70,50,25,.05);
--sh-md:0 12px 24px -8px rgba(70,50,25,.14);--r:1rem;--r-sm:.4rem;}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--txt);font:15px/1.55 Inter,system-ui,sans-serif;-webkit-font-smoothing:antialiased}
body{margin:0;background:var(--bg);color:var(--txt);font:15px/1.6 Lato,-apple-system,system-ui,sans-serif;-webkit-font-smoothing:antialiased}
.wrap{max-width:1000px;margin:0 auto;padding:36px 28px 60px}
h1,h2,h3{font-family:'Space Grotesk',sans-serif;color:var(--navy);letter-spacing:-.02em;line-height:1.2}
h1{font-size:32px;margin:0 0 6px;font-weight:700}
h2{font-size:20px;margin:34px 0 12px;font-weight:600;border-bottom:1px solid var(--line);padding-bottom:8px}
h3{font-size:15px;margin:18px 0 6px;font-weight:600;color:var(--blue)}
h1,h2,h3{font-family:Lato,sans-serif;color:var(--ink);line-height:1.2}
h1{font-size:34px;margin:0 0 6px;font-weight:900;letter-spacing:-.01em}
h2{font-size:21px;margin:34px 0 12px;font-weight:700;border-bottom:2px solid var(--gold);padding-bottom:8px}
h3{font-size:15px;margin:18px 0 6px;font-weight:700;color:var(--gold-ink);text-transform:uppercase;letter-spacing:.04em}
.sub{color:var(--dim);margin:0 0 4px;font-size:15px}.src{color:var(--faint);font-size:12px;margin:0 0 22px}
a{color:var(--blue);text-decoration:none}
.gradient-text{background:linear-gradient(135deg,var(--blue),var(--cyan));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.label{font-family:'Space Grotesk',sans-serif;font-size:11px;font-weight:700;color:var(--blue);text-transform:uppercase;letter-spacing:.1em;background:var(--blue-light);padding:4px 12px;border-radius:100px;display:inline-block;margin-bottom:14px}
.lead{background:#fff;border:1px solid var(--line);border-left:4px solid var(--blue);padding:14px 18px;border-radius:var(--r-sm);margin:16px 0;box-shadow:var(--sh-sm)}
a{color:var(--gold-ink);text-decoration:none}a:hover{text-decoration:underline}
.gradient-text{background:linear-gradient(135deg,var(--gold-2),var(--gold-ink));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
.label{font-size:11px;font-weight:700;color:var(--gold-ink);text-transform:uppercase;letter-spacing:.1em;background:var(--gold-tint);padding:5px 14px;border-radius:50rem;display:inline-block;margin-bottom:14px}
.lead{background:#fff;border:1px solid var(--line);border-left:4px solid var(--gold);padding:14px 18px;border-radius:var(--r-sm);margin:16px 0;box-shadow:var(--sh-sm)}
.grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin:22px 0}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r);padding:18px;box-shadow:var(--sh-sm)}
.card .n{font-family:'Space Grotesk',sans-serif;font-size:27px;font-weight:700;color:var(--navy)}.card .n.blue{color:var(--blue)}.card .l{color:var(--dim);font-size:13px;margin-top:3px}
.card-dark{background:var(--navy);color:#fff;border-radius:var(--r);padding:22px 24px;box-shadow:var(--sh-md);margin:18px 0}.card-dark h3{color:#fff}.card-dark p{color:#cbd5e1;margin:6px 0 0}
.card{background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:18px;box-shadow:var(--sh-sm);min-width:0}
.card .n{font-size:28px;font-weight:900;color:var(--ink);line-height:1.1;overflow-wrap:anywhere}.card .n.blue,.card .n.gold{color:var(--gold-ink)}.card .l{color:var(--dim);font-size:13px;margin-top:3px}
.card-dark{background:var(--ink);color:#fff;border-radius:var(--r-sm);padding:22px 24px;box-shadow:var(--sh-md);margin:18px 0}.card-dark h3{color:var(--gold)}.card-dark p{color:#ded7cd;margin:6px 0 0}.card-dark a{color:var(--gold)}
table{width:100%;border-collapse:collapse;font-size:14px;background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);overflow:hidden}
th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--faint);font-weight:600;font-size:11px;text-transform:uppercase;letter-spacing:.04em;background:#fafbfc}tr:last-child td{border-bottom:none}
.pill{display:inline-block;padding:2px 10px;border-radius:100px;font-size:11.5px;font-weight:600}.pill.live{background:#dcfce7;color:#15803d}.pill.bad{background:#fee2e2;color:#b91c1c}
.box{background:#fff;border:1px solid var(--line);border-left:4px solid var(--ok);border-radius:var(--r-sm);padding:12px 16px;margin:10px 0;box-shadow:var(--sh-sm)}.box.warn{border-left-color:var(--warn)}.box.info{border-left-color:var(--blue)}
code{background:var(--blue-light);border:1px solid #d6e4ff;border-radius:5px;padding:1px 6px;font:12.5px ui-monospace,Consolas,monospace;color:var(--blue-dark)}
th{color:var(--gold-ink);font-weight:700;font-size:11px;text-transform:uppercase;letter-spacing:.06em;background:var(--band)}tr:last-child td{border-bottom:none}
.pill{display:inline-block;padding:2px 10px;border-radius:50rem;font-size:11.5px;font-weight:700}.pill.live{background:#e3f1e4;color:#1f6d24}.pill.bad{background:#fbe4e1;color:#a3271b}
.box{background:#fff;border:1px solid var(--line);border-left:4px solid var(--ok);border-radius:var(--r-sm);padding:12px 16px;margin:10px 0;box-shadow:var(--sh-sm)}.box.warn{border-left-color:var(--warn)}.box.info{border-left-color:var(--gold)}
.steps{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin:18px 0}
.step{background:#fff;border:1px solid var(--line);border-radius:var(--r-sm);padding:16px;position:relative;box-shadow:var(--sh-sm)}.step.here{border-color:var(--gold);box-shadow:0 0 0 2px var(--gold) inset}
.step .num{font-size:11px;color:var(--faint);font-weight:700;text-transform:uppercase;letter-spacing:.06em}.step .ttl{font-size:15px;font-weight:900;color:var(--ink);margin:4px 0 6px}.step .d{font-size:12.5px;color:var(--dim)}
.step.here .badge{position:absolute;top:-10px;right:10px;background:var(--gold);color:#000;font-size:11px;font-weight:700;padding:2px 10px;border-radius:50rem}
code{background:var(--band);border:1px solid var(--line);border-radius:var(--r-sm);padding:1px 6px;font:12.5px ui-monospace,SFMono-Regular,Consolas,monospace;color:var(--gold-ink)}
footer{margin-top:40px;color:var(--faint);font-size:12px;border-top:1px solid var(--line);padding-top:16px}
.hero{background:var(--hero);padding:44px 28px 32px}.hero .wrap{padding:0;max-width:1000px;margin:0 auto}
/* run-of-show extras */
td.beat{width:44%}td.beat b{color:var(--navy)}td.say{color:var(--dim);font-style:italic}
tr.star td{background:#fffdf6}
.tbl-wrap{overflow-x:auto}
@media(max-width:760px){.grid{grid-template-columns:1fr 1fr}h1{font-size:25px}}
.hero{background:var(--hero);border-top:4px solid var(--gold);border-bottom:1px solid #E2D2BB;padding:44px 28px 34px}.hero .wrap{padding:0;max-width:1000px;margin:0 auto}
.hero .label{background:#fff;border:1px solid #E2D2BB}
@media(max-width:760px){.grid,.steps{grid-template-columns:1fr 1fr}h1{font-size:26px}}
@media print{body{background:#fff}.wrap{padding:0}@page{size:A4;margin:12mm}}
/* run-of-show extras */
td.beat{width:44%}td.beat b{color:var(--ink)}td.say{color:var(--dim);font-style:italic}
tr.star td{background:#FCF6EE}
.tbl-wrap{overflow-x:auto}
</style>
</head>
<body>
@@ -73,7 +84,7 @@ tr.star td{background:#fffdf6}
<h2>What is standing by</h2>
<div class="grid">
<div class="card"><div class="n blue">{{STAT1_N}}</div><div class="l">{{STAT1_L}}</div></div>
<div class="card"><div class="n gold">{{STAT1_N}}</div><div class="l">{{STAT1_L}}</div></div>
<div class="card"><div class="n">{{STAT2_N}}</div><div class="l">{{STAT2_L}}</div></div>
<div class="card"><div class="n">{{STAT3_N}}</div><div class="l">{{STAT3_L}}</div></div>
<div class="card"><div class="n">{{STAT4_N}}</div><div class="l">{{STAT4_L}}</div></div>