Files
t0ng7u a64f26d1d2 🎨 feat(web/default): add Anthropic theme preset and configurable serif typography
Introduce a switchable Anthropic-inspired color preset and a new Font customization axis so users can adopt the editorial serif look across the entire UI, including sidebar navigation, tabs, form controls, buttons, and table headers.

Theme preset

Add anthropic to the theme preset registry with warm cream canvas, slate foreground, and clay/coral accent tokens for light and dark modes
Define explicit surface colors for the Anthropic preset instead of relying on the semantic surface bridge
Exclude anthropic from the primary-color surface bridge so bespoke warm neutrals are not overridden by accent-tinted mixes
Typography system

Add @fontsource-variable/lora and a global --font-serif token with CJK serif fallbacks (Noto Serif SC, Source Han Serif, Songti SC, etc.)
Introduce a --font-body token and drive <body> font-family from it
Add a Font axis (default | sans | serif) parallel to radius/scale
Resolve font: 'default' against preset defaults (anthropic → serif)
Persist font preference via cookie and apply data-theme-font on <body>
Apply serif OpenType features (kern, liga, calt, tnum) and heading display tuning when serif is active
Remove per-component sans opt-outs so serif inherits through sidebar, tabs, inputs, buttons, badges, and table headers via natural CSS cascade
Keep monospace contexts unchanged via Tailwind preflight and .font-mono
UI and i18n

Add Font selector to the theme config drawer (Auto / Sans / Serif)
Add "Font" and "Select body font" translations for en, zh, fr, ja, ru, vi
Misc

Tighten group and status badge sizing for better balance with serif text
2026-05-26 04:31:13 +08:00

608 lines
14 KiB
CSS
Vendored

