Merge pull request 'Feat/ui design tokens' (#43) from feat/ui-design-tokens into dev

Reviewed-on: #43
This commit was merged in pull request #43.
This commit is contained in:
2026-09-14 21:32:14 -04:00
7 changed files with 755 additions and 0 deletions
+136
View File
@@ -0,0 +1,136 @@
# Admin spec — parent phone card
Screens `1d`–`1i`. One card, `nestquest-admin-card`, four tabs. Reference viewport
390×844. Plain CubeCraft styling and plain domain words — no parchment, no Cinzel, no
quest vocabulary in this surface.
## 1. Shell
- Page `--nq-a-page`; cards `--nq-a-surface` with 1px `--nq-a-border`.
- **Header** white, 1px bottom border: screen title 24px/800, tracking −.02em, plus a
12px/600 `--nq-a-ink-secondary` sub-line. Optional trailing action (icon square 36px,
or a gradient `New` button).
- **Tab bar** absolute bottom, `--nq-a-tabbar-height` 70px + 12px bottom padding, white,
1px top border, 4 items: `house` Today · `list-checks` Tasks · `calendar-days` Schedule ·
`history` History. Active icon and label `--nq-brand-blue` at 700; inactive icon
`--nq-a-ink-tertiary`, label `--nq-a-ink-secondary` 600.
- **Every scroll column ends with 92px bottom padding** so content clears the tab bar.
This was a real defect in review — do not omit it.
- Two densities exist. Ship **compact**; `--nq-a-density-*` carries the roomy values.
- Everything this card mutates goes through a NestQuest service, which enforces the admin
allowlist server-side. The card must assume it can be rendered by a non-admin and still
be safe — hiding controls is not the boundary (D-003).
## 2. Today (1d compact, 1h roomy)
### 2.1 Header
Title `Today`; sub `<weekday>, <Month D> · cycle day N of 14`; trailing `settings` square.
### 2.2 Stat row
Three equal cards, 8px gap, padding 12px: numeral 22px/800 (done in `--nq-brand-blue`,
remaining in `--nq-a-ink`, overdue in `--nq-a-danger`), label 10px/600 uppercase tracking
.08em `--nq-a-ink-secondary` — `Done`, `Remaining`, `Overdue`.
### 2.3 Child rows
One card, rows divided by `--nq-a-divider`, row padding 11px 13px:
30px gradient avatar square (radius 7px, initial 13px/800 white) · name 14px/700 ·
meta 11px/600 (`3 of 7 · 1 overdue`; all-done in `--nq-a-success`; away reads
`Away · returns <Mon D>`) · 82×6px progress track with gradient fill. An away child's
avatar is flat `#9898A8` and the row carries a lowercase `override` pill when the absence
comes from a presence override rather than the pattern.
### 2.4 Roomy variant (1h)
Header becomes a hero: kicker 11px/700 uppercase, `8 of 12 done` 32px/800, 10px gradient
progress bar, 13px/600 sub-line. Each child gets its own 12px-radius card with
`0 4px 12px rgba(0,0,0,.06)`, 48px avatar, 19px/800 name, right-aligned `3/7` +
status line, and its overdue item inlined as a `--nq-a-danger-bg` row with a
`Mark done` action.
### 2.5 Needs attention
Section label 11px/700 uppercase, then one card of event rows: Lucide glyph 18px ·
title `<Quest> · <Child>` 13px/700 · meta 11px/600 (`Overdue since 9:00 AM` in
`--nq-a-danger`; `Completed 7:04 AM from panel` in `--nq-a-ink-secondary`) · trailing
text action `Mark done` (`--nq-brand-blue`) or `Undo` (`--nq-brand-purple`).
### 2.6 Permission note
An info strip (`--nq-a-info-bg`, `info` glyph, 11.5px/600 `--nq-a-info-ink`):
"Undo is admin-only. A panel tap can never reverse a completion." Keep this copy — it is
the one place the security model is explained to the user.
## 3. Definitions (1e)
### 3.1 List
Header `Tasks` / `7 definitions · 3 children` + gradient `New` button. Filter chips row
(All / one per child): active `#18181D` fill white text, inactive white with border.
Rows: glyph 17px · title 13.5px/700 · meta 11px/600 `<assignees> · <recurrence> · <windows>`
· `chevron-right`. An inactive definition renders the whole row at
`--nq-a-ink-tertiary` with `· inactive` in the meta — never hard-deleted (glossary).
The row being edited takes `--nq-a-selected` with brand-blue glyph, `Editing` meta and
`chevron-down`.
### 3.2 Edit sheet
Bottom sheet, `--nq-a-radius-sheet`, `--nq-a-sheet-shadow`, 38×4px grabber, padding
14px 18px 24px. Title 19px/800. Fields, 11px gap, each with a 10px/700 uppercase label:
| Field | Control |
|---|---|
| Assigned children | multi-select row → sheet; shows `Declan` or `All three` |
| Repeats | select (Daily / Weekly / Monthly / Yearly / Custom + interval) |
| Due time | time field per window |
| Window | 3-up segmented, **multi-select** — a definition may own several |
| Skip on away days | toggle, gradient when on, sub "No instance when the child is absent" |
Footer: `Cancel` (flex 1, bordered) + `Save changes` (flex 2, gradient), both 13px vertical
padding. Sheet enters over the dimmed list in `--nq-dur-modal`.
### 3.3 Icon picker
Each definition carries a Lucide glyph, shown on both surfaces. **The allowed subset is not
yet defined** — pick 24–40 household-relevant glyphs and ship them as a grid picker.
## 4. Schedule (1f)
- Header `Schedule` / `2-week cycle · anchor <weekday>, <Mon D>`.
- **Month grid** card: month label 11px/700 uppercase + chevron pair; weekday initials
10px/700 `--nq-a-ink-tertiary`; day cells `aspect-ratio: 1`, radius 6px —
rotation-home days `--nq-a-selected` with `#201F7A` numerals, away days
`--nq-a-page` with `--nq-a-ink-secondary`, override days `--nq-a-warning-bg` with
`--nq-a-warning-ink` and a 1px warning border, today filled with the brand gradient in
white 800. Legend row beneath, 10.5px/600, one swatch per state.
- **Presence pattern** card: one row per child, name 13px/700 + rule 12px/600
(`Weeks 1 & 3 of cycle`, `Every day`).
- **Overrides** card: `Add` text action; rows with `calendar-x` / `calendar-check`,
title `<Child> away · <Mon D – D>`, meta reason, trailing `trash-2`.
- Cycle position is always computed from the anchor date. **Never** from ISO week numbers
or week parity (D-004).
## 5. History (1g)
- Header `History` / `Append-only event log · last 14 days` + bordered `CSV` button
(`download` glyph) that exports the filtered range.
- Stat row: `91%` On time · `128` Completed · `7` Missed, numerals 20px/800.
- Filter chips: `All events` / `Reversals` / `Missed`.
- Day-grouped sections (label 11px/700 uppercase) of event rows: 7px status dot
(green completed, `--nq-a-reversal` un-completed, `--nq-a-danger` missed) ·
title `<Quest> · <Child>` 12.5px/700 · meta 10.5px/600 giving event type, actor and
on-time flag (`completed · panel (Declan) · on time`, `uncompleted · joshua (admin)`,
`missed · nightly sweep`, `completed · panel (Declan) · late 22 min`) · time 11px/600.
- Footer strip (`--nq-a-page`, `lock` glyph): "Events are never edited or deleted. A
reversal is its own row." Rows are never editable in the UI (D-005).
## 6. Presence override editor (1i)
Pushed screen with a back chevron; title `Presence override` / sub "Beats the custody
pattern for these dates".
- Form card (roomy density, 18px padding, 16px gaps):
**Child** 3-up segmented, selected = gradient;
**Status for these dates** 2-up segmented (`Away` / `Home`), selected = `#18181D`;
**From** / **Through** date fields side by side, 13px padding, 14px/700 values;
**Reason (optional)** text field.
- **Consequence warning** — `--nq-a-warning-bg` card with `alert-circle`:
headline `This removes N upcoming tasks` 13.5px/700 `--nq-a-warning-ink`, body 12px/600
`--nq-a-warning-ink-2` naming the child, the date range, and stating "Completed days are
untouched." The count must be computed live from the materialiser before saving.
- Footer: `Cancel` (flex 1, white, bordered) + `Save override` (flex 2, gradient), 15px
vertical padding.
- Saving must never delete an instance that already has a completion event.
+82
View File
@@ -0,0 +1,82 @@
# NestQuest — design handoff
Approved UI design for the NestQuest Home Assistant integration, ready to implement.
Source of truth for visuals: the mockup file `NestQuest Mockups.dc.html` (9 screens,
option ids `1a`–`1j`). This folder is the written form of those screens.
## What is in here
| File | Contents |
|---|---|
| `PANEL-SPEC.md` | Kids' DAKOS wall panel: party board, quest log, confirm, quest complete |
| `ADMIN-SPEC.md` | Parent phone admin: today, definitions, schedule, history, overrides |
| `ENTITIES-AND-SERVICES.md` | Every screen mapped to the HA entities, services and events it needs |
| `IMPLEMENTATION-ORDER.md` | Build order, with the decisions that change existing code first |
| `tokens/nestquest-panel-tokens.css` | Panel card custom properties |
| `tokens/nestquest-admin-tokens.css` | Admin card custom properties |
## Screen index
| id | Screen | Surface | Spec |
|---|---|---|---|
| 1a | The Party (attract) | panel 1920×1080 | PANEL §2 |
| 1b | Quest Log | panel 1920×1080 | PANEL §3 |
| 1j | Complete confirm | panel 1920×1080 | PANEL §4 |
| 1c | Quest complete | panel 1920×1080 | PANEL §5 |
| 1d | Today (compact) | phone 390×844 | ADMIN §2 |
| 1e | Definitions + edit sheet | phone 390×844 | ADMIN §3 |
| 1f | Schedule + custody | phone 390×844 | ADMIN §4 |
| 1g | History + CSV | phone 390×844 | ADMIN §5 |
| 1h | Today (roomy) | phone 390×844 | ADMIN §2.4 |
| 1i | Presence override | phone 390×844 | ADMIN §6 |
## Design decisions taken during this handoff
These were open in CTXD and are now settled. Several contradict the current repo and
docs — read `IMPLEMENTATION-ORDER.md` §1 before writing code.
1. **`quest_*` naming throughout.** The domain model is renamed from `task_*` to
`quest_*` in DB, services, entities and UI. Migration note in IMPLEMENTATION-ORDER §1.1.
2. **Instance key becomes `(definition_id, child_id, due_date, window)`.** Required by
multi-assignee definitions and by twice-daily quests. Supersedes the documented
`(definition_id, due_date)`.
3. **A definition may have multiple assignees**, and materialises one instance per
assigned child per date per window. Each child completes their own.
4. **A definition may declare more than one window** (morning / afternoon / evening),
producing one instance per window per day.
5. **Missed quests vanish from the panel at rollover.** Kids never see a wall of failure;
the missed event is in the parent's history only.
6. **Three Lovelace cards**: `nestquest-party-board-card`, `nestquest-quest-log-card`,
`nestquest-admin-card`. Board and log live in separate Lovelace views; a crest tap
navigates.
7. **Completion needs a confirm step** (screen 1j) because children cannot undo.
8. **The quest log owns idle return**: 40 s of no touch navigates back to the board.
TouchHub Auto-Return must be configured longer than 40 s so the two do not fight.
9. **Actor on a panel completion** is `panel` plus the tapped child profile, per glossary.
10. **Weather is a configured entity** on the party board card (`weather_entity`), not
NestQuest data.
11. **Fonts are bundled** as local woff2 in the card's `www/` folder — the panel may be
offline. Nunito (400/600/700/800) and Cinzel + Cinzel Decorative.
## Vocabulary
The panel speaks tabletop; the admin speaks plainly. Both sit on the same `quest_*` model.
| Model / admin | Panel display string |
|---|---|
| quest instance | quest |
| child | adventurer |
| child's task list | Quest Log |
| household | The Party |
| all complete | Quest complete |
| absent | On travels |
## Open questions
- Empty and error states are unspecified by request (child with no quests, everyone away,
integration unconfigured, DB unreachable). Panel must not render a blank parchment —
decide before shipping the card.
- No gamification is in scope. XP, levels, streaks and rarity were explicitly deferred,
but the append-only event log already supports computing them later.
- Icon per quest definition is chosen by the parent from a Lucide subset; the subset is
not yet defined (`ADMIN-SPEC.md` §3.3).
+117
View File
@@ -0,0 +1,117 @@
# Screens → HA entities, services and events
Planned surface from CTXD CONTEXT.MD, adjusted for the design decisions in
`DESIGN-HANDOFF.md`. Names use the `quest_*` model.
## 1. Entities the cards read
| Entity | Type | Used by |
|---|---|---|
| `sensor.nestquest_<child>_quests_due_today` | int | 1a plate progress, 1b counts, 1d stats |
| `sensor.nestquest_<child>_quests_completed_today` | int | 1a, 1b, 1c, 1d, 1h |
| `sensor.nestquest_<child>_quests_remaining_today` | int | 1b remaining block, 1d |
| `sensor.nestquest_<child>_completion_pct_today` | float | progress bars |
| `sensor.nestquest_<child>_next_quest` | str + attrs | optional panel hint |
| `binary_sensor.nestquest_<child>_all_done` | bool | 1c trigger, 1d "all done" |
| `binary_sensor.nestquest_<child>_present_today` | bool | 1a away plate, 1d away row |
| `sensor.nestquest_household_quests_due_today` | int | 1b party line, 1c party stat |
| `sensor.nestquest_household_quests_completed_today` | int | same |
| `sensor.nestquest_cycle_day` | int | 1d sub-line, 1f header |
| *configured* `weather.<entity>` | HA core | panel dock |
Per-instance data is too granular for entity state. The cards read the day's instance list
from an attribute payload on the per-child sensor (or a websocket command —
implementer's call), shaped:
```json
{
"instances": [
{ "id": 4821, "definition_id": 12, "child_id": 3, "title": "Fill bird feeder",
"icon": "bird", "window": "afternoon", "due_time": "17:00",
"state": "open", "overdue": true, "completed_at": null, "on_time": null }
]
}
```
`state` ∈ `open` | `completed`. Missed instances are **omitted** from panel payloads
(decision 5) and included in admin payloads.
## 2. Services the cards call
| Service | Admin only | Called from |
|---|---|---|
| `nestquest.complete_quest` | no (panel allowed) | 1j confirm |
| `nestquest.uncomplete_quest` | **yes** | 1d `Undo` |
| `nestquest.create_quest_definition` | **yes** | 1e `New` |
| `nestquest.update_quest_definition` | **yes** | 1e sheet `Save changes` |
| `nestquest.set_quest_definition_active` | **yes** | 1e row action |
| `nestquest.set_presence_pattern` | **yes** | 1f presence card |
| `nestquest.create_presence_override` | **yes** | 1i `Save override` |
| `nestquest.delete_presence_override` | **yes** | 1f override row `trash-2` |
| `nestquest.export_history_csv` | **yes** | 1g `CSV` |
| `nestquest.manage_child` | **yes** | settings |
`complete_quest` payload from the panel:
```yaml
service: nestquest.complete_quest
data:
instance_id: 4821
actor: panel # kiosk user context
actor_child_id: 3 # the tapped profile (decision 9)
```
Every service resolves `call.context.user_id` against the admin allowlist and **fails
closed** with no user context (D-003). The panel's kiosk user is never allowlisted, so
`uncomplete_quest` is unreachable from the wall even though the card never renders the
control.
## 3. Events the cards listen for
| Event | Effect |
|---|---|
| `nestquest_quest_completed` | stamp the seal, re-sort the column, refresh counts |
| `nestquest_quest_uncompleted` | un-seal, restore the card (admin action, panel must reflect it) |
| `nestquest_quest_missed` | remove the instance from the panel; add a history row |
| `nestquest_child_day_complete` | show screen 1c |
All four come off the `DataUpdateCoordinator`; cards should also refresh on
`state_changed` for their sensors so they recover if an event is dropped.
## 4. Card configuration
```yaml
# kids' panel, view 1
type: custom:nestquest-party-board-card
weather_entity: weather.home # optional; dock falls back to date/time
quest_log_path: /nestquest/log # view the crest tap navigates to
child_order: [declan, jordyn, chloe]
# kids' panel, view 2
type: custom:nestquest-quest-log-card
board_path: /nestquest/board
idle_return_seconds: 40 # must be shorter than TouchHub Auto-Return
confirm_timeout_seconds: 15
complete_screen_seconds: 12
# parent phone
type: custom:nestquest-admin-card
density: compact # compact | roomy
default_tab: today
```
The quest log reads the selected child from the view's URL/`hass` path parameter so the
board → log hop is a plain Lovelace navigation (decision 6).
## 5. Data-shape consequences of the design
1. Instance key is `(definition_id, child_id, due_date, window)` — the schema's
`task_instances` unique index must widen.
2. `quest_definitions` needs an **assignees** join table, replacing the single
`child_id` column.
3. `quest_definitions` needs a **windows** set (morning/afternoon/evening) plus an
optional due time per window.
4. The materialiser emits one instance per (assignee × window × firing date), still
idempotent on the widened key, still only when the child is present.
5. `completion_events` gains `actor_child_id` so a panel completion records which
profile was tapped, while `actor` stays `panel`.
+80
View File
@@ -0,0 +1,80 @@
# Implementation order
Written for an agent working in `code.cubecraftlabs.com/Maniacs_Mansion/NestQuest`.
Steps 1.x change existing code and must land first; the frontend is step 4 onward.
## 1. Model changes (breaking, pre-release)
### 1.1 Rename `task_*` → `quest_*`
Nothing is released (main/dev are at "first commit", version 0.1.0), so rename in place
rather than aliasing.
- `schema.py`: `task_definitions` → `quest_definitions`, `task_instances` →
`quest_instances`. Keep `completion_events`, `children`, `admin_users`,
`schedule_rules`, `presence_schedules`, `presence_overrides`.
- `db.py` / `store.py`: rename DAO methods and any SQL literals.
- `const.py`: no table names live there today; keep it that way.
- `tests/test_schema.py`, `tests/test_store.py`, `tests/test_db.py`: update expectations.
- **Migration note:** the versioned migration runner has no released version to upgrade
from. Bump the schema version, and make migration 1 create the `quest_*` tables
directly. If any dev machine already holds a `nestquest.db` with `task_*` tables, the
runner should detect the legacy names and `ALTER TABLE ... RENAME TO` before applying
the widened index — a dev-only path, worth ~20 lines and a test.
### 1.2 Widen the instance key
Unique index becomes `(definition_id, child_id, due_date, window)`. Materialisation stays
idempotent on that tuple. Add a test for the twice-daily case (one definition, two windows,
one child, one date → two rows) and the shared case (one definition, three assignees, one
window → three rows).
### 1.3 Multi-assignee definitions
Add `quest_definition_assignees(definition_id, child_id)`. Drop the single `child_id`
column on the definition. The "exactly one assigned child" rule in GLOSSARY.MD no longer
holds — CTXD has been updated.
### 1.4 Windows
Add `quest_definition_windows(definition_id, window, due_time)` with
`window ∈ ('morning','afternoon','evening')`. Window clock ranges are fixed for now:
morning ≤ 11:59, afternoon 12:00–17:00, evening 17:00–21:00. Store the ranges in
`const.py` so they are configurable later.
### 1.5 `actor_child_id` on completion events
Nullable int. Set for panel completions, null for admin ones. Still append-only.
## 2. Engines and materialisation
Unchanged in intent: pure recurrence engine, pure presence engine with anchor-date
arithmetic, daily materialiser over `horizon_days` at `day_rollover_time`. Only the
instance fan-out changes (assignees × windows).
## 3. Services, permissions, entities
Implement the service table in `ENTITIES-AND-SERVICES.md` §2 behind the permission
decorator, then the sensors and binary sensors in §1, then the four events in §3.
`complete_quest` is the only service a non-admin context may call.
## 4. Frontend groundwork
1. Create `custom_components/nestquest/www/`.
2. Bundle fonts as woff2: Nunito 400/600/700/800; Cinzel 600/700/900; Cinzel Decorative
700/900. Declare `@font-face` with `font-display: swap` in a shared
`nestquest-fonts.css`. No CDN — the panel may be offline.
3. Copy `design/tokens/nestquest-panel-tokens.css` and
`design/tokens/nestquest-admin-tokens.css` in as the cards' `:host` blocks.
4. Register the three cards with `customCards` and ship editors later; hand-written YAML
is fine for v1.
## 5. Cards, in this order
1. `nestquest-party-board-card` (screen 1a) — read-only, proves the entity payload.
2. `nestquest-quest-log-card` (1b) — read-only first, no completion.
3. Confirm dialog + `complete_quest` (1j) with optimistic seal and revert-on-failure.
4. Quest complete screen (1c) driven by `nestquest_child_day_complete`.
5. Idle return (40 s) and the documented TouchHub Auto-Return constraint.
6. `nestquest-admin-card`: Today (1d) → Definitions + sheet (1e) → Schedule (1f) →
History + CSV (1g) → Presence override (1i).
## 6. Before shipping
- `use_x_frame_options: false` is required for TouchHub embedding — document the
clickjacking trade-off next to the setting, per D-006.
- Verify no NestQuest control in the dock strip is tappable.
- Verify the kiosk HA user is absent from the admin allowlist, and that
`uncomplete_quest` denies it with a logged denial.
- Empty and error states are still unspecified (see DESIGN-HANDOFF "Open questions").
Do not ship a card that renders a blank parchment.
+170
View File
@@ -0,0 +1,170 @@
# Panel spec — kids' DAKOS wall panel
Screens `1a`, `1b`, `1j`, `1c` of the mockups. Two cards:
`nestquest-party-board-card` (§2) and `nestquest-quest-log-card` (§3–5).
## 1. Frame and constraints
- **Canvas** 1920×1080, landscape, fixed. No scrolling — content must fit. Every screen in
the mockups measures exactly 1920×1080 with no overflow; keep it that way.
- **Viewing distance** ~3 ft. **No text below 22px.** Meta lines are 22px/600 Nunito;
quest titles 30px/700 Cinzel; section heads 32px.
- **Contrast** every ink token is ≥4.5:1 on parchment. Do not introduce alpha-muted text.
- **Ground** `--nq-p-parchment` + `--nq-p-vignette`, CSS only, no image assets.
- **Double frame** inset 26px (`--nq-p-frame-outer`, radius 14px) and inset 36px
(`--nq-p-frame-inner`, radius 8px), both `pointer-events: none`.
- **Dock** bottom strip, 84px, inset 46px, radius 14px, `--nq-p-dock-bg`. Weather only.
**Nothing in the dock is tappable** (D-006).
- **No login.** Every action comes from the kiosk HA user; the kiosk user is never in the
admin allowlist.
## 2. The Party — attract state (1a)
Default view on the wall. Layout top to bottom:
1. **Wordmark row** at y=64. Rule · d20 hexagon · `NestQuest` (66px/900 display, brand
gradient via `background-clip: text`) · d20 · rule. d20 marks are 34px hexagons
(`clip-path: polygon(50% 0, 93% 25%, 93% 75%, 50% 100%, 7% 75%, 7% 25%)`) filled with
the brand gradient, numeral 15px/700 Cinzel.
2. **Kicker** `The Party · <weekday>, <Month D>` — 21px/600 Cinzel, tracking .30em, uppercase.
3. **Adventurer plates**, 3-up grid, 56px gap, page inset 110px, top y=262. One plate per
child returned by the party sensor, in configured order.
4. **Hint line** `Tap your crest to open your Quest Log`, 27px/600 Nunito, y≈930.
5. **Dock.**
### 2.1 Adventurer plate (present)
Radius 20px, `--nq-p-card-panel`, 2px `--nq-p-panel-border`, `--nq-p-panel-shadow`,
padding 44px 28px 40px, contents centred, 26px gap. **The whole plate is the tap target**
(≥188px wide, ≥600px tall — far above minimum).
- **Crest** 188×214, shield via `clip-path: polygon(0 0, 100% 0, 100% 62%, 50% 100%, 0 62%)`,
`--nq-p-crest`, inset ring `inset 0 0 0 4px rgba(255,255,255,.22)`, child's initial
82px/700 display in white.
- **Name** 48px/700 Cinzel, `--nq-p-ink`, tracking .02em.
- **Presence pill** radius 9999px, `#e8f4ea` on `rgba(22,120,60,.35)`, `check-circle` 22px
`#157a3c`, label `Home today` 19px/600 Cinzel uppercase `#146b36`.
- **Progress line** `N of M quests claimed` 26px/700 Cinzel `--nq-p-ink-secondary`.
- **Progress bar** full width, 16px, radius 9999px, track `rgba(92,62,26,.18)` with 1px
border, fill `--nq-brand-gradient-h`.
### 2.2 Adventurer plate (away)
Same geometry, muted: `linear-gradient(#f2ecdd, #e6dcc6)`, **2px dashed** border, no lift
shadow. Crest uses `--nq-p-crest-away`; initial stays **full-opacity white** (never alpha).
Name in `--nq-p-ink-away`. Pill: `tent` icon, label `On travels`, neutral fill. Progress
line reads `Returns <weekday>, <Mon D>`; empty progress track, no fill. **Not tappable** —
the plate must not navigate, since the child has no instances.
### 2.3 Dock
`cloud-sun` 40px · temperature 38px/800 Nunito `--nq-p-dock-ink` · 1px divider ·
condition + high/low 26px/600 `--nq-p-dock-ink-secondary` · divider · one short forecast
phrase. All read from the configured `weather_entity`; if unset, render the dock with date
and time only rather than removing it (the strip is part of the frame).
## 3. Quest Log (1b)
Reached by tapping a present crest. Header at y=56, page inset 62px:
- Small crest 96×110 (initial 44px/700 display).
- Title `<Name>'s Quest Log` 56px/700 display.
- Sub `<weekday>, <Month D> · N of M claimed` 20px/600 Cinzel, tracking .24em, uppercase.
- **Remaining block** right-aligned: radius 14px panel, d20 mark 44px with the remaining
count, `N quests left` 28px/700 Cinzel, and a party line
`Party progress · X of Y today` 22px/600 Nunito.
### 3.1 Window columns
Three equal columns, 34px gap, top y=236, one per window — **Morning** (until 11:59 AM),
**Afternoon** (12:00–5:00 PM), **Evening** (5:00–9:00 PM). Column head: icon
(`sunrise` / `sun` / `moon`) 34px, name 32px/700 Cinzel, sub-label 21px/600 Cinzel
uppercase with the window's clock range, right-aligned `done/total` 26px/700 Cinzel,
14px bottom padding, `--nq-p-rule` underline.
Cards stack at `--nq-p-quest-gap` (24px). Order within a column: **incomplete first,
sealed below**. A window with no quests renders its head and nothing else — do not
collapse the column, the three-column rhythm is the layout.
### 3.2 Quest card — open
`--nq-p-quest-min-height` 116px, radius 12px, `--nq-p-card-open`, 1px
`--nq-p-card-border`, `--nq-p-card-shadow`, padding 22px, 22px gap, row layout:
1. **Icon tile** 64×64, radius 10px, `--nq-p-icon-tile`, Lucide glyph 34px white.
2. **Text** title 30px/700 Cinzel `--nq-p-ink`; meta 22px/600 Nunito
`--nq-p-ink-secondary` reading `Due by <h:mm A>`. If past due:
`Overdue · due <h:mm A>` in 22px/700 Cinzel uppercase, tracking .10em,
`--nq-p-ink-late`.
3. **Complete button** `--nq-p-button-height` 72px × min 150px, radius 10px,
`--nq-brand-gradient`, `check` 30px + `Complete` 24px/700 Cinzel white,
`box-shadow: 0 3px 0 rgba(40,20,60,.35)`.
Both the card body and the button open the confirm dialog (§4). Press feedback:
`scale(.98)`, `--nq-dur-micro`.
### 3.3 Quest card — sealed
`--nq-p-card-done`, `opacity: .82`, no shadow, **not tappable**. Icon tile
`--nq-p-icon-tile-done` with the glyph in `--nq-p-ink-secondary`. Title 30px/700 Cinzel
`--nq-p-ink-muted` with `text-decoration: line-through`. Meta reads
`Claimed <h:mm A>` 22px/600 Nunito `--nq-p-ink-secondary`.
**Wax seal** `--nq-p-seal-size` 78px circle, `--nq-p-seal`, `--nq-p-seal-shadow`,
`check` 38px in `rgba(255,235,235,.95)`, absolutely positioned `right: 16px; top: -12px`,
rotated a few degrees — **vary the rotation per card** (−9°, +6°, −4° in the mockup) so the
board looks stamped by hand, not templated. Entrance: fade + `scale(1.15 → 1)`,
`--nq-dur-base`, `--nq-ease-out`.
### 3.4 Party roll-up
An optional dashed panel in the afternoon column: label `Party roll-up` 18px/600 Cinzel
uppercase, body 23px/600 Nunito `--nq-p-ink-secondary` — one or two sentences on the other
children ("Jordyn has finished her log. Chloe is on travels until Sunday."). Read-only.
### 3.5 Idle return
40 s with no touch → navigate back to the party board view. Reset on any pointer or touch
event. The card owns this; TouchHub Auto-Return must be set longer than 40 s.
## 4. Complete confirm (1j)
Modal over the dimmed log — scrim `rgba(30,20,10,.62)` over the live board.
Dialog 1000px wide at `top: 196px; left: 460px`, radius 20px, parchment panel with 3px
`rgba(92,62,26,.5)`, `box-shadow: 0 26px 60px rgba(20,10,0,.5)`, padding 64px 68px 56px.
- Header row: 104px icon tile (radius 12px, brand gradient, glyph 56px) · window kicker
21px/600 Cinzel uppercase tracking .24em · quest title 58px/700 display.
- Body 32px/600 Nunito `#3f2f1c`: `Mark this quest complete? Once the seal is set, only a
parent can undo it.`
- Buttons, 24px gap, both `--nq-p-confirm-button` 108px tall:
**Not yet** flex 1, `rgba(92,62,26,.1)` with 2px border, `x` 34px + 30px/700 Cinzel;
**Complete** flex 2, brand gradient, `check` 38px + 32px/700 Cinzel white.
- Dialog enters with fade + `scale(.96 → 1)`, `--nq-dur-modal`, `--nq-ease-out`.
- Dismiss: **Not yet**, scrim tap, or 15 s of no input.
On confirm, call the completion service optimistically, stamp the seal, and re-sort the
column. On service failure, revert the card and surface a single-line parchment toast.
## 5. Quest complete (1c)
Shown when the last instance for a child's day is completed; auto-returns to the board
after 12 s (countdown line `Returning to The Party in N seconds`, 25px/600 Nunito).
- Large seal 168px at y=96 (`check` 86px), rotated −6°, `0 10px 24px rgba(60,10,20,.42)`.
- `Quest complete` 86px/900 display, brand-gradient text fill.
- Sub 34px/600 Nunito `--nq-p-ink-secondary`, max-width 1000px, centred, `text-wrap: pretty`:
names the child, the date, and how the day went.
- **Stat blocks** 4-up at y=540, inset 200px, 26px gap: radius 16px parchment panels,
numeral 58px/900 Cinzel, label 17px/600 Cinzel uppercase tracking .14em —
`Quests claimed`, `On time`, `Late`, `The Party` (household X/Y).
- Dock switches to the evening variant (`cloud-moon`).
## 6. Accessibility and input
- Minimum tap target on this surface is the quest card itself (116px) and the 72px button;
nothing interactive is smaller than 72px.
- 24px between adjacent tap targets so a stray finger cannot claim the wrong quest.
- No hover states — this is a touch-only surface. Provide `:active` feedback only.
- No animation loops. Entrances only, per the CubeCraft motion rules.
+76
View File
@@ -0,0 +1,76 @@
/* ============================================================
NestQuest — Parent admin card tokens
Surface: phone, 390x844 reference. Plain CubeCraft styling,
no tabletop treatment, plain domain words in the UI.
Scope on the card host: nestquest-admin-card.
Derived from the approved mockups (admin screens 1d-1i).
Two densities were built; COMPACT is the default. Flip
--nq-a-density-* to the roomy values for the alternative.
============================================================ */
:host {
/* ── Brand + neutrals (CubeCraft tokens, verbatim) ─────── */
--nq-brand-blue: #3B3AB8;
--nq-brand-purple: #A035CC;
--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));
--nq-brand-gradient-h: linear-gradient(90deg, var(--nq-brand-blue), var(--nq-brand-purple));
--nq-a-page: #F4F4F6;
--nq-a-surface: #FFFFFF;
--nq-a-surface-subtle: #FAFAFA;
--nq-a-selected: #EBEBF8; /* row being edited */
--nq-a-border: #E4E4EA;
--nq-a-divider: #EDEDF1;
--nq-a-ink: #18181D;
--nq-a-ink-secondary: #52525E;
--nq-a-ink-tertiary: #70707E; /* inactive rows, unselected nav icons */
--nq-a-success: #15803D;
--nq-a-danger: #DC2626;
--nq-a-danger-strong: #B91C1C;
--nq-a-danger-bg: #FEE2E2;
--nq-a-warning-ink: #92400E;
--nq-a-warning-ink-2: #78350F;
--nq-a-warning-bg: #FEF3C7;
--nq-a-warning-border: rgba(217,119,6,.35);
--nq-a-info-bg: #DBEAFE;
--nq-a-info-ink: #1E3A8A;
--nq-a-info-icon: #1D4ED8;
--nq-a-reversal: #7D28A0; /* un-complete events in history */
/* ── Type ─────────────────────────────────────────────── */
--nq-a-font: Nunito, system-ui, sans-serif;
--nq-a-size-screen: 24px; /* 800, screen title */
--nq-a-size-hero: 32px; /* 800, roomy "8 of 12 done" */
--nq-a-size-card-title: 19px; /* 800, roomy child card */
--nq-a-size-stat: 22px; /* 800, compact stat number */
--nq-a-size-row: 13.5px;/* 700, list row title */
--nq-a-size-meta: 11px; /* 600, row meta */
--nq-a-size-label: 10px; /* 700, uppercase field label */
--nq-a-track-label: .10em;
/* ── Density: COMPACT (default) ───────────────────────── */
--nq-a-density-page-pad: 14px 16px 92px; /* bottom clears the 70px tab bar */
--nq-a-density-gap: 10px;
--nq-a-density-card-pad: 13px;
--nq-a-density-row-pad: 11px 13px;
--nq-a-density-radius: 8px;
--nq-a-density-shadow: none;
/* Density: ROOMY — page-pad 18px 20px 92px; gap 16px;
card-pad 18px; radius 12px; shadow 0 4px 12px rgba(0,0,0,.06) */
/* ── Metrics ──────────────────────────────────────────── */
--nq-a-tabbar-height: 70px; /* + 12px safe-area padding */
--nq-a-tap-min: 44px; /* every row/control clears this */
--nq-a-radius-pill: 9999px;
--nq-a-radius-sheet: 20px 20px 0 0;
--nq-a-sheet-shadow: 0 -8px 28px rgba(9,9,11,.18);
--nq-a-progress-height: 6px; /* 8px roomy, 10px hero */
/* ── Motion ───────────────────────────────────────────── */
--nq-ease-out: cubic-bezier(0, 0, .2, 1);
--nq-dur-micro: 120ms;
--nq-dur-base: 200ms;
--nq-dur-modal: 350ms;
}
+94
View File
@@ -0,0 +1,94 @@
/* ============================================================
NestQuest — Kids' panel card tokens
Surface: DAKboard DAKOS wall panel, 1920x1080 landscape,
viewed from ~3 ft, no login, read/complete only.
Scope these on the card host: nestquest-quest-log-card,
nestquest-party-board-card.
Derived from the approved mockups (panel screens 1a, 1b, 1c, 1j).
============================================================ */
:host {
/* ── Parchment ground ─────────────────────────────────── */
--nq-p-parchment-top: #f0e4c7;
--nq-p-parchment-bottom: #ddcca3;
--nq-p-parchment: radial-gradient(ellipse at 25% 10%, rgba(255,255,255,.5), transparent 55%),
radial-gradient(ellipse at 85% 90%, rgba(120,86,44,.28), transparent 60%),
repeating-linear-gradient(93deg, rgba(150,115,70,.05) 0 2px, transparent 2px 6px),
repeating-linear-gradient(2deg, rgba(150,115,70,.04) 0 3px, transparent 3px 7px),
linear-gradient(var(--nq-p-parchment-top), var(--nq-p-parchment-bottom));
--nq-p-vignette: inset 0 0 200px rgba(80,52,20,.35);
/* Card surfaces sitting on parchment */
--nq-p-card-open: linear-gradient(#fdf7e6, #f2e7c9); /* incomplete quest */
--nq-p-card-done: linear-gradient(#f0e8d3, #e6dcc2); /* sealed quest */
--nq-p-card-panel: linear-gradient(#fcf6e6, #f1e6ca); /* adventurer plate, stat block */
--nq-p-card-border: rgba(120,88,48,.38);
--nq-p-panel-border: rgba(92,62,26,.40);
--nq-p-card-shadow: 0 3px 0 rgba(120,88,48,.20), inset 0 1px 0 rgba(255,255,255,.70);
--nq-p-panel-shadow: 0 6px 0 rgba(92,62,26,.18), inset 0 2px 0 rgba(255,255,255,.70);
--nq-p-frame-outer: 2px solid rgba(92,62,26,.45);
--nq-p-frame-inner: 1px solid rgba(92,62,26,.28);
--nq-p-rule: 2px solid rgba(92,62,26,.35);
/* ── Ink (all values >= 4.5:1 on parchment) ───────────── */
--nq-p-ink: #2b1f14; /* titles, quest names */
--nq-p-ink-secondary: #5c452a; /* meta, window labels, counts */
--nq-p-ink-muted: #6f6455; /* sealed quest titles only */
--nq-p-ink-away: #4d4433; /* away adventurer name */
--nq-p-ink-late: #8f1526; /* overdue line */
/* ── Brand (CubeCraft, toned down: accents only) ──────── */
--nq-brand-blue: #3B3AB8;
--nq-brand-purple: #A035CC;
--nq-brand-gradient: linear-gradient(135deg, var(--nq-brand-blue), var(--nq-brand-purple));
--nq-p-icon-tile: var(--nq-brand-gradient); /* open quest icon tile */
--nq-p-icon-tile-done: rgba(92,62,26,.16);
--nq-p-crest: var(--nq-brand-gradient);
--nq-p-crest-away: linear-gradient(135deg, #6b6b7a, #7a7286);
/* ── Wax seal (completed marker) ──────────────────────── */
--nq-p-seal: radial-gradient(circle at 35% 30%, #a8283a, #6d1322);
--nq-p-seal-shadow: 0 4px 10px rgba(60,10,20,.40), inset 0 0 0 4px rgba(255,255,255,.14);
--nq-p-seal-size: 78px; /* 168px on the quest-complete screen */
/* ── Dock (bottom strip, non-interactive) ─────────────── */
--nq-p-dock-bg: rgba(43,31,20,.90);
--nq-p-dock-ink: #f7efdb;
--nq-p-dock-ink-secondary: #d8c9a6;
--nq-p-dock-divider: rgba(233,220,189,.30);
--nq-p-dock-height: 84px;
/* ── Type ─────────────────────────────────────────────── */
--nq-p-font-display: 'Cinzel Decorative', Cinzel, serif; /* NestQuest, names, Quest complete */
--nq-p-font-heading: Cinzel, serif; /* section heads, quest titles, numerals */
--nq-p-font-body: Nunito, system-ui, sans-serif; /* meta lines, dock */
--nq-p-size-hero: 86px; /* 900 weight, "Quest complete" */
--nq-p-size-wordmark: 66px; /* 900 weight */
--nq-p-size-title: 56px; /* 700, "Declan's Quest Log" */
--nq-p-size-name: 48px; /* 700, adventurer name */
--nq-p-size-section: 32px; /* 700, Morning / Afternoon / Evening */
--nq-p-size-quest: 30px; /* 700, quest title */
--nq-p-size-body: 22px; /* 600 Nunito, meta — PANEL MINIMUM */
--nq-p-size-label: 21px; /* 600 Cinzel, uppercase, tracked */
--nq-p-track-label: .14em;
--nq-p-track-kicker: .30em;
/* ── Metrics: touch ───────────────────────────────────── */
--nq-p-quest-min-height: 116px; /* whole card is the tap target */
--nq-p-quest-gap: 24px; /* separation between tap targets */
--nq-p-button-height: 72px; /* "Complete" button on a quest card */
--nq-p-button-min-width: 150px;
--nq-p-confirm-button: 108px; /* confirm dialog buttons */
--nq-p-radius-card: 12px;
--nq-p-radius-panel: 20px;
--nq-p-radius-pill: 9999px;
--nq-p-page-inset: 62px;
--nq-p-frame-inset: 26px;
/* ── Motion (CubeCraft easing) ────────────────────────── */
--nq-ease-out: cubic-bezier(0, 0, .2, 1);
--nq-dur-micro: 120ms;
--nq-dur-base: 200ms;
--nq-dur-modal: 350ms;
}