@fayz-ai/pos 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -0
  3. package/dist/app/config.d.ts +238 -0
  4. package/dist/app/config.d.ts.map +1 -0
  5. package/dist/index.d.ts +24 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +2638 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/keyboard/index.d.ts +3 -0
  10. package/dist/keyboard/index.d.ts.map +1 -0
  11. package/dist/lib/haptics.d.ts +3 -0
  12. package/dist/lib/haptics.d.ts.map +1 -0
  13. package/dist/lib/useSwipeAway.d.ts +18 -0
  14. package/dist/lib/useSwipeAway.d.ts.map +1 -0
  15. package/dist/screen/DeliveryBoard.d.ts +12 -0
  16. package/dist/screen/DeliveryBoard.d.ts.map +1 -0
  17. package/dist/screen/DispatchRail.d.ts +13 -0
  18. package/dist/screen/DispatchRail.d.ts.map +1 -0
  19. package/dist/screen/IdentitySheet.d.ts +7 -0
  20. package/dist/screen/IdentitySheet.d.ts.map +1 -0
  21. package/dist/screen/ModifierSheet.d.ts +13 -0
  22. package/dist/screen/ModifierSheet.d.ts.map +1 -0
  23. package/dist/screen/Notice.d.ts +21 -0
  24. package/dist/screen/Notice.d.ts.map +1 -0
  25. package/dist/screen/OpenTableSheet.d.ts +13 -0
  26. package/dist/screen/OpenTableSheet.d.ts.map +1 -0
  27. package/dist/screen/PinSheet.d.ts +9 -0
  28. package/dist/screen/PinSheet.d.ts.map +1 -0
  29. package/dist/screen/PosScreen.d.ts +96 -0
  30. package/dist/screen/PosScreen.d.ts.map +1 -0
  31. package/dist/screen/ReservationsView.d.ts +11 -0
  32. package/dist/screen/ReservationsView.d.ts.map +1 -0
  33. package/dist/screen/ShortcutsOverlay.d.ts +6 -0
  34. package/dist/screen/ShortcutsOverlay.d.ts.map +1 -0
  35. package/dist/screen/SplitSheet.d.ts +9 -0
  36. package/dist/screen/SplitSheet.d.ts.map +1 -0
  37. package/dist/screen/TicketRail.d.ts +36 -0
  38. package/dist/screen/TicketRail.d.ts.map +1 -0
  39. package/dist/screen/money.d.ts +11 -0
  40. package/dist/screen/money.d.ts.map +1 -0
  41. package/dist/screen/session.d.ts +105 -0
  42. package/dist/screen/session.d.ts.map +1 -0
  43. package/dist/screen/views.d.ts +51 -0
  44. package/dist/screen/views.d.ts.map +1 -0
  45. package/dist/styles.css +1664 -0
  46. package/dist/theme/index.d.ts +2 -0
  47. package/dist/theme/index.d.ts.map +1 -0
  48. package/dist/theme/index.js +53 -0
  49. package/dist/theme/index.js.map +1 -0
  50. package/dist/theme/tokens.d.ts +62 -0
  51. package/dist/theme/tokens.d.ts.map +1 -0
  52. package/package.json +61 -0
