@aquera/nile-elements 2.0.9 → 2.0.11

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.
Files changed (142) hide show
  1. package/README.md +11 -0
  2. package/demo/app-shell.html +852 -0
  3. package/demo/nxtgen-classes.css +5 -5
  4. package/demo/nxtgen-dark.css +25 -25
  5. package/demo/nxtgen-properties.css +2 -2
  6. package/demo/nxtgen-utilities.css +5 -5
  7. package/demo/nxtgen.css +5 -5
  8. package/demo/utilities.css +2 -2
  9. package/demo/variables.css +2 -2
  10. package/dist/index.cjs.js +1 -1
  11. package/dist/index.esm.js +1 -1
  12. package/dist/index.js +594 -308
  13. package/dist/internal/app-shell-utility-control-styles.cjs.js +2 -0
  14. package/dist/internal/app-shell-utility-control-styles.cjs.js.map +1 -0
  15. package/dist/internal/app-shell-utility-control-styles.esm.js +16 -0
  16. package/dist/nile-app-shell/index.cjs.js +1 -1
  17. package/dist/nile-app-shell/index.esm.js +1 -1
  18. package/dist/nile-app-shell/nile-app-shell.cjs.js +1 -1
  19. package/dist/nile-app-shell/nile-app-shell.cjs.js.map +1 -1
  20. package/dist/nile-app-shell/nile-app-shell.css.cjs.js +1 -1
  21. package/dist/nile-app-shell/nile-app-shell.css.cjs.js.map +1 -1
  22. package/dist/nile-app-shell/nile-app-shell.css.esm.js +270 -92
  23. package/dist/nile-app-shell/nile-app-shell.esm.js +61 -10
  24. package/dist/nile-app-shell/shell-controller.cjs.js.map +1 -1
  25. package/dist/nile-app-shell-main/nile-app-shell-main.cjs.js.map +1 -1
  26. package/dist/nile-app-shell-nav-toggle/index.cjs.js +1 -1
  27. package/dist/nile-app-shell-nav-toggle/index.esm.js +1 -1
  28. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.cjs.js +1 -1
  29. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.cjs.js.map +1 -1
  30. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.cjs.js +1 -1
  31. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.cjs.js.map +1 -1
  32. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.esm.js +7 -3
  33. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.esm.js +3 -3
  34. package/dist/nile-app-shell-panel/index.cjs.js +1 -1
  35. package/dist/nile-app-shell-panel/index.esm.js +1 -1
  36. package/dist/nile-app-shell-panel/nile-app-shell-panel.cjs.js +1 -1
  37. package/dist/nile-app-shell-panel/nile-app-shell-panel.cjs.js.map +1 -1
  38. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.cjs.js +1 -1
  39. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.cjs.js.map +1 -1
  40. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.esm.js +29 -7
  41. package/dist/nile-app-shell-panel/nile-app-shell-panel.esm.js +18 -5
  42. package/dist/nile-app-shell-side-nav/nile-app-shell-side-nav.cjs.js.map +1 -1
  43. package/dist/nile-app-shell-side-nav/nile-app-shell-side-nav.css.cjs.js.map +1 -1
  44. package/dist/nile-badge/nile-badge.css.cjs.js +1 -1
  45. package/dist/nile-badge/nile-badge.css.cjs.js.map +1 -1
  46. package/dist/nile-badge/nile-badge.css.esm.js +2 -2
  47. package/dist/nile-button-toggle/nile-button-toggle.css.cjs.js +1 -1
  48. package/dist/nile-button-toggle/nile-button-toggle.css.cjs.js.map +1 -1
  49. package/dist/nile-button-toggle/nile-button-toggle.css.esm.js +2 -2
  50. package/dist/nile-code-editor/nile-code-editor.cjs.js +1 -1
  51. package/dist/nile-code-editor/nile-code-editor.cjs.js.map +1 -1
  52. package/dist/nile-code-editor/nile-code-editor.esm.js +1 -1
  53. package/dist/nile-menu-item/nile-menu-item.css.cjs.js +1 -1
  54. package/dist/nile-menu-item/nile-menu-item.css.cjs.js.map +1 -1
  55. package/dist/nile-menu-item/nile-menu-item.css.esm.js +1 -1
  56. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js +1 -1
  57. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js.map +1 -1
  58. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.esm.js +5 -2
  59. package/dist/nile-toast/nile-toast.css.cjs.js +1 -1
  60. package/dist/nile-toast/nile-toast.css.cjs.js.map +1 -1
  61. package/dist/nile-toast/nile-toast.css.esm.js +5 -5
  62. package/dist/src/internal/app-shell-utility-control-styles.d.ts +12 -0
  63. package/dist/src/internal/app-shell-utility-control-styles.js +29 -0
  64. package/dist/src/internal/app-shell-utility-control-styles.js.map +1 -0
  65. package/dist/src/nile-app-shell/nile-app-shell-regions.test.d.ts +3 -0
  66. package/dist/src/nile-app-shell/nile-app-shell-regions.test.js +206 -0
  67. package/dist/src/nile-app-shell/nile-app-shell-regions.test.js.map +1 -1
  68. package/dist/src/nile-app-shell/nile-app-shell.css.d.ts +1 -9
  69. package/dist/src/nile-app-shell/nile-app-shell.css.js +270 -99
  70. package/dist/src/nile-app-shell/nile-app-shell.css.js.map +1 -1
  71. package/dist/src/nile-app-shell/nile-app-shell.d.ts +90 -122
  72. package/dist/src/nile-app-shell/nile-app-shell.js +224 -143
  73. package/dist/src/nile-app-shell/nile-app-shell.js.map +1 -1
  74. package/dist/src/nile-app-shell/nile-app-shell.test.d.ts +3 -0
  75. package/dist/src/nile-app-shell/nile-app-shell.test.js +898 -29
  76. package/dist/src/nile-app-shell/nile-app-shell.test.js.map +1 -1
  77. package/dist/src/nile-app-shell/shell-controller.d.ts +1 -8
  78. package/dist/src/nile-app-shell/shell-controller.js +2 -14
  79. package/dist/src/nile-app-shell/shell-controller.js.map +1 -1
  80. package/dist/src/nile-app-shell-main/nile-app-shell-main.d.ts +2 -6
  81. package/dist/src/nile-app-shell-main/nile-app-shell-main.js +2 -6
  82. package/dist/src/nile-app-shell-main/nile-app-shell-main.js.map +1 -1
  83. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.d.ts +1 -4
  84. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.js +7 -5
  85. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.js.map +1 -1
  86. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.d.ts +4 -8
  87. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.js +5 -6
  88. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.js.map +1 -1
  89. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.css.js +28 -5
  90. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.css.js.map +1 -1
  91. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.d.ts +18 -13
  92. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.js +45 -16
  93. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.js.map +1 -1
  94. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.d.ts +1 -4
  95. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.js +1 -4
  96. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.js.map +1 -1
  97. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.d.ts +4 -12
  98. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.js +4 -12
  99. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.js.map +1 -1
  100. package/dist/src/nile-badge/nile-badge.css.js +2 -2
  101. package/dist/src/nile-badge/nile-badge.css.js.map +1 -1
  102. package/dist/src/nile-button-toggle/nile-button-toggle.css.js +2 -2
  103. package/dist/src/nile-button-toggle/nile-button-toggle.css.js.map +1 -1
  104. package/dist/src/nile-code-editor/nile-code-editor.d.ts +1 -1
  105. package/dist/src/nile-code-editor/nile-code-editor.js +2 -2
  106. package/dist/src/nile-code-editor/nile-code-editor.js.map +1 -1
  107. package/dist/src/nile-menu-item/nile-menu-item.css.js +1 -1
  108. package/dist/src/nile-menu-item/nile-menu-item.css.js.map +1 -1
  109. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js +5 -2
  110. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js.map +1 -1
  111. package/dist/src/nile-toast/nile-toast.css.js +5 -5
  112. package/dist/src/nile-toast/nile-toast.css.js.map +1 -1
  113. package/dist/src/version.js +1 -1
  114. package/dist/src/version.js.map +1 -1
  115. package/dist/tsconfig.tsbuildinfo +1 -1
  116. package/dist/version.cjs.js +1 -1
  117. package/dist/version.cjs.js.map +1 -1
  118. package/dist/version.esm.js +1 -1
  119. package/package.json +1 -1
  120. package/plop-templates/lit/lit.test.ts.hbs +22 -0
  121. package/plop-templates/lit/lit.ts.hbs +10 -5
  122. package/plopfile.js +12 -0
  123. package/src/internal/app-shell-utility-control-styles.ts +30 -0
  124. package/src/nile-app-shell/nile-app-shell-regions.test.ts +248 -0
  125. package/src/nile-app-shell/nile-app-shell.css.ts +270 -99
  126. package/src/nile-app-shell/nile-app-shell.test.ts +1120 -29
  127. package/src/nile-app-shell/nile-app-shell.ts +241 -148
  128. package/src/nile-app-shell/shell-controller.ts +2 -14
  129. package/src/nile-app-shell-main/nile-app-shell-main.ts +2 -6
  130. package/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.ts +7 -5
  131. package/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.ts +7 -11
  132. package/src/nile-app-shell-panel/nile-app-shell-panel.css.ts +28 -5
  133. package/src/nile-app-shell-panel/nile-app-shell-panel.ts +44 -16
  134. package/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.ts +1 -4
  135. package/src/nile-app-shell-side-nav/nile-app-shell-side-nav.ts +4 -12
  136. package/src/nile-badge/nile-badge.css.ts +2 -2
  137. package/src/nile-button-toggle/nile-button-toggle.css.ts +2 -2
  138. package/src/nile-code-editor/nile-code-editor.ts +2 -2
  139. package/src/nile-menu-item/nile-menu-item.css.ts +1 -1
  140. package/src/nile-rich-text-editor/nile-rich-text-editor.css.ts +5 -2
  141. package/src/nile-toast/nile-toast.css.ts +5 -5
  142. package/vscode-html-custom-data.json +114 -37
