Cloud security posture across three AWS accounts, built as a wall display for a security operations room. It answers one question, from six metres: is anything wrong.
Direction
| Lever | Decision |
|---|---|
| Archetype | Console. Ten regions on a fixed grid, filling one viewport at 1400px and wider with nothing to scroll. |
| Data stance | Chart-first. Six of the ten regions carry a drawn mark; every figure states its comparison. |
| Density | Cloudscape compact. space-scaled-* at the compact column for gaps that should compress, space-static-* for gaps that must not. |
| The one bold thing | The legend prints the contrast ratio. A 2px line takes the -500 step, a filled bar takes -400, and the browser measures both at load and on every theme change — per series where the two lines differ, and as one range under the four fills. |
The eleventh tile was drafted — a cost-anomaly panel — and cut. Ten is what a person reads in fifteen seconds; eleven makes it a report.
Generator
cloudscape_tokens.py --both > tokens.css # the linked file, never hand-edited
cloudscape_tokens.py --charts # the 155 data-visualisation tokens
cloudscape_tokens.py --density # comfortable vs compact, 24 of 140 differ
tokens.css is generated output. Every colour and every radius on the page is a var() from it.
Where the ratio is printed, and where it is not
The two trend series print their own measured ratio, because the two lines are a real choice: -500 for a 2px mark, and the number differs by theme. The four severity fills do not. Measured, they land inside 0.03 of each other in either theme, so four ratios in four legend slots discriminate nothing and cost the slot the count needs; the four slots carry 21, 47, 108 and 72, and the range the four fills actually hold is stated once under the chart. The measurement is still live in both places — read from the browser at load and on every theme change, never transcribed.
Ranking, and the verdict that has to be true
Six of the ten regions are a measure with a target. Each states in its own head whether it is inside that target, at two levels: error for what the verdict pages on, warning for what is tracked. One region reads 9 of 9 under a tick. That is what makes the bottom row rank from six metres rather than read left to right.
The verdict counts the same way. It names the two over threshold, then the four outside target and the account three of them sit in. It said "Everything else sits inside its threshold" under a green tick until the critique round, over four regions that each print a target they miss.
The two states
Reporting. Every feed is live, the trend line is solid to today, and the detector tile reads 9 of 9.
One feed stopped. Security Hub in eu-west-1 goes quiet at 13:41. The trend line stops at the last real reading and a faint dashed segment bridges the gap with no markers and no end label; the sandbox account's stacked bar disappears and its total becomes an em dash; the detector tile says Not reporting rather than 3 of 3; the verdict changes from "Two measures are over threshold" to "Two over threshold, one unknown". Zero and no data are two different marks, which is the whole point of the state — a stale value that looks live is worse than no value.
Measured contrast
Every pair sits on color-background-container-content (#ffffff light, #161d26 dark) and every number comes from web-design/scripts/contrast.py.
| What | Token | Light | Dark | Floor |
|---|---|---|---|---|
| A value read off a region | color-text-body-default | 18.50:1 | 9.98:1 | 4.5:1 |
| Critical line, 2px | color-charts-red-500 | 5.01:1 | 6.21:1 | 3:1 |
| High line, 2px | color-charts-orange-500 | 5.00:1 | 6.23:1 | 3:1 |
| Critical bar, filled | color-charts-red-400 | 4.01:1 | 4.99:1 | 3:1 |
| Low bar, filled | color-charts-blue-1-400 | 4.03:1 | 4.96:1 | 3:1 |
| Primary button label | color-text-button-primary-default | 4.97:1 | 8.11:1 | 4.5:1 |
In the light theme the number in a chart token's name is its ratio to the second decimal. In the dark theme it is a floor: the -500 step measures 6.21:1 rather than 5:1, because that ramp is indexed against a lighter surface than this one. The legend prints what the browser measures, so a reader sees the difference rather than taking it on trust.
Measuring also found one thing reading the names could not. color-charts-red-300 is 2.998:1 on white — it rounds to 3.00:1 and misses WCAG 1.4.11's floor for a graphic by a thousandth, while orange, yellow and blue at the same step clear it. The four filled bars take the -400 step instead, which holds 4.00:1 or better in light and 4.96:1 or better in dark across all four hues. A palette indexed by ratio still has to be measured.
Deviations
- Spacing and motion are authored.
cloudscape_tokens.pyemits colour, radius, border, type and shadow as CSS and stops there;--densityand the skill's typography reference print the space and motion scales but emit neither. The page declares both families at the top of its own stylesheet, copying the compact column and the motion table verbatim. - The modal scrim is a
color-mix(). The embedded token set has nocolor-background-modal-overlay, so the scrim is 62% ofcolor-background-home-header, which is#0f141ain both themes. No literal. AppLayoutis a React component. Its three regions are present as markup — top navigation, the notifications slot, the content grid — and nothing composes a second shell beside them.- The console never scrolls; the document does. The spec panel sits below the viewport-height grid, which the portfolio asks for and the product does not.
<meta name="theme-color">carries two literals. A meta attribute cannot hold avar(); the two values arecolor-background-layout-mainin each theme.
Kept warnings
copy-lint.ignore keeps operator-copy, which fires on 0.0.0.0/0. On a cloud security console the CIDR block is the finding itself, in the vocabulary the reader uses to write the security-group rule that caused it.