@santi020k/lumen 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/README.md +207 -3
  2. package/dist/cli.js +49 -5
  3. package/dist/consumer-upgrade-audit.d.ts +10 -0
  4. package/dist/consumer-upgrade-audit.d.ts.map +1 -0
  5. package/dist/consumer-upgrade-audit.js +52 -0
  6. package/dist/index.d.ts +3 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +3 -0
  9. package/dist/installer.d.ts.map +1 -1
  10. package/dist/installer.js +16 -0
  11. package/dist/integration-diagnostics.d.ts.map +1 -1
  12. package/dist/integration-diagnostics.js +199 -3
  13. package/dist/registry-component-name.d.ts +2 -0
  14. package/dist/registry-component-name.d.ts.map +1 -0
  15. package/dist/registry-component-name.js +53 -0
  16. package/dist/registry-data.d.ts +339 -18
  17. package/dist/registry-data.d.ts.map +1 -1
  18. package/dist/registry-data.js +789 -3
  19. package/dist/registry.d.ts.map +1 -1
  20. package/dist/registry.js +11 -9
  21. package/dist/source-script-ranges.d.ts +3 -0
  22. package/dist/source-script-ranges.d.ts.map +1 -0
  23. package/dist/source-script-ranges.js +41 -0
  24. package/dist/v2-migration.d.ts +38 -0
  25. package/dist/v2-migration.d.ts.map +1 -1
  26. package/dist/v2-migration.js +26 -14
  27. package/dist/v4-migration.d.ts +26 -0
  28. package/dist/v4-migration.d.ts.map +1 -0
  29. package/dist/v4-migration.js +272 -0
  30. package/dist/version-migration-dependencies.d.ts +8 -0
  31. package/dist/version-migration-dependencies.d.ts.map +1 -0
  32. package/dist/version-migration-dependencies.js +10 -0
  33. package/dist/version-migration-source.d.ts +4 -0
  34. package/dist/version-migration-source.d.ts.map +1 -0
  35. package/dist/version-migration-source.js +281 -0
  36. package/dist/version-migration.d.ts +34 -0
  37. package/dist/version-migration.d.ts.map +1 -0
  38. package/dist/version-migration.js +158 -0
  39. package/native-components.json +563 -16
  40. package/package.json +26 -11
  41. package/release-manifest.json +22 -17
  42. package/styles/critical.css +634 -61
  43. package/styles/motion.css +61 -0
  44. package/styles/world-map.css +30 -0
  45. package/styles.css +1736 -455
  46. package/templates/astro/advanced-fields/src/lumen/advanced-fields.astro +35 -0
  47. package/templates/astro/ai-docs/llms.txt +3 -0
  48. package/templates/astro/analytics-dashboard/src/lumen/analytics-dashboard.astro +110 -0
  49. package/templates/astro/auth-onboarding/src/lumen/auth-onboarding.astro +107 -0
  50. package/templates/astro/command-center/src/lumen/command-center.astro +34 -0
  51. package/templates/astro/commerce-dashboard/src/lumen/commerce-dashboard.astro +108 -0
  52. package/templates/astro/content-flow-actions/src/lumen/content-flow-actions.astro +10 -0
  53. package/templates/astro/content-flow-header/src/lumen/content-flow-header.astro +14 -0
  54. package/templates/astro/content-flow-list/src/lumen/content-flow-list.astro +13 -0
  55. package/templates/astro/content-flow-settings/src/lumen/content-flow-settings.astro +17 -0
  56. package/templates/astro/data-collections/src/lumen/data-collections.astro +52 -0
  57. package/templates/astro/feature-preview/src/lumen/feature-preview.astro +32 -0
  58. package/templates/astro/guided-onboarding/src/lumen/guided-onboarding.astro +35 -0
  59. package/templates/astro/interactive-pricing/src/lumen/interactive-pricing.astro +32 -0
  60. package/templates/astro/media-workspace/src/lumen/adjustment-controls.astro +89 -0
  61. package/templates/astro/media-workspace/src/lumen/media-processing.astro +47 -0
  62. package/templates/astro/media-workspace/src/lumen/media-workspace.astro +77 -0
  63. package/templates/astro/page-header/src/lumen/page-header.astro +41 -0
  64. package/templates/astro/project-workspace/src/lumen/project-workspace.astro +103 -0
  65. package/templates/astro/rich-text-editor/src/lumen/rich-text-editor.astro +21 -0
  66. package/templates/astro/saas-admin/src/lumen/saas-admin.astro +110 -0
  67. package/templates/astro/scheduler/src/lumen/scheduler.astro +36 -0
  68. package/templates/astro/section-header/src/lumen/section-header.astro +35 -0
  69. package/templates/astro/theme-builder/src/lumen/theme-builder.astro +28 -0
  70. package/templates/elements/advanced-fields/src/lumen/advanced-fields.html +25 -0
  71. package/templates/elements/analytics-dashboard/src/lumen/analytics-dashboard.html +28 -0
  72. package/templates/elements/auth-onboarding/src/lumen/auth-onboarding.html +79 -0
  73. package/templates/elements/command-center/src/lumen/command-center.html +15 -0
  74. package/templates/elements/commerce-dashboard/src/lumen/commerce-dashboard.html +24 -0
  75. package/templates/elements/content-flow-actions/src/lumen/content-flow-actions.html +11 -0
  76. package/templates/elements/content-flow-header/src/lumen/content-flow-header.html +15 -0
  77. package/templates/elements/content-flow-list/src/lumen/content-flow-list.html +14 -0
  78. package/templates/elements/content-flow-settings/src/lumen/content-flow-settings.html +18 -0
  79. package/templates/elements/data-collections/src/lumen/data-collections.html +31 -0
  80. package/templates/elements/feature-preview/src/lumen/feature-preview.html +13 -0
  81. package/templates/elements/guided-onboarding/src/lumen/guided-onboarding.html +16 -0
  82. package/templates/elements/interactive-pricing/src/lumen/interactive-pricing.html +13 -0
  83. package/templates/elements/media-workspace/src/lumen/media-workspace.html +57 -0
  84. package/templates/elements/media-workspace/src/lumen/media-workspace.ts +163 -0
  85. package/templates/elements/page-header/src/lumen/page-header.html +20 -0
  86. package/templates/elements/project-workspace/src/lumen/project-workspace.html +17 -0
  87. package/templates/elements/rich-text-editor/src/lumen/rich-text-editor.html +17 -0
  88. package/templates/elements/saas-admin/src/lumen/saas-admin.html +18 -0
  89. package/templates/elements/scheduler/src/lumen/scheduler.html +27 -0
  90. package/templates/elements/section-header/src/lumen/section-header.html +21 -0
  91. package/templates/elements/theme-builder/src/lumen/theme-builder.html +59 -0
  92. package/templates/react/advanced-fields/src/lumen/advanced-fields.tsx +37 -0
  93. package/templates/react/analytics-dashboard/src/lumen/analytics-dashboard.tsx +143 -0
  94. package/templates/react/auth-onboarding/src/lumen/auth-onboarding.tsx +129 -0
  95. package/templates/react/command-center/src/lumen/command-center.tsx +85 -0
  96. package/templates/react/commerce-dashboard/src/lumen/commerce-dashboard.tsx +148 -0
  97. package/templates/react/content-flow-actions/src/lumen/content-flow-actions.tsx +11 -0
  98. package/templates/react/content-flow-header/src/lumen/content-flow-header.tsx +14 -0
  99. package/templates/react/content-flow-list/src/lumen/content-flow-list.tsx +19 -0
  100. package/templates/react/content-flow-settings/src/lumen/content-flow-settings.tsx +26 -0
  101. package/templates/react/data-collections/src/lumen/data-collections.tsx +60 -0
  102. package/templates/react/feature-preview/src/lumen/feature-preview.tsx +42 -0
  103. package/templates/react/guided-onboarding/src/lumen/guided-onboarding.tsx +100 -0
  104. package/templates/react/import-review/src/lumen/import-review.tsx +150 -0
  105. package/templates/react/interactive-pricing/src/lumen/interactive-pricing.tsx +60 -0
  106. package/templates/react/media-workspace/src/lumen/adjustment-controls.tsx +61 -0
  107. package/templates/react/media-workspace/src/lumen/media-processing.tsx +43 -0
  108. package/templates/react/media-workspace/src/lumen/media-workspace.tsx +100 -0
  109. package/templates/react/operational-records/src/lumen/operational-records.tsx +92 -0
  110. package/templates/react/page-header/src/lumen/page-header.tsx +41 -0
  111. package/templates/react/project-workspace/src/lumen/project-workspace.tsx +112 -0
  112. package/templates/react/record-workspace/src/lumen/record-workspace.tsx +67 -0
  113. package/templates/react/review-workflow/src/lumen/review-workflow.tsx +202 -0
  114. package/templates/react/rich-text-editor/src/lumen/rich-text-editor.tsx +33 -0
  115. package/templates/react/saas-admin/src/lumen/saas-admin.tsx +119 -0
  116. package/templates/react/scheduler/src/lumen/scheduler.tsx +45 -0
  117. package/templates/react/section-header/src/lumen/section-header.tsx +33 -0
  118. package/templates/react/styles.d.ts +1 -0
  119. package/templates/react/theme-builder/src/lumen/theme-builder.tsx +57 -0
  120. package/templates/react/tsconfig.json +14 -0
  121. package/templates/react/validated-form/src/lumen/validated-form.tsx +166 -0
  122. package/templates/shared/common/src/lumen/lumen-template.css +247 -0
  123. package/templates/shared/media-workspace/src/lumen/media-workspace.css +14 -0
  124. package/templates/shared/visual-blocks/src/lumen/product-blocks.ts +224 -0
  125. package/v4-migration.json +447 -0
