lanre-ds 0.0.0-stage → 0.2.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 (240) hide show
  1. package/LICENSE +21 -0
  2. package/components/buttons/Button.d.ts +28 -0
  3. package/components/buttons/Button.jsx +50 -0
  4. package/components/buttons/Button.prompt.md +16 -0
  5. package/components/buttons/IconButton.d.ts +23 -0
  6. package/components/buttons/IconButton.jsx +53 -0
  7. package/components/buttons/IconButton.prompt.md +12 -0
  8. package/components/buttons/SegmentedTabs.d.ts +21 -0
  9. package/components/buttons/SegmentedTabs.jsx +15 -0
  10. package/components/buttons/SegmentedTabs.prompt.md +10 -0
  11. package/components/contributor/AudioRecorder.d.ts +27 -0
  12. package/components/contributor/AudioRecorder.jsx +79 -0
  13. package/components/contributor/AudioRecorder.prompt.md +13 -0
  14. package/components/contributor/RadioCard.d.ts +17 -0
  15. package/components/contributor/RadioCard.jsx +30 -0
  16. package/components/contributor/RadioCard.prompt.md +12 -0
  17. package/components/contributor/RecorderPanel.d.ts +30 -0
  18. package/components/contributor/RecorderPanel.jsx +49 -0
  19. package/components/contributor/RecorderPanel.prompt.md +11 -0
  20. package/components/contributor/StepBar.d.ts +17 -0
  21. package/components/contributor/StepBar.jsx +55 -0
  22. package/components/contributor/StepBar.prompt.md +12 -0
  23. package/components/contributor/TextField.d.ts +25 -0
  24. package/components/contributor/TextField.jsx +29 -0
  25. package/components/contributor/TextField.prompt.md +7 -0
  26. package/components/contributor/UploadTile.d.ts +39 -0
  27. package/components/contributor/UploadTile.jsx +116 -0
  28. package/components/contributor/UploadTile.prompt.md +21 -0
  29. package/components/contributor/WorldChip.d.ts +18 -0
  30. package/components/contributor/WorldChip.jsx +16 -0
  31. package/components/contributor/WorldChip.prompt.md +9 -0
  32. package/components/contributor/WorldOption.d.ts +19 -0
  33. package/components/contributor/WorldOption.jsx +19 -0
  34. package/components/contributor/WorldOption.prompt.md +11 -0
  35. package/components/contributor/WorldSign.d.ts +25 -0
  36. package/components/contributor/WorldSign.jsx +32 -0
  37. package/components/contributor/WorldSign.prompt.md +14 -0
  38. package/components/contributor/WorldSigns.d.ts +13 -0
  39. package/components/contributor/WorldSigns.jsx +12 -0
  40. package/components/contributor/WorldSigns.prompt.md +8 -0
  41. package/components/effects/Confetti.d.ts +14 -0
  42. package/components/effects/Confetti.jsx +38 -0
  43. package/components/effects/Confetti.prompt.md +12 -0
  44. package/components/effects/Sparkles.d.ts +9 -0
  45. package/components/effects/Sparkles.jsx +13 -0
  46. package/components/effects/Sparkles.prompt.md +11 -0
  47. package/components/effects/Sunburst.d.ts +23 -0
  48. package/components/effects/Sunburst.jsx +26 -0
  49. package/components/effects/Sunburst.prompt.md +12 -0
  50. package/components/feedback/Badge.d.ts +17 -0
  51. package/components/feedback/Badge.jsx +16 -0
  52. package/components/feedback/Badge.prompt.md +10 -0
  53. package/components/feedback/ErrorText.d.ts +11 -0
  54. package/components/feedback/ErrorText.jsx +8 -0
  55. package/components/feedback/ErrorText.prompt.md +9 -0
  56. package/components/feedback/LoadingScreen.d.ts +14 -0
  57. package/components/feedback/LoadingScreen.jsx +24 -0
  58. package/components/feedback/LoadingScreen.prompt.md +9 -0
  59. package/components/feedback/Notice.d.ts +15 -0
  60. package/components/feedback/Notice.jsx +16 -0
  61. package/components/feedback/Notice.prompt.md +10 -0
  62. package/components/feedback/SaveAlert.d.ts +15 -0
  63. package/components/feedback/SaveAlert.jsx +24 -0
  64. package/components/feedback/SaveAlert.prompt.md +9 -0
  65. package/components/feedback/SavePill.d.ts +13 -0
  66. package/components/feedback/SavePill.jsx +29 -0
  67. package/components/feedback/SavePill.prompt.md +8 -0
  68. package/components/feedback/SoundToggle.d.ts +16 -0
  69. package/components/feedback/SoundToggle.jsx +15 -0
  70. package/components/feedback/SoundToggle.prompt.md +6 -0
  71. package/components/feedback/Sticker.d.ts +15 -0
  72. package/components/feedback/Sticker.jsx +12 -0
  73. package/components/feedback/Sticker.prompt.md +9 -0
  74. package/components/feedback/Tag.d.ts +15 -0
  75. package/components/feedback/Tag.jsx +15 -0
  76. package/components/feedback/Tag.prompt.md +10 -0
  77. package/components/feedback/Toast.d.ts +21 -0
  78. package/components/feedback/Toast.jsx +19 -0
  79. package/components/feedback/Toast.prompt.md +7 -0
  80. package/components/forms/Slider.d.ts +24 -0
  81. package/components/forms/Slider.jsx +28 -0
  82. package/components/forms/Slider.prompt.md +6 -0
  83. package/components/forms/Switch.d.ts +19 -0
  84. package/components/forms/Switch.jsx +20 -0
  85. package/components/forms/Switch.prompt.md +6 -0
  86. package/components/games/MatchCard.d.ts +18 -0
  87. package/components/games/MatchCard.jsx +27 -0
  88. package/components/games/MatchCard.prompt.md +11 -0
  89. package/components/hud/Countdown.d.ts +33 -0
  90. package/components/hud/Countdown.jsx +74 -0
  91. package/components/hud/Countdown.prompt.md +15 -0
  92. package/components/hud/CurrencyPill.d.ts +24 -0
  93. package/components/hud/CurrencyPill.jsx +34 -0
  94. package/components/hud/CurrencyPill.prompt.md +12 -0
  95. package/components/hud/HudBar.d.ts +18 -0
  96. package/components/hud/HudBar.jsx +14 -0
  97. package/components/hud/HudBar.prompt.md +15 -0
  98. package/components/hud/HudChip.d.ts +25 -0
  99. package/components/hud/HudChip.jsx +30 -0
  100. package/components/hud/HudChip.prompt.md +13 -0
  101. package/components/hud/ProgressBar.d.ts +27 -0
  102. package/components/hud/ProgressBar.jsx +39 -0
  103. package/components/hud/ProgressBar.prompt.md +11 -0
  104. package/components/hud/TimerChip.d.ts +19 -0
  105. package/components/hud/TimerChip.jsx +17 -0
  106. package/components/hud/TimerChip.prompt.md +7 -0
  107. package/components/icons/GameIcon.d.ts +25 -0
  108. package/components/icons/GameIcon.jsx +43 -0
  109. package/components/icons/GameIcon.prompt.md +13 -0
  110. package/components/identity/Avatar.d.ts +26 -0
  111. package/components/identity/Avatar.jsx +31 -0
  112. package/components/identity/Avatar.prompt.md +11 -0
  113. package/components/identity/AvatarButton.d.ts +19 -0
  114. package/components/identity/AvatarButton.jsx +13 -0
  115. package/components/identity/AvatarButton.prompt.md +8 -0
  116. package/components/identity/Gemstone.d.ts +28 -0
  117. package/components/identity/Gemstone.jsx +46 -0
  118. package/components/identity/Gemstone.prompt.md +15 -0
  119. package/components/identity/PersonTile.d.ts +22 -0
  120. package/components/identity/PersonTile.jsx +27 -0
  121. package/components/identity/PersonTile.prompt.md +10 -0
  122. package/components/levels/ChoiceButton.d.ts +20 -0
  123. package/components/levels/ChoiceButton.jsx +31 -0
  124. package/components/levels/ChoiceButton.prompt.md +10 -0
  125. package/components/map/DiscoveryMarker.d.ts +19 -0
  126. package/components/map/DiscoveryMarker.jsx +31 -0
  127. package/components/map/DiscoveryMarker.prompt.md +11 -0
  128. package/components/map/LevelNode.d.ts +19 -0
  129. package/components/map/LevelNode.jsx +37 -0
  130. package/components/map/LevelNode.prompt.md +11 -0
  131. package/components/map/PortraitMedallion.d.ts +27 -0
  132. package/components/map/PortraitMedallion.jsx +29 -0
  133. package/components/map/PortraitMedallion.prompt.md +14 -0
  134. package/components/map/StarRating.d.ts +22 -0
  135. package/components/map/StarRating.jsx +21 -0
  136. package/components/map/StarRating.prompt.md +6 -0
  137. package/components/map/Tile.d.ts +22 -0
  138. package/components/map/Tile.jsx +32 -0
  139. package/components/map/Tile.prompt.md +8 -0
  140. package/components/map/WorldGate.d.ts +21 -0
  141. package/components/map/WorldGate.jsx +27 -0
  142. package/components/map/WorldGate.prompt.md +13 -0
  143. package/components/map/worlds.d.ts +18 -0
  144. package/components/map/worlds.js +9 -0
  145. package/components/media/Album.d.ts +33 -0
  146. package/components/media/Album.jsx +157 -0
  147. package/components/media/Album.prompt.md +16 -0
  148. package/components/media/Cassette.d.ts +23 -0
  149. package/components/media/Cassette.jsx +64 -0
  150. package/components/media/Cassette.prompt.md +13 -0
  151. package/components/media/Film.d.ts +20 -0
  152. package/components/media/Film.jsx +39 -0
  153. package/components/media/Film.prompt.md +14 -0
  154. package/components/media/MemoryText.d.ts +20 -0
  155. package/components/media/MemoryText.jsx +14 -0
  156. package/components/media/MemoryText.prompt.md +9 -0
  157. package/components/media/Unavailable.d.ts +13 -0
  158. package/components/media/Unavailable.jsx +13 -0
  159. package/components/media/Unavailable.prompt.md +10 -0
  160. package/components/media/Words.d.ts +21 -0
  161. package/components/media/Words.jsx +57 -0
  162. package/components/media/Words.prompt.md +12 -0
  163. package/components/media/shared.js +19 -0
  164. package/components/panels/Modal.d.ts +25 -0
  165. package/components/panels/Modal.jsx +68 -0
  166. package/components/panels/Modal.prompt.md +20 -0
  167. package/components/panels/Panel.d.ts +29 -0
  168. package/components/panels/Panel.jsx +109 -0
  169. package/components/panels/Panel.prompt.md +13 -0
  170. package/components/quiz/LeaderboardRow.d.ts +26 -0
  171. package/components/quiz/LeaderboardRow.jsx +24 -0
  172. package/components/quiz/LeaderboardRow.prompt.md +10 -0
  173. package/components/rewards/ItemCell.d.ts +24 -0
  174. package/components/rewards/ItemCell.jsx +24 -0
  175. package/components/rewards/ItemCell.prompt.md +9 -0
  176. package/components/rewards/RewardCard.d.ts +24 -0
  177. package/components/rewards/RewardCard.jsx +34 -0
  178. package/components/rewards/RewardCard.prompt.md +8 -0
  179. package/components/rewards/ShopRow.d.ts +23 -0
  180. package/components/rewards/ShopRow.jsx +26 -0
  181. package/components/rewards/ShopRow.prompt.md +7 -0
  182. package/components/text/Heading.d.ts +19 -0
  183. package/components/text/Heading.jsx +15 -0
  184. package/components/text/Heading.prompt.md +10 -0
  185. package/components/text/OutlineText.d.ts +24 -0
  186. package/components/text/OutlineText.jsx +20 -0
  187. package/components/text/OutlineText.prompt.md +11 -0
  188. package/components/toolbar/FastTravelDock.d.ts +50 -0
  189. package/components/toolbar/FastTravelDock.jsx +36 -0
  190. package/components/toolbar/FastTravelDock.prompt.md +12 -0
  191. package/components/toolbar/Toolbar.d.ts +16 -0
  192. package/components/toolbar/Toolbar.jsx +8 -0
  193. package/components/toolbar/Toolbar.prompt.md +14 -0
  194. package/components/toolbar/ToolbarNext.d.ts +21 -0
  195. package/components/toolbar/ToolbarNext.jsx +21 -0
  196. package/components/toolbar/ToolbarNext.prompt.md +13 -0
  197. package/components/toolbar/ToolbarTab.d.ts +15 -0
  198. package/components/toolbar/ToolbarTab.jsx +12 -0
  199. package/components/toolbar/ToolbarTab.prompt.md +9 -0
  200. package/components/viewer/ClueCard.d.ts +18 -0
  201. package/components/viewer/ClueCard.jsx +14 -0
  202. package/components/viewer/ClueCard.prompt.md +9 -0
  203. package/components/viewer/ClueTab.d.ts +16 -0
  204. package/components/viewer/ClueTab.jsx +12 -0
  205. package/components/viewer/ClueTab.prompt.md +8 -0
  206. package/components/viewer/RevealTray.d.ts +54 -0
  207. package/components/viewer/RevealTray.jsx +58 -0
  208. package/components/viewer/RevealTray.prompt.md +21 -0
  209. package/components/viewer/RoadsideViewer.d.ts +60 -0
  210. package/components/viewer/RoadsideViewer.jsx +96 -0
  211. package/components/viewer/RoadsideViewer.prompt.md +20 -0
  212. package/components/viewer/ViewerKeys.d.ts +26 -0
  213. package/components/viewer/ViewerKeys.jsx +22 -0
  214. package/components/viewer/ViewerKeys.prompt.md +14 -0
  215. package/components/viewer/ViewerKnob.d.ts +20 -0
  216. package/components/viewer/ViewerKnob.jsx +12 -0
  217. package/components/viewer/ViewerKnob.prompt.md +11 -0
  218. package/components/viewer/WhoButton.d.ts +21 -0
  219. package/components/viewer/WhoButton.jsx +15 -0
  220. package/components/viewer/WhoButton.prompt.md +10 -0
  221. package/package.json +62 -4
  222. package/readme.md +145 -0
  223. package/styles.css +15 -0
  224. package/tokens/colors.css +140 -0
  225. package/tokens/effects.css +40 -0
  226. package/tokens/fonts.css +3 -0
  227. package/tokens/fx.css +186 -0
  228. package/tokens/gems.css +8 -0
  229. package/tokens/hud.css +510 -0
  230. package/tokens/icons.css +7 -0
  231. package/tokens/media.css +774 -0
  232. package/tokens/signs.css +459 -0
  233. package/tokens/spacing.css +38 -0
  234. package/tokens/typography.css +34 -0
  235. package/tokens/ui.css +432 -0
  236. package/tokens/utilities.css +34 -0
  237. package/tokens/viewer.css +1176 -0
  238. package/tokens/worlds.css +73 -0
  239. package/tools/ds-loader.js +60 -0
  240. package/README.md +0 -3
