@bitbaum/sitekit 0.3.1 → 0.4.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/README.md +21 -0
- package/dist/react/MenuDismiss.d.ts +4 -0
- package/dist/react/MenuDismiss.js +40 -0
- package/dist/react/SiteChrome.d.ts +19 -13
- package/dist/react/SiteChrome.js +7 -3
- package/dist/react/SiteNav.d.ts +4 -1
- package/dist/react/SiteNav.js +10 -3
- package/dist/react/sections/HeroSection.d.ts +2 -1
- package/dist/react/sections/HeroSection.js +3 -1
- package/package.json +9 -5
- package/styles.css +289 -0
package/README.md
CHANGED
|
@@ -58,6 +58,27 @@ import { SiteMasthead, SiteFooter, SiteSections } from '@bitbaum/sitekit/react';
|
|
|
58
58
|
correct everywhere; a Next.js app passes `next/link` to get client-side
|
|
59
59
|
navigation back.
|
|
60
60
|
|
|
61
|
+
## Application shell
|
|
62
|
+
|
|
63
|
+
Product apps use the same masthead, navigation and footer with the application
|
|
64
|
+
appearance. Import `@bitbaum/sitekit/styles.css` once. No Tailwind setup is
|
|
65
|
+
required for this appearance; `--sk-*` variables map it to the product's shared
|
|
66
|
+
design tokens. Existing document sites keep their utility-class appearance.
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
<SiteMasthead appearance="application" chrome={chrome} navItems={nav}
|
|
70
|
+
currentPath="analyze" mainId="main-content" Link={Link}
|
|
71
|
+
brand={<Brand />} actions={<AccountControl />} />
|
|
72
|
+
<SiteFooter appearance="application" chrome={chrome} Link={Link}
|
|
73
|
+
links={[{ href: '/roadmap', label: 'Roadmap' }]} extra="Beta" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The package owns the responsive menu, its native no-JavaScript fallback,
|
|
77
|
+
Escape/outside/route dismissal, skip link, active states, touch targets and
|
|
78
|
+
footer layout. Products supply labels, navigation data, identity and account
|
|
79
|
+
slots. Fix shared chrome here, then release and update consumers through their
|
|
80
|
+
normal dependency checks and deployment pipeline.
|
|
81
|
+
|
|
61
82
|
## Provenance — the check behind "fabricate no facts"
|
|
62
83
|
|
|
63
84
|
A closed schema invites completion: a model handed a `stats` section wants
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
/** Native details works without JS; this adds Escape, outside click and route dismissal. */
|
|
4
|
+
export function MenuDismiss({ currentPath }) {
|
|
5
|
+
useEffect(() => {
|
|
6
|
+
const menus = Array.from(document.querySelectorAll('[data-sitekit-menu]'));
|
|
7
|
+
for (const menu of menus)
|
|
8
|
+
menu.open = false;
|
|
9
|
+
const key = (event) => {
|
|
10
|
+
if (event.key !== 'Escape')
|
|
11
|
+
return;
|
|
12
|
+
for (const menu of menus)
|
|
13
|
+
if (menu.open) {
|
|
14
|
+
menu.open = false;
|
|
15
|
+
menu.querySelector('summary')?.focus();
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
const outside = (event) => {
|
|
19
|
+
for (const menu of menus)
|
|
20
|
+
if (!menu.contains(event.target))
|
|
21
|
+
menu.open = false;
|
|
22
|
+
};
|
|
23
|
+
const follow = (event) => {
|
|
24
|
+
if (event.target.closest('a'))
|
|
25
|
+
for (const menu of menus)
|
|
26
|
+
menu.open = false;
|
|
27
|
+
};
|
|
28
|
+
document.addEventListener('keydown', key);
|
|
29
|
+
document.addEventListener('pointerdown', outside);
|
|
30
|
+
for (const menu of menus)
|
|
31
|
+
menu.addEventListener('click', follow);
|
|
32
|
+
return () => {
|
|
33
|
+
document.removeEventListener('keydown', key);
|
|
34
|
+
document.removeEventListener('pointerdown', outside);
|
|
35
|
+
for (const menu of menus)
|
|
36
|
+
menu.removeEventListener('click', follow);
|
|
37
|
+
};
|
|
38
|
+
}, [currentPath]);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Masthead and footer.
|
|
3
|
-
*
|
|
4
|
-
* The masthead is a rule, not a bar. It sticks so the nav stays reachable on
|
|
5
|
-
* a long page, but it carries no shadow, no fill beyond the page's own
|
|
6
|
-
* surface, and no logo — a hairline and a wordmark are enough, and anything
|
|
7
|
-
* heavier competes with the document.
|
|
8
|
-
*
|
|
9
|
-
* There is deliberately no "hosted on" or "built by" line. A generated site
|
|
10
|
-
* belongs to its subject; its footer has no business advertising anyone else.
|
|
11
|
-
*/
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
12
2
|
import type { SiteChrome as SiteChromeSpec, SiteNavItem } from '../schema.js';
|
|
13
3
|
import { type LinkLike } from './link.js';
|
|
14
4
|
interface Props {
|
|
@@ -17,9 +7,25 @@ interface Props {
|
|
|
17
7
|
currentPath: string;
|
|
18
8
|
/** Framework link component (e.g. next/link). Defaults to a plain anchor. */
|
|
19
9
|
Link?: LinkLike;
|
|
10
|
+
/** Opt-in application shell, styled by @bitbaum/sitekit/styles.css. */
|
|
11
|
+
appearance?: 'application';
|
|
12
|
+
brand?: ReactNode;
|
|
13
|
+
actions?: ReactNode;
|
|
14
|
+
mainId?: string;
|
|
15
|
+
skipLabel?: string;
|
|
16
|
+
navLabel?: string;
|
|
17
|
+
menuLabel?: string;
|
|
20
18
|
}
|
|
21
|
-
export declare function SiteMasthead({ chrome, navItems, currentPath, Link }: Props): import("react").JSX.Element;
|
|
22
|
-
export declare function SiteFooter({ chrome }: {
|
|
19
|
+
export declare function SiteMasthead({ chrome, navItems, currentPath, Link, appearance, brand, actions, mainId, skipLabel, navLabel, menuLabel, }: Props): import("react").JSX.Element;
|
|
20
|
+
export declare function SiteFooter({ chrome, appearance, links, Link, label, extra, }: {
|
|
23
21
|
chrome: SiteChromeSpec;
|
|
22
|
+
appearance?: 'application';
|
|
23
|
+
links?: Array<{
|
|
24
|
+
href: string;
|
|
25
|
+
label: string;
|
|
26
|
+
}>;
|
|
27
|
+
Link?: LinkLike;
|
|
28
|
+
label?: string;
|
|
29
|
+
extra?: ReactNode;
|
|
24
30
|
}): import("react").JSX.Element;
|
|
25
31
|
export {};
|
package/dist/react/SiteChrome.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Masthead and footer.
|
|
4
4
|
*
|
|
@@ -13,9 +13,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
13
13
|
import { href } from '../model.js';
|
|
14
14
|
import { DefaultLink } from './link.js';
|
|
15
15
|
import { SiteNav } from './SiteNav.js';
|
|
16
|
-
export function SiteMasthead({ chrome, navItems, currentPath, Link = DefaultLink }) {
|
|
16
|
+
export function SiteMasthead({ chrome, navItems, currentPath, Link = DefaultLink, appearance, brand, actions, mainId, skipLabel = 'Skip to content', navLabel = 'Main navigation', menuLabel = 'Menu', }) {
|
|
17
|
+
if (appearance === 'application')
|
|
18
|
+
return (_jsxs(_Fragment, { children: [mainId && (_jsx("a", { className: "sk-skip", href: '#' + mainId, children: skipLabel })), _jsx("header", { className: "sk-masthead", children: _jsxs("div", { className: "sk-shell-frame sk-masthead-row", children: [_jsx(Link, { href: "/", className: "sk-brand", children: brand ?? chrome.name }), _jsx(SiteNav, { items: navItems, currentPath: currentPath, Link: Link, appearance: "application", label: navLabel, menuLabel: menuLabel }), actions && _jsx("div", { className: "sk-account-actions", children: actions })] }) })] }));
|
|
17
19
|
return (_jsx("header", { className: "sticky top-0 z-30 border-b border-subtle bg-surface-page/85 backdrop-blur", children: _jsx("div", { className: "mx-auto max-w-shell px-4 sm:px-6 lg:px-8", children: _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-6 py-2", children: [_jsx(Link, { href: href(), className: "inline-flex min-h-11 shrink-0 items-center rounded focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent", children: _jsx("span", { className: "font-heading text-lg font-semibold tracking-display text-fg-primary", children: chrome.name }) }), _jsx(SiteNav, { items: navItems, currentPath: currentPath, Link: Link })] }) }) }));
|
|
18
20
|
}
|
|
19
|
-
export function SiteFooter({ chrome }) {
|
|
21
|
+
export function SiteFooter({ chrome, appearance, links = [], Link = DefaultLink, label = 'Footer navigation', extra, }) {
|
|
22
|
+
if (appearance === 'application')
|
|
23
|
+
return (_jsx("footer", { className: "sk-footer", children: _jsxs("div", { className: "sk-shell-frame", children: [_jsxs("div", { className: "sk-footer-top", children: [_jsxs("div", { children: [_jsx(Link, { href: "/", className: "sk-footer-brand", children: chrome.name }), _jsx("p", { children: chrome.footerNote })] }), _jsx("nav", { "aria-label": label, children: links.map((item) => (_jsx(Link, { href: item.href, children: item.label }, item.href))) })] }), _jsxs("div", { className: "sk-footer-bottom", children: [_jsxs("span", { children: ["\u00A9 ", new Date().getFullYear(), " ", chrome.name] }), extra] })] }) }));
|
|
20
24
|
return (_jsx("footer", { className: "mt-24 border-t border-subtle", children: _jsxs("div", { className: "mx-auto max-w-shell px-4 py-12 sm:px-6 lg:px-8", children: [_jsx("p", { className: "max-w-prose text-sm leading-relaxed text-fg-secondary", children: chrome.footerNote }), _jsxs("div", { className: "mt-8 flex flex-col gap-2 border-t border-subtle pt-6 font-mono text-xs uppercase tracking-caps text-fg-muted sm:flex-row sm:items-center sm:justify-between", children: [chrome.host && _jsx("span", { children: chrome.host }), _jsxs("span", { className: "normal-case tracking-normal", children: ["\u00A9 ", chrome.name] })] })] }) }));
|
|
21
25
|
}
|
package/dist/react/SiteNav.d.ts
CHANGED
|
@@ -17,6 +17,9 @@ interface Props {
|
|
|
17
17
|
currentPath: string;
|
|
18
18
|
/** Framework link component (e.g. next/link). Defaults to a plain anchor. */
|
|
19
19
|
Link?: LinkLike;
|
|
20
|
+
appearance?: 'application';
|
|
21
|
+
label?: string;
|
|
22
|
+
menuLabel?: string;
|
|
20
23
|
}
|
|
21
|
-
export declare function SiteNav({ items, currentPath, Link }: Props): import("react").JSX.Element;
|
|
24
|
+
export declare function SiteNav({ items, currentPath, Link, appearance, label, menuLabel, }: Props): import("react").JSX.Element | null;
|
|
22
25
|
export {};
|
package/dist/react/SiteNav.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* The masthead's section nav.
|
|
4
4
|
*
|
|
@@ -9,9 +9,16 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
*/
|
|
10
10
|
import { href } from '../model.js';
|
|
11
11
|
import { DefaultLink } from './link.js';
|
|
12
|
+
import { MenuDismiss } from './MenuDismiss.js';
|
|
12
13
|
import { NavAutoScroll } from './NavAutoScroll.js';
|
|
13
|
-
export function SiteNav({ items, currentPath, Link = DefaultLink }) {
|
|
14
|
-
|
|
14
|
+
export function SiteNav({ items, currentPath, Link = DefaultLink, appearance, label = 'Sections', menuLabel = 'Menu', }) {
|
|
15
|
+
if (appearance === 'application') {
|
|
16
|
+
if (items.length === 0)
|
|
17
|
+
return null;
|
|
18
|
+
const links = () => items.map((item) => (_jsx(Link, { href: href(item.path), "aria-current": item.path === currentPath ? 'page' : undefined, children: item.label }, item.path)));
|
|
19
|
+
return (_jsxs(_Fragment, { children: [_jsx("nav", { className: "sk-desktop-nav", "aria-label": label, "data-sitekit-nav": true, children: links() }), _jsxs("details", { className: "sk-mobile-menu", "data-sitekit-menu": true, children: [_jsxs("summary", { children: [menuLabel, _jsx("span", { "aria-hidden": "true", children: "\u2630" })] }), _jsx("nav", { "aria-label": label, children: links() })] }), _jsx(MenuDismiss, { currentPath: currentPath })] }));
|
|
20
|
+
}
|
|
21
|
+
return (_jsxs("nav", { "data-sitekit-nav": true, "aria-label": label, className: "scrollbar-hide -mx-1 flex max-w-full flex-nowrap items-center gap-x-1 overflow-x-auto", children: [items.map((item) => {
|
|
15
22
|
const isCurrent = item.path === currentPath;
|
|
16
23
|
return (_jsx(Link, { href: href(item.path), "aria-current": isCurrent ? 'page' : undefined, className: [
|
|
17
24
|
// min-h-11 is the 44px touch floor. `px-2 py-1` on text-xs came out
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* illustration, no button. The claim is the design.
|
|
7
7
|
*/
|
|
8
8
|
import type { SiteSection } from '../../schema.js';
|
|
9
|
-
export declare function HeroSection({ section }: {
|
|
9
|
+
export declare function HeroSection({ section, appearance, }: {
|
|
10
10
|
section: Extract<SiteSection, {
|
|
11
11
|
kind: 'hero';
|
|
12
12
|
}>;
|
|
13
|
+
appearance?: 'application';
|
|
13
14
|
}): import("react").JSX.Element;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
export function HeroSection({ section }) {
|
|
2
|
+
export function HeroSection({ section, appearance, }) {
|
|
3
|
+
if (appearance === 'application')
|
|
4
|
+
return (_jsxs("section", { className: "sk-hero", children: [section.eyebrow && _jsx("p", { className: "sk-eyebrow", children: section.eyebrow }), _jsx("h1", { className: "sk-hero-title", children: section.statement }), _jsx("div", { className: "sk-hero-lead", children: section.lead.map((text, i) => (_jsx("p", { children: text }, i))) }), !!section.actions?.length && (_jsx("div", { className: "sk-hero-actions", children: section.actions.map((action, i) => (_jsx("a", { className: i === 0 ? 'sk-primary-action' : 'sk-secondary-action', href: action.href, children: action.label }, action.href))) }))] }));
|
|
3
5
|
return (_jsxs("section", { className: "border-b border-subtle pb-12", children: [section.eyebrow && (_jsx("p", { className: "font-mono text-xs uppercase tracking-caps text-fg-tertiary", children: section.eyebrow })), _jsx("h1", { className: "mt-5 max-w-4xl font-heading text-4xl font-semibold leading-tight tracking-display text-fg-primary sm:text-5xl lg:text-6xl", children: section.statement }), _jsx("div", { className: "mt-7 max-w-prose space-y-4", children: section.lead.map((paragraph, index) => (_jsx("p", { className: "text-lg leading-relaxed text-fg-secondary", children: paragraph }, index))) }), section.actions && section.actions.length > 0 && (_jsx("div", { className: "mt-8 flex flex-wrap gap-3", children: section.actions.map((action, i) => (_jsx("a", { href: action.href, className: [
|
|
4
6
|
'inline-flex min-h-11 items-center justify-center rounded-full px-6 py-2.5 text-sm font-medium transition-colors',
|
|
5
7
|
i === 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bitbaum/sitekit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A website as data: a closed union of section shapes validated at runtime, one set of React renderers, and per-field provenance so a generated site can prove it fabricated nothing.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Mao Nakamoto",
|
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
"headless-cms"
|
|
22
22
|
],
|
|
23
23
|
"type": "module",
|
|
24
|
-
"sideEffects":
|
|
24
|
+
"sideEffects": [
|
|
25
|
+
"*.css"
|
|
26
|
+
],
|
|
25
27
|
"packageManager": "pnpm@11.25.0",
|
|
26
28
|
"engines": {
|
|
27
29
|
"node": ">=20"
|
|
@@ -31,7 +33,8 @@
|
|
|
31
33
|
"provenance": true
|
|
32
34
|
},
|
|
33
35
|
"files": [
|
|
34
|
-
"dist"
|
|
36
|
+
"dist",
|
|
37
|
+
"styles.css"
|
|
35
38
|
],
|
|
36
39
|
"exports": {
|
|
37
40
|
".": {
|
|
@@ -41,7 +44,8 @@
|
|
|
41
44
|
"./react": {
|
|
42
45
|
"types": "./dist/react/index.d.ts",
|
|
43
46
|
"default": "./dist/react/index.js"
|
|
44
|
-
}
|
|
47
|
+
},
|
|
48
|
+
"./styles.css": "./styles.css"
|
|
45
49
|
},
|
|
46
50
|
"scripts": {
|
|
47
51
|
"build": "tsc -p tsconfig.json",
|
|
@@ -65,7 +69,7 @@
|
|
|
65
69
|
"@types/react": "^19.0.0",
|
|
66
70
|
"eslint": "^10.9.1",
|
|
67
71
|
"globals": "^17.12.0",
|
|
68
|
-
"prettier": "3.9.
|
|
72
|
+
"prettier": "3.9.10",
|
|
69
73
|
"react": "^19.0.0",
|
|
70
74
|
"react-dom": "^19.0.0",
|
|
71
75
|
"typescript": "^6.0.3",
|
package/styles.css
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/* Shared application chrome. Consumers supply semantic theme variables only. */
|
|
2
|
+
:where(.sk-masthead, .sk-footer) {
|
|
3
|
+
color: var(--sk-fg, CanvasText);
|
|
4
|
+
font-family: var(--sk-font, system-ui, sans-serif);
|
|
5
|
+
}
|
|
6
|
+
.sk-shell-frame {
|
|
7
|
+
width: min(100%, var(--sk-width, 76rem));
|
|
8
|
+
margin-inline: auto;
|
|
9
|
+
padding-inline: var(--sk-gutter, clamp(1rem, 4vw, 2.5rem));
|
|
10
|
+
}
|
|
11
|
+
.sk-masthead {
|
|
12
|
+
position: sticky;
|
|
13
|
+
top: 0;
|
|
14
|
+
z-index: 40;
|
|
15
|
+
background: var(--sk-bg, Canvas);
|
|
16
|
+
border-bottom: 1px solid var(--sk-line, #d6d6d6);
|
|
17
|
+
}
|
|
18
|
+
.sk-masthead-row {
|
|
19
|
+
min-height: 80px;
|
|
20
|
+
display: flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
gap: clamp(1rem, 2vw, 2rem);
|
|
23
|
+
}
|
|
24
|
+
.sk-brand,
|
|
25
|
+
.sk-footer-brand {
|
|
26
|
+
color: inherit;
|
|
27
|
+
text-decoration: none;
|
|
28
|
+
font-weight: 650;
|
|
29
|
+
letter-spacing: -0.035em;
|
|
30
|
+
display: inline-flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: 0.75rem;
|
|
33
|
+
min-height: 44px;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
font-size: 1.2rem;
|
|
36
|
+
}
|
|
37
|
+
.sk-brand img,
|
|
38
|
+
.sk-brand svg {
|
|
39
|
+
width: 38px;
|
|
40
|
+
height: 38px;
|
|
41
|
+
object-fit: contain;
|
|
42
|
+
flex-shrink: 0;
|
|
43
|
+
}
|
|
44
|
+
.sk-desktop-nav {
|
|
45
|
+
margin-inline: auto;
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 0.15rem;
|
|
49
|
+
min-width: 0;
|
|
50
|
+
}
|
|
51
|
+
.sk-desktop-nav a,
|
|
52
|
+
.sk-mobile-menu nav a,
|
|
53
|
+
.sk-footer nav a {
|
|
54
|
+
min-height: 44px;
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
color: var(--sk-muted, #555);
|
|
58
|
+
text-decoration: none;
|
|
59
|
+
font-size: 0.875rem;
|
|
60
|
+
line-height: 1.4;
|
|
61
|
+
}
|
|
62
|
+
.sk-desktop-nav a {
|
|
63
|
+
padding: 0.75rem 0.7rem;
|
|
64
|
+
border-radius: var(--sk-radius, 0.5rem);
|
|
65
|
+
white-space: nowrap;
|
|
66
|
+
}
|
|
67
|
+
.sk-desktop-nav a[aria-current='page'],
|
|
68
|
+
.sk-mobile-menu a[aria-current='page'] {
|
|
69
|
+
background: var(--sk-raised, #f0f0f0);
|
|
70
|
+
color: var(--sk-fg, CanvasText);
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
}
|
|
73
|
+
.sk-account-actions {
|
|
74
|
+
margin-inline-start: auto;
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: 0.5rem;
|
|
78
|
+
flex-shrink: 0;
|
|
79
|
+
}
|
|
80
|
+
.sk-account-actions > a {
|
|
81
|
+
display: inline-flex;
|
|
82
|
+
align-items: center;
|
|
83
|
+
justify-content: center;
|
|
84
|
+
min-height: 44px;
|
|
85
|
+
padding: 0.65rem 1rem;
|
|
86
|
+
color: var(--sk-fg, CanvasText);
|
|
87
|
+
border: 1px solid var(--sk-line, #d6d6d6);
|
|
88
|
+
border-radius: var(--sk-radius, 0.5rem);
|
|
89
|
+
text-decoration: none;
|
|
90
|
+
font-weight: 600;
|
|
91
|
+
font-size: 0.875rem;
|
|
92
|
+
}
|
|
93
|
+
.sk-mobile-menu {
|
|
94
|
+
display: none;
|
|
95
|
+
}
|
|
96
|
+
.sk-mobile-menu summary {
|
|
97
|
+
cursor: pointer;
|
|
98
|
+
list-style: none;
|
|
99
|
+
min-height: 44px;
|
|
100
|
+
align-items: center;
|
|
101
|
+
justify-content: center;
|
|
102
|
+
gap: 0.5rem;
|
|
103
|
+
padding: 0.65rem 0.85rem;
|
|
104
|
+
border: 1px solid var(--sk-line, #d6d6d6);
|
|
105
|
+
border-radius: var(--sk-radius, 0.5rem);
|
|
106
|
+
font-size: 0.875rem;
|
|
107
|
+
font-weight: 600;
|
|
108
|
+
}
|
|
109
|
+
.sk-mobile-menu summary::-webkit-details-marker {
|
|
110
|
+
display: none;
|
|
111
|
+
}
|
|
112
|
+
.sk-mobile-menu nav {
|
|
113
|
+
position: absolute;
|
|
114
|
+
top: 100%;
|
|
115
|
+
left: var(--sk-gutter, 1rem);
|
|
116
|
+
right: var(--sk-gutter, 1rem);
|
|
117
|
+
background: var(--sk-bg, Canvas);
|
|
118
|
+
border: 1px solid var(--sk-line, #d6d6d6);
|
|
119
|
+
border-radius: var(--sk-radius, 0.5rem);
|
|
120
|
+
box-shadow: 0 18px 40px #00000014;
|
|
121
|
+
padding: 0.5rem;
|
|
122
|
+
max-height: 75dvh;
|
|
123
|
+
overflow-y: auto;
|
|
124
|
+
}
|
|
125
|
+
.sk-mobile-menu nav a {
|
|
126
|
+
padding: 0.8rem 1rem;
|
|
127
|
+
border-radius: var(--sk-radius, 0.5rem);
|
|
128
|
+
}
|
|
129
|
+
.sk-footer {
|
|
130
|
+
margin-top: clamp(4rem, 8vw, 7rem);
|
|
131
|
+
border-top: 1px solid var(--sk-line, #d6d6d6);
|
|
132
|
+
background: var(--sk-bg, Canvas);
|
|
133
|
+
padding-block: 3rem 1.5rem;
|
|
134
|
+
}
|
|
135
|
+
.sk-footer-top {
|
|
136
|
+
display: grid;
|
|
137
|
+
grid-template-columns: 1fr 1fr;
|
|
138
|
+
gap: 3rem;
|
|
139
|
+
}
|
|
140
|
+
.sk-footer-top p {
|
|
141
|
+
margin-top: 0.5rem;
|
|
142
|
+
color: var(--sk-muted, #555);
|
|
143
|
+
max-width: 36ch;
|
|
144
|
+
line-height: 1.7;
|
|
145
|
+
font-size: 0.875rem;
|
|
146
|
+
}
|
|
147
|
+
.sk-footer nav {
|
|
148
|
+
display: grid;
|
|
149
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
150
|
+
column-gap: 1.5rem;
|
|
151
|
+
}
|
|
152
|
+
.sk-footer nav a {
|
|
153
|
+
padding-block: 0.5rem;
|
|
154
|
+
}
|
|
155
|
+
.sk-footer-bottom {
|
|
156
|
+
display: flex;
|
|
157
|
+
align-items: center;
|
|
158
|
+
justify-content: space-between;
|
|
159
|
+
flex-wrap: wrap;
|
|
160
|
+
gap: 1rem;
|
|
161
|
+
margin-top: 2rem;
|
|
162
|
+
padding-top: 1.5rem;
|
|
163
|
+
border-top: 1px solid var(--sk-line, #d6d6d6);
|
|
164
|
+
color: var(--sk-muted, #555);
|
|
165
|
+
font-size: 0.75rem;
|
|
166
|
+
}
|
|
167
|
+
.sk-footer-bottom a {
|
|
168
|
+
color: inherit;
|
|
169
|
+
min-height: 44px;
|
|
170
|
+
display: inline-flex;
|
|
171
|
+
align-items: center;
|
|
172
|
+
}
|
|
173
|
+
.sk-skip {
|
|
174
|
+
position: fixed;
|
|
175
|
+
left: 1rem;
|
|
176
|
+
top: -10rem;
|
|
177
|
+
z-index: 100;
|
|
178
|
+
padding: 1rem;
|
|
179
|
+
color: var(--sk-bg, Canvas);
|
|
180
|
+
background: var(--sk-fg, CanvasText);
|
|
181
|
+
border-radius: 0.5rem;
|
|
182
|
+
}
|
|
183
|
+
.sk-skip:focus {
|
|
184
|
+
top: 1rem;
|
|
185
|
+
}
|
|
186
|
+
:where(.sk-masthead, .sk-footer) :is(a, summary, button):focus-visible,
|
|
187
|
+
.sk-skip:focus-visible {
|
|
188
|
+
outline: 3px solid var(--sk-focus, Highlight);
|
|
189
|
+
outline-offset: 3px;
|
|
190
|
+
}
|
|
191
|
+
@media (max-width: 1100px) {
|
|
192
|
+
.sk-desktop-nav {
|
|
193
|
+
display: none;
|
|
194
|
+
}
|
|
195
|
+
.sk-mobile-menu {
|
|
196
|
+
display: block;
|
|
197
|
+
order: 3;
|
|
198
|
+
}
|
|
199
|
+
.sk-mobile-menu summary {
|
|
200
|
+
display: flex;
|
|
201
|
+
}
|
|
202
|
+
.sk-masthead-row {
|
|
203
|
+
gap: 0.5rem;
|
|
204
|
+
min-height: 72px;
|
|
205
|
+
}
|
|
206
|
+
.sk-brand {
|
|
207
|
+
margin-inline-end: auto;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
@media (max-width: 600px) {
|
|
211
|
+
.sk-footer-top {
|
|
212
|
+
grid-template-columns: 1fr;
|
|
213
|
+
gap: 1.5rem;
|
|
214
|
+
}
|
|
215
|
+
.sk-brand {
|
|
216
|
+
font-size: 1.05rem;
|
|
217
|
+
gap: 0.5rem;
|
|
218
|
+
}
|
|
219
|
+
.sk-account-actions > a {
|
|
220
|
+
padding-inline: 0.75rem;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.sk-hero {
|
|
225
|
+
padding-block: var(--sk-hero-space, clamp(3rem, 7vw, 6rem));
|
|
226
|
+
}
|
|
227
|
+
.sk-eyebrow {
|
|
228
|
+
font: 600 0.7rem/1.5 var(--sk-mono, monospace);
|
|
229
|
+
text-transform: uppercase;
|
|
230
|
+
letter-spacing: 0.13em;
|
|
231
|
+
color: var(--sk-muted, #555);
|
|
232
|
+
}
|
|
233
|
+
.sk-hero-title {
|
|
234
|
+
margin-block: 1rem 1.5rem;
|
|
235
|
+
font-family: var(--sk-display, Georgia, serif);
|
|
236
|
+
font-size: var(--sk-title-size, clamp(3rem, 6vw, 5rem));
|
|
237
|
+
font-weight: 400;
|
|
238
|
+
line-height: 1.05;
|
|
239
|
+
letter-spacing: -0.045em;
|
|
240
|
+
text-wrap: balance;
|
|
241
|
+
max-width: var(--sk-title-measure, 20ch);
|
|
242
|
+
}
|
|
243
|
+
.sk-hero-lead {
|
|
244
|
+
max-width: var(--sk-measure, 64ch);
|
|
245
|
+
display: grid;
|
|
246
|
+
gap: 1rem;
|
|
247
|
+
color: var(--sk-muted, #555);
|
|
248
|
+
font-size: clamp(1.05rem, 2vw, 1.2rem);
|
|
249
|
+
line-height: 1.75;
|
|
250
|
+
}
|
|
251
|
+
.sk-hero-actions {
|
|
252
|
+
display: grid;
|
|
253
|
+
grid-auto-flow: column;
|
|
254
|
+
grid-auto-columns: 1fr;
|
|
255
|
+
gap: 0.75rem;
|
|
256
|
+
margin-top: 2rem;
|
|
257
|
+
max-width: 36rem;
|
|
258
|
+
}
|
|
259
|
+
.sk-hero-actions a {
|
|
260
|
+
display: flex;
|
|
261
|
+
align-items: center;
|
|
262
|
+
justify-content: center;
|
|
263
|
+
min-height: 48px;
|
|
264
|
+
padding: 0.8rem 1rem;
|
|
265
|
+
font-size: 0.9rem;
|
|
266
|
+
font-weight: 600;
|
|
267
|
+
border-radius: var(--sk-radius, 0.5rem);
|
|
268
|
+
text-decoration: none;
|
|
269
|
+
text-align: center;
|
|
270
|
+
}
|
|
271
|
+
.sk-primary-action {
|
|
272
|
+
background: var(--sk-fg, CanvasText);
|
|
273
|
+
color: var(--sk-bg, Canvas);
|
|
274
|
+
border: 1px solid var(--sk-fg, CanvasText);
|
|
275
|
+
}
|
|
276
|
+
.sk-secondary-action {
|
|
277
|
+
background: transparent;
|
|
278
|
+
color: var(--sk-fg, CanvasText);
|
|
279
|
+
border: 1px solid var(--sk-line, #d6d6d6);
|
|
280
|
+
}
|
|
281
|
+
.sk-hero-actions a:focus-visible {
|
|
282
|
+
outline: 3px solid var(--sk-focus, Highlight);
|
|
283
|
+
outline-offset: 3px;
|
|
284
|
+
}
|
|
285
|
+
@media (max-width: 420px) {
|
|
286
|
+
.sk-hero-actions {
|
|
287
|
+
grid-auto-flow: row;
|
|
288
|
+
}
|
|
289
|
+
}
|