④段整套同步(六节 + P/S/F/J + 模板)+ open-design 移到技能根

一、④段整套同步(用户选候选 A)
- 新增 `references/stage-proto-doc/assets/产品文档模板.html`(16 KB/247 行,从备份拷、转 LF)。
- ④段 SKILL.md 换成备份 15:28 那版,并按模板口径改齐:
  · 「产品文档框架」从「四块+一附录」改成**六节** —— 页面结构/功能清单/使用场景/操作流程/
    跨页与界面状态/待确认与偏离;与模板同构,⛔ 不从空白页重写结构。
  · 编号从 R/S/F 改成 **P 页面/S 场景/F 功能/J 二级功能**,各自唯一、按展示顺序通排。
  · 保留旧版有价值的要求:每项功能补齐 做什么/入口在哪/输入边界/异常与恢复;重点功能三处标红;
    横向流程图每步写清页面/功能/操作/完成判据。
  · 完成标准「七节」→「六节」,自查清单同步换掉「需求」那批条目,补模板与配图两条。
  · 路径 `designs/<项目>/` → `docs/pm/<项目>/ui/`(2 处)。
  · 补回本机那条「每份产出文档开头都要有一张图」的引用(指向主 skill,段内不复述)。
- 主 skill 同步:四段结构表④行、④段沿革、画图小节(④段画页面结构图 + 用户路径)。

二、open-design 移到技能根(用户选候选 A)
- `git mv product-planning/references/stage-delivery/assets/open-design → open-design/`(4178 文件)。
- `.gitignore` 白名单加 `!/open-design/` —— 否则移出去就不再纳入版本控制。
- 引用改 11 处:路径形态 `assets/open-design/...` → `<技能根>/open-design/...`;
  「随段携带的自带件」改称「同根技能」;「路径怎么找」那条改成自带件(段内相对路径)与
  同根技能(`<技能根>/<技能名>/`)两类分开写。
- open-design 自己的 ATTRIBUTION.md 两处同步(它现在与 oil-ui-pro 同层)。
- 副作用(有益):命名闸门读数由 2063 降到 225 —— 此前它把 open-design 里 919 个第三方 md
  当成规则一起扫了;现在只扫本包 23 个 md。已确认④段的 H1 与 4a/4b/4c 子步仍在检查范围内。

三、验收
- check_naming.py --ws E:/ProgramData/AIProject/contentm_agent = 通过 225 / 失败 0 / 提示 27。
- 活文件里 `assets/open-design` 残留 0;工作区 CRLF 文件 0。
This commit is contained in:
admin committed 2026-10-15 21:17:28 +08:00
1 parent fa0a9026c8
commit 5afeb6b78a
4184 files changed
+326 -70

No files matched your search

