@fishies/ui 0.8.3
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/LICENSE +26 -0
- package/README.md +60 -0
- package/dist/FishiesImg.d.ts +9 -0
- package/dist/FishiesImg.js +64 -0
- package/dist/Select.d.ts +22 -0
- package/dist/Select.js +95 -0
- package/dist/fx-image.d.ts +3 -0
- package/dist/fx-image.js +25 -0
- package/dist/icons.d.ts +28 -0
- package/dist/icons.generated.d.ts +5 -0
- package/dist/icons.generated.js +658 -0
- package/dist/icons.js +53 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/kit.css +267 -0
- package/dist/kit.d.ts +159 -0
- package/dist/kit.js +104 -0
- package/dist/loader.css +48 -0
- package/dist/loader.d.ts +17 -0
- package/dist/loader.js +27 -0
- package/dist/select-state.d.ts +34 -0
- package/dist/select-state.js +55 -0
- package/dist/select.css +87 -0
- package/dist/tokens.d.ts +17 -0
- package/dist/tokens.generated.css +97 -0
- package/dist/tokens.js +175 -0
- package/handbook/README.md +236 -0
- package/icons/add.png +0 -0
- package/icons/announce.png +0 -0
- package/icons/arrange.png +0 -0
- package/icons/bell.png +0 -0
- package/icons/block.png +0 -0
- package/icons/book.png +0 -0
- package/icons/bug.png +0 -0
- package/icons/camera.png +0 -0
- package/icons/cart-add.png +0 -0
- package/icons/cart.png +0 -0
- package/icons/chat.png +0 -0
- package/icons/check.png +0 -0
- package/icons/clean.png +0 -0
- package/icons/clock.png +0 -0
- package/icons/coin.png +0 -0
- package/icons/coral.png +0 -0
- package/icons/decorate.png +0 -0
- package/icons/download.png +0 -0
- package/icons/drop.png +0 -0
- package/icons/edit.png +0 -0
- package/icons/egg.png +0 -0
- package/icons/emote-food.png +0 -0
- package/icons/emote-heart.png +0 -0
- package/icons/emote-hello.png +0 -0
- package/icons/emote-music.png +0 -0
- package/icons/emote-question.png +0 -0
- package/icons/emote-sleep.png +0 -0
- package/icons/emote-sparkle.png +0 -0
- package/icons/emote-surprise.png +0 -0
- package/icons/exit.png +0 -0
- package/icons/explore.png +0 -0
- package/icons/external.png +0 -0
- package/icons/feed.png +0 -0
- package/icons/fish.png +0 -0
- package/icons/follow.png +0 -0
- package/icons/fullscreen.png +0 -0
- package/icons/gear.png +0 -0
- package/icons/gem.png +0 -0
- package/icons/gift.png +0 -0
- package/icons/globe.png +0 -0
- package/icons/growth.png +0 -0
- package/icons/heart.png +0 -0
- package/icons/help.png +0 -0
- package/icons/home.png +0 -0
- package/icons/idea.png +0 -0
- package/icons/inspect.png +0 -0
- package/icons/inventory.png +0 -0
- package/icons/keeper.png +0 -0
- package/icons/kelp.png +0 -0
- package/icons/keyboard.png +0 -0
- package/icons/level.png +0 -0
- package/icons/link.png +0 -0
- package/icons/lock.png +0 -0
- package/icons/mail.png +0 -0
- package/icons/map.png +0 -0
- package/icons/menu.png +0 -0
- package/icons/music.png +0 -0
- package/icons/mute.png +0 -0
- package/icons/net.png +0 -0
- package/icons/people.png +0 -0
- package/icons/pin.png +0 -0
- package/icons/quest.png +0 -0
- package/icons/quote.png +0 -0
- package/icons/remedy.png +0 -0
- package/icons/reply.png +0 -0
- package/icons/report.png +0 -0
- package/icons/search.png +0 -0
- package/icons/sell.png +0 -0
- package/icons/send.png +0 -0
- package/icons/share.png +0 -0
- package/icons/shell.png +0 -0
- package/icons/shop.png +0 -0
- package/icons/showcase.png +0 -0
- package/icons/smile.png +0 -0
- package/icons/solved.png +0 -0
- package/icons/sound.png +0 -0
- package/icons/staff.png +0 -0
- package/icons/star.png +0 -0
- package/icons/tank.png +0 -0
- package/icons/thermometer.png +0 -0
- package/icons/trash.png +0 -0
- package/icons/trophy.png +0 -0
- package/icons/updates.png +0 -0
- package/icons/upvote.png +0 -0
- package/icons/views.png +0 -0
- package/icons/warning.png +0 -0
- package/icons/wave.png +0 -0
- package/icons/whisper.png +0 -0
- package/package.json +66 -0
- package/src/FishiesImg.tsx +58 -0
- package/src/Select.tsx +119 -0
- package/src/css.d.ts +2 -0
- package/src/fx-image.ts +17 -0
- package/src/icons.generated.ts +658 -0
- package/src/icons.ts +74 -0
- package/src/index.ts +8 -0
- package/src/kit.css +267 -0
- package/src/kit.tsx +190 -0
- package/src/loader.css +48 -0
- package/src/loader.tsx +35 -0
- package/src/select-state.ts +59 -0
- package/src/select.css +87 -0
- package/src/tokens.generated.css +97 -0
- package/src/tokens.ts +196 -0
package/dist/loader.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import './loader.css';
|
|
4
|
+
export const SPINNER_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">' +
|
|
5
|
+
'<style>.a{transform-origin:24px 24px;animation:s 1.1s linear infinite}' +
|
|
6
|
+
'.b{opacity:0;animation:u 1.8s ease-in infinite}.c{animation-delay:.6s}.d{animation-delay:1.2s}' +
|
|
7
|
+
'@keyframes s{to{transform:rotate(360deg)}}' +
|
|
8
|
+
'@keyframes u{0%{opacity:0;transform:translateY(7px)}30%{opacity:.95}100%{opacity:0;transform:translateY(-9px)}}' +
|
|
9
|
+
'@media (prefers-reduced-motion:reduce){.a{animation-duration:3.6s}.b{animation:none;opacity:.8}}</style>' +
|
|
10
|
+
'<circle cx="24" cy="24" r="21" fill="#0b2a3c" fill-opacity=".74" stroke="#c4ecfa" stroke-opacity=".24"/>' +
|
|
11
|
+
'<circle cx="24" cy="24" r="16" fill="none" stroke="#c4ecfa" stroke-opacity=".13" stroke-width="3"/>' +
|
|
12
|
+
'<circle class="a" cx="24" cy="24" r="16" fill="none" stroke="#ffc46b" stroke-width="3" stroke-linecap="round" stroke-dasharray="25 76"/>' +
|
|
13
|
+
'<g fill="#dff6ff" fill-opacity=".16" stroke="#dff6ff" stroke-width="1.1">' +
|
|
14
|
+
'<circle class="b" cx="21.5" cy="26" r="2.7"/><circle class="b c" cx="27" cy="27.5" r="1.9"/><circle class="b d" cx="23.5" cy="30" r="1.3"/>' +
|
|
15
|
+
'</g></svg>';
|
|
16
|
+
export function svgDataUri(svg) {
|
|
17
|
+
return `url("data:image/svg+xml,${svg.replace(/"/g, "'").replace(/[<>#%{}]/g, c => encodeURIComponent(c))}")`;
|
|
18
|
+
}
|
|
19
|
+
export function Spinner({ size = 32, label = 'Loading', className, style }) {
|
|
20
|
+
return _jsx("span", { className: `fx-spinner${className ? ` ${className}` : ''}`, role: "status", "aria-label": label, style: { '--fx-size': `${size}px`, ...style }, dangerouslySetInnerHTML: { __html: SPINNER_SVG.replace('<svg ', '<svg aria-hidden="true" focusable="false" ') } });
|
|
21
|
+
}
|
|
22
|
+
/** A progress bar: a glass channel filling with gold, a bubble riding its leading edge; with no value the gold sweeps. */
|
|
23
|
+
export function Progress({ value, label = 'Loading', className, style }) {
|
|
24
|
+
const known = typeof value === 'number' && Number.isFinite(value);
|
|
25
|
+
const percent = known ? Math.round(Math.min(1, Math.max(0, value)) * 100) : undefined;
|
|
26
|
+
return _jsx("span", { className: `fx-progress${className ? ` ${className}` : ''}`, role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": 100, "aria-valuenow": percent, "data-indeterminate": known ? undefined : '', style: { '--fx-value': known ? percent / 100 : 0, ...style }, children: _jsx("i", {}) });
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type SelectOptionState = {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export type SelectState = {
|
|
7
|
+
open: boolean;
|
|
8
|
+
active: number;
|
|
9
|
+
};
|
|
10
|
+
export type SelectKeyResult = SelectState & {
|
|
11
|
+
choose?: string;
|
|
12
|
+
close?: 'focus' | 'blur';
|
|
13
|
+
handled: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function selectedIndex(options: readonly SelectOptionState[], value: string): number;
|
|
16
|
+
export declare function moveActive(options: readonly SelectOptionState[], from: number, step: 1 | -1): number;
|
|
17
|
+
export declare function typeahead(options: readonly SelectOptionState[], from: number, key: string): number;
|
|
18
|
+
export declare function selectKey(state: SelectState, key: string, options: readonly SelectOptionState[], value: string): SelectKeyResult;
|
|
19
|
+
export declare function placePopup(rect: {
|
|
20
|
+
top: number;
|
|
21
|
+
bottom: number;
|
|
22
|
+
left: number;
|
|
23
|
+
width: number;
|
|
24
|
+
}, viewport: {
|
|
25
|
+
width: number;
|
|
26
|
+
height: number;
|
|
27
|
+
}, wanted: number, gap?: number, margin?: number): {
|
|
28
|
+
up: boolean;
|
|
29
|
+
maxHeight: number;
|
|
30
|
+
width: number;
|
|
31
|
+
left: number;
|
|
32
|
+
top: number | undefined;
|
|
33
|
+
bottom: number | undefined;
|
|
34
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const enabled = (options) => options.map((option, index) => option.disabled ? -1 : index).filter(index => index >= 0);
|
|
2
|
+
export function selectedIndex(options, value) {
|
|
3
|
+
const index = options.findIndex(option => option.value === value);
|
|
4
|
+
return index >= 0 && !options[index].disabled ? index : enabled(options)[0] ?? -1;
|
|
5
|
+
}
|
|
6
|
+
export function moveActive(options, from, step) {
|
|
7
|
+
for (let index = from + step; index >= 0 && index < options.length; index += step)
|
|
8
|
+
if (!options[index].disabled)
|
|
9
|
+
return index;
|
|
10
|
+
return from;
|
|
11
|
+
}
|
|
12
|
+
export function typeahead(options, from, key) {
|
|
13
|
+
const needle = key.toLocaleLowerCase();
|
|
14
|
+
for (let offset = 1; offset <= options.length; offset++) {
|
|
15
|
+
const index = (from + offset + options.length) % options.length;
|
|
16
|
+
const option = options[index];
|
|
17
|
+
if (!option.disabled && option.label.trim().toLocaleLowerCase().startsWith(needle))
|
|
18
|
+
return index;
|
|
19
|
+
}
|
|
20
|
+
return -1;
|
|
21
|
+
}
|
|
22
|
+
export function selectKey(state, key, options, value) {
|
|
23
|
+
const list = enabled(options);
|
|
24
|
+
if (!state.open) {
|
|
25
|
+
if (key === 'Enter' || key === ' ' || key === 'ArrowDown' || key === 'ArrowUp')
|
|
26
|
+
return { open: true, active: selectedIndex(options, value), handled: true };
|
|
27
|
+
return { ...state, handled: false };
|
|
28
|
+
}
|
|
29
|
+
switch (key) {
|
|
30
|
+
case 'ArrowDown': return { open: true, active: moveActive(options, state.active, 1), handled: true };
|
|
31
|
+
case 'ArrowUp': return { open: true, active: moveActive(options, state.active, -1), handled: true };
|
|
32
|
+
case 'Home': return { open: true, active: list[0] ?? -1, handled: true };
|
|
33
|
+
case 'End': return { open: true, active: list[list.length - 1] ?? -1, handled: true };
|
|
34
|
+
case 'Enter':
|
|
35
|
+
case ' ': {
|
|
36
|
+
const option = options[state.active];
|
|
37
|
+
return { open: false, active: state.active, choose: option && !option.disabled ? option.value : undefined, close: 'focus', handled: true };
|
|
38
|
+
}
|
|
39
|
+
case 'Escape': return { open: false, active: state.active, close: 'focus', handled: true };
|
|
40
|
+
case 'Tab': return { open: false, active: state.active, close: 'blur', handled: false };
|
|
41
|
+
}
|
|
42
|
+
if (key.length === 1) {
|
|
43
|
+
const found = typeahead(options, state.active, key);
|
|
44
|
+
return { open: true, active: found >= 0 ? found : state.active, handled: true };
|
|
45
|
+
}
|
|
46
|
+
return { ...state, handled: false };
|
|
47
|
+
}
|
|
48
|
+
export function placePopup(rect, viewport, wanted, gap = 6, margin = 8) {
|
|
49
|
+
const below = viewport.height - rect.bottom - gap - margin, above = rect.top - gap - margin;
|
|
50
|
+
const up = below < Math.min(wanted, 180) && above > below;
|
|
51
|
+
const maxHeight = Math.max(0, Math.min(wanted, up ? above : below));
|
|
52
|
+
const width = Math.min(Math.max(rect.width, 160), viewport.width - margin * 2);
|
|
53
|
+
const left = Math.min(Math.max(margin, rect.left), viewport.width - margin - width);
|
|
54
|
+
return { up, maxHeight, width, left, top: up ? undefined : rect.bottom + gap, bottom: up ? viewport.height - rect.top + gap : undefined };
|
|
55
|
+
}
|
package/dist/select.css
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--fs-select-ink: var(--fx-ground-ink);
|
|
3
|
+
--fs-select-muted: var(--fx-muted);
|
|
4
|
+
--fs-select-line: #b7d3d342;
|
|
5
|
+
--fs-select-bg: #09232b8c;
|
|
6
|
+
--fs-select-panel: #09232bf5;
|
|
7
|
+
--fs-select-hover: #163741;
|
|
8
|
+
--fs-select-accent: var(--fx-gold);
|
|
9
|
+
--fs-select-radius: 8px;
|
|
10
|
+
--fs-select-shadow: 0 15px 35px #020d1688;
|
|
11
|
+
--fs-select-font: 800 13px/1.2;
|
|
12
|
+
}
|
|
13
|
+
.aquarium-game {
|
|
14
|
+
--fs-select-ink: var(--fx-ink);
|
|
15
|
+
--fs-select-muted: var(--fx-ink-3);
|
|
16
|
+
--fs-select-line: rgb(140 200 255 / 24%);
|
|
17
|
+
--fs-select-bg: rgb(255 255 255 / 5%);
|
|
18
|
+
--fs-select-panel: rgb(8 26 66 / 96%);
|
|
19
|
+
--fs-select-hover: rgb(140 200 255 / 14%);
|
|
20
|
+
--fs-select-accent: var(--fx-gold);
|
|
21
|
+
--fs-select-radius: 12px;
|
|
22
|
+
--fs-select-shadow: 0 16px 36px rgb(2 12 26 / 70%);
|
|
23
|
+
}
|
|
24
|
+
.fishies-admin {
|
|
25
|
+
--fs-select-ink: var(--admin-ink, #edf5f5);
|
|
26
|
+
--fs-select-muted: var(--admin-muted, #a5bac2);
|
|
27
|
+
--fs-select-line: #385966;
|
|
28
|
+
--fs-select-bg: #061c28;
|
|
29
|
+
--fs-select-panel: #0b2531f7;
|
|
30
|
+
--fs-select-hover: #17394a;
|
|
31
|
+
--fs-select-accent: var(--admin-gold, #ffc46b);
|
|
32
|
+
--fs-select-radius: 9px;
|
|
33
|
+
}
|
|
34
|
+
.fishies-env {
|
|
35
|
+
--fs-select-ink: var(--env-ink, #edf5f5);
|
|
36
|
+
--fs-select-muted: var(--env-muted, #a5bac2);
|
|
37
|
+
--fs-select-line: var(--env-line, #385966);
|
|
38
|
+
--fs-select-bg: #102c38;
|
|
39
|
+
--fs-select-panel: #0c2530f7;
|
|
40
|
+
--fs-select-hover: #17394a;
|
|
41
|
+
--fs-select-accent: var(--env-accent, #ffc46b);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.fs-select.fs-select {
|
|
45
|
+
display: inline-flex; align-items: center; justify-content: flex-start; gap: 8px;
|
|
46
|
+
min-width: 0; max-width: 100%; min-height: 40px; margin: 0; padding: 8px 12px;
|
|
47
|
+
border: 1px solid var(--fs-select-line); border-radius: var(--fs-select-radius);
|
|
48
|
+
background: var(--fs-select-bg); color: var(--fs-select-ink);
|
|
49
|
+
font: var(--fs-select-font) var(--font-ui), system-ui, sans-serif; letter-spacing: 0; text-align: left; text-transform: none;
|
|
50
|
+
box-shadow: none; text-shadow: none; cursor: pointer; transition: border-color .15s, background .15s;
|
|
51
|
+
}
|
|
52
|
+
.fs-select.fs-select:hover:not(:disabled), .fs-select.fs-select.is-open { border-color: color-mix(in srgb, var(--fs-select-accent) 55%, transparent); }
|
|
53
|
+
.fs-select.fs-select:disabled { opacity: .55; cursor: not-allowed; }
|
|
54
|
+
.fs-select.fs-select:focus-visible { outline: 2px solid var(--fs-select-accent); outline-offset: 2px; }
|
|
55
|
+
.fs-select .fs-select-value { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
56
|
+
.fs-select i { flex: none; margin-left: auto; color: var(--fs-select-muted); font-size: 14px; font-style: normal; line-height: 1; transition: transform .15s; }
|
|
57
|
+
.fs-select.is-open i { transform: rotate(180deg); }
|
|
58
|
+
.fs-select-icon { display: inline-grid; place-items: center; flex: none; width: 20px; }
|
|
59
|
+
.fs-select.fs-select.fs-select-compact { min-height: 32px; padding: 5px 10px; gap: 6px; }
|
|
60
|
+
|
|
61
|
+
.fs-select-popover {
|
|
62
|
+
width: max-content;
|
|
63
|
+
position: fixed; z-index: 10000; box-sizing: border-box; margin: 0; padding: 5px; list-style: none;
|
|
64
|
+
overflow-y: auto; overscroll-behavior: contain;
|
|
65
|
+
border: 1px solid var(--fs-select-line); border-radius: var(--fs-select-radius);
|
|
66
|
+
background: var(--fs-select-panel); color: var(--fs-select-ink); box-shadow: var(--fs-select-shadow);
|
|
67
|
+
font: var(--fs-select-font) var(--font-ui), system-ui, sans-serif; color-scheme: dark;
|
|
68
|
+
animation: fs-select-in .12s ease-out;
|
|
69
|
+
}
|
|
70
|
+
.fs-select-popover.is-up { animation-name: fs-select-in-up; }
|
|
71
|
+
.fs-select-popover li {
|
|
72
|
+
display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 8px;
|
|
73
|
+
min-height: 36px; padding: 7px 9px; border-radius: max(4px, calc(var(--fs-select-radius) - 3px));
|
|
74
|
+
color: var(--fs-select-ink); cursor: pointer; user-select: none;
|
|
75
|
+
}
|
|
76
|
+
.fs-select-popover li > span:not(.fs-select-icon) { grid-column: 2; overflow-wrap: anywhere; }
|
|
77
|
+
.fs-select-popover li.is-active, .fs-select-popover li[aria-selected=true] { background: var(--fs-select-hover); }
|
|
78
|
+
.fs-select-popover li[aria-disabled=true] { opacity: .45; cursor: not-allowed; }
|
|
79
|
+
.fs-select-popover b { grid-column: 3; color: var(--fs-select-accent); }
|
|
80
|
+
.fs-select-popover.fs-select-compact li { min-height: 32px; padding: 5px 8px; }
|
|
81
|
+
@media (pointer: coarse) {
|
|
82
|
+
.fs-select-popover li, .fs-select-popover.fs-select-compact li { min-height: 44px; }
|
|
83
|
+
}
|
|
84
|
+
@media (prefers-reduced-motion: reduce) { .fs-select-popover { animation: none; } }
|
|
85
|
+
@keyframes fs-select-in { from { opacity: 0; transform: translateY(-4px); } }
|
|
86
|
+
@keyframes fs-select-in-up { from { opacity: 0; transform: translateY(4px); } }
|
|
87
|
+
.fs-select-popover li.is-active { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fs-select-accent) 40%, transparent); }
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type Token = {
|
|
2
|
+
readonly name: `--${string}`;
|
|
3
|
+
readonly value?: string;
|
|
4
|
+
readonly use?: string;
|
|
5
|
+
};
|
|
6
|
+
export type TokenGroup = {
|
|
7
|
+
readonly id: 'brand' | 'water' | 'ink' | 'gold' | 'tone' | 'glass' | 'rim' | 'depth' | 'type' | 'radius' | 'space' | 'layer' | 'motion';
|
|
8
|
+
readonly title: string;
|
|
9
|
+
readonly use: string;
|
|
10
|
+
readonly swatch: 'colour' | 'surface' | 'shadow' | 'text' | 'radius' | 'space' | 'layer' | 'motion' | 'font';
|
|
11
|
+
readonly slot?: true;
|
|
12
|
+
readonly tokens: readonly Token[];
|
|
13
|
+
};
|
|
14
|
+
export declare const TOKEN_GROUPS: readonly TokenGroup[];
|
|
15
|
+
export declare const TOKENS: ReadonlyMap<string, Token>;
|
|
16
|
+
export declare const fx: (name: Token['name']) => string;
|
|
17
|
+
export declare function tokensCss(): string;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
|
|
3
|
+
--fx-ground: #071a24;
|
|
4
|
+
--fx-ground-ink: #edf5ed;
|
|
5
|
+
--fx-gold: #ffc46b;
|
|
6
|
+
--fx-sea: #83cfbd;
|
|
7
|
+
--fx-muted: #adc3c7;
|
|
8
|
+
|
|
9
|
+
--fx-abyss: #04101b;
|
|
10
|
+
--fx-deep: #08202f;
|
|
11
|
+
--fx-deep-2: #0d2c3f;
|
|
12
|
+
--fx-deep-3: #14435c;
|
|
13
|
+
--fx-navy: #173d63;
|
|
14
|
+
|
|
15
|
+
--fx-ink: #eef6ff;
|
|
16
|
+
--fx-ink-2: rgb(238 246 255 / 80%);
|
|
17
|
+
--fx-ink-3: rgb(238 246 255 / 62%);
|
|
18
|
+
--fx-ink-blue: #a9c9ea;
|
|
19
|
+
--fx-ink-on-gold: #12304a;
|
|
20
|
+
|
|
21
|
+
--fx-gold-light: #ffe2a6;
|
|
22
|
+
--fx-gold-face: #ffe4a8;
|
|
23
|
+
--fx-gold-deep: #e59a3e;
|
|
24
|
+
--fx-gold-foot: #c88a3d;
|
|
25
|
+
--fx-gold-warm: #ff9a6b;
|
|
26
|
+
--fx-amber: #f4c656;
|
|
27
|
+
|
|
28
|
+
--fx-tone-sky: #6cc8ff;
|
|
29
|
+
--fx-tone-coral: #ff8f87;
|
|
30
|
+
--fx-tone-kelp: #7fdc8f;
|
|
31
|
+
--fx-tone-violet: #b8a2ff;
|
|
32
|
+
--fx-tone-lagoon: #5cd8c6;
|
|
33
|
+
--fx-tone-mute: #8fa7b5;
|
|
34
|
+
--fx-good: #6fe3b0;
|
|
35
|
+
--fx-alarm: #ff8478;
|
|
36
|
+
--fx-pearl: #fff4d6;
|
|
37
|
+
--fx-cream: #f3dfb2;
|
|
38
|
+
--fx-plum: #36203f;
|
|
39
|
+
|
|
40
|
+
--fx-glass-top: rgb(22 58 120 / 70%);
|
|
41
|
+
--fx-glass-bottom: rgb(8 26 66 / 88%);
|
|
42
|
+
--fx-glass-quiet-top: rgb(22 58 120 / 62%);
|
|
43
|
+
--fx-glass-quiet-bottom: rgb(8 26 66 / 82%);
|
|
44
|
+
--fx-glass-sheen: rgb(255 255 255 / 16%);
|
|
45
|
+
--fx-glass: linear-gradient(180deg, var(--fx-glass-sheen), transparent 46%), linear-gradient(180deg, var(--fx-glass-top), var(--fx-glass-bottom));
|
|
46
|
+
--fx-glass-quiet: linear-gradient(180deg, var(--fx-glass-sheen), transparent 46%), linear-gradient(180deg, var(--fx-glass-quiet-top), var(--fx-glass-quiet-bottom));
|
|
47
|
+
--fx-glass-well: rgb(4 16 30 / 55%);
|
|
48
|
+
--fx-glass-blur: blur(14px) saturate(1.2);
|
|
49
|
+
|
|
50
|
+
--fx-rim: rgb(140 200 255 / 34%);
|
|
51
|
+
--fx-rim-bright: rgb(140 200 255 / 55%);
|
|
52
|
+
--fx-gold-rim: rgb(255 196 107 / 58%);
|
|
53
|
+
--fx-gold-rim-quiet: rgb(255 196 107 / 26%);
|
|
54
|
+
--fx-rim-light: inset 0 1px rgb(255 240 210 / 30%);
|
|
55
|
+
--fx-ground-rim: #9ebfbe26;
|
|
56
|
+
|
|
57
|
+
--fx-lift-1: 0 2px 10px rgb(2 10 18 / 34%);
|
|
58
|
+
--fx-lift-2: 0 10px 30px rgb(2 8 20 / 45%);
|
|
59
|
+
--fx-lift-3: 0 24px 60px rgb(2 8 20 / 60%);
|
|
60
|
+
--fx-foot: 0 4px 0 rgb(4 18 34 / 55%);
|
|
61
|
+
--fx-foot-deep: 0 6px 0 rgb(4 18 34 / 55%);
|
|
62
|
+
--fx-plump: inset 0 2px rgb(255 255 255 / 40%), inset 0 -6px 12px rgb(0 0 0 / 28%);
|
|
63
|
+
--fx-on-water: 0 1px 2px #020c1a, 0 4px 16px rgb(2 12 26 / 80%);
|
|
64
|
+
|
|
65
|
+
--fx-font-display: var(--font-display), "Baloo 2", ui-rounded, sans-serif;
|
|
66
|
+
--fx-font-ui: var(--font-ui), Nunito, ui-rounded, sans-serif;
|
|
67
|
+
--fx-t-xs: 12px;
|
|
68
|
+
--fx-t-sm: 13px;
|
|
69
|
+
--fx-t-md: 15px;
|
|
70
|
+
--fx-t-lg: 19px;
|
|
71
|
+
--fx-t-xl: 24px;
|
|
72
|
+
--fx-t-2xl: 31px;
|
|
73
|
+
--fx-t-3xl: 40px;
|
|
74
|
+
--fx-track-tight: -.015em;
|
|
75
|
+
--fx-track-label: .14em;
|
|
76
|
+
|
|
77
|
+
--fx-r-sm: 10px;
|
|
78
|
+
--fx-r-md: 14px;
|
|
79
|
+
--fx-r-lg: 20px;
|
|
80
|
+
--fx-r-xl: 28px;
|
|
81
|
+
--fx-r-full: 999px;
|
|
82
|
+
|
|
83
|
+
--fx-s-1: 4px;
|
|
84
|
+
--fx-s-2: 8px;
|
|
85
|
+
--fx-s-3: 12px;
|
|
86
|
+
--fx-s-4: 16px;
|
|
87
|
+
--fx-s-5: 24px;
|
|
88
|
+
--fx-s-6: 32px;
|
|
89
|
+
--fx-s-7: 48px;
|
|
90
|
+
|
|
91
|
+
--fx-z-raised: 10;
|
|
92
|
+
--fx-z-sticky: 100;
|
|
93
|
+
--fx-z-dock: 900;
|
|
94
|
+
--fx-z-modal: 1000;
|
|
95
|
+
--fx-z-toast: 1100;
|
|
96
|
+
--fx-z-tooltip: 1200;
|
|
97
|
+
}
|
package/dist/tokens.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
export const TOKEN_GROUPS = [
|
|
2
|
+
{
|
|
3
|
+
id: 'brand', title: 'Brand', swatch: 'colour',
|
|
4
|
+
use: 'The five colours every store page, capsule, banner, the site and the Reef Journal sit on.',
|
|
5
|
+
tokens: [
|
|
6
|
+
{ name: '--fx-ground', value: '#071a24', use: 'The night-water ground behind the site, the Reef Journal and every brand picture.' },
|
|
7
|
+
{ name: '--fx-ground-ink', value: '#edf5ed', use: 'Words on the ground: headings and links on the site.' },
|
|
8
|
+
{ name: '--fx-gold', value: '#ffc46b', use: 'The one warm accent: the thing to press, the coin, the playhead, a focus ring.' },
|
|
9
|
+
{ name: '--fx-sea', value: '#83cfbd', use: 'Sea-green for links and quiet highlights on the ground.' },
|
|
10
|
+
{ name: '--fx-muted', value: '#adc3c7', use: 'Body copy on the ground, a step quieter than the headings.' },
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
id: 'water', title: 'Water', swatch: 'colour',
|
|
15
|
+
use: 'The deep blues under the glass; each one is the water a few metres further down.',
|
|
16
|
+
tokens: [
|
|
17
|
+
{ name: '--fx-abyss', value: '#04101b', use: 'The deepest water: behind full-screen moments and the video frame.' },
|
|
18
|
+
{ name: '--fx-deep', value: '#08202f', use: 'A sunken well inside a panel: inputs, tracks, empty slots.' },
|
|
19
|
+
{ name: '--fx-deep-2', value: '#0d2c3f', use: 'A raised well: a hovered row, a chosen tab’s ground.' },
|
|
20
|
+
{ name: '--fx-deep-3', value: '#14435c', use: 'The lightest deep: dividers that need to show on glass.' },
|
|
21
|
+
{ name: '--fx-navy', value: '#173d63', use: 'The body of a round toy button (window controls, the player’s buttons).' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: 'ink', title: 'Ink', swatch: 'colour',
|
|
26
|
+
use: 'Words on glass, in three weights; the quietest still reads at 6.7:1 on a panel.',
|
|
27
|
+
tokens: [
|
|
28
|
+
{ name: '--fx-ink', value: '#eef6ff', use: 'Everything a player reads on glass: titles, values, labels.' },
|
|
29
|
+
{ name: '--fx-ink-2', value: 'rgb(238 246 255 / 80%)', use: 'Sentences under a title.' },
|
|
30
|
+
{ name: '--fx-ink-3', value: 'rgb(238 246 255 / 62%)', use: 'Captions, units and hints; never anything a player must act on.' },
|
|
31
|
+
{ name: '--fx-ink-blue', value: '#a9c9ea', use: 'Labels that name a value beside it (Level, Coins, Fish).' },
|
|
32
|
+
{ name: '--fx-ink-on-gold', value: '#12304a', use: 'Words on a gold button or a gold chip.' },
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'gold', title: 'Gold', swatch: 'colour',
|
|
37
|
+
use: 'The toy gold of the HUD: a face, a rim and a darker foot that makes a button sit up.',
|
|
38
|
+
tokens: [
|
|
39
|
+
{ name: '--fx-gold-light', value: '#ffe2a6', use: 'Gold words on glass: window titles, the clock, numbers that matter.' },
|
|
40
|
+
{ name: '--fx-gold-face', value: '#ffe4a8', use: 'The lit top of a gold button.' },
|
|
41
|
+
{ name: '--fx-gold-deep', value: '#e59a3e', use: 'The far end of a gold fill: a progress bar, the played part of a video.' },
|
|
42
|
+
{ name: '--fx-gold-foot', value: '#c88a3d', use: 'The solid edge under a gold button, its toy thickness.' },
|
|
43
|
+
{ name: '--fx-gold-warm', value: '#ff9a6b', use: 'Gold leaning to coral, where a fill runs warm (sunrise, a streak).' },
|
|
44
|
+
{ name: '--fx-amber', value: '#f4c656', use: 'The landing’s amber: the wordmark’s gleam and the reel’s titles.' },
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: 'tone', title: 'Tones', swatch: 'colour',
|
|
49
|
+
use: 'Six hues that tell kinds apart (an app tile, a chip, a chart line) and two that mean good and bad.',
|
|
50
|
+
tokens: [
|
|
51
|
+
{ name: '--fx-tone-sky', value: '#6cc8ff', use: 'Sky blue: water, visits, anything about other keepers.' },
|
|
52
|
+
{ name: '--fx-tone-coral', value: '#ff8f87', use: 'Coral: reports, a count that wants you, the close button.' },
|
|
53
|
+
{ name: '--fx-tone-kelp', value: '#7fdc8f', use: 'Kelp green: live, online, growing.' },
|
|
54
|
+
{ name: '--fx-tone-violet', value: '#b8a2ff', use: 'Violet: rare things and night.' },
|
|
55
|
+
{ name: '--fx-tone-lagoon', value: '#5cd8c6', use: 'Lagoon: the landing’s shallow water.' },
|
|
56
|
+
{ name: '--fx-tone-mute', value: '#8fa7b5', use: 'Grey-blue: off, empty, not yet.' },
|
|
57
|
+
{ name: '--fx-good', value: '#6fe3b0', use: 'Done, fed, clean, saved.' },
|
|
58
|
+
{ name: '--fx-alarm', value: '#ff8478', use: 'Hungry, dirty, failed: what needs a hand now.' },
|
|
59
|
+
{ name: '--fx-pearl', value: '#fff4d6', use: 'Pearl: the warm white of a badge rim and the landing’s cream.' },
|
|
60
|
+
{ name: '--fx-cream', value: '#f3dfb2', use: 'Cream: the shaded side of a pearl knob, warm sand.' },
|
|
61
|
+
{ name: '--fx-plum', value: '#36203f', use: 'Plum: the landing’s deep warm shadow under cream.' },
|
|
62
|
+
],
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 'glass', title: 'Glass', swatch: 'surface',
|
|
66
|
+
use: 'The one material everything floats on: tinted water glass with a sheen across its top.',
|
|
67
|
+
tokens: [
|
|
68
|
+
{ name: '--fx-glass-top', value: 'rgb(22 58 120 / 70%)', use: 'The upper tint of glass.' },
|
|
69
|
+
{ name: '--fx-glass-bottom', value: 'rgb(8 26 66 / 88%)', use: 'The lower tint of glass, darker so words sit on it.' },
|
|
70
|
+
{ name: '--fx-glass-quiet-top', value: 'rgb(22 58 120 / 62%)', use: 'Glass over the live tank, a little thinner so the fish show through.' },
|
|
71
|
+
{ name: '--fx-glass-quiet-bottom', value: 'rgb(8 26 66 / 82%)', use: 'The lower tint of quiet glass.' },
|
|
72
|
+
{ name: '--fx-glass-sheen', value: 'rgb(255 255 255 / 16%)', use: 'The light across the top half of a pane.' },
|
|
73
|
+
{ name: '--fx-glass', value: 'linear-gradient(180deg, var(--fx-glass-sheen), transparent 46%), linear-gradient(180deg, var(--fx-glass-top), var(--fx-glass-bottom))', use: 'A pane: windows, panels, menus, the dock.' },
|
|
74
|
+
{ name: '--fx-glass-quiet', value: 'linear-gradient(180deg, var(--fx-glass-sheen), transparent 46%), linear-gradient(180deg, var(--fx-glass-quiet-top), var(--fx-glass-quiet-bottom))', use: 'A pane over the tank: HUD capsules, the fish card.' },
|
|
75
|
+
{ name: '--fx-glass-well', value: 'rgb(4 16 30 / 55%)', use: 'The darker water inside a window, under its title bar.' },
|
|
76
|
+
{ name: '--fx-glass-blur', value: 'blur(14px) saturate(1.2)', use: 'What glass does to the reef behind it (backdrop-filter).' },
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'rim', title: 'Rims', swatch: 'surface',
|
|
81
|
+
use: 'A hairline of light round every pane; gold marks the panes that are yours to use.',
|
|
82
|
+
tokens: [
|
|
83
|
+
{ name: '--fx-rim', value: 'rgb(140 200 255 / 34%)', use: 'The blue hairline round glass.' },
|
|
84
|
+
{ name: '--fx-rim-bright', value: 'rgb(140 200 255 / 55%)', use: 'The hairline on hover.' },
|
|
85
|
+
{ name: '--fx-gold-rim', value: 'rgb(255 196 107 / 58%)', use: 'The gold hairline round windows, the dock, tiles and the chosen tab.' },
|
|
86
|
+
{ name: '--fx-gold-rim-quiet', value: 'rgb(255 196 107 / 26%)', use: 'Gold dividers: under a title bar, between dock groups.' },
|
|
87
|
+
{ name: '--fx-rim-light', value: 'inset 0 1px rgb(255 240 210 / 30%)', use: 'The lit inner top edge of a gold-rimmed pane.' },
|
|
88
|
+
{ name: '--fx-ground-rim', value: '#9ebfbe26', use: 'Hairlines on the ground: card edges and rules between sections of the site.' },
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: 'depth', title: 'Depth', swatch: 'shadow',
|
|
93
|
+
use: 'Soft lifts for panes over water, and the solid foot that makes a toy control look pressable.',
|
|
94
|
+
tokens: [
|
|
95
|
+
{ name: '--fx-lift-1', value: '0 2px 10px rgb(2 10 18 / 34%)', use: 'A chip or a small button just off the water.' },
|
|
96
|
+
{ name: '--fx-lift-2', value: '0 10px 30px rgb(2 8 20 / 45%)', use: 'A panel, the dock, a menu.' },
|
|
97
|
+
{ name: '--fx-lift-3', value: '0 24px 60px rgb(2 8 20 / 60%)', use: 'A window or a moment that takes the screen.' },
|
|
98
|
+
{ name: '--fx-foot', value: '0 4px 0 rgb(4 18 34 / 55%)', use: 'The toy foot under a glass button or tile; pressing sinks into it.' },
|
|
99
|
+
{ name: '--fx-foot-deep', value: '0 6px 0 rgb(4 18 34 / 55%)', use: 'The foot under a desktop tile, which stands taller.' },
|
|
100
|
+
{ name: '--fx-plump', value: 'inset 0 2px rgb(255 255 255 / 40%), inset 0 -6px 12px rgb(0 0 0 / 28%)', use: 'The lit top and shaded belly that make a tile round.' },
|
|
101
|
+
{ name: '--fx-on-water', value: '0 1px 2px #020c1a, 0 4px 16px rgb(2 12 26 / 80%)', use: 'The shadow under words that sit straight on the water.' },
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'type', title: 'Type', swatch: 'text',
|
|
106
|
+
use: 'Baloo 2 for the game’s voice (numbers, labels, buttons, titles), Nunito for sentences; nothing under 12px.',
|
|
107
|
+
tokens: [
|
|
108
|
+
{ name: '--fx-font-display', value: 'var(--font-display), "Baloo 2", ui-rounded, sans-serif', use: 'Titles, numbers, buttons, labels.' },
|
|
109
|
+
{ name: '--fx-font-ui', value: 'var(--font-ui), Nunito, ui-rounded, sans-serif', use: 'Sentences and anything read as text.' },
|
|
110
|
+
{ name: '--fx-t-xs', value: '12px', use: 'The floor: units, captions, a badge’s count.' },
|
|
111
|
+
{ name: '--fx-t-sm', value: '13px', use: 'Chips, list details, helper lines.' },
|
|
112
|
+
{ name: '--fx-t-md', value: '15px', use: 'Buttons, list titles, body copy in the game.' },
|
|
113
|
+
{ name: '--fx-t-lg', value: '19px', use: 'A panel’s title.' },
|
|
114
|
+
{ name: '--fx-t-xl', value: '24px', use: 'A stat’s value, a window’s hero number.' },
|
|
115
|
+
{ name: '--fx-t-2xl', value: '31px', use: 'A page heading.' },
|
|
116
|
+
{ name: '--fx-t-3xl', value: '40px', use: 'A moment’s headline (level up, a new fish).' },
|
|
117
|
+
{ name: '--fx-track-tight', value: '-.015em', use: 'Display type at size.' },
|
|
118
|
+
{ name: '--fx-track-label', value: '.14em', use: 'Upper-case eyebrows above a group.' },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'radius', title: 'Radii', swatch: 'radius',
|
|
123
|
+
use: 'Round, never sharp: the smaller the thing, the rounder it reads.',
|
|
124
|
+
tokens: [
|
|
125
|
+
{ name: '--fx-r-sm', value: '10px', use: 'Rows, inputs, keys.' },
|
|
126
|
+
{ name: '--fx-r-md', value: '14px', use: 'Cards, menus, tooltips.' },
|
|
127
|
+
{ name: '--fx-r-lg', value: '20px', use: 'Panels and windows.' },
|
|
128
|
+
{ name: '--fx-r-xl', value: '28px', use: 'Desktop tiles and the dock.' },
|
|
129
|
+
{ name: '--fx-r-full', value: '999px', use: 'Buttons, chips, tabs, badges, tracks.' },
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
id: 'space', title: 'Spacing', swatch: 'space',
|
|
134
|
+
use: 'A 4px grid: every gap, padding and margin is one of these.',
|
|
135
|
+
tokens: [
|
|
136
|
+
{ name: '--fx-s-1', value: '4px', use: 'Inside a chip, between a value and its unit.' },
|
|
137
|
+
{ name: '--fx-s-2', value: '8px', use: 'Between an icon and its label, between chips.' },
|
|
138
|
+
{ name: '--fx-s-3', value: '12px', use: 'Inside a button, between rows.' },
|
|
139
|
+
{ name: '--fx-s-4', value: '16px', use: 'A card’s inset.' },
|
|
140
|
+
{ name: '--fx-s-5', value: '24px', use: 'A panel’s inset, between groups.' },
|
|
141
|
+
{ name: '--fx-s-6', value: '32px', use: 'Between sections of a page.' },
|
|
142
|
+
{ name: '--fx-s-7', value: '48px', use: 'Around a page’s hero.' },
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: 'layer', title: 'Layers', swatch: 'layer',
|
|
147
|
+
use: 'Named heights, so a toast never slides under a window and a tooltip never under a toast.',
|
|
148
|
+
tokens: [
|
|
149
|
+
{ name: '--fx-z-raised', value: '10', use: 'A card lifted above its siblings (a dragged tile).' },
|
|
150
|
+
{ name: '--fx-z-sticky', value: '100', use: 'Headers and rails that stay as the page scrolls.' },
|
|
151
|
+
{ name: '--fx-z-dock', value: '900', use: 'The menu bar and the dock.' },
|
|
152
|
+
{ name: '--fx-z-modal', value: '1000', use: 'A window or sheet that holds the screen, with its backdrop.' },
|
|
153
|
+
{ name: '--fx-z-toast', value: '1100', use: 'Toasts, above any window.' },
|
|
154
|
+
{ name: '--fx-z-tooltip', value: '1200', use: 'Tooltips and open menus, above everything.' },
|
|
155
|
+
],
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'motion', title: 'Motion', swatch: 'motion', slot: true,
|
|
159
|
+
use: 'Named slots the motion system fills (apps/web/app/motion/motion.css, where each one is described): arrivals overshoot a little and settle, presses squash, exits get out of the way.',
|
|
160
|
+
tokens: ['--m-quick', '--m-base', '--m-move', '--m-show', '--m-ease-out', '--m-ease-spring', '--m-ease-calm', '--m-ease-in', '--m-stagger'].map(name => ({ name: name })),
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
export const TOKENS = new Map(TOKEN_GROUPS.flatMap(group => group.tokens.map(token => [token.name, token])));
|
|
164
|
+
export const fx = (name) => `var(${name})`;
|
|
165
|
+
export function tokensCss() {
|
|
166
|
+
const lines = [];
|
|
167
|
+
for (const group of TOKEN_GROUPS) {
|
|
168
|
+
if (group.slot)
|
|
169
|
+
continue;
|
|
170
|
+
lines.push(` /* ${group.title} */`);
|
|
171
|
+
for (const token of group.tokens)
|
|
172
|
+
lines.push(` ${token.name}: ${token.value};`);
|
|
173
|
+
}
|
|
174
|
+
return `:root {\n${lines.join('\n')}\n}\n`;
|
|
175
|
+
}
|