damus.io

damus.io website
git clone git://jb55.com/damus.io
Log | Files | Refs | README | LICENSE

commit e5b824c0b089b16034bef76ea0cc472ca3ba46c9
parent 5c5daf6d0273c616287986ab86d11c9de6087bfb
Author: Daniel D’Aquino <daniel@daquino.me>
Date:   Sat, 30 Nov 2024 15:44:00 +0900

Merge pull request #35 from danieldaquino/notedeck#418

Notedeck#418 Notedeck landing page and install instructions
Diffstat:
Mcontent/compiled-locales/en.json | 450++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mcontent/locales/en.json | 228+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mpackage-lock.json | 1288+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mpackage.json | 1+
Apublic/logo_icon_2.png | 0
Apublic/notedeck/notedeck-hero-2.png | 0
Apublic/notedeck/npub_example.png | 0
Apublic/notedeck/share-feedback.png | 0
Ashell.nix | 5+++++
Asrc/components/NotedeckInstallLayout.tsx | 46++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/pages/home.tsx | 2++
Dsrc/components/pages/notedeck.tsx | 21---------------------
Asrc/components/pages/notedeck/index.tsx | 48++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/pages/notedeck/install.tsx | 19+++++++++++++++++++
Msrc/components/sections/Benefits.tsx | 9++++-----
Msrc/components/sections/Hero.tsx | 10+++++-----
Asrc/components/sections/Notedeck/Benefits.tsx | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/sections/Notedeck/InstallInstructions.tsx | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/sections/Notedeck/ItemSection.tsx | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/sections/Notedeck/NotIncluded.tsx | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/sections/Notedeck/NotedeckFAQ.tsx | 110+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/sections/Notedeck/NotedeckHero.tsx | 197+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Asrc/components/sections/Notedeck/NotedeckWaitlistHero.tsx | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/sections/Notedeck/Recap.tsx | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/sections/Notedeck/ShareFeedback.tsx | 93+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/components/sections/Notedeck/WhatIsNotedeck.tsx | 221+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/components/sections/PurpleAccount.tsx | 20+++++++++++++++-----
Msrc/components/sections/PurpleHero.tsx | 36++++++++++++++++++------------------
Msrc/components/sections/TopMenu.tsx | 111+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Asrc/components/ui/MarkdownView.tsx | 23+++++++++++++++++++++++
Asrc/pages/notedeck/install.tsx | 42++++++++++++++++++++++++++++++++++++++++++
31 files changed, 3168 insertions(+), 225 deletions(-)

