feat: move and expand Ebox playground references

Keep the runner generic, evaluate executable property expressions with ETAF-compatible quote semantics, and add the complete Grid reference beside the migrated Basic, Comprehensive, Flex, and Responsive fixtures.
This commit is contained in:
Kinneyzhang 2026-08-05 20:36:35 +08:00
parent 748a578c31
commit 1e0568c7f9
14 changed files with 1041 additions and 58 deletions

View File

@ -4,7 +4,7 @@
- Status: Active
- Last refreshed: 2026-08-05
- Primary product surfaces: The `.ebox` files under `examples/`, the buffer opened by `ebox-playground-open`, and the generic file mode.
- Evidence reviewed: `ebox-playground.el`, `examples/public-layout-gallery.ebox`, `tests/ebox-playground-tests.el`, `README.md`, `README.zh-CN.md`, and the rendered current example.
- Evidence reviewed: `ebox-playground.el`, the `.ebox` fixtures under `examples/`, `tests/ebox-playground-tests.el`, `README.md`, `README.zh-CN.md`, and rendered gallery/reference previews.
## Brand
- Personality: Precise, modern, restrained, and educational.
@ -44,7 +44,7 @@
- Existing components to reuse: `ebox-create`, `ebox-column`, `ebox-row`, `ebox-flex`, `ebox-grid`, and `ebox-spacer`.
- New/changed components: Header band, capability section band, fixed/fractional track cards, placement cards, and public-contract footer.
- Variants and states: Static semantic color families only.
- Token/component ownership: Palette values and example composition live in `.ebox` fixtures; the runner owns only parsing and rendering.
- Token/component ownership: Palette values and example composition live in `.ebox` fixtures; the runner owns only parsing and rendering. The migrated references remain data files, so adding a layout does not add a fixture-specific Elisp branch.
## Accessibility
- Target standard: High contrast and readable labels in ordinary GUI Emacs themes.

View File

@ -4,7 +4,7 @@
- 状态:有效
- 最近更新2026-08-05
- 主要产品界面:`examples/` 下的 `.ebox` 文件、`ebox-playground-open` 打开的缓冲区,以及通用文件模式。
- 已审阅证据:`ebox-playground.el`、`examples/public-layout-gallery.ebox`、`tests/ebox-playground-tests.el`、`README.md`、`README.zh-CN.md` 和当前示例的实际渲染。
- 已审阅证据:`ebox-playground.el`、`examples/` 下的 `.ebox` fixture、`tests/ebox-playground-tests.el`、`README.md`、`README.zh-CN.md` 以及画廊/参考文件的实际渲染。
## 品牌
- 个性:精确、现代、克制、适合教学。
@ -44,7 +44,7 @@
- 复用的现有组件:`ebox-create`、`ebox-column`、`ebox-row`、`ebox-flex`、`ebox-grid` 和 `ebox-spacer`
- 新增/调整的组件:标题带、能力分区标题带、固定/分数轨道卡片、放置卡片和公开契约页脚。
- 变体与状态:仅使用静态语义色组。
- Token/组件归属:配色值和示例组合放在 `.ebox` fixture 中;运行器只负责解析和渲染。
- Token/组件归属:配色值和示例组合放在 `.ebox` fixture 中;运行器只负责解析和渲染。迁移来的参考仍然是数据文件,新增布局不需要在 Elisp 中增加针对 fixture 的分支。
## 无障碍
- 目标标准:在常见 GUI Emacs 主题下保持高对比度和标签可读性。

View File

@ -1,6 +1,6 @@
# ebox-playground
`ebox-playground` is the independent, generic file runner for Ebox DSL examples. It uses only public Ebox APIs; concrete layouts live in readable `.ebox` fixtures under `examples/`. The maintained gallery demonstrates fixed and fractional tracks, explicit Grid placement, and public Column/Flex/Grid composition with a restrained terracotta, sage, blue, and violet palette.
`ebox-playground` is the independent, generic file runner for Ebox DSL examples. It uses only public Ebox APIs; concrete layouts live in readable `.ebox` fixtures under `examples/`. The migrated low-level fixtures include the original Basic, Comprehensive, Flex, and Responsive references, while `grid-reference.ebox` provides a complete Grid property gallery with the same restrained terracotta, sage, blue, violet, ochre, and teal visual language.
The gallery owns one definite 720 px canvas so its nested Grid examples cannot each reinterpret the surrounding viewport width. It fits a compact GUI Emacs window with at least 760 px of body width. `etaf-playground` remains the separate higher-level Component and Runtime showcase.
@ -15,4 +15,12 @@ The package also owns the `.ebox` file boundary. Visiting an Ebox DSL file selec
(add-to-list 'auto-mode-alist '("\\.ebox\\'" . ebox-dsl-mode))
```
The reference fixtures live directly under `examples/`. For example:
```elisp
(ebox-playground-open-file
(expand-file-name "examples/grid-reference.ebox"
ebox-playground-directory))
```
Run `make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs` from this directory.

View File

@ -1,6 +1,6 @@
# ebox-playground
`ebox-playground` 是 Ebox DSL 的独立通用文件运行器,只使用 Ebox 公共 API具体布局都放在 `examples/` 下易读的 `.ebox` fixture 中。维护中的画廊通过这些源文件展示固定与分数轨道、显式 Grid 放置,以及公开的 Column/Flex/Grid 组合能力,并使用克制的陶土色、鼠尾草绿、蓝色与紫色配色
`ebox-playground` 是 Ebox DSL 的独立通用文件运行器,只使用 Ebox 公共 API具体布局都放在 `examples/` 下易读的 `.ebox` fixture 中。迁移后的低层 fixture 包含原有的 Basic、Comprehensive、Flex 和 Responsive 参考;`grid-reference.ebox` 则用同一套克制的陶土色、鼠尾草绿、蓝色、赭石色和青绿色视觉语言,完整展示 Grid 属性
画廊统一拥有一个明确的 720 px 画布,因此嵌套 Grid 不会各自重新解释外层视口宽度。只要 GUI Emacs 的正文区域不小于 760 px它就能完整显示。高层 Component/Runtime 展示仍由独立的 `etaf-playground` 提供。
@ -15,4 +15,12 @@
(add-to-list 'auto-mode-alist '("\\.ebox\\'" . ebox-dsl-mode))
```
参考 fixture 直接位于 `examples/` 下。例如:
```elisp
(ebox-playground-open-file
(expand-file-name "examples/grid-reference.ebox"
ebox-playground-directory))
```
在该目录运行 `make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs`

View File