@@ -5,28 +5,31 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import { css } from 'lit';
8
- /**
9
- * AppShell CSS.
10
- *
11
- * One grid, two columns, one flex row. The grid owns the banner row, the
12
- * optional shell-wide top bar, the side-nav track and the content column. The
13
- * content column is a flex row holding the content container, the optional
14
- * aside and — when docked — the AI panel. Nothing else in an app sets these
15
- * boxes.
16
- */
8
+ import { utilityControlStyles } from '../internal/app-shell-utility-control-styles';
9
+ /** AppShell CSS: a grid of banner, top bar, side-nav track and content column, whose flex row holds the container, aside and docked AI panel. */
17
10
  export const styles = css `
18
11
  :host {
19
- /* Geometry. Values come from design tokens wherever one exists; the bare
20
- pixels are shell geometry with no matching token. */
21
- --nile-app-shell-side-nav-width: 260px;
22
- --nile-app-shell-side-nav-rail-width: 64px;
23
- --nile-app-shell-gutter: var(--nile-spacing-md, var(--ng-spacing-md));
12
+ /* Geometry: design tokens where one exists, bare pixels where none does. */
13
+ /* The nav track mirrors nile-side-bar's own width expressions (enterprise 240/70, NxtGen 296/68); keep in step with nile-side-bar.css.ts. */
14
+ --nile-app-shell-side-nav-width: var(
15
+ --nile-width-240px,
16
+ var(--ng-spacing-296, 240px)
17
+ );
18
+ --nile-app-shell-side-nav-rail-width: var(
19
+ --nile-width-70px,
20
+ var(--ng-spacing-68, 70px)
21
+ );
22
+ --nile-app-shell-gutter: var(--nile-spacing-xs, var(--ng-spacing-xs));
24
23
  --nile-app-shell-content-padding: var(
25
24
  --nile-spacing-3xl,
26
25
  var(--ng-spacing-3xl)
27
26
  );
28
- --nile-app-shell-top-bar-height: 56px;
29
- --nile-app-shell-tabs-height: 48px;
27
+ --nile-app-shell-top-bar-height: 68px;
28
+ /* The panel header keeps its own height, distinct from the page header. */
29
+ --nile-app-shell-panel-header-height: 56px;
30
+ --nile-app-shell-tabs-height: 33px;
31
+ /* The leading app icon is its own 20px size, not the 32px utility box. */
32
+ --nile-app-shell-app-icon-size: 20px;
30
33
  --nile-app-shell-panel-width: 480px;
31
34
  --nile-app-shell-panel-min-width: 360px;
32
35
  --nile-app-shell-aside-width: 320px;
@@ -34,9 +37,10 @@ export const styles = css `
34
37
  --nile-app-shell-content-min-width: 640px;
35
38
  --nile-app-shell-min-width: 768px;
36
39
  --nile-app-shell-centered-width: 824px;
40
+ /* Both tokens are 32px; the literal is the floor with no theme sheet loaded. */
37
41
  --nile-app-shell-utility-size: var(
38
42
  --nile-spacing-4xl,
39
- var(--ng-spacing-4xl)
43
+ var(--ng-spacing-4xl, 32px)
40
44
  );
41
45
  --nile-app-shell-radius: var(--nile-radius-xl, var(--ng-radius-2xl));
42
46
  --nile-app-shell-duration: var(
@@ -76,8 +80,7 @@ export const styles = css `
76
80
  height: 100dvh;
77
81
  }
78
82
 
79
- /* Effective rail state, set by the component: the user's preference, or a
80
- forced rail at narrow widths. */
83
+ /* Effective rail state: the user's preference, or a forced rail when narrow. */
81
84
  :host([rail]) {
82
85
  --_side-nav-track: var(--nile-app-shell-side-nav-rail-width);
83
86
  }
@@ -86,10 +89,19 @@ export const styles = css `
86
89
  --_side-nav-track: 0px;
87
90
  }
88
91
 
92
+ /* The Description axis: a present description adds the 8px its second line occupies. */
93
+ :host([has-description]) {
94
+ --nile-app-shell-top-bar-height: 76px;
95
+ }
96
+
89
97
  /* The top bar compresses before the content does. */
90
98
  @media (max-height: 600px) {
91
99
  :host {
92
- --nile-app-shell-top-bar-height: 48px;
100
+ --nile-app-shell-top-bar-height: 56px;
101
+ }
102
+
103
+ :host([has-description]) {
104
+ --nile-app-shell-top-bar-height: 64px;
93
105
  }
94
106
  }
95
107
 
@@ -117,13 +129,12 @@ export const styles = css `
117
129
  display: none;
118
130
  }
119
131
 
120
- /*
121
- * The nav and the page share one surface, and the shell owns the track
122
- * width, so a slotted sidebar fills the track and drops its own background
123
- * and edge. Set --nile-app-shell-side-nav-bg to put a surface back.
124
- */
132
+ /* A slotted sidebar fills the shell-owned track and drops its own surface and edge; set --nile-app-shell-side-nav-bg to restore one. */
133
+ /* width is deliberately not !important: the 100% lets a raw <nav> or nile-app-shell-side-nav fill the track, while a slotted
134
+ nile-side-bar sizes itself from the same tokens the track reads (see the :host geometry block). Tests 21/22 in
135
+ nile-app-shell-regions.test.ts lock the two together; change both or neither. */
125
136
  ::slotted([slot='side-nav']) {
126
- width: 100% !important;
137
+ width: 100%;
127
138
  height: 100%;
128
139
  background: var(--nile-app-shell-side-nav-bg, transparent) !important;
129
140
  border-right: var(--nile-app-shell-side-nav-border, 0) !important;
@@ -138,19 +149,19 @@ export const styles = css `
138
149
  min-width: 0;
139
150
  gap: var(--nile-app-shell-gutter);
140
151
  padding: var(--nile-app-shell-gutter);
152
+ /* Flush against the nav track; the gutter insets the other three sides. */
153
+ padding-inline-start: 0;
141
154
  box-sizing: border-box;
142
155
  }
143
156
 
157
+ /* With no nav track there is nothing to sit flush against, so the start edge takes the gutter too. */
158
+ :host(:not([has-side-nav])) .content-section {
159
+ padding-inline-start: var(--nile-app-shell-gutter);
160
+ }
161
+
144
162
  .container {
145
163
  box-sizing: border-box;
146
- /*
147
- * The floor is the flex basis, not a min-width: a hard min-width cannot
148
- * yield, so the row overflows and :host's overflow:hidden clips the
149
- * regions after it. As a basis the container holds the floor while there
150
- * is room and gives way when there is not. The shell keeps a docked panel
151
- * off the row until it fits (see _dockRequirement), so the give-way path
152
- * is the last resort, not the usual one.
153
- */
164
+ /* The floor is the flex basis, not a min-width, so the container yields instead of overflowing the clipped row. */
154
165
  flex: 1 1 var(--nile-app-shell-content-min-width);
155
166
  display: flex;
156
167
  flex-direction: column;
@@ -158,24 +169,15 @@ export const styles = css `
158
169
  overflow: hidden;
159
170
  background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));
160
171
  border: var(--nile-border-width-1, var(--ng-border-width-1)) solid
161
- var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
172
+ var(--nile-colors-neutral-500, var(--ng-colors-border-primary));
162
173
  border-radius: var(--nile-app-shell-radius);
163
174
  box-shadow: var(--nile-box-shadow-none, var(--ng-shadow-none));
164
- /*
165
- * The container — not the viewport — is the sizing context for page
166
- * content. See the content-container note below for querying it.
167
- */
175
+ /* The container, not the viewport, is the sizing context for page content. */
168
176
  container-type: inline-size;
169
177
  container-name: nile-content;
170
178
  }
171
179
 
172
- /*
173
- * Container queries cannot cross a shadow boundary: a rule in the page's
174
- * stylesheet cannot see a container declared in here. The
175
- * content-container attribute moves the context onto the slotted page root instead, so page CSS can
176
- * write an @container nile-content query. It brings layout containment with
177
- * it, so it is opt-in.
178
- */
180
+ /* content-container moves the size query context onto the slotted page root so page CSS can query nile-content; opt-in as it brings containment. */
179
181
  :host([content-container]) slot:not([name])::slotted(*) {
180
182
  container-type: inline-size;
181
183
  container-name: nile-content;
@@ -199,12 +201,7 @@ export const styles = css `
199
201
  padding-inline: var(--nile-app-shell-content-padding);
200
202
  }
