diff --git a/DESIGN.md b/DESIGN.md index 9377e34..5e9a226 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -18,19 +18,19 @@ ## Personas and jobs - Primary personas: Ebox authors and maintainers. -- User jobs: Open one buffer, understand the public node contract, and see fixed/fractional tracks, placement, and composition rendered attractively. +- User jobs: Open one buffer, understand the public node contract, and see the complete supported Grid property/value set rendered attractively. - Key contexts of use: GUI Emacs, fullscreen demonstrations, and ERT rendering checks. ## Information architecture - Primary navigation: None; this is one self-contained reference surface. -- Core routes/screens: One header and three capability sections. +- Core routes/screens: One header, grouped property sections, and one public-contract footer. - Content hierarchy: Title and description, labeled capability bands, then small contrasting examples. ## Design principles - Principle 1: Mirror the Flex reference's title—explanation—example rhythm. - Principle 2: Use one restrained semantic accent per capability section. - Principle 3: Keep the runner generic; put each concrete layout in a readable `.ebox` source file. -- Tradeoffs: Prefer a compact representative gallery over exhaustive property coverage. +- Tradeoffs: Cover the complete public Grid property/value surface while keeping each example compact and grouped by responsibility. ## Visual language - Color: Warm paper canvas with terracotta, sage, slate blue, and violet section families; explicit dark ink or white text according to contrast. @@ -42,7 +42,7 @@ ## Components - 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. +- New/changed components: Header band, property section bands, track-function cards, implicit-track/flow cards, gap/placement cards, alignment 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. The migrated references remain data files, so adding a layout does not add a fixture-specific Elisp branch. @@ -54,8 +54,8 @@ - Reduced motion and sensory considerations: No motion. ## Responsive behavior -- Supported breakpoints/devices: GUI Emacs windows with at least 760 px of body width, from compact frames to fullscreen desktop widths. -- Layout adaptations: The gallery owns one fixed 720 px content canvas; nested Grid and Flex sections divide that width through public Ebox layout behavior instead of reading the viewport independently. +- Supported breakpoints/devices: GUI Emacs windows from compact split previews to fullscreen desktop widths. +- Layout adaptations: Standalone gallery entry points default to a 720 px content canvas; split-window `.ebox` previews bind `(viewport)` to the preview pane's display-safe width so viewport-based sections fit the active pane. - Touch/hover differences: None. ## Interaction states @@ -74,7 +74,7 @@ ## Implementation constraints - Framework/styling system: Emacs 29.1+ and public Ebox constructors/properties only. - Design-token constraints: Reuse the Flex reference's restrained color families without introducing a theme package. -- Performance constraints: One synchronous render, no timers or background work. +- Performance constraints: One synchronous initial render; window resizing uses one bounded idle debounce and Ebox's incremental viewport update path. - Compatibility constraints: No ETAF dependency and no `ebox--*` calls. - Test/screenshot expectations: `make check` passes and a fullscreen one-window screenshot shows complete, unclipped labeled sections. diff --git a/Makefile b/Makefile index 9486f90..6a83369 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ EMACS ?= emacs -LOAD_PATH = -L . -L ../ebox +ECSS_DIR ?= ../ecss +TP_DIR ?= ../tp +LOAD_PATH = -L . -L ../ebox -L $(ECSS_DIR) -L $(TP_DIR) .PHONY: all compile test check checkdoc load clean diff --git a/README.md b/README.md index 1eec646..7af2bd6 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ # 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 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. +`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 public Grid property/value 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. +ECSS 0.1.0 and TP 1.0.0 are independent packages and may be installed in either order. Install both before Ebox 2.0.0, then install this package. `ebox-playground` never reaches into those packages' private APIs. -Standalone fixture renders use the same compact 720 px default viewport unless the caller dynamically binds `ebox-viewport-width`; viewport-based references therefore stay inside the example canvas. +Standalone gallery entry points default to a definite 720 px canvas so nested examples have stable space in batch renders and one-window demos. Split-window `.ebox` previews instead bind `(viewport)` to the preview window's display-safe width, so viewport-based separators and sections fit the right-hand pane. `etaf-playground` remains the separate higher-level Component and Runtime showcase. + +Standalone and batch fixture renders use the same compact 720 px default viewport unless the caller dynamically binds `ebox-viewport-width`; viewport-based references therefore stay inside the example canvas. When `C-c C-c` renders an `.ebox` source buffer, the command keeps the source on the left, opens the preview on the right, and binds `(viewport)` to the preview window's display-safe width. The preview then debounces window-size changes and republishes the width through Ebox's incremental viewport update path. ```elisp (require 'ebox-playground) (ebox-playground-open) ``` -The package also owns the `.ebox` file boundary. Visiting an Ebox DSL file selects `ebox-dsl-mode`; its header line shows `C-c C-c Render preview`. Press `C-c C-c` to build the single DSL form, render it through the public Ebox API, and automatically display the preview buffer. +The package also owns the `.ebox` file boundary. Visiting an Ebox DSL file selects `ebox-dsl-mode`; its header line shows `C-c C-c Render preview`. Press `C-c C-c` to build the single DSL form, render it through the public Ebox API, and automatically display the source and preview side by side. ```elisp (add-to-list 'auto-mode-alist '("\\.ebox\\'" . ebox-dsl-mode)) diff --git a/README.zh-CN.md b/README.zh-CN.md index 8eaa9f5..fd1cfe9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,17 +1,19 @@ # ebox-playground -`ebox-playground` 是 Ebox DSL 的独立通用文件运行器,只使用 Ebox 公共 API;具体布局都放在 `examples/` 下易读的 `.ebox` fixture 中。迁移后的低层 fixture 包含原有的 Basic、Comprehensive、Flex 和 Responsive 参考;`grid-reference.ebox` 则用同一套克制的陶土色、鼠尾草绿、蓝色、赭石色和青绿色视觉语言,完整展示 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` 提供。 +ECSS 0.1.0 与 TP 1.0.0 是互相独立的包,安装顺序任意;两者都安装后再安装 Ebox 2.0.0,最后安装本包。`ebox-playground` 不调用这些依赖的私有 API。 -独立 fixture 渲染默认也使用紧凑的 720 px 视口;如果调用方动态绑定了 `ebox-viewport-width`,则保留调用方的值。因此依赖 viewport 的参考例子也会保持在示例画布内。 +独立画廊入口默认使用明确的 720 px 画布,因此 batch 渲染和单窗口演示里的嵌套示例有稳定空间。分屏 `.ebox` 预览则会把 `(viewport)` 绑定为右侧预览窗口的显示安全宽度,所以依赖 viewport 的分隔条和区块会适配右侧窗格。高层 Component/Runtime 展示仍由独立的 `etaf-playground` 提供。 + +独立和 batch fixture 渲染默认也使用紧凑的 720 px 视口;如果调用方动态绑定了 `ebox-viewport-width`,则保留调用方的值。因此依赖 viewport 的参考例子也会保持在示例画布内。通过 `C-c C-c` 渲染 `.ebox` 源文件时,命令会把源文件保留在左侧、在右侧打开预览,并将 `(viewport)` 绑定为预览窗口的显示安全像素宽度;预览随后会合并连续的 window-size 变化,并通过 Ebox 已有的 viewport 增量更新路径发布宽度。 ```elisp (require 'ebox-playground) (ebox-playground-open) ``` -这个包也拥有 `.ebox` 文件入口。访问 Ebox DSL 文件时会自动进入 `ebox-dsl-mode`,header line 会显示 `C-c C-c Render preview`;按 `C-c C-c` 会通过 Ebox 公开 API 构建单个 DSL form、渲染并自动弹出预览 buffer。 +这个包也拥有 `.ebox` 文件入口。访问 Ebox DSL 文件时会自动进入 `ebox-dsl-mode`,header line 会显示 `C-c C-c Render preview`;按 `C-c C-c` 会通过 Ebox 公开 API 构建单个 DSL form、渲染并自动并排显示源文件和预览 buffer。 ```elisp (add-to-list 'auto-mode-alist '("\\.ebox\\'" . ebox-dsl-mode)) diff --git a/ebox-playground.el b/ebox-playground.el index 2c4cefc..39f119d 100644 --- a/ebox-playground.el +++ b/ebox-playground.el @@ -3,7 +3,7 @@ ;; SPDX-License-Identifier: GPL-3.0-or-later ;; Author: Ebox contributors ;; Version: 0.1.0 -;; Package-Requires: ((emacs "29.1") (ebox "1.0.1")) +;; Package-Requires: ((emacs "29.1") (ebox "2.0.0")) ;; URL: https://github.com/ginqi7/ebox-playground ;;; Commentary: @@ -15,10 +15,23 @@ ;;; Code: (require 'ebox) +(require 'cl-lib) (require 'elisp-mode) (require 'subr-x) (defvar read-eval) +(defvar-local ebox-playground--resize-timer nil + "Pending idle timer for the current preview buffer.") + +(defvar-local ebox-playground--preview-viewport-width nil + "Last viewport width published for the current preview buffer.") + +(defvar-local ebox-playground--preview-auto-resize-p nil + "Non-nil when the current preview follows its displaying window.") + +(defvar ebox-playground--tracked-preview-buffers (make-hash-table :test 'eq) + "Preview buffers whose windows should drive viewport updates.") + (defconst ebox-playground-buffer-name "*Ebox Playground*" "Default buffer name used by `ebox-playground-open'.") @@ -49,17 +62,37 @@ ;;;###autoload (defun ebox-dsl-render () - "Render the current Ebox DSL buffer into a preview buffer." + "Render the current DSL buffer beside its right-hand preview." (interactive) - (let ((ebox-viewport-width - (ebox-playground--effective-viewport-width))) - (let* ((source (ebox-playground--source-form)) - (name (format "*Ebox Preview: %s*" - (or (buffer-file-name) (buffer-name)))) - (node (ebox-build source))) - (ebox-display-buffer name node) - (message "Rendered Ebox DSL into %s" name) - (get-buffer name)))) + (delete-other-windows) + (let* ((window-min-height 1) + (window-min-width 1) + (source-window (selected-window)) + (preview-window (split-window-right)) + (preview-width + (ebox-playground--window-viewport-width preview-window)) + (bound-width (and (numberp ebox-viewport-width) + (> ebox-viewport-width 0) + (floor ebox-viewport-width))) + (viewport-width + (or bound-width preview-width + ebox-playground-default-viewport-width)) + (auto-resize-p (and (null bound-width) (numberp preview-width))) + (ebox-viewport-width viewport-width) + (source-buffer (current-buffer)) + (source-form (ebox-playground--source-form)) + (name (format "*Ebox Preview: %s*" + (or (buffer-file-name) (buffer-name)))) + (node (ebox-build source-form)) + (buffer (ebox-playground--render-to-preview-buffer name node))) + (set-window-buffer source-window source-buffer) + (set-window-buffer preview-window buffer) + (select-window source-window) + (ebox-playground--track-preview-buffer + buffer viewport-width auto-resize-p + (plist-get node :bgcolor)) + (message "Rendered Ebox DSL into %s" name) + buffer)) ;;;###autoload (add-to-list 'auto-mode-alist '("\\.ebox\\'" . ebox-dsl-mode)) @@ -68,6 +101,11 @@ "Run Ebox DSL examples from source files." :group 'ebox) +(defcustom ebox-playground-resize-idle-delay 0.05 + "Seconds to wait before publishing a settled preview window width." + :type 'number + :group 'ebox-playground) + (defconst ebox-playground-directory (file-name-directory (or load-file-name buffer-file-name)) "Directory containing the Ebox Playground package.") @@ -84,18 +122,171 @@ :type 'positive-integer :group 'ebox-playground) -(defun ebox-playground--effective-viewport-width () - "Return the caller's viewport width or the compact playground default." - (if (and (numberp ebox-viewport-width) - (> ebox-viewport-width 0)) - (floor ebox-viewport-width) - ebox-playground-default-viewport-width)) +(defun ebox-playground--window-viewport-width (&optional window) + "Return WINDOW's display-safe viewport width in pixels, or nil when unavailable." + (when (window-live-p window) + (let ((width (window-body-width window t))) + (and (numberp width) + (> width 0) + (max 1 (- (floor width) + (max 1 (frame-char-width (window-frame window))))))))) -(defun ebox-playground--render-node-to-buffer (buffer node) - "Render NODE into BUFFER using the effective playground viewport width." - (let ((ebox-viewport-width - (ebox-playground--effective-viewport-width))) - (ebox-render-to-buffer buffer node))) +(defun ebox-playground--effective-viewport-width (&optional window) + "Return the caller's width, WINDOW's text width, or the compact default." + (cond + ((and (numberp ebox-viewport-width) + (> ebox-viewport-width 0)) + (floor ebox-viewport-width)) + ((ebox-playground--window-viewport-width window)) + (t ebox-playground-default-viewport-width))) + +(defun ebox-playground--cancel-resize-timer () + "Cancel the current preview's pending resize publication." + (when (timerp ebox-playground--resize-timer) + (cancel-timer ebox-playground--resize-timer)) + (setq ebox-playground--resize-timer nil)) + +(defun ebox-playground--apply-preview-display-settings (&optional canvas-background) + "Apply canvas redisplay settings to the current preview buffer. +CANVAS-BACKGROUND fills display space outside generated root lines." + (setq-local truncate-lines t) + (setq-local header-line-format nil) + (setq-local mode-line-format nil) + (setq-local auto-hscroll-mode t) + (setq-local fringe-indicator-alist + (cl-remove-if + (lambda (entry) + (memq (car-safe entry) '(truncation continuation))) + (copy-tree fringe-indicator-alist))) + (setq-local bidi-display-reordering nil) + (setq-local bidi-paragraph-direction 'left-to-right) + (setq-local bidi-inhibit-bpa t) + (setq-local face-remapping-alist + (when (stringp canvas-background) + (list (list 'default (list :background canvas-background)))))) + +(defconst ebox-playground--preview-display-variables + '(truncate-lines header-line-format mode-line-format auto-hscroll-mode + fringe-indicator-alist bidi-display-reordering bidi-paragraph-direction + bidi-inhibit-bpa face-remapping-alist) + "Buffer-local display variables owned by a playground preview.") + +(defun ebox-playground--preview-display-snapshot () + "Return the current values and locality of preview display variables." + (mapcar + (lambda (variable) + (list variable + (local-variable-p variable) + (and (boundp variable) (symbol-value variable)))) + ebox-playground--preview-display-variables)) + +(defun ebox-playground--restore-preview-display-snapshot (snapshot) + "Restore preview display variable SNAPSHOT in the current buffer." + (dolist (entry snapshot) + (let ((variable (nth 0 entry)) + (was-local-p (nth 1 entry)) + (value (nth 2 entry))) + (if was-local-p + (set (make-local-variable variable) value) + (kill-local-variable variable))))) + +(defun ebox-playground--render-to-preview-buffer (buffer node) + "Render NODE into BUFFER with transactional preview display settings." + (with-current-buffer (get-buffer-create buffer) + (let ((snapshot (ebox-playground--preview-display-snapshot)) + (published-p nil)) + (unwind-protect + (prog1 + (progn + (ebox-playground--apply-preview-display-settings + (and (consp node) (plist-get node :bgcolor))) + (ebox-render-to-buffer buffer node)) + (setq published-p t)) + (unless published-p + (ebox-playground--restore-preview-display-snapshot snapshot)))))) + +(defun ebox-playground--resize-preview (buffer window) + "Publish BUFFER's current WINDOW viewport after a resize settles." + (when (buffer-live-p buffer) + (with-current-buffer buffer + (setq-local ebox-playground--resize-timer nil) + (when (and ebox-playground--preview-auto-resize-p + (window-live-p window) + (eq (window-buffer window) buffer)) + (let ((width (ebox-playground--window-viewport-width window))) + (when (and width + (/= width + (or ebox-playground--preview-viewport-width 0))) + (let ((report (ebox-rerender-buffer-with-context buffer width))) + ;; Keep the last published width as the retry key. A failed + ;; candidate must not make a later identical resize look like a + ;; successful no-op. + (when (plist-get report :runtime-published) + (setq-local ebox-playground--preview-viewport-width width))))))))) + +(defun ebox-playground--window-size-change (frame) + "Schedule a viewport update after a size change in FRAME. +The default `window-size-change-functions' dispatcher passes a frame, so the +preview window must be resolved from that frame before its viewport is read." + (when (frame-live-p frame) + (dolist (window (window-list frame 'no-minibuf)) + (let ((buffer (window-buffer window))) + (when (and (buffer-live-p buffer) + (gethash buffer ebox-playground--tracked-preview-buffers)) + (with-current-buffer buffer + (let ((width (ebox-playground--window-viewport-width window))) + (when (and width + (/= width + (or ebox-playground--preview-viewport-width 0))) + (ebox-playground--cancel-resize-timer) + (setq-local ebox-playground--resize-timer + (run-with-idle-timer + (max 0 ebox-playground-resize-idle-delay) + nil #'ebox-playground--resize-preview + buffer window)))))))))) + +(defun ebox-playground--untrack-preview-buffer (buffer) + "Stop tracking BUFFER and remove the shared window hook when unused." + (remhash buffer ebox-playground--tracked-preview-buffers) + (when (= (hash-table-count ebox-playground--tracked-preview-buffers) 0) + (remove-hook 'window-size-change-functions + #'ebox-playground--window-size-change))) + +(defun ebox-playground--cleanup-preview () + "Release resize state owned by the current preview buffer." + (ebox-playground--cancel-resize-timer) + (ebox-playground--untrack-preview-buffer (current-buffer))) + +(defun ebox-playground--track-preview-buffer + (buffer viewport-width &optional auto-resize-p canvas-background) + "Track BUFFER at VIEWPORT-WIDTH and optionally follow its display window. +AUTO-RESIZE-P enables following the display window. CANVAS-BACKGROUND fills +display space outside generated root lines." + (ebox-playground--untrack-preview-buffer buffer) + (with-current-buffer buffer + (ebox-playground--cancel-resize-timer) + (ebox-playground--apply-preview-display-settings canvas-background) + (setq-local ebox-playground--preview-viewport-width viewport-width) + (setq-local ebox-playground--preview-auto-resize-p auto-resize-p) + (add-hook 'kill-buffer-hook #'ebox-playground--cleanup-preview nil t)) + (when auto-resize-p + (puthash buffer t ebox-playground--tracked-preview-buffers) + (add-hook 'window-size-change-functions + #'ebox-playground--window-size-change)) + buffer) + +(defun ebox-playground--render-node-to-buffer + (buffer node viewport-width &optional auto-resize-p) + "Render NODE into BUFFER using VIEWPORT-WIDTH. +AUTO-RESIZE-P enables following the display window." + (let* ((ebox-viewport-width + (or viewport-width + (ebox-playground--effective-viewport-width))) + (target buffer)) + (ebox-playground--track-preview-buffer + (ebox-playground--render-to-preview-buffer target node) + ebox-viewport-width auto-resize-p + (and (consp node) (plist-get node :bgcolor))))) (defun ebox-playground--static-value-p (value) "Return non-nil when VALUE is a literal or explicitly quoted form." @@ -156,10 +347,10 @@ (buffer-substring-no-properties (point-min) (point-max))))) ;;;###autoload -(defun ebox-playground-view (&optional file) - "Build the Ebox DSL form in FILE without buffer side effects." +(defun ebox-playground-view (&optional file viewport-width) + "Build the Ebox DSL form in FILE at VIEWPORT-WIDTH without buffer side effects." (let ((ebox-viewport-width - (ebox-playground--effective-viewport-width))) + (or viewport-width (ebox-playground--effective-viewport-width)))) (ebox-build (ebox-playground--evaluate-form (ebox-playground--read-file @@ -169,18 +360,22 @@ (defun ebox-playground-open (&optional buffer-name) "Render the default Ebox DSL example into BUFFER-NAME and return its buffer." (interactive) - (ebox-playground--render-node-to-buffer - (or buffer-name ebox-playground-buffer-name) - (ebox-playground-view))) + (let ((viewport-width (ebox-playground--effective-viewport-width))) + (ebox-playground--render-node-to-buffer + (or buffer-name ebox-playground-buffer-name) + (ebox-playground-view nil viewport-width) + viewport-width))) ;;;###autoload (defun ebox-playground-open-file (file &optional buffer-name) "Render Ebox DSL FILE into BUFFER-NAME and return its buffer." (interactive "fEbox DSL file: ") - (ebox-playground--render-node-to-buffer - (or buffer-name - (format "*Ebox Preview: %s*" (file-name-nondirectory file))) - (ebox-playground-view file))) + (let ((viewport-width (ebox-playground--effective-viewport-width))) + (ebox-playground--render-node-to-buffer + (or buffer-name + (format "*Ebox Preview: %s*" (file-name-nondirectory file))) + (ebox-playground-view file viewport-width) + viewport-width))) ;;;###autoload (defun ebox-playground-close (&optional buffer-name) diff --git a/examples/README.md b/examples/README.md index 5b54d29..9b71258 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,4 +4,4 @@ The `.ebox` files in this directory are the concrete layouts consumed by the gen 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`. +`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 Flex reference owns its document viewport with a root `:overflow 'scroll` box: keyboard line scrolling follows the cursor's innermost scroll owner, mouse wheel scrolling follows the pointer, and residual movement bubbles through nested owners before native buffer scrolling. It keeps its original complete-content semantics during splitter resize. The Grid reference is the complete public property/value gallery: track templates (`auto`, `NFR`, `(fr FACTOR)`, `minmax`, and `repeat`), implicit tracks, row/column auto-flow, gap shorthand/longhands/aliases, placement and spans, and every supported item/content alignment value. It also documents that Ebox supports `row` and `column` auto-flow rather than browser `dense` variants. To render another fixture, visit it and press `C-c C-c`, or call `ebox-playground-open-file`. diff --git a/examples/README.zh-CN.md b/examples/README.zh-CN.md index 659f929..cd6d3a6 100644 --- a/examples/README.zh-CN.md +++ b/examples/README.zh-CN.md @@ -4,4 +4,4 @@ 文件本身就是 data form:结构表单保持不加 quote;list 和 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`。 +`public-layout-gallery.ebox` 是 `M-x ebox-playground-open` 使用的默认 fixture。迁移来的低层参考和它并列放在 `examples/` 下:`basic.ebox`、`comprehensive.ebox`、`flex-reference.ebox`、`responsive-reference.ebox` 和 `grid-reference.ebox`。Flex 参考由根部 `:overflow 'scroll` 盒子拥有文档 viewport:键盘逐行作用于光标所在的最内层滚动 owner,鼠标滚轮按指针位置路由,剩余滚动量沿嵌套 owner 向外冒泡,最后才交给普通 buffer;splitter resize 时仍保持原有的完整内容语义。Grid 参考是完整的公共属性/值画廊:轨道模板(`auto`、`NFR`、`(fr FACTOR)`、`minmax`、`repeat`)、隐式轨道、row/column auto-flow、gap 简写/长写/别名、placement 与 span,以及所有受支持的 item/content 对齐值;同时注明 Ebox 支持 `row` 和 `column` auto-flow,不支持浏览器的 `dense` 变体。要渲染其他 fixture,可以访问该文件后按 `C-c C-c`,或调用 `ebox-playground-open-file`。 diff --git a/examples/flex-reference.ebox b/examples/flex-reference.ebox index 4f4a346..8425219 100644 --- a/examples/flex-reference.ebox +++ b/examples/flex-reference.ebox @@ -1,463 +1,464 @@ -(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) +(column + :height '(viewport-height) + :width '(viewport) :overflow 'scroll :bgcolor "#FAF7F0" + (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 :width '(viewport) :height 1 :bgcolor "#FAF7F0") + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - (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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(42) :height 1 :bgcolor "#F0A88F" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(42) :height 1 :bgcolor "#D97757" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(42) :height 1 :bgcolor "#F0A88F" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(42) :height 1 :bgcolor "#D97757" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :height 1 :bgcolor "#F0A88F" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :height 1 :bgcolor "#D97757" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :height 1 :bgcolor "#F0A88F" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :height 1 :bgcolor "#D97757" :color "#1F2328" :text-align 'center))))) - - (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 1 (box :content "B" :width '(92) :height 1 :bgcolor "#BFD6C2" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 1 (box :content "C" :width '(92) :height 1 :bgcolor "#8FAF95" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 0 (box :content "B 118" :width '(118) :height 1 :bgcolor "#BFD6C2" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 0 (box :content "C 118" :width '(118) :height 1 :bgcolor "#8FAF95" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 0 (box :content "B 118" :width '(118) :height 1 :bgcolor "#BFD6C2" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 0 (box :content "C 118" :width '(118) :height 1 :bgcolor "#8FAF95" :color "#1F2328" :text-align 'center))))) - - (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(170) :height 1 :bgcolor "#CBC2E3" :color "#1F2328" :text-align 'center)) - (item (box :content "C wraps" :width '(170) :height 1 :bgcolor "#AFA3D1" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(140) :height 1 :bgcolor "#CBC2E3" :color "#1F2328" :text-align 'center)) - (item (box :content "C new column" :width '(140) :height 1 :bgcolor "#AFA3D1" :color "#1F2328" :text-align 'center))))) - - (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) - (column - (box :content "center" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) - (column - (box :content "flex-end" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) - (column - (box :content "space-between" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) - (column - (box :content "space-around" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) - (column - (box :content "space-evenly" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) - (column - (box :content "start" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) - (column - (box :content "end" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) - (column - (box :content "left" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) - (column - (box :content "right" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) - (column - (box :content "normal" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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 :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :color "#1F2328" :text-align 'center)))) - (column - (box :content "center" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :color "#1F2328" :text-align 'center)))) - (column - (box :content "flex-end" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :color "#1F2328" :text-align 'center)))) - (column - (box :content "stretch" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "line height" :width '(78) :height 3 :bgcolor "#D9AD83" :color "#1F2328" :text-align 'center)))) - (column - (box :content "baseline" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) - (column - (box :content "start" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) - (column - (box :content "end" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) - (column - (box :content "self-start" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) - (column - (box :content "self-end" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :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" :color "#1F2328" :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 :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "center" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "flex-end" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "space-between" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "space-around" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "space-evenly" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "stretch" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "start" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "end" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "baseline" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "normal" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) - (column - (box :content "nowrap note" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center))))) - - (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :color "#1F2328" :text-align 'center)) - (item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :color "#1F2328" :text-align 'center))))) - - (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" + (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 "#FFF8FA" :column-gap '(12) :align-items 'center - (item :order 3 (box :content "source 1\n:order 3" :width '(150) :height 2 :bgcolor "#F2C4D2" :color "#1F2328" :text-align 'center)) - (item :order 1 (box :content "source 2\n:order 1" :width '(150) :height 2 :bgcolor "#DEA4B7" :color "#1F2328" :text-align 'center)) - (item :order 2 (box :content "source 3\n:order 2" :width '(150) :height 2 :bgcolor "#C87F9A" :color "#1F2328" :text-align 'center)) - (item :order 1 (box :content "source 4\nsame order" :width '(150) :height 2 :bgcolor "#A95F7C" :color "#FFFFFF" :text-align 'center))) - - (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" :color "#1F2328" :text-align 'center)) - (item :flex-grow 2 :flex-basis '(120) (box :content "grow 2\nbasis 120" :height 2 :bgcolor "#96C7DD" :color "#1F2328" :text-align 'center)) - (item :flex-grow 3 :flex-basis '(120) (box :content "grow 3\nbasis 120" :height 2 :bgcolor "#5FA4C2" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 3 :flex-basis '(320) (box :content "shrink 3\nbasis 320" :height 2 :bgcolor "#96C7DD" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 5 :flex-basis '(320) (box :content "shrink 5\nbasis 320" :height 2 :bgcolor "#5FA4C2" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item :flex-basis 'auto (box :content "basis auto\nwidth 240" :width '(240) :height 2 :bgcolor "#96C7DD" :color "#1F2328" :text-align 'center)) - (item :flex '(1 1 (160)) (box :content "flex tuple\nbasis 160" :width '(240) :height 2 :bgcolor "#5FA4C2" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 0 :flex-basis 'min-content - (box :content "min-content\nwrap choices" :width '(180) :height 2 :bgcolor "#96C7DD" :color "#1F2328" :text-align 'center)) - (item :flex-shrink 0 :flex-basis 'max-content - (box :content "max-content\nkeeps phrase width" :width '(180) :height 2 :bgcolor "#5FA4C2" :color "#1F2328" :text-align 'center)))) - - (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" + :bgcolor "#FFF8F2" :flex-direction 'row :column-gap '(10) :align-items 'center + (item (box :content "A" :width '(42) :height 1 :bgcolor "#F7C8B3" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(42) :height 1 :bgcolor "#F0A88F" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(42) :height 1 :bgcolor "#D97757" :color "#1F2328" :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 "#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" :color "#1F2328" :text-align 'center)) - (item :flex 'none (box :content ":flex none\n= 0 0 auto" :width '(140) :bgcolor "#C5D681" :color "#1F2328" :text-align 'center)) - (item :flex 'auto (box :content ":flex auto\n= 1 1 auto" :width '(140) :bgcolor "#AABE59" :color "#1F2328" :text-align 'center)) - (item :flex 'initial (box :content ":flex initial\n= 0 1 auto" :width '(140) :bgcolor "#8D9E3C" :color "#1F2328" :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 :width '(viewport) :height 1 :bgcolor "#FAF7F0") - - (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" + :bgcolor "#FFF8F2" :flex-direction 'row-reverse :column-gap '(10) :align-items 'center + (item (box :content "A" :width '(42) :height 1 :bgcolor "#F7C8B3" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(42) :height 1 :bgcolor "#F0A88F" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(42) :height 1 :bgcolor "#D97757" :color "#1F2328" :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 "#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" :color "#1F2328" :text-align 'center)) - (item :align-self 'flex-start (box :content "flex-start" :width '(118) :height 2 :bgcolor "#DBA58D" :color "#1F2328" :text-align 'center)) - (item :align-self 'center (box :content "center" :width '(118) :height 2 :bgcolor "#BF7F63" :color "#1F2328" :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" + :bgcolor "#FFF8F2" :flex-direction 'column :row-gap 1 :align-items 'stretch + (item (box :content "A" :height 1 :bgcolor "#F7C8B3" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :height 1 :bgcolor "#F0A88F" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :height 1 :bgcolor "#D97757" :color "#1F2328" :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 "#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" :color "#1F2328" :text-align 'center)) - (item :align-self 'end (box :content "end" :width '(118) :height 2 :bgcolor "#DBA58D" :color "#1F2328" :text-align 'center)) - (item :align-self 'self-start (box :content "self-start" :width '(118) :height 2 :bgcolor "#BF7F63" :color "#1F2328" :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))) + :bgcolor "#FFF8F2" :flex-direction 'column-reverse :row-gap 1 :align-items 'stretch + (item (box :content "A" :height 1 :bgcolor "#F7C8B3" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :height 1 :bgcolor "#F0A88F" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :height 1 :bgcolor "#D97757" :color "#1F2328" :text-align 'center))))) - (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - (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)) + (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" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 1 (box :content "B" :width '(92) :height 1 :bgcolor "#BFD6C2" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 1 (box :content "C" :width '(92) :height 1 :bgcolor "#8FAF95" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 0 (box :content "B 118" :width '(118) :height 1 :bgcolor "#BFD6C2" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 0 (box :content "C 118" :width '(118) :height 1 :bgcolor "#8FAF95" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 0 (box :content "B 118" :width '(118) :height 1 :bgcolor "#BFD6C2" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 0 (box :content "C 118" :width '(118) :height 1 :bgcolor "#8FAF95" :color "#1F2328" :text-align 'center))))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(170) :height 1 :bgcolor "#CBC2E3" :color "#1F2328" :text-align 'center)) + (item (box :content "C wraps" :width '(170) :height 1 :bgcolor "#AFA3D1" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(140) :height 1 :bgcolor "#CBC2E3" :color "#1F2328" :text-align 'center)) + (item (box :content "C new column" :width '(140) :height 1 :bgcolor "#AFA3D1" :color "#1F2328" :text-align 'center))))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) + (column + (box :content "center" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) + (column + (box :content "flex-end" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) + (column + (box :content "space-between" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) + (column + (box :content "space-around" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) + (column + (box :content "space-evenly" :width '(146) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(28) :height 1 :bgcolor "#A8D2E2" :color "#1F2328" :text-align 'center)))) + (column + (box :content "start" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) + (column + (box :content "end" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) + (column + (box :content "left" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) + (column + (box :content "right" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) + (column + (box :content "normal" :width '(176) :padding '(0 (4)) :border "#3F6D88" :bgcolor "#FFFDF8" :color "#1F2328" :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 :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :color "#1F2328" :text-align 'center)))) + (column + (box :content "center" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :color "#1F2328" :text-align 'center)))) + (column + (box :content "flex-end" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "tall\nitem\nsets line" :width '(78) :height 3 :bgcolor "#D9AD83" :color "#1F2328" :text-align 'center)))) + (column + (box :content "stretch" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "line height" :width '(78) :height 3 :bgcolor "#D9AD83" :color "#1F2328" :text-align 'center)))) + (column + (box :content "baseline" :width '(176) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) + (column + (box :content "start" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) + (column + (box :content "end" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) + (column + (box :content "self-start" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)))) + (column + (box :content "self-end" :width '(146) :padding '(0 (4)) :border "#9A6B43" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :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" :color "#1F2328" :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 :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "center" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "flex-end" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "space-between" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "space-around" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "space-evenly" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "stretch" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "start" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "end" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "baseline" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "normal" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(78) :height 1 :bgcolor "#D9C46D" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(78) :height 1 :bgcolor "#B89B3D" :color "#1F2328" :text-align 'center)))) + (column + (box :content "nowrap note" :width '(176) :padding '(0 (4)) :border "#7C6A2F" :bgcolor "#FFFDF8" :color "#1F2328" :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" :color "#1F2328" :text-align 'center))))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item (box :content "B" :width '(104) :height 1 :bgcolor "#9FD5CB" :color "#1F2328" :text-align 'center)) + (item (box :content "C" :width '(104) :height 1 :bgcolor "#71BDB0" :color "#1F2328" :text-align 'center))))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :text-align 'center)) + (item :order 1 (box :content "source 2\n:order 1" :width '(150) :height 2 :bgcolor "#DEA4B7" :color "#1F2328" :text-align 'center)) + (item :order 2 (box :content "source 3\n:order 2" :width '(150) :height 2 :bgcolor "#C87F9A" :color "#1F2328" :text-align 'center)) + (item :order 1 (box :content "source 4\nsame order" :width '(150) :height 2 :bgcolor "#A95F7C" :color "#FFFFFF" :text-align 'center))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :text-align 'center)) + (item :flex-grow 2 :flex-basis '(120) (box :content "grow 2\nbasis 120" :height 2 :bgcolor "#96C7DD" :color "#1F2328" :text-align 'center)) + (item :flex-grow 3 :flex-basis '(120) (box :content "grow 3\nbasis 120" :height 2 :bgcolor "#5FA4C2" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 3 :flex-basis '(320) (box :content "shrink 3\nbasis 320" :height 2 :bgcolor "#96C7DD" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 5 :flex-basis '(320) (box :content "shrink 5\nbasis 320" :height 2 :bgcolor "#5FA4C2" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item :flex-basis 'auto (box :content "basis auto\nwidth 240" :width '(240) :height 2 :bgcolor "#96C7DD" :color "#1F2328" :text-align 'center)) + (item :flex '(1 1 (160)) (box :content "flex tuple\nbasis 160" :width '(240) :height 2 :bgcolor "#5FA4C2" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 0 :flex-basis 'min-content + (box :content "min-content\nwrap choices" :width '(180) :height 2 :bgcolor "#96C7DD" :color "#1F2328" :text-align 'center)) + (item :flex-shrink 0 :flex-basis 'max-content + (box :content "max-content\nkeeps phrase width" :width '(180) :height 2 :bgcolor "#5FA4C2" :color "#1F2328" :text-align 'center)))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :text-align 'center)) + (item :flex 'none (box :content ":flex none\n= 0 0 auto" :width '(140) :bgcolor "#C5D681" :color "#1F2328" :text-align 'center)) + (item :flex 'auto (box :content ":flex auto\n= 1 1 auto" :width '(140) :bgcolor "#AABE59" :color "#1F2328" :text-align 'center)) + (item :flex 'initial (box :content ":flex initial\n= 0 1 auto" :width '(140) :bgcolor "#8D9E3C" :color "#1F2328" :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 :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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" :color "#1F2328" :text-align 'center)) + (item :align-self 'flex-start (box :content "flex-start" :width '(118) :height 2 :bgcolor "#DBA58D" :color "#1F2328" :text-align 'center)) + (item :align-self 'center (box :content "center" :width '(118) :height 2 :bgcolor "#BF7F63" :color "#1F2328" :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" :color "#1F2328" :text-align 'center)) + (item :align-self 'end (box :content "end" :width '(118) :height 2 :bgcolor "#DBA58D" :color "#1F2328" :text-align 'center)) + (item :align-self 'self-start (box :content "self-start" :width '(118) :height 2 :bgcolor "#BF7F63" :color "#1F2328" :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 :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (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)) diff --git a/examples/grid-reference.ebox b/examples/grid-reference.ebox index 8bd48aa..a30fa07 100644 --- a/examples/grid-reference.ebox +++ b/examples/grid-reference.ebox @@ -1,289 +1,682 @@ (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 :width '(720) :height 1 :bgcolor "#FAF7F0") + :height '(viewport-height) + :width '(viewport) + (box :content "Ebox CSS Grid Reference\nA complete, self-explaining gallery of the public Ebox Grid properties and value forms." + :padding '(1 (20)) :border "#2E3740" + :color "#20252B" :bgcolor "#FFFDF8" + :face 'bold :text-align 'center) - (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 :width '(720) :height 1 :bgcolor "#FAF7F0") + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - (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 :width '(720) :height 1 :bgcolor "#FAF7F0") + (box :content "Container property: :grid-template-columns\nTrack sizing accepts fixed sizes, auto, fractional tracks, minmax, and repeat." + :padding '(0 (18)) :border "#B85C3C" + :bgcolor "#F1D4C9" :color "#4C271D" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#B85C3C" :border-top-p nil + :bgcolor "#FFF9F4" + (column :width '(206) + (box :content "fixed\n:((120) (74))" :padding '(0 (6)) + :border "#B85C3C" :bgcolor "#F7C8B3" :color "#3D2B1F" :text-align 'center) + (grid :width '(206) :height 3 :padding '(0 (2)) + :grid-template-columns '((120) (74)) :grid-template-rows '(2) + :gap '(0 (6)) :border "#B85C3C" :bgcolor "#FFF8F2" + (box :content "120 PX" :bgcolor "#F1D0AF" :color "#3D2B1F" :text-align 'center) + (box :content "74 PX" :bgcolor "#D9AD83" :color "#3D2B1F" :text-align 'center))) + (column :width '(206) + (box :content "auto\nauto tracks use intrinsic content" :padding '(0 (6)) + :border "#B85C3C" :bgcolor "#F7C8B3" :color "#3D2B1F" :text-align 'center) + (grid :width '(206) :height 3 :padding '(0 (6)) + :grid-template-columns '(auto auto) :grid-template-rows '(2) + :gap '(0 (6)) :border "#B85C3C" :bgcolor "#FFF8F2" + (box :content "intrinsic" :bgcolor "#F1D0AF" :color "#3D2B1F" :text-align 'center) + (box :content "content" :bgcolor "#D9AD83" :color "#3D2B1F" :text-align 'center))) + (column :width '(206) + (box :content "fractional symbols\n1fr distributes by weight" :padding '(0 (6)) + :border "#B85C3C" :bgcolor "#F7C8B3" :color "#3D2B1F" :text-align 'center) + (grid :width '(206) :height 3 :padding '(0 (6)) + :grid-template-columns '(1fr 2fr) :grid-template-rows '(2) + :gap '(0 (6)) :border "#B85C3C" :bgcolor "#FFF8F2" + (box :content "1FR" :bgcolor "#F1D0AF" :color "#3D2B1F" :text-align 'center) + (box :content "2FR" :bgcolor "#D9AD83" :color "#3D2B1F" :text-align 'center))) + (column :width '(206) + (box :content "fractional forms\n(fr FACTOR) is equivalent to NFR" :padding '(0 (6)) + :border "#B85C3C" :bgcolor "#F7C8B3" :color "#3D2B1F" :text-align 'center) + (grid :width '(206) :height 3 :padding '(0 (6)) + :grid-template-columns '((fr 1) (fr 2)) :grid-template-rows '(2) + :gap '(0 (6)) :border "#B85C3C" :bgcolor "#FFF8F2" + (box :content "(fr 1)" :bgcolor "#F1D0AF" :color "#3D2B1F" :text-align 'center) + (box :content "(fr 2)" :bgcolor "#D9AD83" :color "#3D2B1F" :text-align 'center))) + (column :width '(206) + (box :content "minmax\nminimum protects a readable track" :padding '(0 (6)) + :border "#B85C3C" :bgcolor "#F7C8B3" :color "#3D2B1F" :text-align 'center) + (grid :width '(206) :height 3 :padding '(0 (6)) + :grid-template-columns '((minmax (74) (120)) (74)) + :grid-template-rows '(2) :gap '(0 (6)) + :border "#B85C3C" :bgcolor "#FFF8F2" + (box :content "74 → 120" :bgcolor "#F1D0AF" :color "#3D2B1F" :text-align 'center) + (box :content "fixed" :bgcolor "#D9AD83" :color "#3D2B1F" :text-align 'center))) + (column :width '(206) + (box :content "repeat\nrepeat 2 visibly expands the template" :padding '(0 (6)) + :border "#B85C3C" :bgcolor "#F7C8B3" :color "#3D2B1F" :text-align 'center) + (grid :width '(206) :height 3 :padding '(0 (6)) + :grid-template-columns '((repeat 2 (62)) (fr 1)) + :grid-template-rows '(2) :gap '(0 (6)) + :border "#B85C3C" :bgcolor "#FFF8F2" + (box :content "REPEAT A" :bgcolor "#F1D0AF" :color "#3D2B1F" :text-align 'center) + (box :content "REPEAT B" :bgcolor "#D9AD83" :color "#3D2B1F" :text-align 'center) + (box :content "FR" :bgcolor "#BF7F63" :color "#FFFFFF" :text-align 'center)))) - (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 :width '(720) :height 1 :bgcolor "#FAF7F0") - (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 :width '(720) :height 1 :bgcolor "#FAF7F0") + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - (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 "R2–R3\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 "C2–C3\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 :width '(720) :height 1 :bgcolor "#FAF7F0") + (box :content "Container property: :grid-template-rows\nThe same track grammar works on the block axis; fixed lines, auto sizing, fractions, minmax, and repeat are shown independently." + :padding '(0 (18)) :border "#537A63" + :bgcolor "#DCEBDD" :color "#26402F" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#537A63" :border-top-p nil + :bgcolor "#F7FBF7" + (column :width '(206) + (box :content "fixed rows\n(1 2)" :padding '(0 (6)) + :border "#537A63" :bgcolor "#C8DDCB" :color "#26402F" :text-align 'center) + (grid :width '(206) :height 5 :padding '(0 (6)) + :grid-template-columns '((192)) :grid-template-rows '(1 2) + :gap '(0 (6)) :border "#537A63" :bgcolor "#F8FCF8" + (box :content "ROW 1" :bgcolor "#DCEBDD" :color "#26402F" :text-align 'center) + (box :content "ROW 2" :bgcolor "#A6C6AB" :color "#26402F" :text-align 'center))) + (column :width '(206) + (box :content "auto rows\nintrinsic line heights" :padding '(0 (6)) + :border "#537A63" :bgcolor "#C8DDCB" :color "#26402F" :text-align 'center) + (grid :width '(206) :height 5 :padding '(0 (6)) + :grid-template-columns '((192)) :grid-template-rows '(auto auto) + :gap '(0 (6)) :border "#537A63" :bgcolor "#F8FCF8" + (box :content "short" :height 1 :bgcolor "#DCEBDD" :color "#26402F" :text-align 'center) + (box :content "tall\ncontent" :height 2 :bgcolor "#A6C6AB" :color "#26402F" :text-align 'center))) + (column :width '(206) + (box :content "fractional rows\n1fr / 2fr" :padding '(0 (6)) + :border "#537A63" :bgcolor "#C8DDCB" :color "#26402F" :text-align 'center) + (grid :width '(206) :height 6 :padding '(0 (6)) + :grid-template-columns '((192)) :grid-template-rows '(1fr 2fr) + :gap '(0 (6)) :border "#537A63" :bgcolor "#F8FCF8" + (box :content "1FR" :bgcolor "#DCEBDD" :color "#26402F" :text-align 'center) + (box :content "2FR" :bgcolor "#A6C6AB" :color "#26402F" :text-align 'center))) + (column :width '(206) + (box :content "fractional row forms\n(fr 1) / (fr 2)" :padding '(0 (6)) + :border "#537A63" :bgcolor "#C8DDCB" :color "#26402F" :text-align 'center) + (grid :width '(206) :height 6 :padding '(0 (6)) + :grid-template-columns '((192)) :grid-template-rows '((fr 1) (fr 2)) + :gap '(0 (6)) :border "#537A63" :bgcolor "#F8FCF8" + (box :content "(fr 1)" :bgcolor "#DCEBDD" :color "#26402F" :text-align 'center) + (box :content "(fr 2)" :bgcolor "#A6C6AB" :color "#26402F" :text-align 'center))) + (column :width '(206) + (box :content "minmax rows\n(min 1 / max 3)" :padding '(0 (6)) + :border "#537A63" :bgcolor "#C8DDCB" :color "#26402F" :text-align 'center) + (grid :width '(206) :height 6 :padding '(0 (6)) + :grid-template-columns '((192)) + :grid-template-rows '((minmax (1) (3)) 1) + :gap '(0 (6)) :border "#537A63" :bgcolor "#F8FCF8" + (box :content "MINMAX" :bgcolor "#DCEBDD" :color "#26402F" :text-align 'center) + (box :content "FIXED" :bgcolor "#A6C6AB" :color "#26402F" :text-align 'center))) + (column :width '(206) + (box :content "repeat rows\nrepeat 3 expands lines" :padding '(0 (6)) + :border "#537A63" :bgcolor "#C8DDCB" :color "#26402F" :text-align 'center) + (grid :width '(206) :height 6 :padding '(0 (6)) + :grid-template-columns '((192)) + :grid-template-rows '((repeat 3 (1))) + :gap '(0 (6)) :border "#537A63" :bgcolor "#F8FCF8" + (box :content "ROW A" :bgcolor "#DCEBDD" :color "#26402F" :text-align 'center) + (box :content "ROW B" :bgcolor "#A6C6AB" :color "#26402F" :text-align 'center) + (box :content "ROW C" :bgcolor "#7EA887" :color "#FFFFFF" :text-align 'center)))) - (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 :width '(720) :height 1 :bgcolor "#FAF7F0") + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") - (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 :width '(720) :height 1 :bgcolor "#FAF7F0") + (box :content "Container properties: :grid-auto-columns and :grid-auto-rows\nImplicit tracks use the same fixed, auto, fractional, and minmax track forms. Ebox resolves one normalized implicit track at a time." + :padding '(0 (18)) :border "#416E8A" + :bgcolor "#D9EAF2" :color "#203A4A" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#416E8A" :border-top-p nil + :bgcolor "#F6FBFD" + (column :width '(206) + (box :content "auto-columns · fixed\n:grid-auto-columns ((64))" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((64)) + :grid-template-rows '(1 1) :grid-auto-columns '((64)) + :grid-auto-flow 'column :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "B" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (box :content "C" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center))) + (column :width '(206) + (box :content "auto-columns · auto\nintrinsic implicit width" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((64)) + :grid-template-rows '(1 1) :grid-auto-columns 'auto + :grid-auto-flow 'column :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "LONG C" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center) + (box :content "C" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center))) + (column :width '(206) + (box :content "auto-columns · 1fr\nimplicit remainder" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((64)) + :grid-template-rows '(1 1) :grid-auto-columns '1fr + :grid-auto-flow 'column :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "1FR" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center) + (box :content "C" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center))) + (column :width '(206) + (box :content "auto-columns · minmax\nminimum implicit width" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((64)) + :grid-template-rows '(1 1) + :grid-auto-columns '((minmax (64) (120))) + :grid-auto-flow 'column :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "MINMAX" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center) + (box :content "C" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center))) + (column :width '(206) + (box :content "auto-rows · fixed\n:grid-auto-rows 2" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((94) (94)) + :grid-template-rows '(1) :grid-auto-rows 2 :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "B" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (box :content "AUTO ROW" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center))) + (column :width '(206) + (box :content "auto-rows · auto\nintrinsic implicit height" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((94) (94)) + :grid-template-rows '(1) :grid-auto-rows 'auto :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "B" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (box :content "TALL\nAUTO ROW" :height 2 :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center))) + (column :width '(206) + (box :content "auto-rows · 1fr\nimplicit remainder" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 6 :grid-template-columns '((94) (94)) + :grid-template-rows '(1) :grid-auto-rows '1fr :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "B" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (box :content "1FR ROW" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center))) + (column :width '(206) + (box :content "auto-rows · minmax\nbounded implicit height" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 6 :grid-template-columns '((94) (94)) + :grid-template-rows '(1) + :grid-auto-rows '((minmax (1) (3))) :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "B" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (box :content "MINMAX ROW" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center)))) - (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)) + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (box :content "Container property: :grid-auto-flow\nAutomatic placement is row-major or column-major. Ebox intentionally supports row and column, not the browser dense variants." + :padding '(0 (18)) :border "#416E8A" + :bgcolor "#D9EAF2" :color "#203A4A" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#416E8A" :border-top-p nil + :bgcolor "#F6FBFD" + (column :width '(206) + (box :content "row\nfills each row before creating the next" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((94) (94)) + :grid-template-rows '(1 1) :grid-auto-flow 'row :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "1A" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "1B" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (box :content "2A" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center) + (box :content "2B" :bgcolor "#8DBDCE" :color "#203A4A" :text-align 'center))) + (column :width '(206) + (box :content "column\nfills each column before creating the next" :padding '(0 (6)) + :border "#416E8A" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((94)) + :grid-template-rows '(1 1) :grid-auto-columns '((94)) + :grid-auto-flow 'column :gap '(0 (6)) + :border "#416E8A" :bgcolor "#F8FCFE" + (box :content "C1/R1" :bgcolor "#D9EAF2" :color "#203A4A" :text-align 'center) + (box :content "C1/R2" :bgcolor "#C3DFEA" :color "#203A4A" :text-align 'center) + (box :content "C2/R1" :bgcolor "#A9D0DE" :color "#203A4A" :text-align 'center) + (box :content "C2/R2" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center)))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (box :content "Container properties: :gap, :row-gap, :column-gap\nThe shorthand accepts one shared value or a row/column pair. Longhands override the matching side; grid-row-gap and grid-column-gap remain public aliases." + :padding '(0 (18)) :border "#3D7068" + :bgcolor "#CDEBE5" :color "#1D3935" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#3D7068" :border-top-p nil + :bgcolor "#F7FCFB" + (column :width '(206) + (box :content "gap scalar\n:gap 1" :padding '(0 (6)) + :border "#3D7068" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((94) (94)) + :grid-template-rows '(1 1) :gap 1 + :border "#3D7068" :bgcolor "#F7FCFB" + (box :content "A" :bgcolor "#CDEBE5" :color "#1D3935" :text-align 'center) + (box :content "B" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (box :content "C" :bgcolor "#71BDB0" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "gap pair\n:gap (1 (14))" :padding '(0 (6)) + :border "#3D7068" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((94) (94)) + :grid-template-rows '(1 1) :gap '(1 (14)) + :border "#3D7068" :bgcolor "#F7FCFB" + (box :content "ROW GAP" :bgcolor "#CDEBE5" :color "#1D3935" :text-align 'center) + (box :content "COLUMN GAP" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (box :content "PAIR" :bgcolor "#71BDB0" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "row-gap\n:row-gap 2" :padding '(0 (6)) + :border "#3D7068" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((94) (94)) + :grid-template-rows '(1 1) :row-gap 2 :column-gap '(8) + :border "#3D7068" :bgcolor "#F7FCFB" + (box :content "A" :bgcolor "#CDEBE5" :color "#1D3935" :text-align 'center) + (box :content "B" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (box :content "ROW 2" :bgcolor "#71BDB0" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "column-gap\n:column-gap (28)" :padding '(0 (6)) + :border "#3D7068" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((88) (88)) + :grid-template-rows '(1 1) :row-gap 1 :column-gap '(28) + :border "#3D7068" :bgcolor "#F7FCFB" + (box :content "LEFT" :bgcolor "#CDEBE5" :color "#1D3935" :text-align 'center) + (box :content "RIGHT" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (box :content "LONG GAP" :bgcolor "#71BDB0" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "aliases\n:grid-row-gap / :grid-column-gap" :padding '(0 (6)) + :border "#3D7068" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((88) (88)) + :grid-template-rows '(1 1) :grid-row-gap 2 :grid-column-gap '(28) + :border "#3D7068" :bgcolor "#F7FCFB" + (box :content "ROW ALIAS" :bgcolor "#CDEBE5" :color "#1D3935" :text-align 'center) + (box :content "COLUMN ALIAS" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (box :content "OVERRIDE" :bgcolor "#71BDB0" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "override\nexplicit sides win over :gap" :padding '(0 (6)) + :border "#3D7068" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((88) (88)) + :grid-template-rows '(1 1) :gap '(1 (8)) + :row-gap 2 :column-gap '(28) + :border "#3D7068" :bgcolor "#F7FCFB" + (box :content "SHORTHAND" :bgcolor "#CDEBE5" :color "#1D3935" :text-align 'center) + (box :content "OVERRIDDEN" :bgcolor "#9FD5CB" :color "#1D3935" :text-align 'center) + (box :content "SIDES" :bgcolor "#71BDB0" :color "#FFFFFF" :text-align 'center)))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (box :content "Item properties: :grid-column, :grid-row, :grid-column-span, :grid-row-span\nPlacement is one-based. A property accepts a start number, (start :span count), or (start end); span longhands work with automatic placement too." + :padding '(0 (18)) :border "#6A5C8B" + :bgcolor "#E7E2F1" :color "#332B4B" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#6A5C8B" :border-top-p nil + :bgcolor "#FBF9FF" + (column :width '(206) + (box :content "automatic placement\nno item coordinates" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (box :content "A" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center) + (box :content "B" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center) + (box :content "C" :bgcolor "#AFA3D1" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content ":grid-column 2\none column start" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (grid-item :grid-column 2 + (box :content "C2" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center)) + (box :content "AUTO" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center))) + (column :width '(206) + (box :content ":grid-row 2\none row start" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (grid-item :grid-row 2 + (box :content "R2" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center)) + (box :content "AUTO" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center))) + (column :width '(206) + (box :content ":grid-column (1 :span 2)\nspan in the column value" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (grid-item :grid-column '(1 :span 2) + (box :content "C1–C2" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center)) + (box :content "C3" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center))) + (column :width '(206) + (box :content ":grid-row (1 :span 2)\nspan in the row value" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (grid-item :grid-row '(1 :span 2) + (box :content "R1–R2" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center)) + (box :content "OTHER" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center))) + (column :width '(206) + (box :content ":grid-column (1 3)\nexplicit end line" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (grid-item :grid-column '(1 3) + (box :content "C1→C2" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center)) + (box :content "C3" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center))) + (column :width '(206) + (box :content ":grid-row (1 3)\nexplicit end line" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (grid-item :grid-row '(1 3) + (box :content "R1→R2" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center)) + (box :content "OTHER" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center))) + (column :width '(206) + (box :content ":grid-column-span 2\nlonghand span" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (grid-item :grid-column-span 2 + (box :content "C1–C2" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center)) + (box :content "C3" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center))) + (column :width '(206) + (box :content ":grid-row-span 2\nlonghand span" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (grid-item :grid-row-span 2 + (box :content "R1–R2" :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center)) + (box :content "OTHER" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center))) + (column :width '(206) + (box :content "direct child placement\n:grid-item is only a convenience" :padding '(0 (6)) + :border "#6A5C8B" :bgcolor "#D8D0E8" :color "#332B4B" :text-align 'center) + (grid :width '(206) :height 4 :grid-template-columns '((58) (58) (58)) + :grid-template-rows '(1 1) :gap '(0 (4)) + :border "#6A5C8B" :bgcolor "#FBF9FF" + (box :grid-column 2 :grid-row 2 :content "DIRECT\nC2/R2" + :bgcolor "#CBC2E3" :color "#332B4B" :text-align 'center) + (box :content "AUTO" :bgcolor "#E7E2F1" :color "#332B4B" :text-align 'center)))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (box :content "Container property: :justify-items\nInline-axis item alignment supports start, center, end, stretch, and normal. Fixed-size items make the first three positions visible." + :padding '(0 (18)) :border "#B07836" + :bgcolor "#F3E4C7" :color "#513718" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#B07836" :border-top-p nil + :bgcolor "#FFF9ED" + (column :width '(206) + (box :content "start" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((194)) + :grid-template-rows '(3) :justify-items 'start :align-items 'center + :border "#B07836" :bgcolor "#FFF9ED" + (box :width '(72) :content "START" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center))) + (column :width '(206) + (box :content "center" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((194)) + :grid-template-rows '(3) :justify-items 'center :align-items 'center + :border "#B07836" :bgcolor "#FFF9ED" + (box :width '(72) :content "CENTER" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center))) + (column :width '(206) + (box :content "end" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((194)) + :grid-template-rows '(3) :justify-items 'end :align-items 'center + :border "#B07836" :bgcolor "#FFF9ED" + (box :width '(72) :content "END" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center))) + (column :width '(206) + (box :content "stretch" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((194)) + :grid-template-rows '(3) :justify-items 'stretch :align-items 'center + :border "#B07836" :bgcolor "#FFF9ED" + (box :content "AUTO WIDTH\nSTRETCH" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center))) + (column :width '(206) + (box :content "normal" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((194)) + :grid-template-rows '(3) :justify-items 'normal :align-items 'center + :border "#B07836" :bgcolor "#FFF9ED" + (box :content "AUTO WIDTH\nNORMAL" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center)))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (box :content "Container property: :align-items\nBlock-axis item alignment supports start, center, end, stretch, and normal. The fixed row height exposes the vertical offset." + :padding '(0 (18)) :border "#B07836" + :bgcolor "#F3E4C7" :color "#513718" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#B07836" :border-top-p nil + :bgcolor "#FFF9ED" + (column :width '(206) + (box :content "start" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((194)) + :grid-template-rows '(5) :justify-items 'center :align-items 'start + :border "#B07836" :bgcolor "#FFF9ED" + (box :width '(82) :height 1 :content "START" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center))) + (column :width '(206) + (box :content "center" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((194)) + :grid-template-rows '(5) :justify-items 'center :align-items 'center + :border "#B07836" :bgcolor "#FFF9ED" + (box :width '(82) :height 1 :content "CENTER" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center))) + (column :width '(206) + (box :content "end" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((194)) + :grid-template-rows '(5) :justify-items 'center :align-items 'end + :border "#B07836" :bgcolor "#FFF9ED" + (box :width '(82) :height 1 :content "END" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center))) + (column :width '(206) + (box :content "stretch" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((194)) + :grid-template-rows '(5) :justify-items 'center :align-items 'stretch + :border "#B07836" :bgcolor "#FFF9ED" + (box :content "AUTO HEIGHT\nSTRETCH" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center))) + (column :width '(206) + (box :content "normal" :padding '(0 (6)) + :border "#B07836" :bgcolor "#EAD2A6" :color "#513718" :text-align 'center) + (grid :width '(206) :height 5 :grid-template-columns '((194)) + :grid-template-rows '(5) :justify-items 'center :align-items 'normal + :border "#B07836" :bgcolor "#FFF9ED" + (box :content "NORMAL" :bgcolor "#F3E4C7" :color "#513718" :text-align 'center)))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (box :content "Container property: :justify-content\nInline-axis track packing supports start, center, end, space-between, space-around, space-evenly, stretch, and normal." + :padding '(0 (18)) :border "#2F7180" + :bgcolor "#CFE8EA" :color "#1E4148" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#2F7180" :border-top-p nil + :bgcolor "#F6FCFC" + (column :width '(206) + (box :content "start" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((60) (60)) + :grid-template-rows '(2) :gap '(0 (6)) :justify-content 'start + :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center))) + (column :width '(206) + (box :content "center" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((60) (60)) + :grid-template-rows '(2) :gap '(0 (6)) :justify-content 'center + :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center))) + (column :width '(206) + (box :content "end" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((60) (60)) + :grid-template-rows '(2) :gap '(0 (6)) :justify-content 'end + :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center))) + (column :width '(206) + (box :content "space-between" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((60) (60)) + :grid-template-rows '(2) :gap '(0 (6)) :justify-content 'space-between + :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center))) + (column :width '(206) + (box :content "space-around" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((60) (60)) + :grid-template-rows '(2) :gap '(0 (6)) :justify-content 'space-around + :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center))) + (column :width '(206) + (box :content "space-evenly" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((60) (60)) + :grid-template-rows '(2) :gap '(0 (6)) :justify-content 'space-evenly + :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center))) + (column :width '(206) + (box :content "stretch" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((60) (60)) + :grid-template-rows '(2) :gap '(0 (6)) :justify-content 'stretch + :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center))) + (column :width '(206) + (box :content "normal" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 3 :grid-template-columns '((60) (60)) + :grid-template-rows '(2) :gap '(0 (6)) :justify-content 'normal + :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center)))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (box :content "Container property: :align-content\nBlock-axis track packing mirrors justify-content: start, center, end, space-between, space-around, space-evenly, stretch, and normal." + :padding '(0 (18)) :border "#2F7180" + :bgcolor "#CFE8EA" :color "#1E4148" :wrap-mode 'word) + (grid :grid-template-columns '((206) (206) (206)) + :grid-auto-rows 'auto :gap '(1 (10)) :justify-content 'space-between + :padding '(0 (12)) :border "#2F7180" :border-top-p nil + :bgcolor "#F6FCFC" + (column :width '(206) + (box :content "start" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 7 :grid-template-columns '((60) (60)) + :grid-template-rows '(1 1) :gap '(0 (6)) :align-content 'start + :justify-content 'center :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center) + (box :content "C" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "center" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 7 :grid-template-columns '((60) (60)) + :grid-template-rows '(1 1) :gap '(0 (6)) :align-content 'center + :justify-content 'center :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center) + (box :content "C" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "end" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 7 :grid-template-columns '((60) (60)) + :grid-template-rows '(1 1) :gap '(0 (6)) :align-content 'end + :justify-content 'center :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center) + (box :content "C" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "space-between" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 7 :grid-template-columns '((60) (60)) + :grid-template-rows '(1 1) :gap '(0 (6)) :align-content 'space-between + :justify-content 'center :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center) + (box :content "C" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "space-around" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 7 :grid-template-columns '((60) (60)) + :grid-template-rows '(1 1) :gap '(0 (6)) :align-content 'space-around + :justify-content 'center :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center) + (box :content "C" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "space-evenly" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 7 :grid-template-columns '((60) (60)) + :grid-template-rows '(1 1) :gap '(0 (6)) :align-content 'space-evenly + :justify-content 'center :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center) + (box :content "C" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "stretch" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 7 :grid-template-columns '((60) (60)) + :grid-template-rows '(1 1) :gap '(0 (6)) :align-content 'stretch + :justify-content 'center :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center) + (box :content "C" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center))) + (column :width '(206) + (box :content "normal" :padding '(0 (6)) + :border "#2F7180" :bgcolor "#B5DDE0" :color "#1E4148" :text-align 'center) + (grid :width '(206) :height 7 :grid-template-columns '((60) (60)) + :grid-template-rows '(1 1) :gap '(0 (6)) :align-content 'normal + :justify-content 'center :justify-items 'center :align-items 'center + :border "#2F7180" :bgcolor "#F6FCFC" + (box :content "A" :bgcolor "#CFE8EA" :color "#1E4148" :text-align 'center) + (box :content "B" :bgcolor "#9ACED2" :color "#1E4148" :text-align 'center) + (box :content "C" :bgcolor "#8DBDCE" :color "#FFFFFF" :text-align 'center)))) + + (spacer :width '(viewport) :height 1 :bgcolor "#FAF7F0") + + (box :content "GRID TOOLKIT\n:grid-template-columns · :grid-template-rows · :grid-auto-columns · :grid-auto-rows · :grid-auto-flow · :gap · :row-gap · :column-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\nTrack forms: fixed · auto · (fr FACTOR) · NFR · minmax · repeat. Placement forms: start · (start :span count) · (start end)." + :padding '(1 (16)) :border "#2E3740" + :color "#FFFFFF" :bgcolor "#3D4B55" + :wrap-mode 'word :text-align 'center)) diff --git a/scripts/ebox-playground-flex-resize-evaluator.el b/scripts/ebox-playground-flex-resize-evaluator.el new file mode 100644 index 0000000..7280914 --- /dev/null +++ b/scripts/ebox-playground-flex-resize-evaluator.el @@ -0,0 +1,293 @@ +;;; ebox-playground-flex-resize-evaluator.el --- Flex resize evaluator -*- lexical-binding: t; -*- + +;; SPDX-License-Identifier: GPL-3.0-or-later + +;;; Commentary: + +;; This evaluator exercises the public playground path used by an interactive +;; `.ebox' preview: source buffer on the left, rendered buffer on the right, +;; and a real window resize that reaches `window-size-change-functions'. + +;;; Code: + +(require 'cl-lib) +(require 'ebox-playground) + +(defconst ebox-playground-flex-resize-evaluator--fixture + (expand-file-name "examples/flex-reference.ebox" + ebox-playground-directory) + "Flex fixture used by the performance evaluator.") + +(defcustom ebox-playground-flex-resize-evaluator-max-seconds 0.5 + "Maximum allowed end-to-end time for one settled preview resize." + :type 'number + :group 'ebox-playground) + +(defvar ebox-playground-flex-resize-evaluator--output-lines nil + "Report lines collected during one evaluator run.") + +(defconst ebox-playground-flex-resize-evaluator--runtime-properties + (delete-dups + (append (mapcar #'cdr ebox-region-types) + '(ebox-content-idx ebox-content-owners ebox-scroll-window))) + "Ebox identity properties omitted from fresh-render comparison.") + +(defun ebox-playground-flex-resize-evaluator--preview-buffer () + "Return the preview buffer for the fixed flex fixture." + (get-buffer + (format "*Ebox Preview: %s*" + ebox-playground-flex-resize-evaluator--fixture))) + +(defun ebox-playground-flex-resize-evaluator--line-widths (buffer) + "Return pixel widths for every rendered line in BUFFER." + (with-current-buffer buffer + (mapcar #'ebox-string-pixel-width + (ebox-string-lines (buffer-string))))) + +(defun ebox-playground-flex-resize-evaluator--visual-text (text) + "Return TEXT with fresh-render identity properties removed. +Fresh renders allocate different region ids by design. Keep character, +face, display, and all non-runtime properties so the comparison still checks +the visible right-hand surface rather than merely checking that rendering +completed." + (let ((copy (copy-sequence text))) + (when (> (length copy) 0) + (remove-list-of-text-properties + 0 (length copy) + ebox-playground-flex-resize-evaluator--runtime-properties + copy)) + copy)) + +(defun ebox-playground-flex-resize-evaluator--check (name condition detail) + "Record a named check and return whether CONDITION passed." + (push (format "%s %s -- %s" + (if condition "PASS" "FAIL") name detail) + ebox-playground-flex-resize-evaluator--output-lines) + condition) + +(defun ebox-playground-flex-resize-evaluator--settle-preview + (preview preview-window) + "Publish PREVIEW at its settled PREVIEW-WINDOW viewport before measuring. +The GUI frame can acquire its final pixel width after `ebox-dsl-render' has +mounted the split. This setup publication is outside the measured splitter +action, so the action starts from a self-consistent runtime generation rather +than charging initial frame settling to resize." + (let* ((width (ebox-playground--window-viewport-width preview-window)) + (state (and (buffer-live-p preview) + (ebox--buffer-render-state preview))) + (published-width (and state (plist-get state :viewport-width)))) + (unless (numberp width) + (error "flex preview has no settled viewport width")) + (when (not (equal width published-width)) + (let ((report (ebox-rerender-buffer-with-context preview width))) + (unless (plist-get report :runtime-published) + (error "flex preview initial viewport was not published")))) + (with-current-buffer preview + (setq-local ebox-playground--preview-viewport-width width)) + width)) + +(defun ebox-playground-flex-resize-evaluator-run () + "Run the real GUI flex preview resize evaluator. + +The function returns non-nil only when one splitter resize schedules exactly +one Ebox viewport update, publishes valid output, and stays under the timing +threshold. It leaves the normal idle-prewarm configuration untouched; the +daemon evaluator does not enable background prewarm, keeping the measured +action free of concurrent cache-warming CPU work." + (let ((ebox-render-gc-cons-threshold nil) + (ebox-render-gc-cons-percentage nil) + (ebox-playground-resize-idle-delay 0.05) + (source nil) + (preview nil) + (preview-window nil) + (resize-advice nil) + (resize-count 0) + (resize-elapsed 0.0) + (resize-finished nil) + (completed-without-error nil) + (ebox-playground-flex-resize-evaluator--output-lines nil) + (checks nil)) + (condition-case error-data + (progn + (delete-other-windows) + (setq source + (find-file ebox-playground-flex-resize-evaluator--fixture)) + (ebox-dsl-mode) + (ebox-dsl-render) + (setq preview + (ebox-playground-flex-resize-evaluator--preview-buffer) + preview-window (get-buffer-window preview (selected-frame))) + (unless (and (buffer-live-p preview) + (window-live-p preview-window)) + (error "flex preview was not mounted in a window")) + (redisplay t) + (sleep-for 0.2) + (ebox-playground-flex-resize-evaluator--settle-preview + preview preview-window) + (redisplay t) + (sleep-for 0.2) + (setq resize-advice + (lambda (original &rest arguments) + (setq resize-count (1+ resize-count)) + (let ((start (float-time))) + (unwind-protect + (apply original arguments) + (setq resize-elapsed + (+ resize-elapsed (- (float-time) start))) + (setq resize-finished t))))) + (advice-add 'ebox-rerender-buffer-with-context + :around resize-advice) + (let* ((start (float-time)) + (old-width + (ebox-playground--window-viewport-width preview-window)) + (delta -10) + (deadline (+ start 15.0))) + (window-resize preview-window delta t) + (while (and (not resize-finished) + (< (float-time) deadline)) + (redisplay t) + (sleep-for 0.01)) + (let* ((elapsed (- (float-time) start)) + (viewport-width + (ebox-playground--window-viewport-width preview-window)) + (report (ebox-buffer-update-report preview)) + (projection-kind (plist-get report :projection-kind)) + (native-flex-calls + (and (boundp + 'ebox-native-reflow--flex-geometry-call-count) + ebox-native-reflow--flex-geometry-call-count)) + (line-widths + (ebox-playground-flex-resize-evaluator--line-widths + preview)) + (background + (with-current-buffer preview + face-remapping-alist)) + (max-line-width (if line-widths + (apply #'max line-widths) + 0)) + (fresh-rendered + (and (numberp viewport-width) + (let ((ebox-viewport-width viewport-width) + ;; Compare the published visible scroll + ;; window with a fresh render at the same + ;; viewport height. Omitting this binding + ;; rendered the entire lazy document and made + ;; every correct retained resize look unequal. + (ebox-viewport-height + (or (plist-get + (ebox--buffer-render-state preview) + :viewport-height) + 36))) + (ebox-render + (ebox-playground-view + ebox-playground-flex-resize-evaluator--fixture + viewport-width))))) + (fresh-render-matches-p + (and (stringp fresh-rendered) + (with-current-buffer preview + (equal-including-properties + (ebox-playground-flex-resize-evaluator--visual-text + (buffer-string)) + (ebox-playground-flex-resize-evaluator--visual-text + fresh-rendered))))) + (tp-operations (plist-get report :tp-operation-count)) + (projection-retained-p + (and (memq projection-kind + '(viewport-reflow + viewport-reflow-mixed-scroll)) + (not (plist-get report :tp-full-root)) + (not (plist-get report :tp-scope-fallback))))) + (setq checks + (list + (ebox-playground-flex-resize-evaluator--check + "preview mounted" + (and (buffer-live-p preview) + (window-live-p preview-window)) + (format "source=%S preview=%S" source preview)) + (ebox-playground-flex-resize-evaluator--check + "one resize update" + (= resize-count 1) + (format "ebox-rerender-buffer-with-context=%d" + resize-count)) + (ebox-playground-flex-resize-evaluator--check + "resize completed" + resize-finished + (format "elapsed=%.6fs" elapsed)) + (ebox-playground-flex-resize-evaluator--check + "viewport was published" + (and (numberp viewport-width) + (not (equal old-width viewport-width)) + (eq (plist-get report :constraint-source) + 'viewport) + (plist-get report :runtime-published)) + (format "viewport=%S strategy=%S" + viewport-width (plist-get report :strategy))) + (ebox-playground-flex-resize-evaluator--check + "retained viewport projection" + projection-retained-p + (format "projection=%S axes=%S reconciled=%S full-root=%S scope-fallback=%S" + projection-kind + (plist-get report :viewport-axes) + (plist-get report :reconciled-objects) + (plist-get report :tp-full-root) + (plist-get report :tp-scope-fallback))) + (ebox-playground-flex-resize-evaluator--check + "rendered output matches a fresh viewport render" + fresh-render-matches-p + (format "max-line-width=%d viewport=%S fresh-length=%S" + max-line-width viewport-width + (and fresh-rendered + (length fresh-rendered)))) + (ebox-playground-flex-resize-evaluator--check + "canvas background is preserved" + (equal background + '((default (:background "#FAF7F0"))) ) + (format "face-remapping=%S" background)) + (ebox-playground-flex-resize-evaluator--check + "resize is millisecond-scale" + (<= elapsed + ebox-playground-flex-resize-evaluator-max-seconds) + (format "elapsed=%.6fs threshold=%.6fs" + elapsed + ebox-playground-flex-resize-evaluator-max-seconds)))) + (push (format + "RESIZE old-viewport-px=%S new-viewport-px=%S total=%.6fs rerender=%.6fs strategy=%S projection=%S axes=%S reconciled=%S native-flex=%S tp-ops=%S cache-hits=%S cache-misses=%S" + old-width viewport-width elapsed resize-elapsed + (plist-get report :strategy) projection-kind + (plist-get report :viewport-axes) + (plist-get report :reconciled-objects) + native-flex-calls tp-operations + (plist-get report :cache-hit-count) + (plist-get report :cache-miss-count)) + ebox-playground-flex-resize-evaluator--output-lines) + (setq completed-without-error t) + (and resize-finished + (cl-every #'identity checks))))) + (error + (push (format "FAIL evaluator error -- %S" error-data) + ebox-playground-flex-resize-evaluator--output-lines) + nil) + (quit + (push "FAIL evaluator interrupted" + ebox-playground-flex-resize-evaluator--output-lines) + nil)) + (when resize-advice + (advice-remove 'ebox-rerender-buffer-with-context resize-advice)) + (when (buffer-live-p preview) + (kill-buffer preview)) + (when (buffer-live-p source) + (kill-buffer source)) + (let ((passed (and completed-without-error + (not (null checks)) + (cl-every #'identity checks)))) + (push (if passed + "FLEX-RESIZE-EVALUATOR PASS" + "FLEX-RESIZE-EVALUATOR FAIL") + ebox-playground-flex-resize-evaluator--output-lines) + (mapconcat #'identity + (nreverse ebox-playground-flex-resize-evaluator--output-lines) + "\n")))) + +(provide 'ebox-playground-flex-resize-evaluator) + +;;; ebox-playground-flex-resize-evaluator.el ends here diff --git a/scripts/flex-resize-evaluator.sh b/scripts/flex-resize-evaluator.sh new file mode 100755 index 0000000..4e6f1b0 --- /dev/null +++ b/scripts/flex-resize-evaluator.sh @@ -0,0 +1,35 @@ +#!/bin/sh +set -eu + +root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +emacs=${EMACS:-/Applications/Emacs.app/Contents/MacOS/Emacs} +emacsclient=${EMACSCLIENT:-/Applications/Emacs.app/Contents/MacOS/bin/emacsclient} +server="ebox-playground-flex-evaluator-$$" + +cleanup() { + "$emacsclient" -n -s "$server" -e '(kill-emacs 0)' >/dev/null 2>&1 || true +} +trap cleanup EXIT INT TERM + +# Keep the evaluator package-only, but reproduce only the four startup values +# that directly affect this render path. Loading the complete user init would +# add unrelated packages, timers, and UI state to the performance gate. +"$emacs" -Q --daemon="$server" \ + -L "$root" -L "$root/../ebox" -L "$root/../new-architecture/ecss" \ + -L "$root/../tp" \ + --eval '(setq load-prefer-newer t + gc-cons-threshold most-positive-fixnum + gc-cons-percentage 0.6 + file-name-handler-alist nil + auto-window-vscroll nil)' \ + -l "$root/ebox-playground.el" \ + -l "$root/scripts/ebox-playground-flex-resize-evaluator.el" + +output=$("$emacsclient" -n -s "$server" -c -e \ + '(ebox-playground-flex-resize-evaluator-run)' 2>&1) +printf '%s\n' "$output" + +if printf '%s\n' "$output" | grep -Fq 'FLEX-RESIZE-EVALUATOR PASS'; then + exit 0 +fi +exit 1 diff --git a/tests/ebox-playground-tests.el b/tests/ebox-playground-tests.el index 3dbf182..2f905d3 100644 --- a/tests/ebox-playground-tests.el +++ b/tests/ebox-playground-tests.el @@ -24,6 +24,88 @@ (list form)) (cl-mapcan #'ebox-playground-test--boxes form)))) +(defun ebox-playground-test--forms-named (name form) + "Return every nested DSL form in FORM whose head is NAME." + (when (consp form) + (append (when (eq (car form) name) + (list form)) + (cl-mapcan (lambda (child) + (ebox-playground-test--forms-named name child)) + form)))) + +(defun ebox-playground-test--line-widths (text) + "Return rendered pixel widths for every line in TEXT." + (mapcar #'ebox-string-pixel-width + (ebox-string-lines text))) + +(defun ebox-playground-test--max-line-width (text) + "Return the widest rendered pixel line in TEXT." + (apply #'max (ebox-playground-test--line-widths text))) + +(defconst ebox-playground-test--grid-value-forms + '(":grid-template-columns '(auto auto)" + ":grid-template-columns '((120) (74))" + ":padding '(0 (2))" + ":grid-template-columns '((192))" + ":grid-template-columns '((88) (88))" + ":grid-template-columns '(1fr 2fr)" + ":grid-template-columns '((fr 1) (fr 2))" + ":grid-template-columns '((minmax" + ":grid-template-columns '((repeat" + ":grid-template-rows '(auto auto)" + ":grid-template-rows '(1fr 2fr)" + ":grid-auto-columns '((64))" + ":grid-auto-columns 'auto" + ":grid-auto-columns '1fr" + ":grid-auto-columns '((minmax" + ":grid-auto-rows 2" + ":grid-auto-rows 'auto" + ":grid-auto-rows '1fr" + ":grid-auto-rows '((minmax" + ":grid-auto-flow 'row" + ":grid-auto-flow 'column" + ":gap 1" + ":gap '(1 (14))" + ":row-gap 2" + ":column-gap '(28)" + ":grid-row-gap 2" + ":grid-column-gap '(28)" + ":grid-column 2" + ":grid-row 2" + ":grid-column '(1 :span 2)" + ":grid-row '(1 :span 2)" + ":grid-column '(1 3)" + ":grid-row '(1 3)" + ":grid-column-span 2" + ":grid-row-span 2" + ":justify-items 'start" + ":justify-items 'center" + ":justify-items 'end" + ":justify-items 'stretch" + ":justify-items 'normal" + ":align-items 'start" + ":align-items 'center" + ":align-items 'end" + ":align-items 'stretch" + ":align-items 'normal" + ":justify-content 'start" + ":justify-content 'center" + ":justify-content 'end" + ":justify-content 'space-between" + ":justify-content 'space-around" + ":justify-content 'space-evenly" + ":justify-content 'stretch" + ":justify-content 'normal" + ":align-content 'start" + ":align-content 'center" + ":align-content 'end" + ":align-content 'space-between" + ":align-content 'space-around" + ":align-content 'space-evenly" + ":align-content 'stretch" + ":align-content 'normal") + "Public Grid property/value fragments required in the reference fixture.") + (ert-deftest ebox-playground-tinted-content-has-explicit-ink () "Every tinted content box in the public fixtures should set its text color." (dolist (file (append ebox-playground-test--fixtures @@ -87,24 +169,352 @@ (should (cl-every (lambda (width) (<= width 720)) widths))))) (ebox-playground-close name)))) -(ert-deftest ebox-playground-fixed-reference-spacers-paint-the-canvas () - "Fixed-width reference separators should keep a continuous canvas surface." - (dolist (file '("grid-reference.ebox" "public-layout-gallery.ebox")) - (let* ((path (expand-file-name (concat "examples/" file) - ebox-playground-directory)) - (lines (ebox-string-lines - (ebox-render (ebox-playground-view path)))) - (separators 0)) - (dolist (line lines) - (when (equal (get-text-property 0 'display line) - '(space :width (720))) - (setq separators (1+ separators)) +(ert-deftest ebox-playground-flex-reference-owns-document-scrolling () + "The Flex reference root must expose one line-oriented Ebox scroll owner." + (let ((path (expand-file-name "examples/flex-reference.ebox" + ebox-playground-directory)) + (name " *ebox-playground-flex-scroll-owner-test*")) + (unwind-protect + (let ((ebox-viewport-width 720) + (ebox-viewport-height 36)) + (ebox-playground-open-file path name) + (with-current-buffer name + (let* ((state (ebox--buffer-render-state (current-buffer))) + (table (plist-get state :scroll-state-table)) + (region-id + (car + (sort (copy-sequence + (plist-get state :scroll-region-ids)) + (lambda (left right) + (> (- (length (plist-get + (gethash left table) + :content-lines)) + (plist-get (gethash left table) + :content-height)) + (- (length (plist-get + (gethash right table) + :content-lines)) + (plist-get (gethash right table) + :content-height)))))))) + (should region-id) + (should (= (ebox--scroll-region-by region-id 1 1) 1)) + (setq table + (plist-get (ebox--buffer-render-state (current-buffer)) + :scroll-state-table)) + (should (= (plist-get (gethash region-id table) :scroll-offset) + 1))))) + (ebox-playground-close name)))) + +(ert-deftest ebox-playground-effective-viewport-prefers-window-text-width () + "Interactive viewport resolution should prefer the window text width." + (let ((ebox-viewport-width nil)) + (cl-letf (((symbol-function 'window-body-width) + (lambda (&optional _window _pixelwise) 987)) + ((symbol-function 'frame-char-width) + (lambda (&optional _frame) 8))) + (should (= (ebox-playground--effective-viewport-width + (selected-window)) + 979))))) + +(ert-deftest ebox-playground-window-viewport-leaves-display-reserve () + "A preview viewport should leave one character column for redisplay." + (cl-letf (((symbol-function 'window-body-width) + (lambda (&optional _window _pixelwise) 987)) + ((symbol-function 'frame-char-width) + (lambda (&optional _frame) 8))) + (should (= (ebox-playground--window-viewport-width (selected-window)) + 979)))) + +(ert-deftest ebox-playground-dsl-render-uses-window-width-for-viewport () + "C-c C-c should build viewport-dependent roots at the preview width." + (let ((preview "*Ebox Preview: /tmp/ebox-playground-viewport-render.ebox*") + (window-width 987)) + (unwind-protect + (cl-letf (((symbol-function 'window-body-width) + (lambda (&optional _window _pixelwise) window-width)) + ((symbol-function 'frame-char-width) + (lambda (&optional _frame) 8))) + (with-temp-buffer + (setq buffer-file-name "/tmp/ebox-playground-viewport-render.ebox") + (insert "(box :content \"viewport\" :width '(viewport) :height 1)") + (ebox-dsl-mode) + (ebox-dsl-render) + (with-current-buffer preview + (should (= (ebox-playground-test--max-line-width + (buffer-string)) + (- window-width 8)))))) + (when (get-buffer preview) + (kill-buffer preview))))) + +(ert-deftest ebox-playground-dsl-render-grid-reference-fits-preview-viewport () + "C-c C-c should render the real Grid reference inside the preview viewport." + (let* ((path (expand-file-name "examples/grid-reference.ebox" + ebox-playground-directory)) + (preview (format "*Ebox Preview: %s*" path)) + (window-width 699) + (viewport 691)) + (unwind-protect + (cl-letf (((symbol-function 'window-body-width) + (lambda (&optional _window _pixelwise) window-width)) + ((symbol-function 'frame-char-width) + (lambda (&optional _frame) (- window-width viewport)))) + (delete-other-windows) + (with-temp-buffer + (setq buffer-file-name path) + (insert-file-contents path) + (ebox-dsl-mode) + (ebox-dsl-render) + (with-current-buffer preview + (should (= ebox-playground--preview-viewport-width viewport)) + (should (cl-every + (lambda (width) (<= width viewport)) + (ebox-playground-test--line-widths + (buffer-string))))))) + (delete-other-windows) + (when (get-buffer preview) + (kill-buffer preview))))) + +(ert-deftest ebox-playground-dsl-render-keeps-source-left-of-preview () + "C-c C-c should keep the source left of its rendered preview." + (let ((preview "*Ebox Preview: /tmp/ebox-playground-two-window.ebox*") + source) + (unwind-protect + (progn + (delete-other-windows) + (with-temp-buffer + (setq source (current-buffer) + buffer-file-name "/tmp/ebox-playground-two-window.ebox") + (insert "(box :content \"two windows\" :width '(viewport) :height 1)") + (ebox-dsl-mode) + (cl-letf (((symbol-function 'window-body-width) + (lambda (&optional _window _pixelwise) 987)) + ((symbol-function 'frame-char-width) + (lambda (&optional _frame) 8))) + (ebox-dsl-render)) + (let* ((windows + (sort (copy-sequence (window-list nil 'no-minibuf)) + (lambda (left right) + (< (window-left-column left) + (window-left-column right))))) + (left (car windows)) + (right (cadr windows))) + (should (= (length windows) 2)) + (should (eq (window-buffer left) source)) + (should (eq (window-buffer right) (get-buffer preview)))))) + (delete-other-windows) + (when (get-buffer preview) + (kill-buffer preview))))) + +(ert-deftest ebox-playground-preview-resizes-through-ebox-viewport-update () + "A preview resize should publish an incremental Ebox viewport update." + (let ((preview "*Ebox Preview: /tmp/ebox-playground-viewport-resize.ebox*") + (window-width 987) + (ebox-playground-resize-idle-delay 0)) + (unwind-protect + (cl-letf (((symbol-function 'window-body-width) + (lambda (&optional _window _pixelwise) window-width)) + ((symbol-function 'frame-char-width) + (lambda (&optional _frame) 8))) + (with-temp-buffer + (setq buffer-file-name "/tmp/ebox-playground-viewport-resize.ebox") + (insert "(box :content \"viewport\" :width '(viewport) :height 1)") + (ebox-dsl-mode) + (ebox-dsl-render) + (setq window-width 1234) + (with-current-buffer preview + (let ((window (get-buffer-window (current-buffer)))) + (should (memq #'ebox-playground--window-size-change + window-size-change-functions)) + (ebox-playground--window-size-change (window-frame window)) + (should (timerp ebox-playground--resize-timer)) + ;; Batch Emacs does not service idle timers from `sit-for'. + (ebox-playground--resize-preview (current-buffer) window))) + (with-current-buffer preview + (should (= (ebox-playground-test--max-line-width + (buffer-string)) + (- window-width 8))) + (let ((report (ebox-buffer-update-report (current-buffer)))) + (should (eq (plist-get report :constraint-source) 'viewport)) + (should (eq (plist-get report :strategy) 'owner-rerender)))))) + (when (get-buffer preview) + (kill-buffer preview))))) + +(ert-deftest ebox-playground-flex-reference-retains-stable-scroll-reflow () + "The Flex reference should retain scroll state when its subtree is stable." + (let* ((ebox-runtime-idle-prewarm nil) + (ebox-runtime-idle-reflow-cache-prewarm nil) + (path (expand-file-name "examples/flex-reference.ebox" + ebox-playground-directory)) + (name " *ebox-playground-flex-scroll-reflow-test*")) + (unwind-protect + (let* ((buffer (ebox-render-to-buffer + name (ebox-playground-view path 720))) + (old-state (ebox--buffer-render-state buffer)) + (old-root-object + (plist-get (plist-get old-state :root-node) + :surface-object)) + (old-scroll-ids (copy-sequence + (plist-get old-state :scroll-region-ids))) + (old-scroll-offsets + (mapcar + (lambda (region-id) + (plist-get + (gethash region-id + (plist-get old-state :scroll-state-table)) + :scroll-offset)) + old-scroll-ids)) + (report (ebox-rerender-buffer-with-context buffer 326 36)) + (new-state (ebox--buffer-render-state buffer)) + (new-root-object + (plist-get (plist-get new-state :root-node) + :surface-object)) + (widths + (ebox-playground-test--line-widths + (with-current-buffer buffer + (buffer-string))))) + (should (eq (plist-get report :projection-kind) + 'viewport-reflow)) + (should (plist-get report :runtime-published)) + (should (eq old-root-object new-root-object)) + (should (cl-every + (lambda (region-id) + (member region-id + (plist-get new-state :scroll-region-ids))) + old-scroll-ids)) + (should (equal old-scroll-offsets + (mapcar + (lambda (region-id) + (plist-get + (gethash region-id + (plist-get new-state + :scroll-state-table)) + :scroll-offset)) + old-scroll-ids))) + (should (cl-every (lambda (width) (<= width 326)) widths))) + (when (get-buffer name) + (kill-buffer name))))) + +(ert-deftest ebox-playground-preview-uses-canvas-redisplay-settings () + "A preview should suppress text-editor redisplay artifacts." + (let ((preview " *ebox-playground-preview-settings-test*")) + (unwind-protect + (progn + (ebox-playground--render-node-to-buffer preview "preview" 240) + (with-current-buffer preview + (should truncate-lines) + (should-not (assq 'truncation fringe-indicator-alist)) + (should-not (assq 'continuation fringe-indicator-alist)) + (should-not bidi-display-reordering) + (should (eq bidi-paragraph-direction 'left-to-right)))) + (when (get-buffer preview) + (kill-buffer preview))))) + +(ert-deftest ebox-playground-preview-display-settings-roll-back-on-render-error () + "A failed preview render should restore its prior display settings." + (let ((preview " *ebox-playground-preview-settings-rollback-test*")) + (unwind-protect + (progn + (with-current-buffer (get-buffer-create preview) + (setq-local truncate-lines nil) + (setq-local face-remapping-alist + '((default (:background "#111111"))))) + (cl-letf (((symbol-function 'ebox-render-to-buffer) + (lambda (&rest _) + (error "Injected preview render failure")))) + (should-error + (ebox-playground--render-to-preview-buffer + preview (ebox-create :content "rollback")))) + (with-current-buffer preview + (should-not truncate-lines) + (should (equal face-remapping-alist + '((default (:background "#111111"))))))) + (when (get-buffer preview) + (kill-buffer preview))))) + +(ert-deftest ebox-playground-flex-reference-paints-root-canvas () + "The Flex reference should carry its canvas color into the preview face." + (let ((preview " *ebox-playground-flex-canvas-test*") + (path (expand-file-name "examples/flex-reference.ebox" + ebox-playground-directory))) + (unwind-protect + (progn + (ebox-playground--render-node-to-buffer + preview (ebox-playground-view path 720) 720) + (with-current-buffer preview + (should (equal face-remapping-alist + '((default (:background "#FAF7F0"))))) + (let ((display-signature (ebox--current-display-signature)) + (matching-cache-entry-p nil) + (render-cache + (plist-get (ebox--buffer-render-state (current-buffer)) + :render-cache))) + (maphash + (lambda (effective-key _entry) + (let ((signature (cadr effective-key))) + (when (and (consp signature) + (equal display-signature (car signature))) + (setq matching-cache-entry-p t)))) + render-cache) + (should matching-cache-entry-p)))) + (when (get-buffer preview) + (kill-buffer preview))))) + +(ert-deftest ebox-playground-grid-reference-fits-preview-widths () + "The Grid reference should stay inside split-preview width." + (let* ((viewport 691) + (path (expand-file-name "examples/grid-reference.ebox" + ebox-playground-directory)) + (text (substring-no-properties + (ebox-render (ebox-playground-view path viewport))))) + (should (cl-every (lambda (width) (<= width viewport)) + (ebox-playground-test--line-widths text))))) + +(ert-deftest ebox-playground-grid-reference-spacers-paint-the-viewport () + "Grid reference separators should fill the active viewport canvas." + (let ((path (expand-file-name "examples/grid-reference.ebox" + ebox-playground-directory))) + (let ((spacers (ebox-playground-test--forms-named + 'spacer (ebox-playground--read-file path)))) + (should (= (length spacers) 11)) + (dolist (spacer spacers) + (should (equal (plist-get (cdr spacer) :width) + '(quote (viewport)))) + (should (equal (plist-get (cdr spacer) :bgcolor) "#FAF7F0")))) + (dolist (viewport '(691 720)) + (let* ((ebox-viewport-width viewport) + (lines (ebox-string-lines + (ebox-render (ebox-playground-view path viewport)))) + (spacer-lines + (cl-remove-if-not + (lambda (line) + (equal (get-text-property 0 'display line) + `(space :width (,viewport)))) + lines))) + (should (= (length spacer-lines) 11)) + (dolist (line spacer-lines) + (should (equal (get-text-property 0 'display line) + `(space :width (,viewport)))) + (should (= (ebox-string-pixel-width line) viewport)) (should (equal (get-text-property 0 'face line) - '(:background "#FAF7F0"))))) - (should (> separators 0))))) + '(:background "#FAF7F0")))))))) + +(ert-deftest ebox-playground-public-layout-gallery-fixed-spacers-paint-the-canvas () + "The default public gallery fixed separators should keep canvas coverage." + (let* ((path (expand-file-name "examples/public-layout-gallery.ebox" + ebox-playground-directory)) + (lines (ebox-string-lines + (ebox-render (ebox-playground-view path)))) + (separators 0)) + (dolist (line lines) + (when (equal (get-text-property 0 'display line) + '(space :width (720))) + (setq separators (1+ separators)) + (should (equal (get-text-property 0 'face line) + '(:background "#FAF7F0"))))) + (should (> separators 0)))) (ert-deftest ebox-playground-grid-reference-covers-public-properties () - "The Grid reference should name every public Grid property in the fixture." + "The Grid reference should cover every public property and supported value form." (let ((source (with-temp-buffer (insert-file-contents (expand-file-name "examples/grid-reference.ebox" @@ -112,11 +522,14 @@ (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 + :grid-row-gap :grid-column-gap :gap :row-gap + :column-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))))) + (should (string-match-p (regexp-quote (symbol-name property)) source))) + (dolist (fragment ebox-playground-test--grid-value-forms) + (should (string-match-p (regexp-quote fragment) source))))) (ert-deftest ebox-playground-opens-and-closes-buffer () "The public open and close commands should own their buffer lifecycle." @@ -158,8 +571,8 @@ (insert "(box :content \"Rendered from .ebox\" :width '(240))") (ebox-dsl-mode) (ebox-dsl-render) - (should (eq (window-buffer (selected-window)) - (get-buffer preview))) + (should (eq (window-buffer (selected-window)) (current-buffer))) + (should (get-buffer-window (get-buffer preview))) (with-current-buffer preview (should (string-match-p "Rendered from .ebox" (buffer-string))))) (when (get-buffer preview)