Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ jobs:
packageName: app.scriptio
releaseFiles: ${{ env.AAB_PATH }}
mappingFile: ${{ env.MAPPING_PATH }}
track: alpha
tracks: alpha
status: completed

publish-windows:
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/deploy-staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -275,14 +275,16 @@ jobs:
MAPPING=$(find src-tauri/gen/android/app/build/outputs/mapping -name 'mapping.txt' 2>/dev/null | head -n 1 || true)
echo "MAPPING_PATH=$MAPPING" >> "$GITHUB_ENV"

- name: Publish to Google Play (closed testing)
# `qa` is an internal-testing track: uploads there need no store
# listing, unlike the closed/open tracks the release workflow uses.
- name: Publish to Google Play (internal testing)
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJsonPlainText: ${{ secrets.GOOGLE_SERVICE_ACCOUNT_JSON }}
packageName: app.scriptio.staging
releaseFiles: ${{ env.AAB_PATH }}
mappingFile: ${{ env.MAPPING_PATH }}
track: alpha
tracks: qa
status: completed

publish-windows:
Expand Down
129 changes: 102 additions & 27 deletions landing/components/home/HomePageContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,33 @@ import {
} from "lucide-react";
import Footer from "./Footer";
import Image from "next/image";
import { useState } from "react";
import { AppleIcon, GooglePlayIcon, LinuxIcon, WindowsIcon } from "./PlatformIcons";

// The app lives at the same origin in production (`/projects` is relative), but
// at a different port in local dev. NEXT_PUBLIC_APP_ORIGIN is set to the app's
// dev URL in .env.development and is empty in the production build.
const APP_ORIGIN = process.env.NEXT_PUBLIC_APP_ORIGIN ?? "";

const APP_STORE_URL = "https://apps.apple.com/app/scriptio";
const MICROSOFT_STORE_URL = "https://apps.microsoft.com/detail/9p4m1xphjks1";
const GOOGLE_PLAY_URL = "https://play.google.com/store/apps/details?id=app.scriptio";

const DESKTOP_STORES: StoreLink[] = [
{ href: APP_STORE_URL, name: "App Store", icon: <AppleIcon size={18} /> },
{ href: MICROSOFT_STORE_URL, name: "Microsoft Store", icon: <WindowsIcon size={16} /> },
];

const MOBILE_STORES: StoreLink[] = [
{ href: APP_STORE_URL, name: "App Store", icon: <AppleIcon size={18} /> },
{ href: GOOGLE_PLAY_URL, name: "Google Play", icon: <GooglePlayIcon size={17} /> },
];

