Files
multica/packages/ui/styles/base.css
Multica Eve 700e6f3f24 fix: prevent mobile input focus zoom
Add a shared mobile/coarse-pointer CSS guard that keeps focused text-editing controls at 16px to avoid iOS Safari page zoom.
2026-04-30 12:28:22 +08:00

163 lines
5.5 KiB
CSS

/* =============================================================================
* Multica shared base styles — imported by all apps
* ============================================================================= */
/* Shiki dual themes: CSS-only light/dark switching via CSS variables */
/* @see https://shiki.style/guide/dual-themes */
.shiki,
.shiki span {
color: var(--shiki-light);
}
.dark .shiki,
.dark .shiki span {
color: var(--shiki-dark) !important;
}
/* Multica icon: entrance spin animation */
@keyframes entrance-spin {
0% { transform: rotate(0deg); opacity: 0; }
50% { opacity: 1; }
100% { transform: rotate(360deg); opacity: 1; }
}
.animate-entrance-spin {
animation: entrance-spin 0.6s ease-out forwards;
}
/* Onboarding: step / phase entry — 400ms fade + 4px rise.
* Matches the design prototype's .fade-in. Applied on mount so every new
* step (and intra-step phase switch, via key=phase remount) plays once.
* `both` fill-mode commits the `from` styles pre-animation to avoid a
* single-frame flash at natural state before the animation grabs. */
@keyframes onboarding-enter {
from { opacity: 0; transform: translateY(4px); }
}
.animate-onboarding-enter {
animation: onboarding-enter 0.4s ease both;
}
/* Onboarding completion: success badge spring-pop.
* Lands with a subtle overshoot (scale 1.12 → 1) so the circle feels
* physical rather than linearly interpolated. Paired with the drawn
* checkmark below which kicks in after the badge has settled. */
@keyframes completion-badge {
0% { transform: scale(0); opacity: 0; }
60% { transform: scale(1.12); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
.animate-completion-badge {
animation: completion-badge 500ms cubic-bezier(0.5, 1.5, 0.4, 1) both;
}
/* Onboarding completion: SVG checkmark drawn by animating
* stroke-dashoffset from 1 → 0. Requires the target <path> to declare
* `pathLength={1}` and `strokeDasharray={1}` so the stroke length is
* normalized and the animation is geometry-agnostic. */
@keyframes completion-check {
from { stroke-dashoffset: 1; }
to { stroke-dashoffset: 0; }
}
.animate-completion-check {
animation: completion-check 400ms ease-out 350ms both;
}
/* Chat FAB: gentle color + border tint while a chat task is running.
* Keeps the ring at the same thickness — only hue shifts towards brand
* at half-cycle, no outer glow. */
@keyframes chat-impulse {
0%, 100% {
color: var(--muted-foreground);
box-shadow: 0 0 0 1px color-mix(in oklab, var(--foreground) 10%, transparent);
}
50% {
color: var(--brand);
box-shadow: 0 0 0 1px color-mix(in oklab, var(--brand) 40%, transparent);
}
}
.animate-chat-impulse {
animation: chat-impulse 1.6s ease-in-out infinite;
}
/* ChatGPT-style "thinking" shimmer for inline text — a soft light sweep
* runs across the glyphs, signalling "the agent is doing something" without
* a separate spinner. Pure CSS: linear-gradient clipped to the text shape,
* the gradient slid across via background-position. Uses the same muted →
* foreground tokens chat copy normally uses, so the effect adapts to light
* and dark mode without per-mode overrides.
*
* Apply to a <span> wrapping the label only — not the whole pill, since
* the timer counter and Cancel button shouldn't shimmer. */
@keyframes chat-text-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.animate-chat-text-shimmer {
background-image: linear-gradient(
90deg,
var(--muted-foreground) 0%,
var(--muted-foreground) 35%,
var(--foreground) 50%,
var(--muted-foreground) 65%,
var(--muted-foreground) 100%
);
background-size: 200% 100%;
background-clip: text;
-webkit-background-clip: text;
color: transparent;
-webkit-text-fill-color: transparent;
animation: chat-text-shimmer 2.5s linear infinite;
}
/* Sidebar: open triggers (dropdown/popover) get active background */
[data-sidebar="menu-button"][data-popup-open] {
background-color: var(--sidebar-accent);
color: var(--sidebar-accent-foreground);
}
/* Sonner toast: align icon to first line of text, not vertically centered */
[data-sonner-toast] {
align-items: flex-start !important;
}
[data-sonner-toast] [data-icon] {
margin-top: 2.5px;
}
@layer base {
* {
@apply border-border outline-ring/50;
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track);
}
*::-webkit-scrollbar { width: 6px; height: 6px; }
*::-webkit-scrollbar-track { background: var(--scrollbar-track); }
*::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 3px; }
*::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-thumb-hover); }
body {
@apply bg-background text-foreground;
}
html {
@apply font-sans;
/* Auto-insert 1/4em space between CJK ideographs and Latin letters/numerals.
* Native CSS text-autospace (Chrome 119+, Electron recent versions).
* Progressive enhancement: browsers that don't support it simply ignore the rule. */
text-autospace: ideograph-alpha ideograph-numeric;
}
@media (max-width: 767px), (pointer: coarse) {
input:not([type="button"]):not([type="checkbox"]):not([type="color"]):not([type="file"]):not([type="hidden"]):not([type="image"]):not([type="radio"]):not([type="range"]):not([type="reset"]):not([type="submit"]),
textarea,
select,
[contenteditable]:not([contenteditable="false"]) {
/* iOS Safari zooms the page when focused editable text is below 16px. */
font-size: 16px !important;
}
}
}