diff --git a/content/compiled-locales/en.json b/content/compiled-locales/en.json @@ -8,7 +8,7 @@ "benefits.benefit1.description": [ { "type": 0, - "value": "Built on open internet protocols, there is no platform that can ban or censor you. You are in control of your data & speech." + "value": "Built on open internet protocols, there is no platform that can ban or censor you. You are in control of your data & speech" } ], "benefits.benefit1.name": [ @@ -32,7 +32,7 @@ "benefits.benefit3.description": [ { "type": 0, - "value": "Traditional social media keeps you addicted in order to sell you ads, which is intoxicating society. Damus doesn't. The result? A healthier community." + "value": "Traditional social media keeps you addicted in order to sell you ads, which is intoxicating society. Damus doesn't. The result? A healthier community" } ], "benefits.benefit3.name": [ @@ -44,7 +44,7 @@ "benefits.benefit4.description": [ { "type": 0, - "value": "Tip your friends, earn tips, support creators, and stack sats with Bitcoin & Lightning, the Free and open internet money." + "value": "Tip your friends, earn tips, support creators, and stack sats with Bitcoin & Lightning, the Free and open internet money" } ], "benefits.benefit4.name": [ @@ -229,6 +229,114 @@ "value": "Tokyo, Japan" } ], + "faq.buy.purple": [ + { + "type": 0, + "value": "How can I buy purple if I am not a purple member?" + } + ], + "faq.buy.purple.a": [ + { + "type": 0, + "value": "[https://damus.io/purple](https://damus.io/purple)" + } + ], + "faq.day.one": [ + { + "type": 0, + "value": "What works on day one?" + } + ], + "faq.day.one.a": [ + { + "type": 0, + "value": "- Login with public key (npub)\n- Login with private key (nsec)\n- Multiple accounts\n- Add profile columns\n- Add notifications columns\n- Add hashtag columns\n- Create post\n- Reply\n- Quote note\n- Log out`" + } + ], + "faq.need.purple": [ + { + "type": 0, + "value": "Do I need purple to get notedeck on day one?" + } + ], + "faq.need.purple.a": [ + { + "type": 0, + "value": "Yes!" + } + ], + "faq.not.work.day.one": [ + { + "type": 0, + "value": "Important stuff that doesn't work on day one" + } + ], + "faq.not.work.day.one.a": [ + { + "type": 0, + "value": "- Auto suggest tagging\n- Zaps\n- DMs\n- Reposts\n- Likes/reactions\n- Embedded video\n- Upload media\n- Modifying profile\n- Modifying relay list\n- See followers/following count\n- Search, and many more" + } + ], + "faq.purple.effect": [ + { + "type": 0, + "value": "How does my purple membership affect notedeck?" + } + ], + "faq.purple.effect.a": [ + { + "type": 0, + "value": "You get access to notedeck before anyone else!" + } + ], + "faq.start": [ + { + "type": 0, + "value": "How do I get started (private key, public key)?" + } + ], + "faq.start.a": [ + { + "type": 0, + "value": "**From iOS Damus:** `Go to side menu` -> `Tap on profile` -> Tap on the *Copy button* adjacent to npub field\n\n**Any public key:** Go to [nostr.band](https://nostr.band) in your browser, and search for a nostr user of interest (e.g. Terry Yiu) to find their npub (public key). Tap on npub to copy it.\n\n![Npub example](/notedeck/npub_example.png)\n\n**Private key:** From iOS Damus -> go to side menu -> settings -> keys -> copy \"secret account login key\" and use this to log in to notedeck." + } + ], + "faq.support.signer.extension": [ + { + "type": 0, + "value": "Does notedeck support signer extension?" + } + ], + "faq.support.signer.extension.a": [ + { + "type": 0, + "value": "No. Notedeck is a desktop app where key storage is local so a signer extension isn't required. Trust in third party apps is eliminated by using the desktop app Notedeck." + } + ], + "faq.supported.os": [ + { + "type": 0, + "value": "Supported operating systems" + } + ], + "faq.supported.os.a": [ + { + "type": 0, + "value": "**On day one:**\n\n- Linux\n- Ubuntu\n- Fedora\n- MacOS\n- Windows\n\n**In the future:**\n- Android\n- iOS\n- iPadOS" + } + ], + "faq.trouble.feedback": [ + { + "type": 0, + "value": "Having trouble, or have feedback?" + } + ], + "faq.trouble.feedback.a": [ + { + "type": 0, + "value": "Tag damus on nostr, or send an email to [support@damus.io](mailto:support@damus.io)." + } + ], "final_cta.headline": [ { "type": 0, @@ -293,12 +401,180 @@ "value": "shortName" } ], + "notedeck.benefits.benefit1.description": [ + { + "type": 0, + "value": "Access your account directly with your private key for enhanced security and privacy" + } + ], + "notedeck.benefits.benefit1.name": [ + { + "type": 0, + "value": "Login with Private Key (nsec)" + } + ], + "notedeck.benefits.benefit2.description": [ + { + "type": 0, + "value": "Login with any public key for a read-only view of any nostr account" + } + ], + "notedeck.benefits.benefit2.name": [ + { + "type": 0, + "value": "Login with Public Key (npub)" + } + ], + "notedeck.benefits.benefit3.description": [ + { + "type": 0, + "value": "Easily manage multiple accounts. Great for quickly switching between work and personal accounts" + } + ], + "notedeck.benefits.benefit3.name": [ + { + "type": 0, + "value": "Multiple Accounts" + } + ], + "notedeck.benefits.benefit4.description": [ + { + "type": 0, + "value": "Add columns to view profiles, hashtags, and notifications at a glance" + } + ], + "notedeck.benefits.benefit4.name": [ + { + "type": 0, + "value": "Profile, Notification, and Hashtag columns" + } + ], + "notedeck.benefits.benefit5.description": [ + { + "type": 0, + "value": "Create posts, reply to others, and quote notes to engage with the nostr community" + } + ], + "notedeck.benefits.benefit5.name": [ + { + "type": 0, + "value": "Create and Interact with Posts" + } + ], + "notedeck.benefits.benefit6.description": [ + { + "type": 0, + "value": "Easily log out of your account whenever needed for privacy" + } + ], + "notedeck.benefits.benefit6.name": [ + { + "type": 0, + "value": "Log Out Securely" + } + ], + "notedeck.benefits.description": [ + { + "type": 0, + "value": "These are currently the features you will receive immediately when you subscribe to Damus Purple" + } + ], + "notedeck.benefits.headline": [ + { + "type": 0, + "value": "What’s included in Notedeck Alpha with your Damus Purple subscription?" + } + ], + "notedeck.feature.availability.description": [ + { + "type": 0, + "value": "Nostriches can also switch between multiple accounts quickly and easily, drastically improving personal and business use cases" + } + ], + "notedeck.feature.availability.name": [ + { + "type": 0, + "value": "Switch Between Accounts" + } + ], + "notedeck.feature.customization.description": [ + { + "type": 0, + "value": "Add profile, hashtag, and notification columns of any nostr public or private key. Which means you can see the nostr landscape through other peoples' eyes" + } + ], + "notedeck.feature.customization.name": [ + { + "type": 0, + "value": "Powerful Views" + } + ], + "notedeck.feature.speed.description": [ + { + "type": 0, + "value": "The fastest nostr client. Built from the ground up with an ultra-fast database made exclusively for nostr, leveraging several state-of-the-art performance techniques not available on web clients" + } + ], + "notedeck.feature.speed.name": [ + { + "type": 0, + "value": "Speed" + } + ], + "notedeck.feedback.headline": [ + { + "type": 0, + "value": "Share your feedback with us" + } + ], + "notedeck.feedback.share-feedback": [ + { + "type": 0, + "value": "Send Feedback" + } + ], + "notedeck.feedback.subheadline": [ + { + "type": 0, + "value": "We encourage you to send feedback to our Damus npub over nostr or Github to we can take it in consideration in our Roadmap" + } + ], + "notedeck.feedback.view_github": [ + { + "type": 0, + "value": "View our GitHub" + } + ], + "notedeck.hero.announcement": [ + { + "type": 0, + "value": "Notedeck Alpha now available on Purple" + } + ], "notedeck.hero.description": [ { "type": 0, "value": "Damus. Everywhere. The fastest native, customizable nostr experience, for all platforms." } ], + "notedeck.hero.headline": [ + { + "type": 0, + "value": "Damus. Everywhere. The fastest native, customizable nostr experience, for all platforms." + } + ], + "notedeck.hero.install": [ + { + "type": 0, + "value": "Install now" + } + ], + "notedeck.hero.learn-more": [ + { + "type": 0, + "value": "Learn more" + } + ], "notedeck.hero.menu.signup-for-the-waitlist": [ { "type": 0, @@ -317,6 +593,144 @@ "value": "Notedeck is a powerful, performant Nostr client for Linux, Windows, and macOS" } ], + "notedeck.not-included.description": [ + { + "type": 0, + "value": "We are working very hard to get these features out as soon as possible. If you want to collaborate please check out our [GitHub](https://github.com/damus-io/notedeck/issues)" + } + ], + "notedeck.not-included.feature1.badge": [ + { + "type": 0, + "value": "Coming 2025 Q1" + } + ], + "notedeck.not-included.feature1.description": [ + { + "type": 0, + "value": "Tip creators and friends on nostr, or get tips for posting content" + } + ], + "notedeck.not-included.feature1.name": [ + { + "type": 0, + "value": "Zaps" + } + ], + "notedeck.not-included.feature2.badge": [ + { + "type": 0, + "value": "Coming 2025 Q1" + } + ], + "notedeck.not-included.feature2.description": [ + { + "type": 0, + "value": "Send direct messages to friends" + } + ], + "notedeck.not-included.feature2.name": [ + { + "type": 0, + "value": "DMs" + } + ], + "notedeck.not-included.feature3.badge": [ + { + "type": 0, + "value": "Coming 2025 Q1" + } + ], + "notedeck.not-included.feature3.description": [ + { + "type": 0, + "value": "View videos directly from your feed" + } + ], + "notedeck.not-included.feature3.name": [ + { + "type": 0, + "value": "Embedded video" + } + ], + "notedeck.not-included.feature4.badge": [ + { + "type": 0, + "value": "Coming 2025 Q1" + } + ], + "notedeck.not-included.feature4.description": [ + { + "type": 0, + "value": "React to posts from your friends" + } + ], + "notedeck.not-included.feature4.name": [ + { + "type": 0, + "value": "Likes/reactions/reposts" + } + ], + "notedeck.not-included.feature5.badge": [ + { + "type": 0, + "value": "Coming 2025 Q1" + } + ], + "notedeck.not-included.feature5.description": [ + { + "type": 0, + "value": "Easily search for content on your feed or around the nostr network, and get suggestions when tagging users" + } + ], + "notedeck.not-included.feature5.name": [ + { + "type": 0, + "value": "Search" + } + ], + "notedeck.not-included.feature6.badge": [ + { + "type": 0, + "value": "Coming 2025 Q1" + } + ], + "notedeck.not-included.feature6.description": [ + { + "type": 0, + "value": "Attach images or videos to your posts" + } + ], + "notedeck.not-included.feature6.name": [ + { + "type": 0, + "value": "Upload media" + } + ], + "notedeck.not-included.headline": [ + { + "type": 0, + "value": "What's NOT included so far, but we are building right now" + } + ], + "notedeck.recap.description": [ + { + "type": 0, + "value": "Damus Notedeck is a lightning fast native app that allows you to explore the nostr social network in a completely new way, with several power features. **Try it today** with our [Purple](/purple) subscription" + } + ], + "notedeck.recap.headline": [ + { + "type": 0, + "value": "Let’s recap" + } + ], + "notedeck.recap.try_it_today": [ + { + "type": 0, + "value": "Install now" + } + ], "notedeck.waitlist.headline": [ { "type": 0, @@ -329,6 +743,30 @@ "value": "Sign up for our waitlist and be the first to know when notedeck is ready" } ], + "notedeck.what-is.description1": [ + { + "type": 0, + "value": "**Nostr is the future of social media** and Notedeck is our latest innovation towards the **future of nostr**.\n\n" + } + ], + "notedeck.what-is.description2": [ + { + "type": 0, + "value": "A **lightning fast native app** that allows you to explore the nostr social network in a **completely new way**! Notedeck includes several power features to both personal and business use cases, including:" + } + ], + "notedeck.what-is.description3": [ + { + "type": 0, + "value": "This isn't a web app and it's not just \"another\" social client. This is nostr **like you've never seen it before!**\n\nNotedeck works on **Linux**, **macOS**, and **Windows** on day one. An Android version will be added in 2025, along with iOS and iPadOS eventually.\n\nFuture nostr apps **will run on Notedeck in 2025.**\n\nAn early Notedeck Alpha version is available **today** to our paid [Purple](/purple) subscribers." + } + ], + "notedeck.what-is.headline": [ + { + "type": 0, + "value": "Introducing Notedeck" + } + ], "purple-checkout.account-will-renew": [ { "type": 0, @@ -389,6 +827,12 @@ "value": "Via Nostr" } ], + "purple.account.notedeck-install-link": [ + { + "type": 0, + "value": "Try Notedeck" + } + ], "purple.account.sign-out": [ { "type": 0, diff --git a/content/locales/en.json b/content/locales/en.json @@ -3,7 +3,7 @@ "string": "Banned in China" }, "benefits.benefit1.description": { - "string": "Built on open internet protocols, there is no platform that can ban or censor you. You are in control of your data & speech." + "string": "Built on open internet protocols, there is no platform that can ban or censor you. You are in control of your data & speech" }, "benefits.benefit1.name": { "string": "You are in control" @@ -15,13 +15,13 @@ "string": "No registration required" }, "benefits.benefit3.description": { - "string": "Traditional social media keeps you addicted in order to sell you ads, which is intoxicating society. Damus doesn't. The result? A healthier community." + "string": "Traditional social media keeps you addicted in order to sell you ads, which is intoxicating society. Damus doesn't. The result? A healthier community" }, "benefits.benefit3.name": { "string": "No addictive algorithms" }, "benefits.benefit4.description": { - "string": "Tip your friends, earn tips, support creators, and stack sats with Bitcoin & Lightning, the Free and open internet money." + "string": "Tip your friends, earn tips, support creators, and stack sats with Bitcoin & Lightning, the Free and open internet money" }, "benefits.benefit4.name": { "string": "Free speech meets Free money" @@ -110,6 +110,60 @@ "events.location.tokyo_japan": { "string": "Tokyo, Japan" }, + "faq.buy.purple": { + "string": "How can I buy purple if I am not a purple member?" + }, + "faq.buy.purple.a": { + "string": "[https://damus.io/purple](https://damus.io/purple)" + }, + "faq.day.one": { + "string": "What works on day one?" + }, + "faq.day.one.a": { + "string": "- Login with public key (npub)\n- Login with private key (nsec)\n- Multiple accounts\n- Add profile columns\n- Add notifications columns\n- Add hashtag columns\n- Create post\n- Reply\n- Quote note\n- Log out`" + }, + "faq.need.purple": { + "string": "Do I need purple to get notedeck on day one?" + }, + "faq.need.purple.a": { + "string": "Yes!" + }, + "faq.not.work.day.one": { + "string": "Important stuff that doesn't work on day one" + }, + "faq.not.work.day.one.a": { + "string": "- Auto suggest tagging\n- Zaps\n- DMs\n- Reposts\n- Likes/reactions\n- Embedded video\n- Upload media\n- Modifying profile\n- Modifying relay list\n- See followers/following count\n- Search, and many more" + }, + "faq.purple.effect": { + "string": "How does my purple membership affect notedeck?" + }, + "faq.purple.effect.a": { + "string": "You get access to notedeck before anyone else!" + }, + "faq.start": { + "string": "How do I get started (private key, public key)?" + }, + "faq.start.a": { + "string": "**From iOS Damus:** `Go to side menu` -> `Tap on profile` -> Tap on the *Copy button* adjacent to npub field\n\n**Any public key:** Go to [nostr.band](https://nostr.band) in your browser, and search for a nostr user of interest (e.g. Terry Yiu) to find their npub (public key). Tap on npub to copy it.\n\n![Npub example](/notedeck/npub_example.png)\n\n**Private key:** From iOS Damus -> go to side menu -> settings -> keys -> copy \"secret account login key\" and use this to log in to notedeck." + }, + "faq.support.signer.extension": { + "string": "Does notedeck support signer extension?" + }, + "faq.support.signer.extension.a": { + "string": "No. Notedeck is a desktop app where key storage is local so a signer extension isn't required. Trust in third party apps is eliminated by using the desktop app Notedeck." + }, + "faq.supported.os": { + "string": "Supported operating systems" + }, + "faq.supported.os.a": { + "string": "**On day one:**\n\n- Linux\n- Ubuntu\n- Fedora\n- MacOS\n- Windows\n\n**In the future:**\n- Android\n- iOS\n- iPadOS" + }, + "faq.trouble.feedback": { + "string": "Having trouble, or have feedback?" + }, + "faq.trouble.feedback.a": { + "string": "Tag damus on nostr, or send an email to [support@damus.io](mailto:support@damus.io)." + }, "final_cta.headline": { "string": "Download Damus today!" }, @@ -140,9 +194,93 @@ "meet_the_team.view_profile": { "string": "Follow {shortName}" }, + "notedeck.benefits.benefit1.description": { + "string": "Access your account directly with your private key for enhanced security and privacy" + }, + "notedeck.benefits.benefit1.name": { + "string": "Login with Private Key (nsec)" + }, + "notedeck.benefits.benefit2.description": { + "string": "Login with any public key for a read-only view of any nostr account" + }, + "notedeck.benefits.benefit2.name": { + "string": "Login with Public Key (npub)" + }, + "notedeck.benefits.benefit3.description": { + "string": "Easily manage multiple accounts. Great for quickly switching between work and personal accounts" + }, + "notedeck.benefits.benefit3.name": { + "string": "Multiple Accounts" + }, + "notedeck.benefits.benefit4.description": { + "string": "Add columns to view profiles, hashtags, and notifications at a glance" + }, + "notedeck.benefits.benefit4.name": { + "string": "Profile, Notification, and Hashtag columns" + }, + "notedeck.benefits.benefit5.description": { + "string": "Create posts, reply to others, and quote notes to engage with the nostr community" + }, + "notedeck.benefits.benefit5.name": { + "string": "Create and Interact with Posts" + }, + "notedeck.benefits.benefit6.description": { + "string": "Easily log out of your account whenever needed for privacy" + }, + "notedeck.benefits.benefit6.name": { + "string": "Log Out Securely" + }, + "notedeck.benefits.description": { + "string": "These are currently the features you will receive immediately when you subscribe to Damus Purple" + }, + "notedeck.benefits.headline": { + "string": "What’s included in Notedeck Alpha with your Damus Purple subscription?" + }, + "notedeck.feature.availability.description": { + "string": "Nostriches can also switch between multiple accounts quickly and easily, drastically improving personal and business use cases" + }, + "notedeck.feature.availability.name": { + "string": "Switch Between Accounts" + }, + "notedeck.feature.customization.description": { + "string": "Add profile, hashtag, and notification columns of any nostr public or private key. Which means you can see the nostr landscape through other peoples' eyes" + }, + "notedeck.feature.customization.name": { + "string": "Powerful Views" + }, + "notedeck.feature.speed.description": { + "string": "The fastest nostr client. Built from the ground up with an ultra-fast database made exclusively for nostr, leveraging several state-of-the-art performance techniques not available on web clients" + }, + "notedeck.feature.speed.name": { + "string": "Speed" + }, + "notedeck.feedback.headline": { + "string": "Share your feedback with us" + }, + "notedeck.feedback.share-feedback": { + "string": "Send Feedback" + }, + "notedeck.feedback.subheadline": { + "string": "We encourage you to send feedback to our Damus npub over nostr or Github to we can take it in consideration in our Roadmap" + }, + "notedeck.feedback.view_github": { + "string": "View our GitHub" + }, + "notedeck.hero.announcement": { + "string": "Notedeck Alpha now available on Purple" + }, "notedeck.hero.description": { "string": "Damus. Everywhere. The fastest native, customizable nostr experience, for all platforms." }, + "notedeck.hero.headline": { + "string": "Damus. Everywhere. The fastest native, customizable nostr experience, for all platforms." + }, + "notedeck.hero.install": { + "string": "Install now" + }, + "notedeck.hero.learn-more": { + "string": "Learn more" + }, "notedeck.hero.menu.signup-for-the-waitlist": { "string": "Sign up" }, @@ -152,12 +290,93 @@ "notedeck.meta_description": { "string": "Notedeck is a powerful, performant Nostr client for Linux, Windows, and macOS" }, + "notedeck.not-included.description": { + "string": "We are working very hard to get these features out as soon as possible. If you want to collaborate please check out our [GitHub](https://github.com/damus-io/notedeck/issues)" + }, + "notedeck.not-included.feature1.badge": { + "string": "Coming 2025 Q1" + }, + "notedeck.not-included.feature1.description": { + "string": "Tip creators and friends on nostr, or get tips for posting content" + }, + "notedeck.not-included.feature1.name": { + "string": "Zaps" + }, + "notedeck.not-included.feature2.badge": { + "string": "Coming 2025 Q1" + }, + "notedeck.not-included.feature2.description": { + "string": "Send direct messages to friends" + }, + "notedeck.not-included.feature2.name": { + "string": "DMs" + }, + "notedeck.not-included.feature3.badge": { + "string": "Coming 2025 Q1" + }, + "notedeck.not-included.feature3.description": { + "string": "View videos directly from your feed" + }, + "notedeck.not-included.feature3.name": { + "string": "Embedded video" + }, + "notedeck.not-included.feature4.badge": { + "string": "Coming 2025 Q1" + }, + "notedeck.not-included.feature4.description": { + "string": "React to posts from your friends" + }, + "notedeck.not-included.feature4.name": { + "string": "Likes/reactions/reposts" + }, + "notedeck.not-included.feature5.badge": { + "string": "Coming 2025 Q1" + }, + "notedeck.not-included.feature5.description": { + "string": "Easily search for content on your feed or around the nostr network, and get suggestions when tagging users" + }, + "notedeck.not-included.feature5.name": { + "string": "Search" + }, + "notedeck.not-included.feature6.badge": { + "string": "Coming 2025 Q1" + }, + "notedeck.not-included.feature6.description": { + "string": "Attach images or videos to your posts" + }, + "notedeck.not-included.feature6.name": { + "string": "Upload media" + }, + "notedeck.not-included.headline": { + "string": "What's NOT included so far, but we are building right now" + }, + "notedeck.recap.description": { + "string": "Damus Notedeck is a lightning fast native app that allows you to explore the nostr social network in a completely new way, with several power features. **Try it today** with our [Purple](/purple) subscription" + }, + "notedeck.recap.headline": { + "string": "Let’s recap" + }, + "notedeck.recap.try_it_today": { + "string": "Install now" + }, "notedeck.waitlist.headline": { "string": "Coming soon!" }, "notedeck.waitlist.subheadline": { "string": "Sign up for our waitlist and be the first to know when notedeck is ready" }, + "notedeck.what-is.description1": { + "string": "**Nostr is the future of social media** and Notedeck is our latest innovation towards the **future of nostr**.\n\n" + }, + "notedeck.what-is.description2": { + "string": "A **lightning fast native app** that allows you to explore the nostr social network in a **completely new way**! Notedeck includes several power features to both personal and business use cases, including:" + }, + "notedeck.what-is.description3": { + "string": "This isn't a web app and it's not just \"another\" social client. This is nostr **like you've never seen it before!**\n\nNotedeck works on **Linux**, **macOS**, and **Windows** on day one. An Android version will be added in 2025, along with iOS and iPadOS eventually.\n\nFuture nostr apps **will run on Notedeck in 2025.**\n\nAn early Notedeck Alpha version is available **today** to our paid [Purple](/purple) subscribers." + }, + "notedeck.what-is.headline": { + "string": "Introducing Notedeck" + }, "purple-checkout.account-will-renew": { "string": "Paying will renew or extend your membership." }, @@ -188,6 +407,9 @@ "purple.account.nostr-support": { "string": "Via Nostr" }, + "purple.account.notedeck-install-link": { + "string": "Try Notedeck" + }, "purple.account.sign-out": { "string": "Sign out" }, diff --git a/package-lock.json b/package-lock.json @@ -26,6 +26,7 @@ "react": "^18", "react-dom": "^18", "react-intl": "^6.4.7", + "react-markdown": "^9.0.1", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", "usehooks-ts": "^2.9.2" @@ -1602,6 +1603,39 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.3.tgz", @@ -1617,6 +1651,21 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.8.6", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.6.tgz", @@ -1655,6 +1704,12 @@ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.4.tgz", "integrity": "sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==" }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@typescript-eslint/parser": { "version": "6.7.5", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.7.5.tgz", @@ -1757,6 +1812,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "license": "ISC" + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -2078,6 +2139,16 @@ "dequal": "^2.0.3" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2245,6 +2316,16 @@ } ] }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2261,6 +2342,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -2340,6 +2461,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2393,7 +2524,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2406,6 +2536,19 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2447,7 +2590,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -2457,6 +2599,19 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -3061,6 +3216,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3070,6 +3235,12 @@ "node": ">=0.10.0" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3517,6 +3688,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -3525,6 +3736,16 @@ "react-is": "^16.7.0" } }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -3592,6 +3813,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/input-otp": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.2.3.tgz", @@ -3634,6 +3861,30 @@ "loose-envify": "^1.0.0" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -3740,6 +3991,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -3786,6 +4047,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-map": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", @@ -3839,6 +4110,18 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -4133,6 +4416,16 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4164,88 +4457,682 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "engines": { - "node": ">= 8" + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=8.6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "license": "MIT", "dependencies": { - "brace-expansion": "^1.1.7" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" }, - "engines": { - "node": "*" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/mitata": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/mitata/-/mitata-0.1.6.tgz", - "integrity": "sha512-VKQ0r3jriTOU9E2Z+mwbZrUmbg4Li4QyFfi7kfHKl6reZhGzL0AYlu3wE0VPXzIwA5xnFzmEQoBwCcNT8stUkA==" - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.3.tgz", + "integrity": "sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mitata": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/mitata/-/mitata-0.1.6.tgz", + "integrity": "sha512-VKQ0r3jriTOU9E2Z+mwbZrUmbg4Li4QyFfi7kfHKl6reZhGzL0AYlu3wE0VPXzIwA5xnFzmEQoBwCcNT8stUkA==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "node_modules/next": { @@ -4536,6 +5423,32 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4752,6 +5665,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -4842,6 +5765,32 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", @@ -4970,6 +5919,39 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -5179,6 +6161,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -5252,6 +6244,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5285,6 +6291,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -5468,6 +6483,26 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", @@ -5625,6 +6660,93 @@ "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", "dev": true }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -5723,6 +6845,34 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -5876,6 +7026,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json @@ -33,6 +33,7 @@ "react": "^18", "react-dom": "^18", "react-intl": "^6.4.7", + "react-markdown": "^9.0.1", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", "usehooks-ts": "^2.9.2" diff --git a/public/logo_icon_2.png b/public/logo_icon_2.png Binary files differ. diff --git a/public/notedeck/notedeck-hero-2.png b/public/notedeck/notedeck-hero-2.png Binary files differ. diff --git a/public/notedeck/npub_example.png b/public/notedeck/npub_example.png Binary files differ. diff --git a/public/notedeck/share-feedback.png b/public/notedeck/share-feedback.png Binary files differ. diff --git a/shell.nix b/shell.nix @@ -0,0 +1,5 @@ +{ pkgs ? import <nixpkgs> {} }: + pkgs.mkShell { + # nativeBuildInputs is usually what you want -- tools you need to run + nativeBuildInputs = with pkgs.buildPackages; [ nodejs_20 ]; +} diff --git a/src/components/NotedeckInstallLayout.tsx b/src/components/NotedeckInstallLayout.tsx @@ -0,0 +1,46 @@ +import { motion, useScroll, useTransform } from "framer-motion"; +import { RoundedContainerWithGradientBorder } from "./ui/RoundedContainerWithGradientBorder"; +import { PurpleIcon } from "./icons/PurpleIcon"; +import { MeshGradient5 } from "./effects/MeshGradient.5"; +import Image from "next/image"; +import Link from "next/link"; +import { Button } from "./ui/Button"; +import { ArrowLeft, LifeBuoy } from "lucide-react"; +import { useIntl } from "react-intl"; +import { cn } from "@/lib/utils"; +import { Onest } from "next/font/google"; +import StarField from "./effects/StarField"; +import { MeshGradient1 } from "@/components/effects/MeshGradient.1"; +import { useRef } from "react"; +import { TopMenu } from "./sections/TopMenu"; +import { MeshGradient2 } from "./effects/MeshGradient.2"; +import { MeshGradient3 } from "./effects/MeshGradient.3"; + +const onest = Onest({ subsets: ['latin'] }) + + +export function NotedeckInstallLayout({ children }: { children: React.ReactNode }) { + const intl = useIntl() + const ref = useRef(null) + const { scrollYProgress } = useScroll({ + target: ref, + offset: ["start start", "end start"] + }) + const mobileScreenshotOffsetX = useTransform(scrollYProgress, [0, 1], [0, -1200]); + const bgOpacity = useTransform(scrollYProgress, [0.3, 1], [1.0, 0.0]); + + return ( + <div + className="bg-black overflow-hidden relative min-h-screen" + > + <TopMenu/> + <motion.div className="absolute z-0 w-full h-full pointer-events-none" style={{ opacity: bgOpacity }}> + <MeshGradient3 className="-translate-x-1/3" /> + </motion.div> + + <div className="container z-10 mx-auto max-w-2xl px-6 pt-6 h-full flex flex-col justify-start mt-36"> + {children} + </div> + </div> + ) +} diff --git a/src/components/pages/home.tsx b/src/components/pages/home.tsx @@ -13,6 +13,7 @@ import { Contribute } from '@/components/sections/Contribute'; import { FinalCTA } from '@/components/sections/FinalCTA'; import { Benefits } from '@/components/sections/Benefits'; import { PurpleBanner } from '../sections/PurpleBanner'; +import { NotedeckHero } from '../sections/Notedeck/NotedeckHero'; const inter = Inter({ subsets: ['latin'] }) @@ -28,6 +29,7 @@ export function Home() { <main style={{ scrollBehavior: "smooth" }}> <Hero/> <Benefits/> + <NotedeckHero introducing={true} /> <PurpleBanner/> <BannedInChina/> <div id="events"> diff --git a/src/components/pages/notedeck.tsx b/src/components/pages/notedeck.tsx @@ -1,21 +0,0 @@ -import Head from "next/head"; -import { useIntl } from 'react-intl' -import { Footer } from '@/components/sections/Footer'; -import { NotedeckHero } from '../sections/Notedeck/NotedeckHero'; -import { NotedeckWaitlistForm } from "../sections/Notedeck/WaitlistForm"; - -export function Notedeck() { - const intl = useIntl() - - return (<> - <Head> - <title>Notedeck</title> - <meta name="description" content={intl.formatMessage({ id: "notedeck.meta_description", defaultMessage: "Notedeck is a powerful, performant Nostr client for Linux, Windows, and macOS" })} /> - </Head> - <main style={{ scrollBehavior: "smooth" }}> - <NotedeckHero/> - <NotedeckWaitlistForm /> - <Footer /> - </main> - </>) -} diff --git a/src/components/pages/notedeck/index.tsx b/src/components/pages/notedeck/index.tsx @@ -0,0 +1,48 @@ +import Head from "next/head"; +import { useIntl } from 'react-intl' +import { Footer } from '@/components/sections/Footer'; +import { NotedeckHero } from '@/components/sections/Notedeck/NotedeckHero'; +import { NotedeckWaitlistForm } from "@/components/sections/Notedeck/WaitlistForm"; +import { Benefits } from "@/components/sections/Notedeck/Benefits"; +import { NotIncluded } from "@/components/sections/Notedeck/NotIncluded"; +import { ShareFeedback } from "@/components/sections/Notedeck/ShareFeedback"; +import { NotedeckFAQ } from "@/components/sections/Notedeck/NotedeckFAQ"; +import { WhatIsNotedeck } from "@/components/sections/Notedeck/WhatIsNotedeck"; +import { Recap } from "@/components/sections/Notedeck/Recap"; +import { motion } from "framer-motion"; +import { TopMenu } from "@/components/sections/TopMenu"; +import { InstallNowButton } from "@/components/sections/Notedeck/NotedeckHero"; + +export function Notedeck() { + const intl = useIntl() + + return (<> + <Head> + <title>Notedeck</title> + <meta name="description" content={intl.formatMessage({ id: "notedeck.meta_description", defaultMessage: "Notedeck is a powerful, performant Nostr client for Linux, Windows, and macOS" })} /> + </Head> + <main style={{ scrollBehavior: "smooth" }}> + <motion.div + className="w-full" + style={{ opacity: 0 }} + animate={{ opacity: 1, transition: { delay: 1.5, duration: 1 } }} + > + <TopMenu + className="w-full z-10" + customCTA={ + <InstallNowButton className="hidden lg:block"/> + } + hideLogoOnTop={true} + /> + </motion.div> + <NotedeckHero/> + <WhatIsNotedeck /> + <Benefits /> + <NotIncluded /> + <NotedeckFAQ /> + <ShareFeedback /> + <Recap /> + <Footer /> + </main> + </>) +} diff --git a/src/components/pages/notedeck/install.tsx b/src/components/pages/notedeck/install.tsx @@ -0,0 +1,19 @@ +import Head from "next/head"; +import { useIntl } from 'react-intl' +import { Footer } from '@/components/sections/Footer'; +import { InstallInstructions } from "@/components/sections/Notedeck/InstallInstructions"; + +export function NotedeckInstallPage() { + const intl = useIntl() + + return (<> + <Head> + <title>Install Notedeck</title> + <meta name="description" content={intl.formatMessage({ id: "notedeck.meta_description", defaultMessage: "Notedeck is a powerful, performant Nostr client for Linux, Windows, and macOS" })} /> + </Head> + <main style={{ scrollBehavior: "smooth" }}> + <InstallInstructions/> + <Footer /> + </main> + </>) +} diff --git a/src/components/sections/Benefits.tsx b/src/components/sections/Benefits.tsx @@ -17,7 +17,7 @@ export function Benefits({ className }: { className?: string }) { { icon: <Joystick className="h-12 w-12 text-white opacity-80"/>, headline: intl.formatMessage({ id: "benefits.benefit1.name", defaultMessage: "You are in control" }), - description: intl.formatMessage({ id: "benefits.benefit1.description", defaultMessage: "Built on open internet protocols, there is no platform that can ban or censor you. You are in control of your data & speech." }), + description: intl.formatMessage({ id: "benefits.benefit1.description", defaultMessage: "Built on open internet protocols, there is no platform that can ban or censor you. You are in control of your data & speech" }), }, { icon: <KeyRound className="text-white h-12 w-12 opacity-80"/>, @@ -27,12 +27,12 @@ export function Benefits({ className }: { className?: string }) { { icon: <Scale className="h-12 w-12 text-white opacity-80"/>, headline: intl.formatMessage({ id: "benefits.benefit3.name", defaultMessage: "No addictive algorithms" }), - description: intl.formatMessage({ id: "benefits.benefit3.description", defaultMessage: "Traditional social media keeps you addicted in order to sell you ads, which is intoxicating society. Damus doesn't. The result? A healthier community." }), + description: intl.formatMessage({ id: "benefits.benefit3.description", defaultMessage: "Traditional social media keeps you addicted in order to sell you ads, which is intoxicating society. Damus doesn't. The result? A healthier community" }), }, { icon: <Wallet className="h-12 w-12 text-white opacity-80"/>, headline: intl.formatMessage({ id: "benefits.benefit4.name", defaultMessage: "Free speech meets Free money" }), - description: intl.formatMessage({ id: "benefits.benefit4.description", defaultMessage: "Tip your friends, earn tips, support creators, and stack sats with Bitcoin & Lightning, the Free and open internet money." }), + description: intl.formatMessage({ id: "benefits.benefit4.description", defaultMessage: "Tip your friends, earn tips, support creators, and stack sats with Bitcoin & Lightning, the Free and open internet money" }), } ] @@ -73,4 +73,4 @@ export function Benefits({ className }: { className?: string }) { </div> </div> </>) -}- \ No newline at end of file +} diff --git a/src/components/sections/Hero.tsx b/src/components/sections/Hero.tsx @@ -17,8 +17,8 @@ export function Hero() { <div className="absolute z-10 pointer-events-none"> <MeshGradient1 className="-translate-x-1/3"/> </div> + <TopMenu className="w-full"/> <div className="container mx-auto px-6 pb-32 pt-12"> - <TopMenu className="w-full"/> <div className="flex flex-col lg:flex-row items-center justify-center mt-32 lg:mt-16"> <div className="w-full z-20 mb-12"> <Link href="/purple"> @@ -33,13 +33,13 @@ export function Hero() { <ChevronRight className="ml-2"/> </motion.div> </Link> - <motion.h1 + <motion.h1 className="my-6 text-5xl sm:text-5xl md:text-7xl text-transparent bg-clip-text pb-6 font-semibold whitespace-pre-line max-w-2xl" - style={{ + style={{ backgroundImage: "linear-gradient(to right, #ffffff -100%, #ffffff -40%, #2D175B 100%)", opacity: 0 }} - animate={{ + animate={{ backgroundImage: "linear-gradient(to right, #ffffff 0%, #ffffff 40%, #2D175B 100%)", transition: { duration: 3 }, opacity: 1 @@ -97,7 +97,7 @@ export function Hero() { style={{ opacity: 0 }} animate={{ opacity: 1, transition: { delay: 1, duration: 1 } }} > - <Image + <Image src="/Hero.webp" className="object-contain" sizes="(max-width: 800px) 100vw, 800px" diff --git a/src/components/sections/Notedeck/Benefits.tsx b/src/components/sections/Notedeck/Benefits.tsx @@ -0,0 +1,55 @@ +import { Onest } from 'next/font/google' +import { FormattedMessage, useIntl } from "react-intl"; +import { motion } from "framer-motion"; +import { RoundedContainerWithGradientBorder } from "@/components/ui/RoundedContainerWithGradientBorder"; +import { cn } from "@/lib/utils"; +import { Lock, User, Layers, Bell, FileText, LogOut } from "lucide-react"; +import { MeshGradient3 } from "@/components/effects/MeshGradient.3"; +import { ItemSection } from './ItemSection'; + +const onest = Onest({ subsets: ['latin'] }) + +export function Benefits({ className }: { className?: string }) { + const intl = useIntl() + + const benefits = [ + { + icon: <Lock className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.benefits.benefit1.name", defaultMessage: "Login with Private Key (nsec)" }), + description: intl.formatMessage({ id: "notedeck.benefits.benefit1.description", defaultMessage: "Access your account directly with your private key for enhanced security and privacy" }), + }, + { + icon: <User className="text-white h-12 w-12 opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.benefits.benefit2.name", defaultMessage: "Login with Public Key (npub)" }), + description: intl.formatMessage({ id: "notedeck.benefits.benefit2.description", defaultMessage: "Login with any public key for a read-only view of any nostr account" }), + }, + { + icon: <Layers className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.benefits.benefit3.name", defaultMessage: "Multiple Accounts" }), + description: intl.formatMessage({ id: "notedeck.benefits.benefit3.description", defaultMessage: "Easily manage multiple accounts. Great for quickly switching between work and personal accounts" }), + }, + { + icon: <Bell className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.benefits.benefit4.name", defaultMessage: "Profile, Notification, and Hashtag columns" }), + description: intl.formatMessage({ id: "notedeck.benefits.benefit4.description", defaultMessage: "Add columns to view profiles, hashtags, and notifications at a glance" }), + }, + { + icon: <FileText className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.benefits.benefit5.name", defaultMessage: "Create and Interact with Posts" }), + description: intl.formatMessage({ id: "notedeck.benefits.benefit5.description", defaultMessage: "Create posts, reply to others, and quote notes to engage with the nostr community" }), + }, + { + icon: <LogOut className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.benefits.benefit6.name", defaultMessage: "Log Out Securely" }), + description: intl.formatMessage({ id: "notedeck.benefits.benefit6.description", defaultMessage: "Easily log out of your account whenever needed for privacy" }), + }, + ] + + return (<> + <ItemSection + heading={intl.formatMessage({ id: "notedeck.benefits.headline", defaultMessage: "What’s included in Notedeck Alpha with your Damus Purple subscription?" })} + subHeading={intl.formatMessage({ id: "notedeck.benefits.description", defaultMessage: "These are currently the features you will receive immediately when you subscribe to Damus Purple" })} + items={benefits} + /> + </>) +} diff --git a/src/components/sections/Notedeck/InstallInstructions.tsx b/src/components/sections/Notedeck/InstallInstructions.tsx @@ -0,0 +1,54 @@ +import { useIntl } from "react-intl"; +import { useEffect, useState } from "react"; +import { ErrorDialog } from "../../ErrorDialog"; +import { usePurpleLoginSession } from "@/hooks/usePurpleLoginSession"; +import { useLocalStorage } from "usehooks-ts"; +import { NotedeckInstallLayout } from "@/components/NotedeckInstallLayout"; +import { MarkdownView } from "@/components/ui/MarkdownView"; + +export function InstallInstructions() { + const intl = useIntl() + const [error, setError] = useState<string | null>(null) + const { accountInfo: loggedInAccountInfo, logout } = usePurpleLoginSession(setError) + const [sessionToken, setSessionToken] = useLocalStorage('session_token', null); + const [installInstructions, setInstallInstructions] = useState<string | undefined | null>(undefined) + + // MARK: - Functions + + const fetchInstallInstructions = async () => { + const response = await fetch(process.env.NEXT_PUBLIC_PURPLE_API_BASE_URL + "/notedeck-install-instructions", { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer ' + sessionToken + }, + }); + + if (!response.ok) { + setError("Failed to get Notedeck install instructions from our servers. Please wait a few minutes and refresh the page. If the problem persists, please contact support."); + return; + } + + const installInstructions = await response.json(); + setInstallInstructions(installInstructions.value); + } + + useEffect(() => { + if (loggedInAccountInfo) { + fetchInstallInstructions(); + } + else if (loggedInAccountInfo === null) { + // Redirect to the login page + window.location.href = "/purple/login?redirect=" + encodeURIComponent("/notedeck/install") + } + }, [loggedInAccountInfo]) + + // MARK: - Render + + return (<> + <ErrorDialog error={error} setError={setError} /> + <NotedeckInstallLayout> + <MarkdownView className="text-purple-200">{installInstructions}</MarkdownView> + </NotedeckInstallLayout> + </>) +} diff --git a/src/components/sections/Notedeck/ItemSection.tsx b/src/components/sections/Notedeck/ItemSection.tsx @@ -0,0 +1,83 @@ +import { Onest } from 'next/font/google' +import { FormattedMessage, useIntl } from "react-intl"; +import { motion } from "framer-motion"; +import { RoundedContainerWithGradientBorder } from "@/components/ui/RoundedContainerWithGradientBorder"; +import { cn } from "@/lib/utils"; +import { Lock, User, Layers, Bell, FileText, LogOut } from "lucide-react"; +import { MeshGradient3 } from "@/components/effects/MeshGradient.3"; +import { MarkdownView } from '@/components/ui/MarkdownView'; +import React from 'react'; + +const onest = Onest({ subsets: ['latin'] }) + +export interface ItemSectionProps { + className?: string, + subHeadingClassName?: string, + heading: string, + subHeading: string, + items: Item[], + customChild?: React.ReactNode +} + +export interface Item { + icon: React.ReactNode + headline: string + description: string + badgeClassName?: string + badgeText?: string +} + +export function ItemSection({ className, subHeadingClassName, heading, subHeading, items, customChild }: ItemSectionProps) { + const intl = useIntl() + + return (<> + <div className={cn("bg-black overflow-hidden relative", className)}> + <div className="container mx-auto px-6 pb-32 pt-20"> + <div className="flex flex-col items-center justify-center mt-32 lg:mt-16"> + <div className="relative mb-16 flex flex-col items-center"> + <motion.h2 + className={cn("mt-6 text-3xl sm:text-4xl md:text-5xl text-center text-transparent bg-clip-text pb-6 font-semibold whitespace-pre-line max-w-4xl tracking-wide leading-relaxed", onest.className)} + style={{ + backgroundImage: "linear-gradient(to right, #ffffff -100%, #ffffff -40%, #2D175B 100%)", + opacity: 0, + }} + animate={{ + backgroundImage: "linear-gradient(to right, #ffffff 0%, #ffffff 40%, #2D175B 100%)", + transition: { duration: 3 }, + opacity: 1 + }} + > + {heading} + </motion.h2> + <motion.div className={cn("text-white/60 text-xl text-center max-w-2xl mb-4 mt-6 break-keep", subHeadingClassName)}> + <MarkdownView>{subHeading}</MarkdownView> + </motion.div> + </div> + {(intl.locale != "ja" || process.env.FORCE_LOAD_ALL_JA_SECTIONS) && ( + <div className="flex flex-wrap gap-x-8 gap-y-16 items-stretch justify-center"> + {items.map((item, index) => ( + <div key={index} className="w-72 flex flex-col items-start justify-start"> + <div className="flex justify-between w-full items-start"> + {item.icon} + {item.badgeText && ( + <div className={cn("py-1 px-2 bg-orange-500/30 text-orange-200 rounded-lg", item.badgeClassName)}> + {item.badgeText} + </div> + )} + </div> + <h3 className="text-xl font-semibold text-transparent bg-clip-text bg-gradient-to-br text-white text-left text-normal mt-6"> + {item.headline} + </h3> + <p className="text-white/80 text-left text-normal mt-4"> + {item.description} + </p> + </div> + ))} + </div> + )} + </div> + {customChild} + </div> + </div> + </>) +} diff --git a/src/components/sections/Notedeck/NotIncluded.tsx b/src/components/sections/Notedeck/NotIncluded.tsx @@ -0,0 +1,62 @@ +import { Onest } from 'next/font/google' +import { FormattedMessage, useIntl } from "react-intl"; +import { motion } from "framer-motion"; +import { RoundedContainerWithGradientBorder } from "@/components/ui/RoundedContainerWithGradientBorder"; +import { cn } from "@/lib/utils"; +import { Film, Joystick, KeyRound, Mail, Scale, Search, ThumbsUp, Upload, Wallet, Zap } from "lucide-react"; +import { MeshGradient3 } from "@/components/effects/MeshGradient.3"; +import { MarkdownView } from '@/components/ui/MarkdownView'; +import { ItemSection } from './ItemSection'; + +const onest = Onest({ subsets: ['latin'] }) + +export function NotIncluded({ className }: { className?: string }) { + const intl = useIntl() + + const itemsNotIncluded = [ + { + icon: <Zap className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.not-included.feature1.name", defaultMessage: "Zaps" }), + description: intl.formatMessage({ id: "notedeck.not-included.feature1.description", defaultMessage: "Tip creators and friends on nostr, or get tips for posting content" }), + badgeText: intl.formatMessage({ id: "notedeck.not-included.feature1.badge", defaultMessage: "Coming 2025 Q1" }) + }, + { + icon: <Mail className="text-white h-12 w-12 opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.not-included.feature2.name", defaultMessage: "DMs" }), + description: intl.formatMessage({ id: "notedeck.not-included.feature2.description", defaultMessage: "Send direct messages to friends" }), + badgeText: intl.formatMessage({ id: "notedeck.not-included.feature2.badge", defaultMessage: "Coming 2025 Q1" }) + }, + { + icon: <Film className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.not-included.feature3.name", defaultMessage: "Embedded video" }), + description: intl.formatMessage({ id: "notedeck.not-included.feature3.description", defaultMessage: "View videos directly from your feed" }), + badgeText: intl.formatMessage({ id: "notedeck.not-included.feature3.badge", defaultMessage: "Coming 2025 Q1" }) + }, + { + icon: <ThumbsUp className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.not-included.feature4.name", defaultMessage: "Likes/reactions/reposts" }), + description: intl.formatMessage({ id: "notedeck.not-included.feature4.description", defaultMessage: "React to posts from your friends" }), + badgeText: intl.formatMessage({ id: "notedeck.not-included.feature4.badge", defaultMessage: "Coming 2025 Q1" }) + }, + { + icon: <Search className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.not-included.feature5.name", defaultMessage: "Search" }), + description: intl.formatMessage({ id: "notedeck.not-included.feature5.description", defaultMessage: "Easily search for content on your feed or around the nostr network, and get suggestions when tagging users" }), + badgeText: intl.formatMessage({ id: "notedeck.not-included.feature5.badge", defaultMessage: "Coming 2025 Q1" }) + }, + { + icon: <Upload className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.not-included.feature6.name", defaultMessage: "Upload media" }), + description: intl.formatMessage({ id: "notedeck.not-included.feature6.description", defaultMessage: "Attach images or videos to your posts" }), + badgeText: intl.formatMessage({ id: "notedeck.not-included.feature6.badge", defaultMessage: "Coming 2025 Q1" }) + }, + ] + + return (<> + <ItemSection + heading={intl.formatMessage({ id: "notedeck.not-included.headline", defaultMessage: "What's NOT included so far, but we are building right now" })} + subHeading={intl.formatMessage({ id: "notedeck.not-included.description", defaultMessage: "We are working very hard to get these features out as soon as possible. If you want to collaborate please check out our [GitHub](https://github.com/damus-io/notedeck/issues)" })} + items={itemsNotIncluded} + /> + </>) +} diff --git a/src/components/sections/Notedeck/NotedeckFAQ.tsx b/src/components/sections/Notedeck/NotedeckFAQ.tsx @@ -0,0 +1,110 @@ +import { FormattedMessage, useIntl } from "react-intl"; +import { motion } from "framer-motion"; +import { RoundedContainerWithGradientBorder } from "@/components/ui/RoundedContainerWithGradientBorder"; +import { cn } from "@/lib/utils"; +import { Award, Badge, Heart, Joystick, KeyRound, Scale, Stars, Wallet } from "lucide-react"; +import { MeshGradient3 } from "@/components/effects/MeshGradient.3"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/Accordion" +import { Onest } from "next/font/google"; +import { MarkdownView } from "@/components/ui/MarkdownView"; + +const onest = Onest({ subsets: ['latin'] }) + + +export function NotedeckFAQ({ className }: { className?: string }) { + const intl = useIntl() + + const faq = [ + { + question: intl.formatMessage({ id: "faq.start", defaultMessage: "How do I get started (private key, public key)?" }), + answer: intl.formatMessage({ + id: "faq.start.a", + defaultMessage: `**From iOS Damus:** \`Go to side menu\` -> \`Tap on profile\` -> Tap on the *Copy button* adjacent to npub field\n\n**Any public key:** Go to [nostr.band](https://nostr.band) in your browser, and search for a nostr user of interest (e.g. Terry Yiu) to find their npub (public key). Tap on npub to copy it.\n\n![Npub example](/notedeck/npub_example.png)\n\n**Private key:** From iOS Damus -> go to side menu -> settings -> keys -> copy "secret account login key" and use this to log in to notedeck.` + }), + }, + { + question: intl.formatMessage({ id: "faq.purple.effect", defaultMessage: "How does my purple membership affect notedeck?" }), + answer: intl.formatMessage({ id: "faq.purple.effect.a", defaultMessage: "You get access to notedeck before anyone else!" }), + }, + { + question: intl.formatMessage({ id: "faq.need.purple", defaultMessage: "Do I need purple to get notedeck on day one?" }), + answer: intl.formatMessage({ id: "faq.need.purple.a", defaultMessage: "Yes!" }), + }, + { + question: intl.formatMessage({ id: "faq.day.one", defaultMessage: "What works on day one?" }), + answer: intl.formatMessage({ + id: "faq.day.one.a", + defaultMessage: "- Login with public key (npub)\n- Login with private key (nsec)\n- Multiple accounts\n- Add profile columns\n- Add notifications columns\n- Add hashtag columns\n- Create post\n- Reply\n- Quote note\n- Log out`" + }), + }, + { + question: intl.formatMessage({ id: "faq.not.work.day.one", defaultMessage: "Important stuff that doesn't work on day one" }), + answer: intl.formatMessage({ + id: "faq.not.work.day.one.a", + defaultMessage: `- Auto suggest tagging\n- Zaps\n- DMs\n- Reposts\n- Likes/reactions\n- Embedded video\n- Upload media\n- Modifying profile\n- Modifying relay list\n- See followers/following count\n- Search, and many more` + }), + }, + { + question: intl.formatMessage({ id: "faq.trouble.feedback", defaultMessage: "Having trouble, or have feedback?" }), + answer: intl.formatMessage({ + id: "faq.trouble.feedback.a", + defaultMessage: "Tag damus on nostr, or send an email to [support@damus.io](mailto:support@damus.io)." + }), + }, + { + question: intl.formatMessage({ id: "faq.buy.purple", defaultMessage: "How can I buy purple if I am not a purple member?" }), + answer: intl.formatMessage({ id: "faq.buy.purple.a", defaultMessage: "[https://damus.io/purple](https://damus.io/purple)" }), + }, + { + question: intl.formatMessage({ id: "faq.supported.os", defaultMessage: "Supported operating systems" }), + answer: intl.formatMessage({ id: "faq.supported.os.a", defaultMessage: `**On day one:**\n\n- Linux\n- Ubuntu\n- Fedora\n- MacOS\n- Windows\n\n**In the future:**\n- Android\n- iOS\n- iPadOS` }), + }, + { + question: intl.formatMessage({ id: "faq.support.signer.extension", defaultMessage: "Does notedeck support signer extension?" }), + answer: intl.formatMessage({ id: "faq.support.signer.extension.a", defaultMessage: "No. Notedeck is a desktop app where key storage is local so a signer extension isn't required. Trust in third party apps is eliminated by using the desktop app Notedeck." }), + }, + ] + + return (<> + <div className={cn("bg-black overflow-hidden relative", className)}> + <MeshGradient3 className="absolute top-0 left-0 pointer-events-none translate-y-3/4 overflow-visible scale-150" /> + <div className="container mx-auto px-6 pb-32 pt-20"> + <div className="flex flex-col items-center justify-center mt-16"> + <div className="relative mb-12 flex flex-col items-center"> + <motion.h2 + className={cn("mt-6 text-3xl sm:text-4xl md:text-5xl text-center text-transparent bg-clip-text pb-6 font-semibold whitespace-pre-line max-w-4xl tracking-wide leading-relaxed", onest.className)} + style={{ + backgroundImage: "linear-gradient(to right, #ffffff -100%, #ffffff -40%, #2D175B 100%)", + opacity: 0, + }} + animate={{ + backgroundImage: "linear-gradient(to right, #ffffff 0%, #ffffff 40%, #2D175B 100%)", + transition: { duration: 3 }, + opacity: 1 + }} + > + {intl.formatMessage({ id: "purple.faq.headline", defaultMessage: "Frequent Questions" })} + </motion.h2> + </div> + </div> + <Accordion type="single" collapsible className="w-full text-white max-w-2xl mx-auto"> + {faq.map((item, index) => ( + <AccordionItem value={`item-${index}`} key={index}> + <AccordionTrigger>{item.question}</AccordionTrigger> + <AccordionContent className="whitespace-pre-line"> + <MarkdownView> + {item.answer} + </MarkdownView> + </AccordionContent> + </AccordionItem> + ))} + </Accordion> + </div> + </div> + </>) +} diff --git a/src/components/sections/Notedeck/NotedeckHero.tsx b/src/components/sections/Notedeck/NotedeckHero.tsx @@ -1,97 +1,154 @@ -import { ArrowUpRight, ChevronRight, Globe2, LucideZapOff, Sparkles, Star, Zap, ZapIcon, ZapOff } from "lucide-react"; -import { MeshGradient1 } from "../../effects/MeshGradient.1"; import { TopMenu } from "../TopMenu"; +import { Onest } from 'next/font/google' import { Button } from "../../ui/Button"; import { FormattedMessage, useIntl } from "react-intl"; import Link from "next/link"; -import { motion } from "framer-motion"; +import { motion, useScroll, useTransform } from "framer-motion"; import Image from "next/image"; -import StarField from "@/components/effects/StarField"; -import { useScroll, useTransform } from "framer-motion"; import { NOTEDECK_WAITLIST_URL } from "@/lib/constants"; +import { cn } from "@/lib/utils"; +import { ArrowUpRight, ChevronRight, Sparkles } from "lucide-react"; +import { useRef } from "react"; -export function NotedeckHero({ className }: { className?: string }) { +const onest = Onest({ subsets: ['latin'] }) + +export function NotedeckHero({ className, introducing }: { className?: string, introducing?: boolean }) { const intl = useIntl() - const { scrollYProgress } = useScroll() - const heroOpacity = useTransform(scrollYProgress, [0.55, 0.8], [1.0, 0.0]); - const bgOpacity = useTransform(scrollYProgress, [0.3, 0.55], [1.0, 0.0]); - const mobileScreenshotOffsetX = useTransform(scrollYProgress, [0.2, 1], [0, -800]); + const ref = useRef(null) + const { scrollYProgress } = useScroll({ + target: ref, + offset: ["start start", "end start"] + }) + const mobileScreenshotOffsetX = useTransform(scrollYProgress, [0, 1], [0, -1200]); + const bgOpacity = useTransform(scrollYProgress, [0.3, 1], [1.0, 0.0]); return (<> - <motion.div - className={`bg-black overflow-hidden relative ${className}`} + <div + ref={ref} + className={cn("bg-black overflow-hidden flex-col relative min-h-screen", className)} > - <motion.div className="absolute z-0 w-full h-full pointer-events-none" style={{ opacity: bgOpacity }}> - <StarField className="z-0 fixed top-0 left-0 object-cover object-center w-full h-full"/> - <MeshGradient1 className="-translate-x-1/3" /> - </motion.div> - <motion.div className="container z-10 mx-auto px-6 pt-6 h-full min-h-screen flex flex-col justify-center" style={{ opacity: heroOpacity }}> - <motion.div - className="w-full z-10 backdrop-blur-sm bg-black/20 rounded-xl p-4 shadow-xl border border-black/30" - style={{ opacity: 0 }} - animate={{ opacity: 1, transition: { delay: 1.5, duration: 1 } }} - > - <TopMenu - className="w-full z-10" - customCTA={ - <Link href={NOTEDECK_WAITLIST_URL}> - <Button variant="accent" className="w-full"> - {intl.formatMessage({ id: "notedeck.hero.menu.signup-for-the-waitlist", defaultMessage: "Sign up" })} - </Button> - </Link> - } - /> - </motion.div> + <motion.div className="container mt-28 z-10 mx-auto px-6 pt-6 flex flex-col justify-center"> <motion.div - className="flex flex-col items-center justify-center h-full grow mt-32 z-10" + className="flex flex-col items-start justify-start h-full grow mt-8 z-10" style={{ opacity: 0 }} animate={{ opacity: 1, transition: { delay: 0.5, duration: 1 } }} > - <div className="flex gap-x-4 items-center mb-2 md:mb-2"> - <Image - src="/notedeck/notedeck-logo.png" - alt="notedeck logo" - width={500} - height={105} + {introducing && ( + <motion.h1 + className={cn("text-2xl uppercase text-white/80 mb-12", onest.className)} + > + Introducing + </motion.h1> + )} + <div className="flex gap-x-4 items-center mb-2 md:mb-6"> + <Image + src="/logo_icon.png" + alt="damus logo" + width={80} + height={80} + className="rounded-lg md:rounded-xl lg:rounded-2xl w-12 md:w-16 lg:w-20 aspect-square" /> + <motion.h1 + className={cn("text-4xl sm:text-4xl md:text-7xl text-transparent bg-clip-text font-semibold whitespace-pre-line max-w-4xl tracking-wide leading-loose", onest.className)} + style={{ + backgroundImage: "linear-gradient(to right, #ffffff -100%, #ffffff -80%, #2D175B 100%)", + opacity: 0, + }} + animate={{ + backgroundImage: "linear-gradient(to right, #ffffff 0%, #ffffff 100%, #2D175B 180%)", + transition: { duration: 3 }, + opacity: 1 + }} + > + Notedeck + </motion.h1> </div> - <div className="text-purple-200/70 text-lg md:text-2xl text-center max-w-2xl mb-8 break-keep"> - {intl.formatMessage({ id: "notedeck.hero.description", defaultMessage: "Damus. Everywhere. The fastest native, customizable nostr experience, for all platforms." })} - </div> + <motion.h2 + className={cn("mt-2 text-2xl sm:text-3xl md:text-5xl text-transparent bg-clip-text pb-6 font-semibold whitespace-pre-line max-w-4xl tracking-wide leading-8 md:leading-snug lg:leading-normal", onest.className)} + style={{ + backgroundImage: "linear-gradient(to right, #ffffff -100%, #ffffff -40%, #2D175B 100%)", + opacity: 0, + }} + animate={{ + backgroundImage: "linear-gradient(to right, #ffffff 0%, #ffffff 40%, #2D175B 100%)", + transition: { duration: 3 }, + opacity: 1 + }} + > + <FormattedMessage defaultMessage="Damus. Everywhere. The fastest native, customizable nostr experience, for all platforms." id="notedeck.hero.headline"/> + </motion.h2> + <motion.div + className="inline-flex gap-2 items-center text-xs md:text-md rounded-full bg-purple-100/10 backdrop-blur-sm shadow-lg p-1 px-3 text-purple-100/80 border border-purple-100/30 active:scale-95 transition mt-2 mb-8 md:mb-10" + style={{ opacity: 0 }} + animate={{ opacity: 1, transition: { delay: 1.5, duration: 1 } }} + > + <Sparkles className="h-4 mr-1 text-purple-50" aria-hidden="true" /> + {intl.formatMessage({ id: "notedeck.hero.announcement", defaultMessage: "Notedeck Alpha now available on Purple" })} + </motion.div> <motion.div className="mt-6 md:mt-4 mb-8 flex flex-col md:flex-row items-center md:items-center gap-y-4 gap-x-6 w-full md:w-auto" style={{ opacity: 0 }} animate={{ opacity: 1, transition: { delay: 1.5, duration: 1 } }} > - <Link href={NOTEDECK_WAITLIST_URL} className="w-full md:w-auto"> - <Button variant="default" className="w-full"> - {intl.formatMessage({ id: "notedeck.hero.signup-for-the-waitlist", defaultMessage: "Sign up for the waitlist" })} - </Button> - </Link> + <InstallNowButton/> + <a href={"/notedeck#introducing"}> + <Button variant="link" className="w-full md:w-auto"> + { intl.formatMessage({ id: "notedeck.hero.learn-more", defaultMessage: "Learn more" }) } + + </Button> + </a> </motion.div> - <div className="hidden md:flex gap-x-4 items-center mb-12 md:mb-6 z-20 relative"> - <Image - src="/notedeck/notedeck-hero.png" - alt="notedeck screenshot" - className="" - width={1200} - height={105} - /> - </div> - <div className="flex md:hidden mb-12 md:mb-6 z-20 relative overflow-hidden w-screen"> - <motion.img - src="/notedeck/notedeck-hero.png" - alt="notedeck screenshot" - className="max-w-fit" - style={{ - marginLeft: mobileScreenshotOffsetX - }} - width={1200} - height={105} - /> - </div> </motion.div> </motion.div> - </motion.div> + <motion.div className="flex justify-end w-full relative"> + {/* Gradient fade to black on the bottom */} + <div className="w-full h-96 bottom-0 absolute bg-gradient-to-b from-transparent to-black z-10" /> + <motion.div + className="z-0 w-auto pointer-events-none mt-[-553px] overflow-x-hidden overflow-y-visible flex" + > + <motion.img + src="/notedeck/notedeck-hero-2.png" + className="block md:hidden" + style={{ + width: "1728px", + height: "1106px", + maxWidth: "1728px", + marginLeft: mobileScreenshotOffsetX, + opacity: bgOpacity + }} + alt="Notedeck screenshot" + sizes="1728px" + width={1728} + height={1106} + /> + <motion.img + src="/notedeck/notedeck-hero-2.png" + className="hidden md:block" + style={{ + width: "1728px", + height: "1106px", + maxWidth: "1728px", + }} + alt="Notedeck screenshot" + sizes="1728px" + width={1728} + height={1106} + /> + </motion.div> + </motion.div> + </div> </>) } + +export function InstallNowButton({ className }: { className?: string }) { + const intl = useIntl() + + return ( + <Link href={"/notedeck/install"} className={cn("w-full md:w-auto", className)}> + <Button variant="accent" className="w-full flex gap-x-2 text-lg font-semibold"> + <Sparkles className="w-4 h-4"/> + {intl.formatMessage({ id: "notedeck.hero.install", defaultMessage: "Install now" })} + </Button> + </Link> + ) +} diff --git a/src/components/sections/Notedeck/NotedeckWaitlistHero.tsx b/src/components/sections/Notedeck/NotedeckWaitlistHero.tsx @@ -0,0 +1,97 @@ +import { ArrowUpRight, ChevronRight, Globe2, LucideZapOff, Sparkles, Star, Zap, ZapIcon, ZapOff } from "lucide-react"; +import { MeshGradient1 } from "../../effects/MeshGradient.1"; +import { TopMenu } from "../TopMenu"; +import { Button } from "../../ui/Button"; +import { FormattedMessage, useIntl } from "react-intl"; +import Link from "next/link"; +import { motion } from "framer-motion"; +import Image from "next/image"; +import StarField from "@/components/effects/StarField"; +import { useScroll, useTransform } from "framer-motion"; +import { NOTEDECK_WAITLIST_URL } from "@/lib/constants"; + +export function NotedeckWaitlistHero({ className }: { className?: string }) { + const intl = useIntl() + const { scrollYProgress } = useScroll() + const heroOpacity = useTransform(scrollYProgress, [0.55, 0.8], [1.0, 0.0]); + const bgOpacity = useTransform(scrollYProgress, [0.3, 0.55], [1.0, 0.0]); + const mobileScreenshotOffsetX = useTransform(scrollYProgress, [0.2, 1], [0, -800]); + + return (<> + <motion.div + className={`bg-black overflow-hidden relative ${className}`} + > + <motion.div className="absolute z-0 w-full h-full pointer-events-none" style={{ opacity: bgOpacity }}> + <StarField className="z-0 fixed top-0 left-0 object-cover object-center w-full h-full"/> + <MeshGradient1 className="-translate-x-1/3" /> + </motion.div> + <motion.div className="container z-10 mx-auto px-6 pt-6 h-full min-h-screen flex flex-col justify-center" style={{ opacity: heroOpacity }}> + <motion.div + className="w-full z-10 backdrop-blur-sm bg-black/20 rounded-xl p-4 shadow-xl border border-black/30" + style={{ opacity: 0 }} + animate={{ opacity: 1, transition: { delay: 1.5, duration: 1 } }} + > + <TopMenu + className="w-full z-10" + customCTA={ + <Link href={NOTEDECK_WAITLIST_URL}> + <Button variant="accent" className="w-full"> + {intl.formatMessage({ id: "notedeck.hero.menu.signup-for-the-waitlist", defaultMessage: "Sign up" })} + </Button> + </Link> + } + /> + </motion.div> + <motion.div + className="flex flex-col items-center justify-center h-full grow mt-32 z-10" + style={{ opacity: 0 }} + animate={{ opacity: 1, transition: { delay: 0.5, duration: 1 } }} + > + <div className="flex gap-x-4 items-center mb-2 md:mb-2"> + <Image + src="/notedeck/notedeck-logo.png" + alt="notedeck logo" + width={500} + height={105} + /> + </div> + <div className="text-purple-200/70 text-lg md:text-2xl text-center max-w-2xl mb-8 break-keep"> + {intl.formatMessage({ id: "notedeck.hero.description", defaultMessage: "Damus. Everywhere. The fastest native, customizable nostr experience, for all platforms." })} + </div> + <motion.div + className="mt-6 md:mt-4 mb-8 flex flex-col md:flex-row items-center md:items-center gap-y-4 gap-x-6 w-full md:w-auto" + style={{ opacity: 0 }} + animate={{ opacity: 1, transition: { delay: 1.5, duration: 1 } }} + > + <Link href={NOTEDECK_WAITLIST_URL} className="w-full md:w-auto"> + <Button variant="default" className="w-full"> + {intl.formatMessage({ id: "notedeck.hero.signup-for-the-waitlist", defaultMessage: "Sign up for the waitlist" })} + </Button> + </Link> + </motion.div> + <div className="hidden md:flex gap-x-4 items-center mb-12 md:mb-6 z-20 relative"> + <Image + src="/notedeck/notedeck-hero.png" + alt="notedeck screenshot" + className="" + width={1200} + height={105} + /> + </div> + <div className="flex md:hidden mb-12 md:mb-6 z-20 relative overflow-hidden w-screen"> + <motion.img + src="/notedeck/notedeck-hero.png" + alt="notedeck screenshot" + className="max-w-fit" + style={{ + marginLeft: mobileScreenshotOffsetX + }} + width={1200} + height={105} + /> + </div> + </motion.div> + </motion.div> + </motion.div> + </>) +} diff --git a/src/components/sections/Notedeck/Recap.tsx b/src/components/sections/Notedeck/Recap.tsx @@ -0,0 +1,62 @@ +import { Onest } from 'next/font/google' +import { FormattedMessage, useIntl } from "react-intl"; +import { motion } from "framer-motion"; +import { RoundedContainerWithGradientBorder } from "@/components/ui/RoundedContainerWithGradientBorder"; +import { cn } from "@/lib/utils"; +import { Lock, User, Layers, Bell, FileText, LogOut, Settings2, Gauge, Globe, Sparkles } from "lucide-react"; +import { MeshGradient3 } from "@/components/effects/MeshGradient.3"; +import { Item, ItemSection } from './ItemSection'; +import Link from 'next/link'; +import { Button } from '@/components/ui/Button'; + +const onest = Onest({ subsets: ['latin'] }) + +export function Recap({ className }: { className?: string }) { + const intl = useIntl() + + const items: Item[] = [ + { + icon: <Settings2 className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.feature.customization.name", defaultMessage: "Customization" }), + description: intl.formatMessage({ id: "notedeck.feature.customization.description", defaultMessage: "Custom feeds, ability to switch between your accounts, follow hashtags in separate columns, the list goes on. Nostr prioritizes user choice and Notedeck takes this to the next level" }), + }, + { + icon: <Gauge className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.feature.speed.name", defaultMessage: "Speed" }), + description: intl.formatMessage({ id: "notedeck.feature.speed.description", defaultMessage: "The fastest nostr client. Built from the ground up with an ultra-fast database made exclusively for nostr, leveraging several state-of-the-art performance techniques not available on web clients" }), + }, + { + icon: <Globe className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.feature.availability.name", defaultMessage: "Available Everywhere" }), + description: intl.formatMessage({ id: "notedeck.feature.availability.description", defaultMessage: "We're no longer tied to Apple restrictions... Notedeck will eventually be available on all platforms, for everyone and with the ability to zap without approval. The sky's the limit!" }), + }, + ] + + return (<> + <ItemSection + heading={intl.formatMessage({ id: "notedeck.recap.headline", defaultMessage: "Let’s recap" })} + subHeading={intl.formatMessage({ id: "notedeck.recap.description", defaultMessage: "Damus Notedeck is a lightning fast native app that allows you to explore the nostr social network in a completely new way, with several power features. **Try it today** with our [Purple](/purple) subscription" })} + items={items} + customChild={ + <div className="flex flex-col md:flex-row gap-x-8 gap-y-4 mx-auto my-32 w-full justify-center"> + {(intl.locale != "ja" || + process.env.FORCE_LOAD_ALL_JA_SECTIONS) && ( + <Link + href="/notedeck/install" + target="_blank" + > + <Button variant="accent" className="w-full md:w-auto text-xl"> + <Sparkles className="mr-2" /> + {intl.formatMessage({ + id: "notedeck.recap.try_it_today", + defaultMessage: "Install now", + })} + </Button> + </Link> + )} + </div> + } + /> + + </>) +} diff --git a/src/components/sections/Notedeck/ShareFeedback.tsx b/src/components/sections/Notedeck/ShareFeedback.tsx @@ -0,0 +1,93 @@ +import { Button } from "@/components/ui/Button"; +import { useIntl } from "react-intl"; +import Link from "next/link"; +import { motion } from "framer-motion"; +import { cn } from "@/lib/utils"; +import { ArrowUpRight, MessageCircleIcon, GitBranch, Megaphone, Github } from "lucide-react"; +import { MeshGradient4 } from "@/components/effects/MeshGradient.4"; +import { GithubIcon } from "@/components/icons/GithubIcon"; +import Image from "next/image"; + +export function ShareFeedback({ className }: { className?: string }) { + const intl = useIntl(); + + return ( + <> + <div className={cn("bg-black overflow-hidden relative", className)}> + <div className="container mx-auto px-6 pb-24 pt-24"> + <div className="flex flex-col items-center justify-center mt-32 lg:mt-16"> + <div className="relative mb-24 flex flex-col items-center justify-center"> + <Image + src="/notedeck/share-feedback.png" + width={192} + height={110.5} + className="mb-8" + alt={"Share feedback icon"} + /> + <motion.h2 className="mb-8 text-3xl md:text-5xl lg:text-6xl text-center text-transparent bg-clip-text bg-gradient-to-r from-white from-5% to-[#E0A4D3] to-100% font-semibold"> + {intl.formatMessage({ + id: "notedeck.feedback.headline", + defaultMessage: "Share your feedback with us", + })} + </motion.h2> + {(intl.locale != "ja" || + process.env.FORCE_LOAD_ALL_JA_SECTIONS) && ( + <motion.div className="text-xl text-center max-w-2xl mb-12 text-white"> + {intl.formatMessage({ + id: "notedeck.feedback.subheadline", + defaultMessage: + "We encourage you to send feedback to our Damus npub over nostr or Github to we can take it in consideration in our Roadmap", + })} + </motion.div> + )} + <div className="flex flex-col md:flex-row gap-x-8 gap-y-4"> + {(intl.locale != "ja" || + process.env.FORCE_LOAD_ALL_JA_SECTIONS) && ( + <Link + href="mailto:support@damus.io" + target="_blank" + > + <Button variant="accent" className="w-full md:w-auto"> + <Megaphone className="mr-2" /> + {intl.formatMessage({ + id: "notedeck.feedback.share-feedback", + defaultMessage: "Send Feedback", + })} + <ArrowUpRight className="ml-2" /> + </Button> + </Link> + )} + <Link href="https://github.com/damus-io/notedeck/issues/" target="_blank"> + <Button variant="default" className="w-full md:w-auto"> + <Github className="mr-2" /> + {intl.formatMessage({ + id: "notedeck.feedback.view_github", + defaultMessage: "View our GitHub", + })} + <ArrowUpRight className="ml-2" /> + </Button> + </Link> + {(intl.locale != "ja" || + process.env.FORCE_LOAD_ALL_JA_SECTIONS) && ( + <Link + href="https://njump.me/npub1zafcms4xya5ap9zr7xxr0jlrtrattwlesytn2s42030lzu0dwlzqpd26k5" + target="_blank" + > + <Button variant="default" className="w-full md:w-auto"> + <MessageCircleIcon className="mr-2" /> + {intl.formatMessage({ + id: "contribute.talk_to_a_team_member", + defaultMessage: "Talk to a team member", + })} + <ArrowUpRight className="ml-2" /> + </Button> + </Link> + )} + </div> + </div> + </div> + </div> + </div> + </> + ); +} diff --git a/src/components/sections/Notedeck/WhatIsNotedeck.tsx b/src/components/sections/Notedeck/WhatIsNotedeck.tsx @@ -0,0 +1,221 @@ +import { Onest } from 'next/font/google' +import { FormattedMessage, useIntl } from "react-intl"; +import { motion } from "framer-motion"; +import { RoundedContainerWithGradientBorder } from "@/components/ui/RoundedContainerWithGradientBorder"; +import { cn } from "@/lib/utils"; +import { Columns, Eye, Film, Gauge, Globe, Joystick, KeyRound, Mail, Scale, Search, Settings2, ThumbsUp, Upload, Wallet, Zap, ArrowLeftRightIcon } from "lucide-react"; +import { MeshGradient3 } from "@/components/effects/MeshGradient.3"; +import { MarkdownView } from '@/components/ui/MarkdownView'; +import { Item, ItemSection } from './ItemSection'; +import Markdown from 'react-markdown'; + +const onest = Onest({ subsets: ['latin'] }) + +export function WhatIsNotedeck({ className }: { className?: string }) { + const intl = useIntl() + + const list = { + visible: { + opacity: 1, + transition: { + when: "beforeChildren", + staggerChildren: 2, + }, + }, + hidden: { + opacity: 0, + transition: { + when: "afterChildren", + }, + }, + } + + const item = { + visible: { + opacity: 1 + }, + hidden: { + opacity: 0 + }, + } + + const items: Item[] = [ + { + icon: <Eye className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.feature.customization.name", defaultMessage: "Powerful Views" }), + description: intl.formatMessage({ id: "notedeck.feature.customization.description", defaultMessage: "Add profile, hashtag, and notification columns of any nostr public or private key. Which means you can see the nostr landscape through other peoples' eyes" }), + }, + { + icon: <Gauge className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.feature.speed.name", defaultMessage: "Speed" }), + description: intl.formatMessage({ id: "notedeck.feature.speed.description", defaultMessage: "The fastest nostr client. Built from the ground up with an ultra-fast database made exclusively for nostr, leveraging several state-of-the-art performance techniques not available on web clients" }), + }, + { + icon: <ArrowLeftRightIcon className="h-12 w-12 text-white opacity-80"/>, + headline: intl.formatMessage({ id: "notedeck.feature.availability.name", defaultMessage: "Switch Between Accounts" }), + description: intl.formatMessage({ id: "notedeck.feature.availability.description", defaultMessage: "Nostriches can also switch between multiple accounts quickly and easily, drastically improving personal and business use cases" }), + }, + ] + + return (<> + <div id="introducing" className={cn("bg-black overflow-hidden relative", className)}> + <div className="container mx-auto px-6 pb-32 pt-20"> + <div className="flex flex-col items-center justify-center mt-32 lg:mt-16"> + <div className="relative mb-16 flex flex-col items-center"> + <motion.h2 + className={cn("mt-6 text-3xl sm:text-4xl md:text-5xl text-center text-transparent bg-clip-text pb-6 font-semibold whitespace-pre-line max-w-4xl tracking-wide leading-relaxed", onest.className)} + style={{ + backgroundImage: "linear-gradient(to right, #ffffff -100%, #ffffff -40%, #2D175B 100%)", + opacity: 0, + }} + animate={{ + backgroundImage: "linear-gradient(to right, #ffffff 0%, #ffffff 60%, #2D175B 150%)", + transition: { duration: 2 }, + opacity: 1 + }} + > + {intl.formatMessage({ id: "notedeck.what-is.headline", defaultMessage: "Introducing Notedeck" })} + </motion.h2> + <motion.div + initial="hidden" + whileInView="visible" + transition={{ duration: 1 }} + variants={list} + viewport={{ once: true }} + > + <motion.div + className={cn("text-white/60 text-4xl leading-relaxed text-left max-w-4xl my-32 break-keep")} + variants={item} + transition={{ duration: 1 }} + > + <SpecialMarkdownView> + {intl.formatMessage({ + id: "notedeck.what-is.description1", + defaultMessage: "**Nostr is the future of social media** and Notedeck is our latest innovation towards the **future of nostr**.\n\n" + })} + </SpecialMarkdownView> + </motion.div> + <motion.div + className={cn("text-white/60 text-4xl leading-relaxed text-left max-w-4xl my-32 break-keep")} + variants={item} + transition={{ duration: 1 }} + > + <SpecialMarkdownView> + {intl.formatMessage({ + id: "notedeck.what-is.description2", + defaultMessage: "A **lightning fast native app** that allows you to explore the nostr social network in a **completely new way**! Notedeck includes several power features to both personal and business use cases, including:" + })} + </SpecialMarkdownView> + </motion.div> + {(intl.locale != "ja" || process.env.FORCE_LOAD_ALL_JA_SECTIONS) && ( + <motion.div + className="flex flex-wrap gap-x-8 gap-y-16 items-stretch justify-center mt-28 mb-56" + variants={item} + > + {items.map((item, index) => ( + <ItemView key={index} item={item} index={index} /> + ))} + </motion.div> + )} + <motion.div + className={cn("text-white/60 text-4xl leading-relaxed text-left max-w-4xl my-32 break-keep")} + variants={item} + transition={{ duration: 1 }} + > + <SpecialMarkdownView> + {intl.formatMessage({ + id: "notedeck.what-is.description3", + defaultMessage: "This isn't a web app and it's not just \"another\" social client. This is nostr **like you've never seen it before!**\n\nNotedeck works on **Linux**, **macOS**, and **Windows** on day one. An Android version will be added in 2025, along with iOS and iPadOS eventually.\n\nFuture nostr apps **will run on Notedeck in 2025.**\n\nAn early Notedeck Alpha version is available **today** to our paid [Purple](/purple) subscribers." + })} + </SpecialMarkdownView> + </motion.div> + </motion.div> + </div> + </div> + </div> + </div> + </>) +} + +interface ItemViewProps { + item: Item, + index: number +} + +function ItemView({ item, index }: ItemViewProps) { + return ( + <div key={index} className="w-72 flex flex-col items-start justify-start"> + <div className="flex justify-between w-full items-start"> + {item.icon} + {item.badgeText && ( + <div className={cn("py-1 px-2 bg-orange-500/30 text-orange-200 rounded-lg", item.badgeClassName)}> + {item.badgeText} + </div> + )} + </div> + <h3 className="text-xl font-semibold text-transparent bg-clip-text bg-gradient-to-br text-white text-left text-normal mt-6"> + {item.headline} + </h3> + <p className="text-white/80 text-left text-normal mt-4"> + {item.description} + </p> + </div> + ) +} + +function SpecialMarkdownView({ children, className }: { children?: string | null | undefined, className?: string }) { + + const container = { + hidden: { opacity: 0 }, + show: { + opacity: 1, + transition: { + delayChildren: 1, + staggerChildren: 1 + } + } + } + + const item = { + hidden: { opacity: 0.6 }, + show: { opacity: 1 } + } + + return ( + <motion.div + initial="hidden" + whileInView="show" + transition={{ duration: 0.5 }} + variants={container} + className={className} + > + <Markdown components={{ + h1: ({node, ...props}) => <h1 className="opacity-90 font-bold text-6xl mb-6" {...props} />, + h2: ({node, ...props}) => <h2 className="opacity-90 font-bold text-4xl mb-4" {...props} />, + h3: ({node, ...props}) => <h3 className="opacity-90 font-bold text-2xl" {...props} />, + ul: ({node, ...props}) => <ul className="opacity-80 list-disc list-inside mb-5" {...props} />, + ol: ({node, ...props}) => <ol className="opacity-80 list-decimal list-inside mb-5" {...props} />, + li: ({node, ...props}) => <li className="opacity-80 m-1" {...props} />, + em: ({node, ...props}) => <i className="italic" {...props} />, + // @ts-ignore + strong: ({node, ...props}) => <motion.strong + variants={item} + className="font-bold text-white" + {...props} + />, + p: ({node, ...props}) => <p className="opacity-80 mb-4" {...props} />, + // @ts-ignore + a: ({node, ...props}) => <motion.a + variants={item} + className="text-white font-bold underline" + target="_blank" + {...props} + />, + pre: ({node, ...props}) => <pre className="my-2 bg-white/10 overflow-scroll p-3 rounded-lg font-mono" {...props} />, + code: ({node, ...props}) => { + return (<code className="my-2 bg-white/10 overflow-scroll p-1 font-mono rounded-sm" {...props} />) + }, + }} className={className}>{children}</Markdown> + </motion.div> + ) +} diff --git a/src/components/sections/PurpleAccount.tsx b/src/components/sections/PurpleAccount.tsx @@ -33,7 +33,7 @@ export function PurpleAccount() { setError("Failed to get profile info from the relay. Please wait a few minutes and refresh the page. If the problem persists, please contact support.") } } - + // MARK: - Effects and hooks // Load the profile when the pubkey changes @@ -96,11 +96,21 @@ export function PurpleAccount() { <AccountInfoRow label={intl.formatMessage({ id: "purple.account.account-creation", defaultMessage: "Account creation" })} value={(loggedInAccountInfo?.created_at && unixTimestampToDateString(loggedInAccountInfo?.created_at)) || "N/A"} /> <AccountInfoRow label={intl.formatMessage({ id: "purple.account.subscriber-number", defaultMessage: "Subscriber number" })} value={(loggedInAccountInfo?.subscriber_number && "#" + loggedInAccountInfo?.subscriber_number) || "N/A"} last={loggedInAccountInfo?.testflight_url ? false : true} /> {loggedInAccountInfo?.testflight_url && <Link href={loggedInAccountInfo?.testflight_url} target="_blank"> - <Button variant="link" className="w-full text-left my-2"> - <ArrowUpRight className="text-damuspink-600 mr-2" /> - {intl.formatMessage({ id: "purple.account.testflight-link", defaultMessage: "Join TestFlight" })} - </Button> + <div className="py-2 border-b border-purple-200/20"> + <Button variant="link" className="w-full text-left"> + <ArrowUpRight className="text-damuspink-600 mr-2" /> + {intl.formatMessage({ id: "purple.account.testflight-link", defaultMessage: "Join TestFlight" })} + </Button> + </div> </Link>} + <div className="py-2"> + <Link href="/notedeck/install" target="_blank"> + <Button variant="link" className="w-full text-left"> + <ArrowUpRight className="text-damuspink-600 mr-2" /> + {intl.formatMessage({ id: "purple.account.notedeck-install-link", defaultMessage: "Try Notedeck" })} + </Button> + </Link> + </div> </div> <Button className="w-full md:w-auto opacity-70 hover:opacity-100 transition mt-4 text-sm" onClick={() => logout()} variant="link"> <LogOut className="text-damuspink-600 mr-2" /> diff --git a/src/components/sections/PurpleHero.tsx b/src/components/sections/PurpleHero.tsx @@ -25,25 +25,25 @@ export function PurpleHero() { <Image src="/stars-bg.webp" fill className="absolute top-0 left-0 object-cover lg:object-contain object-center w-full h-full" alt="" aria-hidden="true" /> <MeshGradient1 className="-translate-x-1/3" /> </div> + <TopMenu + className="w-full" + customCTA={<> + {loggedInAccountInfo ? + <Link href="/purple/account"> + <Button variant="accent"> + {intl.formatMessage({ id: "purple.hero.menu.go-to-my-account", defaultMessage: "My Account" })} + </Button> + </Link> + : + <Link href="/purple/login"> + <Button variant="accent"> + {intl.formatMessage({ id: "purple.hero.menu.login", defaultMessage: "Login" })} + </Button> + </Link> + } + </>} + /> <div className="container z-10 mx-auto px-6 pt-12 h-full min-h-screen flex flex-col justify-center"> - <TopMenu - className="w-full" - customCTA={<> - {loggedInAccountInfo ? - <Link href="/purple/account"> - <Button variant="accent"> - {intl.formatMessage({ id: "purple.hero.menu.go-to-my-account", defaultMessage: "My Account" })} - </Button> - </Link> - : - <Link href="/purple/login"> - <Button variant="accent"> - {intl.formatMessage({ id: "purple.hero.menu.login", defaultMessage: "Login" })} - </Button> - </Link> - } - </>} - /> <div className="flex flex-col items-center justify-center h-full grow"> <Link href="/purple/checkout"> <motion.div diff --git a/src/components/sections/TopMenu.tsx b/src/components/sections/TopMenu.tsx @@ -2,11 +2,11 @@ import * as React from "react" import Link from "next/link" import { cn } from "@/lib/utils" import * as NavigationMenu from '@radix-ui/react-navigation-menu'; -import { Zap } from "lucide-react" +import { Menu, X, Zap } from "lucide-react" import { Button } from "../ui/Button"; import { DAMUS_APP_STORE_URL, DAMUS_MERCH_STORE_URL } from "@/lib/constants"; import { useIntl } from "react-intl"; -import { motion } from "framer-motion"; +import { AnimatePresence, motion, useScroll, useTransform } from "framer-motion"; let regularNavItems: { nameIntlId: string, href: string, target?: string }[] = [ { nameIntlId: "topbar.purple", href: "/purple" }, @@ -22,10 +22,19 @@ const ENABLE_FULL_MENU = true export interface TopMenuProps { className?: string customCTA?: React.ReactNode + hideLogoOnTop?: boolean } -export function TopMenu({ className, customCTA }: TopMenuProps) { +export function TopMenu({ className, customCTA, hideLogoOnTop: hideLogoOnTop = false }: TopMenuProps) { let navItemDefaultStyles = "hover:opacity-80 transition-opacity duration-200 ease-in-out" + const [showMobileMenu, setShowMobileMenu] = React.useState<boolean>(false) + const ref = React.useRef(null) + const { scrollYProgress } = useScroll({ + target: ref, + offset: ["start start", "end start"] + }) + const logoOpacity = useTransform(scrollYProgress, [0.1, 0.5], [hideLogoOnTop ? 0.0 : 1.0, 1.0]); + const menuBgColor = useTransform(scrollYProgress, [0.1, 0.5], ["rgba(0,0,0,0)", "rgba(0,0,0,0.5)"]); const intl = useIntl() // This is needed to allow intl commands to extract the strings @@ -38,42 +47,82 @@ export function TopMenu({ className, customCTA }: TopMenuProps) { "topbar.contribute": intl.formatMessage({ id: "topbar.contribute", defaultMessage: "Contribute" }), } - return ( + return (<> + <div ref={ref} className="absolute top-0 h-screen bg-transparent pointer-events-none w-4 left-0" /> <motion.div - style={{ opacity: 0 }} + style={{ + opacity: 0, + backgroundColor: showMobileMenu ? "rgba(0,0,0,0.5)" : menuBgColor, + borderColor: showMobileMenu ? "rgba(0,0,0,0.5)" : menuBgColor, + }} animate={{ opacity: 1, transition: { delay: 1.5, duration: 1 } }} + className="w-full border-b z-50 backdrop-blur-sm fixed" > - <NavigationMenu.Root className={cn("flex justify-between items-center", className)}> - <div> + <NavigationMenu.Root className={cn("flex flex-col items-center z-50", className)}> + <div className="container justify-between items-center flex p-6"> + <motion.div + style={{ opacity: logoOpacity }} + > <NavigationMenu.Link className={cn(navItemDefaultStyles, "text-white")} href="/"> - <img src="/logo.png" className="h-12" alt={ intl.formatMessage({ id: "topbar.logo_alt_text", defaultMessage: "Damus logo" }) }/> + <img src="/logo_icon_2.png" className="h-12" alt={ intl.formatMessage({ id: "topbar.logo_alt_text", defaultMessage: "Damus logo" }) }/> </NavigationMenu.Link> + </motion.div> + <div className="hidden lg:block"> + <NavigationMenu.List className={cn("inline-flex space-x-6 items-center justify-self-center")}> + {ENABLE_FULL_MENU && (<> + {regularNavItems.map((item, index) => ( + <NavigationMenu.Item key={index}> + <NavigationMenu.Link className={cn(navItemDefaultStyles, "text-white")} href={item.href} target={item.target}> + {topbarItemNameIntl[item.nameIntlId]} + </NavigationMenu.Link> + </NavigationMenu.Item> + ))} + <NavigationMenu.Item> + <NavigationMenu.Link className={cn("text-yellow-500 flex items-center")} href="lightning:lnurl1dp68gurn8ghj7um9dej8xct5wvhxcmmv9uh8wetvdskkkmn0wahz7mrww4excup0v3sk6atnjmrn5a" target="_blank"> + <Zap className="h-4"/> + { intl.formatMessage({ id: "topbar.zap_us", defaultMessage: "Zap Us" }) } + </NavigationMenu.Link> + </NavigationMenu.Item> + </>)} + </NavigationMenu.List> </div> - <NavigationMenu.List className={cn("hidden lg:inline-flex space-x-6 items-center justify-self-center")}> - {ENABLE_FULL_MENU && (<> - {regularNavItems.map((item, index) => ( - <NavigationMenu.Item key={index}> - <NavigationMenu.Link className={cn(navItemDefaultStyles, "text-white")} href={item.href} target={item.target}> - {topbarItemNameIntl[item.nameIntlId]} + <div className="hidden lg:block"> + {customCTA ? customCTA : <> + <Link href={DAMUS_APP_STORE_URL} target="_blank" className="hidden lg:block"> + <Button variant="accent"> + {intl.formatMessage({ id: "topbar.download", defaultMessage: "Download" })} + </Button> + </Link> + </>} + </div> + <button className="lg:hidden" onClick={() => setShowMobileMenu(!showMobileMenu)}> + {showMobileMenu ? <X className="h-8 text-white"/> : <Menu className="h-8 text-white"/>} + </button> + </div> + <AnimatePresence> + {showMobileMenu && ( + <motion.div className="lg:hidden container flex justify-end mb-6" key="mobile-menu"> + <NavigationMenu.List className={cn("flex flex-col gap-6 justify-start items-end text-3xl font-bold p-6")}> + {ENABLE_FULL_MENU && (<> + {regularNavItems.map((item, index) => ( + <NavigationMenu.Item key={index}> + <NavigationMenu.Link className={cn(navItemDefaultStyles, "text-white")} href={item.href} target={item.target}> + {topbarItemNameIntl[item.nameIntlId]} + </NavigationMenu.Link> + </NavigationMenu.Item> + ))} + <NavigationMenu.Item> + <NavigationMenu.Link className={cn("text-yellow-500 flex items-center")} href="lightning:lnurl1dp68gurn8ghj7um9dej8xct5wvhxcmmv9uh8wetvdskkkmn0wahz7mrww4excup0v3sk6atnjmrn5a" target="_blank"> + <Zap className="h-4"/> + { intl.formatMessage({ id: "topbar.zap_us", defaultMessage: "Zap Us" }) } </NavigationMenu.Link> </NavigationMenu.Item> - ))} - <NavigationMenu.Item> - <NavigationMenu.Link className={cn("text-yellow-500 flex items-center")} href="lightning:lnurl1dp68gurn8ghj7um9dej8xct5wvhxcmmv9uh8wetvdskkkmn0wahz7mrww4excup0v3sk6atnjmrn5a" target="_blank"> - <Zap className="h-4"/> - { intl.formatMessage({ id: "topbar.zap_us", defaultMessage: "Zap Us" }) } - </NavigationMenu.Link> - </NavigationMenu.Item> - </>)} - </NavigationMenu.List> - {customCTA ? customCTA : <> - <Link href={DAMUS_APP_STORE_URL} target="_blank"> - <Button variant="accent"> - {intl.formatMessage({ id: "topbar.download", defaultMessage: "Download" })} - </Button> - </Link> - </>} + </>)} + </NavigationMenu.List> + </motion.div> + )} + </AnimatePresence> </NavigationMenu.Root> </motion.div> - ) + </>) } diff --git a/src/components/ui/MarkdownView.tsx b/src/components/ui/MarkdownView.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import Markdown from 'react-markdown' + +export function MarkdownView({ children, className }: { children?: string | null | undefined, className?: string }) { + return ( + <Markdown components={{ + h1: ({node, ...props}) => <h1 className="opacity-90 font-bold text-6xl mb-6" {...props} />, + h2: ({node, ...props}) => <h2 className="opacity-90 font-bold text-4xl mb-4" {...props} />, + h3: ({node, ...props}) => <h3 className="opacity-90 font-bold text-2xl" {...props} />, + ul: ({node, ...props}) => <ul className="opacity-80 list-disc list-inside mb-5" {...props} />, + ol: ({node, ...props}) => <ol className="opacity-80 list-decimal list-inside mb-5" {...props} />, + li: ({node, ...props}) => <li className="opacity-80 m-1" {...props} />, + em: ({node, ...props}) => <i className="italic" {...props} />, + strong: ({node, ...props}) => <strong className="font-bold text-white" {...props} />, + p: ({node, ...props}) => <p className="opacity-80 mb-4" {...props} />, + a: ({node, ...props}) => <a className="text-damuspink-600 underline" {...props} />, + pre: ({node, ...props}) => <pre className="my-2 bg-white/10 overflow-scroll p-3 rounded-lg font-mono" {...props} />, + code: ({node, ...props}) => { + return (<code className="my-2 bg-white/10 overflow-scroll p-1 font-mono rounded-sm" {...props} />) + }, + }} className={className}>{children}</Markdown> + ) +} diff --git a/src/pages/notedeck/install.tsx b/src/pages/notedeck/install.tsx @@ -0,0 +1,42 @@ +import { Inter } from 'next/font/google' +import { IntlProvider, useIntl } from 'react-intl' +import English from "@/../content/compiled-locales/en.json"; +import Japanese from "@/../content/compiled-locales/ja.json"; +import { useEffect } from 'react'; +import { useState } from 'react'; +import { Notedeck } from '@/components/pages/notedeck'; +import { NotedeckInstallPage } from '@/components/pages/notedeck/install'; + +export default function Page() { + // Automatically detect the user's locale based on their browser settings + const [language, setLanguage] = useState("en"); + const [messages, setMessages] = useState(English); + + useEffect(() => { + setLanguage(navigator.language); + }, []); + + useEffect(() => { + switch (language) { + case "en": + setMessages(English); + break; + case "ja": + // TODO: Add Japanese translations and then switch to "Japanese" below + setMessages(English); + break; + default: + setMessages(English); + break; + } + }, [language]); + + return (<> + <IntlProvider + locale={language} + messages={messages} + onError={() => null}> + <NotedeckInstallPage /> + </IntlProvider> + </>) +}