@ -31,8 +31,9 @@
(defun ebox-playground--source-form ()
"Read the single Ebox DSL form from the current buffer."
(ebox-playground--read-form
(buffer-substring-no-properties (point-min) (point-max))))
(ebox-playground--evaluate-form
(ebox-playground--read-form
(buffer-substring-no-properties (point-min) (point-max)))))
;;;###autoload
(define-derived-mode ebox-dsl-mode emacs-lisp-mode "EboxDSL"
@ -71,6 +72,40 @@
:type 'file
:group 'ebox-playground)
(defun ebox-playground--static-value-p (value)
"Return non-nil when VALUE is a literal or explicitly quoted form."
(or (null value) (numberp value) (stringp value) (characterp value)
(keywordp value) (eq value t)
(and (consp value) (memq (car value) '(quote function)))))
(defun ebox-playground--evaluate-value (value)
"Evaluate executable DSL VALUE while preserving literal data values."
(if (ebox-playground--static-value-p value)
(if (and (consp value) (memq (car value) '(quote function)))
(eval value nil)
value)
(eval value nil)))
(defun ebox-playground--evaluate-items (items)
"Evaluate property values and recurse into child forms in ITEMS."
(let (result)
(while items
(let ((item (pop items)))
(if (keywordp item)
(progn
(unless items
(error "Ebox playground: missing value for %S" item))
(push item result)
(push (ebox-playground--evaluate-value (pop items)) result))
(push (ebox-playground--evaluate-form item) result))))
(nreverse result)))
(defun ebox-playground--evaluate-form (form)
"Evaluate property expressions in one structural DSL FORM."
(if (and (consp form) (symbolp (car form)))
(cons (car form) (ebox-playground--evaluate-items (cdr form)))
form))
(defun ebox-playground--read-form (source)
"Read one Ebox DSL form from SOURCE."
(when (string-empty-p (string-trim source))
@ -99,8 +134,9 @@
(defun ebox-playground-view (&optional file)
"Build the Ebox DSL form in FILE without buffer side effects."
(ebox-build
(ebox-playground--read-file
(expand-file-name (or file ebox-playground-default-file)))))
(ebox-playground--evaluate-form
(ebox-playground--read-file
(expand-file-name (or file ebox-playground-default-file))))))
;;;###autoload
(defun ebox-playground-open (&optional buffer-name)

View File

@ -2,4 +2,6 @@
The `.ebox` files in this directory are the concrete layouts consumed by the generic `ebox-playground` runner. They are intentionally ordinary, reader-valid Ebox DSL forms so authors can copy, edit, and open them directly.
`public-layout-gallery.ebox` is the default fixture used by `M-x ebox-playground-open`. To render another fixture, visit it and press `C-c C-c`, or call `ebox-playground-open-file`.
The file itself is the data form: structural forms stay unquoted, while literal list and symbol property values use quote just as they do in `etaf-view` (for example, `:gap '(1 (16))` and `:justify-content 'center`). An unquoted property value is an executable Elisp expression and is evaluated by the runner. The reader itself keeps read-time evaluation disabled.
`public-layout-gallery.ebox` is the default fixture used by `M-x ebox-playground-open`. The migrated low-level references live directly beside it: `basic.ebox`, `comprehensive.ebox`, `flex-reference.ebox`, `responsive-reference.ebox`, and `grid-reference.ebox`. The Grid reference covers track templates, implicit tracks, auto-flow, gaps, placement, spans, item alignment, and content alignment. To render another fixture, visit it and press `C-c C-c`, or call `ebox-playground-open-file`.

View File

@ -2,4 +2,6 @@
此目录下的 `.ebox` 文件是通用 `ebox-playground` 运行器读取的具体布局。它们保持为普通、可读、可被 reader 读取的 Ebox DSL form便于直接复制、编辑和打开。
`public-layout-gallery.ebox``M-x ebox-playground-open` 使用的默认 fixture。要渲染其他 fixture可以访问该文件后按 `C-c C-c`,或调用 `ebox-playground-open-file`
文件本身就是 data form结构表单保持不加 quotelist 和 symbol 类型的字面量属性值要和 `etaf-view` 一样加 quote例如 `:gap '(1 (16))`、`:justify-content 'center`。未加 quote 的属性值会被运行器当作可执行的 Elisp 表达式求值;读取器本身仍然关闭 reader 求值。
`public-layout-gallery.ebox``M-x ebox-playground-open` 使用的默认 fixture。迁移来的低层参考和它并列放在 `examples/` 下:`basic.ebox`、`comprehensive.ebox`、`flex-reference.ebox`、`responsive-reference.ebox` 和 `grid-reference.ebox`。其中 Grid 参考覆盖轨道模板、隐式轨道、auto-flow、gap、placement、span、item 对齐和 content 对齐。要渲染其他 fixture可以访问该文件后按 `C-c C-c`,或调用 `ebox-playground-open-file`

7
examples/basic.ebox Normal file
View File

@ -0,0 +1,7 @@
(box :content "Playground file"
:box-sizing 'border-box
:padding '(1 (12))
:border t
:border-color "#2563EB"
:bgcolor "#EFF6FF"
:color "#1E3A8A")

View File

@ -0,0 +1,45 @@
(column
(box :content "Ebox comprehensive DSL showcase\nThe outer sections omit :width so Playground viewport sizing drives the layout."
:padding '(1 (16)) :border "#334155"
:bgcolor "#F8FAFC" :color "#0F172A" :text-align 'center)
(spacer :height 1)
(box :content "Width rule in this file\nContainer sections use auto width. Fixed widths remain only where the example demonstrates exact item sizing, basis, min-width, or max-width."
:padding '(0 (14)) :border "#64748B"
:bgcolor "#F8FAFC" :color "#111827" :wrap-mode 'word)
(spacer :height 1)
(flex
:margin '(1 0 0 1) :padding '(1 (8))
:border "#475569" :bgcolor "#F8FAFC"
:flex-flow '(row wrap)
:justify-content 'space-between :align-items 'center
:align-content 'flex-start :row-gap 1 :column-gap '(12)
(item
:order -1 :flex-basis '(270) :align-self 'flex-start
(box :content "item order -1\n:flex-basis 270\nalign-self start"
:width '(270) :padding '(0 (8))
:border "#BE185D" :bgcolor "#FCE7F3" :color "#831843"
:wrap-mode 'word))
(item
:flex '(1 1 (220))
(box :content "item :flex (1 1 220)\ngrow shrink basis"
:width '(220) :height 4
:padding '(0 (8)) :border "#1D4ED8"
:bgcolor "#DBEAFE" :color "#111827" :wrap-mode 'word))
(item
:flex-grow 2 :flex-shrink 1 :flex-basis '(240)
:align-self 'center
(box :content "item grow 2\nshrink 1\nbasis 240"
:width '(240) :height 4 :padding '(0 (8))
:border "#B45309" :bgcolor "#FEF3C7" :color "#78350F"
:wrap-mode 'word))
(item :flex-shrink 3
:flex-basis '(190)
:align-self 'flex-end
(box :content "item shrink 3\nbasis 190\nalign-self flex-end"
:width '(190) :height 4 :padding '(0 (8))
:border "#15803D" :bgcolor "#DCFCE7" :color "#14532D"
:wrap-mode 'word))))

View File

@ -0,0 +1,463 @@
(column ;; :height (viewport-height)
:width '(viewport)
(box :content "Ebox CSS Flex Reference\nA modern, self-explaining DSL gallery for CSS flex container and item properties."
:padding '(1 (24)) :border "#2F3437"
:bgcolor "#FAF7F0" :color "#1F2328" :text-align 'center)
(spacer :height 1)
(box :content "Container property: :flex-direction\nMain axis selection. Reverse values swap main-start and main-end while keeping source order intact."
:padding '(0 (18)) :border "#D97757"
:bgcolor "#FFF5ED" :color "#3D2B1F")
(flex :flex-flow '(row wrap) :row-gap 1 :column-gap '(12) :padding '(0 (12))
:border "#D97757" :border-top-p nil
:bgcolor "#FFF8F2"
(column
(box :content "row" :width '(220) :padding '(0 (10)) :border "#D97757"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :width '(220) :height 4 :padding '(0 (8)) :border "#D97757"
:border-top-p nil
:bgcolor "#FFF8F2" :flex-direction 'row :column-gap '(10) :align-items 'center
(item (box :content "A" :width '(42) :height 1 :bgcolor "#F7C8B3" :text-align 'center))
(item (box :content "B" :width '(42) :height 1 :bgcolor "#F0A88F" :text-align 'center))
(item (box :content "C" :width '(42) :height 1 :bgcolor "#D97757" :color "#FFFFFF" :text-align 'center))))
(column
(box :content "row-reverse" :width '(220) :padding '(0 (10)) :border "#D97757"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :width '(220) :height 4 :padding '(0 (8)) :border "#D97757"
:border-top-p nil
:bgcolor "#FFF8F2" :flex-direction 'row-reverse :column-gap '(10) :align-items 'center
(item (box :content "A" :width '(42) :height 1 :bgcolor "#F7C8B3" :text-align 'center))
(item (box :content "B" :width '(42) :height 1 :bgcolor "#F0A88F" :text-align 'center))
(item (box :content "C" :width '(42) :height 1 :bgcolor "#D97757" :color "#FFFFFF" :text-align 'center))))
(column
(box :content "column" :width '(220) :padding '(0 (10)) :border "#D97757"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :width '(220) :height 6 :padding '(0 (8)) :border "#D97757"
:border-top-p nil
:bgcolor "#FFF8F2" :flex-direction 'column :row-gap 1 :align-items 'stretch
(item (box :content "A" :height 1 :bgcolor "#F7C8B3" :text-align 'center))
(item (box :content "B" :height 1 :bgcolor "#F0A88F" :text-align 'center))
(item (box :content "C" :height 1 :bgcolor "#D97757" :color "#FFFFFF" :text-align 'center))))
(column
(box :content "column-reverse" :width '(220) :padding '(0 (10)) :border "#D97757"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :width '(220) :height 6 :padding '(0 (8)) :border "#D97757"
:border-top-p nil
:bgcolor "#FFF8F2" :flex-direction 'column-reverse :row-gap 1 :align-items 'stretch
(item (box :content "A" :height 1 :bgcolor "#F7C8B3" :text-align 'center))
(item (box :content "B" :height 1 :bgcolor "#F0A88F" :text-align 'center))
(item (box :content "C" :height 1 :bgcolor "#D97757" :color "#FFFFFF" :text-align 'center)))))
(spacer :height 1)
(box :content "Container property: :flex-wrap\nLine creation. nowrap keeps one flex line; wrap creates new lines; wrap-reverse stacks lines from the opposite cross side."
:padding '(0 (18)) :border "#5E7F6A"
:bgcolor "#F3FAF4" :color "#24352A")
(flex :flex-flow '(row wrap) :row-gap 1 :column-gap '(12) :padding '(0 (12))
:border "#5E7F6A" :border-top-p nil
:bgcolor "#F8FCF8"
(column
(box :content "nowrap" :max-width '(293) :padding '(0 (10)) :border "#5E7F6A"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :max-width '(293) :padding '(0 (8)) :border "#5E7F6A"
:border-top-p nil
:bgcolor "#F8FCF8" :flex-wrap 'nowrap :column-gap '(8) :align-items 'center
(item :flex-shrink 1 (box :content "A" :width '(92) :height 1 :bgcolor "#DCEBDD" :text-align 'center))
(item :flex-shrink 1 (box :content "B" :width '(92) :height 1 :bgcolor "#BFD6C2" :text-align 'center))
(item :flex-shrink 1 (box :content "C" :width '(92) :height 1 :bgcolor "#8FAF95" :text-align 'center))))
(column
(box :content "wrap" :max-width '(293) :padding '(0 (10)) :border "#5E7F6A"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :max-width '(293) :padding '(0 (8)) :border "#5E7F6A"
:border-top-p nil
:bgcolor "#F8FCF8" :flex-wrap 'wrap :column-gap '(8) :row-gap 1
(item :flex-shrink 0 (box :content "A 118" :width '(118) :height 1 :bgcolor "#DCEBDD" :text-align 'center))
(item :flex-shrink 0 (box :content "B 118" :width '(118) :height 1 :bgcolor "#BFD6C2" :text-align 'center))
(item :flex-shrink 0 (box :content "C 118" :width '(118) :height 1 :bgcolor "#8FAF95" :text-align 'center))))
(column
(box :content "wrap-reverse" :max-width '(293) :padding '(0 (10)) :border "#5E7F6A"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :max-width '(293) :padding '(0 (8)) :border "#5E7F6A"
:border-top-p nil
:bgcolor "#F8FCF8" :flex-wrap 'wrap-reverse :column-gap '(8) :row-gap 1
(item :flex-shrink 0 (box :content "A 118" :width '(118) :height 1 :bgcolor "#DCEBDD" :text-align 'center))
(item :flex-shrink 0 (box :content "B 118" :width '(118) :height 1 :bgcolor "#BFD6C2" :text-align 'center))
(item :flex-shrink 0 (box :content "C 118" :width '(118) :height 1 :bgcolor "#8FAF95" :text-align 'center)))))
(spacer :height 1)
(box :content "Container property: :flex-flow\nShorthand for direction and wrapping. These examples use one property to set both behaviors."
:padding '(0 (18)) :border "#6F6A95"
:bgcolor "#F7F4FC" :color "#2F2B45")
(flex :flex-flow '(row wrap) :row-gap 1 :column-gap '(12) :padding '(0 (12))
:border "#6F6A95" :border-top-p nil
:bgcolor "#FBF9FF"
(column
(box :content ":flex-flow (row wrap)" :max-width '(440) :padding '(0 (10))
:border "#6F6A95" :bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :max-width '(440) :padding '(0 (8)) :border "#6F6A95"
:border-top-p nil
:bgcolor "#FBF9FF" :flex-flow '(row wrap) :gap '(1 (12))
(item (box :content "A" :width '(170) :height 1 :bgcolor "#E4DEF4" :text-align 'center))
(item (box :content "B" :width '(170) :height 1 :bgcolor "#CBC2E3" :text-align 'center))
(item (box :content "C wraps" :width '(170) :height 1 :bgcolor "#AFA3D1" :text-align 'center))))
(column
(box :content ":flex-flow (column wrap)" :max-width '(440) :padding '(0 (10))
:border "#6F6A95" :bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :max-width '(440) :padding '(0 (8)) :border "#6F6A95"
:border-top-p nil
:bgcolor "#FBF9FF" :flex-flow '(column wrap) :gap '(1 (12))
(item (box :content "A" :width '(140) :height 1 :bgcolor "#E4DEF4" :text-align 'center))
(item (box :content "B" :width '(140) :height 1 :bgcolor "#CBC2E3" :text-align 'center))
(item (box :content "C new column" :width '(140) :height 1 :bgcolor "#AFA3D1" :text-align 'center)))))
(spacer :height 1)
(box :content "Container property: :justify-content\nMain-axis packing after sizing. Distributed values change the space before, between, and after items."
:padding '(0 (18)) :border "#3F6D88"
:bgcolor "#F1F8FB" :color "#20313A")
(flex :flex-flow '(row wrap) :row-gap 1 :column-gap '(12) :padding '(0 (12))
:border "#3F6D88" :border-top-p nil
:bgcolor "#F6FBFD"
(column
(box :content "flex-start" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'flex-start
(item (box :content "A" :width '(28) :height 1 :bgcolor "#CAE6F0" :text-align 'center))
(item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :text-align 'center))))
(column
(box :content "center" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'center
(item (box :content "A" :width '(28) :height 1 :bgcolor "#CAE6F0" :text-align 'center))
(item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :text-align 'center))))
(column
(box :content "flex-end" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'flex-end
(item (box :content "A" :width '(28) :height 1 :bgcolor "#CAE6F0" :text-align 'center))
(item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :text-align 'center))))
(column
(box :content "space-between" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'space-between
(item (box :content "A" :width '(28) :height 1 :bgcolor "#CAE6F0" :text-align 'center))
(item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :text-align 'center))))
(column
(box :content "space-around" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'space-around
(item (box :content "A" :width '(28) :height 1 :bgcolor "#CAE6F0" :text-align 'center))
(item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :text-align 'center))))
(column
(box :content "space-evenly" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'space-evenly
(item (box :content "A" :width '(28) :height 1 :bgcolor "#CAE6F0" :text-align 'center))
(item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :text-align 'center))))
(column
(box :content "start" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'start
(item (box :content "start" :width '(70) :height 1 :bgcolor "#CAE6F0" :text-align 'center))))
(column
(box :content "end" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'end
(item (box :content "end" :width '(70) :height 1 :bgcolor "#A8D2E2" :text-align 'center))))
(column
(box :content "left" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'left
(item (box :content "left" :width '(70) :height 1 :bgcolor "#7DB8CE" :text-align 'center))))
(column
(box :content "right" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'right
(item (box :content "right" :width '(70) :height 1 :bgcolor "#5F9CB5" :text-align 'center))))
(column
(box :content "normal" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :border "#3F6D88" :border-top-p nil :bgcolor "#F6FBFD" :justify-content 'normal
(item (box :content "normal" :width '(70) :height 1 :bgcolor "#3F6D88" :color "#FFFFFF" :text-align 'center)))))
(spacer :height 1)
(box :content "Container property: :align-items\nCross-axis alignment inside each flex line. Stretch/normal enlarge auto-sized items; start/end/center move shorter ones."
:padding '(0 (18)) :border "#9A6B43"
:bgcolor "#FFF7EE" :color "#3A2A1D")
(flex :flex-flow '(row wrap) :row-gap 1 :column-gap '(12) :padding '(0 (12))
:border "#9A6B43" :border-top-p nil
:bgcolor "#FFFAF5"
(column
(box :content "flex-start" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 5 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'flex-start
(item (box :content "short" :width '(68) :height 1 :bgcolor "#F1D0AF" :text-align 'center))
(item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :text-align 'center))))
(column
(box :content "center" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 5 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'center
(item (box :content "short" :width '(68) :height 1 :bgcolor "#F1D0AF" :text-align 'center))
(item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :text-align 'center))))
(column
(box :content "flex-end" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 5 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'flex-end
(item (box :content "short" :width '(68) :height 1 :bgcolor "#F1D0AF" :text-align 'center))
(item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :text-align 'center))))
(column
(box :content "stretch" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 5 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'stretch
(item (box :content "auto height stretches" :width '(72) :bgcolor "#F1D0AF" :text-align 'center))
(item (box :content "line height" :width '(78) :height 3 :bgcolor "#D9AD83" :text-align 'center))))
(column
(box :content "baseline" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 5 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'baseline
(item (box :content "baseline" :width '(90) :height 1 :bgcolor "#BA8456" :text-align 'center))))
(column
(box :content "start" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :height 4 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'start
(item (box :content "start" :width '(62) :height 1 :bgcolor "#F1D0AF" :text-align 'center))))
(column
(box :content "end" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :height 4 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'end
(item (box :content "end" :width '(62) :height 1 :bgcolor "#D9AD83" :text-align 'center))))
(column
(box :content "self-start" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :height 4 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'self-start
(item (box :content "self-start" :width '(78) :height 1 :bgcolor "#BA8456" :text-align 'center))))
(column
(box :content "self-end" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :height 4 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'self-end
(item (box :content "self-end" :width '(70) :height 1 :bgcolor "#9A6B43" :color "#FFFFFF" :text-align 'center))))
(column
(box :content "normal" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(146) :height 4 :border "#9A6B43" :border-top-p nil :bgcolor "#FFFAF5" :align-items 'normal
(item (box :content "normal" :width '(62) :bgcolor "#7A5132" :color "#FFFFFF" :text-align 'center))))
(column
(box :content "stretch note" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :text-align 'center)
(box :content "normal behaves stretch-like for auto cross size in this DSL." :width '(146)
:height 4 :padding '(0 (8)) :border "#9A6B43" :border-top-p nil
:bgcolor "#FFFAF5" :color "#3A2A1D")))
(spacer :height 1)
(box :content "Container property: :align-content\nCross-axis packing between multiple flex lines. It has no visible effect on a nowrap single-line container."
:padding '(0 (18)) :border "#7C6A2F"
:bgcolor "#FBF8E8" :color "#362F16")
(flex :flex-flow '(row wrap) :row-gap 1 :column-gap '(12) :padding '(0 (12))
:border "#7C6A2F" :border-top-p nil
:bgcolor "#FFFDF0"
(column
(box :content "flex-start" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'flex-start :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "center" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'center :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "flex-end" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'flex-end :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "space-between" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'space-between :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "space-around" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'space-around :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "space-evenly" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'space-evenly :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "stretch" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'stretch :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "start" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'start :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "end" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'end :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "baseline" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'baseline :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "normal" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'wrap :align-content 'normal :row-gap 1 :column-gap '(8)
(item (box :content "A" :width '(78) :height 1 :bgcolor "#EEE1A8" :text-align 'center))
(item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :text-align 'center))
(item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :text-align 'center))))
(column
(box :content "nowrap note" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :text-align 'center)
(flex :width '(176) :height 7 :border "#7C6A2F" :border-top-p nil :bgcolor "#FFFDF0"
:flex-wrap 'nowrap :align-content 'normal :row-gap 1 :column-gap '(8)
(item (box :content "single line" :width '(96) :height 1 :bgcolor "#EEE1A8" :text-align 'center)))))
(spacer :height 1)
(box :content "Container properties: :gap, :row-gap, :column-gap\n:gaps define spacing between flex items and flex lines. Explicit row/column values override matching sides."
:padding '(0 (18)) :border "#3D7068"
:bgcolor "#F1FAF8" :color "#1D3935")
(flex :flex-flow '(row wrap) :row-gap 1 :column-gap '(12) :padding '(0 (12))
:border "#3D7068" :border-top-p nil
:bgcolor "#F7FCFB"
(column
(box :content ":gap (1 (14))" :max-width '(293) :padding '(0 (10)) :border "#3D7068"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :max-width '(293)
:border "#3D7068" :border-top-p nil :bgcolor "#F7FCFB"
:flex-wrap 'wrap :gap '(1 (14))
(item (box :content "A" :width '(104) :height 1 :bgcolor "#CDEBE5" :text-align 'center))
(item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :text-align 'center))
(item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :text-align 'center))))
(column
(box :content ":row-gap 2" :max-width '(293) :padding '(0 (10)) :border "#3D7068"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :max-width '(293) :border "#3D7068" :border-top-p nil :bgcolor "#F7FCFB"
:flex-wrap 'wrap :row-gap 2 :column-gap '(8)
(item (box :content "A" :width '(104) :height 1 :bgcolor "#CDEBE5" :text-align 'center))
(item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :text-align 'center))
(item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :text-align 'center))))
(column
(box :content ":column-gap (32)" :max-width '(293) :padding '(0 (10)) :border "#3D7068"
:bgcolor "#FFFDF8" :color "#3D3A35" :text-align 'center)
(flex :max-width '(293) :border "#3D7068" :border-top-p nil :bgcolor "#F7FCFB"
:flex-wrap 'wrap :row-gap 1 :column-gap '(32)
(item (box :content "A" :width '(104) :height 1 :bgcolor "#CDEBE5" :text-align 'center))
(item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :text-align 'center))
(item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :text-align 'center)))))
(spacer :height 1)
(box :content "Item property: :order\nVisual ordering sorts by :order, then source order. The labels expose both values."
:padding '(0 (18)) :border "#8E4A63"
:bgcolor "#FFF4F7" :color "#3A1F2A")
(flex :height 5 :padding '(0 (12)) :border "#8E4A63"
:border-top-p nil
:bgcolor "#FFF8FA" :column-gap '(12) :align-items 'center
(item :order 3 (box :content "source 1\n:order 3" :width '(150) :height 2 :bgcolor "#F2C4D2" :text-align 'center))
(item :order 1 (box :content "source 2\n:order 1" :width '(150) :height 2 :bgcolor "#DEA4B7" :text-align 'center))
(item :order 2 (box :content "source 3\n:order 2" :width '(150) :height 2 :bgcolor "#C87F9A" :text-align 'center))
(item :order 1 (box :content "source 4\nsame order" :width '(150) :height 2 :bgcolor "#A95F7C" :color "#FFFFFF" :text-align 'center)))
(spacer :height 1)
(box :content "Item sizing: :flex-grow, :flex-shrink, :flex-basis, and child :width\nFlex starts from basis or width/content, then distributes positive or negative space."
:padding '(0 (18)) :border "#2F5F7C"
:bgcolor "#F1F8FC" :color "#1C303D")
(column
(box :content "Grow factors: same basis, different growth weights."
:padding '(0 (12)) :border "#2F5F7C" :border-top-p nil
:bgcolor "#FFFDF8" :color "#3D3A35")
(flex :padding '(0 (12)) :border "#2F5F7C"
:border-top-p nil
:bgcolor "#F8FCFE" :column-gap '(10)
(item :flex-grow 1 :flex-basis '(120) (box :content "grow 1\nbasis 120" :height 2 :bgcolor "#C7E3F0" :text-align 'center))
(item :flex-grow 2 :flex-basis '(120) (box :content "grow 2\nbasis 120" :height 2 :bgcolor "#96C7DD" :text-align 'center))
(item :flex-grow 3 :flex-basis '(120) (box :content "grow 3\nbasis 120" :height 2 :bgcolor "#5FA4C2" :text-align 'center))))
(column
(box :content "Shrink factors: overflowing bases lose space by weighted shrink."
:padding '(0 (12)) :border "#2F5F7C" :border-top-p nil
:bgcolor "#FFFDF8" :color "#3D3A35")
(flex :padding '(0 (12)) :border "#2F5F7C"
:border-top-p nil
:bgcolor "#F8FCFE" :column-gap '(10)
(item :flex-shrink 1 :flex-basis '(320) (box :content "shrink 1\nbasis 320" :height 2 :bgcolor "#C7E3F0" :text-align 'center))
(item :flex-shrink 3 :flex-basis '(320) (box :content "shrink 3\nbasis 320" :height 2 :bgcolor "#96C7DD" :text-align 'center))
(item :flex-shrink 5 :flex-basis '(320) (box :content "shrink 5\nbasis 320" :height 2 :bgcolor "#5FA4C2" :text-align 'center))))
(column
(box :content "Basis versus width: explicit basis overrides width; auto basis uses width/content."
:padding '(0 (12)) :border "#2F5F7C" :border-top-p nil
:bgcolor "#FFFDF8" :color "#3D3A35")
(flex :padding '(0 (12)) :border "#2F5F7C"
:border-top-p nil
:bgcolor "#F8FCFE" :column-gap '(10)
(item :flex-basis '(120) (box :content "basis 120\nwidth 240" :width '(240) :height 2 :bgcolor "#C7E3F0" :text-align 'center))
(item :flex-basis 'auto (box :content "basis auto\nwidth 240" :width '(240) :height 2 :bgcolor "#96C7DD" :text-align 'center))
(item :flex '(1 1 (160)) (box :content "flex tuple\nbasis 160" :width '(240) :height 2 :bgcolor "#5FA4C2" :text-align 'center))))
(column
(box :content "Intrinsic basis keywords: content, min-content, and max-content derive size from text."
:padding '(0 (12)) :border "#2F5F7C" :border-top-p nil
:bgcolor "#FFFDF8" :color "#3D3A35")
(flex :padding '(0 (12)) :border "#2F5F7C"
:border-top-p nil
:bgcolor "#F8FCFE" :flex-wrap 'wrap :row-gap 1 :column-gap '(10)
(item :flex-shrink 0 :flex-basis 'content
(box :content "content\nnatural text" :width '(180) :height 2 :bgcolor "#C7E3F0" :text-align 'center))
(item :flex-shrink 0 :flex-basis 'min-content
(box :content "min-content\nwrap choices" :width '(180) :height 2 :bgcolor "#96C7DD" :text-align 'center))
(item :flex-shrink 0 :flex-basis 'max-content
(box :content "max-content\nkeeps phrase width" :width '(180) :height 2 :bgcolor "#5FA4C2" :text-align 'center))))
(spacer :height 1)
(box :content "Item property: :flex shorthand\nSupported forms: number, none, auto, initial, and (grow shrink basis). Longhands can override shorthand parts."
:padding '(0 (18)) :border "#52612F"
:bgcolor "#F8FAEF" :color "#293116")
(flex :height 7 :padding '(0 (12)) :border "#52612F"
:border-top-p nil
:bgcolor "#FCFDF6" :flex-wrap 'wrap :row-gap 1 :column-gap '(8) :align-items 'center
(item :flex 1 (box :content ":flex 1\n= 1 1 0" :bgcolor "#DDE8B2" :text-align 'center))
(item :flex 'none (box :content ":flex none\n= 0 0 auto" :width '(140) :bgcolor "#C5D681" :text-align 'center))
(item :flex 'auto (box :content ":flex auto\n= 1 1 auto" :width '(140) :bgcolor "#AABE59" :text-align 'center))
(item :flex 'initial (box :content ":flex initial\n= 0 1 auto" :width '(140) :bgcolor "#8D9E3C" :text-align 'center))
(item :flex '(2 1 (120)) :flex-basis '(160)
(box :content ":flex (2 1 120)\nthen basis 160" :bgcolor "#66762D" :color "#FFFFFF" :text-align 'center)))
(spacer :height 1)
(box :content "Item property: :align-self\nPer-item cross-axis override. auto inherits :align-items; other values replace the container setting."
:padding '(0 (18)) :border "#7B4D38"
:bgcolor "#FFF6F0" :color "#392319")
(flex :height 7 :padding '(0 (12)) :border "#7B4D38"
:border-top-p nil
:bgcolor "#FFFAF7" :align-items 'flex-start
:flex-wrap 'wrap :row-gap 1 :column-gap '(10)
(item :align-self 'auto (box :content "auto\ninherits start" :width '(118) :height 2 :bgcolor "#EFC7B6" :text-align 'center))
(item :align-self 'flex-start (box :content "flex-start" :width '(118) :height 2 :bgcolor "#DBA58D" :text-align 'center))
(item :align-self 'center (box :content "center" :width '(118) :height 2 :bgcolor "#BF7F63" :text-align 'center))
(item :align-self 'flex-end (box :content "flex-end" :width '(118) :height 2 :bgcolor "#9D6048" :color "#FFFFFF" :text-align 'center))
(item :align-self 'stretch (box :content "stretch\nauto cross" :width '(118) :bgcolor "#7B4D38" :color "#FFFFFF" :text-align 'center))
(item :align-self 'normal (box :content "normal\nstretch-like" :width '(118) :bgcolor "#5D3929" :color "#FFFFFF" :text-align 'center)))
(flex :height 6 :padding '(0 (12)) :border "#7B4D38"
:border-top-p nil
:bgcolor "#FFFAF7" :align-items 'flex-start
:flex-wrap 'wrap :row-gap 1 :column-gap '(10)
(item :align-self 'start (box :content "start" :width '(118) :height 2 :bgcolor "#EFC7B6" :text-align 'center))
(item :align-self 'end (box :content "end" :width '(118) :height 2 :bgcolor "#DBA58D" :text-align 'center))
(item :align-self 'self-start (box :content "self-start" :width '(118) :height 2 :bgcolor "#BF7F63" :text-align 'center))
(item :align-self 'self-end (box :content "self-end" :width '(118) :height 2 :bgcolor "#9D6048" :color "#FFFFFF" :text-align 'center))
(item :align-self 'baseline (box :content "baseline" :width '(118) :height 2 :bgcolor "#7B4D38" :color "#FFFFFF" :text-align 'center)))
(spacer :height 1)
(box :content "Summary\nUse direction and wrap to create lines, basis/width to choose starting sizes, grow/shrink to distribute space, then justify/align properties to pack items and lines."
:padding '(1 (24)) :border "#2F3437"
:bgcolor "#FAF7F0" :color "#1F2328" :text-align 'center))

View File

@ -0,0 +1,289 @@
(column
(box :content "Ebox CSS Grid Reference\nA visual guide to every Grid property in the public Ebox DSL."
:width '(720)
:padding '(1 (20))
:border "#2E3740"
:color "#20252B"
:bgcolor "#FFFDF8"
:face 'bold
:text-align 'center)
(spacer :height 1)
(box :content "01 TRACK SIZING\nFixed pixels, intrinsic auto tracks, and fractional space share one definite canvas."
:width '(720)
:padding '(0 (16))
:border "#B85C3C"
:border-bottom-p nil
:color "#4C271D"
:bgcolor "#F1D4C9"
:wrap-mode 'word)
(grid
:width '(720)
:grid-template-columns '((176) auto 1fr)
:grid-template-rows '(2)
:gap '(1 (10))
:justify-content 'space-between
:align-content 'center
:border "#B85C3C"
:bgcolor "#FFF9F4"
(box :content "FIXED\n176 PX"
:padding '(0 (8))
:color "#542A1D"
:bgcolor "#F1D4C9"
:text-align 'center)
(box :content "AUTO\nINTRINSIC"
:padding '(0 (8))
:color "#31543C"
:bgcolor "#DCEBDD"
:text-align 'center)
(box :content "1FR\nREMAINDER"
:padding '(0 (8))
:color "#28485B"
:bgcolor "#D9EAF2"
:text-align 'center))
(spacer :height 1)
(box :content "02 TRACK FUNCTIONS\n`minmax` protects a readable minimum while `repeat` expands a compact template."
:width '(720)
:padding '(0 (16))
:border "#537A63"
:border-bottom-p nil
:color "#26402F"
:bgcolor "#DCEBDD"
:wrap-mode 'word)
(grid
:width '(720)
:grid-template-columns '((minmax (150) (220)) (repeat 1 (fr 1)) 1fr)
:grid-template-rows '(2 2)
:grid-row-gap 1
:grid-column-gap '(12)
:justify-content 'start
:align-content 'stretch
:border "#537A63"
:bgcolor "#F7FBF7"
(box :content "MINMAX\n150 → 220"
:padding '(0 (8))
:color "#26402F"
:bgcolor "#DCEBDD"
:text-align 'center)
(box :content "REPEAT\n(fr 1)"
:padding '(0 (8))
:color "#26402F"
:bgcolor "#C8DDCB"
:text-align 'center)
(box :content "1FR\nSYMBOL"
:padding '(0 (8))
:color "#26402F"
:bgcolor "#B5D0BA"
:text-align 'center)
(box :content "EXPLICIT ROW 2\nTRACK TEMPLATE"
:padding '(0 (8))
:color "#26402F"
:bgcolor "#EDF5EE"
:text-align 'center)
(box :content "EXPLICIT ROW 2\nREPEAT + 1FR"
:padding '(0 (8))
:color "#26402F"
:bgcolor "#E3F0E5"
:text-align 'center)
(box :content "MINMAX\nMAX 220"
:padding '(0 (8))
:color "#26402F"
:bgcolor "#D4E6D7"
:text-align 'center))
(spacer :height 1)
(box :content "03 IMPLICIT TRACKS + AUTO-FLOW\nDeclared tracks establish the map; auto rows, auto columns, and flow fill the rest."
:width '(720)
:padding '(0 (16))
:border "#416E8A"
:border-bottom-p nil
:color "#203A4A"
:bgcolor "#D9EAF2"
:wrap-mode 'word)
(box :content "ROW FLOW · :grid-auto-rows 2"
:width '(720)
:padding '(0 (12))
:border "#416E8A"
:border-bottom-p nil
:color "#203A4A"
:bgcolor "#EEF7FB"
:text-align 'center)
(grid
:width '(720)
:grid-template-columns '((220) (220) (220))
:grid-template-rows '(2)
:grid-auto-rows 2
:grid-auto-flow 'row
:gap '(1 (10))
:border "#416E8A"
:bgcolor "#F6FBFD"
(box :content "ROW 1 / A" :padding '(0 (8)) :color "#203A4A" :bgcolor "#D9EAF2" :text-align 'center)
(box :content "ROW 1 / B" :padding '(0 (8)) :color "#203A4A" :bgcolor "#C3DFEA" :text-align 'center)
(box :content "ROW 1 / C" :padding '(0 (8)) :color "#203A4A" :bgcolor "#A9D0DE" :text-align 'center)
(box :content "AUTO ROW 2 / A" :padding '(0 (8)) :color "#203A4A" :bgcolor "#E5F2F7" :text-align 'center)
(box :content "AUTO ROW 2 / B" :padding '(0 (8)) :color "#203A4A" :bgcolor "#D7EAF1" :text-align 'center)
(box :content "AUTO ROW 2 / C" :padding '(0 (8)) :color "#203A4A" :bgcolor "#C8E1EB" :text-align 'center))
(spacer :height 1)
(box :content "COLUMN FLOW · :grid-auto-columns ((220))"
:width '(720)
:padding '(0 (12))
:border "#416E8A"
:border-bottom-p nil
:color "#203A4A"
:bgcolor "#EEF7FB"
:text-align 'center)
(grid
:width '(720)
:grid-template-columns '((220))
:grid-template-rows '(2 2)
:grid-auto-columns '((220))
:grid-auto-flow 'column
:row-gap 1
:column-gap '(10)
:border "#416E8A"
:bgcolor "#F6FBFD"
(box :content "COL 1 / ROW 1" :padding '(0 (8)) :color "#203A4A" :bgcolor "#D9EAF2" :text-align 'center)
(box :content "COL 1 / ROW 2" :padding '(0 (8)) :color "#203A4A" :bgcolor "#C3DFEA" :text-align 'center)
(box :content "AUTO COL 2 / ROW 1" :padding '(0 (8)) :color "#203A4A" :bgcolor "#A9D0DE" :text-align 'center)
(box :content "AUTO COL 2 / ROW 2" :padding '(0 (8)) :color "#203A4A" :bgcolor "#E5F2F7" :text-align 'center))
(spacer :height 1)
(box :content "04 PLACEMENT + SPANS\nGrid items can target one-based rows and columns, span tracks, or fall back to auto-placement."
:width '(720)
:padding '(0 (16))
:border "#6A5C8B"
:border-bottom-p nil
:color "#332B4B"
:bgcolor "#E7E2F1"
:wrap-mode 'word)
(grid
:width '(720)
:grid-template-columns '((170) (170) (170) 1fr)
:grid-template-rows '(2 2 2)
:gap '(1 (10))
:justify-content 'center
:align-content 'center
:border "#6A5C8B"
:bgcolor "#FBF9FF"
(grid-item :grid-column '(1 :span 3) :grid-row 1
(box :content "COLUMN 1 → 3\n:grid-column (1 :span 3)"
:padding '(0 (8)) :color "#332B4B" :bgcolor "#E7E2F1" :text-align 'center))
(grid-item :grid-column 4 :grid-row 1
(box :content "C4\nEXPLICIT"
:padding '(0 (8)) :color "#332B4B" :bgcolor "#D8D0E8" :text-align 'center))
(grid-item :grid-column 1 :grid-row 2 :grid-row-span 2
(box :content "R2R3\nROW SPAN"
:padding '(0 (8)) :color "#332B4B" :bgcolor "#CBC2E3" :text-align 'center))
(grid-item :grid-column 2 :grid-row 2 :grid-column-span 2
(box :content "C2C3\nCOLUMN SPAN"
:padding '(0 (8)) :color "#332B4B" :bgcolor "#B9ADD8" :text-align 'center))
(grid-item :grid-column 4 :grid-row 2
(box :content "R2 C4\nPLACED"
:padding '(0 (8)) :color "#332B4B" :bgcolor "#AFA3D1" :text-align 'center))
(box :content "AUTO\nREMAINDER"
:padding '(0 (8)) :color "#332B4B" :bgcolor "#EDE9F6" :text-align 'center))
(spacer :height 1)
(box :content "05 ITEM ALIGNMENT\n`:justify-items` controls the inline axis; `:align-items` controls the block axis inside each cell."
:width '(720)
:padding '(0 (16))
:border "#B07836"
:border-bottom-p nil
:color "#513718"
:bgcolor "#F3E4C7"
:wrap-mode 'word)
(box :content "START / START"
:width '(720) :padding '(0 (12)) :border "#B07836" :border-bottom-p nil
:color "#513718" :bgcolor "#FBF4E6" :text-align 'center)
(grid
:width '(720) :height 3
:grid-template-columns '((220) (220) (220)) :grid-template-rows '(3)
:gap '(1 (10)) :justify-items 'start :align-items 'start
:border "#B07836" :bgcolor "#FFF9ED"
(box :content "START" :padding '(0 (8)) :color "#513718" :bgcolor "#F3E4C7" :text-align 'center)
(box :content "INLINE START" :padding '(0 (8)) :color "#513718" :bgcolor "#EAD2A6" :text-align 'center)
(box :content "BLOCK START" :padding '(0 (8)) :color "#513718" :bgcolor "#E1C18A" :text-align 'center))
(box :content "CENTER / CENTER"
:width '(720) :padding '(0 (12)) :border "#B07836" :border-bottom-p nil
:color "#513718" :bgcolor "#FBF4E6" :text-align 'center)
(grid
:width '(720) :height 3
:grid-template-columns '((220) (220) (220)) :grid-template-rows '(3)
:gap '(1 (10)) :justify-items 'center :align-items 'center
:border "#B07836" :bgcolor "#FFF9ED"
(box :content "CENTER" :padding '(0 (8)) :color "#513718" :bgcolor "#F3E4C7" :text-align 'center)
(box :content "INLINE CENTER" :padding '(0 (8)) :color "#513718" :bgcolor "#EAD2A6" :text-align 'center)
(box :content "BLOCK CENTER" :padding '(0 (8)) :color "#513718" :bgcolor "#E1C18A" :text-align 'center))
(box :content "END / STRETCH"
:width '(720) :padding '(0 (12)) :border "#B07836" :border-bottom-p nil
:color "#513718" :bgcolor "#FBF4E6" :text-align 'center)
(grid
:width '(720) :height 3
:grid-template-columns '((220) (220) (220)) :grid-template-rows '(3)
:gap '(1 (10)) :justify-items 'end :align-items 'stretch
:border "#B07836" :bgcolor "#FFF9ED"
(box :content "END" :padding '(0 (8)) :color "#513718" :bgcolor "#F3E4C7" :text-align 'center)
(box :content "INLINE END" :padding '(0 (8)) :color "#513718" :bgcolor "#EAD2A6" :text-align 'center)
(box :content "BLOCK STRETCH" :padding '(0 (8)) :color "#513718" :bgcolor "#E1C18A" :text-align 'center))
(spacer :height 1)
(box :content "06 CONTENT ALIGNMENT\nWhen tracks do not consume the full canvas, `:justify-content` and `:align-content` distribute the leftover space."
:width '(720)
:padding '(0 (16))
:border "#2F7180"
:border-bottom-p nil
:color "#1E4148"
:bgcolor "#CFE8EA"
:wrap-mode 'word)
(box :content "CENTER / CENTER"
:width '(720) :padding '(0 (12)) :border "#2F7180" :border-bottom-p nil
:color "#1E4148" :bgcolor "#EEF9F9" :text-align 'center)
(grid
:width '(720) :height 8
:grid-template-columns '((140) (140) (140)) :grid-template-rows '(2 2)
:gap '(1 (10)) :justify-content 'center :align-content 'center
:justify-items 'center :align-items 'center
:border "#2F7180" :bgcolor "#F6FCFC"
(box :content "A" :padding '(0 (8)) :color "#1E4148" :bgcolor "#CFE8EA" :text-align 'center)
(box :content "B" :padding '(0 (8)) :color "#1E4148" :bgcolor "#B5DDE0" :text-align 'center)
(box :content "C" :padding '(0 (8)) :color "#1E4148" :bgcolor "#9ACED2" :text-align 'center)
(box :content "D" :padding '(0 (8)) :color "#1E4148" :bgcolor "#DDF1F2" :text-align 'center)
(box :content "E" :padding '(0 (8)) :color "#1E4148" :bgcolor "#C7E7E9" :text-align 'center)
(box :content "F" :padding '(0 (8)) :color "#1E4148" :bgcolor "#ADD9DC" :text-align 'center))
(box :content "SPACE-BETWEEN / SPACE-AROUND"
:width '(720) :padding '(0 (12)) :border "#2F7180" :border-bottom-p nil
:color "#1E4148" :bgcolor "#EEF9F9" :text-align 'center)
(grid
:width '(720) :height 8
:grid-template-columns '((140) (140) (140)) :grid-template-rows '(2 2)
:gap '(1 (10)) :justify-content 'space-between :align-content 'space-around
:justify-items 'center :align-items 'center
:border "#2F7180" :bgcolor "#F6FCFC"
(box :content "SPACE" :padding '(0 (8)) :color "#1E4148" :bgcolor "#CFE8EA" :text-align 'center)
(box :content "BETWEEN" :padding '(0 (8)) :color "#1E4148" :bgcolor "#B5DDE0" :text-align 'center)
(box :content "AROUND" :padding '(0 (8)) :color "#1E4148" :bgcolor "#9ACED2" :text-align 'center)
(box :content "ROW 2" :padding '(0 (8)) :color "#1E4148" :bgcolor "#DDF1F2" :text-align 'center))
(box :content "END / SPACE-EVENLY"
:width '(720) :padding '(0 (12)) :border "#2F7180" :border-bottom-p nil
:color "#1E4148" :bgcolor "#EEF9F9" :text-align 'center)
(grid
:width '(720) :height 8
:grid-template-columns '((140) (140) (140)) :grid-template-rows '(2 2)
:gap '(1 (10)) :justify-content 'end :align-content 'space-evenly
:justify-items 'center :align-items 'center
:border "#2F7180" :bgcolor "#F6FCFC"
(box :content "END" :padding '(0 (8)) :color "#1E4148" :bgcolor "#CFE8EA" :text-align 'center)
(box :content "EVEN" :padding '(0 (8)) :color "#1E4148" :bgcolor "#B5DDE0" :text-align 'center)
(box :content "LY" :padding '(0 (8)) :color "#1E4148" :bgcolor "#9ACED2" :text-align 'center)
(box :content "TRACKS" :padding '(0 (8)) :color "#1E4148" :bgcolor "#DDF1F2" :text-align 'center))
(spacer :height 1)
(box :content "GRID TOOLKIT :grid-template-columns · :grid-template-rows · :grid-auto-columns · :grid-auto-rows · :grid-auto-flow · :gap · :grid-row-gap · :grid-column-gap · :justify-items · :align-items · :justify-content · :align-content · :grid-column · :grid-row · :grid-column-span · :grid-row-span"
:width '(720)
:padding '(0 (16))
:border "#2E3740"
:color "#FFFFFF"
:bgcolor "#3D4B55"
:wrap-mode 'word
:text-align 'center))

View File

@ -1,98 +1,98 @@
(column
(box :content "Ebox Playground\nPixel-precise layout, composed only from public nodes."
:width (720)
:padding (1 (18))
:width '(720)
:padding '(1 (18))
:border "#8F432F"
:color "#252A2E"
:bgcolor "#FFFDF8"
:face bold
:text-align center)
:face 'bold
:text-align 'center)
(spacer :height 1)
(box :content "01 FIXED + FRACTIONAL\nA definite Grid width divides into one exact track and one flexible track."
:width (720)
:padding (0 (16))
:width '(720)
:padding '(0 (16))
:border "#C97252"
:border-bottom-p nil
:color "#252A2E"
:bgcolor "#F1D4C9"
:wrap-mode word)
:wrap-mode 'word)
(grid
:width (718)
:grid-template-columns ((220) 1fr)
:gap (1 (12))
:width '(720)
:grid-template-columns '((220) 1fr)
:gap '(1 (12))
:border "#C97252"
:bgcolor "#FFF9F5"
(box :content "FIXED TRACK\n220 px stays exact"
:width (220) :padding (1 0) :text-align center
:width '(220) :padding '(1 0) :text-align 'center
:color "#252A2E" :bgcolor "#F1D4C9")
(box :content "FRACTIONAL TRACK\n1fr receives the remaining width"
:width (486) :padding (1 0) :text-align center
:width '(486) :padding '(1 0) :text-align 'center
:color "#252A2E" :bgcolor "#F9E8E0")
(box :content "The Grid owns one definite width; its tracks divide that space without competing viewport calculations."
:grid-column (1 :span 2) :width (718) :padding (1 0)
:grid-column '(1 :span 2) :width '(718) :padding '(1 0)
:color "#663322" :bgcolor "#FFFDF8"
:text-align center :wrap-mode word))
:text-align 'center :wrap-mode 'word))
(spacer :height 1)
(box :content "02 EXPLICIT PLACEMENT\nSpan columns, target cells, and leave the remaining nodes to auto placement."
:width (720)
:padding (0 (16))
:width '(720)
:padding '(0 (16))
:border "#6D8A73"
:border-bottom-p nil
:color "#252A2E"
:bgcolor "#DCEBDD"
:wrap-mode word)
:wrap-mode 'word)
(grid
:width (718)
:grid-template-columns ((210) (210) 1fr)
:gap (1 (10))
:width '(720)
:grid-template-columns '((210) (210) 1fr)
:gap '(1 (10))
:border "#6D8A73"
:bgcolor "#F7FBF7"
(box :content "SPAN 1 → 3" :grid-column (1 :span 2)
:width (430) :padding (1 0) :text-align center
(box :content "SPAN 1 → 3" :grid-column '(1 :span 2)
:width '(430) :padding '(1 0) :text-align 'center
:color "#24422D" :bgcolor "#DCEBDD")
(box :content "COLUMN 3" :grid-column 3 :grid-row 1
:width (278) :padding (1 0) :text-align center
:width '(278) :padding '(1 0) :text-align 'center
:color "#24422D" :bgcolor "#C8DDCB")
(box :content "ROW 2 / COL 1" :grid-column 1 :grid-row 2
:width (210) :padding (1 0) :text-align center
:width '(210) :padding '(1 0) :text-align 'center
:color "#24422D" :bgcolor "#EDF5EE")
(box :content "AUTO PLACED" :width (210) :padding (1 0)
:text-align center :color "#24422D" :bgcolor "#E3F0E5")
(box :content "STABLE CELLS" :width (278) :padding (1 0)
:text-align center :color "#24422D" :bgcolor "#D4E6D7"))
(box :content "AUTO PLACED" :width '(210) :padding '(1 0)
:text-align 'center :color "#24422D" :bgcolor "#E3F0E5")
(box :content "STABLE CELLS" :width '(278) :padding '(1 0)
:text-align 'center :color "#24422D" :bgcolor "#D4E6D7"))
(spacer :height 1)
(box :content "03 PUBLIC COMPOSITION\nColumn, Flex, and Grid share the same public node contract."
:width (720)
:padding (0 (16))
:width '(720)
:padding '(0 (16))
:border "#4E7890"
:border-bottom-p nil
:color "#252A2E"
:bgcolor "#D9EAF2"
:wrap-mode word)
:wrap-mode 'word)
(flex
:width (720)
:flex-flow (row wrap)
:gap (1 (12))
:padding (1 (12))
:width '(720)
:flex-flow '(row wrap)
:gap '(1 (12))
:padding '(1 (12))
:border "#4E7890"
:bgcolor "#F7FAFC"
(box :content "COLUMN\nvertical composition"
:flex-grow 1 :flex-shrink 1 :flex-basis (200)
:min-width (180) :padding (1 (12))
:flex-grow 1 :flex-shrink 1 :flex-basis '(200)
:min-width '(180) :padding '(1 (12))
:color "#193846" :bgcolor "#D9EAF2")
(box :content "FLEX\nwrap and distribute"
:flex-grow 1 :flex-shrink 1 :flex-basis (200)
:min-width (180) :padding (1 (12))
:flex-grow 1 :flex-shrink 1 :flex-basis '(200)
:min-width '(180) :padding '(1 (12))
:color "#3F3655" :bgcolor "#E7E2F1")
(box :content "GRID\nplace in two dimensions"
:flex-grow 1 :flex-shrink 1 :flex-basis (200)
:min-width (180) :padding (1 (12))
:flex-grow 1 :flex-shrink 1 :flex-basis '(200)
:min-width '(180) :padding '(1 (12))
:color "#6B3020" :bgcolor "#F1D4C9"))
(spacer :height 1)
(box :content "PUBLIC CONTRACT ebox-create · ebox-column · ebox-flex · ebox-grid · ebox-spacer"
:width (720)
:padding (0 (16))
:width '(720)
:padding '(0 (16))
:border "#8D887F"
:color "#4D5651"
:bgcolor "#EEEAE2"
:wrap-mode word))
:wrap-mode 'word))

View File

@ -0,0 +1,65 @@
(column
:margin-left 1 :margin-top 1 :padding '(1 2 1 2) :bgcolor "linen"
(box :content "Ebox Responsive Layout\nDrag the preview window: sections stretch to the current viewport, cards reflow, and text wraps without changing the window layout."
:padding '(1 (18)) :border "#334155" :text-align 'center
:color "#000" :bgcolor "ghost white")
(spacer :height 1)
(box :content "No explicit viewport width here\nThis page mostly omits :width. The Playground binds the current preview width while rendering, so auto width behaves like a responsive container."
:padding '(0 (16)) :border "#0F766E"
:bgcolor "#F0FDFA" :color "#134E4A" :wrap-mode 'word)
(spacer :height 1)
(box :content "Adaptive toolbar\nButtons below keep stable sizes. The row packs as many as fit and then wraps."
:padding '(0 (16)) :border "#EA580C" :border-bottom-p nil
:bgcolor "#FFF7ED" :color "#431407" :wrap-mode 'word)
(flex :flex-flow '(row wrap) :column-gap '(8) :row-gap 1
:padding '(1 (8)) :border "#FDBA74" :bgcolor "#FFFBF5"
(box :content "Render" :width '(96) :padding '(0 (8))
:border "#EA580C" :bgcolor "#FED7AA" :color "#111827"
:text-align 'center)
(box :content "Save" :width '(88) :padding '(0 (8))
:border "#D97706" :bgcolor "#FEF3C7" :color "#111827"
:text-align 'center)
(box :content "Format" :width '(96) :padding '(0 (8))
:border "#16A34A" :bgcolor "#DCFCE7" :color "#14532D"
:text-align 'center)
(box :content "Sample" :width '(104) :padding '(0 (8))
:border "#0284C7" :bgcolor "#E0F2FE" :color "#0C4A6E"
:text-align 'center)
(box :content "Write" :width '(88) :padding '(0 (8))
:border "#9333EA" :bgcolor "#F3E8FF" :color "#3B0764"
:text-align 'center))
(spacer :height 1)
(box :content "Responsive cards\nEach card starts from a flex-basis, grows when space is available, and wraps to new rows when the preview becomes narrow."
:padding '(0 (16)) :border "#2563EB" :border-bottom-p nil
:bgcolor "#EFF6FF" :color "#1E3A8A" :wrap-mode 'word)
(flex :flex-flow '(row wrap) :column-gap '(12) :row-gap 1
:padding '(1 (8)) :border "#93C5FD" :bgcolor "#F8FBFF"
:justify-content 'center
(item :flex '(1 1 (210))
(box :content "Overview\nThe row uses the current preview width automatically."
:min-width '(180) :max-width '(280)
:padding '(0 (10)) ;; :border "#2563EB"
:bgcolor "#DBEAFE" :color "#111827" :wrap-mode 'word))
(item :flex '(1 1 (210))
(box :content "Editor\nDrag slowly or quickly; resize rendering is debounced."
:min-width '(180) :max-width '(300)
:padding '(0 (10)) ;; :border "#4F46E5"
:bgcolor "#E0E7FF" :color "#111827" :wrap-mode 'word))
(item :flex '(1 1 (210))
(box :content "Preview\nThe preview buffer rerenders after the viewport settles."
:min-width '(180) :max-width '(320)
:padding '(0 (10)) ;; :border "#7C3AED"
:bgcolor "#EDE9FE" :color "#111827" :wrap-mode 'word))
(item :flex '(1 1 (210))
(box :content "Flow\nCards keep readable widths instead of overflowing."
:min-width '(180) :max-width '(280)
:padding '(0 (10)) ;; :border "#0891B2"
:bgcolor "#CFFAFE" :color "#164E63" :wrap-mode 'word)))
(spacer :height 1)
(box :content "What to watch\nNarrow preview: cards stack and text wraps. Wide preview: cards share a row and expand up to max-width. The source/preview split is preserved."
:padding '(0 (16)) :border "#475569"
:bgcolor "#F8FAFC" :color "#0F172A" :wrap-mode 'word))

View File

@ -1,8 +1,22 @@
;;; ebox-playground-tests.el --- Public Ebox example tests -*- lexical-binding: t; -*-
(require 'ert)
(require 'cl-lib)
(require 'ebox-playground)
(defconst ebox-playground-test--fixtures
'("basic.ebox" "comprehensive.ebox" "flex-reference.ebox"
"responsive-reference.ebox" "grid-reference.ebox")
"Migrated and maintained `.ebox' fixtures in the playground package.")
(defun ebox-playground-test--dynamic-width ()
"Return a list-valued width from executable DSL property code."
'(240))
(defun ebox-playground-test--dynamic-color ()
"Return a color from executable DSL property code."
"#123456")
(ert-deftest ebox-playground-uses-only-public-layout-apis ()
"The example should build and render through the public Ebox entry point."
(let* ((node (ebox-playground-view))
@ -22,7 +36,37 @@
(should (file-readable-p ebox-playground-default-file))
(should (string-suffix-p ".ebox" ebox-playground-default-file))
(should-not (string-match-p "FIXED TRACK" runner))
(should-not (string-match-p "PUBLIC COMPOSITION" runner))))
(should-not (string-match-p "PUBLIC COMPOSITION" runner))
(should-not (string-match-p "GRID TOOLKIT" runner))))
(ert-deftest ebox-playground-migrated-fixtures-render-at-the-canvas-width ()
"Every migrated fixture should render through the generic file boundary."
(let ((ebox-viewport-width 720))
(dolist (file ebox-playground-test--fixtures)
(let* ((path (expand-file-name (concat "examples/" file)
ebox-playground-directory))
(text (substring-no-properties
(ebox-render (ebox-playground-view path))))
(widths (mapcar #'ebox-string-pixel-width
(ebox-string-lines text))))
(should (file-readable-p path))
(should (stringp text))
(should (cl-every (lambda (width) (<= width 720)) widths))))))
(ert-deftest ebox-playground-grid-reference-covers-public-properties ()
"The Grid reference should name every public Grid property in the fixture."
(let ((source (with-temp-buffer
(insert-file-contents
(expand-file-name "examples/grid-reference.ebox"
ebox-playground-directory))
(buffer-string))))
(dolist (property '(:grid-template-columns :grid-template-rows
:grid-auto-columns :grid-auto-rows :grid-auto-flow
:grid-row-gap :grid-column-gap :gap
:justify-items :align-items :justify-content
:align-content :grid-column :grid-row
:grid-column-span :grid-row-span))
(should (string-match-p (regexp-quote (symbol-name property)) source)))))
(ert-deftest ebox-playground-opens-and-closes-buffer ()
"The public open and close commands should own their buffer lifecycle."
@ -59,7 +103,7 @@
(unwind-protect
(with-temp-buffer
(setq buffer-file-name "/tmp/ebox-playground-render-test.ebox")
(insert "(box :content \"Rendered from .ebox\" :width (240))")
(insert "(box :content \"Rendered from .ebox\" :width '(240))")
(ebox-dsl-mode)
(ebox-dsl-render)
(with-current-buffer preview
@ -67,6 +111,20 @@
(when (get-buffer preview)
(kill-buffer preview)))))
(ert-deftest ebox-playground-evaluates-property-expressions ()
"Evaluate executable property values while preserving quoted constants."
(let* ((form (ebox-playground--evaluate-form
'(box :content "Dynamic property values"
:width (ebox-playground-test--dynamic-width)
:color (ebox-playground-test--dynamic-color)
:padding '(0 (4))
:text-align 'center)))
(node (ebox-build form)))
(should (equal (plist-get node :width) 240))
(should (equal (plist-get node :color) "#123456"))
(should (equal (plist-get node :padding-left-pixel) 4))
(should (eq (plist-get node :text-align) 'center))))
(provide 'ebox-playground-tests)
;;; ebox-playground-tests.el ends here