@reference "../../../../app.css"; /* ---------- Build a web of trust: the PR thread laid flat in iso (VouchCard.svelte) ---------- */ .landing { .vc { position: relative; display: grid; justify-items: center; width: 100%; /* Its width comes from the stage, never from the art it holds; its height from its width, as the scaled thread's, so scaling doesn't resize it (and re-run the observer that sets the scale). */ contain: inline-size; aspect-ratio: 372 / 282; min-height: 0; max-height: 282px; } /* Stacked, it keeps to the scale of the PR and CI art beside it (zoomed to 0.66) rather than filling the column. */ @media (width < 64rem) { .vc { max-width: 264px; } } /* Phones: a size up, as the PR and CI art are. */ @media (width < 48rem) { .vc { max-width: 312px; } } /* Scaled down to fit a narrower stage, from its top centre (VouchCard sets --z from the stage's width: Safari got tan(atan2(100cqi, 372px)) wrong). A transform, not `zoom`: mobile browsers misplace the composited bubbles inside a zoomed box mid-animation. */ .vc-thread { position: absolute; top: 0; left: 50%; width: 372px; height: 282px; transform-origin: 50% 0; translate: -50% 0; scale: var(--z, 1); } /* The whole thread, held back by --lift so the newest line lands at the bottom left; each new line lowers it, so the thread rises like a chat. The travel runs along the plane's own up axis — rotate 19° · skewX -12° sends local (0, -1) to screen (0.52, -0.857), up and to the right. */ .vc-feed { position: absolute; inset: 0; translate: calc(var(--lift) * -0.52) calc(var(--lift) * 0.857); transition: translate 0.55s var(--ease-out); } /* Empty between plays: drop back to the start without travelling. */ .vc-feed.idle { transition: none; } /* Clearing for a replay: bottom line first (--i), each fading as it slips a little down the plane — local (0, 10) is screen (-5.2, 8.6). */ .vc-feed.clearing .vc-at { animation: vc-out 0.28s var(--ease-out) calc(var(--i) * 60ms) both; } /* Once you've picked, the whole visual is the replay button. */ .vc-replay { position: absolute; inset: 0; padding: 0; border: 0; border-radius: 16px; background: none; cursor: pointer; } .vc-replay:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; } /* One piece (.at), anchored at its centre. */ .vc-at { --ax: 50%; --ay: 50%; display: grid; place-items: center; } /* A message popping in from its tail corner. On the wrapper: the piece's own `transform` holds the iso plane, and its origin has to stay at the centre. */ .vc-at.pop { transform-origin: var(--o, center); animation: vc-msg 0.4s var(--ease-out) var(--d, 0s) both; } /* Every piece is on the feature plane, composited (.composite). */ .vc-b, .vc-btn, .vc-shield, .vc-sys { transform: var(--iso-plane); } .vc-shield, .vc-sys { animation: d-in 0.5s var(--ease-out) both; } .vc-b { position: relative; box-sizing: border-box; margin: 0; font-size: 14px; line-height: 1.4; letter-spacing: -0.01em; color: var(--text); white-space: nowrap; } .vc-b > span, .vc-btn > span { display: block; text-box: trim-both cap alphabetic; } /* Every piece is a plate with a silhouette, and its thickness a second silhouette straight down the screen: a wall-filled ring, as two hard box-shadows. A box-shadow is laid in the piece's own plane and would skew with it, so the offsets are the screen ones run back through --iso-plane: (0, 4) is local (2.12, 3.86), (0, 3) is (1.59, 2.89), (0, 2) is (1.06, 1.93), (0, 1) is (0.53, 0.96). */ .vc-b.theirs { width: max-content; height: 42.3px; padding: 16px 12px; border: 1px solid var(--hl-edge); border-radius: 11px 10px 11px 7px; background: var(--hl-glass); -webkit-backdrop-filter: var(--hl-blur); backdrop-filter: var(--hl-blur); box-shadow: 2.12px 3.86px 0 -1px var(--hl-side), 2.12px 3.86px 0 0 var(--hl-edge); } /* You: the accent plate, its walls a darker accent. */ .vc-b.mine { padding: 14px 16px; --vc-mine-side: color-mix(in srgb, var(--ind-grid), black 32%); border: 1px solid var(--ind-grid); border-radius: 11px 15px 4px 11px; background: var(--ind-grid); box-shadow: 2.12px 3.86px 0 -1px var(--vc-mine-side), 2.12px 3.86px 0 0 var(--vc-mine-side); } /* The verdict badge, sitting on the bubble's lower-right corner, in a hairline ring. */ .vc-shield { position: relative; display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; background: var(--hl-plate); box-shadow: 0 0 0 1px var(--hl-edge); color: var(--ind-grid); animation: d-pop 0.45s var(--ease-pop); } .vc-shield.bad { color: var(--danger); } .vc-shield > svg { width: 12px; height: 12px; } .vc-shield path { fill: currentColor; } .vc-sys { margin: 0; font-size: 12px; font-weight: 450; line-height: 1.2; letter-spacing: -0.01em; color: var(--muted); white-space: nowrap; text-box: trim-both cap alphabetic; } svg.vc-sys { width: 10px; height: 10px; } .vc-sys.d { animation-delay: 0.25s; } .vc-sys b { font-weight: inherit; color: var(--text); } .vc-sys em { font-style: normal; } .vc-sys.d em { color: var(--danger); } .vc-sys.v em { color: var(--accent-text); } /* Your move: two plates, staggered down the thread like replies. */ .vc-btn { position: relative; box-sizing: border-box; width: 118px; height: 38.4px; padding: 0 12px; border: 1px solid var(--hl-edge); border-radius: 11px 10px 11px 7px; background: var(--hl-glass); -webkit-backdrop-filter: var(--hl-blur); backdrop-filter: var(--hl-blur); box-shadow: 1.59px 2.89px 0 -1px var(--hl-side), 1.59px 2.89px 0 0 var(--hl-edge); color: var(--text); font-size: 12px; font-weight: 450; line-height: 1.2; letter-spacing: -0.01em; white-space: nowrap; cursor: pointer; transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, translate 0.15s var(--ease-out), box-shadow 0.15s var(--ease-out); } .vc-btn i { font-style: normal; color: var(--muted); } /* Hover is a subtle lift in tone: the face a step lighter, the silhouette a step stronger, and the face presses 1px into its edge. Pressed, it sinks another. Denounce warms toward the danger colour instead. `translate` in its own rules: next to `transform`, the build folds it in. */ .vc-btn { --vc-hover-edge: color-mix(in srgb, var(--hl-edge), var(--hl-hi) 25%); } .vc-btn.bad { --vc-bad-edge: color-mix(in srgb, var(--hl-edge), var(--danger) 45%); --vc-bad-side: color-mix(in srgb, var(--hl-side), var(--danger) 14%); } .vc-btn:hover { border-color: var(--vc-hover-edge); background: var(--glass-solid-hover); box-shadow: 1.06px 1.93px 0 -1px var(--hl-side), 1.06px 1.93px 0 0 var(--vc-hover-edge); translate: 0 1px; } .vc-btn.bad:hover { border-color: var(--vc-bad-edge); background: color-mix(in srgb, var(--hl-plate), var(--danger) 6%); box-shadow: 1.06px 1.93px 0 -1px var(--vc-bad-side), 1.06px 1.93px 0 0 var(--vc-bad-edge); color: var(--danger); } .vc-btn:active { box-shadow: 0.53px 0.96px 0 -1px var(--hl-side), 0.53px 0.96px 0 0 var(--vc-hover-edge); translate: 0 2px; } .vc-btn.bad:active { box-shadow: 0.53px 0.96px 0 -1px var(--vc-bad-side), 0.53px 0.96px 0 0 var(--vc-bad-edge); } .vc-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } @media (prefers-reduced-motion: reduce) { .vc-feed { transition: none; } .vc-at.pop { animation-name: d-fade; } .vc-feed.clearing .vc-at { animation-name: d-fade-out; } } @media (prefers-reduced-motion: reduce) { .vc-b, .vc-shield, .vc-sys { transition: none; animation: none; } } } .landing { @variant light { /* Your bubble stays indigo, so its words stay light. */ .vc-b.mine { color: var(--color-foreground-on-emphasis); } } } @keyframes vc-msg { from { opacity: 0; scale: 0.9; } } /* `translate` restates .vc-at's placement (.at, centred), plus the slip. */ @keyframes vc-out { to { opacity: 0; translate: calc(var(--x) - 50% - 5.2px) calc(var(--y) - 50% + 8.6px); } }