201
203
 
202
- /* Inside the container the bar carries the rule that separates it from the
203
- page body; spanning the shell it carries the container's edge instead. */
204
- .container > .top-bar {
205
- border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid
206
- var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
207
- }
204
+ /* The header bar draws no bottom rule in any variant; the only divider comes from the tab rail (see .tabs). */
208
205
 
209
206
  .shell-top-bar {
210
207
  grid-area: top-bar;
@@ -228,6 +225,171 @@ export const styles = css `
228
225
  flex: 1 1 auto;
229
226
  }
230
227
 
228
+ /* The title block: the header's own heading, the Leading axis and the badge; renders before the top-bar slot. */
229
+ .title-area {
230
+ display: flex;
231
+ /* Top-aligned, not centred: the leading element belongs to the title's row, not the title-plus-description stack. */
232
+ align-items: flex-start;
233
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
234
+ min-width: 0;
235
+ }
236
+
237
+ /* The leading line box: exactly the page title's line height, centring whatever leading control sits in it. */
238
+ .leading {
239
+ flex: 0 0 auto;
240
+ display: inline-flex;
241
+ align-items: center;
242
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
243
+ min-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));
244
+ }
245
+
246
+ .title-stack {
247
+ display: flex;
248
+ flex-direction: column;
249
+ justify-content: center;
250
+ gap: var(--nile-spacing-xxs, var(--ng-spacing-xxs));
251
+ min-width: 0;
252
+ }
253
+
254
+ .title-row {
255
+ display: flex;
256
+ align-items: center;
257
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
258
+ min-width: 0;
259
+ }
260
+
261
+ .page-title {
262
+ margin: 0;
263
+ min-width: 0;
264
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
265
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));
266
+ line-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));
267
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-semibold));
268
+ color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
269
+ white-space: nowrap;
270
+ overflow: hidden;
271
+ text-overflow: ellipsis;
272
+ }
273
+
274
+ .description {
275
+ margin: 0;
276
+ min-width: 0;
277
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
278
+ font-size: var(--nile-type-scale-1, var(--ng-font-size-text-xs));
279
+ line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-xs));
280
+ font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-regular));
281
+ color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));
282
+ white-space: nowrap;
283
+ overflow: hidden;
284
+ text-overflow: ellipsis;
285
+ }
286
+
287
+ /* The badge sits on the title row and never shrinks the title away; each surface token pairs a Nile name with its NxtGen equivalent. */
288
+ .badge {
289
+ flex: 0 0 auto;
290
+ display: inline-flex;
291
+ align-items: center;
292
+ padding-inline: var(--nile-spacing-md, var(--ng-spacing-md));
293
+ padding-block: var(--nile-spacing-xxs, var(--ng-spacing-xxs));
294
+ border: var(--nile-border-width-1, var(--ng-border-width-1)) solid
295
+ var(
296
+ --nile-colors-neutral-400,
297
+ var(--ng-componentcolors-utility-gray-200)
298
+ );
299
+ border-radius: var(--nile-radius-full, var(--ng-radius-full));
300
+ background: var(
301
+ --nile-colors-neutral-100,
302
+ var(--ng-componentcolors-utility-gray-50)
303
+ );
304
+ color: var(
305
+ --nile-colors-dark-500,
306
+ var(--ng-componentcolors-utility-gray-700)
307
+ );
308
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
309
+ font-size: var(--nile-type-scale-1, var(--ng-font-size-text-xs));
310
+ line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-xs));
311
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
312
+ white-space: nowrap;
313
+ }
314
+
315
+ /* Leading=Back: drawn as a breadcrumb label; the shell owns no history, so pressing it only emits nile-header-back. */
316
+ .back {
317
+ flex: 0 0 auto;
318
+ display: inline-flex;
319
+ align-items: center;
320
+ gap: var(--nile-spacing-xs, var(--ng-spacing-xs));
321
+ margin: 0;
322
+ padding: 0;
323
+ border: 0;
324
+ background: none;
325
+ cursor: pointer;
326
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
327
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));
328
+ line-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));
329
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-semibold));
330
+ color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));
331
+ }
332
+
333
+ .back:hover {
334
+ color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
335
+ }
336
+
337
+ .crumb-divider {
338
+ flex: 0 0 auto;
339
+ display: inline-flex;
340
+ align-items: center;
341
+ color: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));
342
+ }
343
+
344
+ /* Leading=AppIcon: a 20px block before the title; the page slots the mark, the shell owns the box, surface and radius. */
345
+ .app-icon {
346
+ position: relative;
347
+ flex: 0 0 auto;
348
+ display: inline-flex;
349
+ align-items: center;
350
+ justify-content: center;
351
+ width: var(--nile-app-shell-app-icon-size);
352
+ height: var(--nile-app-shell-app-icon-size);
353
+ border-radius: var(--nile-radius-sm, var(--ng-radius-sm));
354
+ overflow: hidden;
355
+ }
356
+
357
+ /* The slotted mark fills the wrapper edge to edge, clipped to the box. */
358
+ .app-icon slot::slotted(*) {
359
+ width: 100%;
360
+ height: 100%;
361
+ display: block;
362
+ object-fit: cover;
363
+ }
364
+
365
+ /* The wrapper takes no fill; the rim is a white-12% gradient drawn as a masked ::before, with literal mask fallbacks for the nxtgen-only tokens. */
366
+ .app-icon::before {
367
+ content: '';
368
+ position: absolute;
369
+ inset: 0;
370
+ border-radius: inherit;
371
+ pointer-events: none;
372
+ z-index: 1;
373
+ background: linear-gradient(
374
+ 180deg,
375
+ rgb(255 255 255 / 12%) 0%,
376
+ rgb(255 255 255 / 0%) 100%
377
+ );
378
+ mask: var(
379
+ --ng-mask-rim,
380
+ linear-gradient(#000 0 0) content-box,
381
+ linear-gradient(#000 0 0)
382
+ );
383
+ mask-composite: var(--ng-mask-composite-exclude, exclude);
384
+ padding: 1.4px;
385
+ }
386
+
387
+ :host(:not([leading='app-icon'])) .app-icon,
388
+ :host(:not([leading='back'])) .back,
389
+ :host(:not([leading='back'])) .crumb-divider {
390
+ display: none;
391
+ }
392
+
231
393
  .top-bar-actions {
232
394
  display: flex;
233
395
  align-items: center;
@@ -235,25 +397,12 @@ export const styles = css `
235
397
  margin-inline-start: auto;
236
398
  }
237
399
 
238
- /*
239
- * An empty flex item still takes the row's gap before it, which pulls the
240
- * bar's content 16px short of the padding edge and leaves a sliver of bare
241
- * container at the end of the header. Nothing in the group, no box.
242
- */
400
+ /* Hidden when empty, so an empty group's gap does not pull the bar's content short of the padding edge. */
243
401
  :host(:not([has-top-bar-actions]):not([has-ask])) .top-bar-actions {
244
402
  display: none;
245
403
  }
246
404
 
247
- /*
248
- * The Ask button. The one control in the bar the shell renders itself: it
249
- * turns on the panel being mounted and it calls openPanel(), neither of
250
- * which slotted markup can reach.
251
- *
252
- * It is a stock <nile-button variant="primary">: the shell sets no surface,
253
- * radius, height or type of its own, so the button's appearance — and its
254
- * focus ring, hover, disabled and loading states — stays the component's.
255
- * The only rule here is the flex behaviour it needs as a top-bar item.
256
- */
405
+ /* The Ask button: a stock <nile-button variant="primary"> the shell renders itself; the only rule here is its flex behaviour as a top-bar item. */
257
406
  .ask {
258
407
  flex: 0 0 auto;
259
408
  }
@@ -276,12 +425,9 @@ export const styles = css `
276
425
  gap: var(--nile-spacing-lg, var(--ng-spacing-lg));
277
426
  }
278
427
 
279
- /*
280
- * Lands the 24px glyph box, not nile-button's 38px circle, on the
281
- * container's 24px origin: (38 - 24) / 2 = 7.
282
- */
428
+ /* Lands the 20px glyph box, not the 32px utility circle, on the 24px origin: (32 - 20) / 2 = 6. */
283
429
  .nav-toggle {
284
- margin-inline-start: -7px;
430
+ margin-inline-start: -6px;
285
431
  }
286
432
 
287
433
  .rule {
@@ -294,13 +440,14 @@ export const styles = css `
294
440
  );
295
441
  }
296
442
 
