Core visual traits: bold black borders, solid offset shadows, high-contrast palette, zero border-radius, bold typography, press interaction feedback.
Aligned with PicaComicNow's Neubrutalism design language to form a unified FreeNowOrg brand identity.
| Token | Value | Purpose |
|---|---|---|
--fnb-bg |
#EEF2FF |
Page background (blue-tinted warm white) |
--fnb-brand |
#4993FF |
Primary color, primary buttons |
--fnb-brand-hover |
#6AABFF |
Primary hover state |
--fnb-accent |
#A78BFA |
Secondary accent (view count icons, etc.) |
--fnb-success |
#7FD957 |
Success state |
--fnb-highlight |
#FFE066 |
Highlight, marker effect, hover background |
--fnb-danger |
#FF5555 |
Error, danger actions |
--fnb-bookmark |
#FF69B4 |
Bookmark marker |
--fnb-border |
#000 |
Borders, primary text |
--fnb-shadow-color |
var(--fnb-border) |
Hard-shadow color (decoupled from border — see Theming) |
--fnb-surface |
#fff |
Card, input background |
--fnb-bg-alt |
#f0f0f0 |
Alternate/raised surface |
--fnb-text |
#1a1a1a |
Body text |
--fnb-text-muted |
#666 |
Secondary text |
--fnb-on-brand |
#fff |
Foreground on dark/colored fills (brand, danger, badges) — light in both themes |
--fnb-on-light |
#1a1a1a |
Foreground on light fills (highlight, success) — dark in both themes |
--fnb-skeleton |
#e8e8e8 |
Skeleton / placeholder / code fill |
--fnb-divider |
#dedede |
Dividers, hairlines, focus rings |
--fnb-grid-line |
rgba(0,0,0,.03) |
Body background grid lines |
--fnb-silver / --fnb-bronze |
#d1d5db / #f0b27a |
Rank medals |
Project-domain semantic colors live under a separate --pixiv-* prefix (R18 / AI / original-work badges) — see Theming. Values above are the light theme; dark overrides are in the Theming section.
| Token | Default | Purpose |
|---|---|---|
--fnb-radius |
0 |
Global base radius |
--fnb-radius-sm |
0 |
Small (tags, inputs) |
--fnb-radius-lg |
0 |
Large (dialogs, large cards) |
Zero by default (Neubrutalism core). Consumers can override these tokens for rounded variants.
| Purpose | Font family | Token |
|---|---|---|
| Body | Noto Sans SC, PingFang SC, system-ui | --fnb-font-sans |
| Display / Headings | Archivo Black, Noto Sans SC, system-ui | --fnb-font-display |
| Mono | Space Grotesk, ui-monospace, monospace | --fnb-font-mono |
- h1: Archivo Black,
2.2rem,font-weight: 900 - h2: Yellow marker background (
--fnb-highlight) + 3px black bottom border
Zero border-radius — no border-radius site-wide. Sharp corners are a Neubrutalism core trait.
| Level | Border | Shadow | Mixin |
|---|---|---|---|
| Standard | 3px solid #000 |
6px 6px 0 0 #000 |
@include fnb-border; @include fnb-shadow; |
| Small | 2px solid #000 |
4px 4px 0 0 #000 |
@include fnb-border-sm; @include fnb-shadow-sm; |
| Large | 3px solid #000 |
8px 8px 0 0 #000 |
@include fnb-shadow-lg; |
| Micro | 2px solid #000 |
3px 3px 0 0 #000 |
@include fnb-shadow-xs; |
Hover/active: element shifts down-right, shadow collapses to 0 — simulates a tactile "push" feedback:
hover/active: translate(1.5px, 1.5px); box-shadow: 0 0 0 0 #000;
Theming is driven entirely by CSS custom properties. useColorMode (@vueuse/core, auto-imported) writes a light / dark class on <html>; :root holds the light values and html.dark { … } overrides the subset that changes. The useTheme() composable (app/composables/useTheme.ts) wraps this into a tri-state ('auto' | 'light' | 'dark') — note useColorMode's emitAuto is deprecated in v14, read colorMode.store instead. ThemeToggle.vue is the segmented switcher. Since the app is a client-only SPA, there is no SSR flash.
--fnb-*— design-system / component-library tokens (generic UI semantics). Portable across sister projects.--pixiv-*— project-domain semantic colors (R18 / AI / original-work). A sister project swaps these for its own domain set.
html.dark {
--fnb-bg: #14151b; // page
--fnb-surface: #1e222b; // cards (slightly lighter than bg)
--fnb-bg-alt: #1d2029;
--fnb-border: #4c5160; // subtle, NOT near-white
--fnb-shadow-color: #2f5ea8;// brand-blue hard shadow (see principle 1)
--fnb-text: #eef0f3;
--fnb-text-muted: #9aa1ac;
--fnb-skeleton: #2a2e38;
--fnb-divider: #343a45;
--fnb-grid-line: rgba(255,255,255,.04);
// brand/accent/success/highlight/danger/bookmark stay vivid (principle 2)
// pixiv text tones brighten for dark legibility:
--pixiv-original: #ff4fa3; --pixiv-r18-text: #ff6b6b; --pixiv-ai-text: #e0a44b;
}--fnb-on-brand / --fnb-on-light / --fnb-silver / --fnb-bronze and the --pixiv-* badge fills are theme-invariant (same value in both modes), so they need no dark override.
- Don't invert the hard shadow/border to near-white. A white border + white shadow on a near-black page reads like a photographic negative. Two approaches that work instead:
- (this project) keep a subtle border + decouple the shadow into
--fnb-shadow-colorand tint it with the brand color (deepened, e.g.#2f5ea8) so the signature hard offset stays visible without glare. - (authentic neubrutalism, e.g. neubrutalism.com) white border + shadow, but only with a mid-dark page (
~#2d2c31) and cards darker than the page — never near-black bg with cards lighter than bg.
- (this project) keep a subtle border + decouple the shadow into
- Keep semantic colors vibrant in dark mode — do NOT desaturate them. Muting success/highlight/danger to dull tones drifts toward generic "dark-UI" and kills the neubrutalist punch. Fix contrast, not saturation.
- Foreground tokens carry the contrast, not the fill. Light fills (highlight yellow, success green) pair with
--fnb-on-light(fixed dark); dark/colored fills (brand, danger, badges) pair with--fnb-on-brand(fixed light). Because these are fixed per fill, a fill stays readable in both themes without per-theme overrides. Every place that paints a--fnb-highlight/--fnb-successbackground must set its foreground to--fnb-on-light(including:hoverbackground swaps). - Media literals stay literal. Colors layered over artwork images — black scrims (
rgba(0,0,0,.x)), white-on-image text, text-shadows, carousel backdrops, modal/preview scrims — are theme-invariant media treatments. Leave them as literals; do not tokenize. - Base text color + font belong on
body. Form controls (button,input,select,textarea) don't inheritcolor— add a globalcolor: inheritreset, or unstyled controls fall back to UA black and vanish in dark mode.
All Neubrutalism patterns are encapsulated as fnb-* mixins in app/assets/styles/_fnb.scss. Auto-injected globally via Vite preprocessorOptions — no manual @use needed in components.
| Mixin | Purpose |
|---|---|
fnb-border |
3px black solid border |
fnb-border-sm |
2px black solid border |
fnb-shadow / sm / lg / xs |
Standard / small / large / micro offset shadow |
fnb-card |
Card base (border + shadow + white bg + transition) |
fnb-btn |
Button base (border + shadow + press effect + padding) |
fnb-tag |
Tag (border-sm + shadow-xs + small padding) |
fnb-input |
Input (border-sm + shadow-sm + focus brand shadow) |
fnb-press |
Press effect only (hover/active translate + shadow collapse) |
- UI base components:
Fnbprefix, located inapp/components/ui/ - Business components: no prefix, located in
app/components/ - Nuxt auto-imports with
pathPrefix: falseforui/directory
| Component | Description |
|---|---|
FnbButton |
Button — variant (default/primary/success/danger), size (sm/md/lg) |
FnbCard |
Card container — color (white/brand/highlight/success) |
FnbTag |
Tag — color, active, clickable |
FnbSkeleton |
Skeleton placeholder — width, height, circle, block, text |
FnbInput |
Text input — modelValue, placeholder, type |
FnbSelect |
Dropdown select — modelValue, options |
FnbPagination |
Pagination — modelPage, pageCount, pageSlot |
FnbDialog |
Modal dialog — driven by useDialog() composable |
FnbToast |
Toast notification — driven by useToast() composable |
FnbMbox |
Message box — type (info/success/warning/error), header |
FnbSpin |
Loading spinner — show |
FnbProgress |
Progress bar — percentage, color |
FnbEllipsis |
Text truncation — lineClamp, tooltip |
FnbTabs |
Tab switcher — modelValue, tabs: {key, label}[] |
FnbTable |
Simple data table — columns, data |
FnbImage |
Image with preview — src, alt, fallback |
FnbScrollbar |
Custom scrollbar container |
FnbResult |
Result status page — status, title, description |
FnbFloatButton |
Floating action button |
FnbProvider |
Global container — mounts Dialog + Toast |
| Composable | Description |
|---|---|
useDialog() |
Imperative dialog open, returns Promise |
useToast() |
Imperative toast display (info/success/warning/error) |
.responsive:max-width: 1200px,margin: auto,padding: 0 2rem(mobile1rem)- Page background:
--fnb-bg+ 72x72px grid lines
- Height: 60px content + 3px bottom border = 63px
- Background:
var(--fnb-brand) - Bottom border:
3px solid var(--fnb-border)(no shadow)
- Top border:
3px solid var(--fnb-border) - Background:
var(--fnb-surface)(white) - h4 headings: yellow marker background
- Link hover: marker effect
| Width | Behavior |
|---|---|
>=1200px |
Centered container, max-width |
>=800px |
Multi-column grids |
>=600px |
Reduced columns |
<600px |
Single column, compact padding |
- Default: black text + underline (2px thick, 3px offset)
- Hover: thicker underline
- Default background:
var(--fnb-bg) - Hover: press effect (translate + shadow collapse)
- Disabled:
opacity: 0.5, no press effect
- Page transitions:
fade-in-up/fade-out-down(existing) - Dialog: scale(0.9) + opacity, 200ms ease-out
- Toast: translateY(-20px) + opacity, 250ms ease-out
- All component transitions: 150-250ms,
ease-out
Using unplugin-icons with @iconify-json/fa-solid. Icons imported as Vue components via ~icons/fa-solid/xxx.
Components accept icons via #icon slot for portability — no hardcoded icon import method inside UI components.
Vue templates use Pug syntax (lang="pug"). SCSS classes with special characters must use string form:
| Syntax | Safe? |
|---|---|
.fnb-card, .flex, .gap-4 |
Yes |
Dynamic classes via :class |
Yes |
app/
assets/styles/
_fnb.scss — Fnb mixin library
_variables.scss — CSS custom properties (--fnb-* tokens)
_elements.scss — Global HTML element styles
_formats.scss — Utility classes
_animate.scss — Transition animations
index.scss — Entry point
components/
ui/ — Fnb* base UI components (pathPrefix: false)
SiteHeader.vue — Global header
SiteFooter.vue — Global footer
SideNav/ — Side navigation
Artwork/ — Artwork display components
Comment/ — Comment components
composables/
useDialog.ts — Imperative dialog
useToast.ts — Imperative toast
stores/ — Pinia stores
pages/ — Route pages
- Template: Pug (
lang="pug") - Script: TypeScript (
lang="ts") - Style: SCSS (
lang="scss"), scoped preferred - Code comments: English
- UI copy: Simplified Chinese
- Commits: English, Conventional Commits format
- No third-party UI library: All UI components self-implemented