@humanforest/slidev-theme 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +67 -0
- package/README.md +577 -0
- package/assetUrl.js +38 -0
- package/components/Alert.vue +209 -0
- package/components/AreaBox.vue +152 -0
- package/components/AreaChart.vue +145 -0
- package/components/Avatar.vue +267 -0
- package/components/AvatarGroup.vue +94 -0
- package/components/BadgeStrip.vue +203 -0
- package/components/Bars.vue +207 -0
- package/components/BoroughShape.vue +127 -0
- package/components/BrowserFrame.vue +484 -0
- package/components/CalendarGrid.vue +480 -0
- package/components/Card.vue +70 -0
- package/components/Carousel.vue +266 -0
- package/components/ChartFrame.vue +426 -0
- package/components/ChordChart.vue +264 -0
- package/components/ComposedChart.vue +187 -0
- package/components/Contents.vue +86 -0
- package/components/DataTable.vue +244 -0
- package/components/DeckMap.vue +409 -0
- package/components/DeviceFrame.vue +331 -0
- package/components/DonutChart.vue +103 -0
- package/components/DottedMap.vue +126 -0
- package/components/Endorsement.vue +76 -0
- package/components/Grid.vue +169 -0
- package/components/GroupedBarChart.vue +141 -0
- package/components/IconTile.vue +116 -0
- package/components/Kpi.vue +188 -0
- package/components/LineChart.vue +133 -0
- package/components/Logo.vue +203 -0
- package/components/LondonMap.vue +348 -0
- package/components/MarkPair.vue +109 -0
- package/components/Marquee.vue +263 -0
- package/components/NestedDonutChart.vue +295 -0
- package/components/Placeholder.vue +88 -0
- package/components/SankeyChart.vue +223 -0
- package/components/ScatterChart.vue +142 -0
- package/components/StackedBarChart.vue +143 -0
- package/components/StatCard.vue +134 -0
- package/components/StatusTrack.vue +334 -0
- package/components/Timeline.vue +249 -0
- package/components/TimelineChart.vue +329 -0
- package/components/TreemapChart.vue +267 -0
- package/components/backgrounds.js +135 -0
- package/components/boroughGeometry.ts +59 -0
- package/components/chartData.ts +277 -0
- package/components/chartTip.ts +201 -0
- package/components/codeTheme.ts +239 -0
- package/components/deckPalette.ts +157 -0
- package/components/dottedMapGeometry.js +60 -0
- package/components/iconSlugs.js +55 -0
- package/components/iconUrl.js +51 -0
- package/components/mermaidTheme.ts +972 -0
- package/components/motionDuration.ts +41 -0
- package/components/tableRules.ts +257 -0
- package/confidential-mark.vue +151 -0
- package/custom-nav-controls.vue +79 -0
- package/endorsements/b-corp-black.svg +30 -0
- package/endorsements/b-corp-white.svg +30 -0
- package/global-top.vue +1662 -0
- package/icons/forest/bike-asterisk.svg +21 -0
- package/icons/forest/bike-down.svg +19 -0
- package/icons/forest/bike-off.svg +20 -0
- package/icons/forest/bike-up.svg +19 -0
- package/icons/forest/bike-x.svg +19 -0
- package/icons/forest/bike.svg +18 -0
- package/icons/forest/mark-fill.svg +6 -0
- package/icons/forest/mark.svg +6 -0
- package/icons/forest/parking.svg +3 -0
- package/icons/forest/star-fill.svg +4 -0
- package/icons/forest/star.svg +4 -0
- package/icons/forest/traffic-light-caution.svg +8 -0
- package/icons/forest/traffic-light-go.svg +8 -0
- package/icons/forest/traffic-light-lit.svg +8 -0
- package/icons/forest/traffic-light-stop.svg +8 -0
- package/icons/forest/traffic-light.svg +8 -0
- package/icons/lucide/battery.svg +1 -0
- package/icons/lucide/bike.svg +1 -0
- package/icons/lucide/chart-line.svg +1 -0
- package/icons/lucide/check.svg +1 -0
- package/icons/lucide/circle-check.svg +1 -0
- package/icons/lucide/clock.svg +1 -0
- package/icons/lucide/code.svg +1 -0
- package/icons/lucide/component.svg +1 -0
- package/icons/lucide/frame.svg +1 -0
- package/icons/lucide/layout-grid.svg +1 -0
- package/icons/lucide/map-pin.svg +1 -0
- package/icons/lucide/map.svg +1 -0
- package/icons/lucide/palette.svg +1 -0
- package/icons/lucide/table.svg +1 -0
- package/icons/lucide/triangle-alert.svg +1 -0
- package/icons/lucide/type.svg +1 -0
- package/icons/lucide/wrench.svg +1 -0
- package/layouts/bleed.vue +317 -0
- package/layouts/cover.vue +1514 -0
- package/layouts/default.vue +187 -0
- package/layouts/divider.vue +286 -0
- package/layouts/end.vue +384 -0
- package/layouts/quote.vue +218 -0
- package/layouts/split.vue +1875 -0
- package/layouts/stack.vue +944 -0
- package/layouts/statement.vue +67 -0
- package/manifest/chrome.ts +36 -0
- package/manifest/components.ts +49 -0
- package/manifest/index.ts +57 -0
- package/manifest/layouts.ts +37 -0
- package/manifest/manifest.json +6514 -0
- package/manifest/presets.ts +635 -0
- package/notch.js +458 -0
- package/notchPath.js +188 -0
- package/package.json +77 -0
- package/section.js +110 -0
- package/setup/mermaid-renderer.ts +2120 -0
- package/setup/mermaid.ts +30 -0
- package/setup/shiki.ts +44 -0
- package/setup/shortcuts.ts +28 -0
- package/slide-bottom.vue +305 -0
- package/slide-top.vue +268 -0
- package/styles/base.css +2611 -0
- package/styles/index.ts +71 -0
- package/styles/tokens.css +144 -0
- package/uno.config.ts +231 -0
- package/useNotchCuts.js +200 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/*
|
|
3
|
+
A line chart sized for a room — one to five series over a shared category axis.
|
|
4
|
+
|
|
5
|
+
Built on @unovis/vue directly, which is the deck's whole chart stack: the marks are real Unovis
|
|
6
|
+
components, the ceremony is ChartFrame.vue, and the data shaping is chartData.ts. Colours come
|
|
7
|
+
from --deck-series-1…5, which alias the published categorical palette in the product's order, so
|
|
8
|
+
a deck line and a product line of the same data are the same chart.
|
|
9
|
+
|
|
10
|
+
A line CLAIMS the space between its points, so it is the right mark only when the x axis is a
|
|
11
|
+
real sequence — weeks, months, versions. For unordered categories the claim is false and the
|
|
12
|
+
chart wants bars.
|
|
13
|
+
|
|
14
|
+
Missing readings stay missing: chartData.ts never coerces a null to zero, and Unovis renders the
|
|
15
|
+
gap. A run of rows with no readings at all draws the empty state rather than a line along the
|
|
16
|
+
floor.
|
|
17
|
+
*/
|
|
18
|
+
import { computed } from 'vue';
|
|
19
|
+
import { VisXYContainer, VisAxis, VisLine, VisCrosshair, VisTooltip } from '@unovis/vue';
|
|
20
|
+
import { deckTooltip, seriesTip } from './chartTip';
|
|
21
|
+
import { motionDuration } from './motionDuration';
|
|
22
|
+
import ChartFrame from './ChartFrame.vue';
|
|
23
|
+
import { chartState, formatValue, labelAt, stateMessage, tickIndices, CROSSHAIR_CIRCLE_R, CROSSHAIR_HIDE_WHEN_FAR } from './chartData';
|
|
24
|
+
|
|
25
|
+
const props = defineProps({
|
|
26
|
+
/** Rows: `[{ label, <seriesKey>: number, … }]`. `label` names the point on the x axis. */
|
|
27
|
+
data: { type: Array, required: true },
|
|
28
|
+
/** Series to plot, in legend order: `[{ key, name }]`. Omit and every numeric field on the first row becomes a series. */
|
|
29
|
+
series: { type: Array, default: null },
|
|
30
|
+
/** Plot height in px. 0 FILLS, and is the default: the chart takes whatever box it is given,
|
|
31
|
+
* which is what a chart under a heading wants. Unovis resolves `config.height || containerHeight`,
|
|
32
|
+
* so a falsy height makes the container measure itself. Pass a number where the box has no
|
|
33
|
+
* definite height of its own — a tiled grid cell, a figure beside copy. */
|
|
34
|
+
height: { type: Number, default: 0 },
|
|
35
|
+
/** Draw the series legend. */
|
|
36
|
+
legend: { type: Boolean, default: true },
|
|
37
|
+
/** Where the legend sits: `top-left`, `top-center`, `top-right`, `bottom-left`, `bottom-center`
|
|
38
|
+
* or `bottom-right`. The EDGE is reading order — above is read before the marks, below is a
|
|
39
|
+
* caption read after them; the ALIGNMENT is for the column the chart sits in, centred under a
|
|
40
|
+
* full-width plot and pulled to an edge where a centred key would float in a gutter. */
|
|
41
|
+
legendPosition: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: 'bottom-center',
|
|
44
|
+
validator: (p) => ['top-left', 'top-center', 'top-right', 'bottom-left', 'bottom-center', 'bottom-right'].includes(p),
|
|
45
|
+
},
|
|
46
|
+
/** Title under the x axis. Omit when the labels already say what they are. */
|
|
47
|
+
xLabel: { type: String, default: null },
|
|
48
|
+
/** Title beside the y axis. Omit when the unit is in the slide's heading. */
|
|
49
|
+
yLabel: { type: String, default: null },
|
|
50
|
+
/** Formats the y tick labels. Defaults to British grouping, compact past four digits. */
|
|
51
|
+
format: { type: Function, default: formatValue },
|
|
52
|
+
/** Ceiling on x tick labels. Deck labels are 36px, so more than a handful collide. */
|
|
53
|
+
maxTicks: { type: Number, default: 6 },
|
|
54
|
+
/** Draw the two axes. Off is a SHAPE reading — the marks alone, no scale — for a chart that is
|
|
55
|
+
* being shown rather than read: a specimen, a register test, a sparkline beside a figure.
|
|
56
|
+
*
|
|
57
|
+
* ★ IT REMOVES THEM, IT DOES NOT HIDE THEM. Unovis reserves the axis band whether or not the
|
|
58
|
+
* axis paints, so hiding one in CSS leaves the plot inset by a tick row and a label column that
|
|
59
|
+
* are no longer there — measured at a 150px plot, the marks drew 22px of a 63px box. Not
|
|
60
|
+
* rendering the component is what gives the room back.
|
|
61
|
+
*
|
|
62
|
+
* ⚠ A chart carrying VALUES needs its scale. Turning this off says the reading is the shape. */
|
|
63
|
+
axes: { type: Boolean, default: true },
|
|
64
|
+
/** The sentence drawn when there is nothing to plot. */
|
|
65
|
+
empty: { type: String, default: 'No data' },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const state = computed(() => chartState(props.data, props.series));
|
|
69
|
+
const message = computed(() => (state.value.kind === 'ready' ? null : stateMessage(state.value, props.empty)));
|
|
70
|
+
const seriesList = computed(() => (state.value.kind === 'ready' ? state.value.series : []));
|
|
71
|
+
|
|
72
|
+
const x = (_d, i) => i;
|
|
73
|
+
const y = computed(() => seriesList.value.map((s) => (d) => d[s.key]));
|
|
74
|
+
const colors = computed(() => seriesList.value.map((s) => s.color));
|
|
75
|
+
|
|
76
|
+
const xTicks = computed(() => tickIndices(props.data?.length ?? 0, props.maxTicks));
|
|
77
|
+
const xFormat = (v) => labelAt(props.data ?? [], v);
|
|
78
|
+
|
|
79
|
+
/*
|
|
80
|
+
★ THE CROSSHAIR CARRIES THE TOOLTIP, not the line. A line chart is read at an x position — every
|
|
81
|
+
series has a value there — so the hover target is the COLUMN, not the stroke. Hovering a 6px line
|
|
82
|
+
to a pixel is not something anyone does from a lectern, and a per-mark trigger would report one
|
|
83
|
+
series where the reader is comparing all of them.
|
|
84
|
+
|
|
85
|
+
The title falls out of the row itself: `forestTooltip` defaults it to the datum's first property,
|
|
86
|
+
and the deck's rows lead with `label`.
|
|
87
|
+
*/
|
|
88
|
+
/* Read at mount so `prefers-reduced-motion` is honoured; see motionDuration.ts. */
|
|
89
|
+
const crosshairMs = motionDuration('instant');
|
|
90
|
+
|
|
91
|
+
const tip = computed(() => seriesTip(seriesList.value, props.format));
|
|
92
|
+
</script>
|
|
93
|
+
|
|
94
|
+
<template>
|
|
95
|
+
<ChartFrame
|
|
96
|
+
:series="seriesList"
|
|
97
|
+
:height="height"
|
|
98
|
+
:legend="legend"
|
|
99
|
+
:legend-position="legendPosition"
|
|
100
|
+
:message="message"
|
|
101
|
+
>
|
|
102
|
+
<VisXYContainer :data="data" :height="height" :duration="0" :margin="{ top: 16, right: 24, bottom: 0, left: 0 }">
|
|
103
|
+
<!-- 6px, not the library's 2: a 2px stroke is a hairline on a projector, and the line is the
|
|
104
|
+
whole message. -->
|
|
105
|
+
<VisLine :x="x" :y="y" :color="colors" :line-width="6" />
|
|
106
|
+
<VisAxis
|
|
107
|
+
v-if="axes"
|
|
108
|
+
type="x"
|
|
109
|
+
:grid-line="false"
|
|
110
|
+
:tick-line="false"
|
|
111
|
+
:tick-values="xTicks"
|
|
112
|
+
:tick-format="xFormat"
|
|
113
|
+
:tick-padding="16"
|
|
114
|
+
:label="xLabel"
|
|
115
|
+
:label-margin="24"
|
|
116
|
+
tick-text-hide-overlapping
|
|
117
|
+
/>
|
|
118
|
+
<VisAxis
|
|
119
|
+
v-if="axes"
|
|
120
|
+
type="y"
|
|
121
|
+
:tick-format="format"
|
|
122
|
+
:num-ticks="4"
|
|
123
|
+
:tick-line="false"
|
|
124
|
+
:domain-line="false"
|
|
125
|
+
:tick-padding="16"
|
|
126
|
+
:label="yLabel"
|
|
127
|
+
:label-margin="24"
|
|
128
|
+
/>
|
|
129
|
+
<VisCrosshair :duration="crosshairMs" :hideWhenFarFromPointer="CROSSHAIR_HIDE_WHEN_FAR" :circleRadius="CROSSHAIR_CIRCLE_R" :x="x" :y="y" :template="tip" :color="colors" />
|
|
130
|
+
<VisTooltip v-bind="deckTooltip()" />
|
|
131
|
+
</VisXYContainer>
|
|
132
|
+
</ChartFrame>
|
|
133
|
+
</template>
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
/*
|
|
3
|
+
The Forest identity, in the four forms the brand ships — and the reason this is a component
|
|
4
|
+
rather than an <img> in a layout is that two of them cannot be drawn the same way.
|
|
5
|
+
|
|
6
|
+
mark the rounded tile with the white tree, drawn as INLINE paths rather than the
|
|
7
|
+
generated mark.svg. Its four parts do not share one rule: the square is
|
|
8
|
+
forest-700 and the tree white, both brand constants; the RING is currentColor,
|
|
9
|
+
so it reverses with the register; the inner keeps forest-950. That is
|
|
10
|
+
packages/ui/src/logo/FLogo.vue's own split, part for part.
|
|
11
|
+
wordmark "forest" as outlines, shipped as a single dark fill. Painted straight it
|
|
12
|
+
dies on any dark ground — so it is drawn as a MASK and filled with
|
|
13
|
+
currentColor, which lets it reverse. The colour that fills it is the BRAND's
|
|
14
|
+
ink, pinned in the stylesheet below (forest-950, white on .deck-dark), not
|
|
15
|
+
whatever the surrounding slide is set in.
|
|
16
|
+
lockup mark + wordmark, composed HERE rather than taken from lockup-horizontal.svg.
|
|
17
|
+
That file is one flat SVG: masking it would flatten the mark's fixed green
|
|
18
|
+
along with the ink. Composing keeps the two halves on their own rules — the
|
|
19
|
+
mark's green is a brand constant, the ink follows the REGISTER.
|
|
20
|
+
lockup-vertical the same composition, stacked.
|
|
21
|
+
|
|
22
|
+
★★ THE MARK USED TO BE AN <img src="mark.svg">, AND THAT MADE IT A ONE-REGISTER LOGO. Every fill in
|
|
23
|
+
that file is baked, so the ring stayed Darkest Forest on a dark slide where the product reverses it
|
|
24
|
+
to white — the wordmark beside it turned and the mark did not. The docs' own logo page states the
|
|
25
|
+
rule the deck was quietly breaking: "the mark stays green; the ink — wordmark AND OUTLINE — is
|
|
26
|
+
forest-950 on light and reverses to white on dark". Inline paths are what let the outline take
|
|
27
|
+
currentColor, which is the only reason this is markup rather than a file.
|
|
28
|
+
|
|
29
|
+
Geometry comes from the engine's own lockup constants, so a composed lockup is metrically identical
|
|
30
|
+
to the generated lockup-horizontal.svg / lockup-vertical.svg rather than approximating them.
|
|
31
|
+
*/
|
|
32
|
+
import { computed } from 'vue'
|
|
33
|
+
import wordmarkUrl from '@humanforest/tokens/logos/wordmark.svg'
|
|
34
|
+
// The lockup GEOMETRY, from the engine rather than measured off the artwork. `@humanforest/tokens/logo`
|
|
35
|
+
// is a public export with zero imports — a pure geometry leaf, so taking it costs nothing and this
|
|
36
|
+
// component can no longer drift from the SVGs scripts/logo.ts generates from the same constants.
|
|
37
|
+
import { MARK, MARK_SIZE, WORDMARK_W, WORDMARK_H, LOCKUP_H, LOCKUP_V } from '@humanforest/tokens/logo'
|
|
38
|
+
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
/** Which identity form to draw: the self-contained mark, the currentColor-masked wordmark, or a composed mark+wordmark lockup (horizontal or vertical). */
|
|
41
|
+
variant: {
|
|
42
|
+
type: String,
|
|
43
|
+
default: 'mark',
|
|
44
|
+
validator: (v) => ['mark', 'wordmark', 'lockup', 'lockup-vertical'].includes(v),
|
|
45
|
+
},
|
|
46
|
+
/* Height of the MARK in px, which is what sets the scale of every form. Named `size` rather
|
|
47
|
+
than `width` because the wordmark and the lockup have different widths at the same size. */
|
|
48
|
+
size: { type: Number, default: 160 },
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
/* Both gaps are the distance from the mark's edge to where the wordmark starts, taken from the
|
|
52
|
+
canonical lockups. They are NOT the same number — horizontal 104.03, vertical 61.32 — which is
|
|
53
|
+
why this was worth deriving: a single hand-copied gap made the vertical lockup 1.7x too airy.
|
|
54
|
+
The vertical lockup also CENTRES the mark over the wordmark (LOCKUP_V.mark[0] = 203.387 in an
|
|
55
|
+
842-wide box, i.e. dead centre), where the horizontal one is baseline-ish and left-aligned. */
|
|
56
|
+
const GAP_H = LOCKUP_H.word[0] - MARK_SIZE
|
|
57
|
+
const GAP_V = LOCKUP_V.word[1] - MARK_SIZE
|
|
58
|
+
|
|
59
|
+
const unit = computed(() => props.size / MARK_SIZE)
|
|
60
|
+
const wordmarkWidth = computed(() => WORDMARK_W * unit.value)
|
|
61
|
+
const wordmarkHeight = computed(() => WORDMARK_H * unit.value)
|
|
62
|
+
const gap = computed(() => (props.variant === 'lockup-vertical' ? GAP_V : GAP_H) * unit.value)
|
|
63
|
+
|
|
64
|
+
/* The wordmark as a mask. background-color: currentColor is what the mask reveals, so the ink
|
|
65
|
+
is inherited rather than baked. */
|
|
66
|
+
const inkStyle = computed(() => ({
|
|
67
|
+
width: `${wordmarkWidth.value}px`,
|
|
68
|
+
height: `${wordmarkHeight.value}px`,
|
|
69
|
+
maskImage: `url(${wordmarkUrl})`,
|
|
70
|
+
WebkitMaskImage: `url(${wordmarkUrl})`,
|
|
71
|
+
maskSize: 'contain',
|
|
72
|
+
WebkitMaskSize: 'contain',
|
|
73
|
+
maskRepeat: 'no-repeat',
|
|
74
|
+
WebkitMaskRepeat: 'no-repeat',
|
|
75
|
+
maskPosition: 'center',
|
|
76
|
+
WebkitMaskPosition: 'center',
|
|
77
|
+
}))
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<template>
|
|
81
|
+
<svg
|
|
82
|
+
v-if="variant === 'mark'"
|
|
83
|
+
:width="size"
|
|
84
|
+
:height="size"
|
|
85
|
+
:viewBox="`0 0 ${MARK_SIZE} ${MARK_SIZE}`"
|
|
86
|
+
fill="none"
|
|
87
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
88
|
+
class="forest-logo-mark"
|
|
89
|
+
role="img"
|
|
90
|
+
aria-label="Forest"
|
|
91
|
+
>
|
|
92
|
+
<path :d="MARK.square" fill="var(--forest-700)" />
|
|
93
|
+
<path :d="MARK.ring" fill="currentColor" />
|
|
94
|
+
<path :d="MARK.tree" fill="white" />
|
|
95
|
+
<path :d="MARK.inner" fill="var(--forest-950)" />
|
|
96
|
+
</svg>
|
|
97
|
+
|
|
98
|
+
<span v-else-if="variant === 'wordmark'" class="forest-logo-ink" :style="inkStyle" role="img" aria-label="Forest" />
|
|
99
|
+
|
|
100
|
+
<span
|
|
101
|
+
v-else
|
|
102
|
+
class="forest-logo-lockup"
|
|
103
|
+
:class="variant === 'lockup-vertical' ? 'is-vertical' : 'is-horizontal'"
|
|
104
|
+
:style="{ gap: `${gap}px` }"
|
|
105
|
+
role="img"
|
|
106
|
+
aria-label="Forest"
|
|
107
|
+
>
|
|
108
|
+
<svg
|
|
109
|
+
:width="size"
|
|
110
|
+
:height="size"
|
|
111
|
+
:viewBox="`0 0 ${MARK_SIZE} ${MARK_SIZE}`"
|
|
112
|
+
fill="none"
|
|
113
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
114
|
+
class="forest-logo-mark"
|
|
115
|
+
aria-hidden="true"
|
|
116
|
+
>
|
|
117
|
+
<path :d="MARK.square" fill="var(--forest-700)" />
|
|
118
|
+
<path :d="MARK.ring" fill="currentColor" />
|
|
119
|
+
<path :d="MARK.tree" fill="white" />
|
|
120
|
+
<path :d="MARK.inner" fill="var(--forest-950)" />
|
|
121
|
+
</svg>
|
|
122
|
+
<span class="forest-logo-ink" :style="inkStyle" />
|
|
123
|
+
</span>
|
|
124
|
+
</template>
|
|
125
|
+
|
|
126
|
+
<style scoped>
|
|
127
|
+
/*
|
|
128
|
+
★★ `flex: none` ON BOTH HALVES — WITHOUT IT THE LOCKUP SILENTLY DEFORMS THE LOGOTYPE.
|
|
129
|
+
|
|
130
|
+
`.forest-logo-lockup` is an inline-flex, and a flex item shrinks by default. The mark is an <img>
|
|
131
|
+
with an intrinsic aspect ratio so it resists; the wordmark is a MASKED SPAN with no intrinsic size
|
|
132
|
+
at all, so it absorbs the entire shortfall on its own. MEASURED in a 400px cell at `size: 180`,
|
|
133
|
+
where the lockup's natural width is 571.6px: the mark stayed exactly 180px and the wordmark was
|
|
134
|
+
compressed from 348.6px to 176.9px — 49% narrower — while its HEIGHT stayed at 83.2px. Not scaled:
|
|
135
|
+
squashed. The letterforms were horizontally distorted, and "forest" beside the same mark rendered
|
|
136
|
+
visibly smaller than the vertical lockup's identical wordmark.
|
|
137
|
+
|
|
138
|
+
Nothing could catch it. Nothing overflowed, no contrast changed, and the component's own promise —
|
|
139
|
+
"metrically identical to the generated lockup-horizontal.svg rather than approximating them" — was
|
|
140
|
+
false in a way only the eye could see. Reported by Jose looking at a specimen slide: "why the
|
|
141
|
+
lockup have a smaller wordmark?"
|
|
142
|
+
|
|
143
|
+
With `flex: none` a lockup that does not fit OVERFLOWS instead, which is loud, catchable by
|
|
144
|
+
scripts/deckAudit.ts, and the correct direction to fail in: an identity that does not fit its box
|
|
145
|
+
is a layout to change, never a logotype to stretch. Same call as `font-synthesis-style: none` in
|
|
146
|
+
base.css, and the same fix BadgeStrip.vue already documents at length for its own pills.
|
|
147
|
+
*/
|
|
148
|
+
/*
|
|
149
|
+
── THE INK IS THE BRAND'S, NOT THE PAGE'S ─────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
★★ `currentColor` ON THE RING AND THE WORDMARK IS THE REVERSING MECHANISM, NOT THE COLOUR. Left
|
|
152
|
+
to inherit, both took whatever ink the slide happened to be set in — and on this deck's light
|
|
153
|
+
register that is `--deck-fg`, which is `--neutral-900`. A neutral near-black, which on cream reads
|
|
154
|
+
exactly like the artwork and is not the artwork.
|
|
155
|
+
|
|
156
|
+
The design system's own answer is unambiguous. scripts/logo.ts bakes the downloadable files
|
|
157
|
+
straight off the forest ramp:
|
|
158
|
+
|
|
159
|
+
const INK = 950, GREEN = 700; // wordmark + ring + inner = Darkest Forest; square = Dark Forest
|
|
160
|
+
|
|
161
|
+
So the ink is --forest-950 (#002A24) and the square --forest-700 (#046C4C) — and this file already
|
|
162
|
+
pinned the square and the tree's inner detail to exactly those, leaving only the two currentColor
|
|
163
|
+
paths to drift. They no longer do.
|
|
164
|
+
|
|
165
|
+
★ THE REVERSE STAYS, because that is what currentColor was for: on `.deck-dark` the ink goes to
|
|
166
|
+
--neutral-0, which is the rule apps/docs' logo foundations page states and the one the specimen's
|
|
167
|
+
own Logo slides assert in prose ("forest-950 on the left and reverses to white on the right").
|
|
168
|
+
Pinning the light half is what makes that sentence true; it was measurably false before.
|
|
169
|
+
|
|
170
|
+
⚠ THE CHROME IS NOT A DESCENDANT OF THE SLIDE, and it still gets this right. slide-top.vue puts
|
|
171
|
+
its own `.deck-dark` on `.deck-chrome-mark` per END rather than inheriting one — the fix it
|
|
172
|
+
documents at length for the pill — so the mark inside it resolves on the register it is drawn in.
|
|
173
|
+
*/
|
|
174
|
+
.forest-logo-mark,
|
|
175
|
+
.forest-logo-ink,
|
|
176
|
+
.forest-logo-lockup {
|
|
177
|
+
color: var(--forest-950);
|
|
178
|
+
}
|
|
179
|
+
.deck-dark .forest-logo-mark,
|
|
180
|
+
.deck-dark .forest-logo-ink,
|
|
181
|
+
.deck-dark .forest-logo-lockup {
|
|
182
|
+
color: var(--neutral-0);
|
|
183
|
+
}
|
|
184
|
+
.forest-logo-mark {
|
|
185
|
+
display: block;
|
|
186
|
+
height: auto;
|
|
187
|
+
flex: none;
|
|
188
|
+
}
|
|
189
|
+
.forest-logo-ink {
|
|
190
|
+
display: block;
|
|
191
|
+
background-color: currentColor;
|
|
192
|
+
flex: none;
|
|
193
|
+
}
|
|
194
|
+
.forest-logo-lockup {
|
|
195
|
+
display: inline-flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
}
|
|
198
|
+
/* Centred, matching LOCKUP_V — the vertical lockup stacks the mark over the wordmark's midline. */
|
|
199
|
+
.forest-logo-lockup.is-vertical {
|
|
200
|
+
flex-direction: column;
|
|
201
|
+
align-items: center;
|
|
202
|
+
}
|
|
203
|
+
</style>
|