- Logo 渐变从粉紫改为橙/粉/蓝蜡笔色,快门圆点改橙色 - 批量替换 accent:violet→orange、fuchsia→rose、indigo→sky、purple→orange, 十六进制与 rgb 字面量同步换算为 Tailwind 对应色 - 修复类名合并冲突:管理员筛选 pill 改 fp-orange、会员号改 fp-pink(新增), style.css 中去重 fp-rose/fp-orange 并补 fp-pink 的浅色模式变体 - 选区色、标签输入框、筛选 pill、图表、滑块等所有紫色残留全部归位 Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
695 lines
33 KiB
CSS
695 lines
33 KiB
CSS
@import "tailwindcss";
|
|
|
|
/* Edge: suppress the hover "visual search / 视觉搜索" overlay icon on images and
|
|
videos (legacy-Edge -ms-touch-action trick). Doesn't affect clicks. */
|
|
img,
|
|
video {
|
|
-ms-touch-action: none;
|
|
}
|
|
|
|
:root {
|
|
font-family: "Inter", ui-sans-serif, system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
|
|
--tw-ring-color: rgb(17 17 17 / 0.12);
|
|
|
|
/* ===== Theme palette — LIGHT (default): Memphis / neo-brutalism =====
|
|
Drives the app chrome (rails/header) and the public marketing pages.
|
|
The `.dark` class on <html> flips these to the dark values below.
|
|
Text over generated images keeps a literal text-white (handled in markup). */
|
|
--app-bg: #f8f6f2; /* warm paper */
|
|
--fg: #1a1a1a; /* primary heading/text */
|
|
--fg-2: rgb(26 26 26 / 0.64); /* body / secondary */
|
|
--fg-3: rgb(26 26 26 / 0.48); /* muted */
|
|
--fg-faint: rgb(26 26 26 / 0.34); /* labels / hints */
|
|
--surface: #ffffff; /* raised card on page bg */
|
|
--surface-2: rgb(26 26 26 / 0.045); /* subtle inset panel (counter cells) */
|
|
--hairline: rgb(26 26 26 / 0.12); /* faint borders / separators */
|
|
--hover: rgb(254 240 138 / 0.55); /* hover wash — memphis yellow tint */
|
|
/* ink = the heavy outline color; shadow-ink = the offset "chunky" shadow */
|
|
--ink: #111111;
|
|
--ink-soft: rgb(17 17 17 / 0.45);
|
|
--shadow-ink: #111111;
|
|
/* memphis accent crayons */
|
|
--m-orange: #ec5b13;
|
|
--m-yellow: #fef08a;
|
|
--m-pink: #ffc1cc;
|
|
--m-blue: #bae6fd;
|
|
--m-mint: #a7f3d0;
|
|
/* solid CTA button that sits on the page bg — inverts per theme */
|
|
--btn-solid-bg: #1a1a1a;
|
|
--btn-solid-bg-h: #000000;
|
|
--btn-solid-fg: #fff;
|
|
--menu-bg: #ffffff; /* dropdown / popover panel */
|
|
}
|
|
|
|
html.dark {
|
|
--app-bg: #131318;
|
|
--fg: rgb(255 255 255 / 0.92);
|
|
--fg-2: rgb(255 255 255 / 0.55);
|
|
--fg-3: rgb(255 255 255 / 0.4);
|
|
--fg-faint: rgb(255 255 255 / 0.3);
|
|
--surface: rgb(255 255 255 / 0.05);
|
|
--surface-2: rgb(255 255 255 / 0.05);
|
|
--hairline: rgb(255 255 255 / 0.1);
|
|
--hover: rgb(236 91 19 / 0.2);
|
|
--ink: rgb(244 244 245 / 0.85);
|
|
--ink-soft: rgb(244 244 245 / 0.4);
|
|
--shadow-ink: rgb(244 244 245 / 0.22);
|
|
--btn-solid-bg: #ec5b13;
|
|
--btn-solid-bg-h: #f97316;
|
|
--btn-solid-fg: #fff;
|
|
--menu-bg: #1d1e26;
|
|
}
|
|
|
|
/* Smooth the flip so toggling doesn't hard-cut. */
|
|
body, .theme-x { transition: background-color 0.25s ease, color 0.25s ease; }
|
|
|
|
/* Footer CTA band — flat memphis-yellow panel with a heavy ink outline. */
|
|
.cta-band {
|
|
background:
|
|
radial-gradient(circle, rgb(17 17 17 / 0.1) 1.3px, transparent 1.3px) 0 0 / 20px 20px,
|
|
var(--m-yellow);
|
|
border: 3px solid var(--ink);
|
|
box-shadow: 10px 10px 0 var(--shadow-ink);
|
|
}
|
|
html.dark .cta-band {
|
|
background:
|
|
radial-gradient(circle, rgb(255 255 255 / 0.1) 1.3px, transparent 1.3px) 0 0 / 20px 20px,
|
|
rgb(236 91 19 / 0.16);
|
|
}
|
|
|
|
/* ===== Light-mode rescue for hardcoded-dark pages =====
|
|
The admin shell + settings/workbench views were authored dark with literal
|
|
text-white / white-alpha / bg-white utilities (not the slate system). In
|
|
LIGHT mode, remap those neutral whites to the dark-on-light palette. Scoped
|
|
to a `.theme-text` wrapper so the marketing homepage's over-image whites are
|
|
never touched. Un-layered, so these win over Tailwind's @layer utilities. */
|
|
html:not(.dark) .theme-text :is(.text-white, [class*="text-white/9"], [class*="text-white/8"]) { color: rgb(15 23 42 / 0.95); }
|
|
html:not(.dark) .theme-text :is([class*="text-white/7"], [class*="text-white/6"]) { color: rgb(15 23 42 / 0.7); }
|
|
html:not(.dark) .theme-text :is([class*="text-white/5"], [class*="text-white/4"]) { color: rgb(15 23 42 / 0.52); }
|
|
html:not(.dark) .theme-text :is([class*="text-white/3"], [class*="text-white/2"], [class*="text-white/1"]) { color: rgb(15 23 42 / 0.4); }
|
|
html:not(.dark) .theme-text [class*="bg-white/"] { background-color: var(--surface-2); }
|
|
html:not(.dark) .theme-text [class*="border-white/"] { border-color: var(--hairline); }
|
|
html:not(.dark) .theme-text [class*="ring-white/"] { --tw-ring-color: var(--hairline); }
|
|
/* White placeholders (placeholder:text-white/30 etc.) — the rescue above only
|
|
recolors element text, not the ::placeholder pseudo. */
|
|
html:not(.dark) .theme-text [class*="placeholder:text-white"]::placeholder { color: var(--fg-faint); }
|
|
/* Exception: "selected" pills / dark buttons keep their white text. They paint a
|
|
genuinely dark surface (bg-slate-900/800, bg-black) so the rescue must NOT
|
|
flip their text to dark — that's where it goes invisible. */
|
|
html:not(.dark) .theme-text :is([class*="bg-slate-900"], [class*="bg-slate-800"], [class*="bg-black"])[class*="text-white"] { color: #fff !important; }
|
|
/* Pastel -200/-300 accent text (labels, KPI numbers, badges) is tuned for a dark
|
|
shell and washes out on white. In LIGHT mode, darken to the -600 tone. The
|
|
matcher also catches the /80 opacity variants. Badges keep their pale tinted
|
|
bg (bg-*-500/10) which reads fine on white. */
|
|
html:not(.dark) .theme-text :is([class*="text-sky-300"], [class*="text-sky-200"]) { color: rgb(79 70 229); }
|
|
html:not(.dark) .theme-text :is([class*="text-orange-300"], [class*="text-orange-200"]) { color: rgb(234 88 12); }
|
|
html:not(.dark) .theme-text :is([class*="text-rose-300"], [class*="text-rose-200"]) { color: rgb(225 29 72); }
|
|
html:not(.dark) .theme-text :is([class*="text-emerald-300"], [class*="text-emerald-200"]) { color: rgb(5 150 105); }
|
|
html:not(.dark) .theme-text :is([class*="text-sky-300"], [class*="text-sky-200"]) { color: rgb(2 132 199); }
|
|
html:not(.dark) .theme-text :is([class*="text-amber-300"], [class*="text-amber-200"]) { color: rgb(217 119 6); }
|
|
html:not(.dark) .theme-text :is([class*="text-rose-300"], [class*="text-rose-200"]) { color: rgb(225 29 72); }
|
|
html:not(.dark) .theme-text :is([class*="text-teal-300"], [class*="text-teal-200"]) { color: rgb(13 148 136); }
|
|
|
|
/* Per-view scoped pills / buttons / chips hardcode white for the dark shell.
|
|
Recolor them centrally in light mode — the extra .theme-text class outranks
|
|
the scoped `.x[data-v-*]` rules. Dark mode keeps the scoped look (gated on
|
|
:not(.dark)). */
|
|
html:not(.dark) .theme-text :is(.fp, .pg, .act, .filter-pill, .kind-btn, .preset-btn, .opt, .chip-x, .ro-chip) {
|
|
color: var(--fg-2); background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--hairline);
|
|
}
|
|
html:not(.dark) .theme-text :is(.fp, .pg, .act, .filter-pill, .kind-btn, .preset-btn, .opt, .chip-x):hover {
|
|
color: var(--fg); background: var(--hover);
|
|
}
|
|
html:not(.dark) .theme-text :is(.fp-on, .pg-on, .seg-on, .opt-on) { background: var(--fg); color: #fff; box-shadow: 2px 2px 0 var(--m-orange); }
|
|
/* Tag chips (TagInput) — near-white orange text is unreadable on white; keep
|
|
the orange tint but switch the label to dark ink. */
|
|
html:not(.dark) .theme-text .chip { color: rgb(15 23 42 / 0.9); }
|
|
html:not(.dark) .theme-text .chip .chip-x { color: rgb(15 23 42 / 0.55); background: transparent; box-shadow: none; }
|
|
html:not(.dark) .theme-text .chip .chip-x:hover { color: #fff; background: rgb(244 63 94 / 0.6); }
|
|
/* ShowcaseView marks the selected filter/kind tab with an `.on` modifier on
|
|
`.filter-pill`/`.kind-btn` (not the `-on` class above). Without this, the
|
|
neutral `.filter-pill`/`.kind-btn` rescue paints selected == unselected and
|
|
the highlight vanishes on a white page. Equal-or-higher specificity, later. */
|
|
html:not(.dark) .theme-text :is(.filter-pill, .kind-btn).on { background: var(--fg); color: #fff; box-shadow: 2px 2px 0 var(--m-orange); }
|
|
/* Selected COLORED filter pills — their scoped (dark) selected colors get
|
|
overridden by the neutral `.fp` rescue above, so re-state a light-mode
|
|
variant (tinted bg + dark-enough text + colored ring) with equal specificity
|
|
placed later, so the selection reads on a white page. */
|
|
html:not(.dark) .theme-text .fp-emerald { background: rgb(16 185 129 / 0.14); color: rgb(4 120 87); box-shadow: inset 0 0 0 1px rgb(16 185 129 / 0.45); }
|
|
html:not(.dark) .theme-text .fp-rose { background: rgb(244 63 94 / 0.12); color: rgb(190 18 60); box-shadow: inset 0 0 0 1px rgb(244 63 94 / 0.45); }
|
|
html:not(.dark) .theme-text .fp-amber { background: rgb(245 158 11 / 0.16); color: rgb(180 83 9); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.5); }
|
|
html:not(.dark) .theme-text .fp-orange { background: rgb(249 115 22 / 0.14); color: rgb(194 65 12); box-shadow: inset 0 0 0 1px rgb(249 115 22 / 0.45); }
|
|
html:not(.dark) .theme-text .fp-pink { background: rgb(244 114 182 / 0.14); color: rgb(190 24 93); box-shadow: inset 0 0 0 1px rgb(244 114 182 / 0.45); }
|
|
html:not(.dark) .theme-text .fp-slate { background: rgb(100 116 139 / 0.14); color: rgb(51 65 85); box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.45); }
|
|
html:not(.dark) .theme-text .fp-sky { background: rgb(14 165 233 / 0.14); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.45); }
|
|
html:not(.dark) .theme-text .fp-teal { background: rgb(20 184 166 / 0.14); color: rgb(15 118 110); box-shadow: inset 0 0 0 1px rgb(20 184 166 / 0.45); }
|
|
|
|
/* Custom checkbox for admin tables (<input type="checkbox" class="chk">).
|
|
Replaces the plain accent-color box with a rounded, branded one + white check.
|
|
Neutral border + transparent bg so it reads on both the dark admin shell and
|
|
light mode; fills rose when checked. */
|
|
.chk {
|
|
appearance: none;
|
|
-webkit-appearance: none;
|
|
width: 1rem;
|
|
height: 1rem;
|
|
flex: none;
|
|
border-radius: 0.3rem;
|
|
border: 1.5px solid rgb(148 163 184 / 0.55);
|
|
background: transparent;
|
|
cursor: pointer;
|
|
display: inline-grid;
|
|
place-content: center;
|
|
vertical-align: middle;
|
|
transition: background 0.15s, border-color 0.15s;
|
|
}
|
|
.chk { border-color: var(--ink-soft); }
|
|
.chk:hover:not(:checked) { border-color: var(--m-orange); }
|
|
.chk:checked {
|
|
background: var(--m-orange);
|
|
border-color: var(--ink);
|
|
box-shadow: 2px 2px 0 var(--shadow-ink);
|
|
}
|
|
.chk:checked::after {
|
|
content: "";
|
|
width: 0.3rem;
|
|
height: 0.55rem;
|
|
border: solid #fff;
|
|
border-width: 0 2px 2px 0;
|
|
transform: rotate(45deg);
|
|
margin-top: -0.12rem;
|
|
}
|
|
.chk:focus-visible { outline: 2px solid rgb(236 91 19 / 0.5); outline-offset: 1px; }
|
|
/* Account identity tags (AccountsView: 子号 / 母号 / 普号 / 限额). The colors live
|
|
here rather than as inline dark-palette utilities, which were invisible on the
|
|
light theme: light values first, html.dark override after. */
|
|
.acct-tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex-shrink: 0;
|
|
padding: 0.1rem 0.35rem;
|
|
border-radius: 0.25rem;
|
|
font-size: 10px;
|
|
line-height: 1.5;
|
|
font-weight: 500;
|
|
white-space: nowrap;
|
|
}
|
|
.acct-tag-sub { background: rgb(14 165 233 / 0.14); color: rgb(3 105 161); box-shadow: inset 0 0 0 1px rgb(14 165 233 / 0.4); }
|
|
.acct-tag-master { background: rgb(249 115 22 / 0.14); color: rgb(194 65 12); box-shadow: inset 0 0 0 1px rgb(249 115 22 / 0.4); }
|
|
.acct-tag-free { background: rgb(100 116 139 / 0.14); color: rgb(51 65 85); box-shadow: inset 0 0 0 1px rgb(100 116 139 / 0.4); }
|
|
.acct-tag-limit { background: rgb(245 158 11 / 0.16); color: rgb(180 83 9); box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.4); }
|
|
html.dark .acct-tag-sub { background: rgb(14 165 233 / 0.18); color: rgb(125 211 252); box-shadow: inset 0 0 0 1px rgb(56 189 248 / 0.35); }
|
|
html.dark .acct-tag-master { background: rgb(249 115 22 / 0.22); color: rgb(253 186 116); box-shadow: inset 0 0 0 1px rgb(253 186 116 / 0.35); }
|
|
html.dark .acct-tag-free { background: rgb(148 163 184 / 0.22); color: rgb(226 232 240); box-shadow: inset 0 0 0 1px rgb(148 163 184 / 0.35); }
|
|
html.dark .acct-tag-limit { background: rgb(245 158 11 / 0.18); color: rgb(252 211 77); box-shadow: inset 0 0 0 1px rgb(251 191 36 / 0.35); }
|
|
/* Capability chips (ModelsView): all variants need bg + ring + color reset in light mode.
|
|
The scoped dark-shell values (white-alpha bg/ring, pale text) are invisible on white. */
|
|
html:not(.dark) .theme-text .cap-emerald {
|
|
background: rgb(16 185 129 / 0.12);
|
|
color: rgb(4 120 87);
|
|
box-shadow: inset 0 0 0 1px rgb(16 185 129 / 0.4);
|
|
}
|
|
html:not(.dark) .theme-text .cap-amber {
|
|
background: rgb(245 158 11 / 0.12);
|
|
color: rgb(180 83 9);
|
|
box-shadow: inset 0 0 0 1px rgb(245 158 11 / 0.45);
|
|
}
|
|
html:not(.dark) .theme-text :is(.cap-slate, .cap-mono, .seg) {
|
|
color: var(--fg-2);
|
|
background: var(--surface-2);
|
|
box-shadow: inset 0 0 0 1px var(--hairline);
|
|
}
|
|
/* Price chips (ModelsView): the white-alpha bg/ring disappear on a white card. */
|
|
html:not(.dark) .theme-text .price-chip {
|
|
background: var(--surface-2);
|
|
box-shadow: inset 0 0 0 1px var(--hairline);
|
|
}
|
|
/* fp-white selected pill: on white bg the all-white fill vanishes — use a neutral dark variant. */
|
|
html:not(.dark) .theme-text .fp-white {
|
|
background: var(--surface-2);
|
|
color: var(--fg-2);
|
|
box-shadow: inset 0 0 0 1px var(--hairline);
|
|
}
|
|
/* label / hint text classes (form labels in modals & config) */
|
|
html:not(.dark) .theme-text .lbl { color: var(--fg); }
|
|
html:not(.dark) .theme-text :is(.hint, .flbl) { color: var(--fg-3); }
|
|
/* Delete icon button: a clean neutral button with a red glyph (not a heavy pink
|
|
box), filling solid red on hover. */
|
|
html:not(.dark) .theme-text .act.danger { color: rgb(225 29 72); background: var(--surface-2); box-shadow: inset 0 0 0 1px var(--hairline); }
|
|
html:not(.dark) .theme-text .act.danger:hover { color: #fff; background: rgb(244 63 94); box-shadow: inset 0 0 0 1px rgb(225 29 72); }
|
|
/* Soft "danger" toolbar buttons (删除选中 / 删除异常账号 …): a muted red, not a
|
|
heavy pink fill; solid red on hover. Outranks the per-view scoped pink. */
|
|
html:not(.dark) .theme-text .btn-soft.danger { color: rgb(190 18 60); background: rgb(244 63 94 / 0.08); box-shadow: inset 0 0 0 1px rgb(244 63 94 / 0.22); }
|
|
html:not(.dark) .theme-text .btn-soft.danger:hover { color: #fff; background: rgb(225 29 72); box-shadow: none; }
|
|
|
|
/* ===== Media card =====
|
|
An image/video thumbnail whose overlay text sits ON the media — it must stay
|
|
white even when the surrounding page is in LIGHT mode. The page itself stays
|
|
light; only these cards keep the dark-on-image treatment. */
|
|
html:not(.dark) .media-card :is(.text-white, [class*="text-white/9"], [class*="text-white/8"], [class*="text-white/7"], [class*="text-white/6"]) { color: rgb(255 255 255 / 0.95) !important; }
|
|
html:not(.dark) .media-card :is([class*="text-white/5"], [class*="text-white/4"]) { color: rgb(255 255 255 / 0.6) !important; }
|
|
html:not(.dark) .media-card :is([class*="text-white/3"], [class*="text-white/2"]) { color: rgb(255 255 255 / 0.45) !important; }
|
|
html:not(.dark) .media-card [class*="bg-white/"] { background-color: rgb(255 255 255 / 0.12) !important; }
|
|
html:not(.dark) .media-card :is([class*="border-white/"], [class*="ring-white/"]) { border-color: rgb(255 255 255 / 0.12) !important; --tw-ring-color: rgb(255 255 255 / 0.12) !important; }
|
|
/* Accent text inside a media-card sits on dark imagery too — keep it at its
|
|
bright (dark-mode) shade instead of the darkened light-theme rescue above. */
|
|
html:not(.dark) .media-card :is([class*="text-emerald-3"], [class*="text-emerald-2"]) { color: rgb(110 231 183) !important; }
|
|
html:not(.dark) .media-card :is([class*="text-rose-3"], [class*="text-rose-2"]) { color: rgb(251 207 232) !important; }
|
|
html:not(.dark) .media-card :is([class*="text-sky-3"], [class*="text-sky-2"]) { color: rgb(147 197 253) !important; }
|
|
html:not(.dark) .media-card :is([class*="text-orange-3"], [class*="text-orange-2"]) { color: rgb(253 186 116) !important; }
|
|
html:not(.dark) .media-card :is([class*="text-sky-3"], [class*="text-sky-2"]) { color: rgb(125 211 252) !important; }
|
|
html:not(.dark) .media-card :is([class*="text-amber-3"], [class*="text-amber-2"]) { color: rgb(252 211 77) !important; }
|
|
|
|
/* Code example blocks (docs) read like an editor — solid dark surface in BOTH
|
|
themes. Paired with .media-card so the code/title text stays light in light
|
|
mode instead of being darkened by the rescue above. */
|
|
.doc-code { background-color: #0f172a; }
|
|
/* `.public-dark code` gives the inner <code> its faint highlight in dark mode;
|
|
doc-code is dark in BOTH themes, so re-apply the same in light mode. */
|
|
html:not(.dark) .doc-code code { background: rgb(255 255 255 / 0.08); color: rgb(255 255 255 / 0.9); }
|
|
|
|
html {
|
|
scroll-behavior: smooth;
|
|
/* Kill the bounce/glow when scrolling past the top or bottom of any
|
|
scroll surface (the dark admin shell has fixed viewport height and we
|
|
don't want a rubber-band-y feel at the edges of inner scroll areas). */
|
|
overscroll-behavior: none;
|
|
}
|
|
|
|
body {
|
|
margin: 0;
|
|
-webkit-font-smoothing: antialiased;
|
|
text-rendering: optimizeLegibility;
|
|
overscroll-behavior: none;
|
|
/* Subtle ambient gradient + grain (used by PublicLayout via .ambient-bg) */
|
|
}
|
|
|
|
::selection { background: var(--m-yellow); color: #111; }
|
|
|
|
/* Tailwind v4 / preflight leaves <button> on the default arrow cursor, so
|
|
admins miss the affordance on icon-only actions (复制 / 删除 / 生成 等).
|
|
One global rule restores the pointer cursor everywhere, while disabled
|
|
buttons keep the "not-allowed" cursor already applied via Tailwind. */
|
|
button:not(:disabled), [role="button"]:not([aria-disabled="true"]) { cursor: pointer; }
|
|
|
|
/* ===== Global horizontal table scroll =====
|
|
Data tables use fixed column widths; on a narrow screen the table can be
|
|
wider than its card. Instead of clipping (overflow-hidden) or squishing the
|
|
columns, let the card scroll horizontally. overflow-y is left to the card's
|
|
own utility so vertical clipping / rounded corners are preserved. */
|
|
.card:has(> table) { overflow-x: auto; }
|
|
|
|
::-webkit-scrollbar { width: 9px; height: 9px; }
|
|
::-webkit-scrollbar-thumb { background: rgb(26 26 26 / 0.28); border-radius: 5px; border: 2px solid transparent; background-clip: content-box; }
|
|
::-webkit-scrollbar-thumb:hover { background: rgb(26 26 26 / 0.45); background-clip: content-box; }
|
|
::-webkit-scrollbar-track { background: transparent; }
|
|
html.dark ::-webkit-scrollbar-thumb { background: rgb(255 255 255 / 0.22); background-clip: content-box; }
|
|
html.dark ::-webkit-scrollbar-thumb:hover { background: rgb(255 255 255 / 0.35); background-clip: content-box; }
|
|
|
|
/* ===== Ambient background — used by the public shell ===== */
|
|
.ambient-bg {
|
|
background-color: var(--app-bg);
|
|
background-image: radial-gradient(circle, rgb(17 17 17 / 0.07) 1.3px, transparent 1.3px);
|
|
background-size: 22px 22px;
|
|
}
|
|
html.dark .ambient-bg {
|
|
background-image: radial-gradient(circle, rgb(255 255 255 / 0.06) 1.3px, transparent 1.3px);
|
|
}
|
|
|
|
/* ===== Memphis / neo-brutalist design kit =====
|
|
Heavy ink outlines, hard offset shadows, flat crayon accents (orange / yellow
|
|
/ pink / blue / mint), rotated stickers and squishy press feedback. Reusable
|
|
across the workbench + user subpages + public pages so the whole front-end
|
|
shares one punchy, print-like look. */
|
|
|
|
/* Highlighter text — black words "marked" with a yellow swipe. */
|
|
.anime-grad-text {
|
|
background: linear-gradient(transparent 55%, var(--m-yellow) 55%);
|
|
padding: 0 0.1em;
|
|
color: inherit;
|
|
}
|
|
html.dark .anime-grad-text {
|
|
background: linear-gradient(transparent 55%, rgb(236 91 19 / 0.55) 55%);
|
|
}
|
|
|
|
/* Section eyebrow — tiny uppercase kicker with a leading star. */
|
|
.anime-eyebrow {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.4rem;
|
|
font-size: 11px;
|
|
font-weight: 800;
|
|
letter-spacing: 0.22em;
|
|
text-transform: uppercase;
|
|
}
|
|
.anime-eyebrow::before { content: '✦'; font-size: 0.85em; }
|
|
|
|
/* Sticker label — rotated yellow chip with a hard shadow. */
|
|
.sticker {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.3rem;
|
|
padding: 0.3rem 0.75rem;
|
|
border-radius: 0.55rem;
|
|
font-size: 12px;
|
|
font-weight: 800;
|
|
color: #111;
|
|
background: var(--m-yellow);
|
|
border: 2.5px solid var(--ink);
|
|
box-shadow: 4px 4px 0 var(--shadow-ink);
|
|
transform: rotate(-1.5deg);
|
|
}
|
|
|
|
/* Chunky CTA — flat orange with an offset ink shadow and a press feel. */
|
|
.bubble-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
padding: 0.7rem 1.4rem;
|
|
border-radius: 0.8rem;
|
|
font-size: 14px;
|
|
font-weight: 800;
|
|
color: #fff;
|
|
border: 2.5px solid var(--ink);
|
|
cursor: pointer;
|
|
background: var(--m-orange);
|
|
box-shadow: 4px 4px 0 var(--shadow-ink);
|
|
transition: transform 0.15s ease, box-shadow 0.15s ease, filter 0.15s ease;
|
|
}
|
|
.bubble-btn:hover { transform: translate(-2px, -2px); box-shadow: 6px 6px 0 var(--shadow-ink); filter: brightness(1.05); }
|
|
.bubble-btn:active { transform: scale(0.97) translateY(2px); box-shadow: 1px 1px 0 var(--shadow-ink); }
|
|
|
|
/* Memphis card — flat surface, heavy ink outline, hard offset shadow. */
|
|
.anime-card {
|
|
position: relative;
|
|
border-radius: 1rem;
|
|
background: var(--surface);
|
|
border: 2.5px solid var(--ink);
|
|
box-shadow: 5px 5px 0 var(--shadow-ink);
|
|
transition: transform 0.18s ease, box-shadow 0.18s ease;
|
|
}
|
|
.anime-card:hover {
|
|
transform: translate(-2px, -2px);
|
|
box-shadow: 8px 8px 0 var(--shadow-ink);
|
|
}
|
|
html.dark .anime-card {
|
|
background: rgb(255 255 255 / 0.05);
|
|
}
|
|
|
|
/* Selected pill — solid ink with an orange offset shadow. */
|
|
.chip-on {
|
|
color: #fff !important;
|
|
background: #1a1a1a !important;
|
|
border: 2px solid var(--ink) !important;
|
|
box-shadow: 3px 3px 0 var(--m-orange) !important;
|
|
}
|
|
html.dark .chip-on {
|
|
background: var(--m-orange) !important;
|
|
box-shadow: 3px 3px 0 var(--shadow-ink) !important;
|
|
}
|
|
|
|
/* Twinkling star field — drop into any relatively-positioned decorative layer.
|
|
Uses layered radial-gradients so it's a single element, no extra DOM. */
|
|
.anime-stars {
|
|
position: absolute;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
background-image:
|
|
radial-gradient(1.5px 1.5px at 12% 22%, rgb(236 91 19 / 0.7), transparent),
|
|
radial-gradient(1.5px 1.5px at 78% 12%, rgb(56 189 248 / 0.7), transparent),
|
|
radial-gradient(1px 1px at 42% 62%, rgb(236 91 19 / 0.6), transparent),
|
|
radial-gradient(1.5px 1.5px at 88% 72%, rgb(244 114 182 / 0.6), transparent),
|
|
radial-gradient(1px 1px at 24% 84%, rgb(56 189 248 / 0.55), transparent),
|
|
radial-gradient(1px 1px at 62% 34%, rgb(250 204 21 / 0.55), transparent);
|
|
animation: twinkle 4.5s ease-in-out infinite;
|
|
}
|
|
@keyframes twinkle { 0%, 100% { opacity: 0.85; } 50% { opacity: 0.4; } }
|
|
@media (prefers-reduced-motion: reduce) { .anime-stars { animation: none; } }
|
|
.ambient-grain::before {
|
|
content: '';
|
|
position: fixed;
|
|
inset: 0;
|
|
pointer-events: none;
|
|
background-image: url('data:image/svg+xml;utf8,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22180%22 height=%22180%22><filter id=%22n%22><feTurbulence baseFrequency=%220.9%22 numOctaves=%222%22 seed=%225%22/></filter><rect width=%22100%25%22 height=%22100%25%22 filter=%22url(%23n)%22 opacity=%220.4%22/></svg>');
|
|
opacity: 0.035;
|
|
mix-blend-mode: multiply;
|
|
z-index: 1;
|
|
}
|
|
|
|
/* Float a soft mesh "orb" anywhere */
|
|
.mesh-orb {
|
|
position: absolute;
|
|
border-radius: 9999px;
|
|
filter: blur(80px);
|
|
pointer-events: none;
|
|
z-index: 0;
|
|
}
|
|
|
|
/* ===== Memphis utilities ===== */
|
|
/* Polka-dot texture layer (drop on a decorative absolutely-positioned div). */
|
|
.memphis-dots {
|
|
background-image: radial-gradient(circle, rgb(17 17 17 / 0.14) 1.4px, transparent 1.4px);
|
|
background-size: 22px 22px;
|
|
}
|
|
html.dark .memphis-dots {
|
|
background-image: radial-gradient(circle, rgb(255 255 255 / 0.1) 1.4px, transparent 1.4px);
|
|
}
|
|
/* Heavy outline + hard offset shadow helpers for one-off markup. */
|
|
.ink-card {
|
|
background: var(--surface);
|
|
border: 2.5px solid var(--ink);
|
|
box-shadow: 6px 6px 0 var(--shadow-ink);
|
|
}
|
|
.ink-card-sm {
|
|
background: var(--surface);
|
|
border: 2px solid var(--ink);
|
|
box-shadow: 4px 4px 0 var(--shadow-ink);
|
|
}
|
|
/* Lift on hover — the signature memphis card interaction. */
|
|
.chunky-hover { transition: transform 0.18s ease, box-shadow 0.18s ease; }
|
|
.chunky-hover:hover { transform: translate(-2px, -2px); box-shadow: 8px 8px 0 var(--shadow-ink); }
|
|
/* Squishy press for buttons (mirrors the reference site's .memphis-press). */
|
|
.memphis-press { transition: transform 0.15s ease; }
|
|
.memphis-press:active { transform: scale(0.97) translateY(2px); }
|
|
|
|
/* Empty-state badge — a rotated crayon tile with a star, replaces the old
|
|
mascot illustration in empty lists. */
|
|
.empty-badge {
|
|
width: 4.5rem;
|
|
height: 4.5rem;
|
|
display: grid;
|
|
place-items: center;
|
|
font-size: 1.8rem;
|
|
font-weight: 900;
|
|
color: #111;
|
|
background: var(--m-yellow);
|
|
border: 3px solid var(--ink);
|
|
border-radius: 1rem;
|
|
box-shadow: 5px 5px 0 var(--shadow-ink);
|
|
transform: rotate(-6deg);
|
|
animation: memphis-float 6s ease-in-out infinite;
|
|
}
|
|
@media (prefers-reduced-motion: reduce) { .empty-badge { animation: none; } }
|
|
|
|
@keyframes memphis-wiggle { 0%, 100% { transform: rotate(-3deg); } 50% { transform: rotate(3deg); } }
|
|
@keyframes memphis-float { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-10px) rotate(1.5deg); } }
|
|
@keyframes memphis-bounce { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } }
|
|
.animate-memphis-wiggle { animation: memphis-wiggle 2s ease-in-out infinite; }
|
|
.animate-memphis-float { animation: memphis-float 6s ease-in-out infinite; }
|
|
.animate-memphis-bounce { animation: memphis-bounce 2s ease-in-out infinite; }
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.animate-memphis-wiggle, .animate-memphis-float, .animate-memphis-bounce { animation: none; }
|
|
}
|
|
|
|
/* ===== Dark public-shell theme overrides =====
|
|
Apply by adding `.public-dark` to a wrapper element. Views can keep their
|
|
existing Tailwind classes (text-slate-*, .card, .field) — these rules
|
|
remap them to the dark palette. */
|
|
.public-dark { color: rgb(255 255 255 / 0.9); }
|
|
.public-dark .card {
|
|
background: rgb(255 255 255 / 0.035);
|
|
border-color: var(--ink);
|
|
color: rgb(255 255 255 / 0.92);
|
|
box-shadow: 5px 5px 0 var(--shadow-ink);
|
|
}
|
|
.public-dark .field {
|
|
background: rgb(255 255 255 / 0.04);
|
|
border-color: var(--ink-soft);
|
|
color: rgb(255 255 255 / 0.95);
|
|
transition: border-color 0.18s, background 0.18s, box-shadow 0.18s;
|
|
}
|
|
.public-dark .field::placeholder { color: rgb(255 255 255 / 0.3); }
|
|
/* Single focus treatment shared by every <.field> across the admin shell —
|
|
an orange offset shadow, matching the light theme's focus affordance. */
|
|
.public-dark .field:focus,
|
|
.public-dark .field:focus-visible {
|
|
border-color: var(--ink);
|
|
background: rgb(255 255 255 / 0.06);
|
|
box-shadow: 3px 3px 0 rgb(236 91 19 / 0.6);
|
|
outline: none;
|
|
}
|
|
.public-dark .btn-soft {
|
|
background: rgb(255 255 255 / 0.06);
|
|
border-color: var(--ink-soft);
|
|
color: rgb(255 255 255 / 0.85);
|
|
}
|
|
.public-dark .btn-soft:hover { background: rgb(236 91 19 / 0.25); color: white; }
|
|
.public-dark .btn-ghost { color: rgb(255 255 255 / 0.65); border-color: transparent; }
|
|
.public-dark .btn-ghost:hover { background: rgb(236 91 19 / 0.18); color: white; }
|
|
.public-dark .btn-primary {
|
|
background: var(--m-orange); color: #fff; box-shadow: 3px 3px 0 var(--shadow-ink);
|
|
}
|
|
.public-dark .btn-primary:hover { background: #f97316; }
|
|
.public-dark .pill { background: rgb(255 255 255 / 0.06); color: rgb(255 255 255 / 0.85); border-color: var(--ink-soft); }
|
|
|
|
/* Remap slate text tokens */
|
|
.public-dark .text-slate-900,
|
|
.public-dark .text-slate-800,
|
|
.public-dark .text-slate-700 { color: rgb(255 255 255 / 0.95); }
|
|
.public-dark .text-slate-600 { color: rgb(255 255 255 / 0.75); }
|
|
.public-dark .text-slate-500 { color: rgb(255 255 255 / 0.55); }
|
|
.public-dark .text-slate-400 { color: rgb(255 255 255 / 0.4); }
|
|
.public-dark .text-slate-300 { color: rgb(255 255 255 / 0.3); }
|
|
|
|
/* Backgrounds & borders that views used */
|
|
.public-dark .bg-white { background: rgb(255 255 255 / 0.12); color: rgb(255 255 255 / 0.95); }
|
|
.public-dark .bg-slate-50, .public-dark .bg-slate-50\/60 { background: rgb(255 255 255 / 0.025); }
|
|
.public-dark .bg-slate-100 { background: rgb(255 255 255 / 0.06); }
|
|
.public-dark .bg-slate-200 { background: rgb(255 255 255 / 0.1); }
|
|
.public-dark .border-slate-100,
|
|
.public-dark .border-slate-200,
|
|
.public-dark .border-slate-300 { border-color: rgb(255 255 255 / 0.08); }
|
|
.public-dark .ring-slate-200 { --tw-ring-color: rgb(255 255 255 / 0.08); }
|
|
|
|
/* Tables in dark */
|
|
.public-dark .th { color: rgb(255 255 255 / 0.4); }
|
|
.public-dark .row { border-color: rgb(255 255 255 / 0.06); }
|
|
.public-dark .row:hover { background: rgb(255 255 255 / 0.03); }
|
|
|
|
/* Inline code blocks (pill highlight) — only for inline <code>, NOT code inside
|
|
a <pre> block (docs examples), which would get an unwanted full-width tint. */
|
|
.public-dark code { background: rgb(255 255 255 / 0.08); color: rgb(255 255 255 / 0.9); }
|
|
.public-dark pre code { background: transparent; padding: 0; }
|
|
|
|
/* Filter pills + segmented controls that admin pages stamp with bg-slate-900
|
|
for the "selected" state — that lands invisible on the dark shell. Treat
|
|
slate-900 as a strong-contrast surface in the dark palette. */
|
|
.public-dark .bg-slate-900 { background: rgb(255 255 255 / 0.92); color: rgb(15 23 42); }
|
|
.public-dark .hover\:bg-slate-700:hover { background: rgb(255 255 255 / 0.85); color: rgb(15 23 42); }
|
|
.public-dark .text-slate-700 { color: rgb(255 255 255 / 0.85); }
|
|
.public-dark .text-slate-200 { color: rgb(255 255 255 / 0.7); }
|
|
.public-dark .text-slate-100 { color: rgb(255 255 255 / 0.92); }
|
|
|
|
/* Divider utilities used by lists (recent activity, table rows). */
|
|
.public-dark .divide-slate-50 > :not([hidden]) ~ :not([hidden]),
|
|
.public-dark .divide-slate-100 > :not([hidden]) ~ :not([hidden]) {
|
|
border-color: rgb(255 255 255 / 0.05);
|
|
}
|
|
.public-dark .border-slate-100\/80 { border-color: rgb(255 255 255 / 0.06); }
|
|
.public-dark .hover\:bg-slate-50:hover { background: rgb(255 255 255 / 0.04); }
|
|
.public-dark .hover\:bg-slate-100:hover { background: rgb(255 255 255 / 0.06); }
|
|
|
|
@layer components {
|
|
/* surfaces — flat white card with the memphis ink outline + offset shadow */
|
|
.card {
|
|
@apply bg-white rounded-xl;
|
|
border: 2.5px solid var(--ink);
|
|
box-shadow: 5px 5px 0 var(--shadow-ink);
|
|
}
|
|
|
|
/* buttons — each variant is self-contained (Tailwind v4 can't @apply a custom class) */
|
|
.btn-primary {
|
|
@apply inline-flex items-center justify-center gap-1.5 rounded-lg text-sm font-bold
|
|
disabled:opacity-50 disabled:cursor-not-allowed px-3.5 py-2;
|
|
color: #fff;
|
|
background: var(--m-orange);
|
|
border: 2.5px solid var(--ink);
|
|
box-shadow: 3px 3px 0 var(--shadow-ink);
|
|
transition: transform 0.13s ease, box-shadow 0.13s ease, filter 0.13s ease;
|
|
}
|
|
.btn-primary:hover:not(:disabled) { transform: translate(-1px, -1px); box-shadow: 4px 4px 0 var(--shadow-ink); filter: brightness(1.05); }
|
|
.btn-primary:active:not(:disabled) { transform: scale(0.97) translateY(2px); box-shadow: 0 0 0 var(--shadow-ink); }
|
|
.btn-soft {
|
|
@apply inline-flex items-center justify-center gap-1.5 rounded-lg text-xs font-semibold
|
|
disabled:opacity-50 disabled:cursor-not-allowed px-3 py-1.5;
|
|
color: var(--fg-2);
|
|
background: var(--surface);
|
|
border: 2px solid var(--ink-soft);
|
|
transition: transform 0.13s ease, box-shadow 0.13s ease, background 0.15s ease, color 0.15s ease;
|
|
}
|
|
.btn-soft:hover:not(:disabled) {
|
|
color: var(--fg);
|
|
background: var(--m-yellow);
|
|
border-color: var(--ink);
|
|
box-shadow: 3px 3px 0 var(--shadow-ink);
|
|
}
|
|
.btn-soft:active:not(:disabled) { transform: scale(0.97) translateY(2px); box-shadow: none; }
|
|
.btn-ghost {
|
|
@apply inline-flex items-center justify-center gap-1.5 rounded-lg text-xs font-medium
|
|
disabled:opacity-50 disabled:cursor-not-allowed px-3 py-1.5;
|
|
color: var(--fg-2);
|
|
border: 2px solid transparent;
|
|
transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
|
|
}
|
|
.btn-ghost:hover:not(:disabled) {
|
|
color: var(--fg);
|
|
background: var(--m-yellow);
|
|
border-color: var(--ink);
|
|
}
|
|
|
|
/* text link / quiet action */
|
|
.link { @apply hover:text-slate-900 transition-colors cursor-pointer; color: var(--fg-3); }
|
|
.link:hover { color: var(--m-orange); }
|
|
|
|
/* form controls — heavy outline, orange offset shadow on focus */
|
|
.field {
|
|
@apply w-full rounded-lg bg-white px-3 py-2 text-sm
|
|
outline-none placeholder:text-slate-400;
|
|
color: var(--fg);
|
|
border: 2px solid var(--ink-soft);
|
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
.field:focus, .field:focus-visible {
|
|
border-color: var(--ink);
|
|
box-shadow: 3px 3px 0 var(--m-orange);
|
|
}
|
|
|
|
/* Native <select> using .field: drop the OS arrow for a custom chevron that
|
|
sits away from the right edge, with room so the text never overlaps it. */
|
|
select.field {
|
|
-webkit-appearance: none;
|
|
-moz-appearance: none;
|
|
appearance: none;
|
|
padding-right: 2.5rem;
|
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
|
|
background-repeat: no-repeat;
|
|
background-position: right 0.9rem center;
|
|
background-size: 1rem;
|
|
}
|
|
select.field::-ms-expand { display: none; }
|
|
|
|
/* Number inputs using .field: hide the native +/- spinner arrows for a
|
|
cleaner look (value is still typeable / editable). */
|
|
input.field[type='number'] { -moz-appearance: textfield; appearance: textfield; }
|
|
input.field[type='number']::-webkit-outer-spin-button,
|
|
input.field[type='number']::-webkit-inner-spin-button {
|
|
-webkit-appearance: none;
|
|
margin: 0;
|
|
}
|
|
|
|
/* status pill */
|
|
.pill {
|
|
@apply inline-flex items-center rounded-md px-2 py-0.5 text-xs font-semibold;
|
|
border: 1.5px solid var(--ink-soft);
|
|
}
|
|
|
|
/* table primitives */
|
|
.th { @apply text-left px-4 py-3 text-[11px] font-bold uppercase tracking-wider; color: var(--fg-faint); }
|
|
.td { @apply px-4 py-3.5 align-middle; }
|
|
.row { @apply border-t transition-colors; border-color: var(--hairline); }
|
|
.row:hover { background: rgb(254 240 138 / 0.35); }
|
|
|
|
/* avatar chip for identity columns */
|
|
.avatar { @apply w-8 h-8 rounded-full grid place-items-center text-xs font-bold shrink-0; border: 2px solid var(--ink); }
|
|
}
|