@@ -0,0 +1,342 @@
# Clinic Console — live artifact template
A `html_template_v1` template for a friendly clinic / hospital / telemedicine
operations console. Soft-mint healthcare aesthetic: cool off-white canvas,
single mint accent, generous 18px card radii, signature diagonal-stripe
pattern fills inside KPI tiles and bar-chart bars, illustrated CSS-gradient
avatars, and one dark surface (the calendar activity popover).
## Files
```text
clinic-console/
├── template.html # html_template_v1 source — only DOM, CSS tokens, and {{data.*}} bindings
├── data.json # canonical default sample (renders straight out of the box)
└── README.md # this file — data contract + customization notes
```
## How an agent uses this template
This template is intended to be copied (or referenced) when the
[`live-artifact`](../../../SKILL.md) skill is invoked with a healthcare
operations brief such as *"clinic dashboard", "doctors schedule", "hospital
admin", "appointment console", "telemedicine ops", "诊所后台", "医院管理"*.
The agent should:
1. Copy `template.html` and `data.json` into the project's live-artifact
workspace directory as `template.html` and `data.json`.
2. Edit `data.json` to reflect the actual brand, names, schedules, and
numbers from the brief or connector source. The shape of the JSON must
match what `template.html` references (every `{{data.path}}` interpolation
below).
3. Author `artifact.json` and `provenance.json` per the live-artifact
protocol, then register the artifact through the daemon wrapper:
```bash
"$OD_NODE_BIN" "$OD_BIN" tools live-artifacts create --input artifact.json
```
4. The daemon renders `template.html + data.json` into the preview
`index.html` automatically. The agent does **not** author `index.html`.
When the user clicks **Refresh**, the daemon re-runs the registered source,
maps results back into `data.json`, re-renders the preview, and snapshots
the change — the layout never changes; only the numbers, names, and pill
states do.
## Default sample renders out of the box
If you create a live artifact using the default `data.json` shipped here,
you get the canonical "St. Lukes Wellness" demo screen:
- Greeting: `Hey Lukmon, glad to have you back! 🙌`
- Four KPI tiles: Total doctors / Total bookings / Available rooms / Total
visitors, with mixed amber- and blue-stripe pattern footers and an inline
General/Private rooms mini-list.
- Patient overview chart with paired diagonal-stripe bars across Jan–Jul,
Mar 2025 highlighted with a mint outline.
- March 2025 mini-calendar with day 8 active (mint circle + dot) and a dark
Activity Detail popover floating below it.
- Top requested clinics donut: Dental 120 / Cardiology 249 / Surgery 165.
- Doctor schedule with three pastel pills (Available / Unavailable / Leave).
- Today's appointments list with five illustrated avatars + venue / mode
hints (`room 204`, `video call`, …).
## Default sample provenance.json
If you ship the default sample without re-sourcing the data, use:
```json
{
"generatedAt": "2026-04-29T12:00:00.000Z",
"generatedBy": "agent",
"notes": "Default sample data shipped with the clinic-console template. Replace with real clinic data before sharing externally.",
"sources": [
{ "label": "Template default sample", "type": "user_input" }
]
}
```
## Data contract
The shape below is the contract between `template.html` and `data.json`.
Every key listed is referenced by at least one `{{data.path}}` interpolation
in `template.html`. All values are scalars (string or number); the template
does not invoke any expression / helper / conditional logic — it is a
straight `html_template_v1` substitution.
### Top-level scalars
| Key | Example | Notes |
|---|---|---|
| `brand_name` | `"ST. LUKES"` | Sidebar wordmark. Keep ≤14 characters. |
| `greeting` | `"Hey Lukmon, glad to have you back! 🙌"` | Single emoji allowed at the end; no other emoji anywhere in the artifact. |
| `search_placeholder` | `"Search doctors, patients, rooms…"` | Greeting-row search input ghost text. |
| `search_shortcut` | `"⌘K"` | Right-side keycap label. |
| `secondary_action_label` | `"Export CSV"` | Greeting-row secondary button text. |
| `primary_action_label` | `"Add new"` | Greeting-row primary mint CTA text. |
### `user`
| Key | Example | Notes |
|---|---|---|
| `name` | `"Lukmon Olabode"` | Sidebar bottom row. |
| `role` | `"Admin"` | One-word role; longer roles wrap. |
| `av_class` | `"av-orange"` | One of `av-orange`, `av-pink`, `av-mint`, `av-blue`, `av-violet`, `av-amber`, `av-rose`. |
| `initial` | `"L"` | Single uppercase letter. |
### `nav_main` and `nav_management` (5 items each)
Each item shape:
| Key | Example | Notes |
|---|---|---|
| `label` | `"Dashboard"` | Nav text. |
| `active_class` | `""` or `"active"` | Set to `"active"` on exactly one nav item across both groups. |
| `count` | `""` or `"10"` | Empty string hides the count badge (CSS `:empty { display: none }`). |
> **Icons are template-locked.** Each nav slot's icon is hardcoded inside
> `template.html` (see [Icons are template-locked](#icons-are-template-locked)
> below) and is not exposed through `data.json`. The `html_template_v1`
> security validator forbids `{{data.*}}` interpolation inside URL-bearing
> attributes (`<use href>`, `<a href>`, `<img src>`, …) — and even if it
> didn't, the validator runs *before* substitution, so a malformed `data.json`
> could smuggle a `javascript:` URL past it. The reorder rule is therefore:
> if you change the meaning of a nav slot, also edit the corresponding
> `<use href="#icon-…">` literal in `template.html`.
### `pro_card`
| Key | Example | Notes |
|---|---|---|
| `tag` | `"Pro"` | Black pill in the upgrade card. Keep ≤6 characters. |
| `title` | `"Pssst!"` | Display title. |
| `body` | `"Your subscription expires in 9 days."` | One-sentence nudge. |
| `primary_label` | `"Renew"` | Mint primary action. |
| `secondary_label` | `"Cancel"` | Outlined secondary action. |
### KPI tiles `kpi_a` `kpi_b` `kpi_c` `kpi_d`
Tiles A, B, D share the **caption + pattern strip** layout. Tile C uses a
**2-row mini-list** layout instead. Every tile must have either a strip or a
mini-list — never bare.
Common keys:
| Key | Example | Notes |
|---|---|---|
| `label` | `"Total doctors"` | Tile label. |
| `value` | `"1,089"` | Big number (Plus Jakarta Sans 700). Use commas for thousands. |
| `trend_class` | `"up"` or `"down"` | Pill grammar — `up` = mint, `down` = rose. |
| `trend_label` | `"↑ 5.5%"` | Always include the arrow glyph. |
> KPI icons are also template-locked — see
> [Icons are template-locked](#icons-are-template-locked) below.
A / B / D additional keys:
| Key | Example | Notes |
|---|---|---|
| `caption` | `"An increase of 20 doctors in the last 7 days."` | One sentence answering "compared to what". |
| `strip_class` | `"stripe-amber"` | One of `stripe-amber`, `stripe-blue`, `stripe-mint`. Adjacent tiles should alternate hues. |
| `mini_stat` (B / D only) | `"1,635 today"` | Right-aligned tiny caption below the strip. |
C (`kpi_c`) additional keys:
| Key | Example |
|---|---|
| `rows` | array of 2 objects: `{ "label": "General room", "value": "100" }` |
### `chart`
| Key | Example | Notes |
|---|---|---|
| `title` | `"Patient overview"` | Card title. |
| `dropdown_label` | `"Last 6 months"` | Time-range chip text. |
| `legend_a` `legend_b` `legend_c` | `"Total patients"` etc. | Three legend captions. |
| `bars` | array of 14 objects: `{ "x": "34", "y": "148", "h": "92" }` | 7 month pairs (mint back, blue front). Bar 5 (index 5) is the highlighted month — the template adds a 2px mint stroke to bar 5 only. |
| `x_labels` | `["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]` | Seven month labels matching the seven bar pairs. |
### `calendar`
| Key | Example | Notes |
|---|---|---|
| `month_label` | `"March 2025"` | Header. |
| `dow` | `["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"]` | Always 7 items. |
| `days` | array of exactly **35** objects: `{ "label": "1", "modifier": "" }` | 5 weeks × 7 days. `modifier` = `""`, `"muted"` (leading/trailing month), or `"active"` (single highlighted day, mint circle). |
### `activity`
| Key | Example | Notes |
|---|---|---|
| `title` | `"Activity Detail · Mar 8"` | Popover header — should reference the active calendar day. |
| `events` | array of exactly 3 objects: `{ "av_class": "blue", "name": "Dr. Sarah · post-op review", "time": "11:00 am" }` | Three events. `av_class` ∈ `blue`, `pink`, `violet`, `mint`, `amber`. |
| `add_label` | `"+ Add item"` | Footer link. |
### `donut`
| Key | Example | Notes |
|---|---|---|
| `title` | `"Top 3 most requested clinics"` | Card title. |
| `center_label` | `"Total patients"` | Above the center number. |
| `center_num` | `"534"` | The big tabular number in the donut hole. |
| `segment_b` | `{ "dasharray": "120 240", "dashoffset": "0" }` | SVG `stroke-dasharray` + `stroke-dashoffset` for the pink slice. Sum of arc lengths over a r=38 circle equals `2π × 38 ≈ 239`. |
| `segment_c` | `{ "dasharray": "35 240", "dashoffset": "-120" }` | Same for the mint slice. The blue background ring is the full circumference — no per-segment math needed. |
| `legend` | array of exactly 3 objects: `{ "value": "120", "label": "Dental" }` | Three entries in blue / pink / mint order. |
### `schedule`
| Key | Example | Notes |
|---|---|---|
| `title` | `"Doctors's schedule"` | Card title. |
| `stats` | array of exactly 3 objects: `{ "value": "51", "small": "Total", "label": "Available" }` | Available / Unavailable / Leave counts in a 3-column grid. |
| `list_header_label` | `"List of Doctor"` | Sortable list header label. |
| `doctors` | array of exactly 4 objects | See below. |
Each `doctors` row:
| Key | Example | Notes |
|---|---|---|
| `av_class` | `"av-blue"` | Avatar gradient. |
| `initial` | `"P"` | Single uppercase letter. |
| `name` | `"Peter Bashir"` | Real-feeling name. |
| `role` | `"Anesthesiologist"` | Specialty. |
| `status_class` | `"avail"` `"unav"` `"leave"` | Pill grammar. |
| `status_label` | `"Available"` `"Unavailable"` `"Leave"` | Pill text. |
### `appointments`
| Key | Example |
|---|---|
| `title` | `"Today's appointments"` |
| `list` | array of exactly 5 objects |
Each `list` row:
| Key | Example | Notes |
|---|---|---|
| `av_class` | `"av-pink"` | Avatar gradient. |
| `initial` | `"R"` | Single uppercase letter. |
| `name` | `"Ruth Tubonimi"` | Real-feeling name. |
| `role` | `"Gastroenterology · room 204"` | Specialty + venue / mode hint (`room N`, `video call`, `telemedicine`). |
| `date` | `"Today"` | Short date label. |
| `time` | `"09:40"` | 24h or 12h, pick one and stay consistent. |
## Icons are template-locked
`html_template_v1` forbids `{{data.*}}` interpolation inside URL-bearing
attributes such as `<use href>`, `<a href>`, `<img src>`,
`<form action>`, etc. (see
[`design-templates/live-artifact/references/artifact-schema.md`](../../../references/artifact-schema.md#html-template-v1-binding-rules)).
The renderer's security validator runs *before* `{{data.*}}` substitution, so
even a well-formed validator pass would not protect a future `data.json`
that put `javascript:alert(1)` (or any other URL value) into one of these
attributes.
This template therefore hardcodes every `<use href="#icon-…">` reference in
`template.html` itself. Each slot has a fixed icon id:
| Slot | Hardcoded icon id |
|---|---|
| Sidebar brand mark | `#icon-leaf` |
| Sidebar collapse toggle | `#icon-collapse` |
| `nav_main[0]` Dashboard | `#icon-dashboard` |
| `nav_main[1]` Message | `#icon-message` |
| `nav_main[2]` Schedule | `#icon-schedule` |
| `nav_main[3]` Notification | `#icon-bell` |
| `nav_main[4]` Transaction | `#icon-card` |
| `nav_management[0]` Doctor | `#icon-user` |
| `nav_management[1]` Medicine | `#icon-pill` |
| `nav_management[2]` Bedroom | `#icon-bed` |
| `nav_management[3]` Appointment | `#icon-check-square` |
| `nav_management[4]` Patient | `#icon-people` |
| Sidebar logout | `#icon-logout` |
| Greeting-row search | `#icon-search` |
| Greeting-row secondary CTA | `#icon-download` |
| Greeting-row primary CTA | `#icon-plus` |
| `kpi_a` glyph | `#icon-user` |
| `kpi_b` glyph | `#icon-schedule` |
| `kpi_c` glyph | `#icon-bed` |
| `kpi_d` glyph | `#icon-people` |
| Patient-overview card | `#icon-clock` |
| Time-range dropdown chevron | `#icon-chev-down` |
| Calendar prev / next | `#icon-chev-left`, `#icon-chev-right` |
| Top-clinics card | `#icon-stethoscope` |
| Doctor-schedule card | `#icon-schedule` |
| List header chevron | `#icon-chev-down` |
| Today's-appointments card | `#icon-check-square` |
If you re-purpose a slot (e.g. swap `nav_main[2] Schedule` for
`nav_main[2] Reports`), edit the corresponding `<use href="#icon-…">` literal
in `template.html` to match — the icon set inside the inline `<symbol>`
defs at the top of `template.html` already includes 21 icons covering the
common clinic / hospital / pharmacy / telemedicine vocabulary
(`#icon-dashboard`, `#icon-message`, `#icon-schedule`, `#icon-bell`,
`#icon-card`, `#icon-user`, `#icon-pill`, `#icon-bed`, `#icon-check-square`,
`#icon-people`, `#icon-leaf`, `#icon-clock`, `#icon-stethoscope`,
`#icon-search`, `#icon-download`, `#icon-plus`, `#icon-chev-left`,
`#icon-chev-right`, `#icon-chev-down`, `#icon-collapse`, `#icon-logout`).
If you need a runtime-configurable icon, add a new constrained,
non-URL-bearing mechanism (for example a `data.kpi_a.icon_class` that toggles
between a fixed list of CSS classes the template enumerates) — never
interpolate into `<use href>` directly.
## Style guarantees
The template enforces, in CSS only (no JavaScript):
- Cool off-white canvas (`#EEF2F6`), bright white surfaces, 18px card radii, 1px hairline borders.
- Mint accent (`#10B981`) restricted to five places: active sidebar nav row, primary CTA, KPI icon glyphs, success metric pill, active calendar date.
- Diagonal-stripe pattern fills (135°, 8px line + 8px gap) on KPI footer strips and inside bar-chart bars.
- Pastel-only status pills (mint / rose / amber).
- Tabular lining numerals on every numeric value (`font-feature-settings: "tnum","lnum"`).
- The dark calendar activity popover is the only dark surface in the artifact.
- Mobile reflow at ≤920px: sidebar stacks above main, KPI strip becomes 2 cols then 1 col, mid and bottom rows stack.
- No external CDN imports. Fonts use system fallback (`Plus Jakarta Sans, Inter, system-ui, sans-serif`).
## Customization tips
- **Telemedicine** variant: replace `kpi_c` (Available rooms) with `Live sessions`, swap the donut to `Top consultation types` (Video / Audio / Chat), and add `· video call` / `· audio call` venue hints in appointment rows.
- **Pharmacy** variant: replace the doctor schedule with stock levels — keep the same shape, just rename the columns to SKU / drug / stock pill.
- **Pediatric** variant: tilt the avatar palette toward `av-pink`, `av-amber`, `av-orange`, keep the active calendar day on a children's milestone.
For all variants, **do not** introduce new colors, fonts, or radii. Every visual lever is already a token in `:root{}`.
## Bounded JSON envelope
This default `data.json` is well within the live-artifact bounded JSON
constraints:
| Constraint | Limit | This sample |
|---|---|---|
| Object/array depth | 8 | 4 |
| Object keys | 100 / object | ≤20 |
| Array length | 500 | 35 (calendar.days) |
| String length | 16 KiB | <100 chars |
| Serialized size | 256 KiB | ~7 KiB |
If you scale up the bar count, calendar density, or list rows, stay well
under these limits. Refresh writes go through the same validation, so
oversized data will be rejected before persistence.
@@ -0,0 +1,165 @@
{
"brand_name": "ST. LUKES",
"greeting": "Hey Lukmon, glad to have you back! 🙌",
"search_placeholder": "Search doctors, patients, rooms…",
"search_shortcut": "⌘K",
"secondary_action_label": "Export CSV",
"primary_action_label": "Add new",
"user": {
"name": "Lukmon Olabode",
"role": "Admin",
"av_class": "av-orange",
"initial": "L"
},
"nav_main_label": "Main Menu",
"nav_main": [
{ "label": "Dashboard", "active_class": "active", "count": "" },
{ "label": "Message", "active_class": "", "count": "10" },
{ "label": "Schedule", "active_class": "", "count": "" },
{ "label": "Notification", "active_class": "", "count": "12" },
{ "label": "Transaction", "active_class": "", "count": "" }
],
"nav_management_label": "Management",
"nav_management": [
{ "label": "Doctor", "active_class": "", "count": "" },
{ "label": "Medicine", "active_class": "", "count": "" },
{ "label": "Bedroom", "active_class": "", "count": "" },
{ "label": "Appointment", "active_class": "", "count": "" },
{ "label": "Patient", "active_class": "", "count": "" }
],
"pro_card": {
"tag": "Pro",
"title": "Pssst!",
"body": "Your subscription expires in 9 days.",
"primary_label": "Renew",
"secondary_label": "Cancel"
},
"kpi_a": {
"label": "Total doctors",
"value": "1,089",
"trend_class": "down",
"trend_label": "↓ 4.2%",
"caption": "An increase of 20 doctors in the last 7 days.",
"strip_class": "stripe-amber"
},
"kpi_b": {
"label": "Total bookings",
"value": "17,610",
"trend_class": "up",
"trend_label": "↑ 5.5%",
"caption": "Last 7 days: 5,231 → 8,323 visitors.",
"strip_class": "stripe-blue",
"mini_stat": "1,635 today"
},
"kpi_c": {
"label": "Available rooms",
"value": "8,450",
"trend_class": "up",
"trend_label": "↑ 462",
"rows": [
{ "label": "General room", "value": "100" },
{ "label": "Private room", "value": "75" }
]
},
"kpi_d": {
"label": "Total visitors",
"value": "29,709",
"trend_class": "up",
"trend_label": "↑ 3.5%",
"caption": "Top 3 in-demand clinics this month.",
"strip_class": "stripe-amber",
"mini_stat": "1,070 today"
},
"chart": {
"title": "Patient overview",
"dropdown_label": "Last 6 months",
"legend_a": "Total patients",
"legend_b": "Avg. hospitalized",
"legend_c": "Avg. outpatient care",
"bars": [
{ "x": "34", "y": "148", "h": "92" },
{ "x": "58", "y": "108", "h": "132" },
{ "x": "120", "y": "124", "h": "116" },
{ "x": "144", "y": "92", "h": "148" },
{ "x": "206", "y": "96", "h": "144" },
{ "x": "230", "y": "56", "h": "184" },
{ "x": "292", "y": "156", "h": "84" },
{ "x": "316", "y": "116", "h": "124" },
{ "x": "378", "y": "140", "h": "100" },
{ "x": "402", "y": "100", "h": "140" },
{ "x": "464", "y": "120", "h": "120" },
{ "x": "488", "y": "80", "h": "160" },
{ "x": "550", "y": "160", "h": "80" },
{ "x": "574", "y": "128", "h": "112" }
],
"x_labels": ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]
},
"calendar": {
"month_label": "March 2025",
"dow": ["SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"],
"days": [
{ "label": "23", "modifier": "muted" }, { "label": "24", "modifier": "muted" }, { "label": "25", "modifier": "muted" }, { "label": "26", "modifier": "muted" }, { "label": "27", "modifier": "muted" }, { "label": "28", "modifier": "muted" }, { "label": "1", "modifier": "" },
{ "label": "2", "modifier": "" }, { "label": "3", "modifier": "" }, { "label": "4", "modifier": "" }, { "label": "5", "modifier": "" }, { "label": "6", "modifier": "" }, { "label": "7", "modifier": "" }, { "label": "8", "modifier": "active" },
{ "label": "9", "modifier": "" }, { "label": "10", "modifier": "" }, { "label": "11", "modifier": "" }, { "label": "12", "modifier": "" }, { "label": "13", "modifier": "" }, { "label": "14", "modifier": "" }, { "label": "15", "modifier": "" },
{ "label": "16", "modifier": "" }, { "label": "17", "modifier": "" }, { "label": "18", "modifier": "" }, { "label": "19", "modifier": "" }, { "label": "20", "modifier": "" }, { "label": "21", "modifier": "" }, { "label": "22", "modifier": "" },
{ "label": "23", "modifier": "" }, { "label": "24", "modifier": "" }, { "label": "25", "modifier": "" }, { "label": "26", "modifier": "" }, { "label": "27", "modifier": "" }, { "label": "28", "modifier": "" }, { "label": "29", "modifier": "" }
]
},
"activity": {
"title": "Activity Detail · Mar 8",
"events": [
{ "av_class": "blue", "name": "Dr. Sarah · post-op review", "time": "11:00 am" },
{ "av_class": "pink", "name": "Dental staff meetup", "time": "3:00 pm" },
{ "av_class": "violet", "name": "Ola Muhammad intake", "time": "4:00 pm" }
],
"add_label": "+ Add item"
},
"donut": {
"title": "Top 3 most requested clinics",
"center_label": "Total patients",
"center_num": "534",
"segment_b": { "dasharray": "120 240", "dashoffset": "0" },
"segment_c": { "dasharray": "35 240", "dashoffset": "-120" },
"legend": [
{ "value": "120", "label": "Dental" },
{ "value": "249", "label": "Cardiology" },
{ "value": "165", "label": "Surgery" }
]
},
"schedule": {
"title": "Doctors's schedule",
"stats": [
{ "value": "51", "small": "Total", "label": "Available" },
{ "value": "23", "small": "Total", "label": "Unavailable" },
{ "value": "09", "small": "Total", "label": "Leave" }
],
"list_header_label": "List of Doctor",
"doctors": [
{ "av_class": "av-blue", "initial": "P", "name": "Peter Bashir", "role": "Anesthesiologist", "status_class": "avail", "status_label": "Available" },
{ "av_class": "av-violet", "initial": "D", "name": "Deborah Fagbemi", "role": "Cardiologist", "status_class": "unav", "status_label": "Unavailable" },
{ "av_class": "av-rose", "initial": "H", "name": "Hannah Diongoli", "role": "Dermatologist", "status_class": "avail", "status_label": "Available" },
{ "av_class": "av-amber", "initial": "A", "name": "Aisha Bello", "role": "Pediatrician", "status_class": "leave", "status_label": "Leave" }
]
},
"appointments": {
"title": "Today's appointments",
"list": [
{ "av_class": "av-pink", "initial": "R", "name": "Ruth Tubonimi", "role": "Gastroenterology · room 204", "date": "Today", "time": "09:40" },
{ "av_class": "av-amber", "initial": "J", "name": "Joseph Obiano", "role": "Psychiatry · video call", "date": "Today", "time": "10:25" },
{ "av_class": "av-mint", "initial": "T", "name": "Timothy Jibrin", "role": "Hematology · room 117", "date": "Today", "time": "11:00" },
{ "av_class": "av-violet", "initial": "E", "name": "Elizabeth Kanu", "role": "Ophthalmology · room 09", "date": "Today", "time": "02:15" },
{ "av_class": "av-blue", "initial": "S", "name": "Simon Garba", "role": "Otolaryngology · room 21", "date": "Today", "time": "03:40" }
]
}
}
@@ -0,0 +1,548 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{data.brand_name}} — Clinic Operations Console</title>
<style>
:root {
--canvas: #eef2f6;
--surface: #ffffff;
--surface-muted: #f8fafc;
--surface-inverse: #0f172a;
--border: #e5e7eb;
--border-strong: #d6dbe3;
--fg: #0f172a;
--fg-muted: #64748b;
--fg-tertiary: #94a3b8;
--fg-on-inverse: #f8fafc;
--accent: #10b981;
--accent-strong: #059669;
--accent-soft: #d1fae5;
--pill-up-bg: #d1fae5; --pill-up-fg: #059669;
--pill-down-bg: #fee2e2; --pill-down-fg: #dc2626;
--pill-avail-bg: #d1fae5; --pill-avail-fg: #047857;
--pill-unav-bg: #fee2e2; --pill-unav-fg: #b91c1c;
--pill-leave-bg: #fef3c7; --pill-leave-fg: #b45309;
--stripe-blue: #bfdbfe; --stripe-amber: #fde68a; --stripe-mint: #a7f3d0;
--donut-blue: #bfdbfe; --donut-pink: #fbcfe8; --donut-mint: #ccfbf1;
--legend-blue: #3b82f6; --legend-amber: #f59e0b; --legend-pink: #ec4899; --legend-mint: #2dd4bf;
--av-orange-1: #fed7aa; --av-orange-2: #f97316; --av-orange-fg: #7c2d12;
--av-pink-1: #fbcfe8; --av-pink-2: #ec4899;
--av-mint-1: #a7f3d0; --av-mint-2: #10b981;
--av-blue-1: #bfdbfe; --av-blue-2: #3b82f6;
--av-violet-1: #ddd6fe; --av-violet-2: #7c3aed;
--av-amber-1: #fde68a; --av-amber-2: #f59e0b; --av-amber-fg: #78350f;
--av-rose-1: #fecaca; --av-rose-2: #ef4444;
--av-fg-light: #ffffff;
--r-card: 18px; --r-pill: 999px; --r-md: 14px; --r-sm: 10px; --r-xs: 8px;
--shadow-card: 0 1px 2px rgba(15,23,42,.04), 0 2px 6px rgba(15,23,42,.04);
--shadow-popover: 0 12px 28px rgba(2,6,23,.32);
--font-display: 'Plus Jakarta Sans', 'SF Pro Display', system-ui, sans-serif;
--font-body: 'Inter', system-ui, -apple-system, sans-serif;
}
* { box-sizing: border-box; }
body {
margin: 0; padding: 20px;
background: var(--canvas); color: var(--fg);
font: 14px/1.5 var(--font-body);
font-feature-settings: "tnum", "lnum";
display: grid; grid-template-columns: 240px 1fr; gap: 20px;
min-height: 100vh;
}
@media (max-width: 920px) {
body { grid-template-columns: 1fr; }
aside.sidebar { position: static !important; height: auto !important; }
}
.card, aside.sidebar {
background: var(--surface); border: 1px solid var(--border);
border-radius: var(--r-card); box-shadow: var(--shadow-card);
}
aside.sidebar {
padding: 18px; display: flex; flex-direction: column; gap: 16px;
position: sticky; top: 20px; height: calc(100vh - 40px);
}
.brand { display: flex; align-items: center; justify-content: space-between; padding: 0 6px; }
.brand-mark { display: flex; align-items: center; gap: 8px; font-family: var(--font-display); font-weight: 700; font-size: 17px; letter-spacing: -0.01em; }
.brand-leaf { width: 22px; height: 22px; border-radius: var(--r-pill); background: var(--accent); color: var(--surface); display: inline-flex; align-items: center; justify-content: center; }
.brand-toggle { color: var(--fg-tertiary); }
.nav-section { display: flex; flex-direction: column; gap: 2px; }
.nav-label { color: var(--fg-tertiary); font-size: 10px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.06em; padding: 8px 10px 4px; }
.nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: var(--r-sm); color: var(--fg-muted); font-size: 13px; }
.nav-item .ico { color: var(--fg-muted); }
.nav-item .count { margin-left: auto; background: var(--surface-muted); color: var(--fg-muted); border-radius: var(--r-pill); padding: 1px 8px; font-size: 11px; font-weight: 500; }
.nav-item .count:empty { display: none; }
.nav-item.active { background: var(--accent-soft); color: var(--accent-strong); font-weight: 600; }
.nav-item.active .ico { color: var(--accent-strong); }
.pro-card { margin-top: auto; background: var(--surface-muted); border-radius: var(--r-md); padding: 14px; display: flex; flex-direction: column; gap: 8px; }
.pro-tag { display: inline-flex; background: var(--fg); color: var(--surface); border-radius: var(--r-pill); padding: 2px 8px; font-size: 10px; font-weight: 600; width: fit-content; }
.pro-card h4 { margin: 0; font-size: 13px; font-weight: 600; font-family: var(--font-display); }
.pro-card p { margin: 0; font-size: 11px; color: var(--fg-muted); }
.pro-actions { display: flex; gap: 8px; margin-top: 4px; }
.pro-actions button { font-size: 11px; padding: 6px 12px; border-radius: var(--r-sm); border: 1px solid var(--border); background: var(--surface); color: var(--fg-muted); cursor: pointer; }
.pro-actions .primary { background: var(--accent); color: var(--surface); border-color: var(--accent); font-weight: 600; }
.user-row { display: flex; align-items: center; gap: 10px; padding: 10px 6px 0; border-top: 1px solid var(--border); }
.user-row .meta { display: flex; flex-direction: column; }
.user-row .name { font-size: 13px; font-weight: 600; }
.user-row .role { font-size: 11px; color: var(--fg-tertiary); }
.user-row .out { margin-left: auto; color: var(--fg-tertiary); }
.av { border-radius: var(--r-pill); flex-shrink: 0; display: inline-flex; align-items: center; justify-content: center; color: var(--av-fg-light); font-weight: 600; }
.av-32 { width: 32px; height: 32px; font-size: 12px; }
.av-40 { width: 40px; height: 40px; font-size: 14px; }
.av-orange { background: linear-gradient(135deg, var(--av-orange-1), var(--av-orange-2)); color: var(--av-orange-fg); }
.av-pink { background: linear-gradient(135deg, var(--av-pink-1), var(--av-pink-2)); }
.av-mint { background: linear-gradient(135deg, var(--av-mint-1), var(--av-mint-2)); }
.av-blue { background: linear-gradient(135deg, var(--av-blue-1), var(--av-blue-2)); }
.av-violet { background: linear-gradient(135deg, var(--av-violet-1), var(--av-violet-2)); }
.av-amber { background: linear-gradient(135deg, var(--av-amber-1), var(--av-amber-2)); color: var(--av-amber-fg); }
.av-rose { background: linear-gradient(135deg, var(--av-rose-1), var(--av-rose-2)); }
main { display: flex; flex-direction: column; gap: 24px; }
.greeting-row { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.greeting { font-family: var(--font-display); font-size: 26px; font-weight: 700; letter-spacing: -0.01em; }
.actions { display: flex; align-items: center; gap: 12px; }
.search { display: flex; align-items: center; gap: 8px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 9px 14px; width: 260px; color: var(--fg-tertiary); font-size: 13px; }
.search .kbd { margin-left: auto; font-size: 11px; border: 1px solid var(--border); padding: 1px 6px; border-radius: 4px; }
.btn { display: inline-flex; align-items: center; gap: 8px; padding: 9px 16px; border-radius: var(--r-sm); font-size: 13px; font-weight: 500; cursor: pointer; border: 1px solid var(--border); background: var(--surface); color: var(--fg); font-family: var(--font-body); }
.btn.primary { background: var(--accent); color: var(--surface); border-color: var(--accent); font-weight: 600; }
.kpi-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
@media (max-width: 1100px) { .kpi-strip { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .kpi-strip { grid-template-columns: 1fr; } }
.kpi { padding: 18px; display: flex; flex-direction: column; gap: 12px; min-height: 150px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-card); box-shadow: var(--shadow-card); }
.kpi-head { display: flex; align-items: center; gap: 10px; }
.kpi-glyph { width: 32px; height: 32px; border-radius: var(--r-sm); background: var(--accent-soft); color: var(--accent-strong); display: inline-flex; align-items: center; justify-content: center; }
.kpi-label { font-size: 13px; color: var(--fg-muted); font-weight: 500; flex: 1; }
.kpi-menu { color: var(--fg-tertiary); }
.kpi-num-row { display: flex; align-items: baseline; gap: 10px; }
.kpi-num { font-family: var(--font-display); font-size: 30px; font-weight: 700; line-height: 1.1; letter-spacing: -0.015em; }
.pill { display: inline-flex; align-items: center; gap: 4px; border-radius: var(--r-pill); padding: 2px 8px; font-size: 11px; font-weight: 600; }
.pill.up { background: var(--pill-up-bg); color: var(--pill-up-fg); }
.pill.down { background: var(--pill-down-bg); color: var(--pill-down-fg); }
.pill.avail { background: var(--pill-avail-bg); color: var(--pill-avail-fg); font-weight: 500; }
.pill.unav { background: var(--pill-unav-bg); color: var(--pill-unav-fg); font-weight: 500; }
.pill.leave { background: var(--pill-leave-bg); color: var(--pill-leave-fg); font-weight: 500; }
.kpi-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: auto; }
.kpi-cap { font-size: 12px; color: var(--fg-tertiary); line-height: 1.4; max-width: 60%; }
.kpi-strip-pat { height: 32px; flex-shrink: 0; border-radius: var(--r-xs); }
.stripe-amber { background: repeating-linear-gradient(135deg, var(--stripe-amber) 0 8px, var(--surface) 8px 16px); width: 96px; }
.stripe-blue { background: repeating-linear-gradient(135deg, var(--stripe-blue) 0 8px, var(--surface) 8px 16px); width: 120px; }
.stripe-mint { background: repeating-linear-gradient(135deg, var(--stripe-mint) 0 8px, var(--surface) 8px 16px); width: 96px; }
.kpi-mini-stat { font-size: 11px; color: var(--fg-tertiary); margin-top: 4px; text-align: right; }
.kpi-rooms-list { display: flex; flex-direction: column; gap: 6px; margin-top: auto; }
.kpi-rooms-list .row { display: flex; justify-content: space-between; font-size: 12px; }
.kpi-rooms-list .row .lbl { color: var(--fg-muted); display: flex; align-items: center; gap: 6px; }
.kpi-rooms-list .row .lbl::before { content: ""; width: 6px; height: 6px; border-radius: var(--r-pill); background: var(--fg-tertiary); }
.kpi-rooms-list .row .val { color: var(--fg); font-weight: 600; }
.mid-row { display: grid; grid-template-columns: 2fr 1fr; gap: 20px; }
@media (max-width: 1100px) { .mid-row { grid-template-columns: 1fr; } }
.card-pad { padding: 20px; }
.card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.card-title { display: flex; align-items: center; gap: 8px; font-family: var(--font-display); font-size: 15px; font-weight: 600; }
.card-title .ico-tile { width: 26px; height: 26px; border-radius: var(--r-sm); background: var(--surface-muted); color: var(--fg-muted); display: inline-flex; align-items: center; justify-content: center; }
.dropdown { display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border); border-radius: var(--r-sm); padding: 6px 10px; font-size: 12px; color: var(--fg-muted); background: var(--surface); }
.legend { display: flex; gap: 16px; margin-bottom: 8px; flex-wrap: wrap; }
.legend .lg { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--fg-muted); }
.legend .dot { width: 8px; height: 8px; border-radius: var(--r-pill); }
.cal-head { display: flex; align-items: center; justify-content: space-between; }
.cal-month { font-family: var(--font-display); font-size: 16px; font-weight: 600; }
.cal-nav { color: var(--fg-muted); display: inline-flex; gap: 6px; }
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; text-align: center; font-size: 12px; margin-top: 10px; position: relative; }
.cal-grid .dow { color: var(--fg-tertiary); font-weight: 500; padding: 6px 0; font-size: 11px; }
.cal-grid .day { height: 32px; display: inline-flex; align-items: center; justify-content: center; border-radius: var(--r-pill); }
.cal-grid .day.muted { color: var(--fg-tertiary); }
.cal-grid .day.active { background: var(--accent); color: var(--surface); font-weight: 700; position: relative; }
.cal-grid .day.active::after { content: ""; position: absolute; bottom: -7px; left: 50%; transform: translateX(-50%); width: 4px; height: 4px; border-radius: var(--r-pill); background: var(--accent); }
.activity-popover { background: var(--surface-inverse); color: var(--fg-on-inverse); border-radius: var(--r-md); padding: 14px; box-shadow: var(--shadow-popover); margin-top: 14px; }
.activity-popover h6 { margin: 0 0 10px; font-size: 12px; font-weight: 600; }
.activity-popover .ev { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.08); }
.activity-popover .ev:last-of-type { border-bottom: none; }
.activity-popover .ev .av-pop { width: 22px; height: 22px; border-radius: var(--r-pill); flex-shrink: 0; }
.activity-popover .ev .av-pop.blue { background: linear-gradient(135deg, var(--av-blue-1), var(--av-blue-2)); }
.activity-popover .ev .av-pop.pink { background: linear-gradient(135deg, var(--av-pink-1), var(--av-pink-2)); }
.activity-popover .ev .av-pop.violet { background: linear-gradient(135deg, var(--av-violet-1), var(--av-violet-2)); }
.activity-popover .ev .av-pop.mint { background: linear-gradient(135deg, var(--av-mint-1), var(--av-mint-2)); }
.activity-popover .ev .av-pop.amber { background: linear-gradient(135deg, var(--av-amber-1), var(--av-amber-2)); }
.activity-popover .ev .name { flex: 1; font-size: 11px; }
.activity-popover .ev .time { font-size: 10px; color: rgba(248,250,252,0.6); }
.activity-popover .add { margin-top: 8px; font-size: 11px; color: rgba(248,250,252,0.7); }
.bottom-row { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 20px; }
@media (max-width: 1100px) { .bottom-row { grid-template-columns: 1fr; } }
.donut-wrap { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 8px 0; }
.donut { position: relative; width: 180px; height: 180px; }
.donut .center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
.donut .center .lbl { font-size: 12px; color: var(--fg-tertiary); }
.donut .center .num { font-family: var(--font-display); font-size: 30px; font-weight: 700; line-height: 1; margin-top: 2px; }
.donut-legend { display: flex; gap: 18px; flex-wrap: wrap; justify-content: center; font-size: 12px; color: var(--fg-muted); }
.donut-legend .lg { display: inline-flex; align-items: center; gap: 6px; }
.donut-legend .lg .dot { width: 8px; height: 8px; border-radius: var(--r-pill); }
.donut-legend .lg .v { color: var(--fg); font-weight: 600; margin-right: 2px; }
.sched-stats { display: grid; grid-template-columns: 1fr 1fr 1fr; margin-bottom: 12px; }
.sched-stats .col { padding: 4px 12px; border-left: 1px solid var(--border); }
.sched-stats .col:first-child { border-left: none; padding-left: 0; }
.sched-stats .col .v { font-family: var(--font-display); font-size: 18px; font-weight: 700; line-height: 1.1; }
.sched-stats .col .v small { font-size: 11px; color: var(--fg-tertiary); font-weight: 400; margin-left: 2px; }
.sched-stats .col .lbl { font-size: 11px; color: var(--fg-tertiary); margin-top: 2px; }
.list-head { display: flex; align-items: center; gap: 4px; padding: 8px 0; border-bottom: 1px solid var(--border); font-size: 11px; color: var(--fg-tertiary); font-weight: 500; }
.list-row { display: flex; align-items: center; gap: 10px; padding: 12px 0; border-bottom: 1px solid var(--border); }
.list-row:last-child { border-bottom: none; }
.person { flex: 1; display: flex; flex-direction: column; }
.person .n { font-size: 13px; font-weight: 600; }
.person .r { font-size: 11px; color: var(--fg-tertiary); }
.pill.list { padding: 3px 10px; }
.appt-time { display: flex; flex-direction: column; align-items: flex-end; }
.appt-time .d { font-size: 11px; color: var(--fg-tertiary); }
.appt-time .t { font-size: 13px; font-weight: 600; }
.ic { width: 18px; height: 18px; stroke-width: 1.6; }
.ic-sm { width: 14px; height: 14px; stroke-width: 1.6; }
</style>
</head>
<body data-od-id="clinic-console-template">
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
<defs>
<symbol id="icon-dashboard" viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="3" width="7" height="9" rx="2"/><rect x="14" y="3" width="7" height="5" rx="2"/><rect x="14" y="12" width="7" height="9" rx="2"/><rect x="3" y="16" width="7" height="5" rx="2"/></symbol>
<symbol id="icon-message" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 11.5a8.4 8.4 0 0 1-9 8.4 8.4 8.4 0 0 1-3.8-.9L3 21l1.9-5.7A8.4 8.4 0 1 1 21 11.5z"/></symbol>
<symbol id="icon-schedule" viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></symbol>
<symbol id="icon-bell" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 8a6 6 0 0 0-12 0c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.7 21a2 2 0 0 1-3.4 0"/></symbol>
<symbol id="icon-card" viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="2" y="6" width="20" height="12" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/></symbol>
<symbol id="icon-user" viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="4"/><path d="M4 21v-1a8 8 0 0 1 16 0v1"/></symbol>
<symbol id="icon-pill" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M10 2v4M14 2v4M5 10h14M6 6h12a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2zM12 13v6M9 16h6"/></symbol>
<symbol id="icon-bed" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 17h18M5 17v-5a3 3 0 0 1 3-3h8a3 3 0 0 1 3 3v5"/></symbol>
<symbol id="icon-check-square" viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="4" width="18" height="18" rx="2"/><path d="M8 14l3 3 5-5"/></symbol>
<symbol id="icon-people" viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="8" r="3"/><path d="M3 21v-1a6 6 0 0 1 12 0v1"/><circle cx="17" cy="6" r="2.5"/><path d="M14 14a4 4 0 0 1 7 4"/></symbol>
<symbol id="icon-leaf" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M5 12c4-8 14-8 14 0 0 6-7 8-7 8s-7-2-7-8z"/></symbol>
<symbol id="icon-clock" viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="9"/><path d="M12 6v6l4 2"/></symbol>
<symbol id="icon-stethoscope" viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="4" y="11" width="16" height="9" rx="2"/><path d="M8 11V7a4 4 0 1 1 8 0v4"/></symbol>
<symbol id="icon-search" viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="11" cy="11" r="7"/><path d="M21 21l-4.35-4.35"/></symbol>
<symbol id="icon-download" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/></symbol>
<symbol id="icon-plus" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 5v14M5 12h14"/></symbol>
<symbol id="icon-chev-left" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M15 18l-6-6 6-6"/></symbol>
<symbol id="icon-chev-right" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 18l6-6-6-6"/></symbol>
<symbol id="icon-chev-down" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M6 9l6 6 6-6"/></symbol>
<symbol id="icon-collapse" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 6l-6 6 6 6M21 6l-6 6 6 6"/></symbol>
<symbol id="icon-logout" viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4M16 17l5-5-5-5M21 12H9"/></symbol>
</defs>
</svg>
<aside class="sidebar" data-od-id="sidebar">
<div class="brand">
<div class="brand-mark">
<span class="brand-leaf"><svg class="ic-sm"><use href="#icon-leaf"/></svg></span>
{{data.brand_name}}
</div>
<span class="brand-toggle"><svg class="ic"><use href="#icon-collapse"/></svg></span>
</div>
<div class="nav-section">
<div class="nav-label">{{data.nav_main_label}}</div>
<a class="nav-item {{data.nav_main.0.active_class}}"><svg class="ic"><use href="#icon-dashboard"/></svg>{{data.nav_main.0.label}}<span class="count">{{data.nav_main.0.count}}</span></a>
<a class="nav-item {{data.nav_main.1.active_class}}"><svg class="ic"><use href="#icon-message"/></svg>{{data.nav_main.1.label}}<span class="count">{{data.nav_main.1.count}}</span></a>
<a class="nav-item {{data.nav_main.2.active_class}}"><svg class="ic"><use href="#icon-schedule"/></svg>{{data.nav_main.2.label}}<span class="count">{{data.nav_main.2.count}}</span></a>
<a class="nav-item {{data.nav_main.3.active_class}}"><svg class="ic"><use href="#icon-bell"/></svg>{{data.nav_main.3.label}}<span class="count">{{data.nav_main.3.count}}</span></a>
<a class="nav-item {{data.nav_main.4.active_class}}"><svg class="ic"><use href="#icon-card"/></svg>{{data.nav_main.4.label}}<span class="count">{{data.nav_main.4.count}}</span></a>
</div>
<div class="nav-section">
<div class="nav-label">{{data.nav_management_label}}</div>
<a class="nav-item {{data.nav_management.0.active_class}}"><svg class="ic"><use href="#icon-user"/></svg>{{data.nav_management.0.label}}<span class="count">{{data.nav_management.0.count}}</span></a>
<a class="nav-item {{data.nav_management.1.active_class}}"><svg class="ic"><use href="#icon-pill"/></svg>{{data.nav_management.1.label}}<span class="count">{{data.nav_management.1.count}}</span></a>
<a class="nav-item {{data.nav_management.2.active_class}}"><svg class="ic"><use href="#icon-bed"/></svg>{{data.nav_management.2.label}}<span class="count">{{data.nav_management.2.count}}</span></a>
<a class="nav-item {{data.nav_management.3.active_class}}"><svg class="ic"><use href="#icon-check-square"/></svg>{{data.nav_management.3.label}}<span class="count">{{data.nav_management.3.count}}</span></a>
<a class="nav-item {{data.nav_management.4.active_class}}"><svg class="ic"><use href="#icon-people"/></svg>{{data.nav_management.4.label}}<span class="count">{{data.nav_management.4.count}}</span></a>
</div>
<div class="pro-card">
<span class="pro-tag">{{data.pro_card.tag}}</span>
<h4>{{data.pro_card.title}}</h4>
<p>{{data.pro_card.body}}</p>
<div class="pro-actions">
<button class="primary">{{data.pro_card.primary_label}}</button>
<button>{{data.pro_card.secondary_label}}</button>
</div>
</div>
<div class="user-row">
<span class="av av-32 {{data.user.av_class}}">{{data.user.initial}}</span>
<div class="meta">
<span class="name">{{data.user.name}}</span>
<span class="role">{{data.user.role}}</span>
</div>
<span class="out"><svg class="ic"><use href="#icon-logout"/></svg></span>
</div>
</aside>
<main>
<header class="greeting-row" data-od-id="greeting">
<div class="greeting">{{data.greeting}}</div>
<div class="actions">
<div class="search">
<svg class="ic-sm"><use href="#icon-search"/></svg>
{{data.search_placeholder}}
<span class="kbd">{{data.search_shortcut}}</span>
</div>
<button class="btn"><svg class="ic-sm"><use href="#icon-download"/></svg>{{data.secondary_action_label}}</button>
<button class="btn primary"><svg class="ic-sm"><use href="#icon-plus"/></svg>{{data.primary_action_label}}</button>
</div>
</header>
<section class="kpi-strip" data-od-id="kpi-strip">
<div class="kpi">
<div class="kpi-head">
<span class="kpi-glyph"><svg class="ic-sm"><use href="#icon-user"/></svg></span>
<span class="kpi-label">{{data.kpi_a.label}}</span>
<span class="kpi-menu">⋯</span>
</div>
<div class="kpi-num-row"><div class="kpi-num">{{data.kpi_a.value}}</div><span class="pill {{data.kpi_a.trend_class}}">{{data.kpi_a.trend_label}}</span></div>
<div class="kpi-foot">
<div class="kpi-cap">{{data.kpi_a.caption}}</div>
<div class="kpi-strip-pat {{data.kpi_a.strip_class}}"></div>
</div>
</div>
<div class="kpi">
<div class="kpi-head">
<span class="kpi-glyph"><svg class="ic-sm"><use href="#icon-schedule"/></svg></span>
<span class="kpi-label">{{data.kpi_b.label}}</span>
<span class="kpi-menu">⋯</span>
</div>
<div class="kpi-num-row"><div class="kpi-num">{{data.kpi_b.value}}</div><span class="pill {{data.kpi_b.trend_class}}">{{data.kpi_b.trend_label}}</span></div>
<div class="kpi-foot">
<div class="kpi-cap">{{data.kpi_b.caption}}</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;">
<div class="kpi-strip-pat {{data.kpi_b.strip_class}}"></div>
<div class="kpi-mini-stat">{{data.kpi_b.mini_stat}}</div>
</div>
</div>
</div>
<div class="kpi">
<div class="kpi-head">
<span class="kpi-glyph"><svg class="ic-sm"><use href="#icon-bed"/></svg></span>
<span class="kpi-label">{{data.kpi_c.label}}</span>
<span class="kpi-menu">⋯</span>
</div>
<div class="kpi-num-row"><div class="kpi-num">{{data.kpi_c.value}}</div><span class="pill {{data.kpi_c.trend_class}}">{{data.kpi_c.trend_label}}</span></div>
<div class="kpi-rooms-list">
<div class="row"><span class="lbl">{{data.kpi_c.rows.0.label}}</span><span class="val">{{data.kpi_c.rows.0.value}}</span></div>
<div class="row"><span class="lbl">{{data.kpi_c.rows.1.label}}</span><span class="val">{{data.kpi_c.rows.1.value}}</span></div>
</div>
</div>
<div class="kpi">
<div class="kpi-head">
<span class="kpi-glyph"><svg class="ic-sm"><use href="#icon-people"/></svg></span>
<span class="kpi-label">{{data.kpi_d.label}}</span>
<span class="kpi-menu">⋯</span>
</div>
<div class="kpi-num-row"><div class="kpi-num">{{data.kpi_d.value}}</div><span class="pill {{data.kpi_d.trend_class}}">{{data.kpi_d.trend_label}}</span></div>
<div class="kpi-foot">
<div class="kpi-cap">{{data.kpi_d.caption}}</div>
<div style="display:flex;flex-direction:column;align-items:flex-end;">
<div class="kpi-strip-pat {{data.kpi_d.strip_class}}"></div>
<div class="kpi-mini-stat">{{data.kpi_d.mini_stat}}</div>
</div>
</div>
</div>
</section>
<section class="mid-row" data-od-id="patient-overview-and-calendar">
<div class="card card-pad">
<div class="card-head">
<div class="card-title">
<span class="ico-tile"><svg class="ic-sm"><use href="#icon-clock"/></svg></span>
{{data.chart.title}}
</div>
<div class="dropdown">{{data.chart.dropdown_label}}<svg class="ic-sm"><use href="#icon-chev-down"/></svg></div>
</div>
<div class="legend">
<span class="lg"><span class="dot" style="background: var(--accent);"></span>{{data.chart.legend_a}}</span>
<span class="lg"><span class="dot" style="background: var(--legend-blue);"></span>{{data.chart.legend_b}}</span>
<span class="lg"><span class="dot" style="background: var(--legend-amber);"></span>{{data.chart.legend_c}}</span>
</div>
<svg viewBox="0 0 600 240" preserveAspectRatio="none" style="width:100%; height:240px; display:block;">
<defs>
<pattern id="stripeBlue" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)">
<rect width="12" height="12" style="fill: var(--surface);" />
<rect width="6" height="12" style="fill: var(--stripe-blue);" />
</pattern>
<pattern id="stripeMint" width="12" height="12" patternUnits="userSpaceOnUse" patternTransform="rotate(-45)">
<rect width="12" height="12" style="fill: var(--surface);" />
<rect width="6" height="12" style="fill: var(--stripe-mint);" opacity="0.7" />
</pattern>
</defs>
<g style="stroke: var(--border);" stroke-width="1">
<line x1="0" y1="48" x2="600" y2="48" />
<line x1="0" y1="96" x2="600" y2="96" />
<line x1="0" y1="144" x2="600" y2="144" />
<line x1="0" y1="192" x2="600" y2="192" />
</g>
<g>
<rect x="{{data.chart.bars.0.x}}" y="{{data.chart.bars.0.y}}" width="22" height="{{data.chart.bars.0.h}}" rx="6" fill="url(#stripeMint)" />
<rect x="{{data.chart.bars.1.x}}" y="{{data.chart.bars.1.y}}" width="22" height="{{data.chart.bars.1.h}}" rx="6" fill="url(#stripeBlue)" />
<rect x="{{data.chart.bars.2.x}}" y="{{data.chart.bars.2.y}}" width="22" height="{{data.chart.bars.2.h}}" rx="6" fill="url(#stripeMint)" />
<rect x="{{data.chart.bars.3.x}}" y="{{data.chart.bars.3.y}}" width="22" height="{{data.chart.bars.3.h}}" rx="6" fill="url(#stripeBlue)" />
<rect x="{{data.chart.bars.4.x}}" y="{{data.chart.bars.4.y}}" width="22" height="{{data.chart.bars.4.h}}" rx="6" fill="url(#stripeMint)" />
<rect x="{{data.chart.bars.5.x}}" y="{{data.chart.bars.5.y}}" width="22" height="{{data.chart.bars.5.h}}" rx="6" fill="url(#stripeBlue)" style="stroke: var(--accent);" stroke-width="2" />
<rect x="{{data.chart.bars.6.x}}" y="{{data.chart.bars.6.y}}" width="22" height="{{data.chart.bars.6.h}}" rx="6" fill="url(#stripeMint)" />
<rect x="{{data.chart.bars.7.x}}" y="{{data.chart.bars.7.y}}" width="22" height="{{data.chart.bars.7.h}}" rx="6" fill="url(#stripeBlue)" />
<rect x="{{data.chart.bars.8.x}}" y="{{data.chart.bars.8.y}}" width="22" height="{{data.chart.bars.8.h}}" rx="6" fill="url(#stripeMint)" />
<rect x="{{data.chart.bars.9.x}}" y="{{data.chart.bars.9.y}}" width="22" height="{{data.chart.bars.9.h}}" rx="6" fill="url(#stripeBlue)" />
<rect x="{{data.chart.bars.10.x}}" y="{{data.chart.bars.10.y}}" width="22" height="{{data.chart.bars.10.h}}" rx="6" fill="url(#stripeMint)" />
<rect x="{{data.chart.bars.11.x}}" y="{{data.chart.bars.11.y}}" width="22" height="{{data.chart.bars.11.h}}" rx="6" fill="url(#stripeBlue)" />
<rect x="{{data.chart.bars.12.x}}" y="{{data.chart.bars.12.y}}" width="22" height="{{data.chart.bars.12.h}}" rx="6" fill="url(#stripeMint)" />
<rect x="{{data.chart.bars.13.x}}" y="{{data.chart.bars.13.y}}" width="22" height="{{data.chart.bars.13.h}}" rx="6" fill="url(#stripeBlue)" />
</g>
<g style="fill: var(--fg-tertiary);" font-size="11" text-anchor="middle">
<text x="46" y="232">{{data.chart.x_labels.0}}</text>
<text x="132" y="232">{{data.chart.x_labels.1}}</text>
<text x="218" y="232">{{data.chart.x_labels.2}}</text>
<text x="304" y="232">{{data.chart.x_labels.3}}</text>
<text x="390" y="232">{{data.chart.x_labels.4}}</text>
<text x="476" y="232">{{data.chart.x_labels.5}}</text>
<text x="562" y="232">{{data.chart.x_labels.6}}</text>
</g>
</svg>
</div>
<div class="card card-pad" data-od-id="calendar">
<div class="cal-head">
<span class="cal-nav"><svg class="ic-sm"><use href="#icon-chev-left"/></svg></span>
<div class="cal-month">{{data.calendar.month_label}}</div>
<span class="cal-nav"><svg class="ic-sm"><use href="#icon-chev-right"/></svg></span>
</div>
<div class="cal-grid">
<div class="dow">{{data.calendar.dow.0}}</div><div class="dow">{{data.calendar.dow.1}}</div><div class="dow">{{data.calendar.dow.2}}</div><div class="dow">{{data.calendar.dow.3}}</div><div class="dow">{{data.calendar.dow.4}}</div><div class="dow">{{data.calendar.dow.5}}</div><div class="dow">{{data.calendar.dow.6}}</div>
<div class="day {{data.calendar.days.0.modifier}}">{{data.calendar.days.0.label}}</div><div class="day {{data.calendar.days.1.modifier}}">{{data.calendar.days.1.label}}</div><div class="day {{data.calendar.days.2.modifier}}">{{data.calendar.days.2.label}}</div><div class="day {{data.calendar.days.3.modifier}}">{{data.calendar.days.3.label}}</div><div class="day {{data.calendar.days.4.modifier}}">{{data.calendar.days.4.label}}</div><div class="day {{data.calendar.days.5.modifier}}">{{data.calendar.days.5.label}}</div><div class="day {{data.calendar.days.6.modifier}}">{{data.calendar.days.6.label}}</div>
<div class="day {{data.calendar.days.7.modifier}}">{{data.calendar.days.7.label}}</div><div class="day {{data.calendar.days.8.modifier}}">{{data.calendar.days.8.label}}</div><div class="day {{data.calendar.days.9.modifier}}">{{data.calendar.days.9.label}}</div><div class="day {{data.calendar.days.10.modifier}}">{{data.calendar.days.10.label}}</div><div class="day {{data.calendar.days.11.modifier}}">{{data.calendar.days.11.label}}</div><div class="day {{data.calendar.days.12.modifier}}">{{data.calendar.days.12.label}}</div><div class="day {{data.calendar.days.13.modifier}}">{{data.calendar.days.13.label}}</div>
<div class="day {{data.calendar.days.14.modifier}}">{{data.calendar.days.14.label}}</div><div class="day {{data.calendar.days.15.modifier}}">{{data.calendar.days.15.label}}</div><div class="day {{data.calendar.days.16.modifier}}">{{data.calendar.days.16.label}}</div><div class="day {{data.calendar.days.17.modifier}}">{{data.calendar.days.17.label}}</div><div class="day {{data.calendar.days.18.modifier}}">{{data.calendar.days.18.label}}</div><div class="day {{data.calendar.days.19.modifier}}">{{data.calendar.days.19.label}}</div><div class="day {{data.calendar.days.20.modifier}}">{{data.calendar.days.20.label}}</div>
<div class="day {{data.calendar.days.21.modifier}}">{{data.calendar.days.21.label}}</div><div class="day {{data.calendar.days.22.modifier}}">{{data.calendar.days.22.label}}</div><div class="day {{data.calendar.days.23.modifier}}">{{data.calendar.days.23.label}}</div><div class="day {{data.calendar.days.24.modifier}}">{{data.calendar.days.24.label}}</div><div class="day {{data.calendar.days.25.modifier}}">{{data.calendar.days.25.label}}</div><div class="day {{data.calendar.days.26.modifier}}">{{data.calendar.days.26.label}}</div><div class="day {{data.calendar.days.27.modifier}}">{{data.calendar.days.27.label}}</div>
<div class="day {{data.calendar.days.28.modifier}}">{{data.calendar.days.28.label}}</div><div class="day {{data.calendar.days.29.modifier}}">{{data.calendar.days.29.label}}</div><div class="day {{data.calendar.days.30.modifier}}">{{data.calendar.days.30.label}}</div><div class="day {{data.calendar.days.31.modifier}}">{{data.calendar.days.31.label}}</div><div class="day {{data.calendar.days.32.modifier}}">{{data.calendar.days.32.label}}</div><div class="day {{data.calendar.days.33.modifier}}">{{data.calendar.days.33.label}}</div><div class="day {{data.calendar.days.34.modifier}}">{{data.calendar.days.34.label}}</div>
</div>
<div class="activity-popover" data-od-id="activity-popover">
<h6>{{data.activity.title}}</h6>
<div class="ev"><span class="av-pop {{data.activity.events.0.av_class}}"></span><span class="name">{{data.activity.events.0.name}}</span><span class="time">{{data.activity.events.0.time}}</span></div>
<div class="ev"><span class="av-pop {{data.activity.events.1.av_class}}"></span><span class="name">{{data.activity.events.1.name}}</span><span class="time">{{data.activity.events.1.time}}</span></div>
<div class="ev"><span class="av-pop {{data.activity.events.2.av_class}}"></span><span class="name">{{data.activity.events.2.name}}</span><span class="time">{{data.activity.events.2.time}}</span></div>
<div class="add">{{data.activity.add_label}}</div>
</div>
</div>
</section>
<section class="bottom-row">
<div class="card card-pad" data-od-id="top-clinics">
<div class="card-head">
<div class="card-title">
<span class="ico-tile"><svg class="ic-sm"><use href="#icon-stethoscope"/></svg></span>
{{data.donut.title}}
</div>
<span class="kpi-menu">⋯</span>
</div>
<div class="donut-wrap">
<div class="donut">
<svg viewBox="0 0 100 100" style="width:100%; height:100%; transform: rotate(-90deg);">
<circle cx="50" cy="50" r="38" fill="none" stroke-width="14" style="stroke: var(--donut-blue);" />
<circle cx="50" cy="50" r="38" fill="none" stroke-width="14" style="stroke: var(--donut-pink);" stroke-dasharray="{{data.donut.segment_b.dasharray}}" stroke-dashoffset="{{data.donut.segment_b.dashoffset}}" />
<circle cx="50" cy="50" r="38" fill="none" stroke-width="14" style="stroke: var(--donut-mint);" stroke-dasharray="{{data.donut.segment_c.dasharray}}" stroke-dashoffset="{{data.donut.segment_c.dashoffset}}" />
</svg>
<div class="center">
<span class="lbl">{{data.donut.center_label}}</span>
<span class="num">{{data.donut.center_num}}</span>
</div>
</div>
<div class="donut-legend">
<div class="lg"><span class="dot" style="background: var(--legend-blue);"></span><span class="v">{{data.donut.legend.0.value}}</span> {{data.donut.legend.0.label}}</div>
<div class="lg"><span class="dot" style="background: var(--legend-pink);"></span><span class="v">{{data.donut.legend.1.value}}</span> {{data.donut.legend.1.label}}</div>
<div class="lg"><span class="dot" style="background: var(--legend-mint);"></span><span class="v">{{data.donut.legend.2.value}}</span> {{data.donut.legend.2.label}}</div>
</div>
</div>
</div>
<div class="card card-pad" data-od-id="doctor-schedule">
<div class="card-head">
<div class="card-title">
<span class="ico-tile"><svg class="ic-sm"><use href="#icon-schedule"/></svg></span>
{{data.schedule.title}}
</div>
<span class="kpi-menu">⋯</span>
</div>
<div class="sched-stats">
<div class="col"><div class="v">{{data.schedule.stats.0.value}} <small>{{data.schedule.stats.0.small}}</small></div><div class="lbl">{{data.schedule.stats.0.label}}</div></div>
<div class="col"><div class="v">{{data.schedule.stats.1.value}} <small>{{data.schedule.stats.1.small}}</small></div><div class="lbl">{{data.schedule.stats.1.label}}</div></div>
<div class="col"><div class="v">{{data.schedule.stats.2.value}} <small>{{data.schedule.stats.2.small}}</small></div><div class="lbl">{{data.schedule.stats.2.label}}</div></div>
</div>
<div class="list-head">{{data.schedule.list_header_label}}<svg class="ic-sm"><use href="#icon-chev-down"/></svg></div>
<div class="list-row">
<span class="av av-32 {{data.schedule.doctors.0.av_class}}">{{data.schedule.doctors.0.initial}}</span>
<div class="person"><span class="n">{{data.schedule.doctors.0.name}}</span><span class="r">{{data.schedule.doctors.0.role}}</span></div>
<span class="pill list {{data.schedule.doctors.0.status_class}}">{{data.schedule.doctors.0.status_label}}</span>
</div>
<div class="list-row">
<span class="av av-32 {{data.schedule.doctors.1.av_class}}">{{data.schedule.doctors.1.initial}}</span>
<div class="person"><span class="n">{{data.schedule.doctors.1.name}}</span><span class="r">{{data.schedule.doctors.1.role}}</span></div>
<span class="pill list {{data.schedule.doctors.1.status_class}}">{{data.schedule.doctors.1.status_label}}</span>
</div>
<div class="list-row">
<span class="av av-32 {{data.schedule.doctors.2.av_class}}">{{data.schedule.doctors.2.initial}}</span>
<div class="person"><span class="n">{{data.schedule.doctors.2.name}}</span><span class="r">{{data.schedule.doctors.2.role}}</span></div>
<span class="pill list {{data.schedule.doctors.2.status_class}}">{{data.schedule.doctors.2.status_label}}</span>
</div>
<div class="list-row">
<span class="av av-32 {{data.schedule.doctors.3.av_class}}">{{data.schedule.doctors.3.initial}}</span>
<div class="person"><span class="n">{{data.schedule.doctors.3.name}}</span><span class="r">{{data.schedule.doctors.3.role}}</span></div>
<span class="pill list {{data.schedule.doctors.3.status_class}}">{{data.schedule.doctors.3.status_label}}</span>
</div>
</div>
<div class="card card-pad" data-od-id="appointments">
<div class="card-head">
<div class="card-title">
<span class="ico-tile"><svg class="ic-sm"><use href="#icon-check-square"/></svg></span>
{{data.appointments.title}}
</div>
<span class="kpi-menu">⋯</span>
</div>
<div class="list-row">
<span class="av av-40 {{data.appointments.list.0.av_class}}">{{data.appointments.list.0.initial}}</span>
<div class="person"><span class="n">{{data.appointments.list.0.name}}</span><span class="r">{{data.appointments.list.0.role}}</span></div>
<div class="appt-time"><span class="d">{{data.appointments.list.0.date}}</span><span class="t">{{data.appointments.list.0.time}}</span></div>
</div>
<div class="list-row">
<span class="av av-40 {{data.appointments.list.1.av_class}}">{{data.appointments.list.1.initial}}</span>
<div class="person"><span class="n">{{data.appointments.list.1.name}}</span><span class="r">{{data.appointments.list.1.role}}</span></div>
<div class="appt-time"><span class="d">{{data.appointments.list.1.date}}</span><span class="t">{{data.appointments.list.1.time}}</span></div>
</div>
<div class="list-row">
<span class="av av-40 {{data.appointments.list.2.av_class}}">{{data.appointments.list.2.initial}}</span>
<div class="person"><span class="n">{{data.appointments.list.2.name}}</span><span class="r">{{data.appointments.list.2.role}}</span></div>
<div class="appt-time"><span class="d">{{data.appointments.list.2.date}}</span><span class="t">{{data.appointments.list.2.time}}</span></div>
</div>
<div class="list-row">
<span class="av av-40 {{data.appointments.list.3.av_class}}">{{data.appointments.list.3.initial}}</span>
<div class="person"><span class="n">{{data.appointments.list.3.name}}</span><span class="r">{{data.appointments.list.3.role}}</span></div>
<div class="appt-time"><span class="d">{{data.appointments.list.3.date}}</span><span class="t">{{data.appointments.list.3.time}}</span></div>
</div>
<div class="list-row">
<span class="av av-40 {{data.appointments.list.4.av_class}}">{{data.appointments.list.4.initial}}</span>
<div class="person"><span class="n">{{data.appointments.list.4.name}}</span><span class="r">{{data.appointments.list.4.role}}</span></div>
<div class="appt-time"><span class="d">{{data.appointments.list.4.date}}</span><span class="t">{{data.appointments.list.4.time}}</span></div>
</div>
</div>
</section>
</main>
</body>
</html>