@@ -0,0 +1,1664 @@
1
+ /* ---------------------------------------------------------------------------
2
+ @fayz-ai/pos — operator surface design system.
3
+
4
+ Self-contained: the POS is a dark operational terminal, not the admin's
5
+ light back office, so it does NOT inherit @fayz-ai/ui/styles.css.
6
+
7
+ The system, in one paragraph: four surface levels so panels read as depth
8
+ instead of as borders; one type scale where size and weight — not colour —
9
+ carry hierarchy; a touch floor expressed as a token so no component invents
10
+ its own; pastel reserved for category identity; and status on a separate
11
+ axis of accent + icon + word, because colour alone fails the colour-blind
12
+ operator and the screen washed out by afternoon sun.
13
+ --------------------------------------------------------------------------- */
14
+
15
+ .pos {
16
+ /* --- surfaces: four levels, never pure #000 (OLED smear, and it leaves
17
+ nothing below the lowest surface) --------------------------------- */
18
+ --pos-0: #0B0B0C; /* app canvas */
19
+ --pos-1: #131315; /* panels, rails */
20
+ --pos-2: #1A1A1D; /* cards on a panel */
21
+ --pos-3: #232326; /* hover / raised card */
22
+ --pos-4: #2E2E32; /* pressed / selected */
23
+
24
+ --pos-line: #242427; /* hairline between regions */
25
+ --pos-line-strong: #35353A; /* control borders */
26
+
27
+ /* --- text: three steps, all AA on --pos-1 or darker ------------------- */
28
+ --pos-fg: #F7F7F8;
29
+ --pos-fg-2: #A0A0A6;
30
+ --pos-fg-3: #6E6E75;
31
+ --pos-on-light: #121214;
32
+
33
+ /* Accent is for FOCUS and SELECTION only. The primary action is white —
34
+ the brightest thing on a near-black screen should be the thing you press,
35
+ and a saturated button competes with the status colours that carry
36
+ meaning. */
37
+ --pos-accent: #5B8DEF;
38
+ --pos-accent-fg: #FFFFFF;
39
+ --pos-accent-dim: #1C2436;
40
+ --pos-primary: #F7F7F8;
41
+ --pos-primary-fg: #121214;
42
+
43
+ /* --- status: its own language, always paired with icon + word --------- */
44
+ --pos-ok: #4AA76C;
45
+ --pos-warn: #D3982F;
46
+ --pos-info: #5B8DEF;
47
+ --pos-danger: #DC5C55;
48
+
49
+ /* --- category identity: assigned per category by data, never a status -- */
50
+ --pos-tone-mint: #BDE3CD;
51
+ --pos-tone-lilac: #D9CDED;
52
+ --pos-tone-sky: #C2DDF1;
53
+ --pos-tone-periwinkle: #CDD3F3;
54
+ --pos-tone-blush: #F1C2D5;
55
+ --pos-tone-sand: #E3D9CF;
56
+ --pos-tone-teal: #B6E4D6;
57
+ --pos-tone-rose: #EFC4C4;
58
+
59
+ /* --- shape: softer than an instrument panel, tighter than a phone app -- */
60
+ --pos-r: 10px;
61
+ --pos-r-sm: 7px;
62
+ --pos-r-xs: 5px;
63
+
64
+ /* --- rhythm: 4px base, and nothing off it ----------------------------- */
65
+ --pos-1x: 4px; --pos-2x: 8px; --pos-3x: 12px;
66
+ --pos-4x: 16px; --pos-6x: 24px; --pos-8x: 32px;
67
+
68
+ /* --- touch: a token, so no component gets to invent its own size ------ */
69
+ --pos-hit: 72px; /* primary targets during service (~19mm @96dpi) */
70
+ --pos-hit-2: 48px; /* secondary controls */
71
+ --pos-hit-3: 40px; /* icon buttons inside a card */
72
+
73
+ /* One header band across the screen. Content and rail were sized apart, so
74
+ their hairlines and baselines missed each other by a few pixels — enough
75
+ to read as a seam. */
76
+ --pos-header-h: 72px;
77
+
78
+ --pos-t: 140ms;
79
+ --pos-e: cubic-bezier(0.2, 0, 0, 1);
80
+
81
+ /* Safe areas as tokens. index.html asks for viewport-fit=cover so the panels
82
+ reach the physical edges of the tablet; the components that TOUCH an edge
83
+ then pay the inset back, instead of every one of them calling env() and
84
+ one of them forgetting. Zero in a browser tab, ~20px under an iPad's home
85
+ indicator. */
86
+ --pos-safe-t: env(safe-area-inset-top, 0px);
87
+ --pos-safe-r: env(safe-area-inset-right, 0px);
88
+ --pos-safe-b: env(safe-area-inset-bottom, 0px);
89
+ --pos-safe-l: env(safe-area-inset-left, 0px);
90
+
91
+ position: fixed;
92
+ /* Height, not `inset: 0`: installed on iOS the fixed box is laid out against
93
+ the LAYOUT viewport, which stays tall when the keyboard is up — the dock
94
+ then hangs below the visible screen. dvh follows the viewport that is
95
+ actually on screen; in standalone it equals the full height anyway. */
96
+ inset: 0 0 auto 0;
97
+ height: 100%;
98
+ height: 100dvh;
99
+ box-sizing: border-box;
100
+ /* The status bar is translucent, so the app draws under it; the shell gives
101
+ that strip back rather than putting the header behind the clock. */
102
+ padding-top: var(--pos-safe-t);
103
+ display: grid;
104
+ grid-template-rows: minmax(0, 1fr);
105
+ background: var(--pos-0);
106
+ color: var(--pos-fg);
107
+ /* Inter first when the machine has it; the system grotesks below are close
108
+ enough in metrics that the layout does not move. A self-hosted face ships
109
+ with the Electron shell — a POS must not wait on a CDN to draw its own
110
+ screen, so no @import here. */
111
+ font-family: Inter, 'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text',
112
+ 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
113
+ font-size: 15px;
114
+ line-height: 1.45;
115
+ letter-spacing: -0.006em;
116
+ font-feature-settings: 'cv05' 1, 'ss03' 1;
117
+ overflow: hidden;
118
+ /* Stops the whole app rubber-banding when an inner list hits its end — on a
119
+ tablet that bounce looks like the layout came loose. */
120
+ overscroll-behavior: none;
121
+ touch-action: manipulation;
122
+ -webkit-tap-highlight-color: transparent;
123
+ -webkit-font-smoothing: antialiased;
124
+ }
125
+
126
+ .pos *, .pos *::before, .pos *::after { box-sizing: border-box; }
127
+ .pos button { font: inherit; }
128
+ /* No `color: inherit` here: `.pos button` outranks a single-class rule, and it
129
+ silently repainted every button that set its own colour. */
130
+
131
+ /* Money, counts and timers: tabular figures so a changing digit never reflows
132
+ the column it sits in. */
133
+ .pos-num { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }
134
+
135
+ .pos :focus-visible { outline: 2px solid var(--pos-accent); outline-offset: 2px; border-radius: var(--pos-r-xs); }
136
+
137
+ @media (prefers-reduced-motion: reduce) {
138
+ .pos *, .pos *::before, .pos *::after {
139
+ transition-duration: 0.01ms !important; animation-duration: 0.01ms !important;
140
+ }
141
+ }
142
+
143
+ .pos-scroll {
144
+ overflow-y: auto; overscroll-behavior: contain;
145
+ /* Room for the last row to clear the sticky bottom of its column. */
146
+ padding-bottom: var(--pos-2x);
147
+ scrollbar-width: thin; scrollbar-color: var(--pos-line-strong) transparent;
148
+ }
149
+ .pos-scroll::-webkit-scrollbar { width: 10px; height: 10px; }
150
+ .pos-scroll::-webkit-scrollbar-thumb { background: var(--pos-line-strong); border-radius: 5px; border: 3px solid transparent; background-clip: padding-box; }
151
+ .pos-scroll::-webkit-scrollbar-track { background: transparent; }
152
+
153
+ /* --- type scale ---------------------------------------------------------
154
+ Size and weight carry hierarchy. Colour is for status, not for rank.
155
+ ------------------------------------------------------------------------- */
156
+ .pos-t-display { font-size: 34px; font-weight: 680; letter-spacing: -0.03em; line-height: 1.05; }
157
+ .pos-t-xl { font-size: 21px; font-weight: 640; letter-spacing: -0.022em; line-height: 1.2; }
158
+ .pos-t-lg { font-size: 16px; font-weight: 600; letter-spacing: -0.014em; }
159
+ .pos-t-md { font-size: 15px; font-weight: 520; letter-spacing: -0.008em; }
160
+ .pos-t-sm { font-size: 13px; font-weight: 480; color: var(--pos-fg-2); letter-spacing: -0.002em; }
161
+ .pos-t-xs { font-size: 10.5px; font-weight: 620; letter-spacing: 0.09em; text-transform: uppercase; color: var(--pos-fg-3); }
162
+
163
+ /* --- top bar ------------------------------------------------------------ */
164
+
165
+
166
+
167
+
168
+
169
+
170
+ .pos-pill {
171
+ display: inline-flex; align-items: center; gap: var(--pos-2x);
172
+ height: 36px; padding: 0 var(--pos-3x);
173
+ border: 1px solid var(--pos-line); border-radius: var(--pos-r-sm);
174
+ background: var(--pos-2); font-size: 13px; color: var(--pos-fg-2);
175
+ }
176
+ .pos-pill strong { color: var(--pos-fg); font-weight: 600; }
177
+ .pos-pill__dot { width: 7px; height: 7px; border-radius: 50%; flex: none; }
178
+ .pos-pill[data-state='online'] .pos-pill__dot { background: var(--pos-ok); }
179
+ .pos-pill[data-state='reconnecting'] .pos-pill__dot { background: var(--pos-warn); }
180
+ .pos-pill[data-state='offline'] .pos-pill__dot { background: var(--pos-danger); }
181
+
182
+ /* --- body --------------------------------------------------------------- */
183
+
184
+ .pos-body {
185
+ display: grid;
186
+ grid-template-columns: 232px minmax(0, 1fr) 400px;
187
+ min-height: 0; overflow: hidden; position: relative;
188
+ transition: grid-template-columns 200ms var(--pos-e);
189
+ }
190
+ /* The details column is permanent — the order and the table are always on the
191
+ right. Expanding gives the order more room; it never leaves. */
192
+
193
+ /* --- left rail ---------------------------------------------------------- */
194
+
195
+ .pos-rail {
196
+ display: flex; flex-direction: column;
197
+ background: var(--pos-1); border-right: 1px solid var(--pos-line);
198
+ padding: var(--pos-3x) var(--pos-2x); min-width: 0;
199
+ padding-left: calc(var(--pos-2x) + var(--pos-safe-l));
200
+ }
201
+ .pos-rail__label { padding: var(--pos-2x) var(--pos-3x) var(--pos-1x); }
202
+ .pos-nav { display: flex; flex-direction: column; gap: 2px; }
203
+ .pos-nav__item {
204
+ position: relative;
205
+ display: flex; align-items: center; gap: var(--pos-3x);
206
+ min-height: var(--pos-hit-2); padding: 0 var(--pos-3x);
207
+ border: none; border-radius: var(--pos-r); background: transparent;
208
+ color: var(--pos-fg-2); font-size: 15px; font-weight: 500;
209
+ text-align: left; cursor: pointer;
210
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
211
+ }
212
+ .pos-nav__item:hover { background: var(--pos-2); color: var(--pos-fg); }
213
+ /* Tinted, not saturated. Full accent made the rail the loudest thing on a
214
+ screen whose job is the floor and the order — navigation should say where you
215
+ are, not compete with what you are doing there. */
216
+ .pos-nav__item[aria-current='page'] {
217
+ background: color-mix(in srgb, var(--pos-accent) 20%, var(--pos-1));
218
+ color: var(--pos-fg); font-weight: 620;
219
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pos-accent) 42%, transparent);
220
+ }
221
+ .pos-nav__item[aria-current='page'] .pos-nav__icon { color: var(--pos-accent); }
222
+ .pos-nav__item[aria-current='page']:hover {
223
+ background: color-mix(in srgb, var(--pos-accent) 26%, var(--pos-1));
224
+ color: var(--pos-fg);
225
+ }
226
+ /* No marker. A 3px bar was doing the job of saying "you are here" while sitting
227
+ next to a whole filled pill that could say it louder — one signal, the strong
228
+ one. */
229
+ .pos-nav__icon { flex: none; display: grid; place-items: center; width: 20px; height: 20px; }
230
+ .pos-nav__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
231
+ .pos-nav__count {
232
+ margin-left: auto; min-width: 22px; height: 20px; padding: 0 6px;
233
+ display: grid; place-items: center; border-radius: 10px;
234
+ background: var(--pos-3); color: var(--pos-fg-2); font-size: 12px; font-weight: 650;
235
+ }
236
+ .pos-nav__item[aria-current='page'] .pos-nav__count { background: color-mix(in srgb, var(--pos-accent) 32%, transparent); color: var(--pos-fg); }
237
+
238
+ /* Uma fenda para o host pôr o que diz onde esta tela ESTÁ (a chave Balcão ⇄
239
+ Retaguarda de quem abre o PDV de dentro de um back office). */
240
+ .pos-rail__top { padding: var(--pos-2x) var(--pos-1x) 0; }
241
+
242
+ /* O disco com a inicial de quem atende. Sobrou da lista de turno que saiu do
243
+ rail — a escolha do atendente ao abrir a mesa ainda o usa. */
244
+ .pos-person__av {
245
+ width: 28px; height: 28px; flex: none; display: grid; place-items: center;
246
+ border-radius: 50%; font-size: 12px; font-weight: 700; color: #101318;
247
+ }
248
+
249
+ /* --- content ------------------------------------------------------------ */
250
+
251
+ /* Deliberately NOT inset at the bottom: the dock is a strip you glance at and
252
+ tap, and the operator asked for it flush with the edge. iPad draws the home
253
+ indicator over it as a hairline and still delivers the tap — only the swipe
254
+ up from the very edge belongs to the system. */
255
+ .pos-main {
256
+ display: flex; flex-direction: column; min-height: 0; min-width: 0;
257
+ background: var(--pos-0);
258
+ /* Anchors the confirmation band over the dock. */
259
+ position: relative;
260
+ }
261
+
262
+ /* --- confirmation band ---------------------------------------------------
263
+ Lands over the strip of open tables: the one band nobody reads while
264
+ ringing, and the place the eye already returns to between taps. It blurs
265
+ the chips instead of hiding them, so the message reads as passing over
266
+ the screen rather than as a new screen.
267
+ ------------------------------------------------------------------------- */
268
+ .pos-notice {
269
+ position: absolute; left: 0; right: 0; bottom: 0; z-index: 40;
270
+ min-height: 69px; padding: var(--pos-3x) var(--pos-6x);
271
+ display: flex; align-items: center; justify-content: center; gap: var(--pos-3x);
272
+ border-top: 1px solid var(--pos-line);
273
+ background: rgba(11, 11, 12, 0.88);
274
+ -webkit-backdrop-filter: blur(20px) saturate(130%);
275
+ backdrop-filter: blur(20px) saturate(130%);
276
+ font-size: 16px; font-weight: 600; letter-spacing: -0.014em; color: var(--pos-fg);
277
+ text-align: center;
278
+ /* It confirms; it never intercepts. A tap during the two seconds it is up
279
+ belongs to the chip underneath. */
280
+ pointer-events: none;
281
+ animation: pos-notice-in 180ms var(--pos-e) both;
282
+ }
283
+ .pos-notice[data-leaving='true'] { animation: pos-notice-out 260ms var(--pos-e) both; }
284
+ .pos-notice__i {
285
+ width: 30px; height: 30px; flex: none; border-radius: 50%;
286
+ display: grid; place-items: center;
287
+ background: var(--pos-3); color: var(--pos-fg);
288
+ }
289
+ .pos-notice[data-tone='ok'] .pos-notice__i { background: var(--pos-ok); color: #08130C; }
290
+ .pos-notice[data-tone='warn'] .pos-notice__i { background: var(--pos-warn); color: #1A1205; }
291
+
292
+ /* Rises from the band it covers, which is where the tap that caused it was. */
293
+ @keyframes pos-notice-in {
294
+ from { opacity: 0; transform: translateY(10px); }
295
+ to { opacity: 1; transform: translateY(0); }
296
+ }
297
+ @keyframes pos-notice-out {
298
+ from { opacity: 1; }
299
+ to { opacity: 0; }
300
+ }
301
+
302
+ /* No page title here. The rail's highlighted entry already says which screen
303
+ this is, and repeating it across the top is a breadcrumb for a product with
304
+ nothing to breadcrumb — one level, no hierarchy, no way to get lost. */
305
+ .pos-head {
306
+ display: flex; align-items: center; gap: var(--pos-3x);
307
+ height: var(--pos-header-h); padding: 0 var(--pos-4x);
308
+ border-bottom: 1px solid var(--pos-line);
309
+ }
310
+ .pos-head__title { font-size: 17px; font-weight: 650; letter-spacing: -0.015em; white-space: nowrap; }
311
+
312
+ .pos-seg { display: inline-flex; gap: 2px; padding: 3px; background: var(--pos-2); border-radius: 999px; }
313
+ /* As áreas encostam na direita; o título fica na esquerda. */
314
+ .pos-head > .pos-seg { margin-left: auto; }
315
+ .pos-seg__btn {
316
+ min-height: 36px; padding: 0 var(--pos-4x);
317
+ border: none; border-radius: 999px; background: transparent;
318
+ color: var(--pos-fg-2); font-size: 13px; font-weight: 550; cursor: pointer;
319
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
320
+ }
321
+ .pos-seg__btn:hover { color: var(--pos-fg); }
322
+ .pos-seg__btn[aria-pressed='true'] { background: var(--pos-4); color: var(--pos-fg); }
323
+
324
+ .pos-field {
325
+ display: flex; align-items: center; gap: var(--pos-2x);
326
+ /* Always open. Collapsing it saved width on a screen that had it to spare,
327
+ and cost a tap plus the sight of the control that answers "where is this
328
+ item" — code+Enter is the fastest path in the product, so the field it
329
+ types into must never be something to go find. */
330
+ /* Largura da trilha, não da barra: o campo virou linha do rail (a mesma
331
+ posição que a busca ocupa no back office), então ele preenche a coluna. */
332
+ width: 100%; min-width: 0; margin: var(--pos-2x) var(--pos-1x);
333
+ height: 36px; padding: 0 var(--pos-2x);
334
+ /* Superfície tonal, sem contorno — a mesma leitura da busca do back office.
335
+ Numa trilha escura um 1px é uma aresta dura brigando com todas as outras;
336
+ um preenchimento mais claro do mesmo tom já diz "campo" sem desenhar uma
337
+ caixa, e deixa o contorno livre para significar FOCO, que é o que ele
338
+ precisa significar. */
339
+ border: 1px solid transparent; border-radius: 999px;
340
+ background: rgba(255, 255, 255, 0.06);
341
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e);
342
+ }
343
+ .pos-field:hover { background: rgba(255, 255, 255, 0.09); }
344
+ .pos-field:focus-within {
345
+ border-color: var(--pos-line-strong); background: var(--pos-1);
346
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--pos-accent) 20%, transparent);
347
+ }
348
+ .pos-field input {
349
+ flex: 1; min-width: 0; border: none; background: none; outline: none;
350
+ color: var(--pos-fg); font: inherit; font-size: 13px;
351
+ }
352
+ .pos-field input::placeholder { color: var(--pos-fg-3); }
353
+ .pos-field__icon { color: var(--pos-fg-3); display: grid; place-items: center; flex: none; }
354
+
355
+ /* --- floor -------------------------------------------------------------- */
356
+
357
+ .pos-floor {
358
+ flex: 1; display: grid; align-content: start; padding-bottom: var(--pos-6x);
359
+ grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
360
+ gap: var(--pos-3x); padding: var(--pos-4x);
361
+ }
362
+ .pos-tbl {
363
+ position: relative; display: flex; flex-direction: column; gap: var(--pos-2x);
364
+ min-height: 132px; padding: var(--pos-3x) var(--pos-3x) var(--pos-3x) calc(var(--pos-3x) + 4px);
365
+ border: 1px solid var(--pos-line); border-radius: var(--pos-r);
366
+ background: var(--pos-2); text-align: left; cursor: pointer;
367
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e), transform var(--pos-t) var(--pos-e);
368
+ }
369
+ .pos-tbl:hover { background: var(--pos-3); }
370
+ .pos-tbl:active { transform: scale(0.985); }
371
+ /* One selected state for every card surface. "Where am I" gets the same answer
372
+ on the floor, on the board and in a list — and it is a ring in the brightest
373
+ ink available, not a tinted border that a coloured card swallows. */
374
+ .pos-tbl[aria-current='true'],
375
+ .pos-card[aria-current='true'],
376
+ .pos-lrow[aria-current='true'] {
377
+ border-color: var(--pos-fg); background: var(--pos-3);
378
+ box-shadow: 0 0 0 2px var(--pos-fg), 0 8px 22px -8px rgba(0, 0, 0, 0.8);
379
+ }
380
+ .pos-card[aria-current='true'] { --pos-edge: var(--pos-fg); }
381
+ .pos-tbl__top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--pos-2x); }
382
+ .pos-tbl__no { font-size: 21px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; }
383
+ .pos-tbl__seats { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--pos-fg-3); }
384
+ .pos-tbl__foot { margin-top: auto; display: flex; align-items: center; justify-content: space-between; gap: var(--pos-2x); }
385
+ .pos-tbl__time { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--pos-fg-2); }
386
+ .pos-tbl__total { font-size: 15px; font-weight: 650; }
387
+ .pos-tbl__who { font-size: 12px; color: var(--pos-fg-3); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
388
+
389
+ /* One edge treatment across every card surface: the stripe is the card's own
390
+ left edge, clipped by its radius. A `border-left` squares off against the
391
+ corner and reads as a misalignment — the same defect the menu grid had.
392
+ The badge still spells the state out; colour never carries it alone. */
393
+ .pos-tbl::before, .pos-card::before, .pos-lrow::before, .pos-dchip::before {
394
+ content: ''; position: absolute; inset: 0 auto 0 0; width: 4px;
395
+ background: var(--pos-edge, var(--pos-line-strong));
396
+ }
397
+ .pos-tbl, .pos-card, .pos-lrow, .pos-dchip { position: relative; overflow: hidden; }
398
+ /* No edge here. The status pill on the row already says it, and a coloured
399
+ bar plus a coloured pill is the same fact charged twice. */
400
+ .pos-dchip::before { display: none; }
401
+ .pos-tbl[data-status='free'] { --pos-edge: var(--pos-ok); }
402
+ .pos-tbl[data-status='occupied'] { --pos-edge: var(--pos-warn); }
403
+ .pos-tbl[data-status='bill_requested'] { --pos-edge: var(--pos-info); }
404
+ .pos-tbl[data-status='attention'] { --pos-edge: var(--pos-danger); }
405
+
406
+ .pos-badge {
407
+ display: inline-flex; align-items: center; gap: 5px; align-self: flex-start;
408
+ height: 22px; padding: 0 8px; border-radius: var(--pos-r-xs);
409
+ font-size: 12px; font-weight: 600; white-space: nowrap;
410
+ }
411
+ .pos-badge svg { flex: none; }
412
+ .pos-badge[data-status='free'] { color: var(--pos-ok); background: color-mix(in srgb, var(--pos-ok) 15%, transparent); }
413
+ .pos-badge[data-status='occupied'] { color: var(--pos-warn); background: color-mix(in srgb, var(--pos-warn) 15%, transparent); }
414
+ .pos-badge[data-status='bill_requested'] { color: var(--pos-info); background: color-mix(in srgb, var(--pos-info) 15%, transparent); }
415
+ .pos-badge[data-status='attention'] { color: var(--pos-danger); background: color-mix(in srgb, var(--pos-danger) 15%, transparent); }
416
+
417
+ /* --- catalogue ---------------------------------------------------------- */
418
+
419
+ .pos-cats {
420
+ display: grid; grid-template-columns: repeat(auto-fill, minmax(156px, 1fr));
421
+ /* The selected card's outer ring needs somewhere to be. */
422
+ gap: var(--pos-3x); padding: calc(var(--pos-3x) + 3px) var(--pos-4x) 3px;
423
+ }
424
+ /* A rule between the filter and what it filtered. Without it the two grids run
425
+ together and the eye has to work out where the choosing stops. */
426
+ .pos-cats-rule {
427
+ height: 1px; margin: var(--pos-3x) var(--pos-4x) 0;
428
+ background: var(--pos-line-strong); border: none;
429
+ }
430
+ /* Same footprint as an item: a row of short chips above a grid of tall cards
431
+ made the categories read as a toolbar instead of as the first choice. */
432
+ .pos-cat {
433
+ /* A filter, not a product. Every pixel above the minimum is a pixel the
434
+ items below it do not get, and the items are what gets tapped. */
435
+ display: flex; flex-direction: row; align-items: center; gap: var(--pos-2x);
436
+ min-height: 52px; padding: 0 var(--pos-2x) 0 var(--pos-3x);
437
+ border: 2px solid transparent; border-radius: var(--pos-r);
438
+ /* Ink is fixed and opaque. Every pastel here sits above 85% lightness, so
439
+ near-black lands well past 4.5:1 — and nothing dims it, because the one
440
+ job a category colour has is to be readable across the room. */
441
+ color: #101318;
442
+ text-align: left; cursor: pointer;
443
+ transition: box-shadow var(--pos-t) var(--pos-e), transform var(--pos-t) var(--pos-e);
444
+ }
445
+ .pos-cat:hover { box-shadow: 0 0 0 2px var(--pos-line-strong); }
446
+ .pos-cat { box-shadow: 0 0 0 0 transparent; }
447
+ .pos-cat:active { transform: scale(0.985); }
448
+ /* Selection is a ring, not a brightness change: dimming the unselected ones is
449
+ what pushed their labels under contrast in the first place. */
450
+ /* Two rings, because one cannot work on both sides at once: a dark ring reads
451
+ against the pastel and vanishes into the canvas; a light one reads against
452
+ the canvas and vanishes into the pastel. Dark inside separates the card from
453
+ its own colour, light outside separates it from the room. */
454
+ .pos-cat[aria-pressed='true'] {
455
+ border-color: #101318;
456
+ box-shadow:
457
+ 0 0 0 2px #101318,
458
+ 0 0 0 5px var(--pos-fg),
459
+ 0 8px 20px -6px rgba(0, 0, 0, 0.8);
460
+ transform: translateY(-2px);
461
+ }
462
+ .pos-cat[aria-pressed='true'] .pos-cat__name { font-weight: 800; }
463
+ .pos-cat[aria-pressed='true'] .pos-cat__icon { background: rgba(0, 0, 0, 0.22); }
464
+ /* The glyph sits right, as a badge: leading it made every card start with a
465
+ picture and the words line up nowhere. */
466
+ .pos-cat__icon {
467
+ order: 2; margin-left: auto; flex: none;
468
+ width: 26px; height: 26px; display: grid; place-items: center;
469
+ border-radius: 50%; background: rgba(0, 0, 0, 0.10);
470
+ }
471
+ .pos-cat__name { display: block; font-size: 14px; font-weight: 700; letter-spacing: -0.015em; line-height: 1.15; }
472
+ .pos-cat__n { font-size: 11px; font-weight: 600; color: rgba(16, 19, 24, 0.60); line-height: 1.2; }
473
+
474
+ .pos-items {
475
+ flex: 1; display: grid; align-content: start; padding-bottom: var(--pos-6x);
476
+ grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
477
+ gap: var(--pos-3x); padding: var(--pos-4x);
478
+ }
479
+ .pos-item {
480
+ display: flex; flex-direction: column; gap: var(--pos-1x);
481
+ min-height: 128px; padding: var(--pos-3x);
482
+ position: relative;
483
+ border: 1px solid var(--pos-line); border-radius: var(--pos-r);
484
+ background: var(--pos-2); text-align: left;
485
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e);
486
+ }
487
+ /* The stripe is the card's own left edge, clipped by the card's radius — a
488
+ floating pill inset from the corners reads as a sticker someone put there.
489
+ And it is the same element that fills: ringing the item grows the edge
490
+ across the whole card, so "this is on the table" is one continuous motion
491
+ instead of a colour that pops out of nowhere. */
492
+ .pos-item { overflow: hidden; padding-left: calc(var(--pos-3x) + 5px); }
493
+ .pos-item::before {
494
+ content: ''; position: absolute; inset: 0 auto 0 0; width: 5px;
495
+ background: var(--pos-item-tone, var(--pos-line-strong));
496
+ transition: width 280ms var(--pos-spring);
497
+ }
498
+ .pos-item[data-filled='true']::before { width: 100%; }
499
+ /* Content rides above the growing edge. */
500
+ .pos-item > * { position: relative; z-index: 1; }
501
+ .pos-item { cursor: pointer; }
502
+ .pos-item:hover { background: var(--pos-3); }
503
+ .pos-item:active { transform: scale(0.99); }
504
+ /* Quantity on the ticket paints the card in its own category identity — the
505
+ most legible selected state there is at arm's length. */
506
+ .pos-item { transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e), color 180ms 90ms linear; }
507
+ .pos-item[data-filled='true'] { color: #121214; border-color: transparent; }
508
+ .pos-item__route { font-size: 10px; font-weight: 650; letter-spacing: 0.07em; text-transform: uppercase; color: var(--pos-fg-3); }
509
+ .pos-item__name { font-size: 15px; font-weight: 600; line-height: 1.25; }
510
+ .pos-item__price { font-size: 13px; font-weight: 550; color: var(--pos-fg-2); }
511
+ /* On a filled card the secondary text needs its own ink: --pos-fg-2 is tuned
512
+ for a dark surface and turns invisible on a pastel one. */
513
+ .pos-item[data-filled='true'] .pos-item__route { color: rgba(16, 19, 24, 0.6); }
514
+ .pos-item[data-filled='true'] .pos-item__price { color: rgba(16, 19, 24, 0.72); }
515
+ .pos-item__foot { margin-top: auto; display: flex; align-items: center; justify-content: flex-end; gap: var(--pos-2x); }
516
+
517
+ /* The stepper on the card: add to THIS table without leaving the grid. */
518
+ .pos-step { display: inline-flex; align-items: center; gap: var(--pos-1x); }
519
+ .pos-step__b {
520
+ width: var(--pos-hit-3); height: var(--pos-hit-3);
521
+ display: grid; place-items: center;
522
+ /* Explicit colours, not opacity: a control faded to 0.2 is a control the
523
+ operator cannot see, and "why is there no minus" is a support call. */
524
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r-xs);
525
+ background: var(--pos-2); color: var(--pos-fg); cursor: pointer;
526
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e);
527
+ }
528
+ .pos-step__b:hover:not(:disabled) { background: var(--pos-3); border-color: var(--pos-fg-3); }
529
+ .pos-step__b:disabled { color: var(--pos-fg-3); border-color: var(--pos-line); cursor: not-allowed; }
530
+ /* On a pastel-filled card the dark ink is the readable one. */
531
+ .pos-item[data-filled='true'] .pos-step__b {
532
+ background: rgba(255,255,255,0.55); border-color: rgba(0,0,0,0.18); color: #121214;
533
+ }
534
+ .pos-item[data-filled='true'] .pos-step__b:hover:not(:disabled) { background: rgba(255,255,255,0.85); }
535
+ .pos-item[data-filled='true'] .pos-step__b:disabled { color: rgba(0,0,0,0.35); }
536
+ .pos-step__n { min-width: 26px; text-align: center; font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; }
537
+
538
+ /* --- list view ---------------------------------------------------------- */
539
+
540
+ .pos-list { flex: 1; display: flex; flex-direction: column; }
541
+ .pos-lrow {
542
+ display: grid; grid-template-columns: 40px minmax(0, 1fr) auto;
543
+ align-items: center; gap: var(--pos-3x);
544
+ min-height: var(--pos-hit); padding: var(--pos-2x) var(--pos-4x);
545
+ border: none; border-bottom: 1px solid var(--pos-line);
546
+ background: transparent; text-align: left; cursor: pointer;
547
+ transition: background var(--pos-t) var(--pos-e);
548
+ }
549
+ .pos-lrow:hover { background: var(--pos-2); }
550
+ .pos-lrow { --pos-edge: transparent; }
551
+ .pos-lrow[aria-current='true'] { background: var(--pos-2); --pos-edge: var(--pos-accent); }
552
+ .pos-lrow__av {
553
+ width: 40px; height: 40px; display: grid; place-items: center;
554
+ border-radius: var(--pos-r-sm); background: var(--pos-3);
555
+ font-size: 13px; font-weight: 700;
556
+ }
557
+ .pos-lrow__t { display: block; font-size: 15px; font-weight: 600; }
558
+ .pos-lrow__s { display: block; font-size: 13px; color: var(--pos-fg-2); }
559
+ .pos-lrow__e { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; }
560
+
561
+ /* --- action bar --------------------------------------------------------- */
562
+
563
+
564
+
565
+
566
+ .pos-kbd {
567
+ padding: 1px 5px; border: 1px solid var(--pos-line-strong); border-radius: 4px;
568
+ background: var(--pos-0); color: var(--pos-fg-3);
569
+ font-size: 10px; font-weight: 700; font-family: inherit; letter-spacing: 0.03em;
570
+ }
571
+
572
+ /* --- right rail: order + table detail (permanent) ----------------------- */
573
+
574
+ .pos-aside {
575
+ display: flex; flex-direction: column; min-height: 0; min-width: 0;
576
+ background: var(--pos-1); border-left: 1px solid var(--pos-line);
577
+ padding-right: var(--pos-safe-r);
578
+ }
579
+ .pos-tkt {
580
+ height: var(--pos-header-h); padding: 0 var(--pos-4x);
581
+ display: flex; flex-direction: column; justify-content: center; gap: 2px;
582
+ border-bottom: 1px solid var(--pos-line);
583
+ }
584
+ .pos-tkt__row { display: flex; align-items: center; gap: var(--pos-2x); min-width: 0; }
585
+ .pos-tkt__row > .pos-t-xl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
586
+ .pos-tkt__sub {
587
+ display: flex; align-items: baseline; gap: 6px; min-width: 0;
588
+ font-size: 13px; color: var(--pos-fg-2); line-height: 1.3;
589
+ }
590
+ .pos-tkt__meta { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
591
+
592
+ .pos-icon-btn {
593
+ width: 34px; height: 34px; flex: none;
594
+ display: grid; place-items: center;
595
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r-xs);
596
+ background: transparent; color: var(--pos-fg-2); cursor: pointer;
597
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
598
+ }
599
+ .pos-icon-btn:hover { background: var(--pos-2); color: var(--pos-fg); }
600
+
601
+ /* Tabs, because everything the panel can say does not fit above a closing
602
+ total — and what was getting squeezed was the list the operator reads. */
603
+ .pos-tabs {
604
+ display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
605
+ padding: 0 var(--pos-4x); border-bottom: 1px solid var(--pos-line);
606
+ }
607
+ .pos-tab {
608
+ position: relative;
609
+ display: inline-flex; align-items: center; justify-content: center; gap: 6px;
610
+ min-height: 44px; border: none; background: transparent;
611
+ border-bottom: 2px solid transparent; margin-bottom: -1px;
612
+ color: var(--pos-fg-3); font-size: 14px; font-weight: 600; cursor: pointer;
613
+ transition: color var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e);
614
+ }
615
+ .pos-tab:hover { color: var(--pos-fg-2); }
616
+ .pos-tab[aria-selected='true'] { color: var(--pos-fg); border-bottom-color: var(--pos-accent); }
617
+ .pos-tab__n {
618
+ min-width: 20px; height: 18px; padding: 0 5px; display: grid; place-items: center;
619
+ border-radius: 9px; background: var(--pos-3); color: var(--pos-fg-2);
620
+ font-size: 11px; font-weight: 700;
621
+ }
622
+ .pos-tab[aria-selected='true'] .pos-tab__n { background: var(--pos-4); color: var(--pos-fg); }
623
+ .pos-tab__dot { width: 7px; height: 7px; border-radius: 50%; background: var(--pos-warn); }
624
+
625
+ .pos-activity { padding: var(--pos-3x) var(--pos-4x); display: grid; gap: var(--pos-4x); }
626
+
627
+
628
+
629
+ .pos-ln {
630
+ display: grid; grid-template-columns: 22px minmax(0, 1fr) auto auto;
631
+ align-items: center; gap: var(--pos-2x);
632
+ min-height: 56px; margin: 0 var(--pos-3x) var(--pos-2x);
633
+ padding: var(--pos-2x) var(--pos-3x);
634
+ border: 1px solid transparent; border-radius: var(--pos-r);
635
+ background: var(--pos-2); text-align: left; cursor: pointer;
636
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e);
637
+ }
638
+ .pos-ln:first-of-type { margin-top: var(--pos-2x); }
639
+ .pos-ln:hover { background: var(--pos-3); }
640
+ .pos-ln[aria-current='true'] { background: var(--pos-3); border-color: var(--pos-line-strong); }
641
+ .pos-ln__i { font-size: 12px; font-weight: 650; color: var(--pos-fg-3); }
642
+ .pos-ln__n { display: block; font-size: 14px; font-weight: 550; line-height: 1.3; }
643
+ .pos-ln__note { display: block; font-size: 12px; color: var(--pos-fg-2); }
644
+ .pos-ln__p { font-size: 14px; font-weight: 600; min-width: 78px; text-align: right; }
645
+ .pos-ln .pos-step__b { border-color: var(--pos-line-strong); opacity: 1; color: var(--pos-fg); width: 32px; height: 32px; }
646
+ .pos-ln .pos-step__n { min-width: 20px; font-size: 14px; }
647
+
648
+ .pos-tot { padding: var(--pos-3x) var(--pos-4x); border-top: 1px solid var(--pos-line); background: transparent; }
649
+ .pos-tot__r { display: flex; align-items: baseline; justify-content: space-between; gap: var(--pos-3x); font-size: 13px; color: var(--pos-fg-2); margin-bottom: 6px; }
650
+ .pos-tot__r > span:last-child { color: var(--pos-fg-2); font-weight: 500; }
651
+ .pos-tot__g { margin-top: var(--pos-2x); padding-top: var(--pos-2x); border-top: 1px dashed var(--pos-line-strong); }
652
+ .pos-tot__g { display: flex; justify-content: space-between; align-items: baseline; gap: var(--pos-3x); margin-top: var(--pos-3x); padding-top: var(--pos-3x); border-top: 1px dashed var(--pos-line-strong); }
653
+
654
+ .pos-pay { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: var(--pos-3x); padding: var(--pos-3x) var(--pos-4x) 0; }
655
+ /* Square holds the glyph; the word sits outside it. Keeping the label out of
656
+ the box lets the box stay a proper touch square instead of a tall pill
657
+ stretched to fit two lines of text. */
658
+ .pos-pay__b {
659
+ display: flex; flex-direction: column; align-items: center; gap: 7px;
660
+ border: none; background: none; padding: 0; cursor: pointer;
661
+ color: var(--pos-fg-2); font-size: 13px; font-weight: 550;
662
+ transition: color var(--pos-t) var(--pos-e);
663
+ }
664
+ .pos-pay__b:hover { color: var(--pos-fg); }
665
+ .pos-pay__b span {
666
+ width: 100%; min-height: var(--pos-hit);
667
+ display: grid; place-items: center;
668
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r);
669
+ background: var(--pos-2);
670
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
671
+ }
672
+ .pos-pay__b:hover span { background: var(--pos-3); }
673
+ /* The radio reports itself with aria-checked, not aria-pressed — the selected
674
+ method has to read as chosen from across the counter, so it goes solid. */
675
+ .pos-pay__b[aria-checked='true'] { color: var(--pos-fg); font-weight: 620; }
676
+ .pos-pay__b[aria-checked='true'] span {
677
+ background: var(--pos-primary); border-color: var(--pos-primary); color: var(--pos-primary-fg);
678
+ box-shadow: 0 0 0 3px rgba(247, 247, 248, 0.16);
679
+ }
680
+
681
+ .pos-cta { padding: var(--pos-4x); padding-bottom: calc(var(--pos-4x) + var(--pos-safe-b)); }
682
+ .pos-btn {
683
+ display: inline-flex; align-items: center; justify-content: center; gap: var(--pos-2x);
684
+ width: 100%; min-height: 60px;
685
+ border: 1px solid var(--pos-primary); border-radius: var(--pos-r-pill, 999px);
686
+ background: var(--pos-primary); color: var(--pos-primary-fg);
687
+ font-size: 17px; font-weight: 620; letter-spacing: -0.012em; cursor: pointer;
688
+ transition: background var(--pos-t) var(--pos-e), transform var(--pos-t) var(--pos-e);
689
+ }
690
+ .pos-btn:hover:not(:disabled) { background: #FFFFFF; border-color: #FFFFFF; }
691
+ .pos-btn:active:not(:disabled) { transform: scale(0.99); }
692
+ .pos-btn:disabled { background: var(--pos-2); border-color: var(--pos-line); color: var(--pos-fg-3); cursor: not-allowed; }
693
+ .pos-btn .pos-kbd { background: rgba(0,0,0,0.08); border-color: rgba(0,0,0,0.16); color: inherit; opacity: 0.75; }
694
+
695
+ /* Expanded-only detail: the docked rail stays a working surface rather than a
696
+ cramped report. */
697
+
698
+
699
+
700
+
701
+ /* --- empty states ------------------------------------------------------- */
702
+
703
+ .pos-empty {
704
+ flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
705
+ gap: var(--pos-2x); padding: var(--pos-8x) var(--pos-4x); text-align: center; color: var(--pos-fg-3);
706
+ }
707
+ .pos-empty__icon { width: 44px; height: 44px; display: grid; place-items: center; border-radius: 50%; background: var(--pos-2); color: var(--pos-fg-3); }
708
+ .pos-empty__t { font-size: 15px; font-weight: 600; color: var(--pos-fg-2); }
709
+ .pos-empty__s { font-size: 13px; max-width: 34ch; }
710
+
711
+ /* --- bottom dock: open tickets ------------------------------------------ */
712
+
713
+ .pos-dock {
714
+ display: flex; gap: 0; padding: 0 var(--pos-2x);
715
+ background: var(--pos-0); border-top: 1px solid var(--pos-line);
716
+ /* Scroll snapping so a chip never comes to rest half-cut at the edge. */
717
+ scroll-snap-type: x proximity; scroll-padding-inline: var(--pos-4x);
718
+ overflow-x: auto; scrollbar-width: none;
719
+ }
720
+ .pos-dock::-webkit-scrollbar { display: none; }
721
+ .pos-dchip {
722
+ flex: 0 0 auto; display: flex; align-items: center; gap: var(--pos-3x);
723
+ min-height: 68px; padding: 0 var(--pos-4x);
724
+ /* No card. Five bordered boxes in a strip is five frames competing with the
725
+ one thing that matters — which table you are on. A hairline between
726
+ neighbours does the separating; the active one does the shouting. */
727
+ border: none; border-radius: 0;
728
+ background: transparent; text-align: left; cursor: pointer;
729
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
730
+ }
731
+ .pos-dchip { scroll-snap-align: start; }
732
+ .pos-dchip + .pos-dchip { box-shadow: inset 1px 0 0 var(--pos-line); }
733
+ .pos-dchip:hover { background: var(--pos-1); }
734
+ /* The state reads on the edge instead of on a saturated tag square: enough
735
+ colour to scan the row by, not enough to make five chips shout at once. */
736
+ .pos-dchip[data-status='occupied'] { --pos-edge: var(--pos-warn); }
737
+ .pos-dchip[data-status='bill_requested'] { --pos-edge: var(--pos-info); }
738
+ .pos-dchip[data-status='attention'] { --pos-edge: var(--pos-danger); }
739
+ .pos-dchip[data-status='free'] { --pos-edge: var(--pos-ok); }
740
+ /* The active one inverts. On a near-black strip the loudest thing available is
741
+ light, and "which table am I on" has to be answerable without reading. */
742
+ .pos-dchip[aria-current='true'] {
743
+ background: var(--pos-primary); color: var(--pos-primary-fg);
744
+ }
745
+ .pos-dchip[aria-current='true'] .pos-dchip__t { color: var(--pos-primary-fg); }
746
+ .pos-dchip[aria-current='true'] .pos-dchip__s { color: rgba(18, 18, 20, 0.66); }
747
+ .pos-dchip[aria-current='true'] .pos-dchip__tag { background: rgba(18, 18, 20, 0.10); color: var(--pos-primary-fg); }
748
+ .pos-dchip[aria-current='true']::before { display: none; }
749
+ /* The tag is an identifier, not a status. Painting it with the status colour
750
+ made a row of saturated squares that all shouted equally; the state now
751
+ rides a small pill where it can be read instead of just seen. */
752
+ .pos-dchip__tag {
753
+ min-width: 36px; height: 32px; padding: 0 7px; display: grid; place-items: center;
754
+ border-radius: var(--pos-r-xs); background: var(--pos-2); color: var(--pos-fg);
755
+ font-size: 13px; font-weight: 700;
756
+ }
757
+ .pos-dchip__st {
758
+ margin-left: var(--pos-2x); width: 26px; height: 26px; flex: none;
759
+ display: grid; place-items: center; border-radius: 50%;
760
+ }
761
+ .pos-dchip__st[data-status='occupied'] { background: color-mix(in srgb, var(--pos-warn) 18%, transparent); color: var(--pos-warn); }
762
+ .pos-dchip__st[data-status='bill_requested'] { background: color-mix(in srgb, var(--pos-info) 18%, transparent); color: var(--pos-info); }
763
+ .pos-dchip__st[data-status='attention'] { background: color-mix(in srgb, var(--pos-danger) 18%, transparent); color: var(--pos-danger); }
764
+ .pos-dchip__st[data-status='free'] { background: color-mix(in srgb, var(--pos-ok) 18%, transparent); color: var(--pos-ok); }
765
+ .pos-dchip__t { display: block; font-size: 13.5px; font-weight: 620; line-height: 1.25; color: var(--pos-fg); }
766
+ .pos-dchip__s { display: block; font-size: 12.5px; color: var(--pos-fg-2); line-height: 1.3; }
767
+
768
+ /* --- responsive ---------------------------------------------------------
769
+ Three real terminals: the 24" counter machine, the 13" till, the 10" waiter
770
+ tablet. The details column is the LAST thing to give ground — the order is
771
+ the job.
772
+ ------------------------------------------------------------------------- */
773
+
774
+ @media (max-width: 1439px) {
775
+ .pos-body { grid-template-columns: 208px minmax(0, 1fr) 364px; }
776
+ }
777
+
778
+ @media (max-width: 1180px) {
779
+ /* The rail keeps its icons and loses its words before it loses itself: a
780
+ collapsed rail still says where you are, a hidden one does not. */
781
+ .pos-body { grid-template-columns: 68px minmax(0, 1fr) 344px; }
782
+ .pos-rail { padding: var(--pos-3x) var(--pos-2x); }
783
+ .pos-rail__label { display: none; }
784
+ .pos-rail__top { display: none; }
785
+ .pos-field { justify-content: center; padding: 0; margin: var(--pos-2x) 0; }
786
+ .pos-field input, .pos-field__hint, .pos-field__clear { display: none; }
787
+ .pos-nav__item { justify-content: center; padding: 0; }
788
+ .pos-nav__text { display: none; }
789
+ .pos-nav__count { position: absolute; top: 4px; right: 6px; margin: 0; }
790
+ }
791
+
792
+ @media (max-width: 900px) {
793
+ /* Tablet in a waiter's hand: content and the order, nothing else. */
794
+ .pos-body { grid-template-columns: 60px minmax(0, 1fr); }
795
+ .pos-aside {
796
+ position: absolute; inset: 0 0 0 auto; width: min(420px, 88vw); z-index: 30;
797
+ transform: translateX(101%); box-shadow: -24px 0 48px rgba(0,0,0,0.5);
798
+ transition: transform 220ms var(--pos-e);
799
+ }
800
+ .pos-aside[data-open='true'] { transform: translateX(0); }
801
+ }
802
+
803
+ @media (max-width: 640px) {
804
+ .pos-floor { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); padding: var(--pos-3x); }
805
+ .pos-items { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); padding: var(--pos-3x); }
806
+ .pos-head { flex-wrap: wrap; }
807
+ .pos-field { max-width: none; margin-left: 0; width: 100%; }
808
+ }
809
+
810
+ /* --- floor: plan layout -------------------------------------------------
811
+ The room as it actually is. Costs someone a layout session; pays for itself
812
+ with a waiter who finds table 12 by looking where table 12 is, instead of
813
+ reading eight labels.
814
+ ------------------------------------------------------------------------- */
815
+
816
+ .pos-plan {
817
+ flex: 1; position: relative; margin: var(--pos-4x); min-height: 0;
818
+ display: flex; flex-direction: column;
819
+ border: 1px solid var(--pos-line); border-radius: var(--pos-r);
820
+ background:
821
+ linear-gradient(var(--pos-line) 1px, transparent 1px) 0 0 / 100% var(--pos-plan-cell, 44px),
822
+ linear-gradient(90deg, var(--pos-line) 1px, transparent 1px) 0 0 / var(--pos-plan-cell, 44px) 100%,
823
+ var(--pos-1);
824
+ background-blend-mode: normal;
825
+ overflow: auto;
826
+ }
827
+ .pos-plan__canvas { position: relative; margin: var(--pos-3x) auto; flex: none; }
828
+ .pos-plan { align-items: center; }
829
+ .pos-pt {
830
+ position: absolute;
831
+ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
832
+ border: 1.5px solid var(--pos-line-strong); border-radius: var(--pos-r-sm);
833
+ background: var(--pos-3); color: var(--pos-fg);
834
+ cursor: pointer; text-align: center;
835
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e), transform var(--pos-t) var(--pos-e);
836
+ }
837
+ .pos-pt:hover { transform: scale(1.03); z-index: 3; }
838
+ .pos-pt[data-shape='round'] { border-radius: 50%; }
839
+ .pos-pt[data-shape='bar'] { border-radius: var(--pos-r-xs); }
840
+ .pos-pt[data-status='free'] { background: var(--pos-3); border-color: var(--pos-line-strong); color: var(--pos-fg-2); }
841
+ .pos-pt[data-status='occupied'] { background: var(--pos-warn); border-color: var(--pos-warn); color: var(--pos-on-light); }
842
+ .pos-pt[data-status='bill_requested'] { background: var(--pos-info); border-color: var(--pos-info); color: #fff; }
843
+ .pos-pt[data-status='attention'] { background: var(--pos-danger); border-color: var(--pos-danger); color: #fff; }
844
+ /* The selected table is the answer to "where am I", and a 2px hairline at plan
845
+ scale is not an answer. A thick ring, a halo that separates it from its
846
+ neighbours, and a nudge forward. */
847
+ .pos-pt[aria-current='true'] {
848
+ outline: 3px solid var(--pos-fg);
849
+ outline-offset: 3px;
850
+ box-shadow: 0 0 0 7px var(--pos-1), 0 8px 26px -6px rgba(0, 0, 0, 0.8);
851
+ transform: scale(1.04);
852
+ z-index: 5;
853
+ }
854
+ .pos-pt__no { font-size: 14px; font-weight: 700; line-height: 1; }
855
+ .pos-pt__n { font-size: 10px; font-weight: 600; opacity: 0.75; line-height: 1; }
856
+
857
+ /* Seats: small dots hugging the table, so a six-top reads as a six-top. */
858
+ .pos-seat {
859
+ position: absolute; width: 13px; height: 13px; border-radius: 50%;
860
+ background: var(--pos-3); border: 1px solid var(--pos-line-strong);
861
+ pointer-events: none;
862
+ }
863
+ .pos-pt[data-status='occupied'] ~ .pos-seat { }
864
+
865
+ .pos-plan__legend {
866
+ position: sticky; bottom: 0; left: 0; margin-top: auto; z-index: 5;
867
+ backdrop-filter: blur(6px);
868
+ display: flex; gap: var(--pos-4x); padding: var(--pos-2x) var(--pos-3x);
869
+ background: color-mix(in srgb, var(--pos-1) 88%, transparent);
870
+ border-top: 1px solid var(--pos-line); font-size: 12px; color: var(--pos-fg-2);
871
+ }
872
+ .pos-legend__k { display: inline-flex; align-items: center; gap: 6px; }
873
+ .pos-legend__sw { width: 12px; height: 12px; border-radius: 3px; }
874
+
875
+ /* --- modifier sheet -----------------------------------------------------
876
+ Only for items that ask a question. A sheet in front of a beer is a tax on
877
+ every round.
878
+ ------------------------------------------------------------------------- */
879
+
880
+ .pos-sheet { position: fixed; inset: 0; z-index: 60; display: grid; place-items: center; }
881
+ .pos-sheet__scrim {
882
+ position: absolute; inset: 0;
883
+ /* Strong enough to isolate the foreground; below that the floor behind keeps
884
+ competing for the eye at exactly the moment a decision is due. */
885
+ background: rgba(4, 5, 7, 0.62);
886
+ backdrop-filter: blur(2px);
887
+ }
888
+ .pos-sheet__panel {
889
+ position: relative; display: flex; flex-direction: column;
890
+ width: min(560px, 92vw); max-height: min(80vh, 720px);
891
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r);
892
+ background: var(--pos-1); box-shadow: 0 32px 64px rgba(0, 0, 0, 0.55);
893
+ animation: pos-sheet-in 180ms var(--pos-e);
894
+ }
895
+ @keyframes pos-sheet-in { from { opacity: 0; transform: translateY(8px) scale(0.99); } }
896
+ @media (prefers-reduced-motion: reduce) { .pos-sheet__panel { animation: none; } }
897
+
898
+ .pos-sheet__head {
899
+ display: flex; align-items: flex-start; justify-content: space-between; gap: var(--pos-3x);
900
+ padding: var(--pos-4x); border-bottom: 1px solid var(--pos-line);
901
+ }
902
+ .pos-sheet__body { padding: var(--pos-4x); display: grid; gap: var(--pos-4x); min-height: 0; }
903
+ .pos-sheet__foot {
904
+ display: grid; grid-template-columns: auto 1fr; gap: var(--pos-2x);
905
+ padding: var(--pos-3x) var(--pos-4x) var(--pos-4x); border-top: 1px solid var(--pos-line);
906
+ }
907
+ /* The secondary action carries no border and no fill. A second bordered box
908
+ under the primary reads as a second primary, and on a screen where the one
909
+ white pill is THE action, that competition is the whole problem. */
910
+ .pos-btn--ghost {
911
+ width: auto; padding: 0 var(--pos-6x); min-height: 52px;
912
+ border: none; border-radius: var(--pos-r);
913
+ background: transparent; color: var(--pos-fg-2);
914
+ font-size: 15px; font-weight: 550;
915
+ }
916
+ .pos-btn--ghost:hover:not(:disabled) { background: var(--pos-2); color: var(--pos-fg); }
917
+
918
+ .pos-mgroup__head { display: flex; align-items: center; gap: var(--pos-2x); margin-bottom: var(--pos-2x); }
919
+ .pos-mgroup__req {
920
+ padding: 1px 7px; border-radius: var(--pos-r-xs);
921
+ background: color-mix(in srgb, var(--pos-warn) 18%, transparent); color: var(--pos-warn);
922
+ font-size: 11px; font-weight: 700;
923
+ }
924
+ .pos-mgroup__opts { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: var(--pos-2x); }
925
+ .pos-mopt {
926
+ display: flex; align-items: center; gap: var(--pos-2x);
927
+ min-height: var(--pos-hit-2); padding: 0 var(--pos-3x);
928
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r);
929
+ background: var(--pos-2); color: var(--pos-fg-2); text-align: left; cursor: pointer;
930
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
931
+ }
932
+ .pos-mopt:hover { background: var(--pos-3); color: var(--pos-fg); }
933
+ .pos-mopt[aria-pressed='true'] { border-color: var(--pos-accent); background: var(--pos-accent-dim); color: var(--pos-fg); }
934
+ .pos-mopt__check {
935
+ width: 20px; height: 20px; flex: none; display: grid; place-items: center;
936
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r-xs); color: var(--pos-accent);
937
+ }
938
+ .pos-mopt[aria-pressed='true'] .pos-mopt__check { border-color: var(--pos-accent); }
939
+ .pos-mopt__label { flex: 1; min-width: 0; font-size: 14px; font-weight: 550; }
940
+ .pos-mopt__delta { font-size: 13px; font-weight: 600; color: var(--pos-fg-2); }
941
+
942
+ .pos-note {
943
+ width: 100%; min-height: var(--pos-hit-2); padding: 0 var(--pos-3x);
944
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r);
945
+ background: var(--pos-0); color: var(--pos-fg); font: inherit; font-size: 15px; outline: none;
946
+ }
947
+ .pos-note:focus { border-color: var(--pos-accent); }
948
+ .pos-note::placeholder { color: var(--pos-fg-3); }
949
+
950
+ /* Modifiers as they read on the ticket line. */
951
+ .pos-ln__mods { display: block; font-size: 12px; color: var(--pos-fg-2); line-height: 1.35; }
952
+ .pos-ln__mod { display: inline-block; margin-right: 8px; }
953
+ .pos-ln__mod::before { content: '· '; color: var(--pos-fg-3); }
954
+
955
+ /* --- order detail: total, timeline, per-line state ----------------------
956
+ What the reference gets right and a plain list does not: the order carries
957
+ a clock and each line carries a state. "Fired 20 minutes ago, nothing
958
+ delivered" is the sentence a floor manager needs the screen to say.
959
+ ------------------------------------------------------------------------- */
960
+
961
+
962
+
963
+
964
+
965
+
966
+
967
+
968
+
969
+
970
+ .pos-ln__state {
971
+ width: 22px; height: 22px; flex: none;
972
+ display: grid; place-items: center; border-radius: 50%;
973
+ background: var(--pos-3); color: var(--pos-fg-2);
974
+ }
975
+ /* Pending is the only state that still needs someone to do something, so it
976
+ carries the edge and the marker. It does NOT carry a word: right after
977
+ ringing, EVERY line is pending, and a label on each one turns the whole list
978
+ amber — at which point nothing is marked. The count lives once, below. */
979
+ /* The menu card's pattern: the stripe IS the row's left edge, clipped by the
980
+ row's own radius. One edge treatment across the product — the item grid, the
981
+ floor, the board and now the ticket. */
982
+ .pos-ln { overflow: hidden; }
983
+ .pos-ln[data-state='pending']::before {
984
+ content: ''; position: absolute; inset: 0 auto 0 0; width: 4px;
985
+ background: var(--pos-warn);
986
+ }
987
+ .pos-ln[data-state='pending'] { padding-left: calc(var(--pos-4x) + 4px); }
988
+ .pos-ln[data-state='pending'] .pos-ln__state {
989
+ background: var(--pos-warn); color: #14100A;
990
+ }
991
+ .pos-ln[data-state='fired'] .pos-ln__state { background: color-mix(in srgb, var(--pos-warn) 22%, transparent); }
992
+ .pos-ln[data-state='delivered'] .pos-ln__state { background: color-mix(in srgb, var(--pos-ok) 22%, transparent); }
993
+ .pos-ln[data-state='pending'] .pos-ln__state { color: var(--pos-fg-3); }
994
+ .pos-ln[data-state='fired'] .pos-ln__state { color: var(--pos-warn); }
995
+ .pos-ln[data-state='delivered'] .pos-ln__state { color: var(--pos-ok); }
996
+ /* A delivered line is history: it stays readable but stops competing with the
997
+ lines the operator can still act on. */
998
+ .pos-ln[data-state='delivered'] .pos-ln__n { color: var(--pos-fg-2); }
999
+
1000
+ /* --- plan table: server chip + item count ------------------------------- */
1001
+
1002
+ .pos-pt__badge {
1003
+ position: absolute; top: -9px; left: 50%; transform: translateX(-50%);
1004
+ min-width: 24px; height: 18px; padding: 0 5px;
1005
+ display: grid; place-items: center; border-radius: 9px;
1006
+ background: var(--pos-4); color: var(--pos-fg);
1007
+ font-size: 11px; font-weight: 700; border: 1px solid var(--pos-0);
1008
+ }
1009
+ .pos-pt__chip {
1010
+ position: absolute; bottom: -8px; left: 50%; transform: translateX(-50%);
1011
+ min-width: 26px; height: 17px; padding: 0 5px;
1012
+ display: grid; place-items: center; border-radius: var(--pos-r-xs);
1013
+ font-size: 10px; font-weight: 800; letter-spacing: 0.02em;
1014
+ color: #101318; border: 1px solid var(--pos-0);
1015
+ }
1016
+
1017
+ /* --- table meta grid (expanded rail) ------------------------------------ */
1018
+
1019
+
1020
+
1021
+
1022
+
1023
+
1024
+
1025
+ /* --- open-table sheet: one question, answered with a thumb -------------- */
1026
+
1027
+ .pos-guests {
1028
+ display: flex; align-items: center; justify-content: center; gap: var(--pos-4x);
1029
+ padding: var(--pos-3x) 0;
1030
+ }
1031
+ .pos-guests__pm { width: 56px; height: 56px; border-color: var(--pos-line-strong); color: var(--pos-fg); opacity: 1; }
1032
+ .pos-guests__n { font-size: 48px; font-weight: 700; letter-spacing: -0.03em; min-width: 72px; text-align: center; }
1033
+ .pos-quick { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--pos-2x); }
1034
+ .pos-quick__b {
1035
+ min-height: var(--pos-hit-2);
1036
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r);
1037
+ background: var(--pos-2); color: var(--pos-fg-2);
1038
+ font-size: 17px; font-weight: 650; cursor: pointer;
1039
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e);
1040
+ }
1041
+ .pos-quick__b:hover { background: var(--pos-3); color: var(--pos-fg); }
1042
+ .pos-quick__b[aria-pressed='true'] { background: var(--pos-primary); border-color: var(--pos-primary); color: var(--pos-primary-fg); }
1043
+
1044
+ /* --- PIN pad ------------------------------------------------------------ */
1045
+
1046
+ .pos-pin__dots { display: flex; justify-content: center; gap: var(--pos-3x); padding: var(--pos-2x) 0; }
1047
+ .pos-pin__dot {
1048
+ width: 16px; height: 16px; border-radius: 50%;
1049
+ border: 2px solid var(--pos-line-strong); background: transparent;
1050
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e);
1051
+ }
1052
+ .pos-pin__dot[data-on='true'] { background: var(--pos-accent); border-color: var(--pos-accent); }
1053
+ .pos-pin__err { margin: 0 0 var(--pos-2x); text-align: center; font-size: 13px; font-weight: 600; color: var(--pos-danger); min-height: 18px; }
1054
+ .pos-pad { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--pos-2x); }
1055
+ .pos-pad__b {
1056
+ min-height: 64px;
1057
+ display: grid; place-items: center;
1058
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r);
1059
+ background: var(--pos-2); color: var(--pos-fg);
1060
+ font-size: 24px; font-weight: 600; cursor: pointer;
1061
+ transition: background var(--pos-t) var(--pos-e), transform var(--pos-t) var(--pos-e);
1062
+ }
1063
+ .pos-pad__b:hover:not(:disabled) { background: var(--pos-3); }
1064
+ .pos-pad__b:active:not(:disabled) { transform: scale(0.97); }
1065
+ .pos-pad__b:disabled { opacity: 0.4; cursor: not-allowed; }
1066
+ .pos-pin__hint {
1067
+ display: inline-flex; align-items: center; justify-content: center; gap: var(--pos-2x);
1068
+ font-size: 12px; color: var(--pos-fg-3);
1069
+ }
1070
+
1071
+ /* --- split sheet --------------------------------------------------------- */
1072
+
1073
+ .pos-split__line {
1074
+ display: grid; grid-template-columns: minmax(0, 1fr) auto auto;
1075
+ align-items: center; gap: var(--pos-3x);
1076
+ padding: var(--pos-2x) 0; border-bottom: 1px solid var(--pos-line);
1077
+ }
1078
+ .pos-split__name { font-size: 14px; font-weight: 550; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1079
+ .pos-split__pick { display: flex; gap: 4px; flex-wrap: wrap; }
1080
+ .pos-split__b {
1081
+ width: 38px; height: 38px;
1082
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r-xs);
1083
+ background: var(--pos-2); color: var(--pos-fg-2);
1084
+ font-size: 14px; font-weight: 650; cursor: pointer;
1085
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
1086
+ }
1087
+ .pos-split__b:hover { background: var(--pos-3); color: var(--pos-fg); }
1088
+ .pos-split__b[aria-pressed='true'] { background: var(--pos-primary); border-color: var(--pos-primary); color: var(--pos-primary-fg); }
1089
+ .pos-split__v { font-size: 14px; font-weight: 600; min-width: 84px; text-align: right; }
1090
+ .pos-split__sum { margin: var(--pos-3x) 0 0; font-size: 13px; color: var(--pos-fg-2); }
1091
+
1092
+ .pos-parts { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--pos-2x); }
1093
+ .pos-part {
1094
+ display: flex; flex-direction: column; gap: 2px;
1095
+ padding: var(--pos-3x); border: 1px solid var(--pos-line); border-radius: var(--pos-r);
1096
+ background: var(--pos-0);
1097
+ }
1098
+ .pos-part__k { font-size: 11px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--pos-fg-3); }
1099
+ .pos-part__v { font-size: 19px; font-weight: 700; }
1100
+
1101
+ /* --- delivery board ------------------------------------------------------ */
1102
+
1103
+ .pos-board {
1104
+ flex: 1; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr));
1105
+ gap: var(--pos-2x); padding: var(--pos-4x); min-height: 0;
1106
+ }
1107
+ /* Columns are lanes, not floating cards: giving them a surface makes an empty
1108
+ "Pronto" read as a state of the kitchen instead of a rendering gap. */
1109
+ .pos-col {
1110
+ display: flex; flex-direction: column; min-height: 0;
1111
+ border: 1px solid var(--pos-line); border-radius: var(--pos-r);
1112
+ background: var(--pos-1); overflow: hidden;
1113
+ }
1114
+ .pos-col__head {
1115
+ display: flex; align-items: center; gap: var(--pos-2x);
1116
+ padding: var(--pos-3x); border-bottom: 1px solid var(--pos-line);
1117
+ color: var(--pos-fg-2);
1118
+ }
1119
+ .pos-col__t { font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--pos-fg); }
1120
+ .pos-col__n {
1121
+ margin-left: auto; min-width: 20px; height: 18px; padding: 0 5px;
1122
+ display: grid; place-items: center; border-radius: 9px;
1123
+ background: var(--pos-2); font-size: 11px; font-weight: 700; color: var(--pos-fg-2);
1124
+ }
1125
+ .pos-col__body {
1126
+ flex: 1; display: grid; gap: var(--pos-2x); align-content: start;
1127
+ padding: var(--pos-2x); overflow-y: auto; min-height: 0;
1128
+ scrollbar-width: thin; scrollbar-color: var(--pos-line-strong) transparent;
1129
+ }
1130
+ .pos-col__empty { margin: 0; padding: var(--pos-4x) 0; text-align: center; color: var(--pos-fg-3); }
1131
+
1132
+ .pos-card {
1133
+ display: flex; flex-direction: column; gap: var(--pos-1x);
1134
+ padding: var(--pos-3x) var(--pos-3x) var(--pos-3x) calc(var(--pos-3x) + 4px);
1135
+ min-height: var(--pos-hit);
1136
+ border: 1px solid var(--pos-line); border-radius: var(--pos-r); background: var(--pos-2);
1137
+ text-align: left; cursor: pointer;
1138
+ transition: background var(--pos-t) var(--pos-e), border-color var(--pos-t) var(--pos-e);
1139
+ }
1140
+ .pos-card:hover { background: var(--pos-3); }
1141
+ .pos-card { --pos-edge: var(--pos-line-strong); }
1142
+ .pos-card[data-late='true'] { --pos-edge: var(--pos-danger); }
1143
+
1144
+ /* Waiting to be accepted: the edge breathes. Slow enough not to nag from the
1145
+ corner of the eye, bright enough to be the thing you notice on a board where
1146
+ everything else is still. It stops the moment the card is moved — the pulse
1147
+ IS the "somebody deal with this", so it must not outlive the dealing. */
1148
+ .pos-card[data-await='true'] { --pos-edge: var(--pos-warn); }
1149
+ .pos-card[data-await='true']::before { animation: pos-await 1.7s var(--pos-e) infinite; }
1150
+ @keyframes pos-await {
1151
+ 0%, 100% { opacity: 1; }
1152
+ 50% { opacity: 0.28; }
1153
+ }
1154
+ @media (prefers-reduced-motion: reduce) {
1155
+ .pos-card[data-await='true']::before { animation: none; }
1156
+ }
1157
+
1158
+ /* What the card left behind: the shape of it, dissolving where it was. */
1159
+ .pos-card--ghost {
1160
+ min-height: 96px; pointer-events: none;
1161
+ border-style: dashed; background: transparent;
1162
+ animation: pos-ghost 300ms var(--pos-e) forwards;
1163
+ }
1164
+ .pos-card--ghost::before { background: var(--pos-line-strong); }
1165
+ @keyframes pos-ghost {
1166
+ from { opacity: 0.7; transform: none; }
1167
+ to { opacity: 0; transform: translateX(10px) scale(0.97); }
1168
+ }
1169
+ .pos-card__top { display: flex; align-items: center; gap: var(--pos-2x); }
1170
+ .pos-card__id { font-size: 15px; font-weight: 700; color: var(--pos-fg); }
1171
+ .pos-card__ch {
1172
+ padding: 2px 7px; border-radius: var(--pos-r-xs);
1173
+ background: var(--pos-3); color: var(--pos-fg); font-size: 11px; font-weight: 650;
1174
+ }
1175
+ .pos-card__who { font-size: 15px; font-weight: 600; color: var(--pos-fg); }
1176
+ .pos-card__addr {
1177
+ font-size: 12.5px; color: var(--pos-fg-2);
1178
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1179
+ }
1180
+ .pos-card__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--pos-2x); margin-top: var(--pos-1x); }
1181
+ .pos-card__due, .pos-card__late { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; }
1182
+ .pos-card__due { color: var(--pos-fg-2); }
1183
+ .pos-card__late { color: var(--pos-danger); font-weight: 650; white-space: nowrap; }
1184
+ .pos-card__v { font-size: 14px; font-weight: 650; color: var(--pos-fg); }
1185
+ .pos-card__courier { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; color: var(--pos-fg-2); }
1186
+
1187
+ /* --- shortcuts sheet ----------------------------------------------------- */
1188
+
1189
+ .pos-keys { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: var(--pos-2x); }
1190
+ .pos-key {
1191
+ display: flex; align-items: center; gap: var(--pos-2x);
1192
+ padding: var(--pos-2x) var(--pos-3x);
1193
+ border: 1px solid var(--pos-line); border-radius: var(--pos-r);
1194
+ background: var(--pos-2); font-size: 14px;
1195
+ }
1196
+ .pos-key .pos-kbd { flex: none; min-width: 34px; text-align: center; }
1197
+ .pos-key__pin {
1198
+ margin-left: auto; padding: 1px 6px; border-radius: var(--pos-r-xs);
1199
+ background: color-mix(in srgb, var(--pos-warn) 18%, transparent);
1200
+ color: var(--pos-warn); font-size: 10px; font-weight: 700;
1201
+ }
1202
+
1203
+ /* --- dispatch rail ------------------------------------------------------- */
1204
+
1205
+ .pos-dispatch { padding: var(--pos-3x) var(--pos-4x); display: grid; gap: var(--pos-2x); }
1206
+ .pos-fact { display: flex; align-items: flex-start; gap: var(--pos-2x); font-size: 14px; color: var(--pos-fg-2); }
1207
+ .pos-fact svg { flex: none; margin-top: 2px; color: var(--pos-fg-3); }
1208
+ .pos-alert {
1209
+ display: flex; align-items: center; gap: var(--pos-2x);
1210
+ padding: var(--pos-2x) var(--pos-3x); border-radius: var(--pos-r);
1211
+ background: color-mix(in srgb, var(--pos-danger) 16%, transparent);
1212
+ color: var(--pos-danger); font-size: 13px; font-weight: 650;
1213
+ }
1214
+ .pos-paid {
1215
+ display: flex; align-items: center; gap: var(--pos-2x);
1216
+ margin: var(--pos-2x) 0 0; font-size: 12px; font-weight: 600; color: var(--pos-ok);
1217
+ }
1218
+ /* A read-only line has no stepper, so it drops the column instead of leaving a
1219
+ gap where a control used to be. */
1220
+ .pos-ln[data-readonly='true'] { grid-template-columns: 32px minmax(0, 1fr) auto; cursor: default; }
1221
+ .pos-ln[data-readonly='true']:hover { background: transparent; }
1222
+ .pos-ln__qty { font-size: 14px; font-weight: 700; color: var(--pos-fg-2); }
1223
+
1224
+ /* --- marca no topo, pessoa no pé — a ordem da trilha do back office ------ */
1225
+
1226
+ .pos-brand {
1227
+ display: flex; align-items: center; gap: var(--pos-2x);
1228
+ min-height: 40px; margin-bottom: var(--pos-2x); padding: 0 var(--pos-2x);
1229
+ color: var(--pos-fg);
1230
+ }
1231
+ .pos-brand__mark {
1232
+ width: 26px; height: 26px; flex: none; display: grid; place-items: center;
1233
+ font-size: 15px; font-weight: 700;
1234
+ }
1235
+ .pos-brand__name {
1236
+ font-size: 15px; font-weight: 660; letter-spacing: -0.016em;
1237
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1238
+ }
1239
+
1240
+ /* Na borda de baixo. `margin-top: auto` porque esta é a última coisa do rail
1241
+ desde que a lista de turno saiu — sem isso ela cola no último item do menu.
1242
+ O ponto de conexão mora aqui e em nenhum outro lugar: o operador sabe em que
1243
+ máquina está, e o que ele não sabe é se a rede caiu. */
1244
+ .pos-user {
1245
+ display: flex; align-items: center; gap: var(--pos-2x);
1246
+ min-height: 48px; margin-top: auto; padding: var(--pos-2x) var(--pos-2x) 0;
1247
+ padding-bottom: var(--pos-safe-b);
1248
+ border-top: 1px solid var(--pos-line); color: var(--pos-fg-2);
1249
+ }
1250
+ .pos-user__av {
1251
+ width: 30px; height: 30px; flex: none; display: grid; place-items: center;
1252
+ border-radius: 50%; background: var(--pos-primary); color: var(--pos-primary-fg);
1253
+ font-size: 13px; font-weight: 700;
1254
+ }
1255
+ .pos-user__txt { flex: 1; min-width: 0; display: flex; flex-direction: column; }
1256
+ .pos-user__name {
1257
+ font-size: 13px; font-weight: 650; color: var(--pos-fg); line-height: 1.25;
1258
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1259
+ }
1260
+ /* Sem segunda linha, o ponto acompanha o nome em vez de flutuar sob ele. */
1261
+ .pos-user__name--dotted { display: flex; align-items: center; gap: 6px; }
1262
+ .pos-user__meta { display: flex; align-items: center; gap: 5px; font-size: 11px; line-height: 1.25; }
1263
+ .pos-user__dot { width: 6px; height: 6px; border-radius: 50%; flex: none; background: var(--pos-ok); }
1264
+ .pos-user[data-state='reconnecting'] .pos-user__dot { background: var(--pos-warn); }
1265
+ .pos-user[data-state='offline'] .pos-user__dot { background: var(--pos-danger); }
1266
+ /* Losing the network is the one thing worth shouting about from the rail. */
1267
+ .pos-user[data-state='offline'] { color: var(--pos-danger); }
1268
+ .pos-user__act {
1269
+ flex: none; width: 30px; height: 30px; display: grid; place-items: center;
1270
+ border: none; border-radius: var(--pos-r-sm); background: transparent;
1271
+ color: var(--pos-fg-3); cursor: pointer;
1272
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
1273
+ }
1274
+ .pos-user__act:hover { background: var(--pos-2); color: var(--pos-fg); }
1275
+
1276
+ /* --- command field: wide only where there is something to search --------- */
1277
+
1278
+ /* Collapsed it is a circle: width must equal the field's height or the pill
1279
+ radius leaves flat sides, which is what "não tá redondinho" was. */
1280
+ /* The hint only fits once the field is open; collapsed it just spills. */
1281
+ .pos-field__clear {
1282
+ flex: none; width: 26px; height: 26px; display: grid; place-items: center;
1283
+ border: none; border-radius: 50%; background: var(--pos-3); color: var(--pos-fg-2);
1284
+ cursor: pointer;
1285
+ }
1286
+ .pos-field__clear:hover { background: var(--pos-4); color: var(--pos-fg); }
1287
+ .pos-field__hint {
1288
+ flex: none; padding: 2px 6px; border-radius: 4px; border: 1px solid var(--pos-line-strong);
1289
+ background: var(--pos-0); color: var(--pos-fg-3); font-size: 10px; font-weight: 700;
1290
+ }
1291
+
1292
+ @media (max-width: 1180px) {
1293
+ .pos-brand { justify-content: center; padding: 0; }
1294
+ .pos-brand__name { display: none; }
1295
+ .pos-user { justify-content: center; padding-left: 0; padding-right: 0; }
1296
+ .pos-user__txt, .pos-user__act { display: none; }
1297
+ }
1298
+
1299
+ /* --- activity: a history, drawn as one ----------------------------------
1300
+ The old version stacked two saturated slabs for two facts. A vertical rail
1301
+ with a marker per event says the same thing quieter and, unlike the slabs,
1302
+ keeps saying it when there are six events instead of two.
1303
+ ------------------------------------------------------------------------- */
1304
+
1305
+ .pos-activity { padding: var(--pos-4x); display: grid; gap: var(--pos-6x); align-content: start; }
1306
+
1307
+ .pos-tl { list-style: none; margin: 0; padding: 0; display: grid; gap: 0; }
1308
+ .pos-ev { position: relative; display: grid; grid-template-columns: 22px minmax(0, 1fr); gap: var(--pos-3x); padding-bottom: var(--pos-4x); }
1309
+ .pos-ev:last-child { padding-bottom: 0; }
1310
+ /* The connector stops at the last marker instead of trailing into nothing. */
1311
+ /* 2px, not 1: a hairline connector at this contrast is a connector nobody
1312
+ sees, and then the markers read as three unrelated bullets. */
1313
+ .pos-ev:not(:last-child)::before {
1314
+ content: ''; position: absolute; left: 10px; top: 16px; bottom: -2px; width: 2px;
1315
+ border-radius: 1px; background: var(--pos-4);
1316
+ }
1317
+ .pos-ev__mark {
1318
+ position: relative; z-index: 1;
1319
+ width: 10px; height: 10px; margin: 5px auto 0; border-radius: 50%;
1320
+ background: var(--pos-fg-2); box-shadow: 0 0 0 3px var(--pos-1);
1321
+ }
1322
+ .pos-ev[data-done='false'] .pos-ev__mark {
1323
+ background: transparent; border: 2px solid var(--pos-warn); width: 11px; height: 11px; margin-top: 4px;
1324
+ }
1325
+ .pos-ev__body { min-width: 0; }
1326
+ .pos-ev__row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--pos-2x); }
1327
+ .pos-ev__t { font-size: 14px; font-weight: 580; }
1328
+ .pos-ev[data-done='false'] .pos-ev__t { color: var(--pos-warn); }
1329
+ .pos-ev__at { font-size: 13px; color: var(--pos-fg-2); }
1330
+ .pos-ev__d { display: block; margin-top: 2px; font-size: 13px; color: var(--pos-fg-2); line-height: 1.4; }
1331
+ .pos-ev__none { color: var(--pos-fg-3); font-size: 14px; }
1332
+
1333
+ .pos-note-block {
1334
+ padding: var(--pos-3x); border-radius: var(--pos-r);
1335
+ background: var(--pos-2); border: 1px solid var(--pos-line);
1336
+ }
1337
+ .pos-note-block__t { margin: 6px 0 0; font-size: 14px; color: var(--pos-fg); font-style: italic; line-height: 1.4; }
1338
+
1339
+ .pos-dl { margin: 0; display: grid; grid-template-columns: 1fr 1fr; gap: var(--pos-4x); }
1340
+ .pos-dl dd { margin: 4px 0 0; font-size: 17px; font-weight: 600; letter-spacing: -0.014em; }
1341
+
1342
+ /* --- motion -------------------------------------------------------------
1343
+ Every animation here says something happened. None decorate. Durations sit
1344
+ in the 120-260ms band a hand reads as "instant but not abrupt", and exits
1345
+ run shorter than entrances so removing never feels like waiting.
1346
+
1347
+ The reduced-motion block at the top of this file flattens all of it.
1348
+ ------------------------------------------------------------------------- */
1349
+
1350
+ .pos {
1351
+ --pos-in: 200ms;
1352
+ --pos-out: 140ms;
1353
+ --pos-spring: cubic-bezier(0.16, 1, 0.3, 1);
1354
+ }
1355
+
1356
+ /* A line arriving on the ticket: it slides down into place and flashes once,
1357
+ because the eye is on the catalogue when the line appears in the rail. */
1358
+ @keyframes pos-line-in {
1359
+ from { opacity: 0; transform: translateY(-6px) scale(0.99); }
1360
+ to { opacity: 1; transform: none; }
1361
+ }
1362
+ @keyframes pos-flash {
1363
+ from { background: var(--pos-4); }
1364
+ to { background: var(--pos-2); }
1365
+ }
1366
+ .pos-ln { animation: pos-line-in var(--pos-in) var(--pos-spring), pos-flash 520ms ease-out; }
1367
+ .pos-ln[data-swiping='true'] { animation: none; transition: none; }
1368
+ .pos-ln[data-removing='true'] {
1369
+ animation: pos-line-out var(--pos-out) ease-in forwards;
1370
+ pointer-events: none;
1371
+ }
1372
+ @keyframes pos-line-out {
1373
+ to { opacity: 0; transform: translateX(-40%); max-height: 0; margin-bottom: 0; padding-block: 0; }
1374
+ }
1375
+
1376
+ /* Catalogue items come in staggered. The delay is set per card from the index
1377
+ so a category change reads as a deal of cards rather than a repaint. */
1378
+ @keyframes pos-card-in {
1379
+ from { opacity: 0; transform: translateY(8px); }
1380
+ to { opacity: 1; transform: none; }
1381
+ }
1382
+ .pos-item, .pos-tbl, .pos-card {
1383
+ animation: pos-card-in var(--pos-in) var(--pos-spring) backwards;
1384
+ animation-delay: var(--pos-stagger, 0ms);
1385
+ }
1386
+ /* Once a card is on the ticket it stops re-entering: replaying the entrance on
1387
+ a card the operator is actively tapping is exactly the flash they saw. */
1388
+ .pos-item[data-filled='true'] { animation: none; }
1389
+
1390
+ /* No pop on quantity change: the edge growing across the card already answers
1391
+ "which one took the tap", and a scale on top of it read as a flash. What is
1392
+ left is the press itself, which is feedback for the finger, not for the eye. */
1393
+
1394
+ /* Paying: the CTA becomes the confirmation instead of handing off to a toast
1395
+ that appears somewhere else on screen. */
1396
+ @keyframes pos-paid {
1397
+ 0% { transform: scale(1); }
1398
+ 40% { transform: scale(0.985); }
1399
+ 100% { transform: scale(1); }
1400
+ }
1401
+ .pos-btn[data-paid='true'] {
1402
+ animation: pos-paid 320ms var(--pos-spring);
1403
+ background: var(--pos-ok); border-color: var(--pos-ok); color: #06130B;
1404
+ }
1405
+
1406
+ /* --- swipe to remove ----------------------------------------------------- */
1407
+
1408
+ .pos-ln-wrap { position: relative; }
1409
+ .pos-ln-wrap .pos-ln { position: relative; z-index: 1; touch-action: pan-y; }
1410
+ .pos-swipe-bg {
1411
+ position: absolute; inset: 0 var(--pos-3x) var(--pos-2x) auto; width: 88px;
1412
+ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
1413
+ border: none; border-radius: var(--pos-r);
1414
+ background: var(--pos-danger); color: #fff;
1415
+ font-size: 12px; font-weight: 650; cursor: pointer;
1416
+ }
1417
+ .pos-swipe-bg:hover { filter: brightness(1.08); }
1418
+ /* The row rides above it; sliding left is what uncovers it. */
1419
+ .pos-ln-wrap .pos-ln { transition: transform 220ms var(--pos-spring); }
1420
+ .pos-ln-wrap[data-swiping='true'] .pos-ln { transition: none; }
1421
+
1422
+ /* --- tablet: the machine this actually runs on -------------------------- */
1423
+
1424
+ @media (pointer: coarse) {
1425
+ /* No hover on a tablet, so hover states must not be the only feedback —
1426
+ press states carry it instead. */
1427
+ .pos-item:active, .pos-tbl:active, .pos-card:active, .pos-dchip:active { background: var(--pos-4); }
1428
+ .pos-step__b { width: 46px; height: 46px; }
1429
+ .pos-nav__item { min-height: 56px; }
1430
+ .pos-seg__btn { min-height: 44px; }
1431
+ }
1432
+
1433
+ @media (max-width: 1100px) and (orientation: portrait) {
1434
+ /* A tablet held upright: the catalogue wants columns more than the plan
1435
+ wants width. */
1436
+ .pos-items { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
1437
+ .pos-floor { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
1438
+ }
1439
+
1440
+ /* --- checkout: a step with its own head, not a permanent panel ---------- */
1441
+
1442
+ .pos-checkout { border-top: 1px solid var(--pos-line); background: var(--pos-1); }
1443
+ .pos-back {
1444
+ display: flex; align-items: center; gap: var(--pos-2x);
1445
+ width: 100%; min-height: 56px; padding: 0 var(--pos-4x);
1446
+ border: none; border-bottom: 1px solid var(--pos-line);
1447
+ background: transparent; color: var(--pos-fg-2);
1448
+ font-size: 15px; font-weight: 550; text-align: left; cursor: pointer;
1449
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
1450
+ }
1451
+ .pos-back:hover { background: var(--pos-2); color: var(--pos-fg); }
1452
+ .pos-back:active { background: var(--pos-3); }
1453
+ .pos-back svg { flex: none; }
1454
+
1455
+ .pos-checkout__head {
1456
+ display: flex; align-items: center; gap: var(--pos-2x);
1457
+ padding: var(--pos-4x) var(--pos-4x) 0;
1458
+ }
1459
+
1460
+ .pos-empty__b {
1461
+ margin-top: var(--pos-2x); min-height: var(--pos-hit-2); padding: 0 var(--pos-4x);
1462
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r);
1463
+ background: var(--pos-2); color: var(--pos-fg);
1464
+ font-size: 14px; font-weight: 600; cursor: pointer;
1465
+ transition: background var(--pos-t) var(--pos-e);
1466
+ }
1467
+ .pos-empty__b:hover { background: var(--pos-3); }
1468
+
1469
+ /* --- naming a ticket ---------------------------------------------------- */
1470
+
1471
+ .pos-tkt__edit {
1472
+ width: 28px; height: 28px; flex: none; display: grid; place-items: center;
1473
+ border: none; border-radius: var(--pos-r-xs);
1474
+ background: transparent; color: var(--pos-fg-3); cursor: pointer;
1475
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e);
1476
+ }
1477
+ .pos-tkt__edit:hover { background: var(--pos-2); color: var(--pos-fg); }
1478
+ .pos-tkt__phone { display: inline-flex; align-items: center; gap: 5px; }
1479
+
1480
+ .pos-labelled { display: grid; gap: 6px; }
1481
+ .pos-input {
1482
+ display: flex; align-items: center; gap: var(--pos-2x);
1483
+ min-height: var(--pos-hit-2); padding: 0 var(--pos-3x);
1484
+ border: 1px solid var(--pos-line-strong); border-radius: var(--pos-r);
1485
+ background: var(--pos-0); color: var(--pos-fg-3);
1486
+ transition: border-color var(--pos-t) var(--pos-e);
1487
+ }
1488
+ /* The wrapper owns the focus state. Letting the input draw its own ring inside
1489
+ a bordered box gives two rectangles of blue, 2px apart — which is what a
1490
+ focused field looked like. */
1491
+ .pos-input:focus-within {
1492
+ border-color: var(--pos-line-strong);
1493
+ box-shadow: 0 0 0 3px color-mix(in srgb, var(--pos-accent) 24%, transparent);
1494
+ }
1495
+ .pos-input input:focus-visible { outline: none; }
1496
+ .pos-field input:focus-visible { outline: none; }
1497
+ .pos-note:focus { border-color: var(--pos-line-strong); box-shadow: 0 0 0 3px color-mix(in srgb, var(--pos-accent) 24%, transparent); }
1498
+ .pos-note:focus-visible { outline: none; }
1499
+ .pos-input input {
1500
+ flex: 1; min-width: 0; border: none; background: none; outline: none;
1501
+ color: var(--pos-fg); font: inherit; font-size: 16px;
1502
+ }
1503
+ .pos-input input::placeholder { color: var(--pos-fg-3); }
1504
+
1505
+ .pos-tkt__alias {
1506
+ flex: 0 1 auto; min-width: 0; color: var(--pos-fg); font-size: 13.5px; font-weight: 620;
1507
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
1508
+ }
1509
+ .pos-tkt__alias::after { content: ' ·'; color: var(--pos-fg-3); font-weight: 400; }
1510
+
1511
+ /* --- reservations -------------------------------------------------------- */
1512
+
1513
+ .pos-res { flex: 1; padding: var(--pos-4x); display: grid; gap: var(--pos-6x); align-content: start; }
1514
+ .pos-res__group { display: grid; gap: var(--pos-2x); }
1515
+ .pos-res__group h3 { margin: 0 0 var(--pos-1x); }
1516
+ .pos-res__group[data-muted='true'] { opacity: 0.62; }
1517
+
1518
+ .pos-res__row {
1519
+ display: grid; grid-template-columns: 64px minmax(0, 1fr) auto auto;
1520
+ align-items: center; gap: var(--pos-3x);
1521
+ min-height: var(--pos-hit); padding: var(--pos-3x);
1522
+ border: 1px solid var(--pos-line); border-radius: var(--pos-r);
1523
+ background: var(--pos-2);
1524
+ position: relative; overflow: hidden;
1525
+ }
1526
+ .pos-res__row::before {
1527
+ content: ''; position: absolute; inset: 0 auto 0 0; width: 4px;
1528
+ background: var(--pos-line-strong);
1529
+ }
1530
+ .pos-res__row[data-late='true']::before { background: var(--pos-danger); }
1531
+ .pos-res__time { font-size: 19px; font-weight: 700; letter-spacing: -0.02em; padding-left: 4px; }
1532
+ .pos-res__who { min-width: 0; }
1533
+ .pos-res__name { display: block; font-size: 15px; font-weight: 620; }
1534
+ .pos-res__meta { display: flex; flex-wrap: wrap; gap: var(--pos-2x); margin-top: 3px; }
1535
+ .pos-res__chip {
1536
+ display: inline-flex; align-items: center; gap: 4px;
1537
+ font-size: 12px; color: var(--pos-fg-2);
1538
+ }
1539
+ .pos-res__note { display: block; margin-top: 3px; font-size: 12px; color: var(--pos-fg-2); font-style: italic; }
1540
+ .pos-res__end { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; min-width: 108px; }
1541
+ .pos-res__late { font-size: 11px; font-weight: 650; color: var(--pos-danger); }
1542
+ /* The action column keeps its width whether it holds two buttons or the words
1543
+ that replace them — otherwise the seated row's badge slides right and the
1544
+ list stops reading as columns. */
1545
+ .pos-res__do {
1546
+ display: flex; align-items: center; justify-content: flex-end;
1547
+ gap: var(--pos-2x); min-width: 232px;
1548
+ }
1549
+ /* Same shape as the bill's primary: a pill is what "this is the action" looks
1550
+ like everywhere else in the product, and seating is exactly that. */
1551
+ .pos-res__b {
1552
+ display: inline-flex; align-items: center; justify-content: center; gap: 7px;
1553
+ min-height: 52px; padding: 0 var(--pos-4x);
1554
+ border: 1px solid var(--pos-line-strong); border-radius: 999px;
1555
+ background: transparent; color: var(--pos-fg-2);
1556
+ font-size: 14px; font-weight: 600; cursor: pointer;
1557
+ transition: background var(--pos-t) var(--pos-e), color var(--pos-t) var(--pos-e), transform var(--pos-t) var(--pos-e);
1558
+ }
1559
+ .pos-res__b:hover { background: var(--pos-3); color: var(--pos-fg); }
1560
+ .pos-res__b:active { transform: scale(0.985); }
1561
+ .pos-res__b--primary {
1562
+ min-width: 116px;
1563
+ background: var(--pos-primary); border-color: var(--pos-primary); color: var(--pos-primary-fg);
1564
+ font-weight: 650;
1565
+ }
1566
+ .pos-res__b--primary:hover { background: #fff; border-color: #fff; color: var(--pos-primary-fg); }
1567
+ .pos-res__at {
1568
+ min-width: 116px;
1569
+ border-color: color-mix(in srgb, var(--pos-ok) 45%, transparent);
1570
+ color: var(--pos-ok); font-weight: 650;
1571
+ }
1572
+ .pos-res__at:hover { background: color-mix(in srgb, var(--pos-ok) 14%, transparent); color: var(--pos-ok); }
1573
+ .pos-res__at:disabled { opacity: 0.5; cursor: default; }
1574
+
1575
+ @media (max-width: 1180px) {
1576
+ /* The actions drop under the row before anything gets truncated: a name the
1577
+ host cannot read is worse than a row two lines tall. */
1578
+ .pos-res__row { grid-template-columns: 56px minmax(0, 1fr) auto; }
1579
+ .pos-res__do { grid-column: 2 / -1; justify-content: flex-end; }
1580
+ }
1581
+
1582
+ /* --- seating a party ----------------------------------------------------
1583
+ A booking waiting for a table. It rides in the dock with the open tickets
1584
+ because that is where the operator already looks for "what is in my hands
1585
+ right now".
1586
+ ------------------------------------------------------------------------- */
1587
+
1588
+ .pos-seating {
1589
+ /* Same borderless language as the rest of the strip, but FILLED: this is not
1590
+ another table to pick from, it is the app being in a mode, and a mode has
1591
+ to be visible from anywhere on screen.
1592
+ It also has to stop touching the strip: two filled blocks flush against
1593
+ each other read as one confused thing, which is exactly what happened next
1594
+ to the inverted active chip. */
1595
+ /* Sticky, because it is the MODE and not an item in the list: when the strip
1596
+ scrolls to the active table it would carry the one thing the operator has
1597
+ to finish off the screen. */
1598
+ position: sticky; left: 0; z-index: 2;
1599
+ flex: 0 0 auto; display: flex; align-items: center; gap: var(--pos-3x);
1600
+ min-height: 68px; padding: 0 var(--pos-3x) 0 var(--pos-4x);
1601
+ margin-right: var(--pos-3x);
1602
+ border: none; border-right: 2px solid var(--pos-0); border-radius: 0;
1603
+ background: var(--pos-accent); color: #fff;
1604
+ box-shadow: 6px 0 12px -6px rgba(0, 0, 0, 0.6);
1605
+ }
1606
+ /* While a party is waiting, nothing else in the strip claims to be primary:
1607
+ the mode is the one thing the operator has to finish. */
1608
+ .pos-dock:has(.pos-seating) .pos-dchip[aria-current='true'] {
1609
+ background: var(--pos-3); color: var(--pos-fg);
1610
+ }
1611
+ .pos-dock:has(.pos-seating) .pos-dchip[aria-current='true'] .pos-dchip__t { color: var(--pos-fg); }
1612
+ .pos-dock:has(.pos-seating) .pos-dchip[aria-current='true'] .pos-dchip__s { color: var(--pos-fg-2); }
1613
+ .pos-dock:has(.pos-seating) .pos-dchip[aria-current='true'] .pos-dchip__tag { background: var(--pos-4); color: var(--pos-fg); }
1614
+ .pos-seating__pulse {
1615
+ width: 9px; height: 9px; flex: none; border-radius: 50%;
1616
+ background: #fff; animation: pos-pulse 1.6s ease-in-out infinite;
1617
+ }
1618
+ @keyframes pos-pulse { 50% { opacity: 0.25; } }
1619
+ @media (prefers-reduced-motion: reduce) { .pos-seating__pulse { animation: none; } }
1620
+ .pos-seating__t { display: block; font-size: 13.5px; font-weight: 650; }
1621
+ .pos-seating__s { display: block; font-size: 12.5px; color: rgba(255, 255, 255, 0.82); }
1622
+ .pos-seating__x {
1623
+ width: 32px; height: 32px; flex: none; display: grid; place-items: center;
1624
+ border: none; border-radius: var(--pos-r-xs); background: transparent;
1625
+ color: rgba(255, 255, 255, 0.82); cursor: pointer;
1626
+ }
1627
+ .pos-seating__x:hover { background: rgba(255, 255, 255, 0.18); color: #fff; }
1628
+
1629
+ /* While picking, the room answers one question: which table is free. */
1630
+ [data-picking='true'] .pos-tbl[data-target='false'],
1631
+ [data-picking='true'] .pos-pt[data-target='false'] {
1632
+ opacity: 0.32; cursor: not-allowed;
1633
+ }
1634
+ [data-picking='true'] .pos-tbl[data-target='true'] {
1635
+ border-color: var(--pos-accent);
1636
+ box-shadow: 0 0 0 1px var(--pos-accent), 0 0 24px -6px var(--pos-accent);
1637
+ }
1638
+ [data-picking='true'] .pos-pt[data-target='true'] {
1639
+ border-color: var(--pos-accent); border-width: 2.5px;
1640
+ box-shadow: 0 0 24px -6px var(--pos-accent);
1641
+ animation: pos-target 1.8s ease-in-out infinite;
1642
+ }
1643
+ @keyframes pos-target { 50% { box-shadow: 0 0 30px -4px var(--pos-accent); } }
1644
+ @media (prefers-reduced-motion: reduce) { [data-picking='true'] .pos-pt[data-target='true'] { animation: none; } }
1645
+
1646
+ .pos-warnline {
1647
+ margin: 0; padding: var(--pos-2x) var(--pos-3x);
1648
+ border-radius: var(--pos-r); font-size: 13px; font-weight: 550;
1649
+ background: color-mix(in srgb, var(--pos-warn) 16%, transparent);
1650
+ color: var(--pos-warn);
1651
+ }
1652
+
1653
+ /* The order stage can carry two actions: what to do next, and the way past it. */
1654
+ .pos-cta--stack { display: grid; gap: var(--pos-2x); }
1655
+ .pos-btn--wide { width: 100%; min-height: 48px; font-size: 14px; }
1656
+ .pos-hint {
1657
+ display: flex; align-items: center; justify-content: center; gap: 7px;
1658
+ margin: 0; padding: var(--pos-2x) var(--pos-3x); border-radius: var(--pos-r);
1659
+ background: color-mix(in srgb, var(--pos-warn) 14%, transparent);
1660
+ color: var(--pos-warn); font-size: 12.5px; font-weight: 600;
1661
+ }
1662
+
1663
+ .pos-scroll-x { overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: none; }
1664
+ .pos-scroll-x::-webkit-scrollbar { display: none; }