@refrakt-md/lumina 0.19.0 → 0.20.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/base.css CHANGED
@@ -33,5 +33,9 @@
33
33
  @import './styles/dimensions/state.css';
34
34
  @import './styles/dimensions/media.css';
35
35
  @import './styles/dimensions/surfaces.css';
36
+ @import './styles/dimensions/frame.css';
37
+ @import './styles/dimensions/substrate.css';
38
+ @import './styles/dimensions/cover.css';
39
+ @import './styles/dimensions/guest-posture.css';
36
40
  @import './styles/dimensions/checklist.css';
37
41
  @import './styles/dimensions/sequence.css';
@@ -288,6 +288,18 @@
288
288
  "collapse": {
289
289
  "source": "meta",
290
290
  "dataAttribute": "data-collapse"
291
+ },
292
+ "content-place": {
293
+ "source": "meta",
294
+ "dataAttribute": "data-content-place"
295
+ },
296
+ "height": {
297
+ "source": "meta",
298
+ "dataAttribute": "data-height"
299
+ },
300
+ "aspect": {
301
+ "source": "meta",
302
+ "dataAttribute": "data-aspect"
291
303
  }
292
304
  },
293
305
  "elements": {
@@ -305,6 +317,76 @@
305
317
  "inlineStyles": {
306
318
  "valign": {
307
319
  "prop": "--split-valign"
320
+ },
321
+ "aspect": "aspect-ratio"
322
+ },
323
+ "variants": {
324
+ "media-position": {
325
+ "cover": {
326
+ "block": "card",
327
+ "root": ".rf-card",
328
+ "dataRune": "card",
329
+ "childOrder": [
330
+ "media",
331
+ "content",
332
+ "{content}"
333
+ ],
334
+ "modifiers": {
335
+ "media-position": {
336
+ "source": "meta",
337
+ "default": "top",
338
+ "dataAttribute": "data-media-position"
339
+ },
340
+ "media-ratio": {
341
+ "source": "meta",
342
+ "dataAttribute": "data-media-ratio"
343
+ },
344
+ "valign": {
345
+ "source": "meta",
346
+ "dataAttribute": "data-valign"
347
+ },
348
+ "collapse": {
349
+ "source": "meta",
350
+ "dataAttribute": "data-collapse"
351
+ },
352
+ "content-place": {
353
+ "source": "meta",
354
+ "dataAttribute": "data-content-place"
355
+ },
356
+ "height": {
357
+ "source": "meta",
358
+ "dataAttribute": "data-height"
359
+ },
360
+ "aspect": {
361
+ "source": "meta",
362
+ "dataAttribute": "data-aspect"
363
+ }
364
+ },
365
+ "staticModifiers": [
366
+ {
367
+ "name": "cover",
368
+ "selector": ".rf-card--cover"
369
+ }
370
+ ],
371
+ "elements": {
372
+ "content": {
373
+ "tag": "div",
374
+ "selector": ".rf-card__content",
375
+ "source": "layout",
376
+ "children": [
377
+ "eyebrow",
378
+ "body",
379
+ "footer"
380
+ ]
381
+ }
382
+ },
383
+ "inlineStyles": {
384
+ "valign": {
385
+ "prop": "--split-valign"
386
+ },
387
+ "aspect": "aspect-ratio"
388
+ }
389
+ }
308
390
  }
309
391
  }
310
392
  },
@@ -876,44 +958,12 @@
876
958
  "childOrder": [
877
959
  "{content}"
878
960
  ],
879
- "modifiers": {
880
- "shadow": {
881
- "source": "meta",
882
- "default": "none",
883
- "classPattern": ".rf-showcase--{value}",
884
- "dataAttribute": "data-shadow"
885
- },
886
- "bleed": {
887
- "source": "meta",
888
- "default": "none",
889
- "classPattern": ".rf-showcase--{value}",
890
- "dataAttribute": "data-bleed"
891
- },
892
- "aspect": {
893
- "source": "meta",
894
- "dataAttribute": "data-aspect"
895
- },
896
- "offset": {
897
- "source": "meta",
898
- "dataAttribute": "data-offset"
899
- },
900
- "place": {
901
- "source": "meta",
902
- "dataAttribute": "data-place"
903
- }
904
- },
905
961
  "contextModifiers": {
906
962
  "bento-cell": {
907
963
  "suffix": "in-bento-cell",
908
964
  "selector": ".rf-showcase--in-bento-cell"
909
965
  }
910
966
  },
911
- "inlineStyles": {
912
- "offset": {
913
- "prop": "--showcase-offset"
914
- },
915
- "aspect": "--showcase-aspect"
916
- },
917
967
  "childDensity": "compact"
918
968
  },
919
969
  "TabGroup": {
@@ -3266,6 +3316,10 @@
3266
3316
  "collapse": {
3267
3317
  "source": "meta",
3268
3318
  "dataAttribute": "data-collapse"
3319
+ },
3320
+ "content-place": {
3321
+ "source": "meta",
3322
+ "dataAttribute": "data-content-place"
3269
3323
  }
3270
3324
  },