export default function HomePageContainer() {
// Which platform CTA has its store menu open (hover on pointer devices, tap
// on touch ones — CSS handles hover/focus, this handles the tap).
const [openMenu, setOpenMenu] = useState<"desktop" | "mobile" | null>(null);

return (
<div className={styles.wrapper}>
<div className={styles.gradientBackground}></div>
Expand Down Expand Up @@ -71,36 +91,37 @@ export default function HomePageContainer() {
{/* Layer 1.3: Platform CTAs */}
<div className={styles.heroContent}>
<div className={styles.ctaRow}>
<a
href="https://apps.apple.com/app/scriptio"
target="_blank"
className={styles.ctaPlatform}
>
<svg viewBox="0 0 24 24" width="22" height="22" fill="currentColor">
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
</svg>
<div className={styles.ctaPlatformText}>
<span className={styles.ctaPlatformLabel}>Get it on</span>
<span className={styles.ctaPlatformName}>App Store</span>
</div>
</a>
<PlatformMenuCta
name="Desktop"
icons={
<>
<AppleIcon size={20} />
<WindowsIcon size={17} />
<LinuxIcon size={20} />
</>
}
stores={DESKTOP_STORES}
isOpen={openMenu === "desktop"}
onToggle={(open) => setOpenMenu(open ? "desktop" : null)}
/>

<a
href="https://apps.microsoft.com/detail/9p4m1xphjks1"
target="_blank"
className={styles.ctaPlatform}
>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<path d="M11.4 24H0V12.6h11.4V24zM24 24H12.6V12.6H24V24zM11.4 11.4H0V0h11.4v11.4zM24 11.4H12.6V0H24v11.4z" />
</svg>
<div className={styles.ctaPlatformText}>
<span className={styles.ctaPlatformLabel}>Get it on</span>
<span className={styles.ctaPlatformName}>Microsoft Store</span>
</div>
</a>
<PlatformMenuCta
name="Mobile"
icons={
<>
<AppleIcon size={20} />
<GooglePlayIcon size={18} />
</>
}
stores={MOBILE_STORES}
isOpen={openMenu === "mobile"}
onToggle={(open) => setOpenMenu(open ? "mobile" : null)}
/>

<a href={`${APP_ORIGIN}/projects`} className={styles.ctaPlatform}>
<Globe size={22} />
<div className={styles.ctaPlatformIcons}>
<Globe size={20} />
</div>
<div className={styles.ctaPlatformText}>
<span className={styles.ctaPlatformLabel}>Open in</span>
<span className={styles.ctaPlatformName}>Browser</span>
Expand Down Expand Up @@ -459,6 +480,60 @@ export default function HomePageContainer() {
);
}

interface StoreLink {
href: string;
name: string;
icon: React.ReactNode;
}

interface PlatformMenuCtaProps {
name: string;
icons: React.ReactNode;
stores: StoreLink[];
isOpen: boolean;
onToggle: (open: boolean) => void;
}

const PlatformMenuCta: React.FC<PlatformMenuCtaProps> = ({ name, icons, stores, isOpen, onToggle }) => (
<div
className={`${styles.ctaMenu} ${isOpen ? styles.ctaMenuOpen : ""}`}
onMouseEnter={() => onToggle(true)}
onMouseLeave={() => onToggle(false)}
onKeyDown={(e) => e.key === "Escape" && onToggle(false)}
>
<button
type="button"
className={styles.ctaPlatform}
aria-haspopup="menu"
aria-expanded={isOpen}
onClick={() => onToggle(!isOpen)}
>
<div className={styles.ctaPlatformIcons}>{icons}</div>
<div className={styles.ctaPlatformText}>
<span className={styles.ctaPlatformLabel}>Download for</span>
<span className={styles.ctaPlatformName}>{name}</span>
</div>
</button>

<div className={styles.ctaDropdown} role="menu" aria-label={`${name} downloads`}>
<div className={styles.ctaDropdownPanel}>
{stores.map((store) => (
<a
key={store.name}
href={store.href}
target="_blank"
role="menuitem"
className={styles.ctaDropdownItem}
>
{store.icon}
<span>{store.name}</span>
</a>
))}
</div>
</div>
</div>
);

interface ScriptStripeProps {
children: React.ReactNode;
speed?: "slow" | "medium" | "fast";
Expand Down
72 changes: 69 additions & 3 deletions landing/components/home/Landing.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -253,26 +253,92 @@
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1.5rem;
padding: 0.75rem 1.4rem;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 14px;
background-color: rgba(255, 255, 255, 0.06);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
color: var(--primary-text);
text-decoration: none;
cursor: pointer;
transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
}

.ctaPlatform:hover {
transform: translateY(-2px);
border-color: rgba(255, 255, 255, 0.35);
background-color: rgba(255, 255, 255, 0.1);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
color: var(--primary-text);
}

/* Store menu (desktop & mobile CTAs) */
.ctaMenu {
position: relative;
display: flex;
}

.ctaDropdown {
position: absolute;
top: 100%;
left: 50%;
min-width: 100%;
padding-top: 0.6rem; /* keeps hover alive across the gap */
transform: translate(-50%, -6px);
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: all 0.25s cubic-bezier(0.25, 0.46, 0.45, 0.94);
z-index: 3;
}

.ctaMenu:hover .ctaDropdown,
.ctaMenu:focus-within .ctaDropdown,
.ctaMenuOpen .ctaDropdown {
transform: translate(-50%, 0);
opacity: 1;
visibility: visible;
pointer-events: auto;
}

.ctaDropdownPanel {
display: flex;
flex-direction: column;
gap: 0.15rem;
padding: 0.4rem;
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 12px;
background-color: rgba(255, 255, 255, 0.06);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
}

.ctaDropdownItem {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.5rem 0.8rem;
border-radius: 8px;
color: var(--primary-text);
font-size: 0.9rem;
white-space: nowrap;
text-decoration: none;
transition: background-color 0.15s ease;
}

.ctaDropdownItem:hover {
background-color: rgba(255, 255, 255, 0.12);
color: var(--primary-text);
}

.ctaPlatformIcons {
display: flex;
align-items: center;
gap: 0.45rem;
}

.ctaPlatformText {
display: flex;
flex-direction: column;
Expand Down Expand Up @@ -300,7 +366,7 @@
}

.ctaPlatform {
width: 220px;
width: 240px;
}
}

Expand Down
33 changes: 33 additions & 0 deletions landing/components/home/PlatformIcons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Platform glyphs for the landing page CTAs. lucide ships no brand icons, so
* these are inlined from Simple Icons (https://simpleicons.org) rather than
* pulling in a whole icon dependency for a handful of paths.
*/

interface PlatformIconProps {
size?: number;
}

export const AppleIcon: React.FC<PlatformIconProps> = ({ size = 20 }) => (
<svg viewBox="0 0 24 24" width={size} height={size} fill="currentColor" aria-hidden="true">
<path d="M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.8-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" />
</svg>
);

export const WindowsIcon: React.FC<PlatformIconProps> = ({ size = 18 }) => (
<svg viewBox="0 0 24 24" width={size} height={size} fill="currentColor" aria-hidden="true">
<path d="M11.4 24H0V12.6h11.4V24zM24 24H12.6V12.6H24V24zM11.4 11.4H0V0h11.4v11.4zM24 11.4H12.6V0H24v11.4z" />
</svg>
);

export const LinuxIcon: React.FC<PlatformIconProps> = ({ size = 20 }) => (
<svg viewBox="0 0 24 24" width={size} height={size} fill="currentColor" aria-hidden="true">
<path d="M12.504 0c-.155 0-.315.008-.48.021-4.226.333-3.105 4.807-3.17 6.298-.076 1.092-.3 1.953-1.05 3.02-.885 1.051-2.127 2.75-2.716 4.521-.278.832-.41 1.684-.287 2.489a.424.424 0 00-.11.135c-.26.268-.45.6-.663.839-.199.199-.485.267-.797.4-.313.136-.658.269-.864.68-.09.189-.136.394-.132.602 0 .199.027.4.055.536.058.399.116.728.04.97-.249.68-.28 1.145-.106 1.484.174.334.535.47.94.601.81.2 1.91.135 2.774.6.926.466 1.866.67 2.616.47.526-.116.97-.464 1.208-.946.587-.003 1.23-.269 2.26-.334.699-.058 1.574.267 2.577.2.025.134.063.198.114.333l.003.003c.391.778 1.113 1.132 1.884 1.071.771-.06 1.592-.536 2.257-1.306.631-.765 1.683-1.084 2.378-1.503.348-.199.629-.469.649-.853.023-.4-.2-.811-.714-1.376v-.097l-.003-.003c-.17-.2-.25-.535-.338-.926-.085-.401-.182-.786-.492-1.046h-.003c-.059-.054-.123-.067-.188-.135a.357.357 0 00-.19-.064c.431-1.278.264-2.55-.173-3.694-.533-1.41-1.465-2.638-2.175-3.483-.796-1.005-1.576-1.957-1.56-3.368.026-2.152.236-6.133-3.544-6.139zm.529 3.405h.013c.213 0 .396.062.584.198.19.135.33.332.438.533.105.259.158.459.166.724 0-.02.006-.04.006-.06v.105a.086.086 0 01-.004-.021l-.004-.024a1.807 1.807 0 01-.15.706.953.953 0 01-.213.335.71.71 0 00-.088-.042c-.104-.045-.198-.064-.284-.133a1.312 1.312 0 00-.22-.066c.05-.06.146-.133.183-.198.053-.128.082-.264.088-.402v-.02a1.21 1.21 0 00-.061-.4c-.045-.134-.101-.2-.183-.333-.084-.066-.167-.132-.267-.132h-.016c-.093 0-.176.03-.262.132a.8.8 0 00-.205.334 1.18 1.18 0 00-.09.4v.019c.002.089.008.179.02.267-.193-.067-.438-.135-.607-.202a1.635 1.635 0 01-.018-.2v-.02a1.772 1.772 0 01.15-.768c.082-.22.232-.406.43-.533a.985.985 0 01.594-.2zm-2.962.059h.036c.142 0 .27.048.399.135.146.129.264.288.344.465.09.199.14.4.153.667v.004c.007.134.006.2-.002.266v.08c-.03.007-.056.018-.083.024-.152.055-.274.135-.393.2.012-.09.013-.18.003-.267v-.015c-.012-.133-.04-.2-.082-.333a.613.613 0 00-.166-.267.248.248 0 00-.183-.064h-.021c-.071.006-.13.04-.186.132a.552.552 0 00-.12.27.944.944 0 00-.023.33v.015c.012.135.037.2.08.334.046.134.098.2.166.268.01.009.02.018.034.024-.07.057-.117.07-.176.136a.304.304 0 01-.131.068 2.62 2.62 0 01-.275-.402 1.772 1.772 0 01-.155-.667 1.759 1.759 0 01.08-.668 1.43 1.43 0 01.283-.535c.128-.133.26-.2.418-.2zm1.37 1.706c.332 0 .733.065 1.216.399.293.2.523.269 1.052.468h.003c.255.136.405.266.478.399v-.131a.571.571 0 01.016.47c-.123.31-.516.643-1.063.842v.002c-.268.135-.501.333-.775.465-.276.135-.588.292-1.012.267a1.139 1.139 0 01-.448-.067 3.566 3.566 0 01-.322-.198c-.195-.135-.363-.332-.612-.465v-.005h-.005c-.4-.246-.616-.512-.686-.71-.07-.268-.005-.47.193-.6.224-.135.38-.271.483-.336.104-.074.143-.102.176-.131h.002v-.003c.169-.202.436-.47.839-.601.139-.036.294-.065.466-.065zm2.8 2.142c.358 1.417 1.196 3.475 1.735 4.473.286.534.855 1.659 1.102 3.024.156-.005.33.018.513.064.646-1.671-.546-3.467-1.089-3.966-.22-.2-.232-.335-.123-.335.59.534 1.365 1.572 1.646 2.757.13.535.16 1.104.021 1.67.067.028.135.06.205.067 1.032.534 1.413.938 1.23 1.537v-.043c-.06-.003-.12 0-.18 0h-.016c.151-.467-.182-.825-1.065-1.224-.915-.4-1.646-.336-1.77.465-.008.043-.013.066-.018.135-.068.023-.139.053-.209.064-.43.268-.662.669-.793 1.187-.13.533-.17 1.156-.205 1.869v.003c-.02.334-.17.838-.319 1.35-1.5 1.072-3.58 1.538-5.348.334a2.645 2.645 0 00-.402-.533 1.45 1.45 0 00-.275-.333c.182 0 .338-.03.465-.067a.615.615 0 00.314-.334c.108-.267 0-.697-.345-1.163-.345-.467-.931-.995-1.788-1.521-.63-.4-.986-.87-1.15-1.396-.165-.534-.143-1.085-.015-1.645.245-1.07.873-2.11 1.274-2.763.107-.065.037.135-.408.974-.396.751-1.14 2.497-.122 3.854a8.123 8.123 0 01.647-2.876c.564-1.278 1.743-3.504 1.836-5.268.048.036.217.135.289.202.218.133.38.333.59.465.21.201.477.335.876.335.039.003.075.006.11.006.412 0 .73-.134.997-.268.29-.134.52-.334.74-.4h.005c.467-.135.835-.402 1.044-.7zm2.185 8.958c.037.6.343 1.245.882 1.377.588.134 1.434-.333 1.791-.765l.211-.01c.315-.007.577.01.847.268l.003.003c.208.199.305.53.391.876.085.4.154.78.409 1.066.486.527.645.906.636 1.14l.003-.007v.018l-.003-.012c-.015.262-.185.396-.498.595-.63.401-1.746.712-2.457 1.57-.618.737-1.37 1.14-2.036 1.191-.664.053-1.237-.2-1.574-.898l-.005-.003c-.21-.4-.12-1.025.056-1.69.176-.668.428-1.344.463-1.897.037-.714.076-1.335.195-1.814.12-.465.308-.797.641-.984l.045-.022zm-10.814.049h.01c.053 0 .105.005.157.014.376.055.706.333 1.023.752l.91 1.664.003.003c.243.533.754 1.064 1.189 1.637.434.598.77 1.131.729 1.57v.006c-.057.744-.48 1.148-1.125 1.294-.645.135-1.52.002-2.395-.464-.968-.536-2.118-.469-2.857-.602-.369-.066-.61-.2-.723-.4-.11-.2-.113-.602.123-1.23v-.004l.002-.003c.117-.334.03-.752-.027-1.118-.055-.401-.083-.71.043-.94.16-.334.396-.4.69-.533.294-.135.64-.202.915-.47h.002v-.002c.256-.268.445-.601.668-.838.19-.201.38-.336.663-.336zm7.159-9.074c-.435.201-.945.535-1.488.535-.542 0-.97-.267-1.28-.466-.154-.134-.28-.268-.373-.335-.164-.134-.144-.333-.074-.333.109.016.129.134.199.2.096.066.215.2.36.333.292.2.68.467 1.167.467.485 0 1.053-.267 1.398-.466.195-.135.445-.334.648-.467.156-.136.149-.267.279-.267.128.016.034.134-.147.332a8.097 8.097 0 01-.69.468zm-1.082-1.583V5.64c-.006-.02.013-.042.029-.05.074-.043.18-.027.26.004.063 0 .16.067.15.135-.006.049-.085.066-.135.066-.055 0-.092-.043-.141-.068-.052-.018-.146-.008-.163-.065zm-.551 0c-.02.058-.113.049-.166.066-.047.025-.086.068-.14.068-.05 0-.13-.02-.136-.068-.01-.066.088-.133.15-.133.08-.031.184-.047.259-.005.019.009.036.03.03.05v.02h.003z" />
</svg>
);

export const GooglePlayIcon: React.FC<PlatformIconProps> = ({ size = 18 }) => (
<svg viewBox="0 0 24 24" width={size} height={size} fill="currentColor" aria-hidden="true">
<path d="M22.018 13.298l-3.919 2.218-3.515-3.493 3.543-3.521 3.891 2.202a1.49 1.49 0 0 1 0 2.594zM1.337.924a1.486 1.486 0 0 0-.112.568v21.017c0 .217.045.419.124.6l11.155-11.087L1.337.924zm12.207 10.065l3.258-3.238L3.45.195a1.466 1.466 0 0 0-.946-.179l11.04 10.973zm0 2.067l-11 10.933c.298.036.612-.016.906-.183l13.324-7.54-3.23-3.21z" />
</svg>
);
Loading