@atelic-action/ui 0.8.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -1
- package/package.json +8 -3
- package/src/components/StackedBar.tsx +25 -12
- package/src/components/StarRating.tsx +153 -0
- package/src/components/index.ts +1 -0
- package/src/styles/components.css +130 -116
- package/src/styles/star-rating.css +67 -0
package/README.md
CHANGED
|
@@ -41,7 +41,7 @@ The package sheets sit inside `@layer atelic-ui`, so any rule a site writes outs
|
|
|
41
41
|
| Import | Exports |
|
|
42
42
|
|---|---|
|
|
43
43
|
| `@atelic-action/ui/chrome` | `SiteHeader`, `SiteMenu`, `Footer`, `CreditBar`, `StickyCTABar`, `BrandLockup`, `SkipLink`, and their prop types |
|
|
44
|
-
| `@atelic-action/ui/components` | `NotFound` and the page components (`Checklist`, `DataTable`, `StatRow`, `Callout`, `Collapsible`, `StackedBar`, `ColumnChart`, `ThresholdScale`), with their prop types (see [The Not Found Page](#the-not-found-page) and [Page Components](#page-components)) |
|
|
44
|
+
| `@atelic-action/ui/components` | `NotFound` and the page components (`Checklist`, `DataTable`, `StatRow`, `Callout`, `Collapsible`, `StackedBar`, `ColumnChart`, `ThresholdScale`, `StarRating`), with their prop types (see [The Not Found Page](#the-not-found-page) and [Page Components](#page-components)) |
|
|
45
45
|
| `@atelic-action/ui/email` | The email components, the theme provider, the plain text helpers, and their prop types (see [Email](#email)) |
|
|
46
46
|
| `@atelic-action/ui/email/render` | `renderEmail` and `renderFailureEmail`, the only entry that imports `react-dom/server` |
|
|
47
47
|
| `@atelic-action/ui/tokens` | `Palette`, `atelicPalette`, `Fonts`, `atelicFonts`, `toThemeCSS`, `themeTokenMap` |
|
|
@@ -50,6 +50,7 @@ The package sheets sit inside `@layer atelic-ui`, so any rule a site writes outs
|
|
|
50
50
|
| `@atelic-action/ui/styles/base.css` | Resets, the `.mkt` canvas, typography, and layout helpers |
|
|
51
51
|
| `@atelic-action/ui/styles/chrome.css` | Styles for everything under `chrome` |
|
|
52
52
|
| `@atelic-action/ui/styles/components.css` | Layout defaults for everything under `components` |
|
|
53
|
+
| `@atelic-action/ui/styles/star-rating.css` | `StarRating`'s styles alone, for an app that wears none of the site chrome; `components.css` already imports it |
|
|
53
54
|
|
|
54
55
|
Every component renders from props alone. None reads a config file or a router, so a site maps its own config onto the props in its shell:
|
|
55
56
|
|
|
@@ -261,9 +262,61 @@ The blocks a page composes, each styled in `styles/components.css` inside the `a
|
|
|
261
262
|
| `StackedBar` | One bar split into the parts of a whole, with a key | A part of zero is keyed and not drawn |
|
|
262
263
|
| `ColumnChart` | A bar a period, the periods in groups that carry a label and a total | Months in quarters is the case it was cut for: a quarter reads against the one before it and the same one a year earlier |
|
|
263
264
|
| `ThresholdScale` | Zones on a scale and a pin for each reading | For a number judged against lines, like a load time against good and poor |
|
|
265
|
+
| `StarRating` | A row of stars showing a rating in half star steps, and taking one when given `onChange` | Each star is two hit targets; picking the current rating clears it. Editable, it is one slider to a keyboard and a screen reader; read only, a picture named by the rating. `fillColor` and `outlineColor` take any CSS color, `size` and `max` set the star and the scale |
|
|
266
|
+
|
|
267
|
+
**StarRating outside a site.** It is the one component that does not sit under `.mkt` or read a theme token: its styles are in `styles/star-rating.css`, and its two colors are the `--star-fill` and `--star-outline` custom properties, falling back to the text color and a faint tint of it. An app that installs the package for this component alone imports that one sheet, not `components.css`.
|
|
268
|
+
|
|
269
|
+
**A part's own color.** `StackedBar` takes a `color` on a segment, any CSS color, for parts that are not good or bad (the protein, carbs, and fat of a meal), and it wins over `tone`. `showKey={false}` draws the bar alone for a page that keys the parts its own way; the bar keeps its spoken summary.
|
|
264
270
|
|
|
265
271
|
**Tones.** `good`, `warn`, `bad`, `info`, and `muted` are the one palette the package carries, because good, a warning, and broken read green, amber, and red whatever the brand is. A component sets `data-tone`, and `--tone`, `--tone-fill`, and `--tone-bg` follow for whatever sits inside. A site's theme may restate `--tone-good` and the rest on `.mkt`; `info` and `muted` come from the theme's own primary and neutral tokens.
|
|
266
272
|
|
|
273
|
+
## In an App
|
|
274
|
+
|
|
275
|
+
The page components also work in an app that has its own base styles (Tailwind, shadcn) and wears none of the site chrome. Three things make that so (0.10.0):
|
|
276
|
+
|
|
277
|
+
1. **A second scope.** Every rule in `styles/components.css` answers to `.atelic-ui` as well as `.mkt`. `.atelic-ui` has no resets behind it, so an app puts the class on the region that holds a package component and nothing else in it restyles. Prefer a region to `body`: the components use plain class names (`.stat .num`, `.callout`, `.note`), and an app with markup of its own under those names would have a page wide scope reach it. `body` is right only for an app that has checked it has none, and it is the one way to cover a component rendered in a portal.
|
|
278
|
+
2. **The app imports one sheet**, `@atelic-action/ui/styles/components.css`, and neither `base.css` nor `chrome.css`. An app's own unlayered CSS always wins over the package. With Tailwind, where the app can declare the layer order before Tailwind loads, `@layer theme, base, atelic-ui, components, utilities;` keeps utilities winning over the package too; where it cannot, the package's layer lands last and wins over utilities set on a package element, which only matters to a caller styling one with a utility class.
|
|
279
|
+
3. **The app maps its tokens** onto the ones the components read, in its own stylesheet on the same scope:
|
|
280
|
+
|
|
281
|
+
| Token | What it colors |
|
|
282
|
+
|---|---|
|
|
283
|
+
| `--ink`, `--ink-soft` | Text and quieter text |
|
|
284
|
+
| `--neutral-100`, `--neutral-200`, `--neutral-500` | Hairlines, tracks, and captions |
|
|
285
|
+
| `--surface-alt` | The quiet ground behind a muted tone |
|
|
286
|
+
| `--ui-primary` (falls back to `--primary`), `--primary-bright`, `--primary-tint` | The accent, its brighter fill, and its tint |
|
|
287
|
+
| `--ui-card` (falls back to `--card`) | A raised surface |
|
|
288
|
+
| `--font-sans`, `--font-mono`, `--radius-sm` | Type and the small radius |
|
|
289
|
+
| `--tone-good`, `--tone-warn`, `--tone-bad`, each with `-fill` and `-bg` | Optional: the package defines these, and a dark theme restates them |
|
|
290
|
+
|
|
291
|
+
`--ui-primary` and `--ui-card` exist because an app's own `--primary` and `--card` may hold something else: shadcn keeps bare HSL channels there, which are not a color on their own. A site sets neither and nothing changes.
|
|
292
|
+
|
|
293
|
+
```css
|
|
294
|
+
/* app.css */
|
|
295
|
+
.atelic-ui {
|
|
296
|
+
--ink: hsl(var(--foreground));
|
|
297
|
+
--ink-soft: hsl(var(--muted-foreground));
|
|
298
|
+
--neutral-500: hsl(var(--muted-foreground));
|
|
299
|
+
--neutral-200: hsl(var(--border));
|
|
300
|
+
--neutral-100: hsl(var(--muted));
|
|
301
|
+
--surface-alt: hsl(var(--muted));
|
|
302
|
+
--ui-primary: hsl(var(--brand));
|
|
303
|
+
--ui-card: hsl(var(--card));
|
|
304
|
+
}
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`StarRating` needs none of this: its sheet is unscoped and reads no token. The chrome (`SiteHeader`, `Footer`, and the rest) and `NotFound` remain site shaped and are not meant for a signed in app.
|
|
308
|
+
|
|
309
|
+
## The Gallery
|
|
310
|
+
|
|
311
|
+
`gallery/` is one static page, The Components, that shows every page component on its own with made up data for a fictional bakery, wearing the practice's own palette and type. It lives at [ui.atelic.me](https://ui.atelic.me), noindexed and disallowed in its `robots.txt`, since it is for the people building with the package and never search inventory. It imports the components and stylesheets from `src/` by relative path rather than from npm, so it always shows the code in the repo, and it reads the package name and version from `package.json` at build time. It sits outside `src/`, so it is never published.
|
|
312
|
+
|
|
313
|
+
```bash
|
|
314
|
+
bun run gallery:dev # a dev server on the repo's own source
|
|
315
|
+
bun run gallery:build # the static page, written to gallery/dist
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
`vercel.json` builds it the same way and serves `gallery/dist`. Typecheck and lint cover it, and CI builds it, so a component change that breaks the gallery fails CI. **A new page component gets a gallery section in the same change**, with each of its meaningfully different forms shown.
|
|
319
|
+
|
|
267
320
|
## Releasing
|
|
268
321
|
|
|
269
322
|
1. Bump `version` in `package.json` and merge it to `main`: patch for a fix, minor for a new component or prop, major for a breaking prop or class rename.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atelic-action/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Shared UI for the Atelic templates: site chrome, base styles, and the component library they install",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
@@ -28,13 +28,16 @@
|
|
|
28
28
|
"./styles/base.css": "./src/styles/base.css",
|
|
29
29
|
"./styles/chrome.css": "./src/styles/chrome.css",
|
|
30
30
|
"./styles/components.css": "./src/styles/components.css",
|
|
31
|
+
"./styles/star-rating.css": "./src/styles/star-rating.css",
|
|
31
32
|
"./package.json": "./package.json"
|
|
32
33
|
},
|
|
33
34
|
"scripts": {
|
|
34
35
|
"lint": "biome check .",
|
|
35
36
|
"lint:fix": "biome check . --write",
|
|
36
|
-
"typecheck": "tsc --noEmit",
|
|
37
|
-
"test:run": "vitest run"
|
|
37
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p gallery",
|
|
38
|
+
"test:run": "vitest run",
|
|
39
|
+
"gallery:dev": "vite gallery",
|
|
40
|
+
"gallery:build": "vite build gallery"
|
|
38
41
|
},
|
|
39
42
|
"peerDependencies": {
|
|
40
43
|
"lucide-react": "^1.23.0",
|
|
@@ -47,11 +50,13 @@
|
|
|
47
50
|
"@testing-library/react": "^16.0.1",
|
|
48
51
|
"@types/react": "^19.0.0",
|
|
49
52
|
"@types/react-dom": "^19.0.0",
|
|
53
|
+
"@vitejs/plugin-react": "^6.1.1",
|
|
50
54
|
"jsdom": "^25.0.1",
|
|
51
55
|
"lucide-react": "^1.23.0",
|
|
52
56
|
"react": "^19.0.0",
|
|
53
57
|
"react-dom": "^19.0.0",
|
|
54
58
|
"typescript": "^5.6.3",
|
|
59
|
+
"vite": "^8.3.2",
|
|
55
60
|
"vitest": "^4.0.0"
|
|
56
61
|
},
|
|
57
62
|
"trustedDependencies": [
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
1
2
|
import type { Tone } from "./tone";
|
|
2
3
|
|
|
3
4
|
export interface StackedBarSegment {
|
|
@@ -5,16 +6,26 @@ export interface StackedBarSegment {
|
|
|
5
6
|
value: number;
|
|
6
7
|
/** The figure as printed, e.g. "1,158"; defaults to the value with separators. */
|
|
7
8
|
display?: string;
|
|
8
|
-
|
|
9
|
+
/** How the part reads. Leave it out when `color` says it instead. */
|
|
10
|
+
tone?: Tone;
|
|
11
|
+
/** A color of the part's own, any CSS color, for parts that are not good or bad. It wins over `tone`. */
|
|
12
|
+
color?: string;
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
export interface StackedBarProps {
|
|
12
16
|
segments: StackedBarSegment[];
|
|
17
|
+
/** False draws the bar alone, for a page that keys the parts its own way. The bar keeps its spoken summary. */
|
|
18
|
+
showKey?: boolean;
|
|
13
19
|
className?: string;
|
|
14
20
|
}
|
|
15
21
|
|
|
22
|
+
/** A part's own color, set as the fill its bar and its swatch both read. */
|
|
23
|
+
function fill(segment: StackedBarSegment): CSSProperties | undefined {
|
|
24
|
+
return segment.color ? ({ "--tone-fill": segment.color } as CSSProperties) : undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
16
27
|
/** One bar split into the parts of a whole, with a key naming each part and its figure. */
|
|
17
|
-
export function StackedBar({ segments, className }: StackedBarProps) {
|
|
28
|
+
export function StackedBar({ segments, showKey = true, className }: StackedBarProps) {
|
|
18
29
|
const total = segments.reduce((sum, segment) => sum + segment.value, 0) || 1;
|
|
19
30
|
const figure = (segment: StackedBarSegment) =>
|
|
20
31
|
segment.display ?? segment.value.toLocaleString("en-US");
|
|
@@ -28,19 +39,21 @@ export function StackedBar({ segments, className }: StackedBarProps) {
|
|
|
28
39
|
<span
|
|
29
40
|
key={segment.label}
|
|
30
41
|
data-tone={segment.tone}
|
|
31
|
-
style={{ flexGrow: segment.value / total }}
|
|
42
|
+
style={{ flexGrow: segment.value / total, ...fill(segment) }}
|
|
32
43
|
/>
|
|
33
44
|
))}
|
|
34
45
|
</div>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
{showKey && (
|
|
47
|
+
<ul className="chart-key">
|
|
48
|
+
{segments.map((segment) => (
|
|
49
|
+
<li key={segment.label} data-tone={segment.tone} style={fill(segment)}>
|
|
50
|
+
<span className="chart-swatch" aria-hidden="true" />
|
|
51
|
+
<b>{figure(segment)}</b>
|
|
52
|
+
<span>{segment.label}</span>
|
|
53
|
+
</li>
|
|
54
|
+
))}
|
|
55
|
+
</ul>
|
|
56
|
+
)}
|
|
44
57
|
</div>
|
|
45
58
|
);
|
|
46
59
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Star } from "lucide-react";
|
|
2
|
+
import { type CSSProperties, type KeyboardEvent, useState } from "react";
|
|
3
|
+
|
|
4
|
+
const STEP = 0.5;
|
|
5
|
+
|
|
6
|
+
export interface StarRatingProps {
|
|
7
|
+
/** The rating, in half star steps from 0.5 up to `max`; null is unrated. */
|
|
8
|
+
value: number | null;
|
|
9
|
+
/** Called with the new rating, or null to clear it. Leave it out for a read only row. */
|
|
10
|
+
onChange?: (value: number | null) => void;
|
|
11
|
+
/** How many stars the scale runs to. */
|
|
12
|
+
max?: number;
|
|
13
|
+
/** The side of one star, in pixels. */
|
|
14
|
+
size?: number;
|
|
15
|
+
/** The color of a filled star: any CSS color. Defaults to the text color around it. */
|
|
16
|
+
fillColor?: string;
|
|
17
|
+
/** The color of a star's outline: any CSS color. Defaults to a faint tint of the text color. */
|
|
18
|
+
outlineColor?: string;
|
|
19
|
+
/** What a screen reader calls the row. */
|
|
20
|
+
label?: string;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** "4.5 stars", "1 star", "Not rated": the rating as a person would say it. */
|
|
25
|
+
export function describeStarRating(value: number | null): string {
|
|
26
|
+
if (value === null) return "Not rated";
|
|
27
|
+
return `${value} ${value === 1 ? "star" : "stars"}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* A row of stars that shows a rating in half star steps and, given `onChange`,
|
|
32
|
+
* takes one. Each star is two hit targets, its left half and its right, so a
|
|
33
|
+
* pointer lands on 3.5 or 4 without a second control. Picking the rating
|
|
34
|
+
* already set clears it, which is the only way back to unrated and needs no
|
|
35
|
+
* button of its own.
|
|
36
|
+
*
|
|
37
|
+
* To a keyboard and a screen reader an editable row is one slider: the arrows
|
|
38
|
+
* move half a star, Home clears, End is the top of the scale. The halves stay
|
|
39
|
+
* out of the tab order, so the row is one stop rather than ten. Without
|
|
40
|
+
* `onChange` it is a picture with the rating as its name.
|
|
41
|
+
*
|
|
42
|
+
* The two colors are the `--star-fill` and `--star-outline` custom properties.
|
|
43
|
+
* `fillColor` and `outlineColor` set them on this row; a site may also set
|
|
44
|
+
* them in its own CSS for every row at once.
|
|
45
|
+
*/
|
|
46
|
+
export function StarRating({
|
|
47
|
+
value,
|
|
48
|
+
onChange,
|
|
49
|
+
max = 5,
|
|
50
|
+
size = 24,
|
|
51
|
+
fillColor,
|
|
52
|
+
outlineColor,
|
|
53
|
+
label = "Rating",
|
|
54
|
+
className,
|
|
55
|
+
}: StarRatingProps) {
|
|
56
|
+
const [hover, setHover] = useState<number | null>(null);
|
|
57
|
+
const shown = hover ?? value ?? 0;
|
|
58
|
+
|
|
59
|
+
const style = {
|
|
60
|
+
"--star-size": `${size}px`,
|
|
61
|
+
...(fillColor ? { "--star-fill": fillColor } : {}),
|
|
62
|
+
...(outlineColor ? { "--star-outline": outlineColor } : {}),
|
|
63
|
+
} as CSSProperties;
|
|
64
|
+
const classes = ["star-rating", className].filter(Boolean).join(" ");
|
|
65
|
+
|
|
66
|
+
const stars = Array.from({ length: max }, (_, i) => i + 1).map((star) => {
|
|
67
|
+
const fill = shown >= star ? "100%" : shown >= star - STEP ? "50%" : "0%";
|
|
68
|
+
return (
|
|
69
|
+
<span key={star} className="star-rating-star">
|
|
70
|
+
<Star aria-hidden="true" size={size} className="star-rating-outline" />
|
|
71
|
+
<span aria-hidden="true" className="star-rating-fill" style={{ width: fill }}>
|
|
72
|
+
<Star size={size} />
|
|
73
|
+
</span>
|
|
74
|
+
{onChange &&
|
|
75
|
+
[star - STEP, star].map((rating, half) => (
|
|
76
|
+
<button
|
|
77
|
+
key={rating}
|
|
78
|
+
type="button"
|
|
79
|
+
tabIndex={-1}
|
|
80
|
+
aria-label={describeStarRating(rating)}
|
|
81
|
+
className={`star-rating-hit ${half === 0 ? "is-left" : "is-right"}`}
|
|
82
|
+
onMouseEnter={() => setHover(rating)}
|
|
83
|
+
onClick={() => {
|
|
84
|
+
// Drop the preview with the pick: the pointer is still on this
|
|
85
|
+
// half, and a preview left up would hide a clear behind the
|
|
86
|
+
// very stars it just emptied.
|
|
87
|
+
setHover(null);
|
|
88
|
+
onChange(rating === value ? null : rating);
|
|
89
|
+
}}
|
|
90
|
+
/>
|
|
91
|
+
))}
|
|
92
|
+
</span>
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (!onChange) {
|
|
97
|
+
return (
|
|
98
|
+
<span
|
|
99
|
+
role="img"
|
|
100
|
+
aria-label={`${label}: ${describeStarRating(value)}`}
|
|
101
|
+
className={classes}
|
|
102
|
+
style={style}
|
|
103
|
+
>
|
|
104
|
+
{stars}
|
|
105
|
+
</span>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const set = (next: number | null) => {
|
|
110
|
+
if (next !== value) onChange(next);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
114
|
+
const current = value ?? 0;
|
|
115
|
+
switch (event.key) {
|
|
116
|
+
case "ArrowRight":
|
|
117
|
+
case "ArrowUp":
|
|
118
|
+
set(Math.min(max, current + STEP));
|
|
119
|
+
break;
|
|
120
|
+
case "ArrowLeft":
|
|
121
|
+
case "ArrowDown":
|
|
122
|
+
set(current - STEP > 0 ? current - STEP : null);
|
|
123
|
+
break;
|
|
124
|
+
case "Home":
|
|
125
|
+
set(null);
|
|
126
|
+
break;
|
|
127
|
+
case "End":
|
|
128
|
+
set(max);
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<span
|
|
138
|
+
role="slider"
|
|
139
|
+
tabIndex={0}
|
|
140
|
+
aria-label={label}
|
|
141
|
+
aria-valuemin={0}
|
|
142
|
+
aria-valuemax={max}
|
|
143
|
+
aria-valuenow={value ?? 0}
|
|
144
|
+
aria-valuetext={describeStarRating(value)}
|
|
145
|
+
className={`${classes} is-editable`}
|
|
146
|
+
style={style}
|
|
147
|
+
onKeyDown={onKeyDown}
|
|
148
|
+
onMouseLeave={() => setHover(null)}
|
|
149
|
+
>
|
|
150
|
+
{stars}
|
|
151
|
+
</span>
|
|
152
|
+
);
|
|
153
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ export {
|
|
|
18
18
|
} from "./DataTable";
|
|
19
19
|
export { NotFound, type NotFoundProps } from "./NotFound";
|
|
20
20
|
export { StackedBar, type StackedBarProps, type StackedBarSegment } from "./StackedBar";
|
|
21
|
+
export { describeStarRating, StarRating, type StarRatingProps } from "./StarRating";
|
|
21
22
|
export {
|
|
22
23
|
type StatDelta,
|
|
23
24
|
type StatDirection,
|
|
@@ -1,20 +1,34 @@
|
|
|
1
|
+
@import "./star-rating.css";
|
|
2
|
+
|
|
1
3
|
/* Layout defaults for the components under src/components. A site's own
|
|
2
4
|
classes (.page-hero, .eyebrow, .lead, .btn) arrive unlayered and win, so
|
|
3
|
-
these only fill in where a site does not style them.
|
|
5
|
+
these only fill in where a site does not style them.
|
|
6
|
+
|
|
7
|
+
Every rule answers to either of two scopes. `.mkt` is a site's canvas,
|
|
8
|
+
which base.css also resets headings, links, and buttons under.
|
|
9
|
+
`.atelic-ui` is the same scope with no resets behind it, for an app that
|
|
10
|
+
has its own base (Tailwind, shadcn) and wants only the components. The
|
|
11
|
+
two weigh the same, so nothing a site already overrides changes.
|
|
12
|
+
|
|
13
|
+
Two tokens are read through names of the package's own first:
|
|
14
|
+
`--ui-primary` and `--ui-card`, each falling back to `--primary` and
|
|
15
|
+
`--card`. A site sets neither and nothing changes. An app whose own
|
|
16
|
+
`--primary` and `--card` hold something else (shadcn keeps bare HSL
|
|
17
|
+
channels there) sets the `--ui-` names and leaves its own alone. */
|
|
4
18
|
@layer atelic-ui {
|
|
5
|
-
.mkt .not-found-hero {
|
|
19
|
+
:is(.mkt, .atelic-ui) .not-found-hero {
|
|
6
20
|
padding-top: calc(var(--nav-height, 72px) + clamp(40px, 7vw, 86px));
|
|
7
21
|
padding-bottom: clamp(28px, 5vw, 60px);
|
|
8
22
|
}
|
|
9
|
-
.mkt .not-found-hero h1 {
|
|
23
|
+
:is(.mkt, .atelic-ui) .not-found-hero h1 {
|
|
10
24
|
margin: 16px 0 0;
|
|
11
25
|
}
|
|
12
|
-
.mkt .not-found-hero .lead {
|
|
26
|
+
:is(.mkt, .atelic-ui) .not-found-hero .lead {
|
|
13
27
|
margin-top: 22px;
|
|
14
28
|
max-width: 56ch;
|
|
15
29
|
}
|
|
16
|
-
.mkt .not-found-links .cta-row,
|
|
17
|
-
.mkt .not-found-closing .cta-row {
|
|
30
|
+
:is(.mkt, .atelic-ui) .not-found-links .cta-row,
|
|
31
|
+
:is(.mkt, .atelic-ui) .not-found-closing .cta-row {
|
|
18
32
|
margin-top: 22px;
|
|
19
33
|
}
|
|
20
34
|
|
|
@@ -22,7 +36,7 @@
|
|
|
22
36
|
warning, and broken read green, amber, and red whatever the brand is.
|
|
23
37
|
A site's theme may restate any of these on .mkt. A component sets
|
|
24
38
|
data-tone, and --tone, --tone-fill, and --tone-bg follow. ---- */
|
|
25
|
-
.mkt {
|
|
39
|
+
:is(.mkt, .atelic-ui) {
|
|
26
40
|
--tone-good: #1b7f3b;
|
|
27
41
|
--tone-good-fill: #37a35c;
|
|
28
42
|
--tone-good-bg: #eaf6ee;
|
|
@@ -33,27 +47,27 @@
|
|
|
33
47
|
--tone-bad-fill: #d9483f;
|
|
34
48
|
--tone-bad-bg: #fdecea;
|
|
35
49
|
}
|
|
36
|
-
.mkt [data-tone="good"] {
|
|
50
|
+
:is(.mkt, .atelic-ui) [data-tone="good"] {
|
|
37
51
|
--tone: var(--tone-good);
|
|
38
52
|
--tone-fill: var(--tone-good-fill);
|
|
39
53
|
--tone-bg: var(--tone-good-bg);
|
|
40
54
|
}
|
|
41
|
-
.mkt [data-tone="warn"] {
|
|
55
|
+
:is(.mkt, .atelic-ui) [data-tone="warn"] {
|
|
42
56
|
--tone: var(--tone-warn);
|
|
43
57
|
--tone-fill: var(--tone-warn-fill);
|
|
44
58
|
--tone-bg: var(--tone-warn-bg);
|
|
45
59
|
}
|
|
46
|
-
.mkt [data-tone="bad"] {
|
|
60
|
+
:is(.mkt, .atelic-ui) [data-tone="bad"] {
|
|
47
61
|
--tone: var(--tone-bad);
|
|
48
62
|
--tone-fill: var(--tone-bad-fill);
|
|
49
63
|
--tone-bg: var(--tone-bad-bg);
|
|
50
64
|
}
|
|
51
|
-
.mkt [data-tone="info"] {
|
|
52
|
-
--tone: var(--primary);
|
|
65
|
+
:is(.mkt, .atelic-ui) [data-tone="info"] {
|
|
66
|
+
--tone: var(--ui-primary, var(--primary));
|
|
53
67
|
--tone-fill: var(--primary-bright);
|
|
54
68
|
--tone-bg: var(--primary-tint);
|
|
55
69
|
}
|
|
56
|
-
.mkt [data-tone="muted"] {
|
|
70
|
+
:is(.mkt, .atelic-ui) [data-tone="muted"] {
|
|
57
71
|
--tone: var(--neutral-500);
|
|
58
72
|
--tone-fill: var(--neutral-200);
|
|
59
73
|
--tone-bg: var(--surface-alt);
|
|
@@ -61,50 +75,50 @@
|
|
|
61
75
|
|
|
62
76
|
/* ---- StatRow: large figures, each over what it counts, its period,
|
|
63
77
|
and its comparisons ---- */
|
|
64
|
-
.mkt .stat-row {
|
|
78
|
+
:is(.mkt, .atelic-ui) .stat-row {
|
|
65
79
|
display: grid;
|
|
66
80
|
gap: 24px;
|
|
67
81
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
68
82
|
}
|
|
69
83
|
@media (min-width: 760px) {
|
|
70
|
-
.mkt .stat-row {
|
|
84
|
+
:is(.mkt, .atelic-ui) .stat-row {
|
|
71
85
|
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
|
-
.mkt .stat-row.is-center .stat {
|
|
88
|
+
:is(.mkt, .atelic-ui) .stat-row.is-center .stat {
|
|
75
89
|
text-align: center;
|
|
76
90
|
}
|
|
77
|
-
.mkt .stat .num {
|
|
91
|
+
:is(.mkt, .atelic-ui) .stat .num {
|
|
78
92
|
display: flex;
|
|
79
93
|
align-items: center;
|
|
80
94
|
gap: 0.12em;
|
|
81
95
|
font-size: clamp(2.2rem, 5vw, 3rem);
|
|
82
96
|
font-weight: 800;
|
|
83
97
|
letter-spacing: -0.03em;
|
|
84
|
-
color: var(--tone, var(--primary));
|
|
98
|
+
color: var(--tone, var(--ui-primary, var(--primary)));
|
|
85
99
|
line-height: 1;
|
|
86
100
|
font-variant-numeric: tabular-nums;
|
|
87
101
|
}
|
|
88
|
-
.mkt .stat-row.is-center .stat .num {
|
|
102
|
+
:is(.mkt, .atelic-ui) .stat-row.is-center .stat .num {
|
|
89
103
|
justify-content: center;
|
|
90
104
|
}
|
|
91
|
-
.mkt .stat-trend {
|
|
105
|
+
:is(.mkt, .atelic-ui) .stat-trend {
|
|
92
106
|
width: 0.8em;
|
|
93
107
|
height: 0.8em;
|
|
94
108
|
flex: none;
|
|
95
109
|
}
|
|
96
|
-
.mkt .stat .lbl {
|
|
110
|
+
:is(.mkt, .atelic-ui) .stat .lbl {
|
|
97
111
|
font-size: 0.95rem;
|
|
98
112
|
font-weight: 650;
|
|
99
113
|
color: var(--ink);
|
|
100
114
|
margin-top: 8px;
|
|
101
115
|
}
|
|
102
|
-
.mkt .stat-period {
|
|
116
|
+
:is(.mkt, .atelic-ui) .stat-period {
|
|
103
117
|
margin-left: 0.5em;
|
|
104
118
|
font-weight: 400;
|
|
105
119
|
color: var(--neutral-500);
|
|
106
120
|
}
|
|
107
|
-
.mkt .stat-delta {
|
|
121
|
+
:is(.mkt, .atelic-ui) .stat-delta {
|
|
108
122
|
display: flex;
|
|
109
123
|
flex-wrap: wrap;
|
|
110
124
|
align-items: baseline;
|
|
@@ -115,27 +129,27 @@
|
|
|
115
129
|
color: var(--neutral-500);
|
|
116
130
|
font-variant-numeric: tabular-nums;
|
|
117
131
|
}
|
|
118
|
-
.mkt .stat-delta-change {
|
|
132
|
+
:is(.mkt, .atelic-ui) .stat-delta-change {
|
|
119
133
|
display: inline-flex;
|
|
120
134
|
align-items: center;
|
|
121
135
|
gap: 2px;
|
|
122
136
|
font-weight: 700;
|
|
123
137
|
color: var(--tone);
|
|
124
138
|
}
|
|
125
|
-
.mkt .stat-delta-change svg {
|
|
139
|
+
:is(.mkt, .atelic-ui) .stat-delta-change svg {
|
|
126
140
|
align-self: center;
|
|
127
141
|
}
|
|
128
|
-
.mkt .stat-delta-basis {
|
|
142
|
+
:is(.mkt, .atelic-ui) .stat-delta-basis {
|
|
129
143
|
text-decoration: none;
|
|
130
144
|
}
|
|
131
|
-
.mkt .stat .note {
|
|
145
|
+
:is(.mkt, .atelic-ui) .stat .note {
|
|
132
146
|
font-size: 0.8rem;
|
|
133
147
|
color: var(--neutral-500);
|
|
134
148
|
margin-top: 4px;
|
|
135
149
|
}
|
|
136
150
|
|
|
137
151
|
/* ---- Collapsible: a labeled line that opens to the detail behind it ---- */
|
|
138
|
-
.mkt .collapsible > summary {
|
|
152
|
+
:is(.mkt, .atelic-ui) .collapsible > summary {
|
|
139
153
|
display: flex;
|
|
140
154
|
align-items: center;
|
|
141
155
|
justify-content: space-between;
|
|
@@ -143,77 +157,77 @@
|
|
|
143
157
|
cursor: pointer;
|
|
144
158
|
list-style: none;
|
|
145
159
|
}
|
|
146
|
-
.mkt .collapsible > summary::-webkit-details-marker {
|
|
160
|
+
:is(.mkt, .atelic-ui) .collapsible > summary::-webkit-details-marker {
|
|
147
161
|
display: none;
|
|
148
162
|
}
|
|
149
|
-
.mkt .collapsible-text {
|
|
163
|
+
:is(.mkt, .atelic-ui) .collapsible-text {
|
|
150
164
|
display: flex;
|
|
151
165
|
flex-direction: column;
|
|
152
166
|
gap: 2px;
|
|
153
167
|
min-width: 0;
|
|
154
168
|
}
|
|
155
|
-
.mkt .collapsible-label {
|
|
169
|
+
:is(.mkt, .atelic-ui) .collapsible-label {
|
|
156
170
|
font-weight: 650;
|
|
157
171
|
color: var(--ink);
|
|
158
172
|
}
|
|
159
|
-
.mkt .collapsible-summary {
|
|
173
|
+
:is(.mkt, .atelic-ui) .collapsible-summary {
|
|
160
174
|
color: var(--ink-soft);
|
|
161
175
|
text-wrap: pretty;
|
|
162
176
|
}
|
|
163
|
-
.mkt .collapsible > summary:hover .collapsible-label {
|
|
164
|
-
color: var(--primary);
|
|
177
|
+
:is(.mkt, .atelic-ui) .collapsible > summary:hover .collapsible-label {
|
|
178
|
+
color: var(--ui-primary, var(--primary));
|
|
165
179
|
}
|
|
166
|
-
.mkt .collapsible-chevron {
|
|
180
|
+
:is(.mkt, .atelic-ui) .collapsible-chevron {
|
|
167
181
|
flex: none;
|
|
168
182
|
color: var(--neutral-500);
|
|
169
183
|
transition: transform 0.15s;
|
|
170
184
|
}
|
|
171
|
-
.mkt .collapsible[open] > summary .collapsible-chevron {
|
|
185
|
+
:is(.mkt, .atelic-ui) .collapsible[open] > summary .collapsible-chevron {
|
|
172
186
|
transform: rotate(180deg);
|
|
173
187
|
}
|
|
174
|
-
.mkt .collapsible-body {
|
|
188
|
+
:is(.mkt, .atelic-ui) .collapsible-body {
|
|
175
189
|
margin-top: 12px;
|
|
176
190
|
}
|
|
177
191
|
@media (prefers-reduced-motion: reduce) {
|
|
178
|
-
.mkt .collapsible-chevron {
|
|
192
|
+
:is(.mkt, .atelic-ui) .collapsible-chevron {
|
|
179
193
|
transition: none;
|
|
180
194
|
}
|
|
181
195
|
}
|
|
182
196
|
/* Paper cannot be clicked, so a printed collapsible is open. */
|
|
183
197
|
@media print {
|
|
184
|
-
.mkt .collapsible::details-content {
|
|
198
|
+
:is(.mkt, .atelic-ui) .collapsible::details-content {
|
|
185
199
|
content-visibility: visible;
|
|
186
200
|
display: block;
|
|
187
201
|
}
|
|
188
|
-
.mkt .collapsible-chevron {
|
|
202
|
+
:is(.mkt, .atelic-ui) .collapsible-chevron {
|
|
189
203
|
display: none;
|
|
190
204
|
}
|
|
191
205
|
}
|
|
192
206
|
|
|
193
207
|
/* ---- Callout: an icon in its tone beside the title, the explainer
|
|
194
208
|
under it; boxed, it sits apart on a tinted ground ---- */
|
|
195
|
-
.mkt .callout {
|
|
209
|
+
:is(.mkt, .atelic-ui) .callout {
|
|
196
210
|
display: grid;
|
|
197
211
|
grid-template-columns: auto minmax(0, 1fr);
|
|
198
212
|
gap: 10px;
|
|
199
213
|
align-items: start;
|
|
200
214
|
}
|
|
201
|
-
.mkt .callout-icon {
|
|
215
|
+
:is(.mkt, .atelic-ui) .callout-icon {
|
|
202
216
|
margin-top: 0.15em;
|
|
203
217
|
color: var(--tone);
|
|
204
218
|
flex: none;
|
|
205
219
|
}
|
|
206
|
-
.mkt .callout-title {
|
|
220
|
+
:is(.mkt, .atelic-ui) .callout-title {
|
|
207
221
|
font-weight: 650;
|
|
208
222
|
color: var(--ink);
|
|
209
223
|
text-wrap: pretty;
|
|
210
224
|
}
|
|
211
|
-
.mkt .callout-body {
|
|
225
|
+
:is(.mkt, .atelic-ui) .callout-body {
|
|
212
226
|
margin-top: 2px;
|
|
213
227
|
color: var(--ink-soft);
|
|
214
228
|
text-wrap: pretty;
|
|
215
229
|
}
|
|
216
|
-
.mkt .callout.is-box {
|
|
230
|
+
:is(.mkt, .atelic-ui) .callout.is-box {
|
|
217
231
|
padding: 14px 16px;
|
|
218
232
|
border-radius: var(--radius-sm);
|
|
219
233
|
background: var(--tone-bg);
|
|
@@ -221,7 +235,7 @@
|
|
|
221
235
|
}
|
|
222
236
|
|
|
223
237
|
/* ---- Checklist: checks passed or failed, two columns when there is room ---- */
|
|
224
|
-
.mkt .checklist {
|
|
238
|
+
:is(.mkt, .atelic-ui) .checklist {
|
|
225
239
|
list-style: none;
|
|
226
240
|
margin: 0;
|
|
227
241
|
padding: 0;
|
|
@@ -230,36 +244,36 @@
|
|
|
230
244
|
gap: 12px 28px;
|
|
231
245
|
}
|
|
232
246
|
@media print, (min-width: 721px) {
|
|
233
|
-
.mkt .checklist {
|
|
247
|
+
:is(.mkt, .atelic-ui) .checklist {
|
|
234
248
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
235
249
|
}
|
|
236
250
|
}
|
|
237
|
-
.mkt .checklist li {
|
|
251
|
+
:is(.mkt, .atelic-ui) .checklist li {
|
|
238
252
|
display: grid;
|
|
239
253
|
grid-template-columns: auto minmax(0, 1fr);
|
|
240
254
|
gap: 10px;
|
|
241
255
|
align-items: start;
|
|
242
256
|
line-height: 1.4;
|
|
243
257
|
}
|
|
244
|
-
.mkt .checklist li > svg {
|
|
258
|
+
:is(.mkt, .atelic-ui) .checklist li > svg {
|
|
245
259
|
margin-top: 0.1em;
|
|
246
260
|
color: var(--tone);
|
|
247
261
|
}
|
|
248
|
-
.mkt .checklist-label {
|
|
262
|
+
:is(.mkt, .atelic-ui) .checklist-label {
|
|
249
263
|
display: block;
|
|
250
264
|
font-weight: 600;
|
|
251
265
|
color: var(--ink);
|
|
252
266
|
}
|
|
253
|
-
.mkt .checklist li[data-tone="bad"] .checklist-label {
|
|
267
|
+
:is(.mkt, .atelic-ui) .checklist li[data-tone="bad"] .checklist-label {
|
|
254
268
|
color: var(--tone);
|
|
255
269
|
}
|
|
256
|
-
.mkt .checklist-detail {
|
|
270
|
+
:is(.mkt, .atelic-ui) .checklist-detail {
|
|
257
271
|
display: block;
|
|
258
272
|
color: var(--ink-soft);
|
|
259
273
|
}
|
|
260
274
|
|
|
261
275
|
/* ---- The charts' shared key: a swatch, a figure, a label ---- */
|
|
262
|
-
.mkt .chart-key {
|
|
276
|
+
:is(.mkt, .atelic-ui) .chart-key {
|
|
263
277
|
list-style: none;
|
|
264
278
|
margin: 10px 0 0;
|
|
265
279
|
padding: 0;
|
|
@@ -269,17 +283,17 @@
|
|
|
269
283
|
font-size: 0.82rem;
|
|
270
284
|
color: var(--ink-soft);
|
|
271
285
|
}
|
|
272
|
-
.mkt .chart-key li {
|
|
286
|
+
:is(.mkt, .atelic-ui) .chart-key li {
|
|
273
287
|
display: inline-flex;
|
|
274
288
|
align-items: baseline;
|
|
275
289
|
gap: 7px;
|
|
276
290
|
}
|
|
277
|
-
.mkt .chart-key b {
|
|
291
|
+
:is(.mkt, .atelic-ui) .chart-key b {
|
|
278
292
|
font-weight: 650;
|
|
279
293
|
color: var(--ink);
|
|
280
294
|
font-variant-numeric: tabular-nums;
|
|
281
295
|
}
|
|
282
|
-
.mkt .chart-swatch {
|
|
296
|
+
:is(.mkt, .atelic-ui) .chart-swatch {
|
|
283
297
|
width: 10px;
|
|
284
298
|
height: 10px;
|
|
285
299
|
border-radius: 3px;
|
|
@@ -289,35 +303,35 @@
|
|
|
289
303
|
}
|
|
290
304
|
|
|
291
305
|
/* ---- StackedBar: one bar, the parts of a whole ---- */
|
|
292
|
-
.mkt .stacked-bar-track {
|
|
306
|
+
:is(.mkt, .atelic-ui) .stacked-bar-track {
|
|
293
307
|
display: flex;
|
|
294
308
|
gap: 2px;
|
|
295
309
|
height: 16px;
|
|
296
310
|
border-radius: 999px;
|
|
297
311
|
overflow: hidden;
|
|
298
312
|
}
|
|
299
|
-
.mkt .stacked-bar-track span {
|
|
313
|
+
:is(.mkt, .atelic-ui) .stacked-bar-track span {
|
|
300
314
|
background: var(--tone-fill);
|
|
301
315
|
min-width: 4px;
|
|
302
316
|
}
|
|
303
317
|
|
|
304
318
|
/* ---- ColumnChart: a bar a period, the periods in labeled groups ---- */
|
|
305
|
-
.mkt .column-chart-plot {
|
|
319
|
+
:is(.mkt, .atelic-ui) .column-chart-plot {
|
|
306
320
|
display: flex;
|
|
307
321
|
align-items: flex-end;
|
|
308
322
|
gap: 12px;
|
|
309
323
|
min-width: 0;
|
|
310
324
|
}
|
|
311
|
-
.mkt .column-chart-group {
|
|
325
|
+
:is(.mkt, .atelic-ui) .column-chart-group {
|
|
312
326
|
flex: 1 1 0;
|
|
313
327
|
min-width: 0;
|
|
314
328
|
}
|
|
315
|
-
.mkt .column-chart-bars {
|
|
329
|
+
:is(.mkt, .atelic-ui) .column-chart-bars {
|
|
316
330
|
display: flex;
|
|
317
331
|
align-items: flex-end;
|
|
318
332
|
gap: 3px;
|
|
319
333
|
}
|
|
320
|
-
.mkt .column-chart-col {
|
|
334
|
+
:is(.mkt, .atelic-ui) .column-chart-col {
|
|
321
335
|
flex: 1 1 0;
|
|
322
336
|
min-width: 0;
|
|
323
337
|
display: flex;
|
|
@@ -325,24 +339,24 @@
|
|
|
325
339
|
align-items: center;
|
|
326
340
|
gap: 3px;
|
|
327
341
|
}
|
|
328
|
-
.mkt .column-chart-value {
|
|
342
|
+
:is(.mkt, .atelic-ui) .column-chart-value {
|
|
329
343
|
font-size: 0.64rem;
|
|
330
344
|
font-variant-numeric: tabular-nums;
|
|
331
345
|
color: var(--ink-soft);
|
|
332
346
|
white-space: nowrap;
|
|
333
347
|
}
|
|
334
|
-
.mkt .column-chart-bar {
|
|
348
|
+
:is(.mkt, .atelic-ui) .column-chart-bar {
|
|
335
349
|
width: 100%;
|
|
336
350
|
max-width: 48px;
|
|
337
351
|
border-radius: 4px 4px 0 0;
|
|
338
352
|
background: var(--tone-fill);
|
|
339
353
|
}
|
|
340
|
-
.mkt .column-chart-label {
|
|
354
|
+
:is(.mkt, .atelic-ui) .column-chart-label {
|
|
341
355
|
font-size: 0.66rem;
|
|
342
356
|
color: var(--ink-soft);
|
|
343
357
|
white-space: nowrap;
|
|
344
358
|
}
|
|
345
|
-
.mkt .column-chart-group-label {
|
|
359
|
+
:is(.mkt, .atelic-ui) .column-chart-group-label {
|
|
346
360
|
display: flex;
|
|
347
361
|
flex-wrap: wrap;
|
|
348
362
|
justify-content: center;
|
|
@@ -356,7 +370,7 @@
|
|
|
356
370
|
color: var(--neutral-500);
|
|
357
371
|
white-space: nowrap;
|
|
358
372
|
}
|
|
359
|
-
.mkt .column-chart-group-label b {
|
|
373
|
+
:is(.mkt, .atelic-ui) .column-chart-group-label b {
|
|
360
374
|
font-family: var(--font-sans);
|
|
361
375
|
font-size: 0.8rem;
|
|
362
376
|
letter-spacing: 0;
|
|
@@ -366,37 +380,37 @@
|
|
|
366
380
|
}
|
|
367
381
|
|
|
368
382
|
/* ---- ThresholdScale: zones, and a pin a reading ---- */
|
|
369
|
-
.mkt .threshold-scale {
|
|
383
|
+
:is(.mkt, .atelic-ui) .threshold-scale {
|
|
370
384
|
display: flex;
|
|
371
385
|
flex-direction: column;
|
|
372
386
|
gap: 10px;
|
|
373
387
|
}
|
|
374
|
-
.mkt .threshold-scale-row {
|
|
388
|
+
:is(.mkt, .atelic-ui) .threshold-scale-row {
|
|
375
389
|
display: grid;
|
|
376
390
|
grid-template-columns: 4.4em minmax(0, 1fr) 3.4em;
|
|
377
391
|
gap: 10px;
|
|
378
392
|
align-items: center;
|
|
379
393
|
}
|
|
380
|
-
.mkt .threshold-scale-label {
|
|
394
|
+
:is(.mkt, .atelic-ui) .threshold-scale-label {
|
|
381
395
|
color: var(--ink-soft);
|
|
382
396
|
}
|
|
383
|
-
.mkt .threshold-scale-track {
|
|
397
|
+
:is(.mkt, .atelic-ui) .threshold-scale-track {
|
|
384
398
|
position: relative;
|
|
385
399
|
display: flex;
|
|
386
400
|
gap: 2px;
|
|
387
401
|
height: 12px;
|
|
388
402
|
}
|
|
389
|
-
.mkt .threshold-scale-zone {
|
|
403
|
+
:is(.mkt, .atelic-ui) .threshold-scale-zone {
|
|
390
404
|
background: var(--tone-fill);
|
|
391
405
|
opacity: 0.32;
|
|
392
406
|
}
|
|
393
|
-
.mkt .threshold-scale-zone:first-child {
|
|
407
|
+
:is(.mkt, .atelic-ui) .threshold-scale-zone:first-child {
|
|
394
408
|
border-radius: 999px 0 0 999px;
|
|
395
409
|
}
|
|
396
|
-
.mkt .threshold-scale-zone:nth-last-child(2) {
|
|
410
|
+
:is(.mkt, .atelic-ui) .threshold-scale-zone:nth-last-child(2) {
|
|
397
411
|
border-radius: 0 999px 999px 0;
|
|
398
412
|
}
|
|
399
|
-
.mkt .threshold-scale-pin {
|
|
413
|
+
:is(.mkt, .atelic-ui) .threshold-scale-pin {
|
|
400
414
|
position: absolute;
|
|
401
415
|
top: 50%;
|
|
402
416
|
width: 18px;
|
|
@@ -404,30 +418,30 @@
|
|
|
404
418
|
margin: -9px 0 0 -9px;
|
|
405
419
|
border-radius: 50%;
|
|
406
420
|
background: var(--tone-fill);
|
|
407
|
-
border: 3px solid var(--card);
|
|
421
|
+
border: 3px solid var(--ui-card, var(--card));
|
|
408
422
|
box-shadow: 0 0 0 1.5px var(--tone-fill);
|
|
409
423
|
}
|
|
410
|
-
.mkt .threshold-scale-value {
|
|
424
|
+
:is(.mkt, .atelic-ui) .threshold-scale-value {
|
|
411
425
|
font-weight: 700;
|
|
412
426
|
font-variant-numeric: tabular-nums;
|
|
413
427
|
color: var(--tone);
|
|
414
428
|
text-align: right;
|
|
415
429
|
}
|
|
416
|
-
.mkt .threshold-scale-axis {
|
|
430
|
+
:is(.mkt, .atelic-ui) .threshold-scale-axis {
|
|
417
431
|
position: relative;
|
|
418
432
|
display: flex;
|
|
419
433
|
gap: 2px;
|
|
420
434
|
height: 2.3em;
|
|
421
435
|
font-size: 0.8rem;
|
|
422
436
|
}
|
|
423
|
-
.mkt .threshold-scale-axis > span {
|
|
437
|
+
:is(.mkt, .atelic-ui) .threshold-scale-axis > span {
|
|
424
438
|
color: var(--tone);
|
|
425
439
|
font-weight: 600;
|
|
426
440
|
white-space: nowrap;
|
|
427
441
|
overflow: hidden;
|
|
428
442
|
text-overflow: ellipsis;
|
|
429
443
|
}
|
|
430
|
-
.mkt .threshold-scale-axis > i {
|
|
444
|
+
:is(.mkt, .atelic-ui) .threshold-scale-axis > i {
|
|
431
445
|
position: absolute;
|
|
432
446
|
top: 1.25em;
|
|
433
447
|
transform: translateX(-50%);
|
|
@@ -441,12 +455,12 @@
|
|
|
441
455
|
/* ---- DataTable: on a phone each row stacks, its facts wearing their
|
|
442
456
|
column's label, and a dropdown sorts; wide, it is a table whose
|
|
443
457
|
headings sort ---- */
|
|
444
|
-
.mkt .data-table table,
|
|
445
|
-
.mkt .data-table tbody {
|
|
458
|
+
:is(.mkt, .atelic-ui) .data-table table,
|
|
459
|
+
:is(.mkt, .atelic-ui) .data-table tbody {
|
|
446
460
|
display: block;
|
|
447
461
|
width: 100%;
|
|
448
462
|
}
|
|
449
|
-
.mkt .data-table thead {
|
|
463
|
+
:is(.mkt, .atelic-ui) .data-table thead {
|
|
450
464
|
position: absolute;
|
|
451
465
|
width: 1px;
|
|
452
466
|
height: 1px;
|
|
@@ -454,7 +468,7 @@
|
|
|
454
468
|
clip: rect(0, 0, 0, 0);
|
|
455
469
|
white-space: nowrap;
|
|
456
470
|
}
|
|
457
|
-
.mkt .data-table-sort {
|
|
471
|
+
:is(.mkt, .atelic-ui) .data-table-sort {
|
|
458
472
|
display: flex;
|
|
459
473
|
align-items: center;
|
|
460
474
|
gap: 8px;
|
|
@@ -462,16 +476,16 @@
|
|
|
462
476
|
font-size: 0.85rem;
|
|
463
477
|
color: var(--ink-soft);
|
|
464
478
|
}
|
|
465
|
-
.mkt .data-table-sort select {
|
|
479
|
+
:is(.mkt, .atelic-ui) .data-table-sort select {
|
|
466
480
|
font: inherit;
|
|
467
481
|
color: var(--ink);
|
|
468
|
-
background: var(--card);
|
|
482
|
+
background: var(--ui-card, var(--card));
|
|
469
483
|
border: 1px solid var(--neutral-200);
|
|
470
484
|
border-radius: var(--radius-sm);
|
|
471
485
|
padding: 5px 8px;
|
|
472
486
|
max-width: 100%;
|
|
473
487
|
}
|
|
474
|
-
.mkt .data-table tbody tr {
|
|
488
|
+
:is(.mkt, .atelic-ui) .data-table tbody tr {
|
|
475
489
|
display: flex;
|
|
476
490
|
flex-wrap: wrap;
|
|
477
491
|
align-items: center;
|
|
@@ -480,25 +494,25 @@
|
|
|
480
494
|
border-bottom: 1px solid var(--neutral-100);
|
|
481
495
|
line-height: 1.35;
|
|
482
496
|
}
|
|
483
|
-
.mkt .data-table tbody tr:first-child {
|
|
497
|
+
:is(.mkt, .atelic-ui) .data-table tbody tr:first-child {
|
|
484
498
|
border-top: 1.5px solid var(--ink);
|
|
485
499
|
}
|
|
486
|
-
.mkt .data-table tbody tr:last-child {
|
|
500
|
+
:is(.mkt, .atelic-ui) .data-table tbody tr:last-child {
|
|
487
501
|
border-bottom: 0;
|
|
488
502
|
padding-bottom: 0;
|
|
489
503
|
}
|
|
490
|
-
.mkt .data-table tbody th {
|
|
504
|
+
:is(.mkt, .atelic-ui) .data-table tbody th {
|
|
491
505
|
width: 100%;
|
|
492
506
|
text-align: left;
|
|
493
507
|
font-weight: 400;
|
|
494
508
|
}
|
|
495
|
-
.mkt .data-table td {
|
|
509
|
+
:is(.mkt, .atelic-ui) .data-table td {
|
|
496
510
|
display: inline-flex;
|
|
497
511
|
align-items: center;
|
|
498
512
|
gap: 6px;
|
|
499
513
|
font-variant-numeric: tabular-nums;
|
|
500
514
|
}
|
|
501
|
-
.mkt .data-table td::before {
|
|
515
|
+
:is(.mkt, .atelic-ui) .data-table td::before {
|
|
502
516
|
content: attr(data-label);
|
|
503
517
|
font-family: var(--font-mono);
|
|
504
518
|
font-size: 0.7rem;
|
|
@@ -507,33 +521,33 @@
|
|
|
507
521
|
color: var(--neutral-500);
|
|
508
522
|
}
|
|
509
523
|
@media print, (min-width: 721px) {
|
|
510
|
-
.mkt .data-table-sort {
|
|
524
|
+
:is(.mkt, .atelic-ui) .data-table-sort {
|
|
511
525
|
display: none;
|
|
512
526
|
}
|
|
513
|
-
.mkt .data-table table {
|
|
527
|
+
:is(.mkt, .atelic-ui) .data-table table {
|
|
514
528
|
display: table;
|
|
515
529
|
border-collapse: collapse;
|
|
516
530
|
}
|
|
517
|
-
.mkt .data-table tbody {
|
|
531
|
+
:is(.mkt, .atelic-ui) .data-table tbody {
|
|
518
532
|
display: table-row-group;
|
|
519
533
|
}
|
|
520
|
-
.mkt .data-table thead {
|
|
534
|
+
:is(.mkt, .atelic-ui) .data-table thead {
|
|
521
535
|
position: static;
|
|
522
536
|
width: auto;
|
|
523
537
|
height: auto;
|
|
524
538
|
overflow: visible;
|
|
525
539
|
clip: auto;
|
|
526
540
|
}
|
|
527
|
-
.mkt .data-table tbody tr {
|
|
541
|
+
:is(.mkt, .atelic-ui) .data-table tbody tr {
|
|
528
542
|
display: table-row;
|
|
529
543
|
padding: 0;
|
|
530
544
|
border: 0;
|
|
531
545
|
}
|
|
532
|
-
.mkt .data-table tbody tr:first-child {
|
|
546
|
+
:is(.mkt, .atelic-ui) .data-table tbody tr:first-child {
|
|
533
547
|
border-top: 0;
|
|
534
548
|
}
|
|
535
|
-
.mkt .data-table th,
|
|
536
|
-
.mkt .data-table td {
|
|
549
|
+
:is(.mkt, .atelic-ui) .data-table th,
|
|
550
|
+
:is(.mkt, .atelic-ui) .data-table td {
|
|
537
551
|
display: table-cell;
|
|
538
552
|
padding: 7px 14px 7px 0;
|
|
539
553
|
border-bottom: 1px solid var(--neutral-100);
|
|
@@ -541,23 +555,23 @@
|
|
|
541
555
|
vertical-align: middle;
|
|
542
556
|
font-weight: 400;
|
|
543
557
|
}
|
|
544
|
-
.mkt .data-table tbody th {
|
|
558
|
+
:is(.mkt, .atelic-ui) .data-table tbody th {
|
|
545
559
|
width: auto;
|
|
546
560
|
}
|
|
547
|
-
.mkt .data-table td::before {
|
|
561
|
+
:is(.mkt, .atelic-ui) .data-table td::before {
|
|
548
562
|
content: none;
|
|
549
563
|
}
|
|
550
|
-
.mkt .data-table th:last-child,
|
|
551
|
-
.mkt .data-table td:last-child {
|
|
564
|
+
:is(.mkt, .atelic-ui) .data-table th:last-child,
|
|
565
|
+
:is(.mkt, .atelic-ui) .data-table td:last-child {
|
|
552
566
|
padding-right: 0;
|
|
553
567
|
}
|
|
554
|
-
.mkt .data-table [data-align="center"] {
|
|
568
|
+
:is(.mkt, .atelic-ui) .data-table [data-align="center"] {
|
|
555
569
|
text-align: center;
|
|
556
570
|
}
|
|
557
|
-
.mkt .data-table [data-align="right"] {
|
|
571
|
+
:is(.mkt, .atelic-ui) .data-table [data-align="right"] {
|
|
558
572
|
text-align: right;
|
|
559
573
|
}
|
|
560
|
-
.mkt .data-table thead th {
|
|
574
|
+
:is(.mkt, .atelic-ui) .data-table thead th {
|
|
561
575
|
padding-top: 0;
|
|
562
576
|
padding-bottom: 8px;
|
|
563
577
|
border-bottom: 1.5px solid var(--ink);
|
|
@@ -569,7 +583,7 @@
|
|
|
569
583
|
white-space: nowrap;
|
|
570
584
|
vertical-align: bottom;
|
|
571
585
|
}
|
|
572
|
-
.mkt .data-table thead th button {
|
|
586
|
+
:is(.mkt, .atelic-ui) .data-table thead th button {
|
|
573
587
|
display: inline-flex;
|
|
574
588
|
align-items: center;
|
|
575
589
|
gap: 4px;
|
|
@@ -582,23 +596,23 @@
|
|
|
582
596
|
color: inherit;
|
|
583
597
|
cursor: pointer;
|
|
584
598
|
}
|
|
585
|
-
.mkt .data-table thead th button:hover,
|
|
586
|
-
.mkt .data-table thead th button[data-active] {
|
|
587
|
-
color: var(--primary);
|
|
599
|
+
:is(.mkt, .atelic-ui) .data-table thead th button:hover,
|
|
600
|
+
:is(.mkt, .atelic-ui) .data-table thead th button[data-active] {
|
|
601
|
+
color: var(--ui-primary, var(--primary));
|
|
588
602
|
}
|
|
589
|
-
.mkt .data-table thead th button svg {
|
|
603
|
+
:is(.mkt, .atelic-ui) .data-table thead th button svg {
|
|
590
604
|
opacity: 0.55;
|
|
591
605
|
}
|
|
592
|
-
.mkt .data-table thead th button[data-active] svg {
|
|
606
|
+
:is(.mkt, .atelic-ui) .data-table thead th button[data-active] svg {
|
|
593
607
|
opacity: 1;
|
|
594
608
|
}
|
|
595
|
-
.mkt .data-table tbody tr:last-child th,
|
|
596
|
-
.mkt .data-table tbody tr:last-child td {
|
|
609
|
+
:is(.mkt, .atelic-ui) .data-table tbody tr:last-child th,
|
|
610
|
+
:is(.mkt, .atelic-ui) .data-table tbody tr:last-child td {
|
|
597
611
|
border-bottom: 0;
|
|
598
612
|
}
|
|
599
613
|
}
|
|
600
614
|
@media print {
|
|
601
|
-
.mkt .data-table thead th button svg {
|
|
615
|
+
:is(.mkt, .atelic-ui) .data-table thead th button svg {
|
|
602
616
|
display: none;
|
|
603
617
|
}
|
|
604
618
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* StarRating: a row of stars in half star steps. On its own sheet, and not
|
|
2
|
+
under .mkt, so an app that wears none of the site chrome can import this
|
|
3
|
+
file alone; components.css imports it for every site.
|
|
4
|
+
|
|
5
|
+
The two colors are custom properties with fallbacks off the text color, so
|
|
6
|
+
the row needs no theme token and takes a color from a prop, from a site's
|
|
7
|
+
own rule, or from whatever it sits in. */
|
|
8
|
+
@layer atelic-ui {
|
|
9
|
+
.star-rating {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
align-items: center;
|
|
12
|
+
vertical-align: middle;
|
|
13
|
+
line-height: 0;
|
|
14
|
+
border-radius: 4px;
|
|
15
|
+
}
|
|
16
|
+
.star-rating.is-editable:focus-visible {
|
|
17
|
+
outline: 2px solid var(--star-focus, currentColor);
|
|
18
|
+
outline-offset: 2px;
|
|
19
|
+
}
|
|
20
|
+
.star-rating-star {
|
|
21
|
+
position: relative;
|
|
22
|
+
display: inline-block;
|
|
23
|
+
width: var(--star-size, 24px);
|
|
24
|
+
height: var(--star-size, 24px);
|
|
25
|
+
}
|
|
26
|
+
/* Pinned to the star's size: a base reset that caps an svg at its
|
|
27
|
+
container's width would shrink the filled star inside its clip to a
|
|
28
|
+
small whole star, where it has to stay full size and be cut in half. */
|
|
29
|
+
.star-rating .star-rating-star svg {
|
|
30
|
+
display: block;
|
|
31
|
+
width: var(--star-size, 24px);
|
|
32
|
+
height: var(--star-size, 24px);
|
|
33
|
+
max-width: none;
|
|
34
|
+
}
|
|
35
|
+
.star-rating-outline {
|
|
36
|
+
color: var(--star-outline, color-mix(in srgb, currentColor 40%, transparent));
|
|
37
|
+
}
|
|
38
|
+
/* The filled star sits over the outline and is clipped to none, half, or
|
|
39
|
+
all of its width. */
|
|
40
|
+
.star-rating-fill {
|
|
41
|
+
position: absolute;
|
|
42
|
+
inset-block: 0;
|
|
43
|
+
left: 0;
|
|
44
|
+
overflow: hidden;
|
|
45
|
+
pointer-events: none;
|
|
46
|
+
color: var(--star-fill, currentColor);
|
|
47
|
+
}
|
|
48
|
+
.star-rating-fill svg {
|
|
49
|
+
fill: currentColor;
|
|
50
|
+
}
|
|
51
|
+
.star-rating-hit {
|
|
52
|
+
position: absolute;
|
|
53
|
+
inset-block: 0;
|
|
54
|
+
width: 50%;
|
|
55
|
+
margin: 0;
|
|
56
|
+
padding: 0;
|
|
57
|
+
border: 0;
|
|
58
|
+
background: none;
|
|
59
|
+
cursor: pointer;
|
|
60
|
+
}
|
|
61
|
+
.star-rating-hit.is-left {
|
|
62
|
+
left: 0;
|
|
63
|
+
}
|
|
64
|
+
.star-rating-hit.is-right {
|
|
65
|
+
right: 0;
|
|
66
|
+
}
|
|
67
|
+
}
|