3271
3325
  "elements": {
@@ -3333,6 +3387,144 @@
3333
3387
  "valign": {
3334
3388
  "prop": "--split-valign"
3335
3389
  }
3390
+ },
3391
+ "variants": {
3392
+ "media-position": {
3393
+ "cover": {
3394
+ "block": "recipe",
3395
+ "root": ".rf-recipe",
3396
+ "dataRune": "recipe",
3397
+ "childOrder": [
3398
+ "cover-band",
3399
+ "content",
3400
+ "{content}"
3401
+ ],
3402
+ "modifiers": {
3403
+ "media-position": {
3404
+ "source": "meta",
3405
+ "default": "top",
3406
+ "dataAttribute": "data-media-position"
3407
+ },
3408
+ "prepTime": {
3409
+ "source": "meta",
3410
+ "dataAttribute": "data-prep-time"
3411
+ },
3412
+ "cookTime": {
3413
+ "source": "meta",
3414
+ "dataAttribute": "data-cook-time"
3415
+ },
3416
+ "servings": {
3417
+ "source": "meta",
3418
+ "dataAttribute": "data-servings"
3419
+ },
3420
+ "difficulty": {
3421
+ "source": "meta",
3422
+ "default": "medium",
3423
+ "classPattern": ".rf-recipe--{value}",
3424
+ "dataAttribute": "data-difficulty"
3425
+ },
3426
+ "media-ratio": {
3427
+ "source": "meta",
3428
+ "dataAttribute": "data-media-ratio"
3429
+ },
3430
+ "valign": {
3431
+ "source": "meta",
3432
+ "dataAttribute": "data-valign"
3433
+ },
3434
+ "collapse": {
3435
+ "source": "meta",
3436
+ "dataAttribute": "data-collapse"
3437
+ },
3438
+ "content-place": {
3439
+ "source": "meta",
3440
+ "dataAttribute": "data-content-place"
3441
+ }
3442
+ },
3443
+ "staticModifiers": [
3444
+ {
3445
+ "name": "cover",
3446
+ "selector": ".rf-recipe--cover"
3447
+ }
3448
+ ],
3449
+ "elements": {
3450
+ "metadata": {
3451
+ "tag": "dl",
3452
+ "selector": ".rf-recipe__metadata",
3453
+ "source": "block",
3454
+ "layout": "definition-list",
3455
+ "fields": [
3456
+ "prepTime",
3457
+ "cookTime",
3458
+ "servings",
3459
+ "difficulty"
3460
+ ]
3461
+ },
3462
+ "preamble": {
3463
+ "tag": "header",
3464
+ "selector": ".rf-recipe__preamble",
3465
+ "source": "layout",
3466
+ "children": [
3467
+ "eyebrow",
3468
+ "headline",
3469
+ "blurb"
3470
+ ]
3471
+ },
3472
+ "eyebrow": {
3473
+ "tag": "eyebrow",
3474
+ "selector": ".rf-recipe__eyebrow",
3475
+ "source": "autoLabel"
3476
+ },
3477
+ "headline": {
3478
+ "tag": "headline",
3479
+ "selector": ".rf-recipe__headline",
3480
+ "source": "autoLabel"
3481
+ },
3482
+ "blurb": {
3483
+ "tag": "blurb",
3484
+ "selector": ".rf-recipe__blurb",
3485
+ "source": "autoLabel"
3486
+ },
3487
+ "image": {
3488
+ "tag": "image",
3489
+ "selector": ".rf-recipe__image",
3490
+ "source": "autoLabel"
3491
+ },
3492
+ "media": {
3493
+ "tag": "media",
3494
+ "selector": ".rf-recipe__media",
3495
+ "source": "autoLabel"
3496
+ },
3497
+ "content": {
3498
+ "tag": "div",
3499
+ "selector": ".rf-recipe__content",
3500
+ "source": "layout",
3501
+ "children": [
3502
+ "metadata",
3503
+ "ingredients",
3504
+ "steps",
3505
+ "tips"
3506
+ ]
3507
+ },
3508
+ "cover-band": {
3509
+ "tag": "div",
3510
+ "selector": ".rf-recipe__cover-band",
3511
+ "source": "layout",
3512
+ "children": [
3513
+ "media",
3514
+ "preamble"
3515
+ ],
3516
+ "attrs": {
3517
+ "data-color-scheme": "dark"
3518
+ }
3519
+ }
3520
+ },
3521
+ "inlineStyles": {
3522
+ "valign": {
3523
+ "prop": "--split-valign"
3524
+ }
3525
+ }
3526
+ }
3527
+ }
3336
3528
  }
3337
3529
  },