@@ -0,0 +1,45 @@
1
+ import {
2
+ Agenda,
3
+ Calendar,
4
+ DatePicker,
5
+ Schedule,
6
+ useSchedule
7
+ } from '@santi020k/lumen-react'
8
+
9
+ export const SchedulerRecipe = () => {
10
+ const schedule = useSchedule()
11
+
12
+ return (
13
+ <section className="lumen-recipe lumen-recipe--scheduler">
14
+ <Calendar aria-label="Choose schedule date" />
15
+ <DatePicker aria-label="Jump to date" />
16
+ <Schedule>
17
+ <header>
18
+ <h2>Launch week</h2>
19
+ </header>
20
+ <div data-ui-schedule-grid>
21
+ <section {...schedule.getSlotProps('monday')}>
22
+ <article {...schedule.getEventProps('schedule-planning')}>
23
+ Planning
24
+ </article>
25
+ </section>
26
+ <section {...schedule.getSlotProps('friday')}>
27
+ <article {...schedule.getEventProps('schedule-ship')}>Ship</article>
28
+ </section>
29
+ </div>
30
+ </Schedule>
31
+ <Agenda>
32
+ <ol>
33
+ <li>
34
+ <strong>Design review</strong>
35
+ <span>10:00</span>
36
+ </li>
37
+ <li>
38
+ <strong>Release notes</strong>
39
+ <span>14:30</span>
40
+ </li>
41
+ </ol>
42
+ </Agenda>
43
+ </section>
44
+ )
45
+ }
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { Badge, Stack, Typography } from '@santi020k/lumen-react'
4
+
5
+ export interface SectionHeaderRecipeProps {
6
+ actions?: ReactNode
7
+ actionsLabel?: string
8
+ count?: string
9
+ description?: string
10
+ headingId: string
11
+ level?: 2 | 3 | 4 | 5 | 6
12
+ title: string
13
+ }
14
+
15
+ export const SectionHeaderRecipe = ({ actions, actionsLabel = 'Section actions', count, description, headingId, level = 2, title }: SectionHeaderRecipeProps) => {
16
+ const headings = { 2: 'h2', 3: 'h3', 4: 'h4', 5: 'h5', 6: 'h6' } as const
17
+ const Heading = headings[level]
18
+
19
+ return (
20
+ <header aria-labelledby={headingId}>
21
+ <Stack direction="horizontal" align="start" justify="between" gap="group" wrap>
22
+ <Stack gap="related" style={{ flex: '1 1 20rem', minWidth: 0, overflowWrap: 'anywhere' }}>
23
+ <Stack direction="horizontal" align="center" gap="related" wrap>
24
+ <Typography><Heading id={headingId}>{title}</Heading></Typography>
25
+ {count !== undefined && <Badge variant="secondary">{count}</Badge>}
26
+ </Stack>
27
+ {description && <Typography><p>{description}</p></Typography>}
28
+ </Stack>
29
+ {actions && <Stack direction="horizontal" align="center" gap="related" wrap role="group" aria-label={actionsLabel}>{actions}</Stack>}
30
+ </Stack>
31
+ </header>
32
+ )
33
+ }
@@ -0,0 +1 @@
1
+ declare module '*.css' {}
@@ -0,0 +1,57 @@
1
+ import type { CSSProperties } from 'react'
2
+ import { useMemo, useState } from 'react'
3
+
4
+ import { createThemeFromHue, exportThemeCss } from '@santi020k/lumen-core'
5
+ import { Button, Card, Input, ThemeBuilder } from '@santi020k/lumen-react'
6
+
7
+ export const ThemeBuilderRecipe = () => {
8
+ const [hue, setHue] = useState(264)
9
+ const tokens = useMemo(() => createThemeFromHue(hue), [hue])
10
+
11
+ const previewStyle = useMemo(
12
+ () => Object.fromEntries(
13
+ Object.entries(tokens).map(([token, value]) => [`--${token}`, value])
14
+ ) as CSSProperties, [tokens]
15
+ )
16
+
17
+ const css = useMemo(() => exportThemeCss(tokens), [tokens])
18
+
19
+ return (
20
+ <ThemeBuilder className="lumen-recipe lumen-recipe--theme-builder">
21
+ <header>
22
+ <h2>Brand theme</h2>
23
+ <Button
24
+ type="button"
25
+ onClick={() => navigator.clipboard.writeText(css)}
26
+ >
27
+ Copy CSS
28
+ </Button>
29
+ </header>
30
+ <Card id="lumen-theme-preview" style={previewStyle}>
31
+ <span
32
+ data-ui-swatch
33
+ style={{ '--ui-swatch': 'hsl(var(--brand))' } as CSSProperties}
34
+ >
35
+ </span>
36
+ <span
37
+ data-ui-swatch
38
+ style={{ '--ui-swatch': 'hsl(var(--accent))' } as CSSProperties}
39
+ >
40
+ </span>
41
+ </Card>
42
+ <Card>
43
+ <Input
44
+ aria-label="Brand hue"
45
+ max="359"
46
+ min="0"
47
+ type="range"
48
+ value={hue}
49
+ onChange={event => {
50
+ setHue(Number(event.currentTarget.value))
51
+ }}
52
+ />
53
+ </Card>
54
+ <textarea aria-label="Theme CSS" readOnly value={css}></textarea>
55
+ </ThemeBuilder>
56
+ )
57
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "jsx": "react-jsx",
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "rootDir": "../../../../",
8
+ "paths": {
9
+ "@santi020k/lumen-react": ["../../../react/src/index.ts"]
10
+ },
11
+ "noEmit": true
12
+ },
13
+ "include": ["**/*.ts", "**/*.tsx", "**/*.d.ts"]
14
+ }
@@ -0,0 +1,166 @@
1
+ import type { ReactNode, SyntheticEvent } from 'react'
2
+ import { useLayoutEffect, useRef, useState } from 'react'
3
+
4
+ import { type LumenFormErrorInput, type LumenFormErrors, normalizeLumenFormErrors } from '@santi020k/lumen-core'
5
+ import { Alert, ErrorSummary } from '@santi020k/lumen-react'
6
+
7
+ export interface ValidatedFormRecipeProps {
8
+ children: (state: { errors: LumenFormErrors, pending: boolean }) => ReactNode
9
+ id: string
10
+ label: string
11
+ summaryHeading: string
12
+ failureMessage: string
13
+ successMessage: string
14
+ validate?: (form: HTMLFormElement) => LumenFormErrorInput
15
+ submit: (values: FormData) => Promise<LumenFormErrorInput | undefined>
16
+ onFailure?: (error: unknown) => void
17
+ onReset?: (event: SyntheticEvent<HTMLFormElement>) => void
18
+ }
19
+
20
+ const emptyErrors: LumenFormErrors = { fields: [], form: [] }
21
+
22
+ const nativeErrors = (form: HTMLFormElement): LumenFormErrors => {
23
+ const view = form.ownerDocument.defaultView
24
+ const fields: LumenFormErrors['fields'] = []
25
+
26
+ if (!view) return emptyErrors
27
+
28
+ for (const control of form.elements) {
29
+ const isControl = control instanceof view.HTMLInputElement ||
30
+ control instanceof view.HTMLSelectElement || control instanceof view.HTMLTextAreaElement
31
+
32
+ if (!isControl) continue
33
+
34
+ if (control.willValidate && !control.validity.valid) {
35
+ fields.push({ controlId: control.id, name: control.name, message: control.validationMessage })
36
+ }
37
+ }
38
+
39
+ return { fields, form: [] }
40
+ }
41
+
42
+ /** Product validation, network policy and idempotency remain in the supplied callbacks. */
43
+ export const ValidatedFormRecipe = ({
44
+ children, id, label, summaryHeading, failureMessage, successMessage, validate, submit, onFailure, onReset
45
+ }: ValidatedFormRecipeProps) => {
46
+ const formRef = useRef<HTMLFormElement>(null)
47
+ const activeRequestRef = useRef(false)
48
+ const attemptedRef = useRef(false)
49
+ const [errors, setErrors] = useState<LumenFormErrors>(emptyErrors)
50
+ const [pending, setPending] = useState(false)
51
+ const [succeeded, setSucceeded] = useState(false)
52
+ const [focusErrors, setFocusErrors] = useState(false)
53
+
54
+ const readErrors = (form: HTMLFormElement) => {
55
+ const native = nativeErrors(form)
56
+ const product = normalizeLumenFormErrors(validate?.(form))
57
+
58
+ return { fields: [...native.fields, ...product.fields], form: [...native.form, ...product.form] }
59
+ }
60
+
61
+ useLayoutEffect(() => {
62
+ const form = formRef.current
63
+
64
+ if (!form || !focusErrors) return
65
+
66
+ const targetId = errors.fields.find(item => item.controlId)?.controlId
67
+ const target = targetId ? form.ownerDocument.getElementById(targetId) : undefined
68
+
69
+ if (target instanceof HTMLElement && (form.contains(target) || ('form' in target && target.form === form))) target.focus()
70
+ else form.querySelector<HTMLElement>('[data-ui-error-summary]')?.focus()
71
+
72
+ setFocusErrors(false)
73
+ }, [errors, focusErrors])
74
+
75
+ const handleSubmit = async (event: SyntheticEvent<HTMLFormElement>) => {
76
+ event.preventDefault()
77
+
78
+ if (activeRequestRef.current) return
79
+
80
+ const form = event.currentTarget
81
+ const next = readErrors(form)
82
+
83
+ attemptedRef.current = true
84
+
85
+ setErrors(next)
86
+
87
+ setSucceeded(false)
88
+
89
+ if (next.fields.length || next.form.length) {
90
+ setFocusErrors(true)
91
+
92
+ return
93
+ }
94
+
95
+ // Capture values before pending UI disables the controls.
96
+ const values = new FormData(form)
97
+
98
+ activeRequestRef.current = true
99
+
100
+ setPending(true)
101
+
102
+ try {
103
+ const result = normalizeLumenFormErrors(await submit(values))
104
+
105
+ setErrors(result)
106
+
107
+ if (result.fields.length || result.form.length) setFocusErrors(true)
108
+ else setSucceeded(true)
109
+ } catch (error) {
110
+ setErrors({ fields: [], form: [failureMessage] })
111
+
112
+ setFocusErrors(true)
113
+
114
+ onFailure?.(error)
115
+ } finally {
116
+ activeRequestRef.current = false
117
+
118
+ setPending(false)
119
+ }
120
+ }
121
+
122
+ return (
123
+ <form
124
+ ref={formRef}
125
+ id={id}
126
+ aria-label={label}
127
+ aria-busy={pending}
128
+ noValidate
129
+ onSubmit={event => {
130
+ void handleSubmit(event)
131
+ }}
132
+ onBlur={event => {
133
+ const next = event.relatedTarget
134
+
135
+ // Keep a focused action stationary between pointer down and click.
136
+ if (next instanceof HTMLElement && next.closest('button') && event.currentTarget.contains(next)) return
137
+
138
+ if (attemptedRef.current && !activeRequestRef.current) setErrors(readErrors(event.currentTarget))
139
+ }}
140
+ onChange={event => {
141
+ if (attemptedRef.current && !activeRequestRef.current) setErrors(readErrors(event.currentTarget))
142
+ }}
143
+ onReset={event => {
144
+ if (activeRequestRef.current) {
145
+ event.preventDefault()
146
+
147
+ return
148
+ }
149
+
150
+ onReset?.(event)
151
+
152
+ if (event.defaultPrevented) return
153
+
154
+ attemptedRef.current = false
155
+
156
+ setErrors(emptyErrors)
157
+
158
+ setSucceeded(false)
159
+ }}
160
+ >
161
+ <ErrorSummary id={`${id}-errors`} heading={summaryHeading} errors={errors} />
162
+ {children({ errors, pending })}
163
+ {succeeded && <Alert role="status">{successMessage}</Alert>}
164
+ </form>
165
+ )
166
+ }
@@ -0,0 +1,247 @@
1
+ .lumen-template {
2
+ min-height: 100vh;
3
+ background: hsl(var(--canvas));
4
+ color: hsl(var(--ink));
5
+ }
6
+
7
+ .lumen-template__shell {
8
+ display: grid;
9
+ grid-template-columns: 15rem minmax(0, 1fr);
10
+ min-height: 100vh;
11
+ }
12
+
13
+ .lumen-template__sidebar {
14
+ display: flex;
15
+ flex-direction: column;
16
+ gap: 1.5rem;
17
+ padding: 1.25rem;
18
+ border: 0;
19
+ border-right: 1px solid hsl(var(--line));
20
+ border-radius: 0;
21
+ background: hsl(var(--surface));
22
+ }
23
+
24
+ .lumen-template__sidebar nav {
25
+ display: grid;
26
+ gap: 0.35rem;
27
+ }
28
+
29
+ .lumen-template__sidebar a {
30
+ padding: 0.7rem 0.8rem;
31
+ border-radius: 0.65rem;
32
+ color: hsl(var(--ink-soft));
33
+ text-decoration: none;
34
+ }
35
+
36
+ .lumen-template__sidebar a[aria-current="page"] {
37
+ background: hsl(var(--brand-soft));
38
+ color: hsl(var(--brand));
39
+ font-weight: 700;
40
+ }
41
+
42
+ .lumen-template__main {
43
+ display: grid;
44
+ align-content: start;
45
+ gap: 1rem;
46
+ min-width: 0;
47
+ padding: clamp(1rem, 4vw, 3rem);
48
+ }
49
+
50
+ .lumen-template__header,
51
+ .lumen-template__section-header,
52
+ .lumen-template__actions {
53
+ display: flex;
54
+ gap: 1rem;
55
+ align-items: center;
56
+ justify-content: space-between;
57
+ }
58
+
59
+ .lumen-template__header h1 {
60
+ margin: 0.4rem 0;
61
+ font-size: clamp(2rem, 5vw, 3.25rem);
62
+ letter-spacing: -0.045em;
63
+ }
64
+
65
+ .lumen-template__header p,
66
+ .lumen-template__section-header p,
67
+ .lumen-template__muted {
68
+ margin: 0;
69
+ color: hsl(var(--ink-soft));
70
+ }
71
+
72
+ .lumen-template__metrics,
73
+ .lumen-template__grid,
74
+ .lumen-template__choices {
75
+ display: grid;
76
+ grid-template-columns: repeat(4, minmax(0, 1fr));
77
+ gap: 1rem;
78
+ }
79
+
80
+ .lumen-template__metric,
81
+ .lumen-template__panel,
82
+ .lumen-template__form {
83
+ display: grid;
84
+ gap: 1rem;
85
+ padding: 1.2rem;
86
+ }
87
+
88
+ .lumen-template__metric .ui-sparkline {
89
+ width: 100%;
90
+ }
91
+
92
+ .lumen-template__grid {
93
+ grid-template-columns: repeat(2, minmax(0, 1fr));
94
+ }
95
+
96
+ .lumen-template__table {
97
+ overflow-x: auto;
98
+ }
99
+
100
+ .lumen-template__table table {
101
+ width: 100%;
102
+ border-collapse: collapse;
103
+ }
104
+
105
+ .lumen-template__table th,
106
+ .lumen-template__table td {
107
+ padding: 0.8rem;
108
+ border-bottom: 1px solid hsl(var(--line));
109
+ text-align: left;
110
+ white-space: nowrap;
111
+ }
112
+
113
+ .lumen-template__list {
114
+ display: grid;
115
+ gap: 0.75rem;
116
+ padding: 0;
117
+ margin: 0;
118
+ list-style: none;
119
+ }
120
+
121
+ .lumen-template__list li {
122
+ padding: 0.75rem;
123
+ border-radius: 0.65rem;
124
+ background: hsl(var(--surface-muted));
125
+ }
126
+
127
+ .lumen-template__form-shell {
128
+ display: grid;
129
+ min-height: 100vh;
130
+ place-items: center;
131
+ padding: 1rem;
132
+ background:
133
+ radial-gradient(circle at 15% 15%, hsl(var(--brand) / 0.2), transparent 28rem),
134
+ hsl(var(--canvas));
135
+ }
136
+
137
+ .lumen-template__form {
138
+ width: min(100%, 38rem);
139
+ padding: clamp(1.4rem, 4vw, 2.5rem);
140
+ }
141
+
142
+ .lumen-template__field {
143
+ display: grid;
144
+ gap: 0.45rem;
145
+ }
146
+
147
+ .lumen-template__choices {
148
+ grid-template-columns: 1fr 1fr;
149
+ }
150
+
151
+ .lumen-template__choice {
152
+ padding: 1rem;
153
+ border: 1px solid hsl(var(--line));
154
+ border-radius: 0.75rem;
155
+ background: hsl(var(--surface-muted));
156
+ }
157
+
158
+ @media (max-width: 64rem) {
159
+ .lumen-template__metrics {
160
+ grid-template-columns: repeat(2, minmax(0, 1fr));
161
+ }
162
+
163
+ .lumen-template__grid {
164
+ grid-template-columns: 1fr;
165
+ }
166
+ }
167
+
168
+ @media (max-width: 48rem) {
169
+ .lumen-template__shell {
170
+ grid-template-columns: 1fr;
171
+ }
172
+
173
+ .lumen-template__sidebar {
174
+ position: static;
175
+ border-right: 0;
176
+ border-bottom: 1px solid hsl(var(--line));
177
+ }
178
+
179
+ .lumen-template__sidebar nav {
180
+ grid-template-columns: repeat(3, max-content);
181
+ overflow-x: auto;
182
+ }
183
+ }
184
+
185
+ @media (max-width: 34rem) {
186
+ .lumen-template__header,
187
+ .lumen-template__section-header {
188
+ align-items: start;
189
+ flex-direction: column;
190
+ }
191
+
192
+ .lumen-template__metrics,
193
+ .lumen-template__choices {
194
+ grid-template-columns: 1fr;
195
+ }
196
+ }
197
+
198
+ .lumen-login-examples {
199
+ box-sizing: border-box;
200
+ grid-column: 1 / -1;
201
+ width: 100%;
202
+ min-width: 0;
203
+ padding: clamp(1.25rem, 4vw, 3rem);
204
+ color: hsl(var(--ink));
205
+ }
206
+
207
+ .lumen-login-examples header {
208
+ max-width: 44rem;
209
+ margin-bottom: 1.5rem;
210
+ }
211
+
212
+ .lumen-login-examples p {
213
+ margin: 0;
214
+ color: hsl(var(--ink-soft));
215
+ line-height: 1.6;
216
+ }
217
+
218
+ .lumen-login-examples h2,
219
+ .lumen-login-examples h3 {
220
+ margin: 0;
221
+ color: hsl(var(--ink));
222
+ line-height: 1.2;
223
+ }
224
+
225
+ .lumen-login-examples h2 {
226
+ margin-block: 0.5rem;
227
+ font-size: clamp(1.75rem, 3vw, 2.5rem);
228
+ }
229
+
230
+ .lumen-login-examples__grid {
231
+ display: grid;
232
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
233
+ gap: 1.25rem;
234
+ }
235
+
236
+ .lumen-login-examples__card {
237
+ display: flex;
238
+ min-width: 0;
239
+ flex-direction: column;
240
+ gap: 1rem;
241
+ padding: 1.5rem;
242
+ }
243
+
244
+ .lumen-login-examples__card .ui-field {
245
+ display: grid;
246
+ gap: 0.5rem;
247
+ }
@@ -0,0 +1,14 @@
1
+ .lumen-media-workspace { display: grid; gap: var(--ui-space-lg); min-width: 0; container-type: inline-size; }
2
+ .lumen-media-workspace__layout { display: grid; grid-template-columns: minmax(0, 1fr) minmax(12rem, 20rem); gap: var(--ui-space-lg); align-items: start; }
3
+ .lumen-media-workspace__preview, .lumen-media-workspace__inspector, .lumen-media-workspace__bottom { min-width: 0; }
4
+ .lumen-media-workspace__preview { color: hsl(var(--ink)); background: hsl(var(--surface)); }
5
+ .lumen-media-workspace__tools { min-width: 0; }
6
+ .lumen-media-workspace__tools .ui-toolbar { flex-wrap: wrap; }
7
+ .lumen-media-workspace__inspector > * + * { margin-block-start: var(--ui-space-lg); }
8
+ @container (max-width: 45rem) {
9
+ .lumen-media-workspace__layout { grid-template-columns: minmax(0, 1fr); }
10
+ .lumen-media-workspace__tools { position: sticky; bottom: var(--ui-space-sm); z-index: 2; }
11
+ }
12
+ @media (prefers-reduced-transparency: reduce), (prefers-contrast: more) {
13
+ .lumen-media-workspace__tools { background: hsl(var(--surface)); backdrop-filter: none; }
14
+ }