@luz-ds/landing 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024–2026 Fernando Muñoz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,91 @@
1
- # Temporary Holding Version
1
+ # @luz-ds/landing
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The page structure of one kind of site, a landing page: the page of a product, a service or an event that asks the visitor to act. Its header, hero, main, sections, stats, footer and glow are classes on native elements, laid out in the markup by core's objects (`.o-page`, `.o-sticky`, `.o-container`, `.o-row`, `.o-stack`, `.o-grid--split`) and core's navbar; this package adds only their skin and rhythm. `@luz-ds/catalog` is optional: load it for the search, the language switch or the cookie banner.
4
+
5
+ > **Beta, version 0.x.** Landing has a version of its own, apart from tokens, core and catalog, and grows in minor versions while it is 0.x: any of them may change a class. Pin an exact version and read the changelog before you upgrade.
6
+
7
+ ## When to use it
8
+
9
+ - You build the page of a product, a service or an event, with a hero and features to show side by side.
10
+ - Not for articles or an app: `@luz-ds/cms` and core's shell have what they need.
11
+
12
+ The blocks of an event landing page (hero, programme, speakers, tickets, call to action, registration) are markup to copy, on the [Landing page templates](https://nandovejer.github.io/luz/docs/landing/templates/) page of the docs. They use the classes of core and this package; a block of features of equal weight is a list on core's split grid, `ul.o-grid.o-grid--split`.
13
+
14
+ ## Prerequisites
15
+
16
+ Requires `@luz-ds/tokens` and `@luz-ds/core`, version `^0.1.0 || ^0.2.0`, declared as peer dependencies, loaded in that order before this package. `@luz-ds/catalog` (`^0.1.0 || ^0.2.0`) is an optional peer: load it before this package if you use its pieces; the patterns work without it.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @luz-ds/tokens @luz-ds/core @luz-ds/catalog @luz-ds/landing
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ### Via npm/bundler
27
+
28
+ ```js
29
+ import '@luz-ds/tokens/css'
30
+ import '@luz-ds/core/css'
31
+ import '@luz-ds/catalog/css'
32
+ import '@luz-ds/landing/css'
33
+ ```
34
+
35
+ ### Via CDN
36
+
37
+ ```html
38
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/tokens@0.1.0/dist/luz-tokens.min.css">
39
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/core@0.1.0/dist/luz.min.css">
40
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/catalog@0.1.0/dist/catalog.min.css">
41
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@luz-ds/landing@0.1.0/dist/landing.min.css">
42
+ ```
43
+
44
+ There is no script. Add core's and catalog's if you use their behaviours.
45
+
46
+ ## Package exports
47
+
48
+ - **`./css`** → `landing.css` — the patterns, in the cascade layer `luz.landing`, after `luz.catalog` (`./css/min` for the minified twin)
49
+ - **`./components/*`** → `dist/components/` — one stylesheet per pattern, `<name>.css` and `<name>.min.css`, each in `luz.landing`, for a page that loads only what it uses
50
+
51
+ ## Classes included
52
+
53
+ - `.luz-landing-header`, `__brand`, `__actions` — The header: on `header.o-sticky > .o-container.o-container--inline.o-row`, with core's `.luz-nav-toggle` and `.luz-navbar`. The page surface, a rule underneath, the brand and the row's gaps; its panels open under it. [Docs](https://nandovejer.github.io/luz/docs/landing/header/)
54
+ - `.luz-landing-hero`, `__copy`, `__eyebrow`, `__title`, `__lead`, `__media` — The first block: on `section.o-grid.o-grid--split`, two equal columns when each gets 24rem, one below. `--centered`: one column, the copy centred above the media. `__media--bleed`, in a centred hero: the media runs from edge to edge of the window, with no sideways scroll. [Docs](https://nandovejer.github.io/luz/docs/landing/hero/)
55
+ - `.luz-landing-main`, `.luz-landing-section`, `__eyebrow`, `__title`, `__lead`, `.luz-landing-glow` — The rhythm of the page, on `main.o-container.o-stack` and `section.o-stack`; the head of a section, which shares the hero's rules (load `hero.css` with `section.css`) one step smaller; and a tint behind the `.o-page`. Two knobs: `--luz-landing-title-accent`, the colour of the `em` in a hero or section title (unset, the title's), and `--luz-landing-glow-x`, where along the top edge the tint sits (unset, the edge where reading starts). [Docs](https://nandovejer.github.io/luz/docs/landing/section/)
56
+ - `.luz-landing-stat`, `__value`, `__label` — A figure and what it counts, alone or in core's `.luz-card`: a big number in the heading face with tabular figures, the label under it. [Docs](https://nandovejer.github.io/luz/docs/landing/stat/)
57
+ - `.luz-landing-footer`, `__brand`, `__col`, `__col-title` — The footer: on `footer > .o-container.o-container--inline.o-row`, a rule on top and its items at the two ends of the row; or on `footer > .o-container.o-container--inline.o-grid`, a brand block and columns of links, side by side from 768px, one under the other below. [Docs](https://nandovejer.github.io/luz/docs/landing/footer/)
58
+
59
+ <!-- luz:ai-agents:start -->
60
+ ## For AI agents
61
+
62
+ `@luz-ds/landing` 0.2.0 is in beta: any release may break anything. Read the breaking changes below before you update, and use only what the files under "Read first" list.
63
+
64
+ ### Breaking changes
65
+
66
+ #### Breaking: `@luz-ds/site` is now `@luz-ds/catalog`, the name the home and the docs already gave to level 3. Install `@luz-ds/catalog` and load `catalog.css` / `catalog.min.css` and `catalog.js` / `catalog.min.js` where you loaded the `site.*` files; its subpaths (`/search`, `/consent`, `/mode`, `/css`, `/css/min`, `/print.css`, `/components/*`, `/classes.json`, `/custom-elements.json`) keep their names. The cascade layer is `luz.catalog` instead of `luz.site`, in the order statement every stylesheet repeats (so tokens and core change too). The landing, cms, app and docs packages take `@luz-ds/catalog` as their optional peer. `luz-check` reads the new names and its `load/order` rule says tokens, core, catalog. The docs of the package are at `/docs/catalog/` (was `/docs/site/`). The tags (`<luz-search>`, `<luz-consent>`, `<luz-mode>`, `<luz-lang-switch>`, `<luz-submenu>`, `<luz-banner>`) and their classes do not change.
67
+
68
+ Older versions: https://github.com/nandovejer/luz/blob/main/packages/landing/CHANGELOG.md
69
+
70
+ ### Read first
71
+
72
+ 1. `dist/classes.json` in this package: the classes and custom properties its CSS defines. Write only those.
73
+ 2. https://nandovejer.github.io/luz/llms.txt: the index of the documentation, short enough for a small context.
74
+ 3. https://nandovejer.github.io/luz/llms-full.txt: the same pages in full.
75
+ 4. https://nandovejer.github.io/luz/examples/: one standalone page of markup for each component and pattern.
76
+
77
+ ### Load order
78
+
79
+ This package needs `@luz-ds/tokens` and `@luz-ds/core` loaded first.
80
+
81
+ `@luz-ds/catalog` is optional: the CSS of this package works with the packages above alone.
82
+
83
+ ### Check what you write
84
+
85
+ Run `npx @luz-ds/check <files>` on every HTML and CSS file you write, and fix what it reports. To copy the accessibility reviewer into your project: `npx @luz-ds/check agent > .claude/agents/luz-accessibility-reviewer.md`.
86
+
87
+ <!-- luz:ai-agents:end -->
88
+
89
+ ## License
90
+
91
+ MIT
@@ -0,0 +1,39 @@
1
+ {
2
+ "schema": 1,
3
+ "package": "@luz-ds/landing",
4
+ "version": "0.2.0",
5
+ "classes": [
6
+ { "name": "luz-landing-footer", "kind": "pattern", "layer": "luz.landing", "files": ["components/footer.css"], "defined": true, "block": "luz-landing-footer" },
7
+ { "name": "luz-landing-footer__brand", "kind": "pattern", "layer": "luz.landing", "files": ["components/footer.css"], "defined": true, "block": "luz-landing-footer", "part": "brand" },
8
+ { "name": "luz-landing-footer__col", "kind": "pattern", "layer": "luz.landing", "files": ["components/footer.css"], "defined": true, "block": "luz-landing-footer", "part": "col" },
9
+ { "name": "luz-landing-footer__col-title", "kind": "pattern", "layer": "luz.landing", "files": ["components/footer.css"], "defined": true, "block": "luz-landing-footer", "part": "col-title" },
10
+ { "name": "luz-landing-glow", "kind": "pattern", "layer": "luz.landing", "files": ["components/glow.css"], "defined": true, "block": "luz-landing-glow" },
11
+ { "name": "luz-landing-header", "kind": "pattern", "layer": "luz.landing", "files": ["components/header.css"], "defined": true, "block": "luz-landing-header" },
12
+ { "name": "luz-landing-header__actions", "kind": "pattern", "layer": "luz.landing", "files": ["components/header.css"], "defined": true, "block": "luz-landing-header", "part": "actions" },
13
+ { "name": "luz-landing-header__brand", "kind": "pattern", "layer": "luz.landing", "files": ["components/header.css"], "defined": true, "block": "luz-landing-header", "part": "brand" },
14
+ { "name": "luz-landing-hero", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css"], "defined": true, "block": "luz-landing-hero" },
15
+ { "name": "luz-landing-hero--centered", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css"], "defined": true, "block": "luz-landing-hero", "modifier": "centered" },
16
+ { "name": "luz-landing-hero__copy", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css"], "defined": true, "block": "luz-landing-hero", "part": "copy" },
17
+ { "name": "luz-landing-hero__eyebrow", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css"], "defined": true, "block": "luz-landing-hero", "part": "eyebrow" },
18
+ { "name": "luz-landing-hero__lead", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css"], "defined": true, "block": "luz-landing-hero", "part": "lead" },
19
+ { "name": "luz-landing-hero__media", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css"], "defined": true, "block": "luz-landing-hero", "part": "media" },
20
+ { "name": "luz-landing-hero__media--bleed", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css"], "defined": true, "block": "luz-landing-hero", "modifier": "bleed", "part": "media" },
21
+ { "name": "luz-landing-hero__title", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css"], "defined": true, "block": "luz-landing-hero", "part": "title" },
22
+ { "name": "luz-landing-main", "kind": "pattern", "layer": "luz.landing", "files": ["components/main.css"], "defined": true, "block": "luz-landing-main" },
23
+ { "name": "luz-landing-section", "kind": "pattern", "layer": "luz.landing", "files": ["components/section.css"], "defined": true, "block": "luz-landing-section" },
24
+ { "name": "luz-landing-section__eyebrow", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css", "components/section.css"], "defined": true, "block": "luz-landing-section", "part": "eyebrow" },
25
+ { "name": "luz-landing-section__lead", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css", "components/section.css"], "defined": true, "block": "luz-landing-section", "part": "lead" },
26
+ { "name": "luz-landing-section__title", "kind": "pattern", "layer": "luz.landing", "files": ["components/hero.css", "components/section.css"], "defined": true, "block": "luz-landing-section", "part": "title" },
27
+ { "name": "luz-landing-stat", "kind": "pattern", "layer": "luz.landing", "files": ["components/stat.css"], "defined": true, "block": "luz-landing-stat" },
28
+ { "name": "luz-landing-stat__label", "kind": "pattern", "layer": "luz.landing", "files": ["components/stat.css"], "defined": true, "block": "luz-landing-stat", "part": "label" },
29
+ { "name": "luz-landing-stat__value", "kind": "pattern", "layer": "luz.landing", "files": ["components/section.css", "components/stat.css"], "defined": true, "block": "luz-landing-stat", "part": "value" },
30
+ { "name": "o-actions", "kind": "object", "layer": "luz.landing", "files": ["components/hero.css"], "defined": false, "block": "o-actions" },
31
+ { "name": "o-grid", "kind": "object", "layer": "luz.landing", "files": ["components/footer.css"], "defined": false, "block": "o-grid" },
32
+ { "name": "o-row", "kind": "object", "layer": "luz.landing", "files": ["components/footer.css", "components/header.css"], "defined": false, "block": "o-row" }
33
+ ],
34
+ "properties": [
35
+ { "name": "--luz-grid-min", "scope": "component", "layer": "luz.landing", "files": ["components/hero.css"] },
36
+ { "name": "--luz-landing-glow-start", "scope": "component", "layer": "luz.landing", "files": ["components/glow.css"] },
37
+ { "name": "--luz-lang-switch-top", "scope": "component", "layer": "luz.landing", "files": ["components/header.css"] }
38
+ ]
39
+ }
@@ -0,0 +1,80 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.catalog, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.landing {
3
+ /* ==========================================================================
4
+ LUZ Landing: footer.
5
+ footer.luz-landing-footer > div.o-container.o-container--inline.o-row: a
6
+ divider above, its items at the two ends of the row; at 768px and below
7
+ a column aligned to the start.
8
+ Or > div.o-container.o-container--inline.o-grid holding a __brand (the
9
+ name, a version, one line) and __col columns (nav > h2.__col-title + ul
10
+ of links, each a target of 24px or more): one under the other, side
11
+ by side from 768px, the brand twice as wide as a column.
12
+ ========================================================================== */
13
+
14
+ .luz-landing-footer {
15
+ padding-block: var(--luz-space-lg) var(--luz-space-xl);
16
+ border-block-start: 1px solid var(--luz-divider);
17
+ }
18
+
19
+ .luz-landing-footer > .o-row {
20
+ justify-content: space-between;
21
+ gap: var(--luz-space-md);
22
+ }
23
+
24
+ /* A column that does not wrap: a wrapping column nested in grids makes
25
+ Chromium's layout take tens of seconds */
26
+ @media (max-width: 768px) {
27
+ .luz-landing-footer > .o-row {
28
+ flex-flow: column nowrap;
29
+ align-items: flex-start;
30
+ }
31
+ }
32
+
33
+ .luz-landing-footer > .o-grid {
34
+ gap: var(--luz-space-lg);
35
+ }
36
+
37
+ @media (min-width: 768px) {
38
+ .luz-landing-footer > .o-grid {
39
+ grid-template-columns: minmax(0, 2fr);
40
+ grid-auto-columns: minmax(0, 1fr);
41
+ grid-auto-flow: column;
42
+ }
43
+ }
44
+
45
+ .luz-landing-footer__brand,
46
+ .luz-landing-footer__col,
47
+ .luz-landing-footer__col :where(ul) {
48
+ display: grid;
49
+ align-content: start;
50
+ gap: var(--luz-space-xs);
51
+ margin: 0;
52
+ }
53
+
54
+ .luz-landing-footer__brand > *,
55
+ .luz-landing-footer__col-title {
56
+ margin: 0;
57
+ }
58
+
59
+ .luz-landing-footer__col-title {
60
+ font-family: var(--luz-font-sans);
61
+ font-size: var(--luz-text-sm);
62
+ font-weight: 600;
63
+ letter-spacing: 0;
64
+ color: var(--luz-text-primary);
65
+ }
66
+
67
+ .luz-landing-footer__col :where(ul) {
68
+ padding: 0;
69
+ list-style: none;
70
+ }
71
+
72
+ /* Each link is a target of 24px or more (WCAG 2.5.8): a box of its own,
73
+ as tall as a small control, the text centred in it */
74
+ .luz-landing-footer__col :where(a) {
75
+ display: inline-flex;
76
+ align-items: center;
77
+ min-block-size: var(--luz-control-sm);
78
+ }
79
+
80
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.catalog,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.landing{.luz-landing-footer{padding-block:var(--luz-space-lg) var(--luz-space-xl);border-block-start:1px solid var(--luz-divider)}.luz-landing-footer>.o-row{justify-content:space-between;gap:var(--luz-space-md)}@media(max-width:768px){.luz-landing-footer>.o-row{flex-flow:column nowrap;align-items:flex-start}}.luz-landing-footer>.o-grid{gap:var(--luz-space-lg)}@media(min-width:768px){.luz-landing-footer>.o-grid{grid-template-columns:minmax(0,2fr);grid-auto-columns:minmax(0,1fr);grid-auto-flow:column}}.luz-landing-footer__brand,.luz-landing-footer__col,.luz-landing-footer__col :where(ul){display:grid;align-content:start;gap:var(--luz-space-xs);margin:0}.luz-landing-footer__brand>*,.luz-landing-footer__col-title{margin:0}.luz-landing-footer__col-title{font-family:var(--luz-font-sans);font-size:var(--luz-text-sm);font-weight:600;letter-spacing:0;color:var(--luz-text-primary)}.luz-landing-footer__col :where(ul){padding:0;list-style:none}.luz-landing-footer__col :where(a){display:inline-flex;align-items:center;min-block-size:var(--luz-control-sm)}}
@@ -0,0 +1,31 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.catalog, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.landing {
3
+ /* ==========================================================================
4
+ LUZ Landing: glow.
5
+ On the .o-page element: the brand's subtle tint at the top of the page,
6
+ over a fade from the page surface to the muted one. Decoration only: it
7
+ carries no meaning, and paper does not get it.
8
+ --luz-landing-glow-x: where along the top edge the tint sits, a physical
9
+ position (0% the left, 100% the right). Unset, the edge where reading
10
+ starts: the left in left-to-right text, the right in right-to-left.
11
+ ========================================================================== */
12
+
13
+ .luz-landing-glow {
14
+ --luz-landing-glow-start: 100%;
15
+
16
+ background:
17
+ radial-gradient(circle at var(--luz-landing-glow-x, var(--luz-landing-glow-start)) 0%, var(--luz-brand-subtle), transparent 28%),
18
+ linear-gradient(180deg, var(--luz-surface-page) 0%, var(--luz-surface-muted) 100%);
19
+ }
20
+
21
+ .luz-landing-glow:dir(ltr) {
22
+ --luz-landing-glow-start: 0%;
23
+ }
24
+
25
+ @media print {
26
+ .luz-landing-glow {
27
+ background: none;
28
+ }
29
+ }
30
+
31
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.catalog,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.landing{.luz-landing-glow{--luz-landing-glow-start: 100%;background:radial-gradient(circle at var(--luz-landing-glow-x, var(--luz-landing-glow-start)) 0%,var(--luz-brand-subtle),transparent 28%),linear-gradient(180deg,var(--luz-surface-page) 0%,var(--luz-surface-muted) 100%)}.luz-landing-glow:dir(ltr){--luz-landing-glow-start: 0%}@media print{.luz-landing-glow{background:none}}}
@@ -0,0 +1,71 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.catalog, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.landing {
3
+ /* ==========================================================================
4
+ LUZ Landing: header.
5
+ header.luz-landing-header.o-sticky > div.o-container.o-container--inline.o-row
6
+ holding core's .luz-nav-toggle, the __brand link, core's
7
+ nav.luz-navbar[popover] and a div.o-row.__actions (search, language,
8
+ a login link). Core gives the stickiness, the width and the row; the
9
+ landing adds the surface, the divider, the brand and the gaps.
10
+ ========================================================================== */
11
+
12
+ /* A one-row bar of 44px controls is core's --luz-bar-h tall: two 2xs
13
+ paddings, the row and the divider. With enlarged text the row wraps
14
+ and the bar grows. */
15
+ .luz-landing-header {
16
+ padding-block: var(--luz-space-2xs);
17
+ border-block-end: 1px solid var(--luz-divider);
18
+ background: var(--luz-surface-page);
19
+ }
20
+
21
+ /* The row wraps rather than overflow (WCAG 1.4.4, 1.4.10); its gutters are
22
+ the page's, so the brand starts where the content starts */
23
+ .luz-landing-header > .o-row {
24
+ gap: var(--luz-space-xs) var(--luz-space-md);
25
+ }
26
+
27
+ /* A link outside a <nav>, so core would underline it. It takes the free
28
+ room of the row, which pushes the links and the actions to the end */
29
+ .luz-landing-header__brand {
30
+ display: inline-flex;
31
+ align-items: center;
32
+ gap: var(--luz-space-xs);
33
+ min-block-size: var(--luz-control-lg);
34
+ margin-inline-end: auto;
35
+ color: var(--luz-text-primary);
36
+ font-family: var(--luz-font-sans);
37
+ font-size: var(--luz-text-base);
38
+ font-weight: 600;
39
+ text-decoration: none;
40
+ }
41
+
42
+ .luz-landing-header__actions {
43
+ gap: var(--luz-space-xs);
44
+ }
45
+
46
+ /* The language list of site's <luz-lang-switch> opens under this bar */
47
+ .luz-landing-header :where(luz-lang-switch) {
48
+ --luz-lang-switch-top: var(--luz-bar-h);
49
+ }
50
+
51
+ @media (min-width: 769px) {
52
+ .luz-landing-header > .o-row {
53
+ column-gap: var(--luz-space-lg);
54
+ }
55
+ }
56
+
57
+ /* With anchor positioning the panels opened inside the bar (core's navbar,
58
+ the language list) open at its block end, on two rows as on one. Scoped,
59
+ so with two bars on a page each panel finds its own */
60
+ @supports (anchor-name: --luz-bar) {
61
+ .luz-landing-header {
62
+ anchor-name: --luz-bar;
63
+ anchor-scope: --luz-bar;
64
+ }
65
+
66
+ .luz-landing-header :where(luz-lang-switch) {
67
+ --luz-lang-switch-top: anchor(--luz-bar end, var(--luz-bar-h));
68
+ }
69
+ }
70
+
71
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.catalog,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.landing{.luz-landing-header{padding-block:var(--luz-space-2xs);border-block-end:1px solid var(--luz-divider);background:var(--luz-surface-page)}.luz-landing-header>.o-row{gap:var(--luz-space-xs) var(--luz-space-md)}.luz-landing-header__brand{display:inline-flex;align-items:center;gap:var(--luz-space-xs);min-block-size:var(--luz-control-lg);margin-inline-end:auto;color:var(--luz-text-primary);font-family:var(--luz-font-sans);font-size:var(--luz-text-base);font-weight:600;text-decoration:none}.luz-landing-header__actions{gap:var(--luz-space-xs)}.luz-landing-header :where(luz-lang-switch){--luz-lang-switch-top: var(--luz-bar-h)}@media(min-width:769px){.luz-landing-header>.o-row{column-gap:var(--luz-space-lg)}}@supports (anchor-name: --luz-bar){.luz-landing-header{anchor-name:--luz-bar;anchor-scope:--luz-bar}.luz-landing-header :where(luz-lang-switch){--luz-lang-switch-top: anchor(--luz-bar end, var(--luz-bar-h))}}}
@@ -0,0 +1,141 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.catalog, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.landing {
3
+ /* ==========================================================================
4
+ LUZ Landing: hero.
5
+ The first block of a home or a landing page:
6
+ section.luz-landing-hero.o-grid.o-grid--split > __copy (+ __media). Core's
7
+ split grid gives two equal columns when each gets 24rem and one below:
8
+ intrinsic, so it stacks in a narrow column and with larger text. Copy
9
+ first in the source, so first in reading order in both directions.
10
+ --centered: one column, the copy centred above the media. __media--bleed,
11
+ in a centred hero: the media runs from edge to edge of the window.
12
+ The eyebrow, the title and the lead share their rules with the head of a
13
+ section (section.css).
14
+ ========================================================================== */
15
+
16
+ .luz-landing-hero {
17
+ --luz-grid-min: 24rem;
18
+
19
+ gap: var(--luz-space-xl) var(--luz-space-2xl);
20
+ align-items: center;
21
+ padding-block-start: var(--luz-space-3xl);
22
+ }
23
+
24
+ /* Grid items: a long word or a wide child never widens the column */
25
+ .luz-landing-hero__copy,
26
+ .luz-landing-hero__media {
27
+ min-inline-size: 0;
28
+ }
29
+
30
+ /* A word longer than the line (400 % text on a phone, WCAG 1.4.10) breaks
31
+ instead of widening the column: the centred copy is sized to its content,
32
+ so its narrowest width is its longest word */
33
+ .luz-landing-hero__copy {
34
+ overflow-wrap: anywhere;
35
+ }
36
+
37
+ .luz-landing-hero__eyebrow,
38
+ .luz-landing-section__eyebrow {
39
+ margin-block-end: var(--luz-space-sm);
40
+ font-family: var(--luz-font-mono);
41
+ font-size: var(--luz-text-xs);
42
+ font-weight: 500;
43
+ line-height: 1.5;
44
+ letter-spacing: 0.1em;
45
+ text-transform: uppercase;
46
+ color: var(--luz-text-secondary);
47
+ }
48
+
49
+ .luz-landing-hero__title,
50
+ .luz-landing-section__title {
51
+ font-family: var(--luz-font-heading);
52
+ font-size: var(--luz-text-4xl);
53
+ font-weight: 300;
54
+ line-height: 1.1;
55
+ letter-spacing: -0.02em;
56
+ text-wrap: balance;
57
+ color: var(--luz-text-primary);
58
+ }
59
+
60
+ /* The mono has no Arabic letters; spacing between letters breaks the joins */
61
+ .luz-landing-hero__eyebrow:lang(ar),
62
+ .luz-landing-section__eyebrow:lang(ar) {
63
+ font-family: var(--luz-font-sans);
64
+ }
65
+
66
+ .luz-landing-hero__eyebrow:lang(ar),
67
+ .luz-landing-section__eyebrow:lang(ar),
68
+ .luz-landing-hero__title:lang(ar),
69
+ .luz-landing-section__title:lang(ar) {
70
+ letter-spacing: 0;
71
+ }
72
+
73
+ .luz-landing-hero__lead,
74
+ .luz-landing-section__lead {
75
+ max-inline-size: 70ch;
76
+ font-size: var(--luz-text-xl);
77
+ line-height: 1.4;
78
+ text-wrap: pretty;
79
+ color: var(--luz-text-secondary);
80
+ }
81
+
82
+ .luz-landing-hero__lead {
83
+ margin-block: var(--luz-space-md) var(--luz-space-lg);
84
+ }
85
+
86
+ /* One column: core's split grid with a column as wide as the row */
87
+ .luz-landing-hero--centered {
88
+ --luz-grid-min: 100%;
89
+
90
+ text-align: center;
91
+ }
92
+
93
+ .luz-landing-hero--centered > .luz-landing-hero__copy {
94
+ justify-self: center;
95
+ max-inline-size: min(100%, 48rem);
96
+ }
97
+
98
+ .luz-landing-hero--centered .luz-landing-hero__lead {
99
+ margin-inline: auto;
100
+ }
101
+
102
+ .luz-landing-hero--centered .o-actions {
103
+ justify-content: center;
104
+ }
105
+
106
+ /* The window's width without a viewport unit, which counts a visible
107
+ scrollbar and scrolls sideways: the root is the query container, and
108
+ the media's margins go out from the centre of the column to its edges */
109
+ :root:has(.luz-landing-hero__media--bleed) {
110
+ container-type: inline-size;
111
+ }
112
+
113
+ .luz-landing-hero__media--bleed {
114
+ margin-inline: calc(50% - 50cqi);
115
+ }
116
+
117
+ @media (max-width: 1023px) {
118
+ .luz-landing-hero {
119
+ padding-block-start: calc(var(--luz-space-xl) + var(--luz-space-xs));
120
+ }
121
+
122
+ .luz-landing-hero__title {
123
+ font-size: var(--luz-text-3xl);
124
+ }
125
+
126
+ .luz-landing-hero__lead {
127
+ font-size: var(--luz-text-lg);
128
+ }
129
+ }
130
+
131
+ @media (max-width: 640px) {
132
+ .luz-landing-hero__title {
133
+ font-size: var(--luz-text-2xl);
134
+ }
135
+
136
+ .luz-landing-hero__lead {
137
+ font-size: var(--luz-text-base);
138
+ }
139
+ }
140
+
141
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.catalog,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.landing{.luz-landing-hero{--luz-grid-min: 24rem;gap:var(--luz-space-xl) var(--luz-space-2xl);align-items:center;padding-block-start:var(--luz-space-3xl)}.luz-landing-hero__copy,.luz-landing-hero__media{min-inline-size:0}.luz-landing-hero__copy{overflow-wrap:anywhere}.luz-landing-hero__eyebrow,.luz-landing-section__eyebrow{margin-block-end:var(--luz-space-sm);font-family:var(--luz-font-mono);font-size:var(--luz-text-xs);font-weight:500;line-height:1.5;letter-spacing:.1em;text-transform:uppercase;color:var(--luz-text-secondary)}.luz-landing-hero__title,.luz-landing-section__title{font-family:var(--luz-font-heading);font-size:var(--luz-text-4xl);font-weight:300;line-height:1.1;letter-spacing:-.02em;text-wrap:balance;color:var(--luz-text-primary)}.luz-landing-hero__eyebrow:lang(ar),.luz-landing-section__eyebrow:lang(ar){font-family:var(--luz-font-sans)}.luz-landing-hero__eyebrow:lang(ar),.luz-landing-section__eyebrow:lang(ar),.luz-landing-hero__title:lang(ar),.luz-landing-section__title:lang(ar){letter-spacing:0}.luz-landing-hero__lead,.luz-landing-section__lead{max-inline-size:70ch;font-size:var(--luz-text-xl);line-height:1.4;text-wrap:pretty;color:var(--luz-text-secondary)}.luz-landing-hero__lead{margin-block:var(--luz-space-md) var(--luz-space-lg)}.luz-landing-hero--centered{--luz-grid-min: 100%;text-align:center}.luz-landing-hero--centered>.luz-landing-hero__copy{justify-self:center;max-inline-size:min(100%,48rem)}.luz-landing-hero--centered .luz-landing-hero__lead{margin-inline:auto}.luz-landing-hero--centered .o-actions{justify-content:center}:root:has(.luz-landing-hero__media--bleed){container-type:inline-size}.luz-landing-hero__media--bleed{margin-inline:calc(50% - 50cqi)}@media(max-width:1023px){.luz-landing-hero{padding-block-start:calc(var(--luz-space-xl) + var(--luz-space-xs))}.luz-landing-hero__title{font-size:var(--luz-text-3xl)}.luz-landing-hero__lead{font-size:var(--luz-text-lg)}}@media(max-width:640px){.luz-landing-hero__title{font-size:var(--luz-text-2xl)}.luz-landing-hero__lead{font-size:var(--luz-text-base)}}}
@@ -0,0 +1,16 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.catalog, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.landing {
3
+ /* ==========================================================================
4
+ LUZ Landing: main.
5
+ The rhythm of the page: main.o-container.o-stack.luz-landing-main. Wide
6
+ room between the blocks and under the last one; none above the first,
7
+ as the hero brings its own. The landing's layer comes after core's
8
+ objects, so this gap wins over .o-stack's.
9
+ ========================================================================== */
10
+
11
+ .luz-landing-main {
12
+ gap: var(--luz-space-3xl);
13
+ padding-block: 0 var(--luz-space-3xl);
14
+ }
15
+
16
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.catalog,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.landing{.luz-landing-main{gap:var(--luz-space-3xl);padding-block:0 var(--luz-space-3xl)}}
@@ -0,0 +1,54 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.catalog, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.landing {
3
+ /* ==========================================================================
4
+ LUZ Landing: section.
5
+ A block of the page after the hero: section.o-stack.luz-landing-section,
6
+ its heading first. Add core's .luz-band for a tint from edge to edge.
7
+ The head: __eyebrow, __title and __lead share the rules of the hero's
8
+ (one selector list, in hero.css, so load it with this file); the title
9
+ is one step smaller.
10
+ ========================================================================== */
11
+
12
+ .luz-landing-section {
13
+ gap: var(--luz-space-md);
14
+ }
15
+
16
+ .luz-landing-section__eyebrow {
17
+ margin-block-end: 0;
18
+ }
19
+
20
+ .luz-landing-section__title,
21
+ .luz-landing-stat__value {
22
+ font-size: var(--luz-text-3xl);
23
+ }
24
+
25
+ /* A knob for the stressed word of a hero's or a section's title; unset,
26
+ the title's colour */
27
+ .luz-landing-hero__title em,
28
+ .luz-landing-section__title em {
29
+ color: var(--luz-landing-title-accent, inherit);
30
+ }
31
+
32
+ .luz-landing-section__lead {
33
+ font-size: var(--luz-text-lg);
34
+ }
35
+
36
+ @media (max-width: 1023px) {
37
+ .luz-landing-section__title,
38
+ .luz-landing-stat__value {
39
+ font-size: var(--luz-text-2xl);
40
+ }
41
+ }
42
+
43
+ @media (max-width: 640px) {
44
+ .luz-landing-section__title,
45
+ .luz-landing-stat__value {
46
+ font-size: var(--luz-text-xl);
47
+ }
48
+
49
+ .luz-landing-section__lead {
50
+ font-size: var(--luz-text-base);
51
+ }
52
+ }
53
+
54
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.catalog,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.landing{.luz-landing-section{gap:var(--luz-space-md)}.luz-landing-section__eyebrow{margin-block-end:0}.luz-landing-section__title,.luz-landing-stat__value{font-size:var(--luz-text-3xl)}.luz-landing-hero__title em,.luz-landing-section__title em{color:var(--luz-landing-title-accent, inherit)}.luz-landing-section__lead{font-size:var(--luz-text-lg)}@media(max-width:1023px){.luz-landing-section__title,.luz-landing-stat__value{font-size:var(--luz-text-2xl)}}@media(max-width:640px){.luz-landing-section__title,.luz-landing-stat__value{font-size:var(--luz-text-xl)}.luz-landing-section__lead{font-size:var(--luz-text-base)}}}
@@ -0,0 +1,34 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.catalog, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.landing {
3
+ /* ==========================================================================
4
+ LUZ Landing: stat.
5
+ A figure and what it counts: div.luz-landing-stat > p.__value + p.__label,
6
+ alone or inside core's .luz-card. Tabular figures, so a row of stats
7
+ lines up digit under digit.
8
+ ========================================================================== */
9
+
10
+ .luz-landing-stat {
11
+ display: grid;
12
+ gap: var(--luz-space-2xs);
13
+ min-inline-size: 0;
14
+ }
15
+
16
+ .luz-landing-stat > * {
17
+ margin: 0;
18
+ }
19
+
20
+ .luz-landing-stat__value {
21
+ font-family: var(--luz-font-heading);
22
+ font-weight: 300;
23
+ font-variant-numeric: tabular-nums;
24
+ line-height: 1.1;
25
+ overflow-wrap: anywhere;
26
+ color: var(--luz-text-primary);
27
+ }
28
+
29
+ .luz-landing-stat__label {
30
+ font-size: var(--luz-text-base);
31
+ color: var(--luz-text-secondary);
32
+ }
33
+
34
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.catalog,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.landing{.luz-landing-stat{display:grid;gap:var(--luz-space-2xs);min-inline-size:0}.luz-landing-stat>*{margin:0}.luz-landing-stat__value{font-family:var(--luz-font-heading);font-weight:300;font-variant-numeric:tabular-nums;line-height:1.1;overflow-wrap:anywhere;color:var(--luz-text-primary)}.luz-landing-stat__label{font-size:var(--luz-text-base);color:var(--luz-text-secondary)}}
@@ -0,0 +1,421 @@
1
+ @layer luz.tokens, luz.themes, luz.settings, luz.generic, luz.elements, luz.objects, luz.components, luz.utilities, luz.catalog, luz.landing, luz.cms, luz.app, luz.docs, luz.print;
2
+ @layer luz.landing {
3
+ /* ==========================================================================
4
+ LUZ Landing
5
+ The page structure of one kind of site, a landing page: its header,
6
+ hero, main, sections, stats, footer and glow. Each is a class of the landing on
7
+ a native element, laid out in the markup by core's objects (.o-page,
8
+ .o-sticky, .o-container, .o-row, .o-stack, .o-grid--split); the landing
9
+ adds only its skin and its rhythm.
10
+ Requires: @luz-ds/tokens and @luz-ds/core loaded first; @luz-ds/catalog
11
+ optional, loaded before this file.
12
+ ========================================================================== */
13
+
14
+ /* ==========================================================================
15
+ LUZ Landing: header.
16
+ header.luz-landing-header.o-sticky > div.o-container.o-container--inline.o-row
17
+ holding core's .luz-nav-toggle, the __brand link, core's
18
+ nav.luz-navbar[popover] and a div.o-row.__actions (search, language,
19
+ a login link). Core gives the stickiness, the width and the row; the
20
+ landing adds the surface, the divider, the brand and the gaps.
21
+ ========================================================================== */
22
+
23
+ /* A one-row bar of 44px controls is core's --luz-bar-h tall: two 2xs
24
+ paddings, the row and the divider. With enlarged text the row wraps
25
+ and the bar grows. */
26
+ .luz-landing-header {
27
+ padding-block: var(--luz-space-2xs);
28
+ border-block-end: 1px solid var(--luz-divider);
29
+ background: var(--luz-surface-page);
30
+ }
31
+
32
+ /* The row wraps rather than overflow (WCAG 1.4.4, 1.4.10); its gutters are
33
+ the page's, so the brand starts where the content starts */
34
+ .luz-landing-header > .o-row {
35
+ gap: var(--luz-space-xs) var(--luz-space-md);
36
+ }
37
+
38
+ /* A link outside a <nav>, so core would underline it. It takes the free
39
+ room of the row, which pushes the links and the actions to the end */
40
+ .luz-landing-header__brand {
41
+ display: inline-flex;
42
+ align-items: center;
43
+ gap: var(--luz-space-xs);
44
+ min-block-size: var(--luz-control-lg);
45
+ margin-inline-end: auto;
46
+ color: var(--luz-text-primary);
47
+ font-family: var(--luz-font-sans);
48
+ font-size: var(--luz-text-base);
49
+ font-weight: 600;
50
+ text-decoration: none;
51
+ }
52
+
53
+ .luz-landing-header__actions {
54
+ gap: var(--luz-space-xs);
55
+ }
56
+
57
+ /* The language list of site's <luz-lang-switch> opens under this bar */
58
+ .luz-landing-header :where(luz-lang-switch) {
59
+ --luz-lang-switch-top: var(--luz-bar-h);
60
+ }
61
+
62
+ @media (min-width: 769px) {
63
+ .luz-landing-header > .o-row {
64
+ column-gap: var(--luz-space-lg);
65
+ }
66
+ }
67
+
68
+ /* With anchor positioning the panels opened inside the bar (core's navbar,
69
+ the language list) open at its block end, on two rows as on one. Scoped,
70
+ so with two bars on a page each panel finds its own */
71
+ @supports (anchor-name: --luz-bar) {
72
+ .luz-landing-header {
73
+ anchor-name: --luz-bar;
74
+ anchor-scope: --luz-bar;
75
+ }
76
+
77
+ .luz-landing-header :where(luz-lang-switch) {
78
+ --luz-lang-switch-top: anchor(--luz-bar end, var(--luz-bar-h));
79
+ }
80
+ }
81
+
82
+ /* ==========================================================================
83
+ LUZ Landing: hero.
84
+ The first block of a home or a landing page:
85
+ section.luz-landing-hero.o-grid.o-grid--split > __copy (+ __media). Core's
86
+ split grid gives two equal columns when each gets 24rem and one below:
87
+ intrinsic, so it stacks in a narrow column and with larger text. Copy
88
+ first in the source, so first in reading order in both directions.
89
+ --centered: one column, the copy centred above the media. __media--bleed,
90
+ in a centred hero: the media runs from edge to edge of the window.
91
+ The eyebrow, the title and the lead share their rules with the head of a
92
+ section (section.css).
93
+ ========================================================================== */
94
+
95
+ .luz-landing-hero {
96
+ --luz-grid-min: 24rem;
97
+
98
+ gap: var(--luz-space-xl) var(--luz-space-2xl);
99
+ align-items: center;
100
+ padding-block-start: var(--luz-space-3xl);
101
+ }
102
+
103
+ /* Grid items: a long word or a wide child never widens the column */
104
+ .luz-landing-hero__copy,
105
+ .luz-landing-hero__media {
106
+ min-inline-size: 0;
107
+ }
108
+
109
+ /* A word longer than the line (400 % text on a phone, WCAG 1.4.10) breaks
110
+ instead of widening the column: the centred copy is sized to its content,
111
+ so its narrowest width is its longest word */
112
+ .luz-landing-hero__copy {
113
+ overflow-wrap: anywhere;
114
+ }
115
+
116
+ .luz-landing-hero__eyebrow,
117
+ .luz-landing-section__eyebrow {
118
+ margin-block-end: var(--luz-space-sm);
119
+ font-family: var(--luz-font-mono);
120
+ font-size: var(--luz-text-xs);
121
+ font-weight: 500;
122
+ line-height: 1.5;
123
+ letter-spacing: 0.1em;
124
+ text-transform: uppercase;
125
+ color: var(--luz-text-secondary);
126
+ }
127
+
128
+ .luz-landing-hero__title,
129
+ .luz-landing-section__title {
130
+ font-family: var(--luz-font-heading);
131
+ font-size: var(--luz-text-4xl);
132
+ font-weight: 300;
133
+ line-height: 1.1;
134
+ letter-spacing: -0.02em;
135
+ text-wrap: balance;
136
+ color: var(--luz-text-primary);
137
+ }
138
+
139
+ /* The mono has no Arabic letters; spacing between letters breaks the joins */
140
+ .luz-landing-hero__eyebrow:lang(ar),
141
+ .luz-landing-section__eyebrow:lang(ar) {
142
+ font-family: var(--luz-font-sans);
143
+ }
144
+
145
+ .luz-landing-hero__eyebrow:lang(ar),
146
+ .luz-landing-section__eyebrow:lang(ar),
147
+ .luz-landing-hero__title:lang(ar),
148
+ .luz-landing-section__title:lang(ar) {
149
+ letter-spacing: 0;
150
+ }
151
+
152
+ .luz-landing-hero__lead,
153
+ .luz-landing-section__lead {
154
+ max-inline-size: 70ch;
155
+ font-size: var(--luz-text-xl);
156
+ line-height: 1.4;
157
+ text-wrap: pretty;
158
+ color: var(--luz-text-secondary);
159
+ }
160
+
161
+ .luz-landing-hero__lead {
162
+ margin-block: var(--luz-space-md) var(--luz-space-lg);
163
+ }
164
+
165
+ /* One column: core's split grid with a column as wide as the row */
166
+ .luz-landing-hero--centered {
167
+ --luz-grid-min: 100%;
168
+
169
+ text-align: center;
170
+ }
171
+
172
+ .luz-landing-hero--centered > .luz-landing-hero__copy {
173
+ justify-self: center;
174
+ max-inline-size: min(100%, 48rem);
175
+ }
176
+
177
+ .luz-landing-hero--centered .luz-landing-hero__lead {
178
+ margin-inline: auto;
179
+ }
180
+
181
+ .luz-landing-hero--centered .o-actions {
182
+ justify-content: center;
183
+ }
184
+
185
+ /* The window's width without a viewport unit, which counts a visible
186
+ scrollbar and scrolls sideways: the root is the query container, and
187
+ the media's margins go out from the centre of the column to its edges */
188
+ :root:has(.luz-landing-hero__media--bleed) {
189
+ container-type: inline-size;
190
+ }
191
+
192
+ .luz-landing-hero__media--bleed {
193
+ margin-inline: calc(50% - 50cqi);
194
+ }
195
+
196
+ @media (max-width: 1023px) {
197
+ .luz-landing-hero {
198
+ padding-block-start: calc(var(--luz-space-xl) + var(--luz-space-xs));
199
+ }
200
+
201
+ .luz-landing-hero__title {
202
+ font-size: var(--luz-text-3xl);
203
+ }
204
+
205
+ .luz-landing-hero__lead {
206
+ font-size: var(--luz-text-lg);
207
+ }
208
+ }
209
+
210
+ @media (max-width: 640px) {
211
+ .luz-landing-hero__title {
212
+ font-size: var(--luz-text-2xl);
213
+ }
214
+
215
+ .luz-landing-hero__lead {
216
+ font-size: var(--luz-text-base);
217
+ }
218
+ }
219
+
220
+ /* ==========================================================================
221
+ LUZ Landing: main.
222
+ The rhythm of the page: main.o-container.o-stack.luz-landing-main. Wide
223
+ room between the blocks and under the last one; none above the first,
224
+ as the hero brings its own. The landing's layer comes after core's
225
+ objects, so this gap wins over .o-stack's.
226
+ ========================================================================== */
227
+
228
+ .luz-landing-main {
229
+ gap: var(--luz-space-3xl);
230
+ padding-block: 0 var(--luz-space-3xl);
231
+ }
232
+
233
+ /* ==========================================================================
234
+ LUZ Landing: section.
235
+ A block of the page after the hero: section.o-stack.luz-landing-section,
236
+ its heading first. Add core's .luz-band for a tint from edge to edge.
237
+ The head: __eyebrow, __title and __lead share the rules of the hero's
238
+ (one selector list, in hero.css, so load it with this file); the title
239
+ is one step smaller.
240
+ ========================================================================== */
241
+
242
+ .luz-landing-section {
243
+ gap: var(--luz-space-md);
244
+ }
245
+
246
+ .luz-landing-section__eyebrow {
247
+ margin-block-end: 0;
248
+ }
249
+
250
+ .luz-landing-section__title,
251
+ .luz-landing-stat__value {
252
+ font-size: var(--luz-text-3xl);
253
+ }
254
+
255
+ /* A knob for the stressed word of a hero's or a section's title; unset,
256
+ the title's colour */
257
+ .luz-landing-hero__title em,
258
+ .luz-landing-section__title em {
259
+ color: var(--luz-landing-title-accent, inherit);
260
+ }
261
+
262
+ .luz-landing-section__lead {
263
+ font-size: var(--luz-text-lg);
264
+ }
265
+
266
+ @media (max-width: 1023px) {
267
+ .luz-landing-section__title,
268
+ .luz-landing-stat__value {
269
+ font-size: var(--luz-text-2xl);
270
+ }
271
+ }
272
+
273
+ @media (max-width: 640px) {
274
+ .luz-landing-section__title,
275
+ .luz-landing-stat__value {
276
+ font-size: var(--luz-text-xl);
277
+ }
278
+
279
+ .luz-landing-section__lead {
280
+ font-size: var(--luz-text-base);
281
+ }
282
+ }
283
+
284
+ /* ==========================================================================
285
+ LUZ Landing: stat.
286
+ A figure and what it counts: div.luz-landing-stat > p.__value + p.__label,
287
+ alone or inside core's .luz-card. Tabular figures, so a row of stats
288
+ lines up digit under digit.
289
+ ========================================================================== */
290
+
291
+ .luz-landing-stat {
292
+ display: grid;
293
+ gap: var(--luz-space-2xs);
294
+ min-inline-size: 0;
295
+ }
296
+
297
+ .luz-landing-stat > * {
298
+ margin: 0;
299
+ }
300
+
301
+ .luz-landing-stat__value {
302
+ font-family: var(--luz-font-heading);
303
+ font-weight: 300;
304
+ font-variant-numeric: tabular-nums;
305
+ line-height: 1.1;
306
+ overflow-wrap: anywhere;
307
+ color: var(--luz-text-primary);
308
+ }
309
+
310
+ .luz-landing-stat__label {
311
+ font-size: var(--luz-text-base);
312
+ color: var(--luz-text-secondary);
313
+ }
314
+
315
+ /* ==========================================================================
316
+ LUZ Landing: footer.
317
+ footer.luz-landing-footer > div.o-container.o-container--inline.o-row: a
318
+ divider above, its items at the two ends of the row; at 768px and below
319
+ a column aligned to the start.
320
+ Or > div.o-container.o-container--inline.o-grid holding a __brand (the
321
+ name, a version, one line) and __col columns (nav > h2.__col-title + ul
322
+ of links, each a target of 24px or more): one under the other, side
323
+ by side from 768px, the brand twice as wide as a column.
324
+ ========================================================================== */
325
+
326
+ .luz-landing-footer {
327
+ padding-block: var(--luz-space-lg) var(--luz-space-xl);
328
+ border-block-start: 1px solid var(--luz-divider);
329
+ }
330
+
331
+ .luz-landing-footer > .o-row {
332
+ justify-content: space-between;
333
+ gap: var(--luz-space-md);
334
+ }
335
+
336
+ /* A column that does not wrap: a wrapping column nested in grids makes
337
+ Chromium's layout take tens of seconds */
338
+ @media (max-width: 768px) {
339
+ .luz-landing-footer > .o-row {
340
+ flex-flow: column nowrap;
341
+ align-items: flex-start;
342
+ }
343
+ }
344
+
345
+ .luz-landing-footer > .o-grid {
346
+ gap: var(--luz-space-lg);
347
+ }
348
+
349
+ @media (min-width: 768px) {
350
+ .luz-landing-footer > .o-grid {
351
+ grid-template-columns: minmax(0, 2fr);
352
+ grid-auto-columns: minmax(0, 1fr);
353
+ grid-auto-flow: column;
354
+ }
355
+ }
356
+
357
+ .luz-landing-footer__brand,
358
+ .luz-landing-footer__col,
359
+ .luz-landing-footer__col :where(ul) {
360
+ display: grid;
361
+ align-content: start;
362
+ gap: var(--luz-space-xs);
363
+ margin: 0;
364
+ }
365
+
366
+ .luz-landing-footer__brand > *,
367
+ .luz-landing-footer__col-title {
368
+ margin: 0;
369
+ }
370
+
371
+ .luz-landing-footer__col-title {
372
+ font-family: var(--luz-font-sans);
373
+ font-size: var(--luz-text-sm);
374
+ font-weight: 600;
375
+ letter-spacing: 0;
376
+ color: var(--luz-text-primary);
377
+ }
378
+
379
+ .luz-landing-footer__col :where(ul) {
380
+ padding: 0;
381
+ list-style: none;
382
+ }
383
+
384
+ /* Each link is a target of 24px or more (WCAG 2.5.8): a box of its own,
385
+ as tall as a small control, the text centred in it */
386
+ .luz-landing-footer__col :where(a) {
387
+ display: inline-flex;
388
+ align-items: center;
389
+ min-block-size: var(--luz-control-sm);
390
+ }
391
+
392
+ /* ==========================================================================
393
+ LUZ Landing: glow.
394
+ On the .o-page element: the brand's subtle tint at the top of the page,
395
+ over a fade from the page surface to the muted one. Decoration only: it
396
+ carries no meaning, and paper does not get it.
397
+ --luz-landing-glow-x: where along the top edge the tint sits, a physical
398
+ position (0% the left, 100% the right). Unset, the edge where reading
399
+ starts: the left in left-to-right text, the right in right-to-left.
400
+ ========================================================================== */
401
+
402
+ .luz-landing-glow {
403
+ --luz-landing-glow-start: 100%;
404
+
405
+ background:
406
+ radial-gradient(circle at var(--luz-landing-glow-x, var(--luz-landing-glow-start)) 0%, var(--luz-brand-subtle), transparent 28%),
407
+ linear-gradient(180deg, var(--luz-surface-page) 0%, var(--luz-surface-muted) 100%);
408
+ }
409
+
410
+ .luz-landing-glow:dir(ltr) {
411
+ --luz-landing-glow-start: 0%;
412
+ }
413
+
414
+ @media print {
415
+ .luz-landing-glow {
416
+ background: none;
417
+ }
418
+ }
419
+
420
+
421
+ }
@@ -0,0 +1 @@
1
+ @layer luz.tokens,luz.themes,luz.settings,luz.generic,luz.elements,luz.objects,luz.components,luz.utilities,luz.catalog,luz.landing,luz.cms,luz.app,luz.docs,luz.print;@layer luz.landing{.luz-landing-header{padding-block:var(--luz-space-2xs);border-block-end:1px solid var(--luz-divider);background:var(--luz-surface-page)}.luz-landing-header>.o-row{gap:var(--luz-space-xs) var(--luz-space-md)}.luz-landing-header__brand{display:inline-flex;align-items:center;gap:var(--luz-space-xs);min-block-size:var(--luz-control-lg);margin-inline-end:auto;color:var(--luz-text-primary);font-family:var(--luz-font-sans);font-size:var(--luz-text-base);font-weight:600;text-decoration:none}.luz-landing-header__actions{gap:var(--luz-space-xs)}.luz-landing-header :where(luz-lang-switch){--luz-lang-switch-top: var(--luz-bar-h)}@media(min-width:769px){.luz-landing-header>.o-row{column-gap:var(--luz-space-lg)}}@supports (anchor-name: --luz-bar){.luz-landing-header{anchor-name:--luz-bar;anchor-scope:--luz-bar}.luz-landing-header :where(luz-lang-switch){--luz-lang-switch-top: anchor(--luz-bar end, var(--luz-bar-h))}}.luz-landing-hero{--luz-grid-min: 24rem;gap:var(--luz-space-xl) var(--luz-space-2xl);align-items:center;padding-block-start:var(--luz-space-3xl)}.luz-landing-hero__copy,.luz-landing-hero__media{min-inline-size:0}.luz-landing-hero__copy{overflow-wrap:anywhere}.luz-landing-hero__eyebrow,.luz-landing-section__eyebrow{margin-block-end:var(--luz-space-sm);font-family:var(--luz-font-mono);font-size:var(--luz-text-xs);font-weight:500;line-height:1.5;letter-spacing:.1em;text-transform:uppercase;color:var(--luz-text-secondary)}.luz-landing-hero__title,.luz-landing-section__title{font-family:var(--luz-font-heading);font-size:var(--luz-text-4xl);font-weight:300;line-height:1.1;letter-spacing:-.02em;text-wrap:balance;color:var(--luz-text-primary)}.luz-landing-hero__eyebrow:lang(ar),.luz-landing-section__eyebrow:lang(ar){font-family:var(--luz-font-sans)}.luz-landing-hero__eyebrow:lang(ar),.luz-landing-section__eyebrow:lang(ar),.luz-landing-hero__title:lang(ar),.luz-landing-section__title:lang(ar){letter-spacing:0}.luz-landing-hero__lead,.luz-landing-section__lead{max-inline-size:70ch;font-size:var(--luz-text-xl);line-height:1.4;text-wrap:pretty;color:var(--luz-text-secondary)}.luz-landing-hero__lead{margin-block:var(--luz-space-md) var(--luz-space-lg)}.luz-landing-hero--centered{--luz-grid-min: 100%;text-align:center}.luz-landing-hero--centered>.luz-landing-hero__copy{justify-self:center;max-inline-size:min(100%,48rem)}.luz-landing-hero--centered .luz-landing-hero__lead{margin-inline:auto}.luz-landing-hero--centered .o-actions{justify-content:center}:root:has(.luz-landing-hero__media--bleed){container-type:inline-size}.luz-landing-hero__media--bleed{margin-inline:calc(50% - 50cqi)}@media(max-width:1023px){.luz-landing-hero{padding-block-start:calc(var(--luz-space-xl) + var(--luz-space-xs))}.luz-landing-hero__title{font-size:var(--luz-text-3xl)}.luz-landing-hero__lead{font-size:var(--luz-text-lg)}}@media(max-width:640px){.luz-landing-hero__title{font-size:var(--luz-text-2xl)}.luz-landing-hero__lead{font-size:var(--luz-text-base)}}.luz-landing-main{gap:var(--luz-space-3xl);padding-block:0 var(--luz-space-3xl)}.luz-landing-section{gap:var(--luz-space-md)}.luz-landing-section__eyebrow{margin-block-end:0}.luz-landing-section__title,.luz-landing-stat__value{font-size:var(--luz-text-3xl)}.luz-landing-hero__title em,.luz-landing-section__title em{color:var(--luz-landing-title-accent, inherit)}.luz-landing-section__lead{font-size:var(--luz-text-lg)}@media(max-width:1023px){.luz-landing-section__title,.luz-landing-stat__value{font-size:var(--luz-text-2xl)}}@media(max-width:640px){.luz-landing-section__title,.luz-landing-stat__value{font-size:var(--luz-text-xl)}.luz-landing-section__lead{font-size:var(--luz-text-base)}}.luz-landing-stat{display:grid;gap:var(--luz-space-2xs);min-inline-size:0}.luz-landing-stat>*{margin:0}.luz-landing-stat__value{font-family:var(--luz-font-heading);font-weight:300;font-variant-numeric:tabular-nums;line-height:1.1;overflow-wrap:anywhere;color:var(--luz-text-primary)}.luz-landing-stat__label{font-size:var(--luz-text-base);color:var(--luz-text-secondary)}.luz-landing-footer{padding-block:var(--luz-space-lg) var(--luz-space-xl);border-block-start:1px solid var(--luz-divider)}.luz-landing-footer>.o-row{justify-content:space-between;gap:var(--luz-space-md)}@media(max-width:768px){.luz-landing-footer>.o-row{flex-flow:column nowrap;align-items:flex-start}}.luz-landing-footer>.o-grid{gap:var(--luz-space-lg)}@media(min-width:768px){.luz-landing-footer>.o-grid{grid-template-columns:minmax(0,2fr);grid-auto-columns:minmax(0,1fr);grid-auto-flow:column}}.luz-landing-footer__brand,.luz-landing-footer__col,.luz-landing-footer__col :where(ul){display:grid;align-content:start;gap:var(--luz-space-xs);margin:0}.luz-landing-footer__brand>*,.luz-landing-footer__col-title{margin:0}.luz-landing-footer__col-title{font-family:var(--luz-font-sans);font-size:var(--luz-text-sm);font-weight:600;letter-spacing:0;color:var(--luz-text-primary)}.luz-landing-footer__col :where(ul){padding:0;list-style:none}.luz-landing-footer__col :where(a){display:inline-flex;align-items:center;min-block-size:var(--luz-control-sm)}.luz-landing-glow{--luz-landing-glow-start: 100%;background:radial-gradient(circle at var(--luz-landing-glow-x, var(--luz-landing-glow-start)) 0%,var(--luz-brand-subtle),transparent 28%),linear-gradient(180deg,var(--luz-surface-page) 0%,var(--luz-surface-muted) 100%)}.luz-landing-glow:dir(ltr){--luz-landing-glow-start: 0%}@media print{.luz-landing-glow{background:none}}}
package/package.json CHANGED
@@ -1,6 +1,55 @@
1
1
  {
2
2
  "name": "@luz-ds/landing",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.2.0",
4
+ "description": "LUZ Design System — the page structure of a landing page (header, hero, main, sections, footer, glow), built on @luz-ds/core",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "exports": {
8
+ "./css": "./dist/landing.css",
9
+ "./css/min": "./dist/landing.min.css",
10
+ "./components/*": "./dist/components/*",
11
+ "./classes.json": "./dist/classes.json",
12
+ "./package.json": "./package.json"
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "LICENSE"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/nandovejer/luz.git",
21
+ "directory": "packages/landing"
22
+ },
23
+ "homepage": "https://github.com/nandovejer/luz#readme",
24
+ "bugs": {
25
+ "url": "https://github.com/nandovejer/luz/issues"
26
+ },
27
+ "keywords": [
28
+ "design-system",
29
+ "css",
30
+ "landing-page",
31
+ "patterns",
32
+ "vanilla"
33
+ ],
34
+ "sideEffects": [
35
+ "*.css"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "peerDependencies": {
41
+ "@luz-ds/core": "^0.1.0 || ^0.2.0",
42
+ "@luz-ds/catalog": "^0.1.0 || ^0.2.0",
43
+ "@luz-ds/tokens": "^0.1.0 || ^0.2.0"
44
+ },
45
+ "peerDependenciesMeta": {
46
+ "@luz-ds/catalog": {
47
+ "optional": true
48
+ }
49
+ },
50
+ "devDependencies": {
51
+ "@luz-ds/catalog": "0.2.0",
52
+ "@luz-ds/core": "0.2.0",
53
+ "@luz-ds/tokens": "0.2.0"
54
+ }
6
55
  }