+ {hasInteractiveLab && (
+ }
+ size="small"
+ className={s.interactiveEyebrowBadge}
+ />
+ )}
{eyebrowSlot || {duration} }
diff --git a/src/components/tutorial-card/types.ts b/src/components/tutorial-card/types.ts
index 3fbc6ac6bd..4459c21ce3 100644
--- a/src/components/tutorial-card/types.ts
+++ b/src/components/tutorial-card/types.ts
@@ -77,6 +77,12 @@ export interface TutorialCardPropsWithId extends TutorialCardProps {
* achieve this flexibility. We default to our basic bookmark button
* if BookmarkButtonComponent is not provided.
*/
+ hasBookmark?: boolean
+ /**
+ *
+ * A component that renders a bookmark button for the tutorial.
+ *
+ */
BookmarkButtonComponent?: ({
tutorial,
}: {
diff --git a/src/contexts/instruqt-lab/__tests__/instruqt-lab.test.tsx b/src/contexts/instruqt-lab/__tests__/instruqt-lab.test.tsx
index d512105ff7..93404aae61 100644
--- a/src/contexts/instruqt-lab/__tests__/instruqt-lab.test.tsx
+++ b/src/contexts/instruqt-lab/__tests__/instruqt-lab.test.tsx
@@ -4,22 +4,10 @@
*/
import { describe, it, expect, vi, beforeEach } from 'vitest'
-import { render, screen, fireEvent, act } from '@testing-library/react'
-import { useRouter } from 'next/router'
+import { render, screen, fireEvent, act, waitFor } from '@testing-library/react'
import { trackSandboxEvent, SANDBOX_EVENT } from 'lib/posthog-events'
-import React, {
- useState,
- createContext,
- useContext,
- ReactNode,
- Dispatch,
- SetStateAction,
- useEffect,
- useCallback,
- useMemo,
-} from 'react'
-import { validateSandboxConfigWithDetailedErrors } from 'lib/validate-sandbox-config'
-import InstruqtProvider from '../index'
+import React from 'react'
+import { InstruqtProvider, useInstruqtEmbed } from '../index'
vi.mock('components/lab-embed/embed-element', () => ({
default: () => null,
@@ -38,7 +26,9 @@ vi.mock('components/sandbox-error-boundary', () => ({
}))
vi.mock('next/router', () => ({
- useRouter: vi.fn(),
+ useRouter: () => ({
+ asPath: '/test-path',
+ }),
}))
vi.mock('lib/posthog-events', () => ({
@@ -49,12 +39,20 @@ vi.mock('lib/posthog-events', () => ({
},
}))
+vi.mock('lib/validate-sandbox-config', () => ({
+ validateSandboxConfigWithDetailedErrors: () => ({
+ isValid: true,
+ errors: [],
+ warnings: [],
+ }),
+}))
+
vi.mock('content/sandbox/sandbox.json', () => ({
default: {
products: ['test-product'],
labs: [
{
- labId: 'test-product/test-lab/test-lab-id',
+ labId: 'test-lab-id',
title: 'Test Lab',
description:
'Test lab description that is long enough to pass validation',
@@ -62,7 +60,7 @@ vi.mock('content/sandbox/sandbox.json', () => ({
instruqtTrack: 'hashicorp-learn/tracks/test-lab?token=em_test555',
},
{
- labId: 'test-product/stored-lab/stored-lab-id',
+ labId: 'stored-lab-id',
title: 'Stored Lab',
description:
'Stored lab description that is long enough to pass validation',
@@ -70,7 +68,7 @@ vi.mock('content/sandbox/sandbox.json', () => ({
instruqtTrack: 'hashicorp-learn/tracks/stored-lab?token=em_test666',
},
{
- labId: 'test-product/close-test-lab/close-test-lab-id',
+ labId: 'close-test-lab-id',
title: 'Close Test Lab',
description:
'Close test lab description that is long enough to pass validation',
@@ -81,246 +79,30 @@ vi.mock('content/sandbox/sandbox.json', () => ({
},
}))
-const mockUseRouter = vi.mocked(useRouter)
const mockTrackSandboxEvent = vi.mocked(trackSandboxEvent)
-interface InstruqtContextProps {
- labId: string | null
- active: boolean
- setActive: Dispatch
>
- openLab: (labId: string) => void
- closeLab: () => void
- hasConfigError: boolean
- configErrors: string[]
-}
-
-const InstruqtContext = createContext({
- labId: null,
- active: false,
- setActive: () => {},
- openLab: () => {},
- closeLab: () => {},
- hasConfigError: false,
- configErrors: [],
-})
-
-const STORAGE_KEY = 'instruqt-lab-state'
-
-function TestInstruqtProvider({ children }: { children: ReactNode }) {
- const [labId, setLabId] = useState(null)
- const [active, setActive] = useState(false)
- const [hasConfigError, setHasConfigError] = useState(false)
- const [configErrors, setConfigErrors] = useState([])
- const router = useRouter()
-
- const SANDBOX_CONFIG = useMemo(
- () => ({
- products: ['test-product'],
- labs: [
- {
- labId: 'test-lab-id',
- title: 'Test Lab',
- description:
- 'Test lab description that is long enough to pass validation',
- products: ['test-product'],
- instruqtTrack: 'hashicorp-learn/tracks/test-lab?token=em_test555',
- },
- {
- labId: 'stored-lab-id',
- title: 'Stored Lab',
- description:
- 'Stored lab description that is long enough to pass validation',
- products: ['test-product'],
- instruqtTrack: 'hashicorp-learn/tracks/stored-lab?token=em_test666',
- },
- {
- labId: 'close-test-lab-id',
- title: 'Close Test Lab',
- description:
- 'Close test lab description that is long enough to pass validation',
- products: ['test-product'],
- instruqtTrack:
- 'hashicorp-learn/tracks/close-test-lab?token=em_test777',
- },
- ],
- }),
- []
- )
-
- useEffect(() => {
- const validation = validateSandboxConfigWithDetailedErrors(SANDBOX_CONFIG)
-
- if (!validation.isValid) {
- setHasConfigError(true)
- setConfigErrors(validation.errors)
- }
- }, [SANDBOX_CONFIG])
-
- useEffect(() => {
- try {
- const stored = localStorage.getItem(STORAGE_KEY)
- if (stored) {
- const { active: storedActive, storedLabId } = JSON.parse(stored)
- // Validate that the stored lab ID still exists in current configuration
- if (storedLabId && !hasConfigError) {
- const labExists = SANDBOX_CONFIG.labs?.some(
- (lab) => lab.labId === storedLabId
- )
- if (labExists) {
- setLabId(storedLabId)
- setActive(storedActive)
- } else {
- localStorage.removeItem(STORAGE_KEY)
- }
- }
- }
- } catch {
- try {
- localStorage.removeItem(STORAGE_KEY)
- } catch {
- // Storage operations failed
- }
- }
- }, [hasConfigError, SANDBOX_CONFIG])
-
- useEffect(() => {
- if (!hasConfigError) {
- try {
- localStorage.setItem(
- STORAGE_KEY,
- JSON.stringify({
- active,
- storedLabId: labId,
- })
- )
- } catch {
- // Storage persistence failed
- }
- }
- }, [active, labId, hasConfigError])
-
- const openLab = useCallback(
- (newLabId: string) => {
- if (hasConfigError) {
- return
- }
-
- // Validate that the lab ID exists in current configuration
- const labExists = SANDBOX_CONFIG.labs?.some((lab) => {
- return lab.labId === newLabId
- })
-
- if (!labExists) {
- return
- }
-
- // Update state
- if (newLabId !== labId || !active) {
- setLabId(newLabId)
- setActive(true)
-
- // Track sandbox open event immediately
- trackSandboxEvent(SANDBOX_EVENT.SANDBOX_OPEN, {
- labId: newLabId,
- page: router.asPath,
- })
- }
- },
- [labId, active, hasConfigError, router.asPath, SANDBOX_CONFIG]
- )
-
- const closeLab = useCallback(() => {
- if (active && labId) {
- trackSandboxEvent(SANDBOX_EVENT.SANDBOX_CLOSED, {
- labId,
- page: router.asPath,
- })
- }
- setActive(false)
- }, [active, labId, router.asPath])
-
- return (
-
- {children}
-
- )
-}
-
-// Create a test version of the hook
-const useTestInstruqtEmbed = (): InstruqtContextProps =>
- useContext(InstruqtContext)
-
-const createMockLocalStorage = () => {
- const storage = new Map()
-
- return {
- getItem: vi.fn((key: string) => storage.get(key) ?? null),
- setItem: vi.fn((key: string, value: string) => {
- storage.set(key, value)
- }),
- removeItem: vi.fn((key: string) => {
- storage.delete(key)
- }),
- clear: vi.fn(() => storage.clear()),
- length: 0,
- key: vi.fn(),
- }
-}
-
describe('InstruqtEmbed Context', () => {
- let mockLocalStorage: ReturnType
+ let getItemSpy: ReturnType
+ let setItemSpy: ReturnType
beforeEach(() => {
+ window.localStorage.clear()
vi.clearAllMocks()
- mockLocalStorage = createMockLocalStorage()
- Object.defineProperty(window, 'localStorage', {
- value: mockLocalStorage,
- writable: true,
- })
+ getItemSpy = vi.spyOn(Storage.prototype, 'getItem')
+ setItemSpy = vi.spyOn(Storage.prototype, 'setItem')
- mockUseRouter.mockReturnValue({
- asPath: '/test-path',
- route: '/test-path',
- pathname: '/test-path',
- query: {},
- basePath: '',
- isLocaleDomain: false,
- push: vi.fn().mockResolvedValue(true),
- replace: vi.fn().mockResolvedValue(true),
- reload: vi.fn(),
- back: vi.fn(),
- forward: vi.fn(),
- prefetch: vi.fn().mockResolvedValue(undefined),
- beforePopState: vi.fn(),
- events: {
- on: vi.fn(),
- off: vi.fn(),
- emit: vi.fn(),
+ Object.defineProperty(window, 'posthog', {
+ value: {
+ capture: vi.fn(),
},
- isFallback: false,
- isReady: true,
- isPreview: false,
- locale: undefined,
- locales: undefined,
- defaultLocale: undefined,
- domainLocales: undefined,
+ writable: true,
})
})
it('provides default context values', async () => {
const TestComponent = () => {
- const context = useTestInstruqtEmbed()
+ const context = useInstruqtEmbed()
return (
{context.labId || 'no-lab'}
@@ -346,92 +128,109 @@ describe('InstruqtEmbed Context', () => {
active: true,
storedLabId: 'stored-lab-id',
})
- mockLocalStorage.getItem.mockReturnValue(storedState)
+ window.localStorage.setItem('instruqt-lab-state', storedState)
const TestComponent = () => {
- const context = useTestInstruqtEmbed()
+ const context = useInstruqtEmbed()
return (
{context.labId || 'no-lab'}
{context.active ? 'active' : 'inactive'}
+
+ {context.hasConfigError ? 'has-error' : 'no-error'}
+
)
}
await act(async () => {
render(
-
+
-
+
)
})
- expect(mockLocalStorage.getItem).toHaveBeenCalledWith('instruqt-lab-state')
+ expect(getItemSpy).toHaveBeenCalledWith('instruqt-lab-state')
expect(await screen.findByTestId('lab-id')).toHaveTextContent(
'stored-lab-id'
)
expect(await screen.findByTestId('active')).toHaveTextContent('active')
+ expect(await screen.findByTestId('has-error')).toHaveTextContent('no-error')
})
it('persists state changes to localStorage', async () => {
const TestComponent = () => {
- const { openLab } = useTestInstruqtEmbed()
- return
openLab('test-lab-id')}>Open Lab
+ const { openLab, labId, active } = useInstruqtEmbed()
+ return (
+ <>
+
openLab('test-lab-id')}>Open Lab
+
{labId || 'no-lab'}
+
{active ? 'true' : 'false'}
+ >
+ )
}
- await act(async () => {
- render(
-
-
-
- )
- })
+ render(
+
+
+
+ )
- await act(async () => {
- fireEvent.click(await screen.findByText('Open Lab'))
+ fireEvent.click(screen.getByText('Open Lab'))
+
+ await waitFor(() => {
+ expect(screen.getByTestId('lab-id')).toHaveTextContent('test-lab-id')
+ expect(screen.getByTestId('active')).toHaveTextContent('true')
})
- expect(mockLocalStorage.setItem).toHaveBeenCalledWith(
- 'instruqt-lab-state',
- JSON.stringify({
- active: true,
- storedLabId: 'test-lab-id',
- })
+ await waitFor(
+ () => {
+ expect(setItemSpy).toHaveBeenCalledWith(
+ 'instruqt-lab-state',
+ JSON.stringify({
+ active: true,
+ storedLabId: 'test-lab-id',
+ })
+ )
+ },
+ { timeout: 3000 }
)
})
it('tracks sandbox events when opening a lab', async () => {
const TestComponent = () => {
- const { openLab } = useTestInstruqtEmbed()
+ const { openLab } = useInstruqtEmbed()
return
openLab('test-lab-id')}>Open Lab
}
- await act(async () => {
- render(
-
-
-
- )
- })
+ render(
+
+
+
+ )
- await act(async () => {
- fireEvent.click(await screen.findByText('Open Lab'))
- })
+ fireEvent.click(screen.getByText('Open Lab'))
- expect(mockTrackSandboxEvent).toHaveBeenCalledWith(
- SANDBOX_EVENT.SANDBOX_OPEN,
- {
- labId: 'test-lab-id',
- page: '/test-path',
- }
+ await waitFor(
+ () => {
+ expect(mockTrackSandboxEvent).toHaveBeenCalledWith(
+ SANDBOX_EVENT.SANDBOX_OPEN,
+ {
+ labId: 'test-lab-id',
+ page: '/test-path',
+ }
+ )
+ },
+ { timeout: 3000 }
)
})
it('tracks sandbox events when closing a lab', async () => {
const TestComponent = () => {
- const { openLab, closeLab } = useTestInstruqtEmbed()
+ const { openLab, closeLab } = useInstruqtEmbed()
return (
<>
{
)
}
- await act(async () => {
- render(
-
-
-
- )
- })
+ render(
+
+
+
+ )
- await act(async () => {
- fireEvent.click(await screen.findByTestId('open'))
- })
+ fireEvent.click(screen.getByTestId('open'))
- await act(async () => {
- fireEvent.click(await screen.findByTestId('close'))
- })
+ await new Promise((resolve) => setTimeout(resolve, 100))
- expect(mockTrackSandboxEvent).toHaveBeenCalledWith(
- SANDBOX_EVENT.SANDBOX_CLOSED,
- {
- labId: 'close-test-lab-id',
- page: '/test-path',
- }
+ fireEvent.click(screen.getByTestId('close'))
+
+ await waitFor(
+ () => {
+ expect(mockTrackSandboxEvent).toHaveBeenCalledWith(
+ SANDBOX_EVENT.SANDBOX_CLOSED,
+ {
+ labId: 'close-test-lab-id',
+ page: '/test-path',
+ }
+ )
+ },
+ { timeout: 3000 }
)
})
})
diff --git a/src/contexts/instruqt-lab/index-new.tsx b/src/contexts/instruqt-lab/index-new.tsx
index 1bd617307f..f56f665ec3 100644
--- a/src/contexts/instruqt-lab/index-new.tsx
+++ b/src/contexts/instruqt-lab/index-new.tsx
@@ -20,6 +20,7 @@ import Resizable from 'components/lab-embed/resizable'
import { trackSandboxEvent, SANDBOX_EVENT } from 'lib/posthog-events'
import { validateSandboxConfigWithDetailedErrors } from 'lib/validate-sandbox-config'
import SANDBOX_CONFIG from 'content/sandbox/sandbox.json' assert { type: 'json' }
+import posthog from 'posthog-js'
/**
* Tracks Instruqt context errors with PostHog and development logging
@@ -29,9 +30,8 @@ function trackInstruqtError(
errorMessage: string,
context?: Record
) {
- // Track error in PostHog for production monitoring
- if (typeof window !== 'undefined' && window.posthog?.capture) {
- window.posthog.capture('instruqt_context_error', {
+ if (typeof window !== 'undefined' && posthog?.capture) {
+ posthog.capture('instruqt_context_error', {
error_type: errorType,
error_message: errorMessage,
timestamp: new Date().toISOString(),
@@ -103,8 +103,8 @@ const InstruqtProvider: React.FC = ({ children }) => {
}
)
- if (typeof window !== 'undefined' && window.posthog) {
- window.posthog.capture('sandbox_config_error', {
+ if (typeof window !== 'undefined' && posthog) {
+ posthog.capture('sandbox_config_error', {
errors: validation.errors,
timestamp: new Date().toISOString(),
})
diff --git a/src/contexts/instruqt-lab/index.tsx b/src/contexts/instruqt-lab/index.tsx
index 6a8bf8b40a..3da2d49a9e 100644
--- a/src/contexts/instruqt-lab/index.tsx
+++ b/src/contexts/instruqt-lab/index.tsx
@@ -276,6 +276,7 @@ function InstruqtProvider({
}
if (newLabId !== labId || !active) {
setLabId(newLabId)
+ setActive(true)
}
},
[labId, active, hasConfigError, configErrors]
@@ -342,6 +343,7 @@ function InstruqtProvider({
}
export { InstruqtProvider }
+
export default dynamic(() => Promise.resolve(InstruqtProvider), {
ssr: false,
})
diff --git a/src/lib/build-instruqt-url.ts b/src/lib/build-instruqt-url.ts
index 02618a669d..05a70c58e1 100644
--- a/src/lib/build-instruqt-url.ts
+++ b/src/lib/build-instruqt-url.ts
@@ -3,6 +3,7 @@
* SPDX-License-Identifier: MPL-2.0
*/
+import posthog from 'posthog-js'
import { SandboxLab } from 'types/sandbox'
interface InstruqtTokens {
@@ -23,9 +24,8 @@ const trackInstruqtUrlError = (
errorMessage: string,
context?: Record
) => {
- // Track error in PostHog for production monitoring
- if (typeof window !== 'undefined' && window.posthog?.capture) {
- window.posthog.capture('instruqt_url_build_error', {
+ if (typeof window !== 'undefined' && posthog?.capture) {
+ posthog.capture('instruqt_url_build_error', {
error_type: errorType,
error_message: errorMessage,
timestamp: new Date().toISOString(),
diff --git a/src/pages/[productSlug]/sandbox/index.tsx b/src/pages/[productSlug]/sandbox/index.tsx
index 34741ee037..17d89ce1e1 100644
--- a/src/pages/[productSlug]/sandbox/index.tsx
+++ b/src/pages/[productSlug]/sandbox/index.tsx
@@ -28,7 +28,6 @@ const trackSandboxPageError = (
errorMessage: string,
context?: Record
) => {
- // Track error in PostHog for production monitoring
if (typeof window !== 'undefined' && posthog?.capture) {
posthog.capture('sandbox_page_error', {
error_type: errorType,
diff --git a/src/views/sandbox-view/index.tsx b/src/views/sandbox-view/index.tsx
index b93437447d..5bc149e513 100644
--- a/src/views/sandbox-view/index.tsx
+++ b/src/views/sandbox-view/index.tsx
@@ -13,7 +13,7 @@ import { toast, ToastColor } from 'components/toast'
import CardsGridList, {
TutorialCardsGridList,
} from 'components/cards-grid-list'
-import { ProductSlug } from 'types/products'
+import SandboxCard from 'components/sandbox-card'
import { SandboxLab } from 'types/sandbox'
import { ProductOption } from 'lib/learn-client/types'
import { BrandedHeaderCard } from 'views/product-integrations-landing/components/branded-header-card'
@@ -23,16 +23,9 @@ import Tabs, { Tab } from 'components/tabs'
import { ErrorBoundary } from 'react-error-boundary'
import s from './sandbox-view.module.css'
import docsViewStyles from 'views/docs-view/docs-view.module.css'
-import ButtonLink from '@components/button-link'
-import Card from '@components/card'
-import {
- CardTitle,
- CardDescription,
- CardFooter,
-} from '@components/card/components'
-import ProductIcon from '@components/product-icon'
import { PRODUCT_DATA_MAP } from 'data/product-data-map'
import { SidebarProps } from '@components/sidebar'
+import posthog from 'posthog-js'
interface SandboxPageProps {
product: (typeof PRODUCT_DATA_MAP)[keyof typeof PRODUCT_DATA_MAP]
@@ -43,15 +36,29 @@ interface SandboxPageProps {
availableSandboxes: SandboxLab[]
otherSandboxes: SandboxLab[]
}
+export const trackSandboxInteraction = (
+ interactionType: string,
+ sandboxId: string,
+ additionalProps: Record = {}
+) => {
+ if (typeof window !== 'undefined' && posthog?.capture) {
+ posthog.capture(SANDBOX_EVENT.SANDBOX_OPEN, {
+ interaction_type: interactionType,
+ sandbox_id: sandboxId,
+ ...additionalProps,
+ timestamp: new Date().toISOString(),
+ page_url: window.location.href,
+ })
+ }
+}
const trackSandboxPageError = (
errorType: string,
errorMessage: string,
context?: Record
) => {
- // Track error in PostHog for production monitoring
- if (typeof window !== 'undefined' && window.posthog?.capture) {
- window.posthog.capture('sandbox_page_error', {
+ if (typeof window !== 'undefined' && posthog?.capture) {
+ posthog.capture('sandbox_page_error', {
error_type: errorType,
error_message: errorMessage,
timestamp: new Date().toISOString(),
@@ -78,12 +85,17 @@ export const SandboxView = ({
const handleLabClick = useCallback(
(lab: SandboxLab) => {
try {
+ trackSandboxInteraction(SANDBOX_EVENT.SANDBOX_OPEN, lab.labId, {
+ lab_title: lab.title,
+ products: lab.products,
+ })
+
const primaryProduct = lab.products[0]
if (primaryProduct !== product.slug) {
// Redirect to the lab's primary product sandbox page with auto-launch
const targetUrl = `/${primaryProduct}/sandbox?launch=${lab.labId}`
- trackSandboxEvent(SANDBOX_EVENT.SANDBOX_STARTED, {
+ trackSandboxEvent(SANDBOX_EVENT.SANDBOX_OPEN, {
labId: lab.labId,
page: targetUrl,
})
@@ -94,7 +106,7 @@ export const SandboxView = ({
if (hasConfigError) {
trackSandboxPageError(
- 'config_error_lab_launch',
+ SANDBOX_EVENT.SANDBOX_ERROR,
'Cannot launch lab due to configuration error',
{
lab_id: lab.labId,
@@ -114,7 +126,7 @@ export const SandboxView = ({
if (!openLab) {
trackSandboxPageError(
- 'open_lab_function_missing',
+ SANDBOX_EVENT.SANDBOX_ERROR,
'openLab function is not available',
{
lab_id: lab.labId,
@@ -136,7 +148,7 @@ export const SandboxView = ({
if (!embedLabId) {
trackSandboxPageError(
- 'missing_lab_id',
+ SANDBOX_EVENT.SANDBOX_ERROR,
'Lab embed ID is missing or invalid',
{
lab_id: lab.labId,
@@ -157,7 +169,7 @@ export const SandboxView = ({
openLab(embedLabId)
setActive(true)
- trackSandboxEvent(SANDBOX_EVENT.SANDBOX_STARTED, {
+ trackSandboxEvent(SANDBOX_EVENT.SANDBOX_OPEN, {
labId: lab.labId,
page: `/${product.slug}/sandbox`,
})
@@ -244,7 +256,7 @@ export const SandboxView = ({
)
} catch (error) {
trackSandboxPageError(
- 'documentation_render_failed',
+ SANDBOX_EVENT.SANDBOX_ERROR,
'Failed to render sandbox documentation',
{
error_message: error instanceof Error ? error.message : String(error),
@@ -311,43 +323,18 @@ export const SandboxView = ({
{availableSandboxes.length > 0 ? (
<>
-
+
{availableSandboxes.map((lab) => {
return (
-
-
-
-
-
-
- {lab.products.map((productSlug) => (
-
- ))}
-
-
-
-
- {
- e.preventDefault()
- e.stopPropagation()
- handleLabClick(lab)
- }}
- size="medium"
- text="Launch Sandbox"
- />
-
-
-
-
+ handleLabClick(lab)}
+ clickBehavior="button"
+ />
)
})}
@@ -393,6 +380,8 @@ export const SandboxView = ({
)}
>
{
const isSameProduct = lab.products[0] === product.slug
diff --git a/src/views/sandbox-view/sandbox-view.module.css b/src/views/sandbox-view/sandbox-view.module.css
index 746b657ec6..048984e0b3 100644
--- a/src/views/sandbox-view/sandbox-view.module.css
+++ b/src/views/sandbox-view/sandbox-view.module.css
@@ -56,33 +56,6 @@
padding-top: 24px;
}
-.sandboxCard {
- cursor: pointer;
- transition: transform 0.2s ease, box-shadow 0.2s ease;
- max-width: 420px;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- &:hover {
- transform: translateY(-2px);
- box-shadow: var(--token-elevation-high-box-shadow);
- }
-
- @media (max-width: 900px) {
- max-width: 100%;
- width: 100%;
- }
-}
-
-.card {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
.otherSandboxCard {
cursor: pointer;
transition: transform 0.2s ease, box-shadow 0.2s ease;
@@ -140,20 +113,6 @@
margin: var(--token-typography-display-m-tablet-margin-top) 0;
}
-.sandboxCardBox {
- min-height: 200px;
- height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-.sandboxGrid :global([class*='card-link_root__']) {
- height: 100%;
- min-height: 200px;
- display: flex;
- flex-direction: column;
-}
-
.errorMessage {
padding: 16px;
margin: var(--token-typography-display-m-tablet-margin-top) 0;