@johnmorrisdotca/kazoku-ui 0.15.2 → 0.17.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/CHANGELOG.md +18 -0
- package/README.md +7 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/replay-bar/define.d.ts +1 -0
- package/dist/replay-bar/define.js +9 -0
- package/dist/replay-bar/element.d.ts +34 -0
- package/dist/replay-bar/element.js +219 -0
- package/dist/replay-bar/index.d.ts +4 -0
- package/dist/replay-bar/index.js +5 -0
- package/dist/replay-bar/state.d.ts +19 -0
- package/dist/replay-bar/state.js +27 -0
- package/dist/replay-bar/styles.d.ts +1 -0
- package/dist/replay-bar/styles.js +8 -0
- package/dist/replay-bar/words.d.ts +17 -0
- package/dist/replay-bar/words.js +7 -0
- package/dist/view-controls/address.d.ts +20 -0
- package/dist/view-controls/address.js +30 -4
- package/dist/view-controls/controller.d.ts +13 -2
- package/dist/view-controls/controller.js +72 -9
- package/dist/view-controls/element.d.ts +3 -2
- package/dist/view-controls/element.js +216 -27
- package/dist/view-controls/index.d.ts +2 -2
- package/dist/view-controls/index.js +2 -2
- package/dist/view-controls/levels.d.ts +6 -0
- package/dist/view-controls/levels.js +2 -0
- package/dist/view-controls/math.d.ts +50 -0
- package/dist/view-controls/math.js +73 -2
- package/dist/view-controls/sight.js +23 -0
- package/dist/view-controls/styles.d.ts +1 -1
- package/dist/view-controls/styles.js +22 -10
- package/dist/view-controls/words.d.ts +9 -0
- package/dist/view-controls/words.js +12 -0
- package/package.json +18 -2
|
@@ -10,12 +10,16 @@ export interface Size {
|
|
|
10
10
|
export interface Camera {
|
|
11
11
|
yaw: number;
|
|
12
12
|
tilt: number;
|
|
13
|
+
/** Degrees about the view's forward axis. Left out, it is 0. */
|
|
14
|
+
roll?: number;
|
|
13
15
|
zoom: number;
|
|
14
16
|
panX: number;
|
|
15
17
|
panY: number;
|
|
16
18
|
}
|
|
17
19
|
export interface Limits {
|
|
18
20
|
tiltMax: number;
|
|
21
|
+
/** How far a hand may roll, in degrees. 180 (the default) is a full turn. */
|
|
22
|
+
rollMax: number;
|
|
19
23
|
zoomMin: number;
|
|
20
24
|
zoomMax: number;
|
|
21
25
|
}
|
|
@@ -64,3 +68,49 @@ export declare function between(from: Camera, to: Camera, fraction: number): Cam
|
|
|
64
68
|
export declare const wheelZoomFactor: (deltaY: number, deltaMode: number, kind?: "pinch" | "wheel") => number;
|
|
65
69
|
/** Wheel steps closer together than this, in milliseconds, belong to one turn of the wheel or one swipe (with its glide). */
|
|
66
70
|
export declare const WHEEL_GESTURE_MS = 300;
|
|
71
|
+
/** A rotation. `w` is the real part. The identity is `{ x: 0, y: 0, z: 0, w: 1 }`. */
|
|
72
|
+
export interface Quat {
|
|
73
|
+
x: number;
|
|
74
|
+
y: number;
|
|
75
|
+
z: number;
|
|
76
|
+
w: number;
|
|
77
|
+
}
|
|
78
|
+
export interface Vec3 {
|
|
79
|
+
x: number;
|
|
80
|
+
y: number;
|
|
81
|
+
z: number;
|
|
82
|
+
}
|
|
83
|
+
export declare const quatIdentity: () => Quat;
|
|
84
|
+
/** Which way a signed rate points: -1 or 1. Zero and anything that is not a number count as 1. */
|
|
85
|
+
export declare const rateSign: (value: number) => number;
|
|
86
|
+
export declare const normalizeQuat: (q: Quat) => Quat;
|
|
87
|
+
/**
|
|
88
|
+
* Yaw about Y, then tilt about X, then roll about Z, in degrees. Past a right angle the numbers fold, so a tumble that rolls over keeps its quaternion and only shows these for the sliders.
|
|
89
|
+
*/
|
|
90
|
+
export declare function quatFromEuler(yaw: number, tilt: number, roll: number): Quat;
|
|
91
|
+
/** The yaw, tilt and roll a quaternion shows on the sliders. Tilt stays within a right angle; the quaternion itself does not flip. */
|
|
92
|
+
export declare function eulerFromQuat(q: Quat): {
|
|
93
|
+
yaw: number;
|
|
94
|
+
tilt: number;
|
|
95
|
+
roll: number;
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Turn `q` by `yaw`, `tilt` and `roll` degrees about its own axes (Y, X, then Z). Small steps stay continuous through the poles: the up vector never jumps.
|
|
99
|
+
*/
|
|
100
|
+
export declare function spinLocal(q: Quat, yaw: number, tilt: number, roll: number): Quat;
|
|
101
|
+
/** The view's up axis after `q`. */
|
|
102
|
+
export declare function upVector(q: Quat): Vec3;
|
|
103
|
+
/**
|
|
104
|
+
* Tilt swinging inside `amplitude` degrees. `phase` is radians of the swing. At the ends it comes back, so a limited tilt never rolls over.
|
|
105
|
+
*/
|
|
106
|
+
export declare const pendulumTilt: (phase: number, amplitude: number) => number;
|
|
107
|
+
export interface TumbleSigns {
|
|
108
|
+
yaw: number;
|
|
109
|
+
tilt: number;
|
|
110
|
+
roll: number;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Degrees a second for a tumble. The three rates differ, and each drifts on its own incommensurate wave, so the motion does not repeat.
|
|
114
|
+
* `speed` is the size of the angle's rate. A sign below 0 reverses that axis.
|
|
115
|
+
*/
|
|
116
|
+
export declare function tumbleRates(seconds: number, speed: number, signs: TumbleSigns): TumbleSigns;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// The view's arithmetic: pure functions, no DOM, no clock. The controller and the tests both use them.
|
|
2
|
-
export const DEFAULT_LIMITS = { tiltMax: 30, zoomMin: 1, zoomMax: 5 };
|
|
2
|
+
export const DEFAULT_LIMITS = { tiltMax: 30, rollMax: 180, zoomMin: 1, zoomMax: 5 };
|
|
3
3
|
/** Degrees of turn across the view's whole width (a half turn), and of tilt across its whole height. */
|
|
4
4
|
export const TURN_ACROSS = 180;
|
|
5
5
|
export const TILT_ACROSS = 60;
|
|
@@ -58,7 +58,7 @@ export function dragTurn(start, dx, dy, size, tips, limits = DEFAULT_LIMITS, ste
|
|
|
58
58
|
export function between(from, to, fraction) {
|
|
59
59
|
const t = 1 - (1 - clamp(fraction, 0, 1)) ** 3;
|
|
60
60
|
const mix = (a, b) => a + (b - a) * t;
|
|
61
|
-
return { yaw: mix(from.yaw, to.yaw), tilt: mix(from.tilt, to.tilt), zoom: mix(from.zoom, to.zoom), panX: mix(from.panX, to.panX), panY: mix(from.panY, to.panY) };
|
|
61
|
+
return { yaw: mix(from.yaw, to.yaw), tilt: mix(from.tilt, to.tilt), roll: mix(from.roll ?? 0, to.roll ?? 0), zoom: mix(from.zoom, to.zoom), panX: mix(from.panX, to.panX), panY: mix(from.panY, to.panY) };
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
64
|
* The factor a wheel turn zooms by: up (a negative `deltaY`) zooms in. `deltaMode` 1 is lines and 2 is pages. `kind` `"pinch"` (the default) is a
|
|
@@ -71,3 +71,74 @@ export const wheelZoomFactor = (deltaY, deltaMode, kind = "pinch") => {
|
|
|
71
71
|
};
|
|
72
72
|
/** Wheel steps closer together than this, in milliseconds, belong to one turn of the wheel or one swipe (with its glide). */
|
|
73
73
|
export const WHEEL_GESTURE_MS = 300;
|
|
74
|
+
const DEG = Math.PI / 180;
|
|
75
|
+
export const quatIdentity = () => ({ x: 0, y: 0, z: 0, w: 1 });
|
|
76
|
+
/** Which way a signed rate points: -1 or 1. Zero and anything that is not a number count as 1. */
|
|
77
|
+
export const rateSign = (value) => (value < 0 ? -1 : 1);
|
|
78
|
+
const finite = (value) => (Number.isFinite(value) ? value : 0);
|
|
79
|
+
export const normalizeQuat = (q) => {
|
|
80
|
+
const n = Math.hypot(finite(q.x), finite(q.y), finite(q.z), finite(q.w)) || 1;
|
|
81
|
+
return { x: q.x / n, y: q.y / n, z: q.z / n, w: q.w / n };
|
|
82
|
+
};
|
|
83
|
+
const axisQuat = (x, y, z, radians) => {
|
|
84
|
+
const half = finite(radians) / 2;
|
|
85
|
+
const s = Math.sin(half);
|
|
86
|
+
return { x: x * s, y: y * s, z: z * s, w: Math.cos(half) };
|
|
87
|
+
};
|
|
88
|
+
const mulQuat = (a, b) => ({
|
|
89
|
+
x: a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
|
|
90
|
+
y: a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
|
|
91
|
+
z: a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
|
|
92
|
+
w: a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* Yaw about Y, then tilt about X, then roll about Z, in degrees. Past a right angle the numbers fold, so a tumble that rolls over keeps its quaternion and only shows these for the sliders.
|
|
96
|
+
*/
|
|
97
|
+
export function quatFromEuler(yaw, tilt, roll) {
|
|
98
|
+
const qy = axisQuat(0, 1, 0, finite(yaw) * DEG);
|
|
99
|
+
const qx = axisQuat(1, 0, 0, finite(tilt) * DEG);
|
|
100
|
+
const qz = axisQuat(0, 0, 1, finite(roll) * DEG);
|
|
101
|
+
return normalizeQuat(mulQuat(mulQuat(qy, qx), qz));
|
|
102
|
+
}
|
|
103
|
+
/** The yaw, tilt and roll a quaternion shows on the sliders. Tilt stays within a right angle; the quaternion itself does not flip. */
|
|
104
|
+
export function eulerFromQuat(q) {
|
|
105
|
+
const { x, y, z, w } = normalizeQuat(q);
|
|
106
|
+
const tilt = Math.asin(clamp(2 * (w * x - y * z), -1, 1)) / DEG;
|
|
107
|
+
const yaw = Math.atan2(2 * (w * y + z * x), 1 - 2 * (x * x + y * y)) / DEG;
|
|
108
|
+
const roll = Math.atan2(2 * (w * z + x * y), 1 - 2 * (x * x + z * z)) / DEG;
|
|
109
|
+
return { yaw: wrapYaw(yaw), tilt, roll: wrapYaw(roll) };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Turn `q` by `yaw`, `tilt` and `roll` degrees about its own axes (Y, X, then Z). Small steps stay continuous through the poles: the up vector never jumps.
|
|
113
|
+
*/
|
|
114
|
+
export function spinLocal(q, yaw, tilt, roll) {
|
|
115
|
+
const turn = mulQuat(mulQuat(axisQuat(0, 1, 0, finite(yaw) * DEG), axisQuat(1, 0, 0, finite(tilt) * DEG)), axisQuat(0, 0, 1, finite(roll) * DEG));
|
|
116
|
+
return normalizeQuat(mulQuat(normalizeQuat(q), turn));
|
|
117
|
+
}
|
|
118
|
+
/** The view's up axis after `q`. */
|
|
119
|
+
export function upVector(q) {
|
|
120
|
+
const { x, y, z, w } = normalizeQuat(q);
|
|
121
|
+
return {
|
|
122
|
+
x: 2 * (x * y - w * z),
|
|
123
|
+
y: 1 - 2 * (x * x + z * z),
|
|
124
|
+
z: 2 * (y * z + w * x),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Tilt swinging inside `amplitude` degrees. `phase` is radians of the swing. At the ends it comes back, so a limited tilt never rolls over.
|
|
129
|
+
*/
|
|
130
|
+
export const pendulumTilt = (phase, amplitude) => finite(amplitude) * Math.sin(finite(phase));
|
|
131
|
+
/**
|
|
132
|
+
* Degrees a second for a tumble. The three rates differ, and each drifts on its own incommensurate wave, so the motion does not repeat.
|
|
133
|
+
* `speed` is the size of the angle's rate. A sign below 0 reverses that axis.
|
|
134
|
+
*/
|
|
135
|
+
export function tumbleRates(seconds, speed, signs) {
|
|
136
|
+
const t = finite(seconds);
|
|
137
|
+
const base = finite(speed);
|
|
138
|
+
const wave = (hz, phase) => 1 + 0.08 * Math.sin(t * hz + phase);
|
|
139
|
+
return {
|
|
140
|
+
yaw: base * rateSign(signs.yaw) * wave(0.17 * Math.PI, 0.4),
|
|
141
|
+
tilt: base * rateSign(signs.tilt) * 0.61 * wave(0.13 * Math.E, 1.7),
|
|
142
|
+
roll: base * rateSign(signs.roll) * 0.43 * wave(0.11 * Math.SQRT2, 2.5),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
@@ -24,10 +24,33 @@ export function watchSight(target, tell) {
|
|
|
24
24
|
});
|
|
25
25
|
observer?.observe(target);
|
|
26
26
|
doc?.addEventListener?.("visibilitychange", check);
|
|
27
|
+
// A scroll is told at once, in the scroll itself, so the queued frame can be cancelled before it runs. The observer agrees on the next rendering step, which can be a frame later, and that one frame would be a turn nobody can see.
|
|
28
|
+
const onScroll = () => {
|
|
29
|
+
const view = doc?.defaultView;
|
|
30
|
+
if (view === undefined || view === null)
|
|
31
|
+
return;
|
|
32
|
+
const r = target.getBoundingClientRect();
|
|
33
|
+
onScreen = r.width > 0 && r.height > 0 && r.bottom > 0 && r.right > 0 && r.top < view.innerHeight && r.left < view.innerWidth;
|
|
34
|
+
check();
|
|
35
|
+
};
|
|
36
|
+
doc?.addEventListener?.("scroll", onScroll, true);
|
|
37
|
+
doc?.defaultView?.addEventListener?.("scroll", onScroll);
|
|
38
|
+
// Inserting above the view moves it off screen without a scroll event (the page was already at the top). The observer would only say so on the next frame, after the queued turn had run.
|
|
39
|
+
const body = doc?.body;
|
|
40
|
+
const mutations = typeof MutationObserver === "function" && body !== undefined && body !== null ? new MutationObserver(onScroll) : undefined;
|
|
41
|
+
if (mutations !== undefined && body !== undefined && body !== null)
|
|
42
|
+
mutations.observe(body, { childList: true });
|
|
27
43
|
check();
|
|
44
|
+
// The first answer from the observer arrives in a rendering step. With the turn already held, nothing else asks for a frame, and a browser that skips rendering while the page is idle never delivers it. A task that reads the rectangle starts a turn that is already on screen, and asks for no frame when it is not.
|
|
45
|
+
const soon = doc?.defaultView?.setTimeout(onScroll, 0);
|
|
28
46
|
return () => {
|
|
47
|
+
if (soon !== undefined)
|
|
48
|
+
doc?.defaultView?.clearTimeout(soon);
|
|
29
49
|
observer?.disconnect();
|
|
30
50
|
doc?.removeEventListener?.("visibilitychange", check);
|
|
51
|
+
doc?.removeEventListener?.("scroll", onScroll, true);
|
|
52
|
+
doc?.defaultView?.removeEventListener?.("scroll", onScroll);
|
|
53
|
+
mutations?.disconnect();
|
|
31
54
|
};
|
|
32
55
|
}
|
|
33
56
|
/** Stops the controller's frame loop while `target` cannot be seen, and lets it go on when it can. Returns a function that stops watching and takes its hold off. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const STYLES = "\n:host {\n --_ink: var(--kazoku-ink, #1b1b1f);\n --_surface: var(--kazoku-surface, #ffffff);\n --_line: var(--kazoku-line, #c9ccd3);\n --_accent: var(--kazoku-accent, #2457d6);\n display: block;\n box-sizing: border-box;\n color: var(--_ink);\n font: 600 0.9rem/1.2 system-ui, -apple-system, \"Hiragino Sans\", \"Noto Sans JP\", \"Segoe UI\", sans-serif;\n color-scheme: light dark;\n}\n@media (prefers-color-scheme: dark) {\n :host(:not([theme=\"light\"])) {\n --_ink: var(--kazoku-ink, #f1f2f6);\n --_surface: var(--kazoku-surface, #1c1d22);\n --_line: var(--kazoku-line, #4a4d58);\n --_accent: var(--kazoku-accent, #8fb0ff);\n }\n}\n:host([theme=\"dark\"]) {\n --_ink: var(--kazoku-ink, #f1f2f6);\n --_surface: var(--kazoku-surface, #1c1d22);\n --_line: var(--kazoku-line, #4a4d58);\n --_accent: var(--kazoku-accent, #8fb0ff);\n color-scheme: dark;\n}\n:host([theme=\"light\"]) { color-scheme: light; }\n:host([overlay]) { position: absolute; inset-inline: 0; bottom: 0; z-index: 1; }\n:host([overlay]) .bar { background: color-mix(in srgb, var(--_surface) 88%, transparent); }\n[hidden] { display: none !important; }\n.bar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; padding: 4px 8px; box-sizing: border-box; }\n.field { display: flex; align-items: center; gap: 8px; flex: 1 1 200px; min-width: 0; }\n.field.tilt { flex-basis: 150px; }\n.field label { flex: none; }\n.field output { flex: none; min-width: 3.2em; text-align: end; font-variant-numeric: tabular-nums; }\n.group { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px; }\n.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\ninput[type=\"range\"] { -webkit-appearance: none; appearance: none; flex: 1 1 auto; width: 100%; min-width: 60px; height: 48px; margin: 0; padding: 0; background: transparent; cursor: pointer; }\ninput[type=\"range\"]::-webkit-slider-runnable-track { height: 6px; border-radius: 3px; background: var(--_line); }\ninput[type=\"range\"]::-moz-range-track { height: 6px; border-radius: 3px; background: var(--_line); }\ninput[type=\"range\"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 28px; height: 28px; margin-top: -11px; border-radius: 50%; background: var(--_accent); border: 3px solid var(--_surface); box-shadow: 0 0 0 1px var(--_ink); }\ninput[type=\"range\"]::-moz-range-thumb { width: 22px; height: 22px; border-radius: 50%; background: var(--_accent); border: 3px solid var(--_surface); box-shadow: 0 0 0 1px var(--_ink); }\ninput[type=\"range\"]:focus-visible { outline: 2px solid var(--_ink); outline-offset: -4px; border-radius: 8px; }\nbutton { -webkit-appearance: none; appearance: none; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 48px; min-height: 48px; margin: 0; padding: 0 14px; border: 1px solid var(--_line); border-radius: 24px; background: var(--_surface); color: var(--_ink); font: inherit; cursor: pointer; white-space: nowrap; }\nbutton:hover:not(:disabled) { border-color: var(--_ink); }\nbutton:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }\nbutton[aria-pressed=\"true\"] { background: var(--_ink); color: var(--_surface); border-color: var(--_ink); }\nbutton[aria-expanded=\"true\"] { border-color: var(--_ink); box-shadow: inset 0 0 0 1px var(--_ink); }\nbutton:disabled { opacity: 0.4; cursor: default; }\nsvg { width: 18px; height: 18px; fill: currentColor; flex: none; }\nbutton[aria-pressed=\"true\"] .i-play, button[aria-pressed=\"false\"] .i-pause { display: none; }\n.zoom button.level { min-width: 64px; padding: 0 8px; font-variant-numeric: tabular-nums; }\n/* The speed buttons are an element of their own: they are given the colours this bar has, set on the bar so that the buttons' own variables never read themselves. */\n.bar { --_bar-ink: var(--_ink); --_bar-surface: var(--_surface); --_bar-line: var(--_line); --_bar-accent: var(--_accent); }\n.pace { --kazoku-ink: var(--_bar-ink); --kazoku-surface: var(--_bar-surface); --kazoku-line: var(--_bar-line); --kazoku-accent: var(--_bar-accent); }\n/* One small button: the word over the speed, so it takes little room. It comes in on Auto-turn's left, so Auto-turn stays under the pointer that just pressed it. The angle's running number makes the room, so the turning bar is never wider than the still one and adds no row; it stays for a screen reader. */\n.pace-now { flex-direction: column; gap: 0; padding: 0 10px; line-height: 1.1; }\n.pace-now .lbl { font-size: 0.7em; font-weight: 400; }\n:host([data-turning][data-level=\"basic\"]) .field.angle { flex-basis: 110px; }\n:host([data-turning][data-level=\"basic\"]) .field.angle output { position: absolute; width: 1px; min-width: 0; height: 1px; overflow: hidden; clip-path: inset(50%); }\n.drag { margin-inline-start: 8px; }\n.more { margin-inline-start: auto; }\n/* The classic bar on a phone: two rows to start with (angle, Auto-turn and More; the zoom), more rows when the reader asks for more; labels read only by a screen reader. */\n@media (max-width: 640px) {\n :host([data-layout=\"classic\"]) .bar { display: grid; grid-template-columns: minmax(0, 1fr) 48px 48px; grid-template-areas: \"angle play more\" \"tilt tilt front\" \"pace pace pace\" \"zoom zoom zoom\" \"pan pan pan\"; gap: 0 8px; padding: 2px 8px; }\n :host([data-layout=\"classic\"]) .field.angle { grid-area: angle; }\n :host([data-layout=\"classic\"]) .field.tilt { grid-area: tilt; }\n :host([data-layout=\"classic\"]) .field label, :host([data-layout=\"classic\"]) .field output, :host([data-layout=\"classic\"]) .play .text, :host([data-layout=\"classic\"]) .more .text, :host([data-layout=\"classic\"]) .pace::part(label) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }\n :host([data-layout=\"classic\"]) .play, :host([data-layout=\"classic\"]) .more { padding: 0; }\n :host([data-layout=\"classic\"]) .play { grid-area: play; }\n :host([data-layout=\"classic\"]) .more { grid-area: more; }\n :host([data-layout=\"classic\"]) .front { grid-area: front; padding: 0 12px; }\n :host([data-layout=\"classic\"]) .pace { grid-area: pace; justify-self: end; flex-wrap: nowrap; }\n :host([data-layout=\"classic\"]) .zoom { grid-area: zoom; justify-self: end; flex-wrap: nowrap; }\n :host([data-layout=\"classic\"]) .zoom button { padding: 0 8px; }\n :host([data-layout=\"classic\"]) .zoom button.level { padding: 0 6px; }\n /* In the free room left of the zoom buttons, so showing it adds no row. */\n :host([data-layout=\"classic\"]) .pace-now { grid-area: zoom; justify-self: start; }\n :host([data-layout=\"classic\"]) .pan { grid-area: pan; justify-content: center; }\n :host([data-layout=\"classic\"]) .drag { margin-inline-start: 4px; }\n}\n/* In a panel narrower than the buttons and the speed need side by side, the speed takes the row under them. */\n:host([data-layout=\"classic\"]) { container-type: inline-size; }\n@container (max-width: 329px) {\n @media (max-width: 640px) {\n :host([data-layout=\"classic\"]) .pace-now { grid-area: pace; justify-self: end; }\n }\n}\n\n/* The zoom slider (horizontal, vertical and compact): a logarithmic track with ticks at its marks, the labels under it, and the part from the least zoom to the zoom now filled. */\n.zslide { display: none; }\n:host([data-layout=\"horizontal\"]) .zslide, :host([data-layout=\"vertical\"]) .zslide, :host([data-layout=\"compact\"]) .zslide { display: flex; }\n.zbox { position: relative; flex: 1 1 auto; min-width: 96px; height: 52px; }\n.zbox input { position: absolute; inset: 0 0 auto; z-index: 1; min-width: 0; }\n.zbox input::-webkit-slider-runnable-track { background: transparent; }\n.zbox input::-moz-range-track { background: transparent; }\n.ztrack { position: absolute; inset-inline: 14px; top: 21px; height: 6px; border-radius: 3px; background: var(--_line); }\n.zfill { position: absolute; inset-block: 0; left: 0; width: calc(var(--pos, 0) * 100%); border-radius: 3px; background: var(--_accent); }\n.ztick { position: absolute; top: 50%; left: calc(var(--at) * 100%); width: 2px; height: 14px; border-radius: 1px; background: var(--_ink); opacity: 0.5; transform: translate(-50%, -50%); }\n.zmarks { position: absolute; inset-inline: 14px; top: 36px; height: 16px; pointer-events: none; }\n.zmark { position: absolute; left: calc(var(--at) * 100%); transform: translateX(-50%); font-size: 0.72rem; font-weight: 500; line-height: 16px; opacity: 0.85; white-space: nowrap; font-variant-numeric: tabular-nums; }\n.zmark[data-end=\"start\"] { transform: translateX(-35%); }\n.zmark[data-end=\"end\"] { transform: translateX(-65%); }\n\n/* Horizontal: a tidy bar under the view. Two rows (the turn, then the zoom with its slider); one row when its own width allows. More adds its rows between them. */\n:host([data-layout=\"horizontal\"]) { container-type: inline-size; }\n:host([data-layout=\"horizontal\"]) .bar, :host([data-layout=\"vertical\"]) .bar, :host([data-layout=\"compact\"]) .bar { display: grid; row-gap: 0; align-items: center; }\n:host([data-layout=\"horizontal\"]) .bar > *, :host([data-layout=\"vertical\"]) .bar > *, :host([data-layout=\"compact\"]) .zoom > * { margin-block: 2px; }\n:host([data-layout=\"horizontal\"]) .bar { grid-template-columns: minmax(0, 1fr) auto auto auto; grid-template-areas: \"angle speed play more\" \"tilt tilt front front\" \"pace pace pace pace\" \"zoom zoom zoom centre\" \"pan pan pan pan\"; column-gap: 8px; padding: 4px 8px; }\n:host([data-layout=\"horizontal\"][data-flat]) .bar { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: \"zoom centre more\" \"pan pan pan\"; }\n:host([data-layout=\"horizontal\"]) .angle, :host([data-layout=\"vertical\"]) .angle, :host([data-layout=\"compact\"]) .angle { grid-area: angle; }\n:host([data-layout=\"horizontal\"]) .tilt, :host([data-layout=\"vertical\"]) .tilt, :host([data-layout=\"compact\"]) .tilt { grid-area: tilt; }\n:host([data-layout=\"horizontal\"]) .pace-now, :host([data-layout=\"vertical\"]) .pace-now { grid-area: speed; }\n:host([data-layout=\"horizontal\"]) .play, :host([data-layout=\"vertical\"]) .play, :host([data-layout=\"compact\"]) .play { grid-area: play; }\n:host([data-layout=\"horizontal\"]) .pace, :host([data-layout=\"vertical\"]) .pace, :host([data-layout=\"compact\"]) .pace { grid-area: pace; flex-wrap: nowrap; }\n:host([data-layout=\"horizontal\"]) .front, :host([data-layout=\"vertical\"]) .front, :host([data-layout=\"compact\"]) .front { grid-area: front; justify-self: start; }\n:host([data-layout=\"horizontal\"]) .zoom, :host([data-layout=\"vertical\"]) .zoom { grid-area: zoom; }\n:host([data-layout=\"horizontal\"]) .pan, :host([data-layout=\"vertical\"]) .pan, :host([data-layout=\"compact\"]) .pan { grid-area: pan; justify-self: start; }\n:host([data-layout=\"horizontal\"]) .more, :host([data-layout=\"vertical\"]) .more, :host([data-layout=\"compact\"]) .more { grid-area: more; margin-inline-start: 0; }\n/* Centre, shown once the view is zoomed or moved: beside the zoom, never in the turn's row, so Auto-turn and More stay where they are. */\n:host([data-layout=\"horizontal\"]) .centre, :host([data-layout=\"vertical\"]) .centre, :host([data-layout=\"compact\"]) .centre { grid-area: centre; justify-self: end; }\n/* In the two rows it shares More's column, so it is an icon (its name is read) and never widens that column, which would move Auto-turn. */\n@container (max-width: 1039px) {\n :host([data-layout=\"horizontal\"]) .centre .text { position: absolute; width: 1px; min-width: 0; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n :host([data-layout=\"horizontal\"]) .centre { padding: 0; }\n}\n:host([data-layout=\"horizontal\"]) .zoom { display: flex; flex-wrap: nowrap; gap: 4px; min-width: 0; }\n:host([data-layout=\"horizontal\"]) .zslide { flex: 1 1 auto; min-width: 0; }\n:host([data-layout=\"horizontal\"]) .zoom button, :host([data-layout=\"vertical\"]) .zoom button, :host([data-layout=\"compact\"]) .zoom button { padding: 0 10px; }\n:host([data-layout=\"horizontal\"]) .zoom button.level, :host([data-layout=\"vertical\"]) .zoom button.level, :host([data-layout=\"compact\"]) .zoom button.level { padding: 0 6px; }\n@container (max-width: 559px) {\n :host([data-layout=\"horizontal\"]) .field label, :host([data-layout=\"horizontal\"]) .field output, :host([data-layout=\"horizontal\"]) .play .text, :host([data-layout=\"horizontal\"]) .more .text, :host([data-layout=\"horizontal\"]) .pace::part(label) { position: absolute; width: 1px; min-width: 0; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n :host([data-layout=\"horizontal\"]) .play, :host([data-layout=\"horizontal\"]) .more { padding: 0; }\n :host([data-layout=\"horizontal\"]) .zbox { min-width: 72px; }\n /* The readout is Fit here (its name says so), which leaves the slider the room for its marks. */\n :host([data-layout=\"horizontal\"]) [data-b=\"fit\"] { display: none; }\n}\n/* A phone-safe width (a phone's screen less the page's margins, and a face wider than the system's): the zoom slider takes a row of its own, so its marks never meet the buttons, and the buttons sit under it. */\n@container (max-width: 439px) {\n :host([data-layout=\"horizontal\"]) .zoom { flex-wrap: wrap; }\n :host([data-layout=\"horizontal\"]) .zslide { flex: 1 0 100%; }\n :host([data-layout=\"horizontal\"]) .centre { align-self: end; }\n :host([data-layout=\"horizontal\"]) .zbox { min-width: 0; }\n}\n/* Wide enough for the turn, Auto-turn, the zoom with its slider, Centre and More on one row, with the angle slider still a slider. */\n@container (min-width: 1040px) {\n :host([data-layout=\"horizontal\"]) .bar { grid-template-columns: minmax(0, 1fr) auto auto auto auto auto; grid-template-areas: \"angle speed play zoom centre more\" \"tilt tilt tilt pace front front\" \"pan pan pan pan pan pan\"; column-gap: 12px; }\n :host([data-layout=\"horizontal\"][data-flat]) .bar { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: \"zoom centre more\" \"pan pan pan\"; }\n :host([data-layout=\"horizontal\"]) .zslide { width: 15rem; }\n :host([data-layout=\"horizontal\"]) .zoom { display: grid; grid-auto-flow: column; grid-auto-columns: auto; }\n :host([data-layout=\"horizontal\"]) .pan { justify-self: end; }\n}\n\n/* Vertical: a column beside the view, its width --kazoku-column-width (16rem). Auto-turn and More at the top, so More stays put when it opens; then the turn, the zoom and its slider. */\n:host([data-layout=\"vertical\"]) { width: var(--kazoku-column-width, 16rem); max-width: 100%; }\n:host([data-layout=\"vertical\"]) .bar { grid-template-columns: auto auto minmax(0, 1fr) auto; grid-template-areas: \"play speed . more\" \"angle angle angle angle\" \"tilt tilt tilt tilt\" \"pace pace pace pace\" \"front front front front\" \"zoom zoom zoom zoom\" \"centre centre centre centre\" \"pan pan pan pan\"; column-gap: 8px; padding: 6px 8px; }\n:host([data-layout=\"vertical\"]) .field { flex: none; display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: \"label value\" \"slider slider\"; gap: 0 8px; }\n:host([data-layout=\"vertical\"]) .field label { grid-area: label; padding-top: 4px; }\n:host([data-layout=\"vertical\"]) .field output { grid-area: value; padding-top: 4px; }\n:host([data-layout=\"vertical\"]) .field input, :host([data-layout=\"vertical\"]) .zbox { grid-area: slider; }\n:host([data-layout=\"vertical\"]) .zoom { display: grid; grid-template-columns: auto auto auto minmax(0, 1fr); grid-template-areas: \"zs zs zs zs\" \"out level in fit\"; gap: 0 4px; }\n:host([data-layout=\"vertical\"]) .zslide { grid-area: zs; }\n:host([data-layout=\"vertical\"]) [data-b=\"out\"] { grid-area: out; }\n:host([data-layout=\"vertical\"]) [data-b=\"level\"] { grid-area: level; }\n:host([data-layout=\"vertical\"]) [data-b=\"in\"] { grid-area: in; }\n:host([data-layout=\"vertical\"]) [data-b=\"fit\"] { grid-area: fit; justify-self: start; }\n:host([data-layout=\"vertical\"]) .pace::part(label) { position: absolute; width: 1px; min-width: 0; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n:host([data-layout=\"vertical\"]) .pan { flex-wrap: wrap; }\n:host([data-layout=\"vertical\"]) .drag { margin-inline-start: 0; }\n\n/* Compact: one short row (Auto-turn, zoom out, the zoom now, zoom in, More) for a card or a phone. More opens the rest under it: the zoom slider and Fit, the angle, and the advanced controls. */\n:host([data-layout=\"compact\"]) { display: block; }\n:host([data-layout=\"compact\"][overlay]) { inset-inline: 8px auto; bottom: 8px; }\n/* Over a card, an open More never reaches past the card's top: the controls scroll inside themselves. */\n:host([data-layout=\"compact\"][overlay]) { display: flex; flex-direction: column; max-height: calc(100% - 16px); }\n:host([data-layout=\"compact\"][overlay]) .bar { min-height: 0; overflow-y: auto; }\n:host([data-layout=\"compact\"]) .bar { width: fit-content; max-width: 100%; grid-template-columns: repeat(5, auto); grid-template-areas: \"play out level in more\" \"zs zs zs zs fit\" \"centre centre centre centre centre\" \"angle angle angle angle angle\" \"tilt tilt tilt tilt tilt\" \"pace pace pace pace pace\" \"front front front front front\" \"pan pan pan pan pan\"; column-gap: 2px; padding: 2px 4px; border: 1px solid var(--_line); border-radius: 28px; background: var(--_surface); }\n:host([data-layout=\"compact\"][data-level=\"advanced\"]) .bar { border-radius: 20px; padding: 2px 8px 6px; column-gap: 4px; }\n:host([data-layout=\"compact\"][data-flat]) .bar { grid-template-areas: \"out level in more .\" \"zs zs zs fit fit\" \"centre centre centre centre centre\" \"pan pan pan pan pan\"; }\n:host([data-layout=\"compact\"]) .zoom { display: contents; }\n:host([data-layout=\"compact\"]) .zslide { grid-area: zs; }\n:host([data-layout=\"compact\"]) [data-b=\"out\"] { grid-area: out; }\n:host([data-layout=\"compact\"]) [data-b=\"level\"] { grid-area: level; }\n:host([data-layout=\"compact\"]) [data-b=\"in\"] { grid-area: in; }\n:host([data-layout=\"compact\"]) [data-b=\"fit\"] { grid-area: fit; }\n:host([data-layout=\"compact\"]) .play, :host([data-layout=\"compact\"]) .more, :host([data-layout=\"compact\"]) [data-b=\"out\"], :host([data-layout=\"compact\"]) [data-b=\"level\"], :host([data-layout=\"compact\"]) [data-b=\"in\"] { border-color: transparent; }\n:host([data-layout=\"compact\"]) .play:hover:not(:disabled), :host([data-layout=\"compact\"]) .more:hover:not(:disabled), :host([data-layout=\"compact\"]) .zoom button:hover:not(:disabled) { border-color: var(--_ink); }\n:host([data-layout=\"compact\"]) .play .text, :host([data-layout=\"compact\"]) .more .text { position: absolute; width: 1px; min-width: 0; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n:host([data-layout=\"compact\"]) .play, :host([data-layout=\"compact\"]) .more { padding: 0; }\n:host([data-layout=\"compact\"]) .pace-now { display: none; }\n:host([data-layout=\"compact\"]) .pace { flex-wrap: wrap; }\n:host([data-layout=\"vertical\"]) .centre, :host([data-layout=\"compact\"]) .centre { justify-self: start; }\n/* The row stays one row: Centre is with Fit, one press of More away. */\n:host([data-layout=\"compact\"][data-level=\"basic\"]) .centre { display: none; }\n:host([data-layout=\"compact\"][data-level=\"basic\"]) .angle, :host([data-layout=\"compact\"][data-level=\"basic\"]) .zslide, :host([data-layout=\"compact\"][data-level=\"basic\"]) [data-b=\"fit\"] { display: none; }\n";
|
|
1
|
+
export declare const STYLES = "\n:host {\n --_ink: var(--kazoku-ink, #1b1b1f);\n --_surface: var(--kazoku-surface, #ffffff);\n --_line: var(--kazoku-line, #c9ccd3);\n --_accent: var(--kazoku-accent, #2457d6);\n display: block;\n box-sizing: border-box;\n color: var(--_ink);\n font: 600 0.9rem/1.2 system-ui, -apple-system, \"Hiragino Sans\", \"Noto Sans JP\", \"Segoe UI\", sans-serif;\n color-scheme: light dark;\n}\n@media (prefers-color-scheme: dark) {\n :host(:not([theme=\"light\"])) {\n --_ink: var(--kazoku-ink, #f1f2f6);\n --_surface: var(--kazoku-surface, #1c1d22);\n --_line: var(--kazoku-line, #4a4d58);\n --_accent: var(--kazoku-accent, #8fb0ff);\n }\n}\n:host([theme=\"dark\"]) {\n --_ink: var(--kazoku-ink, #f1f2f6);\n --_surface: var(--kazoku-surface, #1c1d22);\n --_line: var(--kazoku-line, #4a4d58);\n --_accent: var(--kazoku-accent, #8fb0ff);\n color-scheme: dark;\n}\n:host([theme=\"light\"]) { color-scheme: light; }\n:host([overlay]) { position: absolute; inset-inline: 0; bottom: 0; z-index: 1; }\n:host([overlay]) .bar { background: color-mix(in srgb, var(--_surface) 88%, transparent); }\n[hidden] { display: none !important; }\n.bar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; padding: 4px 8px; box-sizing: border-box; }\n.field { display: flex; align-items: center; gap: 8px; flex: 1 1 200px; min-width: 0; }\n/* One slider stands for the axis the chips name. The other two stay in the tree for the address and the tests that read them, and take no room. The chips take their own row, so the speed button can come in beside Auto-turn without moving it. */\n.field.tilt, .field.roll { display: none; }\n.axes { order: 8; flex: 1 0 100%; }\n.axes button { padding: 0 12px; }\n.field label { flex: none; }\n.field output { flex: none; min-width: 3.2em; text-align: end; font-variant-numeric: tabular-nums; }\n.group { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px; }\n.vh { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\ninput[type=\"range\"] { -webkit-appearance: none; appearance: none; flex: 1 1 auto; width: 100%; min-width: 60px; height: 48px; margin: 0; padding: 0; background: transparent; cursor: pointer; }\ninput[type=\"range\"]::-webkit-slider-runnable-track { height: 6px; border-radius: 3px; background: var(--_line); }\ninput[type=\"range\"]::-moz-range-track { height: 6px; border-radius: 3px; background: var(--_line); }\ninput[type=\"range\"]::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 28px; height: 28px; margin-top: -11px; border-radius: 50%; background: var(--_accent); border: 3px solid var(--_surface); box-shadow: 0 0 0 1px var(--_ink); }\ninput[type=\"range\"]::-moz-range-thumb { width: 22px; height: 22px; border-radius: 50%; background: var(--_accent); border: 3px solid var(--_surface); box-shadow: 0 0 0 1px var(--_ink); }\ninput[type=\"range\"]:focus-visible { outline: 2px solid var(--_ink); outline-offset: -4px; border-radius: 8px; }\nbutton { -webkit-appearance: none; appearance: none; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; gap: 6px; min-width: 48px; min-height: 48px; margin: 0; padding: 0 14px; border: 1px solid var(--_line); border-radius: 24px; background: var(--_surface); color: var(--_ink); font: inherit; cursor: pointer; white-space: nowrap; }\nbutton:hover:not(:disabled) { border-color: var(--_ink); }\nbutton:focus-visible { outline: 2px solid var(--_ink); outline-offset: 2px; }\nbutton[aria-pressed=\"true\"] { background: var(--_ink); color: var(--_surface); border-color: var(--_ink); }\nbutton[aria-expanded=\"true\"] { border-color: var(--_ink); box-shadow: inset 0 0 0 1px var(--_ink); }\nbutton:disabled { opacity: 0.4; cursor: default; }\nsvg { width: 18px; height: 18px; fill: currentColor; flex: none; }\nbutton[aria-pressed=\"true\"] .i-play, button[aria-pressed=\"false\"] .i-pause { display: none; }\n.zoom button.level { min-width: 64px; padding: 0 8px; font-variant-numeric: tabular-nums; }\n/* The speed buttons are an element of their own: they are given the colours this bar has, set on the bar so that the buttons' own variables never read themselves. */\n.bar { --_bar-ink: var(--_ink); --_bar-surface: var(--_surface); --_bar-line: var(--_line); --_bar-accent: var(--_accent); }\n.pace { --kazoku-ink: var(--_bar-ink); --kazoku-surface: var(--_bar-surface); --kazoku-line: var(--_bar-line); --kazoku-accent: var(--_bar-accent); }\n/* One small button: the word over the speed, so it takes little room. It comes in on Auto-turn's left, so Auto-turn stays under the pointer that just pressed it. The angle's running number makes the room, so the turning bar is never wider than the still one and adds no row; it stays for a screen reader. */\n/* The speed buttons and the Left and Right buttons are one row (two when the room is short) and take the place the speed buttons had. */\n.pacing { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; }\n.dir button { padding: 0 12px; }\n.pace-now { flex-direction: column; gap: 0; padding: 0 10px; line-height: 1.1; }\n.pace-now .lbl { font-size: 0.7em; font-weight: 400; }\n:host([data-turning][data-level=\"basic\"]) .field.angle { flex-basis: 110px; }\n:host([data-turning][data-level=\"basic\"]) .field.angle output { position: absolute; width: 1px; min-width: 0; height: 1px; overflow: hidden; clip-path: inset(50%); }\n.drag { margin-inline-start: 8px; }\n.more { margin-inline-start: auto; }\n/* The classic bar on a phone: two rows to start with (angle, Auto-turn and More; the zoom), more rows when the reader asks for more; labels read only by a screen reader. */\n@media (max-width: 640px) {\n :host([data-layout=\"classic\"]) .bar { display: grid; grid-template-columns: minmax(0, 1fr) 48px 48px; grid-template-areas: \"angle play more\" \"tilt tilt front\" \"roll roll roll\" \"axes axes axes\" \"pace pace pace\" \"zoom zoom zoom\" \"pan pan pan\"; gap: 0 8px; padding: 2px 8px; }\n :host([data-layout=\"classic\"]) .field.angle { grid-area: angle; }\n :host([data-layout=\"classic\"]) .field.tilt { grid-area: tilt; }\n :host([data-layout=\"classic\"]) .field.roll { grid-area: roll; }\n :host([data-layout=\"classic\"]) .axes { grid-area: axes; }\n :host([data-layout=\"classic\"]) .field label, :host([data-layout=\"classic\"]) .field output, :host([data-layout=\"classic\"]) .play .text, :host([data-layout=\"classic\"]) .more .text, :host([data-layout=\"classic\"]) .pace::part(label) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }\n :host([data-layout=\"classic\"]) .play, :host([data-layout=\"classic\"]) .more { padding: 0; }\n :host([data-layout=\"classic\"]) .play { grid-area: play; }\n :host([data-layout=\"classic\"]) .more { grid-area: more; }\n :host([data-layout=\"classic\"]) .front { grid-area: front; padding: 0 12px; }\n :host([data-layout=\"classic\"]) .pacing { grid-area: pace; justify-self: end; }\n :host([data-layout=\"classic\"]) .zoom { grid-area: zoom; justify-self: end; flex-wrap: nowrap; }\n :host([data-layout=\"classic\"]) .zoom button { padding: 0 8px; }\n :host([data-layout=\"classic\"]) .zoom button.level { padding: 0 6px; }\n /* In the free room left of the zoom buttons, so showing it adds no row. */\n :host([data-layout=\"classic\"]) .pace-now { grid-area: zoom; justify-self: start; }\n :host([data-layout=\"classic\"]) .pan { grid-area: pan; justify-content: center; }\n :host([data-layout=\"classic\"]) .drag { margin-inline-start: 4px; }\n}\n/* In a panel narrower than the buttons and the speed need side by side, the speed takes the row under them. */\n:host([data-layout=\"classic\"]) { container-type: inline-size; }\n@container (max-width: 329px) {\n @media (max-width: 640px) {\n :host([data-layout=\"classic\"]) .pace-now { grid-area: pace; justify-self: end; }\n }\n}\n\n/* The zoom slider (horizontal, vertical and compact): a logarithmic track with ticks at its marks, the labels under it, and the part from the least zoom to the zoom now filled. */\n.zslide { display: none; }\n:host([data-layout=\"horizontal\"]) .zslide, :host([data-layout=\"vertical\"]) .zslide, :host([data-layout=\"compact\"]) .zslide { display: flex; }\n.zbox { position: relative; flex: 1 1 auto; min-width: 96px; height: 52px; }\n.zbox input { position: absolute; inset: 0 0 auto; z-index: 1; min-width: 0; }\n.zbox input::-webkit-slider-runnable-track { background: transparent; }\n.zbox input::-moz-range-track { background: transparent; }\n.ztrack { position: absolute; inset-inline: 14px; top: 21px; height: 6px; border-radius: 3px; background: var(--_line); }\n.zfill { position: absolute; inset-block: 0; left: 0; width: calc(var(--pos, 0) * 100%); border-radius: 3px; background: var(--_accent); }\n.ztick { position: absolute; top: 50%; left: calc(var(--at) * 100%); width: 2px; height: 14px; border-radius: 1px; background: var(--_ink); opacity: 0.5; transform: translate(-50%, -50%); }\n.zmarks { position: absolute; inset-inline: 14px; top: 36px; height: 16px; pointer-events: none; }\n.zmark { position: absolute; left: calc(var(--at) * 100%); transform: translateX(-50%); font-size: 0.72rem; font-weight: 500; line-height: 16px; opacity: 0.85; white-space: nowrap; font-variant-numeric: tabular-nums; }\n.zmark[data-end=\"start\"] { transform: translateX(-35%); }\n.zmark[data-end=\"end\"] { transform: translateX(-65%); }\n\n/* Horizontal: a tidy bar under the view. Two rows (the turn, then the zoom with its slider); one row when its own width allows. More adds its rows between them. */\n:host([data-layout=\"horizontal\"]) { container-type: inline-size; }\n:host([data-layout=\"horizontal\"]) .bar, :host([data-layout=\"vertical\"]) .bar, :host([data-layout=\"compact\"]) .bar { display: grid; row-gap: 0; align-items: center; }\n:host([data-layout=\"horizontal\"]) .bar > *, :host([data-layout=\"vertical\"]) .bar > *, :host([data-layout=\"compact\"]) .zoom > * { margin-block: 2px; }\n:host([data-layout=\"horizontal\"]) .bar { grid-template-columns: minmax(0, 1fr) auto auto auto; grid-template-areas: \"angle speed play more\" \"tilt tilt front front\" \"roll roll roll roll\" \"axes axes axes axes\" \"pace pace pace pace\" \"zoom zoom zoom centre\" \"pan pan pan pan\"; column-gap: 8px; padding: 4px 8px; }\n:host([data-layout=\"horizontal\"][data-flat]) .bar { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: \"zoom centre more\" \"pan pan pan\"; }\n:host([data-layout=\"horizontal\"]) .angle, :host([data-layout=\"vertical\"]) .angle, :host([data-layout=\"compact\"]) .angle { grid-area: angle; }\n:host([data-layout=\"horizontal\"]) .tilt, :host([data-layout=\"vertical\"]) .tilt, :host([data-layout=\"compact\"]) .tilt { grid-area: tilt; }\n:host([data-layout=\"horizontal\"]) .roll, :host([data-layout=\"vertical\"]) .roll, :host([data-layout=\"compact\"]) .roll { grid-area: roll; }\n:host([data-layout=\"horizontal\"]) .axes, :host([data-layout=\"vertical\"]) .axes, :host([data-layout=\"compact\"]) .axes { grid-area: axes; }\n:host([data-layout=\"horizontal\"]) .pace-now, :host([data-layout=\"vertical\"]) .pace-now { grid-area: speed; }\n:host([data-layout=\"horizontal\"]) .play, :host([data-layout=\"vertical\"]) .play, :host([data-layout=\"compact\"]) .play { grid-area: play; }\n:host([data-layout=\"horizontal\"]) .pacing, :host([data-layout=\"vertical\"]) .pacing, :host([data-layout=\"compact\"]) .pacing { grid-area: pace; }\n:host([data-layout=\"horizontal\"]) .front, :host([data-layout=\"vertical\"]) .front, :host([data-layout=\"compact\"]) .front { grid-area: front; justify-self: start; }\n:host([data-layout=\"horizontal\"]) .zoom, :host([data-layout=\"vertical\"]) .zoom { grid-area: zoom; }\n:host([data-layout=\"horizontal\"]) .pan, :host([data-layout=\"vertical\"]) .pan, :host([data-layout=\"compact\"]) .pan { grid-area: pan; justify-self: start; }\n:host([data-layout=\"horizontal\"]) .more, :host([data-layout=\"vertical\"]) .more, :host([data-layout=\"compact\"]) .more { grid-area: more; margin-inline-start: 0; }\n/* Centre, shown once the view is zoomed or moved: beside the zoom, never in the turn's row, so Auto-turn and More stay where they are. */\n:host([data-layout=\"horizontal\"]) .centre, :host([data-layout=\"vertical\"]) .centre, :host([data-layout=\"compact\"]) .centre { grid-area: centre; justify-self: end; }\n/* In the two rows it shares More's column, so it is an icon (its name is read) and never widens that column, which would move Auto-turn. */\n@container (max-width: 1039px) {\n :host([data-layout=\"horizontal\"]) .centre .text { position: absolute; width: 1px; min-width: 0; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n :host([data-layout=\"horizontal\"]) .centre { padding: 0; }\n}\n:host([data-layout=\"horizontal\"]) .zoom { display: flex; flex-wrap: nowrap; gap: 4px; min-width: 0; }\n:host([data-layout=\"horizontal\"]) .zslide { flex: 1 1 auto; min-width: 0; }\n:host([data-layout=\"horizontal\"]) .zoom button, :host([data-layout=\"vertical\"]) .zoom button, :host([data-layout=\"compact\"]) .zoom button { padding: 0 10px; }\n:host([data-layout=\"horizontal\"]) .zoom button.level, :host([data-layout=\"vertical\"]) .zoom button.level, :host([data-layout=\"compact\"]) .zoom button.level { padding: 0 6px; }\n@container (max-width: 559px) {\n :host([data-layout=\"horizontal\"]) .field label, :host([data-layout=\"horizontal\"]) .field output, :host([data-layout=\"horizontal\"]) .play .text, :host([data-layout=\"horizontal\"]) .more .text, :host([data-layout=\"horizontal\"]) .pace::part(label) { position: absolute; width: 1px; min-width: 0; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n :host([data-layout=\"horizontal\"]) .play, :host([data-layout=\"horizontal\"]) .more { padding: 0; }\n :host([data-layout=\"horizontal\"]) .zbox { min-width: 72px; }\n /* The readout is Fit here (its name says so), which leaves the slider the room for its marks. */\n :host([data-layout=\"horizontal\"]) [data-b=\"fit\"] { display: none; }\n}\n/* A phone-safe width (a phone's screen less the page's margins, and a face wider than the system's): the zoom slider takes a row of its own, so its marks never meet the buttons, and the buttons sit under it. */\n@container (max-width: 439px) {\n :host([data-layout=\"horizontal\"]) .zoom { flex-wrap: wrap; }\n :host([data-layout=\"horizontal\"]) .zslide { flex: 1 0 100%; }\n :host([data-layout=\"horizontal\"]) .centre { align-self: end; }\n :host([data-layout=\"horizontal\"]) .zbox { min-width: 0; }\n}\n/* Wide enough for the turn, Auto-turn, the zoom with its slider, Centre and More on one row, with the angle slider still a slider. */\n@container (min-width: 1040px) {\n :host([data-layout=\"horizontal\"]) .bar { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto auto auto; grid-template-areas: \"angle angle speed play zoom centre\" \"axes axes axes axes front more\" \"pace pace pace pace pace pace\" \"pan pan pan pan pan pan\"; column-gap: 12px; }\n :host([data-layout=\"horizontal\"]) .axes { flex-wrap: nowrap; }\n :host([data-layout=\"horizontal\"][data-flat]) .bar { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: \"zoom centre more\" \"pan pan pan\"; }\n :host([data-layout=\"horizontal\"]) .zslide { width: 15rem; }\n :host([data-layout=\"horizontal\"]) .zoom { display: grid; grid-auto-flow: column; grid-auto-columns: auto; }\n :host([data-layout=\"horizontal\"]) .pan { justify-self: end; }\n}\n\n/* Vertical: a column beside the view, its width --kazoku-column-width (16rem). Auto-turn and More at the top, so More stays put when it opens; then the turn, the zoom and its slider. */\n:host([data-layout=\"vertical\"]) { width: var(--kazoku-column-width, 16rem); max-width: 100%; }\n:host([data-layout=\"vertical\"]) .bar { grid-template-columns: auto auto minmax(0, 1fr) auto; grid-template-areas: \"play speed . more\" \"angle angle angle angle\" \"tilt tilt tilt tilt\" \"roll roll roll roll\" \"axes axes axes axes\" \"pace pace pace pace\" \"front front front front\" \"zoom zoom zoom zoom\" \"centre centre centre centre\" \"pan pan pan pan\"; column-gap: 8px; padding: 6px 8px; }\n:host([data-layout=\"vertical\"]) .field { flex: none; display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: \"label value\" \"slider slider\"; gap: 0 8px; }\n:host([data-layout=\"vertical\"]) .field.tilt, :host([data-layout=\"vertical\"]) .field.roll { display: none; }\n:host([data-layout=\"vertical\"]) .field label { grid-area: label; padding-top: 4px; }\n:host([data-layout=\"vertical\"]) .field output { grid-area: value; padding-top: 4px; }\n:host([data-layout=\"vertical\"]) .field input, :host([data-layout=\"vertical\"]) .zbox { grid-area: slider; }\n:host([data-layout=\"vertical\"]) .zoom { display: grid; grid-template-columns: auto auto auto minmax(0, 1fr); grid-template-areas: \"zs zs zs zs\" \"out level in fit\"; gap: 0 4px; }\n:host([data-layout=\"vertical\"]) .zslide { grid-area: zs; }\n:host([data-layout=\"vertical\"]) [data-b=\"out\"] { grid-area: out; }\n:host([data-layout=\"vertical\"]) [data-b=\"level\"] { grid-area: level; }\n:host([data-layout=\"vertical\"]) [data-b=\"in\"] { grid-area: in; }\n:host([data-layout=\"vertical\"]) [data-b=\"fit\"] { grid-area: fit; justify-self: start; }\n:host([data-layout=\"vertical\"]) .pace::part(label) { position: absolute; width: 1px; min-width: 0; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n:host([data-layout=\"vertical\"]) .pan { flex-wrap: wrap; }\n:host([data-layout=\"vertical\"]) .drag { margin-inline-start: 0; }\n\n/* Compact: one short row (Auto-turn, zoom out, the zoom now, zoom in, More) for a card or a phone. More opens the rest under it: the zoom slider and Fit, the angle, and the advanced controls. */\n:host([data-layout=\"compact\"]) { display: block; }\n:host([data-layout=\"compact\"][overlay]) { inset-inline: 8px auto; bottom: 8px; }\n/* Over a card, an open More never reaches past the card's top: the controls scroll inside themselves. */\n:host([data-layout=\"compact\"][overlay]) { display: flex; flex-direction: column; max-height: calc(100% - 16px); }\n:host([data-layout=\"compact\"][overlay]) .bar { min-height: 0; overflow-y: auto; }\n:host([data-layout=\"compact\"]) .bar { width: fit-content; max-width: 100%; grid-template-columns: repeat(5, auto); grid-template-areas: \"play out level in more\" \"zs zs zs zs fit\" \"centre centre centre centre centre\" \"angle angle angle angle angle\" \"tilt tilt tilt tilt tilt\" \"roll roll roll roll roll\" \"axes axes axes axes axes\" \"pace pace pace pace pace\" \"front front front front front\" \"pan pan pan pan pan\"; column-gap: 2px; padding: 2px 4px; border: 1px solid var(--_line); border-radius: 28px; background: var(--_surface); }\n:host([data-layout=\"compact\"][data-level=\"advanced\"]) .bar { border-radius: 20px; padding: 2px 8px 6px; column-gap: 4px; }\n:host([data-layout=\"compact\"][data-flat]) .bar { grid-template-areas: \"out level in more .\" \"zs zs zs fit fit\" \"centre centre centre centre centre\" \"pan pan pan pan pan\"; }\n:host([data-layout=\"compact\"]) .zoom { display: contents; }\n:host([data-layout=\"compact\"]) .zslide { grid-area: zs; }\n:host([data-layout=\"compact\"]) [data-b=\"out\"] { grid-area: out; }\n:host([data-layout=\"compact\"]) [data-b=\"level\"] { grid-area: level; }\n:host([data-layout=\"compact\"]) [data-b=\"in\"] { grid-area: in; }\n:host([data-layout=\"compact\"]) [data-b=\"fit\"] { grid-area: fit; }\n:host([data-layout=\"compact\"]) .play, :host([data-layout=\"compact\"]) .more, :host([data-layout=\"compact\"]) [data-b=\"out\"], :host([data-layout=\"compact\"]) [data-b=\"level\"], :host([data-layout=\"compact\"]) [data-b=\"in\"] { border-color: transparent; }\n:host([data-layout=\"compact\"]) .play:hover:not(:disabled), :host([data-layout=\"compact\"]) .more:hover:not(:disabled), :host([data-layout=\"compact\"]) .zoom button:hover:not(:disabled) { border-color: var(--_ink); }\n:host([data-layout=\"compact\"]) .play .text, :host([data-layout=\"compact\"]) .more .text { position: absolute; width: 1px; min-width: 0; height: 1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }\n:host([data-layout=\"compact\"]) .play, :host([data-layout=\"compact\"]) .more { padding: 0; }\n:host([data-layout=\"compact\"]) .pace-now { display: none; }\n:host([data-layout=\"compact\"]) .pacing { flex-wrap: wrap; }\n:host([data-layout=\"vertical\"]) .centre, :host([data-layout=\"compact\"]) .centre { justify-self: start; }\n/* The row stays one row: Centre is with Fit, one press of More away. */\n:host([data-layout=\"compact\"][data-level=\"basic\"]) .centre { display: none; }\n:host([data-layout=\"compact\"][data-level=\"basic\"]) .angle, :host([data-layout=\"compact\"][data-level=\"basic\"]) .tilt, :host([data-layout=\"compact\"][data-level=\"basic\"]) .roll, :host([data-layout=\"compact\"][data-level=\"basic\"]) .axes, :host([data-layout=\"compact\"][data-level=\"basic\"]) .zslide, :host([data-layout=\"compact\"][data-level=\"basic\"]) [data-b=\"fit\"] { display: none; }\n";
|
|
@@ -39,7 +39,10 @@ export const STYLES = `
|
|
|
39
39
|
[hidden] { display: none !important; }
|
|
40
40
|
.bar { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; padding: 4px 8px; box-sizing: border-box; }
|
|
41
41
|
.field { display: flex; align-items: center; gap: 8px; flex: 1 1 200px; min-width: 0; }
|
|
42
|
-
.
|
|
42
|
+
/* One slider stands for the axis the chips name. The other two stay in the tree for the address and the tests that read them, and take no room. The chips take their own row, so the speed button can come in beside Auto-turn without moving it. */
|
|
43
|
+
.field.tilt, .field.roll { display: none; }
|
|
44
|
+
.axes { order: 8; flex: 1 0 100%; }
|
|
45
|
+
.axes button { padding: 0 12px; }
|
|
43
46
|
.field label { flex: none; }
|
|
44
47
|
.field output { flex: none; min-width: 3.2em; text-align: end; font-variant-numeric: tabular-nums; }
|
|
45
48
|
.group { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px; }
|
|
@@ -63,6 +66,9 @@ button[aria-pressed="true"] .i-play, button[aria-pressed="false"] .i-pause { dis
|
|
|
63
66
|
.bar { --_bar-ink: var(--_ink); --_bar-surface: var(--_surface); --_bar-line: var(--_line); --_bar-accent: var(--_accent); }
|
|
64
67
|
.pace { --kazoku-ink: var(--_bar-ink); --kazoku-surface: var(--_bar-surface); --kazoku-line: var(--_bar-line); --kazoku-accent: var(--_bar-accent); }
|
|
65
68
|
/* One small button: the word over the speed, so it takes little room. It comes in on Auto-turn's left, so Auto-turn stays under the pointer that just pressed it. The angle's running number makes the room, so the turning bar is never wider than the still one and adds no row; it stays for a screen reader. */
|
|
69
|
+
/* The speed buttons and the Left and Right buttons are one row (two when the room is short) and take the place the speed buttons had. */
|
|
70
|
+
.pacing { display: inline-flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; }
|
|
71
|
+
.dir button { padding: 0 12px; }
|
|
66
72
|
.pace-now { flex-direction: column; gap: 0; padding: 0 10px; line-height: 1.1; }
|
|
67
73
|
.pace-now .lbl { font-size: 0.7em; font-weight: 400; }
|
|
68
74
|
:host([data-turning][data-level="basic"]) .field.angle { flex-basis: 110px; }
|
|
@@ -71,15 +77,17 @@ button[aria-pressed="true"] .i-play, button[aria-pressed="false"] .i-pause { dis
|
|
|
71
77
|
.more { margin-inline-start: auto; }
|
|
72
78
|
/* The classic bar on a phone: two rows to start with (angle, Auto-turn and More; the zoom), more rows when the reader asks for more; labels read only by a screen reader. */
|
|
73
79
|
@media (max-width: 640px) {
|
|
74
|
-
${CLASSIC} .bar { display: grid; grid-template-columns: minmax(0, 1fr) 48px 48px; grid-template-areas: "angle play more" "tilt tilt front" "pace pace pace" "zoom zoom zoom" "pan pan pan"; gap: 0 8px; padding: 2px 8px; }
|
|
80
|
+
${CLASSIC} .bar { display: grid; grid-template-columns: minmax(0, 1fr) 48px 48px; grid-template-areas: "angle play more" "tilt tilt front" "roll roll roll" "axes axes axes" "pace pace pace" "zoom zoom zoom" "pan pan pan"; gap: 0 8px; padding: 2px 8px; }
|
|
75
81
|
${CLASSIC} .field.angle { grid-area: angle; }
|
|
76
82
|
${CLASSIC} .field.tilt { grid-area: tilt; }
|
|
83
|
+
${CLASSIC} .field.roll { grid-area: roll; }
|
|
84
|
+
${CLASSIC} .axes { grid-area: axes; }
|
|
77
85
|
${CLASSIC} .field label, ${CLASSIC} .field output, ${CLASSIC} .play .text, ${CLASSIC} .more .text, ${CLASSIC} .pace::part(label) { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
78
86
|
${CLASSIC} .play, ${CLASSIC} .more { padding: 0; }
|
|
79
87
|
${CLASSIC} .play { grid-area: play; }
|
|
80
88
|
${CLASSIC} .more { grid-area: more; }
|
|
81
89
|
${CLASSIC} .front { grid-area: front; padding: 0 12px; }
|
|
82
|
-
${CLASSIC} .
|
|
90
|
+
${CLASSIC} .pacing { grid-area: pace; justify-self: end; }
|
|
83
91
|
${CLASSIC} .zoom { grid-area: zoom; justify-self: end; flex-wrap: nowrap; }
|
|
84
92
|
${CLASSIC} .zoom button { padding: 0 8px; }
|
|
85
93
|
${CLASSIC} .zoom button.level { padding: 0 6px; }
|
|
@@ -115,13 +123,15 @@ ${H} .zslide, ${V} .zslide, ${C} .zslide { display: flex; }
|
|
|
115
123
|
${H} { container-type: inline-size; }
|
|
116
124
|
${H} .bar, ${V} .bar, ${C} .bar { display: grid; row-gap: 0; align-items: center; }
|
|
117
125
|
${H} .bar > *, ${V} .bar > *, ${C} .zoom > * { margin-block: 2px; }
|
|
118
|
-
${H} .bar { grid-template-columns: minmax(0, 1fr) auto auto auto; grid-template-areas: "angle speed play more" "tilt tilt front front" "pace pace pace pace" "zoom zoom zoom centre" "pan pan pan pan"; column-gap: 8px; padding: 4px 8px; }
|
|
126
|
+
${H} .bar { grid-template-columns: minmax(0, 1fr) auto auto auto; grid-template-areas: "angle speed play more" "tilt tilt front front" "roll roll roll roll" "axes axes axes axes" "pace pace pace pace" "zoom zoom zoom centre" "pan pan pan pan"; column-gap: 8px; padding: 4px 8px; }
|
|
119
127
|
:host([data-layout="horizontal"][data-flat]) .bar { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "zoom centre more" "pan pan pan"; }
|
|
120
128
|
${H} .angle, ${V} .angle, ${C} .angle { grid-area: angle; }
|
|
121
129
|
${H} .tilt, ${V} .tilt, ${C} .tilt { grid-area: tilt; }
|
|
130
|
+
${H} .roll, ${V} .roll, ${C} .roll { grid-area: roll; }
|
|
131
|
+
${H} .axes, ${V} .axes, ${C} .axes { grid-area: axes; }
|
|
122
132
|
${H} .pace-now, ${V} .pace-now { grid-area: speed; }
|
|
123
133
|
${H} .play, ${V} .play, ${C} .play { grid-area: play; }
|
|
124
|
-
${H} .
|
|
134
|
+
${H} .pacing, ${V} .pacing, ${C} .pacing { grid-area: pace; }
|
|
125
135
|
${H} .front, ${V} .front, ${C} .front { grid-area: front; justify-self: start; }
|
|
126
136
|
${H} .zoom, ${V} .zoom { grid-area: zoom; }
|
|
127
137
|
${H} .pan, ${V} .pan, ${C} .pan { grid-area: pan; justify-self: start; }
|
|
@@ -153,7 +163,8 @@ ${H} .zoom button.level, ${V} .zoom button.level, ${C} .zoom button.level { padd
|
|
|
153
163
|
}
|
|
154
164
|
/* Wide enough for the turn, Auto-turn, the zoom with its slider, Centre and More on one row, with the angle slider still a slider. */
|
|
155
165
|
@container (min-width: 1040px) {
|
|
156
|
-
${H} .bar { grid-template-columns: minmax(0, 1fr)
|
|
166
|
+
${H} .bar { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto auto auto auto; grid-template-areas: "angle angle speed play zoom centre" "axes axes axes axes front more" "pace pace pace pace pace pace" "pan pan pan pan pan pan"; column-gap: 12px; }
|
|
167
|
+
${H} .axes { flex-wrap: nowrap; }
|
|
157
168
|
:host([data-layout="horizontal"][data-flat]) .bar { grid-template-columns: minmax(0, 1fr) auto auto; grid-template-areas: "zoom centre more" "pan pan pan"; }
|
|
158
169
|
${H} .zslide { width: 15rem; }
|
|
159
170
|
${H} .zoom { display: grid; grid-auto-flow: column; grid-auto-columns: auto; }
|
|
@@ -162,8 +173,9 @@ ${H} .zoom button.level, ${V} .zoom button.level, ${C} .zoom button.level { padd
|
|
|
162
173
|
|
|
163
174
|
/* Vertical: a column beside the view, its width --kazoku-column-width (16rem). Auto-turn and More at the top, so More stays put when it opens; then the turn, the zoom and its slider. */
|
|
164
175
|
${V} { width: var(--kazoku-column-width, 16rem); max-width: 100%; }
|
|
165
|
-
${V} .bar { grid-template-columns: auto auto minmax(0, 1fr) auto; grid-template-areas: "play speed . more" "angle angle angle angle" "tilt tilt tilt tilt" "pace pace pace pace" "front front front front" "zoom zoom zoom zoom" "centre centre centre centre" "pan pan pan pan"; column-gap: 8px; padding: 6px 8px; }
|
|
176
|
+
${V} .bar { grid-template-columns: auto auto minmax(0, 1fr) auto; grid-template-areas: "play speed . more" "angle angle angle angle" "tilt tilt tilt tilt" "roll roll roll roll" "axes axes axes axes" "pace pace pace pace" "front front front front" "zoom zoom zoom zoom" "centre centre centre centre" "pan pan pan pan"; column-gap: 8px; padding: 6px 8px; }
|
|
166
177
|
${V} .field { flex: none; display: grid; grid-template-columns: minmax(0, 1fr) auto; grid-template-areas: "label value" "slider slider"; gap: 0 8px; }
|
|
178
|
+
${V} .field.tilt, ${V} .field.roll { display: none; }
|
|
167
179
|
${V} .field label { grid-area: label; padding-top: 4px; }
|
|
168
180
|
${V} .field output { grid-area: value; padding-top: 4px; }
|
|
169
181
|
${V} .field input, ${V} .zbox { grid-area: slider; }
|
|
@@ -183,7 +195,7 @@ ${C} { display: block; }
|
|
|
183
195
|
/* Over a card, an open More never reaches past the card's top: the controls scroll inside themselves. */
|
|
184
196
|
:host([data-layout="compact"][overlay]) { display: flex; flex-direction: column; max-height: calc(100% - 16px); }
|
|
185
197
|
:host([data-layout="compact"][overlay]) .bar { min-height: 0; overflow-y: auto; }
|
|
186
|
-
${C} .bar { width: fit-content; max-width: 100%; grid-template-columns: repeat(5, auto); grid-template-areas: "play out level in more" "zs zs zs zs fit" "centre centre centre centre centre" "angle angle angle angle angle" "tilt tilt tilt tilt tilt" "pace pace pace pace pace" "front front front front front" "pan pan pan pan pan"; column-gap: 2px; padding: 2px 4px; border: 1px solid var(--_line); border-radius: 28px; background: var(--_surface); }
|
|
198
|
+
${C} .bar { width: fit-content; max-width: 100%; grid-template-columns: repeat(5, auto); grid-template-areas: "play out level in more" "zs zs zs zs fit" "centre centre centre centre centre" "angle angle angle angle angle" "tilt tilt tilt tilt tilt" "roll roll roll roll roll" "axes axes axes axes axes" "pace pace pace pace pace" "front front front front front" "pan pan pan pan pan"; column-gap: 2px; padding: 2px 4px; border: 1px solid var(--_line); border-radius: 28px; background: var(--_surface); }
|
|
187
199
|
:host([data-layout="compact"][data-level="advanced"]) .bar { border-radius: 20px; padding: 2px 8px 6px; column-gap: 4px; }
|
|
188
200
|
:host([data-layout="compact"][data-flat]) .bar { grid-template-areas: "out level in more ." "zs zs zs fit fit" "centre centre centre centre centre" "pan pan pan pan pan"; }
|
|
189
201
|
${C} .zoom { display: contents; }
|
|
@@ -197,9 +209,9 @@ ${C} .play:hover:not(:disabled), ${C} .more:hover:not(:disabled), ${C} .zoom but
|
|
|
197
209
|
${C} .play .text, ${C} .more .text { ${UNSEEN} }
|
|
198
210
|
${C} .play, ${C} .more { padding: 0; }
|
|
199
211
|
${C} .pace-now { display: none; }
|
|
200
|
-
${C} .
|
|
212
|
+
${C} .pacing { flex-wrap: wrap; }
|
|
201
213
|
${V} .centre, ${C} .centre { justify-self: start; }
|
|
202
214
|
/* The row stays one row: Centre is with Fit, one press of More away. */
|
|
203
215
|
:host([data-layout="compact"][data-level="basic"]) .centre { display: none; }
|
|
204
|
-
:host([data-layout="compact"][data-level="basic"]) .angle, :host([data-layout="compact"][data-level="basic"]) .zslide, :host([data-layout="compact"][data-level="basic"]) [data-b="fit"] { display: none; }
|
|
216
|
+
:host([data-layout="compact"][data-level="basic"]) .angle, :host([data-layout="compact"][data-level="basic"]) .tilt, :host([data-layout="compact"][data-level="basic"]) .roll, :host([data-layout="compact"][data-level="basic"]) .axes, :host([data-layout="compact"][data-level="basic"]) .zslide, :host([data-layout="compact"][data-level="basic"]) [data-b="fit"] { display: none; }
|
|
205
217
|
`;
|
|
@@ -2,6 +2,11 @@ export interface ViewWords {
|
|
|
2
2
|
group: string;
|
|
3
3
|
angle: string;
|
|
4
4
|
tilt: string;
|
|
5
|
+
roll: string;
|
|
6
|
+
/** The name of the button that tumbles on all three axes. */
|
|
7
|
+
tumble: string;
|
|
8
|
+
/** The name of the row that chooses which axis Auto-turn drives. */
|
|
9
|
+
axis: string;
|
|
5
10
|
autoTurn: string;
|
|
6
11
|
front: string;
|
|
7
12
|
zoom: string;
|
|
@@ -23,6 +28,10 @@ export interface ViewWords {
|
|
|
23
28
|
zoomReset: string;
|
|
24
29
|
more: string;
|
|
25
30
|
speed: string;
|
|
31
|
+
/** The name of the pair of buttons that choose which way Auto-turn goes. */
|
|
32
|
+
direction: string;
|
|
33
|
+
turnLeft: string;
|
|
34
|
+
turnRight: string;
|
|
26
35
|
slow: string;
|
|
27
36
|
medium: string;
|
|
28
37
|
fast: string;
|
|
@@ -4,6 +4,9 @@ export const WORDS = {
|
|
|
4
4
|
group: "View controls",
|
|
5
5
|
angle: "Angle",
|
|
6
6
|
tilt: "Tilt",
|
|
7
|
+
roll: "Roll",
|
|
8
|
+
tumble: "All",
|
|
9
|
+
axis: "Auto-turn axis",
|
|
7
10
|
autoTurn: "Auto-turn",
|
|
8
11
|
front: "Front",
|
|
9
12
|
zoom: "Zoom",
|
|
@@ -24,6 +27,9 @@ export const WORDS = {
|
|
|
24
27
|
zoomReset: "back to Fit",
|
|
25
28
|
more: "More",
|
|
26
29
|
speed: "Speed",
|
|
30
|
+
direction: "Turn direction",
|
|
31
|
+
turnLeft: "Left",
|
|
32
|
+
turnRight: "Right",
|
|
27
33
|
slow: "Slow",
|
|
28
34
|
medium: "Medium",
|
|
29
35
|
fast: "Fast",
|
|
@@ -32,6 +38,9 @@ export const WORDS = {
|
|
|
32
38
|
group: "表示の操作",
|
|
33
39
|
angle: "角度",
|
|
34
40
|
tilt: "傾き",
|
|
41
|
+
roll: "ロール",
|
|
42
|
+
tumble: "すべて",
|
|
43
|
+
axis: "自動回転の軸",
|
|
35
44
|
autoTurn: "自動回転",
|
|
36
45
|
front: "正面",
|
|
37
46
|
zoom: "ズーム",
|
|
@@ -52,6 +61,9 @@ export const WORDS = {
|
|
|
52
61
|
zoomReset: "全体に戻す",
|
|
53
62
|
more: "詳細",
|
|
54
63
|
speed: "速さ",
|
|
64
|
+
direction: "回る向き",
|
|
65
|
+
turnLeft: "左へ",
|
|
66
|
+
turnRight: "右へ",
|
|
55
67
|
slow: "ゆっくり",
|
|
56
68
|
medium: "ふつう",
|
|
57
69
|
fast: "はやい",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@johnmorrisdotca/kazoku-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Kazoku UI 家族, the family's shared interface parts as their own versioned package: view controls for any 3D or zoomable view (angle, tilt, auto-turn, zoom about the pointer, pan, touch, address state) a light, dark and auto theme switch, a language picker, choices kept in the address, a save-as-image button and one set of speed buttons (0.5×, 1×, 2× or your own steps) and the \"Put it in your page\" embed panel (a compact picker, sizes, HTML, ES module, React, Vue, Angular, Svelte and iframe tabs, a picture that fits its box, Copy), each a framework-free custom element and a plain controller. Zero dependencies; the shared 3D renderer takes three.js as an optional peer.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"view-controls",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"./dist/language-picker/define.js",
|
|
57
57
|
"./dist/save-image/define.js",
|
|
58
58
|
"./dist/speed-buttons/define.js",
|
|
59
|
-
"./dist/embed-panel/define.js"
|
|
59
|
+
"./dist/embed-panel/define.js",
|
|
60
|
+
"./dist/replay-bar/define.js"
|
|
60
61
|
],
|
|
61
62
|
"main": "./dist/index.js",
|
|
62
63
|
"module": "./dist/index.js",
|
|
@@ -147,6 +148,21 @@
|
|
|
147
148
|
"import": "./dist/speed-buttons/define.js",
|
|
148
149
|
"default": "./dist/speed-buttons/define.js"
|
|
149
150
|
},
|
|
151
|
+
"./replay-bar": {
|
|
152
|
+
"types": "./dist/replay-bar/index.d.ts",
|
|
153
|
+
"import": "./dist/replay-bar/index.js",
|
|
154
|
+
"default": "./dist/replay-bar/index.js"
|
|
155
|
+
},
|
|
156
|
+
"./replay-bar/element": {
|
|
157
|
+
"types": "./dist/replay-bar/element.d.ts",
|
|
158
|
+
"import": "./dist/replay-bar/element.js",
|
|
159
|
+
"default": "./dist/replay-bar/element.js"
|
|
160
|
+
},
|
|
161
|
+
"./replay-bar/define": {
|
|
162
|
+
"types": "./dist/replay-bar/define.d.ts",
|
|
163
|
+
"import": "./dist/replay-bar/define.js",
|
|
164
|
+
"default": "./dist/replay-bar/define.js"
|
|
165
|
+
},
|
|
150
166
|
"./embed-panel": {
|
|
151
167
|
"types": "./dist/embed-panel/index.d.ts",
|
|
152
168
|
"import": "./dist/embed-panel/index.js",
|