openguessr-ui 0.0.0-stage → 0.1.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.
package/README.md CHANGED
@@ -1,3 +1,462 @@
1
- # Temporary Holding Version
1
+ # OpenGuessr UI
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The user interface design system for OpenGuessr. Install via `npm install openguessr-ui`.
4
+
5
+ Made to be used with [Svelte](https://svelte.dev).
6
+
7
+ > [!IMPORTANT]
8
+ > While this library is publicly available, commercial use is not permitted.
9
+
10
+ ## Structure
11
+
12
+ These core concepts define how UI should be structured.
13
+
14
+ ### Core concepts
15
+
16
+ - **Boxes:** Boxes house controls, text, or other boxes.
17
+
18
+ - **Panels:** Panels are flexible floating UI elements. They typically include controls (e.g. buttons) rather than loads of text or images, perfect for HUDs.
19
+
20
+ - **Containers:** Containers contain boxes. The popup acts as a layout/container hybird that also contains boxes.
21
+
22
+ - **Layouts:** Layouts are typically full pages that containers or panels sit in.
23
+
24
+ ### Hierarchy
25
+
26
+ 1. Every element starts in layout space (level 1), where `--layout-margin` defines the spacing between grouped elements (and `--layout-spacer` between unrelated ones), padding, and border radii of children.
27
+
28
+ 2. The second level is containers, where the spacing and padding is `--box-margin`, and the border radii of children are as well.
29
+
30
+ 3. The third level is boxes, where the spacing and padding is still `--box-margin`, but the border radii of children are `--content-margin`.
31
+
32
+ 4. On the fourth level there is either content or smaller boxes. Smaller boxes still use `--box-margin` for padding and spacing, but `--content-margin` for border radii.
33
+
34
+ The content level (inside buttons, chips, tabs, or other controls) uses spacing of `--content-margin`. Placing content directly into layouts or containers is not allowed, it **needs** to sit inside a box or panel.
35
+
36
+ Popups take some properties from layouts and some from containers. They use `--layout-margin` for spacing, but content inside them is boxes (level 3) with `--box-margin` for border radii. Panels typically live in layout space (level 1), but have unique spacing rules (as outlined above).
37
+
38
+ ## Styling
39
+
40
+ This defines how UI should be styled.
41
+
42
+ ### Variables
43
+
44
+ These variables are used for margins, paddings, and gaps:
45
+
46
+ | Variable | Value |
47
+ | -------- | ----- |
48
+ | --content-margin | 5px |
49
+ | --panel-margin | 8px |
50
+ | --box-margin | 10px |
51
+ | --layout-margin | 15px |
52
+ | --layout-spacer | 50px |
53
+
54
+ These variables are used for colors:
55
+
56
+ | Variable | Explanation |
57
+ | -------- | ------- |
58
+ | --brand-color | Primary red |
59
+ | --panel-color | Blue-ish, for panels |
60
+ | --box-color | Bright transparent for boxes |
61
+ | --line-color | For strokes and separators |
62
+ | --box-color-dark | Accentuated boxes |
63
+ | --box-color-dark-soft | Slightly accentuated boxes |
64
+ | --overlay-color | Dark overlays |
65
+ | --overlay-color-dark | Full-screen menu overlays |
66
+ | --bright-green-color | Experience, perks |
67
+ | --bright-green-color-soft| Indicators, subtle |
68
+ | --bright-green-color-dark | Experience, perk backgrounds |
69
+ | --background-color | Opaque background |
70
+ | --dark-shadow-color | Text or drop shadows |
71
+
72
+ > [!TIP]
73
+ > There should be at most one `--brand-color` element visible at a time (the primary action). Green colors should **not** be used for success, use bright styling instead (e.g. `rgba(255, 255, 255, 0.2)`).
74
+
75
+ These variabels are used for box shadows:
76
+
77
+ | Variable | Used for |
78
+ | -------- | ------- |
79
+ | --button-shadow | Buttons |
80
+ | --button-shadow-dark | Subtle buttons |
81
+ | --box-shadow-top | Boxes that fade out towards the bottom |
82
+ | --box-shadow-bottom | Boxes that fade out towards the top |
83
+ | --box-shadow | Boxes |
84
+ | --bulb-shadow | Pills, chips, badges |
85
+ | --panel-shadow | Floating elements (containers, panels..) |
86
+
87
+ These variables are used for blur:
88
+
89
+ | Variable | Value |
90
+ | -------- | ------- |
91
+ | --weak-blur | 5px |
92
+ | --normal-blur | 10px |
93
+ | --elevated-blur | 15px |
94
+ | --strong-blur | 50px |
95
+
96
+ ### Boxes
97
+
98
+ Boxes typically use this base styling:
99
+
100
+ ```css
101
+ .box {
102
+ background-color: var(--box-color);
103
+ border-radius: var(--box-margin); /* --content-margin when inside another box */
104
+ padding: var(--box-margin);
105
+ gap: var(--box-margin); /* When applicable */
106
+ box-shadow: var(--box-shadow);
107
+ max-width: fit-content;
108
+ }
109
+ ```
110
+
111
+ Their width should not grow to fit their container or the Popup they are in.
112
+
113
+ #### Text inside boxes
114
+
115
+ Since boxes have rather tight padding and text naturally adds top and bottom spacing through the line-height, applying `padding-inline: var(--content-margin)` to text is recommended. If the entire box just contains text, raising the boxes' inline padding to `--layout-margin` has the same effect.
116
+
117
+ ### Panels
118
+
119
+ Panels typically use this base styling:
120
+
121
+ ```css
122
+ .panel {
123
+ background-color: var(--panel-color);
124
+ border-radius: var(--box-margin); /* Or --layout-margin if they contain large buttons */
125
+ padding: var(--panel-margin);
126
+ gap: var(--panel-margin); /* When applicable */
127
+ backdrop-filter: blur(var(--normal-blur)); /* Only if the layout doesn't apply elevated or strong blur already */
128
+ box-shadow: var(--panel-shadow);
129
+ }
130
+ ```
131
+
132
+ Panels that house large buttons which are rounder than usual buttons should have a border radius of `--layout-margin` to avoid uneven corners. Don't mix them, panels should stick to only large or only small buttons.
133
+
134
+ When panels sit inside layouts with a strong background color (e.g. `--overlay-color-dark`), their backgorund color should be `--box-color` instead of `--panel-color` to avoid layering two blue-ish tones (which will produce a color that looks too vibrant).
135
+
136
+ The gap or margin between related panels should be `--box-margin` if they use `--box-margin` for the border radii, otherwise `--layout-margin`.
137
+
138
+ ### Containers
139
+
140
+ Containers typically use this base styling:
141
+
142
+ ```css
143
+ .container {
144
+ background-color: var(--panel-color);
145
+ border-radius: var(--layout-margin);
146
+ padding: var(--box-margin);
147
+ gap: var(--box-margin); /* When applicable */
148
+ backdrop-filter: blur(var(--normal-blur)); /* Only if the layout doesn't apply elevated or strong blur already */
149
+ box-shadow: var(--panel-shadow);
150
+ }
151
+ ```
152
+
153
+ Like panels, containers inside layouts with a strong background color should use `--box-color` instead of `--panel-color`.
154
+
155
+ ### Layouts
156
+
157
+ Layouts typically use this base styling:
158
+
159
+ ```css
160
+ .layout {
161
+ background-color: var(--overlay-color-dark);
162
+ padding: var(--layout-margin);
163
+ padding-block: var(--layout-spacer); /* For full pages to leave some slack at the top and bottom */
164
+ gap: var(--layout-margin); /* When applicable */
165
+ backdrop-filter: blur(var(--elevated-blur));
166
+ max-width: 1100px;
167
+ }
168
+ ```
169
+
170
+ ### Buttons
171
+
172
+ Buttons should always be placed in a box or panel, not just on a blank page. Large buttons should **not** be put into a box.
173
+
174
+ To create a button, apply the `standard-button` class. This will create a red primary button. To make it secondary, add `bright`.
175
+
176
+ There is a tertiary button, and it's created by applying `dark`, but it's not used in combination with other button variants. Instead, it's strictly for repetitive actions to reduce mental load since it is less eye-catching. For example, a sidebar with dozens of items, where each one has a small "X" button to delete it.
177
+
178
+ Large buttons with short text inside often look unnaturally slim when placed as the only button inside a panel, bumping the padding by adding `wide` resolves this.
179
+
180
+ ### Icons in buttons
181
+
182
+ Regular buttons typically have the icon placed on the left side.
183
+
184
+ Large buttons with text place the icon towards the right and use `space-between` to ensure that when multiple buttons are present, icons and text are perfectly aligned below each other, making it easier to scan through them at a glance.
185
+
186
+ ### Icons
187
+
188
+ Lucide (`@lucide/svelte`) should be used for icons.
189
+
190
+ At the default icon size (which inherits from the font size), using `2.25` as the stroke width is suggested. Icons placed inside of buttons right next to text commonly look best at size `20`.
191
+
192
+ Since Lucide icons don't follow a strict universal strokeWidth and size, some might look better at different values, in which case it is okay to deviate from these defaults. For example, the `X` looks a bit thinner than most other icons.
193
+
194
+ Icons should typically have the color white. They inherit color, but if they aren't placed inside an element that sets one (such as a paragraph), it needs to be set explicitly.
195
+
196
+ The spacing between text and an icon inside content should be `--content-margin`.
197
+
198
+ ### Scrollable areas
199
+
200
+ Overfade (`overfade`) is a library used for scrollable areas. It applies a dynamic mask-image on the overflowing element's parent.
201
+
202
+ For example, when an element, that contains overflowing text content and uses `of-top` and `of-bottom`, is scrolled all the way to the top, the top of the text is not faded out, only the bottom is.
203
+
204
+ To use it, add its classes:
205
+
206
+ - `of-top`: Fade out towards the top (for overflow-y)
207
+ - `of-bottom`: Fade out towards the bottom (for overflow-y)
208
+ - `of-left`: Fade out towards the left (for overflow-x)
209
+ - `of-right`: Fade out towards the right (for overflow-x)
210
+ - `of-length-x`: Multiply the length of the fade, defaults to 1 (optional, x = factor)
211
+
212
+ These classes should not be applied directly on boxes or containers, since that would fade their background color. Instead, the scrollable content should go in a separate child div with overfade classes applied.
213
+
214
+ Scrollable content should be intentionally, visibly cut off when scrolling is allowed to communicate the fact that scrolling is possible.
215
+
216
+ ### Opacity values
217
+
218
+ Opacity values used are typically `0.25`, `0.5`, `0.75` and `1`. Supportive text, such as the explanation for a feature, can be `0.75` or even `0.5`. Labels, e.g. for controls, should be full opacity.
219
+
220
+ ### Images
221
+
222
+ Apply `pointer-events: none` and `user-select: none` unless images need to be explicitly interactive.
223
+
224
+ ### Animation and transition durations
225
+
226
+ Use `100ms`, `150ms`, `250ms`, `500ms` or `1000ms`.
227
+
228
+ ### Hover effects
229
+
230
+ Elements with hover styling should get darker when hovered, never brighter. For mobile, many `:hover` effects should also be applied when `:active`.
231
+
232
+ ## Components
233
+
234
+ These built-in components are useful for building common UI flows.
235
+
236
+ ### Popup
237
+
238
+ The popup component is used for dialogs. It takes the following props:
239
+
240
+ - `open`: Whether the popup is open
241
+ - `slim`: Reduces the max width
242
+ - `verySlim`: Reduces the max width further
243
+ - `frameless`: Reduces the padding, e.g. for iFrames
244
+ - `onuserclose`: Called when the user closes the popup
245
+
246
+ A common design pattern is to include a heading at the top of a popup. For this, the built-in `popup-title` class can be used:
247
+
248
+ ```html
249
+ <div class="popup-title">
250
+ <h1>Settings</h1>
251
+ </div>
252
+ ```
253
+
254
+ Many popups include a central action button which gets the `large popup-bottom-button` treatment. Never combine this with the `wide` class.
255
+
256
+ ### Chip
257
+
258
+ The chip component is used for small pieces of information, such as a "New" or "Free" indicator. It takes the following props:
259
+
260
+ - `text`: The chip's text
261
+ - `children`: Content to render instead of text
262
+ - `red`: Color variant
263
+ - `italic`: Makes the text italic, typically combined with red
264
+ - `mutedOpacity`: Makes the text muted, red variant typically disables this
265
+ - `onclick`: Makes the chip clickable
266
+ - `class`, `style`: Class passthrough
267
+
268
+ ### Collapsible
269
+
270
+ The collapsible component is used to contain information that should be tucked away, e.g. information only valuable for a portion of players. It takes the following props:
271
+
272
+ - `title`: The title text
273
+ - `isOpen`: Whether the collapsible is expanded
274
+ - `children`: The content inside
275
+ - `inPage`: Set to false when used in popups, containers etc.
276
+ - `ontoggle`: Called whenever the open state changes
277
+ - `class`: Class passthrough
278
+
279
+ ### ConfirmPopup
280
+
281
+ The confirm popup component is used to request user confirmation or to display information. It needs to be mounted. It takes the following props:
282
+
283
+ - `onconfirm`: Called when the user confirms
284
+
285
+ Confirm popups are shown via `showConfirmPopup(title, text, executeFunction, confirmText)`:
286
+
287
+ - `title`: The popup title
288
+ - `text`: The description
289
+ - `executeFunction`: Called on confirm, also shows the confirm button
290
+ - `confirmText`: Text of the confirm button, defaults to "Confirm"
291
+
292
+ Usage example:
293
+
294
+ ```js
295
+ showConfirmPopup("Remove item?", "The item will be removed from your favorites.", () => removeItem(), "Remove");
296
+ ```
297
+
298
+ ### ErrorPopup
299
+
300
+ The error popup component is used to inform the user about errors. It needs to be mounted.
301
+
302
+ Error popups are shown via `errorPopup(title, description, errorCode, reload, hideDontShowAgainOption)`:
303
+
304
+ - `title`: The popup title
305
+ - `description`: The description
306
+ - `errorCode`: The error code (usually passed by the server)
307
+ - `reload`: Reloads the page when the popup is closed, also shows the popup even if the user chose to hide errors
308
+ - `hideDontShowAgainOption`: Hides the "Never show again" option
309
+
310
+ Usage example:
311
+
312
+ ```js
313
+ errorPopup("Failed to load map", "An error occured loading this map.", "Code 404: Map not found");
314
+ ```
315
+
316
+ ### InfoBanner
317
+
318
+ The info banner component is used for short pieces of information. It takes the following props:
319
+
320
+ - `text`: The banner text
321
+ - `children`: Content to render instead of text
322
+ - `inPage`: Set to true when used in layouts
323
+ - `nowrap`: Keeps the text on one line
324
+ - `center`: Centers the content
325
+ - `transition`: Toggles the slide transition
326
+ - `class`, `style`: Class and style passthrough
327
+
328
+ ### LoadingSpinner
329
+
330
+ The loading spinner component can be displayed while large content loads, such as panoramas or full pages. It takes the following props:
331
+
332
+ - `class`, `style`: Class and style passthrough
333
+
334
+ ### Notification
335
+
336
+ The notification component is used for short messages at the top of the screen, optionally with an action. It needs to be mounted. It takes the following props:
337
+
338
+ - `top`: Distance from the top of the viewport, defaults to `--layout-margin`
339
+ - `onaccept`: Called when the accept button is used
340
+ - `ondismiss`: Called when the dismiss button is used
341
+
342
+ Notifications are shown via `showNotification(text, acceptAction, dismissAction, viewOnly, clickableText, textClickFunction)`:
343
+
344
+ - `text`: The notification text, `%s` marks where the clickable text goes
345
+ - `acceptAction`: Called on accept, also shows the accept button
346
+ - `dismissAction`: Called on dismiss
347
+ - `viewOnly`: Displays a view icon in the accept button
348
+ - `clickableText`: Clickable text inserted at `%s`, e.g. a player name
349
+ - `textClickFunction`: Called when the clickable text is clicked
350
+
351
+ Usage example:
352
+
353
+ ```js
354
+ showNotification("Copied!");
355
+ showNotification("Accept event invite?", () => joinEvent());
356
+ ```
357
+
358
+ ### RoomCodeInput
359
+
360
+ The room code input component is used for entering 6-character room codes. It takes the following props:
361
+
362
+ - `segments`: The entered characters
363
+ - `inPage`: Set to false when used in panels, boxes etc.
364
+ - `onsubmit`: Called with the code when enter or the submit button is pressed
365
+
366
+ ### Tabs
367
+
368
+ The tabs component is great for choosing settings or views, as well as for On/Off switches. It takes the following props:
369
+
370
+ - `tabs`: Text strings or icons
371
+ - `selected`: Selected tab's content
372
+ - `selectedIndex`: Selected tab's index
373
+ - `onchange`: Called with `(selected, selectedIndex)` when the selection changes
374
+ - `disabled`: Disables all tabs
375
+ - `children`: Children rendered inside tab
376
+ - `class`: Class passthrough
377
+
378
+ ### Ticker
379
+
380
+ The ticker component is used for fine-grained numeric inputs. It takes the following props:
381
+
382
+ - `value`: Current value
383
+ - `initialValue`: Start value
384
+ - `step`: Step amount (how much the value changes per step)
385
+ - `minimum`: Maximum value
386
+ - `minimumText`, `maximumText`: Text displayed when the max/min is reached
387
+ - `minimumValue`, `maximumValue`: Custom value to show when max/min is reached
388
+ - `note`: Unit of the value
389
+ - `minValueWidth`: Minimum width of value field to prevent the width from jumping
390
+ - `onchange`: Called when the value changes
391
+
392
+ ### TitleSeparator
393
+
394
+ The title separator component is used to separate content inside Popups or containers. It takes the following props:
395
+
396
+ - `text`: The title text
397
+ - `noMargin`: Removes the default top and bottom margin (`--layout-margin`)
398
+ - `class`: Class passthrough
399
+
400
+ ## Attachments
401
+
402
+ Attachments utilize Svelte's `{@attach...}` syntax.
403
+
404
+ ### Tooltip
405
+
406
+ Regular tooltips are used for icon buttons that have no text, and persistent tooltips for tutorials.
407
+
408
+ Usage example:
409
+
410
+ ```html
411
+ <button {@attach tooltip({ text: "Settings" })}><GearsIcon /></button>
412
+ ```
413
+
414
+ - `text`: The tooltip text
415
+ - `imageSrc`: Image shown above the text
416
+ - `imageAspectRatio`: Aspect ratio of the image
417
+ - `maxWidth`: Max width in pixels
418
+ - `state`: A `$state({ visible: false })` object, makes the tooltip persistent and controllable
419
+ - `onclose`: Called when the close button is used
420
+ - `showDelay`: Delay in ms before showing
421
+ - `zIndex`: Tooltip z-index
422
+ - `mobile`: Set to false to hide the tooltip on mobile
423
+
424
+ ## Sound effects
425
+
426
+ UI sound effects are available as `.ogg` files and can be played with any audio library:
427
+
428
+ ```js
429
+ import basicButtonSound from "openguessr-ui/sound-effects/basic_button.ogg";
430
+ ```
431
+
432
+ | Sound | Purpose |
433
+ | ----- | ------- |
434
+ | basic_button | Default sound for standard buttons and tabs |
435
+ | juicy_button | Selection-like choices (e.g. map selection) |
436
+ | start_button | Starting or joining a game |
437
+ | toggle_button | Anything that toggles, e.g. collapsibles |
438
+ | change_value | Changing a value, e.g. the ticker |
439
+ | item_select | Selecting an item, e.g. a pin, badge, or flag |
440
+ | item_locked | Trying to select a locked item |
441
+
442
+ The components don't play sounds themselves. Instead, play them via their callbacks:
443
+
444
+ - Collapsible `ontoggle`: `toggle_button`
445
+ - Ticker `onchange`: `change_value`
446
+ - Notification `onaccept`, `ondismiss`: `basic_button`
447
+
448
+ ## Scroll bars
449
+
450
+ Typically, scroll bars should be hidden via `scrollbar-width: none`. There can be exceptions, e.g. for text editors where a scroll bar brings utility.
451
+
452
+ ## Text formatting guidelines
453
+
454
+ - Body text and headings should be written as plain sentences (sentence case) as opposed to title case (for example, “Game overview” rather than “Game Overview”). The only exception that OpenGuessr makes is for content that needs a clear title or branding such as modes (e.g. Country Guessr), maps (e.g. Capital Cities), competitions, and tournaments.
455
+ - Using a colon ":" in or for UI labels is not recommended.
456
+ - Headings should not end in a period.
457
+
458
+ ## Flexibility
459
+
460
+ This design system is highly expandable. Instead of using fixed components for everything, most elements are built using the provided CSS variables.
461
+
462
+ Game UIs should feel handcrafted instead of generic, there are many scenarios where custom controls feel more intuitive than any preexisting component would. So, don't use generic buttons for a fancy map selection screen, or don't use a slider for a health bar – creativity is what makes games feel special.