diff --git a/packages/client/public/favicon-32.png b/packages/client/public/favicon-32.png new file mode 100644 index 000000000..39e0608ac Binary files /dev/null and b/packages/client/public/favicon-32.png differ diff --git a/packages/client/public/favicon.ico b/packages/client/public/favicon.ico index 3aa3c7a42..ba523a969 100644 Binary files a/packages/client/public/favicon.ico and b/packages/client/public/favicon.ico differ diff --git a/packages/client/public/index.html b/packages/client/public/index.html index 193ad2233..77e42165a 100644 --- a/packages/client/public/index.html +++ b/packages/client/public/index.html @@ -2,14 +2,16 @@ - + + + + + - - diff --git a/packages/client/public/logo192.png b/packages/client/public/logo192.png index fc44b0a37..ab8b2d697 100644 Binary files a/packages/client/public/logo192.png and b/packages/client/public/logo192.png differ diff --git a/packages/client/public/logo512.png b/packages/client/public/logo512.png index a4e47a654..bff31fa2e 100644 Binary files a/packages/client/public/logo512.png and b/packages/client/public/logo512.png differ diff --git a/packages/client/public/manifest.json b/packages/client/public/manifest.json index 5d91ccf23..65823f317 100644 --- a/packages/client/public/manifest.json +++ b/packages/client/public/manifest.json @@ -1,6 +1,7 @@ { "short_name": "SeaSketch", "name": "SeaSketch", + "description": "The platform for marine spatial planning.", "icons": [ { "src": "favicon.ico", @@ -18,8 +19,8 @@ "sizes": "512x512" } ], - "start_url": ".", + "start_url": "/", "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" + "theme_color": "#267588", + "background_color": "#020617" } diff --git a/packages/client/public/uses/data-governance.png b/packages/client/public/uses/data-governance.png deleted file mode 100644 index b928018f6..000000000 Binary files a/packages/client/public/uses/data-governance.png and /dev/null differ diff --git a/packages/client/public/uses/drag-and-drop-data.png b/packages/client/public/uses/drag-and-drop-data.png deleted file mode 100644 index f07809c1a..000000000 Binary files a/packages/client/public/uses/drag-and-drop-data.png and /dev/null differ diff --git a/packages/client/public/uses/fast-beautiful-maps-cursor.png b/packages/client/public/uses/fast-beautiful-maps-cursor.png deleted file mode 100644 index f94e363fd..000000000 Binary files a/packages/client/public/uses/fast-beautiful-maps-cursor.png and /dev/null differ diff --git a/packages/client/public/uses/fast-beautiful-maps-tiles.png b/packages/client/public/uses/fast-beautiful-maps-tiles.png deleted file mode 100644 index 32d967c67..000000000 Binary files a/packages/client/public/uses/fast-beautiful-maps-tiles.png and /dev/null differ diff --git a/packages/client/public/uses/fast-beautiful-maps.png b/packages/client/public/uses/fast-beautiful-maps.png deleted file mode 100644 index d863209a0..000000000 Binary files a/packages/client/public/uses/fast-beautiful-maps.png and /dev/null differ diff --git a/packages/client/public/uses/graphical-cartography-tools.png b/packages/client/public/uses/graphical-cartography-tools.png deleted file mode 100644 index d706b4839..000000000 Binary files a/packages/client/public/uses/graphical-cartography-tools.png and /dev/null differ diff --git a/packages/client/public/uses/map-portal-hero.png b/packages/client/public/uses/map-portal-hero.png deleted file mode 100644 index 649499d54..000000000 Binary files a/packages/client/public/uses/map-portal-hero.png and /dev/null differ diff --git a/packages/client/public/uses/outer-reef-flat.png b/packages/client/public/uses/outer-reef-flat.png deleted file mode 100644 index 4e057cbec..000000000 Binary files a/packages/client/public/uses/outer-reef-flat.png and /dev/null differ diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index ded6d6549..534395939 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -1,5 +1,11 @@ import React, { Suspense, useEffect, useMemo, useState } from "react"; -import { Switch, Route, Redirect, useLocation } from "react-router-dom"; +import { + Switch, + Route, + Redirect, + useHistory, + useLocation, +} from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; import SignInPage from "./SignInPage"; import ProjectsPage from "./homepage/ProjectsPage"; @@ -21,11 +27,7 @@ import DeveloperApiPage from "./DeveloperAPIPage"; import { Helmet } from "react-helmet"; import LandingPage from "./homepage/LandingPage"; import TeamPage from "./homepage/TeamPage"; -import { - MapPortalHostingPage, - OceanUseSurveysPage, - SketchingAndAnalysisPage, -} from "./homepage/useCases"; +import SiteHelmet from "./homepage/SiteHelmet"; import { CaseStudiesIndex, AzoresCaseStudy, @@ -72,6 +74,24 @@ const LazySubmitOfflineResponsesPage = React.lazy( /* webpackChunkName: "OfflineSurveys" */ "./offline/SubmitOfflineResponsesPage" ) ); +const LazyMapPortalHostingPage = React.lazy( + () => + import( + /* webpackChunkName: "MapPortalHosting" */ "./homepage/useCases/MapPortalHosting" + ) +); +const LazyOceanUseSurveysPage = React.lazy( + () => + import( + /* webpackChunkName: "OceanUseSurveys" */ "./homepage/useCases/OceanUseSurveys" + ) +); +const LazySketchingAndAnalysisPage = React.lazy( + () => + import( + /* webpackChunkName: "SketchingAndAnalysis" */ "./homepage/useCases/SketchingAndAnalysis" + ) +); const LazyFullScreenOfflinePage = React.lazy( () => @@ -103,10 +123,33 @@ const LazySuperuserDashboard = React.lazy( () => import(/* webpackChunkName: "SuperuserDashboard" */ "./Dashboard") ); +/** Public marketing pages that should start at the top on forward navigation. */ +function shouldScrollMarketingPageToTop(pathname: string, hash: string) { + if (hash) { + return false; + } + if (pathname.startsWith("/uses/") || pathname.startsWith("/case-studies")) { + return true; + } + return [ + "/", + "/projects", + "/api", + "/team", + "/new-project", + "/terms-of-use", + "/privacy-policy", + "/signin", + "/submit-offline-surveys", + "/account-settings", + ].includes(pathname); +} + function App() { const { user } = useAuth0(); useTranslation("homepage"); const [error, setError] = useState(null); + const history = useHistory(); const location = useLocation(); const isUseCaseRoute = location.pathname.startsWith("/uses/"); const isDarkRoutes = @@ -147,13 +190,23 @@ function App() { } }, [location.pathname, location.hash]); - // Ensure use-case detail pages always start at the top when navigating - // between routes in the SPA. + // Scroll marketing pages to top on forward navigation only. Using pathname + // in a mount effect also ran on HMR remounts and POP (back/forward), which + // fought browser scroll restoration during development and when using the + // back button. Hash navigations (e.g. /#use-cases) are handled separately. useEffect(() => { - if (location.pathname.startsWith("/uses/")) { - window.scrollTo({ top: 0, left: 0, behavior: "auto" }); - } - }, [location.pathname]); + return history.listen((nextLocation, action) => { + if ( + (action === "PUSH" || action === "REPLACE") && + shouldScrollMarketingPageToTop( + nextLocation.pathname, + nextLocation.hash + ) + ) { + window.scrollTo({ top: 0, left: 0, behavior: "auto" }); + } + }); + }, [history]); const frontOfTheHouse = [ "/signin", @@ -254,13 +307,13 @@ function App() { - + - + - + @@ -298,10 +351,11 @@ function App() { - - SeaSketch - - + {/*