443
+ /* ------------------------------------------------------- utility controls */
444
+
445
+ /* The shell's own icon controls (nav toggle, panel back and close): the shared utility-control recipe. */
446
+ ${utilityControlStyles}
447
+
297
448
  /* --------------------------------------------------------------- tab strip */
298
449
 
299
- /*
300
- * Hoisted out of the scroller so it never scrolls away. It sits flush under
301
- * the top bar — the header carries no bottom rule of its own, so the two read
302
- * as one block of chrome, with the single rule living at the strip's bottom.
303
- */
450
+ /* Hoisted out of the scroller so it never scrolls away; sits flush under the top bar and carries the header block's single bottom rule. */
304
451
  .tabs {
305
452
  box-sizing: border-box;
306
453
  flex: 0 0 auto;
@@ -308,6 +455,7 @@ export const styles = css `
308
455
  align-items: stretch;
309
456
  gap: var(--nile-spacing-xs, var(--ng-spacing-xs));
310
457
  min-width: 0;
458
+ /* A floor, not a fixed box: the strip is as tall as the row it holds plus its rule. */
311
459
  min-height: var(--nile-app-shell-tabs-height);
312
460
  padding-inline: var(--nile-app-shell-content-padding);
313
461
  /* The strip carries the rule for the whole header block. */
@@ -317,14 +465,32 @@ export const styles = css `
317
465
  scrollbar-width: none;
318
466
  }
319
467
 
468
+ /* The header tab's 0/4/12/4 padding composes the 32px row; the 12px bottom gives the active indicator its band, set via the group's public padding hook. */
469
+ ::slotted([slot='tabs']) {
470
+ --nile-nav-tab-padding: var(--nile-spacing-none, var(--ng-spacing-none))
471
+ var(--nile-spacing-xs, var(--ng-spacing-xs))
472
+ var(--nile-spacing-lg, var(--ng-spacing-lg))
473
+ var(--nile-spacing-xs, var(--ng-spacing-xs));
474
+ }
475
+
320
476
  .tabs::-webkit-scrollbar {
321
477
  display: none;
322
478
  }
323
479
 
