@livenetworks/ashlar 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +96 -96
- package/theme/components/_dropdown.scss +4 -1
- package/theme/config/_breakpoints.scss +8 -0
- package/theme/config/_theme.scss +1 -0
- package/theme/config/mixins/_card.scss +7 -4
- package/theme/config/mixins/_dropdown.scss +26 -2
- package/theme/config/mixins/_popover.scss +24 -3
- package/theme/presentation/_effects.scss +68 -0
- package/theme/presentation/_navbar.scss +72 -1
- package/theme/presentation/_typography.scss +13 -0
- package/web-types.json +1 -1
package/package.json
CHANGED
|
@@ -1,96 +1,96 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@livenetworks/ashlar",
|
|
3
|
-
"version": "2.2.
|
|
4
|
-
"description": "LiveNetworks unified frontend library - CSS framework and vanilla JS components",
|
|
5
|
-
"license": "GPL-3.0-or-later",
|
|
6
|
-
"author": "Live Networks",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"publishConfig": {
|
|
9
|
-
"access": "public"
|
|
10
|
-
},
|
|
11
|
-
"repository": {
|
|
12
|
-
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/livenetworks/ln-ashlar.git"
|
|
14
|
-
},
|
|
15
|
-
"homepage": "https://github.com/livenetworks/ln-ashlar#readme",
|
|
16
|
-
"bugs": "https://github.com/livenetworks/ln-ashlar/issues",
|
|
17
|
-
"keywords": [
|
|
18
|
-
"css",
|
|
19
|
-
"scss",
|
|
20
|
-
"design-system",
|
|
21
|
-
"frontend",
|
|
22
|
-
"components",
|
|
23
|
-
"vanilla-js"
|
|
24
|
-
],
|
|
25
|
-
"main": "./dist/ln-ashlar.js",
|
|
26
|
-
"style": "./dist/ln-ashlar.css",
|
|
27
|
-
"web-types": "./web-types.json",
|
|
28
|
-
"files": [
|
|
29
|
-
"dist/",
|
|
30
|
-
"theme/",
|
|
31
|
-
"components/",
|
|
32
|
-
"assets/",
|
|
33
|
-
"ln-ashlar.html-data.json",
|
|
34
|
-
"web-types.json"
|
|
35
|
-
],
|
|
36
|
-
"exports": {
|
|
37
|
-
".": {
|
|
38
|
-
"import": "./dist/ln-ashlar.js",
|
|
39
|
-
"default": "./dist/ln-ashlar.js"
|
|
40
|
-
},
|
|
41
|
-
"./core.css": "./dist/ln-ashlar-core.css",
|
|
42
|
-
"./theme.css": "./dist/ln-ashlar-theme.css",
|
|
43
|
-
"./full.css": "./dist/ln-ashlar.css",
|
|
44
|
-
"./landing.css": "./dist/ln-ashlar-landing.css",
|
|
45
|
-
"./presentation.css": "./dist/ln-ashlar-landing.css",
|
|
46
|
-
"./dev.css": "./dist/ln-ashlar-dev.css",
|
|
47
|
-
"./dev.js": "./dist/ln-ashlar-dev.js",
|
|
48
|
-
"./theme/*": "./theme/*",
|
|
49
|
-
"./components/*": "./components/*",
|
|
50
|
-
"./tailwind.css": "./dist/ln-ashlar-tailwind.css"
|
|
51
|
-
},
|
|
52
|
-
"scripts": {
|
|
53
|
-
"test": "npm run sync:ln-schemas:check && npm run sync:ln-events:check && npm run sync:css-tokens:check && node --test \"tests/*.test.js\"",
|
|
54
|
-
"test:p0": "node --test tests/p0-data-integrity.test.js",
|
|
55
|
-
"test:headless": "node tests/headless/run-all.mjs",
|
|
56
|
-
"dev": "node scripts/build.mjs --watch",
|
|
57
|
-
"build": "npm run sync:ln-schemas && npm run sync:ln-events && npm run sync:css-tokens && node scripts/build.mjs && npm run build:tailwind && npm run build:dev-css && npm run build:demos",
|
|
58
|
-
"build:tailwind": "node scripts/build-tailwind.mjs",
|
|
59
|
-
"build:demos": "npm run build:demo-index && npm run build:demo-admin && npm run build:demo-docuflow && npm run build:demo-spa && npm run build:demo-landing && npm run build:demo-corporate",
|
|
60
|
-
"build:dev-css": "sass theme/ln-ashlar-dev.scss demo/dist/ln-ashlar-dev.css --no-source-map --style=compressed",
|
|
61
|
-
"generate:brand-assets": "powershell -ExecutionPolicy Bypass -File scripts/generate-brand-assets.ps1 && node scripts/generate-svg-favicon.mjs",
|
|
62
|
-
"build:demo-index": "sass demo/index.scss demo/index.css --no-source-map --style=compressed",
|
|
63
|
-
"build:demo-admin": "sass demo/admin/src/admin.scss demo/admin/dist/admin.css --no-source-map --style=compressed && sass demo/admin/src/admin-relaxed.scss demo/admin/dist/admin-relaxed.css --no-source-map --style=compressed && node -e \"const fs = require('fs'); fs.copyFileSync('demo/admin/src/demo.js', 'demo/admin/dist/demo.js')\" && node demo/admin/src/build-pages.mjs",
|
|
64
|
-
"build:demo-docuflow": "sass demo/docuflow/docuflow.scss demo/docuflow/dist/docuflow.css --no-source-map --style=compressed",
|
|
65
|
-
"build:demo-spa": "node demo/spa/build.mjs",
|
|
66
|
-
"build:demo-landing": "sass demo/landing/landing.scss demo/landing/landing.css --no-source-map --style=compressed",
|
|
67
|
-
"build:demo-corporate": "sass demo/corporate/corporate.scss demo/corporate/corporate.css --no-source-map --style=compressed",
|
|
68
|
-
"sync:ln-schemas": "node scripts/sync-ln-schemas.mjs",
|
|
69
|
-
"sync:ln-schemas:check": "node scripts/sync-ln-schemas.mjs --check",
|
|
70
|
-
"sync:ln-events": "node scripts/sync-ln-events.mjs",
|
|
71
|
-
"sync:ln-events:check": "node scripts/sync-ln-events.mjs --check",
|
|
72
|
-
"sync:ln-events:strict": "node scripts/sync-ln-events.mjs --strict",
|
|
73
|
-
"sync:css-tokens": "node scripts/sync-css-tokens.mjs",
|
|
74
|
-
"sync:css-tokens:check": "node scripts/sync-css-tokens.mjs --check",
|
|
75
|
-
"manifest": "node scripts/extract-manifest.mjs",
|
|
76
|
-
"manifest:file": "node scripts/extract-manifest.mjs --output=docs/manifest.txt",
|
|
77
|
-
"generate:llms": "node scripts/generate-llms.mjs",
|
|
78
|
-
"generate:llms:check": "node scripts/generate-llms.mjs --check",
|
|
79
|
-
"test:visual": "node tests/headless/visual-run.mjs",
|
|
80
|
-
"test:perf": "node tests/headless/perf-run.mjs",
|
|
81
|
-
"test:perf:history": "node tests/headless/perf-run.mjs --history",
|
|
82
|
-
"test:perf:compare": "node tests/headless/perf-run.mjs --compare",
|
|
83
|
-
"test:perf:density": "node tests/headless/perf-density.mjs",
|
|
84
|
-
"test:dual": "node tests/headless/dual-run.mjs"
|
|
85
|
-
},
|
|
86
|
-
"devDependencies": {
|
|
87
|
-
"@tabler/icons": "^3.41.1",
|
|
88
|
-
"@tailwindcss/cli": "^4.3.3",
|
|
89
|
-
"fake-indexeddb": "^6.2.4",
|
|
90
|
-
"flag-icons": "^7.5.0",
|
|
91
|
-
"puppeteer-core": "^25.1.0",
|
|
92
|
-
"sass-embedded": "^1.83.0",
|
|
93
|
-
"tailwindcss": "^4.3.3",
|
|
94
|
-
"vite": "^6.0.0"
|
|
95
|
-
}
|
|
96
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@livenetworks/ashlar",
|
|
3
|
+
"version": "2.2.3",
|
|
4
|
+
"description": "LiveNetworks unified frontend library - CSS framework and vanilla JS components",
|
|
5
|
+
"license": "GPL-3.0-or-later",
|
|
6
|
+
"author": "Live Networks",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/livenetworks/ln-ashlar.git"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/livenetworks/ln-ashlar#readme",
|
|
16
|
+
"bugs": "https://github.com/livenetworks/ln-ashlar/issues",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"css",
|
|
19
|
+
"scss",
|
|
20
|
+
"design-system",
|
|
21
|
+
"frontend",
|
|
22
|
+
"components",
|
|
23
|
+
"vanilla-js"
|
|
24
|
+
],
|
|
25
|
+
"main": "./dist/ln-ashlar.js",
|
|
26
|
+
"style": "./dist/ln-ashlar.css",
|
|
27
|
+
"web-types": "./web-types.json",
|
|
28
|
+
"files": [
|
|
29
|
+
"dist/",
|
|
30
|
+
"theme/",
|
|
31
|
+
"components/",
|
|
32
|
+
"assets/",
|
|
33
|
+
"ln-ashlar.html-data.json",
|
|
34
|
+
"web-types.json"
|
|
35
|
+
],
|
|
36
|
+
"exports": {
|
|
37
|
+
".": {
|
|
38
|
+
"import": "./dist/ln-ashlar.js",
|
|
39
|
+
"default": "./dist/ln-ashlar.js"
|
|
40
|
+
},
|
|
41
|
+
"./core.css": "./dist/ln-ashlar-core.css",
|
|
42
|
+
"./theme.css": "./dist/ln-ashlar-theme.css",
|
|
43
|
+
"./full.css": "./dist/ln-ashlar.css",
|
|
44
|
+
"./landing.css": "./dist/ln-ashlar-landing.css",
|
|
45
|
+
"./presentation.css": "./dist/ln-ashlar-landing.css",
|
|
46
|
+
"./dev.css": "./dist/ln-ashlar-dev.css",
|
|
47
|
+
"./dev.js": "./dist/ln-ashlar-dev.js",
|
|
48
|
+
"./theme/*": "./theme/*",
|
|
49
|
+
"./components/*": "./components/*",
|
|
50
|
+
"./tailwind.css": "./dist/ln-ashlar-tailwind.css"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"test": "npm run sync:ln-schemas:check && npm run sync:ln-events:check && npm run sync:css-tokens:check && node --test \"tests/*.test.js\"",
|
|
54
|
+
"test:p0": "node --test tests/p0-data-integrity.test.js",
|
|
55
|
+
"test:headless": "node tests/headless/run-all.mjs",
|
|
56
|
+
"dev": "node scripts/build.mjs --watch",
|
|
57
|
+
"build": "npm run sync:ln-schemas && npm run sync:ln-events && npm run sync:css-tokens && node scripts/build.mjs && npm run build:tailwind && npm run build:dev-css && npm run build:demos",
|
|
58
|
+
"build:tailwind": "node scripts/build-tailwind.mjs",
|
|
59
|
+
"build:demos": "npm run build:demo-index && npm run build:demo-admin && npm run build:demo-docuflow && npm run build:demo-spa && npm run build:demo-landing && npm run build:demo-corporate",
|
|
60
|
+
"build:dev-css": "sass theme/ln-ashlar-dev.scss demo/dist/ln-ashlar-dev.css --no-source-map --style=compressed",
|
|
61
|
+
"generate:brand-assets": "powershell -ExecutionPolicy Bypass -File scripts/generate-brand-assets.ps1 && node scripts/generate-svg-favicon.mjs",
|
|
62
|
+
"build:demo-index": "sass demo/index.scss demo/index.css --no-source-map --style=compressed",
|
|
63
|
+
"build:demo-admin": "sass demo/admin/src/admin.scss demo/admin/dist/admin.css --no-source-map --style=compressed && sass demo/admin/src/admin-relaxed.scss demo/admin/dist/admin-relaxed.css --no-source-map --style=compressed && node -e \"const fs = require('fs'); fs.copyFileSync('demo/admin/src/demo.js', 'demo/admin/dist/demo.js')\" && node demo/admin/src/build-pages.mjs",
|
|
64
|
+
"build:demo-docuflow": "sass demo/docuflow/docuflow.scss demo/docuflow/dist/docuflow.css --no-source-map --style=compressed",
|
|
65
|
+
"build:demo-spa": "node demo/spa/build.mjs",
|
|
66
|
+
"build:demo-landing": "sass demo/landing/landing.scss demo/landing/landing.css --no-source-map --style=compressed",
|
|
67
|
+
"build:demo-corporate": "sass demo/corporate/corporate.scss demo/corporate/corporate.css --no-source-map --style=compressed",
|
|
68
|
+
"sync:ln-schemas": "node scripts/sync-ln-schemas.mjs",
|
|
69
|
+
"sync:ln-schemas:check": "node scripts/sync-ln-schemas.mjs --check",
|
|
70
|
+
"sync:ln-events": "node scripts/sync-ln-events.mjs",
|
|
71
|
+
"sync:ln-events:check": "node scripts/sync-ln-events.mjs --check",
|
|
72
|
+
"sync:ln-events:strict": "node scripts/sync-ln-events.mjs --strict",
|
|
73
|
+
"sync:css-tokens": "node scripts/sync-css-tokens.mjs",
|
|
74
|
+
"sync:css-tokens:check": "node scripts/sync-css-tokens.mjs --check",
|
|
75
|
+
"manifest": "node scripts/extract-manifest.mjs",
|
|
76
|
+
"manifest:file": "node scripts/extract-manifest.mjs --output=docs/manifest.txt",
|
|
77
|
+
"generate:llms": "node scripts/generate-llms.mjs",
|
|
78
|
+
"generate:llms:check": "node scripts/generate-llms.mjs --check",
|
|
79
|
+
"test:visual": "node tests/headless/visual-run.mjs",
|
|
80
|
+
"test:perf": "node tests/headless/perf-run.mjs",
|
|
81
|
+
"test:perf:history": "node tests/headless/perf-run.mjs --history",
|
|
82
|
+
"test:perf:compare": "node tests/headless/perf-run.mjs --compare",
|
|
83
|
+
"test:perf:density": "node tests/headless/perf-density.mjs",
|
|
84
|
+
"test:dual": "node tests/headless/dual-run.mjs"
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"@tabler/icons": "^3.41.1",
|
|
88
|
+
"@tailwindcss/cli": "^4.3.3",
|
|
89
|
+
"fake-indexeddb": "^6.2.4",
|
|
90
|
+
"flag-icons": "^7.5.0",
|
|
91
|
+
"puppeteer-core": "^25.1.0",
|
|
92
|
+
"sass-embedded": "^1.83.0",
|
|
93
|
+
"tailwindcss": "^4.3.3",
|
|
94
|
+
"vite": "^6.0.0"
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -4,8 +4,11 @@
|
|
|
4
4
|
@include dropdown;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
+
// The ln-dropdown coordinator marks its menu [data-ln-dropdown-menu] at
|
|
8
|
+
// init; .dropdown is the same panel + items as a class (visual hook in
|
|
9
|
+
// markup, or a menu the coordinator doesn't own).
|
|
7
10
|
[data-ln-dropdown-menu],
|
|
8
|
-
.
|
|
11
|
+
.dropdown {
|
|
9
12
|
@include dropdown-menu;
|
|
10
13
|
}
|
|
11
14
|
|
|
@@ -28,6 +28,12 @@ $bp-xl: 1280px;
|
|
|
28
28
|
$bp-2xl: 1536px;
|
|
29
29
|
$bp-3xl: 1920px;
|
|
30
30
|
|
|
31
|
+
// -----------------------------------------------------------------------
|
|
32
|
+
// Presentation navbar collapse (for @media — presentation/_navbar.scss)
|
|
33
|
+
// -----------------------------------------------------------------------
|
|
34
|
+
// Below this width .site-header hides .nav-menu and shows .menu-toggle.
|
|
35
|
+
$bp-nav: 820px;
|
|
36
|
+
|
|
31
37
|
// -----------------------------------------------------------------------
|
|
32
38
|
// Container-query breakpoints (for @container — components)
|
|
33
39
|
// -----------------------------------------------------------------------
|
|
@@ -46,6 +52,7 @@ $cq-wide: 1120px;
|
|
|
46
52
|
--bp-xl: #{$bp-xl};
|
|
47
53
|
--bp-2xl: #{$bp-2xl};
|
|
48
54
|
--bp-3xl: #{$bp-3xl};
|
|
55
|
+
--bp-nav: #{$bp-nav};
|
|
49
56
|
|
|
50
57
|
--cq-narrow: #{$cq-narrow};
|
|
51
58
|
--cq-compact: #{$cq-compact};
|
|
@@ -65,6 +72,7 @@ $breakpoints: (
|
|
|
65
72
|
xl: $bp-xl,
|
|
66
73
|
2xl: $bp-2xl,
|
|
67
74
|
3xl: $bp-3xl,
|
|
75
|
+
nav: $bp-nav,
|
|
68
76
|
narrow: $cq-narrow,
|
|
69
77
|
compact: $cq-compact,
|
|
70
78
|
medium: $cq-medium,
|
package/theme/config/_theme.scss
CHANGED
|
@@ -247,6 +247,7 @@
|
|
|
247
247
|
// because --border-block-start is read by ~10 other mixins
|
|
248
248
|
// (cards, tables, accordions) where the divider should NOT appear.
|
|
249
249
|
[data-ln-dropdown-menu],
|
|
250
|
+
.dropdown,
|
|
250
251
|
.menu-items {
|
|
251
252
|
--border-block-start: var(--border-width) solid var(--color-border);
|
|
252
253
|
}
|
|
@@ -30,13 +30,16 @@
|
|
|
30
30
|
|
|
31
31
|
// Only whole-card links/buttons signal interaction. Passive containers
|
|
32
32
|
// must not respond merely because the pointer crosses their content.
|
|
33
|
+
// A whole-card link is the card's ONLY child (`> a:only-child`); a link
|
|
34
|
+
// that sits next to other content (a "Read more" under a paragraph) is
|
|
35
|
+
// an ordinary link and leaves the card alone.
|
|
33
36
|
&:is(a, button):hover:not(:disabled),
|
|
34
|
-
&:has(> a:hover) {
|
|
37
|
+
&:has(> a:only-child:hover) {
|
|
35
38
|
--color-border: color-mix(in srgb, var(--color-accent) 35%, transparent);
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
&:is(a, button):focus-visible,
|
|
39
|
-
&:has(> a:focus-visible) {
|
|
42
|
+
&:has(> a:only-child:focus-visible) {
|
|
40
43
|
@include focus-ring;
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -56,7 +59,7 @@
|
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
// Whole-card-as-link: <a> wraps card content and fills the card.
|
|
59
|
-
> a {
|
|
62
|
+
> a:only-child {
|
|
60
63
|
@include flex-col;
|
|
61
64
|
@include flex-1;
|
|
62
65
|
text-decoration: none;
|
|
@@ -263,7 +266,7 @@
|
|
|
263
266
|
|
|
264
267
|
@mixin _card-accent-base {
|
|
265
268
|
&:is(a, button):hover:not(:disabled),
|
|
266
|
-
&:has(> a:hover) {
|
|
269
|
+
&:has(> a:only-child:hover) {
|
|
267
270
|
background-color: color-mix(in srgb, var(--color-accent) var(--tint-hover), transparent);
|
|
268
271
|
}
|
|
269
272
|
}
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
//
|
|
14
14
|
// Usage:
|
|
15
15
|
// [data-ln-dropdown] { @include dropdown; }
|
|
16
|
-
// [data-ln-dropdown-menu] { @include dropdown-menu; }
|
|
16
|
+
// [data-ln-dropdown-menu], .dropdown { @include dropdown-menu; }
|
|
17
|
+
// (both global bindings in components/_dropdown.scss)
|
|
17
18
|
//
|
|
18
19
|
// Open/close state (display:none ↔ display:block + .open) is JS-driven
|
|
19
20
|
// and lives in the co-located JS SCSS, not here.
|
|
@@ -53,6 +54,16 @@
|
|
|
53
54
|
}
|
|
54
55
|
|
|
55
56
|
:is(li, form) > :is(a, button, input[type="submit"], input[type="reset"], input[type="button"]) {
|
|
57
|
+
// One row recipe, whatever the element. <button> rebinds --padding-*
|
|
58
|
+
// and --gap on itself (base/_global.scss), and ancestor scopes rebind
|
|
59
|
+
// them for their own buttons (@mixin app-header zeroes --btn-padding-*
|
|
60
|
+
// and mutes --color-fg; a popover inside .app-header is still in its
|
|
61
|
+
// DOM). Taking these from the list keeps a <form> submit row on the
|
|
62
|
+
// same geometry and ink as an <a> row.
|
|
63
|
+
--padding-y: inherit;
|
|
64
|
+
--padding-x: inherit;
|
|
65
|
+
--gap: inherit;
|
|
66
|
+
--color-fg: inherit;
|
|
56
67
|
width: 100%;
|
|
57
68
|
box-sizing: border-box;
|
|
58
69
|
display: flex;
|
|
@@ -67,12 +78,21 @@
|
|
|
67
78
|
box-shadow: none;
|
|
68
79
|
text-align: left;
|
|
69
80
|
text-decoration: none;
|
|
81
|
+
white-space: normal; // button-base: nowrap
|
|
70
82
|
cursor: pointer;
|
|
71
83
|
font-family: inherit;
|
|
72
84
|
@include typography(body-sm);
|
|
85
|
+
@include font-normal; // button-base: medium
|
|
73
86
|
color: var(--color-fg);
|
|
74
87
|
transition: background-color 120ms ease, color 120ms ease;
|
|
75
88
|
|
|
89
|
+
// Semantic item (e.g. a destructive "Delete"): ink and wash both
|
|
90
|
+
// follow the status color. Same shape as btn-ghost / btn-outline.
|
|
91
|
+
&:is(.success, .error, .warning, .info) {
|
|
92
|
+
--color-accent: hsl(var(--color-primary));
|
|
93
|
+
--color-fg: var(--color-accent);
|
|
94
|
+
}
|
|
95
|
+
|
|
76
96
|
&:hover:not(:disabled) {
|
|
77
97
|
background-color: color-mix(in srgb, var(--color-accent) var(--tint-hover), transparent);
|
|
78
98
|
color: var(--color-fg);
|
|
@@ -141,7 +161,6 @@
|
|
|
141
161
|
@include fixed;
|
|
142
162
|
inset: auto;
|
|
143
163
|
margin: 0;
|
|
144
|
-
padding: var(--padding-y);
|
|
145
164
|
min-width: var(--dropdown-min-width, 14rem);
|
|
146
165
|
@include enter-scale-fade;
|
|
147
166
|
|
|
@@ -167,4 +186,9 @@
|
|
|
167
186
|
}
|
|
168
187
|
|
|
169
188
|
@include menu-items;
|
|
189
|
+
|
|
190
|
+
// Panel inset — after menu-items on purpose: the panel IS the <ul>,
|
|
191
|
+
// and menu-items' list reset (padding: 0) would otherwise zero it,
|
|
192
|
+
// leaving the item highlight flush against the panel border.
|
|
193
|
+
padding: var(--padding-y);
|
|
170
194
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
@use 'form' as *;
|
|
6
6
|
@use 'focus' as *;
|
|
7
7
|
@use 'dropdown' as *;
|
|
8
|
+
@use 'typography' as *;
|
|
8
9
|
|
|
9
10
|
@mixin popover {
|
|
10
11
|
@include floating-panel;
|
|
@@ -37,7 +38,23 @@
|
|
|
37
38
|
margin: var(--size-xs) 0;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
|
-
> ul
|
|
41
|
+
// Menu popover caption (profile menu: <p> + <hr> + <nav><ul>) — sits on
|
|
42
|
+
// the item labels' inline grid instead of the bare panel inset. An item
|
|
43
|
+
// may sit in a <form> (POST/DELETE action), as @mixin menu-items allows.
|
|
44
|
+
&:has(
|
|
45
|
+
> ul > li > :is(a, button),
|
|
46
|
+
> ul > li > form > button,
|
|
47
|
+
> nav > ul > li > :is(a, button),
|
|
48
|
+
> nav > ul > li > form > button
|
|
49
|
+
) > p {
|
|
50
|
+
margin: 0;
|
|
51
|
+
padding: var(--padding-y) var(--padding-x);
|
|
52
|
+
@include typography(body-sm);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// `nav > ul` is the canonical profile-menu shape (see ln-popover markup).
|
|
56
|
+
> ul,
|
|
57
|
+
> nav > ul {
|
|
41
58
|
list-style: none;
|
|
42
59
|
padding: 0;
|
|
43
60
|
margin: 0;
|
|
@@ -68,8 +85,12 @@
|
|
|
68
85
|
margin-top: var(--size-2xs);
|
|
69
86
|
}
|
|
70
87
|
|
|
71
|
-
// When containing interactive actions/buttons (menus, pickers)
|
|
72
|
-
|
|
88
|
+
// When containing interactive actions/buttons (menus, pickers),
|
|
89
|
+
// directly or inside a <form> (POST/DELETE action).
|
|
90
|
+
&:has(
|
|
91
|
+
> li > :is(a, button, input[type="submit"], input[type="reset"], input[type="button"]),
|
|
92
|
+
> li > form > :is(button, input[type="submit"], input[type="reset"], input[type="button"])
|
|
93
|
+
) {
|
|
73
94
|
@include menu-items;
|
|
74
95
|
}
|
|
75
96
|
|
|
@@ -97,6 +97,74 @@
|
|
|
97
97
|
@include glass-panel;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Photo Backdrop (full-bleed photograph behind a region's content)
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// The region's FIRST child is a <figure><img></figure>; it fills the region
|
|
104
|
+
// behind everything that follows. The scrim is mixed from the region's own
|
|
105
|
+
// --bg-base, so the region's text tokens stay legible in light mode, dark
|
|
106
|
+
// mode and scoped theme islands — set data-mode / data-theme on the region,
|
|
107
|
+
// never a hardcoded text color.
|
|
108
|
+
//
|
|
109
|
+
// <section id="hero" class="hero-section photo-backdrop" data-mode="dark">
|
|
110
|
+
// <figure><img src="…" alt="…"></figure>
|
|
111
|
+
// <div class="landing-container">…</div>
|
|
112
|
+
// </section>
|
|
113
|
+
//
|
|
114
|
+
// #hero { @include photo-backdrop; } // default scrim
|
|
115
|
+
// #cta { @include photo-backdrop(photo-scrim(180deg, 85%, 40%)); }
|
|
116
|
+
// #story { @include photo-backdrop(linear-gradient(…brand tint…)); }
|
|
117
|
+
// #plain { @include photo-backdrop(null); } // no scrim
|
|
118
|
+
//
|
|
119
|
+
// photo-scrim($angle, $from, $to): $from / $to are the --bg-base share at
|
|
120
|
+
// the start / end of the gradient (100% = opaque surface).
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
@function photo-scrim($angle: 90deg, $from: 92%, $to: 30%) {
|
|
123
|
+
$mid: ($from + $to) * 0.5;
|
|
124
|
+
|
|
125
|
+
@return linear-gradient(
|
|
126
|
+
$angle,
|
|
127
|
+
color-mix(in srgb, var(--bg-base) #{$from}, transparent) 0%,
|
|
128
|
+
color-mix(in srgb, var(--bg-base) #{$mid}, transparent) 45%,
|
|
129
|
+
color-mix(in srgb, var(--bg-base) #{$to}, transparent) 100%
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@mixin photo-backdrop($scrim: photo-scrim()) {
|
|
134
|
+
position: relative;
|
|
135
|
+
isolation: isolate;
|
|
136
|
+
// [data-mode] re-derives --color-fg on the region, but `color` itself is
|
|
137
|
+
// inherited already resolved from <body>; re-read it here.
|
|
138
|
+
color: var(--color-fg);
|
|
139
|
+
|
|
140
|
+
> figure:first-child {
|
|
141
|
+
position: absolute;
|
|
142
|
+
inset: 0;
|
|
143
|
+
z-index: -1;
|
|
144
|
+
margin: 0;
|
|
145
|
+
|
|
146
|
+
img {
|
|
147
|
+
display: block;
|
|
148
|
+
width: 100%;
|
|
149
|
+
height: 100%;
|
|
150
|
+
object-fit: cover;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@if $scrim {
|
|
154
|
+
&::after {
|
|
155
|
+
content: '';
|
|
156
|
+
position: absolute;
|
|
157
|
+
inset: 0;
|
|
158
|
+
background: $scrim;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.photo-backdrop {
|
|
165
|
+
@include photo-backdrop;
|
|
166
|
+
}
|
|
167
|
+
|
|
100
168
|
// ---------------------------------------------------------------------------
|
|
101
169
|
// Hover Glow & Elevation
|
|
102
170
|
// ---------------------------------------------------------------------------
|
|
@@ -2,8 +2,29 @@
|
|
|
2
2
|
// Presentation Header & Navigation — ln-ashlar
|
|
3
3
|
// ==========================================================================
|
|
4
4
|
// Sticky frosted glass header, brand mark, navigation links, and action bar.
|
|
5
|
+
//
|
|
6
|
+
// Below the `nav` breakpoint the inline .nav-menu collapses: .menu-toggle
|
|
7
|
+
// in .nav-actions opens a <nav> that is a direct child of .site-header
|
|
8
|
+
// (ln-toggle + .collapsible). Above it, the toggle and the panel are hidden.
|
|
9
|
+
//
|
|
10
|
+
// <header class="site-header">
|
|
11
|
+
// <div class="landing-container">
|
|
12
|
+
// <nav class="site-nav">
|
|
13
|
+
// …brand, ul.nav-menu…
|
|
14
|
+
// <div class="nav-actions">
|
|
15
|
+
// <button type="button" class="btn btn-ghost btn-sm menu-toggle"
|
|
16
|
+
// data-ln-toggle-for="site-menu" aria-label="Menu">…</button>
|
|
17
|
+
// </div>
|
|
18
|
+
// </nav>
|
|
19
|
+
// </div>
|
|
20
|
+
// <nav id="site-menu" data-ln-toggle class="collapsible" aria-label="Menu">
|
|
21
|
+
// <div class="collapsible-body"><ul><li><a href="…">…</a></li></ul></div>
|
|
22
|
+
// </nav>
|
|
23
|
+
// </header>
|
|
5
24
|
// ==========================================================================
|
|
6
25
|
|
|
26
|
+
@use '../config/mixins' as *;
|
|
27
|
+
|
|
7
28
|
.site-header {
|
|
8
29
|
position: sticky;
|
|
9
30
|
top: 0;
|
|
@@ -60,7 +81,7 @@
|
|
|
60
81
|
margin: 0;
|
|
61
82
|
padding: 0;
|
|
62
83
|
|
|
63
|
-
@
|
|
84
|
+
@include mq-down(nav) {
|
|
64
85
|
display: none;
|
|
65
86
|
}
|
|
66
87
|
|
|
@@ -97,6 +118,56 @@
|
|
|
97
118
|
align-items: center;
|
|
98
119
|
gap: var(--size-sm);
|
|
99
120
|
}
|
|
121
|
+
|
|
122
|
+
// Collapsed-menu trigger — only while .nav-menu is hidden.
|
|
123
|
+
.menu-toggle {
|
|
124
|
+
@include mq-up(nav) {
|
|
125
|
+
display: none;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Collapsed-menu panel. The .collapsible parent stays padding-free;
|
|
130
|
+
// the list inside .collapsible-body carries the spacing.
|
|
131
|
+
> nav {
|
|
132
|
+
@include mq-up(nav) {
|
|
133
|
+
display: none;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
ul {
|
|
137
|
+
display: flex;
|
|
138
|
+
flex-direction: column;
|
|
139
|
+
gap: var(--size-2xs);
|
|
140
|
+
list-style: none;
|
|
141
|
+
margin: 0;
|
|
142
|
+
padding: var(--size-sm) var(--container-padding-x) var(--size-md);
|
|
143
|
+
border-top: var(--border-width) solid color-mix(in srgb, var(--color-border) 60%, transparent);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
li {
|
|
147
|
+
margin: 0;
|
|
148
|
+
padding: 0;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
a {
|
|
152
|
+
display: block;
|
|
153
|
+
padding: var(--size-sm-up) var(--size-sm);
|
|
154
|
+
border-radius: var(--radius-md);
|
|
155
|
+
font-weight: 600;
|
|
156
|
+
color: var(--color-fg);
|
|
157
|
+
text-decoration: none;
|
|
158
|
+
transition: color var(--transition-fast), background-color var(--transition-fast);
|
|
159
|
+
|
|
160
|
+
&:hover {
|
|
161
|
+
background-color: var(--bg-hover);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
&.active,
|
|
165
|
+
&[aria-current="page"] {
|
|
166
|
+
color: hsl(var(--color-primary));
|
|
167
|
+
background-color: color-mix(in srgb, hsl(var(--color-primary)) 10%, transparent);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
100
171
|
}
|
|
101
172
|
|
|
102
173
|
// ---------------------------------------------------------------------------
|
|
@@ -161,3 +161,16 @@
|
|
|
161
161
|
text-decoration-color: hsl(var(--color-primary));
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
|
+
|
|
165
|
+
// A link inside a heading is a title link (card, article, news item), not an
|
|
166
|
+
// inline text link: it keeps the heading color and only tints on hover. Same
|
|
167
|
+
// specificity as the rule above and declared after it, so it wins for
|
|
168
|
+
// `li h3 a` without touching inline links in the same list item.
|
|
169
|
+
.landing-section :is(h1, h2, h3, h4, h5, h6) a:not(.btn) {
|
|
170
|
+
color: inherit;
|
|
171
|
+
text-decoration: none;
|
|
172
|
+
|
|
173
|
+
&:hover {
|
|
174
|
+
color: hsl(var(--color-primary));
|
|
175
|
+
}
|
|
176
|
+
}
|