Organism · beta · v1.0.0 · owner: Site system lead · contract 19 of 20 points (open: figma)
Do and don't
Do: Defaults
Rendered by cost-calculator.tsOpen example: Cost of drift calculator · Do: Defaults Use the full-page example to follow page links.
Do: With sources
Rendered by cost-calculator.tsOpen example: Cost of drift calculator · Do: With sources Use the full-page example to follow page links.
Don't: A savings promise
Rendered by cost-calculator.tsOpen example: Cost of drift calculator · Don't: A savings promise Use the full-page example to follow page links.
Don't: Uncited defaults
Rendered by cost-calculator.tsOpen example: Cost of drift calculator · Don't: Uncited defaults Use the full-page example to follow page links.
Rules
| Rule | Why | Check |
|---|---|---|
| Cost of drift calculator is computed by Loom's code from the formula (D + E) × r × W × C with cited defaults (scripts/site/roi/inputs), not drawn by hand. | A showcase is proof only if the product's own code produced what it shows. | Automated: scripts/site/interactives/check.ts (recomputes the showcase data and fails on drift) |
| Every default shows its source link. | An estimate is only as good as its inputs. | Automated: tests/unit/site-roi.test.ts |
API
No props.
Contract details
The full contract: identity, anatomy, variants, states, tokens, layout, motion, accessibility, content, tests
Identity
| Aspect | Value |
|---|---|
| Id | cost-calculator |
| Layer | Organisms |
| Category | showcase |
| Owner | Site system lead |
| Status | beta |
| Version | 1.0.0 |
| Source | apps/showcase/site/system/organisms/cost-calculator.ts (private repository; the contract and the render function are this one module) |
| Version | Date | Change |
|---|---|---|
| 1.0.0 | 2026-10-26 | Cost of drift calculator: computed by Loom's code from the formula (D + E) × r × W × C with cited defaults (scripts/site/roi/inputs). |
Use when
- The cost-of-drift article and the CEO role page. Why: A number the reader computes is more convincing than one we state.
Do not use when
- As a promise of savings. Why: It is an estimate from the reader's inputs, not a claim by Loom; it says so.
Anatomy
| Part | What it is |
|---|---|
| form | div.roi-form: roi-field inputs with units and hints |
| formula | p.roi-formula |
| results | div.roi-results |
| note | p.roi-note: not a claim by Loom, sources linked |
Made of: showcase-frame, form-field, caption.
Used in: pages directly.
Variants
| Variant | When | Why |
|---|---|---|
| full | On its demo page and its section. | The whole experience where the claim is made. |
States
| State | Rendered or not applicable |
|---|---|
| default | Cited defaults and their result. |
| hover | Inputs show hover. |
| focus-visible | --focus on every input. |
| active | Not applicable: Inputs, no pressed state. |
| disabled | Not applicable: Nothing disabled. |
| loading | Not applicable: Calculation runs synchronously from input. The no-JavaScript worked example is a static fallback, not a pending calculation. |
| empty | An empty or nonfinite input uses the cited default in calculation; static hints/sources remain visible. |
| error | Finite out-of-range readings are bounded by the calculation model. Native min/max attributes and static hints remain; no dynamic error text or aria-invalid update is implemented. |
| current | Not applicable: No steps. |
| visited | Not applicable: Not a link. |
| expanded | Not applicable: Does not open. |
Tokens
| Part | Token |
|---|---|
| result.font | --type-h2 (figures) |
| field.border | --line-strong |
| --cost-calculator-gap-space-8 | var(--space-8) |
| --cost-calculator-margin-block-space-9 | var(--space-9) |
| --cost-calculator-top-header-h | var(--header-h) |
| --cost-calculator-top-space-6 | var(--space-6) |
| --cost-calculator-gap-space-7 | var(--space-7) |
| --cost-calculator-padding-space-6 | var(--space-6) |
| --cost-calculator-border-line | var(--line) |
| --cost-calculator-border-radius-radius | var(--radius) |
| --cost-calculator-background-surface | var(--surface) |
| --cost-calculator-padding-space-3 | var(--space-3) |
| --cost-calculator-font-type-strong | var(--type-strong) |
| --cost-calculator-color-ink | var(--ink) |
| --cost-calculator-gap-space-3 | var(--space-3) |
| --cost-calculator-padding-block-space-5 | var(--space-5) |
| --cost-calculator-border-top-line | var(--line) |
| --cost-calculator-font-type-label | var(--type-label) |
| --cost-calculator-color-ink-2 | var(--ink-2) |
| --cost-calculator-padding-space-4 | var(--space-4) |
| --cost-calculator-padding-space-5 | var(--space-5) |
| --cost-calculator-font-type-figure | var(--type-figure) |
| --cost-calculator-background-cotton | var(--cotton) |
| --cost-calculator-border-ink-2 | var(--ink-2) |
| --cost-calculator-outline-focus | var(--focus) |
| --cost-calculator-border-color-ink | var(--ink) |
| --cost-calculator-border-color-madder-ink | var(--madder-ink) |
| --cost-calculator-font-size-fs-small | var(--fs-small) |
| --cost-calculator-padding-space-7 | var(--space-7) |
| --cost-calculator-border-ink | var(--ink) |
| --cost-calculator-margin-space-5 | var(--space-5) |
| --cost-calculator-font-type-h3 | var(--type-h3) |
| --cost-calculator-letter-spacing-ls-h3 | var(--ls-h3) |
| --cost-calculator-gap-space-1 | var(--space-1) |
| --cost-calculator-font-size-fs-h3 | var(--fs-h3) |
| --cost-calculator-color-indigo-ink | var(--indigo-ink) |
| --cost-calculator-background-surface-2 | var(--surface-2) |
| --cost-calculator-padding-space-1 | var(--space-1) |
| --cost-calculator-margin-top-space-5 | var(--space-5) |
| --cost-calculator-margin-space-4 | var(--space-4) |
| --cost-calculator-padding-left-space-7 | var(--space-7) |
| --cost-calculator-gap-space-2 | var(--space-2) |
Layout
| Aspect | Value |
|---|---|
| breakpoints | Required review at 390, 768, 1024 and 1440 px; execution is not implied by this declaration; columns stack under 768 px and controls fill the row on phones. |
| wrapping | Text wraps; code panes scroll inside their own focusable region. |
| textExpansion | Japanese strings come from the i18n tables and wrap; engine output stays English. |
Spacing and rhythm
| Aspect | Value |
|---|---|
| internal | Inside the Showcase frame's padding, controls and caption at its foot. |
| external | Full width of its section. |
| rhythm | One showcase per section, after the section's claim (D-SITE-008). |
Motion
| Aspect | Value |
|---|---|
| None | The result updates instantly; numbers do not animate. |
Runtime and reuse
Render-only element; mount any registered child enhancements separately. Preserve required Site CSS, fonts, responsive and locale rules from nativeElement.
Accessibility passport
| Aspect | Value |
|---|---|
| role | region with a heading, or a figure |
| name | Its heading or caption |
| keyboard | Tab through number inputs; typing recomputes results synchronously. The containing element prevents form submission. |
| focus | Typing retains focus on the number input; updating results does not move it. |
| screenReader | Labels/units/static hints are associated with inputs; the two headline results are announced after the existing800ms typing pause, not as async calculation loading. |
| contrast | Ink on surface 14:1; state dyes only as marks with words. |
| target | Controls are 44 px. |
Content
| Slot | Length | Voice |
|---|---|---|
| note | 2 sentences | An estimate, not a claim |
Good: “Your inputs, our formula. Not a claim by Loom.”
Bad: “Save $1M”
Japanese: English only (the article is English).
Icons and illustrations
None: this element uses no icon or illustration of its own.
Decisions and exceptions
Usage
110 uses across the built pages (measured by npm run site:system).
Tests
| Aspect | Value |
|---|---|
| unit | tests/unit/site-system.test.ts: renders every do and don't example through the registry and checks the classes it owns |
| visual | Not yet: there are no screenshot baselines; the Library page is checked for sideways scroll at 1440 px by tests/site/system.spec.ts |
| a11y | tests/site/system.spec.ts: axe (0 serious or critical) on the element's Library page in light and dark |
| parity | scripts/site/system/check.ts: Library examples are rendered by the same render function the site imports |
Figma
not-yet: The Loom Site Figma library is the last stage of the plan; this element is not mapped yet.