324
- :host(:not([has-tabs])) .tabs {
480
+ :host(:not([has-tabs])) .tabs,
481
+ :host([notabs]) .tabs {
325
482
  display: none;
326
483
  }
327
484
 
485
+ /* A slotted nile-nav-tab-group drops its own track (--track-width:0 on the host) and is bottom-aligned so its indicator sits on the region's single rule. */
486
+ /* Scoped to the group, not the slot, so bare tab controls are not stretched to an equal share of the strip. */
487
+ ::slotted(nile-nav-tab-group[slot='tabs']) {
488
+ --track-width: 0px;
489
+ flex: 1 1 auto;
490
+ min-width: 0;
491
+ align-self: flex-end;
492
+ }
493
+
328
494
  /* ------------------------------------------------------------------- main */
329
495
 
330
496
  .main {
@@ -334,7 +500,9 @@ export const styles = css `
334
500
  flex-direction: column;
335
501
  min-height: 0;
336
502
  overflow: auto;
503
+ /* Top and sides only: the page runs to the container's bottom edge. */
337
504
  padding: var(--nile-app-shell-content-padding);
505
+ padding-block-end: 0;
338
506
  }
339
507
 
340
508
  .main:focus {
@@ -352,13 +520,7 @@ export const styles = css `
352
520
 
353
521
  /* Centred when short, scrolls from the top when tall. */
354
522
  :host([content-align='centered']) .main-inner {
355
- /*
356
- * width:100% + max-width leaves free space for the auto inline margins to
357
- * split; dropping the width would let the auto margins cancel the flex
358
- * stretch and shrink-wrap the column to its widest child. The block margins
359
- * centre it while it is short and compute to 0 once it is taller, so a tall
360
- * page scrolls from the top rather than clipping.
361
- */
523
+ /* width:100% + max-width leaves space for the auto margins to centre it while short, and scroll from the top when tall. */
362
524
  width: 100%;
363
525
  max-width: var(--nile-app-shell-centered-width);
364
526
  margin: auto;
@@ -368,8 +530,7 @@ export const styles = css `
368
530
 
369
531
  .aside {
370
532
  box-sizing: border-box;
371
- /* Gives before the page does, down to its own floor, for the same reason
372
- the panel does: the page is the last region to be asked for room. */
533
+ /* Gives before the page does, down to its own floor. */
373
534
  flex: 0 1 var(--nile-app-shell-aside-width);
374
535
  min-width: var(--nile-app-shell-aside-min-width);
375
536
  display: flex;
@@ -391,13 +552,7 @@ export const styles = css `
391
552
 
392
553
  .panel {
393
554
  box-sizing: border-box;
394
- /*
395
- * The panel pushes the page, it never covers it, so it has to be able to
396
- * give a little: full width while the row can afford it, then shrinking to
397
- * its min. Here the floor can be a real min-width — the container's floor
398
- * is a flex basis that yields all the way, so the row still cannot
399
- * overflow into :host's clip once the panel stops giving.
400
- */
555
+ /* The panel pushes the page rather than covering it: full width while the row affords it, shrinking to a real min-width floor. */
401
556
  flex: 0 1 var(--nile-app-shell-panel-width);
402
557
  width: var(--nile-app-shell-panel-width);
403
558
  min-width: var(--nile-app-shell-panel-min-width);
@@ -451,13 +606,28 @@ export const styles = css `
451
606
  flex: 0 0 auto;
452
607
  display: flex;
453
608
  align-items: center;
454
- gap: var(--nile-spacing-lg, var(--ng-spacing-lg));
455
- height: var(--nile-app-shell-top-bar-height);
456
- padding-inline: var(--nile-spacing-xl, var(--ng-spacing-xl));
609
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
610
+ height: var(--nile-app-shell-panel-header-height);
611
+ padding-inline: var(--nile-spacing-3xl, var(--ng-spacing-3xl));
457
612
  border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid
458
613
  var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
459
614
  }
460
615
 
616
+ /* The back control for a panel sub-view; stands where the brand mark does in the root view. */
617
+ .panel-back {
618
+ flex: 0 0 auto;
619
+ margin-inline-start: -6px;
620
+ }
621
+
622
+ /* Mirrors the back control's offset on the end edge: the 20px glyph, not the 32px circle, lands on the padding line. */
623
+ .panel-close {
624
+ margin-inline-end: -6px;
625
+ }
626
+
627
+ :host([panelback]) .panel-mark {
628
+ display: none;
629
+ }
630
+
461
631
  .panel-heading {
462
632
  display: flex;
463
633
  align-items: center;
@@ -504,7 +674,8 @@ export const styles = css `
504
674
  flex: 1 1 auto;
505
675
  min-height: 0;
506
676
  overflow: auto;
507
- padding: var(--nile-spacing-xl, var(--ng-spacing-xl));
677
+ /* 16 under the header, 24 on the sides and bottom. */
678
+ padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl));
508
679
  display: flex;
509
680
  flex-direction: column;
510
681
  gap: var(--nile-spacing-xl, var(--ng-spacing-xl));
@@ -512,7 +683,7 @@ export const styles = css `
512
683
 
513
684
  .panel-footer {
514
685
  flex: 0 0 auto;
515
- padding: var(--nile-spacing-xl, var(--ng-spacing-xl));
686
+ padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl));
516
687
  border-top: var(--nile-border-width-1, var(--ng-border-width-1)) solid
517
688
  var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
518
689
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nile-app-shell.css.js","sourceRoot":"","sources":["../../../src/nile-app-shell/nile-app-shell.css.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE1B;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyiBxB,CAAC;AAEF,eAAe,CAAC,MAAM,CAAC,CAAC","sourcesContent":["/**\n * Copyright Aquera Inc 2023\n *\n * This source code is licensed under the BSD-3-Clause license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { css } from 'lit';\n\n/**\n * AppShell CSS.\n *\n * One grid, two columns, one flex row. The grid owns the banner row, the\n * optional shell-wide top bar, the side-nav track and the content column. The\n * content column is a flex row holding the content container, the optional\n * aside and — when docked — the AI panel. Nothing else in an app sets these\n * boxes.\n */\nexport const styles = css`\n :host {\n /* Geometry. Values come from design tokens wherever one exists; the bare\n pixels are shell geometry with no matching token. */\n --nile-app-shell-side-nav-width: 260px;\n --nile-app-shell-side-nav-rail-width: 64px;\n --nile-app-shell-gutter: var(--nile-spacing-md, var(--ng-spacing-md));\n --nile-app-shell-content-padding: var(\n --nile-spacing-3xl,\n var(--ng-spacing-3xl)\n );\n --nile-app-shell-top-bar-height: 56px;\n --nile-app-shell-tabs-height: 48px;\n --nile-app-shell-panel-width: 480px;\n --nile-app-shell-panel-min-width: 360px;\n --nile-app-shell-aside-width: 320px;\n --nile-app-shell-aside-min-width: 240px;\n --nile-app-shell-content-min-width: 640px;\n --nile-app-shell-min-width: 768px;\n --nile-app-shell-centered-width: 824px;\n --nile-app-shell-utility-size: var(\n --nile-spacing-4xl,\n var(--ng-spacing-4xl)\n );\n --nile-app-shell-radius: var(--nile-radius-xl, var(--ng-radius-2xl));\n --nile-app-shell-duration: var(\n --nile-transition-duration-default,\n var(--ng-transition-duration-default)\n );\n /* NxtGen ships no easing tokens, so the shell names its own. */\n --nile-app-shell-ease: cubic-bezier(0.2, 0, 0, 1);\n\n --_side-nav-track: var(--nile-app-shell-side-nav-width);\n\n position: relative;\n box-sizing: border-box;\n display: grid;\n grid-template-areas:\n 'banner banner'\n 'top-bar top-bar'\n 'side-nav content';\n grid-template-columns: var(--_side-nav-track) minmax(0, 1fr);\n grid-template-rows: auto auto minmax(0, 1fr);\n /* Below this the window scrolls rather than the shell compressing. */\n min-width: var(--nile-app-shell-min-width);\n height: 100%;\n overflow: hidden;\n background: var(--nile-colors-neutral-100, var(--ng-colors-bg-tertiary));\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n font-family: var(--nile-font-family-sans-serif, var(--ng-font-family-body));\n transition: grid-template-columns var(--nile-app-shell-duration)\n var(--nile-app-shell-ease);\n }\n\n :host([hidden]) {\n display: none;\n }\n\n :host([full-height]) {\n height: 100dvh;\n }\n\n /* Effective rail state, set by the component: the user's preference, or a\n forced rail at narrow widths. */\n :host([rail]) {\n --_side-nav-track: var(--nile-app-shell-side-nav-rail-width);\n }\n\n :host(:not([has-side-nav])) {\n --_side-nav-track: 0px;\n }\n\n /* The top bar compresses before the content does. */\n @media (max-height: 600px) {\n :host {\n --nile-app-shell-top-bar-height: 48px;\n }\n }\n\n /* ---------------------------------------------------------------- banner */\n\n .banner {\n grid-area: banner;\n min-width: 0;\n }\n\n :host(:not([has-banner])) .banner {\n display: none;\n }\n\n /* ------------------------------------------------------------- side nav */\n\n .side-nav {\n grid-area: side-nav;\n display: flex;\n min-width: 0;\n overflow: hidden;\n }\n\n :host(:not([has-side-nav])) .side-nav {\n display: none;\n }\n\n /*\n * The nav and the page share one surface, and the shell owns the track\n * width, so a slotted sidebar fills the track and drops its own background\n * and edge. Set --nile-app-shell-side-nav-bg to put a surface back.\n */\n ::slotted([slot='side-nav']) {\n width: 100% !important;\n height: 100%;\n background: var(--nile-app-shell-side-nav-bg, transparent) !important;\n border-right: var(--nile-app-shell-side-nav-border, 0) !important;\n }\n\n /* -------------------------------------------------------- content column */\n\n .content-section {\n grid-area: content;\n position: relative;\n display: flex;\n min-width: 0;\n gap: var(--nile-app-shell-gutter);\n padding: var(--nile-app-shell-gutter);\n box-sizing: border-box;\n }\n\n .container {\n box-sizing: border-box;\n /*\n * The floor is the flex basis, not a min-width: a hard min-width cannot\n * yield, so the row overflows and :host's overflow:hidden clips the\n * regions after it. As a basis the container holds the floor while there\n * is room and gives way when there is not. The shell keeps a docked panel\n * off the row until it fits (see _dockRequirement), so the give-way path\n * is the last resort, not the usual one.\n */\n flex: 1 1 var(--nile-app-shell-content-min-width);\n display: flex;\n flex-direction: column;\n min-width: 0;\n overflow: hidden;\n background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n border-radius: var(--nile-app-shell-radius);\n box-shadow: var(--nile-box-shadow-none, var(--ng-shadow-none));\n /*\n * The container — not the viewport — is the sizing context for page\n * content. See the content-container note below for querying it.\n */\n container-type: inline-size;\n container-name: nile-content;\n }\n\n /*\n * Container queries cannot cross a shadow boundary: a rule in the page's\n * stylesheet cannot see a container declared in here. The\n * content-container attribute moves the context onto the slotted page root instead, so page CSS can\n * write an @container nile-content query. It brings layout containment with\n * it, so it is opt-in.\n */\n :host([content-container]) slot:not([name])::slotted(*) {\n container-type: inline-size;\n container-name: nile-content;\n }\n\n :host([flat]) .container {\n background: transparent;\n border-color: transparent;\n border-radius: 0;\n }\n\n /* ---------------------------------------------------------------- top bar */\n\n .top-bar {\n box-sizing: border-box;\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-xl, var(--ng-spacing-xl));\n height: var(--nile-app-shell-top-bar-height);\n padding-inline: var(--nile-app-shell-content-padding);\n }\n\n /* Inside the container the bar carries the rule that separates it from the\n page body; spanning the shell it carries the container's edge instead. */\n .container > .top-bar {\n border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n }\n\n .shell-top-bar {\n grid-area: top-bar;\n min-width: 0;\n padding-inline: var(--nile-spacing-xl, var(--ng-spacing-xl));\n background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));\n border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n }\n\n /* No slotted bar content and no nav toggle means no bar at all. */\n :host(:not([has-top-bar])) .top-bar {\n display: none;\n }\n\n .top-bar-content {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-xl, var(--ng-spacing-xl));\n min-width: 0;\n flex: 1 1 auto;\n }\n\n .top-bar-actions {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n margin-inline-start: auto;\n }\n\n /*\n * An empty flex item still takes the row's gap before it, which pulls the\n * bar's content 16px short of the padding edge and leaves a sliver of bare\n * container at the end of the header. Nothing in the group, no box.\n */\n :host(:not([has-top-bar-actions]):not([has-ask])) .top-bar-actions {\n display: none;\n }\n\n /*\n * The Ask button. The one control in the bar the shell renders itself: it\n * turns on the panel being mounted and it calls openPanel(), neither of\n * which slotted markup can reach.\n *\n * It is a stock <nile-button variant=\"primary\">: the shell sets no surface,\n * radius, height or type of its own, so the button's appearance — and its\n * focus ring, hover, disabled and loading states — stays the component's.\n * The only rule here is the flex behaviour it needs as a top-bar item.\n */\n .ask {\n flex: 0 0 auto;\n }\n\n .ask-mark {\n display: inline-flex;\n align-items: center;\n }\n\n .ask-mark nile-glyph {\n --nile-svg-fill: currentColor;\n --nile-svg-stroke-width: var(--ng-svg-stroke-width);\n width: 16px;\n height: 16px;\n }\n\n .nav-toggle-group {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-lg, var(--ng-spacing-lg));\n }\n\n /*\n * Lands the 24px glyph box, not nile-button's 38px circle, on the\n * container's 24px origin: (38 - 24) / 2 = 7.\n */\n .nav-toggle {\n margin-inline-start: -7px;\n }\n\n .rule {\n flex: 0 0 auto;\n width: var(--nile-border-width-1, var(--ng-border-width-1));\n height: 24px;\n background: var(\n --nile-colors-neutral-400,\n var(--ng-colors-border-secondary)\n );\n }\n\n /* --------------------------------------------------------------- tab strip */\n\n /*\n * Hoisted out of the scroller so it never scrolls away. It sits flush under\n * the top bar — the header carries no bottom rule of its own, so the two read\n * as one block of chrome, with the single rule living at the strip's bottom.\n */\n .tabs {\n box-sizing: border-box;\n flex: 0 0 auto;\n display: flex;\n align-items: stretch;\n gap: var(--nile-spacing-xs, var(--ng-spacing-xs));\n min-width: 0;\n min-height: var(--nile-app-shell-tabs-height);\n padding-inline: var(--nile-app-shell-content-padding);\n /* The strip carries the rule for the whole header block. */\n border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n overflow-x: auto;\n scrollbar-width: none;\n }\n\n .tabs::-webkit-scrollbar {\n display: none;\n }\n\n :host(:not([has-tabs])) .tabs {\n display: none;\n }\n\n /* ------------------------------------------------------------------- main */\n\n .main {\n box-sizing: border-box;\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: auto;\n padding: var(--nile-app-shell-content-padding);\n }\n\n .main:focus {\n outline: none;\n }\n\n :host([no-padding]) .main {\n padding: 0;\n }\n\n .main-inner {\n width: 100%;\n min-height: 0;\n }\n\n /* Centred when short, scrolls from the top when tall. */\n :host([content-align='centered']) .main-inner {\n /*\n * width:100% + max-width leaves free space for the auto inline margins to\n * split; dropping the width would let the auto margins cancel the flex\n * stretch and shrink-wrap the column to its widest child. The block margins\n * centre it while it is short and compute to 0 once it is taller, so a tall\n * page scrolls from the top rather than clipping.\n */\n width: 100%;\n max-width: var(--nile-app-shell-centered-width);\n margin: auto;\n }\n\n /* ------------------------------------------------------------------ aside */\n\n .aside {\n box-sizing: border-box;\n /* Gives before the page does, down to its own floor, for the same reason\n the panel does: the page is the last region to be asked for room. */\n flex: 0 1 var(--nile-app-shell-aside-width);\n min-width: var(--nile-app-shell-aside-min-width);\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: auto;\n padding: var(--nile-app-shell-content-padding);\n background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n border-radius: var(--nile-app-shell-radius);\n }\n\n :host(:not([has-aside])) .aside {\n display: none;\n }\n\n /* -------------------------------------------------------------- AI panel */\n\n .panel {\n box-sizing: border-box;\n /*\n * The panel pushes the page, it never covers it, so it has to be able to\n * give a little: full width while the row can afford it, then shrinking to\n * its min. Here the floor can be a real min-width — the container's floor\n * is a flex basis that yields all the way, so the row still cannot\n * overflow into :host's clip once the panel stops giving.\n */\n flex: 0 1 var(--nile-app-shell-panel-width);\n width: var(--nile-app-shell-panel-width);\n min-width: var(--nile-app-shell-panel-min-width);\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));\n /* The panel is elevated, so its edge sits a rung above the container's. */\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n border-radius: var(--nile-app-shell-radius);\n box-shadow: var(--nile-box-shadow-2, var(--ng-shadow-lg));\n }\n\n /* Docked: a flex sibling. It squeezes the content, it does not cover it. */\n :host(:not([panel-open])) .panel {\n display: none;\n }\n\n /* Overlay: contained to the content column, above it, and animated in. */\n :host([panel-mode='overlay']) .panel {\n position: absolute;\n z-index: 1001;\n inset-block: var(--nile-app-shell-gutter);\n inset-inline-end: var(--nile-app-shell-gutter);\n flex: none;\n display: flex;\n width: min(var(--nile-app-shell-panel-width), 92%);\n visibility: hidden;\n transform: translateX(calc(100% + var(--nile-app-shell-gutter)));\n transition: transform var(--nile-app-shell-duration)\n var(--nile-app-shell-ease),\n visibility 0s linear var(--nile-app-shell-duration);\n }\n\n :host([panel-mode='overlay'][panel-open]) .panel {\n visibility: visible;\n transform: none;\n transition: transform var(--nile-app-shell-duration)\n var(--nile-app-shell-ease);\n }\n\n /* An unavailable panel is unmounted in either mode. */\n :host(:not([has-panel])) .panel {\n display: none;\n }\n\n .panel-header {\n box-sizing: border-box;\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-lg, var(--ng-spacing-lg));\n height: var(--nile-app-shell-top-bar-height);\n padding-inline: var(--nile-spacing-xl, var(--ng-spacing-xl));\n border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n }\n\n .panel-heading {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n min-width: 0;\n }\n\n .panel-mark {\n display: inline-flex;\n color: var(\n --nile-colors-primary-600,\n var(--ng-colors-fg-brand-primary-600)\n );\n }\n\n .panel-mark nile-glyph {\n --nile-svg-fill: currentColor;\n --nile-svg-stroke-width: var(--ng-svg-stroke-width);\n width: 20px;\n height: 20px;\n }\n\n .panel-title {\n margin: 0;\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n font-weight: var(\n --nile-font-weight-semi-bold,\n var(--ng-font-weight-semibold)\n );\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .panel-actions {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-xs, var(--ng-spacing-xs));\n margin-inline-start: auto;\n }\n\n .panel-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: var(--nile-spacing-xl, var(--ng-spacing-xl));\n display: flex;\n flex-direction: column;\n gap: var(--nile-spacing-xl, var(--ng-spacing-xl));\n }\n\n .panel-footer {\n flex: 0 0 auto;\n padding: var(--nile-spacing-xl, var(--ng-spacing-xl));\n border-top: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n }\n\n :host(:not([has-panel-footer])) .panel-footer {\n display: none;\n }\n\n /* ---------------------------------------------------------- skip link */\n\n .skip-link {\n position: absolute;\n z-index: 1002;\n inset-inline-start: var(--nile-spacing-md, var(--ng-spacing-md));\n inset-block-start: var(--nile-spacing-md, var(--ng-spacing-md));\n display: inline-flex;\n align-items: center;\n padding: var(--nile-spacing-md, var(--ng-spacing-md))\n var(--nile-spacing-lg, var(--ng-spacing-lg));\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n border-radius: var(--nile-radius-md, var(--ng-radius-md));\n background: var(--nile-colors-white-base, var(--ng-colors-bg-primary));\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n font: inherit;\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n box-shadow: var(--nile-box-shadow-2, var(--ng-shadow-lg));\n cursor: pointer;\n transform: translateY(-200%);\n transition: transform\n var(--nile-transition-duration-short, var(--ng-transition-duration-fast))\n var(--nile-app-shell-ease);\n }\n\n .skip-link:focus-visible {\n outline: none;\n transform: none;\n box-shadow: 0 0 0 2px\n var(--nile-colors-neutral-100, var(--ng-colors-bg-tertiary)),\n 0 0 0 4px\n var(--nile-colors-primary-600, var(--ng-colors-fg-brand-primary-600));\n }\n\n :host([no-skip-link]) .skip-link {\n display: none;\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host,\n .panel,\n .skip-link {\n transition: none;\n }\n }\n`;\n\nexport default [styles];\n"]}
1
+ {"version":3,"file":"nile-app-shell.css.js","sourceRoot":"","sources":["../../../src/nile-app-shell/nile-app-shell.css.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,8CAA8C,CAAC;AAEpF,iJAAiJ;AACjJ,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAobrB,oBAAoB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuSvB,CAAC;AAEF,eAAe,CAAC,MAAM,CAAC,CAAC","sourcesContent":["/**\n * Copyright Aquera Inc 2023\n *\n * This source code is licensed under the BSD-3-Clause license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\nimport { css } from 'lit';\nimport { utilityControlStyles } from '../internal/app-shell-utility-control-styles';\n\n/** AppShell CSS: a grid of banner, top bar, side-nav track and content column, whose flex row holds the container, aside and docked AI panel. */\nexport const styles = css`\n :host {\n /* Geometry: design tokens where one exists, bare pixels where none does. */\n /* The nav track mirrors nile-side-bar's own width expressions (enterprise 240/70, NxtGen 296/68); keep in step with nile-side-bar.css.ts. */\n --nile-app-shell-side-nav-width: var(\n --nile-width-240px,\n var(--ng-spacing-296, 240px)\n );\n --nile-app-shell-side-nav-rail-width: var(\n --nile-width-70px,\n var(--ng-spacing-68, 70px)\n );\n --nile-app-shell-gutter: var(--nile-spacing-xs, var(--ng-spacing-xs));\n --nile-app-shell-content-padding: var(\n --nile-spacing-3xl,\n var(--ng-spacing-3xl)\n );\n --nile-app-shell-top-bar-height: 68px;\n /* The panel header keeps its own height, distinct from the page header. */\n --nile-app-shell-panel-header-height: 56px;\n --nile-app-shell-tabs-height: 33px;\n /* The leading app icon is its own 20px size, not the 32px utility box. */\n --nile-app-shell-app-icon-size: 20px;\n --nile-app-shell-panel-width: 480px;\n --nile-app-shell-panel-min-width: 360px;\n --nile-app-shell-aside-width: 320px;\n --nile-app-shell-aside-min-width: 240px;\n --nile-app-shell-content-min-width: 640px;\n --nile-app-shell-min-width: 768px;\n --nile-app-shell-centered-width: 824px;\n /* Both tokens are 32px; the literal is the floor with no theme sheet loaded. */\n --nile-app-shell-utility-size: var(\n --nile-spacing-4xl,\n var(--ng-spacing-4xl, 32px)\n );\n --nile-app-shell-radius: var(--nile-radius-xl, var(--ng-radius-2xl));\n --nile-app-shell-duration: var(\n --nile-transition-duration-default,\n var(--ng-transition-duration-default)\n );\n /* NxtGen ships no easing tokens, so the shell names its own. */\n --nile-app-shell-ease: cubic-bezier(0.2, 0, 0, 1);\n\n --_side-nav-track: var(--nile-app-shell-side-nav-width);\n\n position: relative;\n box-sizing: border-box;\n display: grid;\n grid-template-areas:\n 'banner banner'\n 'top-bar top-bar'\n 'side-nav content';\n grid-template-columns: var(--_side-nav-track) minmax(0, 1fr);\n grid-template-rows: auto auto minmax(0, 1fr);\n /* Below this the window scrolls rather than the shell compressing. */\n min-width: var(--nile-app-shell-min-width);\n height: 100%;\n overflow: hidden;\n background: var(--nile-colors-neutral-100, var(--ng-colors-bg-tertiary));\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n font-family: var(--nile-font-family-sans-serif, var(--ng-font-family-body));\n transition: grid-template-columns var(--nile-app-shell-duration)\n var(--nile-app-shell-ease);\n }\n\n :host([hidden]) {\n display: none;\n }\n\n :host([full-height]) {\n height: 100dvh;\n }\n\n /* Effective rail state: the user's preference, or a forced rail when narrow. */\n :host([rail]) {\n --_side-nav-track: var(--nile-app-shell-side-nav-rail-width);\n }\n\n :host(:not([has-side-nav])) {\n --_side-nav-track: 0px;\n }\n\n /* The Description axis: a present description adds the 8px its second line occupies. */\n :host([has-description]) {\n --nile-app-shell-top-bar-height: 76px;\n }\n\n /* The top bar compresses before the content does. */\n @media (max-height: 600px) {\n :host {\n --nile-app-shell-top-bar-height: 56px;\n }\n\n :host([has-description]) {\n --nile-app-shell-top-bar-height: 64px;\n }\n }\n\n /* ---------------------------------------------------------------- banner */\n\n .banner {\n grid-area: banner;\n min-width: 0;\n }\n\n :host(:not([has-banner])) .banner {\n display: none;\n }\n\n /* ------------------------------------------------------------- side nav */\n\n .side-nav {\n grid-area: side-nav;\n display: flex;\n min-width: 0;\n overflow: hidden;\n }\n\n :host(:not([has-side-nav])) .side-nav {\n display: none;\n }\n\n /* A slotted sidebar fills the shell-owned track and drops its own surface and edge; set --nile-app-shell-side-nav-bg to restore one. */\n /* width is deliberately not !important: the 100% lets a raw <nav> or nile-app-shell-side-nav fill the track, while a slotted\n nile-side-bar sizes itself from the same tokens the track reads (see the :host geometry block). Tests 21/22 in\n nile-app-shell-regions.test.ts lock the two together; change both or neither. */\n ::slotted([slot='side-nav']) {\n width: 100%;\n height: 100%;\n background: var(--nile-app-shell-side-nav-bg, transparent) !important;\n border-right: var(--nile-app-shell-side-nav-border, 0) !important;\n }\n\n /* -------------------------------------------------------- content column */\n\n .content-section {\n grid-area: content;\n position: relative;\n display: flex;\n min-width: 0;\n gap: var(--nile-app-shell-gutter);\n padding: var(--nile-app-shell-gutter);\n /* Flush against the nav track; the gutter insets the other three sides. */\n padding-inline-start: 0;\n box-sizing: border-box;\n }\n\n /* With no nav track there is nothing to sit flush against, so the start edge takes the gutter too. */\n :host(:not([has-side-nav])) .content-section {\n padding-inline-start: var(--nile-app-shell-gutter);\n }\n\n .container {\n box-sizing: border-box;\n /* The floor is the flex basis, not a min-width, so the container yields instead of overflowing the clipped row. */\n flex: 1 1 var(--nile-app-shell-content-min-width);\n display: flex;\n flex-direction: column;\n min-width: 0;\n overflow: hidden;\n background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n border-radius: var(--nile-app-shell-radius);\n box-shadow: var(--nile-box-shadow-none, var(--ng-shadow-none));\n /* The container, not the viewport, is the sizing context for page content. */\n container-type: inline-size;\n container-name: nile-content;\n }\n\n /* content-container moves the size query context onto the slotted page root so page CSS can query nile-content; opt-in as it brings containment. */\n :host([content-container]) slot:not([name])::slotted(*) {\n container-type: inline-size;\n container-name: nile-content;\n }\n\n :host([flat]) .container {\n background: transparent;\n border-color: transparent;\n border-radius: 0;\n }\n\n /* ---------------------------------------------------------------- top bar */\n\n .top-bar {\n box-sizing: border-box;\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-xl, var(--ng-spacing-xl));\n height: var(--nile-app-shell-top-bar-height);\n padding-inline: var(--nile-app-shell-content-padding);\n }\n\n /* The header bar draws no bottom rule in any variant; the only divider comes from the tab rail (see .tabs). */\n\n .shell-top-bar {\n grid-area: top-bar;\n min-width: 0;\n padding-inline: var(--nile-spacing-xl, var(--ng-spacing-xl));\n background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));\n border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n }\n\n /* No slotted bar content and no nav toggle means no bar at all. */\n :host(:not([has-top-bar])) .top-bar {\n display: none;\n }\n\n .top-bar-content {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-xl, var(--ng-spacing-xl));\n min-width: 0;\n flex: 1 1 auto;\n }\n\n /* The title block: the header's own heading, the Leading axis and the badge; renders before the top-bar slot. */\n .title-area {\n display: flex;\n /* Top-aligned, not centred: the leading element belongs to the title's row, not the title-plus-description stack. */\n align-items: flex-start;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n min-width: 0;\n }\n\n /* The leading line box: exactly the page title's line height, centring whatever leading control sits in it. */\n .leading {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n min-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));\n }\n\n .title-stack {\n display: flex;\n flex-direction: column;\n justify-content: center;\n gap: var(--nile-spacing-xxs, var(--ng-spacing-xxs));\n min-width: 0;\n }\n\n .title-row {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n min-width: 0;\n }\n\n .page-title {\n margin: 0;\n min-width: 0;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));\n line-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));\n font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-semibold));\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .description {\n margin: 0;\n min-width: 0;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-1, var(--ng-font-size-text-xs));\n line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-xs));\n font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-regular));\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n /* The badge sits on the title row and never shrinks the title away; each surface token pairs a Nile name with its NxtGen equivalent. */\n .badge {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n padding-inline: var(--nile-spacing-md, var(--ng-spacing-md));\n padding-block: var(--nile-spacing-xxs, var(--ng-spacing-xxs));\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(\n --nile-colors-neutral-400,\n var(--ng-componentcolors-utility-gray-200)\n );\n border-radius: var(--nile-radius-full, var(--ng-radius-full));\n background: var(\n --nile-colors-neutral-100,\n var(--ng-componentcolors-utility-gray-50)\n );\n color: var(\n --nile-colors-dark-500,\n var(--ng-componentcolors-utility-gray-700)\n );\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-1, var(--ng-font-size-text-xs));\n line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-xs));\n font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));\n white-space: nowrap;\n }\n\n /* Leading=Back: drawn as a breadcrumb label; the shell owns no history, so pressing it only emits nile-header-back. */\n .back {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n gap: var(--nile-spacing-xs, var(--ng-spacing-xs));\n margin: 0;\n padding: 0;\n border: 0;\n background: none;\n cursor: pointer;\n font-family: var(--nile-font-family-serif, var(--ng-font-family-body));\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));\n line-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));\n font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-semibold));\n color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));\n }\n\n .back:hover {\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n }\n\n .crumb-divider {\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n color: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));\n }\n\n /* Leading=AppIcon: a 20px block before the title; the page slots the mark, the shell owns the box, surface and radius. */\n .app-icon {\n position: relative;\n flex: 0 0 auto;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--nile-app-shell-app-icon-size);\n height: var(--nile-app-shell-app-icon-size);\n border-radius: var(--nile-radius-sm, var(--ng-radius-sm));\n overflow: hidden;\n }\n\n /* The slotted mark fills the wrapper edge to edge, clipped to the box. */\n .app-icon slot::slotted(*) {\n width: 100%;\n height: 100%;\n display: block;\n object-fit: cover;\n }\n\n /* The wrapper takes no fill; the rim is a white-12% gradient drawn as a masked ::before, with literal mask fallbacks for the nxtgen-only tokens. */\n .app-icon::before {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: inherit;\n pointer-events: none;\n z-index: 1;\n background: linear-gradient(\n 180deg,\n rgb(255 255 255 / 12%) 0%,\n rgb(255 255 255 / 0%) 100%\n );\n mask: var(\n --ng-mask-rim,\n linear-gradient(#000 0 0) content-box,\n linear-gradient(#000 0 0)\n );\n mask-composite: var(--ng-mask-composite-exclude, exclude);\n padding: 1.4px;\n }\n\n :host(:not([leading='app-icon'])) .app-icon,\n :host(:not([leading='back'])) .back,\n :host(:not([leading='back'])) .crumb-divider {\n display: none;\n }\n\n .top-bar-actions {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n margin-inline-start: auto;\n }\n\n /* Hidden when empty, so an empty group's gap does not pull the bar's content short of the padding edge. */\n :host(:not([has-top-bar-actions]):not([has-ask])) .top-bar-actions {\n display: none;\n }\n\n /* The Ask button: a stock <nile-button variant=\"primary\"> the shell renders itself; the only rule here is its flex behaviour as a top-bar item. */\n .ask {\n flex: 0 0 auto;\n }\n\n .ask-mark {\n display: inline-flex;\n align-items: center;\n }\n\n .ask-mark nile-glyph {\n --nile-svg-fill: currentColor;\n --nile-svg-stroke-width: var(--ng-svg-stroke-width);\n width: 16px;\n height: 16px;\n }\n\n .nav-toggle-group {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-lg, var(--ng-spacing-lg));\n }\n\n /* Lands the 20px glyph box, not the 32px utility circle, on the 24px origin: (32 - 20) / 2 = 6. */\n .nav-toggle {\n margin-inline-start: -6px;\n }\n\n .rule {\n flex: 0 0 auto;\n width: var(--nile-border-width-1, var(--ng-border-width-1));\n height: 24px;\n background: var(\n --nile-colors-neutral-400,\n var(--ng-colors-border-secondary)\n );\n }\n\n /* ------------------------------------------------------- utility controls */\n\n /* The shell's own icon controls (nav toggle, panel back and close): the shared utility-control recipe. */\n ${utilityControlStyles}\n\n /* --------------------------------------------------------------- tab strip */\n\n /* Hoisted out of the scroller so it never scrolls away; sits flush under the top bar and carries the header block's single bottom rule. */\n .tabs {\n box-sizing: border-box;\n flex: 0 0 auto;\n display: flex;\n align-items: stretch;\n gap: var(--nile-spacing-xs, var(--ng-spacing-xs));\n min-width: 0;\n /* A floor, not a fixed box: the strip is as tall as the row it holds plus its rule. */\n min-height: var(--nile-app-shell-tabs-height);\n padding-inline: var(--nile-app-shell-content-padding);\n /* The strip carries the rule for the whole header block. */\n border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n overflow-x: auto;\n scrollbar-width: none;\n }\n\n /* The header tab's 0/4/12/4 padding composes the 32px row; the 12px bottom gives the active indicator its band, set via the group's public padding hook. */\n ::slotted([slot='tabs']) {\n --nile-nav-tab-padding: var(--nile-spacing-none, var(--ng-spacing-none))\n var(--nile-spacing-xs, var(--ng-spacing-xs))\n var(--nile-spacing-lg, var(--ng-spacing-lg))\n var(--nile-spacing-xs, var(--ng-spacing-xs));\n }\n\n .tabs::-webkit-scrollbar {\n display: none;\n }\n\n :host(:not([has-tabs])) .tabs,\n :host([notabs]) .tabs {\n display: none;\n }\n\n /* A slotted nile-nav-tab-group drops its own track (--track-width:0 on the host) and is bottom-aligned so its indicator sits on the region's single rule. */\n /* Scoped to the group, not the slot, so bare tab controls are not stretched to an equal share of the strip. */\n ::slotted(nile-nav-tab-group[slot='tabs']) {\n --track-width: 0px;\n flex: 1 1 auto;\n min-width: 0;\n align-self: flex-end;\n }\n\n /* ------------------------------------------------------------------- main */\n\n .main {\n box-sizing: border-box;\n flex: 1 1 auto;\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: auto;\n /* Top and sides only: the page runs to the container's bottom edge. */\n padding: var(--nile-app-shell-content-padding);\n padding-block-end: 0;\n }\n\n .main:focus {\n outline: none;\n }\n\n :host([no-padding]) .main {\n padding: 0;\n }\n\n .main-inner {\n width: 100%;\n min-height: 0;\n }\n\n /* Centred when short, scrolls from the top when tall. */\n :host([content-align='centered']) .main-inner {\n /* width:100% + max-width leaves space for the auto margins to centre it while short, and scroll from the top when tall. */\n width: 100%;\n max-width: var(--nile-app-shell-centered-width);\n margin: auto;\n }\n\n /* ------------------------------------------------------------------ aside */\n\n .aside {\n box-sizing: border-box;\n /* Gives before the page does, down to its own floor. */\n flex: 0 1 var(--nile-app-shell-aside-width);\n min-width: var(--nile-app-shell-aside-min-width);\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: auto;\n padding: var(--nile-app-shell-content-padding);\n background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n border-radius: var(--nile-app-shell-radius);\n }\n\n :host(:not([has-aside])) .aside {\n display: none;\n }\n\n /* -------------------------------------------------------------- AI panel */\n\n .panel {\n box-sizing: border-box;\n /* The panel pushes the page rather than covering it: full width while the row affords it, shrinking to a real min-width floor. */\n flex: 0 1 var(--nile-app-shell-panel-width);\n width: var(--nile-app-shell-panel-width);\n min-width: var(--nile-app-shell-panel-min-width);\n display: flex;\n flex-direction: column;\n min-height: 0;\n overflow: hidden;\n background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));\n /* The panel is elevated, so its edge sits a rung above the container's. */\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n border-radius: var(--nile-app-shell-radius);\n box-shadow: var(--nile-box-shadow-2, var(--ng-shadow-lg));\n }\n\n /* Docked: a flex sibling. It squeezes the content, it does not cover it. */\n :host(:not([panel-open])) .panel {\n display: none;\n }\n\n /* Overlay: contained to the content column, above it, and animated in. */\n :host([panel-mode='overlay']) .panel {\n position: absolute;\n z-index: 1001;\n inset-block: var(--nile-app-shell-gutter);\n inset-inline-end: var(--nile-app-shell-gutter);\n flex: none;\n display: flex;\n width: min(var(--nile-app-shell-panel-width), 92%);\n visibility: hidden;\n transform: translateX(calc(100% + var(--nile-app-shell-gutter)));\n transition: transform var(--nile-app-shell-duration)\n var(--nile-app-shell-ease),\n visibility 0s linear var(--nile-app-shell-duration);\n }\n\n :host([panel-mode='overlay'][panel-open]) .panel {\n visibility: visible;\n transform: none;\n transition: transform var(--nile-app-shell-duration)\n var(--nile-app-shell-ease);\n }\n\n /* An unavailable panel is unmounted in either mode. */\n :host(:not([has-panel])) .panel {\n display: none;\n }\n\n .panel-header {\n box-sizing: border-box;\n flex: 0 0 auto;\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n height: var(--nile-app-shell-panel-header-height);\n padding-inline: var(--nile-spacing-3xl, var(--ng-spacing-3xl));\n border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n }\n\n /* The back control for a panel sub-view; stands where the brand mark does in the root view. */\n .panel-back {\n flex: 0 0 auto;\n margin-inline-start: -6px;\n }\n\n /* Mirrors the back control's offset on the end edge: the 20px glyph, not the 32px circle, lands on the padding line. */\n .panel-close {\n margin-inline-end: -6px;\n }\n\n :host([panelback]) .panel-mark {\n display: none;\n }\n\n .panel-heading {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-md, var(--ng-spacing-md));\n min-width: 0;\n }\n\n .panel-mark {\n display: inline-flex;\n color: var(\n --nile-colors-primary-600,\n var(--ng-colors-fg-brand-primary-600)\n );\n }\n\n .panel-mark nile-glyph {\n --nile-svg-fill: currentColor;\n --nile-svg-stroke-width: var(--ng-svg-stroke-width);\n width: 20px;\n height: 20px;\n }\n\n .panel-title {\n margin: 0;\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n font-weight: var(\n --nile-font-weight-semi-bold,\n var(--ng-font-weight-semibold)\n );\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n\n .panel-actions {\n display: flex;\n align-items: center;\n gap: var(--nile-spacing-xs, var(--ng-spacing-xs));\n margin-inline-start: auto;\n }\n\n .panel-body {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n /* 16 under the header, 24 on the sides and bottom. */\n padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl));\n display: flex;\n flex-direction: column;\n gap: var(--nile-spacing-xl, var(--ng-spacing-xl));\n }\n\n .panel-footer {\n flex: 0 0 auto;\n padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl));\n border-top: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));\n }\n\n :host(:not([has-panel-footer])) .panel-footer {\n display: none;\n }\n\n /* ---------------------------------------------------------- skip link */\n\n .skip-link {\n position: absolute;\n z-index: 1002;\n inset-inline-start: var(--nile-spacing-md, var(--ng-spacing-md));\n inset-block-start: var(--nile-spacing-md, var(--ng-spacing-md));\n display: inline-flex;\n align-items: center;\n padding: var(--nile-spacing-md, var(--ng-spacing-md))\n var(--nile-spacing-lg, var(--ng-spacing-lg));\n border: var(--nile-border-width-1, var(--ng-border-width-1)) solid\n var(--nile-colors-neutral-500, var(--ng-colors-border-primary));\n border-radius: var(--nile-radius-md, var(--ng-radius-md));\n background: var(--nile-colors-white-base, var(--ng-colors-bg-primary));\n color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));\n font: inherit;\n font-size: var(--nile-type-scale-3, var(--ng-font-size-text-sm));\n box-shadow: var(--nile-box-shadow-2, var(--ng-shadow-lg));\n cursor: pointer;\n transform: translateY(-200%);\n transition: transform\n var(--nile-transition-duration-short, var(--ng-transition-duration-fast))\n var(--nile-app-shell-ease);\n }\n\n .skip-link:focus-visible {\n outline: none;\n transform: none;\n box-shadow: 0 0 0 2px\n var(--nile-colors-neutral-100, var(--ng-colors-bg-tertiary)),\n 0 0 0 4px\n var(--nile-colors-primary-600, var(--ng-colors-fg-brand-primary-600));\n }\n\n :host([no-skip-link]) .skip-link {\n display: none;\n }\n\n @media (prefers-reduced-motion: reduce) {\n :host,\n .panel,\n .skip-link {\n transition: none;\n }\n }\n`;\n\nexport default [styles];\n"]}