Files
pocket-id/frontend/src/app.css
T

791 lines
21 KiB
CSS

@import 'tailwindcss';
@import 'tw-animate-css';
@import 'shadcn-svelte/tailwind.css';
@import './fonts.css';
@variant dark (&:where(.dark, .dark *));
/* The values below are the Mono color theme; applyColorTheme in color-theme-util.ts overrides the neutrals and the primary family at runtime */
:root {
--h-xs: 26px;
--h-sm: 34px;
--h-md: 42px;
--h-xl: 48px;
--row-dense: 36px;
--row: 44px;
--row-2line: 52px;
--col-auth: 464px;
--col-content: 1120px;
/* The identity column gives way on tablets so the other columns fit without scrolling sideways */
--table-id-max: clamp(240px, 36vw, 440px);
--sidebar-w: 256px;
--rail-w: 60px;
--dialog-sm: 420px;
--dialog-md: 480px;
--dialog-lg: 560px;
--sheet-w: 560px;
--palette-w: 640px;
--toast-w: 340px;
--clear-end-phone: 148px;
--z-sticky: 10;
--z-nav: 20;
--z-bar: 30;
--z-overlay: 40;
--z-popover: 50;
--z-toast: 60;
--z-tooltip: 70;
--z-progress: 80;
--halo-width: 3px;
--selection-border: 1.5px;
--dur-fast: 120ms;
--dur-base: 200ms;
--dur-slow: 280ms;
--rise: 4px;
--nudge: 2px;
--slide: 24px;
--press-scale: 0.98;
--pop-scale: 0.96;
--t-success-hold: 800ms;
--t-redirect-fallback: 3000ms;
--t-dim-delay: 200ms;
--t-skeleton-delay: 150ms;
--t-pulse: 1600ms;
--t-spin: 800ms;
--t-caret: 1000ms;
--t-progress: 3000ms;
--canvas: #ffffff;
--surface: #ffffff;
--surface-raised: #ffffff;
--surface-sunken: #f5f5f5;
--fill-muted: #f2f2f2;
--fill-hover: #eeeeee;
--fill-row-hover: #fafafa;
--fill-selected: #ebebeb;
--fill-skeleton: #eeeeee;
--border: #e8e8e8;
--border-strong: #8c8c8c;
--border-control: #8c8c8c;
--fg: #0a0a0a;
--fg-muted: #5e5e5e;
--fg-subtle: #a3a3a3;
--inverse: #111111;
--inverse-fg: #fafafa;
--inverse-muted: #b5b5b5;
--inverse-hover: #262626;
--inverse-border: transparent;
--success-solid: #319751;
--success-fg: #206b38;
--success-soft: #ebfaed;
--success-border: #bcdfc2;
--warning-solid: #c5770f;
--warning-fg: #864e18;
--warning-soft: #fff6e4;
--warning-border: #eecf9c;
--danger-solid: #d73337;
--danger-fg: #b32228;
--danger-soft: #fff1f0;
--danger-border: #f9c1bf;
--danger-fill: #c52b30;
--danger-fill-hover: #b32228;
--danger-fill-fg: #ffffff;
--info-solid: #5e5e5e;
--info-fg: #0a0a0a;
--info-soft: #f5f5f5;
--info-border: #e8e8e8;
--well-light: #f5f5f5;
--well-dark: #000000;
--well-light-fg: #5e5e5e;
--well-dark-fg: #a1a1a1;
--qr-bg: #ffffff;
--scrim: rgb(18 17 16 / 0.4);
--ring: #0a0a0a;
--ring-halo: rgb(10 10 10 / 0.24);
--elevation-popover: 0 1px 2px rgb(31 29 27 / 0.06), 0 8px 24px -6px rgb(31 29 27 / 0.14);
--elevation-overlay: 0 2px 4px rgb(31 29 27 / 0.06), 0 16px 40px -8px rgb(31 29 27 / 0.2);
--elevation-thumb: 0 1px 2px rgb(0 0 0 / 0.2);
--elevation-selected: 0 1px 2px rgb(31 29 27 / 0.06), 0 4px 14px rgb(31 29 27 / 0.07);
--border-selected: color-mix(in oklab, var(--border-strong) 40%, var(--border));
--sidebar: #fafafa;
--primary: #0a0a0a;
--primary-foreground: #ffffff;
--primary-hover: #262626;
--primary-control: #0a0a0a;
--primary-control-foreground: #ffffff;
--primary-edge: transparent;
--fw-display-l: 600;
--fw-display-m: 600;
/* shadcn-svelte variables, mapped onto the tokens above so the ui/ components keep their class names */
--background: var(--canvas);
--foreground: var(--fg);
--card: var(--surface);
--card-foreground: var(--fg);
--popover: var(--surface-raised);
--popover-foreground: var(--fg);
--secondary: var(--fill-muted);
--secondary-foreground: var(--fg);
--muted: var(--fill-muted);
--muted-foreground: var(--fg-muted);
--accent: var(--fill-hover);
--accent-foreground: var(--fg);
--destructive: var(--danger-fg);
--destructive-foreground: var(--danger-fill-fg);
--warning: var(--warning-soft);
--warning-foreground: var(--warning-fg);
--input: var(--fill-muted);
--sidebar-foreground: var(--fg);
--sidebar-primary: var(--primary);
--sidebar-primary-foreground: var(--primary-foreground);
--sidebar-accent: var(--fill-selected);
--sidebar-accent-foreground: var(--fg);
--sidebar-border: var(--border);
--sidebar-ring: var(--ring);
}
.dark {
--canvas: #000000;
--surface: #000000;
--surface-raised: #121212;
--surface-sunken: #0e0e0e;
--fill-muted: #171717;
--fill-hover: #1a1a1a;
--fill-row-hover: #0c0c0c;
--fill-selected: #1f1f1f;
--fill-skeleton: #1a1a1a;
--border: #1f1f1f;
--border-strong: #6b6b6b;
--border-control: #6b6b6b;
--fg: #ededed;
--fg-muted: #a1a1a1;
--fg-subtle: #5c5c5c;
--inverse: #1f1f1f;
--inverse-fg: #ededed;
--inverse-muted: #b5b5b5;
--inverse-hover: #2e2e2e;
--inverse-border: #333333;
--success-solid: #54b66e;
--success-fg: #83d494;
--success-soft: #1a2b1d;
--success-border: #2c5235;
--warning-solid: #eba941;
--warning-fg: #f5ca7a;
--warning-soft: #342611;
--warning-border: #68491a;
--danger-solid: #f45a56;
--danger-fg: #ff8f8d;
--danger-soft: #3b1e1e;
--danger-border: #813939;
--danger-fill: #c52b30;
--danger-fill-hover: #b32228;
--danger-fill-fg: #ffffff;
--info-solid: #a1a1a1;
--info-fg: #ededed;
--info-soft: #0e0e0e;
--info-border: #1f1f1f;
--well-light: #f5f5f5;
--well-dark: #000000;
--well-light-fg: #5e5e5e;
--well-dark-fg: #a1a1a1;
--qr-bg: #ffffff;
--scrim: rgb(0 0 0 / 0.6);
--ring: #ededed;
--ring-halo: rgb(237 237 237 / 0.24);
--elevation-popover: 0 1px 2px rgb(0 0 0 / 0.4), 0 8px 24px -6px rgb(0 0 0 / 0.6);
--elevation-overlay: 0 2px 4px rgb(0 0 0 / 0.4), 0 16px 40px -8px rgb(0 0 0 / 0.7);
--elevation-thumb: 0 1px 2px rgb(0 0 0 / 0.5);
--elevation-selected: 0 1px 2px rgb(0 0 0 / 0.4), 0 4px 14px rgb(0 0 0 / 0.5);
--sidebar: #0a0a0a;
--primary: #ededed;
--primary-foreground: #000000;
--primary-hover: #d6d6d6;
--primary-control: #ededed;
--primary-control-foreground: #000000;
--primary-edge: transparent;
--fw-display-l: 500;
--fw-display-m: 500;
color-scheme: dark;
}
/* Reduced motion: the OS setting and the Disable animations setting share one token collapse */
html.no-motion {
--dur-fast: 0ms;
--dur-base: 0ms;
--dur-slow: 0ms;
--rise: 0px;
--nudge: 0px;
--slide: 0px;
--press-scale: 1;
--pop-scale: 1;
--t-pulse: 0ms;
--t-caret: 0ms;
--t-progress: 0ms;
--t-spin: 1200ms;
}
@media (prefers-reduced-motion: reduce) {
:root {
--dur-fast: 0ms;
--dur-base: 0ms;
--dur-slow: 0ms;
--rise: 0px;
--nudge: 0px;
--slide: 0px;
--press-scale: 1;
--pop-scale: 1;
--t-pulse: 0ms;
--t-caret: 0ms;
--t-progress: 0ms;
--t-spin: 1200ms;
}
}
/* Safety net for motion that does not read the tokens (bits-ui keyframes, svelte-sonner): only spinners and scroll-linked fades keep moving */
html.no-motion *:not(.animate-spin, .scroll-fade-x, .scroll-fade-y),
html.no-motion *::before,
html.no-motion *::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
@media (prefers-reduced-motion: reduce) {
*:not(.animate-spin, .scroll-fade-x, .scroll-fade-y),
*::before,
*::after {
animation-duration: 0s !important;
transition-duration: 0s !important;
}
}
/* Set for two frames by setModeInstantly, so light and dark swap at once instead of fading control by control */
html.theme-switching *,
html.theme-switching *::before,
html.theme-switching *::after {
transition: none !important;
}
@theme {
--font-sans: 'Funnel Sans', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
--font-display: 'Funnel Display', 'Funnel Sans', ui-sans-serif, system-ui, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
--radius-xs: 6px;
--radius-sm: 10px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--radius-2xl: 24px;
--radius-3xl: 28px;
--radius-4xl: 32px;
--ease-out: cubic-bezier(0.2, 0, 0, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-in: cubic-bezier(0.4, 0, 1, 1);
}
@theme inline {
--text-display-l: 34px;
--text-display-l--line-height: 42px;
--text-display-l--font-weight: var(--fw-display-l);
--text-display-l--letter-spacing: -0.02em;
--text-display-m: 28px;
--text-display-m--line-height: 36px;
--text-display-m--font-weight: var(--fw-display-m);
--text-display-m--letter-spacing: -0.015em;
--text-display-s: 22px;
--text-display-s--line-height: 30px;
--text-display-s--font-weight: 500;
--text-display-s--letter-spacing: -0.01em;
--text-title: 18px;
--text-title--line-height: 26px;
--text-title--font-weight: 600;
--text-title--letter-spacing: 0em;
--text-heading: 15px;
--text-heading--line-height: 22px;
--text-heading--font-weight: 600;
--text-heading--letter-spacing: 0em;
--text-body-l: 17px;
--text-body-l--line-height: 26px;
--text-body-l--font-weight: 400;
--text-body-l--letter-spacing: 0em;
--text-body: 15px;
--text-body--line-height: 22px;
--text-body--font-weight: 400;
--text-body--letter-spacing: 0em;
--text-control: 14px;
--text-control--line-height: 20px;
--text-control--font-weight: 400;
--text-control--letter-spacing: 0em;
--text-caption: 13px;
--text-caption--line-height: 18px;
--text-caption--font-weight: 400;
--text-caption--letter-spacing: 0em;
--text-micro: 12px;
--text-micro--line-height: 16px;
--text-micro--font-weight: 500;
--text-micro--letter-spacing: 0.01em;
--text-mono: 14px;
--text-mono--line-height: 20px;
--text-mono--font-weight: 400;
--text-mono--letter-spacing: 0em;
--text-mono-s: 13px;
--text-mono-s--line-height: 18px;
--text-mono-s--font-weight: 400;
--text-mono-s--letter-spacing: 0em;
--text-code-cell: 30px;
--text-code-cell--line-height: 38px;
--text-code-cell--font-weight: 500;
--text-code-cell--letter-spacing: 0em;
--shadow-popover: var(--elevation-popover);
--shadow-overlay: var(--elevation-overlay);
--shadow-thumb: var(--elevation-thumb);
--shadow-selected: var(--elevation-selected);
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-input: var(--input);
--color-ring: var(--ring);
--color-ring-halo: var(--ring-halo);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
--color-scrim: var(--scrim);
--color-canvas: var(--canvas);
--color-surface: var(--surface);
--color-surface-raised: var(--surface-raised);
--color-surface-sunken: var(--surface-sunken);
--color-fill-muted: var(--fill-muted);
--color-fill-hover: var(--fill-hover);
--color-fill-row-hover: var(--fill-row-hover);
--color-fill-selected: var(--fill-selected);
--color-fill-skeleton: var(--fill-skeleton);
--color-border: var(--border);
--color-border-strong: var(--border-strong);
--color-border-selected: var(--border-selected);
--color-fg: var(--fg);
--color-fg-muted: var(--fg-muted);
--color-fg-subtle: var(--fg-subtle);
--color-inverse: var(--inverse);
--color-inverse-fg: var(--inverse-fg);
--color-inverse-muted: var(--inverse-muted);
--color-inverse-hover: var(--inverse-hover);
--color-inverse-border: var(--inverse-border);
--color-success-solid: var(--success-solid);
--color-success-soft: var(--success-soft);
--color-warning-solid: var(--warning-solid);
--color-warning-fg: var(--warning-fg);
--color-danger-solid: var(--danger-solid);
--color-danger-fg: var(--danger-fg);
--color-danger-soft: var(--danger-soft);
--color-danger-fill: var(--danger-fill);
--color-danger-fill-hover: var(--danger-fill-hover);
--color-danger-fill-fg: var(--danger-fill-fg);
--color-well-light: var(--well-light);
--color-well-dark: var(--well-dark);
--color-well-light-fg: var(--well-light-fg);
--color-well-dark-fg: var(--well-dark-fg);
--color-qr-bg: var(--qr-bg);
--color-sidebar: var(--sidebar);
--color-primary-hover: var(--primary-hover);
--color-primary-control: var(--primary-control);
--color-primary-control-foreground: var(--primary-control-foreground);
}
@layer base {
* {
@apply border-border outline-ring;
}
/* Links and anything else without its own focus style get the standard ring, which component utilities override */
:focus-visible {
outline: 2px solid var(--ring);
outline-offset: 2px;
}
html {
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-rendering: optimizeLegibility;
}
/* Names are interpolated into sentences, so a long unbroken one wraps instead of pushing the page sideways */
body {
@apply bg-canvas text-body text-fg wrap-break-word;
}
button {
@apply cursor-pointer;
}
/* Text selection stays neutral: the accent marks state, never decoration */
::selection {
background: var(--fill-selected);
color: var(--fg);
}
/* Lucide icons keep a constant stroke regardless of their rendered size */
svg.lucide {
stroke-width: 1.5px;
}
svg.lucide * {
vector-effect: non-scaling-stroke;
}
}
/* A link inside a sentence stands out by contrast and weight rather than a permanent underline, which appears only on hover */
@utility inline-link {
color: var(--fg);
font-weight: 500;
text-underline-offset: 3px;
text-decoration-line: underline;
text-decoration-thickness: 1px;
text-decoration-color: transparent;
transition: text-decoration-color var(--dur-fast) var(--ease-out);
&:hover {
text-decoration-color: color-mix(in srgb, currentColor 40%, transparent);
}
}
/* A key in a shortcut hint, in the palette footer and on its trigger */
@utility keycap {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding-inline: 4px;
border-radius: var(--radius-xs);
background: var(--fill-muted);
box-shadow: inset 0 0 0 1px var(--border);
font-family: var(--font-sans);
font-size: 11px;
line-height: 14px;
font-weight: 500;
color: var(--fg-muted);
}
/* Compositor-only motion; Tailwind 4 writes scale and translate as their own properties, so they are listed next to transform */
@utility transition-motion {
transition-property: opacity, transform, translate, scale;
transition-duration: var(--dur-fast);
transition-timing-function: var(--ease-out);
}
/* A scroller fades at the edge that has more content; without overflow its scroll timeline is inactive, so nothing fades */
@property --fade-start {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@property --fade-end {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@supports (animation-timeline: scroll()) {
.scroll-fade-x,
.scroll-fade-y {
animation:
scroll-fade-start linear both,
scroll-fade-end linear both;
animation-range:
0 24px,
calc(100% - 24px) 100%;
}
.scroll-fade-x {
mask-image: linear-gradient(
to right,
transparent,
#000 var(--fade-start),
#000 calc(100% - var(--fade-end)),
transparent
);
animation-timeline: scroll(self inline), scroll(self inline);
}
.scroll-fade-y {
mask-image: linear-gradient(
transparent,
#000 var(--fade-start),
#000 calc(100% - var(--fade-end)),
transparent
);
animation-timeline: scroll(self), scroll(self);
}
}
@keyframes scroll-fade-start {
to {
--fade-start: 24px;
}
}
@keyframes scroll-fade-end {
from {
--fade-end: 24px;
}
to {
--fade-end: 0px;
}
}
.settings-content {
view-transition-name: settings-content;
}
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
/* The phone bars swap instantly and stay above the content, whose new snapshot is otherwise painted last because only the new page has its name */
::view-transition-group(phone-top-bar),
::view-transition-group(phone-nav),
::view-transition-group(phone-search) {
z-index: 1;
animation: none;
}
::view-transition-old(settings-content-old) {
animation: 120ms ease-out both vt-fade-out;
}
::view-transition-new(settings-content) {
animation: 180ms ease-out both vt-fade-in;
}
@keyframes vt-fade-out {
to {
opacity: 0;
}
}
@keyframes vt-fade-in {
from {
opacity: 0;
}
}
@keyframes shake {
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-10px);
}
50% {
transform: translateX(10px);
}
75% {
transform: translateX(-10px);
}
}
.animate-shake {
animation: shake 0.5s ease-in-out;
}
/* Marks a moment: the bar arriving, a save settling, a blocked navigation */
@keyframes bloom {
0% {
opacity: 0;
}
30% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.animate-bloom {
animation: bloom 1.5s ease-out forwards;
}
/* The bar's inverse surface is dark in both themes, so one glow rising from under it reads as light in light and dark mode */
.bloom-pending,
.bloom-success,
.bloom-error {
background: radial-gradient(
130% 150% at 50% 130%,
color-mix(in srgb, var(--bloom-tint) 30%, transparent),
transparent 70%
);
}
.bloom-pending {
--bloom-tint: var(--warning-solid);
}
/* Waits out the bar's 380ms motion so it blooms after the width settles, and `both` keeps it hidden during that wait */
.bloom-success {
--bloom-tint: var(--success-solid);
animation-delay: 380ms;
animation-fill-mode: both;
}
.bloom-error {
--bloom-tint: var(--danger-solid);
}
/* bits-ui moves the highlight in selects and the palette with aria-activedescendant, so :focus-visible never applies there and the tint alone fails 3:1 */
/* It sits outside the layers so it beats the items' outline-hidden utility */
html[data-modality='keyboard']
:is(
[role='option'][data-highlighted],
[role^='menuitem'][data-highlighted],
[data-slot='command-item'][data-selected]
) {
outline: 2px solid var(--ring);
outline-offset: -2px;
}
/* Alt+T focuses svelte-sonner's toast list, which has no height, so the newest toast shows the ring instead */
[data-sonner-toaster]:focus-visible > [data-front='true'] {
outline: 2px solid var(--inverse-fg);
outline-offset: -2px;
}
/* Phones show every dropdown as a bottom sheet with its groups as cards */
/* These rules sit outside the layers so they beat the menu's utility classes, and !important beats bits-ui's inline floating position */
@media (width < 40rem) {
[data-bits-floating-content-wrapper]:has(> [data-slot='dropdown-menu-content']) {
position: fixed !important;
inset: auto 0 0 !important;
transform: none !important;
min-width: 0 !important;
}
[data-slot='dropdown-menu-content'] {
display: flex;
flex-direction: column;
width: 100%;
max-height: 85dvh;
padding: 0 16px calc(16px + env(safe-area-inset-bottom));
border: 0;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
background: var(--surface-raised);
backdrop-filter: none;
/* The spread shadow doubles as the scrim, so tapping it lands outside the menu and closes it */
box-shadow: 0 0 0 100vmax var(--scrim);
scale: 1;
transition-property: translate, box-shadow;
transition-duration: var(--dur-slow);
transition-timing-function: var(--ease-out);
@starting-style {
translate: 0 100%;
box-shadow: 0 0 0 100vmax transparent;
}
&[data-state='closed'] {
translate: 0 100%;
opacity: 1;
box-shadow: 0 0 0 100vmax transparent;
transition-duration: var(--dur-base);
transition-timing-function: var(--ease-in);
}
/* An item that navigates sets data-jumping before the menu closes, because the page crossfade would otherwise show the sheet sliding away under the new page */
&[data-state='closed'][data-jumping] {
transition: none;
}
&::before {
content: '';
flex-shrink: 0;
align-self: center;
width: 36px;
height: 4px;
margin: 8px 0 12px;
border-radius: 2px;
background: var(--border-strong);
}
[data-slot='dropdown-menu-group'] + [data-slot='dropdown-menu-group'] {
margin-top: 8px;
}
[data-slot='dropdown-menu-separator'] {
height: 8px;
margin: 0;
background: none;
}
[data-slot='dropdown-menu-group-heading'],
[data-slot='dropdown-menu-label'] {
padding: 8px 4px 6px;
}
/* Consecutive items form one card: each draws its sides and top line, the last one closes the card */
[role^='menuitem'] {
min-height: var(--h-xl);
padding-inline: 16px;
border-radius: 0;
background-color: var(--surface);
box-shadow:
inset 1px 0 var(--border),
inset -1px 0 var(--border),
inset 0 1px var(--border);
font-size: var(--text-body);
line-height: var(--text-body--line-height);
}
[role^='menuitem']:not([role^='menuitem'] + [role^='menuitem']) {
border-top-left-radius: var(--radius-md);
border-top-right-radius: var(--radius-md);
}
[role^='menuitem']:not(:has(+ [role^='menuitem'])) {
border-bottom-left-radius: var(--radius-md);
border-bottom-right-radius: var(--radius-md);
box-shadow:
inset 1px 0 var(--border),
inset -1px 0 var(--border),
inset 0 1px var(--border),
inset 0 -1px var(--border);
}
[role^='menuitem'][data-highlighted] {
background-color: var(--fill-hover);
}
[data-variant='destructive'][data-highlighted] {
background-color: var(--danger-soft);
}
}
}