├── mod.ts
├── fonts
├── Figtree
│ ├── Figtree-Bold.woff2
│ ├── Figtree-Italic.woff2
│ └── Figtree-Regular.woff2
├── FiraCode
│ ├── FiraCode-Bold.woff2
│ └── FiraCode-Regular.woff2
└── LibreCaslonText
│ ├── LibreCaslonText-Bold.woff2
│ ├── LibreCaslonText-Italic.woff2
│ └── LibreCaslonText-Regular.woff2
├── .gitignore
├── src
├── md.ts
└── keynav.ts
├── deno.json
├── CHANGELOG.md
├── LICENSE
└── README.md
/mod.ts:
--------------------------------------------------------------------------------
1 | export { default as md } from './src/md.ts';
2 | export { default as keynav } from './src/keynav.ts';
3 |
--------------------------------------------------------------------------------
/fonts/Figtree/Figtree-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarcajadaArtificial/lunchbox/HEAD/fonts/Figtree/Figtree-Bold.woff2
--------------------------------------------------------------------------------
/fonts/Figtree/Figtree-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarcajadaArtificial/lunchbox/HEAD/fonts/Figtree/Figtree-Italic.woff2
--------------------------------------------------------------------------------
/fonts/FiraCode/FiraCode-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarcajadaArtificial/lunchbox/HEAD/fonts/FiraCode/FiraCode-Bold.woff2
--------------------------------------------------------------------------------
/fonts/Figtree/Figtree-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarcajadaArtificial/lunchbox/HEAD/fonts/Figtree/Figtree-Regular.woff2
--------------------------------------------------------------------------------
/fonts/FiraCode/FiraCode-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarcajadaArtificial/lunchbox/HEAD/fonts/FiraCode/FiraCode-Regular.woff2
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.css.map
2 | deno.lock
3 | .DS_Store
4 | .obsidian
5 | .vscode
6 | .cursor/
7 | _fresh/
8 | docs/
9 | node_modules/
10 |
--------------------------------------------------------------------------------
/fonts/LibreCaslonText/LibreCaslonText-Bold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarcajadaArtificial/lunchbox/HEAD/fonts/LibreCaslonText/LibreCaslonText-Bold.woff2
--------------------------------------------------------------------------------
/fonts/LibreCaslonText/LibreCaslonText-Italic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarcajadaArtificial/lunchbox/HEAD/fonts/LibreCaslonText/LibreCaslonText-Italic.woff2
--------------------------------------------------------------------------------
/fonts/LibreCaslonText/LibreCaslonText-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CarcajadaArtificial/lunchbox/HEAD/fonts/LibreCaslonText/LibreCaslonText-Regular.woff2
--------------------------------------------------------------------------------
/src/md.ts:
--------------------------------------------------------------------------------
1 | import { render, type RenderOptions } from '@deno/gfm';
2 |
3 | interface MarkdownProps {
4 | content: string;
5 | renderOptions?: RenderOptions;
6 | transform?: (content: string) => string;
7 | }
8 |
9 | export default function (
10 | props: MarkdownProps,
11 | ): { dangerouslySetInnerHTML: { __html: string } } {
12 | const content = render(props.content, props.renderOptions);
13 |
14 | return {
15 | dangerouslySetInnerHTML: {
16 | __html: props.transform ? props.transform(content) : content,
17 | },
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/deno.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@lunchbox/ui",
3 | "version": "3.0.1",
4 | "license": "MIT",
5 | "exports": {
6 | ".": "./mod.ts"
7 | },
8 | "tasks": {
9 | "init:clean": "deno run -A tasks.ts init-clean",
10 | "init:map": "deno run -A tasks.ts init-map"
11 | },
12 | "compilerOptions": {
13 | "jsx": "react-jsx",
14 | "jsxImportSource": "preact"
15 | },
16 | "fmt": { "exclude": [".github/dep", "*.md"], "singleQuote": true },
17 | "imports": {
18 | "@deno/gfm": "jsr:@deno/gfm@^0.11.0",
19 | "preact": "npm:preact@^10.26.6",
20 | "zod": "npm:zod@^3.25.36"
21 | },
22 | "nodeModulesDir": "auto",
23 | "lock": false
24 | }
25 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## Backlog
4 |
5 | ### New Features
6 |
7 | - An island for using the IntersectionObserver API.
8 | - Extend the `.layout` class for layouts inside layouts.
9 | - Keynav that keeps the scroll position consitent.
10 | - KeynavTutorial island that displays a `` element, a label and listens
11 | for the first key press of that keys and visually marks the keys as completed.
12 |
13 | ### JSR
14 |
15 | - Module documentation.
16 | - GitHub actions for publish.
17 |
18 | ## Version History
19 |
20 | ### 3.0.1
21 |
22 | - Added an update to the `keynav` utility function where it outputs the effect
23 | function and not the whole island.
24 |
25 | ### 3.0.0
26 |
27 | - Now built on top of DaisyUI and TailwindCSS.
28 | - Added the DaisyUI compatible themes Lunchbox and Supperbox (nickname for the
29 | dark mode theme).
30 | - Added noise style utilities.
31 | - Removed components redundant to DaisyUI.
32 | - Added the `md` utility function replacing the `` component.
33 | - Added the `` island.
34 | - Removed the CSS module in favor of `npm:lunchbox-css`.
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Oscar Alfonso Guerrero
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/src/keynav.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'preact/hooks';
2 |
3 | const DIRECTIONS = [
4 | 'ArrowUp',
5 | 'ArrowDown',
6 | 'ArrowLeft',
7 | 'ArrowRight',
8 | ];
9 | type Direction = typeof DIRECTIONS[number];
10 |
11 | const SHAKE_CLASSES = [
12 | 'shake_up',
13 | 'shake_down',
14 | 'shake_left',
15 | 'shake_right',
16 | 'shake',
17 | ] as const;
18 | type ShakeClass = typeof SHAKE_CLASSES[number];
19 |
20 | function overlaps(aMin: number, aMax: number, bMin: number, bMax: number) {
21 | return !(aMax < bMin || aMin > bMax);
22 | }
23 |
24 | function findCandidate(
25 | current: HTMLElement,
26 | direction: Direction,
27 | candidates: HTMLElement[],
28 | padding: number = 0,
29 | ): HTMLElement | null {
30 | const currentRect = current.getBoundingClientRect();
31 | const paddedY = {
32 | min: currentRect.top - padding,
33 | max: currentRect.bottom + padding,
34 | };
35 | const paddedX = {
36 | min: currentRect.left - padding,
37 | max: currentRect.right + padding,
38 | };
39 |
40 | type Entry = { el: HTMLElement; distance: number };
41 | const entries: Entry[] = [];
42 |
43 | for (const el of candidates) {
44 | if (el === current) continue;
45 | const rect = el.getBoundingClientRect();
46 | let distance = Infinity;
47 | let ok = false;
48 |
49 | switch (direction) {
50 | case 'ArrowRight':
51 | ok = rect.left > currentRect.right &&
52 | overlaps(rect.top, rect.bottom, paddedY.min, paddedY.max);
53 | if (ok) distance = rect.left - currentRect.right;
54 | break;
55 | case 'ArrowLeft':
56 | ok = rect.right < currentRect.left &&
57 | overlaps(rect.top, rect.bottom, paddedY.min, paddedY.max);
58 | if (ok) distance = currentRect.left - rect.right;
59 | break;
60 | case 'ArrowDown':
61 | ok = rect.top > currentRect.bottom &&
62 | overlaps(rect.left, rect.right, paddedX.min, paddedX.max);
63 | if (ok) distance = rect.top - currentRect.bottom;
64 | break;
65 | case 'ArrowUp':
66 | ok = rect.bottom < currentRect.top &&
67 | overlaps(rect.left, rect.right, paddedX.min, paddedX.max);
68 | if (ok) distance = currentRect.top - rect.bottom;
69 | break;
70 | }
71 |
72 | if (ok) entries.push({ el, distance });
73 | }
74 |
75 | if (entries.length === 0) return null;
76 |
77 | // pick the entry with the smallest distance
78 | let best = entries[0];
79 | for (const e of entries) {
80 | if (e.distance < best.distance) best = e;
81 | }
82 |
83 | return best.el;
84 | }
85 |
86 | function resetShake(el: HTMLElement, exclude?: ShakeClass) {
87 | SHAKE_CLASSES.forEach((cls) => {
88 | if (cls !== exclude && el.classList.contains(cls)) {
89 | el.classList.remove(cls);
90 | }
91 | });
92 | }
93 |
94 | function handleKeyDown(this: HTMLElement, e: KeyboardEvent) {
95 | const { key } = e;
96 |
97 | if (key === 'Enter') {
98 | resetShake(this);
99 | void this.offsetWidth;
100 | this.classList.add('shake');
101 | return;
102 | }
103 |
104 | if (key === 'Esc') this.blur();
105 |
106 | if (!DIRECTIONS.includes(key)) return;
107 |
108 | e.preventDefault();
109 | resetShake(this);
110 |
111 | const tabbedElements = Array.from(
112 | document.querySelectorAll('[tabindex="0"]'),
113 | );
114 | const candidate = findCandidate(this, key, tabbedElements, 100);
115 |
116 | if (candidate) {
117 | this.removeEventListener('keydown', handleKeyDown);
118 | candidate.focus();
119 | return;
120 | }
121 |
122 | const dir = key.replace('Arrow', '').toLowerCase();
123 | const shakeClass = `shake_${dir}` as ShakeClass;
124 |
125 | this.classList.add(shakeClass);
126 | }
127 |
128 | /**
129 | * Attach the `handleKeyDown()` listener when an element is focused.
130 | */
131 | function handleFocusIn(e: FocusEvent) {
132 | const t = e.target;
133 | if (t instanceof HTMLElement && t.tabIndex === 0) {
134 | t.addEventListener('keydown', handleKeyDown);
135 | }
136 | }
137 |
138 | /**
139 | * Remove the `handleKeyDown()` listener when an element is focused.
140 | */
141 | function handleFocusOut(e: FocusEvent) {
142 | const t = e.target;
143 | if (t instanceof HTMLElement && t.tabIndex === 0) {
144 | t.removeEventListener('keydown', handleKeyDown);
145 | resetShake(t);
146 | }
147 | }
148 |
149 | export default function () {
150 | document.addEventListener('focusin', handleFocusIn);
151 | document.addEventListener('focusout', handleFocusOut);
152 | }
153 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 🍱 Lunchbox
2 |
3 | [](https://jsr.io/@lunchbox/ui)
4 | [](https://jsr.io/@lunchbox/ui)
5 |
6 | `` Hello ( ´ ω ` )ノ゙ `` Welcome to 🍱 Lunchbox. A lightweight, server-first
7 | styling layer built on top of 💨 **TailwindCSS v4** and 🌼 **DaisyUI v5**,
8 | tailor-made for 🦕 **Deno** 🍋 **Fresh v2** and **Preact**.
9 |
10 | ## Features
11 |
12 | - 🎯 **Custom Design Tokens**: Includes extended spacing scale and responsive
13 | breakpoints tailored for fine-tuned layouts.
14 | - ✍️ **GFM-Optimized Typography**: Integrates the Tailwind Typography plugin
15 | with custom styles specifically tuned for GitHub-Flavored Markdown.
16 | - 🎨 **DaisyUI Theming**: Built on top of DaisyUI with both light and dark
17 | themes ready to go.
18 | - 🧱 **Responsive Grid Layout**: Includes a flexible, column-based layout system
19 | for responsive content arrangement.
20 | - ⌨️ **Keyboard Navigation Island**: Includes an interactive component for
21 | arrow-key-based focus navigation between elements.
22 |
23 | ## Installation
24 |
25 | Before starting add the following configuration to `./deno.json`:
26 |
27 | ```json
28 | {
29 | "nodeModulesDir": "auto"
30 | }
31 | ```
32 |
33 | ### 1. Install 🍋 Fresh
34 |
35 | There are three approaches to installing Fresh: v2 init, v1 init, and manual
36 | setup. All of these have their own benefits so chose any you like as long as you
37 | don't include Tailwind in your installation, if you do you will have to update
38 | it to v4. It is definetly recommended at least a basic understanding of how
39 | Fresh works as well.
40 |
41 | Change `PROJECT_NAME` for the name of the root directory for the project.
42 |
43 | ```sh
44 | deno run -Ar jsr:@fresh/init@2.0.0-alpha.34 PROJECT_NAME --tailwind=false
45 | ```
46 |
47 | ### 2. Install 💨 TailwindCSS
48 |
49 | This library is built on top of the latest version of TailwindCSS (v4) and it's
50 | incompatible for any previous one. It is because of this that TailwindCSS must
51 | not be installed along with the Fresh boilerplate project.
52 |
53 | 1. Install TailwindCSS core v4.
54 |
55 | ```sh
56 | deno add npm:tailwindcss
57 | ```
58 |
59 | 2. Install the Typography plugin for TailwindCSS.
60 |
61 | ```sh
62 | deno add npm:@tailwindcss/typography@^0.5.16
63 | ```
64 |
65 | 3. Install [@pakornv's](https://github.com/pakornv) Fresh Plugin TailwindCSS for
66 | v4. Version 2.0 is still in alpha so try to keep up with this project's
67 | updates. As a note, there is an official Fresh Plugin TailwindCSS, but
68 | currently only supports v3.
69 |
70 | ```sh
71 | deno add --allow-scripts jsr:@pakornv/fresh-plugin-tailwindcss@2.0.0-alpha.1
72 | ```
73 |
74 | 4. Enable the plugin in your application.
75 |
76 | ```ts
77 | // dev.ts
78 |
79 | import { tailwind } from "@fresh/plugin-tailwind";
80 |
81 | tailwind(devApp);
82 | ```
83 |
84 | 5. Add the styles module.
85 |
86 | ```css
87 | /* ./static/styles.css */
88 |
89 | /* Add these: */
90 | @import "tailwindcss";
91 | @plugin "@tailwindcss/typography";
92 | ```
93 |
94 | ### 3. Install 🌼 DaisyUI
95 |
96 | On top of TailwindCSS, DaisyUI adds a layer of purce SSR components made out of
97 | pure HTML and CSS and a powerful theme system.
98 |
99 | ```sh
100 | deno add npm:daisyui
101 | ```
102 |
103 | ```css
104 | /* ./static/styles.css */
105 |
106 | @import "tailwindcss";
107 |
108 | @plugin "@tailwindcss/typography";
109 |
110 | /* Add this: */
111 | @plugin "daisyui" {
112 | /*
113 | Themes are disabled because they will be replaced by Lunchbox's
114 | custom themes.
115 | */
116 | themes: false;
117 | }
118 | ```
119 |
120 | ### 4. Install 🍱 Lunchbox
121 |
122 | Now that everything is set up, you can add this library. There are two parts of
123 | this, one with Preact components and TypeScript utilities and another with a the
124 | CSS modules.
125 |
126 | ```sh
127 | deno add jsr:@lunchbox/ui npm:lunchbox-css
128 | ```
129 |
130 | ```css
131 | /* ./static/styles.css */
132 |
133 | @import "tailwindcss";
134 | /* Add this: */
135 | @import "../node_modules/lunchbox-css/index.css";
136 |
137 | @plugin "@tailwindcss/typography";
138 |
139 | @plugin "daisyui" {
140 | themes: false;
141 | }
142 | ```
143 |
144 | ```ts
145 | import /* Components and utilities. */ "@lunchbox/ui";
146 | ```
147 |
148 | ## Usage
149 |
150 | There are a few layers of the interface where Lunchbox interacts, design tokens,
151 | server components, and interactive islands.
152 |
153 | > [!Warning] Here is where the actual opinions of this library appear. I've seen
154 | > many packages speak about how "opinionated" they are. So here's a word of
155 | > warning, these contain _opinionated opinion that you might not agree with (and
156 | > that's okay)_.
157 |
158 | ### Design Tokens
159 |
160 | - **Breakpoints**: The way the custom breakpoints are thought as "window"
161 | breakpoint and not "device" breakpoints. Lunchbox replaces
162 | [Tailwind's Responsive Design](https://tailwindcss.com/docs/responsive-design#overview)
163 | with two simple breakpoints: `md` with a value of `40em` (equivalent to
164 | Tailwind's `sm`), and `lg` with a value of `80em` (equivalent to Tailwind's
165 | `xl`).
166 |
167 | The _opinion_ here is that having five breakpoints create too many viewport
168 | width ranges that create UI variations for a ver small percentage of window
169 | instances. This implemantation also follows the "mobile first" philosophy by
170 | having no breakpoint for "small" devices by it being the default.
171 |
172 | ```html
173 |
174 |
175 |
176 |
177 |
178 |
179 | ```
180 |
181 | - **Spacing**: These ones are going to be controvertial, that's why they are
182 | additional to the spacing values already on Tailwind's margins, paddings, etc.
183 | The opinion here is that the fractional _real-lifeish_ measuring system for
184 | inches and feet is incredibly convenient. Tailwind already comes with an "inch
185 | analogue" being the `--spacing` variable (`0.25em` by default).
186 |
187 | Lunchbox adds a "foot analogue" with the `--spacing-1-1` variable having a
188 | value of `1em`. The reasoning behind the `-1-1` notation is for it to read as
189 | a "1/1" fraction, meaning a single unit.
190 |
191 | ```html
192 |
193 |
194 |
195 |
196 |
197 |
198 | ```
199 |
200 | Wait are those thirds?
201 |
202 | ```html
203 |
204 |
205 |
206 |
207 | ```
208 |
209 | - **Responsive noise**: DaisyUI comes equipped with `--fx-noise` and `--noise`
210 | variables. The probelm is that `--fx-noise` doesn't look _perfectly balanced_
211 | when you compare it's usage between light and dark modes, light mode is much
212 | more subtle than dark mode. To solve this, the variable `--fx-noise-50` is
213 | added with an important change in opacity that goes from `0.2` to `0.05`, and
214 | using it for dark mode.
215 |
216 | This is an optional CSS module that must be imported individually from
217 | `.../styles/noise.css`. This module will also enable the `.noise` class for
218 | styling any component with the noise aesthetic.
219 |
220 | > [!WARNING] This module will style every page's main background with
221 | > responsive noise, making it really cool.
222 |
223 | ```css
224 | /* ./static/styles.css */
225 |
226 | @import "tailwindcss";
227 | @import "../node_modules/lunchbox-css/index.css";
228 | /* Add this: */
229 | @import "../node_modules/lunchbox-css/styles/noise.css";
230 |
231 | @plugin "@tailwindcss/typography";
232 |
233 | @plugin "daisyui" {
234 | themes: false;
235 | }
236 | ```
237 |
238 | ### Server Side Components
239 |
240 | - **Markdown to Prose**: Lunchbox exports a `md()` utility function tailor made
241 | for the `.prose` component in the TailwindCSS Typography plugin. The idea is
242 | for this component to handle the sanitized parsing of a Markdown file that's
243 | easily readable through the file system or a fetch function.
244 |
245 | > [!NOTE] You can also you the file system to read the content of a markdown
246 | > file using `await Deno.readTextFile("...")`.
247 |
248 | ```tsx
249 | // routes/md.tsx
250 |
251 | import { define } from "@/utils.ts";
252 | import { md } from "@lunchbox/ui";
253 |
254 | const CONTENT_URL = "...";
255 |
256 | export default define.page(async function Md() {
257 | const options = {
258 | content: await (await fetch(CONTENT_URL)).text(),
259 | /*
260 | Optional: This is a configuration for deno-gfm's rendering function.
261 | */
262 | renderOptions: {},
263 | /*
264 | Optional: This is a string transformation function executed after
265 | the sanitation process.
266 | */
267 | transform: (content: string) => content,
268 | };
269 |
270 | /*
271 | md(options) returns:
272 | {
273 | dangerouslySetInnerHTML: { __html: content; }
274 | }
275 | */
276 | return ;
277 | });
278 | ```
279 |
280 | - **Layout**: This component creates a predefined column-based grid system
281 | layout for most pages. It makes it easier not to define a grid system for most
282 | pages of most projects. This was you could only create a customized layout
283 | when it's really specific.
284 |
285 | This class works with the `col-span-*` TailwindCSS utility. In viewport widths
286 | smaller than `80em` (default and `md` breakpoints) it uses a **6** column grid
287 | and in viewports wider than that (`lg` breakpoint) it uses a **12** column
288 | grid.
289 |
290 | ```html
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 | ```
299 |
300 | ### Interactive Islands
301 |
302 | - **Directional Keyboard Navigation**: Without disrrupting the `tab` and
303 | `shift+tab` duo for it being the most common page navigation method, I added
304 | this feature to allow for they arrow keys as an alternative nagivation.
305 | Whenever you're focusing on an element you could use the arrow keys to move to
306 | the closest element in that direction.
307 |
308 | Also, there's included a small batch of animations that makes elements flicker
309 | on focus and shake a bit if there are no elements in that direction, or when
310 | you simply press enter. Credit and shoutout to [Commit Mono](https://commitmono.com/) for being the first implementation of this that I've ever seen.
311 |
312 | ```css
313 | /* ./static/styles.css */
314 |
315 | @import "tailwindcss";
316 | @import "../node_modules/lunchbox-css/index.css";
317 | /* Add this: */
318 | @import "../node_modules/lunchbox-css/styles/keynav.css";
319 |
320 | @plugin "@tailwindcss/typography";
321 |
322 | @plugin "daisyui" {
323 | themes: false;
324 | }
325 | ```
326 |
327 | ```tsx
328 | // ./islands/Keynav.tsx
329 |
330 | import { keynav } from "@lunchbox/ui";
331 |
332 | export default function () {
333 | useEffect(keynav, []);
334 |
335 | return null;
336 | }
337 | ```
338 |
339 | ```tsx
340 | // ./routes/_app.tsx
341 |
342 | import type { PageProps } from "fresh";
343 | // Add this:
344 | import Keynav from "../islands/Keynav.tsx";
345 |
346 | export default function App({ Component }: PageProps) {
347 | return (
348 |
349 |
350 |
351 | {/* Add this: */}
352 |
353 |
354 |
355 | );
356 | }
357 | ```
358 |
--------------------------------------------------------------------------------