@refrakt-md/skeleton 0.43.0 → 0.45.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@refrakt-md/skeleton",
3
3
  "description": "The structural cascade layer + layer-order contract every refrakt theme builds on — ships `@layer skeleton` and the `@layer skeleton, skin;` order declaration, plus the token-name contract",
4
- "version": "0.43.0",
4
+ "version": "0.45.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -29,7 +29,7 @@
29
29
  "styles"
30
30
  ],
31
31
  "dependencies": {
32
- "@refrakt-md/types": "0.43.0"
32
+ "@refrakt-md/types": "0.45.0"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsc"
@@ -1,4 +1,4 @@
1
- /* metadata — structure. The zone-layout primitives (bar, definition-list) and the
1
+ /* metadata — structure. The zone-layout primitives (bar, lead, ladder, definition-list) and the
2
2
  * visually-hidden label mechanism. [data-zone-layout] is layout-uncontested (no
3
3
  * layout file targets it), and a rune overriding these resolves correctly (skin
4
4
  * beats skeleton). Spacing values (gap / padding / rhythm margins), borders,
@@ -26,6 +26,27 @@
26
26
  margin-left: auto;
27
27
  }
28
28
 
29
+ /* ── Layout: lead (SPEC-161 D6) ──
30
+ * A byline: the fields in a column, the lead (`data-lead`) first and the rest
31
+ * beneath it. No value is ranked, so a compact container hides none of them. */
32
+ [data-zone-layout="lead"] {
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: flex-start;
36
+ }
37
+
38
+ /* ── Layout: ladder (SPEC-156 D3) ──
39
+ * A list of peer items in a column, each free to arrange its own inside. A
40
+ * sequenced ladder takes its markers and gutter from the sequence dimension;
41
+ * the unsequenced one is only reset. */
42
+ [data-zone-layout="ladder"]:not([data-sequence]) {
43
+ list-style: none;
44
+ margin: 0;
45
+ padding: 0;
46
+ display: flex;
47
+ flex-direction: column;
48
+ }
49
+
29
50
  /* ── Layout: definition-list ──
30
51
  * Each row stacks dt over dd; rows flow into auto-fit columns that collapse to a
31
52
  * single column on narrow containers without a media-query step. */
@@ -1,25 +1,23 @@
1
- /* sequence — structure. The counter mechanism + list resets, item gutters, the
2
- * numbered counter-circle box placement, and the connected-timeline geometry
1
+ /* sequence — structure. The list resets, item gutters, the numbered marker box
2
+ * placement (the ordinal is a node, WORK-583), and the connected-timeline geometry
3
3
  * (item position/gutter, marker box). The connector *lines* are borders (skin),
4
4
  * as are the marker fill / radius / shadow / colour / type — those live in
5
5
  * Lumina's dimensions/sequence.css. [data-sequence] is uncontested by layouts and
6
6
  * sections, so the split is behavior-preserving. */
7
7
 
8
- /* Numbered — counter circle to the left of each item */
8
+ /* Numbered — the item's ordinal (a `[data-name="ordinal"]` node the engine
9
+ * emits, WORK-583) in a circle to the left of each item */
9
10
  [data-sequence="numbered"] {
10
- counter-reset: sequence;
11
11
  list-style: none;
12
12
  padding-left: 0;
13
13
  }
14
14
 
15
15
  [data-sequence="numbered"] > li {
16
- counter-increment: sequence;
17
16
  position: relative;
18
17
  padding-left: 2.25rem;
19
18
  }
20
19
 
21
- [data-sequence="numbered"] > li::before {
22
- content: counter(sequence);
20
+ [data-sequence="numbered"] > li > [data-name="ordinal"] {
23
21
  position: absolute;
24
22
  left: 0;
25
23
  top: 0.625rem;
@@ -89,7 +87,7 @@
89
87
  padding-left: 1.75rem;
90
88
  }
91
89
 
92
- [data-density="compact"] [data-sequence="numbered"] > li::before {
90
+ [data-density="compact"] [data-sequence="numbered"] > li > [data-name="ordinal"] {
93
91
  width: 1.25rem;
94
92
  height: 1.25rem;
95
93
  }
@@ -15,9 +15,12 @@
15
15
  grid-template-columns: var(--media-share, 50%) 1fr;
16
16
  align-items: var(--split-valign, start);
17
17
  }
18
- /* "end" flips the grid so media lands in column 2 (source order is media-first). */
18
+ /* "end" flips the grid so media lands in column 2 (source order is media-first).
19
+ * `dense` backfills the content into row 1, column 1: placed after the media,
20
+ * it would otherwise start a second row. */
19
21
  [data-media-position="end"] {
20
22
  grid-template-columns: 1fr var(--media-share, 50%);
23
+ grid-auto-flow: dense;
21
24
  }
22
25
  [data-media-position="end"] > [data-section="media"],
23
26
  [data-media-position="end"] > [data-name="media"] {
@@ -27,6 +30,29 @@
27
30
  grid-column: 1;
28
31
  }
29
32
 
33
+ /* A split with nothing in its media zone (a testimonial with no avatar) emits no
34
+ * zone at all, and lays out as the content alone rather than in half a row. */
35
+ :is([data-media-position="start"], [data-media-position="end"]):not(
36
+ :has(> [data-section="media"], > [data-name="media"])
37
+ ) {
38
+ grid-template-columns: minmax(0, 1fr);
39
+ }
40
+ :is([data-media-position="start"], [data-media-position="end"]):not(
41
+ :has(> [data-section="media"], > [data-name="media"])
42
+ )
43
+ > * {
44
+ grid-column: 1;
45
+ }
46
+
47
+ /* A portrait media zone (a round avatar) takes its portrait's width rather than
48
+ * a share of the row, unless a `media-ratio` asks for one. */
49
+ [data-media-position="start"]:not([data-media-ratio]):has(> [data-media="portrait"]) {
50
+ grid-template-columns: auto 1fr;
51
+ }
52
+ [data-media-position="end"]:not([data-media-ratio]):has(> [data-media="portrait"]) {
53
+ grid-template-columns: 1fr auto;
54
+ }
55
+
30
56
  /* `media-ratio` → media's share of the row width. */
31
57
  [data-media-ratio="1/3"] {
32
58
  --media-share: 33.333%;
@@ -168,6 +194,13 @@
168
194
  grid-column: auto;
169
195
  grid-row: auto;
170
196
  }
197
+ :is(
198
+ [data-media-position="end"][data-collapse="sm"],
199
+ [data-media-position="end"]:not([data-collapse])
200
+ )
201
+ > :is([data-section="media"], [data-name="media"]) {
202
+ order: 1;
203
+ }
171
204
  }
172
205
  @container (max-width: 768px) {
173
206
  :is([data-media-position="start"], [data-media-position="end"])[data-collapse="md"] {
@@ -177,6 +210,10 @@
177
210
  grid-column: auto;
178
211
  grid-row: auto;
179
212
  }
213
+ [data-media-position="end"][data-collapse="md"]
214
+ > :is([data-section="media"], [data-name="media"]) {
215
+ order: 1;
216
+ }
180
217
  }
181
218
  @container (max-width: 1024px) {
182
219
  :is([data-media-position="start"], [data-media-position="end"])[data-collapse="lg"] {
@@ -186,4 +223,8 @@
186
223
  grid-column: auto;
187
224
  grid-row: auto;
188
225
  }
226
+ [data-media-position="end"][data-collapse="lg"]
227
+ > :is([data-section="media"], [data-name="media"]) {
228
+ order: 1;
229
+ }
189
230
  }
@@ -37,3 +37,14 @@
37
37
  box-sizing: border-box;
38
38
  }
