- Run record
- loading
Loading this Run…
Loading the Revisions of this Run…
A published source file of Revision 0 of Canvely — Spring Menu 1.0.0, shown as text in the browser.
Source is browser-viewable only. There is no download or archive endpoint, and no additional license is granted beyond inspection and research.
# Canvely — Spring Menu
Build the editor of **Canvely**, a fictional browser-based design tool for
layouts and marketing graphics. The open file is **Hazel & Rye — Spring
Menu** in the workspace **Orchard Studio**.
## Product
- **Name:** Canvely.
- **Feel:** precise, quiet, professional. Designers keep it open all day, so
the canvas is the focus and the interface around it stays calm and dense.
Every direct manipulation must feel exact: things land where the pointer
says they land.
- **You own the visual identity.** Design the Canvely wordmark/logo, color
palette, type scale, icons, selection handles, guides, and layout
yourself; nothing is prescribed. Any graphics you need must be created in
this workspace (inline SVG or CSS). There is no supplied photography.
- **Microcopy is part of the task.** Write all interface copy yourself:
tooltips, empty states, validation messages, confirmations, and the
explanations shown when an action is not possible.
## Data
`public/data/document.json` holds the product, workspace, and file names and
the layer tree of the file. Use it as the initial document. Do not add,
remove, or rename supplied layers in the initial state.
The layer model:
- **Layer types:** `frame`, `group`, `rect`, `ellipse`, and `text`. Every
layer has an `id`, a `name`, `visible`, and `locked`.
- **Stacking:** every `children` array is ordered back to front. The last
child is drawn on top.
- **Frames** never appear inside other layers. Their `x` and `y` are canvas
coordinates. A frame has a fill, clips its children to its bounds, and
shows its name on the canvas. The supplied top level holds only frames.
- **Coordinates:** `x` and `y` of every layer inside a frame are relative to
that frame's top-left corner. Groups do not create their own coordinate
space. Layers at the top level use canvas coordinates.
- **Geometry:** `x`, `y`, `width`, and `height` describe the unrotated box.
`rotation` is in degrees, clockwise, around the box center.
- **Groups** store no geometry of their own. A group's bounds are the
axis-aligned bounds of its children after rotation.
- **Rectangles** have `fill`, `opacity`, `corner_radius`, and an optional
`stroke` (`color` and `width`, drawn inside the bounds).
**Ellipses** have `fill` and `opacity`.
- **Text** has `text`, `font_size`, `font_weight`, `line_height` (a
multiplier), `align`, `fill` (the text color), and `opacity`. Text wraps
within its width and starts at the top of its box. Text that does not fit
the box height overflows below it and is not clipped, except by its frame.
Use the system font stack.
- **Hidden and locked:** `visible: false` layers are not drawn. `locked:
true` layers are drawn but cannot be selected, moved, resized, or rotated
on the canvas, or moved with arrow keys. They can still be selected
through their row, then edited in the properties panel, duplicated, or
deleted. Hiding or locking a frame or group applies to everything inside
it.
## Editor
One application screen with:
1. **App frame:** the Canvely wordmark you design, the workspace name, and
the file name.
2. **Toolbar:** the Move, Rectangle, Ellipse, and Text tools, undo and redo,
the zoom level, and a “reset demo” action.
3. **Layers panel:** the layer tree.
4. **Canvas:** the frames and their layers.
5. **Properties panel:** the properties of the current selection.
6. **Fictional notice:** the note “Canvely is a fictional product created
for UIBenchmark.” (verbatim, must be visible).
## Canvas and view
- On first load the view fits all three frames.
- **Zoom** ranges from 10% to 800%. Ctrl/Cmd + wheel zooms around the
pointer: the document point under the pointer stays under the pointer.
Browsers report trackpad pinch the same way. This must never zoom the
browser page itself. Visible controls zoom in, zoom out, fit all content,
and reset to 100%. The current zoom is shown as a percentage.
- **Pan** with the wheel or trackpad scroll, and by dragging while holding
Space.
## Selection
- A click selects the topmost visible, unlocked layer under the pointer.
Hit-testing follows each layer's real shape: the rotated box for
rectangles and text (rounded corners may count as inside), the ellipse for
ellipses. A click inside a rotated layer's bounding box but outside its
rotated outline does not select it.
- A click on a frame's empty area does not select the frame. A frame is
selected by clicking its name on the canvas or its row in the layers
panel.
- A click on a layer inside a group selects the outermost group inside the
frame, or at the top level. A double-click selects one level deeper toward
the layer under the pointer. While a layer inside a group is selected,
whether by double-click or through its row, a click on a layer inside that
layer's parent group selects at the same depth; a click outside it starts
again at the outermost level.
- Escape selects the parent group. When a frame, a direct child of a frame,
or a top-level layer is selected, Escape clears the selection.
- Shift+click adds a layer to the selection or removes it.
- Dragging on an empty spot starts a marquee. Hidden and locked layers count
as empty. A marquee started inside a frame selects the visible, unlocked
direct children of that frame it intersects (a group counts as one layer).
A marquee started outside all frames selects the frames and top-level
layers it intersects.
## Editing
- **Move:** dragging a selection moves it exactly with the pointer at every
zoom level, apart from snapping. A frame moves by dragging its name.
Moving a group or frame moves everything inside it. While focus is not in
the layers panel or a text field, Arrow keys move the selection by 1 px
and Shift+Arrow by 10 px, in document units.
- **Snapping:** while dragging, the selection's edges and center snap to the
edges and centers of its sibling layers and of its parent frame when they
are within about 5 screen pixels. A guide line shows each active snap and
disappears when the drag ends.
- **Resize:** a single selected layer shows eight resize handles. Dragging a
handle keeps the opposite edge or corner fixed. Shift keeps the aspect
ratio. Alt/Option resizes symmetrically around the center. Width and height
stop at 1 px; layers never flip. The current width and height are shown
while resizing. Resizing text changes its box, and the text rewraps at the
same font size. Resizing a frame changes only the frame; its children keep
their positions relative to its top-left corner.
- **Rotated layers:** handles follow the rotated outline. Resizing works
along the layer's own axes, keeps the opposite corner or edge fixed on
screen, and leaves the rotation unchanged.
- **Multiple layers and groups:** a multi-selection shows one bounding box.
Resizing it, or resizing a group, scales the position and size of each
contained layer proportionally within that box.
- **Rotate:** a single selected rectangle, ellipse, or text layer rotates
around its center by dragging just outside a corner handle or a dedicated
rotation handle. Shift snaps to 15° steps. The angle is shown while
rotating. Frames and groups do not need rotation.
- **Properties panel:** for one selected layer it shows and edits X, Y, W, H,
rotation, opacity, and fill color, plus corner radius for rectangles and
font size for text. Frames show X, Y, W, H, and fill. Groups show X, Y, W,
and H of their bounds; changing them moves or scales the children. X and Y
are relative to the parent frame (canvas coordinates for top-level layers).
A value applies on Enter or when the field loses focus. Invalid input is
rejected and the previous value is restored: non-numeric input, W or H
below 1, opacity outside 0–100%, a negative corner radius, a font size
below 1, or an invalid hex color. The
fields update live while the layer is changed on the canvas.
- **Mixed values:** for a multi-selection, each shared property shows the
common value, or a clear “Mixed” state when the values differ. Entering a
value applies it to every selected layer.
## Structure
- **Layers panel:** shows the full tree with the topmost layer of each parent
listed first. Every row shows the layer type and name. Frames and groups
expand and collapse. Arrow keys move between rows while focus is in the
panel. Selection is synchronized in both directions: a canvas selection
highlights and reveals its row. Rows can be renamed in place; an empty
name keeps the previous name. Every row can toggle visibility and lock.
- **Layer order:** dragging a row reorders the layer among its siblings, and
the canvas stacking follows. Bring forward and send backward move the
selection one step, through a visible control and with the shortcuts below.
- **Create:** the Rectangle, Ellipse, and Text tools create a layer by
dragging on the canvas, or at a default size by clicking. Shift constrains
rectangles and ellipses to a square or circle. A layer drawn over a frame
becomes that frame's topmost child; a layer drawn outside all frames
becomes a top-level layer. After creating, the tool returns to Move and the
new layer is selected. Name new layers by type with an increasing number,
for example “Rectangle 1”.
- **Text editing:** a double-click on a text layer edits it when the text
layer itself is selected or sits directly in a frame; inside a group, the
double-clicks first select one level deeper as described above. Enter
with a text layer selected also edits it. Editing happens in place on the
canvas, at the layer's position, size, zoom level, and rotation. Escape or
a click outside commits. A new text layer starts in text editing.
Committing empty text removes the text layer.
- **Groups:** grouping two or more selected siblings creates a selected group
at the stacking position of the topmost selected layer. Ungrouping keeps
the children selected, in place, and in stacking order. Frames, and layers
with different parents, cannot be grouped, and the interface explains why.
- **Duplicate and delete:** duplicate copies the selection, including all
children of groups and frames. Each copy lands 10 px right and 10 px down,
directly above its original, and the copies become the selection. Delete
removes the selection. Both are also available through a visible control.
## History and persistence
- **Undo and redo** cover at least 50 steps, through visible controls and the
shortcuts below. Every completed change is exactly one step, however many
pointer events it took: move, resize, rotation, property edit, reorder,
group, ungroup, rename, visibility or lock change, creation, deletion,
duplication, and one text-editing session. Each Arrow key press is one
step. Creating a text layer and its first text-editing session together
are one step; a new text layer committed empty leaves no step. Selection
and view changes are not steps. A new change after an undo clears the redo
history.
- **Persistence:** the document survives a page reload using local storage.
A “reset demo” action restores the supplied document and clears the
history after a confirmation step.
## Keyboard shortcuts
Shortcuts refer to a US-English keyboard layout.
| Action | Shortcut |
|---|---|
| Move, Rectangle, Ellipse, Text tool | V, R, O, T |
| Undo / redo | Ctrl/Cmd+Z / Ctrl/Cmd+Shift+Z |
| Duplicate | Ctrl/Cmd+D |
| Delete | Delete or Backspace |
| Group / ungroup | Ctrl/Cmd+G / Ctrl/Cmd+Shift+G |
| Bring forward / send backward | Ctrl/Cmd+] / Ctrl/Cmd+[ |
| Move by 1 px / 10 px | Arrow keys / Shift+Arrow keys |
| Zoom to fit all content / zoom to 100% | Shift+1 / Shift+0 |
| Edit text | Enter or double-click |
| Select parent / clear selection | Escape |
| Pan | Space+drag |
Shortcuts must not fire while the user types in a text field or edits text on
the canvas. Where a shortcut matches a browser shortcut, the editor action
wins.
## Constraints
- Responsive at 1440×900 (desktop) and 390×844 (mobile). At mobile width the
document must not overflow horizontally. The canvas stays visible. With
touch input, a one-finger drag on empty canvas pans instead of drawing a
marquee, and the visible controls zoom. Layers can be selected by tapping
the canvas or through the layers panel, the properties of the selection
can be edited, and undo and redo stay reachable. Resize and rotation
handles are not required at mobile width.
- Every control is reachable by keyboard, has a visible focus indicator and
an accessible name.
- No canvas, drawing, drag-and-drop, or geometry library is installed and
none may be added: build everything with React and browser APIs. The
rendering technique (DOM, SVG, or `<canvas>`) is your choice.
- Respect reduced-motion preferences for any animation you add.
- No external network requests, fonts, or images. System font stack only.
See `BENCHMARK_RULES.md` for binding rules and `REQUIREMENTS.json` for the
behaviors that will be evaluated.