--- version: alpha name: understory-design description: "A deep forest-green near-black canvas (#101a14) with pale sage ink (#eef2ea), the first true dark-green palette in the gallery, distinct from the neon batch and from the warm cream of the trade-shop system next to it. Moss green (#6fbf73) is the working accent and also the literal color of the terrarium's top layer, terracotta clay (#d97a4d) is reserved for the one attention state. Literata carries every headline, Public Sans carries prose and controls. The homepage builds a terrarium bottom-up: four real layers stack in order inside a glass jar, then a dome lowers and seals it." --- ## Brand Positioning Understory sells the parts of a closed-jar terrarium, and it exists because most plant-shop sites sell a finished, styled object, a photo of a lush jar on a windowsill, and never explain what's actually inside it or why it works. Understory's bet is the opposite: show the jar being built, one real layer at a time, in the order that actually keeps a sealed ecosystem alive, and sell each layer on its own, priced honestly, so a visitor understands the thing before they own it. If a component doesn't correspond to a real layer, a real price, or the dome itself, it doesn't belong here. colors: accent: "#6fbf73" accent-alt: "#d97a4d" ink: "#eef2ea" ink-muted: "#9db3a2" canvas: "#101a14" ## Logo / Wordmark System The mark is a terrarium jar in cross-section: a rounded rectangle body, a green moss band across the base, and a shallow open arc across the top where the dome will eventually sit. It is not a leaf, a watering can, or a plant silhouette. It is a miniature reading of the same jar the homepage builds. ```svg ``` - **Lockup**: mark, then a `0.55rem` gap, then "Understory" in Literata 600, sentence case. Mark always left of the wordmark. - **Minimum size**: mark not smaller than `18px` tall, below that the soil and moss bands merge into one shape. - **Clearspace**: empty margin equal to the mark's own height on all four sides. - **Don't**: don't recolor the moss band to anything but `--accent`, it's the same green as the jar's real top layer. Don't draw the dome arc as a closed shape in the mark, it's open, the mark shows a jar mid-build, not a sealed one. Don't add a leaf or sprout above the rim, the fern only appears inside the actual moss layer. ## Color System ### Roles | Role | Token | Value | Used for | |---|---|---|---| | Canvas | `--bg` | `#101a14` | Page background | | Surface | `--panel` | `#17241c` | Nav, layer cards, table, form card, jar glass | | Surface raised | `--raised` | `#1e3226` | Row hover, cta band, input focus fill | | Border | `--border` | `#24382c` | Card edges, table rules, dividers | | Border strong | `--border-strong` | `#33503f` | Decorative-only edges, swatch outline | | Border UI | `--border-ui` | `#4f7860` | Every interactive boundary: secondary button, input, select, jar and dome outline | | Ink | `--ink` | `#eef2ea` | Headlines, body copy, table cells | | Ink muted | `--dim` | `#9db3a2` | Labels, meta lines, helper text | | Moss | `--accent` | `#6fbf73` | Primary button, mark's moss band, top layer fill, "in stock" status, links | | Clay | `--accent-alt` | `#d97a4d` | The "SEALED" readout, the notice's left rule, form errors | Moss is the working color: the primary button, an in-stock status, and the literal fill of the jar's top layer are the same token, because that layer is the part a customer actually looks at. Clay is reserved for one job, marking the moment something completes or needs attention, the sealed-state readout and a form error share it, and nothing else does. There's no second green and no blue anywhere in the interface, the three illustrative layer colors (stone grey, charcoal near-black, soil brown) are real material colors, not system tokens, the same exception this whole gallery already establishes for illustrative fills. ### Contrast (computed from the hex values above via WCAG relative luminance) | Pairing | Ratio | Passes | |---|---|---| | `--ink` on `--bg` (headlines, body copy) | 15.70:1 | AAA | | `--ink` on `--panel` (card copy, table cells) | 14.19:1 | AAA | | `--ink` on `--raised` (hovered row, cta band) | 12.03:1 | AAA | | `--dim` on `--bg` (labels, helper text) | 7.97:1 | AAA | | `--dim` on `--panel` (meta lines, footer) | 7.20:1 | AAA | | `--dim` on `--raised` | 6.11:1 | AA | | `--accent` on `--bg` (links, moss text) | 7.95:1 | AAA | | `--accent` on `--panel` | 7.18:1 | AAA | | `--accent` on `--raised` | 6.09:1 | AA | | `--accent-alt` on `--bg` (sealed readout, error text) | 5.80:1 | AA | | `--accent-alt` on `--panel` | 5.24:1 | AA | | `--accent-alt` on `--raised` (form error helper, not used at large size only) | 4.44:1 | AA (large/UI only) | | dark label (`#08110b`) on `--accent` fill (primary button) | 8.56:1 | AAA | | `--border-ui` control edge on `--bg` (non-text, inputs) | 3.55:1 | AA (1.4.11) | | `--border-ui` control edge on `--panel` (non-text, secondary button) | 3.21:1 | AA (1.4.11) | `--accent-alt` on `--raised` clears large-text and non-text UI use (4.44:1) but falls short of the small-text 4.5:1 minimum, so the form's error helper text intentionally sits on `--bg` or `--panel`, never on `--raised`, and the layers page confirms this before shipping. `--border-strong` at 2.00:1 is kept for decorative-only uses, a swatch chip's outline, never a control boundary, `--border-ui` exists specifically so a real control never inherits that weaker value. The primary button's label is `#08110b`, not pure black and not `--bg`, chosen because it's what the moss green actually needs to clear AAA at 8.56:1 while still reading as "near-black" rather than a fourth ink token. typography: display: fontFamily: Literata fontWeight: 600 body: fontFamily: Public Sans fontWeight: 400 ## Type Scale | Token | Size | Weight | Family | Line-height | Letter-spacing | Used for | |---|---|---|---|---|---|---| | `--text-display` | `clamp(2.1rem, 5vw, 3.5rem)` | 600 | Literata | 1.1 | 0 | Homepage H1 | | `--text-page-h1` | `clamp(1.9rem, 4vw, 2.6rem)` | 600 | Literata | 1.1 | 0 | Second-page H1 | | `--text-h2` | `1.5rem` | 600 | Literata | 1.15 | 0 | Section headings | | `--text-h3` | `1.05rem` | 600 | Literata | 1.2 | 0 | Layer names | | `--text-body` | `1rem` | 400 | Public Sans | 1.6 | 0 | Paragraph copy | | `--text-small` | `0.88rem` | 400 | Public Sans | 1.55 | 0 | Table cells, footer, meta lines | | `--text-label` | `0.78rem` | 700 | Public Sans | 1 | 0.05em, uppercase | Form labels, table headers | Literata carries an organic, slightly wonky serif weight that reads as grown rather than manufactured, reserved for headlines only, never body copy, where its optical-size axis would fight legibility at small sizes. Public Sans carries every sentence and every control at 400 for prose and 700 for labels, with no third weight needed. ## Spacing Scale Base unit `8px`. Tokens in use: `--space-2: .5rem`, `--space-3: 1rem`, `--space-4: 1.5rem`, `--space-6: 2rem`, `--space-8: 3rem`, `--space-12: 4rem`. ## Shape `--radius: 10px` on cards, buttons, inputs, and the table wrap. `--radius-sm: 6px` on badges and swatch chips. Soft enough to read as glass and rounded stone, not the pill-shaped warmth of the candy systems elsewhere in the gallery, and not the zero-radius machined edge of the trade-shop system next to it. The one deliberate exception is the layers page's notice, which drops to `0` specifically because a `3px` accent-colored rule against a rounded corner reads as a collision, not a feature, see Deliberate Exception below. ## Iconography No icon library. The only non-text graphics are the logo mark and the hero's own jar cross-section (hand-built SVG). Stock status is a solid dot paired with the word, never a checkmark or leaf icon standing in for it. ## Motion Principles Scroll builds a terrarium bottom-up: four layers, each a fixed-geometry rect clipped to the jar's rounded interior, scale from `0` to `1` on their vertical axis with a bottom-anchored transform origin, so each one visibly fills upward from where the layer below it ends. The moment a layer finishes filling, the card representing it slides in below. Once all four have landed, a dome, drawn as a shallow arc sitting above the jar's rim, animates down onto the opening, and a "SEALED" readout fades in. One-shot build: nothing loops, and every layer is written out in the card grid and the table regardless of scroll or script state. The scroll trigger measures the page's real available scroll room below the stage at load and caps the build distance at that figure minus a `16px` safety margin (never more than `460px`), instead of assuming a fixed distance is always scrollable. The margin exists because scrolling to the page's true maximum can land a few pixels short of an unpadded measurement, layout can still settle slightly after the measurement runs, which otherwise leaves progress permanently just under `1` and the dome never quite sealing, a subtler version of the same class of bug this system's trigger was already built to avoid, confirmed the same day on Broadside Press. A viewport tall enough to leave under `20px` of room, or a `prefers-reduced-motion` visitor, gets the fully-built, sealed jar immediately with no scroll listener attached at all, the same resolved-state rule this gallery already applies elsewhere, extended here to cover a viewport that structurally cannot scroll far enough to complete a fixed-distance sequence. ## Voice & Tone Quiet, plainly informative, no mystical plant-magic language. Understory talks like someone who actually knows what's in the jar, not like a shop selling a vibe. In practice: - **Primary button**: "Build your own", not "Bring nature indoors" - **Layer description**: "Keeps a closed jar from turning sour. A thin layer is enough.", not "Purifies and balances your ecosystem" - **Why it's sealed**: "A closed jar recycles its own water. Once the moss takes, it rarely needs watering again, that's the entire point of the dome, not decoration.", not "A magical self-sustaining world" - **Form error**: "That doesn't look like a full email address. Check it and resend.", not "Invalid input" - **Pickup note**: "We'll email when it's built and sealed, usually 2 to 3 days." The tell that a sentence is off-brand: it describes the jar as magic, alive with intention, or a lifestyle instead of naming the actual material and the actual job it does. ## Accessibility Baseline - **Focus ring**: `2px solid var(--accent)` at `2px` offset via `:focus-visible` on every link, button, input, and select, never removed. - **Reduced motion**: honored on the layer build and the dome seal, as described under Motion Principles. No scroll listener is attached at all when reduce is set, or when there's no real scroll room to attach one to meaningfully. - **The jar graphic is `aria-hidden`.** It's a continuously changing illustration tied to scroll position. Nothing is communicated by it alone: every layer's name, job, amount, and price is in the card grid below in plain markup at all times, and again in the second page's table. - **Tap targets**: `44px` minimum height on every button, nav link, and form control. - **Color is never the only signal**: every stock status pairs a dot with the word "In stock", not left as color alone. - **Skip link**: the second page opens with a `position: fixed` skip link to its main content. - **Focus is never dropped**: on a successful build request, focus moves to the confirmation message, and the email field is set `readonly` rather than `disabled`, so focus is never dumped to the document root. - **Form errors**: the email field sets `aria-invalid`, changes its border to `--accent-alt`, and the helper text is replaced with a sentence naming the actual problem. - **Structure**: one `

