diff --git a/packages/api/generated-schema.gql b/packages/api/generated-schema.gql index 7623cb281..9ea25c9ab 100644 --- a/packages/api/generated-schema.gql +++ b/packages/api/generated-schema.gql @@ -2287,6 +2287,41 @@ type CreateOptionalBasemapLayerPayload { query: Query } +"""All input for the create `OriginalSourceId` mutation.""" +input CreateOriginalSourceIdInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + + """The `OriginalSourceId` to be created by this mutation.""" + originalSourceId: OriginalSourceIdInput! +} + +"""The output of our create `OriginalSourceId` mutation.""" +type CreateOriginalSourceIdPayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `OriginalSourceId` that was created by this mutation.""" + originalSourceId: OriginalSourceId + + """An edge for our `OriginalSourceId`. May be used by Relay 1.""" + originalSourceIdEdge( + """The method to use when ordering `OriginalSourceId`.""" + orderBy: [OriginalSourceIdsOrderBy!] = [NATURAL] + ): OriginalSourceIdsEdge + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query +} + input CreateProjectGeographyClippingLayerInput { """ If provided, features used for clipping will be filtered based on this @@ -2480,6 +2515,41 @@ input CreateProjectWithGeographiesInput { slug: String! } +"""All input for the create `PublishedTocItemId` mutation.""" +input CreatePublishedTocItemIdInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + + """The `PublishedTocItemId` to be created by this mutation.""" + publishedTocItemId: PublishedTocItemIdInput! +} + +"""The output of our create `PublishedTocItemId` mutation.""" +type CreatePublishedTocItemIdPayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + + """The `PublishedTocItemId` that was created by this mutation.""" + publishedTocItemId: PublishedTocItemId + + """An edge for our `PublishedTocItemId`. May be used by Relay 1.""" + publishedTocItemIdEdge( + """The method to use when ordering `PublishedTocItemId`.""" + orderBy: [PublishedTocItemIdsOrderBy!] = [NATURAL] + ): PublishedTocItemIdsEdge + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query +} + """All input for the `createRemoteGeojsonSource` mutation.""" input CreateRemoteGeojsonSourceInput { bounds: [BigFloat] @@ -8134,6 +8204,31 @@ type GetChildFoldersRecursivePayload { query: Query } +"""All input for the `getPublishedCardIdFromDraft` mutation.""" +input GetPublishedCardIdFromDraftInput { + """ + An arbitrary string value with no semantic meaning. Will be included in the + payload verbatim. May be used to track mutations by the client. + """ + clientMutationId: String + draftReportCardId: Int +} + +"""The output of our `getPublishedCardIdFromDraft` mutation.""" +type GetPublishedCardIdFromDraftPayload { + """ + The exact same `clientMutationId` that was provided in the mutation input, + unchanged and unused. May be used by a client to track mutations. + """ + clientMutationId: String + integer: Int + + """ + Our root query field type. Allows us to run any query from our mutation payload. + """ + query: Query +} + type GoogleMapsTileApiSession implements Node { expiresAt: Datetime! id: Int! @@ -9431,6 +9526,14 @@ type Mutation { """ input: CreateOptionalBasemapLayerInput! ): CreateOptionalBasemapLayerPayload + + """Creates a single `OriginalSourceId`.""" + createOriginalSourceId( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: CreateOriginalSourceIdInput! + ): CreateOriginalSourceIdPayload createPost(message: JSON!, topicId: Int!): Post! """ @@ -9491,6 +9594,14 @@ type Mutation { """ input: CreateProjectWithGeographiesInput! ): CreateProjectPayload + + """Creates a single `PublishedTocItemId`.""" + createPublishedTocItemId( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: CreatePublishedTocItemIdInput! + ): CreatePublishedTocItemIdPayload createRemoteGeojsonSource( """ The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. @@ -10247,6 +10358,12 @@ type Mutation { width: Int! ): Sprite getPresignedPMTilesUploadUrl(bytes: BigInt!, filename: String!): PresignedUrl! + getPublishedCardIdFromDraft( + """ + The exclusive input argument for this mutation. An object type, make sure to see documentation for this object’s fields. + """ + input: GetPublishedCardIdFromDraftInput! + ): GetPublishedCardIdFromDraftPayload """ Give a user admin access to a project. User must have already joined the project and shared their user profile. @@ -11812,6 +11929,48 @@ enum OptionalBasemapLayersOrderBy { PRIMARY_KEY_DESC } +type OriginalSourceId { + dataSourceId: Int +} + +"""An input for mutations affecting `OriginalSourceId`""" +input OriginalSourceIdInput { + dataSourceId: Int +} + +"""A connection to a list of `OriginalSourceId` values.""" +type OriginalSourceIdsConnection { + """ + A list of edges which contains the `OriginalSourceId` and cursor to aid in pagination. + """ + edges: [OriginalSourceIdsEdge!]! + + """A list of `OriginalSourceId` objects.""" + nodes: [OriginalSourceId!]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """ + The count of *all* `OriginalSourceId` you could get from the connection. + """ + totalCount: Int! +} + +"""A `OriginalSourceId` edge in the connection.""" +type OriginalSourceIdsEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The `OriginalSourceId` at the end of the edge.""" + node: OriginalSourceId! +} + +"""Methods to use when ordering `OriginalSourceId`.""" +enum OriginalSourceIdsOrderBy { + NATURAL +} + type OutstandingSurveyInvites { projectId: Int! surveyId: Int! @@ -13503,6 +13662,48 @@ type PublicProjectDetail { supportEmail: String } +type PublishedTocItemId { + id: Int +} + +"""An input for mutations affecting `PublishedTocItemId`""" +input PublishedTocItemIdInput { + id: Int +} + +"""A connection to a list of `PublishedTocItemId` values.""" +type PublishedTocItemIdsConnection { + """ + A list of edges which contains the `PublishedTocItemId` and cursor to aid in pagination. + """ + edges: [PublishedTocItemIdsEdge!]! + + """A list of `PublishedTocItemId` objects.""" + nodes: [PublishedTocItemId!]! + + """Information to aid in pagination.""" + pageInfo: PageInfo! + + """ + The count of *all* `PublishedTocItemId` you could get from the connection. + """ + totalCount: Int! +} + +"""A `PublishedTocItemId` edge in the connection.""" +type PublishedTocItemIdsEdge { + """A cursor for use in pagination.""" + cursor: Cursor + + """The `PublishedTocItemId` at the end of the edge.""" + node: PublishedTocItemId! +} + +"""Methods to use when ordering `PublishedTocItemId`.""" +enum PublishedTocItemIdsOrderBy { + NATURAL +} + """All input for the `publishReport` mutation.""" input PublishReportInput { """ @@ -14297,6 +14498,30 @@ type Query implements Node { """ nodeId: ID! ): OptionalBasemapLayer + + """Reads and enables pagination through a set of `OriginalSourceId`.""" + originalSourceIdsConnection( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Only read the first `n` values of the set.""" + first: Int + + """Only read the last `n` values of the set.""" + last: Int + + """ + Skip the first `n` values from our `after` cursor, an alternative to cursor + based pagination. May not be used with `last`. + """ + offset: Int + + """The method to use when ordering `OriginalSourceId`.""" + orderBy: [OriginalSourceIdsOrderBy!] = [NATURAL] + ): OriginalSourceIdsConnection post(id: Int!): Post """Reads a single `Post` using its globally unique `ID`.""" @@ -14482,6 +14707,30 @@ type Query implements Node { offset: Int ): [Sprite!] + """Reads and enables pagination through a set of `PublishedTocItemId`.""" + publishedTocItemIdsConnection( + """Read all values in the set after (below) this cursor.""" + after: Cursor + + """Read all values in the set before (above) this cursor.""" + before: Cursor + + """Only read the first `n` values of the set.""" + first: Int + + """Only read the last `n` values of the set.""" + last: Int + + """ + Skip the first `n` values from our `after` cursor, an alternative to cursor + based pagination. May not be used with `last`. + """ + offset: Int + + """The method to use when ordering `PublishedTocItemId`.""" + orderBy: [PublishedTocItemIdsOrderBy!] = [NATURAL] + ): PublishedTocItemIdsConnection + """ Exposes the root query type nested one level down. This is helpful for Relay 1 which can only query top level fields if they are in a particular form. diff --git a/packages/client/cypress/integration/1-smoke/map-portal-hosting.spec.js b/packages/client/cypress/integration/1-smoke/map-portal-hosting.spec.js new file mode 100644 index 000000000..101d53cee --- /dev/null +++ b/packages/client/cypress/integration/1-smoke/map-portal-hosting.spec.js @@ -0,0 +1,31 @@ +// type definitions for Cypress object "cy" +/// + +const devices = ["macbook-15", "ipad-2", "iphone-x", "iphone-5"]; + +/** + * Given I am an anonymous user + * When I visit the Map Portal Hosting use case page (on desktop or mobile) + * Then I see key marketing content + * And the page does not introduce horizontal overflow + */ +describe("Map Portal Hosting use case page", () => { + devices.forEach((device) => { + it(`renders key content without horizontal overflow - ${device}`, () => { + cy.viewport(device); + cy.visit("/uses/map-portal-hosting"); + cy.contains("living map"); + cy.contains("Fast, beautiful maps"); + cy.contains("Additional Features"); + cy.get("main").should("exist"); + cy.document().then((doc) => { + const scrollWidth = doc.documentElement.scrollWidth; + const innerWidth = doc.documentElement.clientWidth; + expect(scrollWidth).to.be.at.most(innerWidth + 1); + }); + cy.screenshot({ + capture: "fullPage", + }); + }); + }); +}); diff --git a/packages/client/public/uses/data-governance.png b/packages/client/public/uses/data-governance.png new file mode 100644 index 000000000..b928018f6 Binary files /dev/null and b/packages/client/public/uses/data-governance.png differ diff --git a/packages/client/public/uses/drag-and-drop-data.png b/packages/client/public/uses/drag-and-drop-data.png new file mode 100644 index 000000000..f07809c1a Binary files /dev/null and b/packages/client/public/uses/drag-and-drop-data.png differ diff --git a/packages/client/public/uses/fast-beautiful-maps-cursor.png b/packages/client/public/uses/fast-beautiful-maps-cursor.png new file mode 100644 index 000000000..f94e363fd Binary files /dev/null and b/packages/client/public/uses/fast-beautiful-maps-cursor.png differ diff --git a/packages/client/public/uses/fast-beautiful-maps-tiles.png b/packages/client/public/uses/fast-beautiful-maps-tiles.png new file mode 100644 index 000000000..32d967c67 Binary files /dev/null and b/packages/client/public/uses/fast-beautiful-maps-tiles.png differ diff --git a/packages/client/public/uses/fast-beautiful-maps.png b/packages/client/public/uses/fast-beautiful-maps.png new file mode 100644 index 000000000..d863209a0 Binary files /dev/null and b/packages/client/public/uses/fast-beautiful-maps.png differ diff --git a/packages/client/public/uses/graphical-cartography-tools.png b/packages/client/public/uses/graphical-cartography-tools.png new file mode 100644 index 000000000..d706b4839 Binary files /dev/null and b/packages/client/public/uses/graphical-cartography-tools.png differ diff --git a/packages/client/public/uses/map-portal-hero.png b/packages/client/public/uses/map-portal-hero.png new file mode 100644 index 000000000..649499d54 Binary files /dev/null and b/packages/client/public/uses/map-portal-hero.png differ diff --git a/packages/client/public/uses/outer-reef-flat.png b/packages/client/public/uses/outer-reef-flat.png new file mode 100644 index 000000000..4e057cbec Binary files /dev/null and b/packages/client/public/uses/outer-reef-flat.png differ diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index b9e20ffbe..ded6d6549 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -21,6 +21,11 @@ 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 { CaseStudiesIndex, AzoresCaseStudy, @@ -103,7 +108,9 @@ function App() { useTranslation("homepage"); const [error, setError] = useState(null); const location = useLocation(); + const isUseCaseRoute = location.pathname.startsWith("/uses/"); const isDarkRoutes = + isUseCaseRoute || location.pathname === "/" || location.pathname === "/about" || location.pathname === "/projects" || @@ -140,6 +147,14 @@ function App() { } }, [location.pathname, location.hash]); + // Ensure use-case detail pages always start at the top when navigating + // between routes in the SPA. + useEffect(() => { + if (location.pathname.startsWith("/uses/")) { + window.scrollTo({ top: 0, left: 0, behavior: "auto" }); + } + }, [location.pathname]); + const frontOfTheHouse = [ "/signin", "/projects", @@ -150,9 +165,9 @@ function App() { "/team", "/terms-of-use", "/privacy-policy", - "/uses/map-portal", - "/uses/surveys", - "/uses/planning", + "/uses/map-portal-hosting", + "/uses/ocean-use-surveys", + "/uses/sketching-and-analysis", "/case-studies", "/case-studies/*", ]; @@ -238,6 +253,24 @@ function App() { + + + + + + + + + + + + + + + + + + diff --git a/packages/client/src/admin/uploads/ProjectBackgroundJobContext.tsx b/packages/client/src/admin/uploads/ProjectBackgroundJobContext.tsx index e6bdfeca9..b1eef84c9 100644 --- a/packages/client/src/admin/uploads/ProjectBackgroundJobContext.tsx +++ b/packages/client/src/admin/uploads/ProjectBackgroundJobContext.tsx @@ -1,9 +1,11 @@ +import { AnimatePresence, motion } from "framer-motion"; import { createContext, ReactNode, useCallback, useContext, useEffect, + useRef, useState, } from "react"; import { useDropzone } from "react-dropzone"; @@ -22,12 +24,12 @@ import { import { Trans, useTranslation } from "react-i18next"; import { createPortal } from "react-dom"; import Spinner from "../../components/Spinner"; +import Button from "../../components/Button"; import { ExclamationCircleIcon } from "@heroicons/react/outline"; import useProjectId from "../../useProjectId"; import { useApolloClient } from "@apollo/client"; import { MapManagerContext } from "../../dataLayers/MapContextManager"; -import { useGlobalErrorHandler } from "../../components/GlobalErrorHandler"; import useDialog from "../../components/useDialog"; import ProjectBackgroundJobManager, { DataUploadErrorEvent, @@ -39,6 +41,144 @@ import AiDataAnalystUploadPromptModal from "./AiDataAnalystUploadPromptModal"; export type UploadType = "create" | "replace"; +type SupportedSpatialFormat = + | "geojson" + | "shapefileZip" + | "geotiff" + | "netcdf" + | "flatgeobuf"; + +const SUPPORTED_FORMATS: { + id: SupportedSpatialFormat; + label: string; + extensions: string; + tag: string; +}[] = [ + { + id: "geojson", + label: "GeoJSON", + extensions: ".geojson, .json", + tag: "JSON", + }, + { + id: "shapefileZip", + label: "Shapefile (zipped)", + extensions: ".zip", + tag: "ZIP", + }, + { + id: "geotiff", + label: "GeoTiff", + extensions: ".tif, .tiff", + tag: "TIFF", + }, + { + id: "netcdf", + label: "NetCDF", + extensions: ".nc, .nc4", + tag: "NC", + }, + { + id: "flatgeobuf", + label: "FlatGeobuf", + extensions: ".fgb", + tag: "FGB", + }, +]; + +// A generic "document" glyph (a page with a folded corner) used to represent +// every supported format. The format-specific text lives outside the icon (name +// + extensions); the icon only carries a short, uppercase type tag on its label +// band so the card reads clearly without relying on bespoke iconography. +function DocumentFormatIcon({ + tag, + active, + className, +}: { + tag: string; + active?: boolean; + className?: string; +}) { + const accent = active ? "#0891b2" : "#94a3b8"; + const fold = active ? "#cffafe" : "#e2e8f0"; + return ( + + ); +} + +function fileExtension(fileName: string): string { + const dotIndex = fileName.lastIndexOf("."); + if (dotIndex < 0) return ""; + return fileName.slice(dotIndex).toLowerCase(); +} + +function detectSupportedFormat( + fileName: string | null +): SupportedSpatialFormat | null { + if (!fileName) return null; + const ext = fileExtension(fileName); + switch (ext) { + case ".geojson": + case ".json": + return "geojson"; + case ".zip": + return "shapefileZip"; + case ".tif": + case ".tiff": + return "geotiff"; + case ".nc": + case ".nc4": + return "netcdf"; + case ".fgb": + return "flatgeobuf"; + default: + return null; + } +} + +type DroppedFileInfo = { + name: string; + format: SupportedSpatialFormat | null; +}; + +// How long the drop confirmation lingers before fading out and handing off to +// the background job queue UI. Errors cancel this and require manual dismissal. +const OVERLAY_DISMISS_DELAY = 1500; + export const ProjectBackgroundJobContext = createContext<{ jobs: JobDetailsFragment[]; manager?: ProjectBackgroundJobManager; @@ -74,8 +214,9 @@ export default function DataUploadDropzone({ const projectId = useProjectId(); const [state, setState] = useState<{ droppedFiles: number; + droppedFileInfos: DroppedFileInfo[]; uploads: DataUploadDetailsFragment[]; - error?: string; + error?: ReactNode; manager?: ProjectBackgroundJobManager; disabled?: boolean; uploadType: UploadType; @@ -86,6 +227,7 @@ export default function DataUploadDropzone({ aiDataAnalystUploadPromptOpen: boolean; }>({ droppedFiles: 0, + droppedFileInfos: [], uploads: [], disabled: false, uploadType: "create", @@ -95,8 +237,8 @@ export default function DataUploadDropzone({ aiDataAnalystUploadPromptOpen: false, }); const client = useApolloClient(); + const dismissTimerRef = useRef | null>(null); const { manager } = useContext(MapManagerContext); - const onError = useGlobalErrorHandler(); const { alert } = useDialog(); const { t } = useTranslation("admin:data"); const [hostOnSeaSketch, setHostOnSeasketch] = useState(null); @@ -147,9 +289,14 @@ export default function DataUploadDropzone({ } ); manager.on("upload-error", (event: DataUploadErrorEvent) => { + if (dismissTimerRef.current) { + clearTimeout(dismissTimerRef.current); + dismissTimerRef.current = null; + } setState((prev) => ({ ...prev, droppedFiles: 0, + droppedFileInfos: [], error: event.error, isUploadingReplacement: false, })); @@ -174,12 +321,54 @@ export default function DataUploadDropzone({ manager.destroy(); }; } - }, [client, slug, projectId, manager, alert, onError, t]); + }, [client, slug, projectId, manager, alert, t]); const { confirm } = useDialog(); + const clearDismissTimer = useCallback(() => { + if (dismissTimerRef.current) { + clearTimeout(dismissTimerRef.current); + dismissTimerRef.current = null; + } + }, []); + + // Fade the drop confirmation away after a short delay so the persistent job + // queue UI can take over. Cancelled if an error needs manual dismissal. + const scheduleDismiss = useCallback( + (delay: number) => { + clearDismissTimer(); + dismissTimerRef.current = setTimeout(() => { + dismissTimerRef.current = null; + setState((prev) => { + if (prev.error) { + return prev; + } + return { + ...prev, + droppedFiles: 0, + droppedFileInfos: [], + }; + }); + }, delay); + }, + [clearDismissTimer] + ); + + const dismissOverlay = useCallback(() => { + clearDismissTimer(); + setState((prev) => ({ + ...prev, + droppedFiles: 0, + droppedFileInfos: [], + error: undefined, + })); + }, [clearDismissTimer]); + + useEffect(() => clearDismissTimer, [clearDismissTimer]); + const onDrop = useCallback( async (acceptedFiles: File[]) => { + clearDismissTimer(); function isUploadForSupported(file: File) { let message: string | null = null; let isPartOfShapefile = false; @@ -247,11 +436,18 @@ export default function DataUploadDropzone({ } if (state.uploadType === "replace" && acceptedFiles.length > 1) { - alert(t("You can only upload one file to update a layer."), { - description: t( - "To replace a layer, upload a single file that will replace the existing layer. Close the data source editor if you would like to create new layers." + setState((prev) => ({ + ...prev, + droppedFiles: 0, + droppedFileInfos: [], + error: ( + + You can only upload one file to update a layer. To replace a + layer, drop a single file. Close the data source editor if you + would like to create new layers instead. + ), - }); + })); return; } @@ -281,9 +477,20 @@ export default function DataUploadDropzone({ } } + if (filteredFiles.length === 0) { + return; + } + + const droppedFileInfos: DroppedFileInfo[] = filteredFiles.map((file) => ({ + name: file.name, + format: detectSupportedFormat(file.name), + })); + setState((prev) => ({ ...prev, droppedFiles: filteredFiles.length, + droppedFileInfos, + error: undefined, })); if (state.manager) { @@ -294,6 +501,9 @@ export default function DataUploadDropzone({ finishedWithChangelog: false, })); } + // Keep the confirmation visible briefly, then fade out and let the + // background job queue UI report on progress from here. + scheduleDismiss(OVERLAY_DISMISS_DELAY); state.manager .uploadFiles( filteredFiles, @@ -304,44 +514,38 @@ export default function DataUploadDropzone({ } : undefined ) - .then(() => { - setState((prev) => ({ - ...prev, - droppedFiles: 0, - })); - }) .catch((e) => { + clearDismissTimer(); + const error: ReactNode = /quota exceeded/.test(e.message) ? ( + + This project has exceeded its data storage quota. Please delete + some data to make room for new uploads. You can see how much + space your layers are using by selecting{" "} + View {"->"} Data Hosting Quota from the toolbar. + + ) : ( + e.message + ); setState((prev) => ({ ...prev, droppedFiles: 0, + droppedFileInfos: [], isUploadingReplacement: false, finishedWithChangelog: true, + error, })); - if (/quota exceeded/.test(e.message)) { - alert(t("Quota Exceeded"), { - description: ( - - This project has exceeded its data storage quota. Please - delete some data to make room for new uploads. You can see - how much space your layers are using by selecting{" "} - View {"->"} Data Hosting Quota from the toolbar. - - ), - }); - } else { - onError(e); - } }); } }, [ alert, confirm, - onError, state.manager, t, state.uploadType, state.replaceTableOfContentsItemId, + scheduleDismiss, + clearDismissTimer, ] ); @@ -350,6 +554,24 @@ export default function DataUploadDropzone({ noClick: true, }); + const showOverlay = + isDragActive || state.droppedFiles > 0 || Boolean(state.error); + + const phase: "hover" | "processing" | "error" = state.error + ? "error" + : state.droppedFiles > 0 + ? "processing" + : "hover"; + + const droppedFormatIds = new Set( + state.droppedFileInfos + .map((f) => f.format) + .filter((f): f is SupportedSpatialFormat => Boolean(f)) + ); + + const singleDroppedFile = + state.droppedFileInfos.length === 1 ? state.droppedFileInfos[0] : null; + const setDisabled = useCallback( (disabled: boolean) => { setState((prev) => ({ @@ -435,41 +657,176 @@ export default function DataUploadDropzone({ )} {children} - {(isDragActive || state.droppedFiles > 0) && + {showOverlay && createPortal( - <> -
-
-

- {state.uploadType === "create" - ? t("Drop Files Here to Upload") - : t("Drop a file to update this layer")} -

-

- {t( - "SeaSketch currently supports vector data in GeoJSON, Shapefile (zipped), GeoTiff, NetCDF and FlatGeobuf formats." - )} -

- {Boolean(state.droppedFiles) && !state.error && ( -
- {t("Starting upload")} - -
- )} - {state.error && ( -
- - {state.error} + + {showOverlay && ( + + +
+

+ {phase === "error" + ? t("We couldn't process that") + : phase === "processing" + ? singleDroppedFile + ? t("Processing {{filename}}", { + filename: singleDroppedFile.name, + }) + : t("Processing {{count}} files", { + count: state.droppedFileInfos.length, + }) + : state.uploadType === "create" + ? t("Drop Files Here to Upload") + : t("Drop a file to update this layer")} +

+ + + {phase === "processing" && ( + + + {t("Beginning data processing...")} + + )} + + + {phase === "hover" && ( +
+ {t("Supported Formats")} +
+ )} + + {phase !== "error" && ( + + + {SUPPORTED_FORMATS.filter((format) => + phase === "processing" + ? droppedFormatIds.has(format.id) + : true + ).map((format) => { + const active = phase === "processing"; + return ( + + + + +
+ {t(format.label)} +
+
+ {format.extensions} +
+
+ ); + })} +
+
+ )} + + + {phase === "error" && ( + +
+ +
{state.error}
+
+
+
+
+ )} +
- )} -
-
- , + + + )} + , document.body )}
diff --git a/packages/client/src/generated/graphql.ts b/packages/client/src/generated/graphql.ts index acd2ab85f..613b15179 100644 --- a/packages/client/src/generated/graphql.ts +++ b/packages/client/src/generated/graphql.ts @@ -2022,6 +2022,39 @@ export type CreateOptionalBasemapLayerPayload = { query?: Maybe; }; +/** All input for the create `OriginalSourceId` mutation. */ +export type CreateOriginalSourceIdInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: Maybe; + /** The `OriginalSourceId` to be created by this mutation. */ + originalSourceId: OriginalSourceIdInput; +}; + +/** The output of our create `OriginalSourceId` mutation. */ +export type CreateOriginalSourceIdPayload = { + __typename?: 'CreateOriginalSourceIdPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `OriginalSourceId` that was created by this mutation. */ + originalSourceId?: Maybe; + /** An edge for our `OriginalSourceId`. May be used by Relay 1. */ + originalSourceIdEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our create `OriginalSourceId` mutation. */ +export type CreateOriginalSourceIdPayloadOriginalSourceIdEdgeArgs = { + orderBy?: Maybe>; +}; + export type CreateProjectGeographyClippingLayerInput = { /** * If provided, features used for clipping will be filtered based on this @@ -2193,6 +2226,39 @@ export type CreateProjectsSharedBasemapPayloadProjectsSharedBasemapEdgeArgs = { orderBy?: Maybe>; }; +/** All input for the create `PublishedTocItemId` mutation. */ +export type CreatePublishedTocItemIdInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: Maybe; + /** The `PublishedTocItemId` to be created by this mutation. */ + publishedTocItemId: PublishedTocItemIdInput; +}; + +/** The output of our create `PublishedTocItemId` mutation. */ +export type CreatePublishedTocItemIdPayload = { + __typename?: 'CreatePublishedTocItemIdPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `PublishedTocItemId` that was created by this mutation. */ + publishedTocItemId?: Maybe; + /** An edge for our `PublishedTocItemId`. May be used by Relay 1. */ + publishedTocItemIdEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our create `PublishedTocItemId` mutation. */ +export type CreatePublishedTocItemIdPayloadPublishedTocItemIdEdgeArgs = { + orderBy?: Maybe>; +}; + /** All input for the `createRemoteGeojsonSource` mutation. */ export type CreateRemoteGeojsonSourceInput = { bounds?: Maybe>>; @@ -7042,6 +7108,29 @@ export type GetChildFoldersRecursivePayload = { query?: Maybe; }; +/** All input for the `getPublishedCardIdFromDraft` mutation. */ +export type GetPublishedCardIdFromDraftInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: Maybe; + draftReportCardId?: Maybe; +}; + +/** The output of our `getPublishedCardIdFromDraft` mutation. */ +export type GetPublishedCardIdFromDraftPayload = { + __typename?: 'GetPublishedCardIdFromDraftPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + integer?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + export type GoogleMapsTileApiSession = Node & { __typename?: 'GoogleMapsTileApiSession'; expiresAt: Scalars['Datetime']; @@ -7979,6 +8068,8 @@ export type Mutation = { createOfflineTileSetting?: Maybe; /** Creates a single `OptionalBasemapLayer`. */ createOptionalBasemapLayer?: Maybe; + /** Creates a single `OriginalSourceId`. */ + createOriginalSourceId?: Maybe; createPost: Post; /** * Users with verified emails can create new projects by choosing a unique name @@ -8008,6 +8099,8 @@ export type Mutation = { * layers have not yet been created. */ createProjectWithGeographies?: Maybe; + /** Creates a single `PublishedTocItemId`. */ + createPublishedTocItemId?: Maybe; createRemoteGeojsonSource?: Maybe; createRemoteMvtSource?: Maybe; /** Creates a single `Report`. */ @@ -8205,6 +8298,7 @@ export type Mutation = { */ getOrCreateSprite?: Maybe; getPresignedPMTilesUploadUrl: PresignedUrl; + getPublishedCardIdFromDraft?: Maybe; /** Give a user admin access to a project. User must have already joined the project and shared their user profile. */ grantAdminAccess?: Maybe; importArcgisServices?: Maybe; @@ -8822,6 +8916,12 @@ export type MutationCreateOptionalBasemapLayerArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationCreateOriginalSourceIdArgs = { + input: CreateOriginalSourceIdInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationCreatePostArgs = { message: Scalars['JSON']; @@ -8859,6 +8959,12 @@ export type MutationCreateProjectWithGeographiesArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationCreatePublishedTocItemIdArgs = { + input: CreatePublishedTocItemIdInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationCreateRemoteGeojsonSourceArgs = { input: CreateRemoteGeojsonSourceInput; @@ -9461,6 +9567,12 @@ export type MutationGetPresignedPmTilesUploadUrlArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationGetPublishedCardIdFromDraftArgs = { + input: GetPublishedCardIdFromDraftInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationGrantAdminAccessArgs = { input: GrantAdminAccessInput; @@ -10671,6 +10783,43 @@ export enum OptionalBasemapLayersOrderBy { PrimaryKeyDesc = 'PRIMARY_KEY_DESC' } +export type OriginalSourceId = { + __typename?: 'OriginalSourceId'; + dataSourceId?: Maybe; +}; + +/** An input for mutations affecting `OriginalSourceId` */ +export type OriginalSourceIdInput = { + dataSourceId?: Maybe; +}; + +/** A connection to a list of `OriginalSourceId` values. */ +export type OriginalSourceIdsConnection = { + __typename?: 'OriginalSourceIdsConnection'; + /** A list of edges which contains the `OriginalSourceId` and cursor to aid in pagination. */ + edges: Array; + /** A list of `OriginalSourceId` objects. */ + nodes: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** The count of *all* `OriginalSourceId` you could get from the connection. */ + totalCount: Scalars['Int']; +}; + +/** A `OriginalSourceId` edge in the connection. */ +export type OriginalSourceIdsEdge = { + __typename?: 'OriginalSourceIdsEdge'; + /** A cursor for use in pagination. */ + cursor?: Maybe; + /** The `OriginalSourceId` at the end of the edge. */ + node: OriginalSourceId; +}; + +/** Methods to use when ordering `OriginalSourceId`. */ +export enum OriginalSourceIdsOrderBy { + Natural = 'NATURAL' +} + export type OutstandingSurveyInvites = { __typename?: 'OutstandingSurveyInvites'; projectId: Scalars['Int']; @@ -12245,6 +12394,43 @@ export type PublishTableOfContentsPayload = { tableOfContentsItems?: Maybe>; }; +export type PublishedTocItemId = { + __typename?: 'PublishedTocItemId'; + id?: Maybe; +}; + +/** An input for mutations affecting `PublishedTocItemId` */ +export type PublishedTocItemIdInput = { + id?: Maybe; +}; + +/** A connection to a list of `PublishedTocItemId` values. */ +export type PublishedTocItemIdsConnection = { + __typename?: 'PublishedTocItemIdsConnection'; + /** A list of edges which contains the `PublishedTocItemId` and cursor to aid in pagination. */ + edges: Array; + /** A list of `PublishedTocItemId` objects. */ + nodes: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** The count of *all* `PublishedTocItemId` you could get from the connection. */ + totalCount: Scalars['Int']; +}; + +/** A `PublishedTocItemId` edge in the connection. */ +export type PublishedTocItemIdsEdge = { + __typename?: 'PublishedTocItemIdsEdge'; + /** A cursor for use in pagination. */ + cursor?: Maybe; + /** The `PublishedTocItemId` at the end of the edge. */ + node: PublishedTocItemId; +}; + +/** Methods to use when ordering `PublishedTocItemId`. */ +export enum PublishedTocItemIdsOrderBy { + Natural = 'NATURAL' +} + /** The root query type which gives access points into the data universe. */ export type Query = Node & { __typename?: 'Query'; @@ -12424,6 +12610,8 @@ export type Query = Node & { optionalBasemapLayer?: Maybe; /** Reads a single `OptionalBasemapLayer` using its globally unique `ID`. */ optionalBasemapLayerByNodeId?: Maybe; + /** Reads and enables pagination through a set of `OriginalSourceId`. */ + originalSourceIdsConnection?: Maybe; post?: Maybe; /** Reads a single `Post` using its globally unique `ID`. */ postByNodeId?: Maybe; @@ -12455,6 +12643,8 @@ export type Query = Node & { projectsSharedBasemapsConnection?: Maybe; /** Used by project administrators to access a list of public sprites promoted by the SeaSketch development team. */ publicSprites?: Maybe>; + /** Reads and enables pagination through a set of `PublishedTocItemId`. */ + publishedTocItemIdsConnection?: Maybe; /** * Exposes the root query type nested one level down. This is helpful for Relay 1 * which can only query top level fields if they are in a particular form. @@ -13308,6 +13498,17 @@ export type QueryOptionalBasemapLayerByNodeIdArgs = { }; +/** The root query type which gives access points into the data universe. */ +export type QueryOriginalSourceIdsConnectionArgs = { + after?: Maybe; + before?: Maybe; + first?: Maybe; + last?: Maybe; + offset?: Maybe; + orderBy?: Maybe>; +}; + + /** The root query type which gives access points into the data universe. */ export type QueryPostArgs = { id: Scalars['Int']; @@ -13465,6 +13666,17 @@ export type QueryPublicSpritesArgs = { }; +/** The root query type which gives access points into the data universe. */ +export type QueryPublishedTocItemIdsConnectionArgs = { + after?: Maybe; + before?: Maybe; + first?: Maybe; + last?: Maybe; + offset?: Maybe; + orderBy?: Maybe>; +}; + + /** The root query type which gives access points into the data universe. */ export type QueryReportArgs = { id: Scalars['Int']; diff --git a/packages/client/src/generated/queries.ts b/packages/client/src/generated/queries.ts index 4191daf99..62c4f45b4 100644 --- a/packages/client/src/generated/queries.ts +++ b/packages/client/src/generated/queries.ts @@ -2020,6 +2020,39 @@ export type CreateOptionalBasemapLayerPayload = { query?: Maybe; }; +/** All input for the create `OriginalSourceId` mutation. */ +export type CreateOriginalSourceIdInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: Maybe; + /** The `OriginalSourceId` to be created by this mutation. */ + originalSourceId: OriginalSourceIdInput; +}; + +/** The output of our create `OriginalSourceId` mutation. */ +export type CreateOriginalSourceIdPayload = { + __typename?: 'CreateOriginalSourceIdPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `OriginalSourceId` that was created by this mutation. */ + originalSourceId?: Maybe; + /** An edge for our `OriginalSourceId`. May be used by Relay 1. */ + originalSourceIdEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our create `OriginalSourceId` mutation. */ +export type CreateOriginalSourceIdPayloadOriginalSourceIdEdgeArgs = { + orderBy?: Maybe>; +}; + export type CreateProjectGeographyClippingLayerInput = { /** * If provided, features used for clipping will be filtered based on this @@ -2191,6 +2224,39 @@ export type CreateProjectsSharedBasemapPayloadProjectsSharedBasemapEdgeArgs = { orderBy?: Maybe>; }; +/** All input for the create `PublishedTocItemId` mutation. */ +export type CreatePublishedTocItemIdInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: Maybe; + /** The `PublishedTocItemId` to be created by this mutation. */ + publishedTocItemId: PublishedTocItemIdInput; +}; + +/** The output of our create `PublishedTocItemId` mutation. */ +export type CreatePublishedTocItemIdPayload = { + __typename?: 'CreatePublishedTocItemIdPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + /** The `PublishedTocItemId` that was created by this mutation. */ + publishedTocItemId?: Maybe; + /** An edge for our `PublishedTocItemId`. May be used by Relay 1. */ + publishedTocItemIdEdge?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + + +/** The output of our create `PublishedTocItemId` mutation. */ +export type CreatePublishedTocItemIdPayloadPublishedTocItemIdEdgeArgs = { + orderBy?: Maybe>; +}; + /** All input for the `createRemoteGeojsonSource` mutation. */ export type CreateRemoteGeojsonSourceInput = { bounds?: Maybe>>; @@ -7040,6 +7106,29 @@ export type GetChildFoldersRecursivePayload = { query?: Maybe; }; +/** All input for the `getPublishedCardIdFromDraft` mutation. */ +export type GetPublishedCardIdFromDraftInput = { + /** + * An arbitrary string value with no semantic meaning. Will be included in the + * payload verbatim. May be used to track mutations by the client. + */ + clientMutationId?: Maybe; + draftReportCardId?: Maybe; +}; + +/** The output of our `getPublishedCardIdFromDraft` mutation. */ +export type GetPublishedCardIdFromDraftPayload = { + __typename?: 'GetPublishedCardIdFromDraftPayload'; + /** + * The exact same `clientMutationId` that was provided in the mutation input, + * unchanged and unused. May be used by a client to track mutations. + */ + clientMutationId?: Maybe; + integer?: Maybe; + /** Our root query field type. Allows us to run any query from our mutation payload. */ + query?: Maybe; +}; + export type GoogleMapsTileApiSession = Node & { __typename?: 'GoogleMapsTileApiSession'; expiresAt: Scalars['Datetime']; @@ -7977,6 +8066,8 @@ export type Mutation = { createOfflineTileSetting?: Maybe; /** Creates a single `OptionalBasemapLayer`. */ createOptionalBasemapLayer?: Maybe; + /** Creates a single `OriginalSourceId`. */ + createOriginalSourceId?: Maybe; createPost: Post; /** * Users with verified emails can create new projects by choosing a unique name @@ -8006,6 +8097,8 @@ export type Mutation = { * layers have not yet been created. */ createProjectWithGeographies?: Maybe; + /** Creates a single `PublishedTocItemId`. */ + createPublishedTocItemId?: Maybe; createRemoteGeojsonSource?: Maybe; createRemoteMvtSource?: Maybe; /** Creates a single `Report`. */ @@ -8203,6 +8296,7 @@ export type Mutation = { */ getOrCreateSprite?: Maybe; getPresignedPMTilesUploadUrl: PresignedUrl; + getPublishedCardIdFromDraft?: Maybe; /** Give a user admin access to a project. User must have already joined the project and shared their user profile. */ grantAdminAccess?: Maybe; importArcgisServices?: Maybe; @@ -8820,6 +8914,12 @@ export type MutationCreateOptionalBasemapLayerArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationCreateOriginalSourceIdArgs = { + input: CreateOriginalSourceIdInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationCreatePostArgs = { message: Scalars['JSON']; @@ -8857,6 +8957,12 @@ export type MutationCreateProjectWithGeographiesArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationCreatePublishedTocItemIdArgs = { + input: CreatePublishedTocItemIdInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationCreateRemoteGeojsonSourceArgs = { input: CreateRemoteGeojsonSourceInput; @@ -9459,6 +9565,12 @@ export type MutationGetPresignedPmTilesUploadUrlArgs = { }; +/** The root mutation type which contains root level fields which mutate data. */ +export type MutationGetPublishedCardIdFromDraftArgs = { + input: GetPublishedCardIdFromDraftInput; +}; + + /** The root mutation type which contains root level fields which mutate data. */ export type MutationGrantAdminAccessArgs = { input: GrantAdminAccessInput; @@ -10669,6 +10781,43 @@ export enum OptionalBasemapLayersOrderBy { PrimaryKeyDesc = 'PRIMARY_KEY_DESC' } +export type OriginalSourceId = { + __typename?: 'OriginalSourceId'; + dataSourceId?: Maybe; +}; + +/** An input for mutations affecting `OriginalSourceId` */ +export type OriginalSourceIdInput = { + dataSourceId?: Maybe; +}; + +/** A connection to a list of `OriginalSourceId` values. */ +export type OriginalSourceIdsConnection = { + __typename?: 'OriginalSourceIdsConnection'; + /** A list of edges which contains the `OriginalSourceId` and cursor to aid in pagination. */ + edges: Array; + /** A list of `OriginalSourceId` objects. */ + nodes: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** The count of *all* `OriginalSourceId` you could get from the connection. */ + totalCount: Scalars['Int']; +}; + +/** A `OriginalSourceId` edge in the connection. */ +export type OriginalSourceIdsEdge = { + __typename?: 'OriginalSourceIdsEdge'; + /** A cursor for use in pagination. */ + cursor?: Maybe; + /** The `OriginalSourceId` at the end of the edge. */ + node: OriginalSourceId; +}; + +/** Methods to use when ordering `OriginalSourceId`. */ +export enum OriginalSourceIdsOrderBy { + Natural = 'NATURAL' +} + export type OutstandingSurveyInvites = { __typename?: 'OutstandingSurveyInvites'; projectId: Scalars['Int']; @@ -12243,6 +12392,43 @@ export type PublishTableOfContentsPayload = { tableOfContentsItems?: Maybe>; }; +export type PublishedTocItemId = { + __typename?: 'PublishedTocItemId'; + id?: Maybe; +}; + +/** An input for mutations affecting `PublishedTocItemId` */ +export type PublishedTocItemIdInput = { + id?: Maybe; +}; + +/** A connection to a list of `PublishedTocItemId` values. */ +export type PublishedTocItemIdsConnection = { + __typename?: 'PublishedTocItemIdsConnection'; + /** A list of edges which contains the `PublishedTocItemId` and cursor to aid in pagination. */ + edges: Array; + /** A list of `PublishedTocItemId` objects. */ + nodes: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; + /** The count of *all* `PublishedTocItemId` you could get from the connection. */ + totalCount: Scalars['Int']; +}; + +/** A `PublishedTocItemId` edge in the connection. */ +export type PublishedTocItemIdsEdge = { + __typename?: 'PublishedTocItemIdsEdge'; + /** A cursor for use in pagination. */ + cursor?: Maybe; + /** The `PublishedTocItemId` at the end of the edge. */ + node: PublishedTocItemId; +}; + +/** Methods to use when ordering `PublishedTocItemId`. */ +export enum PublishedTocItemIdsOrderBy { + Natural = 'NATURAL' +} + /** The root query type which gives access points into the data universe. */ export type Query = Node & { __typename?: 'Query'; @@ -12422,6 +12608,8 @@ export type Query = Node & { optionalBasemapLayer?: Maybe; /** Reads a single `OptionalBasemapLayer` using its globally unique `ID`. */ optionalBasemapLayerByNodeId?: Maybe; + /** Reads and enables pagination through a set of `OriginalSourceId`. */ + originalSourceIdsConnection?: Maybe; post?: Maybe; /** Reads a single `Post` using its globally unique `ID`. */ postByNodeId?: Maybe; @@ -12453,6 +12641,8 @@ export type Query = Node & { projectsSharedBasemapsConnection?: Maybe; /** Used by project administrators to access a list of public sprites promoted by the SeaSketch development team. */ publicSprites?: Maybe>; + /** Reads and enables pagination through a set of `PublishedTocItemId`. */ + publishedTocItemIdsConnection?: Maybe; /** * Exposes the root query type nested one level down. This is helpful for Relay 1 * which can only query top level fields if they are in a particular form. @@ -13306,6 +13496,17 @@ export type QueryOptionalBasemapLayerByNodeIdArgs = { }; +/** The root query type which gives access points into the data universe. */ +export type QueryOriginalSourceIdsConnectionArgs = { + after?: Maybe; + before?: Maybe; + first?: Maybe; + last?: Maybe; + offset?: Maybe; + orderBy?: Maybe>; +}; + + /** The root query type which gives access points into the data universe. */ export type QueryPostArgs = { id: Scalars['Int']; @@ -13463,6 +13664,17 @@ export type QueryPublicSpritesArgs = { }; +/** The root query type which gives access points into the data universe. */ +export type QueryPublishedTocItemIdsConnectionArgs = { + after?: Maybe; + before?: Maybe; + first?: Maybe; + last?: Maybe; + offset?: Maybe; + orderBy?: Maybe>; +}; + + /** The root query type which gives access points into the data universe. */ export type QueryReportArgs = { id: Scalars['Int']; diff --git a/packages/client/src/header/Header.tsx b/packages/client/src/header/Header.tsx index b4b5cab05..8fdbb09fb 100644 --- a/packages/client/src/header/Header.tsx +++ b/packages/client/src/header/Header.tsx @@ -3,11 +3,17 @@ import * as Popover from "@radix-ui/react-popover"; import { ChevronDownIcon } from "@radix-ui/react-icons"; import logo from "./seasketch-logo.png"; import { useAuth0 } from "@auth0/auth0-react"; -import { Link, NavLink } from "react-router-dom"; +import { Link, NavLink, useLocation } from "react-router-dom"; import { useTranslation } from "react-i18next"; import ProfileContextMenu from "./ProfileContextMenu"; 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 = [ { @@ -47,39 +53,14 @@ const navigationLinks = [ // }, ]; -const featureLinks = [ - { - to: "/uses/map-portal", - label: "Map Portal", - description: - "Publish, explore, and share authoritative ocean data. Create a common picture of your ocean environment.", - id: "nav-map-portal", - }, - { - to: "/uses/surveys", - label: "Ocean Use Surveys", - description: "Gather essential spatial data with Ocean Use Surveys", - id: "nav-surveys", - }, - { - to: "/uses/planning", - label: "Planning Tools", - description: - "Design and evaluate scenarios, including MPAs and Ocean Uses.", - id: "nav-planning", - }, -]; - export default function Header() { const { isAuthenticated, loginWithRedirect } = useAuth0(); const { t } = useTranslation("nav"); const [profileModalOpen, setProfileModalOpen] = useState(false); const [featuresOpen, setFeaturesOpen] = useState(false); const hoverCloseTimeout = useRef(null); - const [openByKeyboard, setOpenByKeyboard] = useState(false); - const firstFeatureRef = useRef(null); - const featureItemRefs = useRef>([]); const currentProjectQuery = useCurrentProjectMetadata(); + const location = useLocation(); const handleDocumentClick = useCallback( () => setProfileModalOpen(false), @@ -96,11 +77,27 @@ export default function Header() { }); useEffect(() => { - if (featuresOpen && openByKeyboard && firstFeatureRef.current) { - firstFeatureRef.current.focus(); - setOpenByKeyboard(false); + if (hoverCloseTimeout.current) { + window.clearTimeout(hoverCloseTimeout.current); + } + setFeaturesOpen(false); + }, [location.pathname, location.hash]); + + const openFeaturesMenu = () => { + if (hoverCloseTimeout.current) { + window.clearTimeout(hoverCloseTimeout.current); + } + setFeaturesOpen(true); + }; + + const scheduleFeaturesMenuClose = () => { + if (hoverCloseTimeout.current) { + window.clearTimeout(hoverCloseTimeout.current); } - }, [featuresOpen, openByKeyboard]); + hoverCloseTimeout.current = window.setTimeout(() => { + setFeaturesOpen(false); + }, 100); + }; return (
@@ -137,25 +134,74 @@ export default function Header() { {
{navigationLinks.map((link) => - link.to === "/features" ? ( - <> - {/* - {}}> - ... existing dropdown content preserved for future use ... - - */} - + + + + + event.preventDefault()} + onMouseEnter={openFeaturesMenu} + onMouseLeave={scheduleFeaturesMenuClose} + className="z-30 w-80 rounded-2xl bg-slate-900 p-5 text-slate-100 shadow-xl focus:outline-none" + > +

+ What can SeaSketch do? +

+
+ {useCaseLinks.map((feature) => ( + setFeaturesOpen(false)} + className="block rounded-xl px-3 py-2 hover:bg-white/10 focus:outline-none focus-visible:ring-1 focus-visible:ring-white/50" + > + + {feature.navLabel} + + + {feature.summary} + + + ))} +
+ +
+
+ + ) : link.to === "/features" ? ( + + {link.label} + ) : ( {navigationLinks.map((link) => - link.to === "/features" ? ( + link.to === "/features" && enableFeaturesDropdown ? ( +
+ + {link.label} + + {useCaseLinks.map((feature) => ( + + {feature.navLabel} + + ))} +
+ ) : link.to === "/features" ? ( Metadata and version management
  • Integrates with Esri and open-source services
  • - {/*
    - Learn more → - */} + {publishedUseCaseLinks[0].readMoreLabel} +
    @@ -416,12 +418,16 @@ export default function LandingPage() {
  • Offline data collection
  • Understand ocean uses by sector
  • - {/* - Learn more → - */} + {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} @@ -469,12 +475,16 @@ export default function LandingPage() {
  • Online collaboration tools and discussion forums
  • Export products to GIS and Excel
  • - {/* - Learn more → - */} + {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} diff --git a/packages/client/src/homepage/useCases/MapPortalHosting.tsx b/packages/client/src/homepage/useCases/MapPortalHosting.tsx new file mode 100644 index 000000000..f6ff4ec00 --- /dev/null +++ b/packages/client/src/homepage/useCases/MapPortalHosting.tsx @@ -0,0 +1,645 @@ +/* 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, + UploadIcon, + MagicWandIcon, + LockClosedIcon, + FileTextIcon, + GlobeIcon, + ArrowLeftIcon, + ChatBubbleIcon, + LayersIcon, +} from "@radix-ui/react-icons"; + +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 featureCopyPanelClass = + "relative z-20 rounded-2xl border border-white/60 bg-white/65 p-5 shadow-sm backdrop-blur-sm md:p-6"; + +const supportedFormats = [ + "GeoJSON", + "Shapefile (.zip)", + "GeoTIFF", + "NetCDF", + "FlatGeobuf", +]; + +type AdditionalFeatureCard = { + title: string; + description: string; + icon: ReactNode; + iconClassName: string; +}; + +const additionalFeatureCards: AdditionalFeatureCard[] = [ + { + title: "Metadata management", + description: + "Author and maintain metadata for your layers as rich text documents managed directly in SeaSketch, or upload data with XML metadata using standard formats.", + icon: , + iconClassName: "bg-sky-100 text-sky-700 ring-sky-200", + }, + { + title: "Integrates with Esri & open-source services", + description: + "For data already hosted elsewhere, link directly to ArcGIS Online, ArcGIS Enterprise, or OGC services and blend those layers seamlessly into your portal.", + icon: , + iconClassName: "bg-emerald-100 text-emerald-700 ring-emerald-200", + }, + { + title: "Data library", + description: + "Browse a growing library of authoritative datasets curated for ocean planning, then add them with preconfigured cartography and metadata plus source-linked updates over time.", + icon: , + iconClassName: "bg-cyan-100 text-cyan-700 ring-cyan-200", + }, + { + title: "Customizable interactivity", + description: + "Configure layers with popups, tooltips, map banners, and expandable side panels to expose rich context about features directly in the map.", + icon: , + iconClassName: "bg-violet-100 text-violet-700 ring-violet-200", + }, + + { + title: "Basemaps", + description: + "Start each project with a curated basemap set, then tailor it by adding custom Mapbox styles or tiled ArcGIS basemap sources for your region and audience.", + icon: , + iconClassName: "bg-indigo-100 text-indigo-700 ring-indigo-200", + }, +]; + +type FeatureRowProps = { + reverse?: boolean; + eyebrow: string; + icon: ReactNode; + title: string; + image: string; + imageAlt: string; + glowClassName: string; + rowClassName?: string; + imageContainerClassName?: string; + children: ReactNode; +}; + +function FeatureRow({ + reverse, + eyebrow, + icon, + title, + image, + imageAlt, + glowClassName, + rowClassName, + imageContainerClassName, + children, +}: FeatureRowProps) { + return ( +
    +
    +
    +
    +
    + {imageAlt} +
    +
    +
    +
    +
    +
    + + {icon} + + + {eyebrow} + +
    +

    + {title} +

    +
    + {children} +
    +
    +
    +
    + ); +} + +export default function MapPortalHostingPage() { + return ( +
    + + {`SeaSketch | ${mapPortalHostingUseCase.title}`} + + + + {/* Hero */} +
    +
    +
    +
    +
    +
    + +
    + + + Back to SeaSketch capabilities + + +
    + + Map Portal Hosting + +

    + Publish an{" "} + + living map + {" "} + of your ocean space +

    +

    + Host, visualize, and share spatial data in a fast, easy-to-use map + portal. Bring fragmented datasets together into a single common + picture of your ocean environment, accessible to your community of + stakeholders. +

    + +
    + +
    +
    +
    + The Te Baiku Ocean Geodatabase in SeaSketch showing geomorphic reef features for Kiribati with a map legend +
    +
    +
    +
    + + {/* Feature rows */} +
    +
    + } + title="Fast, beautiful maps" + image="https://imagedelivery.net/UvAJR8nUVV-h3iWaqOVMkw/edaf4e0a-32b6-42e5-308b-c844c0254400/hthumb" + imageAlt="A SeaSketch reef habitat map with an Outer Reef Flat tooltip appearing instantly under the cursor" + glowClassName="bg-sky-400/25" + rowClassName="md:grid-cols-[auto_1fr] md:gap-8 lg:gap-10" + imageContainerClassName="mx-auto w-full max-w-sm md:w-64 lg:w-72" + > +

    + One of the first things people notice about SeaSketch is how + quickly maps load. Spatial data is transformed into static map + tiles and distributed over a global content delivery network for + the best possible performance, anywhere in the world. +

    +

    + Vector tiles power instant interactivity, so features such as + popups, tooltips, and hover effects respond immediately as users + explore.{" "} + + No more loading spinners. + +

    +
    + +
    +
    +
    + + + + + Out of the box efficiency + +
    +

    + Build a map portal in minutes +

    +
    +

    + SeaSketch projects are free to create and include hosting for + up to 10 GB of spatial data. Just drag and drop your + files into the project and SeaSketch handles optimizing them + for the web, with tiling, compression, and styling included. +

    +

    + SeaSketch offers all the essentials of a modern map portal, + including legends, metadata, search, folder organization, and + more—all ready to use out of the box. +

    +
    +
    +
    + Supported formats +
    +
    + {supportedFormats.map((format) => ( + + {format} + + ))} +
    +

    + Need another format?{" "} + + Let us know + + . +

    +
    +
    + +
    +
    + +
    + +
    +
    +
    +
    +

    + Drop files here to upload +

    +

    + Spatial data will be optimized for the web and added to + the layers list. +

    +
    + +
    +

    + Drop file to upload +

    +
    + + + +
    +
    + + + + + + ZIP + + +
    + PISCO-monitoring-sites.zip +
    +
    +
    +
    +
    + +
    + {/*
    + processing +
    */} +
    +
    + + + study-areas.json + +
    + + AI cartographer... + +
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + Graphical cartography panel with color palette, opacity, and AI style suggestions +
    +
    + AI Cartographer Notes explaining category styles and color palette recommendations +
    +
    +
    +
    +
    + + + + + Cartography + +
    +

    + Style layers with AI and graphical tools +

    +
    +

    + When you upload a layer, SeaSketch can use AI to suggest a + title, attribution, and an appropriate cartographic style, so + your data looks great from the moment it appears. Add dozens + or hundreds of layers in an afternoon. +

    +

    + Then, fine-tune everything with our advanced graphical + cartography tools. Adjust color palettes, opacity, labels, and + interactivity options without writing a single line of code. + Changes appear instantly. +

    +
    +
    +
    + +
    +
    +
    + + + + + More than just pretty maps + +
    +

    + Powerful data governance +

    +
    +

    + Role-based access control lets you share layers with specific + user groups and decide whether to enable source data download. + Administrators collaborate on a draft layer list together, + then publish final changes to the public after review. +

    +

    + Dedicated views help you manage hundreds of layers, + summarizing access control, quota usage, and other critical + settings. +

    +

    + A complete changelog tracks every update made by project + administrators. In a large, collaboratively managed project, + you always know who changed what, and when. You can even + roll back certain changes like cartographic styles or source + data updates. +

    +
    +
    + +
    +
    +
    +
    + Layer list with a role-based access control panel overlaid on a map +
    +
    + Layer history changelog with actions like updates, publishing, and folder moves +
    +
    +
    +
    +
    +
    + + {/* Vision statement + additional features */} +
    +
    +
    +
    + A comprehensive solution +
    +

    + SeaSketch makes it easy to visualize, share, and manage ocean + data. +

    +

    + With a 14-year track record and a steadily expanding feature set, + SeaSketch supports near-term planning decisions, adaptive + management, and long-term ocean data stewardship. +

    +

    + + + Read the Documentation + +

    +
    + +
    +
    +
    +

    + Additional Features +

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

    + {card.title} +

    +

    + {card.description} +

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

    + Ready to publish your ocean data? +

    +

    + Spin up a free project and build a fast, beautiful map portal today. +

    + +
    +
    +
    + ); +} diff --git a/packages/client/src/homepage/useCases/OceanUseSurveys.tsx b/packages/client/src/homepage/useCases/OceanUseSurveys.tsx new file mode 100644 index 000000000..600749416 --- /dev/null +++ b/packages/client/src/homepage/useCases/OceanUseSurveys.tsx @@ -0,0 +1,86 @@ +/* eslint-disable i18next/no-literal-string */ +import { Helmet } from "react-helmet"; +import { Link } from "react-router-dom"; + +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", + ], +}; + +export default function OceanUseSurveysPage() { + return ( +
    + + {`SeaSketch | ${oceanUseSurveysUseCase.title}`} + + +
    +
    +
    +
    +
    +
    +
    + + Back to SeaSketch capabilities + +
    + + SeaSketch use case + +

    + {oceanUseSurveysUseCase.title} +

    +

    + {oceanUseSurveysUseCase.summary} +

    +
    + +
    + {oceanUseSurveysUseCase.bullets.map((bullet) => ( +
    +

    + {bullet} +

    +

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

    +
    + ))} +
    + +
    +

    + 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. +

    +
    +
    +
    +
    + ); +} diff --git a/packages/client/src/homepage/useCases/SketchingAndAnalysis.tsx b/packages/client/src/homepage/useCases/SketchingAndAnalysis.tsx new file mode 100644 index 000000000..0363c3f04 --- /dev/null +++ b/packages/client/src/homepage/useCases/SketchingAndAnalysis.tsx @@ -0,0 +1,86 @@ +/* eslint-disable i18next/no-literal-string */ +import { Helmet } from "react-helmet"; +import { Link } from "react-router-dom"; + +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", + ], +}; + +export default function SketchingAndAnalysisPage() { + return ( +
    + + {`SeaSketch | ${sketchingAndAnalysisUseCase.title}`} + + +
    +
    +
    +
    +
    +
    +
    + + Back to SeaSketch capabilities + +
    + + SeaSketch use case + +

    + {sketchingAndAnalysisUseCase.title} +

    +

    + {sketchingAndAnalysisUseCase.summary} +

    +
    + +
    + {sketchingAndAnalysisUseCase.bullets.map((bullet) => ( +
    +

    + {bullet} +

    +

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

    +
    + ))} +
    + +
    +

    + 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. +

    +
    +
    +
    +
    + ); +} diff --git a/packages/client/src/homepage/useCases/index.ts b/packages/client/src/homepage/useCases/index.ts new file mode 100644 index 000000000..e402f0792 --- /dev/null +++ b/packages/client/src/homepage/useCases/index.ts @@ -0,0 +1,32 @@ +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 = false; + +export const useCaseLinks = [ + mapPortalHostingUseCase, + oceanUseSurveysUseCase, + sketchingAndAnalysisUseCase, +]; + +export const publishedUseCaseLinks = enableAllUseCaseLinks + ? useCaseLinks + : [mapPortalHostingUseCase]; diff --git a/packages/client/src/homepage/useCases/outer-reef-flat-square.png b/packages/client/src/homepage/useCases/outer-reef-flat-square.png new file mode 100644 index 000000000..0457d8abe Binary files /dev/null and b/packages/client/src/homepage/useCases/outer-reef-flat-square.png differ diff --git a/packages/client/src/homepage/useCases/outer-reef-flat.png b/packages/client/src/homepage/useCases/outer-reef-flat.png new file mode 100644 index 000000000..4e057cbec Binary files /dev/null and b/packages/client/src/homepage/useCases/outer-reef-flat.png differ diff --git a/packages/client/src/index.css b/packages/client/src/index.css index d96c5196b..077d6ef56 100644 --- a/packages/client/src/index.css +++ b/packages/client/src/index.css @@ -5821,6 +5821,14 @@ select{ inset:0px } +.-inset-6{ + inset:-1.5rem +} + +.-inset-8{ + inset:-2rem +} + .inset-x-0{ left:0px; right:0px @@ -5831,6 +5839,26 @@ select{ bottom:0px } +.-inset-y-16{ + top:-4rem; + bottom:-4rem +} + +.-inset-y-20{ + top:-5rem; + bottom:-5rem +} + +.-inset-y-24{ + top:-6rem; + bottom:-6rem +} + +.-inset-y-8{ + top:-2rem; + bottom:-2rem +} + .\!-bottom-2{ bottom:-0.5rem !important } @@ -6339,6 +6367,110 @@ select{ bottom:-0.125rem } +.-top-32{ + top:-8rem +} + +.left-1\/4{ + left:25% +} + +.right-1\/4{ + right:25% +} + +.-bottom-10{ + bottom:-2.5rem +} + +.-bottom-14{ + bottom:-3.5rem +} + +.-bottom-16{ + bottom:-4rem +} + +.-left-10{ + left:-2.5rem +} + +.-left-28{ + left:-7rem +} + +.-left-56{ + left:-14rem +} + +.-left-8{ + left:-2rem +} + +.-left-80{ + left:-20rem +} + +.-right-10{ + right:-2.5rem +} + +.-right-12{ + right:-3rem +} + +.-right-14{ + right:-3.5rem +} + +.-right-20{ + right:-5rem +} + +.-right-28{ + right:-7rem +} + +.-right-5{ + right:-1.25rem +} + +.-right-6{ + right:-1.5rem +} + +.-right-8{ + right:-2rem +} + +.bottom-9{ + bottom:2.25rem +} + +.bottom-\[-2\.5rem\]{ + bottom:-2.5rem +} + +.bottom-\[-48px\]{ + bottom:-48px +} + +.right-16{ + right:4rem +} + +.right-\[-42px\]{ + right:-42px +} + +.top-12{ + top:3rem +} + +.top-\[34\%\]{ + top:34% +} + .isolate{ isolation:isolate } @@ -6687,6 +6819,16 @@ select{ margin-bottom:auto } +.-my-10{ + margin-top:-2.5rem; + margin-bottom:-2.5rem +} + +.mx-6{ + margin-left:1.5rem; + margin-right:1.5rem +} + .-mb-0{ margin-bottom:-0px } @@ -7135,6 +7277,30 @@ select{ margin-top:2px } +.-mr-6{ + margin-right:-1.5rem +} + +.-ml-24{ + margin-left:-6rem +} + +.-ml-14{ + margin-left:-3.5rem +} + +.-ml-16{ + margin-left:-4rem +} + +.-ml-20{ + margin-left:-5rem +} + +.mt-24{ + margin-top:6rem +} + .box-border{ box-sizing:border-box } @@ -7469,6 +7635,18 @@ select{ height:100vh } +.h-\[135\%\]{ + height:135% +} + +.h-\[118\%\]{ + height:118% +} + +.h-\[112\%\]{ + height:112% +} + .max-h-128{ max-height:32rem } @@ -7689,6 +7867,10 @@ select{ min-height:100vh } +.min-h-\[280px\]{ + min-height:280px +} + .\!w-\[340px\]{ width:340px !important } @@ -7999,6 +8181,42 @@ select{ width:100vw } +.w-\[150\%\]{ + width:150% +} + +.w-\[165\%\]{ + width:165% +} + +.w-\[112\%\]{ + width:112% +} + +.w-\[70\%\]{ + width:70% +} + +.w-\[290px\]{ + width:290px +} + +.w-\[52\%\]{ + width:52% +} + +.w-\[56\%\]{ + width:56% +} + +.w-\[62\%\]{ + width:62% +} + +.w-\[min\(86vw\2c 380px\)\]{ + width:min(86vw,380px) +} + .min-w-0{ min-width:0px } @@ -8367,6 +8585,26 @@ select{ max-width:20rem } +.max-w-\[360px\]{ + max-width:360px +} + +.max-w-\[420px\]{ + max-width:420px +} + +.max-w-\[440px\]{ + max-width:440px +} + +.max-w-\[620px\]{ + max-width:620px +} + +.max-w-\[320px\]{ + max-width:320px +} + .flex-1{ flex:1 1 0% } @@ -8719,6 +8957,18 @@ 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)) } +.scale-\[1\.5\]{ + --tw-scale-x:1.5; + --tw-scale-y:1.5; + 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-\[0\.85\]{ + --tw-scale-x:0.85; + --tw-scale-y:0.85; + 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-x-\[-1\]{ --tw-scale-x:-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)) @@ -8822,6 +9072,18 @@ select{ resize:both } +.snap-x{ + scroll-snap-type:x var(--tw-scroll-snap-strictness) +} + +.snap-mandatory{ + --tw-scroll-snap-strictness:mandatory +} + +.snap-start{ + scroll-snap-align:start +} + .list-inside{ list-style-position:inside } @@ -9027,6 +9289,34 @@ select{ gap:1px } +.gap-16{ + gap:4rem +} + +.gap-12{ + gap:3rem +} + +.gap-14{ + gap:3.5rem +} + +.gap-20{ + gap:5rem +} + +.gap-24{ + gap:6rem +} + +.gap-28{ + gap:7rem +} + +.gap-5{ + gap:1.25rem +} + .gap-x-1{ column-gap:0.25rem } @@ -9261,6 +9551,24 @@ select{ margin-bottom:calc(6px * var(--tw-space-y-reverse)) } +.space-y-20 > :not([hidden]) ~ :not([hidden]){ + --tw-space-y-reverse:0; + margin-top:calc(5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom:calc(5rem * var(--tw-space-y-reverse)) +} + +.space-y-28 > :not([hidden]) ~ :not([hidden]){ + --tw-space-y-reverse:0; + margin-top:calc(7rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom:calc(7rem * 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))); + margin-bottom:calc(4rem * var(--tw-space-y-reverse)) +} + .divide-x > :not([hidden]) ~ :not([hidden]){ --tw-divide-x-reverse:0; border-right-width:calc(1px * var(--tw-divide-x-reverse)); @@ -9441,6 +9749,10 @@ select{ white-space:pre-wrap } +.text-balance{ + text-wrap:balance +} + .break-words{ overflow-wrap:break-word } @@ -9489,6 +9801,14 @@ select{ border-radius:0.75rem } +.rounded-\[2\.5rem\]{ + border-radius:2.5rem +} + +.rounded-\[3rem\]{ + border-radius:3rem +} + .rounded-b{ border-bottom-right-radius:0.25rem; border-bottom-left-radius:0.25rem @@ -9554,6 +9874,11 @@ select{ border-top-right-radius:0.375rem } +.rounded-t-\[2\.5rem\]{ + border-top-left-radius:2.5rem; + border-top-right-radius:2.5rem +} + .rounded-bl-lg{ border-bottom-left-radius:0.5rem } @@ -10122,6 +10447,28 @@ select{ border-color:rgb(63 63 70 / var(--tw-border-opacity, 1)) } +.border-sky-200{ + --tw-border-opacity:1; + border-color:rgb(186 230 253 / var(--tw-border-opacity, 1)) +} + +.border-sky-300{ + --tw-border-opacity:1; + border-color:rgb(125 211 252 / var(--tw-border-opacity, 1)) +} + +.border-slate-200\/70{ + border-color:rgb(226 232 240 / 0.7) +} + +.border-white\/60{ + border-color:rgb(255 255 255 / 0.6) +} + +.border-white\/65{ + border-color:rgb(255 255 255 / 0.65) +} + .border-b-black{ --tw-border-opacity:1; border-bottom-color:rgb(0 0 0 / var(--tw-border-opacity, 1)) @@ -11044,6 +11391,83 @@ select{ background-color:rgb(24 24 27 / var(--tw-bg-opacity, 1)) } +.bg-cyan-400\/30{ + background-color:rgb(34 211 238 / 0.3) +} + +.bg-emerald-400\/15{ + background-color:rgb(52 211 153 / 0.15) +} + +.bg-emerald-400\/25{ + background-color:rgb(52 211 153 / 0.25) +} + +.bg-indigo-400\/25{ + background-color:rgb(129 140 248 / 0.25) +} + +.bg-sky-100{ + --tw-bg-opacity:1; + background-color:rgb(224 242 254 / var(--tw-bg-opacity, 1)) +} + +.bg-sky-400\/30{ + background-color:rgb(56 189 248 / 0.3) +} + +.bg-sky-500\/20{ + background-color:rgb(14 165 233 / 0.2) +} + +.bg-sky-500\/25{ + background-color:rgb(14 165 233 / 0.25) +} + +.bg-cyan-400{ + --tw-bg-opacity:1; + background-color:rgb(34 211 238 / var(--tw-bg-opacity, 1)) +} + +.bg-sky-500\/10{ + background-color:rgb(14 165 233 / 0.1) +} + +.bg-sky-400\/25{ + background-color:rgb(56 189 248 / 0.25) +} + +.bg-cyan-100{ + --tw-bg-opacity:1; + background-color:rgb(207 250 254 / var(--tw-bg-opacity, 1)) +} + +.bg-cyan-400\/25{ + background-color:rgb(34 211 238 / 0.25) +} + +.bg-sky-50\/70{ + background-color:rgb(240 249 255 / 0.7) +} + +.bg-slate-300{ + --tw-bg-opacity:1; + background-color:rgb(203 213 225 / var(--tw-bg-opacity, 1)) +} + +.bg-violet-100{ + --tw-bg-opacity:1; + background-color:rgb(237 233 254 / var(--tw-bg-opacity, 1)) +} + +.bg-white\/65{ + background-color:rgb(255 255 255 / 0.65) +} + +.bg-white\/85{ + background-color:rgb(255 255 255 / 0.85) +} + .bg-opacity-10{ --tw-bg-opacity:0.1 } @@ -11144,6 +11568,10 @@ select{ background-image:linear-gradient(to top right, var(--tw-gradient-stops)) } +.bg-\[radial-gradient\(60\%_60\%_at_50\%_0\%\2c rgba\(56\2c 189\2c 248\2c 0\.12\)\2c transparent\)\]{ + background-image:radial-gradient(60% 60% at 50% 0%,rgba(56,189,248,0.12),transparent) +} + .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); @@ -11342,6 +11770,18 @@ select{ --tw-gradient-stops:var(--tw-gradient-from), var(--tw-gradient-to) } +.from-sky-500\/20{ + --tw-gradient-from:rgb(14 165 233 / 0.2) var(--tw-gradient-from-position); + --tw-gradient-to:rgb(14 165 233 / 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); + --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) @@ -11387,6 +11827,16 @@ select{ --tw-gradient-stops:var(--tw-gradient-from), rgb(2 6 23 / 0.3) var(--tw-gradient-via-position), var(--tw-gradient-to) } +.via-cyan-300{ + --tw-gradient-to:rgb(103 232 249 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), #67e8f9 var(--tw-gradient-via-position), var(--tw-gradient-to) +} + +.via-cyan-400\/10{ + --tw-gradient-to:rgb(34 211 238 / 0) var(--tw-gradient-to-position); + --tw-gradient-stops:var(--tw-gradient-from), rgb(34 211 238 / 0.1) var(--tw-gradient-via-position), var(--tw-gradient-to) +} + .to-amber-50{ --tw-gradient-to:#fffbeb var(--tw-gradient-to-position) } @@ -11503,6 +11953,14 @@ select{ --tw-gradient-to:rgb(252 211 77 / 0.3) var(--tw-gradient-to-position) } +.to-emerald-400\/20{ + --tw-gradient-to:rgb(52 211 153 / 0.2) var(--tw-gradient-to-position) +} + +.to-emerald-400{ + --tw-gradient-to:#34d399 var(--tw-gradient-to-position) +} + .bg-contain{ background-size:contain } @@ -11548,6 +12006,10 @@ select{ fill:#b91c1c } +.fill-slate-900{ + fill:#0f172a +} + .stroke-gray-600{ stroke:#4b5563 } @@ -11632,6 +12094,10 @@ select{ padding:1px } +.p-7{ + padding:1.75rem +} + .px-0{ padding-left:0px; padding-right:0px @@ -11832,6 +12298,16 @@ select{ padding-bottom:1px } +.py-28{ + padding-top:7rem; + padding-bottom:7rem +} + +.py-9{ + padding-top:2.25rem; + padding-bottom:2.25rem +} + .\!pl-0{ padding-left:0px !important } @@ -12120,6 +12596,22 @@ select{ padding-top:1px } +.pb-28{ + padding-bottom:7rem +} + +.pt-24{ + padding-top:6rem +} + +.pb-24{ + padding-bottom:6rem +} + +.pb-32{ + padding-bottom:8rem +} + .text-left{ text-align:left } @@ -12407,6 +12899,10 @@ select{ letter-spacing:0.05em } +.tracking-\[0\.22em\]{ + letter-spacing:0.22em +} + .\!text-black{ --tw-text-opacity:1 !important; color:rgb(0 0 0 / var(--tw-text-opacity, 1)) !important @@ -13004,6 +13500,25 @@ select{ color:rgb(113 113 122 / var(--tw-text-opacity, 1)) } +.text-sky-200{ + --tw-text-opacity:1; + color:rgb(186 230 253 / var(--tw-text-opacity, 1)) +} + +.text-sky-300\/90{ + color:rgb(125 211 252 / 0.9) +} + +.text-cyan-700{ + --tw-text-opacity:1; + color:rgb(14 116 144 / var(--tw-text-opacity, 1)) +} + +.text-violet-700{ + --tw-text-opacity:1; + color:rgb(109 40 217 / var(--tw-text-opacity, 1)) +} + .text-opacity-50{ --tw-text-opacity:0.5 } @@ -13160,10 +13675,22 @@ select{ opacity:0.95 } +.opacity-55{ + opacity:0.55 +} + +.opacity-85{ + opacity:0.85 +} + .bg-blend-darken{ background-blend-mode:darken } +.mix-blend-multiply{ + mix-blend-mode:multiply +} + .mix-blend-overlay{ mix-blend-mode:overlay } @@ -13274,6 +13801,16 @@ select{ --tw-shadow:var(--tw-shadow-colored) } +.shadow-sky-500\/20{ + --tw-shadow-color:rgb(14 165 233 / 0.2); + --tw-shadow:var(--tw-shadow-colored) +} + +.shadow-sky-500{ + --tw-shadow-color:#0ea5e9; + --tw-shadow:var(--tw-shadow-colored) +} + .outline-none{ outline:2px solid transparent; outline-offset:2px @@ -13612,6 +14149,50 @@ select{ --tw-ring-color:rgb(248 113 113 / var(--tw-ring-opacity, 1)) } +.ring-sky-400{ + --tw-ring-opacity:1; + --tw-ring-color:rgb(56 189 248 / var(--tw-ring-opacity, 1)) +} + +.ring-emerald-200{ + --tw-ring-opacity:1; + --tw-ring-color:rgb(167 243 208 / var(--tw-ring-opacity, 1)) +} + +.ring-sky-200{ + --tw-ring-opacity:1; + --tw-ring-color:rgb(186 230 253 / var(--tw-ring-opacity, 1)) +} + +.ring-cyan-500{ + --tw-ring-opacity:1; + --tw-ring-color:rgb(6 182 212 / var(--tw-ring-opacity, 1)) +} + +.ring-cyan-200{ + --tw-ring-opacity:1; + --tw-ring-color:rgb(165 243 252 / var(--tw-ring-opacity, 1)) +} + +.ring-indigo-200{ + --tw-ring-opacity:1; + --tw-ring-color:rgb(199 210 254 / var(--tw-ring-opacity, 1)) +} + +.ring-sky-100{ + --tw-ring-opacity:1; + --tw-ring-color:rgb(224 242 254 / var(--tw-ring-opacity, 1)) +} + +.ring-violet-200{ + --tw-ring-opacity:1; + --tw-ring-color:rgb(221 214 254 / var(--tw-ring-opacity, 1)) +} + +.ring-white\/50{ + --tw-ring-color:rgb(255 255 255 / 0.5) +} + .ring-opacity-10{ --tw-ring-opacity:0.1 } @@ -13660,6 +14241,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-\[1px\]{ + --tw-blur:blur(1px); + 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) +} + .brightness-200{ --tw-brightness:brightness(2); 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) @@ -13685,6 +14271,16 @@ 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-xl{ + --tw-drop-shadow:drop-shadow(0 20px 13px rgb(0 0 0 / 0.03)) drop-shadow(0 8px 5px rgb(0 0 0 / 0.08)); + 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_1px_rgba\(15\2c 23\2c 42\2c 0\.25\)\]{ + --tw-drop-shadow:drop-shadow(0 1px 1px rgba(15,23,42,0.25)); + 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) +} + .grayscale{ --tw-grayscale:grayscale(100%); 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) @@ -13847,6 +14443,15 @@ select{ will-change:transform,opacity } +.contain-paint{ + --tw-contain-paint:paint; + contain:var(--tw-contain-size) var(--tw-contain-layout) var(--tw-contain-paint) var(--tw-contain-style) +} + +.\[scrollbar-width\:none\]{ + scrollbar-width:none +} + .\[scrollbar-width\:thin\]{ scrollbar-width:thin } @@ -14366,6 +14971,11 @@ input[type="checkbox"][indeterminate="true"]:checked { border-color:transparent } +.hover\:border-slate-400:hover{ + --tw-border-opacity:1; + border-color:rgb(148 163 184 / var(--tw-border-opacity, 1)) +} + .hover\:border-opacity-100:hover{ --tw-border-opacity:1 } @@ -14629,6 +15239,11 @@ input[type="checkbox"][indeterminate="true"]:checked { background-color:rgb(239 68 68 / 0.1) } +.hover\:bg-slate-400:hover{ + --tw-bg-opacity:1; + background-color:rgb(148 163 184 / var(--tw-bg-opacity, 1)) +} + .hover\:bg-opacity-10:hover{ --tw-bg-opacity:0.1 } @@ -14835,6 +15450,21 @@ input[type="checkbox"][indeterminate="true"]:checked { color:rgb(254 202 202 / var(--tw-text-opacity, 1)) } +.hover\:text-sky-900:hover{ + --tw-text-opacity:1; + color:rgb(12 74 110 / var(--tw-text-opacity, 1)) +} + +.hover\:text-sky-200:hover{ + --tw-text-opacity:1; + color:rgb(186 230 253 / var(--tw-text-opacity, 1)) +} + +.hover\:text-slate-900:hover{ + --tw-text-opacity:1; + color:rgb(15 23 42 / var(--tw-text-opacity, 1)) +} + .hover\:underline:hover{ -webkit-text-decoration-line:underline; text-decoration-line:underline @@ -15330,6 +15960,14 @@ input[type="checkbox"][indeterminate="true"]:checked { --tw-ring-color:rgb(46 115 182 / var(--tw-ring-opacity, 1)) } +.focus-visible\:ring-white\/50:focus-visible{ + --tw-ring-color:rgb(255 255 255 / 0.5) +} + +.focus-visible\:ring-sky-400\/80:focus-visible{ + --tw-ring-color:rgb(56 189 248 / 0.8) +} + .focus-visible\:ring-offset-1:focus-visible{ --tw-ring-offset-width:1px } @@ -15531,6 +16169,11 @@ input[type="checkbox"][indeterminate="true"]:checked { width:auto } +.group:hover .group-hover\:-translate-y-1{ + --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)) +} + .group:hover .group-hover\:border-black\/5{ border-color:rgb(0 0 0 / 0.05) } @@ -15617,6 +16260,10 @@ input[type="checkbox"][indeterminate="true"]:checked { opacity:0.8 } +.group:hover .group-hover\:opacity-90{ + opacity:0.9 +} + .data-\[disabled\]\:pointer-events-none[data-disabled]{ pointer-events:none } @@ -15912,6 +16559,14 @@ input[type="checkbox"][indeterminate="true"]:checked { width:max-content } + .sm\:w-72{ + width:18rem + } + + .sm\:w-\[320px\]{ + width:320px + } + .sm\:max-w-2xl{ max-width:42rem } @@ -16019,6 +16674,10 @@ input[type="checkbox"][indeterminate="true"]:checked { gap:1px } + .sm\:gap-3{ + gap:0.75rem + } + .sm\:gap-x-4{ column-gap:1rem } @@ -16301,6 +16960,11 @@ input[type="checkbox"][indeterminate="true"]:checked { position:relative } + .md\:-inset-y-24{ + top:-6rem; + bottom:-6rem + } + .md\:bottom-6{ bottom:1.5rem } @@ -16325,6 +16989,38 @@ input[type="checkbox"][indeterminate="true"]:checked { top:1rem } + .md\:-left-10{ + left:-2.5rem + } + + .md\:-right-10{ + right:-2.5rem + } + + .md\:bottom-\[-2\.75rem\]{ + bottom:-2.75rem + } + + .md\:top-\[25\%\]{ + top:25% + } + + .md\:-left-80{ + left:-20rem + } + + .md\:-right-28{ + right:-7rem + } + + .md\:bottom-\[-48px\]{ + bottom:-48px + } + + .md\:right-\[-42px\]{ + right:-42px + } + .md\:order-1{ order:1 } @@ -16366,6 +17062,16 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-right:auto } + .md\:-my-10{ + margin-top:-2.5rem; + margin-bottom:-2.5rem + } + + .md\:mx-0{ + margin-left:0px; + margin-right:0px + } + .md\:ml-6{ margin-left:1.5rem } @@ -16378,6 +17084,18 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-top:0px } + .md\:-mr-6{ + margin-right:-1.5rem + } + + .md\:-ml-24{ + margin-left:-6rem + } + + .md\:ml-4{ + margin-left:1rem + } + .md\:block{ display:block } @@ -16434,6 +17152,10 @@ input[type="checkbox"][indeterminate="true"]:checked { min-height:100vh } + .md\:min-h-\[460px\]{ + min-height:460px + } + .md\:w-40{ width:10rem } @@ -16458,6 +17180,14 @@ input[type="checkbox"][indeterminate="true"]:checked { width:100% } + .md\:w-\[65\%\]{ + width:65% + } + + .md\:w-64{ + width:16rem + } + .md\:min-w-lg{ min-width:32rem } @@ -16494,6 +17224,30 @@ input[type="checkbox"][indeterminate="true"]:checked { flex-shrink:0 } + .md\:scale-110{ + --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-105{ + --tw-scale-x:1.05; + --tw-scale-y:1.05; + 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\.5\]{ + --tw-scale-x:1.5; + --tw-scale-y:1.5; + 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\:grid-cols-12{ grid-template-columns:repeat(12, minmax(0, 1fr)) } @@ -16510,6 +17264,18 @@ input[type="checkbox"][indeterminate="true"]:checked { grid-template-columns:repeat(6, minmax(0, 1fr)) } + .md\:grid-cols-2{ + grid-template-columns:repeat(2, minmax(0, 1fr)) + } + + .md\:grid-cols-\[auto_1fr\]{ + grid-template-columns:auto 1fr + } + + .md\:grid-cols-\[1fr_minmax\(0\2c 1\.1fr\)\]{ + grid-template-columns:1fr minmax(0,1.1fr) + } + .md\:flex-col{ flex-direction:column } @@ -16518,6 +17284,26 @@ input[type="checkbox"][indeterminate="true"]:checked { align-items:flex-start } + .md\:gap-16{ + gap:4rem + } + + .md\:gap-0{ + gap:0px + } + + .md\:gap-8{ + gap:2rem + } + + .md\:gap-20{ + gap:5rem + } + + .md\:gap-24{ + gap:6rem + } + .md\:space-x-2 > :not([hidden]) ~ :not([hidden]){ --tw-space-x-reverse:0; margin-right:calc(0.5rem * var(--tw-space-x-reverse)); @@ -16536,6 +17322,16 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-bottom:calc(0px * var(--tw-space-y-reverse)) } + .md\:space-y-20 > :not([hidden]) ~ :not([hidden]){ + --tw-space-y-reverse:0; + margin-top:calc(5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom:calc(5rem * var(--tw-space-y-reverse)) + } + + .md\:overflow-visible{ + overflow:visible + } + .md\:rounded-lg{ border-radius:0.5rem } @@ -16586,6 +17382,18 @@ input[type="checkbox"][indeterminate="true"]:checked { padding:1.25rem } + .md\:p-10{ + padding:2.5rem + } + + .md\:p-6{ + padding:1.5rem + } + + .md\:p-8{ + padding:2rem + } + .md\:px-4{ padding-left:1rem; padding-right:1rem @@ -16628,6 +17436,22 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-top:0px } + .md\:pr-10{ + padding-right:2.5rem + } + + .md\:pl-4{ + padding-left:1rem + } + + .md\:pr-6{ + padding-right:1.5rem + } + + .md\:pt-16{ + padding-top:4rem + } + .md\:text-2xl{ font-size:1.5rem; line-height:2rem @@ -16678,6 +17502,11 @@ input[type="checkbox"][indeterminate="true"]:checked { line-height:1rem } + .md\:text-6xl{ + font-size:3.75rem; + line-height:1 + } + .md\:leading-5{ line-height:1.25rem } @@ -16706,6 +17535,22 @@ input[type="checkbox"][indeterminate="true"]:checked { top:1.25rem } + .lg\:-left-12{ + left:-3rem + } + + .lg\:-right-12{ + right:-3rem + } + + .lg\:bottom-\[-3rem\]{ + bottom:-3rem + } + + .lg\:top-\[25\%\]{ + top:25% + } + .lg\:col-span-6{ grid-column:span 6 / span 6 } @@ -16742,6 +17587,14 @@ input[type="checkbox"][indeterminate="true"]:checked { margin-top:1.5rem } + .lg\:-ml-32{ + margin-left:-8rem + } + + .lg\:ml-6{ + margin-left:1.5rem + } + .lg\:block{ display:block } @@ -16879,12 +17732,30 @@ input[type="checkbox"][indeterminate="true"]:checked { gap:2rem } + .lg\:gap-10{ + gap:2.5rem + } + + .lg\:gap-24{ + gap:6rem + } + + .lg\:gap-28{ + gap:7rem + } + .lg\:space-x-5 > :not([hidden]) ~ :not([hidden]){ --tw-space-x-reverse:0; margin-right:calc(1.25rem * var(--tw-space-x-reverse)); margin-left:calc(1.25rem * calc(1 - var(--tw-space-x-reverse))) } + .lg\:space-y-28 > :not([hidden]) ~ :not([hidden]){ + --tw-space-y-reverse:0; + margin-top:calc(7rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom:calc(7rem * var(--tw-space-y-reverse)) + } + .lg\:p-4{ padding:1rem } @@ -16923,6 +17794,11 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-bottom:2rem } + .lg\:py-28{ + padding-top:7rem; + padding-bottom:7rem + } + .lg\:pb-0{ padding-bottom:0px } @@ -16939,6 +17815,26 @@ input[type="checkbox"][indeterminate="true"]:checked { padding-top:0.125rem } + .lg\:pb-28{ + padding-bottom:7rem + } + + .lg\:pt-16{ + padding-top:4rem + } + + .lg\:pt-24{ + padding-top:6rem + } + + .lg\:pb-32{ + padding-bottom:8rem + } + + .lg\:pb-12{ + padding-bottom:3rem + } + .lg\:text-left{ text-align:left } @@ -17365,6 +18261,10 @@ input[type="checkbox"][indeterminate="true"]:checked { } } +.\[\&\:\:-webkit-scrollbar\]\:hidden::-webkit-scrollbar{ + display:none +} + .\[\&\>span\]\:min-h-0>span{ min-height:0px } diff --git a/packages/client/src/reports/widgets/.InlineMetric.tsx.swp b/packages/client/src/reports/widgets/.InlineMetric.tsx.swp deleted file mode 100644 index 84860736d..000000000 Binary files a/packages/client/src/reports/widgets/.InlineMetric.tsx.swp and /dev/null differ