diff --git a/docs/src/components/starlight/Head.astro b/docs/src/components/starlight/Head.astro
index 957280dc..86aa6449 100644
--- a/docs/src/components/starlight/Head.astro
+++ b/docs/src/components/starlight/Head.astro
@@ -1,11 +1,18 @@
---
-/**
- * Custom Head component for RocketSim docs
- * Includes proper favicon and meta tags matching the main site
- */
import Default from "@astrojs/starlight/components/Head.astro";
import PlausibleAnalytics from "@/components/PlausibleAnalytics.astro";
+import StructuredData from "@/components/StructuredData.astro";
import config from "@/config/config.json";
+import faqEntries from "@/data/faq-support.json";
+
+const { entry } = Astro.props;
+const title = entry?.data?.title ?? "RocketSim Docs";
+const description = entry?.data?.description ?? "";
+const slug = entry?.slug ?? "";
+const pageUrl = `${config.site.base_url}/${slug}`;
+
+const isContentPage = title !== "RocketSim Docs" && title !== "404";
+const isFaqPage = slug.endsWith("support/faq");
const ogImage = `${config.site.base_url}${config.metadata.meta_image}`;
---
@@ -32,4 +39,19 @@ const ogImage = `${config.site.base_url}${config.metadata.meta_image}`;
+{
+ isContentPage && !isFaqPage && (
+
+ )
+}
+
+{
+ isFaqPage && (
+
+ )
+}
+
diff --git a/docs/src/data/faq-support.json b/docs/src/data/faq-support.json
new file mode 100644
index 00000000..7e71d7f1
--- /dev/null
+++ b/docs/src/data/faq-support.json
@@ -0,0 +1,58 @@
+[
+ {
+ "question": "Do you offer out-of-the App Store distribution?",
+ "answer": "Yes, we do. Get in touch via support@rocketsim.app."
+ },
+ {
+ "question": "Are there non-recurring subscriptions as well?",
+ "answer": "Yes, you can consider buying Team Licenses at rocketsim.app/team-insights."
+ },
+ {
+ "question": "I can't reimburse App Store subscriptions, is there an alternative?",
+ "answer": "Yes, we offer Team Licenses at rocketsim.app/team-insights."
+ },
+ {
+ "question": "Do you provide the option for commercial or team licenses?",
+ "answer": "Yes, check out Team Licenses at rocketsim.app/team-insights."
+ },
+ {
+ "question": "Can I buy a lifetime license?",
+ "answer": "No, but you can join SwiftLee Weekly's referral program to get a lifetime RocketSim license. Just join the newsletter and follow the instructions in the email."
+ },
+ {
+ "question": "Why is my video not accepted by App Store Connect?",
+ "answer": "The videos are optimized for App Previews following Apple's specifications. App Store Connect does not accept each device, so your selected Simulator could not support App Previews. Make sure to use a Simulator matching a device from the App Preview specifications."
+ },
+ {
+ "question": "Why wouldn't I just use xcrun simctl?",
+ "answer": "RocketSim uses xcrun simctl as well, but it enhances the output and provides interfaces for quicker access. Recordings, for example, are enhanced with touches and device bezels that you won't get through the command line tools."
+ },
+ {
+ "question": "Why does RocketSim need screen recording permissions?",
+ "answer": "RocketSim is sandboxed and can't read NSWindow titles without screen recording permissions. RocketSim needs to read the title of the Simulator windows to determine the currently active Simulator."
+ },
+ {
+ "question": "Where can I follow active development?",
+ "answer": "RocketSim is developed by Antoine van der Lee. You can follow him or the official RocketSim Twitter Account for updates about development."
+ },
+ {
+ "question": "Where can I report bugs or feature requests?",
+ "answer": "Issues and feature requests are managed on GitHub at github.com/AvdLee/RocketSimApp/issues."
+ },
+ {
+ "question": "I only get JPEG images, how can I get PNG images again?",
+ "answer": "You've likely enabled App Store Connect (ASC) Optimization. ASC requires JPEG images without alpha layer. Disable the option and you should get PNGs again."
+ },
+ {
+ "question": "Why are my iPad captures upside-down?",
+ "answer": "RocketSim cannot detect landscape-left or landscape-right and defaults to one landscape rotation. The fix is simple: rotate your Simulator twice and restart the recording."
+ },
+ {
+ "question": "Can I create transparent captures?",
+ "answer": "Yes, make sure to disable App Preview Optimized and set your background color to transparent."
+ },
+ {
+ "question": "Network Speed Control isn't working for me, what can I do?",
+ "answer": "This is usually caused by missing system permissions. Quit Xcode, all Simulators, and RocketSim, then reopen them. Open System Settings → Privacy & Security and approve any pending RocketSim permissions. On macOS Sequoia and later, enable the Network Extension in System Settings → General → Login Items & Extensions."
+ }
+]
diff --git a/docs/src/layouts/components/StructuredData.astro b/docs/src/layouts/components/StructuredData.astro
index 6af253c6..a5123752 100644
--- a/docs/src/layouts/components/StructuredData.astro
+++ b/docs/src/layouts/components/StructuredData.astro
@@ -39,6 +39,24 @@ type OfferProps = {
};
};
+type TechArticleProps = {
+ type: "techArticle";
+ techArticle: {
+ title: string;
+ description: string;
+ url: string;
+ };
+};
+
+type FAQPageProps = {
+ type: "faqPage";
+ faqEntries: Array<{
+ question: string;
+ answer: string;
+ }>;
+ url: string;
+};
+
type StaticProps = {
type: "static";
};
@@ -48,7 +66,7 @@ type WebsiteProps = {
};
// prettier-ignore
-export type Props = ArticleProps | ProductProps | OfferProps | StaticProps | WebsiteProps;
+export type Props = ArticleProps | ProductProps | OfferProps | TechArticleProps | FAQPageProps | StaticProps | WebsiteProps;
// TypeScript interfaces for JSON-LD schemas
interface SchemaBase {
@@ -157,6 +175,31 @@ interface OfferSchema extends SchemaBase {
description?: string;
}
+interface TechArticleSchema extends SchemaBase {
+ "@type": "TechArticle";
+ headline: string;
+ description: string;
+ url: string;
+ author: OrganizationSchema;
+ publisher: OrganizationSchema;
+ mainEntityOfPage: {
+ "@type": "WebPage";
+ "@id": string;
+ };
+}
+
+interface FAQPageSchema extends SchemaBase {
+ "@type": "FAQPage";
+ mainEntity: Array<{
+ "@type": "Question";
+ name: string;
+ acceptedAnswer: {
+ "@type": "Answer";
+ text: string;
+ };
+ }>;
+}
+
const { type } = Astro.props;
// Author data (hardcoded - single author blog)
@@ -341,6 +384,100 @@ if (type === "product") {
schemas.push(breadcrumbSchema);
}
+if (type === "techArticle") {
+ const { techArticle } = Astro.props;
+
+ const techArticleSchema: TechArticleSchema = {
+ "@context": "https://schema.org",
+ "@type": "TechArticle",
+ headline: techArticle.title,
+ description: techArticle.description,
+ url: techArticle.url,
+ author: organizationData,
+ publisher: {
+ ...organizationData,
+ logo: `${config.site.base_url}/images/rocketsim-app-icon.png`,
+ },
+ mainEntityOfPage: {
+ "@type": "WebPage",
+ "@id": techArticle.url,
+ },
+ };
+ schemas.push(techArticleSchema);
+
+ const breadcrumbSchema: BreadcrumbListSchema = {
+ "@context": "https://schema.org",
+ "@type": "BreadcrumbList",
+ "@id": `${techArticle.url}#breadcrumb`,
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Home",
+ item: config.site.base_url,
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: "Docs",
+ item: `${config.site.base_url}/docs`,
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ name: techArticle.title,
+ item: techArticle.url,
+ },
+ ],
+ };
+ schemas.push(breadcrumbSchema);
+}
+
+if (type === "faqPage") {
+ const { faqEntries, url } = Astro.props;
+
+ const faqPageSchema: FAQPageSchema = {
+ "@context": "https://schema.org",
+ "@type": "FAQPage",
+ mainEntity: faqEntries.map((faq) => ({
+ "@type": "Question" as const,
+ name: faq.question,
+ acceptedAnswer: {
+ "@type": "Answer" as const,
+ text: faq.answer,
+ },
+ })),
+ };
+ schemas.push(faqPageSchema);
+
+ const breadcrumbSchema: BreadcrumbListSchema = {
+ "@context": "https://schema.org",
+ "@type": "BreadcrumbList",
+ "@id": `${url}#breadcrumb`,
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Home",
+ item: config.site.base_url,
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: "Docs",
+ item: `${config.site.base_url}/docs`,
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ name: "FAQ",
+ item: url,
+ },
+ ],
+ };
+ schemas.push(breadcrumbSchema);
+}
+
if (type === "offer") {
const { offers } = Astro.props;
// 1. SoftwareApplication with individual Offer schemas for each tier
@@ -439,7 +576,7 @@ if (type === "offer") {
))
}