<!-- Source: https://motion.svelte.page/examples/pan -->

# Pan

> A swipe-to-dismiss bottom sheet — the canonical pan-gesture pattern. Pull the sheet down past a threshold or flick fast to dismiss; otherwise it springs back. Velocity-aware release.

**Source:** [https://motion.svelte.page/examples/pan](https://motion.svelte.page/examples/pan)

**Markdown mirror:** [https://motion.svelte.page/examples/pan.md](https://motion.svelte.page/examples/pan.md)

---

This mirror preserves the prose, implementation notes, and runnable Svelte source behind the live example page.

## FIG-001: swipe to decide.

A Tinder-style card stack — drag horizontally, watch the top card translate AND rotate (one source MotionValue drives both via `useTransform`), and a LIKE / NOPE badge fades in on the relevant side. Release past the 140px distance threshold OR with > 650 px/s velocity flings the card off screen and the stack springs forward. Anything weaker snaps back.

**Metadata:** tag: `GESTURE` | pattern: `swipe-card stack`

### Notes

- One `useMotionValue` for the top card's horizontal offset, three `useTransform`s on top of it: rotation maps `[-200, 0, 200]` → `[-18°, 0°, 18°]`, and the LIKE / NOPE badge opacities ramp in over the 40–140px gutter. One source, four visuals, can't desync.
- The commit gate is distance OR velocity : 140px past origin or > 650 px/s in either direction fires the fly-off. The direction is taken from `Math.sign(info.offset.x)` with `info.velocity.x` as the fallback for fast flicks with sub-threshold distance.
- The stack uses three card slots with hard-coded depth (Y offset + scale on the back ones — no actual 3D). On commit, the top card animates to ±600px via a soft spring, then `deck = deck.slice(1)` drops it and the `{#each}` block re-keys the remaining cards forward.

### Source

#### SwipeCards.svelte

Source file: [src/lib/examples/pan/demos/SwipeCards.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/pan/demos/SwipeCards.svelte)

```svelte
<script lang="ts">
    import { motion, styleString } from '@humanspeak/svelte-motion'
    import CardItem from './CardItem.svelte'

    type Card = {
        id: number
        name: string
        line: string
        emoji: string
        from: string
        to: string
    }

    const SEED: Card[] = [
        {
            id: 0,
            name: 'Ada',
            line: 'designs algorithms in her sleep',
            emoji: '🧮',
            from: '#a78bfa',
            to: '#7c3aed'
        },
        {
            id: 1,
            name: 'Grace',
            line: 'wrote the manual you read',
            emoji: '🐛',
            from: '#f472b6',
            to: '#db2777'
        },
        {
            id: 2,
            name: 'Linus',
            line: 'built it on a coffee break',
            emoji: '🐧',
            from: '#22d3ee',
            to: '#0891b2'
        },
        {
            id: 3,
            name: 'Margaret',
            line: 'shipped to the moon',
            emoji: '🚀',
            from: '#fb923c',
            to: '#ea580c'
        },
        {
            id: 4,
            name: 'Alan',
            line: 'asked the machine what it wanted',
            emoji: '🤖',
            from: '#34d399',
            to: '#059669'
        }
    ]

    let deck: Card[] = $state([...SEED])

    // Each CardItem owns its own pan MotionValue + commit animation,
    // so the parent's job here is purely "remove the top card once the
    // child has finished flinging itself off-screen". No shared MV to
    // reset → no one-frame flash of the outgoing card snapping back to
    // centre while the unmount is queued.
    const onCommit = () => {
        deck = deck.slice(1)
    }

    const reset = () => {
        deck = [...SEED]
    }
</script>

<!-- dk-strip: docs-kit positioning shell — stripped from the published code. -->
<div class="dk-demo-shell">
    <div class="strip">
        <div class="strip-head">
            <span class="micro">// swipe-deck</span>
            <span class="micro status">
                {String(deck.length).padStart(2, '0')} / {String(SEED.length).padStart(2, '0')} left
            </span>
        </div>

        <div class="stage">
            {#if deck.length === 0}
                <motion.button
                    type="button"
                    onclick={reset}
                    whileHover={{ scale: 1.04 }}
                    whileTap={{ scale: 0.96 }}
                    transition={{ type: 'spring', stiffness: 500, damping: 30 }}
                    style={styleString(() => ({
                        position: 'absolute',
                        top: '50%',
                        left: '50%',
                        transform: 'translate(-50%, -50%)',
                        fontFamily: 'var(--brut-mono, monospace)',
                        fontSize: '0.6875rem',
                        textTransform: 'uppercase',
                        letterSpacing: '0.08em',
                        border: '1px solid var(--brut-accent, #247768)',
                        backgroundColor: 'var(--brut-accent-soft, rgba(36, 119, 104, 0.1))',
                        color: 'var(--brut-accent, #247768)',
                        padding: '0.5rem 0.875rem',
                        cursor: 'pointer'
                    }))}
                >
                    ↺ shuffle the deck
                </motion.button>
            {:else}
                <!-- Render the top 3 cards. Back cards get a subtle Y offset + scale
                     to fake depth without using any actual 3D transforms.
                     Each CardItem owns its own pan MotionValue + LIKE/NOPE
                     derived opacities + commit animation (see CardItem.svelte).
                     The parent only knows about the deck array; cards signal
                     "I'm done flying off, drop me" via the onCommit prop. -->
                {#each deck.slice(0, 3) as card, i (card.id)}
                    <CardItem
                        {card}
                        isTop={i === 0}
                        depthY={i === 0 ? 0 : i * 16}
                        depthRotate={i === 0 ? 0 : i === 1 ? -3 : 4}
                        depthScale={1 - i * 0.03}
                        zIndex={3 - i}
                        {onCommit}
                    />
                {/each}
            {/if}
        </div>

        <div class="strip-foot">
            <span class="micro">← nope</span>
            <span class="micro">like →</span>
        </div>
    </div>
</div>

<style>
    .dk-demo-shell {
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 1.5rem;
        width: 100%;
    }

    .strip {
        width: 100%;
        max-width: 360px;
        display: flex;
        flex-direction: column;
        gap: 0.75rem;
    }

    .micro {
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--brut-ink-3, #9a9a9a);
    }

    .status {
        color: var(--brut-accent, #247768);
        font-variant-numeric: tabular-nums;
    }

    .strip-head,
    .strip-foot {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        border-bottom: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-bottom: 0.5rem;
    }

    .strip-foot {
        border-bottom: none;
        border-top: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-top: 0.75rem;
        padding-bottom: 0;
    }

    .stage {
        position: relative;
        width: 100%;
        height: 460px;
        border: 1px solid var(--brut-rule-2, #bbc4c0);
        background:
            linear-gradient(90deg, var(--brut-rule, #d6dedb) 1px, transparent 1px),
            linear-gradient(0deg, var(--brut-rule, #d6dedb) 1px, transparent 1px),
            var(--brut-bg-2, #eef4f1);
        background-size:
            36px 36px,
            36px 36px,
            auto;
        overflow: hidden;
    }
</style>
```

#### CardItem.svelte

Source file: [src/lib/examples/pan/demos/CardItem.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/pan/demos/CardItem.svelte)

```svelte
<script lang="ts">
    import {
        animate,
        motion,
        styleString,
        useMotionValue,
        useSpring,
        useTransform
    } from '@humanspeak/svelte-motion'

    type Card = { id: number; name: string; line: string; emoji: string; from: string; to: string }

    type Props = {
        card: Card
        isTop: boolean
        depthY: number
        depthRotate: number
        depthScale: number
        zIndex: number
        /**
         * Called once this card has been flung off-screen on a committed
         * swipe — the parent's cue to remove it from the deck. Snap-home
         * releases (offset/velocity below threshold) don't fire this.
         */
        onCommit: () => void
    }

    const { card, isTop, depthY, depthRotate, depthScale, zIndex, onCommit }: Props = $props()

    // Decision thresholds — either pass the distance bar OR fling fast
    // enough to commit. Local to the card because each card owns its own
    // pan lifecycle now.
    const COMMIT_OFFSET_PX = 140
    const COMMIT_VELOCITY_PX_S = 650

    // Per-card pan state. The big win over a shared parent-owned `x`
    // MotionValue: when this card flies off on commit, its `x` stays at
    // ±600 until the parent unmounts it via `deck.slice(1)`. The next-up
    // card has its own local `x = 0` from its own mount — no one-frame
    // flash of the outgoing card snapping back to centre while the
    // unmount is queued.
    const x = useMotionValue(0)
    const rotate = useTransform(x, [-200, 0, 200], [-18, 0, 18])
    const likeOpacity = useTransform(x, [40, 140], [0, 1])
    const nopeOpacity = useTransform(x, [-140, -40], [1, 0])

    // Depth springs settle on the (depthY, depthRotate, depthScale) targets
    // when this card's stack index changes — e.g. promoted from behind →
    // top after a swipe. No CSS transitions: we're a motion library, MV
    // springs do the work.
    const ySpring = useSpring(depthY, { stiffness: 320, damping: 30 })
    const rotateSpring = useSpring(depthRotate, { stiffness: 320, damping: 30 })
    const scaleSpring = useSpring(depthScale, { stiffness: 320, damping: 30 })

    $effect(() => {
        ySpring.set(depthY)
    })
    $effect(() => {
        rotateSpring.set(depthRotate)
    })
    $effect(() => {
        scaleSpring.set(depthScale)
    })

    const handlePan = (_event: PointerEvent, info: { offset: { x: number } }) => {
        x.set(info.offset.x)
    }

    const handlePanEnd = (
        _event: PointerEvent,
        info: { offset: { x: number }; velocity: { x: number } }
    ) => {
        const passDistance = Math.abs(info.offset.x) > COMMIT_OFFSET_PX
        const passVelocity = Math.abs(info.velocity.x) > COMMIT_VELOCITY_PX_S
        // Prefer the velocity's sign when the release was a clear fling —
        // a user who drags right 50px then yank-flicks left at 800 px/s
        // expects the card to fly LEFT (matching the last gesture vector),
        // not RIGHT just because the lingering offset is positive. Fall
        // back to the offset sign when the gesture stops without much
        // velocity, and to the velocity sign when there's no offset at all.
        const direction = passVelocity
            ? Math.sign(info.velocity.x)
            : info.offset.x !== 0
              ? Math.sign(info.offset.x)
              : Math.sign(info.velocity.x)

        if (direction !== 0 && (passDistance || passVelocity)) {
            // Fling the rest of the way across, then signal the parent.
            // `x` stays at ±600 after the promise resolves; the parent's
            // `deck = deck.slice(1)` unmounts this CardItem before the
            // user can see the end-of-animation pose linger.
            animate(x, direction * 600, {
                type: 'spring',
                stiffness: 200,
                damping: 26
            }).then(onCommit)
            return
        }
        // Snap home — snappy spring so re-grabbing feels immediate.
        animate(x, 0, { type: 'spring', stiffness: 360, damping: 30 })
    }
</script>

<motion.div
    style={styleString(() => ({
        position: 'absolute',
        top: '50%',
        left: '50%',
        width: 260,
        height: 360,
        padding: '1.5rem 1.25rem',
        display: 'flex',
        flexDirection: 'column',
        justifyContent: 'flex-end',
        gap: '0.375rem',
        color: 'var(--brut-accent-ink, #f8fcfb)',
        border: '1px solid var(--brut-ink, #0a0a0a)',
        boxShadow: isTop
            ? '6px 6px 0 var(--brut-ink, #0a0a0a)'
            : '6px 6px 0 var(--brut-rule, #d6dedb)',
        background: `linear-gradient(180deg, ${card.from}, ${card.to})`,
        zIndex,
        transform: `translate(-50%, calc(-50% + ${ySpring.current}px)) translateX(${
            isTop ? x.current : 0
        }px) rotate(${(isTop ? rotate.current : 0) + rotateSpring.current}deg) scale(${
            scaleSpring.current
        })`,
        opacity: isTop ? 1 : 0.96,
        cursor: isTop ? 'grab' : 'default',
        pointerEvents: isTop ? 'auto' : 'none',
        userSelect: 'none',
        touchAction: 'none',
        willChange: 'transform'
    }))}
    onPan={isTop ? handlePan : undefined}
    onPanEnd={isTop ? handlePanEnd : undefined}
    whilePan={isTop ? { cursor: 'grabbing' } : undefined}
    role="article"
    aria-label={isTop ? `${card.name} — pan to like or nope` : `${card.name} — behind`}
>
    {#if isTop}
        <span class="badge like" style="opacity: {likeOpacity.current}">LIKE</span>
        <span class="badge nope" style="opacity: {nopeOpacity.current}">NOPE</span>
    {/if}
    <span class="emoji" aria-hidden="true">{card.emoji}</span>
    <h3>{card.name}</h3>
    {#if isTop}
        <p>{card.line}</p>
        <span class="hint">← nope · like →</span>
    {/if}
</motion.div>

<style>
    /* Plain children of the motion `.card` element DO receive scoped styles. */
    .emoji {
        position: absolute;
        top: 1.25rem;
        right: 1.25rem;
        font-size: 44px;
        line-height: 1;
    }

    h3 {
        margin: 0;
        font-family: var(--brut-mono, monospace);
        font-size: 1.25rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.02em;
    }

    p {
        margin: 0;
        font-size: 0.8125rem;
        line-height: 1.4;
        opacity: 0.94;
    }

    .hint {
        margin-top: 0.5rem;
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        opacity: 0.7;
    }

    /* LIKE / NOPE swipe stamps — mono uppercase, hard square corners. */
    .badge {
        position: absolute;
        top: 1.5rem;
        left: 1.25rem;
        padding: 0.375rem 0.75rem;
        font-family: var(--brut-mono, monospace);
        font-size: 0.875rem;
        font-weight: 700;
        letter-spacing: 0.16em;
        text-transform: uppercase;
        border: 2px solid currentColor;
        background: rgba(255, 255, 255, 0.08);
        pointer-events: none;
    }

    .badge.like {
        color: #10b981;
        transform: rotate(-14deg);
    }

    .badge.nope {
        color: #ef4444;
        left: auto;
        right: 1.25rem;
        transform: rotate(14deg);
    }
</style>
```

## FIG-002: flick to dismiss.

A bottom sheet that follows your finger as you pull it down. Past 120px OR with downward velocity > 700 px/s, it commits to dismiss. Otherwise it springs back. `pan` gives you offset + velocity; you decide what to do with them.

**Metadata:** tag: `GESTURE` | pattern: `swipe-to-dismiss sheet`

### Notes

- `onPan` fires every frame with `info.offset` and `info.velocity`. We clamp offset to `>= 0` so the sheet only follows downward drags, and write it straight into a motion value the transform reads.
- The release decision in `onPanEnd` is the distance OR velocity pattern: pulled more than 120px past origin or still moving downward faster than 700px/s ⇒ dismiss. Either condition alone fires the close. This is how a real sheet feels — slow pull works, fast flick also works.
- The overlay opacity is a `useTransform` of the same sheet offset — at 0px it's fully opaque, at 300px it's transparent. One source value drives both the sheet position and the dimmer, so the visuals can't desync.

### Source

#### Default.svelte

Source file: [src/lib/examples/pan/demos/Default.svelte](https://github.com/humanspeak/svelte-motion/blob/main/docs/src/lib/examples/pan/demos/Default.svelte)

```svelte
<script lang="ts">
    import {
        animate,
        AnimatePresence,
        motion,
        styleString,
        useMotionValue,
        useTransform
    } from '@humanspeak/svelte-motion'

    // Dismiss thresholds — either condition fires close.
    const DISMISS_OFFSET_PX = 120
    const DISMISS_VELOCITY_PX_S = 700

    let open = $state(true)
    const y = useMotionValue(0)
    const overlayOpacity = useTransform(y, [0, 300], [1, 0])

    // Live readouts for the strip chrome — reactive off the same MotionValue.
    const offsetY = $derived(Math.max(0, Math.round(y.current)))
    const armed = $derived(open && offsetY > DISMISS_OFFSET_PX)

    const reopen = () => {
        // Full motion round-trip: remount the sheet off-screen and spring it
        // home through the same MotionValue the pan writes — the scrim fade
        // rides along via the shared `overlayOpacity` transform.
        open = true
        y.jump(400)
        animate(y, 0, { type: 'spring', stiffness: 300, damping: 30 })
    }

    const handlePan = (_event: PointerEvent, info: { offset: { y: number } }) => {
        // Clamp at 0 — only follow downward drags. Pulling up shouldn't
        // detach the sheet from its anchor.
        y.set(Math.max(0, info.offset.y))
    }

    const handlePanEnd = (
        _event: PointerEvent,
        info: { offset: { y: number }; velocity: { y: number } }
    ) => {
        const shouldDismiss =
            info.offset.y > DISMISS_OFFSET_PX || info.velocity.y > DISMISS_VELOCITY_PX_S
        if (shouldDismiss) {
            // Animate the sheet off-screen, then mark closed so the overlay
            // unmounts in the same frame the position lands.
            animate(y, 400, { type: 'spring', stiffness: 300, damping: 30 }).then(() => {
                open = false
            })
            return
        }
        // Snap back home with a snappy spring.
        animate(y, 0, { type: 'spring', stiffness: 400, damping: 32 })
    }
</script>

<!-- dk-strip: docs-kit positioning shell — stripped from the published code. -->
<div class="dk-demo-shell">
    <div class="strip">
        <div class="strip-head">
            <span class="micro">// pan-sheet</span>
            <span class="micro status">
                {open ? (armed ? 'armed → dismiss' : `y ${offsetY}px`) : 'dismissed'}
            </span>
        </div>

        <div class="stage">
            {#if open}
                <!-- Backdrop fades as the sheet pulls away. -->
                <div
                    class="overlay"
                    style="opacity: {overlayOpacity.current}"
                    aria-hidden="true"
                ></div>

                <motion.div
                    style={styleString(() => ({
                        position: 'absolute',
                        bottom: 0,
                        left: 0,
                        right: 0,
                        transform: `translateY(${y.current}px)`,
                        padding: '0.875rem 1.25rem 1.5rem',
                        background: 'var(--brut-bg, #f8fcfb)',
                        border: '1px solid var(--brut-ink, #0a0a0a)',
                        borderBottom: 'none',
                        boxShadow: '-6px -6px 0 var(--brut-rule, #d6dedb)',
                        cursor: 'grab',
                        touchAction: 'none',
                        userSelect: 'none',
                        willChange: 'transform'
                    }))}
                    onPan={handlePan}
                    onPanEnd={handlePanEnd}
                    whilePan={{ cursor: 'grabbing' }}
                    role="dialog"
                    aria-label="Demo sheet — pull down or flick to dismiss"
                >
                    <div class="grabber" aria-hidden="true"></div>
                    <span class="sheet-tag">// sheet.01</span>
                    <h3 class="sheet-title">flick to dismiss</h3>
                    <p class="sheet-copy">
                        Pull down past 120px, <em>or</em> flick downward at > 700 px/s. The release decision
                        combines both — fast flicks commit even when distance is short.
                    </p>
                    <ul class="sheet-list">
                        <li>follows your finger 1:1 while you drag</li>
                        <li>springs home if you don't pass the threshold</li>
                        <li>animates off-screen if you do</li>
                    </ul>
                </motion.div>
            {/if}
            <!-- The reopen affordance enters after the dismiss and exits as
                 the sheet springs back — presence choreography, not a mount
                 snap. The wrapper is a flex-centering slot: centering the
                 button via an authored transform would be stomped by the
                 gesture animations' transform writes. -->
            <AnimatePresence>
                {#if !open}
                    <motion.div
                        key="reopen"
                        initial={{ opacity: 0, y: 14, scale: 0.96 }}
                        animate={{ opacity: 1, y: 0, scale: 1 }}
                        exit={{ opacity: 0, y: 14, scale: 0.96 }}
                        transition={{ type: 'spring', stiffness: 400, damping: 32 }}
                        style={styleString(() => ({
                            position: 'absolute',
                            inset: 0,
                            display: 'flex',
                            alignItems: 'center',
                            justifyContent: 'center',
                            pointerEvents: 'none'
                        }))}
                    >
                        <motion.button
                            type="button"
                            onclick={reopen}
                            whileHover={{ scale: 1.04 }}
                            whileTap={{ scale: 0.96 }}
                            transition={{ type: 'spring', stiffness: 500, damping: 30 }}
                            style={styleString(() => ({
                                pointerEvents: 'auto',
                                fontFamily: 'var(--brut-mono, monospace)',
                                fontSize: '0.6875rem',
                                textTransform: 'uppercase',
                                letterSpacing: '0.08em',
                                border: '1px solid var(--brut-accent, #247768)',
                                backgroundColor: 'var(--brut-accent-soft, rgba(36, 119, 104, 0.1))',
                                color: 'var(--brut-accent, #247768)',
                                padding: '0.5rem 0.875rem',
                                cursor: 'pointer'
                            }))}
                        >
                            ↑ reopen the sheet
                        </motion.button>
                    </motion.div>
                {/if}
            </AnimatePresence>
        </div>

        <div class="strip-foot">
            <span class="micro">offset ≥ 120px</span>
            <span class="micro">velocity ≥ 700px/s</span>
        </div>
    </div>
</div>

<style>
    .dk-demo-shell {
        display: flex;
        align-items: center;
        justify-content: center;
        padding: 1.5rem;
        width: 100%;
    }

    .strip {
        width: 100%;
        max-width: 360px;
        display: flex;
        flex-direction: column;
        gap: 0.75rem;
    }

    .micro {
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--brut-ink-3, #9a9a9a);
    }

    .status {
        color: var(--brut-accent, #247768);
        font-variant-numeric: tabular-nums;
    }

    .strip-head,
    .strip-foot {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        border-bottom: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-bottom: 0.5rem;
    }

    .strip-foot {
        border-bottom: none;
        border-top: 1px dashed var(--brut-rule-2, #bbc4c0);
        padding-top: 0.75rem;
        padding-bottom: 0;
    }

    .stage {
        position: relative;
        width: 100%;
        height: 420px;
        border: 1px solid var(--brut-rule-2, #bbc4c0);
        background:
            linear-gradient(90deg, var(--brut-rule, #d6dedb) 1px, transparent 1px),
            linear-gradient(0deg, var(--brut-rule, #d6dedb) 1px, transparent 1px),
            var(--brut-bg-2, #eef4f1);
        background-size:
            36px 36px,
            36px 36px,
            auto;
        overflow: hidden;
    }

    .overlay {
        /* Fixed dark scrim: --brut-ink flips to near-white in dark mode,
           which would BRIGHTEN the backdrop instead of dimming it. */
        position: absolute;
        inset: 0;
        background: rgb(0 0 0 / 0.42);
        pointer-events: none;
    }

    /* Plain children of the motion `.sheet` element DO receive scoped styles. */
    .grabber {
        margin: 0 auto 0.75rem;
        width: 40px;
        height: 4px;
        background: var(--brut-ink, #0a0a0a);
    }

    .sheet-tag {
        display: block;
        font-family: var(--brut-mono, monospace);
        font-size: 0.625rem;
        letter-spacing: 0.08em;
        text-transform: uppercase;
        color: var(--brut-ink-3, #9a9a9a);
    }

    .sheet-title {
        margin: 0.375rem 0 0.5rem;
        font-family: var(--brut-mono, monospace);
        font-size: 0.9375rem;
        font-weight: 700;
        text-transform: uppercase;
        letter-spacing: 0.02em;
        color: var(--brut-ink, #0a0a0a);
    }

    .sheet-copy {
        margin: 0 0 0.75rem;
        font-size: 0.8125rem;
        line-height: 1.5;
        color: var(--brut-ink-2, #525252);
    }

    .sheet-copy em {
        font-style: normal;
        font-weight: 700;
        color: var(--brut-accent, #247768);
    }

    .sheet-list {
        margin: 0;
        padding-left: 1.125rem;
        font-family: var(--brut-mono, monospace);
        font-size: 0.6875rem;
        line-height: 1.7;
        color: var(--brut-ink-3, #9a9a9a);
    }
</style>
```
