1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
# Companion seat: design
Date: 2026-10-24. Status: implemented; the sitting art is generated by the user.
## Goal
While the assistant canvas shows its tall card (a chat, or the history,
picker or models panel), the companion sits on the card's top edge, right
of centre, legs dangling over the empty middle of the header. Typing makes
her lean over and read; BackSpace makes her point and smirk from the ledge;
thinking, listening and the reply wave happen there too. The bar and the
file card do not seat her: she stays in her corner, since standing on a
short card would need its own art to read anything below her feet.
## Verified before designing
- **Hyprland's typing binds fire under the canvas.** With the canvas open
and holding the keyboard `Exclusive`, socket2 carried
`custom>>keystone-typing` and `custom>>keystone-delete` for every burst
typed into it, so the companion needs no input of the canvas's own.
- **The tall card has a fixed top.** `Card.tall` is `mode === "chat" ||
panel`, and while tall its height is `fullHeight`, set by the window to
70% of the screen height. The card is centred, so its top is
`(screenH - fullHeight) / 2` and only changes on entering or leaving the
tall state. Nothing needs to follow a moving edge.
- **The header's middle is empty.** The model name is on the left, the
context meter and buttons on the right, both in a 32 px row inside a
14 px margin. Her legs fall in the gap between them.
- **Sitting art exists.** `base-sitting.png` in the art directory is the
original rest pose: front view, sitting, legs dangling. `dress/base` was
made from it, so the new sitting frames edit it directly.
- **Opening the canvas already remaps her.** `openlayer` for
`keystone-assistant` unmaps and maps her again above the dim, with a drop
out and a drop in. A seat change made while she is unmapped needs no
animation of its own.
## Behaviour
### Seat
`Assistant.qml` gains `readonly property bool tall`, the same expression
as `Card.tall`, and `readonly property real cardTop`, the card's top on
its screen. The canvas owns both so the companion does not reach into the
card.
The companion's `seated` is `asst.open && asst.tall`. Seated, her window
is on `asst.screenObj` and its margins put her seat's bottom 35 px below
the card's top, with her right edge 90 px right of the screen's centre:
the window stays anchored right and bottom, `margins.bottom = screenH -
cardTop - 35`, `margins.right = screenW / 2 - 90`. Her right edge rather
than her centre, because frames differ in width and the window would move
with every frame. Both numbers were checked by eye. Not seated, the
margins are today's 24 and 16.
`seated` changing runs the monitor hop's path: she drops out, the window
moves when the exit has settled, and the entrance plays. The first seat on
opening a chat coincides with the remap and is hidden by it. While seated,
a focused monitor change does not hop her; on unseating she goes to the
focused monitor.
Balloons do not shove her while seated: their stack is in a screen corner,
not on the card.
### Frames
Seated, the frame Mood picks goes through `Mood.seatFrame(frame)`:
| Standing | Sitting |
| --- | --- |
| `watch` | `sit-read`: leaning forward, looking down at the card |
| `point` | `sit-point` |
| `smirk` | `sit-smirk` |
| `think` | `sit-think` |
| `listen` | `sit-listen` |
| `wave`, `wave-l`, `wave-r` | `sit-wave` |
| anything else | `sit` |
Cycles (the goodbye wave, dj, headbang) map frame by frame, so the
goodbye shows `sit-wave` throughout and dj shows `sit`. `Mood.pick` is
unchanged: typing still gives `watch`, delete still gives the mock.
Seven new frames in each outfit, from `base-sitting` in `frames.txt`, with
a sitting counterpart of `POSE` and the leotard stanzas like the others.
`gen-frames` then `prep-frames`, by the user, as with every other frame.
A missing frame falls back to `base`, standing, which is how an
ungenerated set shows.
### Inputs
Typing and BackSpace keep arriving as `keystone-typing` and
`keystone-delete` from Hyprland, which fire under the canvas (see above).
## Check
`test_mood.js` gains `seatFrame` cases: each row of the table, a cycle
frame, and an unknown frame to `sit`. The seat position and the bind check
are by hand.
## Not doing
- Standing on the bar or the file card: needs new art.
- Following the card while it animates: the tall card does not move.
- Sitting on ordinary windows.
|