@pure-ds/core 0.4.5 β 0.4.7
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.
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*
|
|
22
22
|
* 5. Completely custom actions (hides default buttons):
|
|
23
23
|
* <pds-jsonform .jsonSchema=${schema} hide-actions>
|
|
24
|
-
* <div slot="actions"
|
|
24
|
+
* <div slot="actions" class="flex gap-md">
|
|
25
25
|
* <button type="submit" class="btn btn-primary">Custom Submit</button>
|
|
26
26
|
* <button type="button" class="btn">Custom Action</button>
|
|
27
27
|
* </div>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pds-jsonform.d.ts","sourceRoot":"","sources":["../../../../../../public/assets/pds/components/pds-jsonform.js"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAcE;IAGF,yBAEC;IAaC,gBAA2B;IAC3B,cAAyB;IACzB,aAAwB;IACxB,YAAuB;IACvB,eAAoB;IACpB,qBAAwB;IACxB,oBAA2B;IAC3B,mBAAyB;IACzB,mBAAsB;IACtB,oBAAuB;IAiBzB,8CAEC;IACD,4BAEC;IAGD,oBAEC;IAkBD;;;MAIC;IAED,uBAEC;IAED,0BAMC;IAGD,+BAyBC;IA+ND,
|
|
1
|
+
{"version":3,"file":"pds-jsonform.d.ts","sourceRoot":"","sources":["../../../../../../public/assets/pds/components/pds-jsonform.js"],"names":[],"mappings":"AAgCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IACE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAcE;IAGF,yBAEC;IAaC,gBAA2B;IAC3B,cAAyB;IACzB,aAAwB;IACxB,YAAuB;IACvB,eAAoB;IACpB,qBAAwB;IACxB,oBAA2B;IAC3B,mBAAyB;IACzB,mBAAsB;IACtB,oBAAuB;IAiBzB,8CAEC;IACD,4BAEC;IAGD,oBAEC;IAkBD;;;MAIC;IAED,uBAEC;IAED,0BAMC;IAGD,+BAyBC;IA+ND,cAyCC;;CA63CF"}
|
package/package.json
CHANGED
package/public/assets/js/app.js
CHANGED
|
@@ -3684,7 +3684,7 @@ export const pdsConfig = ${JSON.stringify(this.config,null,2)};
|
|
|
3684
3684
|
`,setTimeout(()=>{p.innerHTML=`
|
|
3685
3685
|
<pds-icon icon="clipboard" size="sm"></pds-icon>
|
|
3686
3686
|
Copy HTML
|
|
3687
|
-
`},2e3)})})},100)}async loadShiki(){if(this.#a)return this.#a;if(this.#e){for(;this.#e;)await new Promise(r=>setTimeout(r,
|
|
3687
|
+
`},2e3)})})},100)}async loadShiki(){if(this.#a)return this.#a;if(this.#e){for(;this.#e;)await new Promise(r=>setTimeout(r,50));return this.#a}this.#e=!0;try{let r=await import("https://esm.sh/shiki@1.0.0");return this.#a=await r.getHighlighter({themes:["github-dark","github-light"],langs:["html","css","javascript","json"]}),this.#a}catch(r){return console.error("Failed to load Shiki:",r),null}finally{this.#e=!1}}async highlightWithShiki(r,e="html"){let t=await this.loadShiki();if(!t)return this.escapeHTML(r);try{let o=document.documentElement.getAttribute("data-theme")==="dark"?"github-dark":"github-light",s=t.codeToHtml(r,{lang:e,theme:o}).match(/<code[^>]*>([\s\S]*)<\/code>/);return s?s[1]:this.escapeHTML(r)}catch(a){return console.error("Shiki highlighting failed:",a),this.escapeHTML(r)}}escapeHTML(r){return r.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}async getShowdownConverter(){if(this._showdown)return this._showdown;let r=await this.loadShowdownFromCDN();return!r||!r.Converter?null:(this._showdown=new r.Converter({ghCompatibleHeaderId:!0,tables:!0,strikethrough:!0,tasklists:!0}),this._showdown)}async loadShowdownFromCDN(){if(typeof window<"u"&&window.showdown)return window.showdown;if(this._showdownLoading){for(;this._showdownLoading;)await new Promise(e=>setTimeout(e,50));return window.showdown||null}this._showdownLoading=!0;let r=["https://cdn.jsdelivr.net/npm/showdown@2.1.0/dist/showdown.min.js","https://unpkg.com/showdown@2.1.0/dist/showdown.min.js"];for(let e of r)try{if(await this._injectScript(e,"showdown"),window.showdown)return this._showdownLoading=!1,window.showdown}catch{}return this._showdownLoading=!1,null}_injectScript(r,e){return new Promise((t,a)=>{if(document.querySelector(`script[data-lib="${e}"][src="${r}"]`)){setTimeout(t,0);return}let o=document.createElement("script");o.src=r,o.async=!0,o.defer=!0,o.dataset.lib=e||"lib",o.onload=()=>t(),o.onerror=()=>{o.remove(),a(new Error(`Failed to load script: ${r}`))},document.head.appendChild(o)})}scrollToRelevantSection(r){console.log("\u{1F3AF} Scrolling to section for field:",r);let e=r.startsWith("/")?r.slice(1):r;console.log(" Normalized path:",e);let t={"colors/primary":"color-system","colors/secondary":"color-system","colors/accent":"color-system","colors/background":"color-system","colors/success":"color-system","colors/warning":"color-system","colors/danger":"color-system","colors/info":"color-system","typography/":"typography","spatialRhythm/":"spacing","layers/":"surfaces-shadows","shape/":"buttons","behavior/transitionSpeed":"interactive-states","behavior/":"interactive-states","components/forms":"forms","components/alerts":"alerts","components/badges":"badges","components/tables":"tables","components/toasts":"toasts","components/modals":"modals","components/tabStrip":"tabs","icons/":"icons"},a=null;for(let[o,i]of Object.entries(t))if(e.startsWith(o)){a=i,console.log(` \u2713 Matched pattern "${o}" \u2192 section "${i}"`);break}if(a){let o=this.querySelector(`[data-section="${a}"]`);console.log(` Searching for section: [data-section="${a}"]`,o?"\u2713 Found":"\u2717 Not found"),o?(o.scrollIntoView({behavior:"smooth",block:"start"}),o.style.transition="background-color 0.3s ease",o.style.backgroundColor="var(--color-primary-50)",setTimeout(()=>{o.style.backgroundColor=""},1500),console.log(" \u2713 Scrolled and highlighted section")):console.warn(` \u2717 Section [data-section="${a}"] not found in DOM`)}else console.warn(` \u2717 No section mapping found for field: ${r}`)}renderDisabledSection(r,e){return S`
|
|
3688
3688
|
<section class="showcase-section disabled">
|
|
3689
3689
|
<h2>${r}</h2>
|
|
3690
3690
|
<p class="disabled-message">${e}</p>
|
|
@@ -53,7 +53,7 @@ const DEFAULT_OPTIONS = {
|
|
|
53
53
|
*
|
|
54
54
|
* 5. Completely custom actions (hides default buttons):
|
|
55
55
|
* <pds-jsonform .jsonSchema=${schema} hide-actions>
|
|
56
|
-
* <div slot="actions"
|
|
56
|
+
* <div slot="actions" class="flex gap-md">
|
|
57
57
|
* <button type="submit" class="btn btn-primary">Custom Submit</button>
|
|
58
58
|
* <button type="button" class="btn">Custom Action</button>
|
|
59
59
|
* </div>
|
|
@@ -416,10 +416,7 @@ export class SchemaForm extends LitElement {
|
|
|
416
416
|
render() {
|
|
417
417
|
const tree = this.#compiled;
|
|
418
418
|
if (!tree)
|
|
419
|
-
return html`<div
|
|
420
|
-
class="pds-jsonform-error"
|
|
421
|
-
style="color: red; padding: 1rem; border: 1px solid red; background: #fee;"
|
|
422
|
-
>
|
|
419
|
+
return html`<div class="alert alert-error">
|
|
423
420
|
<p>Failed to generate form schema.</p>
|
|
424
421
|
<pre>${JSON.stringify(this.#data, null, 2)}</pre>
|
|
425
422
|
</div>`;
|
|
@@ -431,6 +428,7 @@ export class SchemaForm extends LitElement {
|
|
|
431
428
|
: "post";
|
|
432
429
|
return html`
|
|
433
430
|
<form
|
|
431
|
+
?data-required=${this.hasAttribute("data-required")}
|
|
434
432
|
method=${m}
|
|
435
433
|
action=${this.action ?? nothing}
|
|
436
434
|
@submit=${this.#onSubmit}
|
|
@@ -501,9 +499,8 @@ export class SchemaForm extends LitElement {
|
|
|
501
499
|
// Check for surface wrapping
|
|
502
500
|
const surface = ui?.["ui:surface"] || pathOptions.surface;
|
|
503
501
|
|
|
504
|
-
// Build layout classes
|
|
502
|
+
// Build layout classes using PDS utilities
|
|
505
503
|
const layoutClasses = [];
|
|
506
|
-
let layoutStyle = "";
|
|
507
504
|
const layoutOptions = ui?.["ui:layoutOptions"] || {};
|
|
508
505
|
|
|
509
506
|
if (layout === "flex") {
|
|
@@ -511,16 +508,8 @@ export class SchemaForm extends LitElement {
|
|
|
511
508
|
if (layoutOptions.wrap) layoutClasses.push("flex-wrap");
|
|
512
509
|
if (layoutOptions.direction === "column") layoutClasses.push("flex-col");
|
|
513
510
|
if (layoutOptions.gap) {
|
|
514
|
-
//
|
|
515
|
-
|
|
516
|
-
layoutOptions.gap.startsWith("var(") ||
|
|
517
|
-
layoutOptions.gap.includes("px") ||
|
|
518
|
-
layoutOptions.gap.includes("rem")
|
|
519
|
-
) {
|
|
520
|
-
layoutStyle += `gap: ${layoutOptions.gap};`;
|
|
521
|
-
} else {
|
|
522
|
-
layoutClasses.push(`gap-${layoutOptions.gap}`);
|
|
523
|
-
}
|
|
511
|
+
// Use PDS gap utility classes (xs, sm, md, lg, xl, 0)
|
|
512
|
+
layoutClasses.push(`gap-${layoutOptions.gap}`);
|
|
524
513
|
}
|
|
525
514
|
} else if (layout === "grid") {
|
|
526
515
|
layoutClasses.push("grid");
|
|
@@ -532,16 +521,8 @@ export class SchemaForm extends LitElement {
|
|
|
532
521
|
layoutClasses.push(`grid-cols-${cols}`);
|
|
533
522
|
}
|
|
534
523
|
if (layoutOptions.gap) {
|
|
535
|
-
//
|
|
536
|
-
|
|
537
|
-
layoutOptions.gap.startsWith("var(") ||
|
|
538
|
-
layoutOptions.gap.includes("px") ||
|
|
539
|
-
layoutOptions.gap.includes("rem")
|
|
540
|
-
) {
|
|
541
|
-
layoutStyle += `gap: ${layoutOptions.gap};`;
|
|
542
|
-
} else {
|
|
543
|
-
layoutClasses.push(`gap-${layoutOptions.gap}`);
|
|
544
|
-
}
|
|
524
|
+
// Use PDS gap utility classes (xs, sm, md, lg, xl, 0)
|
|
525
|
+
layoutClasses.push(`gap-${layoutOptions.gap}`);
|
|
545
526
|
}
|
|
546
527
|
}
|
|
547
528
|
|
|
@@ -553,7 +534,6 @@ export class SchemaForm extends LitElement {
|
|
|
553
534
|
<fieldset
|
|
554
535
|
data-path=${node.path}
|
|
555
536
|
class=${ifDefined(fieldsetClass)}
|
|
556
|
-
style=${ifDefined(layoutStyle || undefined)}
|
|
557
537
|
>
|
|
558
538
|
${!this.hideLegend && !context.hideLegend
|
|
559
539
|
? html`<legend>${legend}</legend>`
|
|
@@ -1612,7 +1592,7 @@ export class SchemaForm extends LitElement {
|
|
|
1612
1592
|
richtextOpts.placeholder || attrs.placeholder
|
|
1613
1593
|
)}
|
|
1614
1594
|
.value=${value ?? ""}
|
|
1615
|
-
toolbar=${
|
|
1595
|
+
?toolbar=${richtextOpts.toolbar}
|
|
1616
1596
|
?required=${!!attrs.required}
|
|
1617
1597
|
?submit-on-enter=${richtextOpts.submitOnEnter ?? false}
|
|
1618
1598
|
spellcheck=${richtextOpts.spellcheck ?? true ? "true" : "false"}
|
package/readme.md
CHANGED
|
@@ -6,18 +6,25 @@
|
|
|
6
6
|
[](#license)
|
|
7
7
|
[](https://www.npmjs.com/package/@pure-ds/core)
|
|
8
8
|
|
|
9
|
-
**Why build a design system when you can generate one?**
|
|
10
|
-
|
|
11
9
|

|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
## With Great Standards Comes Great Power
|
|
12
|
+
|
|
13
|
+
**The browser is the framework. Semantic HTML is the component model. Web Standards are enough.**
|
|
14
|
+
|
|
15
|
+
PDS is a **configuration-first, standards-only design system generator**. Not a framework. Not a CSS library. Not tied to any toolchain.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
You write a small JavaScript config. PDS generates:
|
|
18
|
+
- **Deterministic CSS** (global and Constructable Stylesheets for Web Components)
|
|
19
|
+
- **A complete token hierarchy** (inspect via `PDS.compiled` in DevTools)
|
|
20
|
+
- **Zero-specificity primitives** (`:where()` selectorsβyour CSS always wins)
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
Everything is optional. Use only tokens, or CSS, or add enhancements, or include components. Nothing forces itself into your project.
|
|
23
|
+
|
|
24
|
+
### The Config
|
|
19
25
|
|
|
20
26
|
```javascript
|
|
27
|
+
// pds.config.js
|
|
21
28
|
export const config = {
|
|
22
29
|
design: {
|
|
23
30
|
colors: { primary: '#007acc', secondary: '#5c2d91' },
|
|
@@ -27,43 +34,87 @@ export const config = {
|
|
|
27
34
|
}
|
|
28
35
|
```
|
|
29
36
|
|
|
30
|
-
###
|
|
37
|
+
### The Result
|
|
31
38
|
|
|
32
39
|
```javascript
|
|
40
|
+
// app.js
|
|
33
41
|
import { PDS } from '@pure-ds/core';
|
|
34
42
|
import { config } from './pds.config.js';
|
|
35
43
|
|
|
36
44
|
await PDS.start(config);
|
|
45
|
+
// That's it. Start writing semantic HTML.
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## The PDS Philosophy
|
|
51
|
+
|
|
52
|
+
### Semantic Classes First
|
|
53
|
+
PDS generates **high-level primitives** that style semantic HTML:
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
```html
|
|
56
|
+
<article class="card">...</article>
|
|
57
|
+
<button class="btn-primary">Save</button>
|
|
58
|
+
<div class="alert alert-success">Done!</div>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Layout UtilitiesβSparingly
|
|
62
|
+
A **small set** of layout utilities for composition:
|
|
63
|
+
|
|
64
|
+
```html
|
|
65
|
+
<div class="flex gap-md items-center">...</div>
|
|
66
|
+
<section class="stack-lg">...</section>
|
|
40
67
|
```
|
|
41
68
|
|
|
42
|
-
**
|
|
69
|
+
No `.text-blue-500`. No `.p-4`. No `.rounded-lg`. **Spacing, colors, radii are tokensβnot classes.**
|
|
43
70
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
71
|
+
### Inline Styles? Only for Tokens
|
|
72
|
+
The **only** valid `style=""` in PDS sets CSS custom properties:
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<!-- β Token override -->
|
|
76
|
+
<section style="--surface-bg: var(--color-primary-50);">
|
|
77
|
+
|
|
78
|
+
<!-- β Never do this -->
|
|
79
|
+
<div style="display: flex; gap: 16px;">
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Why PDS Exists
|
|
85
|
+
|
|
86
|
+
| The Old Way | The PDS Way |
|
|
87
|
+
|-------------|-------------|
|
|
88
|
+
| `class="flex items-center gap-4 p-6 bg-white rounded-lg shadow-md"` | `class="card"` |
|
|
89
|
+
| `style="color: #007acc;"` | Uses `--color-primary-500` token |
|
|
90
|
+
| Import a Button component | `<button class="btn-primary">` |
|
|
91
|
+
| 47 utility classes per element | Semantic class + maybe one layout utility |
|
|
92
|
+
|
|
93
|
+
**The result:** Readable HTML. Inspectable CSS. Sites that work without JS. Code that lasts decades.
|
|
94
|
+
|
|
95
|
+
PDS follows the [Pure Web Manifesto](https://pureweb.dev/manifesto)βsustainable architecture for long-lived applications.
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Key Features
|
|
100
|
+
|
|
101
|
+
- π¨ **Configuration-Driven** β Single source of truth generates everything
|
|
102
|
+
- π **Live or Static** β Runtime generation or pre-built bundles
|
|
103
|
+
- π― **Framework Agnostic** β Vanilla, Lit, React, Vue, Svelte, Next.js
|
|
104
|
+
- π **Web Standards** β EventTarget API, Constructable Stylesheets, Shadow DOM
|
|
105
|
+
- π§© **Progressive Enhancement** β Semantic HTML first, enhance where needed
|
|
106
|
+
- βΏ **Accessibility Built-in** β WCAG AA validation, contrast checking
|
|
107
|
+
- π¦ **Zero Build Required** β Works directly in browsers
|
|
108
|
+
- π **IDE IntelliSense** β Full autocomplete via Custom Elements Manifest
|
|
55
109
|
|
|
56
110
|
---
|
|
57
111
|
|
|
58
112
|
## Table of Contents
|
|
59
113
|
|
|
60
|
-
- [
|
|
114
|
+
- [The Three Layers](#the-three-layers)
|
|
61
115
|
- [Who is it For?](#who-is-it-for)
|
|
62
116
|
- [Getting Started](#getting-started)
|
|
63
117
|
- [Core Architecture](#core-architecture)
|
|
64
|
-
- [1. Style Generation & Injection](#1-style-generation--injection)
|
|
65
|
-
- [2. Progressive Enhancements](#2-progressive-enhancements)
|
|
66
|
-
- [3. Web Components](#3-web-components)
|
|
67
118
|
- [Styling Layers](#styling-layers)
|
|
68
119
|
- [Shadow DOM Adoption](#shadow-dom-adoption)
|
|
69
120
|
- [Icon System](#icon-system)
|
|
@@ -81,40 +132,47 @@ await PDS.start(config);
|
|
|
81
132
|
|
|
82
133
|
---
|
|
83
134
|
|
|
84
|
-
##
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- **
|
|
93
|
-
- **
|
|
94
|
-
- **
|
|
95
|
-
- **
|
|
96
|
-
- **
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
- `<pds-
|
|
116
|
-
- `<pds-
|
|
117
|
-
- `<pds-
|
|
135
|
+
## The Three Layers
|
|
136
|
+
|
|
137
|
+
PDS is built on **three fully optional layers**, each powered by your config:
|
|
138
|
+
|
|
139
|
+
### 1. Styles β Deterministic Global CSS
|
|
140
|
+
|
|
141
|
+
Your config generates:
|
|
142
|
+
- **Color scales** (50β900 from base colors)
|
|
143
|
+
- **Surface semantics** (bg, text, border, shadow, states)
|
|
144
|
+
- **Spacing system** (mathematical progression)
|
|
145
|
+
- **Typography scale** (modular scale from base)
|
|
146
|
+
- **Primitives** (`.btn-primary`, `.card`, `.badge`)
|
|
147
|
+
- **Utilities** (`.flex`, `.gap-md`, `.stack-lg`)
|
|
148
|
+
|
|
149
|
+
All exported as CSS Custom Properties. Zero specificity via `:where()`.
|
|
150
|
+
Same values available in JS via `PDS.compiled.tokens`.
|
|
151
|
+
|
|
152
|
+
### 2. Enhancements β Semantic HTML Made Powerful
|
|
153
|
+
|
|
154
|
+
Optional selector-based upgrades that run in Light DOM and Shadow DOM:
|
|
155
|
+
- Required fields show markers and help text automatically
|
|
156
|
+
- `<label data-toggle>` becomes a switch
|
|
157
|
+
- `<nav data-dropdown>` gets dropdown behavior
|
|
158
|
+
- `<dialog>` gets better focus management
|
|
159
|
+
- Form elements gain consistent theming
|
|
160
|
+
|
|
161
|
+
**Think: HTML β UX upgrades, zero integration work.**
|
|
162
|
+
|
|
163
|
+
### 3. Components β Auto-defined, Lazy-loaded Web Components
|
|
164
|
+
|
|
165
|
+
A growing set of PDS components:
|
|
166
|
+
- `<pds-icon>` β SVG sprite icons
|
|
167
|
+
- `<pds-drawer>` β Slide-out panels
|
|
168
|
+
- `<pds-tabstrip>` β Accessible tabs
|
|
169
|
+
- `<pds-jsonform>` β Forms from JSON Schema
|
|
170
|
+
- `<pds-upload>` β Drag & drop file upload
|
|
171
|
+
- `<pds-richtext>` β Rich text / Markdown editor
|
|
172
|
+
- `<pds-splitpanel>` β Resizable panes
|
|
173
|
+
- `<pds-toaster>` β Toast notifications
|
|
174
|
+
|
|
175
|
+
Auto-defined when used. Lazy-loaded via dynamic imports. Styled by your tokens. Zero dependencies.
|
|
118
176
|
|
|
119
177
|
### How It Works
|
|
120
178
|
|
|
@@ -146,7 +204,7 @@ Optional components loaded on demand:
|
|
|
146
204
|
β Tokens: --color-primary-500, --spacing-4 β
|
|
147
205
|
β Primitives: .btn-primary, .card, .badge β
|
|
148
206
|
β Components: <pds-drawer>, <pds-icon> β
|
|
149
|
-
β Utilities: .flex, .gap-
|
|
207
|
+
β Utilities: .flex, .gap-md, .border-gradient β
|
|
150
208
|
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
151
209
|
```
|
|
152
210
|
|