3338
3530
  "RecipeIngredient": {
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,EAAE,iBAmL1B,CAAC"}
1
+ {"version":3,"file":"tokens.d.ts","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,EAAE,iBAyL1B,CAAC"}
package/dist/tokens.js CHANGED
@@ -93,6 +93,7 @@ export const luminaTokens = {
93
93
  breathe: '8rem',
94
94
  },
95
95
  shadow: {
96
+ none: 'none',
96
97
  xs: '0 1px 2px rgba(0,0,0,0.04)',
97
98
  sm: '0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04)',
98
99
  md: '0 4px 12px rgba(0,0,0,0.07), 0 1px 3px rgba(0,0,0,0.04)',
@@ -160,6 +161,8 @@ export const luminaTokens = {
160
161
  // above by the generator.
161
162
  extra: {
162
163
  'rf-color-inline-code-bg': '#2b2b29',
164
+ // SPEC-087 — inset surfaces dip a touch deeper in dark mode.
165
+ 'rf-surface-inset-shift': '0.06',
163
166
  },
164
167
  },
165
168
  },
@@ -175,6 +178,9 @@ export const luminaTokens = {
175
178
  * them here. */
176
179
  extra: {
177
180
  'rf-color-inline-code-bg': '#e6e5e3',
181
+ // SPEC-087 — lightness delta for the derived inset surface (a tint-tracking
182
+ // recessed fill applied at use-site via relative-color). Per-mode tunable.
183
+ 'rf-surface-inset-shift': '0.04',
178
184
  },
179
185
  };
180
186
  //# sourceMappingURL=tokens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,YAAY,GAAsB;IAC9C,IAAI,EAAE;QACL,IAAI,EAAE,mEAAmE;QACzE,IAAI,EAAE,yEAAyE;KAC/E;IAED,KAAK,EAAE;QACN,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,SAAS;QAChB,MAAM,EAAE,SAAS;QACjB,EAAE,EAAE,SAAS;QACb,OAAO,EAAE,SAAS;QAClB,eAAe,EAAE,SAAS;QAC1B,oEAAoE;QACpE,qEAAqE;QACrE,6DAA6D;QAC7D,YAAY,EAAE,+DAA+D;QAC7E,wEAAwE;QACxE,uEAAuE;QACvE,YAAY,EAAE,SAAS;QAEvB,OAAO,EAAE;YACR,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,SAAS;SACjB;QAED,0EAA0E;QAC1E,sEAAsE;QACtE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;QAC3D,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;QAC9D,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;QAC7D,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;QAE9D,IAAI,EAAE;YACL,EAAE,EAAE,SAAS;YACb,IAAI,EAAE,SAAS;YACf,wDAAwD;YACxD,sEAAsE;YACtE,kEAAkE;YAClE,WAAW,EAAE,SAAS;SACtB;QAED,8DAA8D;QAC9D,oEAAoE;QACpE,oEAAoE;QACpE,+DAA+D;QAC/D,8DAA8D;QAC9D,6DAA6D;QAC7D,IAAI,EAAE;YACL,SAAS,EAAE,0DAA0D;YACrE,gBAAgB,EAAE,+CAA+C;YACjE,MAAM,EAAE,uBAAuB;SAC/B;KACD;IAED,MAAM,EAAE;QACP,EAAE,EAAE,KAAK;QACT,EAAE,EAAE,MAAM;QACV,EAAE,EAAE,MAAM;QACV,IAAI,EAAE,QAAQ;KACd;IAED,OAAO,EAAE;QACR,EAAE,EAAE,SAAS;QACb,EAAE,EAAE,QAAQ;QACZ,EAAE,EAAE,QAAQ;QACZ,EAAE,EAAE,MAAM;QACV,EAAE,EAAE,MAAM;QACV,KAAK,EAAE,MAAM;QACb,OAAO,EAAE;YACR,IAAI,EAAE,MAAM;YACZ,KAAK,EAAE,QAAQ;YACf,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,MAAM;SACf;KACD;IAED,KAAK,EAAE;QACN,KAAK,EAAE,GAAG;QACV,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,MAAM;QACb,OAAO,EAAE,MAAM;KACf;IAED,MAAM,EAAE;QACP,EAAE,EAAE,4BAA4B;QAChC,EAAE,EAAE,wDAAwD;QAC5D,EAAE,EAAE,yDAAyD;QAC7D,EAAE,EAAE,yDAAyD;KAC7D;IAED,wEAAwE;IACxE,oEAAoE;IACpE,qEAAqE;IACrE,aAAa;IACb,MAAM,EAAE;QACP,OAAO,EAAE,SAAS,EAAM,YAAY;QACpC,QAAQ,EAAE,SAAS,EAAK,eAAe;QACvC,MAAM,EAAE,SAAS,EAAO,YAAY;QACpC,QAAQ,EAAE,SAAS,EAAK,gBAAgB;QACxC,OAAO,EAAE,SAAS,EAAM,mCAAmC;QAC3D,WAAW,EAAE,SAAS,EAAE,8BAA8B;QACtD,QAAQ,EAAE,SAAS,EAAK,6BAA6B;KACrD;IAED,KAAK,EAAE;QACN,IAAI,EAAE;YACL,KAAK,EAAE;gBACN,IAAI,EAAE,SAAS;gBACf,KAAK,EAAE,SAAS;gBAChB,MAAM,EAAE,SAAS;gBACjB,EAAE,EAAE,SAAS;gBACb,OAAO,EAAE,SAAS;gBAClB,eAAe,EAAE,SAAS;gBAC1B,YAAY,EAAE,SAAS;gBAEvB,OAAO,EAAE;oBACR,IAAI,EAAE,SAAS;oBACf,KAAK,EAAE,SAAS;oBAChB,MAAM,EAAE,SAAS;oBACjB,MAAM,EAAE,SAAS;iBACjB;gBAED,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;gBAC3D,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;gBAC9D,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;gBAC7D,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE;gBAE9D,IAAI,EAAE;oBACL,EAAE,EAAE,SAAS;oBACb,IAAI,EAAE,SAAS;oBACf,WAAW,EAAE,SAAS;iBACtB;aACD;YAED,MAAM,EAAE;gBACP,EAAE,EAAE,2BAA2B;gBAC/B,EAAE,EAAE,sDAAsD;gBAC1D,EAAE,EAAE,uDAAuD;gBAC3D,EAAE,EAAE,uDAAuD;aAC3D;YAED,uEAAuE;YACvE,qEAAqE;YACrE,MAAM,EAAE;gBACP,OAAO,EAAE,SAAS,EAAM,aAAa;gBACrC,QAAQ,EAAE,SAAS,EAAK,qBAAqB;gBAC7C,MAAM,EAAE,SAAS,EAAO,aAAa;gBACrC,QAAQ,EAAE,SAAS,EAAK,sBAAsB;gBAC9C,OAAO,EAAE,SAAS,EAAM,sBAAsB;gBAC9C,WAAW,EAAE,SAAS,EAAE,qBAAqB;gBAC7C,QAAQ,EAAE,SAAS,EAAK,oBAAoB;aAC5C;YAED,oEAAoE;YACpE,kEAAkE;YAClE,qEAAqE;YACrE,0BAA0B;YAC1B,KAAK,EAAE;gBACN,yBAAyB,EAAE,SAAS;aACpC;SACD;KACD;IAED;;;;;;;;;qBASiB;IACjB,KAAK,EAAE;QACN,yBAAyB,EAAE,SAAS;KACpC;CACD,CAAC"}
1
+ {"version":3,"file":"tokens.js","sourceRoot":"","sources":["../src/tokens.ts"],"names":[],"mappings":"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"}
package/index.css CHANGED
@@ -34,6 +34,10 @@
34
34
  @import './styles/dimensions/state.css';
35
35
  @import './styles/dimensions/media.css';
36
36
  @import './styles/dimensions/surfaces.css';
37
+ @import './styles/dimensions/frame.css';
38
+ @import './styles/dimensions/substrate.css';
39
+ @import './styles/dimensions/cover.css';
40
+ @import './styles/dimensions/guest-posture.css';
37
41
  @import './styles/dimensions/checklist.css';
38
42
  @import './styles/dimensions/sequence.css';
39
43
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@refrakt-md/lumina",
3
3
  "description": "Lumina theme for refrakt.md — design tokens, CSS, identity transform, and layout configs",
4
- "version": "0.19.0",
4
+ "version": "0.20.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -83,9 +83,9 @@
83
83
  "build": "tsc"
84
84
  },
85
85
  "dependencies": {
86
- "@refrakt-md/runes": "0.19.0",
87
- "@refrakt-md/transform": "0.19.0",
88
- "@refrakt-md/types": "0.19.0"
86
+ "@refrakt-md/runes": "0.20.0",
87
+ "@refrakt-md/transform": "0.20.0",
88
+ "@refrakt-md/types": "0.20.0"
89
89
  },