39
39
  }
40
+
41
+ /* Size follows the nearest enclosing rune's `prominence` (SPEC-160 D7). The skin
42
+ * sets `--action-padding` / `--action-font-size` on a `[data-prominence]` rune;
43
+ * every other rune root clears them, so they reach only an `actions` whose
44
+ * nearest enclosing rune carries the prominence. A raised card inside a display
45
+ * hero keeps default buttons — the same protection density.css gives titles.
46
+ * `actions` itself is exempt: it is what reads them. */
47
+ [data-rune]:not([data-prominence], [data-rune="actions"]) {
48
+ --action-padding: initial;
49
+ --action-font-size: initial;
50
+ }
@@ -1,6 +1,12 @@
1
1
  /* bg — structure. The background/overlay/scrim layer placement (absolute fill +
2
2
  * z-index + clip), the content-above-bg stacking, and the video fill. The images,
3
3
  * gradients, blur/opacity, overlay tints, and scrim paint are skin. */
4
+ /* The host is the layer's containing block, or the layer would size against
5
+ * the nearest positioned ancestor (often the page). Zero specificity: a rune's
6
+ * own `position` (sticky, absolute) still wins. */
7
+ :where([data-bg]) {
8
+ position: relative;
9
+ }
4
10
  [data-name="bg"] {
5
11
  position: absolute;
6
12
  inset: 0;
@@ -1,4 +1,5 @@
1
- /* cast — structure. The members list reset, grid/list arrangements, name/role
1
+ /* cast — structure. The members list reset, the grid arrangement (the list
2
+ * arrangement is the shared unsequenced ladder, dimensions/metadata.css), name/role
2
3
  * block display, empty-body hide, the avatar box (size/crop/display), and the list
3
4
  * variant flex. Surface chrome, type, radii, and margins are skin. */
4
5
  .rf-cast__items {
@@ -8,10 +9,6 @@
8
9
  display: grid;
9
10
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
10
11
  }
11
- .rf-cast--list .rf-cast__items {
12
- display: flex;
13
- flex-direction: column;
14
- }
15
12
  /* SPEC-100: carousel arrangement reuses the shared track (styles/runes/carousel.css,
16
13
  * keyed on [data-layout="carousel"] [data-name="items"]); cast only sets its
17
14
  * per-slide width. */
@@ -1,26 +1,6 @@
1
- /* howto — structure. The steps list reset + counter, the step item counter +
2
- * positioning, the step-number box placement/centring, and the metadata dense-tile
3
- * grid (+ mobile). The surfaced tools list chrome, the step-number glyph + chrome,
1
+ /* howto — structure. The metadata dense-tile grid (+ mobile). The numbered steps
2
+ * are the sequence dimension's (dimensions/sequence.css, BUG-024). The surfaced tools list chrome, the step-number glyph + chrome,
4
3
  * tip callouts, and type/spacing stay skin. */
5
- .rf-howto__content ol {
6
- list-style: none;
7
- counter-reset: howto-step;
8
- }
9
- .rf-howto__content ol > li {
10
- counter-increment: howto-step;
11
- position: relative;
12
- }
13
- .rf-howto__content ol > li::before {
14
- content: counter(howto-step);
15
- position: absolute;
16
- left: 0;
17
- top: 0.625rem;
18
- width: 1.5rem;
19
- height: 1.5rem;
20
- display: flex;
21
- align-items: center;
22
- justify-content: center;
23
- }
24
4
  .rf-howto__metadata {
25
5
  grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
26
6
  }
@@ -1,6 +1,7 @@
1
- /* playlist — structure. The inner-header flex, the tracks list reset + counter +
2
- * clip, the track-row flex (overriding the sequence dimension's absolute dot), the
3
- * track-cell flex sizing + ellipsis overflow + duration auto-push, the meta hide,
1
+ /* playlist — structure. The inner-header flex, the tracks list reset + clip, the
2
+ * track-row flex (overriding the sequence dimension's absolute marker, so the
3
+ * ordinal sits in the row, WORK-583), the
4
+ * track-cell flex sizing + ellipsis overflow, the meta hide,
4
5
  * the cover aspect, and the narrow-screen column hides. The inset padding + split
5
6
  * gaps + chrome + the track-number glyph stay skin. */
6
7
  .rf-playlist__content > .rf-playlist__header {
@@ -9,7 +10,6 @@
9
10
  }
10
11
  .rf-playlist__tracks {
11
12
  list-style: none;
12
- counter-reset: track;
13
13
  overflow: hidden;
14
14
  }
15
15
  .rf-playlist__tracks > li {
@@ -20,7 +20,7 @@
20
20
  .rf-playlist__tracks > li > meta {
21
21
  display: none;
22
22
  }
23
- .rf-playlist__tracks > li::before {
23
+ .rf-playlist__tracks > li > [data-name="ordinal"] {
24
24
  position: static;
25
25
  width: auto;
26
26
  height: auto;
@@ -32,17 +32,6 @@
32
32
  min-width: 0;
33
33
  overflow: hidden;
34
34
  }
35
- .rf-playlist__tracks [data-name="track-artist"] {
36
- flex-shrink: 0;
37
- }
38
- .rf-playlist__tracks [data-name="track-duration"] {
39
- flex-shrink: 0;
40
- margin-left: auto;
41
- min-width: 3rem;
42
- }
43
- .rf-playlist__tracks [data-name="track-meta"] {
44
- flex-shrink: 0;
45
- }
46
35
  /* Cover aspect — defaults to square (album convention). Authors override via
47
36
  * `frame-aspect="..."`; the engine lands `--frame-aspect` on the media zone
48
37
  * (frameTarget defaults to 'media' since playlist has a media section), and
@@ -54,8 +43,8 @@
54
43
  aspect-ratio: var(--frame-aspect, 1);
55
44
  }
56
45
  @media (max-width: 480px) {
57
- .rf-playlist__tracks [data-name="track-artist"],
58
- .rf-playlist__tracks [data-name="track-meta"] {
46
+ .rf-playlist__tracks [data-meta="row"] [data-field="artist"],
47
+ .rf-playlist__tracks [data-meta="row"] [data-field="date"] {
59
48
  display: none;
60
49
  }
61
50
  }
@@ -1,7 +1,7 @@
1
1
  /* pullquote — structure. The relative root (glyph anchor), the decorative-glyph
2
2
  * positioning + the per-variant glyph hides + centred glyph re-centre, the
3
3
  * centre/right auto-margins, and the float modes (+ responsive). The quote glyph,
4
- * type, borders, backgrounds, and the inner-blockquote reset are skin. */
4
+ * type, borders and backgrounds are skin. */
5
5
  .rf-pullquote {
6
6
  position: relative;
7
7
  }
@@ -1,31 +1,12 @@
1
- /* recipe — structure. The content flex column, the steps list reset + counter, the
2
- * step item counter + positioning, the step-number box placement/centring, and the
3
- * metadata dense-tile grid (+ mobile). The thin-edge inset padding + split gaps,
1
+ /* recipe — structure. The content flex column and the metadata dense-tile grid
2
+ * (+ mobile). The numbered steps are the sequence dimension's
3
+ * (dimensions/sequence.css, BUG-024). The thin-edge inset padding + split gaps,
4
4
  * the ingredients list chrome, the step-number glyph + chrome, tip callouts, media
5
5
  * radius, and type stay skin. */
6
6
  .rf-recipe__content {
7
7
  display: flex;
8
8
  flex-direction: column;
9
9
  }
10
- .rf-recipe__content ol {
11
- list-style: none;
12
- counter-reset: recipe-step;
13
- }
14
- .rf-recipe__content ol > li {
15
- counter-increment: recipe-step;
16
- position: relative;
17
- }
18
- .rf-recipe__content ol > li::before {
19
- content: counter(recipe-step);
20
- position: absolute;
21
- left: 0;
22
- top: 0.625rem;
23
- width: 1.5rem;
24
- height: 1.5rem;
25
- display: flex;
26
- align-items: center;
27
- justify-content: center;
28
- }
29
10
  .rf-recipe__metadata {
30
11
  grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
31
12
  }
@@ -33,6 +33,8 @@
33
33
  .rf-section__preamble {
34
34
  display: flex;
35
35
  flex-direction: column;
36
+ /* Start-aligned by default, so a pill eyebrow keeps its own width. */
37
+ align-items: flex-start;
36
38
  }
37
39
  .rf-section__image {
38
40
  max-width: 100%;
@@ -49,3 +51,48 @@
49
51
  align-items: flex-end;
50
52
  margin-inline-start: auto;
51
53
  }
54
+
55
+ /* The section's `align` positions its own actions row too (SPEC-160 D7), whether
56
+ * it sits in the body or the content column — never an `actions` inside a
57
+ * nested rune, which has its own alignment. */
58
+ .rf-section[data-align="center"] > .rf-section__body > .rf-actions,
59
+ .rf-section[data-align="center"] > .rf-section__content > .rf-section__body > .rf-actions {
60
+ --actions-justify: center;
61
+ }
62
+ .rf-section[data-align="end"] > .rf-section__body > .rf-actions,
63
+ .rf-section[data-align="end"] > .rf-section__content > .rf-section__body > .rf-actions {
64
+ --actions-justify: flex-end;
65
+ }
66
+
67
+ /* Cover band (SPEC-160 D7). A section is a page band, not a card: in cover mode
68
+ * its height comes from its content and `height` / `aspect`, never from the
69
+ * cover layer's portrait default, and an unplaced overlay sits mid-band. These
70
+ * win over the shared cover layer (dimensions/cover.css) by specificity. */
71
+ .rf-section--cover[data-media-position="cover"] {
72
+ --cover-aspect: auto;
73
+ container-type: normal;
74
+ min-height: clamp(22rem, 55vh, 40rem);
75
+ }
76
+ .rf-section--cover[data-media-position="cover"][data-aspect] {
77
+ min-height: 0;
78
+ }
79
+ .rf-section--cover[data-media-position="cover"][data-height="sm"] {
80
+ min-height: 12rem;
81
+ }
82
+ .rf-section--cover[data-media-position="cover"][data-height="md"] {
83
+ min-height: 18rem;
84
+ }
85
+ .rf-section--cover[data-media-position="cover"][data-height="lg"] {
86
+ min-height: 26rem;
87
+ }
88
+ .rf-section--cover[data-media-position="cover"][data-height="xl"] {
89
+ min-height: 34rem;
90
+ }
91
+ .rf-section--cover[data-media-position="cover"]:is(
92
+ [data-content-place="auto"],
93
+ :not([data-content-place])
94
+ )
95
+ > [data-name="content"] {
96
+ align-self: center;
97
+ justify-self: stretch;
98
+ }
@@ -1,17 +1,14 @@
1
- /* steps — structure. The step counter mechanism, the step item positioning, the
2
- * counter-badge box placement, the preamble layout, list resets, and the media-position flip. All spacing, type, surface, and the badge
1
+ /* steps — structure. The preamble layout and the media-position flip. The step
2
+ * numbers, their box and the list reset are the sequence dimension's
3
+ * (dimensions/sequence.css, BUG-024). All spacing, type, surface, and the badge
3
4
  * chrome stay skin (Lumina's styles/runes/steps.css). */
4
5
 
5
- .rf-steps {
6
- counter-reset: step;
7
- }
8
-
9
6
  .rf-steps__preamble {
10
7
  flex-direction: column;
11
8
  align-items: flex-start;
12
9
  }
13
10
 
14
- .rf-steps ol {
11
+ .rf-steps__steps {
15
12
  list-style: none;
16
13
  }
17
14
 
@@ -19,23 +16,6 @@
19
16
  display: contents;
20
17
  }
21
18
 
22
- .rf-step {
23
- counter-increment: step;
24
- position: relative;
25
- }
26
-
27
- .rf-step::before {
28
- content: counter(step);
29
- position: absolute;
30
- left: 0;
31
- top: 0.625rem;
32
- width: 1.5rem;
33
- height: 1.5rem;
34
- display: flex;
35
- align-items: center;
36
- justify-content: center;
37
- }
38
-
39
19
  .rf-step h1,
40
20
  .rf-step h2,
41
21
  .rf-step h3,
@@ -1,37 +1,40 @@
1
- /* track — structure. The row flex + counter, the number box, the cell flex sizing
2
- * + ellipsis overflow + duration auto-push, the description/cue full-row + wrap,
1
+ /* track — structure. The row flex, the cell flex sizing + ellipsis overflow,
2
+ * the details block (WORK-679: in the row its values are cells of the row, and
3
+ * the duration's push to the end is the bar's own `[data-align="end"]`; as a
4
+ * byline it is a row beneath the name), the description/cue full-row + wrap,
3
5
  * the chapter row flex + time auto-push, and the narrow-screen hides. The
4
- * number/separator glyphs, type, borders, and spacing stay skin. */
6
+ * separator glyphs, type and spacing stay skin. The track number, its box and the
7
+ * rule between tracks are the sequence dimension's (dimensions/sequence.css, BUG-024). */
5
8
  .rf-track {
6
9
  display: flex;
7
10
  align-items: center;
8
- counter-increment: track;
9
- }
10
- .rf-track::before {
11
- content: counter(track);
12
- min-width: 1.5rem;
13
- flex-shrink: 0;
14
11
  }
15
12
  .rf-track__track-name {
16
13
  flex: 1;
17
14
  min-width: 0;
18
15
  overflow: hidden;
19
16
  }
20
- .rf-track__track-artist {
21
- flex-shrink: 0;
17
+ .rf-track[data-meta="row"] > .rf-track__details {
18
+ display: contents;
22
19
  }
23
- .rf-track__track-duration {
20
+ .rf-track__details > [data-field] {
24
21
  flex-shrink: 0;
25
- margin-left: auto;
22
+ }
23
+ .rf-track[data-meta="row"] > .rf-track__details > [data-field="duration"] {
26
24
  min-width: 3rem;
27
25
  }
28
- .rf-track__track-meta {
29
- flex-shrink: 0;
26
+ .rf-track__heading {
27
+ flex: 1;
28
+ min-width: 0;
29
+ }
30
+ .rf-track__heading > .rf-track__track-name {
31
+ display: block;
30
32
  }
31
33
  .rf-track__track-description {
32
34
  flex-basis: 100%;
33
35
  }
34
- .rf-track:has(.rf-track__track-description) {
36
+ /* A full-row part (a description, chapters, lyrics) starts a line of its own. */
37
+ .rf-track:has(> .rf-track__track-description, > [data-name="lyrics"], > [data-name="chapters"]) {
35
38
  flex-wrap: wrap;
36
39
  }
37
40
  .rf-track [data-name="lyrics"],
@@ -46,10 +49,8 @@
46
49
  margin-left: auto;
47
50
  }
48
51
  @media (max-width: 480px) {
49
- .rf-track__track-artist {
50
- display: none;
51
- }
52
- .rf-track__track-meta {
52
+ .rf-track[data-meta="row"] [data-field="artist"],
53
+ .rf-track[data-meta="row"] [data-field="date"] {
53
54
  display: none;
54
55
  }
55
56
  }