diff --git a/README.md b/README.md index a2aed451..5b45788a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,16 @@ -

React Native Magic Modal 🦄

+

React Native Magic Modal

+ +

Call show() from any async flow. Await a typed result that records how the modal closed.

+ + + A rating flow moving from magicModal.show to a typed close result + + +

Watch the MP4

- - - - React Native Magic Modal Banner -

@@ -21,33 +27,27 @@

> [!NOTE] -> Magic Modal is a headless orchestration primitive: show modal content from anywhere, await a typed result, and keep styling in your own components. +> Magic Modal owns the flow. Your components own the UI. Open a modal from anywhere and await its typed result. > [!TIP] -> The complete guides and API reference live in the [documentation](https://gstj.github.io/react-native-magic-modal/docs/). - -## Features - -- 📲 [**Easy Integration**](#quickstart): Seamlessly integrate with your React Native app. -- 🔄 [**Complex Flow Management**](#examples): Manage intricate modal sequences effortlessly. -- 🔧 [**Customizable**](#usage): Tailor modals to fit your app's unique requirements. - -## Highlights +> Guides and API reference live in the [documentation](https://gstj.github.io/react-native-magic-modal/docs/). -React Native Magic Modal offers a superior experience compared to traditional modal implementations: +## Core API -- 🎨 [**Bring Your Own UI**](#examples): Style ordinary React Native content for iOS and Android. -- 🚀 [**Developer Friendly**](#quickstart): Simple to use, with a focus on developer experience. -- 🧩 [**Versatile**](#documentation): Adaptable to a wide range of modal scenarios. +- `magicModal.show()` renders content in the portal and returns an entry handle with a promise. +- `modalID` targets one entry for an update or close. +- `update()` replaces the component rendered by an open entry. +- `HideReturn` records how the modal closed and carries submitted data. +- Modal components remain ordinary React Native UI. ## Table of Contents -- [**Installation**](#installation) -- [**Quickstart**](#quickstart) -- [**Examples**](#examples) -- [**Documentation**](#documentation) -- [**FAQ**](#faq) -- [**Contributors**](#contributors) +- [Installation](#installation) +- [Quickstart](#quickstart) +- [Try it](#try-it) +- [Documentation](#documentation) +- [FAQ](#faq) +- [Contributors](#contributors) ## Installation @@ -70,18 +70,18 @@ Minimum peer versions: Both gesture-handler majors work. Swipe-to-dismiss uses 3.x's `usePanGesture` hook when it's available and falls back to 2.x's `Gesture.Pan()` builder otherwise. -Reanimated 4 requires React Native's New Architecture. In a bare React Native app, add `"react-native-worklets/plugin"` last in `babel.config.js`, then run `npx pod-install`. Expo configures the plugin through its Babel preset; install compatible native versions with: +Reanimated 4 requires React Native's New Architecture. In a bare React Native app, add `"react-native-worklets/plugin"` last in `babel.config.js`. Run `npx pod-install`. Expo configures the plugin through its Babel preset; install compatible native versions with: ```bash npx expo install react-native-gesture-handler react-native-reanimated react-native-worklets react-native-screens pnpm add react-native-magic-modal ``` -8.0.0 was the one version that required gesture-handler 3.x. If you're on it and pinned to 2.x, upgrade to 9.0.0 or later, and drop `react-native-gesture-handler` from `expo.install.exclude` if you added it to quiet the version check. +Only 8.0.0 required gesture-handler 3.x. If you're on it and pinned to 2.x, upgrade to 9.0.0 or later, and drop `react-native-gesture-handler` from `expo.install.exclude` if you added it to quiet the version check. ## Quickstart -Insert a `MagicModalPortal` at the top of your application structure, and a `GestureHandlerRootView` if you haven't already: +Mount a `MagicModalPortal` at the app root, and add a `GestureHandlerRootView` if you haven't already: ```tsx import { MagicModalPortal } from "react-native-magic-modal"; @@ -97,21 +97,22 @@ export default function App() { } ``` -Tip: the root `_layout.tsx` is usually the best place to put it in a project using expo-router. +In Expo Router, mount it in the root `_layout.tsx`. The `GestureHandlerRootView` is required. The portal renders a `GestureDetector` for the swipe gesture, and gesture-handler 3.x throws when one renders without a root view above it. 2.x only logs a warning. -## Examples +## Try it -Showcasing modal management on iOS and Android platforms: +The [interactive docs](https://gstj.github.io/react-native-magic-modal/) run the +package directly in the browser. Try the mobile rating flow, stack a second +caller, or watch `update()` drive a web upload. -| iOS | Android | -| ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- | -| | | +The [kitchen-sink app](examples/kitchen-sink) covers the native iOS and Android +paths. ## Usage -Here's the preferred usage pattern for the library: +Start with a modal that returns data to its caller: ```tsx import React from "react"; @@ -128,7 +129,7 @@ const ConfirmationModal = () => { return ( hide({ success: true })}> - Click here to confirm + Confirm ); @@ -148,33 +149,34 @@ const ResponseModal = ({ text }) => { }; const handleConfirmationFlow = async () => { - // You can call `show` with or without props, depending on the requirements of the modal. + // The render callback can pass props to the modal. const result = await magicModal.show(() => ) .promise; - // Hide could potentially be a backdrop press, a back button press, or a swipe gesture. + // Non-intentional closes include backdrop presses, Android back presses, and swipes. if (result.reason !== MagicModalHideReason.INTENTIONAL_HIDE) { // User cancelled the flow return; } if (result.data.success) { - return magicModal.show(() => ).promise; + return magicModal.show(() => ).promise; } - return magicModal.show(() => ).promise; + return magicModal.show(() => ).promise; }; export const MainScreen = () => { return ( - Start the modal flow! + Start confirmation flow ); }; ``` -You can also hide modals imperatively outside of the modal context. For that, we provide the global `hide` method, that requires a modal id: +You can also close a modal outside its component. Pass its `modalID` to the +global `hide` method: ```tsx import { magicModal } from "react-native-magic-modal"; @@ -182,7 +184,7 @@ import { magicModal } from "react-native-magic-modal"; const QuickModal = ({ text }) => { return ( - Hey! I'm going to be closed imperatively + The caller will close this modal. ); }; @@ -193,8 +195,8 @@ const handleQuickModal = async () => { // Wait for 2 seconds before closing the modal await new Promise((resolve) => setTimeout(resolve, 2000)); - // Note that it's usually preferable to use the `hide` method from the modal context - // You can even put it inside useEffects to handle auto-dismissal for you. + // Prefer hide() from the modal context when the modal owns the close action. + // Call it from an effect to auto-dismiss the modal. magicModal.hide(undefined, { modalID }); }; @@ -207,7 +209,7 @@ export const MainScreen = () => { }; ``` -`show` also hands you an `update` function, for when the data driving the modal lives outside of it: +`show` also returns `update()` for data that lives outside the modal: ```tsx import { magicModal } from "react-native-magic-modal"; @@ -229,43 +231,49 @@ const handleUpload = async (file) => { }; ``` -The modal itself stays put: same position in the stack, same backdrop, and the promise from `show` keeps waiting. Only the content is swapped, and it's a new component, so it mounts from scratch and anything it kept in `useState` is gone. If the state belongs to the modal, drive it from inside with a store or context instead. +The entry keeps its stack position, backdrop, and pending promise. Only the +component changes. It mounts from scratch, so local `useState` resets. Keep +modal-owned state in a store or context when it must survive an update. -Refer to the [kitchen-sink example](examples/kitchen-sink) for detailed usage scenarios. +See the [kitchen-sink example](examples/kitchen-sink) for runnable iOS and Android flows. ## Documentation -Access the complete documentation [here](https://gstj.github.io/react-native-magic-modal/). +Read the [setup, guides, and API reference](https://gstj.github.io/react-native-magic-modal/). ## FAQ -**Q:** Can I have two modals showing up at the same time? +**Q:** Can two modals be open at once? -**A:** Yes. With v4+, you can now have multiple modals showing up at the same time. +**A:** Yes. Every `show()` call adds an independent stack entry with its own ID +and promise. --- -**Q:** Can I use Scrollables inside the modal? +**Q:** Can I put scrollable content inside a modal? **A:** -Yes, but Scrollables can't be used with swipe gestures enabled, as they conflict. Pass in `swipeDirection: undefined` on the `magicModal.show` function to disable gestures on them. +Yes, but the scroll gesture conflicts with swipe dismissal. Pass +`swipeDirection: undefined` to `magicModal.show()` for a scrollable modal. -If your use-case is a scrollable bottom-sheet, I recommend going with Gorhom's react-native-bottom-sheet for this use-case temporarily. +For a full bottom-sheet component, use +[React Native Bottom Sheet](https://github.com/gorhom/react-native-bottom-sheet). --- **Q:** Modals are appearing on top of native modal screens, such as the image picker. How can I fix this? **A:** -This behavior can be disabled by calling `magicModal.disableFullWindowOverlay()` before showing the modal. This will prevent the modal from appearing on top of native modal screens. +Call `magicModal.disableFullWindowOverlay()` before opening the modal. -You can also call `magicModal.enableFullWindowOverlay()` to re-enable it. +Call `magicModal.enableFullWindowOverlay()` when the overlay should cover native +screens again. ## Contributors -Special thanks to everyone who contributed to making React Native Magic Modal a robust and user-friendly library. [See the full list](https://github.com/GSTJ/react-native-magic-modal/graphs/contributors). +[See everyone who has contributed](https://github.com/GSTJ/react-native-magic-modal/graphs/contributors). -See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository. +See the [contributing guide](CONTRIBUTING.md). ## License diff --git a/apps/docs/app/(home)/home.css b/apps/docs/app/(home)/home.css new file mode 100644 index 00000000..760cd50c --- /dev/null +++ b/apps/docs/app/(home)/home.css @@ -0,0 +1,5090 @@ +html:has(.magic-home), +body:has(.magic-home) { + background: #151411; + overscroll-behavior-y: none; +} + +.magic-home { + --mm-blue: #9f835b; + --mm-blue-deep: #785f3f; + --mm-canvas: #11100e; + --mm-coral: #c56178; + --mm-dark-line: #37332d; + --mm-fg: #f2ede3; + --mm-fg-muted: #aaa298; + --mm-ink: #151411; + --mm-ink-soft: #24221e; + --mm-lime: #aab79d; + --mm-line: #c9c1b5; + --mm-muted: #655f56; + --mm-paper: #f2ede3; + --mm-paper-bright: #fffdf7; + --mm-surface: #191714; + --mm-pointer-x: 0; + --mm-pointer-y: 0; + background: var(--mm-canvas); + color: var(--mm-fg); + color-scheme: dark; + font-family: "Instrument Sans Variable", sans-serif; + font-size: 16px; + min-height: 100svh; + overflow-x: clip; + position: relative; +} + +.magic-home *, +.magic-home *::after, +.magic-home *::before { + box-sizing: border-box; +} + +.magic-home button, +.magic-home input, +.magic-home textarea { + font: inherit; +} + +.magic-home button, +.magic-home a { + -webkit-tap-highlight-color: transparent; +} + +.magic-home a { + color: inherit; + text-decoration: none; +} + +.magic-home code, +.magic-home pre { + font-family: "JetBrains Mono Variable", monospace; +} + +.magic-home h1, +.magic-home h2, +.magic-home h3, +.magic-home p, +.magic-home pre { + margin: 0; +} + +.magic-home button:focus-visible, +.magic-home a:focus-visible, +.magic-home textarea:focus-visible { + outline: 3px solid var(--mm-blue); + outline-offset: 3px; +} + +.magic-home ::selection { + background: var(--mm-lime); + color: var(--mm-ink); +} + +.mm-nav { + align-items: center; + background: color-mix(in srgb, var(--mm-paper) 94%, transparent); + border-bottom: 1px solid var(--mm-line); + display: flex; + height: 70px; + justify-content: space-between; + margin: 0 auto; + max-width: 1600px; + padding: 0 clamp(1.25rem, 4vw, 4rem); + position: relative; + z-index: 30; +} + +.mm-brand { + align-items: center; + display: inline-flex; + font-size: 0.98rem; + font-weight: 720; + gap: 0.68rem; + letter-spacing: -0.025em; +} + +.mm-brand code { + border-left: 1px solid var(--mm-line); + color: var(--mm-muted); + font-size: 0.62rem; + font-weight: 520; + letter-spacing: -0.02em; + margin-left: 0.15rem; + padding-left: 0.78rem; +} + +.mm-nav nav { + align-items: center; + display: flex; + font-size: 0.8rem; + font-weight: 600; + gap: clamp(1rem, 2.4vw, 2.15rem); +} + +.mm-nav nav > a { + align-items: center; + display: inline-flex; + min-height: 44px; + position: relative; +} + +.mm-nav nav > a:not(.mm-nav-github)::after { + background: var(--mm-ink); + bottom: 7px; + content: ""; + height: 1px; + left: 0; + position: absolute; + transform: scaleX(0); + transform-origin: right; + transition: transform 180ms ease; + width: 100%; +} + +.mm-nav nav > a:not(.mm-nav-github):hover::after { + transform: scaleX(1); + transform-origin: left; +} + +.mm-nav-github { + border: 1px solid var(--mm-ink); + gap: 0.42rem; + padding: 0 0.92rem; + transition: + background 160ms ease, + color 160ms ease, + transform 160ms ease; +} + +.mm-nav-github:hover { + background: var(--mm-ink); + color: var(--mm-paper); + transform: translateY(-2px); +} + +.mm-hero { + display: grid; + gap: clamp(2.5rem, 3vw, 4.5rem); + grid-template-columns: minmax(0, 0.88fr) minmax(560px, 1.12fr); + margin: 0 auto; + max-width: 1600px; + min-height: calc(100svh - 70px); + padding: clamp(3.5rem, 7vh, 6.5rem) clamp(1.5rem, 4vw, 4rem) + clamp(3rem, 6vh, 5rem); + position: relative; +} + +.mm-hero::before, +.mm-hero::after { + background: var(--mm-line); + content: ""; + inset-block: 0; + opacity: 0.54; + pointer-events: none; + position: absolute; + width: 1px; +} + +.mm-hero::before { + left: clamp(1.5rem, 4vw, 4rem); +} + +.mm-hero::after { + right: clamp(1.5rem, 4vw, 4rem); +} + +.mm-hero-copy { + align-self: center; + max-width: 680px; + padding-left: clamp(0rem, 2.5vw, 2.25rem); + position: relative; + z-index: 4; +} + +.mm-overline { + align-items: center; + color: var(--mm-muted); + display: flex; + font-size: 0.75rem; + font-weight: 620; + gap: 0.62rem; + letter-spacing: 0.02em; + margin-bottom: clamp(1.6rem, 3.2vh, 2.5rem) !important; +} + +.mm-overline svg { + filter: drop-shadow( + 0 7px 15px color-mix(in srgb, var(--mm-coral) 24%, transparent) + ); + flex: 0 0 auto; +} + +.mm-hero h1 { + font-size: clamp(3.7rem, 5vw, 5rem); + font-weight: 640; + letter-spacing: -0.045em; + line-height: 0.98; + max-width: 660px; + text-wrap: balance; +} + +.mm-hero h1 em { + color: var(--mm-coral); + font-family: "Instrument Serif", serif; + font-weight: 400; + letter-spacing: -0.055em; +} + +.mm-hero-lede { + color: var(--mm-muted); + font-size: clamp(1.02rem, 1.28vw, 1.18rem); + line-height: 1.58; + margin-top: clamp(1.5rem, 3.2vh, 2.25rem) !important; + max-width: 590px; +} + +.mm-hero-actions { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 0.45rem 1.2rem; + margin-top: 1.75rem; +} + +.mm-run-link, +.mm-article-link { + align-items: center; + display: inline-flex; + font-size: 0.82rem; + font-weight: 690; + gap: 0.48rem; + min-height: 46px; +} + +.mm-run-link { + background: var(--mm-ink); + color: var(--mm-paper-bright) !important; + padding: 0 1.05rem 0 1.15rem; + transition: + background 160ms ease, + box-shadow 160ms ease, + transform 160ms ease; +} + +.mm-run-link:hover { + background: var(--mm-blue); + box-shadow: 7px 7px 0 var(--mm-coral); + color: var(--mm-ink) !important; + transform: translate(-2px, -2px); +} + +.mm-article-link { + border-bottom: 1px solid var(--mm-line); + color: var(--mm-muted) !important; + transition: + border-color 160ms ease, + color 160ms ease; +} + +.mm-article-link:hover { + border-color: var(--mm-ink); + color: var(--mm-ink) !important; +} + +.mh-install-command { + align-items: center; + background: transparent; + border: 1px solid var(--mm-line); + color: var(--mm-ink); + cursor: pointer; + display: inline-flex; + gap: 0.6rem; + margin-top: 0.75rem; + min-height: 44px; + padding: 0.6rem 0.75rem; + transition: + background 160ms ease, + border-color 160ms ease, + transform 160ms ease; +} + +.mh-install-command:hover { + background: var(--mm-paper-bright); + border-color: var(--mm-ink); + transform: translateY(-2px); +} + +.mh-install-command code { + font-size: 0.7rem; + font-weight: 550; + white-space: nowrap; +} + +.mh-install-prompt { + color: var(--mm-blue); + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.78rem; + font-weight: 800; +} + +.mh-install-copy { + align-items: center; + border-left: 1px solid var(--mm-line); + color: var(--mm-muted); + display: inline-flex; + font-size: 0.64rem; + gap: 0.28rem; + margin-left: 0.12rem; + min-width: 3.65rem; + padding-left: 0.62rem; +} + +.mh-install-command.is-compact { + border-color: color-mix(in srgb, var(--mm-ink) 42%, transparent); + margin-top: 0; +} + +.mm-scroll-cue { + align-items: center; + bottom: 1.6rem; + color: var(--mm-muted) !important; + display: flex; + font-size: 0.68rem; + gap: 0.5rem; + left: 50%; + letter-spacing: 0.04em; + min-height: 44px; + position: absolute; + transform: translateX(-50%); +} + +.mm-scroll-cue svg { + animation: mm-cue 1.8s ease-in-out infinite; +} + +.mm-origin-flow { + align-self: center; + height: min(710px, calc(100svh - 112px)); + min-height: 630px; + position: relative; + scroll-margin-top: 24px; + width: 100%; + z-index: 3; +} + +.mm-flow-four { + color: transparent; + font-family: "Instrument Serif", serif; + font-size: clamp(33rem, 42vw, 43rem); + font-style: italic; + font-weight: 400; + left: 2%; + line-height: 0.72; + opacity: 0.5; + pointer-events: none; + position: absolute; + top: 7%; + transform: rotate(-4deg); + -webkit-text-stroke: 2px color-mix(in srgb, var(--mm-coral) 72%, transparent); + user-select: none; + z-index: 0; +} + +.mm-flow-code { + background: var(--mm-ink); + box-shadow: 14px 18px 0 color-mix(in srgb, var(--mm-coral) 84%, transparent); + color: #dcd6cb; + left: 0; + max-width: 350px; + min-height: 410px; + overflow: hidden; + position: absolute; + top: 15%; + transform: translate3d( + calc(var(--mm-pointer-x) * -14px), + calc(var(--mm-pointer-y) * -10px), + 0 + ) + rotate(-1.8deg); + transition: transform 180ms ease-out; + width: 52%; + z-index: 2; +} + +.mm-flow-code::before { + background: var(--mm-coral); + content: ""; + height: 4px; + inset: 0 0 auto; + position: absolute; +} + +.mm-flow-code-head { + align-items: center; + border-bottom: 1px solid #34312c; + display: flex; + height: 48px; + justify-content: space-between; + padding: 0 1rem; +} + +.mm-flow-code-head code { + color: #888278; + font-size: 0.62rem; +} + +.mm-window-dots { + display: flex; + gap: 5px; +} + +.mm-window-dots i { + background: #575149; + border-radius: 50%; + height: 6px; + width: 6px; +} + +.mm-window-dots i:first-child { + background: var(--mm-coral); +} + +.mm-flow-code-body { + font-size: clamp(0.56rem, 0.69vw, 0.67rem); + line-height: 1.75; + padding: 0.85rem 0; + white-space: pre-wrap; +} + +.mm-flow-code-body > code > span { + color: #928b81; + display: block; + padding: 0.55rem 1rem; + position: relative; + transition: + background 220ms ease, + color 220ms ease, + opacity 220ms ease; +} + +.mm-flow-code-body > code > span::after { + background: var(--mm-blue); + content: ""; + inset: 0 auto 0 0; + position: absolute; + transform: scaleY(0); + transition: transform 220ms ease; + width: 3px; +} + +.mm-flow-code-body > code > span.is-active { + background: #211f1b; + color: #fffaf0; +} + +.mm-flow-code-body > code > span.is-active::after { + transform: scaleY(1); +} + +.mm-flow-code-body span > i { + color: #8f887e; + display: inline-block; + font-style: normal; + margin-right: 0.75rem; + user-select: none; + width: 1.1rem; +} + +.mm-flow-code-body b { + color: #91a5ff; + font-weight: 600; +} + +.mm-flow-code-live { + display: none; +} + +.mm-flow-promise { + align-items: center; + border-top: 1px solid #34312c; + bottom: 0; + color: #aaa298; + display: flex; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.62rem; + gap: 0.55rem; + height: 44px; + inset-inline: 0; + padding: 0 1rem; + position: absolute; + transition: color 240ms ease; +} + +.mm-flow-promise-dot, +.mm-stage-meta i, +.mm-stage-footer i { + background: var(--mm-blue); + clip-path: polygon( + 50% 0, + 59% 39%, + 100% 50%, + 59% 61%, + 50% 100%, + 41% 61%, + 0 50%, + 41% 39% + ); + height: 10px; + transition: background 240ms ease; + width: 10px; +} + +.mm-origin-flow[data-step="done"] .mm-flow-promise { + color: var(--mm-lime); +} + +.mm-origin-flow[data-step="done"] .mm-flow-promise-dot, +.mm-origin-flow[data-step="done"] .mm-stage-meta i, +.mm-origin-flow[data-step="done"] .mm-stage-footer i { + background: var(--mm-lime); +} + +.mm-flow-tether { + height: 70px; + left: 38%; + pointer-events: none; + position: absolute; + top: 47%; + width: 28%; + z-index: 1; +} + +.mm-flow-tether span { + background: repeating-linear-gradient( + 90deg, + var(--mm-blue) 0 9px, + transparent 9px 15px + ); + background-size: 30px 2px; + height: 2px; + left: 0; + position: absolute; + top: 50%; + transform: rotate(-8deg); + transform-origin: left; + transition: background 240ms ease; + width: 100%; +} + +.mm-flow-tether i { + background: var(--mm-blue); + border: 5px solid var(--mm-paper); + border-radius: 50%; + height: 17px; + position: absolute; + right: -2px; + top: calc(50% - 14px); + transition: background 240ms ease; + width: 17px; +} + +.mm-origin-flow:not([data-step="done"]) .mm-flow-tether span { + animation: mm-tether 0.9s linear infinite; +} + +.mm-origin-flow[data-step="done"] .mm-flow-tether span { + background: var(--mm-lime); +} + +.mm-origin-flow[data-step="done"] .mm-flow-tether i { + background: var(--mm-lime); +} + +.mm-native-stage { + background: var(--mm-ink); + box-shadow: + 0 44px 85px rgba(31, 26, 19, 0.25), + -10px 12px 0 var(--mm-blue); + height: min(680px, calc(100svh - 135px)); + min-height: 600px; + padding: 46px 18px 48px; + position: absolute; + right: 0; + top: 0; + transform: translate3d( + calc(var(--mm-pointer-x) * 12px), + calc(var(--mm-pointer-y) * 8px), + 0 + ); + transition: transform 180ms ease-out; + width: min(430px, 66%); + z-index: 4; +} + +.mm-stage-meta, +.mm-stage-footer { + align-items: center; + color: #aaa298; + display: flex; + font-size: 0.62rem; + inset-inline: 18px; + justify-content: space-between; + position: absolute; +} + +.mm-stage-meta { + height: 46px; + top: 0; +} + +.mm-stage-meta > span, +.mm-stage-footer > span { + align-items: center; + display: flex; + gap: 0.58rem; +} + +.mm-stage-meta code { + color: #928b81; + font-size: 0.57rem; +} + +.mm-stage-footer { + bottom: 0; + height: 48px; +} + +.mm-stage-footer button { + background: transparent; + border: 0; + color: #aaa298; + cursor: pointer; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.59rem; + min-height: 40px; + padding: 0 0.25rem; + transition: color 160ms ease; +} + +.mm-stage-footer button:hover:not(:disabled) { + color: var(--mm-coral); +} + +.mm-stage-footer button:disabled { + cursor: default; + opacity: 0.35; +} + +.mm-native-surface { + background: #e7e3dc; + color: var(--mm-ink); + height: 100%; + overflow: hidden; + position: relative; +} + +.mm-native-statusbar { + align-items: center; + background: var(--mm-paper-bright); + display: flex; + font-size: 0.57rem; + height: 26px; + justify-content: space-between; + padding: 0 0.8rem; +} + +.mm-native-statusbar span { + align-items: center; + display: flex; + font-size: 0.48rem; + gap: 0.2rem; + letter-spacing: 0.08em; +} + +.mm-native-nav { + align-items: center; + background: var(--mm-paper-bright); + border-bottom: 1px solid #ded8ce; + display: flex; + height: 48px; + justify-content: space-between; + padding: 0 0.85rem 0 1rem; +} + +.mm-native-nav > span { + font-size: 0.83rem; + font-weight: 740; +} + +.mm-native-nav button { + align-items: center; + background: transparent; + border: 1px solid #d4cec4; + border-radius: 50%; + color: var(--mm-ink); + cursor: pointer; + display: flex; + height: 34px; + justify-content: center; + transition: + background 150ms ease, + transform 150ms ease; + width: 34px; +} + +.mm-native-nav button:hover { + background: var(--mm-lime); + transform: rotate(-24deg); +} + +.mm-feed { + padding: 1rem; +} + +.mm-feed-author { + align-items: center; + display: flex; + gap: 0.65rem; +} + +.mm-feed-author > i { + align-items: center; + background: var(--mm-coral); + border-radius: 50%; + color: var(--mm-ink); + display: flex; + font-size: 0.56rem; + font-style: normal; + font-weight: 760; + height: 30px; + justify-content: center; + width: 30px; +} + +.mm-feed-author > span { + display: grid; + gap: 0.1rem; +} + +.mm-feed-author strong { + font-size: 0.64rem; +} + +.mm-feed-author small { + color: #5f5a52; + font-size: 0.52rem; +} + +.mm-feed-image { + align-items: flex-end; + background: linear-gradient(125deg, #ffc8a9, #ff6d49 58%, #e74a2b); + display: flex; + height: 160px; + justify-content: space-between; + margin-top: 0.75rem; + overflow: hidden; + padding: 0.85rem; + position: relative; +} + +.mm-feed-image::before { + border: 1px solid rgba(21, 20, 17, 0.22); + border-radius: 50%; + content: ""; + height: 145px; + position: absolute; + right: -42px; + top: -65px; + width: 145px; +} + +.mm-feed-image span { + font-family: "Instrument Serif", serif; + font-size: 1.2rem; + font-style: italic; +} + +.mm-feed-image b { + align-items: center; + background: var(--mm-paper-bright); + border-radius: 50%; + display: flex; + height: 38px; + justify-content: center; + width: 38px; +} + +.mm-feed-copy { + display: grid; + gap: 0.4rem; + margin-top: 0.75rem; +} + +.mm-feed-copy i { + background: #cec8be; + height: 6px; + width: 90%; +} + +.mm-feed-copy i:nth-child(2) { + width: 76%; +} + +.mm-feed-copy i:nth-child(3) { + width: 42%; +} + +.mm-native-scrim { + background: rgba(14, 13, 11, 0.58); + inset: 0; + opacity: 0; + pointer-events: none; + position: absolute; + transition: opacity 260ms ease; + z-index: 3; +} + +.mm-native-scrim[data-visible="true"] { + opacity: 1; +} + +.mm-sheet-echoes { + bottom: 0; + inset-inline: 10px; + pointer-events: none; + position: absolute; + z-index: 4; +} + +.mm-sheet-echoes i { + background: color-mix(in srgb, var(--mm-paper) 68%, var(--mm-coral)); + border-radius: 22px 22px 0 0; + height: 38px; + inset-inline: 0; + position: absolute; + transform: translateY(-8px); + transition: transform 350ms cubic-bezier(0.22, 1, 0.36, 1); +} + +.mm-sheet-echoes i:nth-child(2) { + background: color-mix(in srgb, var(--mm-paper) 70%, var(--mm-blue)); + inset-inline: 7px; + transform: translateY(-16px); +} + +.mm-sheet-echoes i:nth-child(3) { + background: var(--mm-paper); + inset-inline: 14px; + transform: translateY(-24px); +} + +.mm-origin-flow[data-step="done"] .mm-sheet-echoes i { + transform: translateY(40px); +} + +.mm-native-sheet { + background: var(--mm-paper-bright); + border-radius: 26px 26px 0 0; + bottom: 0; + color: var(--mm-ink); + inset-inline: 0; + min-height: 245px; + padding: 0.82rem 1.15rem 1.1rem; + position: absolute; + transform: translateY(112%); + transition: + opacity 180ms ease, + transform 480ms cubic-bezier(0.2, 0.85, 0.25, 1.08); + z-index: 6; +} + +.mm-native-sheet.is-active { + opacity: 1; + transform: translateY(0); +} + +.mm-native-sheet.is-past { + opacity: 0; + transform: translateY(-14%) scale(0.95); +} + +.mm-native-sheet.is-future { + opacity: 0; + transform: translateY(112%); +} + +.mm-sheet-handle { + background: #cbc5bb; + border-radius: 99px; + display: block; + height: 4px; + margin: 0 auto 0.65rem; + width: 34px; +} + +.mm-sheet-count { + align-items: center; + color: #777168; + display: flex; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.54rem; + justify-content: space-between; + margin-bottom: 0.72rem; +} + +.mm-sheet-count > span { + background: var(--mm-coral); + color: var(--mm-ink); + font-weight: 780; + padding: 0.2rem 0.34rem; +} + +.mm-sheet-count code { + font-size: inherit; +} + +.mm-native-sheet h2 { + font-size: 1.35rem; + font-weight: 710; + letter-spacing: -0.045em; + line-height: 1.05; +} + +.mm-native-sheet > p { + color: #706a61; + font-size: 0.69rem; + line-height: 1.45; + margin-top: 0.38rem; +} + +.mm-rating-options { + border: 0; + display: grid; + gap: 0.35rem; + grid-template-columns: repeat(6, 1fr); + margin-top: 0.92rem; + min-inline-size: 0; + padding: 0; +} + +.mm-rating-options button { + background: var(--mm-paper); + border: 1px solid #d1cbc1; + color: var(--mm-ink); + cursor: pointer; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.78rem; + font-weight: 660; + min-height: 44px; + padding: 0; + transition: + background 150ms ease, + border-color 150ms ease, + color 150ms ease, + transform 150ms ease; +} + +.mm-rating-options button:hover { + background: var(--mm-blue); + border-color: var(--mm-blue); + color: var(--mm-ink); + transform: translateY(-4px); +} + +.mm-rating-sheet > small { + color: var(--mm-muted); + display: block; + font-size: 0.55rem; + margin-top: 0.62rem; +} + +.mm-feedback-options { + display: flex; + flex-wrap: wrap; + gap: 0.34rem; + margin: 0.72rem 0; +} + +.mm-feedback-options button { + background: transparent; + border: 1px solid #d1cbc1; + color: #5f5a52; + cursor: pointer; + font-size: 0.62rem; + min-height: 38px; + padding: 0 0.58rem; +} + +.mm-feedback-options button[aria-pressed="true"] { + background: var(--mm-lime); + border-color: var(--mm-ink); + color: var(--mm-ink); +} + +.mm-sheet-primary, +.mm-sheet-actions > button { + align-items: center; + border: 1px solid var(--mm-ink); + cursor: pointer; + display: inline-flex; + font-size: 0.66rem; + font-weight: 680; + gap: 0.35rem; + justify-content: center; + min-height: 42px; + padding: 0 0.85rem; +} + +.mm-sheet-primary { + background: var(--mm-ink); + color: var(--mm-paper-bright); +} + +.mm-feedback-sheet > .mm-sheet-primary, +.mm-thanks-sheet > .mm-sheet-primary { + width: 100%; +} + +.mm-store-icon, +.mm-thanks-mark { + align-items: center; + display: flex; + justify-content: center; +} + +.mm-store-icon { + background: var(--mm-blue); + color: var(--mm-ink); + float: right; + font-size: 0.85rem; + height: 38px; + margin-left: 0.75rem; + width: 38px; +} + +.mm-sheet-actions { + display: grid; + gap: 0.42rem; + grid-template-columns: 0.72fr 1.28fr; + margin-top: 0.88rem; +} + +.mm-sheet-actions > button { + background: transparent; + color: var(--mm-ink); +} + +.mm-sheet-actions > .mm-sheet-primary { + background: var(--mm-ink); + color: var(--mm-paper-bright); +} + +.mm-thanks-mark { + background: var(--mm-lime); + border-radius: 50%; + font-size: 1rem; + height: 44px; + margin-bottom: 0.78rem; + width: 44px; +} + +.mm-thanks-sheet > .mm-sheet-primary { + margin-top: 1rem; +} + +.mm-resolved-card { + background: var(--mm-lime); + border: 1px solid var(--mm-ink); + box-shadow: 9px 9px 0 var(--mm-ink); + color: var(--mm-ink); + left: 50%; + opacity: 0; + padding: 1.15rem; + pointer-events: none; + position: absolute; + top: 50%; + transform: translate(-50%, -42%) scale(0.94); + transition: + opacity 260ms ease, + transform 420ms cubic-bezier(0.2, 0.85, 0.25, 1.08); + width: calc(100% - 2.5rem); + z-index: 8; +} + +.mm-resolved-card.is-visible { + opacity: 1; + pointer-events: auto; + transform: translate(-50%, -50%) scale(1); +} + +.mm-origin-flow[data-reason="GLOBAL_HIDE_ALL"] .mm-resolved-card { + background: #ffb39f; +} + +.mm-resolved-card > span { + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.55rem; + font-weight: 760; +} + +.mm-resolved-card > strong { + display: block; + font-size: 1.12rem; + letter-spacing: -0.035em; + line-height: 1.1; + margin-top: 0.38rem; +} + +.mm-resolved-card pre { + background: color-mix(in srgb, var(--mm-paper-bright) 65%, transparent); + font-size: 0.61rem; + line-height: 1.65; + margin-top: 0.8rem; + padding: 0.65rem; +} + +.mm-resolved-card button { + align-items: center; + background: var(--mm-ink); + border: 0; + color: var(--mm-paper-bright); + cursor: pointer; + display: flex; + font-size: 0.65rem; + font-weight: 680; + gap: 0.42rem; + justify-content: center; + margin-top: 0.72rem; + min-height: 42px; + width: 100%; +} + +.mm-proof { + background: var(--mm-surface); + border-block: 1px solid var(--mm-dark-line); + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + margin: 0 auto; + max-width: 1600px; +} + +.mm-proof a { + align-items: center; + border-right: 1px solid var(--mm-dark-line); + display: grid; + gap: 0.18rem 0.7rem; + grid-template-columns: auto 1fr auto; + min-height: 92px; + padding: 1.2rem clamp(1rem, 2.5vw, 2.4rem); + transition: + background 180ms ease, + color 180ms ease; +} + +.mm-proof a:last-child { + border-right: 0; +} + +.mm-proof strong { + color: var(--mm-lime); + font-family: "JetBrains Mono Variable", monospace; + font-size: clamp(0.92rem, 1.4vw, 1.2rem); + grid-row: 1 / 3; + letter-spacing: -0.06em; +} + +.mm-proof span { + color: var(--mm-fg-muted); + font-size: 0.69rem; + font-weight: 620; + letter-spacing: 0.01em; + line-height: 1.25; +} + +.mm-proof svg { + color: var(--mm-coral); + grid-row: 1 / 3; + transition: transform 180ms ease; +} + +.mm-proof a:hover { + background: var(--mm-fg); + color: var(--mm-ink); +} + +.mm-proof a:hover strong { + color: var(--mm-blue); +} + +.mm-proof a:hover span { + color: var(--mm-muted); +} + +.mm-proof a:hover svg { + transform: translate(2px, -2px); +} + +.mm-request { + border-top: 1px solid var(--mm-line); + display: grid; + gap: clamp(2rem, 4vw, 5rem); + grid-template-columns: minmax(0, 1.18fr) minmax(330px, 0.72fr); + margin: 0 auto; + max-width: 1600px; + padding: clamp(7rem, 11vw, 11rem) clamp(1.5rem, 7vw, 7rem); + position: relative; +} + +.mm-section-number, +.mm-show-heading > span, +.mm-close-heading > span, +.mm-history-heading > span, +.mm-final-copy > span { + color: var(--mm-muted); + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.62rem; + font-weight: 640; + letter-spacing: 0.05em; +} + +.mm-request-copy h2 { + color: var(--mm-fg); + font-size: clamp(2.5rem, 4vw, 4.25rem); + font-weight: 590; + letter-spacing: -0.045em; + line-height: 1; + max-width: 720px; + text-wrap: pretty; +} + +.mm-request-copy > .mm-section-number { + display: block; + margin-bottom: 1.2rem; +} + +.mm-request-copy > p { + color: var(--mm-fg-muted); + font-size: clamp(1rem, 1.3vw, 1.16rem); + line-height: 1.65; + margin-top: 1.6rem; + max-width: 680px; +} + +.mm-request-branches { + display: grid; + gap: 1px; + grid-template-columns: repeat(2, minmax(0, 1fr)); + margin-top: 2.4rem; +} + +.mm-request-branches article { + border: 1px solid var(--mm-dark-line); + padding: clamp(1.15rem, 2vw, 1.65rem); +} + +.mm-request-branches article + article { + border-left: 0; +} + +.mm-request-branches span, +.mm-request-proof > article > span { + color: var(--mm-fg-muted); + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.56rem; + font-weight: 650; + letter-spacing: 0.06em; +} + +.mm-request-branches strong { + display: block; + font-size: clamp(1rem, 1.45vw, 1.3rem); + letter-spacing: -0.035em; + line-height: 1.1; + margin-top: 0.8rem; +} + +.mm-request-branches p { + color: var(--mm-fg-muted); + font-size: 0.76rem; + line-height: 1.55; + margin-top: 0.65rem; +} + +.mm-request-copy aside { + border-left: 3px solid var(--mm-coral); + margin-top: 2rem; + max-width: 680px; + padding-left: 1.2rem; +} + +.mm-request-copy aside > span { + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.62rem; + font-weight: 680; + text-transform: uppercase; +} + +.mm-request-copy aside p { + color: var(--mm-muted); + font-size: 0.95rem; + line-height: 1.6; + margin-top: 0.55rem; +} + +.mm-request-proof { + align-self: center; + background: var(--mm-ink); + border: 1px solid var(--mm-dark-line); + box-shadow: 12px 12px 0 var(--mm-coral); + color: var(--mm-paper); + display: grid; + padding: 0 1.35rem; + position: relative; + transform: rotate(0.7deg); +} + +.mm-request-proof > article { + border-bottom: 1px solid #3a3732; + padding: 1.3rem 0; +} + +.mm-request-proof > article > strong { + color: var(--mm-paper-bright); + display: block; + font-size: clamp(1rem, 1.45vw, 1.3rem); + letter-spacing: -0.035em; + line-height: 1.15; + margin-top: 0.62rem; +} + +.mm-request-proof > article:first-child > strong { + color: var(--mm-lime); +} + +.mm-request-proof p { + color: #aaa298; + font-size: 0.7rem; + line-height: 1.5; + margin-top: 0.5rem; +} + +.mm-request-proof p code { + color: var(--mm-paper-bright); +} + +.mm-request-proof > a { + align-items: center; + color: var(--mm-lime); + display: flex; + font-size: 0.69rem; + font-weight: 650; + gap: 0.42rem; + justify-content: space-between; + min-height: 58px; +} + +.mm-live { + background: var(--mm-ink); + border-block: 1px solid var(--mm-dark-line); + color: var(--mm-paper); + padding: clamp(6.5rem, 10vw, 10rem) max(5vw, calc((100vw - 1500px) / 2)); + position: relative; +} + +.mm-live::before { + background: var(--mm-coral); + clip-path: polygon( + 50% 0, + 59% 39%, + 100% 50%, + 59% 61%, + 50% 100%, + 41% 61%, + 0 50%, + 41% 39% + ); + content: ""; + height: clamp(48px, 6vw, 82px); + position: absolute; + right: max(5vw, calc((100vw - 1500px) / 2)); + top: clamp(3rem, 6vw, 6rem); + transform: rotate(9deg); + width: clamp(48px, 6vw, 82px); +} + +.mm-live > header { + display: grid; + gap: 1.2rem clamp(3rem, 8vw, 8rem); + grid-template-columns: minmax(0, 0.9fr) minmax(280px, 0.55fr); + margin-bottom: clamp(3rem, 5vw, 5rem); + max-width: 1240px; +} + +.mm-live > header > span { + color: var(--mm-fg-muted); + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.62rem; + font-weight: 640; + grid-column: 1 / -1; + letter-spacing: 0.05em; +} + +.mm-live > header h2 { + font-size: clamp(2.5rem, 4.2vw, 4.35rem); + font-weight: 590; + letter-spacing: -0.045em; + line-height: 1; + text-wrap: pretty; +} + +.mm-live > header p { + align-self: end; + color: #aaa298; + font-size: clamp(0.96rem, 1.25vw, 1.1rem); + line-height: 1.65; + max-width: 480px; +} + +.mm-live-package { + background: #0d0c0a; + border: 1px solid #4d4840; + box-shadow: 12px 12px 0 #26231e; + isolation: isolate; + min-height: 660px; + overflow: hidden; + position: relative; +} + +.mm-live-package-content { + min-height: 660px; + padding: clamp(1.25rem, 2.5vw, 2.4rem); +} + +.mm-package-toolbar { + align-items: center; + border-bottom: 1px solid var(--mm-dark-line); + display: flex; + justify-content: space-between; + min-height: 46px; + padding-bottom: 1rem; +} + +.mm-package-toolbar > div:first-child { + align-items: center; + display: flex; + gap: 0.85rem; +} + +.mm-package-toolbar span, +.mm-package-toolbar code, +.mm-package-copy > span, +.mm-package-code > div, +.mm-package-runtime article > header span { + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.61rem; + font-weight: 700; + letter-spacing: 0.055em; +} + +.mm-package-toolbar > div:first-child span, +.mm-package-copy > span { + color: var(--mm-lime); +} + +.mm-package-toolbar code { + color: #9f988d; +} + +.mm-package-count { + align-items: baseline; + display: flex; + gap: 0.5rem; +} + +.mm-package-count strong { + color: var(--mm-paper); + font-size: 1.1rem; + font-variant-numeric: tabular-nums; +} + +.mm-package-count span { + color: #9f988d; +} + +.mm-package-grid { + align-items: stretch; + display: grid; + gap: clamp(2rem, 5vw, 5.5rem); + grid-template-columns: minmax(0, 1.1fr) minmax(300px, 0.75fr); + padding-block: clamp(2.8rem, 5vw, 5.2rem); +} + +.mm-package-copy { + align-self: center; +} + +.mm-package-copy h3 { + font-size: clamp(2rem, 3.4vw, 3.4rem); + font-weight: 590; + letter-spacing: -0.042em; + line-height: 1.02; + margin-top: 0.85rem; + max-width: 620px; + text-wrap: pretty; +} + +.mm-package-copy p { + color: var(--mm-fg-muted); + line-height: 1.65; + margin-top: 1.35rem; + max-width: 570px; +} + +.mm-package-actions { + display: flex; + flex-wrap: wrap; + gap: 0.65rem; + margin-top: 2rem; +} + +.mm-package-actions button, +.mm-package-modal button { + background: var(--mm-paper); + border: 1px solid var(--mm-paper); + color: var(--mm-ink); + cursor: pointer; + font-size: 0.78rem; + font-weight: 750; + min-height: 44px; + padding: 0.7rem 1rem; + transition: + background-color 150ms ease, + border-color 150ms ease, + color 150ms ease, + transform 150ms ease; +} + +.mm-package-actions button:nth-child(2) { + background: var(--mm-coral); + border-color: var(--mm-coral); + color: #151411; +} + +.mm-package-actions button:nth-child(3) { + background: transparent; + border-color: #5b554b; + color: var(--mm-paper); +} + +.mm-package-modal button:not(.mm-package-modal-primary) { + background: transparent; + border-color: #655f56; + color: var(--mm-ink); +} + +.mm-package-actions button:hover:not(:disabled), +.mm-package-modal button:hover { + transform: translateY(-2px); +} + +.mm-package-actions button:focus-visible, +.mm-package-modal button:focus-visible, +.mm-package-modal:focus-visible { + outline: 3px solid var(--mm-lime); + outline-offset: 3px; +} + +.mm-package-actions button:disabled { + cursor: default; + opacity: 0.45; +} + +.mm-package-code { + background: #171512; + border: 1px solid #454038; + display: flex; + flex-direction: column; + min-height: 300px; +} + +.mm-package-code > div, +.mm-package-code footer { + align-items: center; + color: #9f988d; + display: flex; + justify-content: space-between; + padding: 1rem 1.1rem; +} + +.mm-package-code > div { + border-bottom: 1px solid #37332d; +} + +.mm-package-code pre { + align-content: center; + color: var(--mm-paper); + flex: 1; + font-size: clamp(0.78rem, 1.3vw, 0.95rem); + line-height: 1.9; + margin: 0; + overflow: auto; + padding: 2rem 1.4rem; +} + +.mm-package-code pre span { + color: #b9b1a4; +} + +.mm-package-code pre b { + color: var(--mm-coral); + font-weight: 700; +} + +.mm-package-code footer { + border-top: 1px solid #37332d; + color: var(--mm-lime); + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.62rem; +} + +.mm-package-runtime { + display: grid; + gap: 1px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.mm-package-runtime article { + background: #171512; + border: 1px solid #454038; + min-height: 224px; + padding: 1.2rem; +} + +.mm-package-runtime article > header { + align-items: start; + display: flex; + justify-content: space-between; +} + +.mm-package-runtime article > header div { + display: grid; + gap: 0.4rem; +} + +.mm-package-runtime article > header span { + color: #9f988d; +} + +.mm-package-runtime article > header strong { + color: var(--mm-paper); + font-size: 0.9rem; +} + +.mm-package-runtime article > header > b { + color: var(--mm-lime); + font-size: 1.2rem; +} + +.mm-package-list { + display: grid; + gap: 0.55rem; + margin-top: 1.35rem; +} + +.mm-package-list > p { + color: #9f988d; + display: grid; + font-size: 0.78rem; + gap: 0.3rem; + line-height: 1.45; +} + +.mm-package-list > p strong { + color: var(--mm-paper); +} + +.mm-package-list > div { + align-items: center; + border-top: 1px solid #37332d; + display: grid; + gap: 0.8rem; + grid-template-columns: auto minmax(0, 1fr) auto; + padding-top: 0.65rem; +} + +.mm-package-list > div > span { + border: 1px solid #565045; + color: var(--mm-lime); + display: grid; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.65rem; + height: 24px; + place-items: center; + width: 24px; +} + +.mm-package-list > div p { + display: grid; + gap: 0.15rem; + min-width: 0; +} + +.mm-package-list > div p strong { + color: var(--mm-paper); + font-size: 0.74rem; +} + +.mm-package-list > div p code { + color: #9f988d; + font-size: 0.61rem; +} + +.mm-package-list > div > b { + color: var(--mm-coral); + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.6rem; +} + +.mm-package-list.is-timeline > div { + grid-template-columns: minmax(0, 1fr) auto; +} + +.mm-direct-portal { + inset: 0; + pointer-events: none; + position: absolute; + z-index: 20; +} + +.mm-direct-portal > div { + height: 100%; +} + +.mm-live-package.is-modal-open .mm-direct-portal { + pointer-events: auto; +} + +.mm-live-package.is-modal-open { + isolation: auto; + overflow: visible; + z-index: 100; +} + +.mm-live-package.is-modal-open .mm-direct-portal { + height: 100dvh; + inset: 0; + position: fixed; + width: 100vw; +} + +.mm-live-package.is-modal-open .mm-direct-portal > div { + height: 100dvh; + width: 100%; +} + +.mm-package-modal { + background: var(--mm-paper); + border: 1px solid #c9c1b5; + box-shadow: 8px 8px 0 #26231e; + color: var(--mm-ink); + margin: 1.25rem auto; + max-width: min(460px, calc(100% - 2.5rem)); + padding: clamp(1.25rem, 3vw, 2rem); + position: relative; + width: 100%; +} + +.mm-package-modal.is-mobile { + border: 7px solid #1c1a17; + border-radius: 40px; + box-shadow: + 0 0 0 1px #746e64, + 9px 11px 0 #26231e; + max-width: min(390px, calc(100% - 2.5rem)); + overflow: hidden; +} + +.mm-package-modal.is-web { + max-width: min(560px, calc(100% - 2.5rem)); +} + +.mm-package-surface-bar { + align-items: center; + border-bottom: 1px solid #c9c1b5; + color: #655f56; + display: flex; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.58rem; + justify-content: space-between; + margin: calc(clamp(1.25rem, 3vw, 2rem) * -1) + calc(clamp(1.25rem, 3vw, 2rem) * -1) 1.4rem; + min-height: 38px; + padding-inline: clamp(1.25rem, 3vw, 2rem); +} + +.mm-package-modal.is-mobile .mm-package-surface-bar { + background: var(--mm-ink); + border-bottom: 0; + color: var(--mm-paper); + min-height: 46px; + padding-top: 0.35rem; +} + +.mm-package-island { + background: #050504; + border: 1px solid #302d28; + border-radius: 999px; + height: 15px; + width: 52px; +} + +.mm-package-modal.is-web .mm-package-surface-bar { + justify-content: flex-start; + gap: 1rem; +} + +.mm-package-modal.is-web .mm-package-surface-bar code { + border-left: 1px solid #c9c1b5; + color: #655f56; + padding-left: 1rem; +} + +.mm-package-modal-heading { + align-items: center; + display: flex; + gap: 0.85rem; +} + +.mm-package-modal-heading > div { + display: grid; + gap: 0.25rem; +} + +.mm-package-modal-heading > div > span { + color: #655f56; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.6rem; + font-weight: 750; + letter-spacing: 0.06em; +} + +.mm-package-modal-heading h3 { + color: var(--mm-ink); + font-size: clamp(1.55rem, 4vw, 2.2rem); + font-weight: 680; + letter-spacing: -0.045em; + line-height: 1; +} + +.mm-package-spark { + background: var(--mm-coral); + clip-path: polygon( + 50% 0, + 59% 39%, + 100% 50%, + 59% 61%, + 50% 100%, + 41% 61%, + 0 50%, + 41% 39% + ); + display: block; + flex: 0 0 32px; + height: 32px; +} + +.mm-package-modal > p { + color: #4d4840; + line-height: 1.55; + margin-block: 1.25rem; +} + +.mm-package-modal-primary { + background: var(--mm-ink) !important; + border-color: var(--mm-ink) !important; + color: var(--mm-paper) !important; + width: 100%; +} + +.mm-package-score { + display: grid; + gap: 0.45rem; + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.mm-package-score button { + padding-inline: 0.25rem; +} + +.mm-package-score button:last-child { + background: var(--mm-coral); + border-color: var(--mm-coral); + color: var(--mm-ink); +} + +.mm-package-modal-fact { + align-items: center; + border-block: 1px solid #c9c1b5; + display: flex; + justify-content: space-between; + margin-bottom: 1.25rem; + padding-block: 0.75rem; +} + +.mm-package-modal-fact span { + color: #655f56; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.62rem; +} + +.mm-package-modal-fact strong { + color: var(--mm-ink); + font-size: 0.78rem; +} + +.mm-package-choices { + display: grid; + gap: 0.5rem; +} + +.mm-package-choices button { + align-items: center; + display: flex; + justify-content: space-between; + text-align: left; +} + +.mm-package-choices code { + color: #aaa298; + font-size: 0.58rem; +} + +.mm-package-modal-actions { + display: grid; + gap: 0.55rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.mm-package-progress { + appearance: none; + background: #d4cec4; + border: 0; + height: 8px; + overflow: hidden; + width: 100%; +} + +.mm-package-progress::-webkit-progress-bar { + background: #d4cec4; +} + +.mm-package-progress::-webkit-progress-value { + background: var(--mm-coral); + transition: width 300ms ease; +} + +.mm-package-progress::-moz-progress-bar { + background: var(--mm-coral); +} + +.mm-package-progress-meta { + align-items: baseline; + display: flex; + justify-content: space-between; + margin-block: 0.6rem 1.25rem; +} + +.mm-package-progress-meta strong { + color: var(--mm-ink); + font-variant-numeric: tabular-nums; +} + +.mm-package-progress-meta code { + color: #655f56; + font-size: 0.66rem; +} + +.mm-examples { + background: var(--mm-paper-bright); + border-block: 1px solid var(--mm-line); + overflow: hidden; + padding: clamp(7rem, 10vw, 10rem) max(5vw, calc((100vw - 1500px) / 2)); + position: relative; +} + +.mm-examples::before { + content: none; +} + +.mm-examples > header { + align-items: end; + display: grid; + gap: 2rem clamp(4rem, 9vw, 10rem); + grid-template-columns: minmax(340px, 0.9fr) minmax(280px, 0.55fr); + margin-bottom: clamp(3rem, 5vw, 5.5rem); + position: relative; + z-index: 2; +} + +.mm-examples > header > span { + color: var(--mm-muted); + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.62rem; + font-weight: 640; + grid-column: 1 / -1; + letter-spacing: 0.05em; + margin-bottom: -0.4rem; +} + +.mm-examples > header h2 { + font-size: clamp(2.5rem, 4.2vw, 4.35rem); + font-weight: 590; + letter-spacing: -0.045em; + line-height: 1; + text-wrap: pretty; +} + +.mm-examples > header h2 code { + color: var(--mm-coral); + font-size: 0.72em; + font-weight: 580; + letter-spacing: -0.06em; +} + +.mm-examples > header p { + color: var(--mm-muted); + font-size: clamp(0.98rem, 1.25vw, 1.12rem); + line-height: 1.65; + max-width: 450px; + padding-bottom: 0.3rem; +} + +.mm-examples-shell { + border-block: 1px solid var(--mm-ink); + display: grid; + grid-template-columns: minmax(260px, 0.33fr) minmax(0, 1.67fr); + min-height: 650px; + position: relative; + z-index: 2; +} + +.mm-example-tabs { + border-right: 1px solid var(--mm-ink); + display: grid; + grid-template-rows: repeat(3, 1fr); +} + +.mm-example-tabs button { + background: transparent; + border: 0; + border-bottom: 1px solid var(--mm-line); + color: var(--mm-ink); + cursor: pointer; + display: grid; + gap: 0.55rem; + grid-template-columns: 34px 1fr 22px; + padding: clamp(1.5rem, 2.4vw, 2.35rem); + position: relative; + text-align: left; + transition: + background 180ms ease, + color 180ms ease; +} + +.mm-example-tabs button:last-child { + border-bottom: 0; +} + +.mm-example-tabs button::before { + background: var(--mm-coral); + content: ""; + inset-block: 0; + left: 0; + position: absolute; + transform: scaleY(0); + transform-origin: bottom; + transition: transform 220ms cubic-bezier(0.22, 1, 0.36, 1); + width: 5px; +} + +.mm-example-tabs button[aria-selected="true"] { + background: var(--mm-ink); + color: var(--mm-paper-bright); +} + +.mm-example-tabs button[aria-selected="true"]::before { + transform: scaleY(1); +} + +.mm-example-tabs button > span { + color: var(--mm-muted); + font-family: "Instrument Serif", serif; + font-size: 1.45rem; + font-style: italic; + grid-row: 1 / span 2; +} + +.mm-example-tabs button[aria-selected="true"] > span { + color: var(--mm-coral); +} + +.mm-example-tabs button strong { + align-self: end; + font-size: clamp(0.92rem, 1.2vw, 1.08rem); + letter-spacing: -0.025em; +} + +.mm-example-tabs button small { + color: var(--mm-muted); + font-size: 0.7rem; + grid-column: 2; + line-height: 1.5; + max-width: 220px; +} + +.mm-example-tabs button[aria-selected="true"] small { + color: #aaa298; +} + +.mm-example-tabs button svg { + align-self: center; + grid-column: 3; + grid-row: 1 / span 2; + opacity: 0; + transform: translateX(-8px); + transition: + opacity 180ms ease, + transform 180ms ease; +} + +.mm-example-tabs button[aria-selected="true"] svg { + opacity: 1; + transform: translateX(0); +} + +.mm-example-panel { + background: #201d18; + border: 1px solid #4d4840; + display: grid; + gap: clamp(1.5rem, 3vw, 3rem); + grid-template-columns: minmax(430px, 1.16fr) minmax(280px, 0.84fr); + min-width: 0; + overflow: hidden; + padding: clamp(2rem, 4.2vw, 4.5rem); + position: relative; +} + +.mm-example-panel::before { + content: none; +} + +.mm-example-panel::after { + content: none; +} + +.mm-example-code, +.mm-example-preview { + position: relative; + z-index: 2; +} + +.mm-example-code { + align-self: center; + background: var(--mm-ink); + box-shadow: 10px 10px 0 color-mix(in srgb, var(--mm-coral) 72%, #201d18); + color: #d7d0c5; + min-width: 0; + transform: rotate(-0.7deg); +} + +.mm-example-code > div { + align-items: center; + border-bottom: 1px solid #3a3732; + display: flex; + gap: 1rem; + min-height: 48px; + padding: 0 1rem; +} + +.mm-example-code > div > i { + display: flex; + gap: 5px; +} + +.mm-example-code > div > i span { + background: #645e55; + border-radius: 50%; + height: 6px; + width: 6px; +} + +.mm-example-code > div > i span:first-child { + background: var(--mm-coral); +} + +.mm-example-code > div code { + color: #8f887e; + font-size: 0.58rem; +} + +.mm-example-code pre { + font-size: clamp(0.65rem, 0.87vw, 0.82rem); + line-height: 1.75; + min-height: 405px; + overflow: auto; + padding: clamp(1.4rem, 2.6vw, 2.5rem); + white-space: pre-wrap; +} + +.mm-example-preview { + align-self: center; + background: #0f0e0c; + border: 1px solid #090806; + box-shadow: 10px 10px 0 color-mix(in srgb, var(--mm-blue) 60%, #201d18); + color: var(--mm-paper-bright); + min-height: 470px; + padding: 1rem; + transform: rotate(0.8deg); +} + +.mm-example-preview-meta { + align-items: center; + color: #8f887e; + display: flex; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.54rem; + justify-content: space-between; + letter-spacing: 0.03em; + min-height: 34px; +} + +.mm-example-preview-meta span { + align-items: center; + display: flex; + gap: 0.45rem; +} + +.mm-example-preview-meta span::before { + background: var(--mm-blue); + clip-path: polygon( + 50% 0, + 59% 39%, + 100% 50%, + 59% 61%, + 50% 100%, + 41% 61%, + 0 50%, + 41% 39% + ); + content: ""; + height: 9px; + width: 9px; +} + +.mm-example-preview article { + background: var(--mm-paper-bright); + border-radius: 24px 24px 5px 5px; + color: var(--mm-ink); + margin-top: 2.8rem; + min-height: 295px; + padding: 1.5rem; + position: relative; +} + +.mm-example-handle { + background: var(--mm-line); + border-radius: 99px; + height: 4px; + left: 50%; + position: absolute; + top: 10px; + transform: translateX(-50%); + width: 36px; +} + +.mm-example-preview-icon { + align-items: center; + background: color-mix(in srgb, var(--mm-coral) 15%, transparent); + color: var(--mm-coral); + display: flex; + height: 38px; + justify-content: center; + margin-top: 0.8rem; + width: 38px; +} + +.mm-example-preview article[data-preview="update"] .mm-example-preview-icon { + background: color-mix(in srgb, var(--mm-blue) 12%, transparent); + color: var(--mm-blue); +} + +.mm-example-preview h3 { + font-size: clamp(1.35rem, 2vw, 1.8rem); + letter-spacing: -0.045em; + margin-top: 1rem; +} + +.mm-example-preview article > p { + color: var(--mm-muted); + font-size: 0.78rem; + line-height: 1.5; + margin-top: 0.35rem; +} + +.mm-example-preview article > button { + background: var(--mm-ink); + border: 0; + bottom: 1.5rem; + color: var(--mm-paper-bright); + cursor: pointer; + font-size: 0.7rem; + font-weight: 680; + inset-inline: 1.5rem; + min-height: 45px; + position: absolute; + transition: + opacity 160ms ease, + transform 160ms ease; +} + +.mm-example-preview article > button:hover:not(:disabled) { + transform: translateY(-2px); +} + +.mm-example-preview article > button:disabled { + cursor: wait; + opacity: 0.72; +} + +.mm-example-preview article[data-preview="confirm"] > button { + background: var(--mm-coral); + color: var(--mm-ink); +} + +.mm-example-progress { + background: #ddd7cc; + height: 7px; + margin-top: 2rem; + overflow: hidden; +} + +.mm-example-progress span { + background: var(--mm-blue); + display: block; + height: 100%; + transition: width 180ms ease; + width: var(--mm-example-progress, 0%); +} + +.mm-example-result { + align-items: center; + border-top: 1px solid #39352f; + display: flex; + justify-content: space-between; + margin-top: 1rem; + min-height: 48px; +} + +.mm-example-result span { + color: #928b81; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.52rem; +} + +.mm-example-result code { + color: var(--mm-lime); + font-size: 0.64rem; +} + +.mm-owner { + background: var(--mm-ink); + color: var(--mm-paper); + display: grid; + gap: clamp(4rem, 8vw, 9rem); + grid-template-columns: minmax(300px, 0.72fr) minmax(560px, 1.28fr); + padding: clamp(7rem, 11vw, 11rem) max(5vw, calc((100vw - 1500px) / 2)); + position: relative; +} + +.mm-owner::before { + content: none; +} + +.mm-section-copy { + align-self: center; + position: relative; + z-index: 2; +} + +.mm-owner .mm-section-number { + color: var(--mm-fg-muted); +} + +.mm-section-copy h2 { + font-size: clamp(2.5rem, 4vw, 4.25rem); + font-weight: 590; + letter-spacing: -0.045em; + line-height: 1; + margin-top: 1.2rem; + text-wrap: pretty; +} + +.mm-section-copy > p { + color: #aaa298; + font-size: clamp(0.98rem, 1.25vw, 1.13rem); + line-height: 1.65; + margin-top: 1.8rem; + max-width: 510px; +} + +.mm-section-copy > a, +.mm-close-heading > a { + align-items: center; + border-bottom: 1px solid #555047; + color: var(--mm-lime); + display: inline-flex; + font-size: 0.76rem; + font-weight: 670; + gap: 0.48rem; + margin-top: 1.8rem; + min-height: 42px; + transition: border-color 160ms ease; +} + +.mm-section-copy > a:hover, +.mm-close-heading > a:hover { + border-color: currentColor; +} + +.mm-owner-map { + align-self: center; + min-height: 620px; + padding: 2rem 0; + position: relative; + z-index: 2; +} + +.mm-owner-callers { + border-left: 1px solid #4d4840; + display: grid; + gap: 0; + left: 0; + padding-left: 1.2rem; + position: absolute; + top: 8%; + width: 27%; +} + +.mm-owner-callers > span, +.mm-owner-function > span, +.mm-owner-portal small { + color: #928b81; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.57rem; + letter-spacing: 0.07em; +} + +.mm-owner-callers code { + align-items: center; + border-bottom: 1px solid #302d28; + color: #d4cec3; + display: flex; + font-size: clamp(0.62rem, 0.75vw, 0.72rem); + min-height: 55px; + position: relative; +} + +.mm-owner-callers code::before { + background: var(--mm-coral); + clip-path: polygon( + 50% 0, + 59% 39%, + 100% 50%, + 59% 61%, + 50% 100%, + 41% 61%, + 0 50%, + 41% 39% + ); + content: ""; + height: 9px; + left: -1.48rem; + position: absolute; + width: 9px; +} + +.mm-owner-route { + height: 1px; + left: 27%; + position: absolute; + top: 42%; + width: 13%; +} + +.mm-owner-route i { + background: repeating-linear-gradient( + 90deg, + #777067 0 7px, + transparent 7px 12px + ); + display: block; + height: 1px; + width: 100%; +} + +.mm-owner-route span { + color: #928b81; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.52rem; + left: 50%; + position: absolute; + top: -1.2rem; + transform: translateX(-50%); + white-space: nowrap; +} + +.mm-owner-function { + border-block: 1px solid #4d4840; + left: 40%; + padding: 1.35rem 0; + position: absolute; + top: 33%; + width: 28%; +} + +.mm-owner-function strong { + color: var(--mm-paper-bright); + display: block; + font-size: clamp(1rem, 1.45vw, 1.32rem); + letter-spacing: -0.04em; + margin-top: 0.55rem; +} + +.mm-owner-function code { + color: var(--mm-lime); + display: block; + font-size: 0.57rem; + margin-top: 0.4rem; +} + +.mm-owner-route.is-portal { + left: 68%; + width: 8%; +} + +.mm-owner-portal { + bottom: 4%; + position: absolute; + right: 0; + width: 24%; +} + +.mm-owner-portal > div:first-child { + align-items: center; + border-bottom: 1px solid #4d4840; + display: flex; + gap: 0.85rem; + padding-bottom: 1.1rem; +} + +.mm-owner-portal > div:first-child > span { + display: grid; + gap: 0.22rem; +} + +.mm-owner-portal strong { + font-size: clamp(0.82rem, 1vw, 1rem); +} + +.mm-owner-sheet-stack { + height: 275px; + margin: 1.2rem auto 0; + position: relative; + width: 84%; +} + +.mm-owner-sheet-stack i { + background: #2a2722; + border: 1px solid #555047; + border-radius: 26px 26px 4px 4px; + height: 150px; + inset-inline: 0; + position: absolute; + top: 0; + transform: translateY(calc(var(--sheet-index, 0) * 28px)) + scale(calc(1 - var(--sheet-index, 0) * 0.035)); + transform-origin: top; +} + +.mm-owner-sheet-stack i:nth-child(2) { + --sheet-index: 1; + background: #312d27; +} + +.mm-owner-sheet-stack i:nth-child(3) { + --sheet-index: 2; + background: #38332d; +} + +.mm-owner-sheet-stack i:nth-child(4) { + --sheet-index: 3; + background: var(--mm-coral); + border-color: var(--mm-coral); +} + +.mm-owner-sheet-stack i:nth-child(4)::after { + background: var(--mm-ink); + border-radius: 99px; + content: ""; + height: 4px; + left: 50%; + opacity: 0.5; + position: absolute; + top: 12px; + transform: translateX(-50%); + width: 35px; +} + +.mm-show { + background: #181613; + border-block: 1px solid var(--mm-dark-line); + color: var(--mm-fg); + display: grid; + gap: clamp(3rem, 6vw, 7rem); + grid-template-columns: minmax(280px, 0.76fr) minmax(480px, 1.24fr); + padding: clamp(7rem, 11vw, 11rem) max(5vw, calc((100vw - 1500px) / 2)); + position: relative; +} + +.mm-show::after { + content: none; +} + +.mm-show-heading, +.mm-show-object, +.mm-show-ledger, +.mm-update-note { + position: relative; + z-index: 2; +} + +.mm-show-heading > span { + color: var(--mm-fg-muted); +} + +.mm-final-copy > span { + color: color-mix(in srgb, var(--mm-ink) 68%, transparent); +} + +.mm-show-heading h2, +.mm-close-heading h2, +.mm-history-heading h2 { + font-size: clamp(2.5rem, 4.2vw, 4.35rem); + font-weight: 590; + letter-spacing: -0.045em; + line-height: 1; + margin-top: 1.2rem; + text-wrap: pretty; +} + +.mm-show-heading h2 { + color: var(--mm-fg); +} + +.mm-show-heading h2 code { + background: var(--mm-paper); + color: var(--mm-ink); + font-size: 0.68em; + font-weight: 560; + letter-spacing: -0.04em; + padding: 0.04em 0.12em; +} + +.mm-show-heading p, +.mm-close-heading p, +.mm-history-heading p { + font-size: clamp(0.98rem, 1.25vw, 1.12rem); + line-height: 1.65; + margin-top: 1.8rem; + max-width: 560px; +} + +.mm-show-heading p { + color: var(--mm-fg-muted); +} + +.mm-show-object { + align-self: center; + background: #0d0c0a; + border: 1px solid #4d4840; + box-shadow: 12px 12px 0 color-mix(in srgb, var(--mm-coral) 70%, #181613); + color: var(--mm-paper-bright); + min-height: 370px; + padding: clamp(2rem, 4vw, 4.25rem); + position: sticky; + top: 2rem; +} + +.mm-show-object::before { + content: none; +} + +.mm-show-object pre { + font-size: clamp(0.76rem, 1.25vw, 1.08rem); + line-height: 2; + position: relative; +} + +.mm-show-object pre span { + color: #928b81; +} + +.mm-show-object pre b { + color: #c4af88; + font-weight: 540; +} + +.mm-show-object-tag { + border-top: 1px solid #3f3b35; + bottom: 1.5rem; + color: #928b81; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.56rem; + inset-inline: clamp(2rem, 4vw, 4.25rem); + letter-spacing: 0.06em; + padding-top: 0.75rem; + position: absolute; +} + +.mm-show-ledger { + grid-column: 2; + margin-top: -1.2rem; +} + +.mm-show-ledger > div { + border-top: 1px solid var(--mm-dark-line); + display: grid; + gap: 1.5rem; + grid-template-columns: minmax(150px, 0.5fr) 1fr; + padding: 1.35rem 0; +} + +.mm-show-ledger > div:last-child { + border-bottom: 1px solid var(--mm-dark-line); +} + +.mm-show-ledger dt { + align-items: center; + display: flex; + justify-content: space-between; +} + +.mm-show-ledger dt code { + font-size: 0.76rem; + font-weight: 650; +} + +.mm-show-ledger dt span { + color: var(--mm-coral); + font-family: "Instrument Serif", serif; + font-size: 1.4rem; + font-style: italic; +} + +.mm-show-ledger dd { + color: var(--mm-fg-muted); + font-size: 0.88rem; + line-height: 1.55; + margin: 0; +} + +.mm-update-note { + align-self: end; + border-left: 4px solid var(--mm-blue); + color: var(--mm-fg-muted); + font-size: 0.77rem; + line-height: 1.55; + max-width: 430px; + padding-left: 1rem; +} + +.mm-update-note span { + display: block; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.56rem; + font-weight: 700; + margin-bottom: 0.45rem; + text-transform: uppercase; +} + +.mm-update-note code { + font-size: 0.9em; + font-weight: 660; +} + +.mm-close { + display: grid; + gap: clamp(4rem, 8vw, 9rem); + grid-template-columns: minmax(280px, 0.66fr) minmax(560px, 1.34fr); + margin: 0 auto; + max-width: 1600px; + padding: clamp(7rem, 11vw, 11rem) clamp(1.5rem, 7vw, 7rem); +} + +.mm-close-heading { + align-self: start; + padding-top: 1rem; +} + +.mm-close-heading > span, +.mm-history-heading > span { + color: var(--mm-muted); +} + +.mm-close-heading h2 { + font-size: clamp(2.5rem, 4vw, 4.2rem); +} + +.mm-close-heading h2 code { + font-size: 0.68em; + letter-spacing: -0.04em; +} + +.mm-close-heading > a { + border-color: var(--mm-line); + color: var(--mm-blue); +} + +.mm-result-lab { + border-block: 1px solid var(--mm-ink); + min-width: 0; +} + +.mm-result-controls { + align-items: center; + border-bottom: 1px solid var(--mm-line); + display: flex; + gap: 1rem; + justify-content: space-between; + min-height: 80px; + padding: 0.75rem 0; +} + +.mm-result-controls > span { + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.59rem; + font-weight: 610; + letter-spacing: -0.02em; + max-width: 120px; +} + +.mm-result-controls > fieldset { + border: 0; + display: flex; + flex-wrap: wrap; + gap: 0.32rem; + justify-content: flex-end; + margin: 0; + min-inline-size: 0; + padding: 0; +} + +.mm-result-controls button { + align-items: center; + background: transparent; + border: 1px solid var(--mm-line); + color: var(--mm-muted); + cursor: pointer; + display: inline-flex; + font-size: 0.59rem; + gap: 0.38rem; + min-height: 38px; + padding: 0 0.55rem; + transition: + background 150ms ease, + border-color 150ms ease, + color 150ms ease; +} + +.mm-result-controls button i { + background: var(--mm-line); + border-radius: 50%; + height: 6px; + width: 6px; +} + +.mm-result-controls button[aria-pressed="true"] { + background: var(--mm-ink); + border-color: var(--mm-ink); + color: var(--mm-paper-bright); +} + +.mm-result-controls button[aria-pressed="true"] i { + background: var(--mm-lime); +} + +.mm-result-lab:not([data-reason="INTENTIONAL_HIDE"]):not( + [data-reason="pending"] + ) + .mm-result-controls + button[aria-pressed="true"] + i { + background: var(--mm-coral); +} + +.mm-result-visual { + align-items: center; + display: grid; + grid-template-columns: minmax(160px, 0.78fr) minmax(60px, 0.28fr) minmax( + 250px, + 1.1fr + ); + min-height: 440px; + overflow: hidden; + padding: 3rem 0; + position: relative; +} + +.mm-result-backdrop { + align-items: flex-start; + background: + linear-gradient( + 135deg, + color-mix(in srgb, var(--mm-blue) 10%, transparent), + transparent 62% + ), + repeating-linear-gradient( + 135deg, + var(--mm-dark-line) 0 1px, + transparent 1px 12px + ); + border: 0; + bottom: 3rem; + color: var(--mm-fg-muted); + cursor: pointer; + display: flex; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.52rem; + justify-content: center; + left: 0; + padding-top: 1rem; + position: absolute; + top: 3rem; + width: 29%; +} + +.mm-result-sheet { + align-self: end; + background: var(--mm-paper-bright); + border: 1px solid var(--mm-ink); + border-radius: 28px 28px 0 0; + box-shadow: 0 -18px 50px rgba(39, 33, 26, 0.12); + min-height: 245px; + padding: 1rem 1.1rem 1.2rem; + position: relative; + z-index: 2; +} + +.mm-result-sheet > span { + background: var(--mm-line); + border-radius: 99px; + display: block; + height: 4px; + margin: 0 auto 2.2rem; + width: 36px; +} + +.mm-result-sheet strong { + color: var(--mm-ink); + display: block; + font-size: 1.15rem; + letter-spacing: -0.04em; +} + +.mm-result-sheet p { + color: var(--mm-muted); + font-size: 0.71rem; + line-height: 1.45; + margin-top: 0.5rem; +} + +.mm-result-sheet button { + background: var(--mm-blue); + border: 0; + color: var(--mm-ink); + cursor: pointer; + font-size: 0.68rem; + font-weight: 680; + margin-top: 1.4rem; + min-height: 42px; + width: 100%; +} + +.mm-result-path { + height: 2px; + position: relative; +} + +.mm-result-path span { + background: repeating-linear-gradient( + 90deg, + var(--mm-blue) 0 8px, + transparent 8px 14px + ); + height: 2px; + inset-inline: 0; + position: absolute; +} + +.mm-result-path i { + background: var(--mm-blue); + border: 4px solid var(--mm-paper); + border-radius: 50%; + height: 15px; + position: absolute; + right: -2px; + top: -6px; + width: 15px; +} + +.mm-result-lab:not([data-reason="INTENTIONAL_HIDE"]):not( + [data-reason="pending"] + ) + .mm-result-path + span { + background: repeating-linear-gradient( + 90deg, + var(--mm-coral) 0 8px, + transparent 8px 14px + ); +} + +.mm-result-lab:not([data-reason="INTENTIONAL_HIDE"]):not( + [data-reason="pending"] + ) + .mm-result-path + i { + background: var(--mm-coral); +} + +.mm-result-receipt { + background: var(--mm-ink); + box-shadow: 10px 10px 0 color-mix(in srgb, var(--mm-blue) 85%, transparent); + color: var(--mm-paper-bright); + min-height: 270px; + padding: 1rem; + position: relative; +} + +.mm-result-lab:not([data-reason="INTENTIONAL_HIDE"]):not( + [data-reason="pending"] + ) + .mm-result-receipt { + box-shadow: 10px 10px 0 color-mix(in srgb, var(--mm-coral) 86%, transparent); +} + +.mm-result-receipt > div { + align-items: center; + border-bottom: 1px solid #3b3731; + display: flex; + justify-content: space-between; + padding-bottom: 0.8rem; +} + +.mm-result-receipt > div span, +.mm-result-receipt > div code { + color: #aaa298; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.54rem; +} + +.mm-result-receipt pre { + color: #d9d3c9; + font-size: clamp(0.58rem, 0.76vw, 0.7rem); + line-height: 1.9; + min-height: 140px; + padding-top: 1rem; + white-space: pre-wrap; +} + +.mm-result-receipt pre span { + color: #777168; +} + +.mm-result-receipt pre b { + color: #91a5ff; + font-weight: 520; +} + +.mm-result-receipt pre mark { + background: transparent; + color: var(--mm-lime); + font-weight: 650; +} + +.mm-result-lab:not([data-reason="INTENTIONAL_HIDE"]):not( + [data-reason="pending"] + ) + .mm-result-receipt + pre + mark { + color: #ff987e; +} + +.mm-result-lab[data-reason="pending"] .mm-result-receipt { + box-shadow: 10px 10px 0 + color-mix(in srgb, var(--mm-fg-muted) 34%, transparent); +} + +.mm-result-receipt pre em { + color: #e9adff; + font-style: normal; +} + +.mm-result-receipt > small { + border-top: 1px solid #3b3731; + bottom: 1rem; + color: #928b81; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.52rem; + inset-inline: 1rem; + padding-top: 0.65rem; + position: absolute; +} + +.mm-result-receipt pre { + animation: mm-receipt 280ms ease both; +} + +.mm-history { + border-top: 1px solid var(--mm-line); + margin: 0 auto; + max-width: 1600px; + padding: clamp(7rem, 11vw, 11rem) clamp(1.5rem, 7vw, 7rem); +} + +.mm-history-heading { + display: grid; + gap: 1rem clamp(2rem, 8vw, 8rem); + grid-template-columns: 0.82fr 1.18fr; + margin-bottom: clamp(4rem, 7vw, 7rem); +} + +.mm-history-heading > span { + grid-column: 1 / -1; +} + +.mm-history-heading h2 { + max-width: 720px; +} + +.mm-history-heading p { + align-self: end; + color: var(--mm-muted); + max-width: 490px; +} + +.mm-history-list { + list-style: none; + margin: 0; + padding: 0; +} + +.mm-history-list li { + border-top: 1px solid var(--mm-ink); +} + +.mm-history-list li:last-child { + border-bottom: 1px solid var(--mm-ink); +} + +.mm-history-list a { + align-items: start; + display: grid; + gap: 1rem clamp(1.5rem, 4vw, 4.5rem); + grid-template-columns: 0.45fr 0.44fr 0.92fr 1.45fr 20px; + min-height: 180px; + padding: 2.1rem 0; + transition: + background 180ms ease, + padding 180ms ease; +} + +.mm-history-list a:hover { + background: var(--mm-paper-bright); + padding-inline: 1.2rem; +} + +.mm-history-year { + color: var(--mm-coral); + font-family: "Instrument Serif", serif; + font-size: clamp(3rem, 4.7vw, 4.8rem); + font-style: italic; + line-height: 0.8; +} + +.mm-history-date { + color: var(--mm-muted); + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.58rem; + padding-top: 0.22rem; +} + +.mm-history-list strong { + font-size: clamp(1.2rem, 1.7vw, 1.55rem); + letter-spacing: -0.04em; + line-height: 1.05; +} + +.mm-history-list p { + color: var(--mm-muted); + font-size: 0.84rem; + line-height: 1.6; + max-width: 520px; +} + +.mm-history-list svg { + transition: transform 160ms ease; +} + +.mm-history-list a:hover svg { + transform: translate(3px, -3px); +} + +.mm-final { + background: var(--mm-paper); + color: var(--mm-ink); + min-height: 800px; + overflow: hidden; + padding: clamp(6rem, 10vw, 10rem) max(5vw, calc((100vw - 1500px) / 2)); + position: relative; +} + +.mm-final::before { + border: 1px solid color-mix(in srgb, var(--mm-ink) 35%, transparent); + content: ""; + inset: 2rem; + pointer-events: none; + position: absolute; +} + +.mm-final-copy { + max-width: 720px; + position: relative; + width: 55%; + z-index: 4; +} + +.mm-final-copy h2 { + font-size: clamp(3.4rem, 5.8vw, 5.8rem); + font-weight: 600; + letter-spacing: -0.05em; + line-height: 0.96; + margin-top: clamp(1.5rem, 3vw, 2.8rem); + text-wrap: pretty; +} + +.mm-final-copy h2 em { + color: var(--mm-coral); + font-family: "Instrument Serif", serif; + font-weight: 400; +} + +.mm-final-copy > p { + font-size: clamp(1.05rem, 1.45vw, 1.25rem); + line-height: 1.55; + margin-top: 2.8rem; + max-width: 560px; +} + +.mm-final-copy > div { + align-items: center; + display: flex; + flex-wrap: wrap; + gap: 0.8rem; + margin-top: 2rem; +} + +.mm-final-copy > div > a { + align-items: center; + background: var(--mm-ink); + color: var(--mm-paper-bright); + display: inline-flex; + font-size: 0.8rem; + font-weight: 680; + gap: 0.5rem; + min-height: 48px; + padding: 0 1rem; + transition: + box-shadow 160ms ease, + transform 160ms ease; +} + +.mm-final-copy > div > a:hover { + box-shadow: 7px 7px 0 var(--mm-coral); + transform: translate(-2px, -2px); +} + +.mm-final-device { + background: #171512; + border: 1px solid #5a544a; + border-radius: 58px 58px 0 0; + bottom: -7%; + box-shadow: + 0 34px 70px rgba(20, 18, 15, 0.2), + 12px 14px 0 color-mix(in srgb, var(--mm-blue) 55%, var(--mm-paper)); + height: 82%; + max-width: 500px; + padding: 52px 13px 13px; + position: absolute; + right: 4%; + transform: rotate(1.2deg); + width: 38%; + z-index: 1; +} + +.mm-final-device-bar { + align-items: center; + color: var(--mm-paper); + display: flex; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.55rem; + inset: 0 1.6rem auto; + justify-content: space-between; + min-height: 52px; + position: absolute; +} + +.mm-final-device-bar > i { + background: #050504; + border: 1px solid #38342e; + border-radius: 99px; + height: 16px; + width: 58px; +} + +.mm-final-device-screen { + background: #e2ddd4; + border-radius: 38px 38px 0 0; + height: 100%; + overflow: hidden; + position: relative; +} + +.mm-final-device-screen > header { + align-items: center; + background: var(--mm-paper-bright); + border-bottom: 1px solid #cec7bc; + display: grid; + gap: 0.55rem; + grid-template-columns: auto 1fr auto; + min-height: 62px; + padding: 0 1rem; +} + +.mm-final-device-screen > header strong { + font-size: 0.88rem; +} + +.mm-final-device-screen > header > span { + display: flex; + gap: 3px; +} + +.mm-final-device-screen > header > span i { + background: #776f65; + border-radius: 50%; + height: 3px; + width: 3px; +} + +.mm-final-device-feed { + padding: 1.2rem; +} + +.mm-final-device-feed > i { + background: linear-gradient(135deg, #cbb99b, #9f835b); + display: block; + height: 170px; + margin-bottom: 1rem; +} + +.mm-final-device-feed > span { + background: #c9c3b9; + display: block; + height: 7px; + margin-top: 0.55rem; + width: 86%; +} + +.mm-final-device-feed > span:nth-of-type(2) { + width: 68%; +} + +.mm-final-device-feed > span:nth-of-type(3) { + width: 45%; +} + +.mm-final-device-stack { + bottom: 0; + inset-inline: 0; + position: absolute; +} + +.mm-final-device-stack > i { + background: color-mix(in srgb, var(--mm-paper) 72%, var(--mm-blue)); + border-radius: 28px 28px 0 0; + bottom: 0; + height: 245px; + inset-inline: 1rem; + position: absolute; + transform: translateY(-18px); +} + +.mm-final-device-stack > i:nth-child(2) { + background: color-mix(in srgb, var(--mm-paper) 82%, var(--mm-coral)); + inset-inline: 1.45rem; + transform: translateY(-9px); +} + +.mm-final-device-stack article { + background: var(--mm-paper-bright); + border-radius: 28px 28px 0 0; + color: var(--mm-ink); + min-height: 245px; + padding: 1.6rem; + position: relative; +} + +.mm-final-device-stack article > span { + color: #70695f; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.55rem; + font-weight: 700; + letter-spacing: 0.06em; +} + +.mm-final-device-stack article > strong { + display: block; + font-size: clamp(1.35rem, 2.3vw, 2rem); + letter-spacing: -0.045em; + margin-top: 0.75rem; +} + +.mm-final-device-stack article > small { + color: #70695f; + display: block; + font-size: 0.68rem; + margin-top: 0.35rem; +} + +.mm-final-device-stack article > div { + display: grid; + gap: 0.35rem; + grid-template-columns: repeat(5, 1fr); + margin-top: 1.5rem; +} + +.mm-final-device-stack article b { + align-items: center; + border: 1px solid #cbc4b9; + display: flex; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.7rem; + font-weight: 650; + justify-content: center; + min-height: 44px; +} + +.mm-final-device-stack article b:last-child { + background: var(--mm-coral); + border-color: var(--mm-coral); +} + +.mm-footer { + align-items: start; + background: var(--mm-ink); + border-top: 1px solid var(--mm-dark-line); + color: var(--mm-paper); + display: grid; + gap: 2.5rem clamp(2rem, 5vw, 5rem); + grid-template-columns: + minmax(260px, 1.35fr) minmax(120px, 0.55fr) minmax(120px, 0.55fr) + minmax(150px, 0.55fr); + padding: 4rem max(5vw, calc((100vw - 1500px) / 2)); +} + +.mm-footer .mm-brand code { + display: none; +} + +.mm-footer-intro > p { + color: #928b81; + font-size: 0.78rem; + line-height: 1.6; + margin-top: 1rem; + max-width: 360px; +} + +.mm-footer-intro > a { + align-items: center; + color: var(--mm-lime); + display: inline-flex; + font-size: 0.7rem; + gap: 0.36rem; + margin-top: 1rem; +} + +.mm-footer nav { + display: grid; + font-size: 0.74rem; + gap: 0.72rem; +} + +.mm-footer nav strong { + color: #928b81; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.56rem; + letter-spacing: 0.07em; + margin-bottom: 0.25rem; + text-transform: uppercase; +} + +.mm-footer nav a { + width: max-content; +} + +.mm-footer nav a:hover { + color: var(--mm-lime); +} + +.mm-footer-meta { + align-content: start; + display: grid; + gap: 0.55rem; + justify-items: end; +} + +.mm-footer-meta span { + color: #928b81; + font-family: "JetBrains Mono Variable", monospace; + font-size: 0.58rem; + text-align: right; +} + +/* The landing defaults to a dark canvas. Product demos keep their light modal + surfaces so the portal remains visually separate from the documentation. */ +.mm-nav { + background: color-mix(in srgb, var(--mm-canvas) 94%, transparent); + border-color: var(--mm-dark-line); + color: var(--mm-fg); +} + +.mm-brand code { + border-color: var(--mm-dark-line); + color: #928b81; +} + +.mm-nav nav > a:not(.mm-nav-github)::after { + background: var(--mm-fg); +} + +.mm-nav-github { + border-color: var(--mm-fg); +} + +.mm-nav-github:hover { + background: var(--mm-fg); + color: var(--mm-ink); +} + +.mm-hero::before, +.mm-hero::after { + background: var(--mm-dark-line); +} + +.mm-hero h1, +.mm-request-copy h2, +.mm-close-heading h2, +.mm-history-heading h2 { + color: var(--mm-fg); +} + +.mm-overline, +.mm-hero-lede, +.mm-article-link, +.mm-scroll-cue, +.mm-request-copy aside p, +.mm-examples > header > span, +.mm-examples > header p, +.mm-close-heading > span, +.mm-history-heading > span, +.mm-history-heading p, +.mm-history-list p, +.mm-history-date { + color: var(--mm-fg-muted) !important; +} + +.mm-hero-lede code { + color: var(--mm-fg); + font-size: 0.88em; +} + +.mm-run-link { + background: var(--mm-fg); + color: var(--mm-ink) !important; +} + +.mm-run-link:hover { + background: var(--mm-blue); + color: var(--mm-ink) !important; +} + +.mm-article-link { + border-color: var(--mm-dark-line); +} + +.mm-article-link:hover { + border-color: var(--mm-fg); + color: var(--mm-fg) !important; +} + +.mh-install-command { + border-color: var(--mm-dark-line); + color: var(--mm-fg); +} + +.mh-install-command:hover { + background: var(--mm-surface); + border-color: var(--mm-fg-muted); +} + +.mh-install-prompt { + color: var(--mm-coral); +} + +.mh-install-copy { + border-color: var(--mm-dark-line); + color: var(--mm-fg-muted); +} + +.mm-request { + border-color: var(--mm-dark-line); +} + +.mm-request-copy aside > span { + color: var(--mm-fg-muted); +} + +.mm-request-proof { + border: 1px solid var(--mm-dark-line); +} + +.mm-examples { + background: var(--mm-canvas); + border-color: var(--mm-dark-line); +} + +.mm-examples::before { + color: var(--mm-surface); +} + +.mm-examples > header h2 { + color: var(--mm-fg); +} + +.mm-examples-shell { + border-color: var(--mm-fg-muted); +} + +.mm-example-tabs { + border-color: var(--mm-fg-muted); +} + +.mm-example-tabs button { + border-color: var(--mm-dark-line); + color: var(--mm-fg); +} + +.mm-example-tabs button > span, +.mm-example-tabs button small { + color: var(--mm-fg-muted); +} + +.mm-example-tabs button[aria-selected="true"] { + background: var(--mm-surface); + color: var(--mm-fg); +} + +.mm-close-heading > a { + border-color: var(--mm-dark-line); + color: #8fa1ff; +} + +.mm-result-lab { + border-color: var(--mm-fg-muted); +} + +.mm-result-controls { + border-color: var(--mm-dark-line); +} + +.mm-result-controls button { + border-color: var(--mm-dark-line); + color: var(--mm-fg-muted); +} + +.mm-result-controls button i { + background: var(--mm-fg-muted); +} + +.mm-result-controls button[aria-pressed="true"] { + background: var(--mm-fg); + border-color: var(--mm-fg); + color: var(--mm-ink); +} + +.mm-result-path i { + border-color: var(--mm-canvas); +} + +.mm-history { + border-color: var(--mm-dark-line); +} + +.mm-history-list li, +.mm-history-list li:last-child { + border-color: var(--mm-dark-line); +} + +.mm-history-list a:hover { + background: var(--mm-surface); +} + +.mm-final .mh-install-command { + border-color: color-mix(in srgb, var(--mm-ink) 42%, transparent); + color: var(--mm-ink); +} + +.mm-final .mh-install-command:hover { + background: color-mix(in srgb, white 28%, transparent); + border-color: var(--mm-ink); +} + +.mm-final .mh-install-copy { + border-color: color-mix(in srgb, var(--mm-ink) 32%, transparent); + color: color-mix(in srgb, var(--mm-ink) 68%, transparent); +} + +.mm-final .mh-install-prompt { + color: var(--mm-ink); +} + +@keyframes mm-cue { + 0%, + 100% { + transform: translateY(-2px); + } + 50% { + transform: translateY(3px); + } +} + +@keyframes mm-tether { + to { + background-position: 30px 0; + } +} + +@keyframes mm-receipt { + from { + opacity: 0.35; + transform: translateY(7px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@media (max-width: 1280px) { + .mm-hero { + gap: 2rem; + grid-template-columns: minmax(0, 0.83fr) minmax(520px, 1.17fr); + } + + .mm-hero h1 { + font-size: clamp(3.5rem, 5.4vw, 4.7rem); + } + + .mm-flow-code { + width: 50%; + } + + .mm-native-stage { + width: 64%; + } + + .mm-owner { + gap: 4rem; + grid-template-columns: 0.68fr 1.32fr; + } + + .mm-show, + .mm-close { + gap: 4rem; + } +} + +@media (max-width: 1020px) { + .mm-hero { + grid-template-columns: 1fr; + padding-block: 4.5rem 7rem; + } + + .mm-hero-copy { + max-width: 760px; + padding-inline: clamp(0rem, 5vw, 4rem); + } + + .mm-hero h1 { + font-size: clamp(3.6rem, 7vw, 4.8rem); + max-width: 700px; + } + + .mm-origin-flow { + height: 710px; + margin: 0 auto; + max-width: 780px; + } + + .mm-flow-code { + left: 3%; + width: 48%; + } + + .mm-native-stage { + right: 3%; + width: 57%; + } + + .mm-scroll-cue { + display: none; + } + + .mm-proof { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .mm-proof a:nth-child(2) { + border-right: 0; + } + + .mm-proof a:nth-child(-n + 2) { + border-bottom: 1px solid var(--mm-dark-line); + } + + .mm-request { + grid-template-columns: 1fr; + } + + .mm-request-flags { + grid-column: 2; + max-width: 580px; + width: 100%; + } + + .mm-examples > header { + align-items: start; + gap: 1.5rem; + grid-template-columns: 1fr; + } + + .mm-examples > header > span { + grid-column: auto; + } + + .mm-examples-shell { + grid-template-columns: 1fr; + } + + .mm-example-tabs { + border-bottom: 1px solid var(--mm-fg-muted); + border-right: 0; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: none; + } + + .mm-example-tabs button { + border-bottom: 0; + border-right: 1px solid var(--mm-dark-line); + min-height: 150px; + padding: 1.5rem; + } + + .mm-example-tabs button:last-child { + border-right: 0; + } + + .mm-example-panel { + grid-template-columns: minmax(390px, 1.12fr) minmax(250px, 0.88fr); + } + + .mm-owner, + .mm-close { + grid-template-columns: 1fr; + } + + .mm-owner .mm-section-copy, + .mm-close-heading { + max-width: 700px; + } + + .mm-owner-map { + min-height: 560px; + } + + .mm-show { + grid-template-columns: 1fr; + } + + .mm-show-heading { + max-width: 760px; + } + + .mm-show-object { + max-width: 800px; + width: 100%; + } + + .mm-show-ledger { + grid-column: auto; + } + + .mm-update-note { + max-width: 620px; + } + + .mm-close > div:last-child { + width: 100%; + } + + .mm-history-list a { + grid-template-columns: 0.38fr 0.52fr 1fr 1.35fr 20px; + } +} + +@media (max-width: 700px) { + .magic-home { + overflow-x: hidden; + } + + .mm-nav { + height: 62px; + padding-inline: 1rem; + } + + .mm-brand { + font-size: 0.9rem; + gap: 0.55rem; + } + + .mm-brand code { + display: none; + } + + .mm-nav nav { + gap: 0.4rem; + } + + .mm-nav nav > a { + display: none; + } + + .mm-nav nav > a:nth-child(2), + .mm-nav nav > .mm-nav-github { + display: inline-flex; + } + + .mm-nav-github { + border: 0; + padding: 0 0.45rem; + } + + .mm-nav-github span { + display: none; + } + + .mm-hero { + display: block; + min-height: auto; + padding: 2.4rem 1rem 6.5rem; + } + + .mm-hero::before { + left: 1rem; + } + + .mm-hero::after { + right: 1rem; + } + + .mm-hero-copy { + padding-inline: 0.7rem; + } + + .mm-overline { + font-size: 0.66rem; + margin-bottom: 1.1rem !important; + } + + .mm-hero h1 { + font-size: clamp(2.7rem, 11.5vw, 3.2rem); + letter-spacing: -0.045em; + line-height: 0.98; + max-width: 540px; + } + + .mm-hero-lede { + font-size: 0.9rem; + line-height: 1.5; + margin-top: 1.15rem !important; + max-width: 520px; + } + + .mm-hero-actions { + margin-top: 1.15rem; + } + + .mm-run-link, + .mm-article-link { + min-height: 42px; + } + + .mm-article-link { + font-size: 0.72rem; + } + + .mh-install-command { + max-width: 100%; + min-height: 42px; + width: max-content; + } + + .mh-install-command code { + font-size: 0.61rem; + overflow: hidden; + text-overflow: ellipsis; + } + + .mh-install-copy { + display: none; + } + + .mm-proof { + grid-auto-columns: minmax(205px, 70vw); + grid-auto-flow: column; + grid-template-columns: none; + overflow-x: auto; + overscroll-behavior-inline: contain; + scrollbar-width: none; + scroll-snap-type: x proximity; + } + + .mm-proof::-webkit-scrollbar { + display: none; + } + + .mm-proof a, + .mm-proof a:nth-child(2), + .mm-proof a:nth-child(-n + 2) { + border-bottom: 0; + border-right: 1px solid var(--mm-dark-line); + min-height: 78px; + padding: 1rem 1.2rem; + scroll-snap-align: start; + } + + .mm-proof a:last-child { + border-right: 0; + } + + .mm-origin-flow { + height: 420px; + margin-top: 2rem; + min-height: 0; + } + + .mm-flow-four { + display: none; + } + + .mm-flow-code { + bottom: -44px; + box-shadow: 7px 7px 0 var(--mm-coral); + height: 48px; + left: 1.7rem; + max-width: none; + min-height: 0; + top: auto; + transform: none; + width: calc(100% - 3.4rem); + z-index: 8; + } + + .mm-flow-code::before { + height: 2px; + } + + .mm-flow-code-head, + .mm-flow-code-body, + .mm-flow-promise { + display: none; + } + + .mm-flow-code-live { + align-items: center; + display: flex; + gap: 0.8rem; + height: 48px; + padding: 0 0.85rem; + } + + .mm-flow-code-live span { + color: var(--mm-coral); + font-family: "Instrument Serif", serif; + font-size: 1.25rem; + font-style: italic; + } + + .mm-flow-code-live code { + color: #dcd6cb; + font-size: 0.58rem; + } + + .mm-flow-tether { + display: none; + } + + .mm-native-stage { + box-shadow: + 0 22px 48px rgba(31, 26, 19, 0.2), + -6px 7px 0 var(--mm-blue); + height: 402px; + left: 1.7rem; + min-height: 0; + padding: 36px 10px 38px; + right: auto; + top: 0; + transform: none; + width: calc(100% - 3.4rem); + } + + .mm-stage-meta { + height: 36px; + inset-inline: 10px; + } + + .mm-stage-meta > span { + font-size: 0.55rem; + } + + .mm-stage-meta code { + font-size: 0.5rem; + } + + .mm-stage-footer { + height: 38px; + inset-inline: 10px; + } + + .mm-stage-footer button { + min-height: 34px; + } + + .mm-native-statusbar { + height: 18px; + } + + .mm-native-nav { + height: 38px; + } + + .mm-native-nav button { + height: 30px; + width: 30px; + } + + .mm-feed { + padding: 0.65rem; + } + + .mm-feed-author > i { + height: 24px; + width: 24px; + } + + .mm-feed-image { + height: 95px; + margin-top: 0.5rem; + } + + .mm-feed-copy { + margin-top: 0.5rem; + } + + .mm-native-sheet { + border-radius: 22px 22px 0 0; + min-height: 210px; + padding: 0.65rem 0.85rem 0.8rem; + } + + .mm-sheet-handle { + margin-bottom: 0.48rem; + } + + .mm-sheet-count { + font-size: 0.49rem; + margin-bottom: 0.48rem; + } + + .mm-native-sheet h2 { + font-size: 1.18rem; + } + + .mm-native-sheet > p { + font-size: 0.62rem; + margin-top: 0.24rem; + } + + .mm-rating-options { + gap: 0.25rem; + margin-top: 0.65rem; + } + + .mm-rating-options button { + min-height: 44px; + } + + .mm-rating-sheet > small { + display: none; + } + + .mm-feedback-options { + margin: 0.5rem 0; + } + + .mm-feedback-options button { + min-height: 40px; + } + + .mm-sheet-primary, + .mm-sheet-actions > button { + min-height: 44px; + } + + .mm-store-icon { + height: 32px; + width: 32px; + } + + .mm-sheet-actions { + margin-top: 0.6rem; + } + + .mm-thanks-mark { + height: 36px; + margin-bottom: 0.55rem; + width: 36px; + } + + .mm-thanks-sheet > .mm-sheet-primary { + margin-top: 0.7rem; + } + + .mm-resolved-card { + padding: 0.8rem; + width: calc(100% - 1.4rem); + } + + .mm-resolved-card > strong { + font-size: 0.96rem; + } + + .mm-resolved-card pre { + font-size: 0.55rem; + margin-top: 0.55rem; + padding: 0.45rem; + } + + .mm-resolved-card button { + min-height: 44px; + } + + .mm-request { + display: block; + padding: 6.5rem 1.7rem 7rem; + } + + .mm-request-copy { + margin-top: 0; + } + + .mm-request-copy > p { + font-size: clamp(2rem, 9vw, 2.6rem); + } + + .mm-request-copy aside { + margin-top: 2rem; + } + + .mm-request-flags { + margin: 3.5rem 0.3rem 0; + padding: 1rem; + transform: rotate(0.7deg); + } + + .mm-request-flags code { + font-size: 0.64rem; + min-height: 46px; + } + + .mm-examples { + padding: 6.5rem 1.7rem; + } + + .mm-examples::before { + font-size: 17rem; + top: 4rem; + } + + .mm-examples > header { + display: block; + margin-bottom: 3.5rem; + } + + .mm-examples > header h2 { + font-size: clamp(2.2rem, 9.5vw, 2.8rem); + margin-top: 1.2rem; + } + + .mm-examples > header p { + font-size: 0.94rem; + margin-top: 1.5rem; + } + + .mm-examples-shell { + display: block; + } + + .mm-example-tabs { + display: flex; + overflow-x: auto; + overscroll-behavior-inline: contain; + scrollbar-width: none; + } + + .mm-example-tabs::-webkit-scrollbar { + display: none; + } + + .mm-example-tabs button { + flex: 0 0 225px; + min-height: 142px; + padding: 1.25rem; + } + + .mm-example-panel { + display: grid; + grid-template-columns: 1fr; + min-height: 0; + padding: 2rem 1.5rem 2.5rem; + } + + .mm-example-panel::before { + inset: 0.75rem; + } + + .mm-example-panel::after { + font-size: 17rem; + top: 45%; + } + + .mm-example-code { + transform: rotate(-0.35deg); + } + + .mm-example-code pre { + font-size: 0.64rem; + min-height: 355px; + padding: 1.35rem 1rem; + } + + .mm-example-preview { + justify-self: center; + max-width: 360px; + min-height: 440px; + width: 100%; + } + + .mm-example-preview article { + min-height: 270px; + } + + .mm-owner, + .mm-show, + .mm-close, + .mm-history { + padding: 6.5rem 1.7rem; + } + + .mm-owner { + gap: 4.5rem; + } + + .mm-owner::before { + font-size: 19rem; + top: 5rem; + } + + .mm-section-copy h2, + .mm-show-heading h2, + .mm-close-heading h2, + .mm-history-heading h2 { + font-size: clamp(2.2rem, 9.5vw, 2.8rem); + } + + .mm-section-copy > p, + .mm-show-heading p, + .mm-close-heading p, + .mm-history-heading p { + font-size: 0.94rem; + } + + .mm-owner-map { + min-height: 690px; + padding: 0; + } + + .mm-owner-callers { + left: 0; + top: 0; + width: 44%; + } + + .mm-owner-route { + left: 44%; + top: 19%; + width: 14%; + } + + .mm-owner-route span { + display: none; + } + + .mm-owner-function { + left: 58%; + top: 10%; + width: 42%; + } + + .mm-owner-function strong { + font-size: 0.85rem; + } + + .mm-owner-route.is-portal { + height: 90px; + left: 78%; + top: 32%; + width: 1px; + } + + .mm-owner-route.is-portal i { + background: repeating-linear-gradient( + 180deg, + #777067 0 7px, + transparent 7px 12px + ); + height: 100%; + width: 1px; + } + + .mm-owner-portal { + bottom: 0; + right: 0; + width: 100%; + } + + .mm-owner-portal > div:first-child { + justify-content: center; + } + + .mm-owner-sheet-stack { + height: 300px; + max-width: 290px; + } + + .mm-show { + gap: 4rem; + } + + .mm-show::after, + .mm-final::before { + inset: 1rem; + } + + .mm-show-object { + box-shadow: 9px 9px 0 color-mix(in srgb, var(--mm-coral) 70%, #181613); + min-height: 310px; + padding: 2.4rem 1.3rem; + } + + .mm-show-object pre { + font-size: 0.68rem; + } + + .mm-show-object-tag { + inset-inline: 1.3rem; + } + + .mm-show-ledger > div { + gap: 0.75rem; + grid-template-columns: 1fr; + } + + .mm-show-ledger dt span { + margin-left: auto; + } + + .mm-close { + gap: 3.5rem; + } + + .mm-result-controls { + align-items: flex-start; + flex-direction: column; + padding-block: 1rem; + } + + .mm-result-controls > span { + max-width: none; + } + + .mm-result-controls > fieldset { + justify-content: flex-start; + } + + .mm-result-controls button { + min-height: 44px; + } + + .mm-result-visual { + display: block; + min-height: 620px; + padding-block: 3rem; + } + + .mm-result-sheet { + min-height: 220px; + width: 72%; + } + + .mm-result-path { + height: 90px; + margin-left: 36%; + width: 1px; + } + + .mm-result-path span { + background: repeating-linear-gradient( + 180deg, + var(--mm-blue) 0 8px, + transparent 8px 14px + ); + height: 100%; + width: 2px; + } + + .mm-result-path i { + bottom: -2px; + left: -6px; + right: auto; + top: auto; + } + + .mm-result-lab:not([data-reason="INTENTIONAL_HIDE"]):not( + [data-reason="pending"] + ) + .mm-result-path + span { + background: repeating-linear-gradient( + 180deg, + var(--mm-coral) 0 8px, + transparent 8px 14px + ); + } + + .mm-result-receipt { + margin-left: 10%; + min-height: 255px; + width: 90%; + } + + .mm-history-heading { + display: block; + margin-bottom: 4rem; + } + + .mm-history-heading h2 { + margin-top: 1.2rem; + } + + .mm-history-list a { + gap: 0.8rem 1rem; + grid-template-columns: 0.6fr 1.4fr 20px; + min-height: 0; + padding: 2rem 0; + } + + .mm-history-year { + font-size: 3.2rem; + grid-row: 1 / 3; + } + + .mm-history-date { + grid-column: 2; + } + + .mm-history-list strong { + grid-column: 2; + } + + .mm-history-list p { + grid-column: 1 / -1; + } + + .mm-history-list svg { + grid-column: 3; + grid-row: 1; + } + + .mm-final { + min-height: 0; + padding: 6.5rem 1.7rem; + } + + .mm-final-copy { + max-width: 720px; + width: 100%; + } + + .mm-final-copy h2 { + font-size: clamp(2.8rem, 12vw, 3.5rem); + line-height: 0.98; + } + + .mm-final-copy > p { + font-size: 0.95rem; + } + + .mm-final-copy > div { + align-items: stretch; + flex-direction: column; + max-width: 340px; + } + + .mm-final-copy > div > a, + .mm-final .mh-install-command { + justify-content: center; + width: 100%; + } + + .mm-final-device { + display: none; + } + + .mm-footer { + align-items: start; + display: flex; + flex-direction: column; + padding: 4rem 1.7rem; + } + + .mm-footer nav { + grid-template-columns: repeat(2, 1fr); + width: 100%; + } + + .mm-footer > span { + justify-self: start; + text-align: left; + } +} + +@media (max-width: 420px) { + .mm-hero { + padding-inline: 0.75rem; + } + + .mm-hero::before { + left: 0.75rem; + } + + .mm-hero::after { + right: 0.75rem; + } + + .mm-hero h1 { + font-size: clamp(2.55rem, 11.5vw, 3rem); + } + + .mm-hero-lede { + font-size: 0.84rem; + } + + .mm-article-link { + display: none; + } + + .mm-hero-copy > .mh-install-command { + display: none; + } + + .mm-native-stage, + .mm-flow-code { + left: 1.2rem; + width: calc(100% - 2.4rem); + } + + .mm-result-controls button { + padding-inline: 0.44rem; + } + + .mm-examples { + padding-inline: 1.2rem; + } + + .mm-example-tabs button { + flex-basis: 205px; + } + + .mm-example-panel { + padding-inline: 1rem; + } + + .mm-example-code pre { + font-size: 0.58rem; + } +} + +@media (max-width: 1020px) { + .mm-live > header { + grid-template-columns: 1fr; + } + + .mm-live > header > span { + grid-column: auto; + } + + .mm-live > header p { + align-self: auto; + } + + .mm-package-grid { + grid-template-columns: 1fr; + } + + .mm-package-code { + min-height: 260px; + } + + .mm-show-object { + position: relative; + top: auto; + } + + .mm-final-device { + display: none; + } + + .mm-final-copy { + max-width: 820px; + width: 100%; + } + + .mm-owner-map { + display: grid; + gap: 1rem; + min-height: 0; + padding: 0; + } + + .mm-owner-callers, + .mm-owner-function, + .mm-owner-portal, + .mm-owner-route, + .mm-owner-route.is-portal { + bottom: auto; + height: auto; + left: auto; + position: relative; + right: auto; + top: auto; + width: 100%; + } + + .mm-owner-callers { + border: 1px solid #4d4840; + grid-template-columns: repeat(2, minmax(0, 1fr)); + padding: 0 1rem; + } + + .mm-owner-callers > span { + grid-column: 1 / -1; + padding-top: 1rem; + } + + .mm-owner-callers code { + padding-right: 0.5rem; + } + + .mm-owner-callers code::before { + display: none; + } + + .mm-owner-route, + .mm-owner-route.is-portal { + height: 44px; + margin: 0 auto; + width: 1px; + } + + .mm-owner-route i, + .mm-owner-route.is-portal i { + background: repeating-linear-gradient( + 180deg, + #777067 0 7px, + transparent 7px 12px + ); + height: 100%; + width: 1px; + } + + .mm-owner-route span { + display: none; + } + + .mm-owner-function { + border: 1px solid #4d4840; + padding: 1.4rem; + } + + .mm-owner-portal { + border: 1px solid #4d4840; + padding: 1.4rem; + } + + .mm-owner-portal > div:first-child { + justify-content: flex-start; + } + + .mm-owner-sheet-stack { + height: 245px; + max-width: 360px; + } +} + +@media (max-width: 700px) { + .mm-live { + padding: 6.5rem 1.2rem; + } + + .mm-live::before { + height: 44px; + right: 1.2rem; + width: 44px; + } + + .mm-live > header { + padding-inline: 0.5rem; + } + + .mm-live > header h2 { + font-size: clamp(2.2rem, 9.5vw, 2.85rem); + } + + .mm-live-package { + box-shadow: 7px 7px 0 #26231e; + min-height: 0; + } + + .mm-live-package-content { + min-height: 0; + padding: 1.1rem; + } + + .mm-package-toolbar { + align-items: flex-start; + gap: 0.8rem; + } + + .mm-package-toolbar > div:first-child { + align-items: flex-start; + display: grid; + gap: 0.35rem; + } + + .mm-package-toolbar code { + font-size: 0.54rem; + } + + .mm-package-grid { + gap: 2rem; + padding-block: 2.5rem; + } + + .mm-package-copy h3 { + font-size: clamp(1.95rem, 8.7vw, 2.55rem); + } + + .mm-package-actions { + display: grid; + grid-template-columns: 1fr; + } + + .mm-package-actions button { + width: 100%; + } + + .mm-package-code { + min-height: 240px; + } + + .mm-package-code pre { + font-size: 0.68rem; + padding: 1.5rem 1rem; + } + + .mm-package-runtime { + grid-template-columns: 1fr; + } + + .mm-package-modal { + box-shadow: 5px 5px 0 #26231e; + margin: 0.8rem auto; + max-width: calc(100% - 1.6rem); + } + + .mm-package-modal-actions { + grid-template-columns: 1fr; + } + + .mm-package-list > div { + gap: 0.55rem; + } + + .mm-package-list > div > b { + font-size: 0.52rem; + } + + .mm-request-copy h2 { + font-size: clamp(2.15rem, 9.2vw, 2.75rem); + } + + .mm-request-copy > p { + font-size: 0.96rem; + line-height: 1.6; + } + + .mm-request-branches { + grid-template-columns: 1fr; + } + + .mm-request-branches article + article { + border-left: 1px solid var(--mm-dark-line); + border-top: 0; + } + + .mm-request-proof { + margin: 3.25rem 0.3rem 0; + transform: rotate(0.35deg); + } + + .mm-example-code pre { + max-height: 260px; + min-height: 0; + } + + .mm-example-preview { + min-height: 400px; + } + + .mm-result-backdrop { + bottom: auto; + height: 220px; + top: 3rem; + width: 72%; + } + + .mm-footer { + display: grid; + gap: 2.75rem 1.5rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); + padding: 3.75rem 1.7rem; + } + + .mm-footer-intro, + .mm-footer-meta { + grid-column: 1 / -1; + } + + .mm-footer-meta { + border-top: 1px solid var(--mm-dark-line); + grid-auto-flow: column; + justify-content: space-between; + justify-items: start; + padding-top: 1.25rem; + } + + .mm-footer-meta span { + text-align: left; + } +} + +@media (prefers-reduced-motion: reduce) { + .magic-home { + scroll-behavior: auto; + } + + .magic-home *, + .magic-home *::before, + .magic-home *::after { + animation-duration: 0.001ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: 0.001ms !important; + } + + .mm-flow-code { + transform: rotate(-1.8deg); + } + + .mm-native-stage { + transform: none; + } +} + +@media (forced-colors: active) { + .mm-flow-code, + .mm-native-stage, + .mm-request-flags, + .mm-show-object, + .mm-result-receipt { + border: 2px solid CanvasText; + box-shadow: none; + } + + .mm-flow-tether span, + .mm-result-path span { + background: CanvasText; + } +} diff --git a/apps/docs/app/(home)/layout.tsx b/apps/docs/app/(home)/layout.tsx index 815e01fe..3943dc9e 100644 --- a/apps/docs/app/(home)/layout.tsx +++ b/apps/docs/app/(home)/layout.tsx @@ -1,7 +1,19 @@ -import { HomeLayout } from "fumadocs-ui/layouts/home"; +import type { Metadata } from "next"; -import { baseOptions } from "@/lib/layout"; +import "@fontsource-variable/instrument-sans"; +import "@fontsource-variable/jetbrains-mono"; + +import "@fontsource/instrument-serif/400-italic.css"; +import "./home.css"; + +export const metadata: Metadata = { + title: { + absolute: "Awaitable React Native modals with Magic Modal", + }, + description: + "Open a React Native modal from any async flow, await its typed result, and keep concurrent prompts in one ordered stack.", +}; export default function Layout({ children }: LayoutProps<"/">) { - return {children}; + return children; } diff --git a/apps/docs/app/(home)/page.tsx b/apps/docs/app/(home)/page.tsx index 56d091dd..03a09318 100644 --- a/apps/docs/app/(home)/page.tsx +++ b/apps/docs/app/(home)/page.tsx @@ -1,121 +1,574 @@ import Link from "next/link"; import { + ArrowDown, ArrowRight, - Braces, - Layers3, - MoveDown, - PackageCheck, - Sparkles, + ArrowUpRight, + BookOpen, + Code2, } from "lucide-react"; +import { HomeEffects } from "@/components/home-effects"; +import { InstallCommand } from "@/components/install-command"; import { LegacyAnchorRouter } from "@/components/legacy-anchor-router"; +import { LivePackageDemo } from "@/components/live-package-demo"; import { MagicMark } from "@/components/magic-mark"; -import { ModalPlayground } from "@/components/modal-playground"; +import { OriginFlow } from "@/components/origin-flow"; +import { PracticalExamples } from "@/components/practical-examples"; +import { + ProjectAge, + ProjectLicense, + ProjectStars, + ProjectVersion, + ProjectWeeklyDownloads, +} from "@/components/project-metadata-values"; +import { ResultLab } from "@/components/result-lab"; -const features = [ +const history = [ { + href: "https://github.com/GSTJ/react-native-magic-modal/commit/64612c8", + isoDate: "2022-02-21T00:00:00.000Z", + title: "First release.", description: - "Open a modal from navigation, services, effects, or any plain function.", - icon: Sparkles, - title: "Show from anywhere", + "show() moved visibility state out of the screen and returned a value when the modal closed.", }, { + href: "https://github.com/GSTJ/react-native-magic-modal/pull/81", + isoDate: "2024-06-08T00:00:00.000Z", + title: "Stacks and typed close reasons.", description: - "Await a discriminated, generic result instead of coordinating callback soup.", - icon: Braces, - title: "Typed async flows", + "Each entry gained its own ID and promise. This release also added hideAll(), swipe gestures, and the iOS full-window overlay.", }, { + href: "https://github.com/GSTJ/react-native-magic-modal/releases/tag/magic-modal-9.0.0", + isoDate: "2026-07-27T00:00:00.000Z", + title: "In-place updates and Gesture Handler 3.", description: - "Multiple modals coexist in a predictable stack with individual IDs.", - icon: Layers3, - title: "Real modal stacks", - }, - { - description: - "Directional gestures, animation timings, backdrops, and native overlays.", - icon: MoveDown, - title: "Native-feeling motion", + "update() can replace an open modal in place. Swipe dismissal now supports Gesture Handler 2 and 3.", }, ] as const; +const ratingCallers = [ + "post.tsx", + "comment.tsx", + "product.tsx", + "notifications.ts", +] as const; + +const repeatedRatingState = [ + "isRatingOpen", + "isFeedbackOpen", + "isStoreReviewOpen", + "isThanksOpen", +] as const; + +const formatMilestone = (isoDate: string) => + new Intl.DateTimeFormat("en-US", { + day: "2-digit", + month: "short", + timeZone: "UTC", + year: "numeric", + }) + .format(new Date(isoDate)) + .toUpperCase(); + export default function HomePage() { return ( -
+
-
-
-
-
-
- - React Native · Expo · TypeScript + + +
+ + + Magic Modal + + + + + +
+ +
+
+

+ + A modal stack for React Native +

+

React Native modals you can await

+

+ Mount one MagicModalPortal at the app root. Call{" "} + show() from any async flow and await the typed result. +

+ -
- + +
+ + + + + Why it exists + +
+ + + +
+
+ WHY IT EXISTS +

Two callers can open a modal at the same time

+

+ The first Magic Modal flow asked for an app rating after a like. It + could start from several screens and branch to feedback or the + store. Both paths ended with a thank-you. +

+
+
+ SCREEN OWNED + + {repeatedRatingState.length} pieces of visibility state + +

+ Every screen that can start the flow repeats the modal tree and + its cleanup. +

+
+
+ PORTAL OWNED + {ratingCallers.length} callers share one function +

+ The flow lives outside the screens. Each caller awaits the same + sequence. +

+
-

- Modal flows, -
- without the ceremony. -

+ +
+
+
+ IDLE + 0 modal bodies mounted +

+ The portal stays empty until show() runs. +

+
+
+ OPEN + One ID and promise per entry +

Concurrent prompts keep their own place in the stack.

+
+
+ CODE + + {ratingCallers.length} callers share{" "} + startRatingFlow() + +

+ The shared flow owns the modal components and visibility state. +

+
+ + See the portal tests + +
+
+ +
+
+ LIVE PACKAGE +

Test two calls in one stack

- Show a modal imperatively from anywhere, await its result, and keep - the entire flow type-safe. + Start the rating flow and stack a notification over it. Close the + top entry and the first promise is still waiting underneath.

-
- - Get started - - - - Explore the API - +
+
+ +
+
+ + + +
+
+ APP ROOT +

Mount one portal at the app root

+

+ Modal calls can come from anywhere in the app. Each{" "} + show() gets an ID and promise, so one flow cannot + overwrite the modal that was already open. +

+ + See the flow pattern +
+ +
+
+ CALLERS + {ratingCallers.map((caller) => ( + {caller} + ))} +
+ +
+ FLOW + startRatingFlow() + rating(); branch(); thanks(); +
+ -
- $ - pnpm add react-native-magic-modal +
+
+ + + APP ROOT + MagicModalPortal + +
+
+
+ +
+
+ RETURN VALUE +

+ What magicModal.show() returns +

+

+ Await the close result, target this stack entry by ID, or replace + its component while the same promise stays pending. +

+
-
- +
+
+            
+              {"{"}
+              {"\n  "}
+              promise
+              {": Promise>;"}
+              {"\n  "}
+              modalID: string;
+              {"\n  "}
+              update
+              {": (next: React.FC) => void;"}
+              {"\n"}
+              {"}"}
+            
+          
+ AVAILABLE IMMEDIATELY
+ +
+
+
+ promise + 01 +
+
Resolves when this entry closes.
+
+
+
+ modalID + 02 +
+
Targets this entry from another call site.
+
+
+
+ update + 03 +
+
+ Replaces the component while its ID, backdrop, stack position, and + pending promise stay put. +
+
+
+ +

+ UPDATE REMOUNTS + update() starts a fresh component mount. Local React + state resets. +

-
-
- Small API, serious flows -

The modal becomes a function you can await.

+
+
+ CLOSE RESULTS +

+ What HideReturn<T> records +

- No global state choreography. No prop drilling. No hand-rolled - promise resolver hidden in every feature. + A submitted answer, backdrop tap, swipe, Android back press, and{" "} + hideAll() resolve differently. Only{" "} + hide(data) returns a payload.

+ + Read HideReturn<T> +
-
- {features.map(({ description, icon: Icon, title }) => ( -
-
- -
-

{title}

-

{description}

-
- ))} +
+
-
-
- Ready in five minutes -

Put one portal at the root. Call it from everywhere else.

+
+
+ RELEASE HISTORY +

How the API changed

+

+ Apps needed stacked modals, targeted updates, typed close reasons, + native overlays, and support for newer gesture APIs. +

+
+ +
    + {history.map(({ description, href, isoDate, title }) => { + const year = new Date(isoDate).getUTCFullYear(); + return ( +
  1. + + {year} + + {formatMilestone(isoDate)} + + {title} +

    {description}

    +
    +
  2. + ); + })} +
+
+ +
+ +
+ GET STARTED +

Add MagicModalPortal to your app root

+

+ The setup guide mounts the app-root portal and shows how to await a + typed HideReturn<T>. +

+
+ + Set up the portal +
- - Set up the portal - -
+ +
); } diff --git a/apps/docs/app/global.css b/apps/docs/app/global.css index 657406d0..91ee4603 100644 --- a/apps/docs/app/global.css +++ b/apps/docs/app/global.css @@ -39,10 +39,6 @@ border-color: var(--color-fd-border); } -html { - scroll-behavior: smooth; -} - body { font-family: "Avenir Next", diff --git a/apps/docs/app/icon.svg b/apps/docs/app/icon.svg index 9cdc350c..aba18359 100644 --- a/apps/docs/app/icon.svg +++ b/apps/docs/app/icon.svg @@ -1,12 +1,10 @@ - - - - - - - - - - + + + + + + + + diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx index 18cce423..b27e004b 100644 --- a/apps/docs/app/layout.tsx +++ b/apps/docs/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { createMagicDocsMetadata } from "magic-docs"; +import { ProjectMetadataBoundary } from "@/components/project-metadata-boundary"; import { Provider } from "@/components/provider"; import { publicPaths, site } from "@/lib/site"; @@ -26,7 +27,9 @@ export default function RootLayout({ children }: LayoutProps<"/">) { return ( - {children} + + {children} + ); diff --git a/apps/docs/app/not-found.tsx b/apps/docs/app/not-found.tsx index c6953874..f2548514 100644 --- a/apps/docs/app/not-found.tsx +++ b/apps/docs/app/not-found.tsx @@ -8,11 +8,10 @@ export default function NotFound() { return (
- 404 · spell fizzled -

This page escaped the modal stack.

+ 404 +

Page not found

- The API probably moved. Search the new reference or head back to the - docs. + This URL may have moved. Search the reference or return to the docs.

diff --git a/apps/docs/components/home-effects.tsx b/apps/docs/components/home-effects.tsx new file mode 100644 index 00000000..52baf7e3 --- /dev/null +++ b/apps/docs/components/home-effects.tsx @@ -0,0 +1,136 @@ +"use client"; + +import { useLayoutEffect } from "react"; + +import { gsap } from "gsap"; +import { ScrollTrigger } from "gsap/ScrollTrigger"; + +export const HomeEffects = () => { + useLayoutEffect(() => { + const root = document.querySelector("[data-magic-home]"); + if (!root) return; + + gsap.registerPlugin(ScrollTrigger); + + const many = (selector: string) => [ + ...root.querySelectorAll(selector), + ]; + const revealTargets = many("[data-reveal]"); + const heroTargets = many( + ".mm-overline, .mm-hero h1, .mm-hero-lede, .mm-hero-actions, .mm-hero-copy > .mh-install-command", + ); + const media = gsap.matchMedia(); + + const context = gsap.context(() => { + media.add( + "(prefers-reduced-motion: reduce)", + () => { + gsap.set([...heroTargets, ...revealTargets], { + clearProps: + "opacity,transform,visibility,filter,clipPath,willChange", + }); + }, + root, + ); + + media.add( + "(min-width: 701px) and (prefers-reduced-motion: no-preference)", + () => { + gsap + .timeline({ defaults: { ease: "power3.out" } }) + .from(heroTargets, { + autoAlpha: 0, + duration: 0.7, + stagger: 0.07, + y: 18, + }) + .from( + ".mm-flow-four", + { + autoAlpha: 0, + duration: 0.9, + rotation: -6, + scale: 0.92, + }, + 0.06, + ) + .from( + ".mm-flow-code", + { + autoAlpha: 0, + duration: 0.82, + ease: "expo.out", + x: -24, + }, + 0.16, + ) + .from( + ".mm-native-stage", + { + autoAlpha: 0, + duration: 0.9, + ease: "expo.out", + x: 24, + y: 16, + }, + 0.22, + ); + }, + root, + ); + + media.add( + "(min-width: 701px) and (prefers-reduced-motion: no-preference)", + () => { + gsap.set(revealTargets, { + opacity: 0, + y: 24, + }); + + ScrollTrigger.batch(revealTargets, { + interval: 0.08, + once: true, + onEnter: (targets) => { + gsap.to(targets, { + duration: 0.66, + ease: "power3.out", + opacity: 1, + stagger: 0.06, + y: 0, + }); + }, + start: "top 88%", + }); + }, + root, + ); + + media.add( + "(max-width: 700px)", + () => { + gsap.set( + [ + ...heroTargets, + ...revealTargets, + ".mm-flow-four", + ".mm-flow-code", + ".mm-native-stage", + ], + { + clearProps: + "opacity,transform,visibility,filter,clipPath,willChange", + }, + ); + }, + root, + ); + }, root); + + return () => { + media.revert(); + context.revert(); + }; + }, []); + + return null; +}; diff --git a/apps/docs/components/install-command.tsx b/apps/docs/components/install-command.tsx new file mode 100644 index 00000000..9c2b89ab --- /dev/null +++ b/apps/docs/components/install-command.tsx @@ -0,0 +1,81 @@ +"use client"; + +import { useCallback, useRef, useState } from "react"; + +import { Check, Copy } from "lucide-react"; + +const command = "pnpm add react-native-magic-modal"; +type CopyState = "copied" | "failed" | "idle"; + +const fallbackCopy = () => { + const input = document.createElement("textarea"); + input.value = command; + input.setAttribute("readonly", ""); + input.style.position = "fixed"; + input.style.opacity = "0"; + try { + document.body.append(input); + input.select(); + return document.execCommand("copy"); + } catch { + return false; + } finally { + input.remove(); + } +}; + +export const InstallCommand = ({ compact = false }: { compact?: boolean }) => { + const [copyState, setCopyState] = useState("idle"); + const resetTimer = useRef | undefined>( + undefined, + ); + + const copy = useCallback(async () => { + try { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(command); + } else if (!fallbackCopy()) { + throw new Error("Copy command was rejected"); + } + setCopyState("copied"); + } catch { + setCopyState(fallbackCopy() ? "copied" : "failed"); + } + clearTimeout(resetTimer.current); + resetTimer.current = setTimeout(() => setCopyState("idle"), 1800); + }, []); + + let copyFeedback = ( + <> +