Files
krow_talent_app/src/index.css
2026-08-13 21:41:14 +05:30

435 lines
16 KiB
CSS

@import url('https://fonts.googleapis.com/css2?family=Sora:wght@400;500;600;700;800&display=swap');
/* Astryx component styles, pre-compiled by the package (`@layer astryx-base`).
Only the component layer is imported: Tailwind's preflight is already the
app's reset, and Astryx's own reset would be a second, global one. The theme
package is likewise left out — Astryx components in KROW wear KROW's tokens,
which the `.krow-astryx` scope below supplies. */
@import '@astryxdesign/core/astryx.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* ── KROW ambient theme ─────────────────────────────────────────────
The named tokens the theme is specified in, kept as literal hex so the
source values are readable at a glance. Everything below them is the same
palette expressed as HSL triplets, which is the form Tailwind consumes —
so components keep using `bg-surface`, `text-ink-1`, `border-border` and
pick the theme up without a single component being edited.
Two vocabularies, one palette. Nothing in the app should reach for a raw
hex; these exist so the theme has a stated source, not so components can
bypass the semantic tokens. */
--krow-bg: #F8F8F8;
--krow-bg-blue: #E8F0F8;
--krow-bg-warm: #F8F8E8;
--krow-primary: #0838E0;
--krow-text: #101820;
--krow-text-secondary: #687386;
--krow-text-muted: #8B94A5;
--krow-border: #E0E4EA;
--krow-border-strong: #D8DDE5;
--krow-success: #16A765;
--krow-warning: #E7B900;
--krow-error: #D64545;
/* KROW Official Brand Colors */
/* Primary: KROW Blue #0838E0 */
/* Accent: KROW Yellow #F9E547 (Pantone 106CP) */
/* Pale Yellow: #F8E08E (Pantone 1205CP) */
/* Dark Navy: #333F48 (Pantone 432CP) */
/* Mint: #D1E0D7 (Pantone 621CP) */
--background: 0 0% 97%; /* #F8F8F8 — the neutral base */
--foreground: 210 33% 9%; /* #101820 */
--card: 0 0% 100%;
--card-foreground: 210 33% 9%;
--popover: 0 0% 100%;
--popover-foreground: 210 33% 9%;
--primary: 227 93% 45%; /* #0838E0 */
--primary-foreground: 0 0% 100%;
--secondary: 227 89% 96%;
--secondary-foreground: 227 89% 30%;
--muted: 210 16% 95%;
--muted-foreground: 218 13% 47%; /* #687386 */
--accent: 227 89% 94%;
--accent-foreground: 227 89% 25%;
--destructive: 0 64% 55%; /* #D64545 */
--destructive-foreground: 0 0% 100%;
--border: 216 19% 90%; /* #E0E4EA */
--input: 215 21% 89%; /* #DDE2E9 */
--ring: 227 93% 45%;
--chart-1: 227 93% 45%;
--chart-2: 52 91% 63%;
--chart-3: 210 17% 24%;
--chart-4: 42 73% 74%;
--chart-5: 150 16% 84%;
--radius: 0.75rem;
--font-heading: 'Sora', ui-sans-serif, system-ui, sans-serif;
--font-body: 'Inter', ui-sans-serif, system-ui, sans-serif;
--font-display: 'Sora', ui-sans-serif, system-ui, sans-serif;
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
/* ── Design system tokens ───────────────────────────────────────────
Everything below is consumed through Tailwind (see tailwind.config.js)
rather than referenced as raw CSS in components. */
/* Semantic status — used by StatusBadge, Alert, KpiCard trends */
--success: 153 77% 37%; /* #16A765 */
--success-foreground: 0 0% 100%;
--success-muted: 146 50% 95%; /* #EAF8F0 */
--warning: 48 100% 40%; /* #E7B900, darkened for text contrast */
--warning-foreground: 0 0% 100%;
--warning-muted: 48 90% 94%;
--info: 227 93% 45%;
--info-foreground: 0 0% 100%;
--info-muted: 227 89% 96%;
--neutral: 210 33% 9%;
--neutral-foreground: 0 0% 100%;
--neutral-muted: 216 14% 94%;
--destructive-muted: 0 70% 97%;
/* Ink scale — the text hierarchy every component uses */
--ink-1: 210 33% 9%; /* #101820 — headings */
--ink-2: 216 17% 30%; /* body, one step above secondary */
--ink-3: 218 13% 47%; /* #687386 — secondary */
--ink-4: 219 13% 60%; /* #8B94A5 — placeholder / meta */
/* Surfaces.
Cards stay opaque white by default: the ambient tint is supplied by the
canvas behind them, and making every card translucent would cost
readability for a difference nobody can name. The `.surface-translucent`
utility below exists for the few places a surface genuinely wants the
tint to read through it. */
--surface: 0 0% 100%;
--surface-subtle: 210 20% 98%;
--surface-sunken: 216 16% 96%;
/* Elevation — a 4-step scale, tinted with brand blue not neutral black */
/* Softened for the ambient canvas: with a tinted background providing the
separation, a card only needs to lift off it — not cast a shadow that
announces itself. `sm` is the reference's own `0 2px 10px / 0.04`. */
--shadow-xs: 0 1px 2px 0 rgb(16 24 32 / 0.03);
--shadow-sm: 0 2px 10px 0 rgb(16 24 32 / 0.04);
--shadow-md: 0 4px 16px -2px rgb(16 24 32 / 0.06), 0 2px 6px -3px rgb(16 24 32 / 0.04);
--shadow-lg: 0 12px 28px -6px rgb(16 24 40 / 0.12), 0 4px 10px -4px rgb(16 24 40 / 0.06);
--shadow-xl: 0 24px 56px -12px rgb(16 24 40 / 0.18), 0 8px 20px -8px rgb(16 24 40 / 0.08);
--shadow-brand: 0 10px 30px -6px rgb(10 57 223 / 0.24), 0 2px 6px -2px rgb(51 63 72 / 0.08);
--shadow-panel: 0 24px 64px -12px rgb(9 12 24 / 0.45), 0 8px 24px -8px rgb(9 12 24 / 0.30);
/* Motion — one easing vocabulary across the app */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--duration-fast: 120ms;
--duration-base: 200ms;
--duration-slow: 320ms;
/* Layout — the single container width every page shares */
--container-max: 80rem; /* 1280px — the dashboard's own measure */
--container-wide: 108rem; /* 1728px — the shell, sized to fit the 380px
assistant column beside a full-width dashboard */
--container-admin: 102rem; /* 1632px — the Admin console. Fills the viewport
up to a cap rather than leaving wide gutters. */
--page-gutter: 1.5rem;
/* Owliver assistant panel — the one dark surface in the system */
--panel: 222 47% 9%;
--panel-raised: 222 40% 14%;
--panel-border: 222 30% 22%;
--panel-foreground: 210 20% 96%;
--panel-muted: 217 15% 62%;
}
.dark {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 0% 98%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 0 0% 83.1%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground font-body;
}
/**
* The ambient canvas.
*
* Cool blue on the left, neutral through the middle, the faintest warm cream
* on the right — the atmosphere the whole product sits in. Three decisions
* worth stating:
*
* A fixed pseudo-element rather than a background on `body`. The gradient runs
* left to right, so it must be sized to the *viewport*, not to the document —
* on a long page a body background stretches and the tint stops landing where
* it was drawn. Fixed also means it paints once and never repaints on scroll.
*
* `z-index: -1` puts it behind everything without entering the layout at all,
* so no shell had to grow a wrapper to hold it.
*
* The stops are close together in lightness on purpose. The reference reads as
* "softer", not as "there is a gradient" — at this spread the eye registers
* warmth and coolness without resolving a band.
*/
body::before {
content: '';
position: fixed;
inset: 0;
z-index: -1;
pointer-events: none;
background: linear-gradient(
90deg,
var(--krow-bg-blue) 0%,
#F1F4F7 28%,
#F5F6F7 45%,
var(--krow-bg) 65%,
#F8F8F1 86%,
var(--krow-bg-warm) 100%
);
}
/* The dark palette is configured (`darkMode: 'class'`) but nothing in the app
applies the class today. This keeps the canvas from being a trap the day
something does: a fixed light gradient behind a dark theme would wash out
every page at once. */
.dark body::before {
background: hsl(var(--background));
}
}
@layer utilities {
/**
* A surface that lets the ambient canvas tint read through it.
*
* Opt-in rather than the default for cards: at 0.82 the tint is present
* without body text losing contrast, but applied everywhere it turns the page
* into glassmorphism, which is the thing the theme is explicitly not.
*/
.surface-translucent {
background-color: rgb(255 255 255 / 0.82);
}
.glass {
background: rgba(255, 255, 255, 0.72);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
}
.glass-card {
background: rgba(255, 255, 255, 0.65);
backdrop-filter: blur(20px) saturate(160%);
-webkit-backdrop-filter: blur(20px) saturate(160%);
}
.gradient-mesh {
background-image:
radial-gradient(at 15% 15%, rgba(10, 57, 223, 0.07) 0px, transparent 50%),
radial-gradient(at 85% 5%, rgba(249, 229, 71, 0.12) 0px, transparent 50%),
radial-gradient(at 75% 85%, rgba(10, 57, 223, 0.05) 0px, transparent 50%),
radial-gradient(at 10% 90%, rgba(209, 224, 215, 0.20) 0px, transparent 50%);
}
.gradient-text {
color: #0838E0;
}
.glow-blue {
box-shadow: 0 0 40px rgba(10, 57, 223, 0.15);
}
.glow-yellow {
box-shadow: 0 0 40px rgba(249, 229, 71, 0.30);
}
.no-scrollbar::-webkit-scrollbar { display: none; }
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
/* The dark Owliver panel surface. Kept as a utility so the one AI component
can never drift from the token. */
.panel-surface {
background:
linear-gradient(180deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0) 40%),
hsl(var(--panel));
backdrop-filter: blur(24px) saturate(140%);
-webkit-backdrop-filter: blur(24px) saturate(140%);
}
/* Scrollbar for dark surfaces — the default is invisible against the panel. */
.panel-scroll::-webkit-scrollbar { width: 6px; }
.panel-scroll::-webkit-scrollbar-track { background: transparent; }
.panel-scroll::-webkit-scrollbar-thumb {
background: hsl(var(--panel-border));
border-radius: 9999px;
}
.panel-scroll::-webkit-scrollbar-thumb:hover { background: hsl(var(--panel-muted)); }
/* Standard focus treatment. Applied by DS components so keyboard focus is
identical everywhere and never relies on the browser default. */
.focus-ring {
@apply outline-none ring-offset-2 ring-offset-background
focus-visible:ring-2 focus-visible:ring-[#0838E0]/60;
}
.focus-ring-inset {
@apply outline-none focus-visible:ring-2 focus-visible:ring-[#0838E0]/60;
}
/* Skeleton shimmer — a moving highlight reads as loading far better than a
pulsing block, especially on the glass surfaces. */
.shimmer {
background-image: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.65) 50%,
transparent 100%
);
background-size: 200% 100%;
animation: shimmer 1.6s var(--ease-in-out) infinite;
}
/* Text truncation past one line — Tailwind has line-clamp, this keeps the
DS API consistent with `truncate`. */
.truncate-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
/**
* Astryx components, wearing KROW.
*
* Astryx reads its own CSS custom properties, so dressing it in the KROW
* language is a matter of re-pointing those tokens at the ones the rest of the
* app already uses — not of overriding component rules. Scoped to `.krow-astryx`
* so nothing outside an Astryx island is touched, and unlayered so it wins over
* the package's `@layer astryx-base` defaults.
*
* Only the tokens Tab and TabList actually read are declared. Anything else
* keeps the package default, which is the point of restating as little as
* possible.
*/
.krow-astryx {
/* The selected indicator rule. KROW blue is the active state. */
--color-accent: #0838E0;
/* Tab labels: selected picks up brand, the rest the app's secondary ink. */
--color-text-primary: #0838E0;
--color-text-secondary: hsl(var(--ink-3));
--color-border: hsl(var(--border));
/* The hover wash sits *over* the label, so it has to stay translucent — an
opaque surface token here paints the tab's own text out. */
--color-overlay-hover: rgba(10, 57, 223, 0.06);
--color-overlay-pressed: rgba(10, 57, 223, 0.11);
--font-family-body: var(--font-body);
}
.dark .krow-astryx {
--color-accent: #5B8DEF;
--color-text-primary: #93B4FF;
--color-overlay-hover: rgba(147, 180, 255, 0.10);
--color-overlay-pressed: rgba(147, 180, 255, 0.16);
}
/**
* Give Astryx's own button styling back.
*
* Astryx ships its component CSS inside `@layer astryx-base`, while Tailwind
* emits its preflight unlayered — and unlayered always beats a layer, whatever
* the selectors. So Tailwind's `button { font-size: 100%; padding: 0; color:
* inherit }` reset silently flattened every Astryx tab: no size, no padding, no
* label colour, leaving five words run together under a stray indicator.
*
* `revert-layer` rolls each property back to the previous layer's value, which
* is Astryx's own. Nothing is re-specified here — the design system keeps
* authorship of its sizing, and this only stops the reset from reaching it.
*/
/* Same story for `hasDivider`: preflight's `*{border-width:0}` zeroed the rail
under the strip, so the tabs floated over the content with nothing separating
them from it. */
.krow-astryx .astryx-tab-list {
border-bottom-width: revert-layer;
}
.krow-astryx .astryx-tab {
font-family: revert-layer;
font-size: revert-layer;
font-weight: revert-layer;
line-height: revert-layer;
color: revert-layer;
padding-inline: revert-layer;
}
/**
* Tab strips scroll rather than wrap.
*
* A tab row that wraps to a second line stops reading as one control, and on a
* phone there is never room for five section names. The strip takes the
* overflow so the page itself never gains a horizontal scrollbar.
*/
.krow-astryx-scroll {
overflow-x: auto;
overflow-y: hidden;
scrollbar-width: none;
}
.krow-astryx-scroll::-webkit-scrollbar {
display: none;
}
.krow-astryx-scroll > nav {
flex-wrap: nowrap;
min-width: max-content;
}
@layer base {
/* Respect reduced-motion globally rather than per component. */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
}