Skip to content

Commit 1ddbcdf

Browse files
committed
fix(landing): harden cross-browser compatibility
1 parent c2d9423 commit 1ddbcdf

22 files changed

Lines changed: 339 additions & 48 deletions

File tree

apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useEffect, useRef, useState } from 'react'
44
import {
5+
cn,
56
Loader,
67
Modal,
78
ModalClose,
@@ -22,6 +23,7 @@ import { getEnv, isFalsy } from '@/lib/core/config/env'
2223
import { isSsoEnabled } from '@/lib/core/config/env-flags'
2324
import { captureClientEvent } from '@/lib/posthog/client'
2425
import type { PostHogEventMap } from '@/lib/posthog/events'
26+
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
2527
import { getBrandConfig } from '@/ee/whitelabeling'
2628

2729
const logger = createLogger('AuthModal')
@@ -196,7 +198,12 @@ export function AuthModal({ children, defaultView = 'login', source }: AuthModal
196198
className='h-[22px] w-auto shrink-0 object-contain'
197199
/>
198200
<div className='flex flex-col gap-1 text-left'>
199-
<p className='text-[22px] text-[color-mix(in_srgb,var(--text-muted)_60%,transparent)] leading-[125%] tracking-[0.02em]'>
201+
<p
202+
className={cn(
203+
'text-[22px] leading-[125%] tracking-[0.02em]',
204+
colorMixFallbacks.mutedText60
205+
)}
206+
>
200207
Start building.
201208
</p>
202209
<h2 className='text-[22px] text-[var(--text-primary)] leading-[110%] tracking-[-0.02em]'>

apps/sim/app/(landing)/components/hero/components/hero-visual/hero-visual.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
TYPE_MS_PER_ATOM,
3939
WORKFLOW_FOCUS_SCALE,
4040
} from '@/app/(landing)/components/hero/components/hero-visual/workflow-data'
41+
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
4142

4243
/**
4344
* Animated hero visual - the only client island in the hero, decorative and
@@ -299,7 +300,7 @@ const SEND_BUTTON_INK = {
299300
*/
300301
const LANDING_LOADER_INK = {
301302
'--tl-grad-inner': 'var(--text-body)',
302-
'--tl-grad-outer': 'color-mix(in srgb, var(--text-body) 76%, #fff)',
303+
'--tl-grad-outer': 'var(--landing-loader-outer)',
303304
'--tl-glow': 'transparent',
304305
} as CSSProperties
305306

@@ -1144,6 +1145,7 @@ export function HeroVisual() {
11441145
// layer, so the slide + dock read as smooth sub-pixel motion instead of
11451146
// jittering as the position pixel-snaps each frame.
11461147
'pointer-events-none absolute top-0 left-0 z-20 transform-gpu transition-opacity duration-300 will-change-transform [transition-timing-function:cubic-bezier(0.23,1,0.32,1)]',
1148+
colorMixFallbacks.loaderOuter,
11471149
loaderFading ? 'opacity-0' : 'opacity-100'
11481150
)}
11491151
style={{ transformOrigin: '0 0' }}

apps/sim/app/(landing)/components/navbar/components/logo-mark/logo-mark.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { type CSSProperties, type ReactNode, useState } from 'react'
44
import { cn } from '@sim/emcn'
55
import { ThinkingLoader } from '@/components/ui'
6+
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
67