/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
@import 'tailwindcss';
@import 'tw-animate-css';
@import 'shadcn/tailwind.css';
@import '@fontsource-variable/public-sans';
/* Editorial serif (Lora) backing the `serif` font axis and the Anthropic
* preset's default typography. See `--font-serif` in theme.css for the
* full Latin + CJK fallback stack and `theme-presets.css` for the cascade
* that activates it. Loaded globally so font-switching is instantaneous
* with no FOUT once the variable is fetched. */
@import '@fontsource-variable/lora';
@import './theme.css';
@import './theme-presets.css';
/* Shiki dual themes: token colors follow dark theme (pre background stays `bg-background` on the block) */
@layer components {
.dark .shiki span {
color: var(--shiki-dark) !important;
font-style: var(--shiki-dark-font-style) !important;
font-weight: var(--shiki-dark-font-weight) !important;
text-decoration: var(--shiki-dark-text-decoration) !important;
}
}
@layer base {
* {
@apply border-border outline-ring/50;
scrollbar-width: thin;
scrollbar-color: var(--border) transparent;
}
html {
@apply overflow-x-hidden font-sans;
}
body {
@apply bg-background text-foreground has-[div[data-variant='inset']]:bg-sidebar min-h-svh w-full;
/* Font is driven by the theme's font axis via `--font-body`
* (defined in theme.css, swapped by `[data-theme-font='...']` blocks
* in theme-presets.css). Defaults to the project's humanist sans. */
font-family: var(--font-body);
}
/* Keep sticky headers stable while primitives lock body scrolling. */
body[data-scroll-locked] {
overflow: unset !important;
}
/* Cursor pointer for buttons */
button:not(:disabled),
[role='button']:not(:disabled) {
cursor: pointer;
}
/* Prevent focus zoom on mobile devices */
@media screen and (max-width: 767px) {
input,
select,
textarea {
font-size: 16px !important;
}
}
}
/* Vercel Geist-style skeleton shimmer */
.skeleton-shimmer {
background: linear-gradient(
90deg,
var(--skeleton-base) 0%,
var(--skeleton-base) 33%,
var(--skeleton-highlight) 50%,
var(--skeleton-base) 67%,
var(--skeleton-base) 100%
);
background-size: 300% 100%;
animation: skeleton-shimmer 1.8s ease-in-out infinite;
}
@keyframes skeleton-shimmer {
0% {
background-position: 100% 50%;
}
100% {
background-position: -100% 50%;
}
}
/* Override auto-skeleton-react inline styles with Vercel shimmer */
.auto-skeleton-fade [class^='skeleton-'] {
background: linear-gradient(
90deg,
var(--skeleton-base) 0%,
var(--skeleton-base) 33%,
var(--skeleton-highlight) 50%,
var(--skeleton-base) 67%,
var(--skeleton-base) 100%
) !important;
background-size: 300% 100% !important;
animation: skeleton-shimmer 1.8s ease-in-out infinite !important;
}
@media (prefers-reduced-motion: reduce) {
.skeleton-shimmer,
.auto-skeleton-fade [class^='skeleton-'] {
animation: none !important;
background: var(--skeleton-base) !important;
}
}
@utility container {
margin-inline: auto;
padding-inline: 2rem;
}
@utility max-w-container {
max-width: 1280px;
}
@utility max-w-container-lg {
max-width: 1536px;
}
@utility no-scrollbar {
/* Hide scrollbar for Chrome, Safari and Opera */
&::-webkit-scrollbar {
display: none;
}
/* Hide scrollbar for IE, Edge and Firefox */
-ms-overflow-style: none; /* IE and Edge */
scrollbar-width: none; /* Firefox */
}
/* Tooltip content can still scroll, but should not show a distracting axis. */
[data-slot='tooltip-content'],
[data-slot='tooltip-content'] * {
-ms-overflow-style: none;
scrollbar-width: none;
}
[data-slot='tooltip-content']::-webkit-scrollbar,
[data-slot='tooltip-content'] *::-webkit-scrollbar {
display: none;
}
@utility hover-scrollbar {
/* Hide scrollbar by default */
scrollbar-width: thin;
scrollbar-color: transparent transparent;
/* Show scrollbar on hover */
&:hover {
scrollbar-color: var(--border) transparent;
}
/* Webkit browsers (Chrome, Safari, Edge) */
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: transparent;
border-radius: 4px;
}
&:hover::-webkit-scrollbar-thumb {
background-color: var(--border);
}
&:hover::-webkit-scrollbar-thumb:hover {
background-color: color-mix(in oklch, var(--border) 60%, transparent);
}
}
@utility faded-bottom {
@apply after:pointer-events-none after:absolute after:start-0 after:bottom-0 after:hidden after:h-32 after:w-full after:rounded-b-2xl after:bg-[linear-gradient(180deg,_transparent_10%,_var(--background)_70%)] md:after:block;
}
.CollapsibleContent {
overflow: hidden;
}
@media (prefers-reduced-motion: no-preference) {
.CollapsibleContent[data-open] {
animation: slideDown 300ms ease-out;
}
.CollapsibleContent:not([data-open]) {
animation: slideUp 300ms ease-out;
}
}
@keyframes slideDown {
from {
height: 0;
}
to {
height: var(--collapsible-panel-height);
}
}
@keyframes slideUp {
from {
height: var(--collapsible-panel-height);
}
to {
height: 0;
}
}
/* Launch UI Animations and Effects - Matching Template Exactly */
@layer utilities {
/* Gradient utilities */
.bg-radial {
background-image: radial-gradient(var(--tw-gradient-stops));
}
/* Glass morphism effects - matching Launch UI template */
.glass-1 {
@apply border-border from-card/80 to-card/40 dark:border-border/10 dark:border-b-border/5 dark:border-t-border/20 dark:from-card/5 dark:to-card/0 border bg-linear-to-b;
}
.glass-2 {
@apply border-border from-card/100 to-card/80 dark:border-border/10 dark:border-b-border/5 dark:border-t-border/20 dark:from-card/10 dark:to-card/5 border bg-linear-to-b;
}
.glass-3 {
@apply border-border from-card/30 to-card/20 dark:border-border/10 dark:border-t-border/20 dark:border-b-border/5 dark:from-primary/5 dark:to-primary/2 border bg-linear-to-b;
}
.glass-4 {
@apply border-border border-b-input/90 from-card/60 to-card/20 dark:border-border/10 dark:border-t-border/30 dark:from-primary/10 dark:to-primary/5 border bg-linear-to-b dark:border-b-0;
}
.glass-5 {
@apply border-border border-b-input from-card/100 to-card/20 dark:border-border/10 dark:border-t-border/30 dark:from-primary/15 dark:to-primary/5 border bg-linear-to-b dark:border-b-0;
}
/* Fade effects - matching Launch UI template */
.fade-x {
mask-image: linear-gradient(
to right,
transparent 0%,
black 25%,
black 75%,
transparent 100%
);
}
.fade-y {
mask-image: linear-gradient(
to top,
transparent 0%,
black 25%,
black 75%,
transparent 100%
);
}
.fade-top {
mask-image: linear-gradient(to bottom, transparent 0%, black 35%);
}
.fade-bottom {
mask-image: linear-gradient(to top, transparent 0%, black 35%);
}
.fade-top-lg {
mask-image: linear-gradient(to bottom, transparent 15%, black 100%);
}
.fade-bottom-lg {
mask-image: linear-gradient(to top, transparent 15%, black 100%);
}
.fade-left {
mask-image: linear-gradient(to right, transparent 0%, black 35%);
}
.fade-right {
mask-image: linear-gradient(to left, transparent 0%, black 35%);
}
.fade-left-lg {
mask-image: linear-gradient(to right, transparent 15%, black 100%);
}
.fade-right-lg {
mask-image: linear-gradient(to left, transparent 15%, black 100%);
}
/* Animations */
.animate-appear {
animation: appear 0.6s forwards ease-out;
}
.animate-appear-zoom {
animation: appear-zoom 0.6s forwards ease-out;
}
.animation-delay-100 {
animation-delay: 100ms;
}
.animation-delay-300 {
animation-delay: 300ms;
}
.animation-delay-700 {
animation-delay: 700ms;
}
.animation-delay-1000 {
animation-delay: 1000ms;
}
@keyframes appear {
0% {
opacity: 0;
transform: translateY(1rem);
filter: blur(0.5rem);
}
50% {
filter: blur(0);
}
100% {
opacity: 1;
transform: translateY(0);
filter: blur(0);
}
}
@keyframes appear-zoom {
0% {
opacity: 0;
transform: scale(0.5);
}
100% {
opacity: 1;
transform: scale(1);
}
}
/* Vertical scroll animation for icon lists */
@keyframes scroll-up {
0% {
transform: translateY(0);
}
100% {
transform: translateY(-50%);
}
}
.animate-scroll-up {
animation: scroll-up 20s linear infinite;
will-change: transform;
}
.animate-scroll-down {
animation: scroll-up 20s linear infinite reverse;
will-change: transform;
}
/* Pause animation on hover */
.scroll-container:hover .animate-scroll-up,
.scroll-container:hover .animate-scroll-down {
animation-play-state: paused;
}
}
@media (prefers-reduced-motion: reduce) {
.animate-appear,
.animate-appear-zoom,
.animate-scroll-up,
.animate-scroll-down,
[data-slot='table'] tbody tr {
animation: none !important;
opacity: 1;
transform: none;
}
}
/* ── Landing page scroll-triggered animations ── */
@keyframes landing-fade-up {
from {
opacity: 0;
transform: translateY(24px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes landing-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes landing-scale-in {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes landing-fade-left {
from {
opacity: 0;
transform: translateX(24px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes landing-fade-right {
from {
opacity: 0;
transform: translateX(-24px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.landing-animate-fade-up {
animation: landing-fade-up 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.landing-animate-fade-in {
animation: landing-fade-in 0.6s ease-out both;
}
.landing-animate-scale-in {
animation: landing-scale-in 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.landing-animate-fade-left {
animation: landing-fade-left 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}
.landing-animate-fade-right {
animation: landing-fade-right 0.6s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@media (prefers-reduced-motion: reduce) {
.landing-animate-fade-up,
.landing-animate-fade-in,
.landing-animate-scale-in,
.landing-animate-fade-left,
.landing-animate-fade-right {
animation: none !important;
opacity: 1 !important;
transform: none !important;
}
}
/* Micro-interactions — Vercel-style subtle hover/active feedback */
@media (prefers-reduced-motion: no-preference) {
[data-slot='card'] {
transition:
transform 150ms ease,
box-shadow 150ms ease;
}
@media (min-width: 641px) {
[data-slot='card']:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgb(0 0 0 / 0.06);
}
.dark [data-slot='card']:hover {
box-shadow: 0 4px 12px rgb(0 0 0 / 0.3);
}
}
button:not(:disabled):active,
[role='button']:not(:disabled):active {
transform: scale(0.98);
}
[data-slot='table'] tr {
transition: background-color 120ms ease;
}
/* Table row stagger-in animation */
[data-slot='table'] tbody tr {
animation: tableRowEnter 0.2s cubic-bezier(0.33, 1, 0.68, 1) both;
}
[data-slot='table'] tbody tr:nth-child(2) {
animation-delay: 25ms;
}
[data-slot='table'] tbody tr:nth-child(3) {
animation-delay: 50ms;
}
[data-slot='table'] tbody tr:nth-child(4) {
animation-delay: 75ms;
}
[data-slot='table'] tbody tr:nth-child(5) {
animation-delay: 100ms;
}
[data-slot='table'] tbody tr:nth-child(6) {
animation-delay: 125ms;
}
[data-slot='table'] tbody tr:nth-child(7) {
animation-delay: 150ms;
}
[data-slot='table'] tbody tr:nth-child(8) {
animation-delay: 175ms;
}
[data-slot='table'] tbody tr:nth-child(9) {
animation-delay: 200ms;
}
[data-slot='table'] tbody tr:nth-child(10) {
animation-delay: 225ms;
}
[data-slot='table'] tbody tr:nth-child(n + 11) {
animation-delay: 250ms;
}
}
@keyframes tableRowEnter {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Tabular numbers for data-heavy contexts */
[data-slot='table'] td,
.tabular-nums {
font-variant-numeric: tabular-nums;
}
/* Long list rendering optimization */
.content-auto {
content-visibility: auto;
contain-intrinsic-size: 0 80px;
}
/* ── Hero terminal demo animations ── */
@keyframes terminal-demo-blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
.terminal-demo-blink {
animation: terminal-demo-blink 1s step-end infinite;
}
@keyframes terminal-demo-spin {
to {
transform: rotate(360deg);
}
}
.terminal-demo-spin {
animation: terminal-demo-spin 0.8s linear infinite;
}
@keyframes terminal-demo-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.3;
}
}
.terminal-demo-pulse {
animation: terminal-demo-pulse 1.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.terminal-demo-blink,
.terminal-demo-spin,
.terminal-demo-pulse {
animation: none !important;
}
}