fix: migrate style using tailwind css

This commit is contained in:
2026-09-14 17:26:03 +07:00
parent d0506b7ff2
commit fbe757c7f2
4 changed files with 112 additions and 264 deletions
+36 -15
View File
@@ -26,24 +26,41 @@ export function LoginPage() {
const errorMessage = loginMutation.error instanceof Error ? loginMutation.error.message : 'Unable to sign in';
return (
<main className="auth-page">
<ThemeToggle className="auth-theme-toggle app-nav-icon" />
<div className="auth-brand" aria-label="Upwatch">
<Zap className="auth-brand-mark" fill="currentColor" />
<main className="relative grid min-h-dvh grid-rows-[auto_1fr_auto] justify-items-center bg-[radial-gradient(circle_at_50%_38%,rgb(62_207_142/0.08),transparent_34%),linear-gradient(#fff,#fcfcfc)] p-6 pt-8 pb-6 transition-[background,color] duration-250 ease-out max-compact:px-4 max-compact:py-6 dark:bg-[radial-gradient(ellipse_75%_55%_at_50%_30%,rgba(62,207,142,0.12),transparent_65%),radial-gradient(ellipse_40%_30%_at_50%_12%,rgba(36,180,126,0.07),transparent_50%),linear-gradient(180deg,#0c0e10_0%,#070809_100%)]">
<ThemeToggle className="absolute top-7 right-7 z-1 text-icon-muted transition-[color,background-color,border-color] duration-150 hover:bg-brand/10 hover:text-brand-deep dark:rounded-lg dark:border dark:border-white/8 dark:bg-white/3 dark:text-gray-400 dark:backdrop-blur-md dark:hover:border-brand/25 dark:hover:bg-brand/10 dark:hover:text-brand" />
<div
className="relative z-1 inline-flex items-center gap-2 text-brand font-semibold tracking-brand dark:text-white"
aria-label="Upwatch"
>
<Zap className="size-6.5 text-brand-deep dark:text-brand dark:drop-shadow-brand-glow" fill="currentColor" />
<span>upwatch</span>
</div>
<Card asChild>
<section className="auth-card" aria-labelledby="login-title">
<div className="auth-heading">
<p>Admin access</p>
<h1 id="login-title">Sign in to Upwatch</h1>
<span>Enter the admin password to manage your monitors.</span>
<section
className="relative z-1 w-[min(100%,420px)] self-center rounded-xl border border-hairline bg-white/96 p-9 shadow-card-elevated animate-enter max-compact:px-5.5 max-compact:py-7 dark:border-white/8 dark:bg-night-card/82 dark:shadow-[0_0_0_1px_rgba(255,255,255,0.05),0_24px_64px_-12px_rgba(0,0,0,0.8),0_0_45px_-10px_rgba(62,207,142,0.08),inset_0_1px_0_0_rgba(255,255,255,0.06)] dark:backdrop-blur-[20px]"
aria-labelledby="login-title"
>
<div>
<p className="m-0 mb-3 font-mono text-2xs font-medium leading-overline tracking-overline uppercase text-brand-muted dark:text-brand">
Admin access
</p>
<h1
id="login-title"
className="m-0 text-title font-medium leading-title tracking-title max-compact:text-title-compact dark:text-gray-50"
>
Sign in to Upwatch
</h1>
<span className="mt-3 block text-subtitle leading-subtitle text-ink-subtle dark:text-gray-400">
Enter the admin password to manage your monitors.
</span>
</div>
<form className="auth-form" onSubmit={handleSubmit}>
<div className="auth-field">
<label htmlFor="password">Password</label>
<form className="mt-7.5 grid gap-5" onSubmit={handleSubmit}>
<div className="grid gap-2">
<label htmlFor="password" className="text-caption font-medium text-ink-label dark:text-gray-300">
Password
</label>
<Input
id="password"
type="password"
@@ -52,23 +69,27 @@ export function LoginPage() {
onChange={(event) => setPassword(event.target.value)}
minLength={8}
required
className="transition-[border-color,box-shadow] duration-150 dark:bg-night-input/85 dark:text-gray-50 dark:shadow-[inset_0_1px_2px_rgba(0,0,0,0.4)] dark:hover:border-white/24 dark:focus-visible:border-brand dark:focus-visible:shadow-[0_0_0_3px_rgba(62,207,142,0.2),inset_0_1px_2px_rgba(0,0,0,0.4)]"
/>
</div>
{loginMutation.isError && (
<p className="auth-error" role="alert">
<p
className="-mt-1 rounded-sm border border-error-border bg-error-bg px-3 py-2.5 text-micro leading-alert text-error-text dark:border-red-500/28 dark:bg-red-500/12 dark:text-red-300"
role="alert"
>
{errorMessage}
</p>
)}
<Button variant="unstyled" className="auth-submit" type="submit" disabled={loginMutation.isPending}>
<Button variant="brand" size="xl" className="w-full" type="submit" disabled={loginMutation.isPending}>
{loginMutation.isPending ? 'Signing in…' : 'Sign in'}
</Button>
</form>
</section>
</Card>
<p className="auth-footnote">Protected by an encrypted, seven-day session.</p>
<p className="relative z-1 m-0 text-footnote text-ink-footnote dark:text-faint">Protected by an encrypted, seven-day session.</p>
</main>
);
}
+71 -5
View File
@@ -2,7 +2,6 @@
@import 'tailwindcss';
@import 'tw-animate-css';
@import 'shadcn/tailwind.css';
@import './styles/LoginPage.css';
@import './styles/StatusPage.css';
@import './styles/DashboardPage.css';
@import './styles/MonitorDetailPage.css';
@@ -589,6 +588,72 @@ button {
@theme inline {
--font-sans: 'DM Sans', 'Helvetica Neue', sans-serif;
--font-mono: 'IBM Plex Mono', monospace;
--animate-enter: enter 500ms cubic-bezier(0.16, 1, 0.3, 1) both;
/* Brand emerald palette */
--color-brand: #3ecf8e;
--color-brand-deep: #24b47e;
--color-brand-hover: #36c487;
--color-brand-border: #35c586;
--color-brand-light: #4ae09e;
--color-brand-muted: #55816e;
--color-brand-dark-text: #090a0c;
/* Semantic text & border tokens (auto-adapt in dark mode via CSS variables) */
--color-ink: var(--ink);
--color-hairline: var(--hairline);
--color-faint: var(--faint);
--color-soft: var(--soft);
--color-night: var(--night);
/* Neutral text and icon shades */
--color-ink-label: #353535;
--color-ink-subtle: #626262;
--color-ink-footnote: #858585;
--color-icon-muted: #5f5f5f;
/* Dark mode surfaces */
--color-night-root: #090a0c;
--color-night-card: #111317;
--color-night-input: #0b0d0f;
/* Error alert state */
--color-error-bg: #fff6f6;
--color-error-border: #efcaca;
--color-error-text: #9f2f2f;
/* Letter spacing (tracking) */
--tracking-display: -1.5px;
--tracking-title: -1.2px;
--tracking-brand: -0.6px;
--tracking-overline: 0.08em;
/* Line height (leading) */
--leading-title: 1.16;
--leading-overline: 1.4;
--leading-alert: 1.45;
--leading-subtitle: 1.55;
/* Typography scale (matching DESIGN.md and app UI) */
--text-2xs: 10px;
--text-footnote: 11px;
--text-micro: 12px;
--text-caption: 13px;
--text-body: 14px;
--text-subtitle: 19px;
--text-brand: 20px;
--text-title-compact: 27px;
--text-title: 30px;
--text-display: 40px;
/* Custom breakpoints */
--breakpoint-compact: 440px;
/* Shadows */
--shadow-card-elevated: 0 18px 55px rgb(24 74 52 / 0.08), 0 2px 8px rgb(0 0 0 / 0.04);
--shadow-btn-primary: 0 1px 2px rgb(0 0 0 / 0.08), inset 0 1px rgb(255 255 255 / 0.2);
--drop-shadow-brand-glow: 0 0 12px rgba(62, 207, 142, 0.45);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -622,10 +687,11 @@ button {
--color-card: var(--card);
--color-foreground: var(--foreground);
--color-background: var(--background);
--radius-sm: calc(var(--radius) * 0.6);
--radius-md: calc(var(--radius) * 0.8);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) * 1.4);
--radius-xs: 4px;
--radius-sm: 6px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-2xl: calc(var(--radius) * 1.8);
--radius-3xl: calc(var(--radius) * 2.2);
--radius-4xl: calc(var(--radius) * 2.6);
-244
View File
@@ -1,244 +0,0 @@
.auth-page {
position: relative;
display: grid;
grid-template-rows: auto 1fr auto;
justify-items: center;
min-height: 100dvh;
padding: 32px 24px 24px;
background: radial-gradient(circle at 50% 38%, rgb(62 207 142 / 0.08), transparent 34%), linear-gradient(#fff, #fcfcfc);
transition:
background 0.25s ease,
color 0.25s ease;
}
.dark .auth-page {
background:
radial-gradient(ellipse 75% 55% at 50% 30%, rgba(62, 207, 142, 0.12), transparent 65%),
radial-gradient(ellipse 40% 30% at 50% 12%, rgba(36, 180, 126, 0.07), transparent 50%),
linear-gradient(180deg, #0c0e10 0%, #070809 100%);
}
.auth-theme-toggle {
position: absolute;
z-index: 1;
top: 28px;
right: 28px;
transition:
color 0.15s ease,
background 0.15s ease,
border-color 0.15s ease;
}
.dark .auth-theme-toggle {
color: #9ca3af;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 8px;
backdrop-filter: blur(8px);
}
.dark .auth-theme-toggle:hover:not(:disabled) {
color: #3ecf8e;
background: rgba(62, 207, 142, 0.1);
border-color: rgba(62, 207, 142, 0.25);
}
.auth-brand {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 20px;
font-weight: 600;
letter-spacing: -0.6px;
}
.dark .auth-brand {
color: #ffffff;
}
.auth-brand-mark {
width: 26px;
height: 26px;
color: var(--primary-deep);
}
.dark .auth-brand-mark {
color: #3ecf8e;
filter: drop-shadow(0 0 12px rgba(62, 207, 142, 0.45));
}
.auth-card {
position: relative;
z-index: 1;
align-self: center;
width: min(100%, 420px);
padding: 36px;
border: 1px solid var(--hairline);
border-radius: 12px;
background: rgb(255 255 255 / 0.96);
box-shadow:
0 18px 55px rgb(24 74 52 / 0.08),
0 2px 8px rgb(0 0 0 / 0.04);
animation: enter 500ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.dark .auth-card {
background: rgba(17, 19, 23, 0.82);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
box-shadow:
0 0 0 1px rgba(255, 255, 255, 0.05),
0 24px 64px -12px rgba(0, 0, 0, 0.8),
0 0 45px -10px rgba(62, 207, 142, 0.08),
inset 0 1px 0 0 rgba(255, 255, 255, 0.06);
}
.auth-heading p {
margin: 0 0 12px;
font:
500 10px/1.4 'IBM Plex Mono',
monospace;
letter-spacing: 0.08em;
text-transform: uppercase;
color: #55816e;
}
.dark .auth-heading p {
color: #3ecf8e;
}
.auth-heading h1 {
margin: 0;
font-size: 30px;
font-weight: 500;
line-height: 1.16;
letter-spacing: -1.2px;
}
.dark .auth-heading h1 {
color: #f9fafb;
}
.auth-heading > span {
display: block;
margin-top: 12px;
font-size: 19px;
line-height: 1.55;
color: #626262;
}
.dark .auth-heading > span {
color: #9ca3af;
}
.auth-form {
display: grid;
gap: 20px;
margin-top: 30px;
}
.auth-field {
display: grid;
gap: 8px;
}
.auth-field label {
font-size: 13px;
font-weight: 500;
color: #353535;
}
.dark .auth-field label {
color: #d1d5db;
}
.dark .auth-field input {
background-color: rgba(11, 13, 15, 0.85);
border-color: rgba(255, 255, 255, 0.12);
color: #f9fafb;
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.4);
transition:
border-color 0.15s ease,
box-shadow 0.15s ease;
}
.dark .auth-field input:hover {
border-color: rgba(255, 255, 255, 0.24);
}
.dark .auth-field input:focus-visible {
border-color: #3ecf8e;
box-shadow:
0 0 0 3px rgba(62, 207, 142, 0.2),
inset 0 1px 2px rgba(0, 0, 0, 0.4);
}
.auth-error {
margin: -4px 0 0;
padding: 10px 12px;
border: 1px solid #efcaca;
border-radius: 6px;
font-size: 12px;
line-height: 1.45;
color: #9f2f2f;
background: #fff6f6;
}
.dark .auth-error {
background: rgba(239, 68, 68, 0.12);
border-color: rgba(239, 68, 68, 0.28);
color: #fca5a5;
}
.auth-submit {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 42px;
padding: 8px 16px;
border: 1px solid #35c586;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
color: var(--ink);
background: var(--primary);
box-shadow:
0 1px 2px rgb(0 0 0 / 0.08),
inset 0 1px rgb(255 255 255 / 0.2);
cursor: pointer;
}
.auth-submit:hover:not(:disabled) {
background: #36c487;
transform: translateY(-1px);
}
.auth-submit:active:not(:disabled) {
background: var(--primary-deep);
transform: translateY(1px);
}
.auth-submit:disabled {
cursor: wait;
opacity: 0.62;
}
.dark .auth-submit {
background: #3ecf8e;
border-color: #3ecf8e;
color: #090a0c;
font-weight: 600;
box-shadow:
0 1px 2px rgba(0, 0, 0, 0.3),
0 4px 14px rgba(62, 207, 142, 0.3),
inset 0 1px 0 rgba(255, 255, 255, 0.35);
transition: all 0.15s ease;
}
.dark .auth-submit:hover:not(:disabled) {
background: #4ae09e;
border-color: #4ae09e;
box-shadow:
0 4px 20px rgba(62, 207, 142, 0.45),
inset 0 1px 0 rgba(255, 255, 255, 0.4);
transform: translateY(-1px);
}
.dark .auth-submit:active:not(:disabled) {
background: #24b47e;
transform: translateY(1px);
}
.auth-footnote {
position: relative;
z-index: 1;
margin: 0;
font-size: 11px;
color: #858585;
}
.dark .auth-footnote {
color: var(--faint);
}
@media (max-width: 440px) {
.auth-page {
padding: 24px 16px;
}
.auth-card {
padding: 28px 22px;
}
.auth-heading h1 {
font-size: 27px;
}
}
+5
View File
@@ -12,6 +12,10 @@ const buttonVariants = cva(
unstyled: null,
default:
'bg-oklch(0.205 0 0) text-oklch(0.985 0 0) hover:bg-oklch(0.205 0 0)/80 dark:bg-oklch(0.922 0 0) dark:text-oklch(0.205 0 0) dark:hover:bg-oklch(0.922 0 0)/80',
brand:
'rounded-sm border-brand-border bg-brand font-semibold text-ink shadow-btn-primary transition-all duration-150 hover:not-disabled:-translate-y-px hover:not-disabled:bg-brand-hover active:not-disabled:translate-y-px active:not-disabled:bg-brand-deep disabled:cursor-wait disabled:opacity-[0.62] dark:border-brand dark:bg-brand dark:text-brand-dark-text dark:shadow-[0_1px_2px_rgba(0,0,0,0.3),0_4px_14px_rgba(62,207,142,0.3),inset_0_1px_0_rgba(255,255,255,0.35)] dark:hover:not-disabled:-translate-y-px dark:hover:not-disabled:border-brand-light dark:hover:not-disabled:bg-brand-light dark:hover:not-disabled:shadow-[0_4px_20px_rgba(62,207,142,0.45),inset_0_1px_0_rgba(255,255,255,0.4)] dark:active:not-disabled:translate-y-px dark:active:not-disabled:bg-brand-deep',
primary:
'rounded-sm border-brand-border bg-brand font-semibold text-ink shadow-btn-primary transition-all duration-150 hover:not-disabled:-translate-y-px hover:not-disabled:bg-brand-hover active:not-disabled:translate-y-px active:not-disabled:bg-brand-deep disabled:cursor-wait disabled:opacity-[0.62] dark:border-brand dark:bg-brand dark:text-brand-dark-text dark:shadow-[0_1px_2px_rgba(0,0,0,0.3),0_4px_14px_rgba(62,207,142,0.3),inset_0_1px_0_rgba(255,255,255,0.35)] dark:hover:not-disabled:-translate-y-px dark:hover:not-disabled:border-brand-light dark:hover:not-disabled:bg-brand-light dark:hover:not-disabled:shadow-[0_4px_20px_rgba(62,207,142,0.45),inset_0_1px_0_rgba(255,255,255,0.4)] dark:active:not-disabled:translate-y-px dark:active:not-disabled:bg-brand-deep',
outline:
'border-oklch(0.922 0 0) bg-oklch(1 0 0) hover:bg-oklch(0.97 0 0) hover:text-oklch(0.145 0 0) aria-expanded:bg-oklch(0.97 0 0) aria-expanded:text-oklch(0.145 0 0) dark:border-oklch(0.922 0 0) dark:bg-oklch(0.922 0 0)/30 dark:hover:bg-oklch(0.922 0 0)/50 dark:border-oklch(1 0 0 / 10%) dark:bg-oklch(0.145 0 0) dark:hover:bg-oklch(0.269 0 0) dark:hover:text-oklch(0.985 0 0) dark:aria-expanded:bg-oklch(0.269 0 0) dark:aria-expanded:text-oklch(0.985 0 0) dark:dark:border-oklch(1 0 0 / 15%) dark:dark:bg-oklch(1 0 0 / 15%)/30 dark:dark:hover:bg-oklch(1 0 0 / 15%)/50',
secondary:
@@ -27,6 +31,7 @@ const buttonVariants = cva(
xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
xl: 'min-h-10.5 gap-2 px-4 py-2 text-body',
icon: 'size-8',
'icon-xs':
"size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",