{t( "SeaSketch Supports Collaborative Planning for our Oceans" diff --git a/packages/client/src/DeveloperAPIPage.tsx b/packages/client/src/DeveloperAPIPage.tsx index d052faa0c..1b9e27463 100644 --- a/packages/client/src/DeveloperAPIPage.tsx +++ b/packages/client/src/DeveloperAPIPage.tsx @@ -2,6 +2,7 @@ /* eslint-disable i18next/no-literal-string */ import { Trans } from "react-i18next"; +import SiteHelmet from "./homepage/SiteHelmet"; export default function DeveloperApiPage() { return ( @@ -9,6 +10,11 @@ export default function DeveloperApiPage() { className="bg-gray-800 pt-12" style={{ minHeight: "calc(100vh - 64px)" }} > +
diff --git a/packages/client/src/header/Header.tsx b/packages/client/src/header/Header.tsx index 8fdbb09fb..c12919517 100644 --- a/packages/client/src/header/Header.tsx +++ b/packages/client/src/header/Header.tsx @@ -10,11 +10,6 @@ import ProfileControl from "./ProfileControl"; import useCurrentProjectMetadata from "../useCurrentProjectMetadata"; import { useCaseLinks } from "../homepage/useCases"; -// Temporary launch mode: -// Keep Features as a simple anchor to `/#use-cases` for now. -// To restore the dropdown with all use case pages, set this back to `true`. -const enableFeaturesDropdown = false; - const navigationLinks = [ { to: "/projects", @@ -134,7 +129,7 @@ export default function Header() { {
{navigationLinks.map((link) => - link.to === "/features" && enableFeaturesDropdown ? ( + link.to === "/features" ? ( - ) : link.to === "/features" ? ( - - {link.label} - ) : ( {navigationLinks.map((link) => - link.to === "/features" && enableFeaturesDropdown ? ( + link.to === "/features" ? (
- {link.label} - + {useCaseLinks.map((feature) => ( setProfileModalOpen(false)} + className="block w-full text-left px-8 py-2 text-sm leading-5 text-gray-600 hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" > {feature.navLabel} ))}
- ) : link.to === "/features" ? ( - - {link.label} - ) : ( Integrates with Esri and open-source services - {publishedUseCaseLinks[0].readMoreLabel} + {useCaseLinks[0].readMoreLabel}
@@ -418,16 +418,12 @@ export default function LandingPage() {
  • Offline data collection
  • Understand ocean uses by sector
  • - {enableAllUseCaseLinks ? ( - // Re-enable this once Ocean Use Surveys page is complete. - // Make sure `enableAllUseCaseLinks` is set to true in `homepage/useCases/index.ts`. - - Read more about Ocean Use Surveys - - ) : null} + + {useCaseLinks[1].readMoreLabel} +
    @@ -475,16 +471,12 @@ export default function LandingPage() {
  • Online collaboration tools and discussion forums
  • Export products to GIS and Excel
  • - {/* {enableAllUseCaseLinks ? ( - // Re-enable this once Sketching and Analysis page is complete. - // Make sure `enableAllUseCaseLinks` is set to true in `homepage/useCases/index.ts`. - - Read more about Sketching and Analysis - - ) : null} */} + + {useCaseLinks[2].readMoreLabel} + diff --git a/packages/client/src/homepage/NewProjectPage.tsx b/packages/client/src/homepage/NewProjectPage.tsx index e63a96791..41579205a 100644 --- a/packages/client/src/homepage/NewProjectPage.tsx +++ b/packages/client/src/homepage/NewProjectPage.tsx @@ -6,7 +6,7 @@ import NewProjectForm from "./NewProjectForm"; import { Link } from "react-router-dom"; import { useMeQuery, useVerifyEmailMutation } from "../generated/graphql"; import Skeleton from "../components/Skeleton"; -import { Helmet } from "react-helmet"; +import SiteHelmet from "./SiteHelmet"; const logos = [ { @@ -55,14 +55,11 @@ export default function NewProjectPage() { const { data, loading, error } = useMeQuery(); return (
    - - Create a SeaSketch Project - - - +
    diff --git a/packages/client/src/homepage/ProjectsPage.tsx b/packages/client/src/homepage/ProjectsPage.tsx index 11f4674d7..d89644c63 100644 --- a/packages/client/src/homepage/ProjectsPage.tsx +++ b/packages/client/src/homepage/ProjectsPage.tsx @@ -7,7 +7,7 @@ import { Link, useLocation } from "react-router-dom"; import Skeleton from "../components/Skeleton"; import { useProjectListingQuery } from "../generated/graphql"; import { useTranslatedProps } from "../components/TranslatedPropControl"; -import { Helmet } from "react-helmet"; +import SiteHelmet from "./SiteHelmet"; import ProjectsMap from "./ProjectsMap"; import ProjectSearchBar from "./ProjectSearchBar"; import { caseStudies } from "./caseStudies"; @@ -180,14 +180,11 @@ export default function ProjectsPage() {
    - - SeaSketch Projects - - - +

    diff --git a/packages/client/src/homepage/SiteHelmet.tsx b/packages/client/src/homepage/SiteHelmet.tsx new file mode 100644 index 000000000..05b0ba8fc --- /dev/null +++ b/packages/client/src/homepage/SiteHelmet.tsx @@ -0,0 +1,42 @@ +/* eslint-disable i18next/no-literal-string */ +import { Helmet } from "react-helmet"; + +const SITE_URL = "https://www.seasketch.org"; +const DEFAULT_OG_IMAGE = `${SITE_URL}/logo512.png`; + +type SiteHelmetProps = { + title: string; + description: string; + path?: string; + ogImage?: string; +}; + +export default function SiteHelmet({ + title, + description, + path = "/", + ogImage = DEFAULT_OG_IMAGE, +}: SiteHelmetProps) { + const pageTitle = title.includes("SeaSketch") ? title : `${title} | SeaSketch`; + const canonicalUrl = `${SITE_URL}${path}`; + const resolvedOgImage = ogImage.startsWith("http") + ? ogImage + : `${SITE_URL}${ogImage}`; + + return ( + + {pageTitle} + + + + + + + + + + + + + ); +} diff --git a/packages/client/src/homepage/TeamPage.tsx b/packages/client/src/homepage/TeamPage.tsx index b738ba57e..fb30e798e 100644 --- a/packages/client/src/homepage/TeamPage.tsx +++ b/packages/client/src/homepage/TeamPage.tsx @@ -1,5 +1,5 @@ /* eslint-disable i18next/no-literal-string */ -import { Helmet } from "react-helmet"; +import SiteHelmet from "./SiteHelmet"; type TeamMember = { name: string; @@ -42,10 +42,11 @@ const teamMembers: TeamMember[] = [ export default function TeamPage() { return (
    - - SeaSketch | Team - - + {/* Header space handled by global Header */}
    {/* Decorative background */} diff --git a/packages/client/src/homepage/caseStudies/components/Testimonial.tsx b/packages/client/src/homepage/caseStudies/components/Testimonial.tsx index 2aa6310f4..3d280646b 100644 --- a/packages/client/src/homepage/caseStudies/components/Testimonial.tsx +++ b/packages/client/src/homepage/caseStudies/components/Testimonial.tsx @@ -1,43 +1,77 @@ /* eslint-disable i18next/no-literal-string */ +import { Link1Icon } from "@radix-ui/react-icons"; import React from "react"; +import { Link } from "react-router-dom"; export interface TestimonialProps { quote: string; author: string; affiliation?: string; headshotSrc?: string; + compact?: boolean; + link?: { + label: string; + to: string; + }; } export default function Testimonial(props: TestimonialProps) { - const { quote, author, affiliation, headshotSrc } = props; + const { quote, author, affiliation, headshotSrc, compact, link } = props; return ( -
    -
    +
    +
    -

    {quote}

    +

    + {quote} +

    -
    - {headshotSrc ? ( - {author} - ) : null} -
    -
    {author}
    - {affiliation ? ( -
    {affiliation}
    +
    +
    + {headshotSrc ? ( + {author} ) : null} +
    +
    {author}
    + {affiliation ? ( +
    {affiliation}
    + ) : null} +
    + {link ? ( + + + {link.label} + + ) : null}
    diff --git a/packages/client/src/homepage/caseStudies/components/TopHeroImage.tsx b/packages/client/src/homepage/caseStudies/components/TopHeroImage.tsx index b6934f728..3fe87c1e6 100644 --- a/packages/client/src/homepage/caseStudies/components/TopHeroImage.tsx +++ b/packages/client/src/homepage/caseStudies/components/TopHeroImage.tsx @@ -1,5 +1,6 @@ /* eslint-disable i18next/no-literal-string */ import React from "react"; +import SiteHelmet from "../../SiteHelmet"; import { FeatureCardItem } from "./FeatureCardList"; export interface TopHeroImageProps { @@ -11,6 +12,8 @@ export interface TopHeroImageProps { featureTitle?: string; featureItems?: FeatureCardItem[]; imageCredit?: string; + /** Canonical path for document title and social meta tags */ + documentPath?: string; } export default function TopHeroImage(props: TopHeroImageProps) { @@ -23,9 +26,18 @@ export default function TopHeroImage(props: TopHeroImageProps) { featureTitle, featureItems, imageCredit, + documentPath, } = props; return (
    + {documentPath ? ( + + ) : null}
    +
    diff --git a/packages/client/src/homepage/useCases/MapPortalHosting.tsx b/packages/client/src/homepage/useCases/MapPortalHosting.tsx index fb34b8991..dee009657 100644 --- a/packages/client/src/homepage/useCases/MapPortalHosting.tsx +++ b/packages/client/src/homepage/useCases/MapPortalHosting.tsx @@ -1,7 +1,6 @@ /* eslint-disable i18next/no-literal-string */ import { BookOpenIcon } from "@heroicons/react/outline"; import { ReactNode } from "react"; -import { Helmet } from "react-helmet"; import { Link } from "react-router-dom"; import { RocketIcon, @@ -14,21 +13,14 @@ import { ChatBubbleIcon, LayersIcon, } from "@radix-ui/react-icons"; +import { mapPortalHostingUseCase } from "./useCaseDefs"; +import UseCaseHelmet from "./UseCaseHelmet"; +import { cloudflareImage } from "./cloudflareImages"; -export const mapPortalHostingUseCase = { - id: "map-portal-hosting", - to: "/uses/map-portal-hosting", - title: "Map Portal Hosting", - navLabel: "Map Portal Hosting", - readMoreLabel: "Read more about Map Portal Hosting", - summary: - "Host, visualize, and share spatial data. Create a common picture of your ocean environment.", - bullets: [ - "Host vector and raster data", - "Design approachable maps for stakeholders", - "Manage metadata, versions, and access", - ], -}; +const MAP_PORTAL_HERO_IMAGE = cloudflareImage( + "af4df994-3ed3-4c4a-15f0-1c327eba1200", + "hlarge" +); const featureCopyPanelClass = "relative z-20 rounded-2xl border border-white/60 bg-white/65 p-5 shadow-sm backdrop-blur-sm md:p-6"; @@ -154,13 +146,7 @@ function FeatureRow({ export default function MapPortalHostingPage() { return (
    - - {`SeaSketch | ${mapPortalHostingUseCase.title}`} - - + {/* Hero */}
    @@ -218,14 +204,11 @@ export default function MapPortalHostingPage() { className="absolute -inset-8 -z-10 rounded-[3rem] bg-gradient-to-tr from-sky-500/20 via-cyan-400/10 to-emerald-400/20 blur-3xl" />
    - {/* The Te Baiku Ocean Geodatabase in SeaSketch showing geomorphic reef features for Kiribati with a map legend */} A SeaSketch map portal showing a map of the ocean with a legend and a search bar
    diff --git a/packages/client/src/homepage/useCases/OceanUseSurveys.tsx b/packages/client/src/homepage/useCases/OceanUseSurveys.tsx index 8ac1728ae..3b9f49f3a 100644 --- a/packages/client/src/homepage/useCases/OceanUseSurveys.tsx +++ b/packages/client/src/homepage/useCases/OceanUseSurveys.tsx @@ -1,6 +1,5 @@ /* eslint-disable i18next/no-literal-string */ import { ReactNode } from "react"; -import { Helmet } from "react-helmet"; import { Link } from "react-router-dom"; import { BookOpenIcon, @@ -24,27 +23,13 @@ import { import Testimonial from "../caseStudies/components/Testimonial"; import AppleDeviceFrame from "./AppleDeviceFrame"; import { ipadPro11LandscapeSpaceBlack, macbookPro14 } from "./deviceFrames"; - -export const oceanUseSurveysUseCase = { - id: "ocean-use-surveys", - to: "/uses/ocean-use-surveys", - title: "Ocean Use Surveys", - navLabel: "Ocean Use Surveys", - readMoreLabel: "Read more about Ocean Use Surveys", - summary: - "Collect local knowledge directly on the map—structured, spatial, analysis-ready. Run multi-language campaigns with ease.", - bullets: [ - "Build spatial surveys for desktop and mobile", - "Support multi-language campaigns", - "Prepare survey data for analysis", - ], -}; +import { oceanUseSurveysUseCase } from "./useCaseDefs"; +import UseCaseHelmet from "./UseCaseHelmet"; +import { CLOUDFLARE_IMAGES } from "./cloudflareImages"; const featureCopyPanelClass = "relative z-20 rounded-2xl border border-white/60 bg-white/65 p-5 shadow-sm backdrop-blur-sm md:p-6"; -const CLOUDFLARE_IMAGES = "https://imagedelivery.net/UvAJR8nUVV-h3iWaqOVMkw"; - const PHONE_DIGITIZING_IMAGE = `${CLOUDFLARE_IMAGES}/902e86c5-8372-4e7c-dd14-f5978c92bd00/hlarge`; const MALDIVES_INTRO_IMAGE = `${CLOUDFLARE_IMAGES}/96b66429-2833-4927-e7fb-4d19a0e87700/hlarge`; @@ -678,13 +663,7 @@ const additionalFeatureCards: AdditionalFeatureCard[] = [ export default function OceanUseSurveysPage() { return (
    - - {`SeaSketch | ${oceanUseSurveysUseCase.title}`} - - + {/* Hero */}
    diff --git a/packages/client/src/homepage/useCases/ReportCardMontage.css b/packages/client/src/homepage/useCases/ReportCardMontage.css new file mode 100644 index 000000000..5442c3687 --- /dev/null +++ b/packages/client/src/homepage/useCases/ReportCardMontage.css @@ -0,0 +1,57 @@ +@keyframes report-card-marquee { + from { + transform: translate3d(0, 0, 0); + } + to { + transform: translate3d(calc(-1 * var(--marquee-shift, 50%)), 0, 0); + } +} + +.report-card-marquee-track { + animation: report-card-marquee var(--marquee-duration, 100s) linear infinite; + will-change: transform; +} + +@media (prefers-reduced-motion: reduce) { + .report-card-marquee-track { + animation: none; + } +} + +.report-card-masonry { + display: flex; + margin-left: -0.625rem; + width: auto; +} + +.report-card-masonry-column { + padding-left: 0.625rem; + background-clip: padding-box; + flex: none; + width: 192px; +} + +.report-card-masonry-column > div { + margin-bottom: 0.625rem; +} + +@media (min-width: 640px) { + .report-card-masonry { + margin-left: -0.75rem; + } + + .report-card-masonry-column { + padding-left: 0.75rem; + width: 240px; + } + + .report-card-masonry-column > div { + margin-bottom: 0.75rem; + } +} + +@media (min-width: 1024px) { + .report-card-masonry-column { + width: 320px; + } +} diff --git a/packages/client/src/homepage/useCases/ReportCardMontage.tsx b/packages/client/src/homepage/useCases/ReportCardMontage.tsx new file mode 100644 index 000000000..28362bd6f --- /dev/null +++ b/packages/client/src/homepage/useCases/ReportCardMontage.tsx @@ -0,0 +1,236 @@ +/* eslint-disable i18next/no-literal-string */ +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { CLOUDFLARE_IMAGES } from "./cloudflareImages"; +import "./ReportCardMontage.css"; + +const COLUMN_COUNT = 5; + +type ReportCardItem = { + src: string; + alt: string; + /** height / width at render size — used to balance column stacks */ + aspectRatio: number; +}; + +function ReportCardFrame({ src, alt }: { src: string; alt: string }) { + return ( +
    + {alt} +
    + ); +} + +const REPORT_CARDS: ReportCardItem[] = [ + { + src: `${CLOUDFLARE_IMAGES}/465c0142-8c47-48d8-66c1-c4b3fa46bb00/public`, + alt: "Anchoring sites report card from Blue Azores", + aspectRatio: 1158 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/75739ea5-1f92-4a72-f49e-6f200a5d7600/public`, + alt: "Degree heating weeks histogram report card from Vanuatu", + aspectRatio: 1114 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/b9337859-9528-4fe5-c6c0-535434d8d200/public`, + alt: "Fish density report card from Fiji", + aspectRatio: 1018 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/ef487329-c9dc-4d40-ca45-ef275f89c200/public`, + alt: "Marxan prioritization histogram report card from Fiji", + aspectRatio: 998 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/6e9cc665-95ec-4497-2fd5-775ae740b700/public`, + alt: "Geomorphology overlap table report card from Fiji", + aspectRatio: 942 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/17ca0019-c6d3-4f2f-a132-9ab1e4f15700/public`, + alt: "Bathymetry report card from Blue Azores", + aspectRatio: 936 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/e1d9aece-c1de-4a3f-3972-b911f09c6e00/public`, + alt: "Depth bathymetry histogram report card from Fiji", + aspectRatio: 850 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/65a73c87-4caf-4fcd-1ea4-2dec71ac9000/public`, + alt: "Planning objectives report card from Samoa", + aspectRatio: 810 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/e0279279-41af-4146-8c64-912550a77c00/public`, + alt: "Size objectives table report card from Samoa", + aspectRatio: 690 / 956, + }, + { + src: `${CLOUDFLARE_IMAGES}/7a7f3a3e-3f84-4772-0f51-bbf903dd5f00/public`, + alt: "EEZ size summary report card from Fiji", + aspectRatio: 628 / 956, + }, +]; + +/** + * Pair the tallest cards with the shortest so each column stack ends up + * with a similar total height. + */ +function balanceCardsIntoColumns( + cards: ReportCardItem[], + columnCount: number +): ReportCardItem[][] { + const sorted = [...cards].sort((a, b) => b.aspectRatio - a.aspectRatio); + const columns: ReportCardItem[][] = Array.from( + { length: columnCount }, + () => [] + ); + const columnHeights = new Array(columnCount).fill(0); + + for (let i = 0; i < columnCount && i < sorted.length; i++) { + columns[i].push(sorted[i]); + columnHeights[i] += sorted[i].aspectRatio; + } + + for (let i = columnCount; i < sorted.length; i++) { + const card = sorted[i]; + let shortestColumn = 0; + for (let col = 1; col < columnCount; col++) { + if (columnHeights[col] < columnHeights[shortestColumn]) { + shortestColumn = col; + } + } + columns[shortestColumn].push(card); + columnHeights[shortestColumn] += card.aspectRatio; + } + + return columns; +} + +function MasonryStrip({ + stripId, + measureRef, + ariaHidden, +}: { + stripId: string; + measureRef?: React.RefObject; + ariaHidden?: boolean; +}) { + const columns = useMemo( + () => balanceCardsIntoColumns(REPORT_CARDS, COLUMN_COUNT), + [] + ); + + return ( +
    +
    + {columns.map((column, columnIndex) => ( +
    + {column.map((card) => ( +
    + +
    + ))} +
    + ))} +
    +
    + ); +} + +export default function ReportCardMontage() { + const stripMeasureRef = useRef(null); + const [stripWidth, setStripWidth] = useState(0); + const [viewportWidth, setViewportWidth] = useState( + typeof window !== "undefined" ? window.innerWidth : 0 + ); + + useEffect(() => { + const stripEl = stripMeasureRef.current; + if (!stripEl) { + return; + } + + const measure = () => { + setStripWidth(stripEl.offsetWidth); + setViewportWidth(window.innerWidth); + }; + + measure(); + + const resizeObserver = new ResizeObserver(measure); + resizeObserver.observe(stripEl); + window.addEventListener("resize", measure); + + return () => { + resizeObserver.disconnect(); + window.removeEventListener("resize", measure); + }; + }, []); + + const copyCount = useMemo(() => { + if (stripWidth <= 0) { + return 2; + } + // Enough copies so the viewport is always covered across one loop shift. + return Math.max(2, Math.ceil(viewportWidth / stripWidth) + 1); + }, [stripWidth, viewportWidth]); + + return ( +
    +
    +

    + Endless Customization +

    +

    + A growing library of metrics and visualizations +

    +

    + Overlap tables, depth profiles, histograms, distance from shore, + prioritization scores. Everything you need to evaluate your spatial + plans. All based on your data. +

    +
    + +
    +
    +
    + +
    +
    0 ? `${stripWidth}px` : "50%", + } as React.CSSProperties + } + > + {Array.from({ length: copyCount }, (_, index) => ( + 0} + /> + ))} +
    +
    +
    +
    + ); +} diff --git a/packages/client/src/homepage/useCases/SketchingAndAnalysis.tsx b/packages/client/src/homepage/useCases/SketchingAndAnalysis.tsx index 0363c3f04..53b84eb9c 100644 --- a/packages/client/src/homepage/useCases/SketchingAndAnalysis.tsx +++ b/packages/client/src/homepage/useCases/SketchingAndAnalysis.tsx @@ -1,83 +1,873 @@ /* eslint-disable i18next/no-literal-string */ -import { Helmet } from "react-helmet"; +import { + ChevronRightIcon, + CollectionIcon, + InformationCircleIcon, + LockClosedIcon, + TranslateIcon, +} from "@heroicons/react/outline"; +import { ReactNode } from "react"; import { Link } from "react-router-dom"; +import * as Tooltip from "@radix-ui/react-tooltip"; +import { + ArrowLeftIcon, + BarChartIcon, + ChatBubbleIcon, + CodeIcon, + DownloadIcon, + FileTextIcon, + Pencil2Icon, +} from "@radix-ui/react-icons"; +import Testimonial from "../caseStudies/components/Testimonial"; +import ReportCardMontage from "./ReportCardMontage"; +import { sketchingAndAnalysisUseCase } from "./useCaseDefs"; +import UseCaseHelmet from "./UseCaseHelmet"; +import { CLOUDFLARE_IMAGES } from "./cloudflareImages"; -export const sketchingAndAnalysisUseCase = { - id: "sketching-and-analysis", - to: "/uses/sketching-and-analysis", - title: "Sketching and Analysis", - navLabel: "Sketching and Analysis", - readMoreLabel: "Read more about Sketching and Analysis", - summary: - "Easy to use design and analysis tools empower stakeholders to effectively participate in a science-driven planning process.", - bullets: [ - "Sketch zones and planning options", - "Evaluate scenarios against spatial objectives", - "Export results for GIS and reporting workflows", - ], +const featureCopyPanelClass = + "relative z-20 rounded-2xl border border-white/60 bg-white/65 p-5 shadow-sm backdrop-blur-sm md:p-6"; + +const SKETCH_BACKDROP = `${CLOUDFLARE_IMAGES}/49341eb7-5ac5-4f7a-44a9-32ce5e2cab00/hlarge`; +const REPORT_HERO = `${CLOUDFLARE_IMAGES}/c9452e46-bf89-4d66-ca1f-f21b5ad8d400/hlarge`; +const SKETCHING_HERO_IMAGE = `${CLOUDFLARE_IMAGES}/45106a3c-a728-4e0e-6dc1-71b2228f2400/hlarge`; +const SKETCHING_MAP_BACKDROP = `${CLOUDFLARE_IMAGES}/fe4802cc-75db-48aa-ed36-175e0a712c00/hlarge`; +const FORUM_BOOKMARK_THUMBNAIL = `${CLOUDFLARE_IMAGES}/74561fa4-2a04-4dfd-98a0-72e2d65e5300/public`; +const GEOGRAPHY_SELECTION_IMAGE = `${CLOUDFLARE_IMAGES}/379f8ca3-64aa-4a5b-3019-493ca9bbd900/hlarge`; +const SKETCHING_STEP_IMAGE = `${CLOUDFLARE_IMAGES}/8f8c5920-ff3d-4aea-2e62-6d59fba4e900/hlarge`; +const REPORT_AUTHORING_IMAGE = `${CLOUDFLARE_IMAGES}/5584eeab-e603-4922-8087-1db721eca000/hlarge`; +const CHAD_GRAVATAR = + "https://www.gravatar.com/avatar/b0a4285bfc440a2efad5036bb95d68a9?s=48&d=mp&r=pg"; + +const FEATHERED_MASK_STYLE = { + maskImage: + "radial-gradient(76% 62% at 70% 53%, rgba(0,0,0,1) 12%, rgba(0,0,0,0.98) 40%, rgba(0,0,0,0.78) 54%, rgba(0,0,0,0.4) 68%, rgba(0,0,0,0.14) 80%, transparent 92%)", + WebkitMaskImage: + "radial-gradient(76% 62% at 70% 53%, rgba(0,0,0,1) 12%, rgba(0,0,0,0.98) 40%, rgba(0,0,0,0.78) 54%, rgba(0,0,0,0.4) 68%, rgba(0,0,0,0.14) 80%, transparent 92%)", }; -export default function SketchingAndAnalysisPage() { +const MAP_VIEWBOX = { width: 1024, height: 608 }; + +type SketchVertex = { x: number; y: number; active?: boolean }; + +/** Sketch polygon vertices in map viewBox coordinates */ +const SKETCH_VERTICES: SketchVertex[] = [ + { x: 392, y: 176 }, + { x: 628, y: 176 }, + { x: 652, y: 512, active: true }, + { x: 372, y: 492 }, +]; + +function SketchCursor({ className }: { className?: string }) { + return ( + + + + ); +} + +function VertexHandle({ + cx, + cy, + active, +}: { + cx: number; + cy: number; + active?: boolean; +}) { + const radius = active ? 9 : 7; + return ( + + + + + ); +} + +function MidpointHandle({ cx, cy }: { cx: number; cy: number }) { + return ( + + ); +} + +function midpoint(a: { x: number; y: number }, b: { x: number; y: number }) { + return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }; +} + +function DigitizingWorkflowDemo() { + const polygonPoints = SKETCH_VERTICES.map((v) => `${v.x},${v.y}`).join(" "); + const midpoints = SKETCH_VERTICES.map((vertex, index) => { + const next = SKETCH_VERTICES[(index + 1) % SKETCH_VERTICES.length]; + return midpoint(vertex, next); + }); + const activeVertex = + SKETCH_VERTICES.find((v) => v.active) ?? SKETCH_VERTICES[2]; + + return ( +
    +
    + +
    + +
    + +
    +
    + + + + + {midpoints.map((point, index) => ( + + ))} + {SKETCH_VERTICES.map((vertex, index) => ( + + ))} + + +
    + +
    + +
    + {/*
    + + Drag points to modify + + + +
    */} +
    +
    +
    +
    + ); +} + +const ADRIANO_HEADSHOT = `${CLOUDFLARE_IMAGES}/d519a37f-22b7-40f2-e998-6f9539be8000/thumbnail`; + +const ADRIANO_QUOTE = + "SeaSketch helped empower our region to design the largest offshore MPA network in the North Atlantic—not just with scientific rigor, but with community voices guiding every boundary."; + +function ForumThreadDemo() { + const replyDate = new Date(); + replyDate.setDate(replyDate.getDate() - 7); + const formattedDate = replyDate.toLocaleTimeString([], { + year: "numeric", + month: "numeric", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + return ( -
    - - {`SeaSketch | ${sketchingAndAnalysisUseCase.title}`} - +
    +
    +
    + Chad Burt +
    +
    + + Chad Burt + + replied on + {formattedDate} +
    +
    +
    + +
    +

    + This option was presented to the Minister for consideration as the + final offshore MPA Network. A total of 29.95% of the EEZ, with 9 + no-take zones. The remaining 0.05% of the 30% national protection goal + will be achieved with coastal & inshore protected areas. +

    + +
    +
    +
    + + + +
    + + + +
    + + + Final Draft 1.4 Network + +
    +
    +
    + +

    + Attached is the sketch of final draft 1.4. Feel free to download and + run reports. +

    +
    + +
    +
    + Bookmark preview thumbnail + + + + + + + + + Map bookmarks save the current view—basemap, visible layers, + zoom, and sketches—so others can restore the same map + context with one click. + + + + + + +
    +
    +
    +
    + ); +} + +type WorkflowStepProps = { + step: string; + title: string; + image: string; + imageAlt: string; + imagePosition?: string; + children: ReactNode; +}; + +/** Matches geography selection screenshot (2512×2064) — template for card image height */ +function WorkflowStep({ + step, + title, + image, + imageAlt, + imagePosition = "object-top", + children, +}: WorkflowStepProps) { + return ( +
    +
    + {imageAlt} - -
    -
    -
    -
    -
    +
    +
    +
    + {step}
    -
    +

    + {title} +

    +
    + {children} +
    +
    +
    + ); +} + +type AdditionalFeatureCard = { + title: string; + description: ReactNode; + icon: ReactNode; + iconClassName: string; +}; + +const additionalFeatureCards: AdditionalFeatureCard[] = [ + { + title: "Collections & networks", + description: + "Group individual zones into networks that can be analyzed together, supporting comprehensive planning and MPA network design.", + icon: , + iconClassName: "bg-sky-100 text-sky-700 ring-sky-200", + }, + { + title: "Forum moderation", + description: + "The discussion forums include tools to moderate and manage forum content, including the ability to hide or delete posts and topics.", + icon: , + iconClassName: "bg-emerald-100 text-emerald-700 ring-emerald-200", + }, + { + title: "Export to GIS, CSV, and JSON", + description: + "Download sketches as GeoJSON for desktop GIS workflows, or export report tables to CSV and JSON for further analysis.", + icon: , + iconClassName: "bg-cyan-100 text-cyan-700 ring-cyan-200", + }, + { + title: "Localized content", + description: + "The SeaSketch UI is available in dozens of languages, with support for translated sketch attribute forms and reports.", + icon: , + iconClassName: "bg-violet-100 text-violet-700 ring-violet-200", + }, + { + title: "Personal workspace", + description: + "Every participant gets a private sketching workspace to draft ideas before sharing proposals in forums or collaborative sessions.", + icon: , + iconClassName: "bg-indigo-100 text-indigo-700 ring-indigo-200", + }, + { + title: "Open geoprocessing framework", + description: ( + <> + In addition to the graphical report builder, reports can be authored + using our{" "} + + TypeScript-based geoprocessing framework + + . + + ), + icon: , + iconClassName: "bg-amber-100 text-amber-700 ring-amber-200", + }, +]; + +export default function SketchingAndAnalysisPage() { + return ( +
    + + + {/* Hero */} +
    +
    +
    +
    +
    +
    + +
    + Back to SeaSketch capabilities -
    - - SeaSketch use case + +
    + + Sketching and Analysis -

    - {sketchingAndAnalysisUseCase.title} +

    + Draw zones, run reports,{" "} + + iterate +

    -

    - {sketchingAndAnalysisUseCase.summary} +

    + SeaSketch enables collaborative and inclusive marine spatial + planning with easy-to-use scenario design and analysis tools.

    +
    -
    - {sketchingAndAnalysisUseCase.bullets.map((bullet) => ( +
    +
    +
    + Blue Azores SeaSketch project showing an offshore MPA proposal on the map with a habitat coverage report open in the sidebar +
    +
    +
    +
    + + {/* Feature rows */} +
    +
    +
    +
    + +
    +
    +
    + + + + + Digitizing + +
    +

    + Design tools for everyone +

    +
    +

    + Our sketching tools were built for truly participatory + planning—not just GIS for specialists. Users can draw + polygons, lines, or points directly on the map, then refine + vertices with intuitive editing controls. +

    +

    + SeaSketch can automatically clip sketches to shorelines, EEZ + boundaries, or other project geographies as soon as editing + finishes, so proposals meet project requirements and are ready + for analysis. Attribute forms can be tied to sketches to + capture details such as allowed uses, designation type, or + facility capacity. +

    +

    + Each user maintains a personal workspace to draft ideas before + sharing them with the group. Finished sketches and networks + can be exported for use in desktop GIS workflows if needed. +

    +
    +
    +
    + +
    +
    +
    + + + + + Reports + +
    +

    + Immediate analytical feedback +

    +
    +

    + Analytical reports provide instant feedback on each sketch, + summarizing overlap with habitats, human uses, ecosystem + services, and other key metrics defined by project + administrators. You can set targets and thresholds to assess + proposals against planning objectives. Every stakeholder + accesses the same set of tools to evaluate proposals, + promoting transparency and fairness throughout the process. +

    +

    + Participatory planning is stressful enough without waiting in + a workshop for slow, buggy, or manual analysis. Our{" "} + overlay-engine starts in the + background and runs report calculations across hundreds of + workers in parallel, so results are available almost + immediately after sketching. Reports update as sketches are + refined, supporting rapid iteration during workshops and + online collaboration. +

    +
    +
    + +
    +
    +
    +
    + SeaSketch analytical report showing habitat coverage and planning metrics for a sketched zone +
    +
    +
    +
    + +
    +
    -

    - {bullet} -

    -

    - Placeholder content for this section can be expanded with - examples, screenshots, and customer stories. + +

    + +
    + +
    +
    + +
    +
    + + + + + Discussion Forums + +
    +

    + Collaborative planning +

    +
    +

    + Discussion forums connect sketching and analysis to the + conversations that shape decisions. Participants share + proposals, attach sketches, and bookmark exact map views so + others can see the same context—basemap, layers, zoom, and + sketches included. +

    +

    + Reviewers can toggle shared sketches on the map, generate + reports on attached proposals, and reply with refined + alternatives. Access controls let administrators run public + consultations alongside closed working groups. +

    +

    + Forum threads become a living record of how plans evolve. + There are SeaSketch threads detailing conversations that led + to MPA network designs{" "} + + from over a decade ago + + , complete with draft sketches.

    - ))} +
    +
    + +
    +
    + +
    -
    -

    - Page draft placeholder -

    -

    - This page is ready to be fleshed out with detailed positioning, - visuals, feature walkthroughs, and calls to action for this - SeaSketch use case. -

    + {/* How does it work? */} +
    +
    +
    + How does it work? +
    +

    + From project setup to instant reports +

    +

    + Administrators configure the building blocks—geographies, sketch + classes, and reports—then participants sketch and analyze + scenarios in a structured, customizable workflow. +

    +

    + + + Read the Documentation + +

    +
    + +
    + +

    + When creating a project, choose a planning geography such as + an Exclusive Economic Zone. +

    +

    + Additional geographies can be added later to represent areas + such as territorial seas, bioregions, or administrative + boundaries. These can be used to summarize statistics in + reports. +

    +
    + + +

    + Sketch classes define the zone types participants can draw— + Marine Protected Areas, renewable energy sites, aquaculture + areas, and more. Each class specifies the geometry type and + attribute forms. +

    +

    + SeaSketch can clip sketches to your project's geographies, + including removing land from marine zones. +

    +
    + + +

    + Author custom reports using a rich-text editor. Embed + visualizations of metrics calculated from spatial data + uploaded to your project. Set goals and objectives, report + progress toward targets, and calculate levels of protection. +

    +

    + Reports use the latest versions of referenced data and match + cartographic styles. +

    +
    +
    +
    + + +
    +
    + + {/* Additional features + links */} +
    +
    +
    +

    + Additional Features +

    + +
    + {additionalFeatureCards.map((card) => ( +
    + + {card.icon} + +

    + {card.title} +

    +

    + {card.description} +

    +
    + ))} +
    +
    +
    +
    + + {/* Closing CTA */} +
    +
    +
    +
    +
    +
    +

    + Ready to start planning? +

    +

    + Create a free project, configure sketch classes, and start + iterating. +

    +
    diff --git a/packages/client/src/homepage/useCases/UseCaseHelmet.tsx b/packages/client/src/homepage/useCases/UseCaseHelmet.tsx new file mode 100644 index 000000000..00c3c00bc --- /dev/null +++ b/packages/client/src/homepage/useCases/UseCaseHelmet.tsx @@ -0,0 +1,14 @@ +/* eslint-disable i18next/no-literal-string */ +import SiteHelmet from "../SiteHelmet"; +import { UseCaseDefinition } from "./useCaseDefs"; + +export default function UseCaseHelmet({ useCase }: { useCase: UseCaseDefinition }) { + return ( + + ); +} diff --git a/packages/client/src/homepage/useCases/cloudflareImages.ts b/packages/client/src/homepage/useCases/cloudflareImages.ts new file mode 100644 index 000000000..7c6b52a7e --- /dev/null +++ b/packages/client/src/homepage/useCases/cloudflareImages.ts @@ -0,0 +1,8 @@ +/* eslint-disable i18next/no-literal-string */ + +export const CLOUDFLARE_IMAGES = + "https://imagedelivery.net/UvAJR8nUVV-h3iWaqOVMkw"; + +export function cloudflareImage(id: string, variant: string) { + return `${CLOUDFLARE_IMAGES}/${id}/${variant}`; +} diff --git a/packages/client/src/homepage/useCases/deviceFrames.ts b/packages/client/src/homepage/useCases/deviceFrames.ts index d7e87d07c..044dbb8f0 100644 --- a/packages/client/src/homepage/useCases/deviceFrames.ts +++ b/packages/client/src/homepage/useCases/deviceFrames.ts @@ -1,7 +1,6 @@ /* eslint-disable i18next/no-literal-string */ -const CLOUDFLARE_IMAGES = - "https://imagedelivery.net/UvAJR8nUVV-h3iWaqOVMkw"; +import { CLOUDFLARE_IMAGES } from "./cloudflareImages"; export type DeviceFrameSpec = { id: string; diff --git a/packages/client/src/homepage/useCases/index.ts b/packages/client/src/homepage/useCases/index.ts index 6ea42d64c..3780f9c33 100644 --- a/packages/client/src/homepage/useCases/index.ts +++ b/packages/client/src/homepage/useCases/index.ts @@ -1,32 +1,8 @@ -import { mapPortalHostingUseCase } from "./MapPortalHosting"; -import { oceanUseSurveysUseCase } from "./OceanUseSurveys"; -import { sketchingAndAnalysisUseCase } from "./SketchingAndAnalysis"; - export { - default as MapPortalHostingPage, - mapPortalHostingUseCase, -} from "./MapPortalHosting"; -export { - default as OceanUseSurveysPage, - oceanUseSurveysUseCase, -} from "./OceanUseSurveys"; -export { - default as SketchingAndAnalysisPage, - sketchingAndAnalysisUseCase, -} from "./SketchingAndAnalysis"; - -// Temporary launch mode: -// Keep only the Map Portal use case linked from homepage/nav until -// Ocean Use Surveys and Sketching pages are ready. -// To restore full behavior, set this to `true`. -export const enableAllUseCaseLinks = true; - -export const useCaseLinks = [ mapPortalHostingUseCase, oceanUseSurveysUseCase, sketchingAndAnalysisUseCase, -]; - -export const publishedUseCaseLinks = enableAllUseCaseLinks - ? useCaseLinks - : [mapPortalHostingUseCase]; + useCaseLinks, +} from "./useCaseDefs"; +export type { UseCaseDefinition } from "./useCaseDefs"; +export { CLOUDFLARE_IMAGES, cloudflareImage } from "./cloudflareImages"; diff --git a/packages/client/src/homepage/useCases/outer-reef-flat-square.png b/packages/client/src/homepage/useCases/outer-reef-flat-square.png deleted file mode 100644 index 0457d8abe..000000000 Binary files a/packages/client/src/homepage/useCases/outer-reef-flat-square.png and /dev/null differ diff --git a/packages/client/src/homepage/useCases/outer-reef-flat.png b/packages/client/src/homepage/useCases/outer-reef-flat.png deleted file mode 100644 index 4e057cbec..000000000 Binary files a/packages/client/src/homepage/useCases/outer-reef-flat.png and /dev/null differ diff --git a/packages/client/src/homepage/useCases/useCaseDefs.ts b/packages/client/src/homepage/useCases/useCaseDefs.ts new file mode 100644 index 000000000..3788bc4a6 --- /dev/null +++ b/packages/client/src/homepage/useCases/useCaseDefs.ts @@ -0,0 +1,70 @@ +/* eslint-disable i18next/no-literal-string */ + +export type UseCaseDefinition = { + id: string; + to: string; + title: string; + navLabel: string; + readMoreLabel: string; + summary: string; + bullets: string[]; + /** Open Graph preview image */ + shareImage: string; +}; + +export const mapPortalHostingUseCase: UseCaseDefinition = { + id: "map-portal-hosting", + to: "/uses/map-portal-hosting", + title: "Map Portal Hosting", + navLabel: "Map Portal Hosting", + readMoreLabel: "Read more about Map Portal Hosting", + summary: + "Host, visualize, and share spatial data. Create a common picture of your ocean environment.", + bullets: [ + "Host vector and raster data", + "Design approachable maps for stakeholders", + "Manage metadata, versions, and access", + ], + shareImage: + "https://imagedelivery.net/UvAJR8nUVV-h3iWaqOVMkw/af4df994-3ed3-4c4a-15f0-1c327eba1200/hlarge", +}; + +export const oceanUseSurveysUseCase: UseCaseDefinition = { + id: "ocean-use-surveys", + to: "/uses/ocean-use-surveys", + title: "Ocean Use Surveys", + navLabel: "Ocean Use Surveys", + readMoreLabel: "Read more about Ocean Use Surveys", + summary: + "Collect local knowledge directly on the map—structured, spatial, analysis-ready. Run multi-language campaigns with ease.", + bullets: [ + "Build spatial surveys for desktop and mobile", + "Support multi-language campaigns", + "Prepare survey data for analysis", + ], + shareImage: + "https://imagedelivery.net/UvAJR8nUVV-h3iWaqOVMkw/f62cbeab-146a-463a-2b50-843acffec500/hlarge", +}; + +export const sketchingAndAnalysisUseCase: UseCaseDefinition = { + id: "sketching-and-analysis", + to: "/uses/sketching-and-analysis", + title: "Sketching and Analysis", + navLabel: "Sketching and Analysis", + readMoreLabel: "Read more about Sketching and Analysis", + summary: + "Easy-to-use design and analysis tools empower stakeholders to effectively participate in a science-driven planning process.", + bullets: [ + "Sketch zones and planning options", + "Evaluate scenarios against spatial objectives", + "Export results for GIS and reporting workflows", + ], + shareImage: + "https://imagedelivery.net/UvAJR8nUVV-h3iWaqOVMkw/45106a3c-a728-4e0e-6dc1-71b2228f2400/hlarge", +}; + +export const useCaseLinks = [ + mapPortalHostingUseCase, + oceanUseSurveysUseCase, + sketchingAndAnalysisUseCase, +]; diff --git a/packages/client/src/index.css b/packages/client/src/index.css index bed0b4cfe..f4cc69c92 100644 --- a/packages/client/src/index.css +++ b/packages/client/src/index.css @@ -5813,6 +5813,10 @@ select{ position:sticky } +.-inset-3{ + inset:-0.75rem +} + .-inset-6{ inset:-1.5rem } @@ -5829,6 +5833,41 @@ select{ inset:0px } +.-inset-x-10{ + left:-2.5rem; + right:-2.5rem +} + +.-inset-x-20{ + left:-5rem; + right:-5rem +} + +.-inset-x-4{ + left:-1rem; + right:-1rem +} + +.-inset-x-5{ + left:-1.25rem; + right:-1.25rem +} + +.-inset-x-6{ + left:-1.5rem; + right:-1.5rem +} + +.-inset-y-10{ + top:-2.5rem; + bottom:-2.5rem +} + +.-inset-y-12{ + top:-3rem; + bottom:-3rem +} + .-inset-y-16{ top:-4rem; bottom:-4rem @@ -5844,6 +5883,11 @@ select{ bottom:-6rem } +.-inset-y-6{ + top:-1.5rem; + bottom:-1.5rem +} + .-inset-y-8{ top:-2rem; bottom:-2rem @@ -5859,6 +5903,26 @@ select{ bottom:0px } +.-inset-x-8{ + left:-2rem; + right:-2rem +} + +.-inset-x-16{ + left:-4rem; + right:-4rem +} + +.inset-x-8{ + left:2rem; + right:2rem +} + +.inset-x-6{ + left:1.5rem; + right:1.5rem +} + .\!-bottom-2{ bottom:-0.5rem !important } @@ -5923,6 +5987,10 @@ select{ left:-3rem } +.-left-16{ + left:-4rem +} + .-left-2{ left:-0.5rem } @@ -6495,6 +6563,50 @@ select{ top:1px } +.bottom-\[10\%\]{ + bottom:10% +} + +.left-\[0\%\]{ + left:0% +} + +.left-\[2\%\]{ + left:2% +} + +.right-\[-2\%\]{ + right:-2% +} + +.right-\[0\%\]{ + right:0% +} + +.right-\[4\%\]{ + right:4% +} + +.top-16{ + top:4rem +} + +.top-\[2\%\]{ + top:2% +} + +.top-\[4\%\]{ + top:4% +} + +.top-\[6\%\]{ + top:6% +} + +.top-1\/4{ + top:25% +} + .isolate{ isolation:isolate } @@ -6627,6 +6739,26 @@ select{ grid-column:span 8 / span 8 } +.col-span-full{ + grid-column:1 / -1 +} + +.col-span-1{ + grid-column:span 1 / span 1 +} + +.col-span-12{ + grid-column:span 12 / span 12 +} + +.col-span-5{ + grid-column:span 5 / span 5 +} + +.col-span-7{ + grid-column:span 7 / span 7 +} + .col-start-1{ grid-column-start:1 } @@ -6639,6 +6771,10 @@ select{ grid-row:span 2 / span 2 } +.row-span-3{ + grid-row:span 3 / span 3 +} + .row-start-1{ grid-row-start:1 } @@ -6748,6 +6884,21 @@ select{ margin-bottom:-2.5rem } +.-my-2{ + margin-top:-0.5rem; + margin-bottom:-0.5rem +} + +.-my-6{ + margin-top:-1.5rem; + margin-bottom:-1.5rem +} + +.-my-8{ + margin-top:-2rem; + margin-bottom:-2rem +} + .mx-0{ margin-left:0px; margin-right:0px @@ -7325,6 +7476,14 @@ select{ margin-top:2px } +.mt-28{ + margin-top:7rem +} + +.mt-32{ + margin-top:8rem +} + .box-border{ box-sizing:border-box } @@ -7398,6 +7557,10 @@ select{ display:none } +.aspect-\[2512\/2064\]{ + aspect-ratio:2512/2064 +} + .aspect-\[4\/3\]{ aspect-ratio:4/3 } @@ -7667,6 +7830,10 @@ select{ height:100vh } +.h-\[360px\]{ + height:360px +} + .max-h-128{ max-height:32rem } @@ -8241,6 +8408,26 @@ select{ width:100vw } +.w-\[44\%\]{ + width:44% +} + +.w-\[46\%\]{ + width:46% +} + +.w-\[min\(92\%\2c 21rem\)\]{ + width:min(92%,21rem) +} + +.w-\[min\(72vw\2c 17\.5rem\)\]{ + width:min(72vw,17.5rem) +} + +.w-\[380px\]{ + width:380px +} + .min-w-0{ min-width:0px } @@ -8489,6 +8676,10 @@ select{ max-width:12rem } +.max-w-\[13rem\]{ + max-width:13rem +} + .max-w-\[14rem\]{ max-width:14rem } @@ -8557,6 +8748,10 @@ select{ max-width:440px } +.max-w-\[480px\]{ + max-width:480px +} + .max-w-\[520px\]{ max-width:520px } @@ -8589,6 +8784,10 @@ select{ max-width:min(100%,14rem) } +.max-w-\[min\(100\%\2c 420px\)\]{ + max-width:min(100%,420px) +} + .max-w-\[min\(100\%\2c 680px\)\]{ max-width:min(100%,680px) } @@ -8645,6 +8844,10 @@ select{ max-width:20rem } +.max-w-\[100vw\]{ + max-width:100vw +} + .flex-1{ flex:1 1 0% } @@ -8747,6 +8950,10 @@ select{ transform-origin:top right } +.origin-center{ + transform-origin:center +} + .-translate-x-0{ --tw-translate-x:-0px; transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) @@ -8917,6 +9124,16 @@ select{ transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) } +.translate-x-1\/3{ + --tw-translate-x:33.333333%; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.translate-y-5{ + --tw-translate-y:1.25rem; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + .-rotate-2{ --tw-rotate:-2deg; transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) @@ -8977,6 +9194,36 @@ select{ transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) } +.-rotate-\[1\.5deg\]{ + --tw-rotate:-1.5deg; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.-rotate-\[14deg\]{ + --tw-rotate:-14deg; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.-rotate-\[9deg\]{ + --tw-rotate:-9deg; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.rotate-\[10deg\]{ + --tw-rotate:10deg; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.rotate-\[12deg\]{ + --tw-rotate:12deg; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.rotate-\[9deg\]{ + --tw-rotate:9deg; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + .scale-100{ --tw-scale-x:1; --tw-scale-y:1; @@ -9399,6 +9646,14 @@ select{ column-gap:0.5rem } +.gap-x-20{ + column-gap:5rem +} + +.gap-x-24{ + column-gap:6rem +} + .gap-x-3{ column-gap:0.75rem } @@ -9451,6 +9706,10 @@ select{ row-gap:1.5rem } +.gap-y-8{ + row-gap:2rem +} + .-space-x-1 > :not([hidden]) ~ :not([hidden]){ --tw-space-x-reverse:0; margin-right:calc(-0.25rem * var(--tw-space-x-reverse)); @@ -9559,6 +9818,12 @@ select{ margin-bottom:calc(0.375rem * var(--tw-space-y-reverse)) } +.space-y-10 > :not([hidden]) ~ :not([hidden]){ + --tw-space-y-reverse:0; + margin-top:calc(2.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom:calc(2.5rem * var(--tw-space-y-reverse)) +} + .space-y-16 > :not([hidden]) ~ :not([hidden]){ --tw-space-y-reverse:0; margin-top:calc(4rem * calc(1 - var(--tw-space-y-reverse))); @@ -9717,6 +9982,10 @@ select{ align-self:stretch } +.justify-self-center{ + justify-self:center +} + .overflow-auto{ overflow:auto } @@ -9871,6 +10140,14 @@ select{ border-radius:0.75rem } +.rounded-\[1\.75rem\]{ + border-radius:1.75rem +} + +.rounded-\[2rem\]{ + border-radius:2rem +} + .rounded-b{ border-bottom-right-radius:0.25rem; border-bottom-left-radius:0.25rem @@ -10541,6 +10818,10 @@ select{ border-color:rgb(63 63 70 / var(--tw-border-opacity, 1)) } +.border-white\/90{ + border-color:rgb(255 255 255 / 0.9) +} + .border-b-black{ --tw-border-opacity:1; border-bottom-color:rgb(0 0 0 / var(--tw-border-opacity, 1)) @@ -11553,6 +11834,27 @@ select{ background-color:rgb(24 24 27 / var(--tw-bg-opacity, 1)) } +.bg-sky-300\/30{ + background-color:rgb(125 211 252 / 0.3) +} + +.bg-violet-300\/25{ + background-color:rgb(196 181 253 / 0.25) +} + +.bg-violet-300{ + --tw-bg-opacity:1; + background-color:rgb(196 181 253 / var(--tw-bg-opacity, 1)) +} + +.bg-sky-200\/40{ + background-color:rgb(186 230 253 / 0.4) +} + +.bg-white\/75{ + background-color:rgb(255 255 255 / 0.75) +} + .bg-opacity-10{ --tw-bg-opacity:0.1 } @@ -11657,6 +11959,10 @@ select{ background-image:linear-gradient(to top right, var(--tw-gradient-stops)) } +.bg-gradient-to-l{ + background-image:linear-gradient(to left, var(--tw-gradient-stops)) +} + .from-amber-50{ --tw-gradient-from:#fffbeb var(--tw-gradient-from-position); --tw-gradient-to:rgb(255 251 235 / 0) var(--tw-gradient-to-position); @@ -11777,6 +12083,12 @@ select{ --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) } +.from-sky-50{ + --tw-gradient-from:#f0f9ff var(--tw-gradient-from-position); + --tw-gradient-to:rgb(240 249 255 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) +} + .from-sky-500{ --tw-gradient-from:#0ea5e9 var(--tw-gradient-from-position); --tw-gradient-to:rgb(14 165 233 / 0) var(--tw-gradient-to-position); @@ -11879,6 +12191,36 @@ select{ --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) } +.from-sky-200\/50{ + --tw-gradient-from:rgb(186 230 253 / 0.5) var(--tw-gradient-from-position); + --tw-gradient-to:rgb(186 230 253 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) +} + +.from-sky-200{ + --tw-gradient-from:#bae6fd var(--tw-gradient-from-position); + --tw-gradient-to:rgb(186 230 253 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) +} + +.from-sky-100\/70{ + --tw-gradient-from:rgb(224 242 254 / 0.7) var(--tw-gradient-from-position); + --tw-gradient-to:rgb(224 242 254 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) +} + +.from-sky-100{ + --tw-gradient-from:#e0f2fe var(--tw-gradient-from-position); + --tw-gradient-to:rgb(224 242 254 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) +} + +.from-sky-100\/80{ + --tw-gradient-from:rgb(224 242 254 / 0.8) var(--tw-gradient-from-position); + --tw-gradient-to:rgb(224 242 254 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) +} + .via-cool-gray-800{ --tw-gradient-to:rgb(31 41 55 / 0) var(--tw-gradient-to-position); --tw-gradient-stops:var(--tw-gradient-from), #1F2937 var(--tw-gradient-via-position), var(--tw-gradient-to) @@ -11939,6 +12281,31 @@ select{ --tw-gradient-stops:var(--tw-gradient-from), rgb(2 6 23 / 0.3) var(--tw-gradient-via-position), var(--tw-gradient-to) } +.via-white{ + --tw-gradient-to:rgb(255 255 255 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), #fff var(--tw-gradient-via-position), var(--tw-gradient-to) +} + +.via-violet-200\/30{ + --tw-gradient-to:rgb(221 214 254 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), rgb(221 214 254 / 0.3) var(--tw-gradient-via-position), var(--tw-gradient-to) +} + +.via-violet-200{ + --tw-gradient-to:rgb(221 214 254 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), #ddd6fe var(--tw-gradient-via-position), var(--tw-gradient-to) +} + +.via-slate-100\/80{ + --tw-gradient-to:rgb(241 245 249 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), rgb(241 245 249 / 0.8) var(--tw-gradient-via-position), var(--tw-gradient-to) +} + +.via-slate-100{ + --tw-gradient-to:rgb(241 245 249 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), #f1f5f9 var(--tw-gradient-via-position), var(--tw-gradient-to) +} + .to-amber-50{ --tw-gradient-to:#fffbeb var(--tw-gradient-to-position) } @@ -11967,6 +12334,10 @@ select{ --tw-gradient-to:rgb(52 211 153 / 0.2) var(--tw-gradient-to-position) } +.to-emerald-50{ + --tw-gradient-to:#ecfdf5 var(--tw-gradient-to-position) +} + .to-emerald-600{ --tw-gradient-to:#059669 var(--tw-gradient-to-position) } @@ -12067,6 +12438,26 @@ select{ --tw-gradient-to:rgb(252 211 77 / 0.3) var(--tw-gradient-to-position) } +.to-emerald-200\/40{ + --tw-gradient-to:rgb(167 243 208 / 0.4) var(--tw-gradient-to-position) +} + +.to-emerald-200{ + --tw-gradient-to:#a7f3d0 var(--tw-gradient-to-position) +} + +.to-violet-100\/50{ + --tw-gradient-to:rgb(237 233 254 / 0.5) var(--tw-gradient-to-position) +} + +.to-violet-100{ + --tw-gradient-to:#ede9fe var(--tw-gradient-to-position) +} + +.to-violet-100\/60{ + --tw-gradient-to:rgb(237 233 254 / 0.6) var(--tw-gradient-to-position) +} + .bg-contain{ background-size:contain } @@ -12136,6 +12527,10 @@ select{ object-position:center } +.object-left{ + object-position:left +} + .object-top{ object-position:top } @@ -12422,6 +12817,11 @@ select{ padding-bottom:1px } +.py-\[5px\]{ + padding-top:5px; + padding-bottom:5px +} + .\!pl-0{ padding-left:0px !important } @@ -12726,6 +13126,10 @@ select{ padding-top:1px } +.pl-\[calc\(min\(36vw\2c 8\.75rem\)\+0\.625rem\)\]{ + padding-left:calc(min(36vw,8.75rem) + 0.625rem) +} + .text-left{ text-align:left } @@ -13472,6 +13876,11 @@ select{ color:rgb(3 105 161 / 0.8) } +.text-sky-800{ + --tw-text-opacity:1; + color:rgb(7 89 133 / var(--tw-text-opacity, 1)) +} + .text-sky-900{ --tw-text-opacity:1; color:rgb(12 74 110 / var(--tw-text-opacity, 1)) @@ -13896,6 +14305,18 @@ select{ box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) } +.shadow-\[0_20px_50px_-16px_rgba\(15\2c 23\2c 42\2c 0\.38\)\]{ + --tw-shadow:0 20px 50px -16px rgba(15,23,42,0.38); + --tw-shadow-colored:0 20px 50px -16px var(--tw-shadow-color); + box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + +.shadow-\[0_28px_70px_-18px_rgba\(15\2c 23\2c 42\2c 0\.45\)\]{ + --tw-shadow:0 28px 70px -18px rgba(15,23,42,0.45); + --tw-shadow-colored:0 28px 70px -18px var(--tw-shadow-color); + box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + .shadow-black{ --tw-shadow-color:#000; --tw-shadow:var(--tw-shadow-colored) @@ -13916,6 +14337,31 @@ select{ --tw-shadow:var(--tw-shadow-colored) } +.shadow-slate-300{ + --tw-shadow-color:#cbd5e1; + --tw-shadow:var(--tw-shadow-colored) +} + +.shadow-slate-300\/40{ + --tw-shadow-color:rgb(203 213 225 / 0.4); + --tw-shadow:var(--tw-shadow-colored) +} + +.shadow-slate-300\/35{ + --tw-shadow-color:rgb(203 213 225 / 0.35); + --tw-shadow:var(--tw-shadow-colored) +} + +.shadow-slate-300\/30{ + --tw-shadow-color:rgb(203 213 225 / 0.3); + --tw-shadow:var(--tw-shadow-colored) +} + +.shadow-slate-300\/25{ + --tw-shadow-color:rgb(203 213 225 / 0.25); + --tw-shadow:var(--tw-shadow-colored) +} + .outline-none{ outline:2px solid transparent; outline-offset:2px @@ -14302,6 +14748,18 @@ select{ --tw-ring-color:rgb(253 230 138 / var(--tw-ring-opacity, 1)) } +.ring-slate-200\/60{ + --tw-ring-color:rgb(226 232 240 / 0.6) +} + +.ring-white\/80{ + --tw-ring-color:rgb(255 255 255 / 0.8) +} + +.ring-slate-200\/50{ + --tw-ring-color:rgb(226 232 240 / 0.5) +} + .ring-opacity-10{ --tw-ring-opacity:0.1 } @@ -14335,6 +14793,11 @@ select{ filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) } +.blur-2xl{ + --tw-blur:blur(40px); + filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) +} + .blur-3xl{ --tw-blur:blur(64px); filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) @@ -14390,6 +14853,11 @@ select{ filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) } +.drop-shadow-\[0_1px_2px_rgba\(15\2c 23\2c 42\2c 0\.35\)\]{ + --tw-drop-shadow:drop-shadow(0 1px 2px rgba(15,23,42,0.35)); + filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) +} + .drop-shadow-sm{ --tw-drop-shadow:drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)); filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) @@ -14502,6 +14970,12 @@ select{ transition-duration:150ms } +.transition-\[transform\2c box-shadow\]{ + transition-property:transform,box-shadow; + transition-timing-function:cubic-bezier(0.4, 0, 0.2, 1); + transition-duration:150ms +} + .delay-100{ transition-delay:100ms } @@ -15041,6 +15515,16 @@ input[type="checkbox"][indeterminate="true"]:checked { --tw-ring-opacity:0.5 } +.hover\:-translate-y-0\.5:hover{ + --tw-translate-y:-0.125rem; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + +.hover\:-translate-y-1:hover{ + --tw-translate-y:-0.25rem; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) +} + .hover\:scale-125:hover{ --tw-scale-x:1.25; --tw-scale-y:1.25; @@ -15611,6 +16095,12 @@ input[type="checkbox"][indeterminate="true"]:checked { box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) } +.hover\:shadow-xl:hover{ + --tw-shadow:0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); + box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow) +} + .hover\:outline:hover{ outline-style:solid } @@ -15638,6 +16128,10 @@ input[type="checkbox"][indeterminate="true"]:checked { --tw-ring-color:rgb(255 255 255 / 0.2) } +.hover\:ring-sky-200\/80:hover{ + --tw-ring-color:rgb(186 230 253 / 0.8) +} + .hover\:brightness-95:hover{ --tw-brightness:brightness(.95); filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) @@ -16479,6 +16973,10 @@ input[type="checkbox"][indeterminate="true"]:checked { position:relative } + .sm\:bottom-4{ + bottom:1rem + } + .sm\:left-0{ left:0px } @@ -16558,6 +17056,10 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-top:1.25rem } + .sm\:mt-12{ + margin-top:3rem + } + .sm\:block{ display:block } @@ -16614,6 +17116,10 @@ input[type="checkbox"][indeterminate="true"]:checked { height:100vh } + .sm\:h-\[400px\]{ + height:400px + } + .sm\:max-h-\[min\(90vh\2c calc\(100vh-3rem\)\)\]{ max-height:min(90vh,calc(100vh - 3rem)) } @@ -16659,6 +17165,22 @@ input[type="checkbox"][indeterminate="true"]:checked { width:max-content } + .sm\:w-\[38\%\]{ + width:38% + } + + .sm\:w-\[min\(78\%\2c 24rem\)\]{ + width:min(78%,24rem) + } + + .sm\:w-16{ + width:4rem + } + + .sm\:w-64{ + width:16rem + } + .sm\:max-w-2xl{ max-width:42rem } @@ -16782,6 +17304,10 @@ input[type="checkbox"][indeterminate="true"]:checked { gap:1px } + .sm\:gap-5{ + gap:1.25rem + } + .sm\:gap-x-4{ column-gap:1rem } @@ -16804,6 +17330,12 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-bottom:calc(0px * var(--tw-space-y-reverse)) } + .sm\:space-y-5 > :not([hidden]) ~ :not([hidden]){ + --tw-space-y-reverse:0; + margin-top:calc(1.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom:calc(1.25rem * var(--tw-space-y-reverse)) + } + .sm\:divide-y > :not([hidden]) ~ :not([hidden]){ --tw-divide-y-reverse:0; border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse))); @@ -16898,6 +17430,10 @@ input[type="checkbox"][indeterminate="true"]:checked { padding:2rem } + .sm\:p-5{ + padding:1.25rem + } + .sm\:px-0{ padding-left:0px; padding-right:0px @@ -16908,6 +17444,11 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-right:2.5rem } + .sm\:px-3{ + padding-left:0.75rem; + padding-right:0.75rem + } + .sm\:px-4{ padding-left:1rem; padding-right:1rem @@ -16928,6 +17469,11 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-bottom:0.5rem } + .sm\:py-2\.5{ + padding-top:0.625rem; + padding-bottom:0.625rem + } + .sm\:py-5{ padding-top:1.25rem; padding-bottom:1.25rem @@ -16961,6 +17507,10 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-top:0.5rem } + .sm\:pl-\[calc\(8rem\+0\.625rem\)\]{ + padding-left:calc(8rem + 0.625rem) + } + .sm\:text-left{ text-align:left } @@ -17017,6 +17567,11 @@ input[type="checkbox"][indeterminate="true"]:checked { line-height:1.75rem } + .sm\:text-xs{ + font-size:0.75rem; + line-height:1rem + } + .sm\:leading-10{ line-height:2.5rem } @@ -17064,11 +17619,26 @@ input[type="checkbox"][indeterminate="true"]:checked { position:relative } + .md\:-inset-x-5{ + left:-1.25rem; + right:-1.25rem + } + + .md\:-inset-y-16{ + top:-4rem; + bottom:-4rem + } + .md\:-inset-y-24{ top:-6rem; bottom:-6rem } + .md\:-inset-x-16{ + left:-4rem; + right:-4rem + } + .md\:-left-10{ left:-2.5rem } @@ -17137,6 +17707,10 @@ input[type="checkbox"][indeterminate="true"]:checked { top:25% } + .md\:top-\[30\%\]{ + top:30% + } + .md\:order-1{ order:1 } @@ -17165,14 +17739,35 @@ input[type="checkbox"][indeterminate="true"]:checked { grid-column:span 8 / span 8 } + .md\:col-span-12{ + grid-column:span 12 / span 12 + } + + .md\:col-span-5{ + grid-column:span 5 / span 5 + } + + .md\:col-span-7{ + grid-column:span 7 / span 7 + } + .md\:row-span-2{ grid-row:span 2 / span 2 } + .md\:row-span-3{ + grid-row:span 3 / span 3 + } + .md\:m-1{ margin:0.25rem } + .md\:-my-8{ + margin-top:-2rem; + margin-bottom:-2rem + } + .md\:mx-auto{ margin-left:auto; margin-right:auto @@ -17202,6 +17797,14 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-top:0px } + .md\:mt-14{ + margin-top:3.5rem + } + + .md\:mt-28{ + margin-top:7rem + } + .md\:block{ display:block } @@ -17254,6 +17857,14 @@ input[type="checkbox"][indeterminate="true"]:checked { height:100dvh } + .md\:h-\[460px\]{ + height:460px + } + + .md\:h-64{ + height:16rem + } + .md\:max-h-\[100dvh\]{ max-height:100dvh } @@ -17262,6 +17873,10 @@ input[type="checkbox"][indeterminate="true"]:checked { min-height:320px } + .md\:min-h-\[360px\]{ + min-height:360px + } + .md\:min-h-\[380px\]{ min-height:380px } @@ -17306,6 +17921,22 @@ input[type="checkbox"][indeterminate="true"]:checked { width:100% } + .md\:w-\[40\%\]{ + width:40% + } + + .md\:w-\[min\(68\%\2c 26rem\)\]{ + width:min(68%,26rem) + } + + .md\:w-24{ + width:6rem + } + + .md\:w-72{ + width:18rem + } + .md\:min-w-lg{ min-width:32rem } @@ -17362,12 +17993,23 @@ input[type="checkbox"][indeterminate="true"]:checked { flex-shrink:0 } + .md\:translate-y-5{ + --tw-translate-y:1.25rem; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) + } + .md\:scale-100{ --tw-scale-x:1; --tw-scale-y:1; transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) } + .md\:scale-\[1\.1\]{ + --tw-scale-x:1.1; + --tw-scale-y:1.1; + transform:translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)) + } + .md\:scale-\[1\.35\]{ --tw-scale-x:1.35; --tw-scale-y:1.35; @@ -17440,6 +18082,14 @@ input[type="checkbox"][indeterminate="true"]:checked { gap:2rem } + .md\:gap-5{ + gap:1.25rem + } + + .md\:gap-6{ + gap:1.5rem + } + .md\:space-x-2 > :not([hidden]) ~ :not([hidden]){ --tw-space-x-reverse:0; margin-right:calc(0.5rem * var(--tw-space-x-reverse)); @@ -17464,6 +18114,10 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-bottom:calc(5rem * var(--tw-space-y-reverse)) } + .md\:justify-self-center{ + justify-self:center + } + .md\:overflow-visible{ overflow:visible } @@ -17560,6 +18214,11 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-bottom:1.5rem } + .md\:px-3{ + padding-left:0.75rem; + padding-right:0.75rem + } + .md\:pb-10{ padding-bottom:2.5rem } @@ -17592,6 +18251,10 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-top:4rem } + .md\:pl-\[calc\(9rem\+0\.75rem\)\]{ + padding-left:calc(9rem + 0.75rem) + } + .md\:text-2xl{ font-size:1.5rem; line-height:2rem @@ -17735,6 +18398,14 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-top:1.5rem } + .lg\:mt-32{ + margin-top:8rem + } + + .lg\:mt-14{ + margin-top:3.5rem + } + .lg\:block{ display:block } @@ -17771,6 +18442,10 @@ input[type="checkbox"][indeterminate="true"]:checked { height:16rem } + .lg\:h-\[500px\]{ + height:500px + } + .lg\:min-h-\[420px\]{ min-height:420px } @@ -17803,6 +18478,10 @@ input[type="checkbox"][indeterminate="true"]:checked { width:24rem } + .lg\:w-80{ + width:20rem + } + .lg\:max-w-2xl{ max-width:42rem } @@ -17847,6 +18526,10 @@ input[type="checkbox"][indeterminate="true"]:checked { max-width:36rem } + .lg\:max-w-4xl{ + max-width:56rem + } + .lg\:flex-none{ flex:none } @@ -17912,6 +18595,10 @@ input[type="checkbox"][indeterminate="true"]:checked { gap:2rem } + .lg\:gap-6{ + gap:1.5rem + } + .lg\:space-x-5 > :not([hidden]) ~ :not([hidden]){ --tw-space-x-reverse:0; margin-right:calc(1.25rem * var(--tw-space-x-reverse)); @@ -17924,6 +18611,12 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-bottom:calc(7rem * var(--tw-space-y-reverse)) } + .lg\:space-y-6 > :not([hidden]) ~ :not([hidden]){ + --tw-space-y-reverse:0; + margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom:calc(1.5rem * var(--tw-space-y-reverse)) + } + .lg\:p-4{ padding:1rem } @@ -17979,6 +18672,10 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-bottom:7rem } + .lg\:pl-6{ + padding-left:1.5rem + } + .lg\:pl-8{ padding-left:2rem } @@ -17999,6 +18696,10 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-top:6rem } + .lg\:pl-\[calc\(10rem\+0\.75rem\)\]{ + padding-left:calc(10rem + 0.75rem) + } + .lg\:text-left{ text-align:left } @@ -18022,6 +18723,11 @@ input[type="checkbox"][indeterminate="true"]:checked { font-size:0.875rem; line-height:1.25rem } + + .lg\:text-4xl{ + font-size:2.25rem; + line-height:2.5rem + } } @media (min-width: 1280px){