Adds a CSS-first responsive foundation with breakpoint tokens, fluid typography/spacing via clamp(), and matchMedia-driven hooks. Portal chrome swaps to an off-canvas drawer below md; workspace (IDE) shows a friendly mobile gate below md with links to portal routes. Details in docs/ux-responsive-strategy.md. Co-Authored-By: Nakamoto, S <defi@defi-oracle.io>
133 lines
5.1 KiB
CSS
133 lines
5.1 KiB
CSS
/* ═══════════════════════════════════════════════════════════════
|
||
Design tokens — responsive foundation
|
||
Loaded BEFORE index.css so existing rules can consume these
|
||
variables. Strictly additive: no variable declared here conflicts
|
||
with an existing value in index.css.
|
||
|
||
Breakpoint policy (mobile-first, min-width):
|
||
xs: 0 – 479px (narrow phones, portrait)
|
||
sm: 480 – 767px (large phones, small tablets portrait)
|
||
md: 768 – 1023px (tablets landscape, small laptops)
|
||
lg: 1024 – 1439px (desktops, larger laptops)
|
||
xl: 1440px+ (wide desktops, ultra-wide)
|
||
|
||
We prefer container/fluid behavior; breakpoints gate only layout
|
||
switches that cannot be expressed fluidly (nav drawer, workspace
|
||
availability, table→card transform).
|
||
═══════════════════════════════════════════════════════════════ */
|
||
|
||
:root {
|
||
/* Breakpoints (CSS custom props mirror the JS hook constants) */
|
||
--bp-xs: 0px;
|
||
--bp-sm: 480px;
|
||
--bp-md: 768px;
|
||
--bp-lg: 1024px;
|
||
--bp-xl: 1440px;
|
||
|
||
/* Fluid type scale — clamp(min, preferred, max)
|
||
Preferred uses a viewport-width linear function so text grows
|
||
smoothly between xs and xl without hard breakpoint jumps.
|
||
All values are in rem so user font-size preferences are honored. */
|
||
--fs-2xs: clamp(0.625rem, 0.59rem + 0.17vw, 0.75rem); /* 10→12px */
|
||
--fs-xs: clamp(0.6875rem, 0.65rem + 0.19vw, 0.8125rem); /* 11→13px */
|
||
--fs-sm: clamp(0.75rem, 0.71rem + 0.21vw, 0.875rem); /* 12→14px */
|
||
--fs-base: clamp(0.8125rem, 0.77rem + 0.23vw, 1rem); /* 13→16px */
|
||
--fs-md: clamp(0.875rem, 0.83rem + 0.25vw, 1.125rem); /* 14→18px */
|
||
--fs-lg: clamp(1rem, 0.93rem + 0.38vw, 1.25rem); /* 16→20px */
|
||
--fs-xl: clamp(1.125rem, 1.0rem + 0.63vw, 1.5rem); /* 18→24px */
|
||
--fs-2xl: clamp(1.25rem, 1.04rem + 1.04vw, 1.875rem); /* 20→30px */
|
||
--fs-3xl: clamp(1.5rem, 1.19rem + 1.56vw, 2.25rem); /* 24→36px */
|
||
--fs-4xl: clamp(1.875rem, 1.35rem + 2.60vw, 3rem); /* 30→48px */
|
||
|
||
/* Line heights */
|
||
--lh-tight: 1.2;
|
||
--lh-snug: 1.35;
|
||
--lh-normal: 1.5;
|
||
--lh-relaxed: 1.65;
|
||
|
||
/* Fluid spacing scale (8pt grid, fluid from xs to xl) */
|
||
--space-0: 0;
|
||
--space-1: clamp(0.125rem, 0.11rem + 0.05vw, 0.1875rem); /* 2→3px */
|
||
--space-2: clamp(0.25rem, 0.22rem + 0.10vw, 0.375rem); /* 4→6px */
|
||
--space-3: clamp(0.375rem, 0.33rem + 0.17vw, 0.5rem); /* 6→8px */
|
||
--space-4: clamp(0.5rem, 0.44rem + 0.26vw, 0.75rem); /* 8→12px */
|
||
--space-5: clamp(0.75rem, 0.65rem + 0.42vw, 1rem); /* 12→16px */
|
||
--space-6: clamp(1rem, 0.87rem + 0.56vw, 1.5rem); /* 16→24px */
|
||
--space-7: clamp(1.25rem, 1.04rem + 0.83vw, 2rem); /* 20→32px */
|
||
--space-8: clamp(1.5rem, 1.22rem + 1.04vw, 2.5rem); /* 24→40px */
|
||
--space-10: clamp(2rem, 1.65rem + 1.46vw, 3.5rem); /* 32→56px */
|
||
--space-12: clamp(2.5rem, 2.00rem + 2.08vw, 4.5rem); /* 40→72px */
|
||
|
||
/* Touch target minimum (WCAG 2.5.5 AA is 44×44 CSS px) */
|
||
--tap-min: 44px;
|
||
|
||
/* Container widths */
|
||
--container-sm: 640px;
|
||
--container-md: 768px;
|
||
--container-lg: 1024px;
|
||
--container-xl: 1280px;
|
||
--container-2xl: 1536px;
|
||
|
||
/* Motion tokens */
|
||
--motion-fast: 120ms;
|
||
--motion-base: 200ms;
|
||
--motion-slow: 320ms;
|
||
--motion-ease: cubic-bezier(0.4, 0, 0.2, 1);
|
||
|
||
/* Z-index scale */
|
||
--z-base: 0;
|
||
--z-sticky: 10;
|
||
--z-drawer-backdrop: 40;
|
||
--z-drawer: 50;
|
||
--z-dropdown: 60;
|
||
--z-modal: 100;
|
||
--z-toast: 200;
|
||
--z-tooltip: 300;
|
||
--z-focus: 999;
|
||
|
||
/* Focus ring */
|
||
--focus-ring-color: #60a5fa; /* light blue, visible on dark bg */
|
||
--focus-ring-offset: 2px;
|
||
--focus-ring-width: 2px;
|
||
|
||
/* Safe area insets (iOS notch, Android gesture areas) */
|
||
--safe-top: env(safe-area-inset-top, 0px);
|
||
--safe-right: env(safe-area-inset-right, 0px);
|
||
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
||
--safe-left: env(safe-area-inset-left, 0px);
|
||
}
|
||
|
||
/* Honor OS-level reduced motion — apply across the entire app.
|
||
Anything that relies on animation for state feedback (toasts,
|
||
drawer slide, spinner) must still convey state without motion.
|
||
Spinners use opacity pulses under the same media query. */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*, *::before, *::after {
|
||
animation-duration: 0.001ms !important;
|
||
animation-iteration-count: 1 !important;
|
||
transition-duration: 0.001ms !important;
|
||
scroll-behavior: auto !important;
|
||
}
|
||
}
|
||
|
||
/* Honor OS-level contrast preference */
|
||
@media (prefers-contrast: more) {
|
||
:root {
|
||
--focus-ring-width: 3px;
|
||
--focus-ring-color: #ffffff;
|
||
}
|
||
}
|
||
|
||
/* High-DPI tuning: tighten 1px borders on >= 2x DPR so they read as
|
||
hairlines and don't visually bloom. */
|
||
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 2dppx) {
|
||
:root {
|
||
--hairline: 0.5px;
|
||
}
|
||
}
|
||
@media not all and (-webkit-min-device-pixel-ratio: 2), not all and (min-resolution: 2dppx) {
|
||
:root {
|
||
--hairline: 1px;
|
||
}
|
||
}
|