@naeil/ui 0.5.0 → 0.7.1
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/CHANGELOG.md +21 -0
- package/DESIGN.md +7 -3
- package/README.md +19 -10
- package/contracts/naeil-chrome.v1.json +12 -0
- package/dist/chunk-HSTDPY56.js +74 -0
- package/dist/chunk-OLKTEYLS.js +413 -0
- package/dist/{chunk-CE73AEJZ.js → chunk-QQQQZEPV.js} +249 -3
- package/dist/{chunk-S64H3OYQ.js → chunk-VWIUN35X.js} +4 -343
- package/dist/components/index.d.ts +2 -2
- package/dist/components/index.js +11 -9
- package/dist/components/naeil-footer.d.ts +16 -0
- package/dist/components/naeil-footer.js +76 -0
- package/dist/components/naeil-header.d.ts +23 -5
- package/dist/components/naeil-header.js +5 -2
- package/dist/components/ui/index.d.ts +2 -2
- package/dist/components/ui/index.js +6 -6
- package/dist/lib/ui-release.d.ts +1 -0
- package/dist/lib/ui-release.js +1 -0
- package/dist/naeil-header-content-BvISJ4RV.d.ts +4 -0
- package/dist/{navigation-CL10a6Eb.d.ts → navigation-CgPeL9tF.d.ts} +1 -1
- package/dist/{site-footer-B67meCDv.d.ts → site-footer-vXDa_6yr.d.ts} +4 -2
- package/dist/source/components/footer.js +1 -2
- package/dist/source/components/naeil-footer.d.ts +16 -0
- package/dist/source/components/naeil-footer.js +95 -0
- package/dist/source/components/naeil-header-account.d.ts +26 -0
- package/dist/source/components/naeil-header-account.js +67 -0
- package/dist/source/components/naeil-header.d.ts +2 -1
- package/dist/source/components/naeil-header.js +6 -1
- package/dist/source/components/ui/site-footer.d.ts +4 -2
- package/dist/source/components/ui/site-footer.js +6 -2
- package/dist/source/lib/ui-release.d.ts +6 -0
- package/dist/source/lib/ui-release.js +4 -0
- package/docs/components/README.md +4 -1
- package/docs/components/application-layout.md +103 -0
- package/docs/components/common.md +2 -2
- package/docs/components/naeil-footer.md +20 -0
- package/docs/components/naeil-header.md +22 -6
- package/docs/components/navigation.md +8 -8
- package/docs/design/v2-migration.md +24 -25
- package/docs/package-release.md +12 -6
- package/package.json +16 -4
- package/src/styles/components.css +39 -4
- package/src/styles/theme.css +3 -1
- package/dist/chunk-QSTGUEEI.js +0 -323
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.1 — account presentation correction (candidate)
|
|
4
|
+
|
|
5
|
+
- Add canonical `NaeilHeaderAccountControl` for shared login/account/logout presentation while consumers retain session and save guards.
|
|
6
|
+
- Preserve existing header slots and imports; include authenticated menu/busy/failure coverage and packed consumer checks.
|
|
7
|
+
|
|
8
|
+
## 0.7.0 — unpublished candidate
|
|
9
|
+
|
|
10
|
+
- Add React-only canonical NaeilFooter with full/compact content, localized destinations and save-navigation adapters. Preserve generic and Next Footer overrides.
|
|
11
|
+
- Add SiteFooter onNavigate for every group link including native external anchors.
|
|
12
|
+
- Add package/chrome-contract identity, independent rendered release references and fail-closed service rollout verification.
|
|
13
|
+
- Keep approved 1600px/clamp frame and purpose-specific content widths unchanged.
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
## 0.6.0 — shared application frame (2026-10-16)
|
|
17
|
+
|
|
18
|
+
- Align Navigation, SiteFooter, NaeilHeader and preserved Nav/Footer adapters with a shared 1600px maximum outer frame and responsive `clamp(16px, 2.4vw, 40px)` gutters. Add `ui-app-frame` for main composition; retain explicit classes and product token overrides.
|
|
19
|
+
- Increase the list content cap from 1200px to 1600px within available frame space. Reading/settings remain 640px and short forms 480px. Remove prior `max-w-7xl` or `px-6` frame overrides when adopting defaults.
|
|
20
|
+
- Add application-layout and consumer-agent guidance for shared header/footer selection; synchronize the standalone React example with the matching 0.6.0 archive. Existing external consumers require their own upgrades and adapter migrations.
|
|
21
|
+
|
|
22
|
+
Archive identity, registry publication and hosted deployment outcomes are recorded in the [0.6.0 release](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.6.0). External application upgrades remain separate.
|
|
23
|
+
|
|
3
24
|
## 0.5.0 — shared whole header (2026-10-15)
|
|
4
25
|
|
|
5
26
|
- Add optional React-only NaeilHeader with the actual site catalog, controls, toolbar, fixed frame and responsive behavior. Site adapters consume the same module; preserve existing public compatibility imports/props.
|
package/DESIGN.md
CHANGED
|
@@ -110,13 +110,17 @@ Demonstrate the system in three neutral compositions: a settings form, a selecta
|
|
|
110
110
|
|
|
111
111
|
### Application frame and content widths
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
On 2026-10-16 the owner approved a shared maximum application frame of **1600px**, with responsive inline gutters **clamp(16px, 2.4vw, 40px)**, after inspecting Naeil Home at mobile and desktop widths. This supersedes the prior 1280px branded-header cap and fixed 24px frame gutters. The frame includes its padding, centers on wider screens and shrinks to available space. Numeric values live in `ui.width-frame` and `ui.frame-gutter`; `.ui-app-frame`, `Navigation`, `SiteFooter`, `NaeilHeader`, and the legacy adapters use those same defaults.
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
The optional `@naeil/ui/naeil-header` composition centralizes the actual naeil.dev brand/catalog/control/toolbar/responsive implementation approved on 2026-10-15. Official decorative image URLs remain references; no new artwork rights or redistribution is implied. See [NaeilHeader](docs/components/naeil-header.md).
|
|
116
|
+
|
|
117
|
+
Application navigation and footer must use the shared compositions: `NaeilHeader` for the canonical naeil catalog, `Navigation` for different product menus, and `SiteFooter` for both compact and full footers. Existing `Nav`/`Footer` preserve Next compatibility while using that core. Services own menu data, route guards, locale/theme providers and authentication through adapters and slots. Extend the shared API when a reusable behavior is missing instead of copying menu, responsive or footer implementations. See [the application starter and consumer-agent rules](docs/components/application-layout.md).
|
|
116
118
|
|
|
117
119
|
The application frame owns navigation placement and outer gutters; each content region owns its purpose-specific width. A narrow settings form inside that frame must not move the shared navigation. For routes using the same frame, keep navigation anchors stable at the same viewport, language and theme, including loading and changing summary counts. A shared Header import alone does not establish this consistency.
|
|
118
120
|
|
|
119
|
-
|
|
121
|
+
Content caps are reading **640px**, settings **640px**, short form **480px**, and lists **1600px** (updated from 1200px on 2026-10-16). Put lists, tables and dashboards inside `.ui-app-frame` so they use the space remaining after its gutters. Narrow reading/forms stay inside that stable outer frame. `data-ui-layout="fluid"` uses the available region. At a 1920px viewport the default frame has 1520px of usable width.
|
|
122
|
+
|
|
123
|
+
Products may deliberately override `--ui-width-frame`, `--ui-frame-gutter`, content tokens or `containerClassName`; apply frame overrides consistently to header, main and footer. Choose exceptions from representative tasks and sparse/dense content, and record the reason. An override is not permission to fork shared chrome. Keep the old list cap with `--ui-width-list: 1200px` where required. The earlier product-specific 1600px observation alone was not a shared rule; the owner's explicit 2026-10-16 decision establishes this new default.
|
|
120
124
|
|
|
121
125
|
Internal columns must respond to the space available inside their region. Use container queries or an equivalent content-aware layout when viewport breakpoints no longer represent that space. Keep wide tables locally scrollable; check localized labels, wrapping and controls inside cells as well as page overflow. See the [consumer composition guide](docs/design/v2-migration.md#앱-전체-틀과-콘텐츠-폭) for application ownership and checks.
|
|
122
126
|
|
package/README.md
CHANGED
|
@@ -13,20 +13,29 @@ This repository also contains the **naeil.dev brand/example website** and an **o
|
|
|
13
13
|
|
|
14
14
|
## Use the UI
|
|
15
15
|
|
|
16
|
-
This guide targets
|
|
16
|
+
This guide targets **@naeil/ui 0.6.0** with React 19 / ReactDOM 19 and Tailwind 4. UI v2 names the design generation independently of npm versioning. Install the matching release in your app:
|
|
17
17
|
|
|
18
18
|
```sh
|
|
19
|
-
|
|
20
|
-
pnpm build:pkg
|
|
21
|
-
npm pack --ignore-scripts --pack-destination /tmp
|
|
22
|
-
# In the consumer app:
|
|
23
|
-
pnpm add --save-exact /tmp/naeil-ui-0.5.0.tgz
|
|
19
|
+
pnpm add --save-exact @naeil/ui@0.6.0
|
|
24
20
|
```
|
|
25
21
|
|
|
26
|
-
|
|
22
|
+
For a local candidate, run `pnpm build:pkg` and `npm pack --ignore-scripts --pack-destination /tmp`, then install `/tmp/naeil-ui-0.6.0.tgz`. The [release record](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.6.0) tracks archive identity, publication and deployment separately.
|
|
23
|
+
|
|
24
|
+
The preceding [0.5.0 release](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.5.0) was published on 2026-10-15 and introduced the whole shared header. Version 0.6.0 adds the shared 1600px frame; existing applications must upgrade and adopt its layout composition.
|
|
27
25
|
|
|
28
26
|
Version 0.4.0 was published to npm on 2026-10-11 and verified as `latest`; it adds the approved `.naeil` brand 1.1.1. The [release](https://github.com/naeil-dev/naeil-ui/releases/tag/v0.4.0) retains the byte-verified archive and fresh registry-consumer evidence. Download the [independent brand kit](https://github.com/naeil-dev/naeil-ui/releases/download/v0.4.0/naeil-brand-1.1.1.zip) for SVG/PNG/icons and non-React usage. See the [changelog](CHANGELOG.md), [brand usage](docs/design/v2-migration.md#brand-logo-integration-040) and [publication report](https://github.com/naeil-dev/naeil-ui/blob/main/docs/design/naeil-brand-0.4.0-publication.md).
|
|
29
27
|
|
|
28
|
+
Choose the shared application composition before building a header or footer:
|
|
29
|
+
|
|
30
|
+
| Application need | Shared component |
|
|
31
|
+
| --- | --- |
|
|
32
|
+
| Actual naeil.dev brand, project catalog and controls | `NaeilHeader` from `@naeil/ui/naeil-header` |
|
|
33
|
+
| A product's own menu | `Navigation` from `@naeil/ui/ui` |
|
|
34
|
+
| Full or compact application footer | `SiteFooter` from `@naeil/ui/ui` |
|
|
35
|
+
| Existing Next/next-intl integration | Preserve `Nav` / `Footer` compatibility adapters |
|
|
36
|
+
|
|
37
|
+
Supply application routing, guards, authentication and menu data through adapters and slots. Keep the shared menu, mobile sheet, controls and footer implementation in the package. The [application layout guide](docs/components/application-layout.md) provides a starter and consumer-agent instructions. Header, main and footer share a 1600px maximum frame with `clamp(16px, 2.4vw, 40px)` gutters; use `ui-app-frame` on main and keep 640px reading/settings or 480px short-form sections inside it.
|
|
38
|
+
|
|
30
39
|
Import primitives from the dedicated entrypoint:
|
|
31
40
|
|
|
32
41
|
```tsx
|
|
@@ -66,7 +75,7 @@ Shared CSS follows the OS theme unless the document root has `class="light"` or
|
|
|
66
75
|
|
|
67
76
|
The [component guides](docs/components/README.md) cover exact APIs, state composition and accessibility responsibilities. Each has a built Storybook Docs page and runnable Usage example. The [consumer guide](docs/design/v2-migration.md) covers peer dependencies, fonts, themes, density, Select versus DropdownMenu, overrides, and layout ownership. Fonts are delivered by the consumer; shared CSS does not fetch them automatically. `/ui`, `/utils` and the opt-in `/naeil-header` work without Next.js. The legacy root remains a framework compatibility entry with static Next/next-intl imports; install those optional peers when using it, Nav, Footer or i18n/routing. ThemeProvider/controls and Toaster retain the React library next-themes. Shared deep imports use the exact [allowlist and migration](docs/design/v2-migration.md#03-package-boundary); website-only paths belong in your application.
|
|
68
77
|
|
|
69
|
-
For the actual naeil.dev branded header, import `NaeilHeader` from `@naeil/ui/naeil-header`; generic `Navigation` from `/ui` remains available for different product menus. The whole header owns its catalog, header translations, controls and responsive styles. Supply routing/dirty guards, an existing next-themes provider and real authentication through its props instead of copying the header. Its five decorative project images reference official `naeil.dev` URLs and require network access; their bytes are excluded and unresolved redistribution provenance remains in [third-party notices](THIRD_PARTY_NOTICES.md). See [NaeilHeader](docs/components/naeil-header.md) for the
|
|
78
|
+
For the actual naeil.dev branded header, import `NaeilHeader` from `@naeil/ui/naeil-header`; generic `Navigation` from `/ui` remains available for different product menus. The whole header owns its catalog, header translations, controls and responsive styles. Supply routing/dirty guards, an existing next-themes provider and real authentication through its props instead of copying the header. Its five decorative project images reference official `naeil.dev` URLs and require network access; their bytes are excluded and unresolved redistribution provenance remains in [third-party notices](THIRD_PARTY_NOTICES.md). See [NaeilHeader](docs/components/naeil-header.md) for fixed positioning, the shared frame, supported locales and adapter contract.
|
|
70
79
|
|
|
71
80
|
## Develop the shared UI
|
|
72
81
|
|
|
@@ -83,7 +92,7 @@ Storybook uses the actual public components with simulated example data. It need
|
|
|
83
92
|
```sh
|
|
84
93
|
pnpm build:pkg
|
|
85
94
|
pnpm pack --pack-destination /tmp
|
|
86
|
-
# In your consumer app: pnpm add /tmp/naeil-ui-0.
|
|
95
|
+
# In your consumer app: pnpm add /tmp/naeil-ui-0.6.0.tgz
|
|
87
96
|
```
|
|
88
97
|
|
|
89
98
|
| Command | Purpose |
|
|
@@ -95,7 +104,7 @@ pnpm pack --pack-destination /tmp
|
|
|
95
104
|
| `pnpm build:pkg` / `pnpm check:package` | Build package / inspect package boundary and independently install/build packed React + Next consumers |
|
|
96
105
|
| `pnpm dev` / `pnpm build` | Brand/example website development / build |
|
|
97
106
|
|
|
98
|
-
A [reusable React consumer example](https://github.com/naeil-dev/naeil-ui/
|
|
107
|
+
A [reusable React consumer example](https://github.com/naeil-dev/naeil-ui/tree/main/examples/react) composes the packaged header, a settings form and footer. [Release policy](docs/package-release.md) and [changelog](CHANGELOG.md) distinguish implementation, publication and deployment. CI verifies the package and separate website/Storybook. Downloadable preview artifacts include notices and local fonts. The current local Storybook build contains 17 Docs pages (15 UI families and Brand / Logo plus NaeilHeader), including the whole-header Usage and JapaneseApplication examples. This does not establish a new hosted deployment. [Hosted Docs](https://naeil-dev.github.io/naeil-ui/) have recorded [0.5.0 verification](https://github.com/naeil-dev/naeil-ui/releases/download/v0.5.0/hosted-docs-verification.json). The current release record identifies the subsequent deployed source and its fresh checks. The same approved brand is live on [naeil.dev](https://naeil.dev/). See [preview instructions](docs/package-release.md#storybook-preview-artifact) and [support limits](docs/design/public-ui-support.md). Native/manual platform acceptance and adoption in a real consumer product remain open.
|
|
99
108
|
|
|
100
109
|
Read [DESIGN.md](DESIGN.md) before shared UI changes. Numeric values live in `src/tokens/`; generated CSS is not edited by hand. Shared styles live in `src/styles/`, site-only styles in `src/app/`. Hero art, 3D scenes, cursor effects, and site content belong to the example website.
|
|
101
110
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"contractVersion": "1",
|
|
4
|
+
"authority": "DESIGN.md: approved 2026-10-16 frame; shared-ui-rollout spec: canonical footer and guard",
|
|
5
|
+
"frame": { "maxWidth": 1600, "gutterMin": 16, "gutterVw": 2.4, "gutterMax": 40 },
|
|
6
|
+
"header": { "height": 65, "brandHeight": 28, "desktopBrandReservation": 160 },
|
|
7
|
+
"footer": { "variants": ["full", "compact"], "brandHeight": 20, "requiredPaths": ["/", "/projects", "/blog"], "github": "https://github.com/naeil-dev" },
|
|
8
|
+
"viewports": [390, 768, 1440, 1920],
|
|
9
|
+
"themes": ["light", "dark"],
|
|
10
|
+
"locales": ["en", "ko", "ja"],
|
|
11
|
+
"tolerancePx": 1
|
|
12
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
__objRest,
|
|
4
|
+
__spreadProps,
|
|
5
|
+
__spreadValues,
|
|
6
|
+
cn
|
|
7
|
+
} from "./chunk-JBTDRVZ7.js";
|
|
8
|
+
|
|
9
|
+
// src/components/ui/site-footer.tsx
|
|
10
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
11
|
+
function SiteFooter(_a) {
|
|
12
|
+
var _b = _a, {
|
|
13
|
+
brand,
|
|
14
|
+
description,
|
|
15
|
+
groups = [],
|
|
16
|
+
Link,
|
|
17
|
+
meta,
|
|
18
|
+
children,
|
|
19
|
+
containerClassName,
|
|
20
|
+
className,
|
|
21
|
+
variant = "full",
|
|
22
|
+
onNavigate
|
|
23
|
+
} = _b, props = __objRest(_b, [
|
|
24
|
+
"brand",
|
|
25
|
+
"description",
|
|
26
|
+
"groups",
|
|
27
|
+
"Link",
|
|
28
|
+
"meta",
|
|
29
|
+
"children",
|
|
30
|
+
"containerClassName",
|
|
31
|
+
"className",
|
|
32
|
+
"variant",
|
|
33
|
+
"onNavigate"
|
|
34
|
+
]);
|
|
35
|
+
return /* @__PURE__ */ jsx(
|
|
36
|
+
"footer",
|
|
37
|
+
__spreadProps(__spreadValues({
|
|
38
|
+
"data-slot": "site-footer",
|
|
39
|
+
"data-variant": variant,
|
|
40
|
+
className: cn("ui-site-footer", className)
|
|
41
|
+
}, props), {
|
|
42
|
+
children: /* @__PURE__ */ jsxs("div", { className: cn("ui-site-footer-frame", containerClassName), children: [
|
|
43
|
+
/* @__PURE__ */ jsxs("div", { className: "ui-site-footer-brand", children: [
|
|
44
|
+
brand,
|
|
45
|
+
description && /* @__PURE__ */ jsx("div", { className: "ui-site-footer-description", children: description }),
|
|
46
|
+
children
|
|
47
|
+
] }),
|
|
48
|
+
groups.map((group) => /* @__PURE__ */ jsxs("nav", { "aria-label": group.label, children: [
|
|
49
|
+
/* @__PURE__ */ jsx("h2", { className: "ui-site-footer-label", children: group.label }),
|
|
50
|
+
group.links.map((item) => {
|
|
51
|
+
const Component = !item.external && Link ? Link : "a";
|
|
52
|
+
return /* @__PURE__ */ jsx(
|
|
53
|
+
Component,
|
|
54
|
+
{
|
|
55
|
+
href: item.href,
|
|
56
|
+
target: item.target,
|
|
57
|
+
rel: item.target === "_blank" ? "noopener noreferrer" : void 0,
|
|
58
|
+
className: "ui-site-footer-link ui-focus",
|
|
59
|
+
onClick: (event) => onNavigate == null ? void 0 : onNavigate(event, item),
|
|
60
|
+
children: item.label
|
|
61
|
+
},
|
|
62
|
+
item.id
|
|
63
|
+
);
|
|
64
|
+
})
|
|
65
|
+
] }, group.id)),
|
|
66
|
+
meta && /* @__PURE__ */ jsx("div", { className: "ui-site-footer-meta", children: meta })
|
|
67
|
+
] })
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export {
|
|
73
|
+
SiteFooter
|
|
74
|
+
};
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
Logo,
|
|
4
|
+
uiRelease
|
|
5
|
+
} from "./chunk-VWIUN35X.js";
|
|
6
|
+
import {
|
|
7
|
+
Badge,
|
|
8
|
+
Button,
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuItem,
|
|
12
|
+
DropdownMenuLabel,
|
|
13
|
+
DropdownMenuSeparator,
|
|
14
|
+
DropdownMenuTrigger,
|
|
15
|
+
Navigation,
|
|
16
|
+
Select,
|
|
17
|
+
SelectContent,
|
|
18
|
+
SelectItem,
|
|
19
|
+
SelectTrigger,
|
|
20
|
+
SelectValue
|
|
21
|
+
} from "./chunk-QQQQZEPV.js";
|
|
22
|
+
import {
|
|
23
|
+
__objRest,
|
|
24
|
+
__spreadProps,
|
|
25
|
+
__spreadValues,
|
|
26
|
+
cn
|
|
27
|
+
} from "./chunk-JBTDRVZ7.js";
|
|
28
|
+
|
|
29
|
+
// src/components/naeil-header.tsx
|
|
30
|
+
import { useSyncExternalStore } from "react";
|
|
31
|
+
import { useTheme } from "next-themes";
|
|
32
|
+
import { Moon, Sun } from "lucide-react";
|
|
33
|
+
|
|
34
|
+
// src/components/naeil-header-content.ts
|
|
35
|
+
var headerLocales = ["en", "ko", "ja"];
|
|
36
|
+
var headerLocaleNames = { en: "English", ko: "\uD55C\uAD6D\uC5B4", ja: "\u65E5\u672C\u8A9E" };
|
|
37
|
+
var headerProjects = [
|
|
38
|
+
{
|
|
39
|
+
"key": "cc",
|
|
40
|
+
"href": "/cc",
|
|
41
|
+
"accent": "oklch(0.704 0.140 181)",
|
|
42
|
+
"icon": "coral",
|
|
43
|
+
"status": "active"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"key": "pkm",
|
|
47
|
+
"href": "/pkm",
|
|
48
|
+
"accent": "oklch(0.702 0.183 293)",
|
|
49
|
+
"icon": "jellyfish",
|
|
50
|
+
"status": "coming"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"key": "naeilUi",
|
|
54
|
+
"href": "/design",
|
|
55
|
+
"accent": "oklch(0.623 0.214 259)",
|
|
56
|
+
"icon": "whale",
|
|
57
|
+
"status": "active"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"key": "sa",
|
|
61
|
+
"href": "/sa",
|
|
62
|
+
"accent": "oklch(0.723 0.219 149)",
|
|
63
|
+
"icon": "fish",
|
|
64
|
+
"status": "active"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"key": "babyAgent",
|
|
68
|
+
"href": "/baby-agent",
|
|
69
|
+
"accent": "oklch(0.769 0.188 70.08)",
|
|
70
|
+
"icon": "turtle",
|
|
71
|
+
"status": "coming"
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
var headerContent = {
|
|
75
|
+
"en": {
|
|
76
|
+
"nav": {
|
|
77
|
+
"home": "Home",
|
|
78
|
+
"projects": "Projects",
|
|
79
|
+
"blog": "Blog",
|
|
80
|
+
"navigation": "Main navigation",
|
|
81
|
+
"menu": "Menu",
|
|
82
|
+
"closeMenu": "Close menu"
|
|
83
|
+
},
|
|
84
|
+
"theme": {
|
|
85
|
+
"switchToLight": "Switch to light theme",
|
|
86
|
+
"switchToDark": "Switch to dark theme"
|
|
87
|
+
},
|
|
88
|
+
"locale": {
|
|
89
|
+
"switchLabel": "Language"
|
|
90
|
+
},
|
|
91
|
+
"projects": {
|
|
92
|
+
"cc": {
|
|
93
|
+
"name": "Content Collector",
|
|
94
|
+
"description": "Automated content collection from RSS, YouTube, Reddit + AI summarization"
|
|
95
|
+
},
|
|
96
|
+
"pkm": {
|
|
97
|
+
"name": "PKM",
|
|
98
|
+
"description": "Personal knowledge management \u2014 document parsing, embeddings, semantic search"
|
|
99
|
+
},
|
|
100
|
+
"naeilUi": {
|
|
101
|
+
"name": "naeil-ui",
|
|
102
|
+
"description": "Minimal design system \u2014 OKLCH tokens, Tailwind v4, shadcn"
|
|
103
|
+
},
|
|
104
|
+
"sa": {
|
|
105
|
+
"name": "Sustainability Analyzer",
|
|
106
|
+
"description": "AI-powered ESG report analysis \u2014 PDF parsing, qualitative assessment, comparison tables"
|
|
107
|
+
},
|
|
108
|
+
"babyAgent": {
|
|
109
|
+
"name": "Baby Agent",
|
|
110
|
+
"description": "Parenting AI agent \u2014 LINE-based Q&A, records, reminders"
|
|
111
|
+
},
|
|
112
|
+
"status": {
|
|
113
|
+
"active": "Active",
|
|
114
|
+
"coming": "Coming Soon"
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"ko": {
|
|
119
|
+
"nav": {
|
|
120
|
+
"home": "\uD648",
|
|
121
|
+
"projects": "\uD504\uB85C\uC81D\uD2B8",
|
|
122
|
+
"blog": "\uBE14\uB85C\uADF8",
|
|
123
|
+
"navigation": "\uC8FC\uC694 \uBA54\uB274",
|
|
124
|
+
"menu": "\uBA54\uB274",
|
|
125
|
+
"closeMenu": "\uBA54\uB274 \uB2EB\uAE30"
|
|
126
|
+
},
|
|
127
|
+
"theme": {
|
|
128
|
+
"switchToLight": "\uBC1D\uC740 \uD14C\uB9C8\uB85C \uC804\uD658",
|
|
129
|
+
"switchToDark": "\uC5B4\uB450\uC6B4 \uD14C\uB9C8\uB85C \uC804\uD658"
|
|
130
|
+
},
|
|
131
|
+
"locale": {
|
|
132
|
+
"switchLabel": "\uC5B8\uC5B4"
|
|
133
|
+
},
|
|
134
|
+
"projects": {
|
|
135
|
+
"cc": {
|
|
136
|
+
"name": "Content Collector",
|
|
137
|
+
"description": "RSS, YouTube, Reddit \uC790\uB3D9 \uC218\uC9D1 + AI \uC694\uC57D"
|
|
138
|
+
},
|
|
139
|
+
"pkm": {
|
|
140
|
+
"name": "PKM",
|
|
141
|
+
"description": "\uAC1C\uC778 \uC9C0\uC2DD \uAD00\uB9AC \u2014 \uBB38\uC11C \uD30C\uC2F1, \uC784\uBCA0\uB529, \uC2DC\uB9E8\uD2F1 \uAC80\uC0C9"
|
|
142
|
+
},
|
|
143
|
+
"naeilUi": {
|
|
144
|
+
"name": "naeil-ui",
|
|
145
|
+
"description": "\uBBF8\uB2C8\uBA40 \uB514\uC790\uC778 \uC2DC\uC2A4\uD15C \u2014 OKLCH \uD1A0\uD070, Tailwind v4, shadcn"
|
|
146
|
+
},
|
|
147
|
+
"sa": {
|
|
148
|
+
"name": "Sustainability Analyzer",
|
|
149
|
+
"description": "ESG \uB9AC\uD3EC\uD2B8 AI \uBD84\uC11D \u2014 PDF \uD30C\uC2F1, \uC815\uC131\uD3C9\uAC00, \uBE44\uAD50\uD45C \uC790\uB3D9 \uC0DD\uC131"
|
|
150
|
+
},
|
|
151
|
+
"babyAgent": {
|
|
152
|
+
"name": "Baby Agent",
|
|
153
|
+
"description": "\uC721\uC544 AI \uC5D0\uC774\uC804\uD2B8 \u2014 LINE \uAE30\uBC18 Q&A, \uAE30\uB85D, \uB9AC\uB9C8\uC778\uB354"
|
|
154
|
+
},
|
|
155
|
+
"status": {
|
|
156
|
+
"active": "\uD65C\uC131",
|
|
157
|
+
"coming": "\uC900\uBE44 \uC911"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"ja": {
|
|
162
|
+
"nav": {
|
|
163
|
+
"home": "\u30DB\u30FC\u30E0",
|
|
164
|
+
"projects": "\u30D7\u30ED\u30B8\u30A7\u30AF\u30C8",
|
|
165
|
+
"blog": "\u30D6\u30ED\u30B0",
|
|
166
|
+
"navigation": "\u30E1\u30A4\u30F3\u30CA\u30D3\u30B2\u30FC\u30B7\u30E7\u30F3",
|
|
167
|
+
"menu": "\u30E1\u30CB\u30E5\u30FC",
|
|
168
|
+
"closeMenu": "\u30E1\u30CB\u30E5\u30FC\u3092\u9589\u3058\u308B"
|
|
169
|
+
},
|
|
170
|
+
"theme": {
|
|
171
|
+
"switchToLight": "\u30E9\u30A4\u30C8\u30C6\u30FC\u30DE\u306B\u5207\u308A\u66FF\u3048\u308B",
|
|
172
|
+
"switchToDark": "\u30C0\u30FC\u30AF\u30C6\u30FC\u30DE\u306B\u5207\u308A\u66FF\u3048\u308B"
|
|
173
|
+
},
|
|
174
|
+
"locale": {
|
|
175
|
+
"switchLabel": "\u8A00\u8A9E"
|
|
176
|
+
},
|
|
177
|
+
"projects": {
|
|
178
|
+
"cc": {
|
|
179
|
+
"name": "Content Collector",
|
|
180
|
+
"description": "RSS\u3001YouTube\u3001Reddit\u306E\u81EA\u52D5\u53CE\u96C6 + AI\u8981\u7D04"
|
|
181
|
+
},
|
|
182
|
+
"pkm": {
|
|
183
|
+
"name": "PKM",
|
|
184
|
+
"description": "\u500B\u4EBA\u30CA\u30EC\u30C3\u30B8\u7BA1\u7406 \u2014 \u30C9\u30AD\u30E5\u30E1\u30F3\u30C8\u89E3\u6790\u3001\u57CB\u3081\u8FBC\u307F\u3001\u30BB\u30DE\u30F3\u30C6\u30A3\u30C3\u30AF\u691C\u7D22"
|
|
185
|
+
},
|
|
186
|
+
"naeilUi": {
|
|
187
|
+
"name": "naeil-ui",
|
|
188
|
+
"description": "\u30DF\u30CB\u30DE\u30EB\u30C7\u30B6\u30A4\u30F3\u30B7\u30B9\u30C6\u30E0 \u2014 OKLCH\u30C8\u30FC\u30AF\u30F3\u3001Tailwind v4\u3001shadcn"
|
|
189
|
+
},
|
|
190
|
+
"sa": {
|
|
191
|
+
"name": "Sustainability Analyzer",
|
|
192
|
+
"description": "ESG\u30EC\u30DD\u30FC\u30C8AI\u5206\u6790 \u2014 PDF\u89E3\u6790\u3001\u5B9A\u6027\u8A55\u4FA1\u3001\u6BD4\u8F03\u8868\u306E\u81EA\u52D5\u751F\u6210"
|
|
193
|
+
},
|
|
194
|
+
"babyAgent": {
|
|
195
|
+
"name": "Baby Agent",
|
|
196
|
+
"description": "\u80B2\u5150AI\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8 \u2014 LINE Q&A\u3001\u8A18\u9332\u3001\u30EA\u30DE\u30A4\u30F3\u30C0\u30FC"
|
|
197
|
+
},
|
|
198
|
+
"status": {
|
|
199
|
+
"active": "\u30A2\u30AF\u30C6\u30A3\u30D6",
|
|
200
|
+
"coming": "\u6E96\u5099\u4E2D"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// src/components/naeil-header-account.tsx
|
|
207
|
+
import { UserRound } from "lucide-react";
|
|
208
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
209
|
+
var labels = {
|
|
210
|
+
en: { menu: "Account menu", account: "Account", login: "Log in", logout: "Log out", busy: "Logging out\u2026" },
|
|
211
|
+
ko: { menu: "\uACC4\uC815 \uBA54\uB274", account: "\uACC4\uC815", login: "\uB85C\uADF8\uC778", logout: "\uB85C\uADF8\uC544\uC6C3", busy: "\uB85C\uADF8\uC544\uC6C3 \uC911\u2026" },
|
|
212
|
+
ja: { menu: "\u30A2\u30AB\u30A6\u30F3\u30C8\u30E1\u30CB\u30E5\u30FC", account: "\u30A2\u30AB\u30A6\u30F3\u30C8", login: "\u30ED\u30B0\u30A4\u30F3", logout: "\u30ED\u30B0\u30A2\u30A6\u30C8", busy: "\u30ED\u30B0\u30A2\u30A6\u30C8\u4E2D\u2026" }
|
|
213
|
+
};
|
|
214
|
+
var NativeLink = (props) => /* @__PURE__ */ jsx("a", __spreadValues({}, props));
|
|
215
|
+
function NaeilHeaderAccountControl({ locale, account, Link = NativeLink }) {
|
|
216
|
+
const text = labels[locale];
|
|
217
|
+
if (account.status === "signed-out") {
|
|
218
|
+
return /* @__PURE__ */ jsx(Link, { href: account.loginHref, className: "ui-navigation-link ui-focus naeil-header-login", "data-naeil-account-state": "signed-out", children: text.login });
|
|
219
|
+
}
|
|
220
|
+
return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
221
|
+
/* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
222
|
+
"button",
|
|
223
|
+
{
|
|
224
|
+
type: "button",
|
|
225
|
+
className: "ui-focus naeil-header-account-trigger",
|
|
226
|
+
"data-naeil-account-trigger": true,
|
|
227
|
+
"data-naeil-account-state": "signed-in",
|
|
228
|
+
"aria-label": text.menu,
|
|
229
|
+
"aria-busy": account.busy || void 0,
|
|
230
|
+
children: /* @__PURE__ */ jsx("span", { className: "naeil-header-account-avatar", "aria-hidden": "true", children: /* @__PURE__ */ jsx(UserRound, {}) })
|
|
231
|
+
}
|
|
232
|
+
) }),
|
|
233
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", className: "w-56 max-w-[calc(100vw-2rem)]", "data-naeil-account-menu": true, children: [
|
|
234
|
+
/* @__PURE__ */ jsxs(DropdownMenuLabel, { className: "flex min-w-0 flex-col font-normal", children: [
|
|
235
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-base font-medium", children: account.displayName || text.account }),
|
|
236
|
+
account.email && /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-muted-foreground", children: account.email })
|
|
237
|
+
] }),
|
|
238
|
+
/* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
|
|
239
|
+
/* @__PURE__ */ jsx(
|
|
240
|
+
DropdownMenuItem,
|
|
241
|
+
{
|
|
242
|
+
className: "min-h-11",
|
|
243
|
+
disabled: account.busy,
|
|
244
|
+
onSelect: () => {
|
|
245
|
+
if (!account.busy) account.onLogout();
|
|
246
|
+
},
|
|
247
|
+
children: account.busy ? text.busy : text.logout
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
] })
|
|
251
|
+
] });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// src/components/naeil-header.tsx
|
|
255
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
256
|
+
var subscribe = () => () => {
|
|
257
|
+
};
|
|
258
|
+
function NaeilHeaderThemeControl({ locale }) {
|
|
259
|
+
const { resolvedTheme, setTheme } = useTheme();
|
|
260
|
+
const mounted = useSyncExternalStore(subscribe, () => true, () => false);
|
|
261
|
+
const dark = mounted && resolvedTheme === "dark";
|
|
262
|
+
return /* @__PURE__ */ jsx2(
|
|
263
|
+
Button,
|
|
264
|
+
{
|
|
265
|
+
variant: "ghost",
|
|
266
|
+
size: "icon",
|
|
267
|
+
className: "size-11 shrink-0",
|
|
268
|
+
disabled: !mounted,
|
|
269
|
+
"aria-label": headerContent[locale].theme[dark ? "switchToLight" : "switchToDark"],
|
|
270
|
+
onClick: () => setTheme(dark ? "light" : "dark"),
|
|
271
|
+
children: dark ? /* @__PURE__ */ jsx2(Sun, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx2(Moon, { "aria-hidden": "true" })
|
|
272
|
+
}
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
function NaeilHeaderLocaleControl({ locale, onLocaleChange }) {
|
|
276
|
+
return /* @__PURE__ */ jsxs2(Select, { value: locale, onValueChange: (value) => onLocaleChange(value), children: [
|
|
277
|
+
/* @__PURE__ */ jsx2(
|
|
278
|
+
SelectTrigger,
|
|
279
|
+
{
|
|
280
|
+
className: "h-11 w-auto min-w-20 border-transparent bg-transparent px-2",
|
|
281
|
+
"aria-label": headerContent[locale].locale.switchLabel,
|
|
282
|
+
children: /* @__PURE__ */ jsx2(SelectValue, { children: locale.toUpperCase() })
|
|
283
|
+
}
|
|
284
|
+
),
|
|
285
|
+
/* @__PURE__ */ jsx2(SelectContent, { align: "end", children: headerLocales.map((value) => /* @__PURE__ */ jsx2(SelectItem, { value, className: "min-h-11", lang: value, children: headerLocaleNames[value] }, value)) })
|
|
286
|
+
] });
|
|
287
|
+
}
|
|
288
|
+
var NativeLink2 = (props) => /* @__PURE__ */ jsx2("a", __spreadValues({}, props));
|
|
289
|
+
function NaeilHeader(_a) {
|
|
290
|
+
var _b = _a, {
|
|
291
|
+
locale,
|
|
292
|
+
resolveHref = (path) => `https://naeil.dev/${locale}${path === "/" ? "" : path}`,
|
|
293
|
+
brandHref,
|
|
294
|
+
brandSlot,
|
|
295
|
+
brandLinkProps,
|
|
296
|
+
brandLinkComponent,
|
|
297
|
+
onLocaleChange,
|
|
298
|
+
authSlot,
|
|
299
|
+
toolbarSlot,
|
|
300
|
+
projects,
|
|
301
|
+
projectsExternal = false,
|
|
302
|
+
translations,
|
|
303
|
+
showProjects = true,
|
|
304
|
+
showBlog = true,
|
|
305
|
+
subNav,
|
|
306
|
+
currentProjectKey,
|
|
307
|
+
currentProjectHref,
|
|
308
|
+
labels: labels2,
|
|
309
|
+
Link = NativeLink2,
|
|
310
|
+
currentHref,
|
|
311
|
+
onNavigate,
|
|
312
|
+
className,
|
|
313
|
+
containerClassName
|
|
314
|
+
} = _b, props = __objRest(_b, [
|
|
315
|
+
"locale",
|
|
316
|
+
"resolveHref",
|
|
317
|
+
"brandHref",
|
|
318
|
+
"brandSlot",
|
|
319
|
+
"brandLinkProps",
|
|
320
|
+
"brandLinkComponent",
|
|
321
|
+
"onLocaleChange",
|
|
322
|
+
"authSlot",
|
|
323
|
+
"toolbarSlot",
|
|
324
|
+
"projects",
|
|
325
|
+
"projectsExternal",
|
|
326
|
+
"translations",
|
|
327
|
+
"showProjects",
|
|
328
|
+
"showBlog",
|
|
329
|
+
"subNav",
|
|
330
|
+
"currentProjectKey",
|
|
331
|
+
"currentProjectHref",
|
|
332
|
+
"labels",
|
|
333
|
+
"Link",
|
|
334
|
+
"currentHref",
|
|
335
|
+
"onNavigate",
|
|
336
|
+
"className",
|
|
337
|
+
"containerClassName"
|
|
338
|
+
]);
|
|
339
|
+
var _b2, _c;
|
|
340
|
+
const content = headerContent[locale];
|
|
341
|
+
const projectLinks = !showProjects ? [] : projects != null ? projects : headerProjects.map((project) => {
|
|
342
|
+
var _a3, _b3, _c2;
|
|
343
|
+
const current = project.key === currentProjectKey;
|
|
344
|
+
return {
|
|
345
|
+
id: project.key,
|
|
346
|
+
label: (_a3 = translations == null ? void 0 : translations.project(`${project.key}.name`)) != null ? _a3 : content.projects[project.key].name,
|
|
347
|
+
description: (_b3 = translations == null ? void 0 : translations.project(`${project.key}.description`)) != null ? _b3 : content.projects[project.key].description,
|
|
348
|
+
href: current ? currentProjectHref || "/" : resolveHref(project.href),
|
|
349
|
+
external: !current && projectsExternal,
|
|
350
|
+
active: current || currentHref === project.href || (currentHref == null ? void 0 : currentHref.startsWith(`${project.href}/`)) || currentHref === resolveHref(project.href),
|
|
351
|
+
icon: /* @__PURE__ */ jsx2("span", { className: "naeil-header-project-icon", style: {
|
|
352
|
+
borderColor: `color-mix(in oklch, ${project.accent} 30%, transparent)`,
|
|
353
|
+
backgroundColor: `color-mix(in oklch, ${project.accent} 8%, transparent)`
|
|
354
|
+
}, children: /* @__PURE__ */ jsx2("img", { src: `https://naeil.dev/images/${project.icon}.png`, alt: "", width: 24, height: 24 }) }),
|
|
355
|
+
badge: project.status === "coming" ? /* @__PURE__ */ jsx2(Badge, { variant: "outline", children: (_c2 = translations == null ? void 0 : translations.project("status.coming")) != null ? _c2 : content.projects.status.coming }) : void 0
|
|
356
|
+
};
|
|
357
|
+
});
|
|
358
|
+
const brandDestination = brandHref != null ? brandHref : resolveHref("/");
|
|
359
|
+
const BrandLink = brandLinkComponent != null ? brandLinkComponent : Link;
|
|
360
|
+
const _a2 = brandLinkProps != null ? brandLinkProps : {}, { onClick: onBrandClick } = _a2, brandProps = __objRest(_a2, ["onClick"]);
|
|
361
|
+
const toolbar = /* @__PURE__ */ jsx2("div", { "data-app-toolbar": true, className: "naeil-header-controls", children: toolbarSlot !== void 0 ? toolbarSlot : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
362
|
+
/* @__PURE__ */ jsx2(NaeilHeaderThemeControl, { locale }),
|
|
363
|
+
onLocaleChange && /* @__PURE__ */ jsx2(NaeilHeaderLocaleControl, { locale, onLocaleChange }),
|
|
364
|
+
authSlot && /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
365
|
+
/* @__PURE__ */ jsx2("div", { "aria-hidden": "true", className: "naeil-header-separator" }),
|
|
366
|
+
authSlot
|
|
367
|
+
] })
|
|
368
|
+
] }) });
|
|
369
|
+
return /* @__PURE__ */ jsx2(
|
|
370
|
+
Navigation,
|
|
371
|
+
__spreadProps(__spreadValues({}, props), {
|
|
372
|
+
"data-global-header": true,
|
|
373
|
+
"data-naeil-header": true,
|
|
374
|
+
"data-naeil-ui-version": uiRelease.version,
|
|
375
|
+
"data-naeil-ui-contract": uiRelease.contractVersion,
|
|
376
|
+
Link,
|
|
377
|
+
currentHref,
|
|
378
|
+
onNavigate,
|
|
379
|
+
brand: /* @__PURE__ */ jsx2(
|
|
380
|
+
BrandLink,
|
|
381
|
+
__spreadProps(__spreadValues({
|
|
382
|
+
href: brandDestination,
|
|
383
|
+
"data-app-brand": true,
|
|
384
|
+
"aria-label": brandSlot == null ? ".naeil" : void 0
|
|
385
|
+
}, brandProps), {
|
|
386
|
+
onClick: (event) => {
|
|
387
|
+
onBrandClick == null ? void 0 : onBrandClick(event);
|
|
388
|
+
onNavigate == null ? void 0 : onNavigate(event, { id: "brand", href: brandDestination, label: ".naeil" });
|
|
389
|
+
},
|
|
390
|
+
children: brandSlot != null ? brandSlot : /* @__PURE__ */ jsx2(Logo, { variant: "horizontal", size: 28, className: "block", label: null })
|
|
391
|
+
})
|
|
392
|
+
),
|
|
393
|
+
items: [
|
|
394
|
+
...showProjects ? [{ id: "projects", label: (_b2 = translations == null ? void 0 : translations.projects) != null ? _b2 : content.nav.projects, items: projectLinks, columns: 2 }] : [],
|
|
395
|
+
...showBlog ? [{ id: "blog", label: (_c = translations == null ? void 0 : translations.blog) != null ? _c : content.nav.blog, href: resolveHref("/blog"), external: projectsExternal }] : [],
|
|
396
|
+
...(subNav != null ? subNav : []).map((item) => __spreadProps(__spreadValues({}, item), { id: item.href }))
|
|
397
|
+
],
|
|
398
|
+
labels: labels2 != null ? labels2 : { navigation: content.nav.navigation, menu: content.nav.menu, close: content.nav.closeMenu },
|
|
399
|
+
toolbar,
|
|
400
|
+
mobileToolbar: toolbar,
|
|
401
|
+
className: cn("naeil-header fixed top-0 w-full", className),
|
|
402
|
+
containerClassName: cn("naeil-header-frame", containerClassName)
|
|
403
|
+
})
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export {
|
|
408
|
+
headerLocales,
|
|
409
|
+
NaeilHeaderAccountControl,
|
|
410
|
+
NaeilHeaderThemeControl,
|
|
411
|
+
NaeilHeaderLocaleControl,
|
|
412
|
+
NaeilHeader
|
|
413
|
+
};
|