@@ -0,0 +1,1176 @@
1
+ /* The roadside viewer: a memory in a wooden cabinet over the map. A sign on top, a screen that plays one piece at a time,
2
+ a deck with one key per piece and one big knob that says what happens next, a "who" button, a clue tab, and the guess
3
+ tray that opens centred over it. Used by RoadsideViewer, ViewerKeys, ViewerKnob, WhoButton, ClueTab, ClueCard and
4
+ RevealTray (components/viewer). */
5
+
6
+ /* The root holds the layers: the sheet, the guess tray over it, the keepsake over that. It has no
7
+ filter or transform of its own, so each layer can pin itself to the window while the sheet scrolls. */
8
+ .mp-viewer {
9
+ position: fixed;
10
+ inset: 0;
11
+ z-index: 40;
12
+ }
13
+
14
+ .mp-viewer-overlay {
15
+ position: absolute;
16
+ inset: 0;
17
+ overflow-y: auto;
18
+ overscroll-behavior: contain;
19
+ -webkit-overflow-scrolling: touch;
20
+ padding: max(58px, calc(env(safe-area-inset-top) + 44px)) 12px max(24px, env(safe-area-inset-bottom));
21
+ background: var(--bg-scrim);
22
+ backdrop-filter: blur(3px);
23
+ -webkit-backdrop-filter: blur(3px);
24
+ animation: mp-overlay-in 240ms var(--ease-out) both;
25
+ }
26
+
27
+ @keyframes mp-overlay-in {
28
+ from { opacity: 0; }
29
+ to { opacity: 1; }
30
+ }
31
+
32
+ .mp-viewer-sheet {
33
+ position: relative;
34
+ max-width: 1010px;
35
+ margin: 0 auto;
36
+ padding: 64px 12px 14px;
37
+ display: grid;
38
+ grid-template-columns: minmax(0, 1fr);
39
+ gap: 12px;
40
+ border-radius: var(--radius-xl);
41
+ border: 4px solid var(--cream-300);
42
+ background: linear-gradient(180deg, var(--cream-50), var(--cream-100));
43
+ box-shadow: inset 0 3px 0 oklch(1 0 0 / 0.7), inset 0 -4px 0 oklch(0 0 0 / 0.06), var(--shadow-panel);
44
+ animation: mp-modal-in 380ms var(--ease-bounce) both;
45
+ }
46
+
47
+ @keyframes mp-modal-in {
48
+ from { opacity: 0; transform: translateY(24px) scale(0.96); }
49
+ to { opacity: 1; transform: none; }
50
+ }
51
+
52
+ /* Leaving: the sheet shrinks and fades so the map can take over. */
53
+ .mp-viewer-closing {
54
+ animation: mp-overlay-out 240ms var(--ease-out) 40ms both;
55
+ pointer-events: none;
56
+ }
57
+
58
+ .mp-viewer-closing .mp-viewer-sheet {
59
+ animation: mp-modal-out 260ms var(--ease-in-out) both;
60
+ }
61
+
62
+ @keyframes mp-overlay-out {
63
+ from { opacity: 1; }
64
+ to { opacity: 0; }
65
+ }
66
+
67
+ @keyframes mp-modal-out {
68
+ from { opacity: 1; transform: none; }
69
+ to { opacity: 0; transform: translateY(18px) scale(0.94); }
70
+ }
71
+
72
+ /* ------------------------------------------------------------------ sign, ribbon, close */
73
+
74
+ .mp-viewer-sign {
75
+ position: absolute;
76
+ left: -10px;
77
+ top: -34px;
78
+ z-index: 5;
79
+ min-width: 190px;
80
+ max-width: 250px;
81
+ padding: 12px 18px 10px;
82
+ border-radius: 18px;
83
+ border: 5px solid var(--wood-700);
84
+ rotate: -3deg;
85
+ background: repeating-linear-gradient(180deg, transparent 0 9px, oklch(0 0 0 / 0.06) 9px 11px), linear-gradient(180deg, var(--wood-300), var(--wood-500));
86
+ box-shadow: 0 6px 0 var(--wood-900), 0 14px 22px oklch(0.2 0.05 60 / 0.35), inset 0 3px 0 oklch(1 0 0 / 0.3);
87
+ text-align: center;
88
+ }
89
+
90
+ .mp-viewer-sign::before,
91
+ .mp-viewer-sign::after {
92
+ content: '';
93
+ position: absolute;
94
+ top: 8px;
95
+ width: 9px;
96
+ height: 9px;
97
+ border-radius: 50%;
98
+ background: radial-gradient(circle at 35% 30%, var(--stone-100), var(--stone-500));
99
+ box-shadow: 0 1px 0 var(--wood-900);
100
+ }
101
+
102
+ .mp-viewer-sign::before { left: 9px; }
103
+ .mp-viewer-sign::after { right: 9px; }
104
+
105
+ .mp-viewer-sign-eyebrow {
106
+ display: block;
107
+ font-family: var(--font-display);
108
+ font-size: 13px;
109
+ letter-spacing: 0.08em;
110
+ text-transform: uppercase;
111
+ color: var(--cream-100);
112
+ }
113
+
114
+ .mp-viewer-sign-title {
115
+ margin: 0;
116
+ font-family: var(--font-display);
117
+ font-weight: 400;
118
+ font-size: 34px;
119
+ line-height: 1.05;
120
+ letter-spacing: 0.04em;
121
+ text-transform: uppercase;
122
+ color: #fff;
123
+ -webkit-text-stroke: 0.16em var(--wood-900);
124
+ paint-order: stroke fill;
125
+ text-shadow: 0 3px 0 oklch(0 0 0 / 0.28);
126
+ outline: none;
127
+ }
128
+
129
+ .mp-viewer-sign-title-long {
130
+ font-size: 24px;
131
+ }
132
+
133
+ .mp-viewer-leaf {
134
+ position: absolute;
135
+ color: var(--green-500);
136
+ -webkit-text-stroke: 3px var(--green-900);
137
+ paint-order: stroke fill;
138
+ filter: drop-shadow(0 2px 0 oklch(0 0 0 / 0.25));
139
+ }
140
+
141
+ .mp-viewer-leaf-a { left: -20px; top: -18px; font-size: 40px; rotate: -30deg; }
142
+ .mp-viewer-leaf-b { right: -16px; top: -16px; font-size: 34px; rotate: 60deg; }
143
+ .mp-viewer-leaf-c { left: -14px; bottom: -12px; font-size: 28px; rotate: -120deg; }
144
+
145
+ .mp-viewer-ribbon {
146
+ position: absolute;
147
+ left: 50%;
148
+ top: -18px;
149
+ transform: translateX(-50%);
150
+ z-index: 4;
151
+ height: 34px;
152
+ padding: 0 16px;
153
+ border-radius: 999px;
154
+ border: 3px solid #fff;
155
+ background: linear-gradient(180deg, var(--berry-300), var(--berry-500) 50%);
156
+ box-shadow: 0 4px 0 var(--berry-700);
157
+ display: flex;
158
+ align-items: center;
159
+ gap: 6px;
160
+ font-family: var(--font-display);
161
+ font-size: 14px;
162
+ letter-spacing: 0.06em;
163
+ text-transform: uppercase;
164
+ color: #fff;
165
+ white-space: nowrap;
166
+ }
167
+
168
+ .mp-viewer-close {
169
+ position: absolute;
170
+ top: -12px;
171
+ right: -12px;
172
+ z-index: 6;
173
+ width: 48px;
174
+ height: 48px;
175
+ padding: 0;
176
+ border-radius: 50%;
177
+ border: 3px solid #fff;
178
+ background: linear-gradient(180deg, var(--coral-300), var(--coral-500) 50%);
179
+ box-shadow: 0 4px 0 var(--coral-700), 0 6px 10px oklch(0 0 0 / 0.25);
180
+ color: #fff;
181
+ font-size: 22px;
182
+ display: grid;
183
+ place-items: center;
184
+ cursor: pointer;
185
+ transition: transform var(--dur-fast), box-shadow var(--dur-fast);
186
+ }
187
+
188
+ .mp-viewer-close:active {
189
+ transform: translateY(3px);
190
+ box-shadow: 0 1px 0 var(--coral-700), 0 3px 6px oklch(0 0 0 / 0.25);
191
+ }
192
+
193
+ .mp-viewer-close:focus-visible,
194
+ .mp-who:focus-visible,
195
+ .mp-clue-tab:focus-visible,
196
+ .mp-clue-card:focus-visible,
197
+ .mp-key:focus-visible,
198
+ .mp-knob:focus-visible {
199
+ outline: 4px solid var(--blue-500);
200
+ outline-offset: 3px;
201
+ }
202
+
203
+ /* ------------------------------------------------------------------ title */
204
+
205
+ .mp-viewer-head {
206
+ display: grid;
207
+ gap: 4px;
208
+ justify-items: center;
209
+ padding: 0 8px;
210
+ text-align: center;
211
+ }
212
+
213
+ .mp-viewer-eyebrow {
214
+ font-weight: 700;
215
+ font-size: 12px;
216
+ letter-spacing: 0.14em;
217
+ text-transform: uppercase;
218
+ color: var(--wood-700);
219
+ }
220
+
221
+ .mp-viewer-label {
222
+ display: block;
223
+ font-weight: 700;
224
+ font-size: 12px;
225
+ letter-spacing: 0.12em;
226
+ text-transform: uppercase;
227
+ color: var(--ink-500);
228
+ }
229
+
230
+ .mp-viewer-title {
231
+ margin: 0;
232
+ font-family: var(--font-display);
233
+ font-weight: 400;
234
+ font-size: clamp(22px, 3.4vw, 34px);
235
+ line-height: 1.1;
236
+ color: var(--ink-900);
237
+ text-wrap: balance;
238
+ overflow-wrap: anywhere;
239
+ }
240
+
241
+ /* ------------------------------------------------------------------ the cabinet */
242
+
243
+ .mp-cabinet {
244
+ position: relative;
245
+ min-width: 0;
246
+ margin-top: 30px;
247
+ padding: 12px;
248
+ display: grid;
249
+ grid-template-columns: minmax(0, 1fr);
250
+ gap: 12px;
251
+ border-radius: 26px;
252
+ border: 5px solid var(--wood-700);
253
+ background: repeating-linear-gradient(90deg, transparent 0 14px, oklch(0 0 0 / 0.05) 14px 16px), linear-gradient(180deg, var(--wood-300), var(--wood-500));
254
+ box-shadow: 0 8px 0 var(--wood-900), 0 18px 28px oklch(0.2 0.05 60 / 0.35), inset 0 3px 0 oklch(1 0 0 / 0.3);
255
+ }
256
+
257
+ /* Who sent it: a mystery face until it's guessed, then the friend. It opens the tray either way. */
258
+ .mp-who {
259
+ position: absolute;
260
+ left: 14px;
261
+ top: -36px;
262
+ z-index: 3;
263
+ max-width: calc(100% - 150px);
264
+ display: inline-flex;
265
+ align-items: center;
266
+ gap: 8px;
267
+ padding: 4px 14px 4px 4px;
268
+ border-radius: 999px;
269
+ border: 3px solid var(--blue-300);
270
+ background: linear-gradient(180deg, #fff, var(--blue-100));
271
+ box-shadow: 0 4px 0 var(--blue-700), 0 8px 14px oklch(0.2 0.05 60 / 0.25);
272
+ font-family: var(--font-display);
273
+ font-size: 13px;
274
+ letter-spacing: 0.04em;
275
+ color: var(--blue-900);
276
+ cursor: pointer;
277
+ transition: translate var(--dur-fast), box-shadow var(--dur-fast);
278
+ -webkit-tap-highlight-color: transparent;
279
+ }
280
+
281
+ .mp-who:active {
282
+ translate: 0 3px;
283
+ box-shadow: 0 1px 0 var(--blue-700);
284
+ }
285
+
286
+ .mp-who-berry {
287
+ border-color: var(--berry-300);
288
+ background: linear-gradient(180deg, #fff, var(--berry-100));
289
+ box-shadow: 0 4px 0 var(--berry-700), 0 8px 14px oklch(0.2 0.05 60 / 0.25);
290
+ color: var(--berry-900);
291
+ }
292
+
293
+ .mp-who-found {
294
+ border-color: var(--gold-300);
295
+ background: linear-gradient(180deg, #fff, var(--gold-100));
296
+ box-shadow: 0 4px 0 var(--gold-700), 0 0 0 5px oklch(0.92 0.14 90 / 0.35), 0 8px 14px oklch(0.2 0.05 60 / 0.25);
297
+ color: var(--gold-900);
298
+ animation: mp-pop 420ms var(--ease-bounce) both;
299
+ }
300
+
301
+ .mp-who-text {
302
+ min-width: 0;
303
+ overflow: hidden;
304
+ text-overflow: ellipsis;
305
+ white-space: nowrap;
306
+ line-height: 1.15;
307
+ text-align: left;
308
+ }
309
+
310
+ .mp-who-text small {
311
+ display: block;
312
+ font-family: var(--font-ui);
313
+ font-weight: 700;
314
+ font-size: 10px;
315
+ letter-spacing: 0.12em;
316
+ text-transform: uppercase;
317
+ color: var(--ink-500);
318
+ }
319
+
320
+ /* Their clue is parked as a tab on the cabinet; it can be called over the screen whenever the viewer likes. */
321
+ .mp-clue-tab {
322
+ position: absolute;
323
+ right: 20px;
324
+ top: -30px;
325
+ z-index: 2;
326
+ height: 30px;
327
+ padding: 0 12px;
328
+ display: inline-flex;
329
+ align-items: center;
330
+ gap: 6px;
331
+ border-radius: 12px 12px 0 0;
332
+ border: 3px solid var(--gold-300);
333
+ border-bottom: 0;
334
+ background: oklch(0.97 0.05 90);
335
+ font-family: var(--font-display);
336
+ font-size: 12px;
337
+ letter-spacing: 0.06em;
338
+ text-transform: uppercase;
339
+ color: var(--wood-900);
340
+ cursor: pointer;
341
+ -webkit-tap-highlight-color: transparent;
342
+ }
343
+
344
+ .mp-clue-tab i {
345
+ font-size: 14px;
346
+ color: var(--gold-700);
347
+ }
348
+
349
+ .mp-clue-tab {
350
+ transition: translate var(--dur-fast), background var(--dur-fast);
351
+ }
352
+
353
+ .mp-clue-tab:active {
354
+ translate: 0 2px;
355
+ }
356
+
357
+ .mp-clue-tab-on {
358
+ background: var(--gold-100);
359
+ }
360
+
361
+ /* Narrow phones: the tab just says "Clue", so "Ready to guess" fits beside it. */
362
+ @media (max-width: 419px) {
363
+ .mp-clue-tab-their {
364
+ display: none;
365
+ }
366
+
367
+ .mp-who {
368
+ max-width: calc(100% - 112px);
369
+ }
370
+ }
371
+
372
+ .mp-screen {
373
+ position: relative;
374
+ height: clamp(320px, calc(100dvh - 400px), 600px);
375
+ overflow: hidden;
376
+ border-radius: 18px;
377
+ border: 6px solid var(--cream-300);
378
+ background: radial-gradient(ellipse at 50% 40%, #fff 0%, var(--cream-50) 60%, var(--cream-100) 100%);
379
+ box-shadow: inset 0 4px 12px oklch(0.3 0.05 60 / 0.18), inset 0 0 0 2px oklch(1 0 0 / 0.6);
380
+ }
381
+
382
+ .mp-screen-piece {
383
+ position: absolute;
384
+ inset: 0;
385
+ display: grid;
386
+ place-items: center;
387
+ container-type: size;
388
+ animation: mp-piece-in 320ms var(--ease-out) both;
389
+ }
390
+
391
+ @keyframes mp-piece-in {
392
+ from { opacity: 0; transform: translateY(10px); }
393
+ to { opacity: 1; transform: none; }
394
+ }
395
+
396
+ .mp-clue-card {
397
+ position: absolute;
398
+ left: 50%;
399
+ top: 50%;
400
+ translate: -50% -50%;
401
+ z-index: 4;
402
+ width: min(82%, 380px);
403
+ padding: 18px 18px 14px;
404
+ display: grid;
405
+ gap: 6px;
406
+ border: 0;
407
+ border-radius: 16px;
408
+ background: oklch(0.97 0.05 90);
409
+ box-shadow: 0 3px 0 var(--gold-300), 0 12px 28px oklch(0.2 0.05 60 / 0.35), 0 0 0 100vmax oklch(0.98 0.02 90 / 0.72);
410
+ rotate: -2deg;
411
+ font-family: var(--font-ui);
412
+ text-align: center;
413
+ color: var(--ink-900);
414
+ cursor: pointer;
415
+ animation: mp-pop 300ms var(--ease-bounce) both;
416
+ }
417
+
418
+ .mp-clue-card-leaving {
419
+ animation: mp-layer-shrink 180ms var(--ease-in-out) both;
420
+ pointer-events: none;
421
+ }
422
+
423
+ @keyframes mp-layer-shrink {
424
+ from { opacity: 1; transform: none; }
425
+ to { opacity: 0; transform: scale(0.9); }
426
+ }
427
+
428
+ .mp-clue-card strong {
429
+ font-family: var(--font-display);
430
+ font-weight: 400;
431
+ font-size: clamp(18px, 2.6vw, 22px);
432
+ line-height: 1.2;
433
+ text-wrap: balance;
434
+ overflow-wrap: anywhere;
435
+ }
436
+
437
+ .mp-clue-card em {
438
+ font-style: normal;
439
+ font-weight: 600;
440
+ font-size: 12px;
441
+ color: var(--ink-500);
442
+ }
443
+
444
+ /* The note inside the guess tray. */
445
+ .mp-tray-clue {
446
+ position: relative;
447
+ justify-self: center;
448
+ width: min(100%, 250px);
449
+ margin-top: 4px;
450
+ padding: 14px 14px 12px;
451
+ border-radius: 16px;
452
+ background: #fff;
453
+ box-shadow: 0 3px 0 var(--cream-300), 0 6px 14px oklch(0.2 0.05 60 / 0.22);
454
+ rotate: 2deg;
455
+ font-weight: 700;
456
+ font-size: 16px;
457
+ line-height: 1.4;
458
+ color: var(--ink-700);
459
+ text-align: center;
460
+ text-wrap: pretty;
461
+ overflow-wrap: anywhere;
462
+ }
463
+
464
+ .mp-tray-clue .mp-viewer-label {
465
+ margin-bottom: 4px;
466
+ }
467
+
468
+ .mp-clue-tape {
469
+ position: absolute;
470
+ left: 50%;
471
+ top: -10px;
472
+ width: 62px;
473
+ height: 18px;
474
+ margin-left: -31px;
475
+ border-radius: 3px;
476
+ background: oklch(0.92 0.14 90 / 0.8);
477
+ }
478
+
479
+ /* The deck: one key per piece of the memory, and one knob that says what happens next. */
480
+ .mp-deck {
481
+ display: flex;
482
+ align-items: center;
483
+ justify-content: space-between;
484
+ flex-wrap: wrap;
485
+ gap: 12px;
486
+ }
487
+
488
+ .mp-keys {
489
+ display: flex;
490
+ flex-wrap: wrap;
491
+ gap: 8px;
492
+ min-width: 0;
493
+ }
494
+
495
+ .mp-key {
496
+ min-height: 46px;
497
+ max-width: 100%;
498
+ padding: 0 14px;
499
+ display: inline-flex;
500
+ align-items: center;
501
+ gap: 8px;
502
+ border-radius: 12px;
503
+ border: 3px solid var(--wood-700);
504
+ background: linear-gradient(180deg, var(--cream-50), var(--cream-200));
505
+ box-shadow: 0 4px 0 var(--wood-900);
506
+ font-family: var(--font-ui);
507
+ font-weight: 700;
508
+ font-size: 14px;
509
+ color: var(--ink-900);
510
+ cursor: pointer;
511
+ transition: translate var(--dur-fast), box-shadow var(--dur-fast);
512
+ -webkit-tap-highlight-color: transparent;
513
+ }
514
+
515
+ .mp-key > i {
516
+ font-size: 18px;
517
+ color: var(--wood-700);
518
+ }
519
+
520
+ .mp-key:active,
521
+ .mp-key-on {
522
+ translate: 0 3px;
523
+ box-shadow: 0 1px 0 var(--wood-900), inset 0 3px 6px oklch(0 0 0 / 0.15);
524
+ }
525
+
526
+ .mp-key-on {
527
+ background: linear-gradient(180deg, var(--gold-100), var(--gold-300));
528
+ }
529
+
530
+ /* More than three pieces: only the one on screen keeps its name, the rest are their icons. */
531
+ .mp-keys-many .mp-key:not(.mp-key-on) {
532
+ padding: 0 12px;
533
+ }
534
+
535
+ .mp-keys-many .mp-key:not(.mp-key-on) .mp-key-label,
536
+ .mp-keys-many .mp-key:not(.mp-key-on) .mp-key-meta {
537
+ position: absolute;
538
+ width: 1px;
539
+ height: 1px;
540
+ overflow: hidden;
541
+ clip: rect(0 0 0 0);
542
+ white-space: nowrap;
543
+ }
544
+
545
+ .mp-key-tick {
546
+ width: 18px;
547
+ height: 18px;
548
+ flex: none;
549
+ border-radius: 50%;
550
+ background: var(--green-500);
551
+ color: #fff;
552
+ font-size: 11px;
553
+ display: grid;
554
+ place-items: center;
555
+ animation: mp-pop 300ms var(--ease-out) both;
556
+ }
557
+
558
+ .mp-key-meta {
559
+ font-family: var(--font-display);
560
+ font-size: 12px;
561
+ letter-spacing: 0.04em;
562
+ color: var(--ink-500);
563
+ }
564
+
565
+ .mp-knob {
566
+ width: 100%;
567
+ min-height: 56px;
568
+ padding: 0 8px 0 20px;
569
+ display: inline-flex;
570
+ align-items: center;
571
+ justify-content: space-between;
572
+ gap: 10px;
573
+ border-radius: 999px;
574
+ border: 3px solid var(--gold-700);
575
+ background: linear-gradient(180deg, var(--gold-300), var(--gold-500) 45%);
576
+ box-shadow: 0 6px 0 var(--gold-900), inset 0 2px 0 oklch(1 0 0 / 0.5), 0 8px 14px oklch(0.2 0.05 60 / 0.3);
577
+ font-family: var(--font-display);
578
+ font-size: 16px;
579
+ letter-spacing: 0.02em;
580
+ color: var(--gold-900);
581
+ text-align: left;
582
+ cursor: pointer;
583
+ transition: translate var(--dur-fast), box-shadow var(--dur-fast);
584
+ -webkit-tap-highlight-color: transparent;
585
+ }
586
+
587
+ .mp-knob:active {
588
+ translate: 0 4px;
589
+ box-shadow: 0 2px 0 var(--gold-900), inset 0 2px 0 oklch(1 0 0 / 0.5);
590
+ }
591
+
592
+ .mp-knob:disabled {
593
+ opacity: 0.7;
594
+ cursor: default;
595
+ }
596
+
597
+ .mp-knob-dial {
598
+ width: 40px;
599
+ height: 40px;
600
+ flex: none;
601
+ border-radius: 50%;
602
+ display: grid;
603
+ place-items: center;
604
+ font-size: 20px;
605
+ background: radial-gradient(circle at 35% 30%, #fff, var(--gold-300) 70%);
606
+ box-shadow: inset 0 -2px 0 oklch(0 0 0 / 0.2), 0 2px 0 var(--gold-900);
607
+ }
608
+
609
+ /* Waiting on a guess: the blue knob gives the same gentle nudge as the Level button on the map. */
610
+ .mp-knob-attract {
611
+ animation: mp-knob-attract 6s var(--ease-in-out) 3s infinite;
612
+ }
613
+
614
+ @keyframes mp-knob-attract {
615
+ 0%, 16%, 100% { scale: 1; }
616
+ 4% { scale: 1.05; }
617
+ 8% { scale: 1; }
618
+ 12% { scale: 1.03; }
619
+ }
620
+
621
+ .mp-knob-guess {
622
+ border-color: var(--blue-700);
623
+ background: linear-gradient(180deg, var(--blue-300), var(--blue-500) 45%);
624
+ box-shadow: 0 6px 0 var(--blue-900), inset 0 2px 0 oklch(1 0 0 / 0.45), 0 8px 14px oklch(0.2 0.05 60 / 0.3);
625
+ color: #fff;
626
+ text-shadow: 0 2px 0 var(--blue-700);
627
+ }
628
+
629
+ .mp-knob-guess:active {
630
+ box-shadow: 0 2px 0 var(--blue-900), inset 0 2px 0 oklch(1 0 0 / 0.45);
631
+ }
632
+
633
+ .mp-knob-guess .mp-knob-dial {
634
+ background: radial-gradient(circle at 35% 30%, #fff, var(--blue-100) 70%);
635
+ box-shadow: inset 0 -2px 0 oklch(0 0 0 / 0.2), 0 2px 0 var(--blue-900);
636
+ color: var(--blue-900);
637
+ text-shadow: none;
638
+ }
639
+
640
+ .mp-knob-saved {
641
+ border-color: var(--green-700);
642
+ background: linear-gradient(180deg, var(--green-300), var(--green-500) 45%);
643
+ box-shadow: 0 6px 0 var(--green-900), inset 0 2px 0 oklch(1 0 0 / 0.45);
644
+ color: #fff;
645
+ text-shadow: 0 2px 0 var(--green-700);
646
+ }
647
+
648
+ .mp-knob-saved .mp-knob-dial {
649
+ background: radial-gradient(circle at 35% 30%, #fff, var(--green-100) 70%);
650
+ box-shadow: inset 0 -2px 0 oklch(0 0 0 / 0.2), 0 2px 0 var(--green-900);
651
+ color: var(--green-900);
652
+ text-shadow: none;
653
+ }
654
+
655
+ /* ------------------------------------------------------------------ the tray, centred over the viewer */
656
+
657
+ .mp-tray-layer {
658
+ position: absolute;
659
+ inset: 0;
660
+ z-index: 2;
661
+ overflow-y: auto;
662
+ overscroll-behavior: contain;
663
+ display: grid;
664
+ place-items: center;
665
+ padding: max(78px, calc(env(safe-area-inset-top) + 64px)) 16px max(24px, env(safe-area-inset-bottom));
666
+ background: oklch(0.22 0.05 260 / 0.62);
667
+ backdrop-filter: blur(3px);
668
+ -webkit-backdrop-filter: blur(3px);
669
+ animation: mp-overlay-in 240ms var(--ease-out) both;
670
+ }
671
+
672
+ .mp-tray-in {
673
+ width: min(100%, 380px);
674
+ animation: mp-modal-in 380ms var(--ease-bounce) both;
675
+ }
676
+
677
+ .mp-tray-layer-leaving {
678
+ animation: mp-overlay-out 200ms var(--ease-out) both;
679
+ pointer-events: none;
680
+ }
681
+
682
+ .mp-tray-in-leaving {
683
+ animation: mp-modal-out 200ms var(--ease-in-out) both;
684
+ }
685
+
686
+ .mp-tray {
687
+ position: relative;
688
+ padding: 62px 18px 18px;
689
+ outline: none;
690
+ border-radius: var(--radius-xl);
691
+ border: 4px solid var(--blue-300);
692
+ background: linear-gradient(180deg, #fff, var(--blue-100));
693
+ box-shadow: inset 0 3px 0 oklch(1 0 0 / 0.7), var(--shadow-panel);
694
+ text-align: center;
695
+ transition: border-color 400ms var(--ease-out), box-shadow 400ms var(--ease-out);
696
+ }
697
+
698
+ /* The gold warms in over the blue or berry when the gem lands. */
699
+ .mp-tray::before {
700
+ content: '';
701
+ position: absolute;
702
+ inset: 0;
703
+ border-radius: calc(var(--radius-xl) - 4px);
704
+ background: linear-gradient(180deg, #fff, var(--gold-100));
705
+ opacity: 0;
706
+ transition: opacity 400ms var(--ease-out);
707
+ pointer-events: none;
708
+ }
709
+
710
+ .mp-tray-gold::before {
711
+ opacity: 1;
712
+ }
713
+
714
+ .mp-tray-body {
715
+ position: relative;
716
+ }
717
+
718
+ .mp-tray-wobble {
719
+ animation: mp-tray-wobble 520ms var(--ease-out) both;
720
+ }
721
+
722
+ @keyframes mp-tray-wobble {
723
+ 0%, 100% { rotate: 0deg; translate: 0 0; }
724
+ 20% { rotate: -1.4deg; translate: -6px 0; }
725
+ 45% { rotate: 1.2deg; translate: 5px 0; }
726
+ 70% { rotate: -0.6deg; translate: -3px 0; }
727
+ }
728
+
729
+ .mp-tray-blue { border-color: var(--blue-300); background: linear-gradient(180deg, #fff, var(--blue-100)); }
730
+ .mp-tray-berry { border-color: var(--berry-300); background: linear-gradient(180deg, #fff, var(--berry-100)); }
731
+ .mp-tray-gold {
732
+ border-color: var(--gold-300);
733
+ background: linear-gradient(180deg, #fff, var(--gold-100));
734
+ box-shadow: inset 0 3px 0 oklch(1 0 0 / 0.7), 0 0 0 6px oklch(0.92 0.14 90 / 0.35), var(--shadow-panel);
735
+ }
736
+
737
+ .mp-tray-face {
738
+ position: absolute;
739
+ left: 50%;
740
+ top: -50px;
741
+ transform: translateX(-50%);
742
+ z-index: 2;
743
+ }
744
+
745
+ /* The mystery face flips over to show who it was. */
746
+ .mp-tray-flip {
747
+ position: relative;
748
+ display: block;
749
+ width: 104px;
750
+ height: 104px;
751
+ transform-style: preserve-3d;
752
+ transition: transform 520ms var(--ease-in-out);
753
+ }
754
+
755
+ .mp-tray-flipped {
756
+ transform: rotateY(180deg);
757
+ }
758
+
759
+ .mp-tray-face-front,
760
+ .mp-tray-face-back {
761
+ position: absolute;
762
+ inset: 0;
763
+ border-radius: 50%;
764
+ backface-visibility: hidden;
765
+ -webkit-backface-visibility: hidden;
766
+ }
767
+
768
+ .mp-tray-face-back {
769
+ transform: rotateY(180deg);
770
+ box-shadow: 0 0 0 0 oklch(0.92 0.14 90 / 0);
771
+ transition: box-shadow 400ms var(--ease-out) 300ms;
772
+ }
773
+
774
+ .mp-tray-flipped .mp-tray-face-back {
775
+ box-shadow: 0 0 0 8px oklch(0.92 0.14 90 / 0.45);
776
+ }
777
+
778
+ .mp-tray-gem {
779
+ position: absolute !important;
780
+ right: -18px;
781
+ bottom: -8px;
782
+ animation: mp-gem-drop 620ms var(--ease-bounce) 1100ms both;
783
+ }
784
+
785
+ @keyframes mp-gem-drop {
786
+ 0% { transform: translateY(-46px) scale(0.3); opacity: 0; }
787
+ 55% { transform: translateY(4px) scale(1.12); opacity: 1; }
788
+ 80% { transform: translateY(-2px) scale(0.97); }
789
+ 100% { transform: translateY(0) scale(1); }
790
+ }
791
+
792
+ /* Name, relationship, "You found them!" and the buttons arrive one after another. */
793
+ .mp-beat-1 { animation: mp-beat-drop 480ms var(--ease-bounce) 480ms both; }
794
+ .mp-beat-2 { animation: mp-beat-drop 420ms var(--ease-out) 700ms both; }
795
+ .mp-beat-3 { animation: mp-beat-drop 480ms var(--ease-bounce) 900ms both; }
796
+ .mp-beat-4 { animation: mp-beat-drop 420ms var(--ease-out) 1250ms both; }
797
+
798
+ @keyframes mp-beat-drop {
799
+ 0% { opacity: 0; transform: translateY(-12px) scale(0.85); }
800
+ 100% { opacity: 1; transform: translateY(0) scale(1); }
801
+ }
802
+
803
+ /* A memory that has already been opened: nothing to unwrap, everything sits still. */
804
+ .mp-tray-settled .mp-tray-flip,
805
+ .mp-tray-settled .mp-tray-face-back,
806
+ .mp-tray-settled .mp-tray {
807
+ transition: none;
808
+ }
809
+
810
+ .mp-tray-settled .mp-tray-gem,
811
+ .mp-tray-settled .mp-beat-1,
812
+ .mp-tray-settled .mp-beat-2,
813
+ .mp-tray-settled .mp-beat-3,
814
+ .mp-tray-settled .mp-beat-4 {
815
+ animation: none;
816
+ }
817
+
818
+ .mp-tray-body {
819
+ display: grid;
820
+ gap: 12px;
821
+ }
822
+
823
+ .mp-tray-prompt {
824
+ margin: 0;
825
+ font-family: var(--font-display);
826
+ font-size: 24px;
827
+ line-height: 1.1;
828
+ color: var(--ink-900);
829
+ text-wrap: balance;
830
+ }
831
+
832
+ .mp-tray-hint {
833
+ margin: 0;
834
+ font-weight: 700;
835
+ font-size: 15px;
836
+ color: var(--ink-700);
837
+ text-wrap: pretty;
838
+ }
839
+
840
+ .mp-tray-choices {
841
+ display: grid;
842
+ gap: 12px;
843
+ }
844
+
845
+ .mp-tray-link {
846
+ justify-self: center;
847
+ min-height: 44px;
848
+ padding: 8px 12px;
849
+ border: 0;
850
+ background: none;
851
+ font-family: var(--font-ui);
852
+ font-weight: 700;
853
+ font-size: 16px;
854
+ color: var(--blue-900);
855
+ text-decoration: underline;
856
+ text-underline-offset: 4px;
857
+ cursor: pointer;
858
+ }
859
+
860
+ .mp-tray-link:disabled {
861
+ opacity: 0.5;
862
+ }
863
+
864
+ .mp-tray-back {
865
+ margin-top: -8px;
866
+ color: var(--ink-700);
867
+ }
868
+
869
+ .mp-tray-whowas {
870
+ display: grid;
871
+ justify-items: center;
872
+ gap: 2px;
873
+ }
874
+
875
+ .mp-tray-name {
876
+ font-family: var(--font-display);
877
+ font-size: 30px;
878
+ line-height: 1.05;
879
+ color: var(--ink-900);
880
+ text-wrap: balance;
881
+ }
882
+
883
+ .mp-tray-relationship {
884
+ font-weight: 600;
885
+ font-size: 15px;
886
+ color: var(--ink-700);
887
+ }
888
+
889
+ .mp-tray-actions {
890
+ display: grid;
891
+ gap: 14px;
892
+ margin-top: 4px;
893
+ }
894
+
895
+ .mp-viewer-error {
896
+ margin: 0;
897
+ padding: 10px 14px;
898
+ border-radius: var(--radius-md);
899
+ background: var(--coral-100);
900
+ color: var(--coral-900);
901
+ font-weight: 700;
902
+ }
903
+
904
+ /* ------------------------------------------------------------------ loading: the screen warms up */
905
+
906
+ .mp-screen-off {
907
+ display: grid;
908
+ place-items: center;
909
+ border-color: var(--wood-700);
910
+ background: radial-gradient(ellipse at 50% 45%, oklch(0.36 0.04 70) 0%, oklch(0.26 0.03 60) 70%, oklch(0.2 0.02 55) 100%);
911
+ box-shadow: inset 0 4px 16px oklch(0 0 0 / 0.45), inset 0 0 0 2px oklch(1 0 0 / 0.06);
912
+ }
913
+
914
+ /* Faint scan lines on the dark glass. */
915
+ .mp-screen-off::before {
916
+ content: '';
917
+ position: absolute;
918
+ inset: 0;
919
+ background: repeating-linear-gradient(180deg, oklch(1 0 0 / 0.035) 0 2px, transparent 2px 5px);
920
+ pointer-events: none;
921
+ }
922
+
923
+ /* A band of warm light rolls down the glass, again and again. */
924
+ .mp-screen-sweep {
925
+ position: absolute;
926
+ left: 0;
927
+ right: 0;
928
+ top: 0;
929
+ height: 38%;
930
+ background: linear-gradient(180deg, transparent, oklch(0.9 0.12 85 / 0.12) 40%, oklch(0.95 0.1 90 / 0.28) 55%, transparent);
931
+ animation: mp-screen-sweep 1.8s var(--ease-in-out) infinite;
932
+ pointer-events: none;
933
+ }
934
+
935
+ @keyframes mp-screen-sweep {
936
+ from { transform: translateY(-100%); }
937
+ to { transform: translateY(270%); }
938
+ }
939
+
940
+ .mp-screen-off-message {
941
+ position: relative;
942
+ display: grid;
943
+ justify-items: center;
944
+ gap: 14px;
945
+ padding: 20px;
946
+ text-align: center;
947
+ font-weight: 700;
948
+ font-size: 17px;
949
+ color: var(--cream-100);
950
+ }
951
+
952
+ .mp-screen-off-message p {
953
+ margin: 0;
954
+ max-width: 32ch;
955
+ text-wrap: pretty;
956
+ }
957
+
958
+ .mp-screen-off-message > i {
959
+ font-size: 44px;
960
+ color: var(--gold-300);
961
+ }
962
+
963
+ .mp-screen-tuning {
964
+ display: inline-flex;
965
+ align-items: baseline;
966
+ gap: 2px;
967
+ font-family: var(--font-display);
968
+ font-size: 24px;
969
+ letter-spacing: 0.04em;
970
+ color: var(--gold-300);
971
+ text-shadow: 0 0 18px oklch(0.85 0.14 85 / 0.55);
972
+ animation: mp-screen-flicker 2.6s steps(1) infinite;
973
+ }
974
+
975
+ @keyframes mp-screen-flicker {
976
+ 0%, 100% { opacity: 1; }
977
+ 47% { opacity: 0.82; }
978
+ 49% { opacity: 1; }
979
+ 83% { opacity: 0.9; }
980
+ }
981
+
982
+ .mp-tuning-dots {
983
+ display: inline-flex;
984
+ gap: 4px;
985
+ margin-left: 4px;
986
+ }
987
+
988
+ .mp-tuning-dots i {
989
+ width: 6px;
990
+ height: 6px;
991
+ border-radius: 50%;
992
+ background: currentColor;
993
+ animation: mp-screen-dot 1.1s var(--ease-in-out) infinite;
994
+ }
995
+
996
+ .mp-tuning-dots i:nth-child(2) { animation-delay: 150ms; }
997
+ .mp-tuning-dots i:nth-child(3) { animation-delay: 300ms; }
998
+
999
+ @keyframes mp-screen-dot {
1000
+ 0%, 60%, 100% { transform: translateY(0); opacity: 0.45; }
1001
+ 30% { transform: translateY(-6px); opacity: 1; }
1002
+ }
1003
+
1004
+ /* When the memory arrives the screen switches on: a bright line opens into the picture. */
1005
+ .mp-screen:not(.mp-screen-off) {
1006
+ animation: mp-screen-on 460ms var(--ease-out) both;
1007
+ }
1008
+
1009
+ @keyframes mp-screen-on {
1010
+ 0% { clip-path: inset(49% 0 49% 0 round 12px); filter: brightness(1.8); }
1011
+ 45% { clip-path: inset(0 0 0 0 round 12px); filter: brightness(1.3); }
1012
+ 100% { clip-path: inset(0 0 0 0 round 12px); filter: none; }
1013
+ }
1014
+
1015
+ /* Soft bars where the title and the keys will be. */
1016
+ .mp-placeholder {
1017
+ display: block;
1018
+ border-radius: 999px;
1019
+ background: linear-gradient(90deg, var(--cream-200) 0%, var(--cream-100) 50%, var(--cream-200) 100%) 0 0 / 200% 100%;
1020
+ animation: mp-placeholder-shine 1.4s linear infinite;
1021
+ }
1022
+
1023
+ @keyframes mp-placeholder-shine {
1024
+ from { background-position: 100% 0; }
1025
+ to { background-position: -100% 0; }
1026
+ }
1027
+
1028
+ .mp-placeholder-eyebrow {
1029
+ width: 90px;
1030
+ height: 12px;
1031
+ }
1032
+
1033
+ .mp-placeholder-title {
1034
+ width: min(70%, 360px);
1035
+ height: 30px;
1036
+ border-radius: 12px;
1037
+ }
1038
+
1039
+ .mp-deck .mp-placeholder {
1040
+ background-image: linear-gradient(90deg, oklch(1 0 0 / 0.16) 0%, oklch(1 0 0 / 0.3) 50%, oklch(1 0 0 / 0.16) 100%);
1041
+ }
1042
+
1043
+ .mp-placeholder-key {
1044
+ width: 110px;
1045
+ height: 46px;
1046
+ border-radius: 12px;
1047
+ }
1048
+
1049
+ .mp-placeholder-knob {
1050
+ width: 100%;
1051
+ height: 56px;
1052
+ }
1053
+
1054
+ @media (min-width: 600px) and (min-height: 620px) {
1055
+ .mp-placeholder-knob {
1056
+ width: 220px;
1057
+ margin-left: auto;
1058
+ }
1059
+ }
1060
+
1061
+
1062
+ /* ------------------------------------------------------------------ iPad and desktop: the sheet fits the window and the screen takes what is left */
1063
+
1064
+ @media (min-width: 600px) and (min-height: 620px) {
1065
+ .mp-viewer-overlay {
1066
+ overflow: hidden;
1067
+ display: grid;
1068
+ grid-template-rows: minmax(0, 1fr);
1069
+ place-items: center;
1070
+ padding: max(56px, calc(env(safe-area-inset-top) + 44px)) 28px max(28px, calc(env(safe-area-inset-bottom) + 16px));
1071
+ }
1072
+
1073
+ .mp-viewer-sheet {
1074
+ width: 100%;
1075
+ max-width: 980px;
1076
+ height: min(100%, 900px);
1077
+ margin: 0;
1078
+ padding: 60px 20px 20px;
1079
+ display: flex;
1080
+ flex-direction: column;
1081
+ gap: 14px;
1082
+ }
1083
+
1084
+ .mp-cabinet {
1085
+ flex: 1;
1086
+ min-height: 0;
1087
+ padding: 14px;
1088
+ grid-template-rows: minmax(0, 1fr) auto;
1089
+ }
1090
+
1091
+ .mp-screen {
1092
+ height: auto;
1093
+ min-height: 0;
1094
+ }
1095
+
1096
+ .mp-who {
1097
+ left: 18px;
1098
+ max-width: calc(100% - 190px);
1099
+ font-size: 14px;
1100
+ }
1101
+
1102
+ .mp-keys {
1103
+ flex: 1;
1104
+ }
1105
+
1106
+ .mp-knob {
1107
+ width: auto;
1108
+ margin-left: auto;
1109
+ }
1110
+
1111
+ .mp-viewer-error {
1112
+ flex: none;
1113
+ }
1114
+ }
1115
+
1116
+ /* iPad landscape and desktop: height is the scarce thing, so the title moves up beside the sign. */
1117
+ @media (min-width: 1000px) and (min-height: 620px) {
1118
+ .mp-viewer-overlay {
1119
+ padding: 50px 60px 26px;
1120
+ }
1121
+
1122
+ .mp-viewer-sheet {
1123
+ height: min(100%, 820px);
1124
+ padding-top: 20px;
1125
+ }
1126
+
1127
+ .mp-viewer-head {
1128
+ min-height: 44px;
1129
+ align-content: center;
1130
+ padding: 0 64px 0 250px;
1131
+ }
1132
+ }
1133
+
1134
+ @media (prefers-reduced-motion: reduce) {
1135
+ .mp-tray-flip,
1136
+ .mp-tray-face-back,
1137
+ .mp-tray,
1138
+ .mp-tray::before {
1139
+ transition: none;
1140
+ }
1141
+ .mp-viewer-overlay,
1142
+ .mp-viewer-sheet,
1143
+ .mp-viewer-closing,
1144
+ .mp-viewer-closing .mp-viewer-sheet,
1145
+ .mp-tray-layer,
1146
+ .mp-tray-in,
1147
+ .mp-tray-layer-leaving,
1148
+ .mp-tray-in-leaving,
1149
+ .mp-clue-card-leaving,
1150
+ .mp-knob-attract,
1151
+ .mp-tray-wobble,
1152
+ .mp-tray-gem,
1153
+ .mp-beat-1,
1154
+ .mp-beat-2,
1155
+ .mp-beat-3,
1156
+ .mp-beat-4,
1157
+ .mp-key-tick,
1158
+ .mp-screen-sweep,
1159
+ .mp-screen-tuning,
1160
+ .mp-tuning-dots i,
1161
+ .mp-placeholder,
1162
+ .mp-screen:not(.mp-screen-off),
1163
+ .mp-screen-piece,
1164
+ .mp-who-found,
1165
+ .mp-clue-card {
1166
+ animation: none;
1167
+ }
1168
+ .mp-screen-sweep {
1169
+ display: none;
1170
+ }
1171
+ }
1172
+
1173
+ /* Contained: sits inside a positioned box instead of covering the window, for docs and previews. */
1174
+ .mp-viewer-contained {
1175
+ position: absolute;
1176
+ }