` per page, headings in order, `nav` / `main` / `footer` landmarks, a real `` on the layers table, `scope="col"` on every header cell, `aria-current="page"` on the active nav link. ## Components ### Navigation Single line, `64px`, `--panel` with a `--border` bottom hairline, sticky. Left: the mark plus wordmark lockup. Right: a "Layers" link and exactly one primary button. Below 480px the first nav link drops, the primary button stays. ### Buttons Two variants, `10px` radius, `44px` minimum height: - **Primary**: `--accent` fill, a near-black `#08110b` label, brightness lift on hover. Once per view, for the real action ("Build your own", "Send request"). - **Secondary**: transparent, `1px solid --border-ui`, `--ink` label, edge and label both going `--accent` on hover. ### The jar (signature) Documented in full under Motion Principles. Structurally: a static rounded-rect glass body with a `--border-ui` outline, a clip-path masking four layer rects to the jar's rounded interior, and a separate dome group that translates down onto the rim. ### Layer card A flat panel: `--panel` background, `1px solid --border`, `10px` radius. Holds a swatch, the layer's name, a real material description, the price, and a stock badge. Present in the DOM at all times, decorated by the reveal, never gated by it. ### Layer table (second page) `--raised` header row in `--text-label`, `--panel` body rows, one `--border` rule between rows, a `` naming the sort order. A swatch cell pairs a small color chip with the layer's name, so the same color reading appears on both pages. Below 700px the table restacks into one bordered block per layer, never hidden behind horizontal scroll. ### Notice A `--panel` block with a `3px` `--accent-alt` left rule, carrying the sealed-jar explanation. Square, not rounded, unlike every other panel on the page: see Deliberate Exception below. ### Form Label above control in `--text-label`. Select and input share `--bg` fill, `1px solid --border-ui`, `6px` radius, `44px` height, `--raised` fill plus an accent border on focus. Helper text sits below at a reserved minimum height so validating doesn't shift the layout. On success the helper goes moss, restates the jar size and email back to the visitor, and focus moves to the confirmation before the field is set read-only. ### Footer Two-part row: an honest note that the shop is fictional, and a link list back to the other page and the gallery. No invented legal text. ## Do's and Don'ts ### Do - Fill each layer from the bottom, anchored where the layer below it ends. - Use `--accent` for the moss layer's fill and for nothing that isn't genuinely "the part you see." - State a layer's job in one plain sentence, tied to what it actually does in a sealed jar. - Keep the jar `aria-hidden` and write every fact it shows in plain text nearby. - Measure real scroll room before choosing a scroll-trigger distance, never assume one. ### Don't - Don't use a stock photo of a jungle, a hand cupping soil, or a styled windowsill. - Don't write "bring nature indoors" or any lifestyle copy that skips the actual material. - Don't use a cartoon terrarium mascot or a sparkle/glow effect on the moss. - Don't replay the build sequence on every scroll pass, it plays once, like a real jar being assembled. - Don't round a thick accent-colored rule into a corner, square the element instead. - Don't invent a second green or a blue token, moss is the only green in the interface. ### Deliberate exception The layers page's `.notice` element went through two rounds with the mechanical detector. The first draft used a `3px` `--accent-alt` left rule on a `10px`-radius panel, which the detector flagged twice over: `side-tab`, the single most recognizable AI-generated-UI tell there is, a thick colored stripe down one side of a card, and `border-accent-on-rounded`, the same thick-border-against-a-corner collision Keyway hit the same day. The rule moved from the left edge to the top, matching Keyway's own resolution, and the radius dropped to `0`, so the notice reads as a plate, not a card. The static-HTML pass of the detector still reports `border-accent-on-rounded` on the resulting `border-top: 3px solid`, because its regex engine matches the property text alone and can't see that `.notice` has `border-radius: 0`, the exact same false positive Keyway's own notice produced and documented the same day. The element is square, confirmed by reading the resolved CSS rule directly. ## Known Constraints - Two routes exist (home, layers). There is no finished-terrarium storefront and no authenticated state. "Send request" produces a client-side confirmation only, this is a concept demo. - The jar holds exactly four layers because a real closed terrarium needs exactly those four functions. A fifth material would need a fifth band, not a relabeled existing one. - Responsive behavior is two breakpoints (900px, 700px, 480px) plus fluid type. There is no tablet-specific layout beyond the table's restack point. - The form's email validation is a plain-text pattern check, sufficient for a concept demo, not a production validator. ## Process Built through the impeccable-audited anti-slop workflow: never-list written before any HTML, page structure derived from the signature interaction (four layers, one dome) rather than a template, the scroll-trigger distance computed from real measured scroll room from the outset given the lesson from Keyway earlier the same day, and the mechanical detector run clean.