refactor(ui): retire the shadcn colour aliases

Every class that used the old names (muted, primary, card, border, input and the rest) now uses the Desk & Paper token it resolved to, with text colours on their text tokens (accent-text, danger-text, ink-3). The compatibility block in the theme is gone.
This commit is contained in:
Amruth Pillai
2026-09-29 00:56:59 +02:00
parent 3a69dfc4d5
commit f8767c32f1
47 changed files with 161 additions and 197 deletions
+11 -11
View File
@@ -43,7 +43,7 @@ function ChipDragPreview({ chip }: ChipDragPreviewProps) {
return (
<Badge
variant="outline"
className="h-6 max-w-44 cursor-grabbing select-none justify-start rounded-md border-ring bg-muted px-2 font-medium text-foreground text-xs shadow-lg ring-2 ring-ring/25 sm:max-w-52"
className="h-6 max-w-44 cursor-grabbing select-none justify-start rounded-md border-accent bg-sunken px-2 font-medium text-ink text-xs shadow-lg ring-2 ring-accent/25 sm:max-w-52"
>
<span className="truncate">{chip}</span>
</Badge>
@@ -84,9 +84,9 @@ function ChipItem({ id, chip, index, isEditing, onEdit, onRemove }: ChipItemProp
<Badge
variant="outline"
className={cn(
"h-6 max-w-full cursor-grab select-none justify-start gap-0 rounded-md border-border bg-muted/55 px-2 font-medium text-foreground text-xs transition-colors hover:border-foreground/20 hover:bg-muted active:cursor-grabbing",
isEditing && "border-primary bg-primary/10 ring-1 ring-primary/40",
isDragging && "border-ring bg-muted shadow-sm",
"h-6 max-w-full cursor-grab select-none justify-start gap-0 rounded-md border-line bg-sunken/55 px-2 font-medium text-ink text-xs transition-colors hover:border-ink/20 hover:bg-sunken active:cursor-grabbing",
isEditing && "border-accent bg-accent/10 ring-1 ring-accent/40",
isDragging && "border-accent bg-sunken shadow-sm",
)}
>
<span className="max-w-32 truncate sm:max-w-44">{chip}</span>
@@ -99,7 +99,7 @@ function ChipItem({ id, chip, index, isEditing, onEdit, onRemove }: ChipItemProp
<button
type="button"
tabIndex={-1}
className="rounded-sm p-0.5 text-foreground/70 transition-colors hover:bg-secondary hover:text-foreground focus:outline-none"
className="rounded-sm p-0.5 text-ink/70 transition-colors hover:bg-sunken hover:text-ink focus:outline-none"
aria-label={t({
comment:
"Screen reader label for button that edits a keyword chip. Variable is the current keyword text.",
@@ -115,7 +115,7 @@ function ChipItem({ id, chip, index, isEditing, onEdit, onRemove }: ChipItemProp
<button
type="button"
tabIndex={-1}
className="rounded-sm p-0.5 text-foreground/70 transition-colors hover:bg-destructive/10 hover:text-destructive focus:outline-none"
className="rounded-sm p-0.5 text-ink/70 transition-colors hover:bg-danger/10 hover:text-danger-text focus:outline-none"
aria-label={t({
comment:
"Screen reader label for button that removes a keyword chip. Variable is the current keyword text.",
@@ -344,11 +344,11 @@ export function ChipInput({
<div
role="none"
onClick={() => inputRef.current?.focus()}
className="overflow-hidden rounded-lg border border-input bg-background/40 transition-colors focus-within:border-ring focus-within:ring-3 focus-within:ring-ring/50 dark:bg-input/20"
className="overflow-hidden rounded-lg border border-line-2 bg-bg/40 transition-colors focus-within:border-accent focus-within:ring-3 focus-within:ring-accent/50 dark:bg-line-2/20"
>
<div className="flex flex-col">
<div
className={cn("max-h-24 overflow-y-auto px-2 py-1.5", hasChips ? "border-border/70 border-b" : "hidden")}
className={cn("max-h-24 overflow-y-auto px-2 py-1.5", hasChips ? "border-line/70 border-b" : "hidden")}
>
<SortableContext items={chips} strategy={rectSortingStrategy}>
<div className="flex flex-wrap gap-1">
@@ -390,8 +390,8 @@ export function ChipInput({
className={cn(
"flex h-6 min-w-6 shrink-0 items-center justify-center rounded-md border px-1.5 font-medium text-[0.7rem] tabular-nums",
isEditingKeyword
? "border-primary/30 bg-primary/10 text-primary"
: "border-border bg-muted/50 text-foreground/80 opacity-80",
? "border-accent/30 bg-accent/10 text-accent-text"
: "border-line bg-sunken/50 text-ink/80 opacity-80",
)}
>
{isEditingKeyword ? <Trans>Edit</Trans> : chips.length}
@@ -404,7 +404,7 @@ export function ChipInput({
</DndContext>
{!hideDescription && (
<p className="text-muted-foreground text-xs">
<p className="text-ink-3 text-xs">
<Trans>
Press <Kbd>{RETURN_KEY}</Kbd> or <Kbd>{COMMA_KEY}</Kbd> to add or save the current keyword.
</Trans>
@@ -65,7 +65,7 @@ export function ColorPicker({
{trigger ?? (
<PopoverTrigger>
<div
className="size-6 shrink-0 cursor-pointer rounded-full border border-foreground/60 transition-transform duration-150 ease-out-strong focus-visible:outline-hidden active:scale-[0.97]"
className="size-6 shrink-0 cursor-pointer rounded-full border border-ink/60 transition-transform duration-150 ease-out-strong focus-visible:outline-hidden active:scale-[0.97]"
style={{ backgroundColor: currentValue }}
/>
</PopoverTrigger>
@@ -80,11 +80,11 @@ export function ColorPicker({
)}
<div className="flex flex-col gap-2">
<span className="font-medium text-muted-foreground text-xs">
<span className="font-medium text-ink-3 text-xs">
<Trans>Presets</Trans>
</span>
<div className="grid grid-cols-8 gap-3 rounded bg-muted p-3">
<div className="grid grid-cols-8 gap-3 rounded bg-sunken p-3">
{presetColors.map((color) => (
<button
key={color}
@@ -95,8 +95,8 @@ export function ColorPicker({
aria-pressed={currentValue === color}
onClick={() => setCurrentValue(color)}
className={cn(
"size-5 shrink-0 cursor-pointer rounded-full transition-transform duration-150 ease-out-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring active:scale-[0.97]",
currentValue === color && "border border-foreground/60",
"size-5 shrink-0 cursor-pointer rounded-full transition-transform duration-150 ease-out-strong focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-accent active:scale-[0.97]",
currentValue === color && "border border-ink/60",
)}
/>
))}
@@ -104,11 +104,11 @@ export function ColorPicker({
</div>
<div className="flex flex-col gap-2">
<span className="font-medium text-muted-foreground text-xs">
<span className="font-medium text-ink-3 text-xs">
<Trans>Custom</Trans>
</span>
<div className="rounded bg-muted p-3 *:w-full! [&_.w-color-alpha>div]:rounded-full! [&_.w-color-alpha]:mt-4! [&_.w-color-alpha]:h-4! [&_.w-color-hue]:mt-4! [&_.w-color-hue]:h-4! [&_.w-color-hue]:rounded-full! [&_.w-color-saturation]:h-36! [&_.w-color-saturation]:rounded-[calc(var(--radius-lg)-0.25rem)]!">
<div className="rounded bg-sunken p-3 *:w-full! [&_.w-color-alpha>div]:rounded-full! [&_.w-color-alpha]:mt-4! [&_.w-color-alpha]:h-4! [&_.w-color-hue]:mt-4! [&_.w-color-hue]:h-4! [&_.w-color-hue]:rounded-full! [&_.w-color-saturation]:h-36! [&_.w-color-saturation]:rounded-[calc(var(--radius-lg)-0.25rem)]!">
<ReactColorColorful color={color} onChange={onColorChange} />
</div>
</div>
+13 -13
View File
@@ -565,8 +565,8 @@ export function RichInput({
"data-fullscreen": isFullscreen ? "true" : "false",
class: cn(
"wysiwyg group/editor overflow-y-auto p-3 pb-4 [&_[data-resume-whitespace=preserve]]:whitespace-pre-wrap",
"rounded-md rounded-t-none border outline-none focus-visible:border-ring",
"[td:has(.selectedCell)]:bg-primary",
"rounded-md rounded-t-none border outline-none focus-visible:border-accent",
"[td:has(.selectedCell)]:bg-accent",
"data-[fullscreen=false]:max-h-[400px] data-[fullscreen=false]:min-h-[100px]",
"data-[fullscreen=true]:max-h-none data-[fullscreen=true]:min-h-full",
editorClassName,
@@ -596,7 +596,7 @@ export function RichInput({
const editorElement = (
<div className="relative">
{hasUnsupportedTable ? (
<div role="status" className="rounded-md rounded-b-none border border-b-0 bg-muted px-3 py-2 text-sm">
<div role="status" className="rounded-md rounded-b-none border border-b-0 bg-sunken px-3 py-2 text-sm">
<span>
<Trans>
Original table formatting is preserved. This content is read-only because it cannot be edited safely.
@@ -897,7 +897,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
size={isFullscreen ? "lg" : "sm"}
tabIndex={-1}
variant="ghost"
className={cn("rounded-none px-2", state.isHighlight && "bg-muted text-foreground")}
className={cn("rounded-none px-2", state.isHighlight && "bg-sunken text-ink")}
title={t`Highlight`}
disabled={!state.canHighlightColor}
>
@@ -916,7 +916,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
<PopoverHeader className="flex-row items-start justify-between gap-2">
<div className="flex items-center gap-2.5">
<span
className="grid size-9 place-items-center rounded-lg border border-border bg-muted/60 text-sm shadow-xs"
className="grid size-9 place-items-center rounded-lg border border-line bg-sunken/60 text-sm shadow-xs"
style={{ backgroundColor: visibleHighlightColor ?? defaultHighlightColor }}
>
<HighlighterCircleIcon className="size-4" />
@@ -926,7 +926,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
<PopoverTitle>
<Trans>Highlight Color</Trans>
</PopoverTitle>
<span className="text-muted-foreground text-xs">
<span className="text-ink-3 text-xs">
<Trans comment="Preset or custom shade refer to the color picker">
Choose a preset or custom shade.
</Trans>
@@ -957,7 +957,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
size={isFullscreen ? "lg" : "sm"}
tabIndex={-1}
variant="ghost"
className={cn("rounded-none px-2", state.textColor && "bg-muted text-foreground")}
className={cn("rounded-none px-2", state.textColor && "bg-sunken text-ink")}
title={t`Text Color`}
disabled={!state.canTextColor}
>
@@ -976,7 +976,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
<PopoverHeader className="flex-row items-start justify-between gap-2">
<div className="flex items-center gap-2.5">
<span
className="grid size-9 place-items-center rounded-lg border border-border bg-muted/60 font-semibold text-sm shadow-xs"
className="grid size-9 place-items-center rounded-lg border border-line bg-sunken/60 font-semibold text-sm shadow-xs"
style={{ color: state.textColor ?? "currentColor" }}
>
A
@@ -986,7 +986,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
<PopoverTitle>
<Trans>Text Color</Trans>
</PopoverTitle>
<span className="text-muted-foreground text-xs">
<span className="text-ink-3 text-xs">
<Trans comment="Preset or custom shade refer to the color picker">
Choose a preset or custom shade.
</Trans>
@@ -1006,7 +1006,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
</PopoverHeader>
</ColorPicker>
<div className="mx-1 h-5 w-px bg-border" />
<div className="mx-1 h-5 w-px bg-line" />
<DropdownMenu>
<DropdownMenuTrigger
@@ -1141,7 +1141,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
</DropdownMenuContent>
</DropdownMenu>
<div className="mx-1 h-5 w-px bg-border" />
<div className="mx-1 h-5 w-px bg-line" />
<Toggle
size={isFullscreen ? "lg" : "sm"}
@@ -1191,7 +1191,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
<TextIndentIcon className="size-3.5" />
</Button>
<div className="mx-1 h-5 w-px bg-border" />
<div className="mx-1 h-5 w-px bg-line" />
{state.isLink ? (
<Button
@@ -1251,7 +1251,7 @@ function renderEditorToolbar(state: EditorToolbarState, isFullscreen: boolean) {
<MinusIcon className="size-3.5" />
</Button>
<span className="ml-auto px-2 text-muted-foreground text-xs tabular-nums" aria-live="polite">
<span className="ml-auto px-2 text-ink-3 text-xs tabular-nums" aria-live="polite">
<Trans comment="Character count readout for the rich-text editor">{state.characterCount} characters</Trans>
</span>
</div>
@@ -3,7 +3,7 @@ import { Spinner } from "@reactive-resume/ui/components/spinner";
export function LoadingScreen() {
return (
<div className="fixed inset-0 z-50 flex h-svh w-svw flex-col items-center justify-center gap-y-6 bg-background">
<div className="fixed inset-0 z-50 flex h-svh w-svw flex-col items-center justify-center gap-y-6 bg-bg">
<BrandIcon variant="icon" className="size-12" />
<Spinner className="size-6" />
</div>
@@ -38,7 +38,7 @@ export function FontDisplay({ family, label, type, url }: FontDisplayProps) {
>
{label}
</span>
{label !== family && <span className="text-muted-foreground text-xs">{family}</span>}
{label !== family && <span className="text-ink-3 text-xs">{family}</span>}
</div>
);
}
+2 -2
View File
@@ -224,7 +224,7 @@ function Combobox<TValue extends string | number = string>(props: ComboboxProps<
render ?? (
<Button
variant="secondary"
className={cn("justify-start text-left font-normal hover:bg-muted/20", className)}
className={cn("justify-start text-left font-normal hover:bg-sunken/20", className)}
/>
)
}
@@ -254,7 +254,7 @@ function Combobox<TValue extends string | number = string>(props: ComboboxProps<
<ComboboxClear
aria-label={t`Clear selection`}
disabled={disabled}
className="absolute end-7 top-1/2 z-10 -translate-y-1/2 text-muted-foreground opacity-70 hover:opacity-100 focus-visible:opacity-100"
className="absolute end-7 top-1/2 z-10 -translate-y-1/2 text-ink-3 opacity-70 hover:opacity-100 focus-visible:opacity-100"
/>
)}
</div>
@@ -44,7 +44,7 @@ describe("Copyright", () => {
const { container } = renderCopyright({ className: "extra-class" });
const wrapper = container.firstChild as HTMLElement;
expect(wrapper.className).toContain("extra-class");
expect(wrapper.className).toContain("text-muted-foreground");
expect(wrapper.className).toContain("text-ink-3");
});
it("opens external links in a new tab", () => {
+1 -1
View File
@@ -5,7 +5,7 @@ type Props = React.ComponentProps<"div">;
export function Copyright({ className, ...props }: Props) {
return (
<div className={cn("text-muted-foreground/80 text-xs leading-relaxed", className)} {...props}>
<div className={cn("text-ink-3/80 text-xs leading-relaxed", className)} {...props}>
<p>
<Trans>
Licensed under{" "}
@@ -313,7 +313,7 @@ export function EnableTwoFactorDialog(_: DialogProps<"auth.two-factor.enable">)
<div className="space-y-4">
<div className="grid grid-cols-2 gap-2">
{backupCodes.map((code) => (
<div key={code} className="rounded-md border border-border p-2 text-center font-mono text-sm">
<div key={code} className="rounded-md border border-line p-2 text-center font-mono text-sm">
{code}
</div>
))}
@@ -212,16 +212,16 @@ export function ApplicationFormSheet({ open, onOpenChange, application }: Props)
{/* Pasted job description: stored with the application and used for every AI action.
Collapsed by default so the form stays short; hidden entirely when AI is off. */}
{aiEnabled && (
<Accordion className="rounded-lg border border-border border-dashed px-3">
<Accordion className="rounded-lg border border-line border-dashed px-3">
<AccordionItem value="job-description">
<AccordionTrigger>
<span className="flex items-center gap-1.5">
<SparkleIcon className="text-primary" />
<SparkleIcon className="text-accent-text" />
<Trans>Job description</Trans>
</span>
</AccordionTrigger>
<AccordionContent className="flex flex-col gap-2">
<p className="text-muted-foreground text-xs">
<p className="text-ink-3 text-xs">
<Trans>
Copy the entire job description from the posting and paste it below. We'll fill in the fields for
you and keep the text with this application for match scoring and tailoring.
@@ -239,7 +239,7 @@ export function ApplicationFormSheet({ open, onOpenChange, application }: Props)
onPaste={(event) => runAutofill(event.clipboardData.getData("text"))}
/>
<div className="flex items-center justify-between gap-2">
<p className="text-[11px] text-muted-foreground">
<p className="text-[11px] text-ink-3">
{autofill.isPending ? (
<Trans>Reading the posting…</Trans>
) : (
@@ -348,7 +348,7 @@ export function ApplicationFormSheet({ open, onOpenChange, application }: Props)
attachLabel={t`Or upload a resume PDF`}
onChange={(value) => set("resumeFile", value)}
/>
<p className="text-[11px] text-muted-foreground">
<p className="text-[11px] text-ink-3">
<Trans>Link a Reactive Resume to use AI match scoring and tailoring.</Trans>
</p>
</div>
@@ -401,9 +401,9 @@ export function ApplicationFormSheet({ open, onOpenChange, application }: Props)
function Field({ label, required, children }: { label: string; required?: boolean; children: React.ReactNode }) {
return (
<div className="grid gap-1.5">
<Label className="text-muted-foreground text-xs">
<Label className="text-ink-3 text-xs">
{label}
{required && <span className="text-destructive"> *</span>}
{required && <span className="text-danger-text"> *</span>}
</Label>
{children}
</div>
@@ -455,13 +455,13 @@ function TagsField({ value, suggestions, onChange }: TagsFieldProps) {
{value.map((tag) => (
<span
key={tag}
className="inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-muted-foreground text-xs"
className="inline-flex items-center gap-1 rounded-full bg-sunken px-2 py-0.5 text-ink-3 text-xs"
>
{tag}
<button
type="button"
title={t`Remove tag`}
className="hover:text-destructive"
className="hover:text-danger-text"
onClick={() => onChange(value.filter((t) => t !== tag))}
>
<XIcon className="size-3" />
@@ -88,7 +88,7 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
<div className="flex items-baseline gap-2">
<h2 className="font-semibold text-lg capitalize">{monthFormat.format(month)}</h2>
<span className="text-muted-foreground text-sm">
<span className="text-ink-3 text-sm">
{monthCount === 0 ? (
<Trans>No interviews</Trans>
) : monthCount === 1 ? (
@@ -121,11 +121,11 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
</div>
</div>
<div className="grid shrink-0 grid-cols-7 overflow-hidden rounded-xl border border-border bg-card">
<div className="grid shrink-0 grid-cols-7 overflow-hidden rounded-xl border border-line bg-surface">
{days.slice(0, 7).map((day) => (
<div
key={`weekday-${day.getDay()}`}
className="border-border border-b px-2 py-2 text-center font-medium text-[11px] text-muted-foreground uppercase tracking-wide"
className="border-line border-b px-2 py-2 text-center font-medium text-[11px] text-ink-3 uppercase tracking-wide"
>
{weekdayFormat.format(day)}
</div>
@@ -144,10 +144,10 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
key={key}
data-day={key}
className={cn(
"group/day relative flex min-h-16 flex-col gap-1 border-border p-1 sm:min-h-24 sm:p-1.5",
"group/day relative flex min-h-16 flex-col gap-1 border-line p-1 sm:min-h-24 sm:p-1.5",
i % 7 !== 6 && "border-e",
i < 35 && "border-b",
!inMonth && "bg-muted/30",
!inMonth && "bg-sunken/30",
)}
>
<div className="flex items-center justify-between">
@@ -155,7 +155,7 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
type="button"
title={scheduleLabel}
aria-label={scheduleLabel}
className="flex size-6 items-center justify-center rounded-md text-muted-foreground opacity-0 transition-opacity hover:bg-muted hover:text-foreground focus-visible:opacity-100 group-hover/day:opacity-100 max-sm:hidden"
className="flex size-6 items-center justify-center rounded-md text-ink-3 opacity-0 transition-opacity hover:bg-sunken hover:text-ink focus-visible:opacity-100 group-hover/day:opacity-100 max-sm:hidden"
onClick={() => schedule(day)}
>
<PlusIcon className="size-3.5" />
@@ -163,8 +163,8 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
<span
className={cn(
"flex size-6 items-center justify-center rounded-full text-xs",
!inMonth && "text-muted-foreground/60",
isToday && "bg-primary font-semibold text-primary-foreground",
!inMonth && "text-ink-3/60",
isToday && "bg-accent font-semibold text-on-accent",
)}
>
{day.getDate()}
@@ -184,7 +184,7 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
render={
<button
type="button"
className="self-start rounded-md px-1.5 py-0.5 text-[11px] text-muted-foreground hover:bg-muted hover:text-foreground"
className="self-start rounded-md px-1.5 py-0.5 text-[11px] text-ink-3 hover:bg-sunken hover:text-ink"
/>
}
>
@@ -208,7 +208,7 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
})}
</div>
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-muted-foreground text-xs">
<div className="flex flex-wrap items-center gap-x-4 gap-y-1 text-ink-3 text-xs">
{INTERVIEW_KINDS.map((kind) => (
<span key={kind.value} className="flex items-center gap-1.5">
<span className="size-2 rounded-full" style={{ background: kind.color }} />
@@ -224,15 +224,15 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
</h3>
{upcoming.length === 0 ? (
<div className="flex flex-col items-center gap-3 rounded-xl border border-border border-dashed px-4 py-8 text-center">
<div className="flex size-10 items-center justify-center rounded-full bg-muted">
<CalendarBlankIcon className="size-5 text-muted-foreground" />
<div className="flex flex-col items-center gap-3 rounded-xl border border-line border-dashed px-4 py-8 text-center">
<div className="flex size-10 items-center justify-center rounded-full bg-sunken">
<CalendarBlankIcon className="size-5 text-ink-3" />
</div>
<div className="space-y-1">
<p className="font-medium text-sm">
<Trans>Nothing scheduled yet</Trans>
</p>
<p className="text-muted-foreground text-xs">
<p className="text-ink-3 text-xs">
<Trans>Add screening calls and technical interviews to see them here and on the calendar.</Trans>
</p>
</div>
@@ -247,7 +247,7 @@ export function ApplicationCalendar({ applications, allApplications, onOpen }: A
if (!first) return null;
return (
<div key={dayKey(first.start)} className="flex flex-col gap-2">
<h4 className="font-medium text-muted-foreground text-xs">{dayHeading(first.start)}</h4>
<h4 className="font-medium text-ink-3 text-xs">{dayHeading(first.start)}</h4>
{group.map((item) => (
<UpcomingCard
key={item.interview.id}
@@ -289,26 +289,26 @@ function UpcomingCard({ item, timeRange, locale, onEdit, onOpenApplication }: Up
return (
<div
data-upcoming-interview={item.interview.id}
className="relative overflow-hidden rounded-xl border border-border bg-card ps-4 text-sm"
className="relative overflow-hidden rounded-xl border border-line bg-surface ps-4 text-sm"
>
<span className="absolute inset-y-0 start-0 w-1" style={{ background: kind?.color }} />
<button type="button" className="block w-full py-3 pe-3 text-start hover:opacity-80" onClick={onEdit}>
<span className="flex items-center justify-between gap-2 text-xs">
<span className="font-medium">{timeRange}</span>
<span className="text-muted-foreground">{formatDuration(item.interview.durationMinutes, locale)}</span>
<span className="text-ink-3">{formatDuration(item.interview.durationMinutes, locale)}</span>
</span>
<span className="mt-1 block truncate font-semibold">{item.application.company}</span>
<span className="block truncate text-muted-foreground text-xs">
<span className="block truncate text-ink-3 text-xs">
{kind?.label ?? item.interview.kind} · {item.application.role}
</span>
{item.interview.location && (
<span className="mt-1.5 flex items-center gap-1 text-muted-foreground text-xs">
<span className="mt-1.5 flex items-center gap-1 text-ink-3 text-xs">
<MapPinIcon className="shrink-0" />
<span className="truncate">{item.interview.location}</span>
</span>
)}
</button>
<button type="button" className="mb-2 text-primary text-xs hover:underline" onClick={onOpenApplication}>
<button type="button" className="mb-2 text-accent-text text-xs hover:underline" onClick={onOpenApplication}>
<Trans>View application</Trans>
</button>
</div>
@@ -327,13 +327,13 @@ function InterviewChip({ item, time, onOpen }: InterviewChipProps) {
<button
type="button"
title={`${time} · ${kind?.label ?? item.interview.kind} · ${item.application.company} — ${item.application.role}`}
className="flex min-w-0 items-center gap-1.5 rounded-md border-s-2 bg-muted/60 px-1.5 py-0.5 text-start text-[11px] leading-tight hover:bg-muted max-sm:justify-center max-sm:border-s-0 max-sm:bg-transparent"
className="flex min-w-0 items-center gap-1.5 rounded-md border-s-2 bg-sunken/60 px-1.5 py-0.5 text-start text-[11px] leading-tight hover:bg-sunken max-sm:justify-center max-sm:border-s-0 max-sm:bg-transparent"
style={{ borderInlineStartColor: kind?.color }}
onClick={onOpen}
>
<span className="size-2 shrink-0 rounded-full sm:hidden" style={{ background: kind?.color }} />
<span className="flex min-w-0 items-baseline gap-1.5 max-sm:hidden">
<span className="shrink-0 text-muted-foreground tabular-nums">{time}</span>
<span className="shrink-0 text-ink-3 tabular-nums">{time}</span>
<span className="truncate font-medium">{item.application.company}</span>
</span>
</button>
@@ -97,11 +97,11 @@ export function ExportApplicationsSheet({ open, onOpenChange, applications, filt
</div>
</div>
{!validRange && (
<p role="alert" className="text-destructive text-sm">
<p role="alert" className="text-danger-text text-sm">
<Trans>Start date must be on or before end date.</Trans>
</p>
)}
<p className="text-muted-foreground text-sm">
<p className="text-ink-3 text-sm">
<Plural value={selected.length} one="# application to export" other="# applications to export" />
</p>
</div>
@@ -55,8 +55,8 @@ export function FileAttachmentField({ value, onChange, attachLabel, disabled }:
return (
<>
{value ? (
<div className="flex items-center gap-3 rounded-lg border border-border p-2.5">
<span className="flex size-8 items-center justify-center rounded-md bg-primary/10 text-primary">
<div className="flex items-center gap-3 rounded-lg border border-line p-2.5">
<span className="flex size-8 items-center justify-center rounded-md bg-accent/10 text-accent-text">
<FilePdfIcon />
</span>
<a
@@ -71,7 +71,7 @@ export function FileAttachmentField({ value, onChange, attachLabel, disabled }:
type="button"
title={t`Remove file`}
disabled={disabled}
className="text-muted-foreground hover:text-destructive disabled:opacity-40"
className="text-ink-3 hover:text-danger-text disabled:opacity-40"
onClick={clear}
>
<XIcon />
@@ -82,7 +82,7 @@ export function FileAttachmentField({ value, onChange, attachLabel, disabled }:
type="button"
disabled={disabled || upload.isPending}
onClick={() => inputRef.current?.click()}
className="flex w-full items-center gap-2 rounded-lg border border-border border-dashed p-2.5 text-muted-foreground text-sm hover:bg-muted/50 disabled:opacity-60"
className="flex w-full items-center gap-2 rounded-lg border border-line border-dashed p-2.5 text-ink-3 text-sm hover:bg-sunken/50 disabled:opacity-60"
>
<UploadSimpleIcon />
{upload.isPending ? <Trans>Uploading…</Trans> : attachLabel}
@@ -200,9 +200,7 @@ export function InterviewDialog({
aria-pressed={selected}
className={cn(
"flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs transition-colors",
selected
? "border-foreground bg-foreground text-background"
: "border-border text-muted-foreground hover:bg-muted hover:text-foreground",
selected ? "border-ink bg-ink text-bg" : "border-line text-ink-3 hover:bg-sunken hover:text-ink",
)}
onClick={() => set("kind", kind.value)}
>
@@ -259,7 +257,7 @@ export function InterviewDialog({
<Button
type="button"
variant="ghost"
className="text-destructive"
className="text-danger-text"
disabled={pending}
onClick={async () => {
const confirmed = await confirm(t`Delete this interview?`, {
@@ -294,9 +292,9 @@ type FieldProps = { label: string; htmlFor?: string; required?: boolean; childre
function Field({ label, htmlFor, required, children }: FieldProps) {
return (
<div className="grid gap-1.5">
<Label htmlFor={htmlFor} className="text-muted-foreground text-xs">
<Label htmlFor={htmlFor} className="text-ink-3 text-xs">
{label}
{required && <span className="text-destructive"> *</span>}
{required && <span className="text-danger-text"> *</span>}
</Label>
{children}
</div>
@@ -21,13 +21,13 @@ export function FindingRow({ finding }: FindingRowProps) {
const evidence = finding.evidence;
return (
<li className="space-y-2 rounded-md border bg-card p-3">
<li className="space-y-2 rounded-md border bg-surface p-3">
<div className="flex items-start gap-2">
<span className={cn("mt-1.5 size-2 shrink-0 rounded-full", severityDotClass(finding.severity))} aria-hidden />
<div className="min-w-0 flex-1 space-y-1">
<p className="font-medium text-sm leading-snug">{message.title}</p>
<p className="text-muted-foreground text-xs leading-normal">{message.action}</p>
<p className="text-ink-3 text-xs leading-normal">{message.action}</p>
</div>
<Badge variant="neutral" className="shrink-0">
@@ -38,7 +38,7 @@ export function FindingRow({ finding }: FindingRowProps) {
{(evidence?.snippet || evidence?.page !== undefined) && (
<div className="flex flex-wrap items-center gap-2">
{evidence.snippet && (
<code className="min-w-0 truncate rounded bg-muted px-1.5 py-0.5 font-mono text-muted-foreground text-xs">
<code className="min-w-0 truncate rounded bg-sunken px-1.5 py-0.5 font-mono text-ink-3 text-xs">
{evidence.snippet}
</code>
)}
@@ -16,7 +16,7 @@ type JdCoverageProps = {
export function JdCoverage({ jd }: JdCoverageProps) {
if (jd.totalTerms === 0) {
return (
<p className="rounded-md border border-dashed p-3 text-muted-foreground text-xs leading-normal">
<p className="rounded-md border border-dashed p-3 text-ink-3 text-xs leading-normal">
<Trans>
No specific terms could be pulled out of that job description, so it may be mostly boilerplate. Paste the
requirements section for a more useful comparison.
@@ -29,21 +29,21 @@ export function JdCoverage({ jd }: JdCoverageProps) {
const missing = jd.terms.filter((term) => term.resumeCount === 0);
return (
<div className="space-y-3 rounded-md border bg-card p-3">
<div className="space-y-3 rounded-md border bg-surface p-3">
<div className="space-y-1">
<p className="font-medium text-sm leading-none">
<Trans>
{jd.matchedCount} of {jd.totalTerms} terms found
</Trans>
</p>
<p className="text-muted-foreground text-xs leading-normal">
<p className="text-ink-3 text-xs leading-normal">
<Trans>Counted separately from the parse score. Coverage does not predict anything.</Trans>
</p>
</div>
{missing.length > 0 && (
<div className="space-y-1.5">
<p className="font-medium text-muted-foreground text-xs">
<p className="font-medium text-ink-3 text-xs">
<Trans>Not in your resume</Trans>
</p>
<div className="flex flex-wrap gap-1.5">
@@ -58,7 +58,7 @@ export function JdCoverage({ jd }: JdCoverageProps) {
{matched.length > 0 && (
<div className="space-y-1.5">
<p className="font-medium text-muted-foreground text-xs">
<p className="font-medium text-ink-3 text-xs">
<Trans>Already covered</Trans>
</p>
<div className="flex flex-wrap gap-1.5">
@@ -72,7 +72,7 @@ export function JdCoverage({ jd }: JdCoverageProps) {
)}
{jd.stuffedTerms.length > 0 && (
<p className="text-muted-foreground text-xs leading-normal">
<p className="text-ink-3 text-xs leading-normal">
<Trans>
Repeated far more often than the posting itself uses them: {jd.stuffedTerms.join(", ")}. Recruiters notice.
</Trans>
@@ -80,7 +80,7 @@ export function JdCoverage({ jd }: JdCoverageProps) {
)}
{jd.documentHasHiddenText && (
<p className="text-muted-foreground text-xs leading-normal">
<p className="text-ink-3 text-xs leading-normal">
<Trans>This file contains text a reader cannot see, so some of these matches may be against it.</Trans>
</p>
)}
@@ -43,7 +43,7 @@ export function AtsPdfReportView({ report, className }: ReportViewProps) {
<ScoreHeader report={report} />
{report.document.truncated && (
<p className="text-muted-foreground text-xs leading-normal">
<p className="text-ink-3 text-xs leading-normal">
<Trans>
Only the first {report.document.pageCount} pages were checked, so this report does not cover the whole file.
</Trans>
@@ -64,7 +64,7 @@ export function AtsPdfReportView({ report, className }: ReportViewProps) {
{report.jd && <JdCoverage jd={report.jd} />}
<p className="text-muted-foreground text-xs leading-normal">
<p className="text-ink-3 text-xs leading-normal">
<Trans>
This measures how faithfully software can extract this file's text. It does not predict whether an application
will be rejected, and no tool can. Your file was read in this browser and never uploaded.
@@ -89,7 +89,7 @@ function CategorySection({ category, findings }: CategorySectionProps) {
<span className={categoryTone(category.score)}>{category.score}</span>
</Badge>
{findings.length > 0 && (
<span className="shrink-0 font-normal text-muted-foreground text-xs">
<span className="shrink-0 font-normal text-ink-3 text-xs">
<Trans>{findings.length} to fix</Trans>
</span>
)}
@@ -97,7 +97,7 @@ function CategorySection({ category, findings }: CategorySectionProps) {
</AccordionTrigger>
<AccordionContent className="space-y-3">
<p className="text-muted-foreground text-xs leading-normal">
<p className="text-ink-3 text-xs leading-normal">
{getPdfCategoryDescription(category.category)}{" "}
<Trans>
{category.passedChecks} of {category.applicableChecks} applicable checks passed.
@@ -107,7 +107,7 @@ function CategorySection({ category, findings }: CategorySectionProps) {
{findings.length === 0 ? (
<div className="flex items-center gap-2 rounded-md border border-dashed p-2.5">
<CheckCircleIcon className="size-4 shrink-0 text-accent-text" />
<span className="text-muted-foreground text-xs leading-normal">
<span className="text-ink-3 text-xs leading-normal">
<Trans>Nothing to fix here.</Trans>
</span>
</div>
@@ -141,12 +141,12 @@ function WritingSection({ tips }: WritingSectionProps) {
</AccordionTrigger>
<AccordionContent className="space-y-3">
<p className="text-muted-foreground text-xs leading-normal">{getPdfCategoryDescription("content")}</p>
<p className="text-ink-3 text-xs leading-normal">{getPdfCategoryDescription("content")}</p>
{tips.length === 0 ? (
<div className="flex items-center gap-2 rounded-md border border-dashed p-2.5">
<CheckCircleIcon className="size-4 shrink-0 text-accent-text" />
<span className="text-muted-foreground text-xs leading-normal">
<span className="text-ink-3 text-xs leading-normal">
<Trans>Nothing to suggest.</Trans>
</span>
</div>
@@ -21,15 +21,15 @@ export function ScoreHeader({ report }: ScoreHeaderProps) {
const [cap] = report.cappedBy;
return (
<div className="space-y-3 rounded-md border bg-card p-3">
<div className="space-y-3 rounded-md border bg-surface p-3">
<div className="flex items-baseline gap-2">
<span className={cn("font-bold text-4xl tabular-nums leading-none", tone.text)}>{report.score}</span>
<span className="text-muted-foreground text-sm">
<span className="text-ink-3 text-sm">
<Trans>out of 100</Trans>
</span>
</div>
<div className="h-1.5 overflow-hidden rounded-full bg-muted">
<div className="h-1.5 overflow-hidden rounded-full bg-sunken">
<div
className={cn(
"h-full translate-x-(--fill) rounded-full transition-[translate] duration-300 ease-out-strong rtl:-translate-x-(--fill)",
@@ -39,7 +39,7 @@ export function ScoreHeader({ report }: ScoreHeaderProps) {
/>
</div>
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-muted-foreground text-xs">
<div className="flex flex-wrap items-center gap-x-2 gap-y-1 text-ink-3 text-xs">
<span>
<Trans>
{report.passedChecks} of {report.applicableChecks} applicable checks passed
@@ -67,7 +67,7 @@ export function ScoreHeader({ report }: ScoreHeaderProps) {
</div>
{cap && (
<p className="rounded-md bg-muted/60 p-2 text-muted-foreground text-xs leading-normal">
<p className="rounded-md bg-sunken/60 p-2 text-ink-3 text-xs leading-normal">
<Trans>The score is capped because of a blocking problem: {getPdfFindingMessage(cap).title}</Trans>
</p>
)}
+3 -3
View File
@@ -58,7 +58,7 @@ export function OAuthConsentPage({ oauthQuery, email }: OAuthConsentPageProps) {
<h1 className="font-semibold text-2xl tracking-tight">
<Trans>Connect an application</Trans>
</h1>
<p className="wrap-anywhere text-muted-foreground text-sm">
<p className="wrap-anywhere text-ink-3 text-sm">
<Trans>Signed in as {email}</Trans>
</p>
</div>
@@ -74,7 +74,7 @@ export function OAuthConsentPage({ oauthQuery, email }: OAuthConsentPageProps) {
<div className="space-y-4">
<div className="wrap-anywhere space-y-1">
<p className="font-medium">{client.client_name || clientId}</p>
<p className="text-muted-foreground text-xs">
<p className="text-ink-3 text-xs">
<Trans>Client ID</Trans>: {clientId}
</p>
</div>
@@ -104,7 +104,7 @@ export function OAuthConsentPage({ oauthQuery, email }: OAuthConsentPageProps) {
)}
</ul>
{error && (
<p role="alert" className="text-destructive text-sm">
<p role="alert" className="text-danger-text text-sm">
{error}
</p>
)}
@@ -57,7 +57,7 @@ export function ForgotPasswordPage() {
<Trans>Forgot your password?</Trans>
</h1>
<div className="text-muted-foreground">
<div className="text-ink-3">
<Trans>
Remember your password?{" "}
<Button
@@ -122,7 +122,7 @@ function PostForgotPasswordScreen() {
<h1 className="font-semibold text-2xl tracking-tight">
<Trans>You've got mail!</Trans>
</h1>
<p className="text-muted-foreground">
<p className="text-ink-3">
<Trans>Check your email for a link to reset your password.</Trans>
</p>
</div>
+1 -1
View File
@@ -124,7 +124,7 @@ export function LoginPage({ disableEmailAuth, disableSignups }: Props) {
</h1>
{!disableSignups && (
<div className="text-muted-foreground">
<div className="text-ink-3">
<Trans>
Don't have an account?{" "}
<Button
@@ -94,7 +94,7 @@ export function RegisterPage({ disableEmailAuth }: Props) {
<Trans>Create a new account</Trans>
</h1>
<div className="text-muted-foreground">
<div className="text-ink-3">
<Trans>
Already have an account?{" "}
<Button
@@ -268,7 +268,7 @@ function PostSignupScreen() {
<h1 className="font-semibold text-2xl tracking-tight">
<Trans>You've got mail!</Trans>
</h1>
<p className="text-muted-foreground">
<p className="text-ink-3">
<Trans>Check your email for a link to verify your account.</Trans>
</p>
</div>
@@ -62,7 +62,7 @@ export function ResetPasswordPage({ token }: Props) {
<Trans>Reset your password</Trans>
</h1>
<div className="text-muted-foreground">
<div className="text-ink-3">
<Trans>Enter a new password for your account</Trans>
</div>
</div>
@@ -81,7 +81,7 @@ export function ResumePasswordPage({ username, slug, redirectPath }: ResumePassw
<Trans>This resume is password protected</Trans>
</h1>
<div className="text-muted-foreground leading-relaxed">
<div className="text-ink-3 leading-relaxed">
<Trans>Enter the password the resume owner shared with you.</Trans>
</div>
</div>
@@ -74,7 +74,7 @@ function TwoFactorVerificationPage({ backupCode = false }: TwoFactorVerification
<h1 className="font-semibold text-2xl tracking-tight">
{backupCode ? <Trans>Verify with a Backup Code</Trans> : <Trans>Two-Factor Authentication</Trans>}
</h1>
<div className="text-muted-foreground">
<div className="text-ink-3">
{backupCode ? (
<Trans>Enter one of your saved backup codes to access your account</Trans>
) : (
@@ -122,7 +122,7 @@ export function CommandPalette() {
comment: "Accessible label for command list region inside command palette",
message: "Command Palette",
})}
className="[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group]]:px-2 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3"
className="[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-ink-3 **:[[cmdk-group]]:px-2 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3"
>
<CommandInput
ref={inputRef}
@@ -11,7 +11,7 @@ export function LanguageCommandPage() {
<BaseCommandGroup page="language" heading={<Trans>Language</Trans>}>
{Object.entries(localeMap).map(([value, label]) => (
<CommandItem key={value} onSelect={() => changeLocale(value)}>
<span className="font-mono text-muted-foreground text-xs">{value}</span>
<span className="font-mono text-ink-3 text-xs">{value}</span>
{i18n.t(label)}
</CommandItem>
))}
@@ -181,7 +181,7 @@ export function ResumesCommandGroup() {
>
<BriefcaseIcon />
<span className="min-w-0 truncate">{application.company}</span>
<span className="truncate text-muted-foreground text-xs">{application.role}</span>
<span className="truncate text-ink-3 text-xs">{application.role}</span>
</CommandItem>
))
)}
@@ -207,7 +207,7 @@ export function ResumesCommandGroup() {
>
<ChatCircleDotsIcon />
<span className="min-w-0 truncate">{thread.title}</span>
<span className="truncate text-muted-foreground text-xs">{documentName}</span>
<span className="truncate text-ink-3 text-xs">{documentName}</span>
</CommandItem>
);
})
@@ -10,7 +10,7 @@ export const getLocaleOptions = () => {
value: value as Locale,
label: (
<span className="flex items-center gap-x-2">
<span className="font-mono text-muted-foreground text-xs">{value}</span>
<span className="font-mono text-ink-3 text-xs">{value}</span>
{name}
</span>
),
@@ -96,7 +96,7 @@ export function ResumePasswordDialog({ onSubmit, onClose }: ResumePasswordDialog
aria-invalid={!!error}
aria-describedby={`${id}-hint${error ? ` ${id}-error` : ""}`}
/>
<p id={`${id}-hint`} className="text-muted-foreground text-sm">
<p id={`${id}-hint`} className="text-ink-3 text-sm">
<Trans>Use between 6 and 64 characters.</Trans>
</p>
</div>
@@ -119,7 +119,7 @@ export function ResumePasswordDialog({ onSubmit, onClose }: ResumePasswordDialog
/>
</div>
{error && (
<p id={`${id}-error`} role="alert" className="text-destructive text-sm">
<p id={`${id}-error`} role="alert" className="text-danger-text text-sm">
{error}
</p>
)}
@@ -79,7 +79,7 @@ function PicturePreviewControls({
type="button"
onClick={picture.url ? onDeletePicture : onSelectPicture}
aria-label={picture.url ? t`Delete picture` : t`Upload picture`}
className="group/picture relative size-18 cursor-pointer overflow-hidden rounded-md bg-secondary transition-colors hover:bg-secondary/50"
className="group/picture relative size-18 cursor-pointer overflow-hidden rounded-md bg-sunken transition-colors hover:bg-sunken/50"
>
{normalizedPictureUrl && (
<img
@@ -293,7 +293,7 @@ function PictureGeometryFields({ form, onAutoSave }: PictureFieldProps) {
onAutoSave();
}}
>
<div className="aspect-square min-h-3 min-w-3 border border-primary" />
<div className="aspect-square min-h-3 min-w-3 border border-accent" />
</Button>
<Button
size="icon"
@@ -307,7 +307,7 @@ function PictureGeometryFields({ form, onAutoSave }: PictureFieldProps) {
onAutoSave();
}}
>
<div className="aspect-1.5/1 min-h-3 min-w-3 border border-primary" />
<div className="aspect-1.5/1 min-h-3 min-w-3 border border-accent" />
</Button>
<Button
size="icon"
@@ -321,7 +321,7 @@ function PictureGeometryFields({ form, onAutoSave }: PictureFieldProps) {
onAutoSave();
}}
>
<div className="aspect-1/1.5 min-h-3 min-w-3 border border-primary" />
<div className="aspect-1/1.5 min-h-3 min-w-3 border border-accent" />
</Button>
</ButtonGroup>
</div>
@@ -368,7 +368,7 @@ function PictureGeometryFields({ form, onAutoSave }: PictureFieldProps) {
onAutoSave();
}}
>
<div className="size-3 rounded-none border border-primary" />
<div className="size-3 rounded-none border border-accent" />
</Button>
<Button
size="icon"
@@ -379,7 +379,7 @@ function PictureGeometryFields({ form, onAutoSave }: PictureFieldProps) {
onAutoSave();
}}
>
<div className="size-3 rounded-[10%] border border-primary" />
<div className="size-3 rounded-[10%] border border-accent" />
</Button>
<Button
size="icon"
@@ -390,7 +390,7 @@ function PictureGeometryFields({ form, onAutoSave }: PictureFieldProps) {
onAutoSave();
}}
>
<div className="size-3 rounded-full border border-primary" />
<div className="size-3 rounded-full border border-accent" />
</Button>
</ButtonGroup>
</div>
@@ -632,7 +632,7 @@ export function PictureSettings() {
</DialogHeader>
{cropState && (
<div className="relative h-64 w-full overflow-hidden rounded-md bg-secondary ring-1 ring-border ring-inset">
<div className="relative h-64 w-full overflow-hidden rounded-md bg-sunken ring-1 ring-line ring-inset">
<Cropper
image={cropState.imageSrc}
crop={crop}
@@ -652,10 +652,10 @@ export function PictureSettings() {
<FormLabel className="mb-0">
<Trans>Zoom</Trans>
</FormLabel>
<span className="text-muted-foreground text-xs tabular-nums">{zoom.toFixed(1)}×</span>
<span className="text-ink-3 text-xs tabular-nums">{zoom.toFixed(1)}×</span>
</div>
<div className="flex items-center gap-x-3">
<MagnifyingGlassMinusIcon className="size-4 shrink-0 text-muted-foreground" />
<MagnifyingGlassMinusIcon className="size-4 shrink-0 text-ink-3" />
<Slider
min={1}
max={3}
@@ -667,7 +667,7 @@ export function PictureSettings() {
setZoom(Array.isArray(value) ? value[0] : value);
}}
/>
<MagnifyingGlassPlusIcon className="size-4 shrink-0 text-muted-foreground" />
<MagnifyingGlassPlusIcon className="size-4 shrink-0 text-ink-3" />
</div>
</div>
@@ -185,7 +185,7 @@ export function PdfCanvasPage({
<figure className="shrink-0">
{caption ??
(showPageNumbers ? (
<figcaption className="mb-1 font-medium text-[0.625rem] text-muted-foreground">
<figcaption className="mb-1 font-medium text-[0.625rem] text-ink-3">
Page {pageNumber} of {totalPages}
</figcaption>
) : null)}
@@ -66,7 +66,7 @@ export function ResumePreviewLoader({
return (
<figure key={pageNumber} className="shrink-0">
{showPageNumbers ? (
<figcaption className="mb-1 font-medium text-[0.625rem] text-muted-foreground">
<figcaption className="mb-1 font-medium text-[0.625rem] text-ink-3">
Page {pageNumber} of {pageCount}
</figcaption>
) : null}
@@ -245,11 +245,11 @@ export function PdfViewer({
</div>
{error ? (
<div className="absolute inset-0 flex items-center justify-center bg-background px-6 text-center text-muted-foreground text-sm">
<div className="absolute inset-0 flex items-center justify-center bg-bg px-6 text-center text-ink-3 text-sm">
Unable to display PDF preview.
</div>
) : isReady ? null : (
<div className="absolute inset-0 flex items-center justify-center bg-background">
<div className="absolute inset-0 flex items-center justify-center bg-bg">
<Spinner className="size-6" />
</div>
)}
@@ -330,7 +330,7 @@ export function StylesheetCodeEditor({
type="button"
title={t`Edit color ${selectedColor.token.value}`}
aria-label={t`Edit color ${selectedColor.token.value}`}
className="pointer-events-auto size-3 rounded-full border border-foreground/40"
className="pointer-events-auto size-3 rounded-full border border-ink/40"
style={{ backgroundColor: selectedColor.token.value }}
/>
}
@@ -514,12 +514,12 @@ function StylesheetEditorShell({ readOnly = false }: StylesheetEditorShellProps)
onFocusToggle={toggleFocus}
/>
<p className="flex items-center gap-1.5 text-muted-foreground text-xs">
<p className="flex items-center gap-1.5 text-ink-3 text-xs">
<BookOpenIcon aria-hidden="true" className="shrink-0" />
<span>
<Trans>Not sure what to write?</Trans>{" "}
<a
className="text-primary underline underline-offset-4"
className="text-accent-text underline underline-offset-4"
href="https://docs.rxresu.me/applying-custom-styles"
target="_blank"
rel="noopener noreferrer"
@@ -72,7 +72,7 @@ export function StylesheetStatus({ mode, status, diagnostics }: StylesheetStatus
{diagnostics.map((diagnostic) => (
<li key={`${diagnostic.code}-${diagnostic.range.start.offset}`} className="space-y-0.5">
<p className="font-medium">{diagnostic.message}</p>
<p className="text-muted-foreground">
<p className="text-ink-3">
<Trans>
Line {diagnostic.range.start.line}, column {diagnostic.range.start.column}
</Trans>
@@ -61,7 +61,7 @@ export function Footer() {
<div className="space-y-2">
<h2 className="font-semibold text-lg tracking-tight">Reactive Resume</h2>
<p className="max-w-xs text-muted-foreground text-sm leading-relaxed">
<p className="max-w-xs text-ink-3 text-sm leading-relaxed">
<Trans>
A free and open-source resume builder that makes it easy to create, update, and share your resume.
</Trans>
@@ -109,7 +109,7 @@ export function Footer() {
function FooterLinkGroup({ title, links }: FooterLinkGroupProps) {
return (
<div className="space-y-4">
<h2 className="font-medium text-muted-foreground text-sm tracking-tight">{title}</h2>
<h2 className="font-medium text-ink-3 text-sm tracking-tight">{title}</h2>
<ul className="space-y-3">
{links.map((link) => (
@@ -127,7 +127,7 @@ function FooterLink({ url, label }: FooterLinkItem) {
href={url}
target="_blank"
rel="noopener noreferrer"
className="relative inline-block text-sm transition-colors after:pointer-events-none after:absolute after:inset-x-0 after:-bottom-0.5 after:h-px after:origin-left after:scale-x-0 after:rounded-md after:bg-primary after:content-[''] hover:text-foreground hover:after:scale-x-100 rtl:after:origin-right after:[transition:scale_200ms_var(--ease-out-strong)]"
className="relative inline-block text-sm transition-colors after:pointer-events-none after:absolute after:inset-x-0 after:-bottom-0.5 after:h-px after:origin-left after:scale-x-0 after:rounded-md after:bg-accent after:content-[''] hover:text-ink hover:after:scale-x-100 rtl:after:origin-right after:[transition:scale_200ms_var(--ease-out-strong)]"
>
{label}
@@ -20,7 +20,7 @@ export function Header() {
return (
<header
data-hidden={hidden}
className="fade-in animation-duration-300 fixed inset-x-0 top-0 z-50 animate-in border-transparent border-b bg-background/80 backdrop-blur-lg ease-out-strong [transition:translate_250ms_var(--ease-out-strong)] data-[hidden=true]:-translate-y-full"
className="fade-in animation-duration-300 fixed inset-x-0 top-0 z-50 animate-in border-transparent border-b bg-bg/80 backdrop-blur-lg ease-out-strong [transition:translate_250ms_var(--ease-out-strong)] data-[hidden=true]:-translate-y-full"
>
<nav aria-label={t`Main navigation`} className="container mx-auto flex items-center gap-x-4 p-3 lg:px-12">
<Link to="/" className="transition-opacity hover:opacity-80" aria-label={t`Reactive Resume - Go to homepage`}>
+1 -1
View File
@@ -16,7 +16,7 @@ function RouteComponent() {
{!isHomepage && (
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:inset-s-4 focus:top-4 focus:z-100 focus:rounded-md focus:bg-background focus:px-4 focus:py-2 focus:text-foreground focus:shadow-lg focus:ring-2 focus:ring-ring"
className="sr-only focus:not-sr-only focus:fixed focus:inset-s-4 focus:top-4 focus:z-100 focus:rounded-md focus:bg-bg focus:px-4 focus:py-2 focus:text-ink focus:shadow-lg focus:ring-2 focus:ring-accent"
>
<Trans>Skip to main content</Trans>
</a>
@@ -7,7 +7,7 @@ export function CustomStylesSectionBuilder() {
return (
<SectionBase type="styles" className="space-y-4">
<Suspense
fallback={<div role="status" className="h-72 animate-pulse rounded-md bg-muted" aria-label="Loading editor" />}
fallback={<div role="status" className="h-72 animate-pulse rounded-md bg-sunken" aria-label="Loading editor" />}
>
<StylesheetEditorShell />
</Suspense>
@@ -180,7 +180,7 @@ function QuickColorCircle({ color, active, onSelect, className, ...props }: Quic
onClick={() => onSelect(color)}
className={cn(
"relative flex size-8 items-center justify-center rounded-md bg-transparent",
"transition-[scale,background-color] duration-150 ease-out-strong hover:bg-secondary/80 active:scale-[0.97]",
"transition-[scale,background-color] duration-150 ease-out-strong hover:bg-sunken/80 active:scale-[0.97]",
className,
)}
{...props}
@@ -196,7 +196,7 @@ function QuickColorCircle({ color, active, onSelect, className, ...props }: Quic
transition={{ duration: 0.16, ease: EASE_OUT_STRONG }}
className="absolute inset-0 flex size-8 items-center justify-center"
>
<div className="size-4 rounded-md bg-foreground" />
<div className="size-4 rounded-md bg-ink" />
</m.div>
)}
</AnimatePresence>
@@ -4,7 +4,7 @@ const source = await import("./pages?raw").then((module) => module.default);
describe("layout page header", () => {
it("uses container queries to prevent narrow sidebar control collisions", () => {
expect(source).toContain("@container bg-secondary/50");
expect(source).toContain("@container bg-sunken/50");
expect(source).toContain("grid-cols-[minmax(0,1fr)_auto]");
expect(source).toContain("@max-[22rem]:grid-cols-1");
expect(source).toContain("flex min-w-0 flex-wrap");
@@ -299,8 +299,8 @@ function PageContainer({
const fullWidthSwitchId = useId();
return (
<div className="space-y-3 rounded-md border border-dashed bg-background/40">
<div className="@container bg-secondary/50 px-4 py-3">
<div className="space-y-3 rounded-md border border-dashed bg-bg/40">
<div className="@container bg-sunken/50 px-4 py-3">
<div className="grid @max-[22rem]:grid-cols-1 grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 gap-y-2">
<div className="flex min-w-0 flex-wrap items-center gap-x-4 gap-y-2">
<span className="font-medium text-xs">
@@ -314,7 +314,7 @@ function PageContainer({
onCheckedChange={(checked) => onToggleFullWidth(pageIndex, checked)}
/>
<span className="font-medium text-muted-foreground text-xs">
<span className="font-medium text-ink-3 text-xs">
<Trans comment="Layout editor toggle label that makes a page single-column">Full Width</Trans>
</span>
</label>
@@ -390,7 +390,7 @@ function LayoutColumn({
ref={setNodeRef}
className={cn(
"space-y-2.5 rounded-md border border-dashed p-3 pb-8 transition-colors",
isOver && !disabled ? "border-primary/60 bg-primary/5" : "bg-background/40",
isOver && !disabled ? "border-accent/60 bg-accent/5" : "bg-bg/40",
)}
>
{items.map((id) => (
@@ -398,7 +398,7 @@ function LayoutColumn({
))}
{items.length === 0 && (
<div className="rounded-md border border-dashed p-4 font-medium text-muted-foreground text-xs">
<div className="rounded-md border border-dashed p-4 font-medium text-ink-3 text-xs">
<Trans>Drag and drop sections here to move them between columns</Trans>
</div>
)}
@@ -573,7 +573,7 @@ function SectionBreakItems({ id }: SectionBreakItemsProps) {
<Trans comment="Layout editor toggle that prevents a section from splitting across pages">Keep together</Trans>
</DropdownMenuCheckboxItem>
<p className="px-2 pb-1 text-muted-foreground text-xs">
<p className="px-2 pb-1 text-ink-3 text-xs">
<Trans comment="Helper note explaining the keep-together limitation">
Only applies when the section fits on a single page.
</Trans>
@@ -620,9 +620,9 @@ function LayoutItemContent({
data-overlay={isOverlay ? "true" : undefined}
data-dragging={isDragging ? "true" : undefined}
className={cn(
"group/item flex cursor-grab touch-none select-none items-center gap-x-2 rounded-md border border-border bg-background px-2 py-1.5 font-medium text-sm transition-[background-color,border-color] duration-150",
"hover:bg-secondary/40 active:cursor-grabbing active:border-primary/60 active:bg-secondary/40",
"data-[overlay=true]:cursor-grabbing data-[overlay=true]:border-primary/60 data-[overlay=true]:bg-background data-[overlay=true]:shadow-md",
"group/item flex cursor-grab touch-none select-none items-center gap-x-2 rounded-md border border-line bg-bg px-2 py-1.5 font-medium text-sm transition-[background-color,border-color] duration-150",
"hover:bg-sunken/40 active:cursor-grabbing active:border-accent/60 active:bg-sunken/40",
"data-[overlay=true]:cursor-grabbing data-[overlay=true]:border-accent/60 data-[overlay=true]:bg-bg data-[overlay=true]:shadow-md",
"data-[dragging=true]:cursor-grabbing data-[dragging=true]:opacity-40",
className,
)}
@@ -637,7 +637,7 @@ function LayoutItemContent({
<DropdownMenuTrigger
aria-label={t`Move section to another column or page`}
onPointerDown={(event) => event.stopPropagation()}
className="flex cursor-context-menu items-center rounded p-0.5 opacity-40 transition-opacity hover:bg-secondary/40 focus:outline-none focus-visible:ring-1 group-hover/item:opacity-100"
className="flex cursor-context-menu items-center rounded p-0.5 opacity-40 transition-opacity hover:bg-sunken/40 focus:outline-none focus-visible:ring-1 group-hover/item:opacity-100"
>
<DotsThreeVerticalIcon />
</DropdownMenuTrigger>
@@ -30,7 +30,7 @@ function NotesSectionForm() {
<RichInput value={notes} onChange={onChange} />
<p className="text-muted-foreground">
<p className="text-ink-3">
<Trans>For example, note which companies you sent this resume to, or links to the job descriptions.</Trans>
</p>
</div>
+1 -1
View File
@@ -19,7 +19,7 @@ function OTPFieldInput({ className, ...props }: OTPFieldPrimitive.Input.Props) {
<OTPFieldPrimitive.Input
data-slot="otp-field-input"
className={cn(
"size-9 rounded-lg border border-input bg-transparent text-center font-mono text-sm outline-none transition-[color,background-color,border-color,box-shadow] focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 group-aria-invalid:border-destructive group-aria-invalid:ring-3 group-aria-invalid:ring-destructive/20",
"size-9 rounded-lg border border-line-2 bg-transparent text-center font-mono text-sm outline-none transition-[color,background-color,border-color,box-shadow] focus-visible:border-accent focus-visible:ring-3 focus-visible:ring-accent/50 disabled:cursor-not-allowed disabled:bg-line-2/50 disabled:opacity-50 group-aria-invalid:border-danger group-aria-invalid:ring-3 group-aria-invalid:ring-danger/20",
className,
)}
{...props}
+2 -2
View File
@@ -6,7 +6,7 @@ function ScrollArea({ className, children, ...props }: ScrollAreaPrimitive.Root.
<ScrollAreaPrimitive.Root data-slot="scroll-area" className={cn("relative", className)} {...props}>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] focus-visible:ring-ring/50"
className="size-full rounded-[inherit] outline-none transition-[color,box-shadow] focus-visible:outline-1 focus-visible:ring-[3px] focus-visible:ring-accent/50"
>
{children}
</ScrollAreaPrimitive.Viewport>
@@ -28,7 +28,7 @@ function ScrollBar({ className, orientation = "vertical", ...props }: ScrollArea
)}
{...props}
>
<ScrollAreaPrimitive.Thumb data-slot="scroll-area-thumb" className="relative flex-1 rounded-full bg-border" />
<ScrollAreaPrimitive.Thumb data-slot="scroll-area-thumb" className="relative flex-1 rounded-full bg-line" />
</ScrollAreaPrimitive.Scrollbar>
);
}
-34
View File
@@ -196,40 +196,6 @@
--ease-out-strong: cubic-bezier(0.23, 1, 0.32, 1);
--ease-in-out-strong: cubic-bezier(0.77, 0, 0.175, 1);
--ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
/*
* Compatibility: the previous shadcn-style names resolve to Desk & Paper tokens so screens that
* haven't been rebuilt stay legible. Remove once every screen uses the names above (plan M12).
*/
--color-background: var(--bg);
--color-foreground: var(--ink);
--color-card: var(--surface);
--color-card-foreground: var(--ink);
--color-popover: var(--raised);
--color-popover-foreground: var(--ink);
--color-primary: var(--accent);
--color-primary-foreground: var(--on-accent);
--color-secondary: var(--sunken);
--color-secondary-foreground: var(--ink);
--color-muted: var(--sunken);
--color-muted-foreground: var(--ink-3);
--color-destructive: var(--danger);
--color-border: var(--line);
--color-input: var(--line-2);
--color-ring: var(--accent);
--color-chart-1: var(--ink-3);
--color-chart-2: var(--ink-2);
--color-chart-3: var(--accent);
--color-chart-4: var(--stage-applied);
--color-chart-5: var(--stage-interview);
--color-sidebar: var(--surface);
--color-sidebar-foreground: var(--ink);
--color-sidebar-primary: var(--accent);
--color-sidebar-primary-foreground: var(--on-accent);
--color-sidebar-accent: var(--sunken);
--color-sidebar-accent-foreground: var(--ink);
--color-sidebar-border: var(--line);
--color-sidebar-ring: var(--accent);
}
@layer base {