90
90
  "devDependencies": {
91
91
  "postcss": "^8.4.0"
@@ -18,3 +18,12 @@
18
18
  [data-rune][data-inset="tight"] { padding-inline: var(--rf-inset-tight, 1rem); }
19
19
  [data-rune][data-inset="loose"] { padding-inline: var(--rf-inset-loose, 4rem); }
20
20
  [data-rune][data-inset="breathe"] { padding-inline: var(--rf-inset-breathe, 8rem); }
21
+
22
+ /* ── Elevation — drop shadow (box-shadow) ──────────────────────────────
23
+ * Universal `elevation` attribute (SPEC-086). Maps to the shared --rf-shadow-*
24
+ * token scale; `none` explicitly flattens a rune's default shadow. */
25
+
26
+ [data-rune][data-elevation="none"] { box-shadow: var(--rf-shadow-none); }
27
+ [data-rune][data-elevation="sm"] { box-shadow: var(--rf-shadow-sm); }
28
+ [data-rune][data-elevation="md"] { box-shadow: var(--rf-shadow-md); }
29
+ [data-rune][data-elevation="lg"] { box-shadow: var(--rf-shadow-lg); }
@@ -0,0 +1,140 @@
1
+ /* Cover layout (SPEC-089) — `media-position="cover"`.
2
+ *
3
+ * The media well fills the rune interior and the content overlays it (the poster
4
+ * / cover card). The media stays a media guest — the thin-edge frame and
5
+ * `--rf-radius-media` are preserved — with content floated on top via grid
6
+ * stacking (media + content share one grid cell). No overlay primitive in the
7
+ * layout config; the variant supplies the structure (SPEC-091), CSS positions.
8
+ *
9
+ * Height authority: an external grid track (bento) wins; else the media aspect
10
+ * (`frame-aspect`, default portrait); a card height/aspect knob overrides via the
11
+ * cascade. Cover supersedes the media-vs-content split knobs (content-height /
12
+ * media-ratio), which have no meaning when there is no split. */
13
+
14
+ /* ── full scope — the whole content overlays the media well ──────────── */
15
+ [data-media-position="cover"]:not([data-cover-scope="header"]) {
16
+ display: grid;
17
+ grid-template: minmax(0, 1fr) / minmax(0, 1fr);
18
+ aspect-ratio: var(--frame-aspect, var(--cover-aspect, 3 / 4));
19
+ container-type: size;
20
+ overflow: hidden;
21
+ isolation: isolate;
22
+ }
23
+ [data-media-position="cover"]:not([data-cover-scope="header"]) > [data-section="media"],
24
+ [data-media-position="cover"]:not([data-cover-scope="header"]) > [data-name="content"] {
25
+ grid-area: 1 / 1;
26
+ margin: 0;
27
+ min-width: 0;
28
+ }
29
+
30
+ /* ── header scope — only the cover-band overlays; body flows below ────── */
31
+ [data-cover-scope="header"] > [data-name="cover-band"] {
32
+ display: grid;
33
+ grid-template: minmax(0, 1fr) / minmax(0, 1fr);
34
+ aspect-ratio: var(--frame-aspect, 16 / 9);
35
+ border-radius: var(--rf-radius-media);
36
+ overflow: hidden;
37
+ isolation: isolate;
38
+ container-type: size;
39
+ }
40
+ /* On narrow screens a 16/9 band is too short to seat the preamble over the
41
+ * scrim, so default it to a taller (≥1:1) poster shape. An explicit
42
+ * `frame-aspect` still wins. */
43
+ @media (max-width: 40rem) {
44
+ [data-cover-scope="header"] > [data-name="cover-band"] {
45
+ aspect-ratio: var(--frame-aspect, 4 / 5);
46
+ }
47
+ }
48
+ [data-cover-scope="header"] > [data-name="cover-band"] > [data-section="media"],
49
+ [data-cover-scope="header"] > [data-name="cover-band"] > [data-name="preamble"] {
50
+ grid-area: 1 / 1;
51
+ margin: 0;
52
+ }
53
+
54
+ /* ── shared: the media well fills, the overlaid box positions ─────────── */
55
+ [data-media-position="cover"] [data-section="media"] {
56
+ position: relative;
57
+ height: 100%;
58
+ border-radius: var(--rf-radius-media);
59
+ overflow: hidden;
60
+ }
61
+ [data-media-position="cover"] [data-section="media"] > :is(img, video) {
62
+ width: 100%;
63
+ height: 100%;
64
+ object-fit: cover;
65
+ }
66
+ /* The overlaid box (full: content; header: preamble) anchors via content-place. */
67
+ [data-media-position="cover"]:not([data-cover-scope="header"]) > [data-name="content"],
68
+ [data-cover-scope="header"] > [data-name="cover-band"] > [data-name="preamble"] {
69
+ position: relative;
70
+ z-index: 1;
71
+ align-self: var(--cover-place-block, end);
72
+ justify-self: var(--cover-place-inline, stretch);
73
+ padding: var(--rune-padding, var(--rf-spacing-md));
74
+ }
75
+
76
+ /* ── auto / unset placement ───────────────────────────────────────────
77
+ * `auto` is the cover default, so an unset content-place behaves the same as an
78
+ * explicit `auto`. A header band is always a caption strip — the preamble sits at
79
+ * the block-end over the scrim regardless of the band's orientation. A full-scope
80
+ * overlay adapts to the cover region's orientation (portrait → block-end caption;
81
+ * landscape → inline-start side panel). An explicit value (e.g. "center center")
82
+ * sets the `--cover-place-*` vars on the base rule above and does NOT match here,
83
+ * so it pins regardless of orientation. */
84
+ [data-media-position="cover"]:not([data-cover-scope="header"]):is([data-content-place="auto"], :not([data-content-place])) > [data-name="content"],
85
+ [data-cover-scope="header"]:is([data-content-place="auto"], :not([data-content-place])) > [data-name="cover-band"] > [data-name="preamble"] {
86
+ align-self: end;
87
+ justify-self: stretch;
88
+ }
89
+ @container (min-aspect-ratio: 1 / 1) {
90
+ [data-media-position="cover"]:not([data-cover-scope="header"]):is([data-content-place="auto"], :not([data-content-place])) > [data-name="content"] {
91
+ align-self: center;
92
+ justify-self: start;
93
+ }
94
+ }
95
+
96
+ /* ── default cover scrim (SPEC-088 scrim, on the media surface) ────────
97
+ * Overlaying text on an arbitrary image without a scrim is a legibility footgun,
98
+ * so cover applies a default gradient scrim weighted toward the content edge.
99
+ * `scrim="none"` opts out. The overlaid foreground is set light by the engine
100
+ * (data-color-scheme) so text reads against the darkened media. */
101
+ [data-media-position="cover"]:not([data-scrim="none"]) [data-section="media"]::after {
102
+ content: "";
103
+ position: absolute;
104
+ inset: 0;
105
+ pointer-events: none;
106
+ background-image: linear-gradient(var(--cover-scrim-dir, to top), rgb(0 0 0 / 0.55), transparent 62%);
107
+ }
108
+
109
+ /* Frost treatment (`scrim-type="frost"`) — a frosted-glass blur over the media
110
+ * instead of a gradient. `scrim-blur` (named scale) sets the blur radius; the
111
+ * tint follows the cover scheme (a dark scheme → a dark frost for light text).
112
+ * The frost is masked to the content edge (following `--cover-scrim-dir`, default
113
+ * bottom) so it reads as a band behind the text, not a blur over the whole image. */
114
+ [data-media-position="cover"][data-scrim-blur="none"] { --cover-scrim-blur: 0px; }
115
+ [data-media-position="cover"][data-scrim-blur="sm"] { --cover-scrim-blur: 4px; }
116
+ [data-media-position="cover"][data-scrim-blur="md"] { --cover-scrim-blur: 8px; }
117
+ [data-media-position="cover"][data-scrim-blur="lg"] { --cover-scrim-blur: 16px; }
118
+ [data-media-position="cover"][data-scrim-type="frost"]:not([data-scrim="none"]) [data-section="media"]::after {
119
+ background-image: none;
120
+ background-color: rgb(0 0 0 / 0.18);
121
+ -webkit-backdrop-filter: blur(var(--cover-scrim-blur, 8px));
122
+ backdrop-filter: blur(var(--cover-scrim-blur, 8px));
123
+ -webkit-mask-image: linear-gradient(var(--cover-scrim-dir, to top), #000 30%, transparent 72%);
124
+ mask-image: linear-gradient(var(--cover-scrim-dir, to top), #000 30%, transparent 72%);
125
+ }
126
+ [data-media-position="cover"][data-scrim-type="frost"][data-color-scheme="light"]:not([data-scrim="none"]) [data-section="media"]::after {
127
+ background-color: rgb(255 255 255 / 0.25);
128
+ }
129
+
130
+ /* The overlay carries the cover colour-scheme (set by the engine) so its text
131
+ * reads against the darkened media. The standalone `[data-color-scheme]` rule
132
+ * also paints a `background-color`; null it out here so the overlay stays
133
+ * see-through over the media (the scrim, not a solid box, does the legibility). */
134
+ [data-media-position="cover"] [data-name="content"][data-color-scheme],
135
+ [data-cover-scope="header"] > [data-name="cover-band"][data-color-scheme] {
136
+ background-color: transparent;
137
+ /* Muted text (the blurb) is barely legible over a photo, so on the overlay it
138
+ * reads at full foreground strength — the same colour as the headline. */
139
+ --rf-color-muted: var(--rf-color-text);
140
+ }
@@ -0,0 +1,54 @@
1
+ /* Frame chrome (SPEC-086) — media-surface presentation.
2
+ *
3
+ * The engine lands the frame contract on the frame-target element: a rune's
4
+ * `[data-section="media"]` zone (frameTarget: 'media') or the rune's own root
5
+ * (frameTarget: 'self', e.g. figure/showcase). These selectors are intentionally
6
+ * surface-agnostic — they match wherever the chrome is emitted.
7
+ *
8
+ * data-frame — applied preset name (marker)
9
+ * data-frame-shadow — silhouette drop-shadow (none|sm|md|lg)
10
+ * data-displace — edge/corner the guest moves toward
11
+ * --frame-aspect — aspect-ratio of the framed media
12
+ * --frame-offset — displacement distance (resolved spacing token)
13
+ * --frame-oversize — scale factor by which the guest exceeds its slot
14
+ * --frame-anchor — crop focal point (object-position)
15
+ * --frame-place-x / -y — guest-box alignment within its slot
16
+ *
17
+ * Clip is host-owned: a clipping host (card/bento-cell/figure media well) crops a
18
+ * displaced/oversized guest into a *peek*; a breakout host (showcase-self, a
19
+ * section/page) lets it *spill*. `elevation` (box-shadow on the self surface) and
20
+ * `frame-shadow` (drop-shadow silhouette here) never collide — different property,
21
+ * different surface. */
22
+
23
+ /* ── Silhouette drop-shadow ──────────────────────────────────────────── */
24
+ [data-frame-shadow="none"] { filter: none; }
25
+ [data-frame-shadow="sm"] { filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.10)); }
26
+ [data-frame-shadow="md"] { filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.25)); }
27
+ [data-frame-shadow="lg"] { filter: drop-shadow(0 12px 40px rgba(0, 0, 0, 0.20)); }
28
+
29
+ /* ── Aspect ratio ────────────────────────────────────────────────────── */
30
+ [style*="--frame-aspect"] { aspect-ratio: var(--frame-aspect); }
31
+
32
+ /* ── Crop anchor — focal point when the guest is cut ─────────────────── */
33
+ [style*="--frame-anchor"] > :is(img, video) { object-position: var(--frame-anchor); }
34
+
35
+ /* ── Placement of the framed box within its slot ─────────────────────── */
36
+ [style*="--frame-place-x"] { justify-self: var(--frame-place-x, auto); align-self: var(--frame-place-y, auto); }
37
+
38
+ /* ── Oversize — guest exceeds its slot (clipping hosts crop it) ──────── */
39
+ [style*="--frame-oversize"] > :is(img, video) { width: calc(100% * var(--frame-oversize, 1)); max-width: none; }
40
+
41
+ /* ── Displacement (bleed / peek) — move the guest toward an edge/corner.
42
+ * The host decides whether it spills or is cropped; this only moves it. ─ */
43
+ [data-displace] { position: relative; z-index: 1; }
44
+ [data-displace="top"] { margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
45
+ [data-displace="bottom"] { margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
46
+ [data-displace="both"] { margin-block: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
47
+ [data-displace="end"] { margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
48
+ [data-displace="bottom-end"] { margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
49
+ [data-displace="top-end"] { margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
50
+
51
+ /* Collapse displacement on mobile, regardless of host. */
52
+ @media (max-width: 768px) {
53
+ [data-displace] { margin: 0; }
54
+ }
@@ -0,0 +1,27 @@
1
+ /* Media-guest interaction posture (SPEC-090).
2
+ *
3
+ * A media-slot guest is presentational by default. When its container is itself
4
+ * an interaction target — a `card` / `bento-cell` with a stretched whole-tile
5
+ * `href` link — or the guest is a `cover` backdrop (SPEC-089), the engine marks
6
+ * the media zone `data-guest-posture="presentational"`. That makes the guest
7
+ * non-interactive at the pointer level, so:
8
+ * • a linked tile links reliably — clicks over the media fall through to the
9
+ * stretched link beneath it instead of being eaten by the guest's controls;
10
+ * • a cover backdrop never steals interaction from the overlaid content.
11
+ *
12
+ * The behaviours layer additionally skips JS enhancement for a presentational
13
+ * guest (see @refrakt-md/behaviors), so it renders its static fallback. The
14
+ * posture is scoped to the media zone only — content-overlay controls
15
+ * (body/footer links & buttons) are outside it and stay fully interactive. */
16
+ [data-section="media"][data-guest-posture="presentational"] {
17
+ pointer-events: none;
18
+ }
19
+
20
+ /* Static fallback for demoted tabbed guests (`codegroup`, `tabs`): their tab
21
+ * strip is rendered statically but the behaviour that wires it is skipped, so
22
+ * it would sit there inert. Hide it and let the panels read as plain stacked
23
+ * content — the non-enhanced render is the fallback. (Guests whose chrome is
24
+ * JS-injected — datatable toolbars, map controls — simply never appear.) */
25
+ [data-section="media"][data-guest-posture="presentational"] :is(.rf-tabs__tabs, .rf-codegroup__tabs) {
26
+ display: none;
27
+ }
@@ -0,0 +1,101 @@
1
+ /* Substrate (SPEC-087) — generated surface pattern.
2
+ *
3
+ * The token-driven gradient recipes that realise each pattern ship here in the
4
+ * always-included base layer (not a theme's swappable CSS), so "dots = dots on
5
+ * every theme" is a guarantee: themes retune the `--substrate-*` token hooks
6
+ * (ink colour, cell size), they don't redefine a pattern's geometry. Patterns
7
+ * are pure CSS — no image assets, crisp at any zoom. The engine emits markers
8
+ * only (`data-substrate` + `--substrate-*`); CSS does the drawing.
9
+ *
10
+ * The pattern is a `background-image` over the surface `background-color`; the
11
+ * gradient's `transparent` gaps reveal whatever fill sits beneath (tint/inset).
12
+ * `--substrate-ink` resolves from `--rf-color-border` (tint-bridged), so the
13
+ * pattern recolours with the surface.
14
+ *
15
+ * Runes set their surface with the `background` *shorthand* (e.g.
16
+ * `.rf-card { background: var(--rf-color-surface) }`), which resets
17
+ * `background-image`/`-size`/`-position` and loads *after* this base layer. So
18
+ * the actual `background-*` longhands live here on the higher-specificity
19
+ * `[data-substrate]:not(…)` selector (0,2,0 — beats a rune's `.rf-*`, 0,1,0),
20
+ * and each pattern only feeds custom properties (which never collide with a
21
+ * rune's background). The rune's `background-color` still shows through. */
22
+
23
+ [data-substrate]:not([data-substrate="none"]) {
24
+ --substrate-cell: 16px;
25
+ --substrate-opacity: 0.5;
26
+ /* `--substrate-boost` (per-pattern) and `--substrate-mode-boost` (light/dark)
27
+ * are intentionally NOT set here — only referenced with a `1` fallback — so a
28
+ * lower-specificity pattern rule or an inherited dark-mode rule can drive them
29
+ * without losing to this (0,2,0) base. The product is clamped to 100% by
30
+ * color-mix. */
31
+ --substrate-ink: color-mix(in oklab, var(--rf-color-border) calc(var(--substrate-opacity) * var(--substrate-boost, 1) * var(--substrate-mode-boost, 1) * 100%), transparent);
32
+ background-image: var(--substrate-image);
33
+ background-size: var(--substrate-tile, var(--substrate-cell) var(--substrate-cell));
34
+ background-position: var(--substrate-pos, 0 0);
35
+ background-repeat: repeat;
36
+ }
37
+
38
+ /* dots — one tiled radial-gradient. A small dot reads fainter than a 1px line,
39
+ * so dots get a per-pattern ink boost. */
40
+ [data-substrate="dots"] {
41
+ --substrate-image: radial-gradient(var(--substrate-ink) var(--substrate-dot, 1.5px), transparent 0);
42
+ --substrate-boost: 1.4;
43
+ }
44
+
45
+ /* grid — two tiled linear-gradients */
46
+ [data-substrate="grid"] {
47
+ --substrate-image:
48
+ linear-gradient(var(--substrate-ink) 1px, transparent 1px),
49
+ linear-gradient(90deg, var(--substrate-ink) 1px, transparent 1px);
50
+ }
51
+
52
+ /* lines — diagonal hatching (the repeating gradient owns its own spacing) */
53
+ [data-substrate="lines"] {
54
+ --substrate-image: repeating-linear-gradient(45deg, var(--substrate-ink) 0 1px, transparent 1px var(--substrate-cell));
55
+ --substrate-tile: auto;
56
+ }
57
+
58
+ /* cross — plus marks on a grid (a grid masked down to short ticks at each node) */
59
+ [data-substrate="cross"] {
60
+ --substrate-image:
61
+ linear-gradient(var(--substrate-ink) 1px, transparent 1px),
62
+ linear-gradient(90deg, var(--substrate-ink) 1px, transparent 1px);
63
+ --substrate-pos: center;
64
+ -webkit-mask-image: radial-gradient(circle at center, #000 0 2px, transparent 2px);
65
+ -webkit-mask-size: var(--substrate-cell) var(--substrate-cell);
66
+ mask-image: radial-gradient(circle at center, #000 0 2px, transparent 2px);
67
+ mask-size: var(--substrate-cell) var(--substrate-cell);
68
+ }
69
+
70
+ /* checker — alternating filled cells */
71
+ [data-substrate="checker"] {
72
+ --substrate-image:
73
+ conic-gradient(var(--substrate-ink) 0 25%, transparent 0 50%, var(--substrate-ink) 0 75%, transparent 0);
74
+ }
75
+
76
+ [data-substrate="none"] { background-image: none; }
77
+
78
+ /* substrate-fill="inset" — paint the pattern over the recessed inset fill
79
+ * (SPEC-087 inset surface) instead of the inherited surface colour. */
80
+ [data-substrate][data-substrate-fill="inset"] {
81
+ background-color: oklch(from var(--rf-color-surface) calc(l - var(--rf-surface-inset-shift)) c h);
82
+ }
83
+
84
+ /* Dark mode — the border ink sits close to the dark surface, so patterns need
85
+ * more presence across the board (dots compound their per-pattern boost on top,
86
+ * landing near full strength). Set the multiplier on the dark root so it
87
+ * inherits into every substrate element; mirrors how dark.css triggers dark. */
88
+ [data-theme="dark"],
89
+ [data-color-scheme="dark"] {
90
+ --substrate-mode-boost: 1.6;
91
+ }
92
+ @media (prefers-color-scheme: dark) {
93
+ :root:not([data-theme="light"]) {
94
+ --substrate-mode-boost: 1.6;
95
+ }
96
+ }
97
+ /* A tint-locked light subtree resets to the light intensity (nearest ancestor
98
+ * wins via inheritance), even when nested inside dark mode. */
99
+ [data-color-scheme="light"] {
100
+ --substrate-mode-boost: 1;
101
+ }
@@ -102,3 +102,23 @@
102
102
  border-radius: var(--rf-radius-container);
103
103
  padding: var(--rune-padding, var(--rf-spacing-md));
104
104
  }
105
+
106
+ /* ─── Inset surface (SPEC-087) — tint-tracking recessed fill ──────────
107
+ * Derived at use-site via relative-color (lower L, keep C+H) so it recomputes from a tinted
108
+ * `--rf-color-surface` automatically (a static inset-colour token would
109
+ * freeze to the untinted :root). Writes `background` only — never re-bases
110
+ * `--rf-color-surface` — so insets don't compound under nesting; depth is
111
+ * conveyed by border/elevation. `--rf-surface-inset-shift: 0` flushes it. */
112
+
113
+ /* chart / diagram self surface: the standalone "darker surface". */
114
+ .rf-chart,
115
+ .rf-diagram {
116
+ background: oklch(from var(--rf-color-surface) calc(l - var(--rf-surface-inset-shift)) c h);
117
+ }
118
+
119
+ /* Media wells of media-bearing runes: a recessed sub-surface that tracks the
120
+ * (possibly tinted) container colour — invisible under a full-bleed guest,
121
+ * visible in the gaps (transparent, displaced, or absent guest). */
122
+ :is(.rf-card, .rf-bento-cell, .rf-recipe, .rf-realm, .rf-faction, .rf-playlist) [data-section="media"] {
123
+ background: oklch(from var(--rf-color-surface) calc(l - var(--rf-surface-inset-shift)) c h);
124
+ }
@@ -102,7 +102,7 @@
102
102
  [data-section="media"]:is(
103
103
  :has(> [data-rune="preview"]),
104
104
  :has(> [data-rune="juxtapose"]),
105
- :has(> .rf-showcase[data-bleed]:not(.rf-showcase--in-bento-cell))
105
+ :has(> .rf-showcase[data-displace]:not(.rf-showcase--in-bento-cell))
106
106
  ) {
107
107
  overflow: visible;
108
108
  container-type: normal;
@@ -114,7 +114,7 @@
114
114
  [data-section="media"]:is(
115
115
  :has(> [data-rune="preview"]),
116
116
  :has(> [data-rune="juxtapose"]),
117
- :has(> .rf-showcase[data-bleed]:not(.rf-showcase--in-bento-cell))
117
+ :has(> .rf-showcase[data-displace]:not(.rf-showcase--in-bento-cell))
118
118
  ) > * {
119
119
  width: auto;
120
120
  max-height: none;
@@ -142,6 +142,11 @@
142
142
  aspect-ratio: var(--bento-media-aspect, 16 / 9);
143
143
  }
144
144
  .rf-bento-cell__media > :is(img, video) { object-position: var(--bento-media-anchor, center); }
145
+ /* SPEC-086 — reconcile `frame` facets with bento's existing media vars rather
146
+ * than duplicating them: frame-aspect/anchor (landed on the media zone by the
147
+ * engine) feed the knobs bento already consumes (incl. the collapse path). */
148
+ .rf-bento-cell__media[style*="--frame-aspect"] { --bento-media-aspect: var(--frame-aspect); }
149
+ .rf-bento-cell__media[style*="--frame-anchor"] { --bento-media-anchor: var(--frame-anchor); }
145
150
  .rf-bento-cell__title {
146
151
  font-size: 1.125rem;
147
152
  font-weight: 600;
@@ -51,3 +51,27 @@
51
51
  [data-name="bg-overlay"][data-bg-overlay="light"] {
52
52
  background: rgba(255, 255, 255, 0.6);
53
53
  }
54
+
55
+ /* Scrim (SPEC-088) — a structured legibility treatment behind overlaid text.
56
+ * gradient: a directional darken/lighten; frost: a backdrop blur + tint.
57
+ * `tone` picks the colour, `dir` the heavy edge; strength/blur come inline. */
58
+ [data-name="scrim"] {
59
+ position: absolute;
60
+ inset: 0;
61
+ pointer-events: none;
62
+ z-index: 1;
63
+ }
64
+ [data-name="scrim"][data-scrim-tone="dark"] { --scrim-color: 0 0 0; }
65
+ [data-name="scrim"][data-scrim-tone="light"] { --scrim-color: 255 255 255; }
66
+ [data-name="scrim"][data-scrim-dir="top"] { --scrim-grad-dir: to top; }
67
+ [data-name="scrim"][data-scrim-dir="bottom"] { --scrim-grad-dir: to bottom; }
68
+ [data-name="scrim"][data-scrim-dir="left"] { --scrim-grad-dir: to left; }
69
+ [data-name="scrim"][data-scrim-dir="right"] { --scrim-grad-dir: to right; }
70
+ [data-name="scrim"][data-scrim="gradient"] {
71
+ background-image: linear-gradient(var(--scrim-grad-dir, to top), transparent, rgb(var(--scrim-color, 0 0 0) / var(--scrim-strength, 0.55)));
72
+ }
73
+ [data-name="scrim"][data-scrim="frost"] {
74
+ -webkit-backdrop-filter: blur(var(--scrim-blur, 8px));
75
+ backdrop-filter: blur(var(--scrim-blur, 8px));
76
+ background: rgb(var(--scrim-color, 0 0 0) / 0.18);
77
+ }
@@ -125,6 +125,19 @@
125
125
  flex: 1;
126
126
  }
127
127
 
128
+ /* Height knob (SPEC-089) — a named intrinsic-height scale for cover / bg-only
129
+ * cards that have no natural content height to fill. `aspect` (→ aspect-ratio,
130
+ * emitted inline by the engine) is the proportional alternative; `height` is the
131
+ * absolute one. Both are authority overrides: they win over the cover variant's
132
+ * default media aspect because they sit on the card root with a real value, and
133
+ * over an external grid track only when the card isn't being stretched by one.
134
+ * For cover cards the grid stack (cover.css) fills this box; for bg-only cards
135
+ * the min-height simply gives the surface a poster shape. */
136
+ .rf-card[data-height="sm"] { min-height: 12rem; }
137
+ .rf-card[data-height="md"] { min-height: 18rem; }
138
+ .rf-card[data-height="lg"] { min-height: 26rem; }
139
+ .rf-card[data-height="xl"] { min-height: 34rem; }
140
+
128
141
  /* Whole-card stretched link — covers the card; real links in body/footer
129
142
  * sit above it via position:relative. */
130
143
  .rf-card__link {
@@ -1,56 +1,16 @@
1
- /* Showcase — Media presentation wrapper with shadows, bleed, and aspect ratio */
1
+ /* Showcase — `frameTarget: 'self'` media wrapper (SPEC-086). Shadow, aspect,
2
+ * displacement, place, and crop anchor are now the shared `frame` chrome
3
+ * (dimensions/frame.css), landing on the showcase root. This file keeps only
4
+ * showcase's structural rules and its distinct value: breakout. */
2
5
  .rf-showcase {
3
6
  position: relative;
4
7
  }
5
8
  .rf-showcase__viewport {
6
9
  position: relative;
7
10
  }
8
- /* Shadows */
9
- .rf-showcase[data-shadow="soft"] {
10
- filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
11
- }
12
- .rf-showcase[data-shadow="hard"] {
13
- filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.25));
14
- }
15
- .rf-showcase[data-shadow="elevated"] {
16
- filter: drop-shadow(0 12px 40px rgba(0, 0, 0, 0.2));
17
- }
18
- /* Bleed displacement via negative margins */
19
- .rf-showcase[data-bleed="top"] {
20
- margin-top: calc(-1 * var(--showcase-offset, 2rem));
21
- position: relative;
22
- z-index: 1;
23
- }
24
- .rf-showcase[data-bleed="bottom"] {
25
- margin-bottom: calc(-1 * var(--showcase-offset, 2rem));
26
- position: relative;
27
- z-index: 1;
28
- }
29
- .rf-showcase[data-bleed="both"] {
30
- margin-top: calc(-1 * var(--showcase-offset, 2rem));
31
- margin-bottom: calc(-1 * var(--showcase-offset, 2rem));
32
- position: relative;
33
- z-index: 1;
34
- }
35
- .rf-showcase[data-bleed="end"] {
36
- margin-inline-end: calc(-1 * var(--showcase-offset, 2rem));
37
- position: relative;
38
- z-index: 1;
39
- }
40
- .rf-showcase[data-bleed="bottom-end"] {
41
- margin-bottom: calc(-1 * var(--showcase-offset, 2rem));
42
- margin-inline-end: calc(-1 * var(--showcase-offset, 2rem));
43
- position: relative;
44
- z-index: 1;
45
- }
46
- .rf-showcase[data-bleed="top-end"] {
47
- margin-top: calc(-1 * var(--showcase-offset, 2rem));
48
- margin-inline-end: calc(-1 * var(--showcase-offset, 2rem));
49
- position: relative;
50
- z-index: 1;
51
- }
52
- /* Parent needs overflow visible for bleed — except inside clipping containers like bento cells */
53
- :has(> .rf-showcase[data-bleed]:not(.rf-showcase--in-bento-cell)) {
11
+ /* Breakout — a displaced showcase spills past a non-clipping ancestor; clipping
12
+ * hosts (e.g. bento cells) keep it cropped (host-owned clip). */
13
+ :has(> .rf-showcase[data-displace]:not(.rf-showcase--in-bento-cell)) {
54
14
  overflow: visible;
55
15
  }
56
16
  /* Bento cell context — showcase fills remaining space */
@@ -58,22 +18,6 @@
58
18
  flex: 1;
59
19
  min-height: 0;
60
20
  }
61
- /* Aspect ratio enforcement */
62
- .rf-showcase[data-aspect] .rf-showcase__viewport {
63
- aspect-ratio: var(--showcase-aspect);
64
- overflow: hidden;
65
- }
66
- .rf-showcase[data-aspect] .rf-showcase__viewport > img,
67
- .rf-showcase[data-aspect] .rf-showcase__viewport > video {
68
- width: 100%;
69
- height: 100%;
70
- object-fit: cover;
71
- }
72
- /* Self-positioning within parent via place attribute */
73
- .rf-showcase[data-place] {
74
- justify-self: var(--place-x, auto);
75
- align-self: var(--place-y, auto);
76
- }
77
21
  /* Spacing — vertical margin around the showcase */
78
22
  .rf-showcase[data-spacing="flush"] { margin-top: 0; margin-bottom: 0; }
79
23
  .rf-showcase[data-spacing="tight"] { margin-top: var(--rf-spacing-section-tight, 1.5rem); margin-bottom: var(--rf-spacing-section-tight, 1.5rem); }
package/tokens/base.css CHANGED
@@ -27,6 +27,9 @@
27
27
  --rf-color-surface-hover: #ecebe8;
28
28
  --rf-color-surface-active: #e2e0dd;
29
29
  --rf-color-surface-raised: #ffffff;
30
+ /* SPEC-087 — lightness delta for the derived inset surface (applied at use-site
31
+ * via relative-color — lower L, keep C+H — so it tracks a tinted --rf-color-surface). */
32
+ --rf-surface-inset-shift: 0.04;
30
33
 
31
34
  /* Semantic — muted earthy band per SPEC-051 */
32
35
  --rf-color-info: #34547a;
@@ -89,6 +92,7 @@
89
92
  --rf-inset-breathe: 8rem;
90
93
 
91
94
  /* Shadows */
95
+ --rf-shadow-none: none;
92
96
  --rf-shadow-xs: 0 1px 2px rgba(0,0,0,0.04);
93
97
  --rf-shadow-sm: 0 1px 3px rgba(0,0,0,0.06), 0 1px 2px rgba(0,0,0,0.04);
94
98
  --rf-shadow-md: 0 4px 12px rgba(0,0,0,0.07), 0 1px 3px rgba(0,0,0,0.04);
package/tokens/dark.css CHANGED
@@ -19,6 +19,7 @@
19
19
  --rf-color-surface-hover: #282825;
20
20
  --rf-color-surface-active: #333330;
21
21
  --rf-color-surface-raised: #272723;
22
+ --rf-surface-inset-shift: 0.06;
22
23
 
23
24
  --rf-color-info: #9bb4c7;
24
25
  --rf-color-info-bg: #1f2530;
@@ -79,6 +80,7 @@
79
80
  --rf-color-surface-hover: #282825;
80
81
  --rf-color-surface-active: #333330;
81
82
  --rf-color-surface-raised: #272723;
83
+ --rf-surface-inset-shift: 0.06;
82
84
 
83
85
  --rf-color-info: #9bb4c7;
84
86
  --rf-color-info-bg: #1f2530;