Appearance
SCSS Architecture Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Внедрить модульную SCSS-архитектуру (по образцу ban-hummer) с design tokens, fluid typography, 8px spacing grid и responsive mixins.
Architecture: Адаптация 7-1 SCSS паттерна для Tailwind CSS 4 + Nuxt UI v3. SCSS abstracts (переменные, миксины, функции) инжектятся глобально через Vite additionalData. CSS Custom Properties — для runtime-значений (тема, цвета). SCSS-переменные — для compile-time (breakpoints, spacing). Tailwind остаётся для utility-классов, SCSS — для семантических стилей и design tokens.
Tech Stack: Sass (dart-sass), Nuxt 4.3, Tailwind CSS 4, Nuxt UI v3
Target Structure
app/assets/
├── css/
│ └── main.css # Tailwind + Nuxt UI imports + custom utilities
└── styles/
├── main.scss # Entry point — imports base/
├── abstracts/
│ ├── _index.scss # @forward all partials
│ ├── _variables.scss # General vars (header height, transitions)
│ ├── _typography.scss # Font sizes, weights, families, line-heights
│ ├── _spacing.scss # 8px grid, border-radius
│ ├── _breakpoints.scss # Responsive breakpoints (sm-4xl)
│ ├── _z-index.scss # Z-index layers
│ ├── _colors.scss # SCSS color palette
│ ├── _mixins.scss # media-up/down, flex helpers, text-truncate
│ └── _functions.scss # fluid-type(), px-to-rem()
└── base/
├── _index.scss # @use all base partials
└── _typography.scss # h1-h6, body, links — fluid-typeTask 1: Install sass dependency
Files:
- Modify:
package.json
Step 1: Install sass
Run: npm install -D sass-embedded Expected: sass-embedded added to devDependencies
Step 2: Commit
bash
git add package.json package-lock.json
git commit -m "chore(DEV-200): add sass-embedded dependency"Task 2: Create abstracts — functions
Files:
- Create:
app/assets/styles/abstracts/_functions.scss
Step 1: Create the file
scss
@use 'sass:math';
/// Convert px to rem
/// @param $pixels - Value in px
/// @param $base - Base font size (default 16px)
/// @return Value in rem
@function px-to-rem($pixels, $base: 16px) {
@if math.is-unitless($pixels) {
$pixels: $pixels * 1px;
}
@return math.div($pixels, $base) * 1rem;
}
/// Convert rem to px
@function rem-to-px($rems, $base: 16px) {
@return math.div($rems, 1rem) * $base;
}
/// Fluid typography using CSS clamp()
/// Linearly scales between min and max size across viewport range
/// @param $min-size - Minimum font size (rem)
/// @param $max-size - Maximum font size (rem)
/// @param $min-width - Min viewport (default: 20rem / 320px)
/// @param $max-width - Max viewport (default: 80rem / 1280px)
@function fluid-type($min-size, $max-size, $min-width: 20rem, $max-width: 80rem) {
$slope: math.div($max-size - $min-size, $max-width - $min-width);
$intercept: $min-size - $slope * $min-width;
@return clamp(#{$min-size}, #{$intercept} + #{$slope * 100vw}, #{$max-size});
}
/// Get spacing value from map
/// @param $key - Spacer key (0-12)
@function spacing($key) {
@return map-get($spacers, $key);
}
/// Strip unit from a number
@function strip-unit($number) {
@if type-of($number) == 'number' and not math.is-unitless($number) {
@return math.div($number, $number * 0 + 1);
}
@return $number;
}Step 2: Commit
bash
git add app/assets/styles/abstracts/_functions.scss
git commit -m "feat(DEV-200): add SCSS functions (fluid-type, px-to-rem)"Task 3: Create abstracts — variables, typography, spacing, breakpoints, z-index, colors
Files:
- Create:
app/assets/styles/abstracts/_variables.scss - Create:
app/assets/styles/abstracts/_typography.scss - Create:
app/assets/styles/abstracts/_spacing.scss - Create:
app/assets/styles/abstracts/_breakpoints.scss - Create:
app/assets/styles/abstracts/_z-index.scss - Create:
app/assets/styles/abstracts/_colors.scss
Step 1: Create _variables.scss
scss
// Layout
$header-height: 3.5rem; // 56px
$sidebar-width: 16rem; // 256px
$container-max-width: 80rem; // 1280px
// Transitions
$transition-fast: 150ms ease;
$transition-base: 200ms ease-in-out;
$transition-slow: 300ms ease-in-out;
// Opacity
$opacity-disabled: 0.5;
$opacity-hover: 0.8;Step 2: Create _typography.scss
scss
// Font families
$font-family-primary: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';
$font-family-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
// Font weights
$font-weight-normal: 400;
$font-weight-medium: 500;
$font-weight-semibold: 600;
$font-weight-bold: 700;
// Font sizes (rem, based on 16px root)
// Aligned with wireframes: body 14-16px, headings 18-36px
$font-size-xs: 0.75rem; // 12px — captions, badges
$font-size-sm: 0.875rem; // 14px — secondary text, nav
$font-size-base: 1rem; // 16px — body
$font-size-md: 1.125rem; // 18px — card titles
$font-size-lg: 1.25rem; // 20px — prices, subheadings
$font-size-xl: 1.5rem; // 24px — section headings (h2)
$font-size-2xl: 1.875rem; // 30px — h1 (mobile)
$font-size-3xl: 2.25rem; // 36px — h1 (desktop), hero
// Heading sizes (fluid)
// These use fluid-type() in base/_typography.scss
$h1-size-min: 1.875rem; // 30px mobile
$h1-size-max: 2.25rem; // 36px desktop
$h2-size-min: 1.25rem; // 20px mobile
$h2-size-max: 1.5rem; // 24px desktop
$h3-size-min: 1rem; // 16px mobile
$h3-size-max: 1.25rem; // 20px desktop
// Line heights
$line-height-none: 1;
$line-height-tight: 1.25;
$line-height-snug: 1.375;
$line-height-normal: 1.5;
$line-height-relaxed: 1.625;
// Letter spacing
$letter-spacing-tight: -0.025em;
$letter-spacing-normal: 0;
$letter-spacing-wide: 0.025em;Step 3: Create _spacing.scss
scss
// 8px base grid
$spacer: 0.5rem; // 8px
$spacers: (
0: 0,
1: 0.125rem, // 2px
2: 0.25rem, // 4px
3: 0.5rem, // 8px
4: 0.75rem, // 12px
5: 1rem, // 16px
6: 1.25rem, // 20px
7: 1.5rem, // 24px
8: 2rem, // 32px
10: 3rem, // 48px
12: 4rem, // 64px
);
// Semantic spacing shortcuts
$spacing-xs: 0.25rem; // 4px
$spacing-sm: 0.5rem; // 8px
$spacing-md: 1rem; // 16px
$spacing-lg: 1.5rem; // 24px
$spacing-xl: 2rem; // 32px
$spacing-2xl: 3rem; // 48px
// Border radius
$radius-sm: 0.25rem; // 4px
$radius: 0.5rem; // 8px
$radius-md: 0.75rem; // 12px
$radius-lg: 1rem; // 16px
$radius-xl: 1.5rem; // 24px
$radius-full: 9999px;Step 4: Create _breakpoints.scss
scss
// Aligned with Tailwind defaults
$breakpoints: (
'sm': 40rem, // 640px
'md': 48rem, // 768px
'lg': 64rem, // 1024px
'xl': 80rem, // 1280px
'2xl': 96rem, // 1536px
'3xl': 112.5rem, // 1800px — ultra-HD
'4xl': 120rem, // 1920px — full HD
);
// Container max-widths per breakpoint
$container-max-widths: (
'sm': 38rem, // 608px
'md': 45rem, // 720px
'lg': 60rem, // 960px
'xl': 72rem, // 1152px
'2xl': 80rem, // 1280px
);Step 5: Create _z-index.scss
scss
$z-index-dropdown: 1000;
$z-index-sticky: 1020;
$z-index-fixed: 1030;
$z-index-modal-backdrop: 1040;
$z-index-modal: 1050;
$z-index-popover: 1060;
$z-index-tooltip: 1070;
$z-index-notification: 1080;Step 6: Create _colors.scss
scss
// Brand
$color-primary: #3b82f6; // blue-500 (matches Nuxt UI primary)
$color-primary-hover: #2563eb; // blue-600
// Semantic
$color-success: #10b981; // emerald-500
$color-warning: #f59e0b; // amber-500
$color-error: #ef4444; // red-500
$color-info: #3b82f6; // blue-500
// Neutral (for SCSS usage, runtime via Nuxt UI CSS vars)
$color-text: #1e293b; // slate-800
$color-text-muted: #64748b; // slate-500
$color-border: #e2e8f0; // slate-200
$color-bg: #ffffff;
$color-surface: #f8fafc; // slate-50Step 7: Commit
bash
git add app/assets/styles/abstracts/
git commit -m "feat(DEV-200): add SCSS abstracts (typography, spacing, breakpoints, z-index, colors)"Task 4: Create abstracts — mixins and index
Files:
- Create:
app/assets/styles/abstracts/_mixins.scss - Create:
app/assets/styles/abstracts/_index.scss
Step 1: Create _mixins.scss
scss
@use 'sass:map';
@use 'breakpoints' as *;
// ── Responsive ──
@mixin media-up($breakpoint) {
@if map.has-key($breakpoints, $breakpoint) {
@media (min-width: map.get($breakpoints, $breakpoint)) {
@content;
}
} @else {
@error "Unknown breakpoint: #{$breakpoint}. Available: #{map.keys($breakpoints)}";
}
}
@mixin media-down($breakpoint) {
@if map.has-key($breakpoints, $breakpoint) {
@media (max-width: calc(#{map.get($breakpoints, $breakpoint)} - 0.0625rem)) {
@content;
}
} @else {
@error "Unknown breakpoint: #{$breakpoint}.";
}
}
// ── Layout ──
@mixin flex-center {
display: flex;
align-items: center;
justify-content: center;
}
@mixin flex-between {
display: flex;
align-items: center;
justify-content: space-between;
}
// ── Text ──
@mixin text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
@mixin text-clamp($lines: 2) {
display: -webkit-box;
-webkit-line-clamp: $lines;
-webkit-box-orient: vertical;
overflow: hidden;
}
// ── Interactive ──
@mixin focus-ring($color: var(--ui-primary)) {
outline: 2px solid $color;
outline-offset: 2px;
}
@mixin transition($properties: all, $duration: 0.2s, $easing: ease-in-out) {
transition: $properties $duration $easing;
}Step 2: Create _index.scss (global export)
scss
// Order matters: functions first (used by other partials)
@forward 'functions';
@forward 'colors';
@forward 'typography';
@forward 'spacing';
@forward 'breakpoints';
@forward 'z-index';
@forward 'variables';
@forward 'mixins';Step 3: Commit
bash
git add app/assets/styles/abstracts/
git commit -m "feat(DEV-200): add SCSS mixins and abstracts index"Task 5: Create base typography with fluid-type
Files:
- Create:
app/assets/styles/base/_typography.scss - Create:
app/assets/styles/base/_index.scss - Create:
app/assets/styles/main.scss
Step 1: Create base/_typography.scss
scss
@use '../abstracts' as *;
// ── Headings ──
h1, .h1 {
font-size: fluid-type($h1-size-min, $h1-size-max);
font-weight: $font-weight-bold;
line-height: $line-height-tight;
letter-spacing: $letter-spacing-tight;
}
h2, .h2 {
font-size: fluid-type($h2-size-min, $h2-size-max);
font-weight: $font-weight-bold;
line-height: $line-height-tight;
}
h3, .h3 {
font-size: fluid-type($h3-size-min, $h3-size-max);
font-weight: $font-weight-semibold;
line-height: $line-height-snug;
}
// ── Body ──
body {
font-family: $font-family-primary;
font-size: $font-size-base;
line-height: $line-height-normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
// ── Links ──
a {
text-decoration-skip-ink: auto;
}
// ── Code ──
code, kbd, pre, samp {
font-family: $font-family-mono;
}
// ── Small text ──
small, .text-small {
font-size: $font-size-sm;
line-height: $line-height-normal;
}Step 2: Create base/_index.scss
scss
@use 'typography';Step 3: Create main.scss
scss
@use 'base';Step 4: Commit
bash
git add app/assets/styles/
git commit -m "feat(DEV-200): add base typography with fluid-type headings"Task 6: Configure Vite SCSS injection + register main.scss
Files:
- Modify:
nuxt.config.ts
Step 1: Add SCSS global injection and register main.scss
In nuxt.config.ts, add to the css array and configure vite.css:
typescript
css: ['~/assets/css/main.css', '~/assets/styles/main.scss'],And add Vite SCSS preprocessor config:
typescript
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '@use "~/assets/styles/abstracts" as *;\n',
api: 'modern-compiler',
},
},
},
// ... existing vite config
},Step 2: Run dev server to verify
Run: rm -rf .nuxt && npm run dev Expected: Server starts without SCSS compilation errors
Step 3: Verify in browser — check h1 has fluid-type
Navigate to http://localhost:3000 Check: getComputedStyle(document.querySelector('h1')).fontSize returns value between 30-36px
Step 4: Commit
bash
git add nuxt.config.ts
git commit -m "feat(DEV-200): configure Vite SCSS global injection and register styles"Task 7: Verify all pages render correctly
Step 1: Check homepage
Run: curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/ Expected: 200
Step 2: Check catalog
Run: curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/catalog Expected: 200
Step 3: Check auth pages (SSR)
Run: curl -s -o /dev/null -w '%{http_code}' http://localhost:3000/auth/login Expected: 200
Step 4: Run lint
Run: npm run lint Expected: No errors
Step 5: Run typecheck
Run: npm run typecheck Expected: No errors
Step 6: Final commit with all typography + SCSS architecture
bash
git add -A
git commit -m "feat(DEV-200): complete SCSS architecture with fluid typography"