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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions apps/website/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
ThemePreferences,
} from './components';
import { Button } from './components/button';
import { PartnerDashboardMock } from './components/hero/partner-dashboard-mock';
import { GetStarted } from './components/interactive/get-started';
import { ThemeColorMeta } from './components/theme-color-meta';
import { ThemePreview } from './components/theme-preview';
import { themeColorToPlaygroundPathHex } from './utils/playground-url-hex';

const externalLinkIcon = () => (
Expand All @@ -26,6 +26,20 @@ const externalLinkIcon = () => (

const PLAYGROUND_ORIGIN = 'https://playground.hextimator.com';

function HeroWithDashboard() {
const { color } = useHextimatorTheme();
return (
<>
<Hero />
<div className="flex w-full justify-center px-6 pb-6 pt-2 md:pb-10 lg:px-8">
<div className="flex w-full min-w-0 max-w-5xl justify-center">
<PartnerDashboardMock accentColor={color} />
</div>
</div>
</>
);
}

function PlaygroundSection() {
const { color } = useHextimatorTheme();
const playgroundHref = useMemo(
Expand Down Expand Up @@ -65,14 +79,11 @@ function App() {
<ThemeColorMeta />
<NavBar />
<main className="flex flex-col items-center md:gap-0 mt-12 mb-24">
<Hero />
<HeroWithDashboard />
<div
className="flex flex-col items-stretch gap-8 md:gap-16"
className="flex flex-col items-stretch gap-8 pt-16 md:gap-16 md:pt-24"
id="features"
>
<div className="flex w-full justify-center mt-20 mb-14 px-4">
<ThemePreview />
</div>
<Section
title="Build the rules"
description="Configure lightness, chroma, and hue shifts for your palette. Once saved as a preset, any brand color produces a full theme with these same rules"
Expand Down
10 changes: 5 additions & 5 deletions apps/website/src/components/hero/color-input/color-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ export const ColorInput = forwardRef<HTMLDivElement, ColorInputProps>(
return (
<div
ref={ref}
className="flex bg-surface-weak font-extrabold rounded-sm px-1 gap-1 cursor-text"
className="flex h-fit cursor-text items-center gap-1.5 rounded-sm bg-surface-weak px-1.5 py-0 text-3xl font-extrabold leading-none lg:gap-1 lg:px-1 lg:text-4xl"
>
<HextimatorIcon className="my-2" scale={1.2} />
<HextimatorIcon className="shrink-0" scale={1.2} />
<div className="inline-grid">
<span
aria-hidden="true"
className="invisible whitespace-pre col-start-1 row-start-1"
className="invisible col-start-1 row-start-1 whitespace-pre leading-none"
>
{color || ' '}
{color || '\u00a0'}
</span>
<input
type="text"
value={color}
onChange={onColorChange}
aria-label="Hex color code"
className="text-surface-foreground col-start-1 row-start-1 focus:outline-none w-0 min-w-full underline"
className="text-surface-foreground col-start-1 row-start-1 m-0 w-0 min-w-full appearance-none border-0 bg-transparent p-0 leading-none underline decoration-[0.12em] underline-offset-[0.14em] focus:outline-none"
maxLength={6}
spellCheck={false}
autoComplete="off"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function hexToHS(hex: string): { h: number; s: number } | null {
else if (max === g) h = ((b - r) / d + 2) / 6;
else h = ((r - g) / d + 4) / 6;

return { h: h * 360, s };
return { h: h * 360, s: s };
}

function drawGradient(ctx: CanvasRenderingContext2D, w: number, h: number) {
Expand Down Expand Up @@ -135,8 +135,12 @@ export function ColorPicker({
</Popover.Anchor>
<Popover.Portal>
<Popover.Content
className="rounded-xl bg-surface-weak p-2 shadow-lg border border-surface-strong z-50 flex flex-col gap-2 w-min"
className="flex w-min flex-col gap-2 rounded-xl border border-surface-strong bg-surface-weak p-2 shadow-lg z-50"
align="center"
alignOffset={12}
side="bottom"
sideOffset={8}
collisionPadding={12}
onOpenAutoFocus={(e) => e.preventDefault()}
onFocusOutside={(e) => e.preventDefault()}
onInteractOutside={(e) => {
Expand Down
131 changes: 70 additions & 61 deletions apps/website/src/components/hero/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ function tryApplyColor(value: string, setColor: (c: string) => void) {
}

export function Hero() {
const { color: currentColor, setColor } = useHextimatorTheme();
const [initialColor] = useState(currentColor);
const { color, setColor } = useHextimatorTheme();
const [initialColor] = useState(color);
const [input, setInput] = useState('');
const [pickerOpen, setPickerOpen] = useState(false);

Expand Down Expand Up @@ -57,8 +57,6 @@ export function Hero() {
};

const handleFocus = () => {
// Only open picker on focus if cycler is already stopped
// (prevents Safari's spurious focus events during value updates)
if (isActive) return;
setPickerOpen(true);
};
Expand Down Expand Up @@ -87,65 +85,76 @@ export function Hero() {
restart(input);
};

const hintFadeStyle = {
opacity: showHint ? 0.6 : 0,
transition: 'opacity 300ms ease-in-out',
} satisfies React.CSSProperties;

return (
<section className="relative mt-12 md:mt-20 flex flex-col items-center text-center text-surface-foreground min-h-3/5 pt-6 px-6 gap-2 ">
<div
className="absolute -top-1 left-1/2 -translate-x-1/2 -ml-12 flex items-end gap-0.5 -rotate-3 pointer-events-none"
style={{
opacity: showHint ? 0.6 : 0,
transition: 'opacity 300ms ease-in-out',
}}
>
<span className="text-xs italic text-surface-foreground whitespace-nowrap">
pick any hex color
</span>
<LongArrowRightDown className="size-4" strokeWidth={1} />
</div>
<h1 className="sr-only">Hextimator: one color in, branded theme out</h1>
<div className="flex flex-col items-center">
<div className="flex flex-row gap-1 font-light text-4xl">
<span aria-hidden>One</span>
<ColorPicker
open={pickerOpen}
onOpenChange={setPickerOpen}
color={input}
onColorSelect={handlePickerSelect}
showResume={!isActive}
onResume={handleResume}
>
<ColorInput
color={input}
onColorChange={handleInputChange}
onFocus={handleFocus}
onClick={handleClick}
cycling={isActive}
/>
</ColorPicker>
<span aria-hidden>in.</span>
<section className="relative mt-12 min-h-3/5 w-full pb-12 pt-6 text-surface-foreground md:mt-20 md:pb-16">
<div className="mx-auto flex w-full min-w-0 max-w-5xl flex-col items-center gap-10 px-6 lg:px-8">
<div className="flex w-full min-w-0 flex-col items-center gap-3 text-center">
<h1 className="sr-only">
Hextimator: one color in, branded theme out
</h1>
<div className="flex w-full min-w-0 flex-col items-center">
<div className="relative mx-auto inline-flex max-w-full flex-row flex-wrap items-center justify-center gap-x-1 self-center text-3xl leading-tight font-light tracking-tight lg:text-4xl lg:leading-normal lg:tracking-normal">
<div
className="pointer-events-none absolute bottom-full left-1/2 z-10 mb-1 flex -translate-x-1/2 items-end gap-0.5 -rotate-3 whitespace-nowrap"
style={hintFadeStyle}
aria-hidden={!showHint}
>
<span className="text-xs italic text-surface-foreground">
pick any hex color
</span>
<LongArrowRightDown className="size-4" strokeWidth={1} />
</div>
<span aria-hidden>One</span>
<div className="relative inline-flex shrink-0">
<ColorPicker
open={pickerOpen}
onOpenChange={setPickerOpen}
color={input}
onColorSelect={handlePickerSelect}
showResume={!isActive}
onResume={handleResume}
>
<ColorInput
color={input}
onColorChange={handleInputChange}
onFocus={handleFocus}
onClick={handleClick}
cycling={isActive}
/>
</ColorPicker>
</div>
<span aria-hidden>in.</span>
</div>
<div className="flex max-w-full flex-row flex-wrap justify-center gap-x-1 gap-y-0 text-3xl leading-tight font-light tracking-tight lg:text-4xl lg:leading-normal lg:tracking-normal">
<span aria-hidden>Whole</span>
<span aria-hidden>theme</span>
<span aria-hidden>out.</span>
</div>
</div>
<p className="mx-auto w-full max-w-xs text-sm font-light text-balance md:max-w-sm">
Swap the brand color, and every shade, scale, and contrast ratio
regenerates itself.
</p>
<div className="mt-1 flex max-w-full flex-col items-center gap-3">
<Button icon={NavArrowRight} onClick={handleGetStarted}>
Get started
</Button>
<Button
variant="ghost"
href="https://github.com/fgrgic/hextimator"
target="_blank"
rel="noopener noreferrer"
icon={Star}
>
Star it on GitHub
</Button>
</div>
</div>
<div className="flex flex-row gap-1 font-light text-4xl">
<span aria-hidden>Whole</span>
<span aria-hidden>theme</span>
<span aria-hidden>out.</span>
</div>
</div>
<p className="text-sm font-light max-w-xs">
Swap the brand color, and every shade, scale, and contrast ratio
regenerates itself.
</p>
<div className="flex flex-col gap-3 mt-4">
<Button icon={NavArrowRight} onClick={handleGetStarted}>
Get started
</Button>
<Button
variant="ghost"
href="https://github.com/fgrgic/hextimator"
target="_blank"
rel="noopener noreferrer"
icon={Star}
>
Star it on GitHub
</Button>
</div>
</section>
);
Expand Down
Loading
Loading