78
interface LogoMarkProps {
89
/** Server-rendered Sim wordmark, shown by default. */
@@ -18,7 +19,7 @@ interface LogoMarkProps {
1819
*/
1920
const LOADER_INK = {
2021
'--tl-grad-inner': 'var(--text-body)',
21-
'--tl-grad-outer': 'color-mix(in srgb, var(--text-body) 76%, #fff)',
22+
'--tl-grad-outer': 'var(--landing-loader-outer)',
2223
'--tl-glow': 'transparent',
2324
} as CSSProperties
2425

@@ -35,7 +36,7 @@ export function LogoMark({ children }: LogoMarkProps) {
3536

3637
return (
3738
<span
38-
className='relative inline-flex items-center'
39+
className={cn('relative inline-flex items-center', colorMixFallbacks.loaderOuter)}
3940
// Transform + its transition are inline on purpose: Tailwind's
4041
// `transition-transform` utility (its var-based transform composition)
4142
// prevents the scale from applying on this element, so the transition is

apps/sim/app/(landing)/components/navbar/components/mobile-nav/mobile-nav.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
NAVBAR_GLASS_SURFACE,
1111
useNavbarFrost,
1212
} from '@/app/(landing)/components/navbar/components/navbar-shell'
13+
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
1314
import { DEMO_HREF, SIGNUP_HREF } from '@/app/(landing)/constants'
1415

1516
/**
@@ -92,7 +93,10 @@ export function MobileNav({ stars }: MobileNavProps) {
9293
aria-hidden='true'
9394
tabIndex={-1}
9495
onClick={() => setOpen(false)}
95-
className='fixed inset-0 top-full z-40 cursor-default bg-[color-mix(in_srgb,var(--text-primary)_8%,transparent)]'
96+
className={cn(
97+
'fixed inset-0 top-full z-40 cursor-default',
98+
colorMixFallbacks.mobileBackdrop
99+
)}
96100
/>
97101
) : null}
98102

apps/sim/app/(landing)/components/navbar/components/nav-menu-chip/components/nav-menu-item/nav-menu-item.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const ROW_CLASS =
2626
const TITLE_CLASS = 'truncate text-[14px] text-[var(--text-body)]'
2727
const DESC_CLASS = 'text-[12px] text-[var(--text-muted)] leading-snug'
2828
const ARROW_CLASS =
29-
'size-4 flex-shrink-0 -translate-x-1 text-[var(--text-icon)] opacity-0 transition-all group-hover/item:translate-x-0 group-hover/item:opacity-100 group-focus-visible/item:translate-x-0 group-focus-visible/item:opacity-100 motion-reduce:transition-none'
29+
'size-4 flex-shrink-0 -translate-x-1 text-[var(--text-icon)] opacity-0 transition-[opacity,transform] group-hover/item:translate-x-0 group-hover/item:opacity-100 group-focus-visible/item:translate-x-0 group-focus-visible/item:opacity-100 motion-reduce:transition-none'
3030

3131
export function NavMenuItem({ item, onSelect }: NavMenuItemProps) {
3232
const { title, description, href, external } = item

apps/sim/app/(landing)/components/navbar/components/navbar-shell/navbar-shell.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
import type { ReactNode } from 'react'
44
import { createContext, use, useEffect, useMemo, useRef, useState } from 'react'
55
import { cn } from '@sim/emcn'
6+
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
67

78
/**
89
* Frosted near-white surface for the scrolled bar - `--bg` at 92% + a strong 40px
910
* blur, edge to edge. Exported as the single source of truth so the mobile
1011
* dropdown sheet ({@link MobileNav}) wears the exact same glass as the bar and the
1112
* two can never drift.
1213
*/
13-
export const NAVBAR_GLASS_SURFACE =
14-
'bg-[color-mix(in_srgb,var(--bg)_92%,transparent)] backdrop-blur-2xl'
14+
export const NAVBAR_GLASS_SURFACE = cn(colorMixFallbacks.navbarGlass, 'backdrop-blur-2xl')
1515

1616
interface NavbarFrostContextValue {
1717
/**

apps/sim/app/(landing)/components/shared/code-window-graphic/code-window-graphic.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReactNode } from 'react'
22
import { cn } from '@sim/emcn'
33
import styles from '@/app/(landing)/components/shared/code-window-graphic/code-window-graphic.module.css'
4+
import colorMixFallbacks from '@/app/(landing)/components/shared/color-mix-fallbacks/color-mix-fallbacks.module.css'
45
import { FeatureGraphicShell } from '@/app/(landing)/enterprise/components/feature-graphics'
56

67
/** One inline run of code text inside a {@link CodeWindowGraphic} line. */
@@ -40,7 +41,7 @@ const SEGMENT_TONE_CLASS = {
4041
} as const
4142

4243
/** Shared hairline ink for the window outline, header rule, and icon box. */
43-
const OUTLINE_INK = 'border-[color:color-mix(in_srgb,var(--text-muted-inverse)_45%,transparent)]'
44+
const OUTLINE_INK = colorMixFallbacks.inverseBorder45
4445

4546
/**
4647
* The dark-tile outlined editor window shared by the platform pages' "from
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
.navbarGlass {
2+
background-color: var(--bg);
3+
}
4+
5+
.mobileBackdrop {
6+
background-color: var(--text-primary);
7+
opacity: 0.08;
8+
}
9+
10+
.mutedText60 {
11+
color: var(--text-muted);
12+
}
13+
14+
.loaderOuter {
15+
--landing-loader-outer: var(--text-body);
16+
}
17+
18+
.inverseBorder45 {
19+
border-color: var(--text-muted-inverse);
20+
}
21+
22+
.inverseBackground45 {
23+
background-color: var(--text-muted-inverse);
24+
}
25+
26+
.mutedBackground35 {
27+
background-color: var(--text-muted);
28+
}
29+
30+
.mutedBorder60 {
31+
border-color: var(--text-muted);
32+
}
33+
34+
.inverseBorder22 {
35+
border-color: var(--text-muted-inverse);
36+
}
37+
38+
.inverseBorder35 {
39+
border-color: var(--text-muted-inverse);
40+
}
41+
42+
.inverseBorder70 {
43+
border-color: var(--text-muted-inverse);
44+
}
45+
46+
.mutedStroke35 {
47+
stroke: var(--text-muted);
48+
}
49+
50+
.inverseStroke28 {
51+
stroke: var(--text-muted-inverse);
52+
}
53+
54+
.inverseStroke45 {
55+
stroke: var(--text-muted-inverse);
56+
}
57+
58+
@supports (color: color-mix(in srgb, red, blue)) {
59+
.navbarGlass {
60+
background-color: color-mix(in srgb, var(--bg) 92%, transparent);
61+
}
62+
63+
.mobileBackdrop {
64+
background-color: color-mix(in srgb, var(--text-primary) 8%, transparent);
65+
opacity: 1;
66+
}
67+
68+
.mutedText60 {
69+
color: color-mix(in srgb, var(--text-muted) 60%, transparent);
70+
}
71+
72+
.loaderOuter {
73+
--landing-loader-outer: color-mix(in srgb, var(--text-body) 76%, var(--white));
74+
}
75+
76+
.inverseBorder45 {
77+
border-color: color-mix(in srgb, var(--text-muted-inverse) 45%, transparent);
78+
}
79+
80+
.inverseBackground45 {
81+
background-color: color-mix(in srgb, var(--text-muted-inverse) 45%, transparent);
82+
}
83+
84+
.mutedBackground35 {
85+
background-color: color-mix(in srgb, var(--text-muted) 35%, transparent);
86+
}
87+
88+
.mutedBorder60 {
89+
border-color: color-mix(in srgb, var(--text-muted) 60%, transparent);
90+
}
91+
92+
.inverseBorder22 {
93+
border-color: color-mix(in srgb, var(--text-muted-inverse) 22%, transparent);
94+
}
95+
96+
.inverseBorder35 {
97+
border-color: color-mix(in srgb, var(--text-muted-inverse) 35%, transparent);
98+
}
99+
100+
.inverseBorder70 {
101+
border-color: color-mix(in srgb, var(--text-muted-inverse) 70%, transparent);
102+
}
103+
104+
.mutedStroke35 {
105+
stroke: color-mix(in srgb, var(--text-muted) 35%, transparent);
106+
}
107+
108+
.inverseStroke28 {
109+
stroke: color-mix(in srgb, var(--text-muted-inverse) 28%, transparent);
110+
}
111+
112+
.inverseStroke45 {
113+
stroke: color-mix(in srgb, var(--text-muted-inverse) 45%, transparent);
114+
}
115+
}

apps/sim/app/(landing)/demo/components/demo-booking/demo-booking.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
'use client'
22

3-
import { type CSSProperties, useEffect, useRef, useState } from 'react'
3+
import { type CSSProperties, type RefObject, useEffect, useRef, useState } from 'react'
44
import { chipBorderShadowRing, cn } from '@sim/emcn'
55
import dynamic from 'next/dynamic'
66
import { preconnect } from 'react-dom'
77
import { DemoForm, type DemoLead } from '@/app/(landing)/demo/components/demo-form'
8+
import { applyLegacyInertFallback } from '@/app/(landing)/demo/components/legacy-inert-fallback'
89

910
const importScheduler = () => import('@/app/(landing)/demo/components/demo-scheduler')
1011

@@ -32,6 +33,14 @@ const DemoScheduler = dynamic(() => importScheduler().then((m) => m.DemoSchedule
3233
loading: () => null,
3334
})
3435

36+
function useLegacyInertFallback(ref: RefObject<HTMLDivElement | null>, inert: boolean) {
37+
useEffect(() => {
38+
const node = ref.current
39+
if (!inert || !node || 'inert' in HTMLElement.prototype) return
40+
return applyLegacyInertFallback(node)
41+
}, [inert, ref])
42+
}
43+
3544
interface DemoBookingProps {
3645
/** Layout/placement classes (grid cell). Never chrome. */
3746
className?: string
@@ -59,8 +68,13 @@ export function DemoBooking({ className }: DemoBookingProps) {
5968
const [lead, setLead] = useState<DemoLead | null>(null)
6069
const [formHeight, setFormHeight] = useState<number>()
6170
const formRef = useRef<HTMLDivElement>(null)
71+
const formPanelRef = useRef<HTMLDivElement>(null)
72+
const schedulerPanelRef = useRef<HTMLDivElement>(null)
6273
const showScheduler = lead !== null
6374

75+
useLegacyInertFallback(formPanelRef, showScheduler)
76+
useLegacyInertFallback(schedulerPanelRef, !showScheduler)
77+
6478
useEffect(() => {
6579
const node = formRef.current
6680
if (!node) return
@@ -87,6 +101,7 @@ export function DemoBooking({ className }: DemoBookingProps) {
87101
style={{ transform: showScheduler ? 'translateX(-100%)' : undefined }}
88102
>
89103
<div
104+
ref={formPanelRef}
90105
className='w-full min-w-0 shrink-0'
91106
inert={showScheduler}
92107
onFocusCapture={() => void preloadScheduler()}
@@ -95,7 +110,11 @@ export function DemoBooking({ className }: DemoBookingProps) {
95110
<DemoForm onComplete={setLead} />
96111
</div>
97112
</div>
98-
<div className='h-full w-full min-w-0 shrink-0' inert={!showScheduler}>
113+
<div
114+
ref={schedulerPanelRef}
115+
className='h-full w-full min-w-0 shrink-0'
116+
inert={!showScheduler}
117+
>
99118
{lead ? <DemoScheduler lead={lead} /> : null}
100119
</div>
101120
</div>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* @vitest-environment jsdom
3+
*/
4+
import { describe, expect, it } from 'vitest'
5+
import { applyLegacyInertFallback } from '@/app/(landing)/demo/components/legacy-inert-fallback'
6+
7+
describe('applyLegacyInertFallback', () => {
8+
it('removes descendants from interaction and restores their exact prior state', () => {
9+
const panel = document.createElement('div')
10+
panel.setAttribute('aria-hidden', 'false')
11+
panel.style.pointerEvents = 'auto'
12+
panel.innerHTML = `
13+
<button>Continue</button>
14+
<a href="/demo" tabindex="2">Demo</a>
15+
<input disabled />
16+
`
17+
18+
const button = panel.querySelector('button')
19+
const link = panel.querySelector('a')
20+
const disabledInput = panel.querySelector('input')
21+
const restore = applyLegacyInertFallback(panel)
22+
23+
expect(panel.getAttribute('aria-hidden')).toBe('true')
24+
expect(panel.style.pointerEvents).toBe('none')
25+
expect(button?.getAttribute('tabindex')).toBe('-1')
26+
expect(link?.getAttribute('tabindex')).toBe('-1')
27+
expect(disabledInput?.getAttribute('tabindex')).toBeNull()
28+
29+
restore()
30+
31+
expect(panel.getAttribute('aria-hidden')).toBe('false')
32+
expect(panel.style.pointerEvents).toBe('auto')
33+
expect(button?.getAttribute('tabindex')).toBeNull()
34+
expect(link?.getAttribute('tabindex')).toBe('2')
35+
})
36+
})

0 commit comments

Comments
 (0)